Skip to content

Commit a4ad466

Browse files
authored
Add getManifestHash function for python/typescript sdk. Allow docker-network URLs. (#857)
* Add get_manifest_hash function to escrow * Add getManifestHash function to typescript sdk * Extend validators.url to allow docker URLs
1 parent e0b105f commit a4ad466

File tree

7 files changed

+175
-9
lines changed

7 files changed

+175
-9
lines changed

packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
get_erc20_interface,
1515
handle_transaction,
1616
)
17-
from validators import url as URL
1817
from web3 import Web3, contract
1918
from web3.middleware import geth_poa_middleware
2019

20+
from .utils import validate_url
21+
2122
GAS_LIMIT = int(os.getenv("GAS_LIMIT", 4712388))
2223

2324
LOG = logging.getLogger("human_protocol_sdk.escrow")
@@ -44,7 +45,6 @@ def __init__(
4445
reputation_oracle_fee: Decimal,
4546
manifest_url: str,
4647
hash: str,
47-
skip_manifest_url_validation: bool = False,
4848
):
4949
"""
5050
Initializes a Escrow instance.
@@ -72,7 +72,7 @@ def __init__(
7272
raise EscrowClientError("Fee must be between 0 and 100")
7373
if recording_oracle_fee + reputation_oracle_fee > 100:
7474
raise EscrowClientError("Total fee must be less than 100")
75-
if not URL(manifest_url) and not skip_manifest_url_validation:
75+
if not validate_url(manifest_url):
7676
raise EscrowClientError(f"Invalid manifest URL: {manifest_url}")
7777
if not hash:
7878
raise EscrowClientError("Invalid empty manifest hash")
@@ -321,7 +321,7 @@ def store_results(self, escrow_address: str, url: str, hash: str) -> None:
321321
raise EscrowClientError(f"Invalid escrow address: {escrow_address}")
322322
if not hash:
323323
raise EscrowClientError("Invalid empty hash")
324-
if not URL(url):
324+
if not validate_url(url):
325325
raise EscrowClientError(f"Invalid URL: {url}")
326326
if not self.w3.eth.default_account:
327327
raise EscrowClientError("You must add an account to Web3 instance")
@@ -401,7 +401,7 @@ def bulk_payout(
401401
raise EscrowClientError(
402402
f"Escrow does not have enough balance. Current balance: {balance}. Amounts: {total_amount}"
403403
)
404-
if not URL(final_results_url):
404+
if not validate_url(final_results_url):
405405
raise EscrowClientError(f"Invalid final results URL: {final_results_url}")
406406
if not final_results_hash:
407407
raise EscrowClientError("Invalid empty final results hash")
@@ -507,6 +507,24 @@ def get_balance(self, escrow_address: str) -> Decimal:
507507

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

510+
def get_manifest_hash(self, escrow_address: str) -> str:
511+
"""Gets the manifest file hash.
512+
513+
Args:
514+
escrow_address (str): Address of the escrow
515+
516+
Returns:
517+
str: Manifest file hash
518+
519+
Raises:
520+
EscrowClientError: If an error occurs while checking the parameters
521+
"""
522+
523+
if not Web3.is_address(escrow_address):
524+
raise EscrowClientError(f"Invalid escrow address: {escrow_address}")
525+
526+
return self._get_escrow_contract(escrow_address).functions.manifestHash().call()
527+
510528
def get_manifest_url(self, escrow_address: str) -> str:
511529
"""Gets the manifest file URL.
512530

packages/sdk/python/human-protocol-sdk/human_protocol_sdk/storage.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import requests
99
from minio import Minio
10-
from validators import url as URL
10+
11+
from .utils import validate_url
1112

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

@@ -135,7 +136,7 @@ def download_file_from_url(url: str) -> bytes:
135136
Raises:
136137
StorageClientError: If an error occurs while downloading the file.
137138
"""
138-
if not URL(url):
139+
if not validate_url(url):
139140
raise StorageClientError(f"Invalid URL: {url}")
140141

141142
try:

packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import json
22
import logging
33
import time
4+
import re
45
from typing import Tuple, Optional
56

67
import requests
8+
from validators import url as URL
79
from web3 import Web3
810
from web3.contract import Contract
911
from web3.types import TxReceipt
@@ -239,3 +241,38 @@ def handle_transaction(w3: Web3, tx_name, tx, exception):
239241
raise exception(f"{tx_name} transaction failed: {message}")
240242
else:
241243
raise exception(f"{tx_name} transaction failed.")
244+
245+
246+
def validate_url(url: str) -> bool:
247+
"""Gets the url string.
248+
Args:
249+
url (str): Public or private url address
250+
Returns:
251+
bool: Returns True if url is valid
252+
Raises:
253+
ValidationFailure: If the url is invalid
254+
"""
255+
256+
# validators.url tracks docker network URL as ivalid
257+
pattern = re.compile(
258+
r"^"
259+
# protocol identifier
260+
r"(?:(?:http)://)"
261+
# host name
262+
r"(?:(?:(?:xn--[-]{0,2})|[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]-?)*"
263+
r"[a-z\u00a1-\uffff\U00010000-\U0010ffff0-9]+)"
264+
# port number
265+
r"(?::\d{2,5})?"
266+
# resource path
267+
r"(?:/[-a-z\u00a1-\uffff\U00010000-\U0010ffff0-9._~%!$&'()*+,;=:@/]*)?"
268+
# query string
269+
r"(?:\?\S*)?" r"$",
270+
re.UNICODE | re.IGNORECASE,
271+
)
272+
273+
result = pattern.match(url)
274+
275+
if not result:
276+
return URL(url)
277+
278+
return True

packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_escrow.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_escrow_config_valid_params(self):
100100
self.assertEqual(escrow_config.manifest_url, manifest_url)
101101
self.assertEqual(escrow_config.hash, hash)
102102

103-
def test_escrow_config_valid_params(self):
103+
def test_escrow_config_valid_params_with_docker_network_url(self):
104104
recording_oracle_address = "0x1234567890123456789012345678901234567890"
105105
reputation_oracle_address = "0x1234567890123456789012345678901234567890"
106106
recording_oracle_fee = 10
@@ -115,7 +115,6 @@ def test_escrow_config_valid_params(self):
115115
reputation_oracle_fee,
116116
manifest_url,
117117
hash,
118-
True,
119118
)
120119

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

1481+
def test_get_manifest_hash(self):
1482+
mock_contract = MagicMock()
1483+
mock_contract.functions.manifestHash = MagicMock()
1484+
mock_contract.functions.manifestHash.return_value.call.return_value = (
1485+
"mock_value"
1486+
)
1487+
self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract)
1488+
escrow_address = "0x1234567890123456789012345678901234567890"
1489+
1490+
result = self.escrow.get_manifest_hash(escrow_address)
1491+
1492+
self.escrow._get_escrow_contract.assert_called_once_with(escrow_address)
1493+
mock_contract.functions.manifestHash.assert_called_once_with()
1494+
self.assertEqual(result, "mock_value")
1495+
1496+
def test_get_manifest_hash_invalid_address(self):
1497+
with self.assertRaises(EscrowClientError) as cm:
1498+
self.escrow.get_manifest_hash("invalid_address")
1499+
self.assertEqual(f"Invalid escrow address: invalid_address", str(cm.exception))
1500+
14821501
def test_get_manifest_url(self):
14831502
mock_contract = MagicMock()
14841503
mock_contract.functions.manifestUrl = MagicMock()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import unittest
2+
from validators.utils import ValidationFailure
3+
4+
from human_protocol_sdk.utils import validate_url
5+
6+
7+
class TestStorageClient(unittest.TestCase):
8+
def test_validate_url_with_valid_url(self):
9+
self.assertTrue(validate_url("https://valid-url.tst/valid"))
10+
11+
def test_validate_url_with_docker_network_url(self):
12+
self.assertTrue(validate_url("http://test:8000/valid"))
13+
14+
def test_validate_url_with_invalid_url(self):
15+
assert isinstance(validate_url("htt://test:8000/valid"), ValidationFailure)

packages/sdk/typescript/human-protocol-sdk/src/escrow.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,33 @@ export class EscrowClient {
585585
}
586586
}
587587

588+
/**
589+
* Returns the manifest file hash.
590+
*
591+
* @param {string} escrowAddress - Address of the escrow.
592+
* @returns {Promise<void>}
593+
* @throws {Error} - An error object if an error occurred.
594+
*/
595+
async getManifestHash(escrowAddress: string): Promise<string> {
596+
if (!ethers.utils.isAddress(escrowAddress)) {
597+
throw ErrorInvalidEscrowAddressProvided;
598+
}
599+
600+
if (!(await this.escrowFactoryContract.hasEscrow(escrowAddress))) {
601+
throw ErrorEscrowAddressIsNotProvidedByFactory;
602+
}
603+
604+
try {
605+
this.escrowContract = Escrow__factory.connect(
606+
escrowAddress,
607+
this.signerOrProvider
608+
);
609+
return this.escrowContract.manifestHash();
610+
} catch (e) {
611+
return throwError(e);
612+
}
613+
}
614+
588615
/**
589616
* Returns the manifest file URL.
590617
*

packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ describe('EscrowClient', () => {
8282
abort: vi.fn(),
8383
addTrustedHandlers: vi.fn(),
8484
getBalance: vi.fn(),
85+
manifestHash: vi.fn(),
8586
manifestUrl: vi.fn(),
8687
finalResultsUrl: vi.fn(),
8788
token: vi.fn(),
@@ -1158,6 +1159,54 @@ describe('EscrowClient', () => {
11581159
});
11591160
});
11601161

1162+
describe('getManifestHash', () => {
1163+
test('should throw an error if escrowAddress is an invalid address', async () => {
1164+
const escrowAddress = FAKE_ADDRESS;
1165+
1166+
await expect(escrowClient.getManifestHash(escrowAddress)).rejects.toThrow(
1167+
ErrorInvalidEscrowAddressProvided
1168+
);
1169+
});
1170+
1171+
test('should throw an error if hasEscrow returns false', async () => {
1172+
const escrowAddress = ethers.constants.AddressZero;
1173+
1174+
escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(false);
1175+
1176+
await expect(escrowClient.getManifestHash(escrowAddress)).rejects.toThrow(
1177+
ErrorEscrowAddressIsNotProvidedByFactory
1178+
);
1179+
});
1180+
1181+
test('should successfully getManifestHash', async () => {
1182+
const escrowAddress = ethers.constants.AddressZero;
1183+
const hash = FAKE_HASH;
1184+
1185+
escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true);
1186+
escrowClient.escrowContract.manifestHash.mockReturnValue(hash);
1187+
1188+
const manifestHash = await escrowClient.getManifestHash(escrowAddress);
1189+
1190+
expect(manifestHash).toEqual(hash);
1191+
expect(escrowClient.escrowContract.manifestHash).toHaveBeenCalledWith();
1192+
});
1193+
1194+
test('should throw an error if getManifestHash fails', async () => {
1195+
const escrowAddress = ethers.constants.AddressZero;
1196+
1197+
escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true);
1198+
escrowClient.escrowContract.manifestHash.mockRejectedValueOnce(
1199+
new Error()
1200+
);
1201+
1202+
await expect(
1203+
escrowClient.getManifestHash(escrowAddress)
1204+
).rejects.toThrow();
1205+
1206+
expect(escrowClient.escrowContract.manifestHash).toHaveBeenCalledWith();
1207+
});
1208+
});
1209+
11611210
describe('getManifestUrl', () => {
11621211
test('should throw an error if escrowAddress is an invalid address', async () => {
11631212
const escrowAddress = FAKE_ADDRESS;

0 commit comments

Comments
 (0)