diff --git a/.gitignore b/.gitignore index a11751a5fb..03a667ba70 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,7 @@ build dist #cache -cache \ No newline at end of file +cache + +#docker databases +docker-db \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 69ac83cbea..d2b072134b 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,13 +4,15 @@ "singleQuote": true, "printWidth": 80, "arrowParens": "always", + "plugins": ["prettier-plugin-solidity"], "overrides": [ { "files": "*.sol", "options": { "semi": true, "printWidth": 80, - "explicitTypes": "always" + "explicitTypes": "always", + "parser": "solidity-parse" } } ] diff --git a/.yarnrc b/.yarnrc index 65496704d2..84c4a3bce5 100644 --- a/.yarnrc +++ b/.yarnrc @@ -2,4 +2,4 @@ # yarn lockfile v1 -yarn-path ".yarn/releases/yarn-1.19.0.cjs" +yarn-path ".yarn/releases/yarn-1.19.0.cjs" \ No newline at end of file diff --git a/README.md b/README.md index 0401d977bb..e0ac0d4f4b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ | | | | | | --- | --- | --- | --- | | [![Lint Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-lint.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-lint.yaml) | [![Protocol Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-core.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-core.yaml) | [![Python SDK Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-python-sdk.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-python-sdk.yaml) | [![Node.js SDK Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-node-sdk.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-node-sdk.yaml) | -| [![Subgraph Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-subgraph.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-subgraph.yaml) | [![Fortune Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-fortune.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-fortune.yaml) | [![Escrow Dashboard Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-dashboard-ui.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-dashboard-ui.yaml) | [![Faucet Server Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-faucet-server.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-faucet-server.yaml) | -| [![Meta Code Verify Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-meta-code-verify.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-meta-code-verify.yaml) | | | | +| [![Subgraph Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-subgraph.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-subgraph.yaml) | [![Dashboard UI Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-dashboard-ui.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-dashboard-ui.yaml) | [![Faucet Server Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-faucet-server.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-faucet-server.yaml) | [![Meta Code Verify Check](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-meta-code-verify.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/ci-test-meta-code-verify.yaml) | | [![Core NPM Publish](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-core.yaml/badge.svg?event=release)](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-core.yaml) | [![Python SDK Publish](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-python-sdk.yaml/badge.svg?event=release)](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-python-sdk.yaml) | [![Node.js SDK Publish](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-node-sdk.yaml/badge.svg?event=release)](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-node-sdk.yaml) | [![Subgraph Deploy](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-subgraph.yaml/badge.svg?branch=main)](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-subgraph.yaml) | | [![Contract Deploy](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-deploy-contracts.yaml/badge.svg?event=workflow_dispatch)](https://github.com/humanprotocol/human-protocol/actions/workflows/cd-deploy-contracts.yaml) | | | | @@ -48,14 +47,14 @@ The contribution guidelines are as per the CONTRIBUTING.MD file. │ │ ├── faucet-server # Faucet server │ │ ├── fortune # Fortune application │ │ ├── job-launcher # Job launcher server, and UI +│ │ ├── human-app # Human App server │ │ ├── meta-code-verify # Browser extensions to verify code │ │ │ running in the browser against a │ │ │ published manifest +│ │ ├── reputation-oracle # Reputation Oracle server │ ├── core # EVM compatible smart contracts for HUMAN │ ├── examples │ │ ├── cvat # An open source annotation tool for labeling video and images -│ │ ├── fortune # An example application that combines all the core -│ │ │ components of HUMAN │ ├── sdk │ │ ├── python │ │ │ ├── human-protocol-sdk # Python SDK to interact with Human Protocol diff --git a/docs/sdk/changelog.md b/docs/sdk/changelog.md index 8cd81d62af..9a808f1102 100644 --- a/docs/sdk/changelog.md +++ b/docs/sdk/changelog.md @@ -2,19 +2,18 @@ ### Added -- **Added OperatorUtils module:** new module for fetching leaders and operators information. +- **Add function to get the encryption public key:** get public key function added to KVStore client. ### Changed -- **Update ethers version:** update ethers to version 6. -- **Update cancel escrow method:** return transaction hash and refunded amount. +- **Rename set url function of KVStore client:** function renamed to be descriptive. This function sets the url and the hash +- **Rename get url function of KVStore client:** function renamed to be descriptive. This function gets the url and verifies the hash of the content. +- **Convert operators config keys to snake_case:** use snake_case as standard. ### Deprecated ### Removed -- **Remove staking utils module:** the methods from this module have been moved into the new OperatorUtils module. - ### Fixed ### Security diff --git a/docs/sdk/python/human_protocol_sdk.constants.md b/docs/sdk/python/human_protocol_sdk.constants.md index 52906d7211..4d3e23b977 100644 --- a/docs/sdk/python/human_protocol_sdk.constants.md +++ b/docs/sdk/python/human_protocol_sdk.constants.md @@ -36,6 +36,22 @@ Enum for chain IDs. #### SKALE *= 1273227453* +### *class* human_protocol_sdk.constants.KVStoreKeys(value) + +Bases: `Enum` + +Enum for KVStore keys + +#### fee *= 'fee'* + +#### public_key *= 'public_key'* + +#### role *= 'role'* + +#### url *= 'url'* + +#### webhook_url *= 'webhook_url'* + ### *class* human_protocol_sdk.constants.Role(value) Bases: `Enum` diff --git a/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md b/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md index dbc87a8f20..ba626aa519 100644 --- a/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md +++ b/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md @@ -86,9 +86,9 @@ Gets the value of a key-value pair in the contract. role = kvstore_client.get('0x62dD51230A30401C455c8398d06F85e4EaB6309f', 'Role') ``` -#### get_url(address, key='url') +#### get_file_url_and_verify_hash(address, key='url') -Gets the URL value of the given entity. +Gets the URL value of the given entity, and verify its hash. * **Parameters:** * **address** (`str`) – Address from which to get the URL value. @@ -106,12 +106,38 @@ Gets the URL value of the given entity. w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) kvstore_client = KVStoreClient(w3) - url = kvstore_client.get_url( + url = kvstore_client.get_file_url_and_verify_hash( '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) - linkedin_url = kvstore_client.get_url( + linkedin_url = kvstore_client.get_file_url_and_verify_hash( '0x62dD51230A30401C455c8398d06F85e4EaB6309f', - 'linkedinUrl' + 'linkedin_url' + ) + ``` +* **Return type:** + `str` + +#### get_public_key(address) + +Gets the public key of the given entity, and verify its hash. + +* **Parameters:** + **address** (`str`) – Address from which to get the public key. +* **Return public_key:** + The public key of the given address if exists, and the content is valid +* **Example:** + ```python + from eth_typing import URI + from web3 import Web3 + from web3.providers.auto import load_provider_from_uri + + from human_protocol_sdk.kvstore import KVStoreClient + + w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) + kvstore_client = KVStoreClient(w3) + + public_key = kvstore_client.get_public_key( + '0x62dD51230A30401C455c8398d06F85e4EaB6309f' ) ``` * **Return type:** @@ -192,9 +218,9 @@ Sets multiple key-value pairs in the contract. kvstore_client.set_bulk(keys, values) ``` -#### set_url(url, key='url', tx_options=None) +#### set_file_url_and_hash(url, key='url', tx_options=None) -Sets a URL value for the address that submits the transaction. +Sets a URL value for the address that submits the transaction, and its hash. * **Parameters:** * **url** (`str`) – URL to set @@ -228,8 +254,8 @@ Sets a URL value for the address that submits the transaction. (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') kvstore_client = KVStoreClient(w3) - kvstore_client.set_url('http://localhost') - kvstore_client.set_url('https://linkedin.com/me', 'linkedinUrl') + kvstore_client.set_file_url_and_hash('http://localhost') + kvstore_client.set_file_url_and_hash('https://linkedin.com/me', 'linkedin_url') ``` ### *exception* human_protocol_sdk.kvstore.kvstore_client.KVStoreClientError diff --git a/docs/sdk/python/human_protocol_sdk.kvstore.md b/docs/sdk/python/human_protocol_sdk.kvstore.md index b547c53930..64cf67a11a 100644 --- a/docs/sdk/python/human_protocol_sdk.kvstore.md +++ b/docs/sdk/python/human_protocol_sdk.kvstore.md @@ -10,8 +10,9 @@ This module allows the contract interaction with the key value storage on chain. * [`KVStoreClient`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient) * [`KVStoreClient.__init__()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.__init__) * [`KVStoreClient.get()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.get) - * [`KVStoreClient.get_url()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.get_url) + * [`KVStoreClient.get_file_url_and_verify_hash()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.get_file_url_and_verify_hash) + * [`KVStoreClient.get_public_key()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.get_public_key) * [`KVStoreClient.set()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.set) * [`KVStoreClient.set_bulk()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.set_bulk) - * [`KVStoreClient.set_url()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.set_url) + * [`KVStoreClient.set_file_url_and_hash()`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClient.set_file_url_and_hash) * [`KVStoreClientError`](human_protocol_sdk.kvstore.kvstore_client.md#human_protocol_sdk.kvstore.kvstore_client.KVStoreClientError) diff --git a/docs/sdk/python/human_protocol_sdk.legacy_encryption.md b/docs/sdk/python/human_protocol_sdk.legacy_encryption.md index fa7fa8cde5..920fcd5dcd 100644 --- a/docs/sdk/python/human_protocol_sdk.legacy_encryption.md +++ b/docs/sdk/python/human_protocol_sdk.legacy_encryption.md @@ -21,7 +21,7 @@ Cipher algorithm defintion. alias of `AES` -#### ELLIPTIC_CURVE*: `EllipticCurve`* *= * +#### ELLIPTIC_CURVE *: `EllipticCurve`* *= * Elliptic curve definition. @@ -35,7 +35,7 @@ Cipher mode definition. alias of `CTR` -#### PUBLIC_KEY_LEN*: `int`* *= 64* +#### PUBLIC_KEY_LEN *: `int`* *= 64* Length of public keys: 512 bit keys in uncompressed form, without format byte diff --git a/docs/sdk/python/human_protocol_sdk.md b/docs/sdk/python/human_protocol_sdk.md index 42e977ac82..15b821b663 100644 --- a/docs/sdk/python/human_protocol_sdk.md +++ b/docs/sdk/python/human_protocol_sdk.md @@ -108,6 +108,12 @@ * [`ChainId.POLYGON_MUMBAI`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.POLYGON_MUMBAI) * [`ChainId.RINKEBY`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.RINKEBY) * [`ChainId.SKALE`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId.SKALE) + * [`KVStoreKeys`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys) + * [`KVStoreKeys.fee`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.fee) + * [`KVStoreKeys.public_key`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.public_key) + * [`KVStoreKeys.role`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.role) + * [`KVStoreKeys.url`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.url) + * [`KVStoreKeys.webhook_url`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys.webhook_url) * [`Role`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.Role) * [`Role.exchange_oracle`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.Role.exchange_oracle) * [`Role.job_launcher`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.Role.job_launcher) diff --git a/docs/sdk/python/index.md b/docs/sdk/python/index.md index 485fa0ddec..e768daa262 100644 --- a/docs/sdk/python/index.md +++ b/docs/sdk/python/index.md @@ -41,6 +41,7 @@ pip install human-protocol-sdk[agreement] * [Submodules](human_protocol_sdk.md#submodules) * [human_protocol_sdk.constants module](human_protocol_sdk.constants.md) * [`ChainId`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId) + * [`KVStoreKeys`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.KVStoreKeys) * [`Role`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.Role) * [`Status`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.Status) * [human_protocol_sdk.filter module](human_protocol_sdk.filter.md) diff --git a/docs/sdk/typescript/classes/base.BaseEthersClient.md b/docs/sdk/typescript/classes/base.BaseEthersClient.md index 66e971f571..e75e8362d4 100644 --- a/docs/sdk/typescript/classes/base.BaseEthersClient.md +++ b/docs/sdk/typescript/classes/base.BaseEthersClient.md @@ -50,7 +50,7 @@ This class is used as a base class for other clients making on-chain calls. #### Defined in -[base.ts:20](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) +[base.ts:20](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) ## Properties @@ -60,7 +60,7 @@ This class is used as a base class for other clients making on-chain calls. #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -70,4 +70,4 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) diff --git a/docs/sdk/typescript/classes/encryption.Encryption.md b/docs/sdk/typescript/classes/encryption.Encryption.md index 0ef45c7b5d..be5ada8b27 100644 --- a/docs/sdk/typescript/classes/encryption.Encryption.md +++ b/docs/sdk/typescript/classes/encryption.Encryption.md @@ -81,7 +81,7 @@ Constructor for the Encryption class. #### Defined in -[encryption.ts:53](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L53) +[encryption.ts:53](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L53) ## Properties @@ -91,7 +91,7 @@ Constructor for the Encryption class. #### Defined in -[encryption.ts:46](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L46) +[encryption.ts:46](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L46) ## Methods @@ -140,7 +140,7 @@ const resultMessage = await encription.decrypt('message'); #### Defined in -[encryption.ts:180](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L180) +[encryption.ts:180](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L180) ___ @@ -176,7 +176,7 @@ const resultMessage = await encription.sign('message'); #### Defined in -[encryption.ts:217](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L217) +[encryption.ts:217](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L217) ___ @@ -238,7 +238,7 @@ const resultMessage = await encription.signAndEncrypt('message', publicKeys); #### Defined in -[encryption.ts:129](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L129) +[encryption.ts:129](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L129) ___ @@ -263,4 +263,4 @@ Builds an Encryption instance by decrypting the private key from an encrypted pr #### Defined in -[encryption.ts:64](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L64) +[encryption.ts:64](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L64) diff --git a/docs/sdk/typescript/classes/encryption.EncryptionUtils.md b/docs/sdk/typescript/classes/encryption.EncryptionUtils.md index 8f89dd2f25..20cf3b2cac 100644 --- a/docs/sdk/typescript/classes/encryption.EncryptionUtils.md +++ b/docs/sdk/typescript/classes/encryption.EncryptionUtils.md @@ -108,7 +108,7 @@ const result = await EncriptionUtils.encrypt('message', publicKeys); #### Defined in -[encryption.ts:422](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L422) +[encryption.ts:422](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L422) ___ @@ -157,7 +157,7 @@ const result = await EncriptionUtils.generateKeyPair(name, email, passphrase); #### Defined in -[encryption.ts:360](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L360) +[encryption.ts:360](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L360) ___ @@ -189,7 +189,7 @@ const signedData = await EncriptionUtils.getSignedData('message'); #### Defined in -[encryption.ts:317](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L317) +[encryption.ts:317](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L317) ___ @@ -237,7 +237,7 @@ if (isEncrypted) { #### Defined in -[encryption.ts:471](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L471) +[encryption.ts:471](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L471) ___ @@ -282,4 +282,4 @@ const result = await EncriptionUtils.verify('message', publicKey); #### Defined in -[encryption.ts:284](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L284) +[encryption.ts:284](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L284) diff --git a/docs/sdk/typescript/classes/escrow.EscrowClient.md b/docs/sdk/typescript/classes/escrow.EscrowClient.md index 11a00ab2e4..4885a7c969 100644 --- a/docs/sdk/typescript/classes/escrow.EscrowClient.md +++ b/docs/sdk/typescript/classes/escrow.EscrowClient.md @@ -142,7 +142,7 @@ const escrowClient = await EscrowClient.build(provider); #### Defined in -[escrow.ts:127](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L127) +[escrow.ts:127](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L127) ## Properties @@ -152,7 +152,7 @@ const escrowClient = await EscrowClient.build(provider); #### Defined in -[escrow.ts:119](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L119) +[escrow.ts:119](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L119) ___ @@ -166,7 +166,7 @@ ___ #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -180,7 +180,7 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) ## Methods @@ -223,7 +223,7 @@ await escrowClient.abort('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in -[escrow.ts:835](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L835) +[escrow.ts:835](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L835) ___ @@ -268,7 +268,7 @@ await escrowClient.addTrustedHandlers('0x62dD51230A30401C455c8398d06F85e4EaB6309 #### Defined in -[escrow.ts:883](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L883) +[escrow.ts:883](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L883) ___ @@ -320,7 +320,7 @@ await escrowClient.bulkPayOut('0x62dD51230A30401C455c8398d06F85e4EaB6309f', reci #### Defined in -[escrow.ts:648](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L648) +[escrow.ts:648](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L648) ___ @@ -363,7 +363,7 @@ await escrowClient.cancel('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in -[escrow.ts:751](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L751) +[escrow.ts:751](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L751) ___ @@ -406,7 +406,7 @@ await escrowClient.complete('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); #### Defined in -[escrow.ts:590](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L590) +[escrow.ts:590](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L590) ___ @@ -464,7 +464,7 @@ const escrowAddress = await escrowClient.createAndSetupEscrow(tokenAddress, trus #### Defined in -[escrow.ts:413](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L413) +[escrow.ts:413](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L413) ___ @@ -512,7 +512,7 @@ const escrowAddress = await escrowClient.createEscrow(tokenAddress, trustedHandl #### Defined in -[escrow.ts:207](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L207) +[escrow.ts:207](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L207) ___ @@ -555,7 +555,7 @@ await escrowClient.fund('0x62dD51230A30401C455c8398d06F85e4EaB6309f', amount); #### Defined in -[escrow.ts:461](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L461) +[escrow.ts:461](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L461) ___ @@ -593,7 +593,7 @@ const balance = await escrowClient.getBalance('0x62dD51230A30401C455c8398d06F85e #### Defined in -[escrow.ts:938](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L938) +[escrow.ts:938](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L938) ___ @@ -615,7 +615,7 @@ Connects to the escrow contract #### Defined in -[escrow.ts:167](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L167) +[escrow.ts:167](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L167) ___ @@ -653,7 +653,7 @@ const oracleAddress = await escrowClient.getExchangeOracleAddress('0x62dD51230A3 #### Defined in -[escrow.ts:1318](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1318) +[escrow.ts:1318](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1318) ___ @@ -691,7 +691,7 @@ const factoryAddress = await escrowClient.getFactoryAddress('0x62dD51230A30401C4 #### Defined in -[escrow.ts:1356](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1356) +[escrow.ts:1356](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1356) ___ @@ -729,7 +729,7 @@ const intemediateResultsUrl = await escrowClient.getIntermediateResultsUrl('0x62 #### Defined in -[escrow.ts:1090](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1090) +[escrow.ts:1090](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1090) ___ @@ -767,7 +767,7 @@ const jobLauncherAddress = await escrowClient.getJobLauncherAddress('0x62dD51230 #### Defined in -[escrow.ts:1242](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1242) +[escrow.ts:1242](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1242) ___ @@ -805,7 +805,7 @@ const manifestHash = await escrowClient.getManifestHash('0x62dD51230A30401C455c8 #### Defined in -[escrow.ts:976](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L976) +[escrow.ts:976](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L976) ___ @@ -843,7 +843,7 @@ const manifestUrl = await escrowClient.getManifestUrl('0x62dD51230A30401C455c839 #### Defined in -[escrow.ts:1014](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1014) +[escrow.ts:1014](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1014) ___ @@ -881,7 +881,7 @@ const oracleAddress = await escrowClient.getRecordingOracleAddress('0x62dD51230A #### Defined in -[escrow.ts:1204](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1204) +[escrow.ts:1204](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1204) ___ @@ -919,7 +919,7 @@ const oracleAddress = await escrowClient.getReputationOracleAddress('0x62dD51230 #### Defined in -[escrow.ts:1280](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1280) +[escrow.ts:1280](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1280) ___ @@ -957,7 +957,7 @@ const resultsUrl = await escrowClient.getResultsUrl('0x62dD51230A30401C455c8398d #### Defined in -[escrow.ts:1052](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1052) +[escrow.ts:1052](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1052) ___ @@ -995,7 +995,7 @@ const status = await escrowClient.getStatus('0x62dD51230A30401C455c8398d06F85e4E #### Defined in -[escrow.ts:1166](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1166) +[escrow.ts:1166](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1166) ___ @@ -1033,7 +1033,7 @@ const tokenAddress = await escrowClient.getTokenAddress('0x62dD51230A30401C455c8 #### Defined in -[escrow.ts:1128](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1128) +[escrow.ts:1128](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1128) ___ @@ -1088,7 +1088,7 @@ await escrowClient.setup(escrowAddress, escrowConfig); #### Defined in -[escrow.ts:288](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L288) +[escrow.ts:288](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L288) ___ @@ -1133,7 +1133,7 @@ await storeResults.storeResults('0x62dD51230A30401C455c8398d06F85e4EaB6309f', 'h #### Defined in -[escrow.ts:526](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L526) +[escrow.ts:526](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L526) ___ @@ -1165,4 +1165,4 @@ Thrown if the network's chainId is not supported #### Defined in -[escrow.ts:145](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L145) +[escrow.ts:145](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L145) diff --git a/docs/sdk/typescript/classes/escrow.EscrowUtils.md b/docs/sdk/typescript/classes/escrow.EscrowUtils.md index fa80ea46aa..b05c24a6cb 100644 --- a/docs/sdk/typescript/classes/escrow.EscrowUtils.md +++ b/docs/sdk/typescript/classes/escrow.EscrowUtils.md @@ -138,7 +138,7 @@ const escrowData = new EscrowUtils.getEscrow(ChainId.POLYGON_MUMBAI, "0x12345678 #### Defined in -[escrow.ts:1632](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1632) +[escrow.ts:1632](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1632) ___ @@ -251,4 +251,4 @@ const escrowDatas = await EscrowUtils.getEscrows(filters); #### Defined in -[escrow.ts:1504](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1504) +[escrow.ts:1504](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1504) diff --git a/docs/sdk/typescript/classes/kvstore.KVStoreClient.md b/docs/sdk/typescript/classes/kvstore.KVStoreClient.md index 8053fa2973..302f33649e 100644 --- a/docs/sdk/typescript/classes/kvstore.KVStoreClient.md +++ b/docs/sdk/typescript/classes/kvstore.KVStoreClient.md @@ -93,10 +93,11 @@ const kvstoreClient = await KVStoreClient.build(signer); ### Methods - [get](kvstore.KVStoreClient.md#get) -- [getURL](kvstore.KVStoreClient.md#geturl) +- [getFileUrlAndVerifyHash](kvstore.KVStoreClient.md#getfileurlandverifyhash) +- [getPublicKey](kvstore.KVStoreClient.md#getpublickey) - [set](kvstore.KVStoreClient.md#set) - [setBulk](kvstore.KVStoreClient.md#setbulk) -- [setURL](kvstore.KVStoreClient.md#seturl) +- [setFileUrlAndHash](kvstore.KVStoreClient.md#setfileurlandhash) - [build](kvstore.KVStoreClient.md#build) ## Constructors @@ -124,7 +125,7 @@ const kvstoreClient = await KVStoreClient.build(signer); #### Defined in -[kvstore.ts:100](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L100) +[kvstore.ts:100](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L100) ## Properties @@ -134,7 +135,7 @@ const kvstoreClient = await KVStoreClient.build(signer); #### Defined in -[kvstore.ts:92](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L92) +[kvstore.ts:92](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L92) ___ @@ -148,7 +149,7 @@ ___ #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -162,7 +163,7 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) ## Methods @@ -170,7 +171,7 @@ ___ ▸ **get**(`address`, `key`): `Promise`\<`string`\> -This function returns the value for a specified key and address. +Gets the value of a key-value pair in the contract. #### Parameters @@ -203,15 +204,15 @@ const value = await kvstoreClient.get('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb9226 #### Defined in -[kvstore.ts:301](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L301) +[kvstore.ts:301](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L301) ___ -### getURL +### getFileUrlAndVerifyHash -▸ **getURL**(`address`, `urlKey?`): `Promise`\<`string`\> +▸ **getFileUrlAndVerifyHash**(`address`, `urlKey?`): `Promise`\<`string`\> -This function returns the URL value for the given entity. +Gets the URL value of the given entity, and verify its hash. #### Parameters @@ -237,16 +238,54 @@ const rpcUrl = 'YOUR_RPC_URL'; const provider = new providers.JsonRpcProvider(rpcUrl); const kvstoreClient = await KVStoreClient.build(provider); -const url = await kvstoreClient.getURL('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'); -const linkedinUrl = await kvstoreClient.getURL( +const url = await kvstoreClient.getFileUrlAndVerifyHash('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'); +const linkedinUrl = await kvstoreClient.getFileUrlAndVerifyHash( '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - 'linkedinUrl' + 'linkedin_url' ); ``` #### Defined in -[kvstore.ts:340](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L340) +[kvstore.ts:340](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L340) + +___ + +### getPublicKey + +▸ **getPublicKey**(`address`): `Promise`\<`string`\> + +Gets the public key of the given entity, and verify its hash. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `address` | `string` | Address from which to get the public key. | + +#### Returns + +`Promise`\<`string`\> + +Public key for the given address if exists, and the content is valid + +**Code example** + +```ts +import { providers } from 'ethers'; +import { KVStoreClient } from '@human-protocol/sdk'; + +const rpcUrl = 'YOUR_RPC_URL'; + +const provider = new providers.JsonRpcProvider(rpcUrl); +const kvstoreClient = await KVStoreClient.build(provider); + +const publicKey = await kvstoreClient.getPublicKey('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'); +``` + +#### Defined in + +[kvstore.ts:398](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L398) ___ @@ -290,7 +329,7 @@ await kvstoreClient.set('Role', 'RecordingOracle'); #### Defined in -[kvstore.ts:163](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L163) +[kvstore.ts:163](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L163) ___ @@ -329,22 +368,22 @@ const provider = new providers.JsonRpcProvider(rpcUrl); const signer = new Wallet(privateKey, provider); const kvstoreClient = await KVStoreClient.build(signer); -const keys = ['role', 'webhookUrl']; +const keys = ['role', 'webhook_url']; const values = ['RecordingOracle', 'http://localhost']; await kvstoreClient.set(keys, values); ``` #### Defined in -[kvstore.ts:206](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L206) +[kvstore.ts:206](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L206) ___ -### setURL +### setFileUrlAndHash -▸ **setURL**(`url`, `urlKey?`, `txOptions?`): `Promise`\<`void`\> +▸ **setFileUrlAndHash**(`url`, `urlKey?`, `txOptions?`): `Promise`\<`void`\> -This function sets a URL value for the address that submits the transaction. +Sets a URL value for the address that submits the transaction, and its hash. #### Parameters @@ -373,13 +412,13 @@ const provider = new providers.JsonRpcProvider(rpcUrl); const signer = new Wallet(privateKey, provider); const kvstoreClient = await KVStoreClient.build(signer); -await kvstoreClient.setURL('example.com'); -await kvstoreClient.setURL('linkedin.com/example', 'linkedinUrl); +await kvstoreClient.setFileUrlAndHash('example.com'); +await kvstoreClient.setFileUrlAndHash('linkedin.com/example', 'linkedin_url); ``` #### Defined in -[kvstore.ts:249](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L249) +[kvstore.ts:249](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L249) ___ @@ -411,4 +450,4 @@ Creates an instance of KVStoreClient from a runner. #### Defined in -[kvstore.ts:118](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L118) +[kvstore.ts:118](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L118) diff --git a/docs/sdk/typescript/classes/operator.OperatorUtils.md b/docs/sdk/typescript/classes/operator.OperatorUtils.md index 48bfa3a446..608aec83df 100644 --- a/docs/sdk/typescript/classes/operator.OperatorUtils.md +++ b/docs/sdk/typescript/classes/operator.OperatorUtils.md @@ -58,7 +58,7 @@ const leader = await OperatorUtils.getLeader(ChainId.POLYGON_MUMBAI, '0x62dD5123 #### Defined in -[operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) +[operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) ___ @@ -90,7 +90,7 @@ const leaders = await OperatorUtils.getLeaders(); #### Defined in -[operator.ts:84](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L84) +[operator.ts:84](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L84) ___ @@ -124,7 +124,7 @@ const operators = await OperatorUtils.getReputationNetworkOperators(ChainId.POLY #### Defined in -[operator.ts:123](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L123) +[operator.ts:123](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L123) ___ @@ -157,4 +157,4 @@ const rewards = await OperatorUtils.getRewards(ChainId.POLYGON_MUMBAI, '0x62dD51 #### Defined in -[operator.ts:162](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L162) +[operator.ts:162](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L162) diff --git a/docs/sdk/typescript/classes/staking.StakingClient.md b/docs/sdk/typescript/classes/staking.StakingClient.md index 5a3b3f84b9..4af90b9d85 100644 --- a/docs/sdk/typescript/classes/staking.StakingClient.md +++ b/docs/sdk/typescript/classes/staking.StakingClient.md @@ -132,7 +132,7 @@ const stakingClient = await StakingClient.build(provider); #### Defined in -[staking.ts:111](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L111) +[staking.ts:111](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L111) ## Properties @@ -142,7 +142,7 @@ const stakingClient = await StakingClient.build(provider); #### Defined in -[staking.ts:102](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L102) +[staking.ts:102](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L102) ___ @@ -156,7 +156,7 @@ ___ #### Defined in -[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +[base.ts:12](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) ___ @@ -166,7 +166,7 @@ ___ #### Defined in -[staking.ts:103](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L103) +[staking.ts:103](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L103) ___ @@ -180,7 +180,7 @@ ___ #### Defined in -[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +[base.ts:11](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) ___ @@ -190,7 +190,7 @@ ___ #### Defined in -[staking.ts:101](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L101) +[staking.ts:101](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L101) ___ @@ -200,7 +200,7 @@ ___ #### Defined in -[staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) +[staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) ## Methods @@ -245,7 +245,7 @@ await stakingClient.allocate('0x62dD51230A30401C455c8398d06F85e4EaB6309f', amoun #### Defined in -[staking.ts:458](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L458) +[staking.ts:458](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L458) ___ @@ -287,7 +287,7 @@ await stakingClient.approveStake(amount); #### Defined in -[staking.ts:203](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L203) +[staking.ts:203](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L203) ___ @@ -309,7 +309,7 @@ Check if escrow exists #### Defined in -[staking.ts:167](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L167) +[staking.ts:167](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L167) ___ @@ -353,7 +353,7 @@ await stakingClient.closeAllocation('0x62dD51230A30401C455c8398d06F85e4EaB6309f' #### Defined in -[staking.ts:511](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L511) +[staking.ts:511](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L511) ___ @@ -396,7 +396,7 @@ await stakingClient.distributeReward('0x62dD51230A30401C455c8398d06F85e4EaB6309f #### Defined in -[staking.ts:554](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L554) +[staking.ts:554](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L554) ___ @@ -434,7 +434,7 @@ const allocationInfo = await stakingClient.getAllocation('0x62dD51230A30401C455c #### Defined in -[staking.ts:591](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L591) +[staking.ts:591](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L591) ___ @@ -479,7 +479,7 @@ await stakingClient.slash('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', '0xf39Fd #### Defined in -[staking.ts:387](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L387) +[staking.ts:387](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L387) ___ @@ -524,7 +524,7 @@ await stakingClient.approveStake(amount); #### Defined in -[staking.ts:258](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L258) +[staking.ts:258](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L258) ___ @@ -568,7 +568,7 @@ await stakingClient.unstake(amount); #### Defined in -[staking.ts:303](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L303) +[staking.ts:303](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L303) ___ @@ -610,7 +610,7 @@ await stakingClient.withdraw(); #### Defined in -[staking.ts:349](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L349) +[staking.ts:349](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L349) ___ @@ -642,4 +642,4 @@ Creates an instance of StakingClient from a Runner. #### Defined in -[staking.ts:145](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L145) +[staking.ts:145](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L145) diff --git a/docs/sdk/typescript/classes/statistics.StatisticsClient.md b/docs/sdk/typescript/classes/statistics.StatisticsClient.md index 0c948574f0..a61a7d8025 100644 --- a/docs/sdk/typescript/classes/statistics.StatisticsClient.md +++ b/docs/sdk/typescript/classes/statistics.StatisticsClient.md @@ -77,7 +77,7 @@ const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); #### Defined in -[statistics.ts:68](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L68) +[statistics.ts:68](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L68) ## Properties @@ -87,7 +87,7 @@ const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_MUMBAI]); #### Defined in -[statistics.ts:61](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L61) +[statistics.ts:61](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L61) ## Methods @@ -151,7 +151,7 @@ const escrowStatisticsApril = await statisticsClient.getEscrowStatistics({ #### Defined in -[statistics.ts:121](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L121) +[statistics.ts:121](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L121) ___ @@ -247,7 +247,7 @@ console.log('HMT statistics from 5/8 - 6/8:', { #### Defined in -[statistics.ts:394](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L394) +[statistics.ts:394](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L394) ___ @@ -329,7 +329,7 @@ console.log( #### Defined in -[statistics.ts:285](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L285) +[statistics.ts:285](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L285) ___ @@ -388,4 +388,4 @@ const workerStatisticsApril = await statisticsClient.getWorkerStatistics({ #### Defined in -[statistics.ts:196](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L196) +[statistics.ts:196](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L196) diff --git a/docs/sdk/typescript/classes/storage.StorageClient.md b/docs/sdk/typescript/classes/storage.StorageClient.md index 54dc9c1b38..576834e3e2 100644 --- a/docs/sdk/typescript/classes/storage.StorageClient.md +++ b/docs/sdk/typescript/classes/storage.StorageClient.md @@ -91,7 +91,7 @@ const storageClient = new StorageClient(params, credentials); #### Defined in -[storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) +[storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) ## Properties @@ -101,7 +101,7 @@ const storageClient = new StorageClient(params, credentials); #### Defined in -[storage.ts:64](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L64) +[storage.ts:64](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L64) ___ @@ -111,7 +111,7 @@ ___ #### Defined in -[storage.ts:65](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L65) +[storage.ts:65](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L65) ## Methods @@ -155,7 +155,7 @@ const exists = await storageClient.bucketExists('bucket-name'); #### Defined in -[storage.ts:266](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L266) +[storage.ts:266](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L266) ___ @@ -198,7 +198,7 @@ const files = await storageClient.downloadFiles(keys, 'bucket-name'); #### Defined in -[storage.ts:113](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L113) +[storage.ts:113](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L113) ___ @@ -242,7 +242,7 @@ const fileNames = await storageClient.listObjects('bucket-name'); #### Defined in -[storage.ts:297](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L297) +[storage.ts:297](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L297) ___ @@ -290,7 +290,7 @@ const uploadedFiles = await storageClient.uploadFiles(files, 'bucket-name'); #### Defined in -[storage.ts:201](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L201) +[storage.ts:201](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L201) ___ @@ -322,4 +322,4 @@ const file = await storageClient.downloadFileFromUrl('http://localhost/file.json #### Defined in -[storage.ts:148](https://github.com/humanprotocol/human-protocol/blob/87747676/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L148) +[storage.ts:148](https://github.com/humanprotocol/human-protocol/blob/48066071/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L148) diff --git a/package.json b/package.json index 317dfdbd52..ca047370d0 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,12 @@ "devDependencies": { "@apollo/client": "^3.7.12", "@babel/core": "^7.23.5", - "@babel/preset-env": "^7.23.5", + "@babel/preset-env": "^7.23.9", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@jest/globals": "^29.3.1", "@types/jest": "^29.5.11", - "@types/node": "^20.11.19", + "@types/node": "^20.11.20", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/utils": "^6.10.0", @@ -55,7 +55,7 @@ "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^27.1.5", - "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-prettier": "^5.1.3", "ethers": "^6.9.1", "husky": "^8.0.2", "jest": "^29.7.0", @@ -75,4 +75,4 @@ "qrcode": "^1.5.0", "semver": "^7.5.2" } -} +} \ No newline at end of file diff --git a/packages/apps/dashboard/ui/package.json b/packages/apps/dashboard/ui/package.json index 3b18c46044..0f803fa355 100644 --- a/packages/apps/dashboard/ui/package.json +++ b/packages/apps/dashboard/ui/package.json @@ -8,7 +8,7 @@ "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@human-protocol/sdk": "*", - "@mui/icons-material": "^5.14.14", + "@mui/icons-material": "^5.15.11", "@mui/material": "^5.14.14", "@reduxjs/toolkit": "^1.9.0", "axios": "^1.2.3", @@ -33,11 +33,11 @@ "serve": "^14.1.1", "swr": "^2.2.4", "wagmi": "^0.12.2", - "web-vitals": "^3.5.1" + "web-vitals": "^3.5.2" }, "devDependencies": { "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.0.0", + "@testing-library/react": "^14.2.1", "@types/crypto-js": "^4.1.2", "@types/file-saver": "^2.0.5", "@types/glob": "^8.1.0", @@ -46,7 +46,7 @@ "@types/react-dom": "^18.2.14", "@types/react-gtm-module": "^2.0.3", "@types/react-test-renderer": "^18.0.0", - "@vitejs/plugin-react": "^3.1.0", + "@vitejs/plugin-react": "^4.2.1", "crypto-js": "^4.2.0", "dotenv": "^16.3.2", "eslint-config-react-app": "^7.0.1", diff --git a/packages/apps/dashboard/ui/src/components/Kvstore/KvstoreView.tsx b/packages/apps/dashboard/ui/src/components/Kvstore/KvstoreView.tsx index 93978b5161..aaa46de5eb 100644 --- a/packages/apps/dashboard/ui/src/components/Kvstore/KvstoreView.tsx +++ b/packages/apps/dashboard/ui/src/components/Kvstore/KvstoreView.tsx @@ -18,7 +18,7 @@ export const KvstoreView: FC = () => { address: NETWORKS[chain?.id as ChainId]?.kvstoreAddress as `0x${string}`, abi: KVStore, functionName: 'get', - args: [address, 'publicKey'], + args: [address, 'public_key'], }); useEffect(() => { diff --git a/packages/apps/dashboard/ui/src/components/Kvstore/Success.tsx b/packages/apps/dashboard/ui/src/components/Kvstore/Success.tsx index 722b301598..f28f592bde 100644 --- a/packages/apps/dashboard/ui/src/components/Kvstore/Success.tsx +++ b/packages/apps/dashboard/ui/src/components/Kvstore/Success.tsx @@ -74,7 +74,7 @@ export const Success: FC = ({ setStep, setPage, keys, what }) => { address: NETWORKS[chain?.id as ChainId]?.kvstoreAddress as `0x${string}`, abi: KVStore, functionName: 'set', - args: ['publicKey', cid], + args: ['public_key', cid], }); const { write, isLoading, data } = useContractWrite({ diff --git a/packages/apps/fortune/exchange-oracle/server/.env.example b/packages/apps/fortune/exchange-oracle/server/.env.example index c2451f75c9..8e6abb9f59 100644 --- a/packages/apps/fortune/exchange-oracle/server/.env.example +++ b/packages/apps/fortune/exchange-oracle/server/.env.example @@ -2,11 +2,26 @@ NODE_ENV=development PORT= +# Database +POSTGRES_HOST=0.0.0.0 +POSTGRES_USER=operator +POSTGRES_PASSWORD=qwerty +POSTGRES_DATABASE=reputation-oracle +POSTGRES_SYNC=false +POSTGRES_PORT=5432 +POSTGRES_SSL=false +POSTGRES_LOGGING='all' + +# S3 S3_ENDPOINT= S3_PORT= S3_ACCESS_KEY= S3_SECRET_KEY= S3_REGION= +S3_USE_SSL= WEB3_PRIVATE_KEY= +PGP_ENCRYPT= +PGP_PRIVATE_KEY= +PGP_PASSPHRASE= \ No newline at end of file diff --git a/packages/apps/fortune/exchange-oracle/server/docker-compose.yml b/packages/apps/fortune/exchange-oracle/server/docker-compose.yml index f9dc699676..d1eeb26cae 100644 --- a/packages/apps/fortune/exchange-oracle/server/docker-compose.yml +++ b/packages/apps/fortune/exchange-oracle/server/docker-compose.yml @@ -1,6 +1,24 @@ version: '3.7' -services: +services: + postgres: + image: postgres:latest + restart: always + environment: + - POSTGRES_HOST=${POSTGRES_HOST} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DATABASE} + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_SYNC=${POSTGRES_SYNC} + logging: + options: + max-size: 10m + max-file: "3" + ports: + - '${POSTGRES_PORT}:${POSTGRES_PORT}' + # volumes: + # - ./db:/var/lib/postgresql/data minio: container_name: minio image: minio/minio:RELEASE.2022-05-26T05-48-41Z diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json index bdf9f26be0..0d9ce38f40 100644 --- a/packages/apps/fortune/exchange-oracle/server/package.json +++ b/packages/apps/fortune/exchange-oracle/server/package.json @@ -13,10 +13,18 @@ "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", + "migration:create": "yarn build && typeorm-ts-node-commonjs migration:create", + "migration:generate": "yarn build && typeorm-ts-node-commonjs migration:generate -p -d typeorm.config.ts", + "migration:revert": "yarn build && typeorm-ts-node-commonjs migration:revert -d typeorm.config.ts", + "migration:run": "yarn build && typeorm-ts-node-commonjs migration:run -d typeorm.config.ts", + "migration:show": "yarn build && typeorm-ts-node-commonjs migration:show -d typeorm.config.ts", + "docker:db:up": "docker compose up -d && yarn migration:run", + "docker:db:down": "docker compose down", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "setup:local": "ts-node ./test/setup.ts" }, "dependencies": { "@human-protocol/sdk": "*", @@ -35,13 +43,13 @@ "@nestjs/testing": "^10.3.1", "@types/express": "^4.17.13", "@types/jest": "29.5.11", - "@types/node": "20.11.19", + "@types/node": "20.11.20", "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.55.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-prettier": "^5.1.3", "jest": "29.7.0", "prettier": "^3.2.5", "source-map-support": "^0.5.20", diff --git a/packages/apps/fortune/exchange-oracle/server/src/app.module.ts b/packages/apps/fortune/exchange-oracle/server/src/app.module.ts index d4a0073382..8f4592f89e 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/app.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/app.module.ts @@ -5,6 +5,8 @@ import { ConfigModule } from '@nestjs/config'; import { envValidator } from './common/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; +import { DatabaseModule } from './database/database.module'; +import { WebhookModule } from './modules/webhook/webhook.module'; @Module({ providers: [ @@ -15,12 +17,14 @@ import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; ], imports: [ JobModule, + WebhookModule, ConfigModule.forRoot({ envFilePath: process.env.NODE_ENV ? `.env.${process.env.NODE_ENV as string}` : '.env', validationSchema: envValidator, }), + DatabaseModule, ], controllers: [AppController], }) diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts index 5518058f9a..f5d5354605 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/env.ts @@ -1,8 +1,17 @@ import * as Joi from 'joi'; export const ConfigNames = { + NODE_ENV: 'NODE_ENV', HOST: 'HOST', PORT: 'PORT', + POSTGRES_HOST: 'POSTGRES_HOST', + POSTGRES_USER: 'POSTGRES_USER', + POSTGRES_PASSWORD: 'POSTGRES_PASSWORD', + POSTGRES_DATABASE: 'POSTGRES_DATABASE', + POSTGRES_PORT: 'POSTGRES_PORT', + POSTGRES_SYNC: 'POSTGRES_SYNC', + POSTGRES_SSL: 'POSTGRES_SSL', + POSTGRES_LOGGING: 'POSTGRES_LOGGING', WEB3_PRIVATE_KEY: 'WEB3_PRIVATE_KEY', S3_ENDPOINT: 'S3_ENDPOINT', S3_PORT: 'S3_PORT', @@ -10,14 +19,27 @@ export const ConfigNames = { S3_SECRET_KEY: 'S3_SECRET_KEY', S3_BUCKET: 'S3_BUCKET', S3_USE_SSL: 'S3_USE_SSL', - ENCRYPTION_PRIVATE_KEY: 'ENCRYPTION_PRIVATE_KEY', - ENCRYPTION_PASSPHRASE: 'ENCRYPTION_PASSPHRASE', - RECORDING_ORACLE_ADDRESS: 'RECORDING_ORACLE_ADDRESS', + PGP_ENCRYPT: 'PGP_ENCRYPT', + PGP_PRIVATE_KEY: 'ENCRYPTION_PRIVATE_KEY', + PGP_PASSPHRASE: 'PGP_PASSPHRASE', }; export const envValidator = Joi.object({ + // General + NODE_ENV: Joi.string().default('development'), HOST: Joi.string().default('localhost'), PORT: Joi.string().default(3002), + // Database + DB_TYPE: Joi.string().default('postgres'), + POSTGRES_HOST: Joi.string().default('127.0.0.1'), + POSTGRES_USER: Joi.string().default('operator'), + POSTGRES_PASSWORD: Joi.string().default('qwerty'), + POSTGRES_DATABASE: Joi.string().default('reputation-oracle'), + POSTGRES_PORT: Joi.string().default('5432'), + POSTGRES_SYNC: Joi.string().default('false'), + POSTGRES_SSL: Joi.string().default('false'), + POSTGRES_LOGGING: Joi.string(), + // Web3 WEB3_PRIVATE_KEY: Joi.string().required(), // S3 S3_ENDPOINT: Joi.string().default('127.0.0.1'), @@ -26,7 +48,7 @@ export const envValidator = Joi.object({ S3_SECRET_KEY: Joi.string().required(), S3_BUCKET: Joi.string().default('solution'), S3_USE_SSL: Joi.string().default(false), - ENCRYPTION_PRIVATE_KEY: Joi.string().default(''), - ENCRYPTION_PASSPHRASE: Joi.string().default(''), - RECORDING_ORACLE_ADDRESS: Joi.string().required(), + PGP_ENCRYPT: Joi.boolean().default(false), + PGP_PRIVATE_KEY: Joi.string().optional(), + PGP_PASSPHRASE: Joi.string().optional(), }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts b/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts index 779c41ac20..2366a8537d 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/config/networks.ts @@ -1,3 +1,5 @@ +import { ChainId } from "@human-protocol/sdk"; + export interface NetworkDto { chainId: number; rpcUrl: string; @@ -9,31 +11,35 @@ interface NetworkMapDto { export const networkMap: NetworkMapDto = { polygon: { - chainId: 137, + chainId: ChainId.POLYGON, rpcUrl: 'https://polygon-mainnet.g.alchemy.com/v2/0Lorh5KRkGl5FsRwy2epTg8fEFFoqUfY', }, bsc: { - chainId: 56, + chainId: ChainId.BSC_MAINNET, rpcUrl: 'https://bsc-dataseed1.binance.org/', }, mumbai: { - chainId: 80001, + chainId: ChainId.POLYGON_MUMBAI, rpcUrl: 'https://polygon-mumbai.g.alchemy.com/v2/vKNSJzJf6SW2sdW-05bgFwoyFxUrMzii', }, goerli: { - chainId: 5, + chainId: ChainId.GOERLI, rpcUrl: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', }, moonbeam: { - chainId: 1284, + chainId: ChainId.MOONBEAM, rpcUrl: 'https://rpc.api.moonbeam.network', }, bsctest: { - chainId: 97, + chainId: ChainId.BSC_TESTNET, rpcUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545/', }, + localhost: { + chainId: ChainId.LOCALHOST, + rpcUrl: 'http://localhost:8545/', + }, }; export const networks = Object.values(networkMap).map((network) => network); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts index 6b2ae98f3f..17180042e3 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/constant/index.ts @@ -1,2 +1,3 @@ export const HEADER_SIGNATURE_KEY = 'human-signature'; export const ESCROW_FAILED_ENDPOINT = '/job/escrow-failed-webhook'; +export const NS = 'hmt'; diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/enums/role.ts b/packages/apps/fortune/exchange-oracle/server/src/common/enums/role.ts index 1a36187f02..55850ba398 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/enums/role.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/enums/role.ts @@ -1,5 +1,5 @@ export enum Role { - JobLaucher = 'job_launcher', + JobLauncher = 'job_launcher', Recording = 'recording', Reputation = 'reputation', } diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts index 2c89dd5fea..b0845b5393 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.spec.ts @@ -5,6 +5,7 @@ import { verifySignature } from '../utils/signature'; import { ChainId, EscrowUtils } from '@human-protocol/sdk'; import { MOCK_ADDRESS } from '../../../test/constants'; import { Role } from '../enums/role'; +import { HEADER_SIGNATURE_KEY } from '../constant'; jest.mock('../../common/utils/signature'); @@ -26,7 +27,7 @@ describe('SignatureAuthGuard', () => { providers: [ { provide: SignatureAuthGuard, - useValue: new SignatureAuthGuard([Role.JobLaucher, Role.Recording]), + useValue: new SignatureAuthGuard([Role.JobLauncher, Role.Recording]), }, ], }).compile(); @@ -57,7 +58,7 @@ describe('SignatureAuthGuard', () => { }); it('should return true if signature is verified', async () => { - mockRequest.headers['header-signature-key'] = 'validSignature'; + mockRequest.headers[HEADER_SIGNATURE_KEY] = 'validSignature'; mockRequest.body = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts index e8e57cc4ac..8ecc9d0372 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/guards/signature.auth.ts @@ -24,7 +24,7 @@ export class SignatureAuthGuard implements CanActivate { data.chainId, data.escrowAddress, ); - if (this.role.includes(Role.JobLaucher)) + if (this.role.includes(Role.JobLauncher)) oracleAdresses.push(escrowData.launcher); if (this.role.includes(Role.Recording)) oracleAdresses.push(escrowData.recordingOracle!); diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/utils/case-converter.ts b/packages/apps/fortune/exchange-oracle/server/src/common/utils/case-converter.ts index 980ddc589a..54bd31c904 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/utils/case-converter.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/utils/case-converter.ts @@ -1,15 +1,21 @@ export class CaseConverter { - static transformToCamelCase(obj: Record): Record { - const result: Record = {}; - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - const camelCaseKey = key.replace(/_([a-z])/g, (g) => - g[1].toUpperCase(), - ); - result[camelCaseKey] = obj[key]; - } + static transformToCamelCase(obj: any): any { + if (Array.isArray(obj)) { + return obj.map((item) => CaseConverter.transformToCamelCase(item)); + } else if (typeof obj === 'object' && obj !== null) { + return Object.keys(obj).reduce( + (acc: Record, key: string) => { + const camelCaseKey = key.replace(/_([a-z])/g, (g) => + g[1].toUpperCase(), + ); + acc[camelCaseKey] = CaseConverter.transformToCamelCase(obj[key]); + return acc; + }, + {}, + ); + } else { + return obj; } - return result; } static transformToSnakeCase(obj: any): any { diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/base.entity.ts b/packages/apps/fortune/exchange-oracle/server/src/database/base.entity.ts new file mode 100644 index 0000000000..963505429c --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/base.entity.ts @@ -0,0 +1,31 @@ +import { + BaseEntity as OrmBaseEntity, + BeforeInsert, + BeforeUpdate, + Column, + PrimaryGeneratedColumn, +} from 'typeorm'; + +export abstract class BaseEntity extends OrmBaseEntity { + @PrimaryGeneratedColumn() + public id: number; + + @Column({ type: 'timestamptz' }) + public createdAt: Date; + + @Column({ type: 'timestamptz' }) + public updatedAt: Date; + + @BeforeInsert() + public beforeInsert(): void { + const date = new Date(); + this.createdAt = date; + this.updatedAt = date; + } + + @BeforeUpdate() + public beforeUpdate(): void { + const date = new Date(); + this.updatedAt = date; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/base.repository.ts b/packages/apps/fortune/exchange-oracle/server/src/database/base.repository.ts new file mode 100644 index 0000000000..b00937d7e2 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/base.repository.ts @@ -0,0 +1,40 @@ +import { + DataSource, + EntityTarget, + ObjectLiteral, + QueryFailedError, + Repository, +} from 'typeorm'; +import { handleQueryFailedError } from './database.error'; + +export class BaseRepository extends Repository { + constructor(target: EntityTarget, dataSource: DataSource) { + super(target, dataSource.createEntityManager()); + } + + async createUnique(item: T): Promise { + try { + await this.insert(item); + } catch (error) { + if (error instanceof QueryFailedError) { + throw handleQueryFailedError(error); + } else { + throw error; + } + } + return item; + } + + async updateOne(item: T): Promise { + try { + await this.save(item); + } catch (error) { + if (error instanceof QueryFailedError) { + throw handleQueryFailedError(error); + } else { + throw error; + } + } + return item; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/database.enum.ts b/packages/apps/fortune/exchange-oracle/server/src/database/database.enum.ts new file mode 100644 index 0000000000..622d737e8f --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/database.enum.ts @@ -0,0 +1,4 @@ +export enum PostgresErrorCodes { + Duplicated = '23505', + NumericFieldOverflow = '22003', +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/database.error.ts b/packages/apps/fortune/exchange-oracle/server/src/database/database.error.ts new file mode 100644 index 0000000000..c45ed94452 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/database.error.ts @@ -0,0 +1,26 @@ +import { QueryFailedError } from 'typeorm'; +import { PostgresErrorCodes } from './database.enum'; + +export class DatabaseError extends Error { + constructor(message: string, stack: string) { + super(message); + this.stack = stack; + } +} + +export function handleQueryFailedError(error: QueryFailedError): DatabaseError { + const stack = error.stack || ''; + let message = error.message; + + switch ((error.driverError as any).code) { + case PostgresErrorCodes.Duplicated: + message = (error.driverError as any).detail; + break; + case PostgresErrorCodes.NumericFieldOverflow: + message = 'Incorrect amount'; + break; + default: + break; + } + return new DatabaseError(message, stack); +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts b/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts new file mode 100644 index 0000000000..7d71004f52 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/database.module.ts @@ -0,0 +1,76 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import * as path from 'path'; +import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; +import { NS } from '../common/constant'; + +import { TypeOrmLoggerModule, TypeOrmLoggerService } from './typeorm'; +import { LoggerOptions } from 'typeorm'; +import { ConfigNames } from '../common/config'; + +@Module({ + imports: [ + TypeOrmModule.forRootAsync({ + imports: [TypeOrmLoggerModule, ConfigModule], + inject: [TypeOrmLoggerService, ConfigService], + useFactory: ( + typeOrmLoggerService: TypeOrmLoggerService, + configService: ConfigService, + ) => { + const loggerOptions = configService + .get(ConfigNames.POSTGRES_LOGGING) + ?.split(', '); + typeOrmLoggerService.setOptions( + loggerOptions && loggerOptions[0] === 'all' + ? 'all' + : (loggerOptions as LoggerOptions) ?? false, + ); + return { + name: 'default', + type: 'postgres', + entities: [], + // We are using migrations, synchronize should be set to false. + synchronize: false, + // Run migrations automatically, + // you can disable this if you prefer running migration manually. + migrationsTableName: NS, + migrationsTransactionMode: 'each', + namingStrategy: new SnakeNamingStrategy(), + logging: true, + // Allow both start:prod and start:dev to use migrations + // __dirname is either dist or server folder, meaning either + // the compiled js in prod or the ts in dev. + migrations: [path.join(__dirname, '/migrations/**/*{.ts,.js}')], + //"migrations": ["dist/migrations/*{.ts,.js}"], + logger: typeOrmLoggerService, + host: configService.get( + ConfigNames.POSTGRES_HOST, + 'localhost', + ), + port: configService.get(ConfigNames.POSTGRES_PORT, 5432), + username: configService.get( + ConfigNames.POSTGRES_USER, + 'operator', + ), + password: configService.get( + ConfigNames.POSTGRES_PASSWORD, + 'qwerty', + ), + database: configService.get( + ConfigNames.POSTGRES_DATABASE, + 'exchange-oracle', + ), + keepConnectionAlive: + configService.get(ConfigNames.NODE_ENV) === 'test', + migrationsRun: false, + ssl: + configService + .get(ConfigNames.POSTGRES_SSL) + ?.toLowerCase() === 'true', + }; + }, + }), + ], +}) +export class DatabaseModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1709817540534-initialMigration.ts b/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1709817540534-initialMigration.ts new file mode 100644 index 0000000000..47f1857f69 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/migrations/1709817540534-initialMigration.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class InitialMigration1709817540534 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + } + + public async down(queryRunner: QueryRunner): Promise { + } + +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/index.ts b/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/index.ts new file mode 100644 index 0000000000..5a07b7c166 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/index.ts @@ -0,0 +1,2 @@ +export * from './typeorm-logger.service'; +export * from './typeorm-logger.module'; diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/typeorm-logger.module.ts b/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/typeorm-logger.module.ts new file mode 100644 index 0000000000..b3bc43d760 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/typeorm-logger.module.ts @@ -0,0 +1,9 @@ +import { Module, Logger } from '@nestjs/common'; + +import { TypeOrmLoggerService } from './typeorm-logger.service'; + +@Module({ + providers: [Logger, TypeOrmLoggerService], + exports: [TypeOrmLoggerService], +}) +export class TypeOrmLoggerModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/typeorm-logger.service.ts b/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/typeorm-logger.service.ts new file mode 100644 index 0000000000..f6e47ed3b4 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/src/database/typeorm/typeorm-logger.service.ts @@ -0,0 +1,97 @@ +import * as util from 'util'; +import { + Logger as TypeOrmLogger, + LoggerOptions as TypeOrmLoggerOptions, +} from 'typeorm'; +import { Inject, Injectable, Logger, LoggerService } from '@nestjs/common'; + +@Injectable() +export class TypeOrmLoggerService implements TypeOrmLogger { + private options: TypeOrmLoggerOptions = 'all'; + + constructor(@Inject(Logger) private readonly loggerService: LoggerService) {} + + public setOptions(options: TypeOrmLoggerOptions = 'all'): void { + this.options = options; + } + + logQuery(query: string, parameters?: any[]): void { + if ( + this.options === 'all' || + this.options === true || + (this.options instanceof Array && this.options.indexOf('query') !== -1) + ) { + this.loggerService.log( + `query : ${query} ${this.stringifyParams(parameters)}`, + 'TypeOrm', + ); + } + } + + logQueryError(error: string, query: string, parameters?: any[]): void { + if ( + this.options === 'all' || + this.options === true || + (this.options instanceof Array && this.options.indexOf('error') !== -1) + ) { + this.loggerService.log( + `query failed: ${query} ${this.stringifyParams(parameters)}`, + 'TypeOrm', + ); + this.loggerService.log(`error: ${error}`, 'TypeOrm'); + } + } + + logQuerySlow(time: number, query: string, parameters?: any[]): void { + this.loggerService.log( + `query is slow: ${query} ${this.stringifyParams(parameters)}`, + 'TypeOrm', + ); + this.loggerService.log(`execution time: ${time}`, 'TypeOrm'); + } + + logSchemaBuild(message: string): void { + if ( + this.options === 'all' || + (this.options instanceof Array && this.options.indexOf('schema') !== -1) + ) { + this.loggerService.log(message, 'TypeOrm'); + } + } + + logMigration(message: string): void { + this.loggerService.log(message, 'TypeOrm'); + } + + log(level: 'log' | 'info' | 'warn', message: unknown): void { + switch (level) { + case 'log': + if ( + this.options === 'all' || + (this.options instanceof Array && this.options.indexOf('log') !== -1) + ) + this.loggerService.log(message, 'TypeOrm'); + break; + case 'info': + if ( + this.options === 'all' || + (this.options instanceof Array && this.options.indexOf('info') !== -1) + ) + this.loggerService.log(message, 'TypeOrm'); + break; + case 'warn': + if ( + this.options === 'all' || + (this.options instanceof Array && this.options.indexOf('warn') !== -1) + ) + this.loggerService.warn(message, 'TypeOrm'); + break; + } + } + + protected stringifyParams(parameters: any[] = []): string { + return parameters.length + ? ` -- PARAMETERS: ${util.inspect(parameters)}` + : ''; + } +} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts index ceea64703c..55e8bea3e6 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.controller.ts @@ -8,14 +8,6 @@ import { JobService } from './job.service'; export class JobController { constructor(private readonly jobService: JobService) {} - @Get('details/:chain_id/:escrow_address') - getDetails( - @Param('chain_id') chainId: number, - @Param('escrow_address') escrowAddress: string, - ): Promise { - return this.jobService.getDetails(chainId, escrowAddress); - } - @Get('pending/:chain_id/:worker_address') getPendingJobs( @Param('chain_id') chainId: number, @@ -24,6 +16,14 @@ export class JobController { return this.jobService.getPendingJobs(chainId, workerAddress); } + @Get('details/:chain_id/:escrow_address') + getDetails( + @Param('chain_id') chainId: number, + @Param('escrow_address') escrowAddress: string, + ): Promise { + return this.jobService.getDetails(chainId, escrowAddress); + } + @Post('solve') solveJob(@Body() body: SolveJobDto): Promise { return this.jobService.solveJob( diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts index 2c9a4528a0..a9f5175480 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.module.ts @@ -4,18 +4,12 @@ import { JobController } from './job.controller'; import { JobService } from './job.service'; import { HttpModule } from '@nestjs/axios'; import { Web3Module } from '../web3/web3.module'; -import { s3Config } from 'src/common/config'; import { StorageModule } from '../storage/storage.module'; @Module({ - imports: [ - ConfigModule.forFeature(s3Config), - ConfigModule, - HttpModule, - Web3Module, - StorageModule, - ], + imports: [ConfigModule, HttpModule, Web3Module, StorageModule], controllers: [JobController], providers: [JobService], + exports: [JobService], }) export class JobModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts index 1f21a401c3..579e919d7f 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts @@ -10,6 +10,7 @@ import { EscrowUtils, OperatorUtils, Encryption, + EncryptionUtils, } from '@human-protocol/sdk'; import { JOB_LAUNCHER_WEBHOOK_URL, @@ -152,6 +153,7 @@ describe('JobService', () => { fundAmount: 100, }; + EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true); StorageClient.downloadFileFromUrl = jest .fn() .mockResolvedValueOnce('encrypted string'); @@ -179,6 +181,7 @@ describe('JobService', () => { fundAmount: 100, }; + EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(false); StorageClient.downloadFileFromUrl = jest .fn() .mockResolvedValueOnce(JSON.stringify(manifest)); @@ -217,7 +220,7 @@ describe('JobService', () => { escrow_address: escrowAddress, chain_id: chainId, event_type: EventType.TASK_CREATION_FAILED, - event_data: [{ reason: 'Unable to get manifest' }], + event_data: { assignments: [{ reason: 'Unable to get manifest' }] }, }; expect(httpServicePostMock).toHaveBeenCalledWith( JOB_LAUNCHER_WEBHOOK_URL + ESCROW_FAILED_ENDPOINT, @@ -263,13 +266,14 @@ describe('JobService', () => { }); it('should fail if encrypted manifest is invalid', async () => { - const manifest: ManifestDto = { + const manifest = JSON.stringify({ requesterTitle: 'Example Title', requesterDescription: 'Example Description', submissionsRequired: 5, fundAmount: 100, - }; + }); + EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true); StorageClient.downloadFileFromUrl = jest .fn() .mockResolvedValueOnce(manifest) @@ -358,10 +362,6 @@ describe('JobService', () => { .fn() .mockResolvedValueOnce(manifest); - (Encryption.build as any).mockImplementation(() => ({ - decrypt: jest.fn().mockResolvedValue(JSON.stringify(manifest)), - })); - const solutionsUrl = 'http://localhost:9000/solution/0x1234567890123456789012345678901234567890-1.json'; @@ -439,11 +439,6 @@ describe('JobService', () => { it('should fail if the escrow address is invalid', async () => { const escrowAddress = 'invalid_address'; const solution = 'job-solution'; - (EscrowClient.build as any).mockImplementation(() => ({ - getRecordingOracleAddress: jest - .fn() - .mockRejectedValue(new Error('Invalid address')), - })); await expect( jobService.solveJob(chainId, escrowAddress, workerAddress, solution), @@ -452,13 +447,28 @@ describe('JobService', () => { }); it('should fail if recording oracle url is empty', async () => { + const manifest: ManifestDto = { + requesterTitle: 'Example Title', + requesterDescription: 'Example Description', + submissionsRequired: 5, + fundAmount: 100, + }; + + storageService.downloadJobSolutions = jest.fn().mockResolvedValueOnce([]); + + StorageClient.downloadFileFromUrl = jest + .fn() + .mockResolvedValueOnce(manifest); + + const solutionsUrl = + 'http://localhost:9000/solution/0x1234567890123456789012345678901234567890-1.json'; + + storageService.uploadJobSolutions = jest + .fn() + .mockResolvedValue(solutionsUrl); + const solution = 'job-solution'; - (EscrowClient.build as any).mockImplementation(() => ({ - getRecordingOracleAddress: jest - .fn() - .mockResolvedValue('0x1234567890123456789012345678901234567893'), - })); OperatorUtils.getLeader = jest.fn().mockResolvedValue({ webhookUrl: '', }); @@ -517,7 +527,7 @@ describe('JobService', () => { chainId, escrowAddress, eventType: EventType.SUBMISSION_REJECTED, - eventData: [{ assigneeId: workerAddress }], + eventData: { assignments: [{ assigneeId: workerAddress }] }, }); expect(storageService.uploadJobSolutions).toHaveBeenCalledWith( @@ -553,7 +563,7 @@ describe('JobService', () => { chainId, escrowAddress, eventType: EventType.SUBMISSION_REJECTED, - eventData: [{ assigneeId: workerAddress }], + eventData: { assignments: [{ assigneeId: workerAddress }] }, }), ).rejects.toThrow(`Solution not found in Escrow: ${escrowAddress}`); }); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts index 9468801c8b..0aa0ce3b1d 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts @@ -1,6 +1,7 @@ import { ChainId, Encryption, + EncryptionUtils, EscrowClient, EscrowStatus, EscrowUtils, @@ -28,7 +29,9 @@ import { StorageService } from '../storage/storage.service'; import { Web3Service } from '../web3/web3.service'; import { JobDetailsDto, ManifestDto } from './job.dto'; import { CaseConverter } from '../../common/utils/case-converter'; -import { WebhookDto } from '../webhook/webhook.dto'; +import { firstValueFrom } from 'rxjs'; +import { ethers } from 'ethers'; +import { RejectionEventData, WebhookDto } from '../webhook/webhook.dto'; @Injectable() export class JobService { @@ -94,6 +97,17 @@ export class JobService { workerAddress: string, solution: string, ): Promise { + if (!ethers.isAddress(escrowAddress)) { + throw new Error('Invalid address'); + } + + const solutionsUrl = await this.addSolution( + chainId, + escrowAddress, + workerAddress, + solution, + ); + const signer = this.web3Service.getSigner(chainId); const escrowClient = await EscrowClient.build(signer); const recordingOracleAddress = @@ -108,13 +122,6 @@ export class JobService { if (!recordingOracleWebhookUrl) throw new NotFoundException('Unable to get Recording Oracle webhook URL'); - const solutionsUrl = await this.addSolution( - chainId, - escrowAddress, - workerAddress, - solution, - ); - await this.sendWebhook(recordingOracleWebhookUrl, { escrowAddress: escrowAddress, chainId: chainId, @@ -131,7 +138,9 @@ export class JobService { invalidJobSolution.escrowAddress, invalidJobSolution.chainId, ); - for (const invalidSolution of invalidJobSolution.eventData) { + for (const invalidSolution of ( + invalidJobSolution.eventData as RejectionEventData + )?.assignments) { const foundSolution = existingJobSolutions.find( (sol) => sol.workerAddress === invalidSolution.assigneeId, ); @@ -203,9 +212,11 @@ export class JobService { snake_case_body, this.configService.get(ConfigNames.WEB3_PRIVATE_KEY)!, ); - await this.httpService.post(url, snake_case_body, { - headers: { [HEADER_SIGNATURE_KEY]: signedBody }, - }); + await firstValueFrom( + this.httpService.post(url, snake_case_body, { + headers: { [HEADER_SIGNATURE_KEY]: signedBody }, + }), + ); } private async getManifest( @@ -219,24 +230,29 @@ export class JobService { await StorageClient.downloadFileFromUrl(manifestUrl); let manifest: ManifestDto | null; - - try { - manifest = JSON.parse(manifestEncrypted); - } catch { - manifest = null; - } - - if (!manifest) { + if ( + typeof manifestEncrypted === 'string' && + EncryptionUtils.isEncrypted(manifestEncrypted) + ) { try { const encryption = await Encryption.build( - this.configService.get(ConfigNames.ENCRYPTION_PRIVATE_KEY, ''), - this.configService.get(ConfigNames.ENCRYPTION_PASSPHRASE), + this.configService.get(ConfigNames.PGP_PRIVATE_KEY, ''), + this.configService.get(ConfigNames.PGP_PASSPHRASE), ); manifest = JSON.parse(await encryption.decrypt(manifestEncrypted)); } catch { throw new Error('Unable to decrypt manifest'); } + } else { + try { + manifest = + typeof manifestEncrypted === 'string' + ? JSON.parse(manifestEncrypted) + : manifestEncrypted; + } catch { + manifest = null; + } } if (!manifest) { @@ -258,7 +274,7 @@ export class JobService { escrowAddress: escrowAddress, chainId: chainId, eventType: EventType.TASK_CREATION_FAILED, - eventData: [{ reason: 'Unable to get manifest' }], + eventData: { assignments: [{ reason: 'Unable to get manifest' }] }, }; await this.sendWebhook( jobLauncherWebhookUrl + ESCROW_FAILED_ENDPOINT, diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts index 3938a6b586..332c753ead 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.spec.ts @@ -2,12 +2,14 @@ import { ChainId, Encryption, EncryptionUtils, - OperatorUtils, + KVStoreClient, StorageClient, + EscrowClient, } from '@human-protocol/sdk'; import { ConfigModule, registerAs } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { + MOCK_ADDRESS, MOCK_S3_ACCESS_KEY, MOCK_S3_BUCKET, MOCK_S3_ENDPOINT, @@ -17,12 +19,10 @@ import { } from '../../../test/constants'; import { StorageService } from './storage.service'; import { Web3Service } from '../web3/web3.service'; +import { ConfigService } from '@nestjs/config'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), - OperatorUtils: { - getLeader: jest.fn(), - }, StorageClient: { downloadFileFromUrl: jest.fn(), }, @@ -32,6 +32,14 @@ jest.mock('@human-protocol/sdk', () => ({ EncryptionUtils: { encrypt: jest.fn(), }, + KVStoreClient: { + build: jest.fn().mockImplementation(() => ({ + getPublicKey: jest.fn(), + })), + }, + EscrowClient: { + build: jest.fn(), + }, })); jest.mock('minio', () => { @@ -56,6 +64,10 @@ describe('StorageService', () => { getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), }; + const configServiceMock: Partial = { + get: jest.fn(), + }; + beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ @@ -78,6 +90,10 @@ describe('StorageService', () => { getSigner: jest.fn().mockReturnValue(signerMock), }, }, + { + provide: ConfigService, + useValue: configServiceMock, + }, ], }).compile(); @@ -85,7 +101,13 @@ describe('StorageService', () => { }); describe('uploadJobSolutions', () => { - it('should upload the solutions correctly', async () => { + beforeAll(async () => { + (EscrowClient.build as any).mockImplementation(() => ({ + getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + })); + }); + + it('should upload the solutions with encryption correctly', async () => { const workerAddress = '0x1234567890123456789012345678901234567891'; const escrowAddress = '0x1234567890123456789012345678901234567890'; const chainId = ChainId.LOCALHOST; @@ -95,9 +117,44 @@ describe('StorageService', () => { .fn() .mockResolvedValue(true); EncryptionUtils.encrypt = jest.fn().mockResolvedValue('encrypted'); - OperatorUtils.getLeader = jest.fn().mockResolvedValue({ - publicKey: 'publicKey', + (KVStoreClient.build as jest.Mock).mockResolvedValue({ + getPublicKey: jest.fn().mockResolvedValue('publicKey'), }); + configServiceMock.get = jest.fn().mockReturnValueOnce(true); + + const jobSolution = { + workerAddress, + solution, + }; + const fileUrl = await storageService.uploadJobSolutions( + escrowAddress, + chainId, + [jobSolution], + ); + expect(fileUrl).toBe( + `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${escrowAddress}-${chainId}.json`, + ); + expect(storageService.minioClient.putObject).toHaveBeenCalledWith( + MOCK_S3_BUCKET, + `${escrowAddress}-${chainId}.json`, + 'encrypted', + { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + }, + ); + }); + + it('should upload the solutions without encryption correctly', async () => { + const workerAddress = '0x1234567890123456789012345678901234567891'; + const escrowAddress = '0x1234567890123456789012345678901234567890'; + const chainId = ChainId.LOCALHOST; + const solution = 'test'; + + storageService.minioClient.bucketExists = jest + .fn() + .mockResolvedValue(true); + configServiceMock.get = jest.fn().mockReturnValueOnce(true); const jobSolution = { workerAddress, @@ -152,6 +209,7 @@ describe('StorageService', () => { storageService.minioClient.bucketExists = jest .fn() .mockResolvedValue(true); + configServiceMock.get = jest.fn().mockReturnValueOnce(false); storageService.minioClient.putObject = jest .fn() .mockRejectedValue('Network error'); @@ -177,8 +235,11 @@ describe('StorageService', () => { storageService.minioClient.bucketExists = jest .fn() .mockResolvedValue(true); + configServiceMock.get = jest.fn().mockReturnValueOnce(true); EncryptionUtils.encrypt = jest.fn().mockResolvedValue('encrypted'); - OperatorUtils.getLeader = jest.fn().mockResolvedValue({}); + (KVStoreClient.build as jest.Mock).mockResolvedValue({ + getPublicKey: jest.fn().mockResolvedValue(''), + }); const jobSolution = { workerAddress, @@ -188,12 +249,12 @@ describe('StorageService', () => { storageService.uploadJobSolutions(escrowAddress, chainId, [ jobSolution, ]), - ).rejects.toThrow('Missing public key'); + ).rejects.toThrow('Encryption error'); }); }); describe('downloadJobSolutions', () => { - it('should download the file correctly', async () => { + it('should download the encrypted file correctly', async () => { const workerAddress = '0x1234567890123456789012345678901234567891'; const escrowAddress = '0x1234567890123456789012345678901234567890'; const chainId = ChainId.LOCALHOST; @@ -210,6 +271,8 @@ describe('StorageService', () => { .fn() .mockResolvedValue('encrypted-content'); + EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true); + (Encryption.build as any).mockImplementation(() => ({ decrypt: jest.fn().mockResolvedValue(JSON.stringify(expectedJobFile)), })); @@ -221,6 +284,32 @@ describe('StorageService', () => { expect(solutionsFile).toStrictEqual(expectedJobFile); }); + it('should download the non encrypted file correctly', async () => { + const workerAddress = '0x1234567890123456789012345678901234567891'; + const escrowAddress = '0x1234567890123456789012345678901234567890'; + const chainId = ChainId.LOCALHOST; + const solution = 'test'; + + const expectedJobFile = [ + { + workerAddress, + solution, + }, + ]; + + StorageClient.downloadFileFromUrl = jest + .fn() + .mockResolvedValue(expectedJobFile); + + EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(false); + + const solutionsFile = await storageService.downloadJobSolutions( + escrowAddress, + chainId, + ); + expect(solutionsFile).toStrictEqual(expectedJobFile); + }); + it('should return empty array when file cannot be downloaded', async () => { const escrowAddress = '0x1234567890123456789012345678901234567890'; const chainId = ChainId.LOCALHOST; diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts index 8ef1a72f28..5395afb92b 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/storage/storage.service.ts @@ -2,10 +2,16 @@ import { ChainId, Encryption, EncryptionUtils, - OperatorUtils, + KVStoreClient, + EscrowClient, StorageClient, } from '@human-protocol/sdk'; -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { + BadRequestException, + Inject, + Injectable, + Logger, +} from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as Minio from 'minio'; import { ConfigNames, S3ConfigType, s3ConfigKey } from '../../common/config'; @@ -45,16 +51,19 @@ export class StorageService { ): Promise { const url = this.getJobUrl(escrowAddress, chainId); try { - const encryption = await Encryption.build( - this.configService.get(ConfigNames.ENCRYPTION_PRIVATE_KEY, ''), - this.configService.get(ConfigNames.ENCRYPTION_PASSPHRASE), - ); + const fileContent = await StorageClient.downloadFileFromUrl(url); + if (EncryptionUtils.isEncrypted(fileContent)) { + const encryption = await Encryption.build( + this.configService.get(ConfigNames.PGP_PRIVATE_KEY, ''), + this.configService.get(ConfigNames.PGP_PASSPHRASE), + ); - const encryptedSolution = await StorageClient.downloadFileFromUrl(url); + return JSON.parse(await encryption.decrypt(fileContent)) as ISolution[]; + } - return JSON.parse( - await encryption.decrypt(encryptedSolution), - ) as ISolution[]; + return typeof fileContent == 'string' + ? (JSON.parse(fileContent) as ISolution[]) + : fileContent; } catch { return []; } @@ -69,29 +78,44 @@ export class StorageService { throw new BadRequestException('Bucket not found'); } - const signer = this.web3Service.getSigner(chainId); - const exchangeOracle = await OperatorUtils.getLeader( - chainId, - signer.address, - ); - const recordingOracle = await OperatorUtils.getLeader( - chainId, - this.configService.get(ConfigNames.RECORDING_ORACLE_ADDRESS, ''), - ); - if (!exchangeOracle.publicKey || !recordingOracle.publicKey) { - throw new BadRequestException('Missing public key'); + let fileToUpload = JSON.stringify(solutions); + if (this.configService.get(ConfigNames.PGP_ENCRYPT) as boolean) { + try { + const signer = this.web3Service.getSigner(chainId); + const escrowClient = await EscrowClient.build(signer); + const recordingOracleAddress = + await escrowClient.getRecordingOracleAddress(escrowAddress); + + const kvstoreClient = await KVStoreClient.build(signer); + + const exchangeOraclePublickKey = await kvstoreClient.getPublicKey( + signer.address, + ); + const recordingOraclePublicKey = await kvstoreClient.getPublicKey( + recordingOracleAddress, + ); + if ( + !exchangeOraclePublickKey.length || + !recordingOraclePublicKey.length + ) { + throw new BadRequestException('Missing public key'); + } + + fileToUpload = await EncryptionUtils.encrypt(fileToUpload, [ + exchangeOraclePublickKey, + recordingOraclePublicKey, + ]); + } catch (e) { + Logger.error(e); + throw new BadRequestException('Encryption error'); + } } try { - const solutionsEncrypted = await EncryptionUtils.encrypt( - JSON.stringify(solutions), - [exchangeOracle.publicKey, recordingOracle.publicKey], - ); - await this.minioClient.putObject( this.s3Config.bucket, `${escrowAddress}-${chainId}.json`, - solutionsEncrypted, + fileToUpload, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', @@ -100,6 +124,7 @@ export class StorageService { return this.getJobUrl(escrowAddress, chainId); } catch (e) { + Logger.error(e); throw new BadRequestException('File not uploaded'); } } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts index f2a504b057..c7c6adcb74 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.spec.ts @@ -126,7 +126,7 @@ describe('webhookController', () => { chainId, escrowAddress, eventType: EventType.SUBMISSION_REJECTED, - eventData: [{ assigneeId: workerAddress }], + eventData: { assignments: [{ assigneeId: workerAddress }] }, }; jest.spyOn(webhookService, 'handleWebhook').mockResolvedValue(); diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts index 5be5b41e59..cacc7aeb98 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.controller.ts @@ -14,7 +14,7 @@ export class WebhookController { constructor(private readonly webhookService: WebhookService) {} @UseGuards( - new SignatureAuthGuard([Role.Recording, Role.Reputation, Role.JobLaucher]), + new SignatureAuthGuard([Role.Recording, Role.Reputation, Role.JobLauncher]), ) @Post('webhook') processWebhook( diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts index 66406878cb..6f1d077dee 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.dto.ts @@ -1,10 +1,10 @@ import { ChainId } from '@human-protocol/sdk'; import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEnum, IsString } from 'class-validator'; +import { IsArray, IsEnum, IsString, IsObject } from 'class-validator'; import { IsValidEthereumAddress } from '../../common/validators'; import { EventType } from '../../common/enums/webhook'; -export class EventData { +export class AssignmentRejection { @ApiProperty({ name: 'assignee_id' }) @IsString() assigneeId?: string; @@ -14,6 +14,22 @@ export class EventData { reason?: string; } +export class RejectionEventData { + @ApiProperty({ + type: [AssignmentRejection], + }) + @IsArray() + public assignments: AssignmentRejection[]; +} + +export class SolutionEventData { + @ApiProperty({ name: 'solutions_url' }) + @IsString() + solutionsUrl: string; +} + +export type EventData = RejectionEventData | SolutionEventData; + export class WebhookDto { @ApiProperty({ enum: ChainId, @@ -35,9 +51,8 @@ export class WebhookDto { public eventType: EventType; @ApiProperty({ - type: [EventData], name: 'event_data', }) - @IsArray() - public eventData?: EventData[]; + @IsObject() + public eventData?: EventData; } diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts index 8ac7591f32..a8367b96f9 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.module.ts @@ -2,11 +2,12 @@ import { Module } from '@nestjs/common'; import { WebhookController } from './webhook.controller'; import { WebhookService } from './webhook.service'; -import { JobService } from '../job/job.service'; +import { JobModule } from '../job/job.module'; @Module({ + imports: [JobModule], controllers: [WebhookController], - providers: [WebhookService, JobService], + providers: [WebhookService], exports: [WebhookService], }) export class WebhookModule {} diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts index 94b2b03898..4b6789c2fe 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/modules/webhook/webhook.service.spec.ts @@ -121,7 +121,7 @@ describe('WebhookService', () => { chainId, escrowAddress, eventType: EventType.SUBMISSION_REJECTED, - eventData: [{ assigneeId: workerAddress }], + eventData: { assignments: [{ assigneeId: workerAddress }] }, }; jest.spyOn(jobService, 'processInvalidJobSolution').mockResolvedValue(); diff --git a/packages/apps/fortune/exchange-oracle/server/test/constants.ts b/packages/apps/fortune/exchange-oracle/server/test/constants.ts index 378be6ce7e..931d9890aa 100644 --- a/packages/apps/fortune/exchange-oracle/server/test/constants.ts +++ b/packages/apps/fortune/exchange-oracle/server/test/constants.ts @@ -12,4 +12,4 @@ export const MOCK_S3_BUCKET = 'solution'; export const MOCK_S3_USE_SSL = false; export const MOCK_REPUTATION_ORACLE_WEBHOOK_URL = 'http://localhost:3000'; export const MOCK_MANIFEST_URL = 'http://localhost/manifest.json'; -export const JOB_LAUNCHER_WEBHOOK_URL = 'https://example.com/reputationoracle'; \ No newline at end of file +export const JOB_LAUNCHER_WEBHOOK_URL = 'https://example.com/reputationoracle'; diff --git a/packages/apps/fortune/exchange-oracle/server/test/setup.ts b/packages/apps/fortune/exchange-oracle/server/test/setup.ts new file mode 100644 index 0000000000..66d80d0557 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/test/setup.ts @@ -0,0 +1,16 @@ +import { KVStoreClient, KVStoreKeys, Role } from '@human-protocol/sdk'; +import { Wallet, ethers } from 'ethers'; + +export async function setup(): Promise { + //This private key is generated by hardhat and should be used just for local testing + const privateKey = + '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a'; //0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC + const provider = new ethers.JsonRpcProvider('http://localhost:8545'); + const wallet = new Wallet(privateKey, provider); + + const kvStoreClient = await KVStoreClient.build(wallet); + await kvStoreClient.setBulk( + [KVStoreKeys.role, KVStoreKeys.fee, KVStoreKeys.webhookUrl], + [Role.ExchangeOracle, '1', 'http://localhost:5001/webhook'], + ); +} diff --git a/packages/apps/fortune/exchange-oracle/server/typeorm.config.ts b/packages/apps/fortune/exchange-oracle/server/typeorm.config.ts new file mode 100644 index 0000000000..4b2b47b997 --- /dev/null +++ b/packages/apps/fortune/exchange-oracle/server/typeorm.config.ts @@ -0,0 +1,25 @@ +import { DataSource } from 'typeorm'; +import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; +import * as dotenv from 'dotenv'; + +dotenv.config({ + path: process.env.NODE_ENV + ? `.env.${process.env.NODE_ENV as string}` + : '.env', +}); + +export default new DataSource({ + type: 'postgres', + host: process.env.POSTGRES_HOST, + port: Number(process.env.POSTGRES_PORT), + username: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + database: process.env.POSTGRES_DATABASE, + entities: ['dist/src/**/*.entity{.ts,.js}'], + synchronize: false, + migrations: ['dist/src/database/migrations/*{.ts,.js}'], + migrationsTableName: 'migrations_typeorm', + migrationsRun: true, + namingStrategy: new SnakeNamingStrategy(), + ssl: process.env.POSTGRES_SSL?.toLowerCase() === 'true', +}); diff --git a/packages/apps/fortune/recording-oracle/.env.example b/packages/apps/fortune/recording-oracle/.env.example new file mode 100644 index 0000000000..5d435bbef0 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/.env.example @@ -0,0 +1,22 @@ +#General +NODE_ENV=development +HOST=localhost +PORT=5002 +SESSION_SECRET= + + +#Web3 +WEB3_PRIVATE_KEY= + +# S3 +S3_ENDPOINT= +S3_PORT= +S3_ACCESS_KEY= +S3_SECRET_KEY= +S3_BUCKET= +S3_USE_SSL= + +#Encryption +PGP_ENCRYPT= +PGP_PRIVATE_KEY= +PGP_PASSPHRASE= \ No newline at end of file diff --git a/packages/apps/fortune/recording-oracle/.gitignore b/packages/apps/fortune/recording-oracle/.gitignore index 93befa828d..ed235e7700 100644 --- a/packages/apps/fortune/recording-oracle/.gitignore +++ b/packages/apps/fortune/recording-oracle/.gitignore @@ -27,7 +27,7 @@ yarn-error.log* # env .env.* .env -!.env.sample +!.env.example # VisualStudioCode ../.vscode/ diff --git a/packages/apps/fortune/recording-oracle/package.json b/packages/apps/fortune/recording-oracle/package.json index 2dba5c2c29..8f429b3cba 100644 --- a/packages/apps/fortune/recording-oracle/package.json +++ b/packages/apps/fortune/recording-oracle/package.json @@ -19,7 +19,8 @@ "test:e2e": "jest --config ./test/jest-e2e.json", "postgres": "docker compose up -d postgres", "docker": "docker compose up -d", - "local": "docker compose down && (concurrently --hide 0 \"yarn docker\" \"yarn recording-oracle:dev\" )" + "local": "docker compose down && (concurrently --hide 0 \"yarn docker\" \"yarn recording-oracle:dev\" )", + "setup:local": "ts-node ./test/setup.ts" }, "dependencies": { "@human-protocol/sdk": "*", diff --git a/packages/apps/fortune/recording-oracle/src/app.controller.ts b/packages/apps/fortune/recording-oracle/src/app.controller.ts index 35065f8375..ce6ca41c15 100644 --- a/packages/apps/fortune/recording-oracle/src/app.controller.ts +++ b/packages/apps/fortune/recording-oracle/src/app.controller.ts @@ -1,8 +1,10 @@ import { Controller, Get, Redirect } from '@nestjs/common'; -import { Public } from '@/common/decorators'; +import { Public } from './common/decorators'; +import { ApiExcludeController } from '@nestjs/swagger'; @Controller('/') +@ApiExcludeController() export class AppController { @Public() @Get('/') diff --git a/packages/apps/fortune/recording-oracle/src/app.module.ts b/packages/apps/fortune/recording-oracle/src/app.module.ts index 20eba00598..646746722b 100644 --- a/packages/apps/fortune/recording-oracle/src/app.module.ts +++ b/packages/apps/fortune/recording-oracle/src/app.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; -import { HttpValidationPipe } from '@/common/pipes'; -import { JobModule } from '@/modules/job/job.module'; +import { HttpValidationPipe } from './common/pipes'; +import { JobModule } from './modules/job/job.module'; import { AppController } from './app.controller'; import { @@ -12,6 +12,7 @@ import { web3Config, } from './common/config'; import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; +import { WebhookModule } from './modules/webhook/webhook.module'; @Module({ providers: [ @@ -33,6 +34,7 @@ import { SnakeCaseInterceptor } from './common/interceptors/snake-case'; load: [serverConfig, s3Config, web3Config], }), JobModule, + WebhookModule, ], controllers: [AppController], }) diff --git a/packages/apps/fortune/recording-oracle/src/common/config/server.ts b/packages/apps/fortune/recording-oracle/src/common/config/server.ts index 59fab744b3..6cc3a5f1c6 100644 --- a/packages/apps/fortune/recording-oracle/src/common/config/server.ts +++ b/packages/apps/fortune/recording-oracle/src/common/config/server.ts @@ -4,11 +4,9 @@ export const serverConfig = registerAs('server', () => ({ host: process.env.HOST || 'localhost', port: +(process.env.PORT || 5000), sessionSecret: process.env.SESSION_SECRET || 'session_key', - reputationOracleAddress: process.env.REPUTATION_ORACLE_ADDRESS || '', - reputationOracleWebhookUrl: - process.env.REPUTATION_ORACLE_WEBHOOK_URL || 'http://localhost:4005', - encryptionPrivateKey: process.env.ENCRYPTION_PRIVATE_KEY || '', - encryptionPassphrase: process.env.ENCRYPTION_PASSPHRASE || '', + pgpEncrypt: process.env.PGP_ENCRYPT || false, + encryptionPrivateKey: process.env.PGP_PRIVATE_KEY || '', + encryptionPassphrase: process.env.PGP_PASSPHRASE || '', })); export const serverConfigKey = serverConfig.KEY; export type ServerConfigType = ConfigType; diff --git a/packages/apps/fortune/recording-oracle/src/common/config/validation.ts b/packages/apps/fortune/recording-oracle/src/common/config/validation.ts index 3adb59983a..1973fc5deb 100644 --- a/packages/apps/fortune/recording-oracle/src/common/config/validation.ts +++ b/packages/apps/fortune/recording-oracle/src/common/config/validation.ts @@ -6,16 +6,15 @@ export const ConfigNames = { PORT: 'PORT', SESSION_SECRET: 'SESSION_SECRET', WEB3_PRIVATE_KEY: 'WEB3_PRIVATE_KEY', - REPUTATION_ORACLE_ADDRESS: 'REPUTATION_ORACLE_ADDRESS', - REPUTATION_ORACLE_WEBHOOK_URL: 'REPUTATION_ORACLE_WEBHOOK_URL', S3_ENDPOINT: 'S3_ENDPOINT', S3_PORT: 'S3_PORT', S3_ACCESS_KEY: 'S3_ACCESS_KEY', S3_SECRET_KEY: 'S3_SECRET_KEY', S3_BUCKET: 'S3_BUCKET', S3_USE_SSL: 'S3_USE_SSL', - ENCRYPTION_PRIVATE_KEY: 'ENCRYPTION_PRIVATE_KEY', - ENCRYPTION_PASSPHRASE: 'ENCRYPTION_PASSPHRASE', + PGP_ENCRYPT: 'PGP_ENCRYPT', + PGP_PRIVATE_KEY: 'PGP_PRIVATE_KEY', + PGP_PASSPHRASE: 'PGP_PASSPHRASE', }; export const envValidator = Joi.object({ @@ -26,8 +25,6 @@ export const envValidator = Joi.object({ SESSION_SECRET: Joi.string().default('session_key'), // Web3 WEB3_PRIVATE_KEY: Joi.string().required(), - REPUTATION_ORACLE_ADDRESS: Joi.string().required(), - REPUTATION_ORACLE_WEBHOOK_URL: Joi.string().default('http://localhost:4005'), // S3 S3_ENDPOINT: Joi.string().default('127.0.0.1'), S3_PORT: Joi.string().default(9000), @@ -36,6 +33,7 @@ export const envValidator = Joi.object({ S3_BUCKET: Joi.string().default('solution'), S3_USE_SSL: Joi.string().default(false), // Encryption - ENCRYPTION_PRIVATE_KEY: Joi.string().required(), - ENCRYPTION_PASSPHRASE: Joi.string().required(), + PGP_ENCRYPT: Joi.boolean().default(false), + PGP_PRIVATE_KEY: Joi.string().optional(), + PGP_PASSPHRASE: Joi.string().optional(), }); diff --git a/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts b/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts index 5f65991c54..b17669659a 100644 --- a/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts +++ b/packages/apps/fortune/recording-oracle/src/common/constants/networks.ts @@ -1,31 +1,36 @@ +import { ChainId } from '@human-protocol/sdk'; import { NetworkMapDto } from '../interfaces/network'; export const networkMap: NetworkMapDto = { polygon: { - chainId: 137, + chainId: ChainId.POLYGON, rpcUrl: 'https://polygon-mainnet.g.alchemy.com/v2/0Lorh5KRkGl5FsRwy2epTg8fEFFoqUfY', }, bsc: { - chainId: 56, + chainId: ChainId.BSC_MAINNET, rpcUrl: 'https://bsc-dataseed1.binance.org/', }, mumbai: { - chainId: 80001, + chainId: ChainId.POLYGON_MUMBAI, rpcUrl: 'https://polygon-mumbai.g.alchemy.com/v2/vKNSJzJf6SW2sdW-05bgFwoyFxUrMzii', }, goerli: { - chainId: 5, + chainId: ChainId.GOERLI, rpcUrl: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', }, moonbeam: { - chainId: 1284, + chainId: ChainId.MOONBEAM, rpcUrl: 'https://rpc.api.moonbeam.network', }, bsctest: { - chainId: 97, - rpcUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545/', + chainId: ChainId.BSC_TESTNET, + rpcUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545', + }, + localhost: { + chainId: ChainId.LOCALHOST, + rpcUrl: 'http://localhost:8545', }, }; diff --git a/packages/apps/fortune/recording-oracle/src/common/enums/role.ts b/packages/apps/fortune/recording-oracle/src/common/enums/role.ts index b998ec7fe7..2c40b7bdb3 100644 --- a/packages/apps/fortune/recording-oracle/src/common/enums/role.ts +++ b/packages/apps/fortune/recording-oracle/src/common/enums/role.ts @@ -1,5 +1,5 @@ export enum Role { - JobLaucher = 'job_launcher', + JobLauncher = 'job_launcher', Exchange = 'exchange', Reputation = 'reputation', } diff --git a/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts b/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts index af6c902688..21c8d59661 100644 --- a/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts +++ b/packages/apps/fortune/recording-oracle/src/common/enums/webhook.ts @@ -1,6 +1,6 @@ export enum EventType { - escrow_created = 'escrow_created', - escrow_canceled = 'escrow_canceled', - escrow_recorded = 'escrow_recorded', - submission_rejected = 'submission_rejected', + ESCROW_COMPLETED = 'escrow_completed', + ESCROW_RECORDED = 'escrow_recorded', + SUBMISSION_REJECTED = 'submission_rejected', + SUBMISSION_IN_REVIEW = 'submission_in_review', } diff --git a/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.spec.ts b/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.spec.ts index ed92d3aeee..3693649275 100644 --- a/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.spec.ts @@ -5,6 +5,7 @@ import { verifySignature } from '../utils/signature'; import { ChainId, EscrowUtils } from '@human-protocol/sdk'; import { MOCK_ADDRESS } from '../../../test/constants'; import { Role } from '../enums/role'; +import { HEADER_SIGNATURE_KEY } from '../constants'; jest.mock('../../common/utils/signature'); @@ -27,7 +28,7 @@ describe('SignatureAuthGuard', () => { { provide: SignatureAuthGuard, useValue: new SignatureAuthGuard([ - Role.JobLaucher, + Role.JobLauncher, Role.Exchange, Role.Reputation, ]), @@ -61,10 +62,10 @@ describe('SignatureAuthGuard', () => { }); it('should return true if signature is verified', async () => { - mockRequest.headers['header-signature-key'] = 'validSignature'; + mockRequest.headers[HEADER_SIGNATURE_KEY] = 'validSignature'; mockRequest.body = { - escrowAddress: MOCK_ADDRESS, - chainId: ChainId.LOCALHOST, + escrow_address: MOCK_ADDRESS, + chain_id: ChainId.LOCALHOST, }; (verifySignature as jest.Mock).mockReturnValue(true); diff --git a/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.ts b/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.ts index 0704096d3c..7bdc9a6625 100644 --- a/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.ts +++ b/packages/apps/fortune/recording-oracle/src/common/guards/signature.auth.ts @@ -21,10 +21,10 @@ export class SignatureAuthGuard implements CanActivate { const oracleAdresses: string[] = []; try { const escrowData = await EscrowUtils.getEscrow( - data.chainId, - data.escrowAddress, + data.chain_id, + data.escrow_address, ); - if (this.role.includes(Role.JobLaucher) && escrowData.launcher?.length) + if (this.role.includes(Role.JobLauncher) && escrowData.launcher?.length) oracleAdresses.push(escrowData.launcher); if ( this.role.includes(Role.Exchange) && diff --git a/packages/apps/fortune/recording-oracle/src/common/utils/case-converter.ts b/packages/apps/fortune/recording-oracle/src/common/utils/case-converter.ts index 980ddc589a..54bd31c904 100644 --- a/packages/apps/fortune/recording-oracle/src/common/utils/case-converter.ts +++ b/packages/apps/fortune/recording-oracle/src/common/utils/case-converter.ts @@ -1,15 +1,21 @@ export class CaseConverter { - static transformToCamelCase(obj: Record): Record { - const result: Record = {}; - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - const camelCaseKey = key.replace(/_([a-z])/g, (g) => - g[1].toUpperCase(), - ); - result[camelCaseKey] = obj[key]; - } + static transformToCamelCase(obj: any): any { + if (Array.isArray(obj)) { + return obj.map((item) => CaseConverter.transformToCamelCase(item)); + } else if (typeof obj === 'object' && obj !== null) { + return Object.keys(obj).reduce( + (acc: Record, key: string) => { + const camelCaseKey = key.replace(/_([a-z])/g, (g) => + g[1].toUpperCase(), + ); + acc[camelCaseKey] = CaseConverter.transformToCamelCase(obj[key]); + return acc; + }, + {}, + ); + } else { + return obj; } - return result; } static transformToSnakeCase(obj: any): any { diff --git a/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts b/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts index c8f9cd7f17..a3ce716c4e 100644 --- a/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts +++ b/packages/apps/fortune/recording-oracle/src/common/utils/webhook.ts @@ -4,14 +4,14 @@ import { HttpService } from '@nestjs/axios'; import { ErrorJob } from '../constants/errors'; import { signMessage } from './signature'; import { HEADER_SIGNATURE_KEY } from '../constants'; -import { WebhookBody } from '@/modules/job/job.dto'; import { CaseConverter } from './case-converter'; +import { WebhookDto } from '../../modules/webhook/webhook.dto'; export async function sendWebhook( httpService: HttpService, logger: Logger, webhookUrl: string, - webhookBody: WebhookBody, + webhookBody: WebhookDto, privateKey: string, ): Promise { const snake_case_body = CaseConverter.transformToSnakeCase(webhookBody); diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.controller.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.controller.spec.ts deleted file mode 100644 index dbd495feed..0000000000 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.controller.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { HttpService } from '@nestjs/axios'; -import { ConfigModule, registerAs } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { of } from 'rxjs'; - -import { ChainId } from '@human-protocol/sdk'; -import { - MOCK_ADDRESS, - MOCK_FILE_URL, - MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - MOCK_S3_ACCESS_KEY, - MOCK_S3_BUCKET, - MOCK_S3_ENDPOINT, - MOCK_S3_PORT, - MOCK_S3_SECRET_KEY, - MOCK_S3_USE_SSL, - MOCK_SIGNATURE, - MOCK_WEB3_PRIVATE_KEY, -} from '../../../test/constants'; -import { Web3Service } from '../web3/web3.service'; -import { JobController } from './job.controller'; -import { JobService } from './job.service'; -import { StorageService } from '../storage/storage.service'; -import { verifySignature } from '../../common/utils/signature'; - -jest.mock('../../common/utils/signature'); - -jest.mock('@human-protocol/sdk', () => ({ - ...jest.requireActual('@human-protocol/sdk'), - StorageClient: jest.fn().mockImplementation(() => ({})), -})); - -const signerMock = { - address: MOCK_ADDRESS, - getAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), - getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), -}; - -const httpServicePostMock = jest - .fn() - .mockReturnValue(of({ status: 200, data: {} })); - -describe('JobController', () => { - let jobController: JobController; - let jobService: JobService; - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forFeature( - registerAs('s3', () => ({ - accessKey: MOCK_S3_ACCESS_KEY, - secretKey: MOCK_S3_SECRET_KEY, - endPoint: MOCK_S3_ENDPOINT, - port: MOCK_S3_PORT, - useSSL: MOCK_S3_USE_SSL, - bucket: MOCK_S3_BUCKET, - })), - ), - ConfigModule.forFeature( - registerAs('web3', () => ({ - web3PrivateKey: MOCK_WEB3_PRIVATE_KEY, - })), - ), - ConfigModule.forFeature( - registerAs('server', () => ({ - reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, - })), - ), - ], - providers: [ - JobService, - StorageService, - { - provide: Web3Service, - useValue: { - getSigner: jest.fn().mockReturnValue(signerMock), - }, - }, - { - provide: HttpService, - useValue: { - post: httpServicePostMock, - }, - }, - ], - }).compile(); - - jobService = moduleRef.get(JobService); - jobController = new JobController(jobService); - }); - - describe('solve', () => { - it('should call service', async () => { - jest - .spyOn(jobService, 'processJobSolution') - .mockImplementation(async () => 'OK'); - - (verifySignature as jest.Mock).mockReturnValue(true); - - expect( - await jobController.solve(MOCK_SIGNATURE, { - escrowAddress: MOCK_ADDRESS, - chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, - }), - ).toBe('OK'); - }); - }); -}); diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.controller.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.controller.ts deleted file mode 100644 index 55d2076f95..0000000000 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.controller.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; - -import { JobSolutionsRequestDto } from './job.dto'; -import { JobService } from './job.service'; -import { SignatureAuthGuard } from '../../common/guards'; -import { Role } from '../../common/enums/role'; -import { HEADER_SIGNATURE_KEY } from '../../common/constants'; - -@Controller('/job') -@ApiTags('Job') -export class JobController { - constructor(private readonly jobService: JobService) {} - - @UseGuards(new SignatureAuthGuard([Role.Exchange])) - @Post('/solve') - public async solve( - @Headers(HEADER_SIGNATURE_KEY) _: string, - @Body() fortune: JobSolutionsRequestDto, - ): Promise { - return await this.jobService.processJobSolution(fortune); - } -} diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.dto.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.dto.ts index d53e4d5d15..88c501aee2 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.dto.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.dto.ts @@ -1,43 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { ChainId } from '@human-protocol/sdk'; -import { IsEnum, IsString, IsUrl } from 'class-validator'; - -import { IsValidEthereumAddress } from '@/common/validators'; -import { SolutionError } from '@/common/enums/job'; -import { EventType } from '@/common/enums/webhook'; - -export class JobSolutionsRequestDto { - @ApiProperty({ name: 'escrow_address' }) - @IsString() - @IsValidEthereumAddress() - public escrowAddress: string; - - @ApiProperty({ - enum: ChainId, - name: 'chain_id', - }) - @IsEnum(ChainId) - public chainId: ChainId; - - @ApiProperty({ name: 'solutions_url' }) - @IsString() - @IsUrl() - public solutionsUrl: string; -} - export class SaveSolutionsDto { public url: string; public hash: string; } - -export class WebhookBody { - escrowAddress: string; - chainId: ChainId; - eventType: EventType; - eventData?: EventData[]; -} - -export class EventData { - assigneeId: string; - reason?: SolutionError; -} diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts index 00521cefef..4ab2a2ba48 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.module.ts @@ -1,7 +1,6 @@ import { HttpModule } from '@nestjs/axios'; import { Logger, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { JobController } from './job.controller'; import { JobService } from './job.service'; import { Web3Module } from '../web3/web3.module'; import { serverConfig, web3Config } from '../../common/config'; @@ -15,7 +14,6 @@ import { StorageModule } from '../storage/storage.module'; Web3Module, StorageModule, ], - controllers: [JobController], providers: [Logger, JobService], exports: [JobService], }) diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts index f58acaad57..7928cbabd6 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.spec.ts @@ -5,6 +5,7 @@ import { Web3Service } from '../web3/web3.service'; import { ErrorJob } from '../../common/constants/errors'; import { ChainId, + EncryptionUtils, EscrowClient, EscrowStatus, KVStoreClient, @@ -31,11 +32,11 @@ import { import { ConfigModule, registerAs } from '@nestjs/config'; import { IManifest, ISolution } from '../../common/interfaces/job'; import { of } from 'rxjs'; -import { JobSolutionsRequestDto } from './job.dto'; import { StorageService } from '../storage/storage.service'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; import { signMessage } from '../../common/utils/signature'; -import { EventType } from '@/common/enums/webhook'; +import { EventType } from '../../common/enums/webhook'; +import { WebhookDto } from '../webhook/webhook.dto'; jest.mock('minio', () => { class Client { @@ -69,13 +70,9 @@ jest.mock('@human-protocol/sdk', () => ({ KVStoreClient: { build: jest.fn().mockImplementation(() => ({ get: jest.fn(), + getPublicKey: jest.fn().mockResolvedValue('publicKey'), })), }, - OperatorUtils: { - getLeader: jest.fn().mockResolvedValue({ - publicKey: 'public-key', - }), - }, EncryptionUtils: { encrypt: jest.fn().mockResolvedValue('encrypted'), }, @@ -114,7 +111,6 @@ describe('JobService', () => { ), ConfigModule.forFeature( registerAs('server', () => ({ - reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, encryptionPrivateKey: MOCK_ENCRYPTION_PRIVATE_KEY, encryptionPassphrase: MOCK_ENCRYPTION_PASSPHRASE, })), @@ -157,10 +153,11 @@ describe('JobService', () => { }; (EscrowClient.build as jest.Mock).mockResolvedValue(escrowClient); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; await expect( @@ -175,10 +172,11 @@ describe('JobService', () => { }; (EscrowClient.build as jest.Mock).mockResolvedValue(escrowClient); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; await expect( @@ -203,10 +201,11 @@ describe('JobService', () => { .fn() .mockResolvedValue(JSON.stringify(invalidManifest)); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; await expect( @@ -231,11 +230,13 @@ describe('JobService', () => { StorageClient.downloadFileFromUrl = jest .fn() .mockResolvedValue(JSON.stringify(invalidManifest)); + EncryptionUtils.isEncrypted = jest.fn().mockReturnValueOnce(false); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; await expect( @@ -296,10 +297,11 @@ describe('JobService', () => { .mockResolvedValueOnce(JSON.stringify(existingJobSolutions)) .mockResolvedValue(JSON.stringify(exchangeJobSolutions)); - const newSolution: JobSolutionsRequestDto = { + const newSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; await expect( @@ -318,6 +320,7 @@ describe('JobService', () => { const escrowClient = { getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + getReputationOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getStatus: jest.fn().mockResolvedValue(EscrowStatus.Pending), getManifestUrl: jest .fn() @@ -325,6 +328,7 @@ describe('JobService', () => { getIntermediateResultsUrl: jest .fn() .mockResolvedValue('http://example.com/results'), + storeResults: jest.fn().mockResolvedValue(true), }; (EscrowClient.build as jest.Mock).mockResolvedValue(escrowClient); @@ -356,10 +360,11 @@ describe('JobService', () => { new Error(ErrorJob.WebhookWasNotSent), ); - const newSolution: JobSolutionsRequestDto = { + const newSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; await expect( @@ -370,6 +375,7 @@ describe('JobService', () => { it('should return solution are recorded when one solution is sent', async () => { const escrowClient = { getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + getReputationOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getStatus: jest.fn().mockResolvedValue(EscrowStatus.Pending), getManifestUrl: jest .fn() @@ -410,29 +416,38 @@ describe('JobService', () => { .mockResolvedValueOnce(JSON.stringify(existingJobSolutions)) .mockResolvedValue(JSON.stringify(exchangeJobSolutions)); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; const result = await jobService.processJobSolution(jobSolution); - expect(result).toEqual('Solution are recorded.'); + expect(result).toEqual('Solutions recorded.'); expect(httpServicePostMock).not.toHaveBeenCalled(); }); it('should call send webhook method when all solutions are recorded', async () => { const escrowClient = { getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + getReputationOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getStatus: jest.fn().mockResolvedValue(EscrowStatus.Pending), getManifestUrl: jest .fn() .mockResolvedValue('http://example.com/manifest'), - getIntermediateResultsUrl: jest.fn().mockResolvedValue(''), + getIntermediateResultsUrl: jest + .fn() + .mockResolvedValue('http://existing-solutions'), storeResults: jest.fn().mockResolvedValue(true), }; (EscrowClient.build as jest.Mock).mockResolvedValue(escrowClient); + const kvStoreClient = { + get: jest.fn().mockResolvedValue(MOCK_REPUTATION_ORACLE_WEBHOOK_URL), + }; + (KVStoreClient.build as jest.Mock).mockResolvedValue(kvStoreClient); + const manifest: IManifest = { submissionsRequired: 2, requesterTitle: MOCK_REQUESTER_TITLE, @@ -464,10 +479,11 @@ describe('JobService', () => { .mockResolvedValueOnce(JSON.stringify(existingJobSolutions)) .mockResolvedValue(JSON.stringify(exchangeJobSolutions)); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; const result = await jobService.processJobSolution(jobSolution); @@ -475,7 +491,7 @@ describe('JobService', () => { const expectedBody = { chain_id: jobSolution.chainId, escrow_address: jobSolution.escrowAddress, - event_type: EventType.escrow_recorded, + event_type: EventType.ESCROW_RECORDED, }; expect(result).toEqual('The requested job is completed.'); expect(httpServicePostMock).toHaveBeenCalledWith( @@ -496,11 +512,14 @@ describe('JobService', () => { it('should take one solution more when one is marked as invalid', async () => { const escrowClient = { getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + getReputationOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getStatus: jest.fn().mockResolvedValue(EscrowStatus.Pending), getManifestUrl: jest .fn() .mockResolvedValue('http://example.com/manifest'), - getIntermediateResultsUrl: jest.fn().mockResolvedValue(''), + getIntermediateResultsUrl: jest + .fn() + .mockResolvedValue('http://existing-solutions'), storeResults: jest.fn().mockResolvedValue(true), }; (EscrowClient.build as jest.Mock).mockResolvedValue(escrowClient); @@ -552,10 +571,11 @@ describe('JobService', () => { .mockResolvedValueOnce(JSON.stringify(existingJobSolutions)) .mockResolvedValue(JSON.stringify(exchangeJobSolutions)); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; const result = await jobService.processJobSolution(jobSolution); @@ -563,7 +583,7 @@ describe('JobService', () => { const expectedBody = { chain_id: jobSolution.chainId, escrow_address: jobSolution.escrowAddress, - event_type: EventType.escrow_recorded, + event_type: EventType.ESCROW_RECORDED, }; expect(result).toEqual('The requested job is completed.'); expect(httpServicePostMock).toHaveBeenCalledWith( @@ -584,16 +604,20 @@ describe('JobService', () => { const escrowClient = { getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getExchangeOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + getReputationOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getStatus: jest.fn().mockResolvedValue(EscrowStatus.Pending), getManifestUrl: jest .fn() .mockResolvedValue('http://example.com/manifest'), - getIntermediateResultsUrl: jest.fn().mockResolvedValue(''), + getIntermediateResultsUrl: jest + .fn() + .mockResolvedValue('http://existing-solutions'), storeResults: jest.fn().mockResolvedValue(true), }; (EscrowClient.build as jest.Mock).mockResolvedValue(escrowClient); (KVStoreClient.build as jest.Mock).mockResolvedValue({ get: jest.fn().mockResolvedValue(MOCK_EXCHANGE_ORACLE_WEBHOOK_URL), + getPublicKey: jest.fn().mockResolvedValue('publicKey'), }); const manifest: IManifest = { @@ -626,10 +650,11 @@ describe('JobService', () => { .mockResolvedValueOnce(JSON.stringify(existingJobSolutions)) .mockResolvedValue(JSON.stringify(exchangeJobSolutions)); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; const result = await jobService.processJobSolution(jobSolution); @@ -637,15 +662,17 @@ describe('JobService', () => { const expectedBody = { chain_id: jobSolution.chainId, escrow_address: jobSolution.escrowAddress, - event_type: EventType.submission_rejected, - event_data: [ - { - assignee_id: exchangeJobSolutions[0].workerAddress, - reason: SolutionError.Duplicated, - }, - ], + event_type: EventType.SUBMISSION_REJECTED, + event_data: { + assignments: [ + { + assignee_id: exchangeJobSolutions[0].workerAddress, + reason: SolutionError.Duplicated, + }, + ], + }, }; - expect(result).toEqual('Solution are recorded.'); + expect(result).toEqual('Solutions recorded.'); expect(httpServicePostMock).toHaveBeenCalledWith( MOCK_EXCHANGE_ORACLE_WEBHOOK_URL, expectedBody, @@ -661,16 +688,20 @@ describe('JobService', () => { const escrowClient = { getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getExchangeOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + getReputationOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), getStatus: jest.fn().mockResolvedValue(EscrowStatus.Pending), getManifestUrl: jest .fn() .mockResolvedValue('http://example.com/manifest'), - getIntermediateResultsUrl: jest.fn().mockResolvedValue(''), + getIntermediateResultsUrl: jest + .fn() + .mockResolvedValue('http://existing-solutions'), storeResults: jest.fn().mockResolvedValue(true), }; (EscrowClient.build as jest.Mock).mockResolvedValue(escrowClient); (KVStoreClient.build as jest.Mock).mockResolvedValue({ get: jest.fn().mockResolvedValue(MOCK_EXCHANGE_ORACLE_WEBHOOK_URL), + getPublicKey: jest.fn().mockResolvedValue('publicKey'), }); const manifest: IManifest = { @@ -703,25 +734,28 @@ describe('JobService', () => { .mockResolvedValueOnce(JSON.stringify(existingJobSolutions)) .mockResolvedValue(JSON.stringify(exchangeJobSolutions)); - const jobSolution: JobSolutionsRequestDto = { + const jobSolution: WebhookDto = { escrowAddress: MOCK_ADDRESS, chainId: ChainId.LOCALHOST, - solutionsUrl: MOCK_FILE_URL, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, }; const expectedBody = { chain_id: jobSolution.chainId, escrow_address: jobSolution.escrowAddress, - event_type: EventType.submission_rejected, - event_data: [ - { - assignee_id: exchangeJobSolutions[1].workerAddress, - reason: SolutionError.CurseWord, - }, - ], + event_type: EventType.SUBMISSION_REJECTED, + event_data: { + assignments: [ + { + assignee_id: exchangeJobSolutions[1].workerAddress, + reason: SolutionError.CurseWord, + }, + ], + }, }; const result = await jobService.processJobSolution(jobSolution); - expect(result).toEqual('Solution are recorded.'); + expect(result).toEqual('Solutions recorded.'); expect(httpServicePostMock).toHaveBeenCalledWith( MOCK_EXCHANGE_ORACLE_WEBHOOK_URL, expectedBody, diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts index 05d4e2d33d..53fc526407 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts @@ -14,12 +14,7 @@ import { import { ethers } from 'ethers'; import * as Minio from 'minio'; -import { - ServerConfigType, - Web3ConfigType, - serverConfigKey, - web3ConfigKey, -} from '../../common/config'; +import { Web3ConfigType, web3ConfigKey } from '../../common/config'; import { ErrorJob } from '../../common/constants/errors'; import { JobRequestType, SolutionError } from '../../common/enums/job'; import { IManifest, ISolution } from '../../common/interfaces/job'; @@ -27,8 +22,12 @@ import { checkCurseWords } from '../../common/utils/curseWords'; import { sendWebhook } from '../../common/utils/webhook'; import { StorageService } from '../storage/storage.service'; import { Web3Service } from '../web3/web3.service'; -import { EventData, JobSolutionsRequestDto, WebhookBody } from './job.dto'; -import { EventType } from '@/common/enums/webhook'; +import { EventType } from '../../common/enums/webhook'; +import { + AssignmentRejection, + SolutionEventData, + WebhookDto, +} from '../webhook/webhook.dto'; @Injectable() export class JobService { @@ -36,8 +35,6 @@ export class JobService { public readonly minioClient: Minio.Client; constructor( - @Inject(serverConfigKey) - private serverConfig: ServerConfigType, @Inject(web3ConfigKey) private web3Config: Web3ConfigType, @Inject(Web3Service) @@ -98,15 +95,13 @@ export class JobService { return { errorSolutions, uniqueSolutions }; } - async processJobSolution( - jobSolution: JobSolutionsRequestDto, - ): Promise { - const signer = this.web3Service.getSigner(jobSolution.chainId); + async processJobSolution(webhook: WebhookDto): Promise { + const signer = this.web3Service.getSigner(webhook.chainId); const escrowClient = await EscrowClient.build(signer); const kvstoreClient = await KVStoreClient.build(signer); const recordingOracleAddress = await escrowClient.getRecordingOracleAddress( - jobSolution.escrowAddress, + webhook.escrowAddress, ); if ( ethers.getAddress(recordingOracleAddress) !== (await signer.getAddress()) @@ -115,9 +110,7 @@ export class JobService { throw new BadRequestException(ErrorJob.AddressMismatches); } - const escrowStatus = await escrowClient.getStatus( - jobSolution.escrowAddress, - ); + const escrowStatus = await escrowClient.getStatus(webhook.escrowAddress); if ( escrowStatus !== EscrowStatus.Pending && escrowStatus !== EscrowStatus.Partial @@ -127,7 +120,7 @@ export class JobService { } const manifestUrl = await escrowClient.getManifestUrl( - jobSolution.escrowAddress, + webhook.escrowAddress, ); const { submissionsRequired, requestType }: IManifest = await this.storageService.download(manifestUrl); @@ -143,14 +136,14 @@ export class JobService { } const existingJobSolutionsURL = - await escrowClient.getIntermediateResultsUrl(jobSolution.escrowAddress); + await escrowClient.getIntermediateResultsUrl(webhook.escrowAddress); - const existingJobSolutions = await this.storageService.download( - this.storageService.getJobUrl( - jobSolution.escrowAddress, - jobSolution.chainId, - ), - ); + let existingJobSolutions: ISolution[] = []; + if (existingJobSolutionsURL) { + existingJobSolutions = await this.storageService.download( + existingJobSolutionsURL, + ); + } if (existingJobSolutions.length >= submissionsRequired) { this.logger.log( @@ -161,7 +154,9 @@ export class JobService { } const exchangeJobSolutions: ISolution[] = - await this.storageService.download(jobSolution.solutionsUrl); + await this.storageService.download( + (webhook.eventData as SolutionEventData)?.solutionsUrl, + ); const { errorSolutions, uniqueSolutions } = this.processSolutions( exchangeJobSolutions, @@ -175,24 +170,23 @@ export class JobService { ]; const jobSolutionUploaded = await this.storageService.uploadJobSolutions( - jobSolution.escrowAddress, - jobSolution.chainId, + webhook.escrowAddress, + webhook.chainId, recordingOracleSolutions, ); - if (!existingJobSolutionsURL) { - await escrowClient.storeResults( - jobSolution.escrowAddress, - jobSolutionUploaded.url, - jobSolutionUploaded.hash, - ); - } - - // TODO: Uncomment this to read reputation oracle URL from KVStore - // const reputationOracleAddress = await escrowClient.getReputationOracleAddress(jobSolution.escrowAddress); - // const reputationOracleURL = (await kvstoreClient.get(reputationOracleAddress, "url")) as string; + await escrowClient.storeResults( + webhook.escrowAddress, + jobSolutionUploaded.url, + jobSolutionUploaded.hash, + ); - // TODO: Remove this when KVStore is used + const reputationOracleAddress = + await escrowClient.getReputationOracleAddress(webhook.escrowAddress); + const reputationOracleWebhook = (await kvstoreClient.get( + reputationOracleAddress, + KVStoreKeys.webhookUrl, + )) as string; if ( recordingOracleSolutions.filter((solution) => !solution.error).length >= @@ -201,11 +195,11 @@ export class JobService { await sendWebhook( this.httpService, this.logger, - this.serverConfig.reputationOracleWebhookUrl, + reputationOracleWebhook, { - chainId: jobSolution.chainId, - escrowAddress: jobSolution.escrowAddress, - eventType: EventType.escrow_recorded, + chainId: webhook.chainId, + escrowAddress: webhook.escrowAddress, + eventType: EventType.ESCROW_RECORDED, }, this.web3Config.web3PrivateKey, ); @@ -214,22 +208,24 @@ export class JobService { } if (errorSolutions.length) { const exchangeOracleURL = (await kvstoreClient.get( - await escrowClient.getExchangeOracleAddress(jobSolution.escrowAddress), + await escrowClient.getExchangeOracleAddress(webhook.escrowAddress), KVStoreKeys.webhookUrl, )) as string; - const eventData: EventData[] = errorSolutions.map((solution) => ({ - assigneeId: solution.workerAddress, - reason: solution.error as SolutionError, - })); - - const webhookBody: WebhookBody = { - escrowAddress: jobSolution.escrowAddress, - chainId: jobSolution.chainId, - eventType: EventType.submission_rejected, - eventData: eventData, + + const eventData: AssignmentRejection[] = errorSolutions.map( + (solution) => ({ + assigneeId: solution.workerAddress, + reason: solution.error as SolutionError, + }), + ); + + const webhookBody: WebhookDto = { + escrowAddress: webhook.escrowAddress, + chainId: webhook.chainId, + eventType: EventType.SUBMISSION_REJECTED, + eventData: { assignments: eventData }, }; - // Enviar la llamada al webhook una vez con todos los errores await sendWebhook( this.httpService, this.logger, @@ -239,6 +235,6 @@ export class JobService { ); } - return 'Solution are recorded.'; + return 'Solutions recorded.'; } } diff --git a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts index 7e7d1c99e9..c9edca9058 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.spec.ts @@ -2,16 +2,17 @@ import { ChainId, Encryption, EncryptionUtils, - OperatorUtils, + KVStoreClient, + EscrowClient, StorageClient, } from '@human-protocol/sdk'; import { ConfigModule, registerAs } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { + MOCK_ADDRESS, MOCK_ENCRYPTION_PASSPHRASE, MOCK_ENCRYPTION_PRIVATE_KEY, MOCK_FILE_URL, - MOCK_REPUTATION_ORACLE_WEBHOOK_URL, MOCK_S3_ACCESS_KEY, MOCK_S3_BUCKET, MOCK_S3_ENDPOINT, @@ -20,23 +21,28 @@ import { MOCK_S3_USE_SSL, } from '../../../test/constants'; import { StorageService } from './storage.service'; -import crypto from 'crypto'; import { Web3Service } from '../web3/web3.service'; +import { ServerConfigType, serverConfigKey } from '@/common/config'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), StorageClient: { downloadFileFromUrl: jest.fn(), }, - OperatorUtils: { - getLeader: jest.fn(), - }, Encryption: { build: jest.fn(), }, EncryptionUtils: { encrypt: jest.fn(), }, + KVStoreClient: { + build: jest.fn().mockImplementation(() => ({ + getPublicKey: jest.fn(), + })), + }, + EscrowClient: { + build: jest.fn(), + }, })); jest.mock('minio', () => { @@ -55,6 +61,7 @@ jest.mock('minio', () => { describe('StorageService', () => { let storageService: StorageService; + let serverConfig: ServerConfigType; const signerMock = { address: '0x1234567890123456789012345678901234567892', @@ -76,7 +83,6 @@ describe('StorageService', () => { ), ConfigModule.forFeature( registerAs('server', () => ({ - reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, encryptionPrivateKey: MOCK_ENCRYPTION_PRIVATE_KEY, encryptionPassphrase: MOCK_ENCRYPTION_PASSPHRASE, })), @@ -94,23 +100,32 @@ describe('StorageService', () => { }).compile(); storageService = moduleRef.get(StorageService); + serverConfig = moduleRef.get(serverConfigKey); }); describe('uploadJobSolutions', () => { - it('should upload the solutions correctly', async () => { + beforeAll(async () => { + (EscrowClient.build as any).mockImplementation(() => ({ + getReputationOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), + })); + }); + it('should upload the solutions with encryption correctly', async () => { const workerAddress = '0x1234567890123456789012345678901234567891'; const escrowAddress = '0x1234567890123456789012345678901234567890'; const chainId = ChainId.LOCALHOST; const solution = 'test'; + const hash = 'd92342976d720ff38cf5dcb329be41959ab1ba6c'; storageService.minioClient.bucketExists = jest .fn() .mockResolvedValue(true); EncryptionUtils.encrypt = jest.fn().mockResolvedValue('encrypted'); - OperatorUtils.getLeader = jest - .fn() - .mockResolvedValue({ publicKey: 'publicKey' }); + + (KVStoreClient.build as jest.Mock).mockResolvedValue({ + getPublicKey: jest.fn().mockResolvedValue('publicKey'), + }); + serverConfig.pgpEncrypt = true; const jobSolution = { workerAddress, @@ -121,13 +136,14 @@ describe('StorageService', () => { chainId, [jobSolution], ); + expect(fileData).toEqual({ - url: `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${escrowAddress}-${chainId}.json`, - hash: crypto.createHash('sha1').update('encrypted').digest('hex'), + url: `http://${MOCK_S3_ENDPOINT}:${MOCK_S3_PORT}/${MOCK_S3_BUCKET}/${hash}.json`, + hash, }); expect(storageService.minioClient.putObject).toHaveBeenCalledWith( MOCK_S3_BUCKET, - `${escrowAddress}-${chainId}.json`, + `${hash}.json`, 'encrypted', { 'Content-Type': 'application/json', @@ -169,7 +185,7 @@ describe('StorageService', () => { storageService.minioClient.putObject = jest .fn() .mockRejectedValue('Network error'); - + serverConfig.pgpEncrypt = false; const jobSolution = { workerAddress, solution, @@ -192,8 +208,10 @@ describe('StorageService', () => { .fn() .mockResolvedValue(true); EncryptionUtils.encrypt = jest.fn().mockResolvedValue('encrypted'); - OperatorUtils.getLeader = jest.fn().mockResolvedValue({}); - + (KVStoreClient.build as jest.Mock).mockResolvedValue({ + getPublicKey: jest.fn().mockResolvedValue(''), + }); + serverConfig.pgpEncrypt = true; const jobSolution = { workerAddress, solution, @@ -202,12 +220,12 @@ describe('StorageService', () => { storageService.uploadJobSolutions(escrowAddress, chainId, [ jobSolution, ]), - ).rejects.toThrow('Missing public key'); + ).rejects.toThrow('Encryption error'); }); }); describe('download', () => { - it('should download the file correctly', async () => { + it('should download the non encrypted file correctly', async () => { const exchangeAddress = '0x1234567890123456789012345678901234567892'; const workerAddress = '0x1234567890123456789012345678901234567891'; const solution = 'test'; @@ -224,7 +242,8 @@ describe('StorageService', () => { StorageClient.downloadFileFromUrl = jest .fn() - .mockResolvedValue(JSON.stringify(expectedJobFile)); + .mockResolvedValue(expectedJobFile); + EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(false); const solutionsFile = await storageService.download(MOCK_FILE_URL); expect(solutionsFile).toStrictEqual(expectedJobFile); }); @@ -251,7 +270,7 @@ describe('StorageService', () => { Encryption.build = jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(JSON.stringify(expectedJobFile)), }); - + EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true); const solutionsFile = await storageService.download(MOCK_FILE_URL); expect(solutionsFile).toStrictEqual(expectedJobFile); }); diff --git a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts index 38fef460df..d412f989f9 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/storage/storage.service.ts @@ -2,7 +2,8 @@ import { ChainId, Encryption, EncryptionUtils, - OperatorUtils, + KVStoreClient, + EscrowClient, StorageClient, } from '@human-protocol/sdk'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; @@ -38,26 +39,38 @@ export class StorageService { useSSL: this.s3Config.useSSL, }); } - public getJobUrl(escrowAddress: string, chainId: ChainId): string { + public getJobUrl(hash: string): string { return `${this.s3Config.useSSL ? 'https' : 'http'}://${ this.s3Config.endPoint - }:${this.s3Config.port}/${ - this.s3Config.bucket - }/${escrowAddress}-${chainId}.json`; + }:${this.s3Config.port}/${this.s3Config.bucket}/${hash}.json`; } public async download(url: string): Promise { try { const fileContent = await StorageClient.downloadFileFromUrl(url); - try { - return JSON.parse(fileContent); - } catch { - const encryption = await Encryption.build( - this.serverConfig.encryptionPrivateKey, - this.serverConfig.encryptionPassphrase, - ); - return JSON.parse(await encryption.decrypt(fileContent)); + if ( + typeof fileContent === 'string' && + EncryptionUtils.isEncrypted(fileContent) + ) { + try { + const encryption = await Encryption.build( + this.serverConfig.encryptionPrivateKey, + this.serverConfig.encryptionPassphrase, + ); + + return JSON.parse(await encryption.decrypt(fileContent)); + } catch { + throw new Error('Unable to decrypt manifest'); + } + } else { + try { + return typeof fileContent === 'string' + ? JSON.parse(fileContent) + : fileContent; + } catch { + return null; + } } } catch { return []; @@ -73,37 +86,55 @@ export class StorageService { throw new BadRequestException('Bucket not found'); } - const signer = this.web3Service.getSigner(chainId); - const recordingOracle = await OperatorUtils.getLeader( - chainId, - signer.address, - ); - const reputationOracle = await OperatorUtils.getLeader( - chainId, - this.serverConfig.reputationOracleAddress, - ); - if (!recordingOracle.publicKey || !reputationOracle.publicKey) { - throw new BadRequestException('Missing public key'); + let fileToUpload = JSON.stringify(solutions); + if (this.serverConfig.pgpEncrypt as boolean) { + try { + const signer = this.web3Service.getSigner(chainId); + const escrowClient = await EscrowClient.build(signer); + const reputationOracleAddress = + await escrowClient.getReputationOracleAddress(escrowAddress); + + const kvstoreClient = await KVStoreClient.build(signer); + + const recordingOraclePublicKey = await kvstoreClient.getPublicKey( + signer.address, + ); + const reputationOraclePublicKey = await kvstoreClient.getPublicKey( + reputationOracleAddress, + ); + if ( + !recordingOraclePublicKey.length || + !reputationOraclePublicKey.length + ) { + throw new BadRequestException('Missing public key'); + } + + if (!recordingOraclePublicKey || !reputationOraclePublicKey) { + throw new Error(); + } + + fileToUpload = await EncryptionUtils.encrypt(fileToUpload, [ + recordingOraclePublicKey, + reputationOraclePublicKey, + ]); + } catch (e) { + throw new BadRequestException('Encryption error'); + } } try { - const content = await EncryptionUtils.encrypt(JSON.stringify(solutions), [ - recordingOracle.publicKey, - reputationOracle.publicKey, - ]); - - const hash = crypto.createHash('sha1').update(content).digest('hex'); + const hash = crypto.createHash('sha1').update(fileToUpload).digest('hex'); await this.minioClient.putObject( this.s3Config.bucket, - `${escrowAddress}-${chainId}.json`, - content, + `${hash}.json`, + fileToUpload, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', }, ); - return { url: this.getJobUrl(escrowAddress, chainId), hash }; + return { url: this.getJobUrl(hash), hash }; } catch (e) { throw new BadRequestException('File not uploaded'); } diff --git a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts index 60684d90ed..1a67598c72 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/web3/web3.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { Web3Service } from './web3.service'; import { ConfigModule } from '@nestjs/config'; -import { web3Config } from '@/common/config'; +import { web3Config } from '../../common/config'; @Module({ imports: [ConfigModule.forFeature(web3Config)], diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts new file mode 100644 index 0000000000..72bca40f62 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.spec.ts @@ -0,0 +1,165 @@ +import { ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { WebhookController } from './webhook.controller'; +import { WebhookService } from './webhook.service'; +import { WebhookDto } from './webhook.dto'; +import { Web3Service } from '../web3/web3.service'; +import { HttpService } from '@nestjs/axios'; +import { of } from 'rxjs'; +import { ConfigModule, registerAs } from '@nestjs/config'; +import { + MOCK_FILE_URL, + MOCK_REPUTATION_ORACLE_WEBHOOK_URL, + MOCK_S3_ACCESS_KEY, + MOCK_S3_BUCKET, + MOCK_S3_ENDPOINT, + MOCK_S3_PORT, + MOCK_S3_SECRET_KEY, + MOCK_S3_USE_SSL, + MOCK_SIGNATURE, + MOCK_WEB3_PRIVATE_KEY, +} from '../../../test/constants'; +import { StorageService } from '../storage/storage.service'; +import { verifySignature } from '../../common/utils/signature'; +import { EventType } from '../../common/enums/webhook'; +import { JobService } from '../job/job.service'; + +jest.mock('../../common/utils/signature'); + +describe('webhookController', () => { + let webhookController: WebhookController; + let webhookService: WebhookService; + let jobService: JobService; + + const chainId = 1; + const escrowAddress = '0x1234567890123456789012345678901234567890'; + + const reputationOracleURL = 'https://example.com/reputationoracle'; + const configServiceMock = { + get: jest.fn().mockReturnValue(reputationOracleURL), + }; + + const httpServicePostMock = jest + .fn() + .mockReturnValue(of({ status: 200, data: {} })); + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forFeature( + registerAs('s3', () => ({ + accessKey: MOCK_S3_ACCESS_KEY, + secretKey: MOCK_S3_SECRET_KEY, + endPoint: MOCK_S3_ENDPOINT, + port: MOCK_S3_PORT, + useSSL: MOCK_S3_USE_SSL, + bucket: MOCK_S3_BUCKET, + })), + ), + ConfigModule.forFeature( + registerAs('server', () => ({ + reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, + })), + ), + ConfigModule.forFeature( + registerAs('web3', () => ({ + web3PrivateKey: MOCK_WEB3_PRIVATE_KEY, + })), + ), + ], + controllers: [WebhookController], + providers: [ + WebhookService, + JobService, + { + provide: ConfigService, + useValue: configServiceMock, + }, + { + provide: Web3Service, + useValue: { + getSigner: jest.fn().mockReturnValue({ + address: '0x1234567890123456789012345678901234567892', + getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), + }), + }, + }, + StorageService, + { + provide: HttpService, + useValue: { + post: jest.fn().mockReturnValue(of({ status: 200, data: {} })), + }, + }, + { + provide: HttpService, + useValue: { + post: httpServicePostMock, + }, + }, + ], + }).compile(); + + webhookController = moduleRef.get(WebhookController); + webhookService = moduleRef.get(WebhookService); + jobService = moduleRef.get(JobService); + }); + + describe('processWebhook', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + it('should handle an incoming escrow completed webhook', async () => { + const webhook: WebhookDto = { + chainId, + escrowAddress, + eventType: EventType.ESCROW_COMPLETED, + }; + jest.spyOn(webhookService, 'handleWebhook'); + + (verifySignature as jest.Mock).mockReturnValue(true); + + await webhookController.processWebhook(MOCK_SIGNATURE, webhook); + + expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); + }); + + it('should handle an incoming solution in review webhook', async () => { + const webhook: WebhookDto = { + chainId, + escrowAddress, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, + }; + + jest + .spyOn(jobService, 'processJobSolution') + .mockImplementation(async () => 'OK'); + + (verifySignature as jest.Mock).mockReturnValue(true); + + jest.spyOn(webhookService, 'handleWebhook').mockResolvedValue(); + + await webhookController.processWebhook(MOCK_SIGNATURE, webhook); + + expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); + }); + + it('should return an error when the event type is invalid', async () => { + const webhook: WebhookDto = { + chainId, + escrowAddress, + eventType: EventType.ESCROW_RECORDED, + }; + jest.spyOn(webhookService, 'handleWebhook'); + + (verifySignature as jest.Mock).mockReturnValue(true); + + await expect( + webhookController.processWebhook(MOCK_SIGNATURE, webhook), + ).rejects.toThrow('Invalid webhook event type: escrow_recorded'); + + expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); + }); + }); +}); diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.ts new file mode 100644 index 0000000000..5276e3a751 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.controller.ts @@ -0,0 +1,59 @@ +import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common'; +import { + ApiBody, + ApiHeader, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { Public } from '../../common/decorators'; +import { Role } from '../../common/enums/role'; +import { SignatureAuthGuard } from '../../common/guards'; +import { WebhookService } from './webhook.service'; +import { WebhookDto } from './webhook.dto'; +import { HEADER_SIGNATURE_KEY } from '../../common/constants'; + +@Public() +@ApiTags('Webhook') +@Controller('/webhook') +export class WebhookController { + constructor(private readonly webhookService: WebhookService) {} + + @ApiOperation({ + summary: 'Handle Webhook Events', + description: + 'Receives webhook events related to escrow and task operations.', + }) + @ApiHeader({ + name: HEADER_SIGNATURE_KEY, + description: 'Signature header for authenticating the webhook request.', + required: true, + }) + @ApiBody({ + description: + 'Details of the webhook event, including the type of event and associated data.', + type: WebhookDto, + }) + @ApiResponse({ + status: 200, + description: 'Webhook event processed successfully.', + }) + @ApiResponse({ + status: 400, + description: 'Bad Request.Invalid input parameters.', + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized. Missing or invalid credentials.', + }) + @UseGuards( + new SignatureAuthGuard([Role.Exchange, Role.Reputation, Role.JobLauncher]), + ) + @Post() + processWebhook( + @Headers(HEADER_SIGNATURE_KEY) _: string, + @Body() body: WebhookDto, + ): Promise { + return this.webhookService.handleWebhook(body); + } +} diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.dto.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.dto.ts new file mode 100644 index 0000000000..2fb51e9b42 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.dto.ts @@ -0,0 +1,58 @@ +import { ChainId } from '@human-protocol/sdk'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsObject, IsString } from 'class-validator'; +import { IsValidEthereumAddress } from '../../common/validators'; +import { EventType } from '../../common/enums/webhook'; + +export class AssignmentRejection { + @ApiProperty({ name: 'assignee_id' }) + @IsString() + assigneeId?: string; + + @ApiProperty() + @IsString() + reason?: string; +} + +export class RejectionEventData { + @ApiProperty({ + type: [AssignmentRejection], + }) + @IsArray() + public assignments: AssignmentRejection[]; +} + +export class SolutionEventData { + @ApiProperty({ name: 'solutions_url' }) + @IsString() + solutionsUrl: string; +} + +export type EventData = RejectionEventData | SolutionEventData; + +export class WebhookDto { + @ApiProperty({ + enum: ChainId, + name: 'chain_id', + }) + @IsEnum(ChainId) + public chainId: ChainId; + + @ApiProperty({ name: 'escrow_address' }) + @IsString() + @IsValidEthereumAddress() + public escrowAddress: string; + + @ApiProperty({ + enum: EventType, + name: 'event_type', + }) + @IsEnum(EventType) + public eventType: EventType; + + @ApiProperty({ + name: 'event_data', + }) + @IsObject() + public eventData?: EventData; +} diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.module.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.module.ts new file mode 100644 index 0000000000..cb30eaa2e3 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; + +import { WebhookController } from './webhook.controller'; +import { WebhookService } from './webhook.service'; +import { JobService } from '../job/job.service'; +import { ConfigModule } from '@nestjs/config'; +import { serverConfig, web3Config } from '../../common/config'; +import { HttpModule } from '@nestjs/axios'; +import { Web3Module } from '../web3/web3.module'; +import { StorageModule } from '../storage/storage.module'; + +@Module({ + imports: [ + ConfigModule.forFeature(serverConfig), + ConfigModule.forFeature(web3Config), + HttpModule, + Web3Module, + StorageModule, + ], + controllers: [WebhookController], + providers: [WebhookService, JobService], + exports: [WebhookService], +}) +export class WebhookModule {} diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts new file mode 100644 index 0000000000..3671e7dfe5 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.spec.ts @@ -0,0 +1,141 @@ +import { Test } from '@nestjs/testing'; +import { EventType } from '../../common/enums/webhook'; +import { WebhookDto } from './webhook.dto'; +import { WebhookService } from './webhook.service'; +import { JobService } from '../job/job.service'; +import { ConfigModule, registerAs } from '@nestjs/config'; +import { + MOCK_ENCRYPTION_PASSPHRASE, + MOCK_ENCRYPTION_PRIVATE_KEY, + MOCK_FILE_URL, + MOCK_REPUTATION_ORACLE_WEBHOOK_URL, + MOCK_S3_ACCESS_KEY, + MOCK_S3_BUCKET, + MOCK_S3_ENDPOINT, + MOCK_S3_PORT, + MOCK_S3_SECRET_KEY, + MOCK_S3_USE_SSL, + MOCK_WEB3_PRIVATE_KEY, +} from '../../../test/constants'; +import { Web3Service } from '../web3/web3.service'; +import { of } from 'rxjs'; +import { HttpService } from '@nestjs/axios'; +import { StorageService } from '../storage/storage.service'; + +describe('WebhookService', () => { + let webhookService: WebhookService, jobService: JobService; + + const chainId = 1; + const escrowAddress = '0x1234567890123456789012345678901234567890'; + + const signerMock = { + address: '0x1234567890123456789012345678901234567892', + getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), + }; + + const httpServicePostMock = jest + .fn() + .mockReturnValue(of({ status: 200, data: {} })); + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forFeature( + registerAs('s3', () => ({ + accessKey: MOCK_S3_ACCESS_KEY, + secretKey: MOCK_S3_SECRET_KEY, + endPoint: MOCK_S3_ENDPOINT, + port: MOCK_S3_PORT, + useSSL: MOCK_S3_USE_SSL, + bucket: MOCK_S3_BUCKET, + })), + ), + ConfigModule.forFeature( + registerAs('web3', () => ({ + web3PrivateKey: MOCK_WEB3_PRIVATE_KEY, + })), + ), + ConfigModule.forFeature( + registerAs('server', () => ({ + reputationOracleWebhookUrl: MOCK_REPUTATION_ORACLE_WEBHOOK_URL, + encryptionPrivateKey: MOCK_ENCRYPTION_PRIVATE_KEY, + encryptionPassphrase: MOCK_ENCRYPTION_PASSPHRASE, + })), + ), + ], + providers: [ + WebhookService, + JobService, + StorageService, + { + provide: Web3Service, + useValue: { + getSigner: jest.fn().mockReturnValue(signerMock), + }, + }, + { + provide: HttpService, + useValue: { + post: httpServicePostMock, + axiosRef: { + get: jest.fn(), + }, + }, + }, + ], + }).compile(); + + webhookService = moduleRef.get(WebhookService); + jobService = moduleRef.get(JobService); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('handleWebhook', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + it('should handle an incoming escrow completed webhook', async () => { + const webhook: WebhookDto = { + chainId, + escrowAddress, + eventType: EventType.ESCROW_COMPLETED, + }; + + expect(await webhookService.handleWebhook(webhook)).toBe(undefined); + }); + + it('should handle an incoming solution in review webhook', async () => { + const webhook: WebhookDto = { + chainId, + escrowAddress, + eventType: EventType.SUBMISSION_IN_REVIEW, + eventData: { solutionsUrl: MOCK_FILE_URL }, + }; + + jest + .spyOn(jobService, 'processJobSolution') + .mockResolvedValue('Solution are recorded.'); + + jest.spyOn(webhookService, 'handleWebhook').mockResolvedValue(); + + await webhookService.handleWebhook(webhook); + + expect(webhookService.handleWebhook).toHaveBeenCalledWith(webhook); + }); + + it('should return an error when the event type is invalid', async () => { + const webhook: WebhookDto = { + chainId, + escrowAddress, + eventType: EventType.ESCROW_RECORDED, + }; + + await expect(webhookService.handleWebhook(webhook)).rejects.toThrow( + 'Invalid webhook event type: escrow_recorded', + ); + }); + }); +}); diff --git a/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.ts b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.ts new file mode 100644 index 0000000000..2bc9a185cc --- /dev/null +++ b/packages/apps/fortune/recording-oracle/src/modules/webhook/webhook.service.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { EventType } from '../../common/enums/webhook'; +import { WebhookDto } from './webhook.dto'; +import { JobService } from '../job/job.service'; + +@Injectable() +export class WebhookService { + constructor(private readonly jobService: JobService) {} + + public async handleWebhook(wehbook: WebhookDto): Promise { + switch (wehbook.eventType) { + case EventType.ESCROW_COMPLETED: + break; + + case EventType.SUBMISSION_IN_REVIEW: + await this.jobService.processJobSolution(wehbook); + break; + + default: + throw new BadRequestException( + `Invalid webhook event type: ${wehbook.eventType}`, + ); + } + } +} diff --git a/packages/apps/fortune/recording-oracle/test/setup.ts b/packages/apps/fortune/recording-oracle/test/setup.ts new file mode 100644 index 0000000000..4668420e30 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/test/setup.ts @@ -0,0 +1,16 @@ +import { KVStoreClient, KVStoreKeys, Role } from '@human-protocol/sdk'; +import { Wallet, ethers } from 'ethers'; + +export async function setup(): Promise { + //This private key is generated by hardhat and should be used just for local testing + const privateKey = + '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6'; //0x90F79bf6EB2c4f870365E785982E1f101E93b906 + const provider = new ethers.JsonRpcProvider('http://localhost:8545'); + const wallet = new Wallet(privateKey, provider); + + const kvStoreClient = await KVStoreClient.build(wallet); + await kvStoreClient.setBulk( + [KVStoreKeys.role, KVStoreKeys.fee, KVStoreKeys.webhookUrl], + [Role.RecordingOracle, '1', 'http://localhost:5001/jobs/solve'], + ); +} diff --git a/packages/apps/job-launcher/client/.env.example b/packages/apps/job-launcher/client/.env.example index 78d8bb7f2b..544d26aea9 100644 --- a/packages/apps/job-launcher/client/.env.example +++ b/packages/apps/job-launcher/client/.env.example @@ -4,4 +4,4 @@ VITE_APP_STRIPE_PUBLISHABLE_KEY= VITE_APP_HCAPTCHA_SITE_KEY= VITE_APP_HCAPTCHA_EXCHANGE_URL= VITE_APP_HCAPTCHA_LABELING_BASE_URL= -VITE_APP_NETWORK= \ No newline at end of file +VITE_APP_ENVIRONMENT= \ No newline at end of file diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 54eb5c5060..593c47c70d 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -26,7 +26,7 @@ "swr": "^2.2.4", "typescript": "^4.9.3", "wagmi": "^0.12.2", - "web-vitals": "^3.5.1", + "web-vitals": "^3.5.2", "xml2js": "^0.6.2", "yup": "^1.2.0" }, @@ -56,12 +56,12 @@ }, "devDependencies": { "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.0.0", + "@testing-library/react": "^14.2.1", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.14", "@types/react-test-renderer": "^18.0.0", "@types/xml2js": "^0.4.14", - "@vitejs/plugin-react": "^3.1.0", + "@vitejs/plugin-react": "^4.2.1", "eslint-config-react-app": "^7.0.1", "eslint-import-resolver-typescript": "^3.5.3", "eslint-plugin-import": "^2.29.0", diff --git a/packages/apps/job-launcher/client/src/App.tsx b/packages/apps/job-launcher/client/src/App.tsx index 7f7bc1a53b..1d8f44743b 100644 --- a/packages/apps/job-launcher/client/src/App.tsx +++ b/packages/apps/job-launcher/client/src/App.tsx @@ -10,6 +10,7 @@ import JobDetail from './pages/Job/JobDetail'; import JobList from './pages/Job/JobList'; import TopUpAccount from './pages/Profile/TopUpAccount'; import ResetPassword from './pages/ResetPassword'; +import ValidateEmail from './pages/ValidateEmail'; import VerifyEmail from './pages/VerifyEmail'; import './index.css'; @@ -23,6 +24,7 @@ export default function App() { } /> } /> } /> + } /> { hCaptchaToken, }); dispatch(signIn(data)); - dispatch(fetchUserBalanceAsync()); } catch (err) { showError(err); } diff --git a/packages/apps/job-launcher/client/src/components/Auth/SignUpForm.jsx b/packages/apps/job-launcher/client/src/components/Auth/SignUpForm.jsx index 03741891df..5de00260df 100644 --- a/packages/apps/job-launcher/client/src/components/Auth/SignUpForm.jsx +++ b/packages/apps/job-launcher/client/src/components/Auth/SignUpForm.jsx @@ -16,7 +16,7 @@ import * as authService from '../../services/auth'; import { Password } from './Password'; import { RegisterValidationSchema } from './schema'; -export const SignUpForm = ({ onFinish }) => { +export const SignUpForm = ({ onFinish, setMode, setTabValue }) => { const captchaRef = useRef(null); const [isLoading, setIsLoading] = useState(false); const [isSuccess, setIsSuccess] = useState(false); @@ -96,7 +96,16 @@ export const SignUpForm = ({ onFinish }) => { - diff --git a/packages/apps/job-launcher/client/src/components/Auth/ValidateEmailForm.tsx b/packages/apps/job-launcher/client/src/components/Auth/ValidateEmailForm.tsx new file mode 100644 index 0000000000..0aeecc3de1 --- /dev/null +++ b/packages/apps/job-launcher/client/src/components/Auth/ValidateEmailForm.tsx @@ -0,0 +1,105 @@ +import { Box, Button, CircularProgress, Grid, Typography } from '@mui/material'; +import { useState } from 'react'; +import * as authService from '../../services/auth'; +import { useAppDispatch, useAppSelector } from '../../state'; +import { signOut } from '../../state/auth/reducer'; + +export default function VerifyEmailForm() { + const [isLoading, setIsLoading] = useState(false); + const [emailSent, setEmailSent] = useState(false); + const { user, refreshToken } = useAppSelector((state) => state.auth); + const dispatch = useAppDispatch(); + + const handleResend = async () => { + setIsLoading(true); + try { + await authService.resendEmailVerification(user?.email!); + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + setEmailSent(true); + setIsLoading(false); + }; + const handleLogOut = async () => { + setIsLoading(true); + try { + if (refreshToken) { + await authService.signOut(refreshToken); + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + dispatch(signOut()); + setIsLoading(false); + }; + + if (isLoading) { + return ( + + + + ); + } + + return ( + + + Verify email + + + Hi, +
+
+ We sent an email to {user?.email!}, please check your inbox and verify + your email. If you can’t find our email, please check junk junk/spam + email folder. +
+ + + {!emailSent && ( + <> + + + + + + + + )} + {emailSent && ( + <> + + + + + )} + +
+ ); +} diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx index 63cf3a9b4c..eac28ebf7f 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx @@ -37,7 +37,7 @@ export const CvatJobRequestForm = () => { const { jobRequest, updateJobRequest, goToPrevStep, goToNextStep } = useCreateJobPageUI(); const [searchParams] = useSearchParams(); - const [expanded, setExpanded] = useState('panel1'); + const [expanded, setExpanded] = useState(['panel1']); const initialValues = { labels: [], @@ -57,7 +57,11 @@ export const CvatJobRequestForm = () => { const handleChange = (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { - setExpanded(newExpanded ? panel : false); + if (newExpanded) { + setExpanded([...expanded, panel]); + } else { + setExpanded(expanded.filter((item) => item !== panel)); + } }; const handleNext = ({ @@ -133,7 +137,7 @@ export const CvatJobRequestForm = () => {
@@ -212,7 +216,7 @@ export const CvatJobRequestForm = () => { diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx index d58e141e2f..b35216d6f4 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx @@ -37,7 +37,7 @@ import { HCaptchaJobRequesteValidationSchema } from './schema'; export const HCaptchaJobRequestForm = () => { const { jobRequest, updateJobRequest, goToPrevStep, goToNextStep } = useCreateJobPageUI(); - const [expanded, setExpanded] = useState('panel1'); + const [expanded, setExpanded] = useState(['panel1']); const [searchParams] = useSearchParams(); const initialValues = { @@ -62,7 +62,11 @@ export const HCaptchaJobRequestForm = () => { const handleChange = (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { - setExpanded(newExpanded ? panel : false); + if (newExpanded) { + setExpanded([...expanded, panel]); + } else { + setExpanded(expanded.filter((item) => item !== panel)); + } }; const handleNext = (data: any) => { @@ -125,7 +129,7 @@ export const HCaptchaJobRequestForm = () => { @@ -173,12 +177,12 @@ export const HCaptchaJobRequestForm = () => { - + { > Unsure what job type to choose? - + @@ -274,7 +284,7 @@ export const HCaptchaJobRequestForm = () => { @@ -382,6 +392,7 @@ export const HCaptchaJobRequestForm = () => { fullWidth name="images" placeholder="Place link to an example image" + helperText={errors.images?.[index]} sx={{ flex: 1 }} value={image} onChange={(e) => { @@ -390,7 +401,7 @@ export const HCaptchaJobRequestForm = () => { setFieldValue('images', newImages); }} onBlur={handleBlur} - error={touched.images && Boolean(errors.images)} + error={Boolean(errors.images?.[index])} /> { @@ -506,7 +517,7 @@ export const HCaptchaJobRequestForm = () => { @@ -525,6 +536,7 @@ export const HCaptchaJobRequestForm = () => { onChange={(newValue) => setFieldValue('completionDate', newValue) } + slotProps={{ openPickerIcon: { sx: { color: '#858EC6' } } }} /> diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts index ef77d47cf5..94709da643 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts @@ -37,7 +37,10 @@ export const HCaptchaJobRequesteValidationSchema = Yup.object().shape({ .moreThan(0, 'Min Requests must be greater than 0'), maxRequests: Yup.number() .required('Max Requests is required') - .moreThan(0, 'Max Requests must be greater than 0'), + .moreThan( + Yup.ref('minRequests'), + 'Max Requests must be greater than min requests', + ), dataUrl: Yup.string().required('Data URL is required').url('Invalid URL'), labelingPrompt: Yup.string().required('Labeling Prompt is required'), groundTruths: Yup.string() @@ -48,4 +51,5 @@ export const HCaptchaJobRequesteValidationSchema = Yup.object().shape({ .moreThan(0, 'Accuracy target must be greater than 0') .max(100, 'Accuracy target must be less than or equal to 100'), targetBrowser: Yup.string().required('Target Browser is required'), + images: Yup.array().of(Yup.string().url('Invalid Image URL')), }); diff --git a/packages/apps/job-launcher/client/src/components/ProtectedRoute/index.tsx b/packages/apps/job-launcher/client/src/components/ProtectedRoute/index.tsx index 3568b3abd6..8de2deca44 100644 --- a/packages/apps/job-launcher/client/src/components/ProtectedRoute/index.tsx +++ b/packages/apps/job-launcher/client/src/components/ProtectedRoute/index.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { useLocation, Navigate } from 'react-router-dom'; import { useAppSelector } from '../../state'; +import { UserStatus } from '../../state/auth/types'; export function ProtectedRoute({ children }: { children: JSX.Element }) { - const { isAuthed } = useAppSelector((state) => state.auth); + const { isAuthed, user } = useAppSelector((state) => state.auth); let location = useLocation(); if (!isAuthed) { @@ -12,6 +13,8 @@ export function ProtectedRoute({ children }: { children: JSX.Element }) { // along to that page after they login, which is a nicer user experience // than dropping them off on the home page. return ; + } else if (isAuthed && user?.status === UserStatus.PENDING) { + return ; } return children; diff --git a/packages/apps/job-launcher/client/src/constants/chains.ts b/packages/apps/job-launcher/client/src/constants/chains.ts index 8b879c03b3..297938dd02 100644 --- a/packages/apps/job-launcher/client/src/constants/chains.ts +++ b/packages/apps/job-launcher/client/src/constants/chains.ts @@ -1,16 +1,32 @@ import { ChainId } from '@human-protocol/sdk'; -export const IS_MAINNET = import.meta.env.VITE_APP_NETWORK === 'mainnet'; +export const IS_MAINNET = + import.meta.env.VITE_APP_ENVIRONMENT.toLowerCase() === 'mainnet'; export const IS_TESTNET = !IS_MAINNET; -export const SUPPORTED_CHAIN_IDS = IS_MAINNET - ? [ChainId.POLYGON] // ? [ChainId.BSC_MAINNET, ChainId.POLYGON, ChainId.MOONBEAM] - : [ChainId.BSC_TESTNET, ChainId.POLYGON_MUMBAI, ChainId.GOERLI]; +export let SUPPORTED_CHAIN_IDS: ChainId[]; +switch (import.meta.env.VITE_APP_ENVIRONMENT.toLowerCase()) { + case 'mainnet': + SUPPORTED_CHAIN_IDS = [ChainId.POLYGON]; + break; + case 'testnet': + SUPPORTED_CHAIN_IDS = [ + ChainId.BSC_TESTNET, + ChainId.POLYGON_MUMBAI, + ChainId.GOERLI, + ]; + break; + case 'localhost': + default: + SUPPORTED_CHAIN_IDS = [ChainId.LOCALHOST]; + break; +} export const CHAIN_ID_BY_NAME: Record = { 'Polygon Mumbai': ChainId.POLYGON_MUMBAI, 'Binance Smart Chain': ChainId.BSC_MAINNET, 'Ethereum Goerli': ChainId.GOERLI, + Localhost: ChainId.LOCALHOST, }; export const RPC_URLS: { diff --git a/packages/apps/job-launcher/client/src/layouts/index.tsx b/packages/apps/job-launcher/client/src/layouts/index.tsx index 184b3a6062..8465c04b9e 100644 --- a/packages/apps/job-launcher/client/src/layouts/index.tsx +++ b/packages/apps/job-launcher/client/src/layouts/index.tsx @@ -1,11 +1,12 @@ import { useAppSelector } from '../state'; +import { UserStatus } from '../state/auth/types'; import AuthLayout from './AuthLayout'; import DefaultLayout from './DefaultLayout'; export default function Layout() { - const { isAuthed } = useAppSelector((state) => state.auth); + const { isAuthed, user } = useAppSelector((state) => state.auth); - if (isAuthed) return ; + if (isAuthed && user?.status === UserStatus.ACTIVE) return ; return ; } diff --git a/packages/apps/job-launcher/client/src/main.tsx b/packages/apps/job-launcher/client/src/main.tsx index 22b9321b70..17ac793edf 100644 --- a/packages/apps/job-launcher/client/src/main.tsx +++ b/packages/apps/job-launcher/client/src/main.tsx @@ -1,4 +1,4 @@ -import { Buffer } from 'buffer'; +// import { Buffer } from 'buffer'; import CssBaseline from '@mui/material/CssBaseline'; import { ThemeProvider } from '@mui/material/styles'; import { LocalizationProvider } from '@mui/x-date-pickers'; diff --git a/packages/apps/job-launcher/client/src/pages/Home/index.tsx b/packages/apps/job-launcher/client/src/pages/Home/index.tsx index 696fdb1fed..26c5c15a29 100644 --- a/packages/apps/job-launcher/client/src/pages/Home/index.tsx +++ b/packages/apps/job-launcher/client/src/pages/Home/index.tsx @@ -4,15 +4,23 @@ import { Navigate } from 'react-router-dom'; import { SignInForm } from '../../components/Auth/SignInForm'; import { SignUpForm } from '../../components/Auth/SignUpForm'; import { HomeStyledTab, HomeStyledTabs } from '../../components/Tabs'; -import { useAppSelector } from '../../state'; +import { useAppDispatch, useAppSelector } from '../../state'; +import { fetchUserBalanceAsync } from '../../state/auth/reducer'; +import { UserStatus } from '../../state/auth/types'; export default function Home() { const [tabValue, setTabValue] = useState(0); const [mode, setMode] = useState(); - const { isAuthed } = useAppSelector((state) => state.auth); + const { isAuthed, user } = useAppSelector((state) => state.auth); + const dispatch = useAppDispatch(); if (isAuthed) { - return ; + if (user?.status === UserStatus.PENDING) { + return ; + } else { + dispatch(fetchUserBalanceAsync()); + return ; + } } const handleSignUp = () => { @@ -87,7 +95,11 @@ export default function Home() { {tabValue === 1 && ( <> {mode === 'sign_up' ? ( - + ) : (