Skip to content

Commit

Permalink
Add getManifestHash function for python/typescript sdk. Allow docker-…
Browse files Browse the repository at this point in the history
…network URLs. (#857)

* Add get_manifest_hash function to escrow

* Add getManifestHash function to typescript sdk

* Extend validators.url to allow docker URLs
  • Loading branch information
alidzm authored Sep 4, 2023
1 parent e0b105f commit a4ad466
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
get_erc20_interface,
handle_transaction,
)
from validators import url as URL
from web3 import Web3, contract
from web3.middleware import geth_poa_middleware

from .utils import validate_url

GAS_LIMIT = int(os.getenv("GAS_LIMIT", 4712388))

LOG = logging.getLogger("human_protocol_sdk.escrow")
Expand All @@ -44,7 +45,6 @@ def __init__(
reputation_oracle_fee: Decimal,
manifest_url: str,
hash: str,
skip_manifest_url_validation: bool = False,
):
"""
Initializes a Escrow instance.
Expand Down Expand Up @@ -72,7 +72,7 @@ def __init__(
raise EscrowClientError("Fee must be between 0 and 100")
if recording_oracle_fee + reputation_oracle_fee > 100:
raise EscrowClientError("Total fee must be less than 100")
if not URL(manifest_url) and not skip_manifest_url_validation:
if not validate_url(manifest_url):
raise EscrowClientError(f"Invalid manifest URL: {manifest_url}")
if not hash:
raise EscrowClientError("Invalid empty manifest hash")
Expand Down Expand Up @@ -321,7 +321,7 @@ def store_results(self, escrow_address: str, url: str, hash: str) -> None:
raise EscrowClientError(f"Invalid escrow address: {escrow_address}")
if not hash:
raise EscrowClientError("Invalid empty hash")
if not URL(url):
if not validate_url(url):
raise EscrowClientError(f"Invalid URL: {url}")
if not self.w3.eth.default_account:
raise EscrowClientError("You must add an account to Web3 instance")
Expand Down Expand Up @@ -401,7 +401,7 @@ def bulk_payout(
raise EscrowClientError(
f"Escrow does not have enough balance. Current balance: {balance}. Amounts: {total_amount}"
)
if not URL(final_results_url):
if not validate_url(final_results_url):
raise EscrowClientError(f"Invalid final results URL: {final_results_url}")
if not final_results_hash:
raise EscrowClientError("Invalid empty final results hash")
Expand Down Expand Up @@ -507,6 +507,24 @@ def get_balance(self, escrow_address: str) -> Decimal:

return self._get_escrow_contract(escrow_address).functions.getBalance().call()

def get_manifest_hash(self, escrow_address: str) -> str:
"""Gets the manifest file hash.
Args:
escrow_address (str): Address of the escrow
Returns:
str: Manifest file hash
Raises:
EscrowClientError: If an error occurs while checking the parameters
"""

if not Web3.is_address(escrow_address):
raise EscrowClientError(f"Invalid escrow address: {escrow_address}")

return self._get_escrow_contract(escrow_address).functions.manifestHash().call()

def get_manifest_url(self, escrow_address: str) -> str:
"""Gets the manifest file URL.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

import requests
from minio import Minio
from validators import url as URL

from .utils import validate_url

logging.getLogger("minio").setLevel(logging.INFO)

Expand Down Expand Up @@ -135,7 +136,7 @@ def download_file_from_url(url: str) -> bytes:
Raises:
StorageClientError: If an error occurs while downloading the file.
"""
if not URL(url):
if not validate_url(url):
raise StorageClientError(f"Invalid URL: {url}")

try:
Expand Down
37 changes: 37 additions & 0 deletions packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
import logging
import time
import re
from typing import Tuple, Optional

import requests
from validators import url as URL
from web3 import Web3
from web3.contract import Contract
from web3.types import TxReceipt
Expand Down Expand Up @@ -239,3 +241,38 @@ def handle_transaction(w3: Web3, tx_name, tx, exception):
raise exception(f"{tx_name} transaction failed: {message}")
else:
raise exception(f"{tx_name} transaction failed.")


def validate_url(url: str) -> bool:
"""Gets the url string.
Args:
url (str): Public or private url address
Returns:
bool: Returns True if url is valid
Raises:
ValidationFailure: If the url is invalid
"""

# validators.url tracks docker network URL as ivalid
pattern = re.compile(
r"^"
# protocol identifier
r"(?:(?:http)://)"
# host name
r"(?:(?:(?:xn--[-]{0,2})|[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]-?)*"
r"[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]+)"
# port number
r"(?::\d{2,5})?"
# resource path
r"(?:/[-a-z\u00a1-\uffff\U00010000-\U0010ffff0-9._~%!$&'()*+,;=:@/]*)?"
# query string
r"(?:\?\S*)?" r"$",
re.UNICODE | re.IGNORECASE,
)

result = pattern.match(url)

if not result:
return URL(url)

return True
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def test_escrow_config_valid_params(self):
self.assertEqual(escrow_config.manifest_url, manifest_url)
self.assertEqual(escrow_config.hash, hash)

def test_escrow_config_valid_params(self):
def test_escrow_config_valid_params_with_docker_network_url(self):
recording_oracle_address = "0x1234567890123456789012345678901234567890"
reputation_oracle_address = "0x1234567890123456789012345678901234567890"
recording_oracle_fee = 10
Expand All @@ -115,7 +115,6 @@ def test_escrow_config_valid_params(self):
reputation_oracle_fee,
manifest_url,
hash,
True,
)

self.assertEqual(
Expand Down Expand Up @@ -1479,6 +1478,26 @@ def test_get_balance_invalid_escrow(self):
"Escrow address is not provided by the factory", str(cm.exception)
)

def test_get_manifest_hash(self):
mock_contract = MagicMock()
mock_contract.functions.manifestHash = MagicMock()
mock_contract.functions.manifestHash.return_value.call.return_value = (
"mock_value"
)
self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract)
escrow_address = "0x1234567890123456789012345678901234567890"

result = self.escrow.get_manifest_hash(escrow_address)

self.escrow._get_escrow_contract.assert_called_once_with(escrow_address)
mock_contract.functions.manifestHash.assert_called_once_with()
self.assertEqual(result, "mock_value")

def test_get_manifest_hash_invalid_address(self):
with self.assertRaises(EscrowClientError) as cm:
self.escrow.get_manifest_hash("invalid_address")
self.assertEqual(f"Invalid escrow address: invalid_address", str(cm.exception))

def test_get_manifest_url(self):
mock_contract = MagicMock()
mock_contract.functions.manifestUrl = MagicMock()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import unittest
from validators.utils import ValidationFailure

from human_protocol_sdk.utils import validate_url


class TestStorageClient(unittest.TestCase):
def test_validate_url_with_valid_url(self):
self.assertTrue(validate_url("https://valid-url.tst/valid"))

def test_validate_url_with_docker_network_url(self):
self.assertTrue(validate_url("http://test:8000/valid"))

def test_validate_url_with_invalid_url(self):
assert isinstance(validate_url("htt://test:8000/valid"), ValidationFailure)
27 changes: 27 additions & 0 deletions packages/sdk/typescript/human-protocol-sdk/src/escrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,33 @@ export class EscrowClient {
}
}

/**
* Returns the manifest file hash.
*
* @param {string} escrowAddress - Address of the escrow.
* @returns {Promise<void>}
* @throws {Error} - An error object if an error occurred.
*/
async getManifestHash(escrowAddress: string): Promise<string> {
if (!ethers.utils.isAddress(escrowAddress)) {
throw ErrorInvalidEscrowAddressProvided;
}

if (!(await this.escrowFactoryContract.hasEscrow(escrowAddress))) {
throw ErrorEscrowAddressIsNotProvidedByFactory;
}

try {
this.escrowContract = Escrow__factory.connect(
escrowAddress,
this.signerOrProvider
);
return this.escrowContract.manifestHash();
} catch (e) {
return throwError(e);
}
}

/**
* Returns the manifest file URL.
*
Expand Down
49 changes: 49 additions & 0 deletions packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ describe('EscrowClient', () => {
abort: vi.fn(),
addTrustedHandlers: vi.fn(),
getBalance: vi.fn(),
manifestHash: vi.fn(),
manifestUrl: vi.fn(),
finalResultsUrl: vi.fn(),
token: vi.fn(),
Expand Down Expand Up @@ -1158,6 +1159,54 @@ describe('EscrowClient', () => {
});
});

describe('getManifestHash', () => {
test('should throw an error if escrowAddress is an invalid address', async () => {
const escrowAddress = FAKE_ADDRESS;

await expect(escrowClient.getManifestHash(escrowAddress)).rejects.toThrow(
ErrorInvalidEscrowAddressProvided
);
});

test('should throw an error if hasEscrow returns false', async () => {
const escrowAddress = ethers.constants.AddressZero;

escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(false);

await expect(escrowClient.getManifestHash(escrowAddress)).rejects.toThrow(
ErrorEscrowAddressIsNotProvidedByFactory
);
});

test('should successfully getManifestHash', async () => {
const escrowAddress = ethers.constants.AddressZero;
const hash = FAKE_HASH;

escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true);
escrowClient.escrowContract.manifestHash.mockReturnValue(hash);

const manifestHash = await escrowClient.getManifestHash(escrowAddress);

expect(manifestHash).toEqual(hash);
expect(escrowClient.escrowContract.manifestHash).toHaveBeenCalledWith();
});

test('should throw an error if getManifestHash fails', async () => {
const escrowAddress = ethers.constants.AddressZero;

escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true);
escrowClient.escrowContract.manifestHash.mockRejectedValueOnce(
new Error()
);

await expect(
escrowClient.getManifestHash(escrowAddress)
).rejects.toThrow();

expect(escrowClient.escrowContract.manifestHash).toHaveBeenCalledWith();
});
});

describe('getManifestUrl', () => {
test('should throw an error if escrowAddress is an invalid address', async () => {
const escrowAddress = FAKE_ADDRESS;
Expand Down

1 comment on commit a4ad466

@vercel
Copy link

@vercel vercel bot commented on a4ad466 Sep 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

job-launcher-server – ./packages/apps/job-launcher/server

job-launcher-server-humanprotocol.vercel.app
job-launcher-server-nine.vercel.app
job-launcher-server-git-develop-humanprotocol.vercel.app

Please sign in to comment.