diff --git a/README.md b/README.md index 4bef680473..db8212f900 100644 --- a/README.md +++ b/README.md @@ -4,117 +4,148 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/hyperledger/firefly)](https://goreportcard.com/report/github.com/hyperledger/firefly) [![FireFy Documentation](https://img.shields.io/static/v1?label=FireFly&message=documentation&color=informational)](https://hyperledger.github.io/firefly//) -Hyperledger FireFly is a multiparty system for enterprise data flows, powered by blockchain. It solves all of the layers of complexity that sit between the low level blockchain and high level business processes and user interfaces. FireFly enables developers to build blockchain apps for enterprise radically faster by allowing them to focus on business logic instead of infrastructure. +![Hyperledger FireFly](./images/hyperledger_firefly_logo.png) -FireFly focusses on: +Hyperledger FireFly is an API and data orchestration layer on top of core blockchain technologies. -- Providing a great developer API and experience, with a CLI and UI as first class project components -- Pluggability for implementations of multi-party system infrastructure (blockchains, off-chain data exchange, identity, compute etc.) -- Making proven multi-party system patterns easy for new projects to adopt -- Providing developer friendly access to custom transactions+events in the underlying blockchain platforms -- Giving visibility and control on the private data exchange that occurs between businesses in a multi-party system -- Simplifying the journey of building multi-party business processes, by empowering non-blockchain developers to build great APIs+UX +It implements a [multi-party system](#multi-party-systems) for building enterprise decentralized applications. -You will see enterprise focussed code in FireFly solving hard "plumbing" problems like on-chain/off-chain event sequencing and aggregation, and enough smart contract code to make the patterns possible. You will then find patterns of integration with the individual communities that are already building the deep blockchain & multi-party compute tech, like Hyperledger Fabric, Hyperledger Besu, Quorum, Corda, IPFS, Hyperledger Avalon, OpenZeppelin, NodeRED etc. +- Transaction submission and event streaming + - Radically simplified API access to your on-chain smart contracts +- Multi-protocol blockchain integration + - [Hyperledger Fabric](https://www.hyperledger.org/use/fabric) + - Enterprise Ethereum - [Hyperledger Besu](https://www.hyperledger.org/use/besu) & [Quorum](https://github.com/ConsenSys/quorum) + - [Corda](https://www.corda.net/) *(work in progress)* +- Developer friendly event-driven REST & WebSocket APIs + - For building multi-party business applications that solve real enterprise use cases +- Digital assets + - Tokens and NFTs ready for use, with indexed transaction history, and easy extension/customization +- On-chain/off-chain orchestration + - Enterprise data flows backed by blockchain, with secure off-chain transfer of private docs+data + - Pluggable private data exchange / messaging (inc. [HTTPS + Mutual TLS](https://github.com/hyperledger/firefly-dataexchange-https)) +- Identity, data format, and interface distribution + - Broadcast data schema, proven identity, and on-chain logic integration APIs across the network + - Pluggable data distribution network with batch optimization (inc. [IPFS](https://ipfs.io/)) + - *Pluggable DIDs for identity and multi-protocol on-chain interface definition are work in progress* +- Microservice architecture, optimized for docker deployment + - Fully pluggable architecture, embracing multiple runtime technologies (Go, Node.js, Java etc.) +- Built by developers for developers + - Ready to go in minutes, with a CLI, built-in UI explorer, OpenAPI spec, and samples +- Data operations at the boundary of your data center + - Fast database cache + audit of all data flowing out of your enterprise, to the network -> Watch this space for patterns on integrating Tokens into the model (fungible token value exchange, and NFTs), which is a big current focus of evolution in the gen2 FireFly architecture (building on the work done in gen1, also in this repo). The tokens working group is being lead by [Jim Zhang](https://github.com/jimthematrix) +## Quick Start Guide -![Introducing FireFly](./architecture/intro_to_firefly_teaser.svg) +Follow the [get started](https://hyperledger.github.io/firefly/gettingstarted/gettingstarted.html) guide in the doc, and your +local developer environment will be up in minutes. + +You'll have your own private multi-party system, comprising a blockchain (Ethereum/Fabric) with API/Event connectors, a Private Data Exchange, an IPFS data sharing network, and ERC-1155 Token/NFT implementations. + +All with the Hyperledger FireFly Explorer UI of course, and a [samples to get you building fast](https://github.com/hyperledger/firefly-samples). + +![FireFly Explorer](images/firefly_explorer.png) + +## API Reference + +All the Hyperledger FireFly APIs are self-documenting via Swagger, and you can just open them up on `/api` on your running FireFly. + +Or you can check out the [latest API here](https://hyperledger.github.io/firefly/swagger/swagger.html). ## Documentation -https://hyperledger.github.io/firefly// +https://hyperledger.github.io/firefly -## FireFly repos +## Multi-party Systems -FireFly has a plugin based architecture design, with a microservice runtime footprint. -As such there are a number of repos, and the list will grow as the community evolves. +Hyperledger Firefly is an implementation of a multi-party system. -But not to worry, one of those repos is a CLI designed to get you running with all the components you need in minutes! +![Multi-party System](./images/multi_party_systems.png) -- CLI / Developer experience - https://github.com/hyperledger/firefly-cli -- UI Explorer - https://github.com/hyperledger/firefly-ui -- Sample applications - https://github.com/hyperledger/firefly-samples -- Core (this repo) - https://github.com/hyperledger/firefly -- HTTP Data Exchange - https://github.com/hyperledger/firefly-dataexchange-https -- Ethereum (Hyperledger Besu / Quorum) connector: https://github.com/hyperledger/firefly-ethconnect -- Corda connector: https://github.com/hyperledger/firefly-cordaconnect - contributed from Kaleido generation 1 - porting to generation 2 -- Hyperledger Fabric connector - in design phase, including collaboration with https://github.com/hyperledger/fabric-smart-client +Multi-party systems have the potential to unlock the next wave of digitization in core transaction processing systems. They combine the best features of the existing secure data exchange models for API/WebService/Messaging integration of business data/processes today, with the new technologies of the blockchain revolution. -> Note only the projects that are primarily built to support FireFly are listed here, not all -> of the ecosystem of projects that integrate underneath the plugins. See [below](#firefly-code-hierarchy) for -> more information on the landscape of plugins and components. +Working within existing regulatory environments, and existing IT and data security governance frameworks, multi-party systems provide a secure gateway for organizations to participate securely in blockchain backed business ecosystems. -## Getting Started +They are the middleware tier for decentralized applications, which are fundamentally different to centralized/SaaS applications because they are hosted independently by each IT organization, and can be customized by each organization to their own IT landscape. These applications communicate through a mix of blockchain, and private data exchange, to execute multi-party transactions at scale - powered by revolutionary new programming constructs like digital assets. -Use the FireFly CLI for fast bootstrap: https://github.com/hyperledger/firefly-cli +The next wave of business applications that build in a decentralized way on multi-party systems, can orchestrate data and business process flows across organizational boundaries. The integrity of the end-to-end transactions can be established throughout its lifecycle, rather than requiring complex handoff and compensation logic each time a new party performs its step. Yet the autonomy of each business and IT team is maintained in a way that could not be by pooling data and business logic in a 3rd party centralized business application. -## Navigating this repo +Blockchain and other advanced cryptography technologies like zero-knowledge proofs (ZKPs), and trusted execution environments (TEEs), are the core technologies that enable this new model of cross-organizational data flow. -There are **two core codebases** currently active in this repo: +In an enterprise context these raw technologies are necessary, but not sufficient. Organizations need a comprehensive toolset at the boundary of their *existing core systems of record* to govern the flow of data out of their own secure IT infrastructure. +- To store the private data staged in canonical formats ready for exchange with other parties +- For retrieval of the state of transaction and data flows in-flight in the system +- To provide an audit record and reporting system for what has been shared so far +- Providing event-driven integration APIs fit for purpose to integrate to the core systems of record -### Generation 2: FireFly +This all needs to be fast, secure and reliable. -Directories: +[Learn more in the Hyperledger FireFly Documentation](https://hyperledger.github.io/firefly/) -- [internal](./internal): The core Golang implementation code -- [pkg](./pkg): Interfaces intended for external project use -- [cmd](./cmd): The command line entry point -- [smart_contracts](./smart_contracts): smart contract code for Firefly's onchain logic, with support for Ethereum and Hyperledger Fabric in their respective sub-directories +## Event-driven programming model -[Full code layout here](#firefly-code-hierarchy) +The core programming model of FireFly is event-driven: +- FireFly delivers data and actions from your application instance, reliably to on-chain logic and privately to other parties in the network +- FireFly receives data and actions from on-chain, and other parties in the network, correlates them, and once complete and verified delivers them to your application for processing -This latest generation is re-engineered from the ground up to improve developer experience, runtime performance, and extensibility. - -This means a simplified REST/WebSocket programming model for app development, and a wider range of infrastructure options for deployment. - -It also means a focus on an architecture and code structure for a vibrant open source community. - -A few highlights: - -- Golang codebase - - Strong coding standards, including unit test coverage, translation support, logging and more - - Fast starting, low memory footprint, multi-threaded runtime -- OpenAPI 3.0 API specification (Swagger) - - Generated from the API router code, to avoid divergence with the implementation -- Active/active HA architecture for the core runtime - - Deferring to the core database for state high availability - - Exploiting leader election where required -- Fully pluggable architecture - - Everything from Database through to Blockchain, and Compute - - Golang plugin infrastructure to decouple the core code from the implementation - - Remote Agent model to decouple code languages, and HA designs -- Updated API resource model - - `Asset`, `Data`, `Message`, `Event`, `Topic`, `Transaction` -- Added flexibility, with simplified the developer experience: - - Versioning of data definitions - - Introducing a first class `Context` construct link related events into a single sequence - - Allow many pieces of data to be attached to a single message, and be automatically re-assembled on arrival - - Clearer separation of concerns between the FireFly DB and the Application DB - - Better search, filter and query support - -### Generation 1: Kaleido Asset Trail (KAT) +For this reason FireFly has a pluggable database that keeps track of all those interactions. -Directories: +This database is *not intended to replace* your application database (apart from in early PoC scenarios). Instead it complements it. + +You process the events from the network as they happen, **including ones you submit** because they have to be ordered with other events in the network ([learn more](https://hyperledger.github.io/firefly/keyconcepts/multiparty_process_flow.html)). + +Then you update the indexed business objects in your own database, as a result of the ordered state changes that come from the network. At any point you can go back and retrieve the set of events that caused that update to your "latest" state, whether that's on-chain transaction events, digital asset transfers (Tokens/NFTs), private data transfers, or a combination. + +![FireFly Event-driven Programming API Model](images/event_driven_programming_model.png) + +## Learn more about Hyperledger FireFly Architecture + +- [YouTube Channel](https://www.youtube.com/playlist?list=PL0MZ85B_96CFVEdBNsHRoX_f15AJacZJD) + - Check out the architecture series +- [Architecture reference documentation](https://hyperledger.github.io/firefly/architecture/node_component_architecture.html) + - Still evolving, and open for feedback - let us know what you think [on Rocket Chat](https://chat.hyperledger.org/channel/firefly) +- [Tagged git issues](https://github.com/hyperledger/firefly/issues?q=is%3Aissue+is%3Aopen+label%3Aarchitecture) + - Watch out for a new formalized Feature Improvement Request (FIR) process coming soon + +## Hyperledger FireFly project status + +A number projects are actively building on Hyperledger FireFly today, and the current feature set and API is sufficient to build many decentralized applications. Some of the microservice components have matured through a number of years (including production adoption), others are new, and some areas are still evolving quickly and subject to flux in the APIs and feature set. + +Overall, the community is working hard towards a V1.0 release. -- [kat](./kat): The core TypeScript runtime -- [solidity_kat](./solidity_kat): Ethereum/Solidity smart contract code -- [cordapp_kat](./cordapp_kat): The Corda smart contract (CorDapp) +A good reference for the scope of the V1.0 release is included in issue #117. You might be interested in getting involved. -This was the original implementation of the multi-party systems API by Kaleido, and is already deployed in a number production projects. +## Git repositories -The codebase distilled years of learning, into a set of patterns for performing blockchain orchestrated data exchange. +There are multiple Git repos making up the Hyperledger FireFly project, and this +list is likely to grow as additional pluggable extensions come online in the community: -It depends on the following Kaleido services: +- Command Line Interface (CLI) - https://github.com/hyperledger/firefly-cli +- Core (this repo) - https://github.com/hyperledger/firefly +- Sample applications - https://github.com/hyperledger/firefly-samples +- HTTPS Data Exchange - https://github.com/hyperledger/firefly-dataexchange-https +- Hyperledger Fabric connector - https://github.com/hyperledger/firefly-fabconnect +- Ethereum (Hyperledger Besu / Quorum) connector - https://github.com/hyperledger/firefly-ethconnect +- Corda connector: https://github.com/hyperledger/firefly-cordaconnect - contributed from Kaleido generation 1 - porting to generation 2 +- FireFly Explorer UI - https://github.com/hyperledger/firefly-ui + +## Contributing + +Interested in contributing to the community? + +Check out our [Contributor Guide](https://hyperledger.github.io/firefly/contributors/contributors.html), and **welcome!**. + +## Navigating this core repo + +Directories: + +- [internal](./internal): The core Golang implementation code +- [pkg](./pkg): Interfaces intended for external project use +- [cmd](./cmd): The command line entry point +- [smart_contracts](./smart_contracts): smart contract code for Firefly's onchain logic, with support for Ethereum and Hyperledger Fabric in their respective sub-directories -- Blockchain nodes - - Ethereum with the Kaleido [Kaleido REST API Gateway](https://docs.kaleido.io/kaleido-services/ethconnect/) - - Corda with the Kaleido built-in API for streaming KAT transactions -- [Kaleido Event Streams](https://docs.kaleido.io/kaleido-services/event-streams/) -- [Kaleido App2App Messaging](https://docs.kaleido.io/kaleido-services/app2app/) -- [Kaleido Document Exchange](https://docs.kaleido.io/kaleido-services/document-store/) +[Full code layout here](#firefly-code-hierarchy) -## FireFly code hierarchy +## FireFly Core code hierarchy ``` ┌──────────┐ ┌───────────────┐ @@ -343,39 +374,3 @@ Plugins: Each plugin comprises a Go shim, plus a remote agent microservice runti └───────────────┘ * Plugins integrate by returning their config structure for unmarshaling (JSON tags) ``` - -## API Query Syntax - -REST collections provide filter, `skip`, `limit` and `sort` support. - -- The field in the message is used as the query parameter -- When multiple query parameters are supplied these are combined with AND -- When the same query parameter is supplied multiple times, these are combined with OR - -### Example - -`GET` `/api/v1/messages?confirmed=>0&type=broadcast&topic=t1&topic=t2&context=@someprefix&sort=sequence&descending&skip=100&limit=50` - -This states: - -- Filter on `confirmed` greater than 0 -- Filter on `type` exactly equal to `broadcast` -- Filter on `topic` exactly equal to `t1` _or_ `t2` -- Filter on `context` containing the case-sensitive string `someprefix` -- Sort on `sequence` in `descending` order -- Paginate with `limit` of `50` and `skip` of `100` (e.g. get page 3, with 50/page) - -Table of filter operations, which must be the first character of the query string (after the `=` in the above URL path example) - -| Operator | Description | -| -------- | --------------------------------- | -| (none) | Equal | -| `!` | Not equal | -| `<` | Less than | -| `<=` | Less than or equal | -| `>` | Greater than | -| `>=` | Greater than or equal | -| `@` | Containing - case sensitive | -| `!@` | Not containing - case sensitive | -| `^` | Containing - case insensitive | -| `!^` | Not containing - case insensitive | diff --git a/architecture/intro_to_firefly_teaser.svg b/architecture/intro_to_firefly_teaser.svg deleted file mode 100644 index 88c3b21f8a..0000000000 --- a/architecture/intro_to_firefly_teaser.svg +++ /dev/null @@ -1,2277 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/images/event_driven_programming_model.png b/images/event_driven_programming_model.png new file mode 100644 index 0000000000..3fea1fd08a Binary files /dev/null and b/images/event_driven_programming_model.png differ diff --git a/images/firefly_explorer.png b/images/firefly_explorer.png new file mode 100644 index 0000000000..d5da5acfc8 Binary files /dev/null and b/images/firefly_explorer.png differ diff --git a/images/hyperledger_firefly_logo.png b/images/hyperledger_firefly_logo.png new file mode 100644 index 0000000000..f1f0ddfe28 Binary files /dev/null and b/images/hyperledger_firefly_logo.png differ diff --git a/images/multi_party_systems.png b/images/multi_party_systems.png new file mode 100644 index 0000000000..17a008735f Binary files /dev/null and b/images/multi_party_systems.png differ diff --git a/kat/.gitignore b/kat/.gitignore deleted file mode 100644 index af125da1cd..0000000000 --- a/kat/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.nyc_output -coverage -build -test-resources/sandbox -node_modules \ No newline at end of file diff --git a/kat/.vscode/settings.json b/kat/.vscode/settings.json deleted file mode 100644 index f773748f87..0000000000 --- a/kat/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mochaExplorer.files": "src/test/**/*.ts", - "mochaExplorer.require": "ts-node/register" -} \ No newline at end of file diff --git a/kat/README.md b/kat/README.md deleted file mode 100644 index 940eef7999..0000000000 --- a/kat/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Kaleido Asset Trail - -![Kaleido Asset Trail](asset_trail_overview.png) - -## Setup - -Kaleido asset trail can be run as a Kaleido member service or as a standalone application. -For the latter, deploy an ERC20 token and use its address in the constructor of the [asset Trail smart contract](solidity_new/contracts/AssetTrail.sol), - -For each participating member, deploy the following runtimes: -* IPFS -* App2App Messaging (with 2 destinations representing KAT and the client) -* Document Exchange (with 1 destination) - -You must also define an Event Stream with subscriptions to all relevant -events for your use case (subscribe to all events if unsure). - -Asset trail has built-in storage and can optionally be configured to use MongoDB. - -Create a new folder for your config. -Populate `config.json` with the URLs for the deployed contract API, the event stream, the IPFS/App2App/Document -Exchange runtimes, a valid set of credentials, and the locally running MongoDB. - -```json -{ - "port": 5000, - "assetTrailInstanceID": "asset-trail-org-a", - "protocol": "ethereum", - "apiGateway": { - "apiEndpoint": "https://xx12345678-yy12345678-connect.aws-0.kaleido.io/instances/0xc4ed58b23059e5b2f867b6040b46dcea04396c8b" - }, - "eventStreams": { - "wsEndpoint": "wss://xx12345678-yy12345678-connect.aws-0.kaleido.io/ws", - "topic": "A" - }, - "ipfs": { - "apiEndpoint": "https://xx12345678-yy12345678-ipfs.aws-0.kaleido.io" - }, - "app2app": { - "socketIOEndpoint": "wss://xx12345678-yy12345678-app2app.aws-0.kaleido.io/api/v1", - "destinations": { - "kat": "kld://app2app/z/dev2/m/zzdza6cuf9/e/zzdk0au9rl/s/zzspcgumw6/d/kat", - "client": "kld://app2app/z/dev2/m/zzdza6cuf9/e/zzdk0au9rl/s/zzspcgumw6/d/client" - } - }, - "docExchange": { - "apiEndpoint": "https://xx12345678-yy12345678-documentstore.aws-0.kaleido.io/api/v1", - "socketIOEndpoint": "wss://xx12345678-yy12345678-documentstore.aws-0.kaleido.io/api/v1", - "destination": "kld://documentstore/z/dev2/m/zzdza6cuf9/e/zzdk0au9rl/s/zzu2yrgdqw/d/a" - }, - "appCredentials": { - "user": "xx12345678", - "password": "yyyyyyyyyyy" - }, - "mongodb": { - "connectionUrl": "mongodb://localhost:27017", - "databaseName": "assettrail0" - } -} -``` - -You can create separate config folders for each org you wish to simulate. - -Run the server with the following (substitute the path to your own data directory as needed): -``` -cd core -DATA_DIRECTORY=data/single-region/OrgA nodemon -``` - -If using Visual Studio Code, there is also a provided [.vscode/launch.json](launch.json) file which can be -edited to add launch configurations to the UI. diff --git a/kat/asset_trail_overview.png b/kat/asset_trail_overview.png deleted file mode 100644 index d99b989beb..0000000000 Binary files a/kat/asset_trail_overview.png and /dev/null differ diff --git a/kat/nodemon.json b/kat/nodemon.json deleted file mode 100644 index 759d521c9d..0000000000 --- a/kat/nodemon.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "watch": ["src"], - "ext": ".ts,.js", - "ignore": [], - "exec": "ts-node ./src/app.ts" -} \ No newline at end of file diff --git a/kat/package.json b/kat/package.json deleted file mode 100644 index ade95df96b..0000000000 --- a/kat/package.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "name": "kaleido-asset-trail", - "version": "1.0.3", - "description": "Kaleido Asset Trail", - "main": "build/index.js", - "scripts": { - "start:dev": "nodemon", - "build": "rimraf ./build && tsc", - "start": "npm run build && node build/app.js", - "test_wc": "env DATA_DIRECTORY=$PWD/test-resources/sandbox/ethereum mocha --bail --timeout 25000 \"src/test/ethereum-suite.ts\" && env DATA_DIRECTORY=$PWD/test-resources/sandbox/corda mocha --bail --timeout 25000 \"src/test/corda-suite.ts\"", - "test": "nyc npm run test_wc" - }, - "keywords": [], - "author": "", - "license": "Apache-2.0", - "dependencies": { - "ajv": "^6.12.5", - "axios": "^0.21.1", - "body-parser": "^1.19.0", - "bs58": "^4.0.1", - "busboy": "^0.3.1", - "chokidar": "^3.4.3", - "express": "^4.17.1", - "form-data": "^3.0.0", - "ldapjs": "^2.2.3", - "mock-require": "^3.0.3", - "mongodb": "^3.6.3", - "nanoid": "^3.1.21", - "nedb": "git+https://github.com:hyperledger/nedb.git", - "nedb-promises": "^4.1.0", - "proxyquire": "^2.1.3", - "socket.io": "^2.3.0", - "socket.io-client": "^2.3.1", - "uuid": "^8.3.1", - "ws": "^7.4.5" - }, - "devDependencies": { - "@types/axios": "^0.14.0", - "@types/bs58": "^4.0.1", - "@types/busboy": "^0.2.3", - "@types/express": "^4.17.8", - "@types/ldapjs": "^1.0.9", - "@types/mocha": "^8.0.3", - "@types/mock-require": "^2.0.0", - "@types/mongodb": "^3.6.3", - "@types/node": "^14.11.5", - "@types/proxyquire": "^1.3.28", - "@types/rimraf": "^3.0.0", - "@types/sinon": "^9.0.10", - "@types/socket.io": "^2.1.11", - "@types/socket.io-client": "^1.4.34", - "@types/supertest": "^2.0.10", - "@types/uuid": "^8.3.0", - "@types/ws": "^7.2.7", - "mocha": "^8.4.0", - "nock": "^13.0.4", - "nodemon": "^2.0.4", - "nyc": "^15.1.0", - "rimraf": "^3.0.2", - "sinon": "^10.0.0", - "supertest": "^6.0.0", - "ts-node": "^9.0.0", - "ts-sinon": "^2.0.1", - "typescript": "^4.2.4" - }, - "nyc": { - "extension": [ - ".ts", - ".tsx" - ], - "exclude": [ - "coverage", - "test", - "dist", - "**/*.d.ts", - "src/test" - ], - "reporter": [ - "html", - "text-summary" - ], - "all": true, - "check-coverage": true, - "statements": 64, - "branches": 42, - "functions": 64, - "lines": 66 - }, - "mocha": { - "require": [ - "ts-node/register", - "source-map-support/register" - ] - } -} diff --git a/kat/src/app.ts b/kat/src/app.ts deleted file mode 100644 index 8cd1aa8e2c..0000000000 --- a/kat/src/app.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { start } from './index'; -import * as utils from './lib/utils'; - -const log = utils.getLogger('app.ts'); - -export const promise = start().catch(err => { - log.error(`Failed to start asset trail. ${err}`); -}); \ No newline at end of file diff --git a/kat/src/clients/api-gateway.ts b/kat/src/clients/api-gateway.ts deleted file mode 100644 index cb705d4e40..0000000000 --- a/kat/src/clients/api-gateway.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { config } from '../lib/config'; -import { IAPIGatewayAsyncResponse, IAPIGatewaySyncResponse } from '../lib/interfaces'; -import * as ethereumGateway from './gateway-providers/ethereum'; -import * as cordaGateway from './gateway-providers/corda'; - -// Member APIs - -export const upsertMember = async (address: string, name: string, app2appDestination: string, - docExchangeDestination: string, sync: boolean): Promise => { - return ethereumGateway.upsertMember(address, name, app2appDestination, docExchangeDestination, sync); -}; - - -// Asset definition APIs - -export const createAssetDefinition = async (author: string, assetDefinitionHash: string, sync: boolean): - Promise => { - return ethereumGateway.createAssetDefinition(author, assetDefinitionHash, sync); -}; - - -// Payment definition APIs - -export const createDescribedPaymentDefinition = async (paymentDefinitionID: string, name: string, author: string, - descriptionSchemaHash: string, sync: boolean): Promise => { - return ethereumGateway.createDescribedPaymentDefinition(paymentDefinitionID, name, author, descriptionSchemaHash, sync); -}; - -export const createPaymentDefinition = async (paymentDefinitionID: string, name: string, author: string, sync: boolean): - Promise => { - return ethereumGateway.createPaymentDefinition(paymentDefinitionID, name, author, sync); -}; - - -// Asset instance APIs - -export const createDescribedAssetInstance = async (assetInstanceID: string, assetDefinitionID: string, author: string, - descriptionHash: string, contentHash: string, participants: string[] | undefined, sync = false): Promise => { - switch (config.protocol) { - case 'corda': - return cordaGateway.createDescribedAssetInstance(assetInstanceID, assetDefinitionID, descriptionHash, contentHash, participants); - case 'ethereum': - return ethereumGateway.createDescribedAssetInstance(assetInstanceID, assetDefinitionID, author, descriptionHash, contentHash, sync); - } -}; - -export const createAssetInstance = async (assetInstanceID: string, assetDefinitionID: string, author: string, - contentHash: string, participants: string[] | undefined, sync = false): Promise => { - switch (config.protocol) { - case 'corda': - return cordaGateway.createAssetInstance(assetInstanceID, assetDefinitionID, contentHash, participants); - case 'ethereum': - return ethereumGateway.createAssetInstance(assetInstanceID, assetDefinitionID, author, contentHash, sync); - } -}; - -export const createAssetInstanceBatch = async (batchHash: string, author: string, participants: string[] | undefined, sync = false): Promise => { - switch (config.protocol) { - case 'corda': - return cordaGateway.createAssetInstanceBatch(batchHash, participants); - case 'ethereum': - return ethereumGateway.createAssetInstanceBatch(batchHash, author, sync); - } -} - -export const setAssetInstanceProperty = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, value: string, - participants: string[] | undefined, sync: boolean): Promise => { - switch (config.protocol) { - case 'corda': - return cordaGateway.setAssetInstanceProperty(assetDefinitionID, assetInstanceID, key, value, participants); - case 'ethereum': - return ethereumGateway.setAssetInstanceProperty(assetDefinitionID, assetInstanceID, author, key, value, sync); - } -}; - - -// Payment instance APIs - -export const createDescribedPaymentInstance = async (paymentInstanceID: string, paymentDefinitionID: string, - author: string, member: string, amount: number, descriptionHash: string, participants: string[] | undefined, sync: boolean): - Promise => { - switch (config.protocol) { - case 'corda': - return cordaGateway.createDescribedPaymentInstance(paymentInstanceID, paymentDefinitionID, member, amount, descriptionHash, participants); - case 'ethereum': - return ethereumGateway.createDescribedPaymentInstance(paymentInstanceID, paymentDefinitionID, author, member, amount, descriptionHash, sync); - } -}; - -export const createPaymentInstance = async (paymentInstanceID: string, paymentDefinitionID: string, - author: string, member: string, amount: number, participants: string[] | undefined, sync: boolean): - Promise => { - switch (config.protocol) { - case 'corda': - return cordaGateway.createPaymentInstance(paymentInstanceID, paymentDefinitionID, member, amount, participants); - case 'ethereum': - return ethereumGateway.createPaymentInstance(paymentInstanceID, paymentDefinitionID, author, member, amount, sync); - } -}; diff --git a/kat/src/clients/app2app.ts b/kat/src/clients/app2app.ts deleted file mode 100644 index 0057135dc5..0000000000 --- a/kat/src/clients/app2app.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import io from 'socket.io-client'; -import { config } from '../lib/config'; -import * as utils from '../lib/utils'; -import { AssetTradeMessage, IApp2AppMessage, IApp2AppMessageListener } from '../lib/interfaces'; - -const log = utils.getLogger('clients/app2app.ts'); - -let socket: SocketIOClient.Socket -let listeners: IApp2AppMessageListener[] = []; - -export const init = async () => { - establishSocketIOConnection(); -}; - -function subscribeWithRetry() { - log.trace(`App2App subscription: ${config.app2app.destinations.kat}`) - socket.emit('subscribe', [config.app2app.destinations.kat], (err: any, data: any) => { - if (err) { - log.error(`App2App subscription failure (retrying): ${err}`); - setTimeout(subscribeWithRetry, utils.constants.SUBSCRIBE_RETRY_INTERVAL); - return; - } - log.trace(`App2App subscription succeeded: ${JSON.stringify(data)}`); - }); -} - -const establishSocketIOConnection = () => { - let error = false; - const { APP2APP_BATCH_SIZE, APP2APP_BATCH_TIMEOUT, APP2APP_READ_AHEAD } = utils.constants; - socket = io.connect(`${config.app2app.socketIOEndpoint}?auto_commit=false&read_ahead=${APP2APP_READ_AHEAD}&batch_size=${APP2APP_BATCH_SIZE}&batch_timeout=${APP2APP_BATCH_TIMEOUT}`, { - transportOptions: { - polling: { - extraHeaders: { - Authorization: 'Basic ' + Buffer.from(`${config.appCredentials.user}` + - `:${config.appCredentials.password}`).toString('base64') - } - } - } - }).on('connect', () => { - if (error) { - error = false; - log.info('App2App messaging Socket IO connection restored'); - } - subscribeWithRetry(); - }).on('connect_error', (err: Error) => { - error = true; - log.error(`App2App messaging Socket IO connection error. ${err.toString()}`); - }).on('error', (err: Error) => { - error = true; - log.error(`App2app messaging Socket IO error. ${err.toString()}`); - }).on('exception', (err: Error, extra?: any) => { - // Exceptions are such things as delivery failures. They do not put the connection in error state - log.error(`App2app messaging exception. ${err.toString()}`, extra); - }).on('data', (app2appMessage: IApp2AppMessage) => { - log.trace(`App2App message ${JSON.stringify(app2appMessage)}`); - try { - const content: AssetTradeMessage = JSON.parse(app2appMessage.content); - log.trace(`App2App message type=${content.type}`) - for (const listener of listeners) { - listener(app2appMessage.headers, content); - } - } catch (err) { - log.error(`App2App message error ${err}`); - } finally { - socket.emit('commit'); - } - }) as SocketIOClient.Socket; -}; - -export const addListener = (listener: IApp2AppMessageListener) => { - listeners.push(listener); -}; - -export const removeListener = (listener: IApp2AppMessageListener) => { - listeners = listeners.filter(entry => entry != listener); -}; - -export const dispatchMessage = (to: string, content: any) => { - log.trace(`App2App dispatch type=${content.type}`) - socket.emit('produce', { - headers: { - from: config.app2app.destinations.kat, - to - }, - content: JSON.stringify(content) - }, 'kat', (err: any) => { - if(err) { - log.error(`Failed to dispatch App2App message.`, err); - } - }); -}; - -export const reset = () => { - if (socket) { - log.info('App2App Socket IO connection reset'); - socket.close(); - establishSocketIOConnection(); - } -}; \ No newline at end of file diff --git a/kat/src/clients/database.ts b/kat/src/clients/database.ts deleted file mode 100644 index 939d372731..0000000000 --- a/kat/src/clients/database.ts +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { config } from '../lib/config'; -import { ClientEventType, IClientEventListener, IDatabaseProvider, IDBAssetDefinition, IDBAssetInstance, IDBBatch, IDBBlockchainData, IDBMember, IDBPaymentDefinition, IDBPaymentInstance, IStoredSubscriptions } from '../lib/interfaces'; -import * as utils from '../lib/utils'; -import MongoDBProvider from './db-providers/mongodb'; -import NEDBProvider from './db-providers/nedb'; -const log = utils.getLogger('handlers/asset-trade.ts'); - -let databaseProvider: IDatabaseProvider; - -export const init = async () => { - if (config.mongodb !== undefined) { - databaseProvider = new MongoDBProvider(); - } else { - databaseProvider = new NEDBProvider(); - } - await databaseProvider.init(); -}; - -let listeners: IClientEventListener[] = []; - -// COLLECTION AGNOSTIC QUERIES - -export const createCollection = (collectionName: string, indexes: { fields: string[], unique?: boolean }[]) => { - return databaseProvider.createCollection(collectionName, indexes); -}; - -// MEMBER QUERIES - -export const retrieveMemberByAddress = (address: string): Promise => { - return databaseProvider.findOne('members', { address }); -}; - -export const retrieveMembers = (query: object, skip: number, limit: number): Promise => { - return databaseProvider.find('members', query, { name: 1 }, skip, limit); -}; - -export const upsertMember = async (member: IDBMember) => { - await databaseProvider.updateOne('members', { address: member.address }, { $set: member }, true); - emitEvent('member-registered', member); -}; - -// ASSET DEFINITION QUERIES - -export const retrieveAssetDefinitions = (query: object, skip: number, limit: number): Promise => { - return databaseProvider.find('asset-definitions', query, { name: 1 }, skip, limit) -}; - -export const countAssetDefinitions = (query: object): Promise => { - return databaseProvider.count('asset-definitions', query); -}; - -export const retrieveAssetDefinitionByID = (assetDefinitionID: string): Promise => { - return databaseProvider.findOne('asset-definitions', { assetDefinitionID }); -}; - -export const retrieveAssetDefinitionByName = (name: string): Promise => { - return databaseProvider.findOne('asset-definitions', { name }); -}; - -export const upsertAssetDefinition = async (assetDefinition: IDBAssetDefinition) => { - await databaseProvider.updateOne('asset-definitions', { assetDefinitionID: assetDefinition.assetDefinitionID }, { $set: assetDefinition }, true); - if (assetDefinition.submitted !== undefined) { - emitEvent('asset-definition-submitted', assetDefinition); - } else if (assetDefinition.transactionHash !== undefined) { - emitEvent('asset-definition-created', assetDefinition); - } -}; - -export const markAssetDefinitionAsConflict = async (assetDefinitionID: string, timestamp: number) => { - await databaseProvider.updateOne('asset-definitions', { assetDefinitionID }, { $set: { timestamp, conflict: true } }, false); - emitEvent('asset-definition-name-conflict', { assetDefinitionID }) -}; - -// PAYMENT DEFINITION QUERIES - -export const retrievePaymentDefinitions = (query: object, skip: number, limit: number): Promise => { - return databaseProvider.find('payment-definitions', query, { name: 1 }, skip, limit); -}; - -export const countPaymentDefinitions = (query: object): Promise => { - return databaseProvider.count('payment-definitions', query); -}; - -export const retrievePaymentDefinitionByID = (paymentDefinitionID: string): Promise => { - return databaseProvider.findOne('payment-definitions', { paymentDefinitionID }); -}; - -export const retrievePaymentDefinitionByName = (name: string): Promise => { - return databaseProvider.findOne('payment-definitions', { name }); -}; - -export const upsertPaymentDefinition = async (paymentDefinition: IDBPaymentDefinition) => { - await databaseProvider.updateOne('payment-definitions', { paymentDefinitionID: paymentDefinition.paymentDefinitionID }, { $set: paymentDefinition }, true) - if (paymentDefinition.submitted !== undefined) { - emitEvent('payment-definition-submitted', paymentDefinition); - } else if (paymentDefinition.transactionHash !== undefined) { - emitEvent('payment-definition-created', paymentDefinition); - } -}; - -export const markPaymentDefinitionAsConflict = async (paymentDefinitionID: string, timestamp: number) => { - await databaseProvider.updateOne('payment-definitions', { paymentDefinitionID }, { $set: { conflict: true, timestamp } }, false); - emitEvent('payment-definition-name-conflict', { paymentDefinitionID }) -}; - -// ASSET INSTANCE QUERIES - -export const retrieveAssetInstances = (assetDefinitionID: string, query: object, sort: object, skip: number, limit: number): Promise => { - return databaseProvider.find(`asset-instance-${assetDefinitionID}`, query, sort, skip, limit); -}; - -export const countAssetInstances = (assetDefinitionID: string, query: object): Promise => { - return databaseProvider.count(`asset-instance-${assetDefinitionID}`, query); -}; - -export const retrieveAssetInstanceByID = (assetDefinitionID: string, assetInstanceID: string): Promise => { - return databaseProvider.findOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }); -}; - -export const retrieveAssetInstanceByDefinitionIDAndContentHash = (assetDefinitionID: string, contentHash: string): Promise => { - return databaseProvider.findOne(`asset-instance-${assetDefinitionID}`, { contentHash }); -}; - -export const upsertAssetInstance = async (assetInstance: IDBAssetInstance) => { - await databaseProvider.updateOne(`asset-instance-${assetInstance.assetDefinitionID}`, { assetInstanceID: assetInstance.assetInstanceID }, { $set: assetInstance }, true); - if (assetInstance.submitted !== undefined) { - emitEvent('asset-instance-submitted', assetInstance); - } else if (assetInstance.transactionHash !== undefined) { - emitEvent('asset-instance-created', assetInstance); - } -}; - -export const setAssetInstanceReceipt = async (assetDefinitionID: string, assetInstanceID: string, receipt: string) => { - await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, { $set: { receipt } }, true); -}; - -export const setAssetInstancePrivateContent = async (assetDefinitionID: string, assetInstanceID: string, content: object | undefined, filename: string | undefined) => { - await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, { $set: { content, filename } }, true); - log.info(`Emitting event for private-asset-instance-content-stored`); - emitEvent('private-asset-instance-content-stored', { assetDefinitionID, assetInstanceID, content, filename }); -}; - -export const markAssetInstanceAsConflict = async (assetDefinitionID: string, assetInstanceID: string, timestamp: number) => { - await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, { $set: { conflict: true, timestamp } }, false); - emitEvent('asset-instance-content-conflict', { assetDefinitionID, assetInstanceID }); -}; - -export const setSubmittedAssetInstanceProperty = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, value: string, submitted: number, batchID?: string) => { - await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, - { - $set: { - [`properties.${author}.${key}.value`]: value, - [`properties.${author}.${key}.submitted`]: submitted, - [`properties.${author}.${key}.batchID`]: batchID, - } - }, false); - emitEvent('asset-instance-property-submitted', { assetDefinitionID, assetInstanceID, key, value, submitted, batchID }); -}; - -export const setAssetInstancePropertyReceipt = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, receipt: string) => { - await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, - { - $set: { - [`properties.${author}.${key}.receipt`]: receipt - } - }, false); -}; - -export const setConfirmedAssetInstanceProperty = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, value: string, timestamp: number, { blockNumber, transactionHash }: IDBBlockchainData) => { - await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, - { - $set: { - [`properties.${author}.${key}.value`]: value, - [`properties.${author}.${key}.history.${timestamp}`]: { value, timestamp, blockNumber, transactionHash } - } - }, false); - emitEvent('asset-instance-property-set', { assetDefinitionID, assetInstanceID, author, key, value, timestamp, blockNumber, transactionHash }); -}; - -// PAYMENT INSTANCE QUERIES - -export const retrievePaymentInstances = (query: object, sort: object, skip: number, limit: number): Promise => { - return databaseProvider.find('payment-instances', query, sort, skip, limit); -}; - -export const countPaymentInstances = (query: object): Promise => { - return databaseProvider.count('payment-instances', query); -}; - -export const retrievePaymentInstanceByID = (paymentInstanceID: string): Promise => { - return databaseProvider.findOne('payment-instances', { paymentInstanceID }); -}; - -export const upsertPaymentInstance = async (paymentInstance: IDBPaymentInstance) => { - await databaseProvider.updateOne('payment-instances', { paymentInstanceID: paymentInstance.paymentInstanceID }, { $set: paymentInstance }, true); - if (paymentInstance.submitted !== undefined) { - emitEvent('payment-instance-submitted', paymentInstance); - } else { - emitEvent('payment-instance-created', paymentInstance); - } -}; - - -// BATCH QUERIES - -export const retrieveBatches = (query: object, skip: number, limit: number, sort: {[f: string]: number} = {}): Promise => { - return databaseProvider.find('batches', query, sort, skip, limit); -}; - -export const retrieveBatchByID = (batchID: string): Promise => { - return databaseProvider.findOne('batches', { batchID }); -}; - -export const retrieveBatchByHash = (batchHash: string): Promise => { - return databaseProvider.findOne('batches', { batchHash }); -}; - -export const upsertBatch = async (batch: IDBBatch) => { - await databaseProvider.updateOne('batches', { batchID: batch.batchID }, { $set: batch }, true); -}; - -// SUBSCRIPTION MANAGEMENT - -export const retrieveSubscriptions = (): Promise => { - return databaseProvider.findOne('state', { key: 'subscriptions' }); -}; - -export const upsertSubscriptions = (subscriptions: IStoredSubscriptions): Promise => { - return databaseProvider.updateOne('state', { key: 'subscriptions' }, { $set: subscriptions }, true); -}; - -// EVENT HANDLING - -export const addListener = (listener: IClientEventListener) => { - listeners.push(listener); -}; - -export const removeListener = (listener: IClientEventListener) => { - listeners = listeners.filter(entry => entry != listener); -}; - -const emitEvent = (eventType: ClientEventType, content: object) => { - for (const listener of listeners) { - listener(eventType, content); - } -}; - -export const shutDown = () => { - databaseProvider.shutDown(); -}; diff --git a/kat/src/clients/db-providers/mongodb.ts b/kat/src/clients/db-providers/mongodb.ts deleted file mode 100644 index 8b06f26fee..0000000000 --- a/kat/src/clients/db-providers/mongodb.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Db, MongoClient } from 'mongodb'; -import { config } from '../../lib/config'; -import { databaseCollectionName, IDatabaseProvider, indexes } from '../../lib/interfaces'; -import { databaseCollectionIndexes } from '../../lib/utils'; - -let db: Db; -let mongoClient: MongoClient; - -export default class MongoDBProvider implements IDatabaseProvider { - - async init() { - try { - mongoClient = await MongoClient.connect(config.mongodb.connectionUrl, - { useNewUrlParser: true, useUnifiedTopology: true, ignoreUndefined: true }); - db = mongoClient.db(config.mongodb.databaseName); - for (const [collectionName, indexes] of Object.entries(databaseCollectionIndexes)) { - this.createCollection(collectionName, indexes); - } - } catch (err) { - throw new Error(`Failed to connect to Mongodb. ${err}`); - } - } - - async createCollection(collectionName: string, indexes: indexes) { - try { - for (const index of indexes) { - const fields: { [f: string]: number } = {}; - for (const field of index.fields) { - fields[field] = 1; // all ascending currently - } - db.collection(collectionName).createIndex(fields, { unique: !!index.unique }); - } - } catch (err) { - throw new Error(`Failed to create collection. ${err}`); - } - } - - count(collectionName: databaseCollectionName, query: object): Promise { - return db.collection(collectionName).find(query).count(); - } - - find(collectionName: databaseCollectionName, query: object, sort: object, skip: number, limit: number): Promise { - return db.collection(collectionName).find(query, { projection: { _id: 0 } }).sort(sort).skip(skip).limit(limit).toArray(); - } - - findOne(collectionName: databaseCollectionName, query: object): Promise { - return db.collection(collectionName).findOne(query, { projection: { _id: 0 } }); - } - - async updateOne(collectionName: databaseCollectionName, query: object, value: object, upsert: boolean) { - await db.collection(collectionName).updateOne(query, value, { upsert }); - } - - shutDown() { - mongoClient.close(); - } - -} diff --git a/kat/src/clients/db-providers/nedb.ts b/kat/src/clients/db-providers/nedb.ts deleted file mode 100644 index a539e7a089..0000000000 --- a/kat/src/clients/db-providers/nedb.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Datastore from 'nedb-promises'; -import path from 'path'; -import { databaseCollectionName, IDatabaseProvider, indexes } from '../../lib/interfaces'; -import { constants, databaseCollectionIndexes } from '../../lib/utils'; - -const projection = { _id: 0 }; -let collections: { [name: string]: Datastore } = {}; - -export default class NEDBProvider implements IDatabaseProvider { - - async init() { - try { - for (const [collectionName, indexes] of Object.entries(databaseCollectionIndexes)) { - this.createCollection(collectionName, indexes); - } - } catch (err) { - throw new Error(`Failed to initialize NEDB. ${err}`); - } - } - - async createCollection(collectionName: string, indexes: indexes) { - const collection = Datastore.create({ - filename: path.join(constants.DATA_DIRECTORY, `${collectionName}.json`), - autoload: true - }); - for (const index of indexes) { - // No compound indexes here - for (let fieldName of index.fields) { - collection.ensureIndex({ fieldName, unique: !!index.unique }); - } - } - collections[collectionName] = collection; - } - - count(collectionName: databaseCollectionName, query: object): Promise { - return collections[collectionName].count(query); - } - - find(collectionName: databaseCollectionName, query: object, sort: object, skip: number, limit: number): Promise { - return collections[collectionName].find(query, projection).skip(skip).limit(limit).sort(sort); - } - - findOne(collectionName: databaseCollectionName, query: object): Promise { - return collections[collectionName].findOne(query, projection); - } - - async updateOne(collectionName: databaseCollectionName, query: object, value: object, upsert: boolean) { - await collections[collectionName].update(query, value, { upsert }); - } - - shutDown() { } - -} diff --git a/kat/src/clients/doc-exchange.ts b/kat/src/clients/doc-exchange.ts deleted file mode 100644 index ff9a791019..0000000000 --- a/kat/src/clients/doc-exchange.ts +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { IDocExchangeTransferData, IDocExchangeListener, IDocExchangeDocumentDetails } from '../lib/interfaces'; -import { config } from '../lib/config'; -import { Stream, Readable } from 'stream'; -import io from 'socket.io-client'; -import FormData from 'form-data'; -import axios from 'axios'; -import * as utils from '../lib/utils'; - -const log = utils.getLogger('clients/doc-exchange.ts'); - -let socket: SocketIOClient.Socket -let listeners: IDocExchangeListener[] = []; - -export const init = async () => { - try { - const response = await axios.get(`${config.docExchange.apiEndpoint}/documents`, { - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - if (!Array.isArray(response.data.entries)) { - throw 'Invalid response'; - } else { - establishSocketIOConnection(); - } - } catch (err) { - throw new Error(`Document exchange REST connection failed. ${err}`); - } -}; - -const establishSocketIOConnection = () => { - let error = false; - socket = io.connect(config.docExchange.socketIOEndpoint, { - transportOptions: { - polling: { - extraHeaders: { - Authorization: 'Basic ' + Buffer.from(`${config.appCredentials.user}` + - `:${config.appCredentials.password}`).toString('base64') - } - } - } - }).on('connect', () => { - if (error) { - error = false; - log.info('Document exchange Socket IO connection restored'); - } - }).on('connect_error', (err: Error) => { - error = true; - log.error(`Document exchange Socket IO connection error. ${err.toString()}`); - }).on('error', (err: Error) => { - error = true; - log.error(`Document exchange Socket IO error. ${err.toString()}`); - }).on('document_received', (transferData: IDocExchangeTransferData) => { - log.trace(`Doc exchange transfer event ${JSON.stringify(transferData)}`); - for (const listener of listeners) { - listener(transferData); - } - }) as SocketIOClient.Socket; -}; - -export const addListener = (listener: IDocExchangeListener) => { - listeners.push(listener); -}; - -export const removeListener = (listener: IDocExchangeListener) => { - listeners = listeners.filter(entry => entry != listener); -}; - -export const downloadStream = async (documentPath: string): Promise => { - const response = await axios.get(`${config.docExchange.apiEndpoint}/documents/${documentPath}`, { - responseType: 'arraybuffer', - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - return response.data; -}; - -export const downloadJSON = async (documentPath: string): Promise => { - const response = await axios.get(`${config.docExchange.apiEndpoint}/documents/${documentPath}`, { - responseType: 'json', - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - return response.data; -}; - -export const findDocumentByHash = async (documentHash: string): Promise => { - const result = await axios({ - url: `${config.docExchange.apiEndpoint}/search?query=${documentHash}&by_hash=true`, - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - if (result.data.documents.length > 0) { - return result.data.documents[0].full_path; - } - return null; -} - -export const uploadString = async (value: string, path: string): Promise => { - const readable = new Readable(); - readable.push(value); - readable.push(null); - return uploadStream(readable, path); -}; - -export const uploadStream = async (stream: Stream, path: string): Promise => { - const formData = new FormData(); - formData.append('document', stream); - const result = await axios({ - method: 'put', - url: `${config.docExchange.apiEndpoint}/documents/${path}`, - data: formData, - headers: formData.getHeaders(), - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - return result.data.hash; -}; - -export const transfer = async (from: string, to: string, document: string) => { - await axios({ - method: 'post', - url: `${config.docExchange.apiEndpoint}/transfers`, - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - }, - data: { from, to, document } - }); -} - -export const getDocumentDetails = async (filePath: string): Promise => { - const result = await axios({ - url: `${config.docExchange.apiEndpoint}/documents/${filePath}?details_only=true`, - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - return result.data; -}; - -export const reset = () => { - if (socket) { - log.info('Document exchange Socket IO connection reset'); - socket.close(); - establishSocketIOConnection(); - } -}; diff --git a/kat/src/clients/event-streams.ts b/kat/src/clients/event-streams.ts deleted file mode 100644 index 6ff008cb1c..0000000000 --- a/kat/src/clients/event-streams.ts +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import WebSocket from 'ws'; -import { config } from '../lib/config'; -import * as utils from '../lib/utils'; -import { IDBBlockchainData, IEventAssetDefinitionCreated, IEventAssetInstanceBatchCreated, IEventAssetInstanceCreated, IEventAssetInstancePropertySet, IEventPaymentDefinitionCreated, IEventPaymentInstanceCreated, IEventStreamMessage, IEventStreamRawMessageCorda } from '../lib/interfaces'; -import * as membersHandler from '../handlers/members'; -import * as assetDefinitionsHandler from '../handlers/asset-definitions'; -import * as paymentDefinitionsHandler from '../handlers/payment-definitions'; -import * as assetInstancesHandler from '../handlers/asset-instances'; -import * as paymentInstanceHandler from '../handlers/payment-instances'; -import { IEventMemberRegistered } from '../lib/interfaces'; - -const log = utils.getLogger('clients/event-streams.ts'); - -let ws: WebSocket; -let heartBeatTimeout: NodeJS.Timeout; -let disconnectionDetected = false; -let disconnectionTimeout: NodeJS.Timeout; - -export const init = () => { - ws = new WebSocket(config.eventStreams.wsEndpoint, { - headers: { - Authorization: 'Basic ' + Buffer.from(`${config.eventStreams.auth?.user ?? config.appCredentials.user}` + - `:${config.eventStreams.auth?.password ?? config.appCredentials.password}`).toString('base64') - - } - }); - addEventHandlers(); -}; - -export const shutDown = () => { - if (disconnectionTimeout) { - clearTimeout(disconnectionTimeout); - } - if (ws) { - clearTimeout(heartBeatTimeout); - ws.close(); - } -}; - -const addEventHandlers = () => { - ws.on('open', () => { - if (disconnectionDetected) { - disconnectionDetected = false; - log.info('Event stream websocket restored'); - } - ws.send(JSON.stringify({ - type: 'listen', - topic: config.eventStreams.topic - })); - heartBeat(); - }).on('close', () => { - disconnectionDetected = true; - log.error(`Event stream websocket disconnected, attempting to reconnect in ${utils.constants.EVENT_STREAM_WEBSOCKET_RECONNECTION_DELAY_SECONDS} second(s)`); - disconnectionTimeout = setTimeout(() => { - init(); - }, utils.constants.EVENT_STREAM_WEBSOCKET_RECONNECTION_DELAY_SECONDS * 1000); - }).on('message', async (message: string) => { - await handleMessage(message); - ws.send(JSON.stringify({ - type: 'ack', - topic: config.eventStreams.topic - })); - }).on('pong', () => { - heartBeat(); - }).on('error', err => { - log.error(`Event stream websocket error. ${err}`); - }); -}; - -const heartBeat = () => { - ws.ping(); - clearTimeout(heartBeatTimeout); - heartBeatTimeout = setTimeout(() => { - log.error('Event stream ping timeout'); - ws.terminate(); - }, utils.constants.EVENT_STREAM_PING_TIMEOUT_SECONDS * 1000); -} - -const processRawMessage = (message: string): Array => { - switch (config.protocol) { - case 'ethereum': - return JSON.parse(message); - case 'corda': - const cordaMessages: Array = JSON.parse(message); - return cordaMessages.map(msg => ( - { - data: { - ...msg.data.data, - timestamp: Date.parse(msg.recordedTime) - }, - transactionHash: msg.stateRef.txhash, - subId: msg.subId, - signature: msg.signature - } - ) - ); - } -} - -const getBlockchainData = (message: IEventStreamMessage): IDBBlockchainData => { - switch (config.protocol) { - case 'ethereum': - return { - blockNumber: Number(message.blockNumber), - transactionHash: message.transactionHash - } - case 'corda': { - return { - transactionHash: message.transactionHash - } - } - } -} - -const eventSignatures = () => { - switch (config.protocol) { - case 'ethereum': return utils.contractEventSignatures - case 'corda': return utils.contractEventSignaturesCorda - } -} - -const handleMessage = async (message: string) => { - const messageArray: Array = processRawMessage(message); - log.info(`Event batch (${messageArray.length})`) - const signatures = eventSignatures(); - for (const message of messageArray) { - log.trace(`Event ${JSON.stringify(message)}`); - log.info(`Event signature: ${message.signature}`); - const blockchainData: IDBBlockchainData = getBlockchainData(message); - try { - switch (message.signature) { - case signatures.MEMBER_REGISTERED: - await membersHandler.handleMemberRegisteredEvent(message.data as IEventMemberRegistered, blockchainData); break; - case signatures.ASSET_DEFINITION_CREATED: - await assetDefinitionsHandler.handleAssetDefinitionCreatedEvent(message.data as IEventAssetDefinitionCreated, blockchainData); break; - case signatures.DESCRIBED_PAYMENT_DEFINITION_CREATED: - case signatures.PAYMENT_DEFINITION_CREATED: - await paymentDefinitionsHandler.handlePaymentDefinitionCreatedEvent(message.data as IEventPaymentDefinitionCreated, blockchainData); break; - case signatures.ASSET_INSTANCE_CREATED: - case signatures.DESCRIBED_ASSET_INSTANCE_CREATED: - await assetInstancesHandler.handleAssetInstanceCreatedEvent(message.data as IEventAssetInstanceCreated, blockchainData); break; - case signatures.ASSET_INSTANCE_BATCH_CREATED: - await assetInstancesHandler.handleAssetInstanceBatchCreatedEvent(message.data as IEventAssetInstanceBatchCreated, blockchainData); break; - case signatures.DESCRIBED_PAYMENT_INSTANCE_CREATED: - case signatures.PAYMENT_INSTANCE_CREATED: - await paymentInstanceHandler.handlePaymentInstanceCreatedEvent(message.data as IEventPaymentInstanceCreated, blockchainData); break; - case signatures.ASSET_PROPERTY_SET: - await assetInstancesHandler.handleSetAssetInstancePropertyEvent(message.data as IEventAssetInstancePropertySet, blockchainData); break; - } - } catch (err) { - log.error(`Failed to handle event: ${message.signature} for message: ${JSON.stringify(message)} with error`, err.stack); - } - } -}; diff --git a/kat/src/clients/gateway-providers/corda.ts b/kat/src/clients/gateway-providers/corda.ts deleted file mode 100644 index 65ac70ae45..0000000000 --- a/kat/src/clients/gateway-providers/corda.ts +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import axios from 'axios'; -import { config } from '../../lib/config'; -import { IAPIGatewayAsyncResponse, IAPIGatewaySyncResponse } from '../../lib/interfaces'; -import { getLogger } from '../../lib/utils'; -const log = getLogger('gateway-providers/corda.ts'); - -// Asset instance APIs - -export const createDescribedAssetInstance = async (assetInstanceID: string, assetDefinitionID: string, - descriptionHash: string, contentHash: string, participants: string[] | undefined): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createDescribedAssetInstance`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - assetInstanceID: assetInstanceID, - assetDefinitionID: assetDefinitionID, - descriptionHash, - contentHash, - participants - } - }); - return { ...response.data, type: 'sync' }; - } catch (err) { - return handleError(`Failed to create described asset instance ${assetInstanceID}`, err); - } -}; - -export const createAssetInstance = async (assetInstanceID: string, assetDefinitionID: string, - contentHash: string, participants: string[] | undefined): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createAssetInstance`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - assetInstanceID: assetInstanceID, - assetDefinitionID: assetDefinitionID, - contentHash, - participants - } - }); - return { ...response.data, type: 'sync' }; - } catch (err) { - return handleError(`Failed to create asset instance ${assetInstanceID}`, err); - } -}; - -export const createAssetInstanceBatch = async (batchHash: string, participants: string[] | undefined): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createAssetInstanceBatch`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - batchHash, - participants - } - }); - return { ...response.data, type: 'sync' }; - } catch (err) { - return handleError(`Failed to create asset instance batch ${batchHash}`, err); - } -}; - -export const setAssetInstanceProperty = async (assetDefinitionID: string, assetInstanceID: string, key: string, value: string, - participants: string[] | undefined | undefined): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/setAssetInstanceProperty`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - assetDefinitionID: assetDefinitionID, - assetInstanceID: assetInstanceID, - key, - value, - participants - } - }); - return { ...response.data, type: 'sync' }; - } catch (err) { - return handleError(`Failed to set asset instance property ${key} (instance=${assetInstanceID})`, err); - } -}; - -export const createDescribedPaymentInstance = async (paymentInstanceID: string, paymentDefinitionID: string, member: string, amount: number, descriptionHash: string, participants: string[] | undefined): - Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createDescribedPaymentInstance`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - paymentInstanceID: paymentInstanceID, - paymentDefinitionID: paymentDefinitionID, - member, - amount, - descriptionHash, - participants - } - }); - return { ...response.data, type: 'sync' }; - } catch (err) { - return handleError(`Failed to create described asset payment instance ${paymentInstanceID}`, err); - } -}; - -export const createPaymentInstance = async (paymentInstanceID: string, paymentDefinitionID: string, - member: string, amount: number, participants: string[] | undefined): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createPaymentInstance`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - paymentInstanceID: paymentInstanceID, - paymentDefinitionID: paymentDefinitionID, - member, - amount, - participants - } - }); - return { ...response.data, type: 'sync' }; - } catch (err) { - return handleError(`Failed to create asset payment instance ${paymentInstanceID}`, err); - } -}; - -function handleError(msg: string, err: any): Promise { - const errMsg = err.response?.data?.error ?? err.response.data.message ?? err.toString(); - log.error(`${msg}. ${errMsg}`); - throw new Error(msg); -} \ No newline at end of file diff --git a/kat/src/clients/gateway-providers/ethereum.ts b/kat/src/clients/gateway-providers/ethereum.ts deleted file mode 100644 index 28493d8680..0000000000 --- a/kat/src/clients/gateway-providers/ethereum.ts +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import axios from 'axios'; -import { config } from '../../lib/config'; -import { IAPIGatewayAsyncResponse, IAPIGatewaySyncResponseEthereum } from '../../lib/interfaces'; -import * as utils from '../../lib/utils'; -const log = utils.getLogger('gateway-providers/ethereum.ts'); - -// Member APIs - -export const upsertMember = async (address: string, name: string, app2appDestination: string, - docExchangeDestination: string, sync: boolean): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/registerMember?kld-from=${address}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - name, - assetTrailInstanceID: config.assetTrailInstanceID, - app2appDestination, - docExchangeDestination - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to register new member ${name}`, err); - } -}; - - -// Asset definition APIs - -export const createAssetDefinition = async (author: string, assetDefinitionHash: string, sync: boolean): - Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createAssetDefinition?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - assetDefinitionHash - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create asset definition ${assetDefinitionHash}`, err); - } -}; - - -// Payment definition APIs - -export const createDescribedPaymentDefinition = async (paymentDefinitionID: string, name: string, author: string, - descriptionSchemaHash: string, sync: boolean): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createDescribedPaymentDefinition?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - name, - descriptionSchemaHash - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create described payment definition ${paymentDefinitionID}`, err); - } -}; - -export const createPaymentDefinition = async (paymentDefinitionID: string, name: string, author: string, sync: boolean): - Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createPaymentDefinition?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - name - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create payment definition ${paymentDefinitionID}`, err); - } -}; - - -// Asset instance APIs - -export const createDescribedAssetInstance = async (assetInstanceID: string, assetDefinitionID: string, author: string, - descriptionHash: string, contentHash: string, sync = false): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createDescribedAssetInstance?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - assetInstanceID: utils.uuidToHex(assetInstanceID), - assetDefinitionID: utils.uuidToHex(assetDefinitionID), - descriptionHash, - contentHash - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create described asset instance ${assetInstanceID}`, err); - } -}; - -export const createAssetInstance = async (assetInstanceID: string, assetDefinitionID: string, author: string, - contentHash: string, sync = false): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createAssetInstance?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - assetInstanceID: utils.uuidToHex(assetInstanceID), - assetDefinitionID: utils.uuidToHex(assetDefinitionID), - contentHash - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create asset instance ${assetInstanceID}`, err); - } -}; - -export const createAssetInstanceBatch = async (batchHash: string, author: string, sync = false): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createAssetInstanceBatch?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - batchHash, - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create asset instance batch ${batchHash}`, err); - } -}; - -export const setAssetInstanceProperty = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, value: string, - sync: boolean): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/setAssetInstanceProperty?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - assetDefinitionID: utils.uuidToHex(assetDefinitionID), - assetInstanceID: utils.uuidToHex(assetInstanceID), - key, - value - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to set asset instance property ${key} (instance=${assetInstanceID})`, err); - } -}; - - -// Payment instance APIs - -export const createDescribedPaymentInstance = async (paymentInstanceID: string, paymentDefinitionID: string, - author: string, member: string, amount: number, descriptionHash: string, sync: boolean): - Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createDescribedPaymentInstance?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - paymentInstanceID: utils.uuidToHex(paymentInstanceID), - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - member, - amount, - descriptionHash - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create described payment instance ${paymentInstanceID}`, err); - } -}; - -export const createPaymentInstance = async (paymentInstanceID: string, paymentDefinitionID: string, author: string, - member: string, amount: number, sync: boolean): Promise => { - try { - const response = await axios({ - method: 'post', - url: `${config.apiGateway.apiEndpoint}/createPaymentInstance?kld-from=${author}&kld-sync=${sync}`, - auth: { - username: config.apiGateway.auth?.user ?? config.appCredentials.user, - password: config.apiGateway.auth?.password ?? config.appCredentials.password - }, - data: { - paymentInstanceID: utils.uuidToHex(paymentInstanceID), - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - member, - amount - } - }); - return { ...response.data, type: sync ? 'sync' : 'async' }; - } catch (err) { - return handleError(`Failed to create payment instance ${paymentInstanceID}`, err); - } -}; - -function handleError(msg: string, err: any): Promise { - const errMsg = err.response?.data?.error ?? err.response.data.message ?? err.toString(); - log.error(`${msg}. ${errMsg}`); - throw new Error(msg); -} \ No newline at end of file diff --git a/kat/src/clients/ipfs.ts b/kat/src/clients/ipfs.ts deleted file mode 100644 index f504db29c0..0000000000 --- a/kat/src/clients/ipfs.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import FormData from 'form-data'; -import { Stream, Readable } from 'stream'; -import { constants } from '../lib/utils'; -import { config } from '../lib/config'; -import * as utils from '../lib/utils'; - -export const init = async () => { - try { - const response = await utils.axiosWithRetry({ - url: `${config.ipfs.apiEndpoint}/api/v0/version`, - method: 'post', - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - if (response.data.Version === undefined) { - throw 'Invalid response'; - } - } catch (err) { - throw new Error(`IPFS Connection failed. ${err}`); - } -}; - -export const downloadJSON = async (hash: string): Promise => { - const response = await utils.axiosWithRetry({ - url: `${config.ipfs.gatewayEndpoint || config.ipfs.apiEndpoint}/ipfs/${hash}`, - method: 'get', - responseType: 'json', - timeout: constants.IPFS_TIMEOUT_MS, - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - return response.data; -}; - -export const uploadString = (value: string): Promise => { - const readable = new Readable(); - readable.push(value); - readable.push(null); - return uploadStream(readable); -}; - -export const uploadStream = async (stream: Stream): Promise => { - const formData = new FormData(); - formData.append('document', stream); - const response = await utils.axiosWithRetry({ - url: `${config.ipfs.apiEndpoint}/api/v0/add`, - method: 'post', - data: formData, - headers: formData.getHeaders(), - auth: { - username: config.appCredentials.user, - password: config.appCredentials.password - } - }); - return response.data.Hash; -}; diff --git a/kat/src/handlers/asset-definitions.ts b/kat/src/handlers/asset-definitions.ts deleted file mode 100644 index c409639ab5..0000000000 --- a/kat/src/handlers/asset-definitions.ts +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Ajv from 'ajv'; -import * as utils from '../lib/utils'; -import * as ipfs from '../clients/ipfs'; -import * as apiGateway from '../clients/api-gateway'; -import * as database from '../clients/database'; -import RequestError from '../lib/request-handlers'; -import indexSchema from '../schemas/indexes.json' -import assetDefinitionSchema from '../schemas/asset-definition.json' -const log = utils.getLogger('handler/asset-definitions.ts'); - -import { - IDBBlockchainData, - IDBAssetDefinition, - IEventAssetDefinitionCreated, - IAssetDefinitionRequest, - indexes -} from '../lib/interfaces'; -import { config } from '../lib/config'; - -const ajv = new Ajv(); - -export const handleGetAssetDefinitionsRequest = (query: object, skip: number, limit: number) => { - return database.retrieveAssetDefinitions(query, skip, limit); -}; - -export const handleCountAssetDefinitionsRequest = async (query: object) => { - return { count: await database.countAssetDefinitions(query) }; -}; - -export const handleGetAssetDefinitionRequest = async (assetDefinitionID: string) => { - const assetDefinition = await database.retrieveAssetDefinitionByID(assetDefinitionID); - if (assetDefinition === null) { - throw new RequestError('Asset definition not found', 404); - } - return assetDefinition; -}; - -export const handleCreateAssetDefinitionRequest = async (assetDefinitionID: string, name: string, isContentPrivate: boolean, isContentUnique: boolean, - author: string, descriptionSchema: Object | undefined, contentSchema: Object | undefined, indexes: { fields: string[], unique?: boolean }[] | undefined, sync: boolean) => { - if (descriptionSchema !== undefined && !ajv.validateSchema(descriptionSchema)) { - throw new RequestError('Invalid description schema', 400); - } - if (contentSchema !== undefined && !ajv.validateSchema(contentSchema)) { - throw new RequestError('Invalid content schema', 400); - } - if (indexes !== undefined && !ajv.validate(indexSchema, indexes)) { - throw new RequestError('Indexes do not conform to index schema', 400); - } - if (await database.retrieveAssetDefinitionByName(name) !== null) { - throw new RequestError('Asset definition name conflict', 409); - } - const timestamp = utils.getTimestamp(); - const assetDefinition: IAssetDefinitionRequest = { - assetDefinitionID, - name, - isContentPrivate, - isContentUnique, - descriptionSchema, - contentSchema, - indexes - } - - let assetDefinitionHash: string; - let receipt: string | undefined; - - switch (config.protocol) { - case 'ethereum': - assetDefinitionHash = utils.ipfsHashToSha256(await ipfs.uploadString(JSON.stringify(assetDefinition))); - const apiGatewayResponse = await apiGateway.createAssetDefinition(author, assetDefinitionHash, sync); - if (apiGatewayResponse.type === 'async') { - receipt = apiGatewayResponse.id; - } - break; - case 'corda': - assetDefinitionHash = utils.getSha256(JSON.stringify(assetDefinition)); - await createCollection(assetDefinitionID, indexes); - break; - } - await database.upsertAssetDefinition({ - assetDefinitionID, - author, - name, - isContentPrivate, - isContentUnique, - descriptionSchema, - assetDefinitionHash, - contentSchema, - receipt, - indexes, - submitted: timestamp - }); - log.info(`New asset definition ${assetDefinitionID} from API request published to blockchain and added to local database`); - return assetDefinitionID; -}; - -export const handleAssetDefinitionCreatedEvent = async (event: IEventAssetDefinitionCreated, { blockNumber, transactionHash }: IDBBlockchainData) => { - let assetDefinition = await ipfs.downloadJSON(utils.sha256ToIPFSHash(event.assetDefinitionHash)); - if (!ajv.validate(assetDefinitionSchema, assetDefinition)) { - throw new RequestError(`Invalid asset definition content ${JSON.stringify(ajv.errors)}`, 400); - } - const dbAssetDefinitionByID = await database.retrieveAssetDefinitionByID(assetDefinition.assetDefinitionID); - if (dbAssetDefinitionByID !== null) { - if (dbAssetDefinitionByID.transactionHash !== undefined) { - throw new Error(`Asset definition ID conflict ${assetDefinition.assetDefinitionID}`); - } - } else { - const dbAssetDefinitionByName = await database.retrieveAssetDefinitionByName(assetDefinition.name); - if (dbAssetDefinitionByName !== null) { - if (dbAssetDefinitionByName.transactionHash !== undefined) { - throw new Error(`Asset definition name conflict ${dbAssetDefinitionByName.name}`); - } else { - await database.markAssetDefinitionAsConflict(dbAssetDefinitionByName.assetDefinitionID, Number(event.timestamp)); - } - } - } - - await database.upsertAssetDefinition({ - ...assetDefinition, - author: event.author, - assetDefinitionHash: event.assetDefinitionHash, - timestamp: Number(event.timestamp), - blockNumber, - transactionHash - }); - await createCollection(assetDefinition.assetDefinitionID, assetDefinition.indexes); - - log.info(`New asset definition ${assetDefinition.assetDefinitionID} from blockchain event added to local database`); -}; - -const createCollection = async (assetDefinitionID: string, assetDefinitionIndexes: indexes | undefined) => { - const collectionName = `asset-instance-${assetDefinitionID}`; - let indexes: indexes = [{ fields: ['assetInstanceID'], unique: true }, { fields: ['author'], unique: false }]; - if (assetDefinitionIndexes !== undefined) { - indexes = indexes.concat(assetDefinitionIndexes) - } - await database.createCollection(collectionName, indexes); -}; \ No newline at end of file diff --git a/kat/src/handlers/asset-instances-pinning.ts b/kat/src/handlers/asset-instances-pinning.ts deleted file mode 100644 index 873e406acb..0000000000 --- a/kat/src/handlers/asset-instances-pinning.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as apiGateway from '../clients/api-gateway'; -import * as ipfs from '../clients/ipfs'; -import { BatchManager } from '../lib/batch-manager'; -import { IAPIGatewayAsyncResponse, IAPIGatewaySyncResponse, IAssetInstance, IAssetInstancePropertySet, IBatchRecord, IDBBatch, IPinnedBatch, BatchRecordType } from '../lib/interfaces'; -import * as utils from '../lib/utils'; - -const log = utils.getLogger('lib/asset-instance-pinning.ts'); - -export class AssetInstancesPinning { - - private batchManager = new BatchManager('asset-instances', this.processBatch.bind(this)); - - public async init() { - await this.batchManager.init(); - } - - public async pin(instance: IAssetInstance): Promise { - const pinnedInstance: IBatchRecord = { recordType: BatchRecordType.assetInstance, ...instance }; - if (instance.isContentPrivate) delete pinnedInstance.content; - const batchID = await this.batchManager.getProcessor(instance.author).add(pinnedInstance); - log.trace(`Pinning initiated for asset ${instance.assetInstanceID}/${instance.assetInstanceID} in batch ${batchID}`); - return batchID; - } - - public async pinProperty(property: IAssetInstancePropertySet): Promise { - const pinnedProperty: IBatchRecord = { recordType: BatchRecordType.assetProperty, ...property }; - const batchID = await this.batchManager.getProcessor(property.author).add(pinnedProperty); - log.trace(`Pinning initiated for property ${property.assetInstanceID}/${property.assetInstanceID}/${property.key} in batch ${batchID}`); - return batchID; - } - - private async processBatch(batch: IDBBatch) { - // Extract the hashable portion, and write it to IPFS, and store the hash - const pinnedBatch: IPinnedBatch = { - type: batch.type, - created: batch.created, - author: batch.author, - completed: batch.completed, - batchID: batch.batchID, - records: batch.records, - }; - batch.batchHash = utils.ipfsHashToSha256(await ipfs.uploadString(JSON.stringify(pinnedBatch)));; - - let apiGatewayResponse: IAPIGatewayAsyncResponse | IAPIGatewaySyncResponse; - apiGatewayResponse = await apiGateway.createAssetInstanceBatch(batch.batchHash, batch.author, batch.participants); - batch.receipt = apiGatewayResponse.type === 'async' ? apiGatewayResponse.id : undefined; - - // The batch processor who called us does the store back to the local MongoDB, as part of completing the batch - } - -} - -/** - * Singleton instance - */ -export const assetInstancesPinning = new AssetInstancesPinning(); diff --git a/kat/src/handlers/asset-instances.ts b/kat/src/handlers/asset-instances.ts deleted file mode 100644 index 5250599793..0000000000 --- a/kat/src/handlers/asset-instances.ts +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Ajv from 'ajv'; -import { v4 as uuidV4 } from 'uuid'; -import * as apiGateway from '../clients/api-gateway'; -import * as app2app from '../clients/app2app'; -import * as database from '../clients/database'; -import * as docExchange from '../clients/doc-exchange'; -import * as ipfs from '../clients/ipfs'; -import { config } from '../lib/config'; -import { IAPIGatewayAsyncResponse, IAPIGatewaySyncResponse, IAssetInstance, IAssetInstancePropertySet, IAssetTradePrivateAssetInstancePush, IBatchRecord, IDBAssetInstance, IDBBlockchainData, IEventAssetInstanceBatchCreated, IEventAssetInstanceCreated, IEventAssetInstancePropertySet, IPendingAssetInstancePrivateContentDelivery, BatchRecordType } from '../lib/interfaces'; -import RequestError from '../lib/request-handlers'; -import * as utils from '../lib/utils'; -import { assetInstancesPinning } from './asset-instances-pinning'; -import * as assetTrade from './asset-trade'; - - -const log = utils.getLogger('handlers/asset-instances.ts'); - -const ajv = new Ajv(); - -export let pendingAssetInstancePrivateContentDeliveries: { [assetInstanceID: string]: IPendingAssetInstancePrivateContentDelivery } = {}; - -export const handleGetAssetInstancesRequest = (assetDefinitionID: string, query: object, sort: object, skip: number, limit: number) => { - return database.retrieveAssetInstances(assetDefinitionID, query, sort, skip, limit); -}; - -export const handleCountAssetInstancesRequest = async (assetDefinitionID: string, query: object) => { - return { count: await database.countAssetInstances(assetDefinitionID, query) }; -}; - -export const handleGetAssetInstanceRequest = async (assetDefinitionID: string, assetInstanceID: string, content: boolean) => { - const assetInstance = await database.retrieveAssetInstanceByID(assetDefinitionID, assetInstanceID); - if (assetInstance === null) { - throw new RequestError('Asset instance not found', 404); - } - const assetDefinition = await database.retrieveAssetDefinitionByID(assetDefinitionID); - if (assetDefinition === null) { - throw new RequestError('Asset definition not found', 500); - } - if (content) { - if (assetDefinition.contentSchema) { - return assetInstance.content; - } else { - try { - return await docExchange.downloadStream(utils.getUnstructuredFilePathInDocExchange(assetInstance.assetInstanceID)); - } catch (err) { - if (err.response?.status === 404) { - throw new RequestError('Asset instance content not present in off-chain storage', 404); - } else { - throw new RequestError(`Failed to obtain asset content from off-chain storage. ${err}`, 500); - } - } - } - } - return assetInstance; -}; - -export const handleCreateStructuredAssetInstanceRequest = async (author: string, assetDefinitionID: string, description: Object | undefined, content: Object, isContentPrivate: boolean | undefined, participants: string[] | undefined, sync: boolean) => { - let descriptionHash: string | undefined; - let contentHash: string; - const assetDefinition = await database.retrieveAssetDefinitionByID(assetDefinitionID); - if (assetDefinition === null) { - throw new RequestError('Unknown asset definition', 400); - } - if (assetDefinition.conflict === true) { - throw new RequestError('Cannot instantiate assets of conflicted definition', 400); - } - // For ethereum, we need to make assert definition transaction is mined - if (config.protocol === 'ethereum' && assetDefinition.transactionHash === undefined) { - throw new RequestError('Asset definition transaction must be mined', 400); - } - if (!assetDefinition.contentSchema) { - throw new RequestError('Unstructured asset instances must be created using multipart/form-data', 400); - } - if (assetDefinition.descriptionSchema) { - if (!description) { - throw new RequestError('Missing asset description', 400); - } - if (!ajv.validate(assetDefinition.descriptionSchema, description)) { - throw new RequestError('Description does not conform to asset definition schema', 400); - } - descriptionHash = `0x${utils.getSha256(JSON.stringify(description))}`; - } - if (!ajv.validate(assetDefinition.contentSchema, content)) { - throw new RequestError('Content does not conform to asset definition schema', 400); - } - if(isContentPrivate === undefined) { - isContentPrivate = assetDefinition.isContentPrivate; - } - contentHash = `0x${utils.getSha256(JSON.stringify(content))}`; - if (assetDefinition.isContentUnique && (await database.retrieveAssetInstanceByDefinitionIDAndContentHash(assetDefinition.assetDefinitionID, contentHash)) !== null) { - throw new RequestError(`Asset instance content conflict`); - } - if (config.protocol === 'corda') { - // validate participants are registered members - if (participants !== undefined) { - for (const participant of participants) { - if (await database.retrieveMemberByAddress(participant) === null) { - throw new RequestError('One or more participants are not registered', 400); - } - } - } else { - throw new RequestError('Missing asset participants', 400); - } - } - const assetInstanceID = uuidV4(); - const timestamp = utils.getTimestamp(); - const assetInstance: IAssetInstance = { - assetInstanceID, - author, - assetDefinitionID, - descriptionHash, - description, - contentHash, - content, - isContentPrivate - }; - - let dbAssetInstance: IDBAssetInstance = assetInstance; - dbAssetInstance.submitted = timestamp; - if (config.protocol === 'corda') { - dbAssetInstance.participants = participants; - } - // If there are public IPFS shared parts of this instance, we can batch it together with all other - // assets we are publishing for performance. Reducing both the data we write to the blockchain, and - // most importantly the number of IPFS transactions. - // Curently we do batch only for ethereum - if ((assetDefinition.descriptionSchema || !isContentPrivate) && config.protocol === 'ethereum') { - dbAssetInstance.batchID = await assetInstancesPinning.pin(assetInstance); - await database.upsertAssetInstance(dbAssetInstance); - log.info(`Structured asset instance batch ${dbAssetInstance.batchID} saved in local database and pinned to blockchain`); - } else { - await database.upsertAssetInstance(dbAssetInstance); - // One-for-one blockchain transactions to instances - let apiGatewayResponse: IAPIGatewayAsyncResponse | IAPIGatewaySyncResponse; - if (descriptionHash) { - apiGatewayResponse = await apiGateway.createDescribedAssetInstance(assetInstanceID, assetDefinitionID, author, descriptionHash, contentHash, participants, sync); - } else { - apiGatewayResponse = await apiGateway.createAssetInstance(assetInstanceID, assetDefinitionID, author, contentHash, participants, sync); - } - log.info(`Structured asset instance ${assetInstanceID} saved in local database and pinning transaction submitted to the blockchain`); - // dbAssetInstance.receipt = apiGatewayResponse.type === 'async' ? apiGatewayResponse.id : undefined; - if(apiGatewayResponse.type === 'async') { - await database.setAssetInstanceReceipt(assetDefinitionID, assetInstanceID, apiGatewayResponse.id); - log.trace(`Structured asset instance ${assetInstanceID} published in the blockchain (gateway receipt=${apiGatewayResponse.id})`); - } - } - return assetInstanceID; -}; - -export const handleCreateUnstructuredAssetInstanceRequest = async (author: string, assetDefinitionID: string, description: Object | undefined, content: NodeJS.ReadableStream, filename: string, isContentPrivate: boolean | undefined, participants: string[] | undefined, sync: boolean) => { - let descriptionHash: string | undefined; - let contentHash: string; - const assetDefinition = await database.retrieveAssetDefinitionByID(assetDefinitionID); - if (assetDefinition === null) { - throw new RequestError('Unknown asset definition', 400); - } - if (assetDefinition.contentSchema) { - throw new RequestError('Structured asset instances must be created using JSON', 400); - } - if (assetDefinition.descriptionSchema) { - if (!ajv.validate(assetDefinition.descriptionSchema, description)) { - throw new RequestError('Description does not conform to asset definition schema', 400); - } - descriptionHash = utils.ipfsHashToSha256(await ipfs.uploadString(JSON.stringify(description))); - } - if(isContentPrivate === undefined) { - isContentPrivate = assetDefinition.isContentPrivate; - } - const assetInstanceID = uuidV4(); - if (assetDefinition.isContentPrivate) { - contentHash = `0x${await docExchange.uploadStream(content, utils.getUnstructuredFilePathInDocExchange(assetInstanceID))}`; - } else { - contentHash = utils.ipfsHashToSha256(await ipfs.uploadString(JSON.stringify(content))); - } - if (assetDefinition.isContentUnique && (await database.retrieveAssetInstanceByDefinitionIDAndContentHash(assetDefinitionID, contentHash)) !== null) { - throw new RequestError('Asset instance content conflict', 409); - } - if (config.protocol === 'corda') { - // validate participants are registered - if (participants) { - for (const participant of participants) { - if (await database.retrieveMemberByAddress(participant) === null) { - throw new RequestError(`One or more participants are not registered`, 400); - } - } - } else { - throw new RequestError(`Missing asset participants`, 400); - } - } - let apiGatewayResponse: IAPIGatewayAsyncResponse | IAPIGatewaySyncResponse; - const timestamp = utils.getTimestamp(); - await database.upsertAssetInstance({ - assetInstanceID, - author, - assetDefinitionID, - descriptionHash, - description, - contentHash, - filename, - isContentPrivate, - participants, - submitted: timestamp - }); - if (descriptionHash) { - apiGatewayResponse = await apiGateway.createDescribedAssetInstance(assetInstanceID, assetDefinitionID, author, descriptionHash, contentHash, participants, sync); - } else { - apiGatewayResponse = await apiGateway.createAssetInstance(assetInstanceID, assetDefinitionID, author, contentHash, participants, sync); - } - log.info(`Unstructured asset instance ${assetInstanceID} saved in local database and pinning transaction submitted to the blockchain`); - if(apiGatewayResponse.type === 'async') { - await database.setAssetInstanceReceipt(assetDefinitionID, assetInstanceID, apiGatewayResponse.id); - log.trace(`Unstructured asset instance ${assetInstanceID} published in the blockchain (gateway receipt=${apiGatewayResponse.id})`); - } - return assetInstanceID; -} - -export const handleSetAssetInstancePropertyRequest = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, value: string, sync: boolean) => { - const assetInstance = await database.retrieveAssetInstanceByID(assetDefinitionID, assetInstanceID); - if (assetInstance === null) { - throw new RequestError('Unknown asset instance', 400); - } - if (assetInstance.transactionHash === undefined) { - throw new RequestError('Asset instance transaction must be mined', 400); - } - if (assetInstance.properties) { - const authorMetadata = assetInstance.properties[author]; - if (authorMetadata) { - const valueData = authorMetadata[key]; - if (valueData?.value === value && valueData.history !== undefined) { - const keys = Object.keys(valueData.history); - const lastConfirmedValue = valueData.history[keys[keys.length - 1]]; - if (lastConfirmedValue.value === value) { - throw new RequestError('Property already set', 409); - } - } - } - } - const submitted = utils.getTimestamp(); - if (config.protocol === 'ethereum') { - const property: IAssetInstancePropertySet = { - assetDefinitionID, - assetInstanceID, - author, - key, - value, - }; - const batchID = await assetInstancesPinning.pinProperty(property); - await database.setSubmittedAssetInstanceProperty(assetDefinitionID, assetInstanceID, author, key, value, submitted, batchID); - log.info(`Asset instance property ${key} (instance=${assetInstanceID}) set via batch`); - } else { - await database.setSubmittedAssetInstanceProperty(assetDefinitionID, assetInstanceID, author, key, value, submitted); - log.info(`Asset instance property ${key} (instance=${assetInstanceID}) set in local database`); - const apiGatewayResponse = await apiGateway.setAssetInstanceProperty(assetDefinitionID, assetInstanceID, author, key, value, assetInstance.participants, sync); - if(apiGatewayResponse.type === 'async') { - await database.setAssetInstancePropertyReceipt(assetDefinitionID, assetInstanceID, author, key, apiGatewayResponse.id); - } - log.info(`Asset instance property ${key} (instance=${assetInstanceID}) pinning transaction submitted to blockchain`); - } - -}; - -export const handleAssetInstanceBatchCreatedEvent = async (event: IEventAssetInstanceBatchCreated, { blockNumber, transactionHash }: IDBBlockchainData) => { - - let batch = await database.retrieveBatchByHash(event.batchHash); - if (!batch) { - batch = await ipfs.downloadJSON(utils.sha256ToIPFSHash(event.batchHash)); - } - if (!batch) { - throw new Error('Unknown batch hash: ' + event.batchHash); - } - - // Process each record within the batch, as if it is an individual event - const records: IBatchRecord[] = batch.records || []; - for (let record of records) { - if (!record.recordType || record.recordType === BatchRecordType.assetInstance) { - const recordEvent: IEventAssetInstanceCreated = { - assetDefinitionID: '', - assetInstanceID: '', - author: record.author, - contentHash: record.contentHash!, - descriptionHash: record.descriptionHash!, - timestamp: event.timestamp, - isContentPrivate: record.isContentPrivate - }; - try { - await handleAssetInstanceCreatedEvent(recordEvent, { blockNumber, transactionHash }, record); - } catch (err) { - // We failed to process this record, but continue to attempt the other records in the batch - log.error(`Record ${record.assetDefinitionID}/${record.assetInstanceID} in batch ${batch.batchID} with hash ${event.batchHash} failed`, err.stack); - } - } else if (record.recordType === BatchRecordType.assetProperty) { - try { - const propertyEvent: IEventAssetInstancePropertySet = { - assetDefinitionID: record.assetDefinitionID, - assetInstanceID: record.assetInstanceID, - author: record.author, - key: record.key, - value: record.value, - timestamp: event.timestamp, - }; - await handleSetAssetInstancePropertyEvent(propertyEvent, { blockNumber, transactionHash }, true); - } catch (err) { - // We failed to process this record, but continue to attempt the other records in the batch - log.error(`Property ${record.assetDefinitionID}/${record.assetInstanceID}/${record.key} in batch ${batch.batchID} with hash ${event.batchHash} failed`, err.stack); - } - } else { - log.error(`Batch record type '${record.recordType}' not known`, record); - } - } - - // Write the batch itself to our local database - await database.upsertBatch({ - ...batch, - timestamp: Number(event.timestamp), - blockNumber, - transactionHash - }); - log.info(`Asset instance batch ${event.batchHash} from blockchain event (blockNumber=${blockNumber} hash=${transactionHash}) saved in local database`); - -} - -export const handleAssetInstanceCreatedEvent = async (event: IEventAssetInstanceCreated, { blockNumber, transactionHash }: IDBBlockchainData, batchInstance?: IBatchRecord) => { - let eventAssetInstanceID: string; - let eventAssetDefinitionID: string; - if (batchInstance === undefined) { - switch (config.protocol) { - case 'corda': - eventAssetInstanceID = event.assetInstanceID; - eventAssetDefinitionID = event.assetDefinitionID; - break; - case 'ethereum': - eventAssetInstanceID = utils.hexToUuid(event.assetInstanceID); - eventAssetDefinitionID = utils.hexToUuid(event.assetDefinitionID); - break; - } - } else { - eventAssetInstanceID = batchInstance.assetInstanceID; - eventAssetDefinitionID = batchInstance.assetDefinitionID; - log.info(`batch instance ${eventAssetDefinitionID}:${eventAssetInstanceID}`); - } - const dbAssetInstance = await database.retrieveAssetInstanceByID(eventAssetDefinitionID, eventAssetInstanceID); - if (dbAssetInstance !== null && dbAssetInstance.transactionHash !== undefined) { - throw new Error(`Duplicate asset instance ID`); - } - const assetDefinition = await database.retrieveAssetDefinitionByID(eventAssetDefinitionID); - if (assetDefinition === null) { - throw new Error('Unkown asset definition'); - } - // For ethereum, we need to make asset definition transaction is mined - if (config.protocol === 'ethereum' && assetDefinition.transactionHash === undefined) { - throw new Error('Asset definition transaction must be mined'); - } - if (assetDefinition.isContentUnique) { - const assetInstanceByContentID = await database.retrieveAssetInstanceByDefinitionIDAndContentHash(eventAssetDefinitionID, event.contentHash); - if (assetInstanceByContentID !== null && eventAssetInstanceID !== assetInstanceByContentID.assetInstanceID) { - if (assetInstanceByContentID.transactionHash !== undefined) { - throw new Error(`Asset instance content conflict ${event.contentHash}`); - } else { - await database.markAssetInstanceAsConflict(eventAssetDefinitionID, assetInstanceByContentID.assetInstanceID, Number(event.timestamp)); - } - } - } - let description: Object | undefined = batchInstance?.description; - if (assetDefinition.descriptionSchema && !description) { - if (event.descriptionHash) { - if (event.descriptionHash === dbAssetInstance?.descriptionHash) { - description = dbAssetInstance.description; - } else { - description = await ipfs.downloadJSON(utils.sha256ToIPFSHash(event.descriptionHash)); - if (!ajv.validate(assetDefinition.descriptionSchema, description)) { - throw new Error('Description does not conform to schema'); - } - } - } else { - throw new Error('Missing asset instance description'); - } - } - let content: Object | undefined = batchInstance?.content; - if (assetDefinition.contentSchema && !content) { - if (event.contentHash === dbAssetInstance?.contentHash) { - content = dbAssetInstance.content; - } else if (!assetDefinition.isContentPrivate) { - content = await ipfs.downloadJSON(utils.sha256ToIPFSHash(event.contentHash)); - if (!ajv.validate(assetDefinition.contentSchema, content)) { - throw new Error('Content does not conform to schema'); - } - } - } - log.trace(`Updating asset instance ${eventAssetInstanceID} with blockchain pinned info blockNumber=${blockNumber} hash=${transactionHash}`); - let assetInstanceDB: IDBAssetInstance = { - assetInstanceID: eventAssetInstanceID, - author: event.author, - assetDefinitionID: assetDefinition.assetDefinitionID, - descriptionHash: event.descriptionHash, - description, - contentHash: event.contentHash, - timestamp: Number(event.timestamp), - content, - blockNumber, - transactionHash, - isContentPrivate: event.isContentPrivate ?? assetDefinition.isContentPrivate - }; - if (config.protocol === 'corda') { - assetInstanceDB.participants = event.participants; - } - await database.upsertAssetInstance(assetInstanceDB); - if (assetInstanceDB.isContentPrivate) { - const privateData = pendingAssetInstancePrivateContentDeliveries[eventAssetInstanceID]; - if (privateData !== undefined) { - const author = await database.retrieveMemberByAddress(event.author); - if (author === null) { - throw new Error('Pending private data author unknown'); - } - if (author.app2appDestination !== privateData.fromDestination) { - throw new Error('Pending private data destination mismatch'); - } - if (privateData.content !== undefined) { - const privateDataHash = `0x${utils.getSha256(JSON.stringify(privateData.content))}`; - if (privateDataHash !== event.contentHash) { - throw new Error('Pending private data content hash mismatch'); - } - } - await database.setAssetInstancePrivateContent(eventAssetDefinitionID, eventAssetInstanceID, privateData.content, privateData.filename); - delete pendingAssetInstancePrivateContentDeliveries[eventAssetInstanceID]; - } - } - log.info(`Asset instance ${eventAssetDefinitionID}/${eventAssetInstanceID} from blockchain event (blockNumber=${blockNumber} hash=${transactionHash}) saved in local database`); -}; - -export const handleSetAssetInstancePropertyEvent = async (event: IEventAssetInstancePropertySet, blockchainData: IDBBlockchainData, isBatch?: boolean) => { - let eventAssetInstanceID: string; - let eventAssetDefinitionID: string; - if (config.protocol === 'corda' || isBatch) { - eventAssetInstanceID = event.assetInstanceID; - eventAssetDefinitionID = event.assetDefinitionID; - } else { - eventAssetInstanceID = utils.hexToUuid(event.assetInstanceID); - eventAssetDefinitionID = utils.hexToUuid(event.assetDefinitionID); - } - const dbAssetInstance = await database.retrieveAssetInstanceByID(eventAssetDefinitionID, eventAssetInstanceID); - if (dbAssetInstance === null) { - throw new Error('Uknown asset instance'); - } - if (dbAssetInstance.transactionHash === undefined) { - throw new Error('Unconfirmed asset instance'); - } - if (!event.key) { - throw new Error('Invalid property key'); - } - await database.setConfirmedAssetInstanceProperty(eventAssetDefinitionID, eventAssetInstanceID, event.author, event.key, event.value, Number(event.timestamp), blockchainData); - log.info(`Asset instance property ${event.key} (instance=${eventAssetDefinitionID}) from blockchain event (blockNumber=${blockchainData.blockNumber} hash=${blockchainData.transactionHash}) saved in local database`); -}; - -export const handleAssetInstanceTradeRequest = async (assetDefinitionID: string, requesterAddress: string, assetInstanceID: string, metadata: object | undefined) => { - const assetInstance = await database.retrieveAssetInstanceByID(assetDefinitionID, assetInstanceID); - if (assetInstance === null) { - throw new RequestError('Uknown asset instance', 404); - } - const author = await database.retrieveMemberByAddress(assetInstance.author); - if (author === null) { - throw new RequestError('Asset author must be registered', 400); - } - if (author.assetTrailInstanceID === config.assetTrailInstanceID) { - throw new RequestError('Asset instance authored', 400); - } - const assetDefinition = await database.retrieveAssetDefinitionByID(assetDefinitionID); - if (assetDefinition === null) { - throw new RequestError('Unknown asset definition', 500); - } - if (assetDefinition.contentSchema !== undefined) { - if (assetInstance.content !== undefined) { - throw new RequestError('Asset content already available', 400); - } - } else { - try { - const documentDetails = await docExchange.getDocumentDetails(utils.getUnstructuredFilePathInDocExchange(assetInstanceID)); - if (documentDetails.hash === assetInstance.contentHash) { - throw new RequestError('Asset content already available', 400); - } - } catch (err) { - if (err.response?.status !== 404) { - throw new RequestError(err, 500); - } - } - } - const requester = await database.retrieveMemberByAddress(requesterAddress); - if (requester === null) { - throw new RequestError('Requester must be registered', 400); - } - await assetTrade.coordinateAssetTrade(assetInstance, assetDefinition, requester.address, metadata, author.app2appDestination); - log.info(`Asset instance trade request from requester ${requesterAddress} (instance=${assetInstanceID}) successfully completed`); -}; - -export const handlePushPrivateAssetInstanceRequest = async (assetDefinitionID: string, assetInstanceID: string, memberAddress: string) => { - const member = await database.retrieveMemberByAddress(memberAddress); - if (member === null) { - throw new RequestError('Unknown member', 400); - } - const assetInstance = await database.retrieveAssetInstanceByID(assetDefinitionID, assetInstanceID); - if (assetInstance === null) { - throw new RequestError('Unknown asset instance', 400); - } - const author = await database.retrieveMemberByAddress(assetInstance.author); - if (author === null) { - throw new RequestError('Unknown asset author', 500); - } - if (author.assetTrailInstanceID !== config.assetTrailInstanceID) { - throw new RequestError('Must be asset instance author', 403); - } - const assetDefinition = await database.retrieveAssetDefinitionByID(assetInstance.assetDefinitionID); - if (assetDefinition === null) { - throw new RequestError('Unknown asset definition', 500); - } - let privateAssetTradePrivateInstancePush: IAssetTradePrivateAssetInstancePush = { - type: 'private-asset-instance-push', - assetInstanceID, - assetDefinitionID - }; - if (assetDefinition.contentSchema !== undefined) { - privateAssetTradePrivateInstancePush.content = assetInstance.content; - } else { - await docExchange.transfer(author.docExchangeDestination, member.docExchangeDestination, - utils.getUnstructuredFilePathInDocExchange(assetInstanceID)); - privateAssetTradePrivateInstancePush.filename = assetInstance.filename; - log.info(`Private asset instance push request for member ${memberAddress} (instance=${assetInstanceID}) successfully completed`); - } - app2app.dispatchMessage(member.app2appDestination, privateAssetTradePrivateInstancePush); -}; diff --git a/kat/src/handlers/asset-trade.ts b/kat/src/handlers/asset-trade.ts deleted file mode 100644 index f8a266899f..0000000000 --- a/kat/src/handlers/asset-trade.ts +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { v4 as uuidV4 } from 'uuid'; -import Ajv from 'ajv'; -import { config } from '../lib/config'; -import { AssetTradeMessage, IApp2AppMessageHeader, IApp2AppMessageListener, IAssetTradePrivateAssetInstanceAuthorizationRequest, IAssetTradePrivateAssetInstancePush, IAssetTradePrivateAssetInstanceRequest, IAssetTradePrivateAssetInstanceResponse, IDBAssetDefinition, IDBAssetInstance, IDBMember, IDocExchangeListener, IDocExchangeTransferData } from "../lib/interfaces"; -import * as utils from '../lib/utils'; -import * as database from '../clients/database'; -import * as app2app from '../clients/app2app'; -import * as docExchange from '../clients/doc-exchange'; -import { pendingAssetInstancePrivateContentDeliveries } from './asset-instances'; -const log = utils.getLogger('handlers/asset-trade.ts'); - -const ajv = new Ajv(); - -export const assetTradeHandler = (headers: IApp2AppMessageHeader, content: AssetTradeMessage) => { - if (content.type === 'private-asset-instance-request') { - processPrivateAssetInstanceRequest(headers, content); - } else if (content.type === 'private-asset-instance-push') { - processPrivateAssetInstancePush(headers, content); - } -}; - -const processPrivateAssetInstanceRequest = async (headers: IApp2AppMessageHeader, request: IAssetTradePrivateAssetInstanceRequest) => { - let tradeResponse: IAssetTradePrivateAssetInstanceResponse = { - type: "private-asset-instance-response", - tradeID: request.tradeID, - assetInstanceID: request.assetInstanceID - }; - const requester = await database.retrieveMemberByAddress(request.requester.address); - try { - if (requester === null) { - throw new Error(`Unknown requester ${request.requester.address}`); - } - if (requester.assetTrailInstanceID !== request.requester.assetTrailInstanceID) { - throw new Error(`Requester asset trail instance mismatch. Expected ${requester.assetTrailInstanceID}, ` + - `actual ${request.requester.assetTrailInstanceID}`); - } - if (requester.app2appDestination !== headers.from) { - throw new Error(`Requester App2App destination mismatch. Expected ${requester.app2appDestination}, ` + - `actual ${headers.from}`); - } - const assetInstance = await database.retrieveAssetInstanceByID(request.assetDefinitionID, request.assetInstanceID); - if (assetInstance === null) { - throw new Error(`Unknown asset instance ${request.assetInstanceID}`); - } - const author = await database.retrieveMemberByAddress(assetInstance.author); - if (author === null) { - throw new Error(`Unknown asset instance author`); - } - if (author.assetTrailInstanceID !== config.assetTrailInstanceID) { - throw new Error(`Asset instance ${assetInstance.assetInstanceID} not authored`); - } - const assetDefinition = await database.retrieveAssetDefinitionByID(assetInstance.assetDefinitionID); - if (assetDefinition === null) { - throw new Error(`Unknown asset definition ${assetInstance.assetDefinitionID}`); - } - if (!assetDefinition.isContentPrivate) { - throw new Error(`Asset instance ${assetInstance.assetInstanceID} not private`); - } - const authorized = await handlePrivateAssetInstanceAuthorization(assetInstance, requester, request.metadata); - if (authorized !== true) { - throw new Error('Access denied'); - } - if (assetDefinition.contentSchema) { - tradeResponse.content = assetInstance.content; - } else { - await docExchange.transfer(config.docExchange.destination, requester.docExchangeDestination, - utils.getUnstructuredFilePathInDocExchange(request.assetInstanceID)); - tradeResponse.filename = assetInstance.filename; - log.info(`Private asset instance trade request (instance=${assetInstance.assetDefinitionID}, requester=${request.requester.address}, tradeId=${request.tradeID}) successfully completed`); - } - } catch (err) { - tradeResponse.rejection = err.message; - } finally { - app2app.dispatchMessage(headers.from, tradeResponse); - } -}; - -const handlePrivateAssetInstanceAuthorization = (assetInstance: IDBAssetInstance, requester: IDBMember, metadata: object | undefined): Promise => { - return new Promise((resolve, reject) => { - const authorizationID = uuidV4(); - const authorizationRequest: IAssetTradePrivateAssetInstanceAuthorizationRequest = { - type: 'private-asset-instance-authorization-request', - authorizationID, - assetInstance, - requester, - metadata - }; - const timeout = setTimeout(() => { - app2app.removeListener(app2appListener); - reject(new Error('Asset instance authorization response timed out')); - }, utils.constants.TRADE_AUTHORIZATION_TIMEOUT_SECONDS * 1000); - const app2appListener: IApp2AppMessageListener = (headers: IApp2AppMessageHeader, content: AssetTradeMessage) => { - if (headers.from === config.app2app.destinations.client && content.type === 'private-asset-instance-authorization-response' && - content.authorizationID === authorizationID) { - clearTimeout(timeout); - app2app.removeListener(app2appListener); - resolve(content.authorized); - } - }; - app2app.addListener(app2appListener); - app2app.dispatchMessage(config.app2app.destinations.client, authorizationRequest); - }); -}; - -export const coordinateAssetTrade = async (assetInstance: IDBAssetInstance, assetDefinition: IDBAssetDefinition, - requesterAddress: string, metadata: object | undefined, authorDestination: string) => { - const tradeID = uuidV4(); - const tradeRequest: IAssetTradePrivateAssetInstanceRequest = { - type: 'private-asset-instance-request', - tradeID, - assetInstanceID: assetInstance.assetInstanceID, - assetDefinitionID: assetInstance.assetDefinitionID, - requester: { - assetTrailInstanceID: config.assetTrailInstanceID, - address: requesterAddress - }, - metadata - }; - const docExchangePromise = assetDefinition.contentSchema === undefined ? getDocumentExchangePromise(assetInstance.assetInstanceID) : Promise.resolve(); - const app2appPromise: Promise = new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - app2app.removeListener(app2appListener); - reject(new Error('Asset instance author response timed out')); - }, utils.constants.ASSET_INSTANCE_TRADE_TIMEOUT_SECONDS * 1000); - const app2appListener: IApp2AppMessageListener = (_headers: IApp2AppMessageHeader, content: AssetTradeMessage) => { - if (content.type === 'private-asset-instance-response' && content.tradeID === tradeID) { - clearTimeout(timeout); - app2app.removeListener(app2appListener); - if (content.rejection) { - reject(new Error(`Asset instance request rejected. ${content.rejection}`)); - } else { - const contentHash = `0x${utils.getSha256(JSON.stringify(content.content))}`; - if (contentHash !== assetInstance.contentHash) { - reject(new Error('Asset instance content hash mismatch')); - } else if (assetDefinition.contentSchema && !ajv.validate(assetDefinition.contentSchema, content.content)) { - reject(new Error('Asset instance content does not conform to schema')); - } else { - database.setAssetInstancePrivateContent(assetInstance.assetDefinitionID, content.assetInstanceID, content.content, content.filename); - resolve(); - } - } - } - }; - app2app.addListener(app2appListener); - app2app.dispatchMessage(authorDestination, tradeRequest); - }); - await Promise.all([app2appPromise, docExchangePromise]); -}; - -const getDocumentExchangePromise = (assetInstanceID: string): Promise => { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - docExchange.removeListener(docExchangeListener); - reject(new Error('Off-chain asset transfer timeout')); - }, utils.constants.DOCUMENT_EXCHANGE_TRANSFER_TIMEOUT_SECONDS * 1000); - const docExchangeListener: IDocExchangeListener = (event: IDocExchangeTransferData) => { - if (event.document === utils.getUnstructuredFilePathInDocExchange(assetInstanceID)) { - clearTimeout(timeout); - docExchange.removeListener(docExchangeListener); - resolve(); - } - }; - docExchange.addListener(docExchangeListener); - }); -}; - -const processPrivateAssetInstancePush = async (headers: IApp2AppMessageHeader, push: IAssetTradePrivateAssetInstancePush) => { - log.trace(`Handling private asset instance push event (instance=${push.assetInstanceID}, filename=${push.filename})`); - const assetInstance = await database.retrieveAssetInstanceByID(push.assetDefinitionID, push.assetInstanceID); - if (assetInstance !== null) { - log.trace(`Found existing asset instance, ${JSON.stringify(assetInstance, null, 2)}`); - const author = await database.retrieveMemberByAddress(assetInstance.author); - if (author === null) { - throw new Error(`Unknown author for asset ${assetInstance.assetInstanceID}`); - } - if (author.app2appDestination !== headers.from) { - throw new Error(`Asset instance author destination mismatch ${author.app2appDestination} - ${headers.from}`); - } - if (push.content) { - const contentHash = `0x${utils.getSha256(JSON.stringify(push.content))}`; - if (assetInstance.contentHash !== contentHash) { - throw new Error('Private asset content hash mismatch'); - } - } - await database.setAssetInstancePrivateContent(push.assetDefinitionID, push.assetInstanceID, push.content, push.filename); - log.info(`Private asset instance from push event (instance=${push.assetInstanceID}, filename=${push.filename}) saved in local database`); - } else { - log.info(`Private asset instance ${push.assetDefinitionID}/${push.assetInstanceID} from push event not found in local database, adding to pending instances`); - pendingAssetInstancePrivateContentDeliveries[push.assetInstanceID] = { ...push, fromDestination: headers.from }; - } -} diff --git a/kat/src/handlers/batches.ts b/kat/src/handlers/batches.ts deleted file mode 100644 index 74170c5e8e..0000000000 --- a/kat/src/handlers/batches.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as database from '../clients/database'; -import RequestError from '../lib/request-handlers'; - -export const handleGetBatchRequest = async (batchID: string) => { - const batch = await database.retrieveBatchByID(batchID); - if (batch === null) { - throw new RequestError('Asset instance not found', 404); - } - return batch; -}; diff --git a/kat/src/handlers/client-events.ts b/kat/src/handlers/client-events.ts deleted file mode 100644 index 409db2f647..0000000000 --- a/kat/src/handlers/client-events.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { ClientEventType } from "../lib/interfaces"; -import { config } from '../lib/config'; -import { settings } from '../lib/settings'; -import * as utils from '../lib/utils'; -import * as app2app from '../clients/app2app'; - -const log = utils.getLogger('handlers/client-events.ts'); - -export const clientEventHandler = (eventType: ClientEventType, content: object) => { - if (settings.clientEvents.includes(eventType)) { - log.info(`Dispatched client event ${eventType}`); - app2app.dispatchMessage(config.app2app.destinations.client, { type: eventType, content }); - } -}; diff --git a/kat/src/handlers/members.ts b/kat/src/handlers/members.ts deleted file mode 100644 index 901b6a0dad..0000000000 --- a/kat/src/handlers/members.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as database from '../clients/database'; -import * as apiGateway from '../clients/api-gateway'; -import * as utils from '../lib/utils'; -import { IDBBlockchainData, IDBMember, IEventMemberRegistered } from '../lib/interfaces'; -import RequestError from '../lib/request-handlers'; -import { config } from '../lib/config'; - -export const handleGetMembersRequest = (query: object, skip: number, limit: number) => { - return database.retrieveMembers(query, skip, limit); -}; - -export const handleGetMemberRequest = async (address: string) => { - const member = await database.retrieveMemberByAddress(address); - if (member === null) { - throw new RequestError('Member not found', 404); - } - return member; -}; - -export const handleUpsertMemberRequest = async (address: string, name: string, assetTrailInstanceID: string, app2appDestination: string, docExchangeDestination: string, sync: boolean) => { - const timestamp = utils.getTimestamp(); - let memberDB: IDBMember = { - address, - name, - assetTrailInstanceID, - app2appDestination, - docExchangeDestination, - submitted: timestamp - }; - if(config.protocol === 'ethereum') { - const apiGatewayResponse = await apiGateway.upsertMember(address, name, app2appDestination, docExchangeDestination, sync); - if(apiGatewayResponse.type === 'async') { - memberDB.receipt = apiGatewayResponse.id - } - } - await database.upsertMember(memberDB); -}; - -export const handleMemberRegisteredEvent = async ({ member, name, assetTrailInstanceID, app2appDestination, docExchangeDestination, timestamp }: - IEventMemberRegistered, { blockNumber, transactionHash}: IDBBlockchainData) => { - await database.upsertMember({ - address: member, - name, - app2appDestination, - docExchangeDestination, - assetTrailInstanceID, - timestamp: Number(timestamp), - blockNumber, - transactionHash - }); -}; diff --git a/kat/src/handlers/payment-definitions.ts b/kat/src/handlers/payment-definitions.ts deleted file mode 100644 index e1f085f5d2..0000000000 --- a/kat/src/handlers/payment-definitions.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { v4 as uuidV4 } from 'uuid'; -import Ajv from 'ajv'; -import * as utils from '../lib/utils'; -import * as ipfs from '../clients/ipfs'; -import * as apiGateway from '../clients/api-gateway'; -import * as database from '../clients/database'; -import RequestError from '../lib/request-handlers'; -import { IAPIGatewayAsyncResponse, IAPIGatewaySyncResponse, IDBBlockchainData, IEventPaymentDefinitionCreated } from '../lib/interfaces'; - -const ajv = new Ajv(); - -export const handleGetPaymentDefinitionsRequest = (query: object, skip: number, limit: number) => { - return database.retrievePaymentDefinitions(query, skip, limit); -}; - -export const handleCountPaymentDefinitionsRequest = async (query: object) => { - return { count: await database.countPaymentDefinitions(query) }; -}; - -export const handleGetPaymentDefinitionRequest = async (paymentDefinitionID: string) => { - const paymentDefinition = await database.retrievePaymentDefinitionByID(paymentDefinitionID); - if (paymentDefinition === null) { - throw new RequestError('Payment definition not found', 404); - } - return paymentDefinition; -}; - -export const handleCreatePaymentDefinitionRequest = async (name: string, author: string, descriptionSchema: Object | undefined, sync: boolean) => { - if (descriptionSchema !== undefined && !ajv.validateSchema(descriptionSchema)) { - throw new RequestError('Invalid description schema', 400); - } - if (await database.retrievePaymentDefinitionByName(name) !== null) { - throw new RequestError('Payment definition name conflict', 409); - } - let descriptionSchemaHash: string | undefined; - let apiGatewayResponse: IAPIGatewayAsyncResponse | IAPIGatewaySyncResponse; - const timestamp = utils.getTimestamp(); - - const paymentDefinitionID = uuidV4(); - if (descriptionSchema) { - descriptionSchemaHash = utils.ipfsHashToSha256(await ipfs.uploadString(JSON.stringify(descriptionSchema))); - apiGatewayResponse = await apiGateway.createDescribedPaymentDefinition(paymentDefinitionID, name, author, descriptionSchemaHash, sync); - } else { - apiGatewayResponse = await apiGateway.createPaymentDefinition(paymentDefinitionID, name, author, sync); - } - const receipt = apiGatewayResponse.type === 'async' ? apiGatewayResponse.id : undefined; - await database.upsertPaymentDefinition({ - paymentDefinitionID, - name, - author, - descriptionSchemaHash, - descriptionSchema, - submitted: timestamp, - receipt - }); - return paymentDefinitionID; -}; - -export const handlePaymentDefinitionCreatedEvent = async (event: IEventPaymentDefinitionCreated, { blockNumber, transactionHash }: IDBBlockchainData) => { - const paymentDefinitionID = utils.hexToUuid(event.paymentDefinitionID); - const dbPaymentDefinitionByID = await database.retrievePaymentDefinitionByID(paymentDefinitionID); - if (dbPaymentDefinitionByID !== null) { - if (dbPaymentDefinitionByID.transactionHash !== undefined) { - throw new Error(`Payment definition ID conflict ${paymentDefinitionID}`); - } - } else { - const dbpaymentDefinitionByName = await database.retrievePaymentDefinitionByName(event.name); - if (dbpaymentDefinitionByName !== null) { - if (dbpaymentDefinitionByName.transactionHash !== undefined) { - throw new Error(`Payment definition name conflict ${event.name}`); - } else { - await database.markPaymentDefinitionAsConflict(dbpaymentDefinitionByName.paymentDefinitionID, Number(event.timestamp)); - } - } - } - let descriptionSchema; - if (event.descriptionSchemaHash) { - if (event.descriptionSchemaHash === dbPaymentDefinitionByID?.descriptionSchemaHash) { - descriptionSchema = dbPaymentDefinitionByID?.descriptionSchema - } else { - descriptionSchema = await ipfs.downloadJSON(utils.sha256ToIPFSHash(event.descriptionSchemaHash)); - } - } - database.upsertPaymentDefinition({ - paymentDefinitionID, - author: event.author, - name: event.name, - descriptionSchemaHash: event.descriptionSchemaHash, - descriptionSchema, - timestamp: Number(event.timestamp), - blockNumber, - transactionHash - }); -}; diff --git a/kat/src/handlers/payment-instances.ts b/kat/src/handlers/payment-instances.ts deleted file mode 100644 index e058e71607..0000000000 --- a/kat/src/handlers/payment-instances.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Ajv from 'ajv'; -import { v4 as uuidV4 } from 'uuid'; -import * as database from '../clients/database'; -import * as ipfs from '../clients/ipfs'; -import * as utils from '../lib/utils'; -import * as apiGateway from '../clients/api-gateway'; -import RequestError from '../lib/request-handlers'; -import { IAPIGatewayAsyncResponse, IAPIGatewaySyncResponse, IDBBlockchainData, IDBPaymentInstance, IEventPaymentInstanceCreated } from '../lib/interfaces'; -import { config } from '../lib/config'; - -const ajv = new Ajv(); - -export const handleGetPaymentInstancesRequest = (query: object, sort: object, skip: number, limit: number) => { - return database.retrievePaymentInstances(query, sort, skip, limit); -}; - -export const handleCountPaymentInstancesRequest = async (query: object) => { - return { count: await database.countPaymentInstances(query) }; -}; - -export const handleGetPaymentInstanceRequest = async (paymentInstanceID: string) => { - const assetInstance = await database.retrievePaymentInstanceByID(paymentInstanceID); - if (assetInstance === null) { - throw new RequestError('Payment instance not found', 404); - } - return assetInstance; -}; - -export const handleCreatePaymentInstanceRequest = async (author: string, paymentDefinitionID: string, - member: string, description: object | undefined, amount: number, participants: string[] | undefined, sync: boolean) => { - const paymentDefinition = await database.retrievePaymentDefinitionByID(paymentDefinitionID); - if (paymentDefinition === null) { - throw new RequestError('Unknown payment definition', 400); - } - if (paymentDefinition.transactionHash === undefined) { - throw new RequestError('Payment definition transaction must be mined', 400); - } - if(config.protocol === 'ethereum' && participants !== undefined) { - throw new RequestError('Participants not supported in Ethereum', 400); - } - if(config.protocol === 'corda') { - // validate participants are registered members - if(participants !== undefined) { - for(const participant of participants) { - if (await database.retrieveMemberByAddress(participant) === null) { - throw new RequestError('One or more participants are not registered', 400); - } - } - } else { - throw new RequestError('Missing payment participants', 400); - } - } - let descriptionHash: string | undefined; - if (paymentDefinition.descriptionSchema) { - if (!description) { - throw new RequestError('Missing payment description', 400); - } - if (!ajv.validate(paymentDefinition.descriptionSchema, description)) { - throw new RequestError('Description does not conform to payment definition schema', 400); - } - descriptionHash = utils.ipfsHashToSha256(await ipfs.uploadString(JSON.stringify(description))); - } - const paymentInstanceID = uuidV4(); - const timestamp = utils.getTimestamp(); - let apiGatewayResponse: IAPIGatewayAsyncResponse | IAPIGatewaySyncResponse; - if (descriptionHash) { - apiGatewayResponse = await apiGateway.createDescribedPaymentInstance(paymentInstanceID, - paymentDefinitionID, author, member, amount, descriptionHash, participants,sync); - } else { - apiGatewayResponse = await apiGateway.createPaymentInstance(paymentInstanceID, - paymentDefinitionID, author, member, amount, participants, sync); - } - const receipt = apiGatewayResponse.type === 'async' ? apiGatewayResponse.id : undefined; - await database.upsertPaymentInstance({ - paymentInstanceID, - author, - paymentDefinitionID: paymentDefinition.paymentDefinitionID, - descriptionHash, - description, - member, - participants, - amount, - receipt, - submitted: timestamp - }); - return paymentInstanceID; -}; - -export const handlePaymentInstanceCreatedEvent = async (event: IEventPaymentInstanceCreated, { blockNumber, transactionHash }: IDBBlockchainData) => { - let eventPaymentInstanceID: string; - let eventPaymentDefinitionID: string; - switch(config.protocol) { - case 'corda': - eventPaymentDefinitionID = event.paymentDefinitionID; - eventPaymentInstanceID = event.paymentInstanceID; - break; - case 'ethereum': - eventPaymentDefinitionID = utils.hexToUuid(event.paymentDefinitionID) - eventPaymentInstanceID = utils.hexToUuid(event.paymentInstanceID); - break; - } - const dbPaymentInstance = await database.retrievePaymentInstanceByID(eventPaymentInstanceID); - if (dbPaymentInstance !== null && dbPaymentInstance.transactionHash !== undefined) { - throw new Error(`Duplicate payment instance ID`); - } - const paymentDefinition = await database.retrievePaymentDefinitionByID(eventPaymentDefinitionID); - if (paymentDefinition === null) { - throw new Error('Uknown payment definition'); - } - if (config.protocol === 'ethereum' && paymentDefinition.transactionHash === undefined) { - throw new Error('Payment definition transaction must be mined'); - } - let description: Object | undefined = undefined; - if (paymentDefinition.descriptionSchema) { - if (event.descriptionHash) { - if (event.descriptionHash === dbPaymentInstance?.descriptionHash) { - description = dbPaymentInstance.description; - } else { - description = await ipfs.downloadJSON(utils.sha256ToIPFSHash(event.descriptionHash)); - if (!ajv.validate(paymentDefinition.descriptionSchema, description)) { - throw new Error('Description does not conform to schema'); - } - } - } else { - throw new Error('Missing payment instance description'); - } - } - let paymentInstanceDB: IDBPaymentInstance = { - paymentInstanceID: eventPaymentInstanceID, - author: event.author, - paymentDefinitionID: paymentDefinition.paymentDefinitionID, - descriptionHash: event.descriptionHash, - description, - member: event.member, - amount: Number(event.amount), - timestamp: Number(event.timestamp), - blockNumber, - transactionHash - }; - if(config.protocol === 'corda') { - paymentInstanceDB.participants = event.participants; - } - database.upsertPaymentInstance(paymentInstanceDB); -}; diff --git a/kat/src/index.ts b/kat/src/index.ts deleted file mode 100644 index 99a42b00af..0000000000 --- a/kat/src/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { config, init as initConfig, shutDown as shutDownConfig } from './lib/config'; -import express from 'express'; -import bodyParser from 'body-parser'; -import membersRouter from './routers/members'; -import assetDefinitionsRouter from './routers/asset-definitions'; -import assetInstancesRouter from './routers/asset-instances'; -import paymentDefinitionsRouter from './routers/payment-definitions'; -import paymentInstancesRouter from './routers/payment-instances'; -import settingsRouter from './routers/settings'; -import batchesRouter from './routers/batches'; -import { errorHandler, requestLogger, responseLogger } from './lib/request-handlers'; -import * as database from './clients/database'; -import * as settings from './lib/settings'; -import * as utils from './lib/utils'; -import * as ipfs from './clients/ipfs'; -import * as app2app from './clients/app2app'; -import * as docExchange from './clients/doc-exchange'; -import * as eventStreams from './clients/event-streams'; -import { ensureEventStreamAndSubscriptions } from './lib/event-stream'; -import { assetTradeHandler } from './handlers/asset-trade'; -import { clientEventHandler } from './handlers/client-events'; -import { assetInstancesPinning } from './handlers/asset-instances-pinning'; - -const log = utils.getLogger('index.ts'); - -export const start = () => { - return initConfig(() => { app2app.reset(); docExchange.reset() }) - .then(() => settings.init()) - .then(() => database.init()) - .then(() => ipfs.init()) - .then(() => app2app.init()) - .then(() => docExchange.init()) - .then(() => ensureEventStreamAndSubscriptions()) - .then(() => assetInstancesPinning.init()) - .then(() => { - eventStreams.init(); - const app = express(); - - app.use(bodyParser.urlencoded({ extended: true })); - app.use(bodyParser.json()); - app.use(requestLogger); - - app.use('/api/v1/members', membersRouter); - app.use('/api/v1/assets/definitions', assetDefinitionsRouter); - app.use('/api/v1/assets', assetInstancesRouter); - app.use('/api/v1/payments/definitions', paymentDefinitionsRouter); - app.use('/api/v1/payments/instances', paymentInstancesRouter); - app.use('/api/v1/settings', settingsRouter); - app.use('/api/v1/batches', batchesRouter); - - app.use(responseLogger); - app.use(errorHandler); - - app2app.addListener(assetTradeHandler); - database.addListener(clientEventHandler); - - const server = app.listen(config.port, () => { - log.info(`Asset trail listening on port ${config.port} - log level "${utils.constants.LOG_LEVEL}"`); - }).on('error', (err) => { - log.error(err); - }); - - const shutDown = () => { - server.close(err => { - if (err) { - log.error(`Error closing server. ${err}`); - } else { - log.info(`Stopped server.`) - } - }); - eventStreams.shutDown(); - database.shutDown(); - shutDownConfig(); - }; - - return { app, shutDown }; - - }); -} diff --git a/kat/src/lib/batch-manager.ts b/kat/src/lib/batch-manager.ts deleted file mode 100644 index 3de8975e5c..0000000000 --- a/kat/src/lib/batch-manager.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as database from '../clients/database'; -import { BatchProcessor } from './batch-processor'; -import { IDBBatch } from './interfaces'; -import * as utils from './utils'; - -const log = utils.getLogger('lib/batch-manager.ts'); - -/** - * Lifecycle manager for BatchProcessor instances, within a single type, across multiple authors - */ -export class BatchManager { - - processors: {[author: string]: BatchProcessor} = {}; - - constructor( - private type: string, - private processBatchCallback: (batch: IDBBatch) => Promise, - ) { } - - public async init() { - // Query all incomplete batches for our type, in creation order - const inflightBatches = await database.retrieveBatches({ - type: this.type, - completed: null, - }, 0, 0, { created: 1 }); - const byAuthor: {[author: string]: IDBBatch[]} = {}; - for (const inflight of inflightBatches) { - const forAuthor = byAuthor[inflight.author] = byAuthor[inflight.author] || []; - forAuthor.push(inflight); - } - // Init a set of processors for each distinct authors. - // Note these will be reaped if no work comes in while we're processing the backlog - for (const [author, forAuthor] of Object.entries(byAuthor)) { - await this.getProcessor(author).init(forAuthor); - } - } - - protected processorCompleteCallback(author: string) { - log.trace(`${this.type} batch manager: Reaping processor for ${author}`); - delete this.processors[author]; - } - - public getProcessor(author: string) { - if (!this.processors[author]) { - log.trace(`${this.type} batch manager: Creating processor for ${author}`); - this.processors[author] = new BatchProcessor( - author, - this.type, - this.processBatchCallback, - this.processorCompleteCallback.bind(this), - ); - } - return this.processors[author]; - } - -} \ No newline at end of file diff --git a/kat/src/lib/batch-processor.ts b/kat/src/lib/batch-processor.ts deleted file mode 100644 index 912db91db8..0000000000 --- a/kat/src/lib/batch-processor.ts +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { clearTimeout, setTimeout } from 'timers'; -import { promisify } from 'util'; -import { v4 as uuidV4 } from 'uuid'; -import * as database from '../clients/database'; -import { IBatchRecord, IDBBatch } from './interfaces'; -import * as utils from './utils'; - -const delay = promisify(setTimeout); - -const log = utils.getLogger('lib/batch-processor.ts'); - -export interface IBatchProcessorConfig { - addTimeoutMS: number; - batchTimeoutArrivallMS: number; - batchTimeoutOverallMS: number; - batchMaxRecords: number; - retryInitialDelayMS: number; - retryMaxDelayMS: number; - retryMultiplier: number; -} - -interface BatchAssemblyTask { - timestamp: number; - record: IBatchRecord; - resolve: (batchID: string) => void; - reject: (err: Error) => void; -} - -/** - * A singleton of these should be created for each batch type + author combination. - * - * A persistent batch implementation, which: - * - Is safe for calling concurrently on many async contexts - * - Guarantees to persists batch updates to the database before returning from add - * - Blocks the caller from getting more than one batch ahead - * - Protects against the caller of add (such as a REST API) giving up on a timeout before their record is accepted - * - Recovers in-flight batches on initialization - * - Pipelines the processing of one batch, with the building of the next - * - Retries accepted batches indefinitely - */ -export class BatchProcessor { - - private assemblyList: BatchAssemblyTask[]; - private assembling: boolean; - private assemblyBatch?: IDBBatch; - private dispatchTimeout?: NodeJS.Timeout; - private batchInFlight?: Promise; - public config: IBatchProcessorConfig; - - constructor( - private author: string, - private type: string, - private processBatchCallback: (batch: IDBBatch) => Promise, - private processorCompleteCallback: (author: string, type: string) => void, - config?: Partial, - ) { - this.assemblyList = []; - this.assembling = false; - this.config = { - addTimeoutMS: utils.constants.BATCH_ADD_TIMEOUT_MILLIS, - batchTimeoutArrivallMS: utils.constants.BATCH_TIMEOUT_ARRIVAL_MILLIS, - batchTimeoutOverallMS: utils.constants.BATCH_TIMEOUT_OVERALL_MILLIS, - batchMaxRecords: utils.constants.BATCH_MAX_RECORDS, - retryInitialDelayMS: utils.constants.BATCH_RETRY_INITIAL_DELAY_MILLIS, - retryMaxDelayMS: utils.constants.BATCH_RETRY_MAX_DELAY_MILLIS, - retryMultiplier: utils.constants.BATCH_RETRY_MULTIPLIER, - ...config, - } - } - - public async init(incompleteBatches: IDBBatch[]) { - // Treat the stored batches just as we would do filled batches. - // This logic blocks startup until we queued dispatch of all persisted batches - // (there should be a maximum of two, for the author+type combination) - while (incompleteBatches.length) { - this.assemblyBatch = incompleteBatches.shift(); - await this.dispatchBatch(); - } - } - - /** - * Blocks until the requested record has been assigned to a batch, and its inclusion - * in that batch has been persisted to our local database. - * @param record the record to add to a batch - * @returns {string} the batchID the add was persisted into - */ - public async add(record: IBatchRecord): Promise { - return new Promise((resolve, reject) => { - // Add our record to the assember queue, to resove the parent promise - this.assemblyList.push({ timestamp: Date.now(), record, resolve, reject }); - // Give the assembler a kick, as it might not be already running - this.assembler(); - }); - } - - protected newBatch(): IDBBatch { - const timestamp = Date.now(); - return { - type: this.type, - author: this.author, - batchID: uuidV4(), - created: timestamp, - completed: null, - records: [], - }; - } - - // Safety check to make sure we haven't got work queued into the system - // from a long time ago, that potentially a REST client has forgotten about. - // These are rejected at the point they are detected, before we do any active - // processing on them. - private rejectAnyStale() { - const now = Date.now(); - const newAssemblyList = []; - for (const a of this.assemblyList) { - const inFlightTime = now - a.timestamp; - if (inFlightTime > this.config.addTimeoutMS) { - a.reject(new Error(`Timed out add of record after ${inFlightTime}ms`)) - } else { - newAssemblyList.push(a); - } - } - this.assemblyList = newAssemblyList; - return this.assemblyList; - } - - private async assembler() { - - // Use each add as an opportunity to check for stales - this.rejectAnyStale(); - - // If we've already got an assembler running, nothing more to do - if (this.assembling) return; - - // We are the assembler - stop an duplicate one running (cleared before return) - this.assembling = true; - let chosen: BatchAssemblyTask[] = []; - while (this.rejectAnyStale().length) { - try { - - // Create a new assembly batch if we don't currently have one - if (!this.assemblyBatch) this.assemblyBatch = this.newBatch(); - const batch = this.assemblyBatch; - - // Grab as much capacity as we can out of the assemblyList - let capacity = this.config.batchMaxRecords - batch.records.length; - chosen = this.assemblyList.slice(0, capacity); - this.assemblyList = this.assemblyList.slice(capacity); - - // Add these entries to the in-memory batch object - for (let a of chosen) { - batch.records.push(a.record); - } - - // Persist the batch object to our local database - log.trace(`${this.type}/${this.author}: added ${chosen.length} records to batch ${batch.batchID}`); - await database.upsertBatch(batch); - - // Check if the batch is full - if (batch.records.length >= this.config.batchMaxRecords) { - // Only one batch can be dispatched, so this is a blocking call if we manage - // to run more than one batch ahead of the assembler. - await this.dispatchBatch(); - } else { - // Set/reset the timer to dispatch this batch - const now = Date.now(); - if (this.dispatchTimeout) clearTimeout(this.dispatchTimeout); - this.dispatchTimeout = setTimeout(() => this.dispatchBatch(), - Math.min( - // The next record must arrive within the batchTimeoutArrivallMS - this.config.batchTimeoutArrivallMS, - // The first record in the batch cannot be delayed by more than the batchTimeoutOverallMS - (batch.created + this.config.batchTimeoutOverallMS) - now, - ) - ); - } - - // **** - // Note that this point this.assemblyBatch might be undefined, if we just dispatched it. - // It is also NOT SAFE to do do any async processing here, because the processBatch - // logic relies us to exit if this.assemblyBatch to be undefined when the batch completes. - // So we need to go round to `newBatch` again without any async logic. - // *** - - // We have accepted all the chosen records into a persisted batch, ready for dispatch. - // This unblocks any callers waiting to know what batch they are in. - for (let a of chosen) a.resolve(batch.batchID); - } - catch(err) { - log.error(`${this.type}/${this.author}: Batch assembler failed`, err); - for (let a of chosen) a.reject(err); - } - } - this.assembling = false; - } - - protected async dispatchBatch() { - if (this.batchInFlight) await this.batchInFlight; - if (this.dispatchTimeout) clearTimeout(this.dispatchTimeout); - const batch = this.assemblyBatch; - delete this.assemblyBatch; - delete this.dispatchTimeout; - if (!batch) return; // Covers the posibility of a timer and the assember loop both firing - const batchTime = Date.now() - batch.created; - log.info(`${this.type}/${this.author}: closed batch ${batch.batchID} after ${batchTime}ms with ${batch.records.length} records`); - // Capture the promise for competion of this batch, to block any further dispatchBatch calls - this.batchInFlight = this.processBatch(batch); - } - - protected async processBatch(batch: IDBBatch) { - // We have accepted the batch at this point, and the REST calls to submit it to us have all completed. - // So we cannot fail to process it, and we must retry the processing indefinitely - let attempt = 0; - let complete = false; - while (!complete) { - try { - attempt++; - - // Set the completed time in memory - forms part of uniqueness in the pinning process. - batch.completed = Date.now(); - await this.processBatchCallback(batch); - - // Update the batch as complete - writes the now final completed timestamp, along with any updates made in processBatchCallback - await database.upsertBatch(batch); - - // Ok, we're done here. - complete = true; - } - catch(err) { - let retryDelay = this.config.retryInitialDelayMS; - for (let i = 1; i < attempt; i++) retryDelay *= this.config.retryMultiplier; - retryDelay = Math.min(retryDelay, this.config.retryMaxDelayMS); - log.error(`${this.type}/${this.author}: batch ${batch.batchID} attempt ${attempt} failed (next-retry: ${retryDelay}ms): ${err.stack}`); - await delay(retryDelay); - } - } - - // If there's nothing queued up, we call the completion handler that was passed in, - // to let them unregister this batch processor. - // This is because there are potentially infinite 'author' addresses that could be used, - // so leaving ourselves around indefinitely just because someone submitted on transaction - // would be a memory leak. - if (!this.assemblyBatch) { - this.processorCompleteCallback(this.author, this.type); - } - - } - -} \ No newline at end of file diff --git a/kat/src/lib/config.ts b/kat/src/lib/config.ts deleted file mode 100644 index cce6dafd28..0000000000 --- a/kat/src/lib/config.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { promisify } from 'util'; -import { readFile } from 'fs'; -import Ajv from 'ajv'; -import configSchema from '../schemas/config.json'; -import * as utils from './utils'; -import { IConfig } from './interfaces'; -import path from 'path'; -import chokidar, { FSWatcher } from 'chokidar'; - -const log = utils.getLogger('lib/config.ts'); - -const asyncReadFile = promisify(readFile); -const ajv = new Ajv(); -const validateConfig = ajv.compile(configSchema); -const configFilePath = path.join(utils.constants.DATA_DIRECTORY, utils.constants.CONFIG_FILE_NAME); - -export let config: IConfig; -let fsWatcher: FSWatcher; -let configChangeListener: () => void; - -export const init = async (listener: () => void) => { - await loadConfigFile(); - watchConfigFile(); - configChangeListener = listener; -}; - -const loadConfigFile = async () => { - try { - const data = JSON.parse(await asyncReadFile(configFilePath, 'utf8')); - if(validateConfig(data)) { - config = data; - } else { - throw new Error('Invalid configuration file'); - } - } catch(err) { - throw new Error(`Failed to read configuration file. ${err}`); - } -}; - -const watchConfigFile = () => { - fsWatcher = chokidar.watch(configFilePath, { ignoreInitial: true }).on('change', async () => { - try { - await loadConfigFile(); - log.info('Loaded configuration file changes'); - configChangeListener(); - } catch(err) { - log.error(`Failed to load configuration file. ${err}`); - } - }); -}; - -export const shutDown = () => { - fsWatcher.close(); -}; \ No newline at end of file diff --git a/kat/src/lib/event-stream.ts b/kat/src/lib/event-stream.ts deleted file mode 100644 index 3328de1e05..0000000000 --- a/kat/src/lib/event-stream.ts +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as QueryString from 'querystring'; -import { URL } from 'url'; -import axios, { AxiosInstance } from 'axios'; -import { promisify } from 'util'; -import * as timers from 'timers'; -import * as database from '../clients/database'; -const sleep = promisify(timers.setTimeout); - -import * as utils from './utils'; -import { config } from './config' -import { IConfig, IEventStream, IEventStreamSubscription } from './interfaces'; -const logger = utils.getLogger('lib/event-stream.ts'); - -/* istanbul ignore next */ -const requestLogger = (config: any) => { - const qs = config.params ? `?${QueryString.stringify(config.params)}` : ''; - logger.info(`--> ${config.method} ${config.baseURL}${config.url}${qs}`); - logger.debug(config.data); - return config; -}; - -/* istanbul ignore next */ -const responseLogger = (response: any) => { - const { config, status, data } = response; - logger.info(`<-- ${config.method} ${config.url} [${status}]`); - logger.debug(data); - return response; -}; - -/* istanbul ignore next */ -const errorLogger = (err: any) => { - const { config = {}, response = {} } = err; - const { status, data } = response; - logger.info(`<-- ${config.method} ${config.url} [${status || err}]: ${JSON.stringify(data)}`); - throw err; -}; - -const subscriptionsInfoEthereum = [ - ['Asset instance created', 'AssetInstanceCreated'], - ['Asset instance batch created', 'AssetInstanceBatchCreated'], - ['Payment instance created', 'PaymentInstanceCreated'], - ['Payment definition created', 'PaymentDefinitionCreated'], - ['Asset definition created', 'AssetDefinitionCreated'], - ['Asset instance property set', 'AssetInstancePropertySet'], - ['Described payment instance created', 'DescribedPaymentInstanceCreated'], - ['Described asset instance created', 'DescribedAssetInstanceCreated'], - ['Described payment definition created', 'DescribedPaymentDefinitionCreated'], - ['Member registered', 'MemberRegistered'] -]; - -const subscriptionInfoCorda = [ - ['Asset instance created', 'io.kaleido.kat.states.AssetInstanceCreated'], - ['Described asset instance created', 'io.kaleido.kat.states.DescribedAssetInstanceCreated'], - ['Asset instance batch created', 'io.kaleido.kat.states.AssetInstanceBatchCreated'], - ['Asset instance property set', 'io.kaleido.kat.states.AssetInstancePropertySet'] -] - -export const ensureEventStreamAndSubscriptions = async () => { - if(!config.eventStreams.skipSetup) { - const esMgr = new EventStreamManager(config); - await esMgr.ensureEventStreamsWithRetry(); - } -}; - -class EventStreamManager { - private gatewayPath: string; - private api: AxiosInstance; - private streamDetails: any; - private retryCount: number; - private retryDelay: number; - private protocol: string; - - constructor(config: IConfig) { - const apiURL = new URL(config.apiGateway.apiEndpoint); - this.gatewayPath = apiURL.pathname.replace(/^\//, ''); - apiURL.pathname = ''; - const creds = `${config.apiGateway.auth?.user??config.appCredentials.user}:${config.apiGateway.auth?.password??config.appCredentials.password}`; - this.api = axios.create({ - baseURL: apiURL.href, - headers: { - Authorization: `Basic ${Buffer.from(creds).toString('base64')}` - } - }); - this.api.interceptors.request.use(requestLogger); - this.api.interceptors.response.use(responseLogger, errorLogger); - this.retryCount = 20; - this.retryDelay = 5000; - this.protocol = config.protocol; - this.streamDetails = { - name: config.eventStreams.topic, - errorHandling: config.eventStreams.config?.errorHandling??'block', - batchSize: config.eventStreams.config?.batchSize??50, - batchTimeoutMS: config.eventStreams.config?.batchTimeoutMS??500, - type: "websocket", - websocket: { - topic: config.eventStreams.topic - } - } - if(this.protocol === 'ethereum') { - // Due to spell error in ethconnect, we do this for now until ethconnect is updated - this.streamDetails.blockedReryDelaySec = config.eventStreams.config?.blockedRetryDelaySec??30; - } else { - this.streamDetails.blockedRetryDelaySec = config.eventStreams.config?.blockedRetryDelaySec??30; - } - } - - async ensureEventStreamsWithRetry() { - for (let i = 1; i <= this.retryCount; i++) { - try { - if (i > 1) await sleep(this.retryDelay); - const stream: IEventStream = await this.ensureEventStream(); - await this.ensureSubscriptions(stream); - return; - } catch (err) { - logger.error(`Attempt ${i} to initialize event streams failed`, err); - } - } - throw new Error("Failed to initialize event streams after retries"); - } - - async ensureEventStream(): Promise { - const { data: existingStreams } = await this.api.get('eventstreams'); - let stream = existingStreams.find((s: any) => s.name === this.streamDetails.name); - if (stream) { - const { data: patchedStream } = await this.api.patch(`eventstreams/${stream.id}`, this.streamDetails); - return patchedStream; - } - const { data: newStream } = await this.api.post('eventstreams', this.streamDetails); - return newStream; - } - - subscriptionInfo() { - switch(this.protocol) { - case 'ethereum': - return subscriptionsInfoEthereum; - case 'corda': - return subscriptionInfoCorda; - default: - throw new Error("Unsupported protocol."); - } - } - - async createSubscription(eventType: string, streamId: string, description: string): Promise<{id: string, name: string}> { - switch(this.protocol) { - case 'ethereum': - return this.api.post(`${this.gatewayPath}/${eventType}/Subscribe`, { - description, - name: eventType, - stream: streamId, - fromBlock: "0", // Subscribe from the start of the chain - }).then(r => { logger.info(`Created subscription ${eventType}: ${r.data.id}`); return r.data }); - case 'corda': - return this.api.post('subscriptions', { - name: eventType, - stream: streamId, - fromTime: null, // BEGINNING is specified as `null` in Corda event streams - filter: { - stateType: eventType, - stateStatus: "unconsumed", - relevancyStatus: "all" - } - }).then(r => { logger.info(`Created subscription ${eventType}: ${r.data.id}`); return r.data }); - default: - throw new Error("Unsupported protocol."); - } - } - - - - async ensureSubscriptions(stream: IEventStream) { - const dbSubscriptions = (await database.retrieveSubscriptions()) || {}; - const { data: existing } = await this.api.get('subscriptions'); - for (const [description, eventName] of this.subscriptionInfo()) { - const dbEventName = eventName.replace(/\./g, '_'); - let sub = existing.find((s: IEventStreamSubscription) => s.name === eventName && s.stream === stream.id); - let storedSubId = dbSubscriptions[dbEventName]; - if (!sub || sub.id !== storedSubId) { - if (sub) { - logger.info(`Deleting stale subscription that does not match persisted id ${storedSubId}`, sub); - await this.api.delete(`subscriptions/${sub.id}`); - } - const newSub = await this.createSubscription(eventName, stream.id, description); - dbSubscriptions[dbEventName] = newSub.id; - } else { - logger.info(`Subscription ${eventName}: ${sub.id}`); - } - } - await database.upsertSubscriptions(dbSubscriptions); - } - -} diff --git a/kat/src/lib/interfaces.ts b/kat/src/lib/interfaces.ts deleted file mode 100644 index 552074a0d2..0000000000 --- a/kat/src/lib/interfaces.ts +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// CONFIG INTERFACE - -export interface IConfig { - port: number - assetTrailInstanceID: string - protocol: 'ethereum' | 'corda' - apiGateway: { - apiEndpoint: string - auth?: { - user: string - password: string - } - } - eventStreams: { - wsEndpoint: string - topic: string - auth?: { - user: string - password: string - } - skipSetup?: boolean - config?: { - batchSize: number - batchTimeoutMS: number - blockedRetryDelaySec: number, - errorHandling: 'skip' | 'block' - } - } - ipfs: { - apiEndpoint: string - gatewayEndpoint: string - } - app2app: { - socketIOEndpoint: string - destinations: { - kat: string - client: string - } - } - docExchange: { - apiEndpoint: string - socketIOEndpoint: string - destination: string - } - appCredentials: { - user: string - password: string - } - mongodb: { - connectionUrl: string - databaseName: string - } -} - -// SETTINGS - -export interface ISettings { - clientEvents: string[] -} - -// STORED SUBSCRIPTION CONFIG - -export interface IStoredSubscriptions { - [sub: string]: string; -} - -// API GATEWAY INTERFACES - -export interface IAPIGatewayAsyncResponse { - type: 'async' - id: string - msg: string - sent: boolean -} - -export interface IAPIGatewaySyncResponse { - type: 'sync' - transactionHash: string -} - -export interface IAPIGatewaySyncResponseEthereum extends IAPIGatewaySyncResponse { - protocol: 'ethereum' - blockHash: string - blockNumber: string - cumulativeGasUsed: string - from: string - gasUsed: string - headers: { - id: string - type: 'string', - timeReceived: 'string', - timeElapsed: number - requestOffset: string - } - nonce: string - status: string - to: string - transactionIndex: string -} - -export interface IEventStream { - id: string -} - -export interface IEventStreamSubscription { - name: string - stream: string -} - -// IPFS INTERFACES - -export interface IIPFSAssetDefinition { - assetDefinitionID: string - name: string - isContentPrivate: boolean - isContentUnique: boolean - descriptionSchema?: object - contentSchema?: object - indexes?: indexes -} - -// IPFS INTERFACES - -export interface IIPFSAssetDefinition { - assetDefinitionID: string - name: string - isContentPrivate: boolean - isContentUnique: boolean - descriptionSchema?: object - contentSchema?: object - indexes?: indexes -} - -// REQUEST INTERFACES - -export interface IRequestMultiPartContent { - author?: string - assetDefinitionID?: string - description?: Promise - contentStream: NodeJS.ReadableStream - contentFileName: string - isContentPrivate?: boolean -} - -export interface IAssetDefinitionRequest { - assetDefinitionID: string - name: string - author?: string - isContentPrivate: boolean - isContentUnique: boolean - indexes?: indexes - descriptionSchema?: object - contentSchema?: object -} - -// EVENT STREAM INTERFACES - - -interface IStateRefCorda { - txhash: string, - index: number -} - -interface IStateCorda { - data: object -} - -export interface IEventStreamRawMessageCorda { - data: IStateCorda, - subId: string, - signature: string, - stateRef: IStateRefCorda, - recordedTime: string, - consumedTime: string -} - -export interface IEventStreamMessage { - address?: string - blockNumber?: string - transactionIndex?: string - transactionHash: string - data: object - subId: string - signature: string - logIndex?: string -} - -export interface IEventMemberRegistered { - member: string - name: string - assetTrailInstanceID: string - app2appDestination: string - docExchangeDestination: string - timestamp: number -} - -export interface IEventAssetDefinitionCreated { - author: string - assetDefinitionHash: string - timestamp: string -} - -export interface IEventPaymentDefinitionCreated { - paymentDefinitionID: string - author: string - name: string - descriptionSchemaHash?: string - timestamp: string -} - -export interface IEventAssetInstanceCreated { - assetInstanceID: string - assetDefinitionID: string - author: string - descriptionHash?: string - contentHash: string - timestamp: string - isContentPrivate: boolean - participants?: string[] -} -export interface IEventAssetInstanceBatchCreated { - batchHash: string; - author: string - timestamp: string - participants?: string[] -} - -export interface IEventPaymentInstanceCreated { - paymentInstanceID: string - paymentDefinitionID: string - author: string - member: string - descriptionHash?: string - amount: string - timestamp: string - participants?: string[] -} - -export interface IEventAssetInstancePropertySet extends IAssetInstancePropertySet { - timestamp: string - participants?: string[] -} - -// DATABASE INTERFACES - -//TODO: figure out how to handle variable asset-instance collection names -export type databaseCollectionName = 'members' | 'asset-definitions' | 'payment-definitions' | 'payment-instances' | 'batches' | customCollectionName -export type customCollectionName = string - -export type indexes = {fields: string[], unique?: boolean}[]; - -export interface IDatabaseProvider { - init: () => Promise - createCollection: (collectionName: string, indexes: indexes) => Promise - count: (collectionName: databaseCollectionName, query: object) => Promise - find: (collectionName: databaseCollectionName, query: object, sort: object, skip: number, limit: number) => Promise - findOne: (collectionName: databaseCollectionName, query: object) => Promise - updateOne: (collectionName: databaseCollectionName, query: object, value: object, upsert: boolean) => Promise - shutDown: () => void -} - -export interface IDBBlockchainData { - blockNumber?: number - transactionHash: string - participants?: string[] -} - -export interface IDBBlockchainPinned extends Partial { - submitted?: number - timestamp?: number - receipt?: string -} - -export interface IDBMember extends IDBBlockchainPinned { - _id?: string - address: string - name: string - assetTrailInstanceID: string - app2appDestination: string - docExchangeDestination: string -} - -export interface IDBAssetDefinition extends IIPFSAssetDefinition, IDBBlockchainPinned { - _id?: string - author: string - assetDefinitionHash: string - conflict?: boolean -} - -export interface IDBPaymentDefinition extends IDBBlockchainPinned { - _id?: string - paymentDefinitionID: string - author: string - name: string - descriptionSchema?: object - descriptionSchemaHash?: string - conflict?: boolean -} - -export interface IAssetInstance { - assetInstanceID: string - assetDefinitionID: string - author: string - descriptionHash?: string - description?: object - content?: object - contentHash?: string - isContentPrivate: boolean - conflict?: boolean - filename?: string - properties?: { - [author: string]: { - [key: string]: { - value: string - submitted?: number - receipt?: string - history?: { - [timestamp: string]: { - value: string - timestamp: number - blockNumber: number - transactionHash: string - } - } - } | undefined - } | undefined - } -} - -export interface IDBAssetInstance extends IAssetInstance, IDBBlockchainPinned { - _id?: string - batchID?: string; -} - -export interface IAssetInstancePropertySet { - assetInstanceID: string - assetDefinitionID: string - author: string - key: string - value: string -} - -export interface IDBPaymentInstance extends IDBBlockchainPinned { - _id?: string - paymentInstanceID: string - paymentDefinitionID: string - author: string - member: string - amount: number - descriptionHash?: string - description?: object -} - -export enum BatchRecordType { - assetInstance = 'instance', - assetProperty = 'property', -} - -export interface IBatchRecord { - recordType: BatchRecordType, - [x: string]: any, -} - -export interface IPinnedBatch { - type: string; - author: string; - created: number; - completed: number | null; - batchID: string, - records: IBatchRecord[]; -} - -export interface IDBBatch extends IPinnedBatch, IDBBlockchainPinned { - _id?: string; - batchHash?: string, -} - -// APP2APP INTERFACES - -export interface IApp2AppMessageHeader { - from: string - to: string -} - -export interface IApp2AppMessage { - headers: IApp2AppMessageHeader - content: string -} - -export interface IApp2AppMessageListener { - (header: IApp2AppMessageHeader, content: AssetTradeMessage): void -} - -// DOCUMENT EXCHANGE INTERFACES - -export interface IDocExchangeDocumentDetails { - name: string - is_directory: boolean - size: number - hash: string -} - -export interface IDocExchangeTransferData { - transferId: string - transferHash: string - hash: string - from: string - to: string - senderSignature: string - memberSignature: string - document: string - timestamp: string - status: 'sent' | 'received' | 'failed' -} - -export interface IDocExchangeListener { - (transferData: IDocExchangeTransferData): void -} - -// ASSET TRADE INTERFACES - -export type AssetTradeMessage = - IAssetTradePrivateAssetInstanceRequest - | IAssetTradePrivateAssetInstanceResponse - | IAssetTradePrivateAssetInstancePush - | IAssetTradePrivateAssetInstanceAuthorizationResponse - -export interface IAssetTradePrivateAssetInstanceRequest { - type: 'private-asset-instance-request' - tradeID: string - assetInstanceID: string - assetDefinitionID: string - requester: { - assetTrailInstanceID: string - address: string - } - metadata?: object -} - -export interface IAssetTradePrivateAssetInstanceResponse { - type: 'private-asset-instance-response' - tradeID: string - assetInstanceID: string - rejection?: string - content?: object - filename?: string -} - -export interface IAssetTradePrivateAssetInstancePush { - type: 'private-asset-instance-push' - assetInstanceID: string - assetDefinitionID: string - content?: object - filename?: string -} - -export interface IAssetTradePrivateAssetInstanceAuthorizationRequest { - type: 'private-asset-instance-authorization-request' - authorizationID: string - assetInstance: IDBAssetInstance - requester: IDBMember - metadata?: object -} - -export interface IAssetTradePrivateAssetInstanceAuthorizationResponse { - type: 'private-asset-instance-authorization-response' - authorizationID: string - authorized: boolean -} - -// CLIENT EVENT INTERFACES - -export type ClientEventType = - 'member-registered' - | 'asset-definition-submitted' - | 'asset-definition-created' - | 'asset-definition-name-conflict' - | 'payment-definition-submitted' - | 'payment-definition-created' - | 'payment-definition-name-conflict' - | 'asset-instance-submitted' - | 'asset-instance-created' - | 'asset-instance-content-conflict' - | 'payment-instance-submitted' - | 'payment-instance-created' - | 'private-asset-instance-content-stored' - | 'asset-instance-property-submitted' - | 'asset-instance-property-set' - -export interface IClientEventListener { - (eventType: ClientEventType, content: object): void -} - -export interface IPendingAssetInstancePrivateContentDelivery { - assetInstanceID: string - fromDestination: string - content?: object - filename?: string -} \ No newline at end of file diff --git a/kat/src/lib/logging.ts b/kat/src/lib/logging.ts deleted file mode 100644 index 447ffe6ecf..0000000000 --- a/kat/src/lib/logging.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -"use strict"; - -const LEVEL_NONE = 0; -const LEVEL_ERROR = 1; -const LEVEL_WARN = 2; -const LEVEL_INFO = 3; -const LEVEL_DEBUG = 4; -const LEVEL_TRACE = 5; - -const LEVEL_TAGS = { - [LEVEL_NONE]: 'NONE', - [LEVEL_ERROR]: 'ERROR', - [LEVEL_WARN]: 'WARN ', - [LEVEL_INFO]: 'INFO ', - [LEVEL_DEBUG]: 'DEBUG', - [LEVEL_TRACE]: 'TRACE', -}; - -let logLevel = LEVEL_ERROR; - -export function setLogLevel(level?: string) { - if (!level) level = process.env.LOG_LEVEL || 'info'; - for (let [l,t] of Object.entries(LEVEL_TAGS)) { - if (t.trim().toLowerCase() === level.trim().toLowerCase()) { - logLevel = Number(l); - } - } -} - -export class Logger { - - constructor(private loggerName?: string) {} - - error(...args: any[]) { logLevel >= LEVEL_ERROR && this.log('ERROR', ...args); } - warn(...args: any[]) { logLevel >= LEVEL_WARN && this.log('WARN ', ...args); } - info(...args: any[]) { logLevel >= LEVEL_INFO && this.log('INFO ', ...args); } - debug(...args: any[]) { logLevel >= LEVEL_DEBUG && this.log('DEBUG', ...args); } - trace(...args: any[]) { logLevel >= LEVEL_TRACE && this.log('TRACE', ...args); } - - private log(level: string, ...args: any[]) { - const logArgs = []; - for (const arg of args) { - // Special handling of axios errors to avoid massive dumps in log - if (arg?.isAxiosError) { - let data = arg.response?.data; - data = data?.on ? '[stream]' : JSON.stringify(data); - logArgs.push(`HTTP [${arg.response?.status}] ${arg.message}: ${data}`) - } else { - logArgs.push(arg); - } - } - console.log(`${new Date().toISOString()} [${level}]:`, ...logArgs, this.loggerName); - } - -} - -setLogLevel(); diff --git a/kat/src/lib/request-handlers.ts b/kat/src/lib/request-handlers.ts deleted file mode 100644 index 28924612cd..0000000000 --- a/kat/src/lib/request-handlers.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { NextFunction, Request, Response } from 'express'; -import { nanoid } from 'nanoid'; -import * as utils from './utils'; -const log = utils.getLogger('lib/request-handlers.ts'); - -export default class RequestError extends Error { - - responseCode: number; - - constructor(message: string, responseCode = 500) { - super(message); - this.responseCode = responseCode; - } - -} - -export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction) => { - const statusCode = err instanceof RequestError?err.responseCode : 500; - log.error(`!<-- ${req.method} ${req.url} [${statusCode}]`, statusCode === 404 ? undefined : err.stack); - res.status(statusCode).send({ error: err.message }); -}; - -interface ATRequest extends Request { - entryTime: number - requestId: string -} - -export function requestLogger(req: Request, _: Response, next: NextFunction): void { - const r: ATRequest = req as ATRequest; - r.entryTime = Date.now(); - r.requestId = nanoid(10); - log.info(`[${r.requestId}] --> ${req.method} ${req.path}`); - next(); -} - -export function responseLogger(req: Request, res: Response, next: NextFunction): void { - const r: ATRequest = req as ATRequest; - const timing = (Date.now() - r.entryTime).toFixed(1); - log.info( - `[${r.requestId}] <-- ${req.method} ${req.path} [${res.statusCode}] (${timing}ms)` - ); - next(); -} \ No newline at end of file diff --git a/kat/src/lib/settings.ts b/kat/src/lib/settings.ts deleted file mode 100644 index 89408689eb..0000000000 --- a/kat/src/lib/settings.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Ajv from 'ajv'; -import { promisify } from 'util'; -import { readFile, writeFile } from 'fs'; -import path from 'path'; -import * as utils from './utils'; -import { ISettings } from './interfaces'; -import settingsSchema from '../schemas/settings.json'; -import RequestError from './request-handlers'; - -const log = utils.getLogger('lib/settings.ts'); - -const asyncReadFile = promisify(readFile); -const asyncWriteFile = promisify(writeFile); -const ajv = new Ajv(); -const validateSettings = ajv.compile(settingsSchema); -export let settings: ISettings; - -const settingsFilePath = path.join(utils.constants.DATA_DIRECTORY, utils.constants.SETTINGS_FILE_NAME); - -export const init = async () => { - await readSettingsFile() -}; - -const readSettingsFile = async () => { - try { - const values = JSON.parse(await asyncReadFile(settingsFilePath, 'utf8')); - if(!validateSettings(values)) { - throw new Error('Invalid content'); - } - settings = values; - } catch(err) { - if(err.errno === -2) { - settings = { - clientEvents: [] - }; - } else { - throw new Error(`Failed to read settings file. ${err}`); - } - } -}; - -export const updateSettings = async (key: string, value: any) => { - const updatedSettings = {...settings, [key]: value}; - if (!validateSettings(updatedSettings)) { - throw new RequestError('Invalid Settings', 400); - } - settings = updatedSettings; - await persistSettings(); -}; - -export const setClientEvents = (clientEvents: string[]) => { - settings.clientEvents = clientEvents; - persistSettings(); -}; - -const persistSettings = () => { - try { - asyncWriteFile(settingsFilePath, JSON.stringify(settings)); - } catch(err) { - log.error(`Failed to persist settings. ${err}`); - } -}; \ No newline at end of file diff --git a/kat/src/lib/utils.ts b/kat/src/lib/utils.ts deleted file mode 100644 index 64e833b694..0000000000 --- a/kat/src/lib/utils.ts +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { encode, decode } from 'bs58'; -import crypto from 'crypto'; -import axios, { AxiosRequestConfig } from 'axios'; -import { databaseCollectionName, indexes } from './interfaces'; -import { parseDN } from 'ldapjs'; -import { Logger } from './logging'; - -export const constants = { - DATA_DIRECTORY: process.env.DATA_DIRECTORY || '/data', - LOG_LEVEL: process.env.LOG_LEVEL || 'info', - CONFIG_FILE_NAME: 'config.json', - SETTINGS_FILE_NAME: 'settings.json', - IPFS_TIMEOUT_MS: 15000, - DEFAULT_PAGINATION_LIMIT: 100, - EVENT_STREAM_WEBSOCKET_RECONNECTION_DELAY_SECONDS: 5, - DOC_EXCHANGE_ASSET_FOLDER_NAME: 'assets', - EVENT_STREAM_PING_TIMEOUT_SECONDS: 60, - ASSET_INSTANCE_TRADE_TIMEOUT_SECONDS: 15, - TRADE_AUTHORIZATION_TIMEOUT_SECONDS: 10, - DOCUMENT_EXCHANGE_TRANSFER_TIMEOUT_SECONDS: 15, - SUBSCRIBE_RETRY_INTERVAL: 5 * 1000, - APP2APP_BATCH_SIZE: parseInt(process.env.APP2APP_BATCH_SIZE || "100"), - APP2APP_BATCH_TIMEOUT: parseInt(process.env.APP2APP_BATCH_TIMEOUT || "250"), - APP2APP_READ_AHEAD: parseInt(process.env.APP2APP_READ_AHEAD || "50"), - REST_API_CALL_MAX_ATTEMPTS: parseInt(process.env.REST_API_CALL_MAX_ATTEMPTS || "5"), - REST_API_CALL_RETRY_DELAY_MS: parseInt(process.env.REST_API_CALL_MAX_ATTEMPTS || "500"), - BATCH_ADD_TIMEOUT_MILLIS: parseInt(process.env.BATCH_ADD_TIMEOUT_MILLIS || '30000'), - BATCH_TIMEOUT_OVERALL_MILLIS: parseInt(process.env.BATCH_TIMEOUT_OVERALL_MILLIS || '2500'), - BATCH_TIMEOUT_ARRIVAL_MILLIS: parseInt(process.env.BATCH_TIMEOUT_ARRIVAL_MILLIS || '250'), - BATCH_MAX_RECORDS: parseInt(process.env.BATCH_MAX_RECORDS || '1000'), - BATCH_RETRY_INITIAL_DELAY_MILLIS: parseInt(process.env.BATCH_RETRY_INITIAL_DELAY_MILLIS || '100'), - BATCH_RETRY_MAX_DELAY_MILLIS: parseInt(process.env.BATCH_RETRY_MAX_DELAY_MILLIS || '10000'), - BATCH_RETRY_MULTIPLIER: parseFloat(process.env.BATCH_RETRY_MULTIPLIER || '2.0'), -}; - -const log = new Logger('utis.ts'); - -export const databaseCollectionIndexes: { [name in databaseCollectionName]: indexes } = { - members: [{ fields: ['address'], unique: true }], - 'asset-definitions': [{ fields: ['assetDefinitionID'], unique: true }], - 'payment-definitions': [{ fields: ['paymentDefinitionID'], unique: true }], - 'payment-instances': [{ fields: ['paymentInstanceID'], unique: true }], - 'batches': [ - { fields: ['batchID'], unique: true }, // Primary key - { fields: ['type', 'author', 'completed', 'created'] }, // Search index for startup processing, and other queries - { fields: ['batchHash'] } // To retrieve a batch by its hash, in response to a blockchain event - ], - 'state': [{ fields: ['key'], unique: true }], -}; - -const ETHEREUM_ACCOUNT_REGEXP = /^0x[a-fA-F0-9]{40}$/; - -const isValidX500Name = (name: string) => { - try { - parseDN(name); - } catch (e) { - return false; - } - return true; -}; - -export const isAuthorValid = (author: string, protocol: string) => { - switch (protocol) { - case 'corda': - return isValidX500Name(author); - case 'ethereum': - return ETHEREUM_ACCOUNT_REGEXP.test(author); - } -} - -export const requestKeys = { - ASSET_AUTHOR: 'author', - ASSET_DEFINITION_ID: 'assetDefinitionID', - ASSET_DESCRIPTION: 'description', - ASSET_CONTENT: 'content', - ASSET_IS_CONTENT_PRIVATE: 'isContentPrivate' -}; - -export const contractEventSignaturesCorda = { - ASSET_DEFINITION_CREATED: 'io.kaleido.kat.states.AssetDefinitionCreated', - MEMBER_REGISTERED: 'io.kaleido.kat.states.MemberRegistered', - DESCRIBED_PAYMENT_DEFINITION_CREATED: 'io.kaleido.kat.states.DescribedPaymentDefinitionCreated', - PAYMENT_DEFINITION_CREATED: 'io.kaleido.kat.states.PaymentDefinitionCreated', - DESCRIBED_ASSET_INSTANCE_CREATED: 'io.kaleido.kat.states.DescribedAssetInstanceCreated', - ASSET_INSTANCE_BATCH_CREATED: 'io.kaleido.kat.states.AssetInstanceBatchCreated', - ASSET_INSTANCE_CREATED: 'io.kaleido.kat.states.AssetInstanceCreated', - DESCRIBED_PAYMENT_INSTANCE_CREATED: 'io.kaleido.kat.states.DescribedPaymentInstanceCreated', - PAYMENT_INSTANCE_CREATED: 'io.kaleido.kat.states.PaymentInstanceCreated', - ASSET_PROPERTY_SET: 'io.kaleido.kat.states.AssetInstancePropertySet' -} - -export const contractEventSignatures = { - ASSET_DEFINITION_CREATED: 'AssetDefinitionCreated(bytes32,address,uint256)', - MEMBER_REGISTERED: 'MemberRegistered(address,string,string,string,string,uint256)', - DESCRIBED_PAYMENT_DEFINITION_CREATED: 'DescribedPaymentDefinitionCreated(bytes32,address,string,bytes32,uint256)', - PAYMENT_DEFINITION_CREATED: 'PaymentDefinitionCreated(bytes32,address,string,uint256)', - DESCRIBED_ASSET_INSTANCE_CREATED: 'DescribedAssetInstanceCreated(bytes32,bytes32,address,bytes32,bytes32,uint256)', - ASSET_INSTANCE_BATCH_CREATED: 'AssetInstanceBatchCreated(bytes32,address,uint256)', - ASSET_INSTANCE_CREATED: 'AssetInstanceCreated(bytes32,bytes32,address,bytes32,uint256)', - DESCRIBED_PAYMENT_INSTANCE_CREATED: 'DescribedPaymentInstanceCreated(bytes32,bytes32,address,address,uint256,bytes32,uint256)', - PAYMENT_INSTANCE_CREATED: 'PaymentInstanceCreated(bytes32,bytes32,address,address,uint256,uint256)', - ASSET_PROPERTY_SET: 'AssetInstancePropertySet(bytes32,bytes32,address,string,string,uint256)' -}; - -export const getSha256 = (value: string) => crypto.createHash('sha256').update(value).digest('hex'); - -export const ipfsHashToSha256 = (hash: string) => '0x' + decode(hash).slice(2).toString('hex'); - -export const sha256ToIPFSHash = (short: string) => encode(Buffer.from('1220' + short.slice(2), 'hex')); - -export const getTimestamp = () => { - return Math.round(new Date().getTime() / 1000); -}; - -export const streamToString = (stream: NodeJS.ReadableStream): Promise => { - const chunks: Buffer[] = []; - return new Promise((resolve, reject) => { - stream.on('data', chunk => chunks.push(chunk)); - stream.on('error', reject); - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); - }) -} - -export const getUnstructuredFilePathInDocExchange = (assetInstanceID: string) => { - return `${constants.DOC_EXCHANGE_ASSET_FOLDER_NAME}/${assetInstanceID}`; -}; - -export const uuidToHex = (uuid: string) => { - return '0x' + Buffer.from(uuid.replace(/-/g, '')).toString('hex'); -}; - -export const hexToUuid = (hex: string) => { - const decodedTransferID = Buffer.from(hex.substr(2), 'hex').toString('utf-8'); - return decodedTransferID.substr(0, 8) + '-' + decodedTransferID.substr(8, 4) + '-' + - decodedTransferID.substr(12, 4) + '-' + decodedTransferID.substr(16, 4) + '-' + - decodedTransferID.substr(20, 12); -}; - -export const axiosWithRetry = async (config: AxiosRequestConfig) => { - let attempts = 0; - let currentError; - while (attempts < constants.REST_API_CALL_MAX_ATTEMPTS) { - try { - return await axios(config); - } catch (err) { - const data = err.response?.data; - log.error(`${config.method} ${config.url} attempt ${attempts} [${err.response?.status}]`, (data && !data.on) ? data : err.stack) - if (err.response?.status === 404) { - throw err; - } else { - currentError = err; - attempts++; - await new Promise(resolve => setTimeout(resolve, constants.REST_API_CALL_RETRY_DELAY_MS)); - } - } - } - throw currentError; -}; - -export function getLogger(label: string) { - return new Logger(label); -} \ No newline at end of file diff --git a/kat/src/routers/asset-definitions.ts b/kat/src/routers/asset-definitions.ts deleted file mode 100644 index 8eb8b27675..0000000000 --- a/kat/src/routers/asset-definitions.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Router } from 'express'; -import { v4 as uuidV4 } from 'uuid'; -import RequestError from '../lib/request-handlers'; -import * as assetDefinitionsHandler from '../handlers/asset-definitions'; -import { constants } from '../lib/utils'; -import * as utils from '../lib/utils'; -import { config } from '../lib/config'; - -const router = Router(); - -router.get('/', async (req, res, next) => { - try { - const skip = Number(req.query.skip || 0); - const limit = Number(req.query.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (isNaN(skip) || isNaN(limit)) { - throw new RequestError('Invalid skip / limit', 400); - } - res.send(await assetDefinitionsHandler.handleGetAssetDefinitionsRequest({}, skip, limit)); - } catch (err) { - next(err); - } -}); - -router.get('/:assetDefinitionID', async (req, res, next) => { - try { - res.send(await assetDefinitionsHandler.handleGetAssetDefinitionRequest(req.params.assetDefinitionID)); - } catch (err) { - next(err); - } -}); - -router.post('/search', async (req, res, next) => { - try { - const skip = Number(req.body.skip || 0); - const limit = Number(req.body.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (req.body.count !== true && (isNaN(skip) || isNaN(limit))) { - throw new RequestError('Invalid skip / limit', 400); - } - if (!req.body.query) { - throw new RequestError('Missing search query', 400); - } - res.send(req.body.count === true ? - await assetDefinitionsHandler.handleCountAssetDefinitionsRequest(req.body.query) : - await assetDefinitionsHandler.handleGetAssetDefinitionsRequest(req.body.query, skip, limit) - ); - } catch (err) { - next(err); - } -}); - -router.post('/', async (req, res, next) => { - try { - if (!req.body.name || req.body.name === '') { - throw new RequestError('Missing or invalid asset definition name', 400); - } - if (!utils.isAuthorValid(req.body.author, config.protocol)) { - throw new RequestError('Missing or invalid asset definition author', 400); - } - if (typeof req.body.isContentPrivate !== 'boolean') { - throw new RequestError('Missing asset definition content privacy', 400); - } - if (typeof req.body.isContentUnique !== 'boolean') { - throw new RequestError('Missing asset definition content uniqueness', 400); - } - let assetDefinitionID; - switch (config.protocol) { - case 'corda': - if (!req.body.assetDefinitionID) { - throw new RequestError('Missing asset definition id', 400); - } - assetDefinitionID = req.body.assetDefinitionID; - break; - case 'ethereum': - assetDefinitionID = uuidV4(); - break; - } - const sync = req.query.sync === 'true'; - await assetDefinitionsHandler.handleCreateAssetDefinitionRequest(assetDefinitionID, req.body.name, req.body.isContentPrivate, - req.body.isContentUnique, req.body.author, req.body.descriptionSchema, req.body.contentSchema, req.body.indexes, sync); - res.send({ status: sync ? 'success' : 'submitted', assetDefinitionID }); - } catch (err) { - next(err); - } -}); - -export default router; diff --git a/kat/src/routers/asset-instances.ts b/kat/src/routers/asset-instances.ts deleted file mode 100644 index 180c080345..0000000000 --- a/kat/src/routers/asset-instances.ts +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Router, Request } from 'express'; -import RequestError from '../lib/request-handlers'; -import * as assetInstancesHandler from '../handlers/asset-instances'; -import { constants, requestKeys, streamToString } from '../lib/utils'; -import Busboy from 'busboy'; -import * as utils from '../lib/utils'; -import { IRequestMultiPartContent } from '../lib/interfaces'; -import { config } from '../lib/config'; - -const router = Router(); - -router.get('/:assetDefinitionID', async (req, res, next) => { - try { - const skip = Number(req.query.skip || 0); - const limit = Number(req.query.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (isNaN(skip) || isNaN(limit)) { - throw new RequestError('Invalid skip / limit', 400); - } - res.send(await assetInstancesHandler.handleGetAssetInstancesRequest(req.params.assetDefinitionID, {}, {}, skip, limit)); - } catch (err) { - next(err); - } -}); - -router.get('/:assetDefinitionID/:assetInstanceID', async (req, res, next) => { - try { - const content = req.query.content === 'true'; - res.send(await assetInstancesHandler.handleGetAssetInstanceRequest(req.params.assetDefinitionID, req.params.assetInstanceID, content)); - } catch (err) { - next(err); - } -}); - -router.post('/search/:assetDefinitionID', async (req, res, next) => { - try { - const skip = Number(req.body.skip || 0); - const limit = Number(req.body.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (req.body.count !== true && (isNaN(skip) || isNaN(limit))) { - throw new RequestError('Invalid skip / limit', 400); - } - if (!req.body.query) { - throw new RequestError('Missing search query', 400); - } - res.send(req.body.count === true ? - await assetInstancesHandler.handleCountAssetInstancesRequest(req.params.assetDefinitionID, req.body.query) : - await assetInstancesHandler.handleGetAssetInstancesRequest(req.params.assetDefinitionID, req.body.query, req.body.sort || {}, skip, limit) - ); - } catch (err) { - next(err); - } -}); - -router.post('/:assetDefinitionID', async (req, res, next) => { - try { - let assetInstanceID: string; - const sync = req.query.sync === 'true'; - - if (req.headers["content-type"]?.startsWith('multipart/form-data')) { - let description: Object | undefined; - const formData = await extractDataFromMultipartForm(req); - if (formData.description !== undefined) { - try { - description = JSON.parse(await formData.description); - } catch (err) { - throw new RequestError(`Invalid description. ${err}`, 400); - } - } - if (!formData.author ||!utils.isAuthorValid(formData.author, config.protocol)) { - throw new RequestError('Missing or invalid asset instance author', 400); - } - assetInstanceID = await assetInstancesHandler.handleCreateUnstructuredAssetInstanceRequest(formData.author, req.params.assetDefinitionID, description, formData.contentStream, formData.contentFileName, formData.isContentPrivate, req.body.participants, sync); - } else { - if (!utils.isAuthorValid(req.body.author, config.protocol)) { - throw new RequestError('Missing or invalid asset instance author', 400); - } - if (!(typeof req.body.content === 'object' && req.body.content !== null)) { - throw new RequestError('Missing or invalid asset content', 400); - } - if(req.body.isContentPrivate !== undefined && typeof req.body.isContentPrivate !== 'boolean') { - throw new RequestError('Invalid isContentPrivate', 400); - } - assetInstanceID = await assetInstancesHandler.handleCreateStructuredAssetInstanceRequest(req.body.author, req.params.assetDefinitionID, req.body.description, req.body.content, req.body.isContentPrivate, req.body.participants, sync); - } - res.send({ status: sync ? 'success' : 'submitted', assetInstanceID }); - } catch (err) { - next(err); - } -}); - -router.put('/:assetDefinitionID/:assetInstanceID', async (req, res, next) => { - try { - switch (req.body.action) { - case 'set-property': - if (!req.body.key) { - throw new RequestError('Missing asset property key', 400); - } - if (!req.body.value) { - throw new RequestError('Missing asset property value', 400); - } - if (!utils.isAuthorValid(req.body.author, config.protocol)) { - throw new RequestError('Missing or invalid asset property author', 400); - } - const sync = req.query.sync === 'true'; - await assetInstancesHandler.handleSetAssetInstancePropertyRequest(req.params.assetDefinitionID, req.params.assetInstanceID, req.body.author, req.body.key, req.body.value, sync); - res.send({ status: sync ? 'success' : 'submitted' }); - break; - case 'push': - if (!req.body.memberAddress) { - throw new RequestError('Missing member address', 400); - } - await assetInstancesHandler.handlePushPrivateAssetInstanceRequest(req.params.assetDefinitionID, req.params.assetInstanceID, req.body.memberAddress); - res.send({ status: 'success' }); - break; - default: - throw new RequestError('Missing or invalid action'); - } - } catch (err) { - next(err); - } -}); - -router.patch('/:assetDefinitionID/:assetInstanceID', async (req, res, next) => { - try { - if (!utils.isAuthorValid(req.body.requester, config.protocol)) { - throw new RequestError(`Missing requester`); - } - await assetInstancesHandler.handleAssetInstanceTradeRequest(req.params.assetDefinitionID, req.body.requester, req.params.assetInstanceID, req.body.metadata); - res.send({ status: 'success' }); - } catch (err) { - next(err); - } -}); - -router.post('/:assetDefinitionID/:assetInstanceID/push', async (req, res, next) => { - try { - if (!req.body.memberAddress) { - throw new RequestError('Missing member address', 400); - } - await assetInstancesHandler.handlePushPrivateAssetInstanceRequest(req.params.assetDefinitionID, req.params.assetInstanceID, req.body.memberAddress); - res.send({ status: 'success' }); - } catch (err) { - next(err); - } -}); - -const extractDataFromMultipartForm = (req: Request): Promise => { - return new Promise(async (resolve, reject) => { - let author: string | undefined; - let assetDefinitionID: string | undefined; - let description: Promise | undefined; - let isContentPrivate: boolean | undefined = undefined; - req.pipe(new Busboy({ headers: req.headers }) - .on('field', (fieldname, value) => { - switch (fieldname) { - case requestKeys.ASSET_AUTHOR: author = value; break; - case requestKeys.ASSET_DEFINITION_ID: assetDefinitionID = value; break; - case requestKeys.ASSET_IS_CONTENT_PRIVATE: isContentPrivate = value === 'true'; break; - } - }).on('file', (fieldname, readableStream, fileName) => { - switch (fieldname) { - case requestKeys.ASSET_DESCRIPTION: description = streamToString(readableStream); break; - case requestKeys.ASSET_CONTENT: resolve({ author, assetDefinitionID, description, contentStream: readableStream, contentFileName: fileName, isContentPrivate }); break; - default: readableStream.resume(); - } - })).on('finish', () => { - reject(new RequestError('Missing content', 400)); - }); - }); -}; - -export default router; diff --git a/kat/src/routers/batches.ts b/kat/src/routers/batches.ts deleted file mode 100644 index 290a20ae1a..0000000000 --- a/kat/src/routers/batches.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Router } from 'express'; -import * as batchHandler from '../handlers/batches'; - -const router = Router(); - -router.get('/:batchID', async (req, res, next) => { - try { - res.send(await batchHandler.handleGetBatchRequest(req.params.batchID)); - } catch (err) { - next(err); - } -}); - -export default router; diff --git a/kat/src/routers/members.ts b/kat/src/routers/members.ts deleted file mode 100644 index c86b6b7c74..0000000000 --- a/kat/src/routers/members.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Router } from 'express'; -import * as membersHandler from '../handlers/members'; -import { config } from '../lib/config'; -import RequestError from '../lib/request-handlers'; -import { constants } from '../lib/utils'; - -const router = Router(); - -router.get('/', async (req, res, next) => { - try { - const skip = Number(req.query.skip || 0); - const limit = Number(req.query.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (isNaN(skip) || isNaN(limit)) { - throw new RequestError('Invalid skip / limit', 400); - } - res.send(await membersHandler.handleGetMembersRequest({}, skip, limit)); - } catch (err) { - next(err); - } -}); - -router.get('/:memberAddress', async (req, res, next) => { - try { - res.send(await membersHandler.handleGetMemberRequest(req.params.memberAddress)); - } catch (err) { - next(err) - } -}); - -router.put('/', async (req, res, next) => { - try { - if (!req.body.address) { - throw new RequestError('Missing member address', 400); - } - if (!req.body.name) { - throw new RequestError('Missing member name', 400); - } - let assetTrailInstanceID, app2appDestination, docExchangeDestination; - switch (config.protocol) { - case 'corda': - if (!req.body.assetTrailInstanceID) { - throw new RequestError('Missing member assetTrailInstanceID', 400); - } - if (!req.body.app2appDestination) { - throw new RequestError('Missing member app2appDestination', 400); - } - if (!req.body.docExchangeDestination) { - throw new RequestError('Missing member docExchangeDestination', 400); - } - assetTrailInstanceID = req.body.assetTrailInstanceID; - app2appDestination = req.body.app2appDestination; - docExchangeDestination = req.body.docExchangeDestination; - break; - case 'ethereum': - assetTrailInstanceID = config.assetTrailInstanceID; - app2appDestination = config.app2app.destinations.kat; - docExchangeDestination = config.docExchange.destination; - break; - } - const sync = req.query.sync === 'true'; - await membersHandler.handleUpsertMemberRequest(req.body.address, req.body.name, assetTrailInstanceID, app2appDestination, docExchangeDestination, sync); - res.send({ status: sync ? 'success' : 'submitted' }); - } catch (err) { - next(err); - } -}); - -export default router; \ No newline at end of file diff --git a/kat/src/routers/payment-definitions.ts b/kat/src/routers/payment-definitions.ts deleted file mode 100644 index f9576365ad..0000000000 --- a/kat/src/routers/payment-definitions.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Router } from 'express'; -import RequestError from '../lib/request-handlers'; -import * as paymentDefinitionsHandler from '../handlers/payment-definitions'; -import { constants } from '../lib/utils'; -import * as utils from '../lib/utils'; -import { config } from '../lib/config'; - -const router = Router(); - -router.get('/', async (req, res, next) => { - try { - const skip = Number(req.query.skip || 0); - const limit = Number(req.query.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (isNaN(skip) || isNaN(limit)) { - throw new RequestError('Invalid skip / limit', 400); - } - res.send(await paymentDefinitionsHandler.handleGetPaymentDefinitionsRequest({}, skip, limit)); - } catch (err) { - next(err); - } -}); - -router.get('/:paymentDefinitionID', async (req, res, next) => { - try { - res.send(await paymentDefinitionsHandler.handleGetPaymentDefinitionRequest(req.params.paymentDefinitionID)); - } catch (err) { - next(err); - } -}); - -router.post('/search', async (req, res, next) => { - try { - const skip = Number(req.body.skip || 0); - const limit = Number(req.body.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (req.body.count !== true && (isNaN(skip) || isNaN(limit))) { - throw new RequestError('Invalid skip / limit', 400); - } - if (!req.body.query) { - throw new RequestError('Missing search query', 400); - } - res.send(req.body.count === true ? - await paymentDefinitionsHandler.handleCountPaymentDefinitionsRequest(req.body.query) : - await paymentDefinitionsHandler.handleGetPaymentDefinitionsRequest(req.body.query, skip, limit) - ); - } catch (err) { - next(err); - } -}); - -router.post('/', async (req, res, next) => { - try { - if (!req.body.name || req.body.name === '') { - throw new RequestError('Missing or invalid payment definition name', 400); - } - if (!utils.isAuthorValid(req.body.author, config.protocol)) { - throw new RequestError('Missing or invalid payment definition author', 400); - } - const sync = req.query.sync === 'true'; - const paymentDefinitionID = await paymentDefinitionsHandler.handleCreatePaymentDefinitionRequest(req.body.name, - req.body.author, req.body.descriptionSchema, sync); - res.send({ status: sync? 'success' : 'submitted', paymentDefinitionID }); - } catch (err) { - next(err); - } -}); - -export default router; diff --git a/kat/src/routers/payment-instances.ts b/kat/src/routers/payment-instances.ts deleted file mode 100644 index 032f5045c0..0000000000 --- a/kat/src/routers/payment-instances.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Router } from 'express'; -import RequestError from '../lib/request-handlers'; -import * as paymentInstancesHandler from '../handlers/payment-instances'; -import { constants } from '../lib/utils'; -import * as utils from '../lib/utils'; -import { config } from '../lib/config'; - -const router = Router(); - -router.get('/', async (req, res, next) => { - try { - const skip = Number(req.query.skip || 0); - const limit = Number(req.query.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (isNaN(skip) || isNaN(limit)) { - throw new RequestError('Invalid skip / limit', 400); - } - res.send(await paymentInstancesHandler.handleGetPaymentInstancesRequest({}, req.body.sort || {}, skip, limit)); - } catch (err) { - next(err); - } -}); - -router.get('/:assetInstanceID', async (req, res, next) => { - try { - res.send(await paymentInstancesHandler.handleGetPaymentInstanceRequest(req.params.assetInstanceID)); - } catch (err) { - next(err); - } -}); - -router.post('/search', async (req, res, next) => { - try { - const skip = Number(req.body.skip || 0); - const limit = Number(req.body.limit || constants.DEFAULT_PAGINATION_LIMIT); - if (req.body.count !== true && (isNaN(skip) || isNaN(limit))) { - throw new RequestError('Invalid skip / limit', 400); - } - if (!req.body.query) { - throw new RequestError('Missing search query', 400); - } - res.send(req.body.count === true ? - await paymentInstancesHandler.handleCountPaymentInstancesRequest(req.body.query) : - await paymentInstancesHandler.handleGetPaymentInstancesRequest(req.body.query, req.body.sort || {}, skip, limit) - ); - } catch (err) { - next(err); - } -}); - -router.post('/', async (req, res, next) => { - try { - if (!req.body.paymentDefinitionID) { - throw new RequestError('Missing payment definition ID', 400); - } - if (!utils.isAuthorValid(req.body.author, config.protocol)) { - throw new RequestError('Missing or invalid payment author', 400); - } - if (!utils.isAuthorValid(req.body.member, config.protocol)) { - throw new RequestError('Missing or invalid payment member', 400); - } - if (req.body.author === req.body.member) { - throw new RequestError('Author and member cannot be the same', 400); - } - if (!(Number.isInteger(req.body.amount) && req.body.amount > 0)) { - throw new RequestError('Missing or invalid payment amount', 400); - } - const sync = req.query.sync === 'true'; - const paymentInstanceID = await paymentInstancesHandler.handleCreatePaymentInstanceRequest(req.body.author, req.body.paymentDefinitionID, req.body.member, req.body.description, req.body.amount, req.body.participants, sync); - res.send({ status: sync? 'success' : 'submitted', paymentInstanceID }); - } catch (err) { - next(err); - } - -}); - -export default router; diff --git a/kat/src/routers/settings.ts b/kat/src/routers/settings.ts deleted file mode 100644 index 58e3b55e1c..0000000000 --- a/kat/src/routers/settings.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Router } from 'express'; -import * as settings from '../lib/settings'; -import RequestError from '../lib/request-handlers'; - -const router = Router(); - -router.get('/', async (_req, res, next) => { - try { - res.send(settings.settings); - } catch (err) { - next(err); - } -}); - -router.put('/', async (req, res, next) => { - try { - if (req.body.key === undefined || req.body.value === undefined) { - throw new RequestError('Invalid setting key/value', 400); - } - await settings.updateSettings(req.body.key, req.body.value); - res.send({ status: 'success' }); - } catch (err) { - next(err); - } -}); - -export default router; \ No newline at end of file diff --git a/kat/src/schemas/asset-definition.json b/kat/src/schemas/asset-definition.json deleted file mode 100644 index 82392a60c6..0000000000 --- a/kat/src/schemas/asset-definition.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "additionalProperties": false, - "required": [ - "assetDefinitionID", - "name", - "isContentPrivate", - "isContentUnique" - ], - "properties": { - "assetDefinitionID": { - "type": "string" - }, - "name": { - "type": "string" - }, - "isContentPrivate": { - "type": "boolean" - }, - "isContentUnique": { - "type": "boolean" - }, - "descriptionSchema": { - "type": "object" - }, - "contentSchema": { - "type": "object" - }, - "indexes": { - "type": "array" - } - } -} \ No newline at end of file diff --git a/kat/src/schemas/config.json b/kat/src/schemas/config.json deleted file mode 100644 index b01fa24fa8..0000000000 --- a/kat/src/schemas/config.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "required": [ - "port", - "assetTrailInstanceID", - "protocol", - "apiGateway", - "eventStreams", - "ipfs", - "app2app", - "docExchange", - "appCredentials" - ], - "properties": { - "port": { - "type": "integer" - }, - "assetTrailInstanceID": { - "type": "string" - }, - "protocol": { - "type": "string", - "enum": ["ethereum", "corda"] - }, - "apiGateway": { - "type": "object", - "required": [ - "apiEndpoint" - ], - "properties": { - "apiEndpoint": { - "type": "string" - }, - "auth": { - "type": "object", - "required": [ - "user", - "password" - ], - "properties": { - "user": { - "type": "string" - }, - "password": { - "type": "string" - } - } - } - } - }, - "eventStreams": { - "type": "object", - "required": [ - "wsEndpoint", - "topic" - ], - "properties": { - "wsEndpoint": { - "type": "string" - }, - "topic": { - "type": "string" - }, - "skipSetup": { - "type": "boolean", - "$comment": "Should only be set to true in development or testing" - }, - "auth": { - "type": "object", - "required": [ - "user", - "password" - ], - "properties": { - "user": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "config": { - "type": "object", - "required": [ - "batchSize", - "batchTimeoutMS", - "errorHandling", - "blockedRetryDelaySec" - ], - "properties": { - "batchSize": { - "type": "number" - }, - "batchTimeoutMS": { - "type": "number" - }, - "blockedRetryDelaySec":{ - "type":"number" - }, - "errorHandling":{ - "type":"string", - "enum": ["block", "skip"] - } - } - } - } - }, - "ipfs": { - "type": "object", - "required": [ - "apiEndpoint" - ], - "properties": { - "apiEndpoint": { - "type": "string" - }, - "gatewayEndpoint": { - "type": "string" - } - } - }, - "app2app": { - "type": "object", - "required": [ - "socketIOEndpoint", - "destinations" - ], - "properties": { - "socketIOEndpoint": { - "type": "string" - }, - "destinations": { - "type": "object", - "required": [ - "kat", - "client" - ], - "properties": { - "kat": { - "type": "string" - }, - "client": { - "type": "string" - } - } - } - } - }, - "docExchange": { - "type": "object", - "required": [ - "apiEndpoint", - "socketIOEndpoint", - "destination" - ], - "properties": { - "apiEndpoint": { - "type": "string" - }, - "socketIOEndpoint": { - "type": "string" - }, - "destination": { - "type": "string" - } - } - }, - "appCredentials": { - "type": "object", - "required": [ - "user", - "password" - ], - "properties": { - "user": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "mongodb": { - "type": "object", - "required": [ - "connectionUrl", - "databaseName" - ], - "connectionUrl": { - "type": "string" - }, - "databaseName": { - "type": "string" - } - } - } -} \ No newline at end of file diff --git a/kat/src/schemas/indexes.json b/kat/src/schemas/indexes.json deleted file mode 100644 index fb790095a1..0000000000 --- a/kat/src/schemas/indexes.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": { "$ref": "#/definitions/index" }, - "definitions": { - "index": { - "type": "object", - "required": [ "fields", "unique" ], - "properties": { - "fields": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Fields specified to be an index" - }, - "unique": { - "type": "boolean", - "description": "Whether the index property allows duplicate values" - } - } - } - } -} \ No newline at end of file diff --git a/kat/src/schemas/settings.json b/kat/src/schemas/settings.json deleted file mode 100644 index bdd54108b5..0000000000 --- a/kat/src/schemas/settings.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "required": [ - "clientEvents" - ], - "properties": { - "clientEvents": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "member-registered", - "asset-definition-submitted", - "asset-definition-created", - "asset-definition-name-conflict", - "payment-definition-submitted", - "payment-definition-created", - "payment-definition-name-conflict", - "asset-instance-submitted", - "asset-instance-created", - "asset-instance-content-conflict", - "payment-instance-submitted", - "payment-instance-created", - "private-asset-instance-content-stored", - "asset-instance-property-submitted", - "asset-instance-property-set" - ] - } - } - } -} \ No newline at end of file diff --git a/kat/src/test/common.ts b/kat/src/test/common.ts deleted file mode 100644 index 5c8a537861..0000000000 --- a/kat/src/test/common.ts +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { EventEmitter } from 'events'; -import rimraf from 'rimraf'; -import nock from 'nock'; -import mock from 'mock-require'; -import { promises as fs } from 'fs'; -import path from 'path'; -import assert from 'assert'; -import request from 'supertest'; -import * as utils from '../lib/utils'; -import { IEventMemberRegistered } from "../lib/interfaces"; -export let app: Express.Application; -export let mockEventStreamWebSocket: EventEmitter; -export let mockDocExchangeSocketIO = new EventEmitter(); - -let shutDown: () => void; - -class MockWebSocket extends EventEmitter { - - constructor(url: string) { - super(); - assert.strictEqual(url, 'ws://eventstreams.kaleido.io'); - mockEventStreamWebSocket = this; - } - - send(message: string) { - mockEventStreamWebSocket.emit('send', message); - } - - ping() { } - - close() { } - -}; - -export const setUp = async (protocol: string) => { - await new Promise((resolve, reject) => { - rimraf(path.join(__dirname, `../../test-resources/sandbox/${protocol}`), {}, (err) => { - if (err) { - reject() - } else { - resolve(); - } - }); - }); - - const sandboxPath = path.join(__dirname, `../../test-resources/sandbox/${protocol}`); - await fs.mkdir(sandboxPath, { recursive: true }); - await fs.copyFile(path.join(__dirname, '../../test-resources/settings.json'), path.join(sandboxPath, 'settings.json')); - await fs.copyFile(path.join(__dirname, `../../test-resources/config-${protocol}.json`), path.join(sandboxPath, 'config.json')); - - mock('ws', MockWebSocket); - mock('socket.io-client', { - connect: () => { - return mockDocExchangeSocketIO; - } - }); - // IPFS - nock('https://ipfs.kaleido.io') - .post('/api/v0/version') - .reply(200, { Version: 1 }); - - // Doc exchange REST API - nock('https://docexchange.kaleido.io') - .get('/documents') - .reply(200, { entries: [] }); - - nock('https://apigateway.kaleido.io') - .get('/subscriptions') - .reply(200, [{ - name: 'AssetInstanceCreated', - stream: 'es12345' - }]); - - const nockSubscribe = (description: string, name: string) => { - if(protocol === 'ethereum') { - nock('https://apigateway.kaleido.io') - .post(`/${name}/Subscribe`, { - description, - name, - stream: 'es12345', - }) - .reply(200, {}); - } else { - nock('https://apigateway.kaleido.io') - .post(`/subscriptions`, { - name, - stream: 'es12345', - fromTime: null, - filter: { - stateType: name, - stateStatus: "unconsumed", - relevancyStatus: "all" - } - }) - .reply(200, {}); - } - }; - - const nockEventStreamsWithRetry = () => { - // event stream and subscriptions - nock('https://apigateway.kaleido.io') - .get('/eventstreams') - .times(2) - .reply(200, []); - - nock('https://apigateway.kaleido.io') - .post('/eventstreams', { - name: 'dev', - errorHandling: "block", - blockedReryDelaySec: 30, - batchTimeoutMS: 500, - batchSize: 50, - type: "websocket", - websocket: { - topic: 'dev', - } - }) - .reply(500); - - nock('https://apigateway.kaleido.io') - .post('/eventstreams', { - name: 'dev', - errorHandling: "block", - blockedReryDelaySec: 30, - batchTimeoutMS: 500, - batchSize: 50, - type: "websocket", - websocket: { - topic: 'dev', - } - }) - .reply(200, { - id: 'es12345' - }); - } - - const nockEventStreamsWithRetryCorda = () => { - // event stream and subscriptions - nock('https://apigateway.kaleido.io') - .get('/eventstreams') - .times(2) - .reply(200, []); - - nock('https://apigateway.kaleido.io') - .post('/eventstreams', { - name: 'dev', - errorHandling: "block", - blockedRetryDelaySec: 30, - batchTimeoutMS: 500, - batchSize: 50, - type: "websocket", - websocket: { - topic: 'dev', - } - }) - .reply(500); - - nock('https://apigateway.kaleido.io') - .post('/eventstreams', { - name: 'dev', - errorHandling: "block", - blockedRetryDelaySec: 30, - batchTimeoutMS: 500, - batchSize: 50, - type: "websocket", - websocket: { - topic: 'dev', - } - }) - .reply(200, { - id: 'es12345' - }); - } - if(protocol === 'ethereum') { - nockEventStreamsWithRetry(); - nockSubscribe('Asset instance created', 'AssetInstanceCreated'); - nockSubscribe('Asset instance batch created', 'AssetInstanceBatchCreated'); - nockSubscribe('Payment instance created', 'PaymentInstanceCreated'); - nockSubscribe('Payment definition created', 'PaymentDefinitionCreated'); - nockSubscribe('Asset definition created', 'AssetDefinitionCreated'); - nockSubscribe('Asset instance property set', 'AssetInstancePropertySet'); - nockSubscribe('Described payment instance created', 'DescribedPaymentInstanceCreated'); - nockSubscribe('Described asset instance created', 'DescribedAssetInstanceCreated'); - nockSubscribe('Described payment definition created', 'DescribedPaymentDefinitionCreated'); - nockSubscribe('Member registered', 'MemberRegistered'); - } else { - nockEventStreamsWithRetryCorda(); - nockSubscribe('Asset instance created', 'io.kaleido.kat.states.AssetInstanceCreated'); - nockSubscribe('Asset instance batch created', 'io.kaleido.kat.states.AssetInstanceBatchCreated'); - nockSubscribe('Asset instance property set', 'io.kaleido.kat.states.AssetInstancePropertySet'); - nockSubscribe('Described asset instance created', 'io.kaleido.kat.states.DescribedAssetInstanceCreated'); - } - - const { promise } = require('../app'); - ({ app, shutDown } = await promise); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"listen","topic":"dev"}'); - resolve(); - }) - }); - - mockEventStreamWebSocket.emit('open'); - mockDocExchangeSocketIO.emit('connect'); - - await eventPromise; - - if (protocol === 'corda') { - await setupSampleMembersCorda(); - } else { - await setupSampleMembersEthereum(); - } -} - -const setupSampleMembersCorda = async () => { - console.log('Setting up corda members'); - await request(app) - .put('/api/v1/members') - .send({ - address: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - name: 'Test Member 1', - assetTrailInstanceID: 'service-id_1', - app2appDestination: 'kld://app2app_1', - docExchangeDestination: 'kld://docexchange_1' - }); - - await request(app) - .put('/api/v1/members') - .send({ - address: 'CN=Node of node2 for env1, O=Kaleido, L=Raleigh, C=US', - name: 'Test Member 2', - assetTrailInstanceID: 'service-id_2', - app2appDestination: 'kld://app2app_2', - docExchangeDestination: 'kld://docexchange_2' - }); -}; - -const setupSampleMembersEthereum = async () => { - console.log('Setting up ethereum members'); - nock('https://apigateway.kaleido.io') - .post('/registerMember?kld-from=0x0000000000000000000000000000000000000001&kld-sync=true') - .reply(200); - await request(app) - .put('/api/v1/members') - .send({ - address: '0x0000000000000000000000000000000000000001', - name: 'Test Member 1', - app2appDestination: 'kld://app2app_1', - docExchangeDestination: 'kld://docexchange_1' - }) - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const dataMember1: IEventMemberRegistered = { - member: '0x0000000000000000000000000000000000000001', - name: 'Test Member 1', - assetTrailInstanceID: 'service-instance', - app2appDestination: 'kld://app2app_1', - docExchangeDestination: 'kld://docexchange_1', - timestamp: utils.getTimestamp() - } - const dataMember2: IEventMemberRegistered = - { - member: '0x0000000000000000000000000000000000000002', - name: 'Test Member 2', - assetTrailInstanceID: 'service-instance', - app2appDestination: 'kld://app2app_2', - docExchangeDestination: 'kld://docexchange_2', - timestamp: utils.getTimestamp() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.MEMBER_REGISTERED, - data: dataMember1, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }, { - signature: utils.contractEventSignatures.MEMBER_REGISTERED, - data: dataMember2, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; -}; - -export const closeDown = async () => { - shutDown(); - mock.stop('ws'); - mock.stop('socket.io-client'); - nock.restore(); -}; diff --git a/kat/src/test/corda-suite.ts b/kat/src/test/corda-suite.ts deleted file mode 100644 index a7cf5e1893..0000000000 --- a/kat/src/test/corda-suite.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { closeDown, setUp } from "./common"; -import { cordaTests } from "./corda"; - -describe('corda tests', async () => { - before(async () => { - await setUp('corda'); - }); - cordaTests(); - after(async () => { - await closeDown(); - }); -}); \ No newline at end of file diff --git a/kat/src/test/corda/assets/argument-validation.ts b/kat/src/test/corda/assets/argument-validation.ts deleted file mode 100644 index 8212dc0db7..0000000000 --- a/kat/src/test/corda/assets/argument-validation.ts +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app } from '../../common'; -import request from 'supertest'; -import assert from 'assert'; - -export const testAssetArgumentValidation = () => { -describe('Asset definitions - argument validation', async () => { - - it('Attempting to get an asset definition that does not exist should raise an error', async () => { - const result = await request(app) - .get('/api/v1/assets/definitions/1000000') - .expect(404); - assert.deepStrictEqual(result.body, { error: 'Asset definition not found' }); - }); - - it('Attempting to add an asset definition without a name should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset definition name' }); - }); - - it('Attempting to add an asset definition without an author should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset definition author' }); - }); - - it('Attempting to add an asset definition without an valid author name should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - isContentPrivate: false, - isContentUnique: true, - author: "invalid name" - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset definition author' }); - }); - - it('Attempting to add an asset definition without indicating if the content should be private or not should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing asset definition content privacy' }); - }); - - it('Attempting to add an asset definition without a definition id should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - isContentUnique: true, - isContentPrivate: false - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing asset definition id' }); - }); - - it('Attempting to add an asset definition with an invalid index schema should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - assetDefinitionID: 'some-uuid', - isContentPrivate: false, - isContentUnique: true, - indexes: {} - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Indexes do not conform to index schema' }); - }); - - it('Attempting to add an asset definition with an invalid description schema should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - assetDefinitionID: 'some-uuid', - descriptionSchema: 'INVALID', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Invalid description schema' }); - }); - - it('Attempting to add an asset definition with an invalid content schema should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - assetDefinitionID: 'some-uuid', - contentSchema: 'INVALID', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Invalid content schema' }); - }); -}); - -describe('Asset instances - argument validation', async () => { - - it('Attempting to add an asset instance without specifying the author should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') - .send({ - assetDefinitionID: '', - content: {} - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset instance author' }); - }); - - it('Attempting to add an asset instance without specifying the content should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') - .send({ - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset content' }); - }); -}); -}; \ No newline at end of file diff --git a/kat/src/test/corda/assets/authored-private-structured.ts b/kat/src/test/corda/assets/authored-private-structured.ts deleted file mode 100644 index 281ec55aa5..0000000000 --- a/kat/src/test/corda/assets/authored-private-structured.ts +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../common'; -import { v4 as uuidV4 } from 'uuid'; -import { testContent } from '../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IDBAssetDefinition, IDBAssetInstance} from '../../../lib/interfaces'; -import * as utils from '../../../lib/utils'; - -export const testAssetsAuthoredPrivateStructured = () => { - -describe('Assets: authored - structured', async () => { - - let assetDefinitionID = uuidV4(); - const assetDefinitionName = 'authored - private - structured'; - const timestamp = new Date(); - - describe('Create asset definition', () => { - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition') - .reply(200); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'Qmf71q7zspRmzvH6yVhkrpWCnK54rvxyj6XSTJ5tgBiZfV' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - assetDefinitionID, - isContentPrivate: true, - isContentUnique: true, - contentSchema: testContent.schema.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - private - structured'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - }); - - describe('Asset instances', async () => { - - let assetInstanceID: string; - - describe('Asset instances - argument validation', async () => { - it('Attempting to add an asset instance without specifying participants should raise an error', async () => { - const result = await request(app) - .post(`/api/v1/assets/${assetDefinitionID}`) - .send({ - content: { - my_content_string: 'test sample content string', - my_content_number: 124, - my_content_boolean: false - }, - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing asset participants' }); - }); - - it('Attempting to add an asset instance without specifying participants should raise an error', async () => { - const result = await request(app) - .post(`/api/v1/assets/${assetDefinitionID}`) - .send({ - content: { - my_content_string: 'test sample content string', - my_content_number: 124, - my_content_boolean: false - }, - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - participants: ['CN=Node of node3 for env1, O=Kaleido, L=Raleigh, C=US'] - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: `One or more participants are not registered` }); - }); - - it('Attempting to set an asset instance property without specifying key should raise an error', async () => { - const result = await request(app) - .put(`/api/v1/assets/some-asset-def-id/some-asset-id`) - .send({ - action: 'set-property', - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: `Missing asset property key` }); - }); - - it('Attempting to set an asset instance property without specifying value should raise an error', async () => { - const result = await request(app) - .put(`/api/v1/assets/some-asset-def-id/some-asset-id`) - .send({ - action: 'set-property', - key: 'key', - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: `Missing asset property value` }); - }); - }); - - it('Checks that an asset instance can be created', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetInstance') - .reply(200); - - const result = await request(app) - .post(`/api/v1/assets/${assetDefinitionID}`) - .send({ - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - content: testContent.sample.object, - participants: ['CN=Node of node2 for env1, O=Kaleido, L=Raleigh, C=US'] - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetInstanceID = result.body.assetInstanceID; - - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, testContent.sample.docExchangeSha256); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const assetData: any = { - assetDefinitionID: assetDefinitionID, - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - assetInstanceID: assetInstanceID, - contentHash: testContent.sample.docExchangeSha256, - participants: ['CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', 'CN=Node of node2 for env1, O=Kaleido, L=Raleigh, C=US'] - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignaturesCorda.ASSET_INSTANCE_CREATED, - data: {data: assetData}, - stateRef: { - txhash: "25D867CC5D19AB40AE46E6262F3C274A6B772D68A0AA522F4C5A96196EAF5FCE", - index: 0 - }, - subId: "sb-f5abe54b-53fb-4f63-8236-f3a8a6bc1c60", - recordedTime: timestamp.toISOString(), - consumedTime: null - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, testContent.sample.docExchangeSha256); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.timestamp, timestamp.getTime()); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(assetInstance.transactionHash, '25D867CC5D19AB40AE46E6262F3C274A6B772D68A0AA522F4C5A96196EAF5FCE'); - assert.deepStrictEqual(assetInstance.participants, ['CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', 'CN=Node of node2 for env1, O=Kaleido, L=Raleigh, C=US']) - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - it('Checks that the asset instance property can be set', async () => { - nock('https://apigateway.kaleido.io') - .post('/setAssetInstanceProperty') - .reply(200); - - const result = await request(app) - .put(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .send({ - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - action: 'set-property', - key: 'key1', - value: 'value1' - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(assetInstance.properties[assetInstance.author]['key1'].value, 'value1'); - assert.strictEqual(typeof assetInstance.properties[assetInstance.author]['key1'].submitted, 'number'); - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the asset instance set property is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const assetData: any = { - assetDefinitionID: assetDefinitionID, - author: 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', - assetInstanceID: assetInstanceID, - key: 'key1', - value: 'value1', - participants: ['CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', 'CN=Node of node2 for env1, O=Kaleido, L=Raleigh, C=US'] - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignaturesCorda.ASSET_PROPERTY_SET, - data: {data: assetData}, - stateRef: { - txhash: "35D867CC5D19AB40AE46E6262F3C274A6B772D68A0AA522F4C5A96196EAF5FCE", - index: 0 - }, - subId: "sb-f5abe54b-53fb-4f63-8236-f3a8a6bc1c60", - recordedTime: timestamp.toISOString(), - consumedTime: null - }])); - await eventPromise; - }); - - it('Checks that the asset instance property set is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, 'CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, testContent.sample.docExchangeSha256); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.timestamp, timestamp.getTime()); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(assetInstance.transactionHash, '25D867CC5D19AB40AE46E6262F3C274A6B772D68A0AA522F4C5A96196EAF5FCE'); - assert.deepStrictEqual(assetInstance.participants, ['CN=Node of node1 for env1, O=Kaleido, L=Raleigh, C=US', 'CN=Node of node2 for env1, O=Kaleido, L=Raleigh, C=US']) - assert.strictEqual(assetInstance.properties[assetInstance.author]['key1'].value, 'value1'); - assert.strictEqual(typeof assetInstance.properties[assetInstance.author]['key1'].submitted, 'number'); - assert.strictEqual(assetInstance.properties[assetInstance.author]['key1'].history[assetInstance.timestamp].transactionHash, '35D867CC5D19AB40AE46E6262F3C274A6B772D68A0AA522F4C5A96196EAF5FCE'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/corda/assets/index.ts b/kat/src/test/corda/assets/index.ts deleted file mode 100644 index 61561f04d7..0000000000 --- a/kat/src/test/corda/assets/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testAssetArgumentValidation } from "./argument-validation"; -import { testAssetsAuthoredPrivateStructured } from "./authored-private-structured"; - -export const testAssets = async () => { - describe('Asset tests', async () => { - testAssetArgumentValidation(); - testAssetsAuthoredPrivateStructured(); - }); -}; \ No newline at end of file diff --git a/kat/src/test/corda/index.ts b/kat/src/test/corda/index.ts deleted file mode 100644 index f4f4743ab7..0000000000 --- a/kat/src/test/corda/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testAssets } from './assets'; -import { testMembers } from './members'; - - -export const cordaTests = async () => { - testAssets(); - testMembers(); -}; \ No newline at end of file diff --git a/kat/src/test/corda/members/argument-validation.ts b/kat/src/test/corda/members/argument-validation.ts deleted file mode 100644 index 83f433f928..0000000000 --- a/kat/src/test/corda/members/argument-validation.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app } from '../../common'; -import request from 'supertest'; -import assert from 'assert'; - -export const testMembersArgumentValidation = async () => { - -describe('Members - argument validation', async () => { - - it('Attempting to add a member without an address should raise an error', async () => { - const result = await request(app) - .put('/api/v1/members') - .send({ - name: 'Member A', - app2appDestination: 'kld://app2app', - docExchangeDestination: 'kld://docexchange' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing member address' }); - }); - - it('Attempting to add a member without a name should raise an error', async () => { - const result = await request(app) - .put('/api/v1/members') - .send({ - address: '0x0000000000000000000000000000000000000001', - app2appDestination: 'kld://app2app', - docExchangeDestination: 'kld://docexchange' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing member name' }); - }); - - it('Attempting to add a member without a assetTrailInstanceID should raise an error', async () => { - const result = await request(app) - .put('/api/v1/members') - .send({ - name: 'Member A', - address: '0x0000000000000000000000000000000000000001', - app2appDestination: 'kld://app2app', - docExchangeDestination: 'kld://docexchange' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing member assetTrailInstanceID' }); - }); - - it('Attempting to add a member without a docExchangeDestination should raise an error', async () => { - const result = await request(app) - .put('/api/v1/members') - .send({ - name: 'Member A', - address: '0x0000000000000000000000000000000000000001', - app2appDestination: 'kld://app2app', - assetTrailInstanceID: 'asset-instance-a' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing member docExchangeDestination' }); - }); - - it('Attempting to add a member without a app2appDestination should raise an error', async () => { - const result = await request(app) - .put('/api/v1/members') - .send({ - name: 'Member A', - address: '0x0000000000000000000000000000000000000001', - assetTrailInstanceID: 'asset-instance-a', - docExchangeDestination: 'kld://docexchange' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing member app2appDestination' }); - }); - - it('Attempting to get a member that does not exist should raise an error', async () => { - const result = await request(app) - .get('/api/v1/members/0x0000000000000000000000000000000000000099') - .expect(404); - assert.deepStrictEqual(result.body, { error: 'Member not found' }); - }); - -}); -}; diff --git a/kat/src/test/corda/members/index.ts b/kat/src/test/corda/members/index.ts deleted file mode 100644 index 879158fadc..0000000000 --- a/kat/src/test/corda/members/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testMembersArgumentValidation } from "./argument-validation"; -import { testMemberRegistration } from "./registration"; - -export const testMembers = async () => { - describe('Member tests', async () => { - testMembersArgumentValidation(); - testMemberRegistration(); - }); -}; \ No newline at end of file diff --git a/kat/src/test/corda/members/registration.ts b/kat/src/test/corda/members/registration.ts deleted file mode 100644 index edba546421..0000000000 --- a/kat/src/test/corda/members/registration.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app } from '../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IDBMember } from '../../../lib/interfaces'; - -export const testMemberRegistration = async () => { - -describe('Members - registration', async () => { - it('Checks that adding a member sends a request to API Gateway and updates the database', async () => { - - nock('https://apigateway.kaleido.io') - .post('/registerMember') - .reply(200); - const addMemberResponse = await request(app) - .put('/api/v1/members') - .send({ - address: 'CN=Node of member1 for env1, O=Kaleido, L=Raleigh, C=US', - name: 'Member 1', - assetTrailInstanceID: 'service-id', - app2appDestination: 'kld://app2app/internal', - docExchangeDestination:'kld://docstore/dest' - }) - .expect(200); - assert.deepStrictEqual(addMemberResponse.body, { status: 'submitted' }); - - const getMemberResponse = await request(app) - .get('/api/v1/members') - .expect(200); - const member = getMemberResponse.body.find((member: IDBMember) => member.address === 'CN=Node of member1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(member.address, 'CN=Node of member1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(member.name, 'Member 1'); - assert.strictEqual(member.assetTrailInstanceID, 'service-id'); - assert.strictEqual(member.app2appDestination, 'kld://app2app/internal'); - assert.strictEqual(member.docExchangeDestination, 'kld://docstore/dest'); - assert.strictEqual(typeof member.submitted, 'number'); - - const getMemberByAddressResponse = await request(app) - .get('/api/v1/members/CN=Node of member1 for env1, O=Kaleido, L=Raleigh, C=US') - .expect(200); - assert.deepStrictEqual(member, getMemberByAddressResponse.body); - }); - - it('Get member should return the confirmed member', async () => { - const getMemberResponse = await request(app) - .get('/api/v1/members') - .expect(200); - const member = getMemberResponse.body.find((member: IDBMember) => member.address === 'CN=Node of member1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(member.address, 'CN=Node of member1 for env1, O=Kaleido, L=Raleigh, C=US'); - assert.strictEqual(member.name, 'Member 1'); - assert.strictEqual(member.assetTrailInstanceID, 'service-id'); - assert.strictEqual(member.app2appDestination, 'kld://app2app/internal'); - assert.strictEqual(member.docExchangeDestination, 'kld://docstore/dest'); - - const getMemberByAddressResponse = await request(app) - .get('/api/v1/members/CN=Node of member1 for env1, O=Kaleido, L=Raleigh, C=US') - .expect(200); - assert.deepStrictEqual(member, getMemberByAddressResponse.body); - }); - -}); -}; diff --git a/kat/src/test/ethereum-suite.ts b/kat/src/test/ethereum-suite.ts deleted file mode 100644 index f735c71f93..0000000000 --- a/kat/src/test/ethereum-suite.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { closeDown, setUp } from "./common"; -import { ethereumTests } from "./ethereum"; -describe('ethereum tests for initial bootstrap', async () => { - before(async () => { - await setUp('ethereum'); - }); - ethereumTests(); - after(async () => { - await closeDown(); - }); -}); diff --git a/kat/src/test/ethereum/assets/argument-validation.ts b/kat/src/test/ethereum/assets/argument-validation.ts deleted file mode 100644 index f3760b4595..0000000000 --- a/kat/src/test/ethereum/assets/argument-validation.ts +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app } from '../../common'; -import request from 'supertest'; -import assert from 'assert'; - -export const testAssetArgumentValidation = () => { -describe('Asset definitions - argument validation', async () => { - it('Attempting to get an asset definition that does not exist should raise an error', async () => { - const result = await request(app) - .get('/api/v1/assets/definitions/1000000') - .expect(404); - assert.deepStrictEqual(result.body, { error: 'Asset definition not found' }); - }); - - it('Attempting to add an asset definition without a name should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset definition name' }); - }); - - it('Attempting to add an asset definition without an author should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset definition author' }); - }); - - it('Attempting to add an asset definition with an invalid index schema should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: false, - isContentUnique: true, - indexes: {} - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Indexes do not conform to index schema' }); - }); - - it('Attempting to add an asset definition without indicating if the content should be private or not should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: '0x0000000000000000000000000000000000000001', - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing asset definition content privacy' }); - }); - - it('Attempting to add an asset definition with an invalid description schema should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: '0x0000000000000000000000000000000000000001', - descriptionSchema: 'INVALID', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Invalid description schema' }); - }); - - it('Attempting to add an asset definition with an invalid content schema should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: 'My asset definition', - author: '0x0000000000000000000000000000000000000001', - contentSchema: 'INVALID', - isContentPrivate: false, - isContentUnique: true - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Invalid content schema' }); - }); - -}); - -describe('Asset instances - argument validation', async () => { - - it('Attempting to add an asset instance without specifying the author should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') - .send({ - assetDefinitionID: '', - content: {} - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset instance author' }); - }); - - it('Attempting to add an asset instance without specifying the content should raise an error', async () => { - const result = await request(app) - .post('/api/v1/assets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') - .send({ - author: '0x0000000000000000000000000000000000000001' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid asset content' }); - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/authored/private/described-structured.ts b/kat/src/test/ethereum/assets/authored/private/described-structured.ts deleted file mode 100644 index 067470d225..0000000000 --- a/kat/src/test/ethereum/assets/authored/private/described-structured.ts +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import assert from 'assert'; -import { createHash, randomBytes } from 'crypto'; -import nock from 'nock'; -import request from 'supertest'; -import { promisify } from 'util'; -import { IDBAssetDefinition, IDBAssetInstance, IEventAssetDefinitionCreated, IEventAssetInstanceBatchCreated } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testContent, testDescription, testIndexes } from '../../../../samples'; -const delay = promisify(setTimeout); - -export const testAuthoredPrivateDescribedStructured = () => { -describe('Assets: authored - private - described - structured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - private - described - structured'; - const timestamp = utils.getTimestamp(); - const batchHashSha256 = '0x' + createHash('sha256').update(randomBytes(10)).digest().toString('hex'); - const batchHashIPFSMulti = utils.sha256ToIPFSHash(batchHashSha256); - - let batchMaxRecordsToRestore: number; - beforeEach(() => { - nock.cleanAll(); - // Force batches to close immediately - batchMaxRecordsToRestore = utils.constants.BATCH_MAX_RECORDS; - utils.constants.BATCH_MAX_RECORDS = 1; - }); - - afterEach(() => { - assert.deepStrictEqual(nock.pendingMocks(), []); - utils.constants.BATCH_MAX_RECORDS = batchMaxRecordsToRestore; - }); - - describe('Create asset definition', () => { - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'QmQX1g8GwrMuACuMfQmKXzeYd7yXMXPpcUaFMqLUzSv1yL' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: true, - isContentUnique: true, - descriptionSchema: testDescription.schema.object, - contentSchema: testContent.schema.object, - indexes: testIndexes - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - described - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - private - described - structured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmQX1g8GwrMuACuMfQmKXzeYd7yXMXPpcUaFMqLUzSv1yL') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: true, - isContentUnique: true, - descriptionSchema: testDescription.schema.object, - contentSchema: testContent.schema.object - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0x205ee35b47f713845ea616c805e346cb90e9de82e56069f0de318c59e57d867b', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - described - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.name, 'authored - private - described - structured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - describe('Asset instances', async () => { - - let assetInstanceID: string; - - it('Checks that an asset instance can be created, within a batch', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetInstanceBatch?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: batchHashIPFSMulti }) - - const result = await request(app) - .post(`/api/v1/assets/${assetDefinitionID}`) - .send({ - author: '0x0000000000000000000000000000000000000001', - description: testDescription.sample.object, - content: testContent.sample.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetInstanceID = result.body.assetInstanceID; - - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.deepStrictEqual(assetInstance.description, testDescription.sample.object); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(typeof assetInstance.batchID, 'string'); - - // Expect the batch to have been submitted - let getBatchResponse: any; - for (let i = 0; i < 10; i++) { - getBatchResponse = await request(app) - .get(`/api/v1/batches/${assetInstance.batchID}`) - .expect(200); - if (getBatchResponse.body.completed) break; - await delay(1); - } - assert.strictEqual(typeof getBatchResponse.body.completed, 'number'); - assert.strictEqual(typeof getBatchResponse.body.batchHash, 'string'); - assert.strictEqual(getBatchResponse.body.receipt, 'my-receipt-id'); - assert.strictEqual(getBatchResponse.body.batchHash, batchHashSha256); - // As this is a private asset, the content must not have been written to IPFS in the batch - assert.strictEqual(getBatchResponse.body.records[0].content, undefined); - // The full description payload should be in the batch data written to IPFS in the batch - assert.deepStrictEqual(getBatchResponse.body.records[0].description, testDescription.sample.object); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceBatchCreated = { - author: '0x0000000000000000000000000000000000000001', - batchHash: batchHashSha256, - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_INSTANCE_BATCH_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.deepStrictEqual(assetInstance.description, testDescription.sample.object); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(assetInstance.receipt, undefined); // the batch has the receipt - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - it('sets a property on an asset, which will be public and batched even though the asset is private', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetInstanceBatch?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: batchHashIPFSMulti }) - - const {body: result} = await request(app) - .put(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .send({ - action: 'set-property', - key: 'key', - value: 'value', - author: '0x0000000000000000000000000000000000000001', - }) - .expect(200); - assert.deepStrictEqual(result.status, 'submitted'); - - const {body: asset} = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - const prop = asset.properties['0x0000000000000000000000000000000000000001']['key']; - assert.strictEqual(prop.value, 'value'); - assert(prop.submitted > 0); - const {batchID} = prop; - - // Expect the batch to have been submitted - let getBatchResponse: any; - for (let i = 0; i < 10; i++) { - getBatchResponse = await request(app) - .get(`/api/v1/batches/${batchID}`) - .expect(200); - if (getBatchResponse.body.completed) break; - await delay(1); - } - - assert.strictEqual(typeof getBatchResponse.body.completed, 'number'); - assert.strictEqual(typeof getBatchResponse.body.batchHash, 'string'); - assert.strictEqual(getBatchResponse.body.receipt, 'my-receipt-id'); - assert.strictEqual(getBatchResponse.body.batchHash, batchHashSha256); - // As properties are always public, the full content will have been written to IPFS in the batch - assert.deepStrictEqual(getBatchResponse.body.records[0].recordType, 'property'); - assert.deepStrictEqual(getBatchResponse.body.records[0].key, 'key'); - assert.deepStrictEqual(getBatchResponse.body.records[0].value, 'value'); - - }); - - }); - -}); - -}; diff --git a/kat/src/test/ethereum/assets/authored/private/described-unstructured.ts b/kat/src/test/ethereum/assets/authored/private/described-unstructured.ts deleted file mode 100644 index 2a07f14a42..0000000000 --- a/kat/src/test/ethereum/assets/authored/private/described-unstructured.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IDBAssetDefinition, IEventAssetDefinitionCreated } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; -import { testDescription } from '../../../../samples'; - -export const testAuthoredPrivateDescribedUnstructured = () => { - -describe('Assets: authored - private - described - unstructured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - private - described - unstructured'; - - describe('Create asset definition', () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'QmdkH21EiyQXrgo2sPKtSijtvfYBKqQedBxB7W4RJ4m6jo' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: true, - isContentUnique: true, - descriptionSchema: testDescription.schema.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - described - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.strictEqual(assetDefinition.name, 'authored - private - described - unstructured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmdkH21EiyQXrgo2sPKtSijtvfYBKqQedBxB7W4RJ4m6jo') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: true, - isContentUnique: true, - descriptionSchema: testDescription.schema.object - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0xe4ecb77d78de507f68f5b410e0972cd5c05959ec1de041bb56bde25277786f96', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - described - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - private - described - unstructured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); - -}; diff --git a/kat/src/test/ethereum/assets/authored/private/structured.ts b/kat/src/test/ethereum/assets/authored/private/structured.ts deleted file mode 100644 index e9d7682d71..0000000000 --- a/kat/src/test/ethereum/assets/authored/private/structured.ts +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testContent } from '../../../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IDBAssetDefinition, IDBAssetInstance, IEventAssetDefinitionCreated, IEventAssetInstanceCreated } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testAuthoredPrivateStructured = () => { - -describe('Assets: authored - structured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - private - structured'; - const timestamp = utils.getTimestamp(); - - describe('Create asset definition', () => { - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'Qmf71q7zspRmzvH6yVhkrpWCnK54rvxyj6XSTJ5tgBiZfV' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: true, - isContentUnique: true, - contentSchema: testContent.schema.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - private - structured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - - nock('https://ipfs.kaleido.io') - .get('/ipfs/Qmf71q7zspRmzvH6yVhkrpWCnK54rvxyj6XSTJ5tgBiZfV') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: true, - isContentUnique: true, - contentSchema: testContent.schema.object - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0xf9186d8e20d9e6786aa5e99e8c83be79ef719ddb1482ddcdf3dccf98bf24cd60', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - private - structured'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - describe('Asset instances', async () => { - - let assetInstanceID: string; - - it('Checks that an asset instance can be created', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetInstance?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - const result = await request(app) - .post(`/api/v1/assets/${assetDefinitionID}`) - .send({ - author: '0x0000000000000000000000000000000000000001', - content: testContent.sample.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetInstanceID = result.body.assetInstanceID; - - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, testContent.sample.docExchangeSha256); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - // assert.strictEqual(assetInstance.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceCreated = { - assetDefinitionID: utils.uuidToHex(assetDefinitionID), - author: '0x0000000000000000000000000000000000000001', - assetInstanceID: utils.uuidToHex(assetInstanceID), - contentHash: testContent.sample.docExchangeSha256, - timestamp: timestamp.toString(), - isContentPrivate: true - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, testContent.sample.docExchangeSha256); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/authored/private/unstructured.ts b/kat/src/test/ethereum/assets/authored/private/unstructured.ts deleted file mode 100644 index d0428fc007..0000000000 --- a/kat/src/test/ethereum/assets/authored/private/unstructured.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testAuthoredPrivateUnstructured = () => { - -describe('Assets: authored - private - unstructured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - private - unstructured'; - - describe('Create private asset definition', async () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'QmZCxXPtG4t9QR5VWWrGsPzc5S7cY3dHmwGSwkobcdavpb' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: true, - isContentUnique: true, - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.strictEqual(assetDefinition.name, 'authored - private - unstructured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmZCxXPtG4t9QR5VWWrGsPzc5S7cY3dHmwGSwkobcdavpb') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: true, - isContentUnique: true, - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0xa1780db691781aa3c276e32b24ca552eb08aef5af4671c65426653fda2996804', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - private - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.strictEqual(assetDefinition.name, 'authored - private - unstructured'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/authored/public/described-structured.ts b/kat/src/test/ethereum/assets/authored/public/described-structured.ts deleted file mode 100644 index b7f3115fc2..0000000000 --- a/kat/src/test/ethereum/assets/authored/public/described-structured.ts +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import assert from 'assert'; -import { createHash, randomBytes } from 'crypto'; -import nock from 'nock'; -import request from 'supertest'; -import { promisify } from 'util'; -import { IDBAssetDefinition, IDBAssetInstance, IEventAssetDefinitionCreated, IEventAssetInstanceBatchCreated } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testContent, testDescription } from '../../../../samples'; -const delay = promisify(setTimeout); - -export const testAuthoredPublicDescribedStructured = () => { - -describe('Assets: authored - public - described - structured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - public - described - structured'; - const timestamp = utils.getTimestamp(); - const batchHashSha256 = '0x' + createHash('sha256').update(randomBytes(10)).digest().toString('hex'); - const batchHashIPFSMulti = utils.sha256ToIPFSHash(batchHashSha256); - - let batchMaxRecordsToRestore: number; - beforeEach(() => { - nock.cleanAll(); - // Force batches to close immediately - batchMaxRecordsToRestore = utils.constants.BATCH_MAX_RECORDS; - utils.constants.BATCH_MAX_RECORDS = 1; - }); - - afterEach(() => { - assert.deepStrictEqual(nock.pendingMocks(), []); - utils.constants.BATCH_MAX_RECORDS = batchMaxRecordsToRestore; - }); - - describe('Create asset definition', () => { - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'QmPsTQxwhQjJsCFh3hBijCC4gGReQpEz5VjcCK7gu6sYXX' }) - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: false, - isContentUnique: true, - descriptionSchema: testDescription.schema.object, - contentSchema: testContent.schema.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - described - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - public - described - structured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmPsTQxwhQjJsCFh3hBijCC4gGReQpEz5VjcCK7gu6sYXX') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: false, - isContentUnique: true, - descriptionSchema: testDescription.schema.object, - contentSchema: testContent.schema.object - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0x16bfeb8bb4befe63cc90578fe6fa0f4ef56955c462cd57965279effc69df1ea6', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - described - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - public - described - structured'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - describe('Asset instances', async () => { - - let assetInstanceID: string; - - it('Checks that an asset instance can be created', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetInstanceBatch?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: batchHashIPFSMulti }) - - const result = await request(app) - .post(`/api/v1/assets/${assetDefinitionID}`) - .send({ - author: '0x0000000000000000000000000000000000000001', - description: testDescription.sample.object, - content: testContent.sample.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetInstanceID = result.body.assetInstanceID; - - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.descriptionHash, '0x' + utils.getSha256(JSON.stringify(testDescription.sample.object))); - assert.deepStrictEqual(assetInstance.description, testDescription.sample.object); - assert.strictEqual(assetInstance.contentHash,'0x' + utils.getSha256(JSON.stringify(testContent.sample.object))); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.receipt, undefined); // As this has been batched - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(typeof assetInstance.batchID, 'string'); - - // Expect the batch to have been submitted - let getBatchResponse: any; - for (let i = 0; i < 10; i++) { - getBatchResponse = await request(app) - .get(`/api/v1/batches/${assetInstance.batchID}`) - .expect(200); - if (getBatchResponse.body.completed) break; - await delay(1); - } - assert.strictEqual(typeof getBatchResponse.body.completed, 'number'); - assert.strictEqual(typeof getBatchResponse.body.batchHash, 'string'); - assert.strictEqual(getBatchResponse.body.receipt, 'my-receipt-id'); - assert.strictEqual(getBatchResponse.body.batchHash, batchHashSha256); - // As this is a public asset, the full content will have been written to IPFS in the batch - assert.deepStrictEqual(getBatchResponse.body.records[0].content, testContent.sample.object); - // The full description payload should be in the batch data written to IPFS in the batch - assert.deepStrictEqual(getBatchResponse.body.records[0].description, testDescription.sample.object); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceBatchCreated = { - author: '0x0000000000000000000000000000000000000001', - batchHash: batchHashSha256, - timestamp: timestamp.toString() - }; - - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_INSTANCE_BATCH_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.descriptionHash, '0x' + utils.getSha256(JSON.stringify(testDescription.sample.object))); - assert.deepStrictEqual(assetInstance.description, testDescription.sample.object); - assert.strictEqual(assetInstance.contentHash, '0x' + utils.getSha256(JSON.stringify(testContent.sample.object))); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.receipt, undefined); // the batch has the receipt - assert.strictEqual(typeof assetInstance.timestamp, 'number'); - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/authored/public/described-unstructured.ts b/kat/src/test/ethereum/assets/authored/public/described-unstructured.ts deleted file mode 100644 index 80914c56cf..0000000000 --- a/kat/src/test/ethereum/assets/authored/public/described-unstructured.ts +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IDBAssetDefinition, IEventAssetDefinitionCreated } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; -import { testDescription } from '../../../../samples'; - -export const testAuthoredPublicDescribedUnstructured = () => { - -describe('Asset definitions: authored - public - described - unstructured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - public - described - unstructured'; - - describe('Create asset definition', () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'QmedsLGSbtuo3GsNjh2F4u2nDpNyjVSTkYGet9KtG1WCY5' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: false, - isContentUnique: true, - descriptionSchema: testDescription.schema.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - described - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - public - described - unstructured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmedsLGSbtuo3GsNjh2F4u2nDpNyjVSTkYGet9KtG1WCY5') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: false, - isContentUnique: true, - descriptionSchema: testDescription.schema.object - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0xf22423517fb1783b6b1e913f3915fa3215396000412d3420204b1394ab31e03e', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - described - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - public - described - unstructured'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/authored/public/structured.ts b/kat/src/test/ethereum/assets/authored/public/structured.ts deleted file mode 100644 index 5f0005a44b..0000000000 --- a/kat/src/test/ethereum/assets/authored/public/structured.ts +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import assert from 'assert'; -import { createHash, randomBytes } from 'crypto'; -import nock from 'nock'; -import request from 'supertest'; -import { promisify } from 'util'; -import { IDBAssetDefinition, IDBAssetInstance, IEventAssetDefinitionCreated, IEventAssetInstanceBatchCreated } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testContent } from '../../../../samples'; -const delay = promisify(setTimeout); - -export const testAuthoredPublicStructured = () => { - -describe('Assets: authored - public - structured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - public - structured'; - const timestamp = utils.getTimestamp(); - const batchHashSha256 = '0x' + createHash('sha256').update(randomBytes(10)).digest().toString('hex'); - const batchHashIPFSMulti = utils.sha256ToIPFSHash(batchHashSha256); - - let batchMaxRecordsToRestore: number; - beforeEach(() => { - nock.cleanAll(); - // Force batches to close immediately - batchMaxRecordsToRestore = utils.constants.BATCH_MAX_RECORDS; - utils.constants.BATCH_MAX_RECORDS = 1; - }); - - afterEach(() => { - assert.deepStrictEqual(nock.pendingMocks(), []); - utils.constants.BATCH_MAX_RECORDS = batchMaxRecordsToRestore; - }); - - describe('Create asset definition', () => { - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'QmV85fRf9jng5zhcSC4Zef2dy8ypouazgckRz4GhA5cUgw' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: false, - isContentUnique: true, - contentSchema: testContent.schema.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - public - structured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmV85fRf9jng5zhcSC4Zef2dy8ypouazgckRz4GhA5cUgw') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: false, - isContentUnique: true, - contentSchema: testContent.schema.object - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0x64c97929fb90da1b94d560a29d8522c77b6c662588abb6ad23f1a0377250a2b0', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'authored - public - structured'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - - describe('Asset instances', async () => { - - let assetInstanceID: string; - - it('Checks that an asset instance can be created', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetInstanceBatch?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: batchHashIPFSMulti }) - - const result = await request(app) - .post(`/api/v1/assets/${assetDefinitionID}`) - .send({ - assetDefinitionID, - author: '0x0000000000000000000000000000000000000001', - content: testContent.sample.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetInstanceID = result.body.assetInstanceID; - - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash,'0x' + utils.getSha256(JSON.stringify(testContent.sample.object))); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.receipt, undefined); // As this has been batched - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(typeof assetInstance.batchID, 'string'); - - // Expect the batch to have been submitted - let getBatchResponse: any; - for (let i = 0; i < 10; i++) { - getBatchResponse = await request(app) - .get(`/api/v1/batches/${assetInstance.batchID}`) - .expect(200); - if (getBatchResponse.body.completed) break; - await delay(1); - } - assert.strictEqual(typeof getBatchResponse.body.completed, 'number'); - assert.strictEqual(typeof getBatchResponse.body.batchHash, 'string'); - assert.strictEqual(getBatchResponse.body.receipt, 'my-receipt-id'); - assert.strictEqual(getBatchResponse.body.batchHash, batchHashSha256); - // As this is a public asset, the full content will have been written to IPFS in the batch - assert.deepStrictEqual(getBatchResponse.body.records[0].content, testContent.sample.object); - // There is no description, as this is not a described asset type - assert.deepStrictEqual(getBatchResponse.body.records[0].description, undefined); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceBatchCreated = { - author: '0x0000000000000000000000000000000000000001', - batchHash: batchHashSha256, - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_INSTANCE_BATCH_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, '0x' + utils.getSha256(JSON.stringify(testContent.sample.object))); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.receipt, undefined); // the batch has the receipt - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(typeof assetInstance.submitted, 'number'); - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); - -}; diff --git a/kat/src/test/ethereum/assets/authored/public/unstructured.ts b/kat/src/test/ethereum/assets/authored/public/unstructured.ts deleted file mode 100644 index b9b3c9be41..0000000000 --- a/kat/src/test/ethereum/assets/authored/public/unstructured.ts +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testAuthoredPublicUnstructured = () => { - -describe('Assets: authored - unstructured', async () => { - - let assetDefinitionID: string; - const assetDefinitionName = 'authored - public - unstructured'; - - describe('Create asset definition', async () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the asset definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createAssetDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: 'QmW9kyL5Dd1NxZGGMPYHYydjNp7bwchjMReMsYsrZyZMNr' }); - - const result = await request(app) - .post('/api/v1/assets/definitions') - .send({ - name: assetDefinitionName, - author: '0x0000000000000000000000000000000000000001', - isContentPrivate: false, - isContentUnique: true, - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - assetDefinitionID = result.body.assetDefinitionID; - - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.strictEqual(assetDefinition.name, 'authored - public - unstructured'); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmW9kyL5Dd1NxZGGMPYHYydjNp7bwchjMReMsYsrZyZMNr') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: assetDefinitionName, - isContentPrivate: false, - isContentUnique: true - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000001', - assetDefinitionHash: '0x741330003d1780fb3aec3c569011c4c7d133a99b95a276f45c73b1a30395ea83', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'authored - public - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.isContentUnique, true); - assert.strictEqual(assetDefinition.name, 'authored - public - unstructured'); - assert.strictEqual(typeof assetDefinition.submitted, 'number'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); -}; \ No newline at end of file diff --git a/kat/src/test/ethereum/assets/index.ts b/kat/src/test/ethereum/assets/index.ts deleted file mode 100644 index 0eb9af1e68..0000000000 --- a/kat/src/test/ethereum/assets/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testAssetArgumentValidation } from "./argument-validation"; -import { testAuthoredPrivateDescribedStructured } from "./authored/private/described-structured"; -import { testAuthoredPrivateDescribedUnstructured } from "./authored/private/described-unstructured"; -import { testAuthoredPrivateStructured } from "./authored/private/structured"; -import { testAuthoredPrivateUnstructured } from "./authored/private/unstructured"; -import { testAuthoredPublicDescribedStructured } from "./authored/public/described-structured"; -import { testAuthoredPublicDescribedUnstructured } from "./authored/public/described-unstructured"; -import { testAuthoredPublicStructured } from "./authored/public/structured"; -import { testAuthoredPublicUnstructured } from "./authored/public/unstructured"; -import { testUnauthoredPrivateDescribedStructured } from "./unauthored/private/described-structured"; -import { testUnauthoredPrivateDescribedUnstructured } from "./unauthored/private/described-unstructured"; -import { testUnauthoredPrivateStructured } from "./unauthored/private/structured"; -import { testUnauthoredPrivateUnstructured } from "./unauthored/private/unstructured"; -import { testUnauthoredPublicDescribedStructured } from "./unauthored/public/described-structured"; -import { testUnauthoredPublicDescribedUnstructured } from "./unauthored/public/described-unstructured"; -import { testUnauthoredPublicStructured } from "./unauthored/public/structured"; -import { testUnauthoredPublicUnstructured } from "./unauthored/public/unstructured"; - -export const testAssets = async () => { - describe('Asset tests', async () => { - testAssetArgumentValidation(); - testAuthoredPrivateDescribedStructured(); - testAuthoredPrivateDescribedUnstructured(); - testAuthoredPrivateStructured(); - testAuthoredPrivateUnstructured(); - testAuthoredPublicDescribedStructured(); - testAuthoredPublicDescribedUnstructured(); - testAuthoredPublicStructured(); - testAuthoredPublicUnstructured(); - testUnauthoredPrivateDescribedStructured(); - testUnauthoredPrivateDescribedUnstructured(); - testUnauthoredPrivateStructured(); - testUnauthoredPrivateUnstructured(); - testUnauthoredPublicDescribedStructured(); - testUnauthoredPublicDescribedUnstructured(); - testUnauthoredPublicStructured(); - testUnauthoredPublicUnstructured(); - }); -}; \ No newline at end of file diff --git a/kat/src/test/ethereum/assets/unauthored/private/described-structured.ts b/kat/src/test/ethereum/assets/unauthored/private/described-structured.ts deleted file mode 100644 index ec64e305a5..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/private/described-structured.ts +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import assert from 'assert'; -import { createHash, randomBytes } from 'crypto'; -import nock from 'nock'; -import request from 'supertest'; -import { v4 as uuidV4 } from 'uuid'; -import { IDBAssetDefinition, IDBAssetInstance, IDBBatch, IEventAssetDefinitionCreated, IEventAssetInstanceBatchCreated, BatchRecordType } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testContent, testDescription } from '../../../../samples'; - -export const testUnauthoredPrivateDescribedStructured = () => { - -describe('Assets: unauthored - private - described - structured', async () => { - - const assetDefinitionID = '9ac02d4e-e0a3-4e9e-9d97-be0a41595425'; - const timestamp = utils.getTimestamp(); - - let batchMaxRecordsToRestore: number; - beforeEach(() => { - nock.cleanAll(); - // Force batches to close immediately - batchMaxRecordsToRestore = utils.constants.BATCH_MAX_RECORDS; - utils.constants.BATCH_MAX_RECORDS = 1; - }); - - afterEach(() => { - assert.deepStrictEqual(nock.pendingMocks(), []); - utils.constants.BATCH_MAX_RECORDS = batchMaxRecordsToRestore; - }); - - describe('Asset definition', async () => { - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmbGq3hw6r5C3yUkGvqJHq7c67pyMHXxQJ6XBHUh9kf5Ex') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: 'unauthored - private - described - structured', - isContentPrivate: true, - isContentUnique: true, - descriptionSchema: testDescription.schema.object, - contentSchema: testContent.schema.object - }); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0xc02d4bc2c162538e1ca45d20c472b608520d99d6d3988383251d626b8a3411d9', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - private - described - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.deepStrictEqual(assetDefinition.contentSchema,testContent.schema.object); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.name, 'unauthored - private - described - structured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - describe('Asset instances', async () => { - - const assetInstanceID = 'e07f4682-71d1-4a76-8084-8adbc97ef99c'; - const batchHashSha256 = '0x' + createHash('sha256').update(randomBytes(10)).digest().toString('hex'); - const batchHashIPFSMulti = utils.sha256ToIPFSHash(batchHashSha256); - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - - const testBatch: IDBBatch = { - author: '0x0000000000000000000000000000000000000002', - batchID: uuidV4(), - completed: Date.now() - 100, - created: Date.now() - 200, - type: 'asset-instances', - records: [{ - recordType: BatchRecordType.assetInstance, - assetDefinitionID, - assetInstanceID, - author: '0x0000000000000000000000000000000000000002', - description: testDescription.sample.object, - descriptionHash: '0x' + utils.getSha256(JSON.stringify(testDescription.sample.object)), - contentHash: '0x' + utils.getSha256(JSON.stringify(testContent.sample.object)), - isContentPrivate: true - }], - }; - - nock('https://ipfs.kaleido.io') - .get(`/ipfs/${batchHashIPFSMulti}`) - .reply(200, testBatch) - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceBatchCreated = { - batchHash: batchHashSha256, - author: '0x0000000000000000000000000000000000000002', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_INSTANCE_BATCH_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.descriptionHash, '0x' + utils.getSha256(JSON.stringify(testDescription.sample.object))); - assert.deepStrictEqual(assetInstance.description, testDescription.sample.object); - assert.strictEqual(assetInstance.contentHash, '0x' + utils.getSha256(JSON.stringify(testContent.sample.object))); - assert.deepStrictEqual(assetInstance.content, undefined); - assert.strictEqual(assetInstance.submitted, undefined); - assert.strictEqual(assetInstance.receipt, undefined); - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/unauthored/private/described-unstructured.ts b/kat/src/test/ethereum/assets/unauthored/private/described-unstructured.ts deleted file mode 100644 index 09b3af1c64..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/private/described-unstructured.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; -import { testDescription } from '../../../../samples'; - -export const testUnauthoredPrivateDescribedUnstructured = () => { - -describe('Assets: unauthored - private - described - unstructured', async () => { - - const assetDefinitionID = '0989dfe5-4b4c-43e0-91fc-3fea194a7c56'; - - describe('Asset definition', async () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmbUq3WLWyrXcEtBH8LTpiq3Usg5mkWb4kAGPigg4a7YKU') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: 'unauthored - private - described - unstructured', - isContentPrivate: true, - isContentUnique: true, - descriptionSchema: testDescription.schema.object, - }); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0xc34043022814b07d606980cbd6d605952943faddadac9f32419ee33c2bdf0eab', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - private - described - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.name, 'unauthored - private - described - unstructured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/unauthored/private/structured.ts b/kat/src/test/ethereum/assets/unauthored/private/structured.ts deleted file mode 100644 index 3d9e2d845b..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/private/structured.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testContent } from '../../../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition, IEventAssetInstanceCreated, IDBAssetInstance } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testUnauthoredPrivateStructured = () => { - -describe('Assets: unauthored - private - structured', async () => { - - const assetDefinitionID = '02d7cbbf-aa9e-4724-a645-86edf4572ff3'; - const timestamp = utils.getTimestamp(); - - describe('Asset definition', async () => { - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmUsjZyav8UhEvK41EGCgntbfGJT3fQx5KRzFw8uCFhqTe') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: 'unauthored - private - structured', - isContentPrivate: true, - isContentUnique: true, - contentSchema: testContent.schema.object, - }); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0x611c9f2515ec393c0fd9cacaf51caff2a4462b6d0571b2b5600dc7ea961b8c49', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - private - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.name, 'unauthored - private - structured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - describe('Asset instances', async () => { - - const assetInstanceID = '9d53cce2-0879-4aaa-9b82-cb30eb6331be'; - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceCreated = { - assetDefinitionID: utils.uuidToHex(assetDefinitionID), - author: '0x0000000000000000000000000000000000000002', - assetInstanceID: utils.uuidToHex(assetInstanceID), - contentHash: testContent.sample.docExchangeSha256, - timestamp: timestamp.toString(), - isContentPrivate: true - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_ASSET_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, testContent.sample.docExchangeSha256); - assert.deepStrictEqual(assetInstance.content, undefined); - assert.strictEqual(assetInstance.receipt, undefined); - assert.strictEqual(assetInstance.submitted, undefined); - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/unauthored/private/unstructured.ts b/kat/src/test/ethereum/assets/unauthored/private/unstructured.ts deleted file mode 100644 index f1d7bd72d7..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/private/unstructured.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testUnauthoredPrivateUnstructured = () => { - -describe('Asset definitions: unauthored - unstructured', async () => { - - const assetDefinitionID = '2a624ed6-7d71-4b44-94a8-faea64537036'; - - describe('Asset definition', async () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmVB7euGyrkSZPTaoBEZB4rou3A7Za48GNKidL1hSK2bsA') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: 'unauthored - private - unstructured', - isContentPrivate: true, - isContentUnique: true - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0x65907788050862e3317097ac1b1b7dc6cd9c7b35ea11dcccbcef727aae01450d', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - private - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetDefinition.isContentPrivate, true); - assert.strictEqual(assetDefinition.name, 'unauthored - private - unstructured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); - -}; diff --git a/kat/src/test/ethereum/assets/unauthored/public/described-structured.ts b/kat/src/test/ethereum/assets/unauthored/public/described-structured.ts deleted file mode 100644 index c197b676fd..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/public/described-structured.ts +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testDescription, testContent } from '../../../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition, IEventAssetInstanceCreated, IDBAssetInstance } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testUnauthoredPublicDescribedStructured = () => { - -describe('Assets: unauthored - public - described - structured', async () => { - - const assetDefinitionID = 'cb289f68-5eaf-46d2-9be1-3dcfdde76885'; - const timestamp = utils.getTimestamp(); - - describe('Asset definition', async () => { - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - nock('https://ipfs.kaleido.io') - .get('/ipfs/Qma49mzceUqCmZrBsFAzC26XnwT1gVzhTBkby2JZsYiaUF') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: 'unauthored - public - described - structured', - isContentPrivate: false, - isContentUnique: true, - descriptionSchema: testDescription.schema.object, - contentSchema: testContent.schema.object - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0xae123c4d3b9e77716be55ea8ad616b74ac67c455951698f4e40f22e1ed7981e8', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - public - described - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'unauthored - public - described - structured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - describe('Asset instances', async () => { - - const assetInstanceID = '9e8acd3b-4067-443f-ad6e-739976bc63ee'; - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get(`/ipfs/${testDescription.sample.ipfsMultiHash}`) - .reply(200, testDescription.sample.object) - .get(`/ipfs/${testContent.sample.ipfsMultiHash}`) - .reply(200, testContent.sample.object) - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceCreated = { - assetDefinitionID: utils.uuidToHex(assetDefinitionID), - author: '0x0000000000000000000000000000000000000002', - assetInstanceID: utils.uuidToHex(assetInstanceID), - descriptionHash: testDescription.sample.ipfsSha256, - contentHash: testContent.sample.ipfsSha256, - timestamp: timestamp.toString(), - isContentPrivate: false - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_ASSET_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.descriptionHash, testDescription.sample.ipfsSha256); - assert.deepStrictEqual(assetInstance.description, testDescription.sample.object); - assert.strictEqual(assetInstance.contentHash, testContent.sample.ipfsSha256); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.submitted, undefined); - assert.strictEqual(assetInstance.receipt, undefined); - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/unauthored/public/described-unstructured.ts b/kat/src/test/ethereum/assets/unauthored/public/described-unstructured.ts deleted file mode 100644 index 974aacd255..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/public/described-unstructured.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testDescription } from '../../../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testUnauthoredPublicDescribedUnstructured = () => { - describe('Assets: unauthored - public - described - unstructured', async () => { - - const assetDefinitionID = 'f832ea06-71e4-43e3-b0fb-61cb8f9a9631'; - - describe('Asset definition', async () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmXNQHuaQ9odf4dw7CrvZuQXQ8iyizwaWHV4pUZorQKFrS') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: 'unauthored - public - described - unstructured', - isContentPrivate: false, - isContentUnique: true, - descriptionSchema: testDescription.schema.object - }); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0x862c0ad6e169fc23a45c7c9d4b09ba9ce22344a5b4242843a3549c4ea0d8668b', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - public - described - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.deepStrictEqual(assetDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(assetDefinition.name, 'unauthored - public - described - unstructured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/unauthored/public/structured.ts b/kat/src/test/ethereum/assets/unauthored/public/structured.ts deleted file mode 100644 index 6ece10b508..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/public/structured.ts +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import { testContent } from '../../../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventAssetDefinitionCreated, IDBAssetDefinition, IEventAssetInstanceCreated, IDBAssetInstance } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testUnauthoredPublicStructured = () => { - -describe('Assets: unauthored - public - structured', async () => { - - const assetDefinitionID = 'f5847e19-8632-44fb-a742-17b7a1fd8bc5'; - const timestamp = utils.getTimestamp(); - - describe('Asset definition', async () => { - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get('/ipfs/QmdptNuREBKs2ruCrfb7EwUJcEXj5oEpmQ5C4MSKht6SmY') - .reply(200,{ - assetDefinitionID: assetDefinitionID, - name: 'unauthored - public - structured', - isContentPrivate: false, - isContentUnique: true, - contentSchema: testContent.schema.object - }); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0xe61b05a5914b8b0361d2ae2fa91ac0b76de09a517ef411df049b4af3844b4593', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - public - structured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.deepStrictEqual(assetDefinition.contentSchema, testContent.schema.object); - assert.strictEqual(assetDefinition.name, 'unauthored - public - structured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - - describe('Asset instances', async () => { - - const assetInstanceID = '28bc0765-0261-415d-95cf-ea285c91a09c'; - - it('Checks that the event stream notification for confirming the asset instance creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get(`/ipfs/${testContent.sample.ipfsMultiHash}`) - .reply(200, testContent.sample.object) - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventAssetInstanceCreated = { - assetDefinitionID: utils.uuidToHex(assetDefinitionID), - author: '0x0000000000000000000000000000000000000002', - assetInstanceID: utils.uuidToHex(assetInstanceID), - contentHash: testContent.sample.ipfsSha256, - timestamp: timestamp.toString(), - isContentPrivate: false - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_ASSET_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}`) - .expect(200); - const assetInstance = getAssetInstancesResponse.body.find((assetInstance: IDBAssetInstance) => assetInstance.assetInstanceID === assetInstanceID); - assert.strictEqual(assetInstance.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetInstance.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetInstance.contentHash, testContent.sample.ipfsSha256); - assert.deepStrictEqual(assetInstance.content, testContent.sample.object); - assert.strictEqual(assetInstance.submitted, undefined); - assert.strictEqual(assetInstance.receipt, undefined); - assert.strictEqual(assetInstance.timestamp, timestamp); - assert.strictEqual(assetInstance.blockNumber, 123); - assert.strictEqual(assetInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/assets/${assetDefinitionID}/${assetInstanceID}`) - .expect(200); - assert.deepStrictEqual(assetInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/assets/unauthored/public/unstructured.ts b/kat/src/test/ethereum/assets/unauthored/public/unstructured.ts deleted file mode 100644 index 23d43ca6c1..0000000000 --- a/kat/src/test/ethereum/assets/unauthored/public/unstructured.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../../../common'; -import request from 'supertest'; -import assert from 'assert'; -import nock from 'nock' -import { IEventAssetDefinitionCreated, IDBAssetDefinition } from '../../../../../lib/interfaces'; -import * as utils from '../../../../../lib/utils'; - -export const testUnauthoredPublicUnstructured = () => { - -describe('Assets: unauthored - public - unstructured', async () => { - - const assetDefinitionID = 'c4ecf059-67be-4e61-900f-352876604a7f'; - - describe('Asset definition', async () => { - - const timestamp = utils.getTimestamp(); - - it('Checks that the event stream notification for confirming the asset definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - - nock('https://ipfs.kaleido.io') - .get('/ipfs/Qmc6W97aTfV8QuBMkYYygRBakJZ93ZjadtGPNi3KbYALTh') - .reply(200, { - assetDefinitionID: assetDefinitionID, - name: 'unauthored - public - unstructured', - isContentPrivate: false, - isContentUnique: true - }); - - const data: IEventAssetDefinitionCreated = { - author: '0x0000000000000000000000000000000000000002', - assetDefinitionHash: '0xcc63cbfd00dc7c62c1265a42074afb19531e67b10f85b7f5170b836655a10fd0', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.ASSET_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the asset definition is confirmed', async () => { - const getAssetDefinitionsResponse = await request(app) - .get('/api/v1/assets/definitions') - .expect(200); - const assetDefinition = getAssetDefinitionsResponse.body.find((assetDefinition: IDBAssetDefinition) => assetDefinition.name === 'unauthored - public - unstructured'); - assert.strictEqual(assetDefinition.assetDefinitionID, assetDefinitionID); - assert.strictEqual(assetDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(assetDefinition.isContentPrivate, false); - assert.strictEqual(assetDefinition.name, 'unauthored - public - unstructured'); - assert.strictEqual(assetDefinition.timestamp, timestamp); - assert.strictEqual(assetDefinition.submitted, undefined); - assert.strictEqual(assetDefinition.receipt, undefined); - assert.strictEqual(assetDefinition.blockNumber, 123); - assert.strictEqual(assetDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetDefinitionResponse = await request(app) - .get(`/api/v1/assets/definitions/${assetDefinitionID}`) - .expect(200); - assert.deepStrictEqual(assetDefinition, getAssetDefinitionResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/clients/app2app-test.ts b/kat/src/test/ethereum/clients/app2app-test.ts deleted file mode 100644 index c41548fc4b..0000000000 --- a/kat/src/test/ethereum/clients/app2app-test.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as sinon from "sinon"; -import io from 'socket.io-client'; -import { StubbedInstance, stubInterface } from "ts-sinon"; -import * as app2app from '../../../clients/app2app'; - -export const testApp2App = async () => { - -describe('App2App', () => { - - let mocksSocketIo: StubbedInstance; - let callbacks: {[f: string]: Function}; - - before(() => { - callbacks = {}; - mocksSocketIo = stubInterface(); - mocksSocketIo.on.callsFake((event, fn) => { - callbacks[event] = fn; - return mocksSocketIo; - }); - sinon.stub(io, 'connect').returns(mocksSocketIo); - }); - - after(() => { - (io.connect as sinon.SinonStub).restore(); - }) - - beforeEach(() => { - callbacks = {}; - app2app.reset(); - }); - - describe('establishSocketIOConnection', () => { - - it('subscribes after connect', async () => { - sinon.assert.notCalled(mocksSocketIo.emit); - callbacks['connect'](); - sinon.assert.calledWith(mocksSocketIo.emit, 'subscribe'); - }); - - it('logs connect_error', async () => { - callbacks['connect_error'](new Error('pop')); - }); - - it('logs error', async () => { - callbacks['error'](new Error('pop')); - }); - - it('logs exception', async () => { - callbacks['exception'](new Error('pop'), {batch: 'test'}); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/clients/index.ts b/kat/src/test/ethereum/clients/index.ts deleted file mode 100644 index c350268845..0000000000 --- a/kat/src/test/ethereum/clients/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testApp2App } from "./app2app-test"; - -export const testClients = async () => { - describe('Client tests', async () => { - testApp2App(); - }); -}; \ No newline at end of file diff --git a/kat/src/test/ethereum/index.ts b/kat/src/test/ethereum/index.ts deleted file mode 100644 index dc4fd2262d..0000000000 --- a/kat/src/test/ethereum/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testLibraryFunctions } from './lib'; -import { testMembers } from './members'; -import { testPayments } from './payments'; -import { testAssets } from './assets'; -import { testClients } from './clients'; -export const ethereumTests = async () => { -// test Assets -testAssets(); - -testLibraryFunctions(); - -testMembers(); - -testPayments(); - -testClients(); - -}; diff --git a/kat/src/test/ethereum/lib/batch-manager-test.ts b/kat/src/test/ethereum/lib/batch-manager-test.ts deleted file mode 100644 index a6f76d4658..0000000000 --- a/kat/src/test/ethereum/lib/batch-manager-test.ts +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import assert from 'assert'; -import sinon, { SinonStub } from 'sinon'; -import * as database from '../../../clients/database'; -import { BatchManager } from '../../../lib/batch-manager'; -import { BatchProcessor } from '../../../lib/batch-processor'; - -export const testBatchManager = async () => { - -describe('BatchManager', () => { - - let batchRetriever: SinonStub; - let processorInit: SinonStub; - - beforeEach(() => { - batchRetriever = sinon.stub(database, 'retrieveBatches'); - processorInit = sinon.stub(BatchProcessor.prototype, 'init'); - }); - - afterEach(() => { - batchRetriever.restore(); - processorInit.restore(); - }) - - it('inits all the right processors', async () => { - - batchRetriever.resolves([ - { - type: 't1', - author: 'author1', - batchID: 'author1-batch1', - }, - { - type: 't1', - author: 'author2', - batchID: 'author2-batch1', - }, - { - type: 't1', - author: 'author3', - batchID: 'author3-batch1', - }, - { - type: 't1', - author: 'author2', - batchID: 'author2-batch2', - }, - { - type: 't1', - author: 'author1', - batchID: 'author1-batch2', - }, - ]); - - const bm = new BatchManager('t1', sinon.stub()); - await bm.init(); - - sinon.assert.calledWith(processorInit, [ - { - type: 't1', - author: 'author1', - batchID: 'author1-batch1', - }, - { - type: 't1', - author: 'author1', - batchID: 'author1-batch2', - } - ]); - - sinon.assert.calledWith(processorInit, [ - { - type: 't1', - author: 'author2', - batchID: 'author2-batch1', - }, - { - type: 't1', - author: 'author2', - batchID: 'author2-batch2', - } - ]); - - sinon.assert.calledWith(processorInit, [ - { - type: 't1', - author: 'author3', - batchID: 'author3-batch1', - } - ]); - }); - - it('caches batch processors', async () => { - - class TestBatchManagerWrapper extends BatchManager { - public processorCompleteCallback(author: string) { - return super.processorCompleteCallback(author); - } - } - - const bm = new TestBatchManagerWrapper('t1', sinon.stub()); - const bp1 = bm.getProcessor('author1'); - - // Check it caches - assert(bp1 === bm.getProcessor('author1')); - - // Check it doesn't clear the wrong entry - bm.processorCompleteCallback('author2'); - assert(bp1 === bm.getProcessor('author1')); - - // Check it clears the right entry - bm.processorCompleteCallback('author1'); - assert(bp1 !== bm.getProcessor('author1')); - - }) - -}); -}; diff --git a/kat/src/test/ethereum/lib/batch-processor-test.ts b/kat/src/test/ethereum/lib/batch-processor-test.ts deleted file mode 100644 index 24358d0191..0000000000 --- a/kat/src/test/ethereum/lib/batch-processor-test.ts +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import assert from 'assert'; -import sinon, { SinonStub } from 'sinon'; -import { promisify } from 'util'; -import * as database from '../../../clients/database'; -import { BatchProcessor } from '../../../lib/batch-processor'; -import { IDBBatch, BatchRecordType } from '../../../lib/interfaces'; - -const delay = promisify(setTimeout); - -export const testBatchProcessor = async () => { - -describe('BatchProcessor', () => { - - beforeEach(() => { - sinon.stub(database, 'retrieveBatches'); - sinon.stub(database, 'upsertBatch'); - }); - - afterEach(() => { - (database.retrieveBatches as SinonStub).restore(); - (database.upsertBatch as SinonStub).restore(); - }) - - it('fills a batch full in parallel and dispatches it, then cleans up', async () => { - - const processBatchCallback = sinon.stub(); - const processorCompleteCallback = sinon.stub(); - const p = new BatchProcessor( - 'author1', - 'type1', - processBatchCallback, - processorCompleteCallback, - ); - - const scheduleRandomDelayedAdd = async (i: number) => { - // Introduce some randomness, but with very short delays to keep the test fast - await delay(Math.ceil(Math.random() * 5)); - // Half and half records vs. properties - if (i % 2 === 0) { - await p.add({ - recordType: BatchRecordType.assetInstance, - id: `test_${i}`, - }); - } else { - await p.add({ - recordType: BatchRecordType.assetProperty, - key: `test_${i}`, - value: `value_${i}`, - }); - } - } - - const promises: Promise[] = []; - for (let i = 0; i < p.config.batchMaxRecords; i++) { - promises.push(scheduleRandomDelayedAdd(i)); - } - await Promise.all(promises); - - assert.strictEqual(processBatchCallback.callCount, 1); - assert.strictEqual(processorCompleteCallback.callCount, 1); - const batch: IDBBatch = processBatchCallback.getCall(0).args[0]; - for (let i = 0; i < p.config.batchMaxRecords; i++) { - // Half and half records vs. properties - if (i % 2 === 0) { - assert(batch.records.find(r => r.id === `test_${i}`)); - } else { - assert(batch.records.find(r => r.key === `test_${i}`)); - assert(batch.records.find(r => r.value === `value_${i}`)); - } - } - - }); - - it('takes a batch array on input simulating recovery, and dispatches immediately', async () => { - - const processBatchCallback = sinon.stub(); - const processorCompleteCallback = sinon.stub(); - const p = new BatchProcessor( - 'author1', - 'type1', - processBatchCallback, - processorCompleteCallback, - ); - - let batch: IDBBatch = { - author: 'author1', - type: 'type1', - batchID: 'batch1', - completed: null, - created: Date.now(), - records: [], - }; - for (let i = 0; i < p.config.batchMaxRecords-1; i++) { - assert(batch.records.push({id: `test_${i}`, recordType: BatchRecordType.assetInstance })); - } - await p.init([batch]); - - assert.strictEqual(processBatchCallback.callCount, 1); - assert.strictEqual(processorCompleteCallback.callCount, 1); - batch = processBatchCallback.getCall(0).args[0]; - for (let i = 0; i < p.config.batchMaxRecords-1; i++) { - assert(batch.records.find(r => r.id === `test_${i}`)); - } - - }); - - it('times out a batch with arrival, then cleans up once it dispatches', async () => { - - const processBatchCallback = sinon.stub(); - const processorCompleteCallback = sinon.stub(); - const p = new BatchProcessor( - 'author1', - 'type1', - processBatchCallback, - processorCompleteCallback, - { - batchTimeoutArrivallMS: 10, - } - ); - - const scheduleRandomDelayedAdd = async (i: number) => { - // Introduce some randomness, but with very short delays to keep the test fast - await delay(Math.ceil(Math.random() * 5)); - await p.add({ - recordType: BatchRecordType.assetInstance, - id: `test_${i}`, - }); - } - - const before = Date.now(); - const promises: Promise[] = []; - for (let i = 0; i < (p.config.batchMaxRecords - 1); i++) { - promises.push(scheduleRandomDelayedAdd(i)); - } - await Promise.all(promises); - - // Should not be set yet - wait for timeout - assert.strictEqual(processBatchCallback.callCount, 0); - assert.strictEqual(processorCompleteCallback.callCount, 0); - - for (let i = 0; i < 100; i++) { - if (processBatchCallback.callCount === 0) await delay(1); - } - const after = Date.now(); - - assert(after - before >= 10 /* we must have waited this long */) - assert.strictEqual(processBatchCallback.callCount, 1); - assert.strictEqual(processorCompleteCallback.callCount, 1); - - const batch: IDBBatch = processBatchCallback.getCall(0).args[0]; - for (let i = 0; i < (p.config.batchMaxRecords - 1); i++) { - assert(batch.records.find(r => r.id === `test_${i}`)); - } - - }); - - it('times out a batch with an overall timeout, then continues to add to the next batch', async () => { - - let totalReceived = 0; - let batchCount = 0; - const processorCompleteCallback = sinon.stub(); - const p = new BatchProcessor( - 'author1', - 'type1', - async b => {totalReceived += b.records.length; batchCount++}, - processorCompleteCallback, - { - batchTimeoutArrivallMS: 10, - batchTimeoutOverallMS: 20, - } - ); - - for (let i = 0; i < 50; i++) { - await delay(1); - await p.add({ - recordType: BatchRecordType.assetInstance, - id: `test_${i}`, - }); - } - - while (totalReceived < 50) await delay(5); - - assert(batchCount > 1); - assert(processorCompleteCallback.callCount >= 1); - - }); - - it('fills a batch with a slow persistence to the DB', async () => { - - const processBatchCallback = sinon.stub(); - const processorCompleteCallback = sinon.stub(); - const p = new BatchProcessor( - 'author1', - 'type1', - processBatchCallback, - processorCompleteCallback, - { - batchMaxRecords: 10, - } - ); - - // Make the persistence slow - const dbUpdateStub = (database.upsertBatch as SinonStub); - dbUpdateStub.callsFake(() => delay(10)) - - // Make the adding fast - const addImmediate = async (i: number) => { - await p.add({ - recordType: BatchRecordType.assetInstance, - id: `test_${i}`, - }); - } - const promises: Promise[] = []; - for (let i = 0; i < p.config.batchMaxRecords; i++) { - promises.push(addImmediate(i)); - } - await Promise.all(promises); - - for (let i = 0; i < 100; i++) { - if (processorCompleteCallback.callCount === 0) await delay(1); - } - - // We should have exactly three calls - // - once for the first as the batch started - // - once with everything else in the batch - // - once when we completed the batch - assert.strictEqual(dbUpdateStub.callCount, 3); - - assert.strictEqual(processBatchCallback.callCount, 1); - assert.strictEqual(processorCompleteCallback.callCount, 1); - const batch: IDBBatch = processBatchCallback.getCall(0).args[0]; - for (let i = 0; i < p.config.batchMaxRecords; i++) { - assert(batch.records.find(r => r.id === `test_${i}`)); - } - - }); - - it('handles a failure to persist the batch to the DB', async () => { - - const processBatchCallback = sinon.stub(); - const processorCompleteCallback = sinon.stub(); - const p = new BatchProcessor( - 'author1', - 'type1', - processBatchCallback, - processorCompleteCallback, - ); - - // Make the persistence fail - (database.upsertBatch as SinonStub).rejects(new Error('pop')); - - let failed; - try { - await p.add({ - recordType: BatchRecordType.assetInstance, - id: `test` - }); - } - catch(err) { - failed = true; - assert.strictEqual(err.message, 'pop'); - } - assert(failed); - - }); - - it('times out a requests that are queued too long, when there is a batch in flight, and a batch queued', async () => { - - const processBatchCallback = sinon.stub().callsFake(() => delay(10)); - const processorCompleteCallback = sinon.stub(); - const p = new BatchProcessor( - 'author1', - 'type1', - processBatchCallback, - processorCompleteCallback, - { - batchMaxRecords: 1, // to trigger a batch immeidately - addTimeoutMS: 5, - } - ); - - await p.add({ id: `test-batch1-dispatched`, recordType: BatchRecordType.assetInstance }); - - // Make the persistence fail - (database.upsertBatch as SinonStub).onSecondCall().callsFake(() => delay(10)); - - let failed; - try { - await Promise.all([ - p.add({ id: `test-batch2-blocked`, recordType: BatchRecordType.assetInstance }), - p.add({ id: `test-batch3-timeout`, recordType: BatchRecordType.assetInstance }), - ]); - } - catch(err) { - failed = true; - assert(err.message.includes('Timed out add of record after')); - } - assert(failed); - - // Clear everything out - for (let i = 0; i < 100; i++) { - if (processorCompleteCallback.callCount === 0) await delay(1); - } - // Confirm two batches went through - assert.strictEqual(processBatchCallback.callCount, 2); - - }); - - describe('with test wrapper', () => { - - class TestBatchProcessorWrapper extends BatchProcessor { - public dispatchBatch() { - return super.dispatchBatch(); - } - public processBatch(batch: IDBBatch) { - return super.processBatch(batch); - } - public newBatch(): IDBBatch { - return super.newBatch(); - } - } - - it('protects dispatchBatch against duplicate calls', async () => { - const p = new TestBatchProcessorWrapper( - 'author1', - 'type1', - sinon.stub(), - sinon.stub(), - ); - // p.assemblyBatch is not set, so this is a no-op and can be called many times - await p.dispatchBatch(); - await p.dispatchBatch(); - }); - - it('retries in processBatch, with backoff', async () => { - const p = new TestBatchProcessorWrapper( - 'author1', - 'type1', - sinon.stub() - .onFirstCall().rejects(new Error('try me again')) - .onSecondCall().rejects(new Error('and one more time with feeling')), - sinon.stub(), - { - retryInitialDelayMS: 1, - } - ); - // p.assemblyBatch is not set, so this is a no-op and can be called many times - await p.processBatch(p.newBatch()); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/lib/config.ts b/kat/src/test/ethereum/lib/config.ts deleted file mode 100644 index 77232aa2c3..0000000000 --- a/kat/src/test/ethereum/lib/config.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as utils from '../../../lib/utils'; -import nock from 'nock'; -import sinon from 'sinon'; -import assert from 'assert'; - -export const testUtils = async () => { - -describe('Utils', () => { - - before(() => { - sinon.replace(utils, 'constants', { ...utils.constants, REST_API_CALL_MAX_ATTEMPTS: 3, REST_API_CALL_RETRY_DELAY_MS: 0 }); - }); - - describe('Axios with retry', () => { - - it('First attempt successful', async () => { - nock('https://kaleido.io') - .get('/test') - .reply(200, { data: 'test' }); - const result = await utils.axiosWithRetry({ - url: 'https://kaleido.io/test' - }); - assert.deepStrictEqual(result.data, { data: 'test' }); - }); - - it('1 failed attempt', async () => { - nock('https://kaleido.io') - .get('/test') - .reply(500) - .get('/test') - .reply(200, { data: 'test' }); - - const result = await utils.axiosWithRetry({ - url: 'https://kaleido.io/test' - }); - assert.deepStrictEqual(result.data, { data: 'test' }); - }); - - it('2 failed attempts', async () => { - nock('https://kaleido.io') - .get('/test') - .reply(500) - .get('/test') - .reply(500) - .get('/test') - .reply(200, { data: 'test' }); - const result = await utils.axiosWithRetry({ - url: 'https://kaleido.io/test' - }); - assert.deepStrictEqual(result.data, { data: 'test' }); - }); - - it('3 failed attempts', async () => { - nock('https://kaleido.io') - .get('/test') - .reply(500) - .get('/test') - .reply(500) - .get('/test') - .reply(500) - try { - await utils.axiosWithRetry({ - url: 'https://kaleido.io/test' - }); - } catch (err) { - assert.strictEqual(err.response.status, 500); - } - }); - - it('Not found should return immediately', async () => { - nock('https://kaleido.io') - .get('/test') - .reply(404); - try { - await utils.axiosWithRetry({ - url: 'https://kaleido.io/test' - }); - } catch (err) { - assert.deepStrictEqual(err.response.status, 404); - } - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/lib/index.ts b/kat/src/test/ethereum/lib/index.ts deleted file mode 100644 index 53a1dc9982..0000000000 --- a/kat/src/test/ethereum/lib/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testBatchManager } from "./batch-manager-test"; -import { testBatchProcessor } from "./batch-processor-test"; -import { testUtils } from "./config"; -import { testSettings } from "./settings"; -import { testLogging } from "./logging"; - -export const testLibraryFunctions = () => { - describe('Lib tests', async () => { - testBatchManager(); - testBatchProcessor(); - testUtils(); - testSettings(); - testLogging(); - }); -}; \ No newline at end of file diff --git a/kat/src/test/ethereum/lib/logging.ts b/kat/src/test/ethereum/lib/logging.ts deleted file mode 100644 index 9b19bff433..0000000000 --- a/kat/src/test/ethereum/lib/logging.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Logger, setLogLevel } from '../../../lib/logging'; - -export const testLogging = async () => { - - describe('Logging', () => { - - it('logs all the levels', () => { - - setLogLevel('trace') - - const logger = new Logger('unit test'); - logger.error('this is an error message'); - logger.warn('this is an warning message'); - logger.warn('this is an info message'); - logger.debug('this is a debug message'); - logger.trace('this is a trace message'); - - // Log empty - logger.info(); - - // Log a dodgy axios message exercising paths - logger.info({ - isAxiosError: true, - }) - - // Log an axios error that looks like a stream - logger.info({ - isAxiosError: true, - response: { - data: { - on: () => null, - }, - }, - }) - - }); - - }); -}; diff --git a/kat/src/test/ethereum/lib/settings.ts b/kat/src/test/ethereum/lib/settings.ts deleted file mode 100644 index 2cedc64ca1..0000000000 --- a/kat/src/test/ethereum/lib/settings.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app } from '../../common'; -import assert from 'assert'; -import request from 'supertest'; -import { ISettings } from '../../../lib/interfaces'; - -export const testSettings = async () => { - -describe('Settings', () => { - it('Checks that settings can be retrieved', async () => { - const getSettingsResponse = await request(app) - .get('/api/v1/settings') - .expect(200); - const settings: ISettings = getSettingsResponse.body; - assert.deepStrictEqual(settings.clientEvents, [ 'asset-instance-submitted' ]); - }); - - it('Checks that settings can be updated', async () => { - const result = await request(app) - .put('/api/v1/settings') - .send({ - key: 'clientEvents', - value: ['asset-instance-property-set'] - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'success'); - - const getSettingsResponse = await request(app) - .get('/api/v1/settings') - .expect(200); - const settings: ISettings = getSettingsResponse.body; - assert.deepStrictEqual(settings.clientEvents, [ 'asset-instance-property-set' ]); - }); - - it('Fails when attempting to add an invalid setting', async () => { - const result = await request(app) - .put('/api/v1/settings') - .send({ - key: 'clientEvents', - value: ['invalid'] - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Invalid Settings' }); - }); -}); -}; diff --git a/kat/src/test/ethereum/members/argument-validation.ts b/kat/src/test/ethereum/members/argument-validation.ts deleted file mode 100644 index bfcb83de80..0000000000 --- a/kat/src/test/ethereum/members/argument-validation.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app } from '../../common'; -import request from 'supertest'; -import assert from 'assert'; - -export const testMembersArgumentValidation = async () => { - -describe('Members - argument validation', async () => { - - it('Attempting to add a member without an address should raise an error', async () => { - const result = await request(app) - .put('/api/v1/members') - .send({ - name: 'Member A', - app2appDestination: 'kld://app2app', - docExchangeDestination: 'kld://docexchange' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing member address' }); - }); - - it('Attempting to add a member without a name should raise an error', async () => { - const result = await request(app) - .put('/api/v1/members') - .send({ - address: '0x0000000000000000000000000000000000000001', - app2appDestination: 'kld://app2app', - docExchangeDestination: 'kld://docexchange' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing member name' }); - }); - - it('Attempting to get a member that does not exist should raise an error', async () => { - const result = await request(app) - .get('/api/v1/members/0x0000000000000000000000000000000000000099') - .expect(404); - assert.deepStrictEqual(result.body, { error: 'Member not found' }); - }); - -}); -}; diff --git a/kat/src/test/ethereum/members/index.ts b/kat/src/test/ethereum/members/index.ts deleted file mode 100644 index 95f9145715..0000000000 --- a/kat/src/test/ethereum/members/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testMembersArgumentValidation } from "./argument-validation"; -import { testMemberRegistration } from "./registration"; - -export const testMembers = async () => { - describe('Member tests', async () => { - testMemberRegistration(); - testMembersArgumentValidation(); - }); -}; \ No newline at end of file diff --git a/kat/src/test/ethereum/members/registration.ts b/kat/src/test/ethereum/members/registration.ts deleted file mode 100644 index 3a161ec358..0000000000 --- a/kat/src/test/ethereum/members/registration.ts +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventMemberRegistered, IDBMember } from '../../../lib/interfaces'; -import * as utils from '../../../lib/utils'; - -export const testMemberRegistration = async () => { - -describe('Members - registration', async () => { - - const timestampCreation = utils.getTimestamp(); - const timestampUpdate = utils.getTimestamp(); - - it('Checks that adding a member sends a request to API Gateway and updates the database', async () => { - - nock('https://apigateway.kaleido.io') - .post('/registerMember?kld-from=0x0000000000000000000000000000000000000011&kld-sync=false') - .reply(200); - const addMemberResponse = await request(app) - .put('/api/v1/members') - .send({ - address: '0x0000000000000000000000000000000000000011', - name: 'Member 1' - }) - .expect(200); - assert.deepStrictEqual(addMemberResponse.body, { status: 'submitted' }); - - const getMemberResponse = await request(app) - .get('/api/v1/members') - .expect(200); - const member = getMemberResponse.body.find((member: IDBMember) => member.address === '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.address, '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.name, 'Member 1'); - assert.strictEqual(member.assetTrailInstanceID, 'service-id'); - assert.strictEqual(member.app2appDestination, 'kld://app2app/internal'); - assert.strictEqual(member.docExchangeDestination, 'kld://docstore/dest'); - assert.strictEqual(typeof member.submitted, 'number'); - - const getMemberByAddressResponse = await request(app) - .get('/api/v1/members/0x0000000000000000000000000000000000000011') - .expect(200); - assert.deepStrictEqual(member, getMemberByAddressResponse.body); - }); - - it('Checks that event stream notification for confirming member registrations is handled', async () => { - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - - const data: IEventMemberRegistered = { - member: '0x0000000000000000000000000000000000000011', - name: 'Member 1', - assetTrailInstanceID: 'service-id', - app2appDestination: 'kld://app2app/internal', - docExchangeDestination: 'kld://docstore/dest', - timestamp: timestampCreation - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.MEMBER_REGISTERED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Get member should return the confirmed member', async () => { - const getMemberResponse = await request(app) - .get('/api/v1/members') - .expect(200); - const member = getMemberResponse.body.find((member: IDBMember) => member.address === '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.address, '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.name, 'Member 1'); - assert.strictEqual(member.assetTrailInstanceID, 'service-id'); - assert.strictEqual(member.app2appDestination, 'kld://app2app/internal'); - assert.strictEqual(member.docExchangeDestination, 'kld://docstore/dest'); - assert.strictEqual(member.blockNumber, 123); - assert.strictEqual(member.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - assert.strictEqual(member.timestamp, timestampCreation); - - const getMemberByAddressResponse = await request(app) - .get('/api/v1/members/0x0000000000000000000000000000000000000011') - .expect(200); - assert.deepStrictEqual(member, getMemberByAddressResponse.body); - }); - - it('Checks that updating a member sends a request to API Gateway and updates the database', async () => { - nock('https://apigateway.kaleido.io') - .post('/registerMember?kld-from=0x0000000000000000000000000000000000000011&kld-sync=false') - .reply(200); - const addMemberResponse = await request(app) - .put('/api/v1/members') - .send({ - address: '0x0000000000000000000000000000000000000011', - name: 'Member 2' - }) - .expect(200); - assert.deepStrictEqual(addMemberResponse.body, { status: 'submitted' }); - - const getMemberResponse = await request(app) - .get('/api/v1/members') - .expect(200); - const member = getMemberResponse.body.find((member: IDBMember) => member.address === '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.address, '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.name, 'Member 2'); - assert.strictEqual(member.assetTrailInstanceID, 'service-id'); - assert.strictEqual(member.app2appDestination, 'kld://app2app/internal'); - assert.strictEqual(member.docExchangeDestination, 'kld://docstore/dest'); - assert.strictEqual(member.blockNumber, 123); - assert.strictEqual(member.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - assert.strictEqual(typeof member.timestamp, 'number'); - - const getMemberByAddressResponse = await request(app) - .get('/api/v1/members/0x0000000000000000000000000000000000000011') - .expect(200); - assert.deepStrictEqual(member, getMemberByAddressResponse.body); - }); - - it('Checks that event stream notification for confirming member registrations are handled', async () => { - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - - const data: IEventMemberRegistered = { - member: '0x0000000000000000000000000000000000000011', - name: 'Member 2', - assetTrailInstanceID: 'service-id', - app2appDestination: 'kld://app2app/internal', - docExchangeDestination: 'kld://docstore/dest', - timestamp: timestampUpdate - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.MEMBER_REGISTERED, - data, - blockNumber: '456', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000001' - }])); - await eventPromise; - }); - - it('Get member should return the confirmed member', async () => { - const getMemberResponse = await request(app) - .get('/api/v1/members') - .expect(200); - const member = getMemberResponse.body.find((member: IDBMember) => member.address === '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.address, '0x0000000000000000000000000000000000000011'); - assert.strictEqual(member.name, 'Member 2'); - assert.strictEqual(member.assetTrailInstanceID, 'service-id'); - assert.strictEqual(member.app2appDestination, 'kld://app2app/internal'); - assert.strictEqual(member.docExchangeDestination, 'kld://docstore/dest'); - assert.strictEqual(member.blockNumber, 456); - assert.strictEqual(member.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000001'); - assert.strictEqual(member.timestamp, timestampUpdate); - - const getMemberByAddressResponse = await request(app) - .get('/api/v1/members/0x0000000000000000000000000000000000000011') - .expect(200); - assert.deepStrictEqual(member, getMemberByAddressResponse.body); - }); - -}); -}; diff --git a/kat/src/test/ethereum/payments/argument-validation.ts b/kat/src/test/ethereum/payments/argument-validation.ts deleted file mode 100644 index f9136389ce..0000000000 --- a/kat/src/test/ethereum/payments/argument-validation.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app } from '../../common'; -import request from 'supertest'; -import assert from 'assert'; - -export const testPaymentArgumentValidation = async () => { - -describe('Payment definitions - argument validation', async () => { - - it('Attempting to get a payment definition that does not exist should raise an error', async () => { - const result = await request(app) - .get('/api/v1/payments/definitions/missing') - .expect(404); - assert.deepStrictEqual(result.body, { error: 'Payment definition not found' }); - }); - - it('Attempting to add a payment definition without a name should raise an error', async () => { - const result = await request(app) - .post('/api/v1/payments/definitions') - .send({ - author: '0x0000000000000000000000000000000000000001' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid payment definition name' }); - }); - - it('Attempting to add a payment definition without an author should raise an error', async () => { - const result = await request(app) - .post('/api/v1/payments/definitions') - .send({ - name: 'My payment definition' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Missing or invalid payment definition author' }); - }); - - it('Attempting to add a payment definition with an invalid description schema should raise an error', async () => { - const result = await request(app) - .post('/api/v1/payments/definitions') - .send({ - name: 'My payment definition', - author: '0x0000000000000000000000000000000000000001', - descriptionSchema: 'INVALID' - }) - .expect(400); - assert.deepStrictEqual(result.body, { error: 'Invalid description schema' }); - }); - -}); -}; diff --git a/kat/src/test/ethereum/payments/authored-described.ts b/kat/src/test/ethereum/payments/authored-described.ts deleted file mode 100644 index 05470afb53..0000000000 --- a/kat/src/test/ethereum/payments/authored-described.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../common'; -import { testDescription } from '../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IDBPaymentDefinition, IDBPaymentInstance, IEventPaymentDefinitionCreated, IEventPaymentInstanceCreated } from '../../../lib/interfaces'; -import * as utils from '../../../lib/utils'; - -export const testAuthoredDescribed = async () => { - -describe('Payment definitions: authored - described', async () => { - - let paymentDefinitionID: string; - const timestamp = utils.getTimestamp(); - - describe('Create described payment definition', () => { - - it('Checks that the payment definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createDescribedPaymentDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: testDescription.schema.ipfsMultiHash }); - - const result = await request(app) - .post('/api/v1/payments/definitions') - .send({ - name: 'authored - described', - author: '0x0000000000000000000000000000000000000001', - descriptionSchema: testDescription.schema.object - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - paymentDefinitionID = result.body.paymentDefinitionID; - - const getPaymentDefinitionsResponse = await request(app) - .get('/api/v1/payments/definitions') - .expect(200); - const paymentDefinition = getPaymentDefinitionsResponse.body.find((paymentDefinition: IDBPaymentDefinition) => paymentDefinition.name === 'authored - described'); - assert.strictEqual(paymentDefinition.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.deepStrictEqual(paymentDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(paymentDefinition.name, 'authored - described'); - assert.strictEqual(paymentDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentDefinition.submitted, 'number'); - - const getPaymentDefinitionResponse = await request(app) - .get(`/api/v1/payments/definitions/${paymentDefinitionID}`) - .expect(200); - assert.deepStrictEqual(paymentDefinition, getPaymentDefinitionResponse.body); - }); - - it('Checks that the event stream notification for confirming the payment definition creation is handled', async () => { - nock('https://ipfs.kaleido.io') - .get(`/ipfs/${testDescription.schema.ipfsMultiHash}`) - .reply(200, testDescription.schema.object); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentDefinitionCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000001', - name: 'authored - described', - descriptionSchemaHash: testDescription.schema.ipfsSha256, - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_PAYMENT_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment definition is confirmed', async () => { - const getPaymentDefinitionsResponse = await request(app) - .get('/api/v1/payments/definitions') - .expect(200); - const paymentDefinition = getPaymentDefinitionsResponse.body.find((paymentDefinition: IDBPaymentDefinition) => paymentDefinition.name === 'authored - described'); - assert.strictEqual(paymentDefinition.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.deepStrictEqual(paymentDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(paymentDefinition.name, 'authored - described'); - assert.strictEqual(paymentDefinition.timestamp, timestamp); - assert.strictEqual(paymentDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentDefinition.submitted, 'number'); - assert.strictEqual(paymentDefinition.blockNumber, 123); - assert.strictEqual(paymentDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getPaymentDefinitionResponse = await request(app) - .get(`/api/v1/payments/definitions/${paymentDefinitionID}`) - .expect(200); - assert.deepStrictEqual(paymentDefinition, getPaymentDefinitionResponse.body); - }); - - }); - - describe('Payment instances', async () => { - - let paymentInstanceID: string; - - it('Checks that a payment instance can be created', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createDescribedPaymentInstance?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - nock('https://ipfs.kaleido.io') - .post('/api/v0/add') - .reply(200, { Hash: testDescription.sample.ipfsMultiHash }) - - const result = await request(app) - .post('/api/v1/payments/instances') - .send({ - paymentDefinitionID, - author: '0x0000000000000000000000000000000000000001', - description: testDescription.sample.object, - member: '0x0000000000000000000000000000000000000002', - amount: 10 - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - paymentInstanceID = result.body.paymentInstanceID; - - const getPaymentInstancesResponse = await request(app) - .get('/api/v1/payments/instances') - .expect(200); - const paymentInstance = getPaymentInstancesResponse.body.find((paymentInstance: IDBPaymentInstance) => paymentInstance.paymentInstanceID === paymentInstanceID); - assert.strictEqual(paymentInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentInstance.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentInstance.descriptionHash, testDescription.sample.ipfsSha256); - assert.deepStrictEqual(paymentInstance.description, testDescription.sample.object); - assert.strictEqual(paymentInstance.member, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(paymentInstance.amount, 10); - assert.strictEqual(paymentInstance.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentInstance.submitted, 'number'); - - const getPaymentInstanceResponse = await request(app) - .get(`/api/v1/payments/instances/${paymentInstanceID}`) - .expect(200); - assert.deepStrictEqual(paymentInstance, getPaymentInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the payment instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentInstanceCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000001', - paymentInstanceID: utils.uuidToHex(paymentInstanceID), - descriptionHash: testDescription.sample.ipfsSha256, - amount: '10', - member: '0x0000000000000000000000000000000000000002', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_PAYMENT_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get('/api/v1/payments/instances') - .expect(200); - const paymentInstance = getAssetInstancesResponse.body.find((paymentInstance: IDBPaymentInstance) => paymentInstance.paymentInstanceID === paymentInstanceID); - assert.strictEqual(paymentInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentInstance.member, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(paymentInstance.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentInstance.descriptionHash, testDescription.sample.ipfsSha256); - assert.deepStrictEqual(paymentInstance.description, testDescription.sample.object); - assert.strictEqual(paymentInstance.amount, 10); - assert.strictEqual(paymentInstance.timestamp, timestamp); - assert.strictEqual(paymentInstance.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentInstance.submitted, 'number'); - assert.strictEqual(paymentInstance.blockNumber, 123); - assert.strictEqual(paymentInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/payments/instances/${paymentInstanceID}`) - .expect(200); - assert.deepStrictEqual(paymentInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/payments/authored.ts b/kat/src/test/ethereum/payments/authored.ts deleted file mode 100644 index 9d0256700e..0000000000 --- a/kat/src/test/ethereum/payments/authored.ts +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../common'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IDBPaymentDefinition, IDBPaymentInstance, IEventPaymentDefinitionCreated, IEventPaymentInstanceCreated } from '../../../lib/interfaces'; -import * as utils from '../../../lib/utils'; - -export const testAuthored = async () => { -describe('Payment definitions: authored', async () => { - - let paymentDefinitionID: string; - const timestamp = utils.getTimestamp(); - - describe('Create payment definition', () => { - - it('Checks that the payment definition can be added', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createPaymentDefinition?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - const result = await request(app) - .post('/api/v1/payments/definitions') - .send({ - name: 'authored', - author: '0x0000000000000000000000000000000000000001', - amount: 1 - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - paymentDefinitionID = result.body.paymentDefinitionID; - - const getPaymentDefinitionsResponse = await request(app) - .get('/api/v1/payments/definitions') - .expect(200); - const paymentDefinition = getPaymentDefinitionsResponse.body.find((paymentDefinition: IDBPaymentDefinition) => paymentDefinition.name === 'authored'); - assert.strictEqual(paymentDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentDefinition.name, 'authored'); - assert.strictEqual(paymentDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentDefinition.submitted, 'number'); - }); - - it('Checks that the event stream notification for confirming the payment definition creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentDefinitionCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000001', - name: 'authored', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_PAYMENT_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment definition is confirmed', async () => { - const getPaymentDefinitionsResponse = await request(app) - .get('/api/v1/payments/definitions') - .expect(200); - const paymentDefinition = getPaymentDefinitionsResponse.body.find((paymentDefinition: IDBPaymentDefinition) => paymentDefinition.name === 'authored'); - assert.strictEqual(paymentDefinition.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentDefinition.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentDefinition.name, 'authored'); - assert.strictEqual(paymentDefinition.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentDefinition.submitted, 'number'); - assert.strictEqual(paymentDefinition.timestamp, timestamp); - assert.strictEqual(paymentDefinition.blockNumber, 123); - assert.strictEqual(paymentDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getPaymentDefinitionResponse = await request(app) - .get(`/api/v1/payments/definitions/${paymentDefinitionID}`) - .expect(200); - assert.deepStrictEqual(paymentDefinition, getPaymentDefinitionResponse.body); - }); - - }); - - describe('Payment instances', async () => { - - let paymentInstanceID: string; - - it('Checks that a payment instance can be created', async () => { - - nock('https://apigateway.kaleido.io') - .post('/createPaymentInstance?kld-from=0x0000000000000000000000000000000000000001&kld-sync=false') - .reply(200, { id: 'my-receipt-id' }); - - const result = await request(app) - .post('/api/v1/payments/instances') - .send({ - paymentDefinitionID, - author: '0x0000000000000000000000000000000000000001', - member: '0x0000000000000000000000000000000000000002', - amount: 10 - }) - .expect(200); - assert.deepStrictEqual(result.body.status, 'submitted'); - paymentInstanceID = result.body.paymentInstanceID; - - const getPaymentInstancesResponse = await request(app) - .get('/api/v1/payments/instances') - .expect(200); - const paymentInstance = getPaymentInstancesResponse.body.find((paymentInstance: IDBPaymentInstance) => paymentInstance.paymentInstanceID === paymentInstanceID); - assert.strictEqual(paymentInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentInstance.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentInstance.member, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(paymentInstance.amount, 10); - assert.strictEqual(paymentInstance.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentInstance.submitted, 'number'); - - const getPaymentInstanceResponse = await request(app) - .get(`/api/v1/payments/instances/${paymentInstanceID}`) - .expect(200); - assert.deepStrictEqual(paymentInstance, getPaymentInstanceResponse.body); - - }); - - it('Checks that the event stream notification for confirming the payment instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentInstanceCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000001', - paymentInstanceID: utils.uuidToHex(paymentInstanceID), - amount: '10', - member: '0x0000000000000000000000000000000000000002', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.PAYMENT_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get('/api/v1/payments/instances') - .expect(200); - const paymentInstance = getAssetInstancesResponse.body.find((paymentInstance: IDBPaymentInstance) => paymentInstance.paymentInstanceID === paymentInstanceID); - assert.strictEqual(paymentInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentInstance.member, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(paymentInstance.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentInstance.amount, 10); - assert.strictEqual(paymentInstance.receipt, 'my-receipt-id'); - assert.strictEqual(typeof paymentInstance.submitted, 'number'); - assert.strictEqual(paymentInstance.timestamp, timestamp); - assert.strictEqual(paymentInstance.blockNumber, 123); - assert.strictEqual(paymentInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/payments/instances/${paymentInstanceID}`) - .expect(200); - assert.deepStrictEqual(paymentInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/payments/index.ts b/kat/src/test/ethereum/payments/index.ts deleted file mode 100644 index da7578f3e7..0000000000 --- a/kat/src/test/ethereum/payments/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { testPaymentArgumentValidation } from "./argument-validation"; -import { testAuthored } from "./authored"; -import { testAuthoredDescribed } from "./authored-described"; -import { testUnauthored } from "./unauthored"; -import { testUnauthoredDescribed } from "./unauthored-described"; - -export const testPayments = async () => { - describe('Payment tests', async () => { - testPaymentArgumentValidation(); - testAuthored(); - testAuthoredDescribed(); - testUnauthored(); - testUnauthoredDescribed(); - }); -}; \ No newline at end of file diff --git a/kat/src/test/ethereum/payments/unauthored-described.ts b/kat/src/test/ethereum/payments/unauthored-described.ts deleted file mode 100644 index 1747b3bbd3..0000000000 --- a/kat/src/test/ethereum/payments/unauthored-described.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../common'; -import { testDescription } from '../../samples'; -import nock from 'nock'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventPaymentDefinitionCreated, IDBPaymentDefinition, IDBPaymentInstance, IEventPaymentInstanceCreated } from '../../../lib/interfaces'; -import * as utils from '../../../lib/utils'; - -export const testUnauthoredDescribed = async () => { -describe('Payment definitions: unauthored - described', async () => { - - const paymentDefinitionID = '4b4a3be1-0732-4ba1-b492-6f315bb82f53'; - const timestamp = utils.getTimestamp(); - - describe('Payment definition event', async () => { - - it('Checks that the event stream notification for confirming the payment definition creation is handled', async () => { - - nock('https://ipfs.kaleido.io') - .get(`/ipfs/${testDescription.schema.ipfsMultiHash}`) - .reply(200, testDescription.schema.object); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentDefinitionCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000002', - name: 'unauthored - described', - descriptionSchemaHash: testDescription.schema.ipfsSha256, - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_PAYMENT_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment definition event has been processed', async () => { - const getPaymentDefinitionsResponse = await request(app) - .get('/api/v1/payments/definitions') - .expect(200); - const paymentDefinition = getPaymentDefinitionsResponse.body.find((paymentDefinition: IDBPaymentDefinition) => paymentDefinition.name === 'unauthored - described'); - assert.strictEqual(paymentDefinition.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.deepStrictEqual(paymentDefinition.descriptionSchema, testDescription.schema.object); - assert.strictEqual(paymentDefinition.name, 'unauthored - described'); - assert.strictEqual(paymentDefinition.timestamp, timestamp); - assert.strictEqual(paymentDefinition.blockNumber, 123); - assert.strictEqual(paymentDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getPaymentDefinitionResponse = await request(app) - .get(`/api/v1/payments/definitions/${paymentDefinitionID}`) - .expect(200); - assert.deepStrictEqual(paymentDefinition, getPaymentDefinitionResponse.body); - }); - - }); - - describe('Payment instances', async () => { - - const paymentInstanceID = 'f831b208-9567-4fe2-b388-8e994a6163e1'; - - it('Checks that the event stream notification for confirming the payment instance creation is handled', async () => { - nock('https://ipfs.kaleido.io') - .get(`/ipfs/${testDescription.sample.ipfsMultiHash}`) - .reply(200, testDescription.sample.object); - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentInstanceCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000001', - paymentInstanceID: utils.uuidToHex(paymentInstanceID), - descriptionHash: testDescription.sample.ipfsSha256, - amount: '10', - member: '0x0000000000000000000000000000000000000002', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.DESCRIBED_PAYMENT_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get('/api/v1/payments/instances') - .expect(200); - const paymentInstance = getAssetInstancesResponse.body.find((paymentInstance: IDBPaymentInstance) => paymentInstance.paymentInstanceID === paymentInstanceID); - assert.strictEqual(paymentInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentInstance.member, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(paymentInstance.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentInstance.descriptionHash, testDescription.sample.ipfsSha256); - assert.deepStrictEqual(paymentInstance.description, testDescription.sample.object); - assert.strictEqual(paymentInstance.submitted, undefined); - assert.strictEqual(paymentInstance.receipt, undefined); - assert.strictEqual(paymentInstance.amount, 10); - assert.strictEqual(paymentInstance.timestamp, timestamp); - assert.strictEqual(paymentInstance.blockNumber, 123); - assert.strictEqual(paymentInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/payments/instances/${paymentInstanceID}`) - .expect(200); - assert.deepStrictEqual(paymentInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/ethereum/payments/unauthored.ts b/kat/src/test/ethereum/payments/unauthored.ts deleted file mode 100644 index 0cd63bb3bd..0000000000 --- a/kat/src/test/ethereum/payments/unauthored.ts +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { app, mockEventStreamWebSocket } from '../../common'; -import request from 'supertest'; -import assert from 'assert'; -import { IEventPaymentDefinitionCreated, IDBPaymentDefinition, IEventPaymentInstanceCreated, IDBPaymentInstance } from '../../../lib/interfaces'; -import * as utils from '../../../lib/utils'; - -export const testUnauthored = async () => { - -describe('Payment definitions: unauthored', async () => { - - const paymentDefinitionID = 'f9812952-50f8-4090-9412-e7b0f3eeb930'; - const timestamp = utils.getTimestamp(); - - describe('Payment definition', async () => { - - it('Checks that the event stream notification for confirming the payment definition creation is handled', async () => { - - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentDefinitionCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000002', - name: 'unauthored', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.PAYMENT_DEFINITION_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment definition is confirmed', async () => { - const getPaymentDefinitionsResponse = await request(app) - .get('/api/v1/payments/definitions') - .expect(200); - const paymentDefinition = getPaymentDefinitionsResponse.body.find((paymentDefinition: IDBPaymentDefinition) => paymentDefinition.name === 'unauthored'); - assert.strictEqual(paymentDefinition.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentDefinition.author, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(paymentDefinition.name, 'unauthored'); - assert.strictEqual(paymentDefinition.timestamp, timestamp); - assert.strictEqual(paymentDefinition.blockNumber, 123); - assert.strictEqual(paymentDefinition.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getPaymentDefinitionResponse = await request(app) - .get(`/api/v1/payments/definitions/${paymentDefinitionID}`) - .expect(200); - assert.deepStrictEqual(paymentDefinition, getPaymentDefinitionResponse.body); - }); - - }); - - describe('Payment instances', async () => { - - const paymentInstanceID = '646a2a24-319e-408b-a099-fc163ff3e692'; - - it('Checks that the event stream notification for confirming the payment instance creation is handled', async () => { - const eventPromise = new Promise((resolve) => { - mockEventStreamWebSocket.once('send', message => { - assert.strictEqual(message, '{"type":"ack","topic":"dev"}'); - resolve(); - }) - }); - const data: IEventPaymentInstanceCreated = { - paymentDefinitionID: utils.uuidToHex(paymentDefinitionID), - author: '0x0000000000000000000000000000000000000001', - paymentInstanceID: utils.uuidToHex(paymentInstanceID), - amount: '10', - member: '0x0000000000000000000000000000000000000002', - timestamp: timestamp.toString() - }; - mockEventStreamWebSocket.emit('message', JSON.stringify([{ - signature: utils.contractEventSignatures.PAYMENT_INSTANCE_CREATED, - data, - blockNumber: '123', - transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - }])); - await eventPromise; - }); - - it('Checks that the payment instance is confirmed', async () => { - const getAssetInstancesResponse = await request(app) - .get('/api/v1/payments/instances') - .expect(200); - const paymentInstance = getAssetInstancesResponse.body.find((paymentInstance: IDBPaymentInstance) => paymentInstance.paymentInstanceID === paymentInstanceID); - assert.strictEqual(paymentInstance.author, '0x0000000000000000000000000000000000000001'); - assert.strictEqual(paymentInstance.member, '0x0000000000000000000000000000000000000002'); - assert.strictEqual(paymentInstance.paymentDefinitionID, paymentDefinitionID); - assert.strictEqual(paymentInstance.submitted, undefined); - assert.strictEqual(paymentInstance.receipt, undefined); - assert.strictEqual(paymentInstance.amount, 10); - assert.strictEqual(paymentInstance.timestamp, timestamp); - assert.strictEqual(paymentInstance.blockNumber, 123); - assert.strictEqual(paymentInstance.transactionHash, '0x0000000000000000000000000000000000000000000000000000000000000000'); - - const getAssetInstanceResponse = await request(app) - .get(`/api/v1/payments/instances/${paymentInstanceID}`) - .expect(200); - assert.deepStrictEqual(paymentInstance, getAssetInstanceResponse.body); - }); - - }); - -}); -}; diff --git a/kat/src/test/samples.ts b/kat/src/test/samples.ts deleted file mode 100644 index 9b746d417f..0000000000 --- a/kat/src/test/samples.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -export const testDescription = { - schema: { - object: { - type: 'object', - required: ['my_description_string', 'my_description_number', 'my_description_boolean'], - properties: { - my_description_string: { - type: 'string' - }, - my_description_number: { - type: 'number' - }, - my_description_boolean: { - type: 'boolean' - } - } - }, - ipfsSha256: '0xbde03e0e77b5422ff3ce4889752ac9450343420a6a4354542b9fd14fd5fa435c', - ipfsMultiHash: 'Qmb7r5v11TYsJE8dYfnBFjwQsKapX1my7hzfAnd5GFq2io', - }, - sample: { - object: { - my_description_string: 'sample description string', - my_description_number: 123, - my_description_boolean: true - }, - ipfsSha256:'0x2ef1f576d187bb132068095b05a6796891bd0ee1bd69037c6c60f2a6b705d35a', - ipfsMultiHash: 'QmRVuTF2ktoKop95VbgARPZksdfZgpxx6xCJwFE3UbcMmT' - } -}; - -export const testContent = { - schema: { - object: { - type: 'object', - required: ['my_content_string', 'my_content_number', 'my_content_boolean'], - properties: { - my_content_string: { - type: 'string' - }, - my_content_number: { - type: 'number' - }, - my_content_boolean: { - type: 'boolean' - } - } - }, - }, - sample: { - object: { - my_content_string: 'sample content string', - my_content_number: 456, - my_content_boolean: true - }, - ipfsSha256: '0x12e850feabadae5158666a3d03b449fbd4f04582ef0c9b5a91247a02af110016', - ipfsMultiHash: 'QmPcTWXWiUEwect513QdDtw1wa9QWcRgGTVebGbjhMKNxV', - docExchangeSha256: '0xb681804d7f63b532394091f9a0eab0c94e82a92332b4d299ba7493903b27c9e1' - } -}; - -export const testIndexes = [ - { - fields: ["author"], - unique: false - }, - { - fields: ["author", "assetDefinitionID"], - unique: false - } -]; diff --git a/kat/test-resources/config-corda.json b/kat/test-resources/config-corda.json deleted file mode 100644 index 334912e04a..0000000000 --- a/kat/test-resources/config-corda.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "../src/schemas/config.json", - "port": 3001, - "protocol": "corda", - "assetTrailInstanceID": "service-id", - "apiGateway": { - "apiEndpoint": "https://apigateway.kaleido.io", - "auth": { - "user": "testuser", - "password": "testpassword" - } - }, - "eventStreams": { - "wsEndpoint": "ws://eventstreams.kaleido.io", - "topic": "dev", - "auth": { - "user": "testuser", - "password": "testpassword" - }, - "skipSetup": true - }, - "ipfs": { - "apiEndpoint": "https://ipfs.kaleido.io" - }, - "app2app": { - "socketIOEndpoint": "wss://app2app.ws.kaleido.io", - "destinations": { - "kat": "kld://app2app/internal", - "client": "kld://app2app/external" - } - }, - "docExchange": { - "apiEndpoint": "https://docexchange.kaleido.io", - "socketIOEndpoint": "http://docexchange.ws.kaleido.io", - "destination": "kld://docstore/dest" - }, - "appCredentials": { - "user": "testuser", - "password": "testpassword" - } - } \ No newline at end of file diff --git a/kat/test-resources/config-ethereum.json b/kat/test-resources/config-ethereum.json deleted file mode 100644 index 16217240b1..0000000000 --- a/kat/test-resources/config-ethereum.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "../src/schemas/config.json", - "port": 3000, - "protocol": "ethereum", - "assetTrailInstanceID": "service-id", - "apiGateway": { - "apiEndpoint": "https://apigateway.kaleido.io" - }, - "eventStreams": { - "wsEndpoint": "ws://eventstreams.kaleido.io", - "topic": "dev", - "skipSetup": true - }, - "ipfs": { - "apiEndpoint": "https://ipfs.kaleido.io" - }, - "app2app": { - "socketIOEndpoint": "wss://app2app.ws.kaleido.io", - "destinations": { - "kat": "kld://app2app/internal", - "client": "kld://app2app/external" - } - }, - "docExchange": { - "apiEndpoint": "https://docexchange.kaleido.io", - "socketIOEndpoint": "http://docexchange.ws.kaleido.io", - "destination": "kld://docstore/dest" - }, - "appCredentials": { - "user": "testuser", - "password": "testpassword" - } -} \ No newline at end of file diff --git a/kat/test-resources/settings.json b/kat/test-resources/settings.json deleted file mode 100644 index 5590ea9b92..0000000000 --- a/kat/test-resources/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "clientEvents": ["asset-instance-submitted"] -} \ No newline at end of file diff --git a/kat/tsconfig.json b/kat/tsconfig.json deleted file mode 100644 index 009168bb58..0000000000 --- a/kat/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "include": ["src/**/*.ts"], - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "lib": ["es6"], - "allowJs": true, - "outDir": "build", - "rootDir": "src", - "strict": true, - "sourceMap": true, - "strictNullChecks": true, - "noImplicitAny": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - } -} \ No newline at end of file