From 3d49d09ce0d80a942e3ae8ab77baf742983bb3fd Mon Sep 17 00:00:00 2001 From: Amit Pandey Date: Wed, 11 Feb 2026 22:19:26 +0530 Subject: [PATCH 1/3] removed fixtures Signed-off-by: Amit Pandey --- tests/fixtures/__init__.py | 2 - tests/fixtures/common_fixtures.py | 91 ------------------------------- 2 files changed, 93 deletions(-) delete mode 100644 tests/fixtures/__init__.py delete mode 100644 tests/fixtures/common_fixtures.py diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py deleted file mode 100644 index cc0f186..0000000 --- a/tests/fixtures/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Test fixtures package - diff --git a/tests/fixtures/common_fixtures.py b/tests/fixtures/common_fixtures.py deleted file mode 100644 index f931e32..0000000 --- a/tests/fixtures/common_fixtures.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from unittest.mock import Mock, MagicMock -import io -import secrets -from pathlib import Path - -@pytest.fixture -def mock_sdk_config(): - from sdk.config import SDKConfig - return SDKConfig( - address="mock.akave.ai:5500", - private_key="a5c223e956644f1ba11f0dcc6f3df4992184ff3c919223744d0cf1db33dab4d6", - max_concurrency=5, - block_part_size=128*1024, - use_connection_pool=True, - chunk_buffer=10 - ) - -@pytest.fixture -def mock_grpc_channel(): - mock_channel = Mock() - mock_channel.close = Mock() - return mock_channel - -@pytest.fixture -def mock_ipc_client(): - mock_client = Mock() - mock_client.auth = Mock() - mock_client.auth.address = "0x926c6731DE79ab807e3e7A099f8091e7dD7372D7" - mock_client.auth.key = "a5c223e956644f1ba11f0dcc6f3df4992184ff3c919223744d0cf1db33dab4d6" - - mock_client.storage = Mock() - mock_client.storage.contract_address = "0x0154953F6E583f09D9B38F7C4e7C6265906eC207" - mock_client.storage.get_chain_id = Mock(return_value=21207) - - return mock_client - -@pytest.fixture -def sample_cid(): - return "bafybeigweriqysuigpnsu3jmndgonrihee4dmx27rctlsd5mfn5arrnxyi" - -@pytest.fixture -def sample_file_data(): - return { - "small": b"Hello Akave!", - "medium": b"x" * 1024, # 1KB - "large": b"y" * (1024 * 1024), # 1MB - "binary": bytes(range(256)) - } - -@pytest.fixture -def temp_test_file(tmp_path, sample_file_data): - file_path = tmp_path / "test_upload.bin" - file_path.write_bytes(sample_file_data["medium"]) - return file_path - -@pytest.fixture -def mock_file_reader(sample_file_data): - return io.BytesIO(sample_file_data["medium"]) - -@pytest.fixture -def mock_bucket_info(): - return { - "id": "984a6110b87ca4df9b8b7efa9fcf665fa2a674ad17f3b5b28a1b94848b683e4", - "name": "test-bucket", - "created_at": 1234567890, - "owner": "0x926c6731DE79ab807e3e7A099f8091e7dD7372D7" - } - -@pytest.fixture -def mock_file_meta(): - return { - "root_cid": "bafybeigweriqysuigpnsu3jmndgonrihee4dmx27rctlsd5mfn5arrnxyi", - "name": "test-file.bin", - "bucket_name": "test-bucket", - "size": 1024, - "encoded_size": 1200, - "created_at": 1234567890 - } - -@pytest.fixture -def generate_test_bucket_name(): - def _generate(): - return f"pytest-bucket-{secrets.token_hex(6)}" - return _generate - -@pytest.fixture -def generate_test_file_name(): - def _generate(): - return f"pytest-file-{secrets.token_hex(4)}.bin" - return _generate From 48f36e1bc013b9bd852c3b8ca3a0b29c490a5f27 Mon Sep 17 00:00:00 2001 From: Amit Pandey Date: Mon, 23 Feb 2026 02:38:03 +0530 Subject: [PATCH 2/3] ci: fix black formatting and bandit configuration Signed-off-by: Amit Pandey --- akavesdk/__init__.py | 4 +- akavesdk/akavesdk.py | 5 +- private/__init__.py | 2 +- private/cids/__init__.py | 2 - private/cids/cids.py | 34 +- private/cids/cids_test.py | 54 +- private/eip712/__init__.py | 2 - private/eip712/eip712.py | 98 +- private/eip712/eip712_test.py | 70 +- private/encryption/encryption.py | 13 +- private/httpext/__init__.py | 2 - private/httpext/httpext.py | 6 +- private/ipc/__init__.py | 8 +- private/ipc/batch_client.py | 129 +- private/ipc/batch_client_test.py | 139 +- private/ipc/block_parser.py | 302 +-- private/ipc/block_parser_test.py | 162 +- private/ipc/client.py | 121 +- private/ipc/client_test.py | 67 +- private/ipc/contracts/__init__.py | 40 +- private/ipc/contracts/access_manager.py | 145 +- private/ipc/contracts/akave_token.py | 10 +- private/ipc/contracts/erc1967_proxy.py | 207 +- private/ipc/contracts/errors.py | 33 +- private/ipc/contracts/list_policy.py | 126 +- private/ipc/contracts/pdp_verifier.py | 510 ++--- private/ipc/contracts/sink.py | 61 +- private/ipc/contracts/storage.py | 2557 +++++++---------------- private/ipc/errors.py | 16 +- private/ipc/ipc.py | 32 +- private/ipc/ipc_test.py | 24 +- private/ipc/pdp_test.py | 375 +++- private/ipc/transactiondata_parser.py | 111 +- private/ipctest/__init__.py | 2 +- private/ipctest/ipctest.py | 92 +- private/memory/__init__.py | 2 +- private/memory/memory.py | 4 +- private/pdptest/pdptest.py | 23 +- private/retry/retry.py | 26 +- private/retry/retry_test.py | 20 +- sdk/__init__.py | 204 +- sdk/common.py | 20 +- sdk/config.py | 32 +- sdk/connection.py | 72 +- sdk/dag.py | 327 +-- sdk/model.py | 71 +- sdk/sdk.py | 195 +- sdk/sdk_ipc.py | 896 ++++---- sdk/shared/__init__.py | 3 +- sdk/shared/grpc_base.py | 8 +- tests/__init__.py | 1 - tests/conftest.py | 10 +- tests/e2e/__init__.py | 1 - tests/integration/__init__.py | 1 - tests/mocks/__init__.py | 1 - tests/mocks/mock_ipc.py | 102 +- tests/unit/__init__.py | 1 - tests/unit/test_sdk_ipc.py | 19 +- tests/utils/__init__.py | 1 - tests/utils/test_helpers.py | 24 +- 60 files changed, 3325 insertions(+), 4300 deletions(-) diff --git a/akavesdk/__init__.py b/akavesdk/__init__.py index 9c14e0c..cae9a07 100644 --- a/akavesdk/__init__.py +++ b/akavesdk/__init__.py @@ -12,10 +12,8 @@ from sdk.sdk_ipc import IPC from private.cids import verify_raw, verify, CIDError - # Make SDKError appear under akavesdk in tracebacks SDKError.__module__ = "akavesdk" # Define what gets imported with "from akavesdk import *" -__all__ = ["SDK", "SDKError", "SDKConfig", "IPC", - "BucketCreateResult", "Bucket", "verify_raw", "verify", "CIDError"] \ No newline at end of file +__all__ = ["SDK", "SDKError", "SDKConfig", "IPC", "BucketCreateResult", "Bucket", "verify_raw", "verify", "CIDError"] diff --git a/akavesdk/akavesdk.py b/akavesdk/akavesdk.py index 53af1f8..fc1de39 100644 --- a/akavesdk/akavesdk.py +++ b/akavesdk/akavesdk.py @@ -1,5 +1,6 @@ import sys import os + # Add parent directory to path current_dir = os.path.dirname(os.path.abspath(__file__)) if current_dir not in sys.path: @@ -16,6 +17,6 @@ from sdk.sdk import SDK, BucketCreateResult, Bucket from sdk.config import SDKError from sdk.sdk_ipc import IPC + # Export all classes -__all__ = ["SDK", "SDKError", "IPC", - "BucketCreateResult", "Bucket"] \ No newline at end of file +__all__ = ["SDK", "SDKError", "IPC", "BucketCreateResult", "Bucket"] diff --git a/private/__init__.py b/private/__init__.py index ba99eaa..c8710d1 100644 --- a/private/__init__.py +++ b/private/__init__.py @@ -2,4 +2,4 @@ Private module for Akave SDK internal components. """ -__version__ = "1.0.0" \ No newline at end of file +__version__ = "1.0.0" diff --git a/private/cids/__init__.py b/private/cids/__init__.py index fc4ed4f..bec0f9b 100644 --- a/private/cids/__init__.py +++ b/private/cids/__init__.py @@ -1,5 +1,3 @@ - from .cids import verify_raw, verify, CIDError __all__ = ["verify_raw", "verify", "CIDError"] - diff --git a/private/cids/cids.py b/private/cids/cids.py index 37c9ce0..073b34e 100644 --- a/private/cids/cids.py +++ b/private/cids/cids.py @@ -2,6 +2,7 @@ try: from multiformats import CID, multihash + MULTIFORMATS_AVAILABLE = True except ImportError: MULTIFORMATS_AVAILABLE = False @@ -15,53 +16,50 @@ class CIDError(Exception): def verify_raw(provided_cid: str, data: bytes) -> None: if not MULTIFORMATS_AVAILABLE: raise CIDError("multiformats library is not available") - + try: parsed_cid = CID.decode(provided_cid) except Exception as e: raise CIDError(f"failed to decode provided CID: {e}") - + verify(parsed_cid, data) -def verify(c: 'CID', data: bytes) -> None: +def verify(c: "CID", data: bytes) -> None: if not MULTIFORMATS_AVAILABLE: raise CIDError("multiformats library is not available") - + calculated_cid = _calculate_standard_cid(c, data) - + if calculated_cid != c: - raise CIDError( - f"CID mismatch: provided {str(c)}, calculated {str(calculated_cid)}" - ) + raise CIDError(f"CID mismatch: provided {str(c)}, calculated {str(calculated_cid)}") + +def _calculate_standard_cid(c: "CID", data: bytes) -> "CID": -def _calculate_standard_cid(c: 'CID', data: bytes) -> 'CID': - if not MULTIFORMATS_AVAILABLE: raise CIDError("multiformats library is not available") - + version = c.version codec = c.codec - - if hasattr(c, 'hashfun'): + + if hasattr(c, "hashfun"): # hashfun is a multicodec object, convert to string hash_code = str(c.hashfun).replace("multihash.get('", "").replace("')", "") else: hash_code = "sha2-256" - + try: digest = multihash.digest(data, hash_code) except Exception as e: raise CIDError(f"failed to create multihash: {e}") - + if version == 0: return CID("base58btc", 0, "dag-pb", digest) elif version == 1: - base = "base32" - if hasattr(c, 'base'): + base = "base32" + if hasattr(c, "base"): base = c.base return CID(base, 1, codec, digest) else: raise CIDError(f"unsupported CID version: {version}") - diff --git a/private/cids/cids_test.py b/private/cids/cids_test.py index f894dc1..e7192af 100644 --- a/private/cids/cids_test.py +++ b/private/cids/cids_test.py @@ -9,6 +9,7 @@ try: from multiformats import CID, multihash + MULTIFORMATS_AVAILABLE = True except ImportError: MULTIFORMATS_AVAILABLE = False @@ -22,94 +23,94 @@ def test_verify_raw_valid_cidv0_matches(): test_data = secrets.token_bytes(128) - + v0hash = multihash.digest(test_data, "sha2-256") cidv0 = CID("base58btc", 0, "dag-pb", v0hash) - + verify_raw(str(cidv0), test_data) def test_verify_raw_valid_cidv1_matches(): test_data = secrets.token_bytes(128) - + hash_digest = multihash.digest(test_data, "sha2-256") expected_cid = CID("base32", 1, "dag-pb", hash_digest) - + verify_raw(str(expected_cid), test_data) def test_verify_raw_cid_mismatch(): test_data = secrets.token_bytes(128) - + hash_digest = multihash.digest(test_data, "sha2-256") expected_cid = CID("base32", 1, "dag-pb", hash_digest) - + wrong_data = b"different data" - + with pytest.raises(CIDError) as exc_info: verify_raw(str(expected_cid), wrong_data) - + assert "CID mismatch" in str(exc_info.value) def test_verify_raw_invalid_cid_format(): test_data = secrets.token_bytes(128) - + with pytest.raises(CIDError) as exc_info: verify_raw("invalid-cid", test_data) - + assert "failed to decode provided CID" in str(exc_info.value) def test_verify_raw_empty_data(): empty_data = b"" - + # Calculate CID for empty data hash_digest = multihash.digest(empty_data, "sha2-256") empty_cid = CID("base32", 1, "dag-pb", hash_digest) - + verify_raw(str(empty_cid), empty_data) def test_verify_valid_cidv1_matches(): test_data = secrets.token_bytes(127) - + hash_digest = multihash.digest(test_data, "sha2-256") expected_cid = CID("base32", 1, "dag-pb", hash_digest) - + verify(expected_cid, test_data) def test_verify_valid_cidv0_matches(): test_data = secrets.token_bytes(127) - + hash_digest = multihash.digest(test_data, "sha2-256") cidv0 = CID("base58btc", 0, "dag-pb", hash_digest) - + verify(cidv0, test_data) def test_verify_cid_mismatch(): test_data = secrets.token_bytes(127) - + hash_digest = multihash.digest(test_data, "sha2-256") expected_cid = CID("base32", 1, "dag-pb", hash_digest) - + wrong_data = b"different data" - + with pytest.raises(CIDError) as exc_info: verify(expected_cid, wrong_data) - + assert "CID mismatch" in str(exc_info.value) def test_verify_different_hash_algorithms(): test_data = secrets.token_bytes(64) - + hash_256 = multihash.digest(test_data, "sha2-256") cid_256 = CID("base32", 1, "dag-pb", hash_256) verify(cid_256, test_data) - + hash_512 = multihash.digest(test_data, "sha2-512") cid_512 = CID("base32", 1, "dag-pb", hash_512) verify(cid_512, test_data) @@ -117,20 +118,19 @@ def test_verify_different_hash_algorithms(): def test_verify_different_codecs(): test_data = secrets.token_bytes(64) - + hash_digest = multihash.digest(test_data, "sha2-256") cid_dagpb = CID("base32", 1, "dag-pb", hash_digest) verify(cid_dagpb, test_data) - + cid_raw = CID("base32", 1, "raw", hash_digest) verify(cid_raw, test_data) def test_verify_large_data(): large_data = secrets.token_bytes(1024 * 1024) - + hash_digest = multihash.digest(large_data, "sha2-256") expected_cid = CID("base32", 1, "dag-pb", hash_digest) - - verify(expected_cid, large_data) + verify(expected_cid, large_data) diff --git a/private/eip712/__init__.py b/private/eip712/__init__.py index a332e0f..992e3e9 100644 --- a/private/eip712/__init__.py +++ b/private/eip712/__init__.py @@ -1,5 +1,3 @@ - from .eip712 import Domain, TypedData, sign, recover_signer_address __all__ = ["Domain", "TypedData", "sign", "recover_signer_address"] - diff --git a/private/eip712/eip712.py b/private/eip712/eip712.py index fb2d64c..efcc26d 100644 --- a/private/eip712/eip712.py +++ b/private/eip712/eip712.py @@ -6,9 +6,10 @@ from eth_keys import keys from eth_utils import to_checksum_address -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from sdk.common import SDKError + class TypedData: def __init__(self, name: str, type_name: str): self.name = name @@ -23,8 +24,13 @@ def __init__(self, name: str, version: str, chain_id: int, verifying_contract: s self.verifying_contract = verifying_contract -def sign(private_key_bytes: bytes, domain: Domain, primary_type: str, - data_types: Dict[str, List[TypedData]], data_message: Dict[str, Any]) -> bytes: +def sign( + private_key_bytes: bytes, + domain: Domain, + primary_type: str, + data_types: Dict[str, List[TypedData]], + data_message: Dict[str, Any], +) -> bytes: """Sign EIP-712 data according to the standard - properly hash bytes fields""" try: # Clone data_types and add EIP712Domain @@ -35,15 +41,15 @@ def sign(private_key_bytes: bytes, domain: Domain, primary_type: str, TypedData("chainId", "uint256"), TypedData("verifyingContract", "address"), ] - + typed_data_hash = hash_typed_data(domain, primary_type, data_message, data_types_copy) - + class EncodedMessage: def __init__(self, body): self.body = body - + encoded_message = EncodedMessage(typed_data_hash) - + private_key_obj = keys.PrivateKey(private_key_bytes) signature_obj = private_key_obj.sign_msg_hash(encoded_message.body) signature_bytes = signature_obj.to_bytes() @@ -53,21 +59,21 @@ def __init__(self, body): else: v_out = v return signature_bytes[:64] + bytes([v_out]) - + except Exception as e: raise SDKError(f"EIP-712 signing failed: {str(e)}") def encode_type(primary_type: str, types: Dict[str, List[TypedData]]) -> str: result = primary_type + "(" - + first = True for field in types[primary_type]: if not first: result += "," result += field.type + " " + field.name first = False - + result += ")" return result @@ -79,61 +85,61 @@ def type_hash(primary_type: str, types: Dict[str, List[TypedData]]) -> bytes: return hash_obj.digest() -def hash_typed_data(domain: Domain, primary_type: str, data_message: Dict[str, Any], - data_types: Dict[str, List[TypedData]]) -> bytes: - +def hash_typed_data( + domain: Domain, primary_type: str, data_message: Dict[str, Any], data_types: Dict[str, List[TypedData]] +) -> bytes: + domain_message = { "name": domain.name, "version": domain.version, "chainId": domain.chain_id, "verifyingContract": domain.verifying_contract, } - + domain_hash = encode_data("EIP712Domain", domain_message, data_types) data_hash = encode_data(primary_type, data_message, data_types) raw_data = bytes([0x19, 0x01]) + domain_hash + data_hash - + hash_obj = keccak.new(digest_bits=256) hash_obj.update(raw_data) final_hash = hash_obj.digest() - + return final_hash -def encode_data(primary_type: str, data: Dict[str, Any], - types: Dict[str, List[TypedData]]) -> bytes: - +def encode_data(primary_type: str, data: Dict[str, Any], types: Dict[str, List[TypedData]]) -> bytes: + type_hash_bytes = type_hash(primary_type, types) - + encoded_data = [type_hash_bytes] - + for field in types[primary_type]: value = data[field.name] encoded_value = encode_value(value, field.type) encoded_data.append(encoded_value) - - combined = b''.join(encoded_data) + + combined = b"".join(encoded_data) hash_obj = keccak.new(digest_bits=256) hash_obj.update(combined) return hash_obj.digest() def encode_value(value: Any, type_name: str) -> bytes: - + if type_name == "string": if not isinstance(value, str): raise ValueError(f"expected string, got {type(value)}") hash_obj = keccak.new(digest_bits=256) hash_obj.update(value.encode()) return hash_obj.digest() - + elif type_name == "bytes": if not isinstance(value, (bytes, bytearray)): raise ValueError(f"expected bytes, got {type(value)}") hash_obj = keccak.new(digest_bits=256) hash_obj.update(bytes(value)) return hash_obj.digest() - + elif type_name == "bytes32": if isinstance(value, (bytes, bytearray)): if len(value) != 32: @@ -141,7 +147,7 @@ def encode_value(value: Any, type_name: str) -> bytes: return bytes(value) else: raise ValueError(f"expected bytes32, got {type(value)}") - + elif type_name == "uint8": if not isinstance(value, int): raise ValueError(f"expected int, got {type(value)}") @@ -150,31 +156,31 @@ def encode_value(value: Any, type_name: str) -> bytes: buf = bytearray(32) buf[31] = value return bytes(buf) - + elif type_name == "uint64": if not isinstance(value, int): raise ValueError(f"expected int, got {type(value)}") if not (0 <= value < 2**64): raise ValueError(f"uint64 value out of range: {value}") buf = bytearray(32) - struct.pack_into('>Q', buf, 24, value) + struct.pack_into(">Q", buf, 24, value) return bytes(buf) - + elif type_name == "uint256": if isinstance(value, int): if value < 0: raise ValueError(f"uint256 cannot be negative: {value}") buf = bytearray(32) - value_bytes = value.to_bytes(32, byteorder='big') + value_bytes = value.to_bytes(32, byteorder="big") buf[:] = value_bytes return bytes(buf) else: raise ValueError(f"expected int for uint256, got {type(value)}") - + elif type_name == "address": if isinstance(value, str): addr_str = value.lower() - if addr_str.startswith('0x'): + if addr_str.startswith("0x"): addr_str = addr_str[2:] if len(addr_str) != 40: raise ValueError(f"invalid address length: {len(addr_str)}") @@ -185,18 +191,23 @@ def encode_value(value: Any, type_name: str) -> bytes: addr_bytes = bytes(value) else: raise ValueError(f"expected string or bytes for address, got {type(value)}") - + buf = bytearray(32) buf[12:32] = addr_bytes return bytes(buf) - + else: raise ValueError(f"unsupported type: {type_name}") -def recover_signer_address(signature: bytes, domain: Domain, primary_type: str, - data_types: Dict[str, List[TypedData]], data_message: Dict[str, Any]) -> str: - +def recover_signer_address( + signature: bytes, + domain: Domain, + primary_type: str, + data_types: Dict[str, List[TypedData]], + data_message: Dict[str, Any], +) -> str: + # Clone data_types and add EIP712Domain data_types_copy = data_types.copy() data_types_copy["EIP712Domain"] = [ @@ -205,16 +216,17 @@ def recover_signer_address(signature: bytes, domain: Domain, primary_type: str, TypedData("chainId", "uint256"), TypedData("verifyingContract", "address"), ] - + hash_bytes = hash_typed_data(domain, primary_type, data_message, data_types_copy) - + sig_copy = bytearray(signature) if sig_copy[64] >= 27: sig_copy[64] -= 27 - + from eth_keys import keys + signature_obj = keys.Signature(bytes(sig_copy)) public_key = signature_obj.recover_public_key_from_msg_hash(hash_bytes) - + address = public_key.to_checksum_address() - return address + return address diff --git a/private/eip712/eip712_test.py b/private/eip712/eip712_test.py index f09a502..141d67a 100644 --- a/private/eip712/eip712_test.py +++ b/private/eip712/eip712_test.py @@ -6,13 +6,13 @@ import pytest from typing import Dict, List, Any -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from private.eip712.eip712 import sign, recover_signer_address, Domain, TypedData class TestSignatureAgainstContract: - + test_cases = [ { "chunkCID": "86b258127d599eb74c729f97", @@ -43,12 +43,12 @@ class TestSignatureAgainstContract: "bucketID": "a928e74732b6ca5fd1bf7f3eedfdca3c578a05297157e239e7f7861de2b40f42", "storageAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "signature": "8ccd5143f4b87e898021c4b3a4bf73e3e8d6e8b97e39106374fac72be610629463a0ba6fc4c975c41fbb1ad3940f76a30e6cb916a8e01d09afbe24538ce151ca1b", - } + }, ] - + @pytest.mark.parametrize("tc", test_cases) def test_signature_against_contract(self, tc): - + data_types = { "StorageData": [ TypedData("chunkCID", "bytes"), @@ -61,19 +61,14 @@ def test_signature_against_contract(self, tc): TypedData("bucketId", "bytes32"), ] } - - domain = Domain( - name="Storage", - version="1", - chain_id=31337, - verifying_contract=tc["storageAddress"] - ) - + + domain = Domain(name="Storage", version="1", chain_id=31337, verifying_contract=tc["storageAddress"]) + chunk_cid = bytes.fromhex(tc["chunkCID"]) block_cid = bytes.fromhex(tc["blockCID"]) node_id = bytes.fromhex(tc["nodeID"]) bucket_id = bytes.fromhex(tc["bucketID"]) - + data_message = { "chunkCID": chunk_cid, "blockCID": block_cid, @@ -84,34 +79,34 @@ def test_signature_against_contract(self, tc): "deadline": tc["deadline"], "bucketId": bucket_id, } - + private_key_hex = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" private_key_bytes = bytes.fromhex(private_key_hex) - + signature = sign(private_key_bytes, domain, "StorageData", data_types, data_message) - - assert signature.hex() == tc["signature"], \ - f"Signature mismatch for test case with nonce {tc['nonce']}" + + assert signature.hex() == tc["signature"], f"Signature mismatch for test case with nonce {tc['nonce']}" def test_signature_recovery(): - + private_key_hex = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" private_key_bytes = bytes.fromhex(private_key_hex) - + from eth_keys import keys + private_key_obj = keys.PrivateKey(private_key_bytes) expected_address = private_key_obj.public_key.to_checksum_address() - + block_cid = bytearray(32) block_cid[:9] = b"blockCID1" - + node_id = bytearray(32) node_id[:7] = b"node id" - + bucket_id = bytearray(32) bucket_id[:9] = b"bucket id" - + data_types = { "StorageData": [ TypedData("chunkCID", "bytes"), @@ -124,14 +119,11 @@ def test_signature_recovery(): TypedData("bucketId", "bytes32"), ] } - + domain = Domain( - name="Storage", - version="1", - chain_id=31337, - verifying_contract="0x1234567890123456789012345678901234567890" + name="Storage", version="1", chain_id=31337, verifying_contract="0x1234567890123456789012345678901234567890" ) - + data_message = { "chunkCID": b"rootCID1", "blockCID": bytes(block_cid), @@ -142,17 +134,15 @@ def test_signature_recovery(): "deadline": 12345, "bucketId": bytes(bucket_id), } - + signature = sign(private_key_bytes, domain, "StorageData", data_types, data_message) - - recovered_address = recover_signer_address( - signature, domain, "StorageData", data_types, data_message - ) - - assert recovered_address.lower() == expected_address.lower(), \ - f"Address mismatch: expected {expected_address}, got {recovered_address}" + + recovered_address = recover_signer_address(signature, domain, "StorageData", data_types, data_message) + + assert ( + recovered_address.lower() == expected_address.lower() + ), f"Address mismatch: expected {expected_address}, got {recovered_address}" if __name__ == "__main__": pytest.main([__file__, "-v"]) - diff --git a/private/encryption/encryption.py b/private/encryption/encryption.py index 272f444..f635c16 100644 --- a/private/encryption/encryption.py +++ b/private/encryption/encryption.py @@ -41,22 +41,23 @@ def encrypt(key: bytes, data: bytes, info: bytes) -> bytes: cipher, nonce = make_gcm_cipher(key, info) encryptor = cipher.encryptor() ciphertext = encryptor.update(data) + encryptor.finalize() - tag = encryptor.tag # type: ignore[union-attr] + tag = encryptor.tag # type: ignore[union-attr] encrypted_data = nonce + ciphertext + tag return cast(bytes, encrypted_data) + def decrypt(key: bytes, encrypted_data: bytes, info: bytes) -> bytes: - nonce_size = 12 - tag_size = 16 + nonce_size = 12 + tag_size = 16 if len(encrypted_data) < nonce_size + tag_size: raise ValueError("Invalid encrypted data: insufficient length") - + nonce = encrypted_data[:nonce_size] ciphertext = encrypted_data[nonce_size:-tag_size] tag = encrypted_data[-tag_size:] - + derived_key = derive_key(key, info) cipher = Cipher(algorithms.AES(derived_key), modes.GCM(nonce, tag), backend=default_backend()) decryptor = cipher.decryptor() decrypted_data = decryptor.update(ciphertext) + decryptor.finalize() - return cast(bytes, decrypted_data) \ No newline at end of file + return cast(bytes, decrypted_data) diff --git a/private/httpext/__init__.py b/private/httpext/__init__.py index 03f1cae..c91bb3b 100644 --- a/private/httpext/__init__.py +++ b/private/httpext/__init__.py @@ -4,5 +4,3 @@ from .httpext import range_download __all__ = ["range_download"] - - diff --git a/private/httpext/httpext.py b/private/httpext/httpext.py index 60c7432..3e10222 100644 --- a/private/httpext/httpext.py +++ b/private/httpext/httpext.py @@ -38,9 +38,7 @@ def range_download( body = b"" body_text = body.decode(errors="replace") - raise Exception( - f"download failed with status {response.status_code}: {body_text}" - ) + raise Exception(f"download failed with status {response.status_code}: {body_text}") try: data = response.content @@ -53,5 +51,3 @@ def range_download( response.close() except Exception as close_exc: # pragma: no cover - defensive logging.debug("error closing HTTP response: %s", close_exc) - - diff --git a/private/ipc/__init__.py b/private/ipc/__init__.py index 42107d5..305fe13 100644 --- a/private/ipc/__init__.py +++ b/private/ipc/__init__.py @@ -1,10 +1,4 @@ from .client import Client, Config, TransactionFailedError from .errors import error_hash_to_error, parse_errors_to_hashes -__all__ = [ - 'Client', - 'Config', - 'TransactionFailedError', - 'error_hash_to_error', - 'parse_errors_to_hashes' -] +__all__ = ["Client", "Config", "TransactionFailedError", "error_hash_to_error", "parse_errors_to_hashes"] diff --git a/private/ipc/batch_client.py b/private/ipc/batch_client.py index 5ffdb9a..7e3ba26 100644 --- a/private/ipc/batch_client.py +++ b/private/ipc/batch_client.py @@ -44,147 +44,98 @@ def __init__(self, web3: Web3): self.web3 = web3 def get_transaction_receipts_batch( - self, - requests: List[BatchReceiptRequest], - timeout: float = 30.0 + self, requests: List[BatchReceiptRequest], timeout: float = 30.0 ) -> BatchReceiptResult: # Prepare batch requests batch_requests = [] for req in requests: tx_hash = req.hash - if not tx_hash.startswith('0x'): - tx_hash = '0x' + tx_hash - batch_requests.append(('eth_getTransactionReceipt', [tx_hash])) - + if not tx_hash.startswith("0x"): + tx_hash = "0x" + tx_hash + batch_requests.append(("eth_getTransactionReceipt", [tx_hash])) + try: raw_responses = self.web3.manager.request_blocking_batch(batch_requests) - + responses = [] for i, (req, raw_response) in enumerate(zip(requests, raw_responses)): - if 'error' in raw_response: - error_msg = raw_response['error'].get('message', 'Unknown error') - response = BatchReceiptResponse( - receipt=None, - error=Exception(error_msg), - key=req.key - ) - elif raw_response.get('result') is None: + if "error" in raw_response: + error_msg = raw_response["error"].get("message", "Unknown error") + response = BatchReceiptResponse(receipt=None, error=Exception(error_msg), key=req.key) + elif raw_response.get("result") is None: response = BatchReceiptResponse( - receipt=None, - error=TransactionNotFound(f"Transaction {req.hash} not found"), - key=req.key + receipt=None, error=TransactionNotFound(f"Transaction {req.hash} not found"), key=req.key ) else: - response = BatchReceiptResponse( - receipt=raw_response['result'], - error=None, - key=req.key - ) + response = BatchReceiptResponse(receipt=raw_response["result"], error=None, key=req.key) responses.append(response) - + return BatchReceiptResult(responses=responses) - + except Exception as e: # Fallback: try individual requests responses = [] for req in requests: try: tx_hash = req.hash - if not tx_hash.startswith('0x'): - tx_hash = '0x' + tx_hash + if not tx_hash.startswith("0x"): + tx_hash = "0x" + tx_hash receipt = self.web3.eth.get_transaction_receipt(tx_hash) - response = BatchReceiptResponse( - receipt=dict(receipt), - error=None, - key=req.key - ) + response = BatchReceiptResponse(receipt=dict(receipt), error=None, key=req.key) except TransactionNotFound: response = BatchReceiptResponse( - receipt=None, - error=TransactionNotFound(f"Transaction {req.hash} not found"), - key=req.key + receipt=None, error=TransactionNotFound(f"Transaction {req.hash} not found"), key=req.key ) except Exception as err: - response = BatchReceiptResponse( - receipt=None, - error=err, - key=req.key - ) + response = BatchReceiptResponse(receipt=None, error=err, key=req.key) responses.append(response) - + return BatchReceiptResult(responses=responses) - def get_blocks_batch( - self, - block_numbers: List[int] - ) -> List[BatchBlockResponse]: + def get_blocks_batch(self, block_numbers: List[int]) -> List[BatchBlockResponse]: batch_requests = [] for block_number in block_numbers: - block_hex = hex(block_number) if block_number >= 0 else 'latest' - batch_requests.append(('eth_getBlockByNumber', [block_hex, True])) - + block_hex = hex(block_number) if block_number >= 0 else "latest" + batch_requests.append(("eth_getBlockByNumber", [block_hex, True])) + try: raw_responses = self.web3.manager.request_blocking_batch(batch_requests) - + responses = [] for block_number, raw_response in zip(block_numbers, raw_responses): - if 'error' in raw_response: - error_msg = raw_response['error'].get('message', 'Unknown error') - response = BatchBlockResponse( - block_number=block_number, - block=None, - error=Exception(error_msg) - ) - elif raw_response.get('result') is None: + if "error" in raw_response: + error_msg = raw_response["error"].get("message", "Unknown error") + response = BatchBlockResponse(block_number=block_number, block=None, error=Exception(error_msg)) + elif raw_response.get("result") is None: # Block not found response = BatchBlockResponse( - block_number=block_number, - block=None, - error=BlockNotFound(f"Block {block_number} not found") + block_number=block_number, block=None, error=BlockNotFound(f"Block {block_number} not found") ) else: try: - block_json = json.dumps(raw_response['result']).encode('utf-8') + block_json = json.dumps(raw_response["result"]).encode("utf-8") block_data = block_from_json(block_json) - response = BatchBlockResponse( - block_number=block_number, - block=block_data, - error=None - ) + response = BatchBlockResponse(block_number=block_number, block=block_data, error=None) except Exception as e: - response = BatchBlockResponse( - block_number=block_number, - block=None, - error=e - ) + response = BatchBlockResponse(block_number=block_number, block=None, error=e) responses.append(response) - + return responses - + except Exception as e: responses = [] for block_number in block_numbers: try: block = self.web3.eth.get_block(block_number, full_transactions=True) - block_json = json.dumps(dict(block)).encode('utf-8') + block_json = json.dumps(dict(block)).encode("utf-8") block_data = block_from_json(block_json) - response = BatchBlockResponse( - block_number=block_number, - block=block_data, - error=None - ) + response = BatchBlockResponse(block_number=block_number, block=block_data, error=None) except BlockNotFound: response = BatchBlockResponse( - block_number=block_number, - block=None, - error=BlockNotFound(f"Block {block_number} not found") + block_number=block_number, block=None, error=BlockNotFound(f"Block {block_number} not found") ) except Exception as err: - response = BatchBlockResponse( - block_number=block_number, - block=None, - error=err - ) + response = BatchBlockResponse(block_number=block_number, block=None, error=err) responses.append(response) - + return responses diff --git a/private/ipc/batch_client_test.py b/private/ipc/batch_client_test.py index ab58f3d..20f3a63 100644 --- a/private/ipc/batch_client_test.py +++ b/private/ipc/batch_client_test.py @@ -12,7 +12,6 @@ from .client import Client, Config from ..ipctest.ipctest import new_funded_account, to_wei - DIAL_URI = os.getenv("DIAL_URI", "") PRIVATE_KEY = os.getenv("PRIVATE_KEY", "") @@ -32,59 +31,53 @@ def pick_private_key() -> str: def test_get_transaction_receipts_batch(): dial_uri = pick_dial_uri() private_key = pick_private_key() - + pk = new_funded_account(private_key, dial_uri, to_wei(10)) pk_hex = pk.key.hex() - + # Deploy contracts - client = Client.deploy_contracts(Config( - dial_uri=dial_uri, - private_key=pk_hex - )) - + client = Client.deploy_contracts(Config(dial_uri=dial_uri, private_key=pk_hex)) + batch_client = BatchClient(client.eth) - + num_txs = 55 requests = [] - + from_address = pk.address - nonce = client.eth.eth.get_transaction_count(from_address, 'pending') + nonce = client.eth.eth.get_transaction_count(from_address, "pending") gas_price = client.eth.eth.gas_price chain_id = client.eth.eth.chain_id - + for i in range(num_txs): to_address = "0x000000000000000000000000000000000000dead" - + tx = { - 'chainId': chain_id, - 'nonce': nonce + i, - 'to': to_address, - 'value': 0, - 'gas': 21000, - 'gasPrice': gas_price, + "chainId": chain_id, + "nonce": nonce + i, + "to": to_address, + "value": 0, + "gas": 21000, + "gasPrice": gas_price, } - + signed_tx = pk.sign_transaction(tx) tx_hash = client.eth.eth.send_raw_transaction(signed_tx.rawTransaction) - - requests.append(BatchReceiptRequest( - hash=tx_hash.hex(), - key=f"tx-{i}" - )) - + + requests.append(BatchReceiptRequest(hash=tx_hash.hex(), key=f"tx-{i}")) + result = batch_client.get_transaction_receipts_batch(requests, timeout=10.0) - + assert result is not None assert len(result.responses) == len(requests) - + receipts_found = 0 receipts_not_found = 0 individual_errors = 0 - + for i, response in enumerate(result.responses): expected_key = f"tx-{i}" assert response.key == expected_key - + if response.error is not None: individual_errors += 1 print(f"Transaction {requests[i].hash} has error: {response.error}") @@ -93,88 +86,84 @@ def test_get_transaction_receipts_batch(): print(f"Transaction {requests[i].hash} not yet mined (receipt is None)") else: receipts_found += 1 - tx_hash = response.receipt['transactionHash'] + tx_hash = response.receipt["transactionHash"] if isinstance(tx_hash, bytes): tx_hash = tx_hash.hex() - if not tx_hash.startswith('0x'): - tx_hash = '0x' + tx_hash - + if not tx_hash.startswith("0x"): + tx_hash = "0x" + tx_hash + req_hash = requests[i].hash - if not req_hash.startswith('0x'): - req_hash = '0x' + req_hash - + if not req_hash.startswith("0x"): + req_hash = "0x" + req_hash + assert tx_hash == req_hash - assert response.receipt['status'] in (0, 1) - - block_number = response.receipt['blockNumber'] + assert response.receipt["status"] in (0, 1) + + block_number = response.receipt["blockNumber"] if isinstance(block_number, str): block_number = int(block_number, 16) assert block_number > 0 - - print(f"Transaction {tx_hash} mined in block {block_number} " - f"with status {response.receipt['status']}") - + + print(f"Transaction {tx_hash} mined in block {block_number} " f"with status {response.receipt['status']}") + assert len(requests) == receipts_found + receipts_not_found + individual_errors def test_get_blocks_batch(): dial_uri = pick_dial_uri() private_key = pick_private_key() - + pk = new_funded_account(private_key, dial_uri, to_wei(10)) pk_hex = pk.key.hex() - - client = Client.deploy_contracts(Config( - dial_uri=dial_uri, - private_key=pk_hex - )) - + + client = Client.deploy_contracts(Config(dial_uri=dial_uri, private_key=pk_hex)) + batch_client = BatchClient(client.eth) - + from_address = pk.address - nonce = client.eth.eth.get_transaction_count(from_address, 'pending') + nonce = client.eth.eth.get_transaction_count(from_address, "pending") gas_price = client.eth.eth.gas_price chain_id = client.eth.eth.chain_id - + num_txs = 5 tx_hashes = [] - + for i in range(num_txs): to_address = "0x0000000000000000000000000000000000000000" - + tx = { - 'chainId': chain_id, - 'nonce': nonce + i, - 'to': to_address, - 'value': 0, - 'gas': 21000, - 'gasPrice': gas_price, + "chainId": chain_id, + "nonce": nonce + i, + "to": to_address, + "value": 0, + "gas": 21000, + "gasPrice": gas_price, } - + signed_tx = pk.sign_transaction(tx) tx_hash = client.eth.eth.send_raw_transaction(signed_tx.rawTransaction) tx_hashes.append(tx_hash) - + client.wait_for_tx(tx_hash) - + block_numbers = [] for tx_hash in tx_hashes: receipt = client.eth.eth.get_transaction_receipt(tx_hash) - block_num = receipt['blockNumber'] + block_num = receipt["blockNumber"] if isinstance(block_num, str): block_num = int(block_num, 16) block_numbers.append(block_num) - + non_existent_block = 99999999 block_numbers.append(non_existent_block) - + responses = batch_client.get_blocks_batch(block_numbers) - + assert len(responses) == len(block_numbers) - + found_blocks = 0 not_found_blocks = 0 - + for i, resp in enumerate(responses): if resp.error is not None: assert isinstance(resp.error, BlockNotFound) @@ -182,14 +171,14 @@ def test_get_blocks_batch(): not_found_blocks += 1 else: assert resp.block is not None - - block_num = resp.block.get('number') + + block_num = resp.block.get("number") if isinstance(block_num, str): block_num = int(block_num, 16) - + assert block_num == block_numbers[i] found_blocks += 1 - + assert found_blocks >= 1 assert not_found_blocks == 1 diff --git a/private/ipc/block_parser.py b/private/ipc/block_parser.py index 1dd5519..ec466c8 100644 --- a/private/ipc/block_parser.py +++ b/private/ipc/block_parser.py @@ -13,171 +13,171 @@ def block_from_json(raw_json: bytes) -> Dict[str, Any]: data = json.loads(raw_json) except json.JSONDecodeError as e: raise ValueError(f"Invalid JSON: {e}") - + if data is None: raise BlockNotFound("Block not found") - + block = {} - - if 'hash' in data: - block['hash'] = data['hash'] - if 'number' in data: - if isinstance(data['number'], str): - block['number'] = int(data['number'], 16) - else: - block['number'] = data['number'] - if 'parentHash' in data: - block['parentHash'] = data['parentHash'] - if 'nonce' in data: - block['nonce'] = data['nonce'] - if 'sha3Uncles' in data: - block['sha3Uncles'] = data['sha3Uncles'] - if 'logsBloom' in data: - block['logsBloom'] = data['logsBloom'] - if 'transactionsRoot' in data: - block['transactionsRoot'] = data['transactionsRoot'] - if 'stateRoot' in data: - block['stateRoot'] = data['stateRoot'] - if 'receiptsRoot' in data: - block['receiptsRoot'] = data['receiptsRoot'] - if 'miner' in data: - block['miner'] = data['miner'] - if 'difficulty' in data: - if isinstance(data['difficulty'], str): - block['difficulty'] = int(data['difficulty'], 16) - else: - block['difficulty'] = data['difficulty'] - if 'totalDifficulty' in data: - if isinstance(data['totalDifficulty'], str): - block['totalDifficulty'] = int(data['totalDifficulty'], 16) - else: - block['totalDifficulty'] = data['totalDifficulty'] - if 'extraData' in data: - block['extraData'] = data['extraData'] - if 'size' in data: - if isinstance(data['size'], str): - block['size'] = int(data['size'], 16) - else: - block['size'] = data['size'] - if 'gasLimit' in data: - if isinstance(data['gasLimit'], str): - block['gasLimit'] = int(data['gasLimit'], 16) - else: - block['gasLimit'] = data['gasLimit'] - if 'gasUsed' in data: - if isinstance(data['gasUsed'], str): - block['gasUsed'] = int(data['gasUsed'], 16) - else: - block['gasUsed'] = data['gasUsed'] - if 'timestamp' in data: - if isinstance(data['timestamp'], str): - block['timestamp'] = int(data['timestamp'], 16) - else: - block['timestamp'] = data['timestamp'] - if 'baseFeePerGas' in data: - if isinstance(data['baseFeePerGas'], str): - block['baseFeePerGas'] = int(data['baseFeePerGas'], 16) - else: - block['baseFeePerGas'] = data['baseFeePerGas'] - if 'mixHash' in data: - block['mixHash'] = data['mixHash'] - + + if "hash" in data: + block["hash"] = data["hash"] + if "number" in data: + if isinstance(data["number"], str): + block["number"] = int(data["number"], 16) + else: + block["number"] = data["number"] + if "parentHash" in data: + block["parentHash"] = data["parentHash"] + if "nonce" in data: + block["nonce"] = data["nonce"] + if "sha3Uncles" in data: + block["sha3Uncles"] = data["sha3Uncles"] + if "logsBloom" in data: + block["logsBloom"] = data["logsBloom"] + if "transactionsRoot" in data: + block["transactionsRoot"] = data["transactionsRoot"] + if "stateRoot" in data: + block["stateRoot"] = data["stateRoot"] + if "receiptsRoot" in data: + block["receiptsRoot"] = data["receiptsRoot"] + if "miner" in data: + block["miner"] = data["miner"] + if "difficulty" in data: + if isinstance(data["difficulty"], str): + block["difficulty"] = int(data["difficulty"], 16) + else: + block["difficulty"] = data["difficulty"] + if "totalDifficulty" in data: + if isinstance(data["totalDifficulty"], str): + block["totalDifficulty"] = int(data["totalDifficulty"], 16) + else: + block["totalDifficulty"] = data["totalDifficulty"] + if "extraData" in data: + block["extraData"] = data["extraData"] + if "size" in data: + if isinstance(data["size"], str): + block["size"] = int(data["size"], 16) + else: + block["size"] = data["size"] + if "gasLimit" in data: + if isinstance(data["gasLimit"], str): + block["gasLimit"] = int(data["gasLimit"], 16) + else: + block["gasLimit"] = data["gasLimit"] + if "gasUsed" in data: + if isinstance(data["gasUsed"], str): + block["gasUsed"] = int(data["gasUsed"], 16) + else: + block["gasUsed"] = data["gasUsed"] + if "timestamp" in data: + if isinstance(data["timestamp"], str): + block["timestamp"] = int(data["timestamp"], 16) + else: + block["timestamp"] = data["timestamp"] + if "baseFeePerGas" in data: + if isinstance(data["baseFeePerGas"], str): + block["baseFeePerGas"] = int(data["baseFeePerGas"], 16) + else: + block["baseFeePerGas"] = data["baseFeePerGas"] + if "mixHash" in data: + block["mixHash"] = data["mixHash"] + transactions = [] - if 'transactions' in data: - for tx_data in data['transactions']: + if "transactions" in data: + for tx_data in data["transactions"]: if isinstance(tx_data, dict): tx = _parse_transaction(tx_data) transactions.append(tx) else: transactions.append(tx_data) - block['transactions'] = transactions - + block["transactions"] = transactions + uncles = [] - if 'uncles' in data: - uncles = data['uncles'] - block['uncles'] = uncles - + if "uncles" in data: + uncles = data["uncles"] + block["uncles"] = uncles + withdrawals = [] - if 'withdrawals' in data: - withdrawals = data['withdrawals'] - block['withdrawals'] = withdrawals - + if "withdrawals" in data: + withdrawals = data["withdrawals"] + block["withdrawals"] = withdrawals + return block def _parse_transaction(tx_data: Dict[str, Any]) -> Dict[str, Any]: tx = {} - - if 'hash' in tx_data: - tx['hash'] = tx_data['hash'] - if 'nonce' in tx_data: - if isinstance(tx_data['nonce'], str): - tx['nonce'] = int(tx_data['nonce'], 16) - else: - tx['nonce'] = tx_data['nonce'] - if 'blockHash' in tx_data: - tx['blockHash'] = tx_data['blockHash'] - if 'blockNumber' in tx_data: - if isinstance(tx_data['blockNumber'], str): - tx['blockNumber'] = int(tx_data['blockNumber'], 16) - else: - tx['blockNumber'] = tx_data['blockNumber'] - if 'transactionIndex' in tx_data: - if isinstance(tx_data['transactionIndex'], str): - tx['transactionIndex'] = int(tx_data['transactionIndex'], 16) - else: - tx['transactionIndex'] = tx_data['transactionIndex'] - if 'from' in tx_data: - tx['from'] = tx_data['from'] - if 'to' in tx_data: - tx['to'] = tx_data['to'] - if 'value' in tx_data: - if isinstance(tx_data['value'], str): - tx['value'] = int(tx_data['value'], 16) - else: - tx['value'] = tx_data['value'] - if 'gas' in tx_data: - if isinstance(tx_data['gas'], str): - tx['gas'] = int(tx_data['gas'], 16) - else: - tx['gas'] = tx_data['gas'] - if 'gasPrice' in tx_data: - if isinstance(tx_data['gasPrice'], str): - tx['gasPrice'] = int(tx_data['gasPrice'], 16) - else: - tx['gasPrice'] = tx_data['gasPrice'] - if 'input' in tx_data: - tx['input'] = tx_data['input'] - if 'v' in tx_data: - if isinstance(tx_data['v'], str): - tx['v'] = int(tx_data['v'], 16) - else: - tx['v'] = tx_data['v'] - if 'r' in tx_data: - tx['r'] = tx_data['r'] - if 's' in tx_data: - tx['s'] = tx_data['s'] - if 'type' in tx_data: - if isinstance(tx_data['type'], str): - tx['type'] = int(tx_data['type'], 16) - else: - tx['type'] = tx_data['type'] - if 'chainId' in tx_data: - if isinstance(tx_data['chainId'], str): - tx['chainId'] = int(tx_data['chainId'], 16) - else: - tx['chainId'] = tx_data['chainId'] - if 'accessList' in tx_data: - tx['accessList'] = tx_data['accessList'] - if 'maxPriorityFeePerGas' in tx_data: - if isinstance(tx_data['maxPriorityFeePerGas'], str): - tx['maxPriorityFeePerGas'] = int(tx_data['maxPriorityFeePerGas'], 16) - else: - tx['maxPriorityFeePerGas'] = tx_data['maxPriorityFeePerGas'] - if 'maxFeePerGas' in tx_data: - if isinstance(tx_data['maxFeePerGas'], str): - tx['maxFeePerGas'] = int(tx_data['maxFeePerGas'], 16) - else: - tx['maxFeePerGas'] = tx_data['maxFeePerGas'] - + + if "hash" in tx_data: + tx["hash"] = tx_data["hash"] + if "nonce" in tx_data: + if isinstance(tx_data["nonce"], str): + tx["nonce"] = int(tx_data["nonce"], 16) + else: + tx["nonce"] = tx_data["nonce"] + if "blockHash" in tx_data: + tx["blockHash"] = tx_data["blockHash"] + if "blockNumber" in tx_data: + if isinstance(tx_data["blockNumber"], str): + tx["blockNumber"] = int(tx_data["blockNumber"], 16) + else: + tx["blockNumber"] = tx_data["blockNumber"] + if "transactionIndex" in tx_data: + if isinstance(tx_data["transactionIndex"], str): + tx["transactionIndex"] = int(tx_data["transactionIndex"], 16) + else: + tx["transactionIndex"] = tx_data["transactionIndex"] + if "from" in tx_data: + tx["from"] = tx_data["from"] + if "to" in tx_data: + tx["to"] = tx_data["to"] + if "value" in tx_data: + if isinstance(tx_data["value"], str): + tx["value"] = int(tx_data["value"], 16) + else: + tx["value"] = tx_data["value"] + if "gas" in tx_data: + if isinstance(tx_data["gas"], str): + tx["gas"] = int(tx_data["gas"], 16) + else: + tx["gas"] = tx_data["gas"] + if "gasPrice" in tx_data: + if isinstance(tx_data["gasPrice"], str): + tx["gasPrice"] = int(tx_data["gasPrice"], 16) + else: + tx["gasPrice"] = tx_data["gasPrice"] + if "input" in tx_data: + tx["input"] = tx_data["input"] + if "v" in tx_data: + if isinstance(tx_data["v"], str): + tx["v"] = int(tx_data["v"], 16) + else: + tx["v"] = tx_data["v"] + if "r" in tx_data: + tx["r"] = tx_data["r"] + if "s" in tx_data: + tx["s"] = tx_data["s"] + if "type" in tx_data: + if isinstance(tx_data["type"], str): + tx["type"] = int(tx_data["type"], 16) + else: + tx["type"] = tx_data["type"] + if "chainId" in tx_data: + if isinstance(tx_data["chainId"], str): + tx["chainId"] = int(tx_data["chainId"], 16) + else: + tx["chainId"] = tx_data["chainId"] + if "accessList" in tx_data: + tx["accessList"] = tx_data["accessList"] + if "maxPriorityFeePerGas" in tx_data: + if isinstance(tx_data["maxPriorityFeePerGas"], str): + tx["maxPriorityFeePerGas"] = int(tx_data["maxPriorityFeePerGas"], 16) + else: + tx["maxPriorityFeePerGas"] = tx_data["maxPriorityFeePerGas"] + if "maxFeePerGas" in tx_data: + if isinstance(tx_data["maxFeePerGas"], str): + tx["maxFeePerGas"] = int(tx_data["maxFeePerGas"], 16) + else: + tx["maxFeePerGas"] = tx_data["maxFeePerGas"] + return tx diff --git a/private/ipc/block_parser_test.py b/private/ipc/block_parser_test.py index 36f6dcd..fe63e04 100644 --- a/private/ipc/block_parser_test.py +++ b/private/ipc/block_parser_test.py @@ -12,111 +12,111 @@ def test_parse_block_from_json_valid_block(): account = Account.create() - + tx = { - 'nonce': 0, - 'to': '0x1234567890123456789012345678901234567891', - 'value': 1000, - 'gas': 21000, - 'gasPrice': 1000000000, - 'data': '0x', - 'chainId': 1 + "nonce": 0, + "to": "0x1234567890123456789012345678901234567891", + "value": 1000, + "gas": 21000, + "gasPrice": 1000000000, + "data": "0x", + "chainId": 1, } - + signed_tx = account.sign_transaction(tx) - + tx_data = { - 'hash': signed_tx.hash.hex(), - 'nonce': '0x0', - 'from': account.address, - 'to': '0x1234567890123456789012345678901234567891', - 'value': '0x3e8', - 'gas': '0x5208', - 'gasPrice': '0x3b9aca00', - 'input': '0x', - 'v': hex(signed_tx.v), - 'r': hex(signed_tx.r), - 's': hex(signed_tx.s), - 'type': '0x0', - 'chainId': '0x1' + "hash": signed_tx.hash.hex(), + "nonce": "0x0", + "from": account.address, + "to": "0x1234567890123456789012345678901234567891", + "value": "0x3e8", + "gas": "0x5208", + "gasPrice": "0x3b9aca00", + "input": "0x", + "v": hex(signed_tx.v), + "r": hex(signed_tx.r), + "s": hex(signed_tx.s), + "type": "0x0", + "chainId": "0x1", } - - uncle_hash = '0xdef7890123456789012345678901234567890123456789012345678901234567' - + + uncle_hash = "0xdef7890123456789012345678901234567890123456789012345678901234567" + block_data = { - 'number': '0x1', - 'hash': '0xabc1230000000000000000000000000000000000000000000000000000000001', - 'parentHash': '0x0000000000000000000000000000000000000000000000000000000000000000', - 'nonce': '0x0000000000000000', - 'sha3Uncles': '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', - 'logsBloom': '0x' + '00' * 256, - 'transactionsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', - 'stateRoot': '0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544', - 'receiptsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', - 'miner': '0x0000000000000000000000000000000000000000', - 'difficulty': '0x0', - 'totalDifficulty': '0x0', - 'extraData': '0x', - 'size': '0x219', - 'gasLimit': '0x1c9c380', - 'gasUsed': '0x5208', - 'timestamp': '0x61bc6b5d', - 'transactions': [tx_data], - 'uncles': [uncle_hash] + "number": "0x1", + "hash": "0xabc1230000000000000000000000000000000000000000000000000000000001", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x" + "00" * 256, + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "miner": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0", + "totalDifficulty": "0x0", + "extraData": "0x", + "size": "0x219", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x61bc6b5d", + "transactions": [tx_data], + "uncles": [uncle_hash], } - - block_json = json.dumps(block_data).encode('utf-8') - + + block_json = json.dumps(block_data).encode("utf-8") + parsed_block = block_from_json(block_json) - + assert parsed_block is not None - assert len(parsed_block['transactions']) == 1 - assert parsed_block['transactions'][0]['hash'] == signed_tx.hash.hex() - assert len(parsed_block['uncles']) == 1 - assert parsed_block['uncles'][0] == uncle_hash + assert len(parsed_block["transactions"]) == 1 + assert parsed_block["transactions"][0]["hash"] == signed_tx.hash.hex() + assert len(parsed_block["uncles"]) == 1 + assert parsed_block["uncles"][0] == uncle_hash def test_parse_block_from_json_null_block(): with pytest.raises(BlockNotFound): - block_from_json(b'null') + block_from_json(b"null") def test_parse_block_from_json_invalid_json(): with pytest.raises(ValueError): - block_from_json(b'{invalid json') + block_from_json(b"{invalid json") def test_parse_block_from_json_empty_transactions_and_uncles(): block_data = { - 'number': '0x2', - 'hash': '0xabc1230000000000000000000000000000000000000000000000000000000002', - 'parentHash': '0x0000000000000000000000000000000000000000000000000000000000000000', - 'nonce': '0x0000000000000000', - 'sha3Uncles': '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', - 'logsBloom': '0x' + '00' * 256, - 'transactionsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', - 'stateRoot': '0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544', - 'receiptsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', - 'miner': '0x0000000000000000000000000000000000000000', - 'difficulty': '0x0', - 'totalDifficulty': '0x0', - 'extraData': '0x', - 'size': '0x219', - 'gasLimit': '0x1c9c380', - 'gasUsed': '0x0', - 'timestamp': '0x61bc6b5d', - 'transactions': [], - 'uncles': [] + "number": "0x2", + "hash": "0xabc1230000000000000000000000000000000000000000000000000000000002", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x" + "00" * 256, + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "miner": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0", + "totalDifficulty": "0x0", + "extraData": "0x", + "size": "0x219", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x61bc6b5d", + "transactions": [], + "uncles": [], } - - block_json = json.dumps(block_data).encode('utf-8') - + + block_json = json.dumps(block_data).encode("utf-8") + block = block_from_json(block_json) - + assert block is not None - assert block['hash'] == '0xabc1230000000000000000000000000000000000000000000000000000000002' - assert len(block['transactions']) == 0 - assert len(block['uncles']) == 0 + assert block["hash"] == "0xabc1230000000000000000000000000000000000000000000000000000000002" + assert len(block["transactions"]) == 0 + assert len(block["uncles"]) == 0 if __name__ == "__main__": diff --git a/private/ipc/client.py b/private/ipc/client.py index 19e63ff..5be5c74 100644 --- a/private/ipc/client.py +++ b/private/ipc/client.py @@ -10,6 +10,7 @@ from .contracts import StorageContract, AccessManagerContract from .ipc import StorageData, sign_block + @dataclass class Config: dial_uri: str = "" @@ -18,11 +19,11 @@ class Config: access_contract_address: str = "" @staticmethod - def default_config() -> 'Config': + def default_config() -> "Config": return Config() -@dataclass +@dataclass class ContractsAddresses: storage: str = "" access_manager: str = "" @@ -32,12 +33,17 @@ class TransactionFailedError(Exception): pass -class Client: - def __init__(self, web3: Web3, auth: LocalAccount, storage: StorageContract, - access_manager: Optional[AccessManagerContract] = None, - list_policy_abi: Optional[dict] = None, - addresses: Optional[ContractsAddresses] = None, - chain_id: Optional[int] = None): +class Client: + def __init__( + self, + web3: Web3, + auth: LocalAccount, + storage: StorageContract, + access_manager: Optional[AccessManagerContract] = None, + list_policy_abi: Optional[dict] = None, + addresses: Optional[ContractsAddresses] = None, + chain_id: Optional[int] = None, + ): self.storage = storage self.access_manager = access_manager self.list_policy_abi = list_policy_abi @@ -47,7 +53,7 @@ def __init__(self, web3: Web3, auth: LocalAccount, storage: StorageContract, self._chain_id = chain_id @classmethod - def dial(cls, config: Config) -> 'Client': + def dial(cls, config: Config) -> "Client": try: client = Web3(Web3.HTTPProvider(config.dial_uri)) if not client.is_connected(): @@ -59,7 +65,7 @@ def dial(cls, config: Config) -> 'Client': try: private_key = config.private_key - if private_key.startswith('0x'): + if private_key.startswith("0x"): private_key = private_key[2:] account = Account.from_key(private_key) except Exception as e: @@ -71,62 +77,61 @@ def dial(cls, config: Config) -> 'Client': raise ConnectionError(f"Failed to get chain ID: {e}") storage = StorageContract(client, config.storage_contract_address) - + access_manager = None if config.access_contract_address: access_manager = AccessManagerContract(client, config.access_contract_address) - + list_policy_abi = None try: from .contracts import ListPolicyMetaData + list_policy_abi = ListPolicyMetaData.ABI except ImportError: - pass - + pass + addresses = ContractsAddresses( - storage=config.storage_contract_address, - access_manager=config.access_contract_address + storage=config.storage_contract_address, access_manager=config.access_contract_address ) ipc_client = cls( web3=client, auth=account, - storage=storage, + storage=storage, access_manager=access_manager, list_policy_abi=list_policy_abi, addresses=addresses, - chain_id=chain_id + chain_id=chain_id, ) return ipc_client @classmethod - def deploy_contracts(cls, config: Config) -> 'Client': + def deploy_contracts(cls, config: Config) -> "Client": eth_client = Web3(Web3.HTTPProvider(config.dial_uri)) if not eth_client.is_connected(): raise ConnectionError(f"Failed to connect to {config.dial_uri}") # Setup account private_key = config.private_key - if private_key.startswith('0x'): + if private_key.startswith("0x"): private_key = private_key[2:] account = Account.from_key(private_key) - + chain_id = eth_client.eth.chain_id - - client = cls( - web3=eth_client, - auth=account, - storage=None, - chain_id=chain_id - ) - + + client = cls(web3=eth_client, auth=account, storage=None, chain_id=chain_id) + try: from .contracts import ( - deploy_erc1967_proxy, deploy_access_manager, deploy_list_policy, - StorageContract, AccessManagerContract, ListPolicyMetaData + deploy_erc1967_proxy, + deploy_access_manager, + deploy_list_policy, + StorageContract, + AccessManagerContract, + ListPolicyMetaData, ) - + try: from .contracts import deploy_akave_token, deploy_storage except ImportError: @@ -134,47 +139,45 @@ def deploy_contracts(cls, config: Config) -> 'Client': "AkaveToken and Storage deployment functions are not yet implemented. " "The following functions are missing: deploy_akave_token, deploy_storage" ) - + akave_token_addr, tx_hash, token_contract = deploy_akave_token(eth_client, account) client.wait_for_tx(tx_hash) - + storage_impl_addr, tx_hash, _ = deploy_storage(eth_client, account) client.wait_for_tx(tx_hash) - + storage_abi = StorageContract.get_abi() init_data = StorageContract.encode_function_data("initialize", [akave_token_addr]) - - storage_proxy_addr, tx_hash, _ = deploy_erc1967_proxy( - eth_client, account, storage_impl_addr, init_data - ) + + storage_proxy_addr, tx_hash, _ = deploy_erc1967_proxy(eth_client, account, storage_impl_addr, init_data) client.wait_for_tx(tx_hash) - + storage = StorageContract(eth_client, storage_proxy_addr) client.storage = storage client.addresses.storage = storage_proxy_addr - + minter_role = token_contract.functions.MINTER_ROLE().call() - tx_hash = token_contract.functions.grantRole(minter_role, storage_proxy_addr).transact({ - 'from': account.address - }) + tx_hash = token_contract.functions.grantRole(minter_role, storage_proxy_addr).transact( + {"from": account.address} + ) client.wait_for_tx(tx_hash) - + access_addr, tx_hash, access_manager = deploy_access_manager(eth_client, account, storage_proxy_addr) client.wait_for_tx(tx_hash) - + client.access_manager = access_manager client.addresses.access_manager = access_addr - + tx_hash = storage.set_access_manager(account, access_addr) client.wait_for_tx(tx_hash) - + base_list_policy_addr, tx_hash, _ = deploy_list_policy(eth_client, account) client.wait_for_tx(tx_hash) - + client.list_policy_abi = ListPolicyMetaData.ABI - + return client - + except ImportError as e: raise NotImplementedError( f"Contract deployment functions not available: {e}. " @@ -187,8 +190,8 @@ def chain_id(self) -> int: def wait_for_tx(self, tx_hash: Union[str, bytes], timeout: float = 120.0) -> dict: if isinstance(tx_hash, bytes): tx_hash = tx_hash.hex() - if not tx_hash.startswith('0x'): - tx_hash = '0x' + tx_hash + if not tx_hash.startswith("0x"): + tx_hash = "0x" + tx_hash try: receipt = self.eth.eth.get_transaction_receipt(tx_hash) @@ -197,18 +200,18 @@ def wait_for_tx(self, tx_hash: Union[str, bytes], timeout: float = 120.0) -> dic else: raise TransactionFailedError("Transaction failed") except TransactionNotFound: - pass + pass except Exception as e: raise TransactionFailedError(f"Error checking transaction receipt: {e}") - + start_time = time.time() - poll_interval = 0.2 - + poll_interval = 0.2 + while True: current_time = time.time() if current_time - start_time > timeout: raise TimeoutError(f"Timeout waiting for transaction {tx_hash}") - + try: receipt = self.eth.eth.get_transaction_receipt(tx_hash) if receipt.status == 1: @@ -219,4 +222,4 @@ def wait_for_tx(self, tx_hash: Union[str, bytes], timeout: float = 120.0) -> dic time.sleep(poll_interval) continue except Exception as e: - raise TransactionFailedError(f"Error checking transaction receipt: {e}") \ No newline at end of file + raise TransactionFailedError(f"Error checking transaction receipt: {e}") diff --git a/private/ipc/client_test.py b/private/ipc/client_test.py index a341e07..3ae2205 100644 --- a/private/ipc/client_test.py +++ b/private/ipc/client_test.py @@ -16,27 +16,34 @@ DIAL_URI = os.getenv("DIAL_URI", "") PRIVATE_KEY = os.getenv("PRIVATE_KEY", "") + def pick_dial_uri() -> str: if not DIAL_URI or DIAL_URI.lower() == "omit": pytest.skip("dial uri flag missing, set DIAL_URI environment variable") return DIAL_URI + def pick_private_key() -> str: if not PRIVATE_KEY or PRIVATE_KEY.lower() == "omit": pytest.skip("private key flag missing, set PRIVATE_KEY environment variable") return PRIVATE_KEY + def generate_random_address() -> str: from eth_account import Account + account = Account.create() return account.address + def generate_random_cid() -> bytes: return secrets.token_bytes(32) + def generate_random_nonce() -> int: return secrets.randbits(256) + class TestClient: def test_config_default(self): @@ -51,7 +58,7 @@ def test_config_creation(self): dial_uri="http://localhost:8545", private_key="0x123", storage_contract_address="0xstorage", - access_contract_address="0xaccess" + access_contract_address="0xaccess", ) assert config.dial_uri == "http://localhost:8545" assert config.private_key == "0x123" @@ -61,14 +68,14 @@ def test_config_creation(self): def test_dial_connection(self): dial_uri = pick_dial_uri() private_key = pick_private_key() - + config = Config( dial_uri=dial_uri, private_key=private_key, storage_contract_address="0x" + "0" * 40, # Mock address - access_contract_address="0x" + "0" * 40, # Mock address + access_contract_address="0x" + "0" * 40, # Mock address ) - + try: client = Client.dial(config) assert client.web3.is_connected() @@ -78,27 +85,24 @@ def test_dial_connection(self): assert "Failed to connect" in str(e) or "Invalid" in str(e) def test_wait_for_tx_invalid_hash(self): - with patch('web3.Web3') as mock_web3: + with patch("web3.Web3") as mock_web3: mock_web3_instance = Mock() mock_web3_instance.is_connected.return_value = True mock_web3_instance.eth.chain_id = 1 mock_web3_instance.eth.get_transaction_receipt.side_effect = Exception("not found") mock_web3.return_value = mock_web3_instance - + from ..contracts import StorageContract - with patch.object(StorageContract, '__init__', return_value=None): + + with patch.object(StorageContract, "__init__", return_value=None): mock_storage = Mock() - + from eth_account import Account + account = Account.create() - - client = Client( - web3=mock_web3_instance, - auth=account, - storage=mock_storage, - chain_id=1 - ) - + + client = Client(web3=mock_web3_instance, auth=account, storage=mock_storage, chain_id=1) + with pytest.raises((TimeoutError, TransactionFailedError)): client.wait_for_tx("0x" + "0" * 64, timeout=0.1) @@ -106,8 +110,8 @@ def test_storage_data_structure(self): chunk_cid = b"test_chunk_cid" block_cid = b"0" * 32 # 32 bytes bucket_id = b"1" * 32 # 32 bytes - node_id = b"2" * 32 # 32 bytes - + node_id = b"2" * 32 # 32 bytes + data = StorageData( chunk_cid=chunk_cid, block_cid=block_cid, @@ -116,14 +120,14 @@ def test_storage_data_structure(self): node_id=node_id, nonce=12345, deadline=int(time.time()) + 3600, - bucket_id=bucket_id + bucket_id=bucket_id, ) - + assert data.chunk_cid == chunk_cid assert data.block_cid == block_cid assert data.chunk_index == 0 assert data.block_index == 1 - + # Test message dict conversion msg_dict = data.to_message_dict() assert msg_dict["chunkCID"] == chunk_cid @@ -132,11 +136,11 @@ def test_storage_data_structure(self): def test_sign_block(self): from eth_account import Account - + # Create test account account = Account.create() private_key_hex = account.key.hex() - + # Create test data data = StorageData( chunk_cid=b"test_chunk", @@ -146,13 +150,13 @@ def test_sign_block(self): node_id=b"1" * 32, nonce=12345, deadline=int(time.time()) + 3600, - bucket_id=b"2" * 32 + bucket_id=b"2" * 32, ) - + # Test signing storage_address = "0x" + "0" * 40 chain_id = 1 - + try: signature = sign_block(private_key_hex, storage_address, chain_id, data) assert isinstance(signature, bytes) @@ -161,29 +165,30 @@ def test_sign_block(self): # May fail if EIP712 implementation is not complete assert "not implemented" in str(e).lower() or "import" in str(e).lower() + if __name__ == "__main__": # Run basic tests test_client = TestClient() - + print("Running basic client tests...") - + try: test_client.test_config_default() print("βœ“ Config default test passed") except Exception as e: print(f"βœ— Config default test failed: {e}") - + try: test_client.test_config_creation() print("βœ“ Config creation test passed") except Exception as e: print(f"βœ— Config creation test failed: {e}") - + try: test_client.test_storage_data_structure() print("βœ“ Storage data structure test passed") except Exception as e: print(f"βœ— Storage data structure test failed: {e}") - + print("\nNote: Integration tests require DIAL_URI and PRIVATE_KEY environment variables") print("Example: DIAL_URI=http://localhost:8545 PRIVATE_KEY=0x... python client_test.py") diff --git a/private/ipc/contracts/__init__.py b/private/ipc/contracts/__init__.py index 7b8754f..185166a 100644 --- a/private/ipc/contracts/__init__.py +++ b/private/ipc/contracts/__init__.py @@ -6,23 +6,23 @@ from .sink import SinkContract, new_sink, deploy_sink __all__ = [ - 'StorageContract', - 'AccessManagerContract', - 'new_access_manager', - 'deploy_access_manager', - 'ERC1967Proxy', - 'ERC1967ProxyMetaData', - 'new_erc1967_proxy', - 'deploy_erc1967_proxy', - 'PDPVerifier', - 'PDPVerifierMetaData', - 'new_pdp_verifier', - 'deploy_pdp_verifier', - 'ListPolicyContract', - 'ListPolicyMetaData', - 'new_list_policy', - 'deploy_list_policy', - 'SinkContract', - 'new_sink', - 'deploy_sink' -] \ No newline at end of file + "StorageContract", + "AccessManagerContract", + "new_access_manager", + "deploy_access_manager", + "ERC1967Proxy", + "ERC1967ProxyMetaData", + "new_erc1967_proxy", + "deploy_erc1967_proxy", + "PDPVerifier", + "PDPVerifierMetaData", + "new_pdp_verifier", + "deploy_pdp_verifier", + "ListPolicyContract", + "ListPolicyMetaData", + "new_list_policy", + "deploy_list_policy", + "SinkContract", + "new_sink", + "deploy_sink", +] diff --git a/private/ipc/contracts/access_manager.py b/private/ipc/contracts/access_manager.py index f8084fa..45e4bc3 100644 --- a/private/ipc/contracts/access_manager.py +++ b/private/ipc/contracts/access_manager.py @@ -4,188 +4,135 @@ from web3.contract import Contract import json + class AccessManagerContract: """Python bindings for the AccessManager smart contract.""" - + def __init__(self, web3: Web3, contract_address: HexAddress): """Initialize the AccessManager contract interface. - + Args: web3: Web3 instance contract_address: Address of the deployed AccessManager contract """ self.web3 = web3 self.contract_address = contract_address - + # Contract ABI from the Go bindings self.abi = [ { - "inputs": [ - { - "internalType": "address", - "name": "_storageContract", - "type": "address" - } - ], + "inputs": [{"internalType": "address", "name": "_storageContract", "type": "address"}], "stateMutability": "nonpayable", - "type": "constructor" + "type": "constructor", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "isPublic", - "type": "bool" - } + {"internalType": "bytes32", "name": "fileId", "type": "bytes32"}, + {"internalType": "bool", "name": "isPublic", "type": "bool"}, ], "name": "changePublicAccess", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "fileId", "type": "bytes32"}], "name": "getFileAccessInfo", "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bool", - "name": "", - "type": "bool" - } + {"internalType": "address", "name": "", "type": "address"}, + {"internalType": "bool", "name": "", "type": "bool"}, ], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "fileId", "type": "bytes32"}], "name": "getPolicy", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], + "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "policyContract", - "type": "address" - } + {"internalType": "bytes32", "name": "fileId", "type": "bytes32"}, + {"internalType": "address", "name": "policyContract", "type": "address"}, ], "name": "setPolicy", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [], "name": "storageContract", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], + "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" - } + "type": "function", + }, ] - + self.contract = web3.eth.contract(address=contract_address, abi=self.abi) def change_public_access(self, auth, file_id: bytes, is_public: bool) -> HexStr: """Changes the public access status of a file matching Go SDK signature. - + Args: auth: Authentication object with address and key file_id: ID of the file is_public: Whether the file should be publicly accessible - + Returns: Transaction hash """ from eth_account import Account - + # Build transaction function = self.contract.functions.changePublicAccess(file_id, is_public) - + # Get transaction parameters tx_params = { - 'from': auth.address, - 'gas': 500000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': self.web3.eth.get_transaction_count(auth.address), + "from": auth.address, + "gas": 500000, + "gasPrice": self.web3.eth.gas_price, + "nonce": self.web3.eth.get_transaction_count(auth.address), } - + # Build the transaction tx = function.build_transaction(tx_params) - + # Sign the transaction if isinstance(auth.key, str): # Key is hex string, parse it - key_str = auth.key.replace('0x', '') + key_str = auth.key.replace("0x", "") private_key_bytes = bytes.fromhex(key_str) else: # Key is already bytes private_key_bytes = auth.key - + signed_tx = Account.sign_transaction(tx, private_key_bytes) - + # Send the transaction tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) - + return tx_hash.hex() def change_public_access_simple(self, file_id: bytes, is_public: bool, from_address: HexAddress) -> None: """Changes the public access status of a file (simple version that waits for receipt). - + Args: file_id: ID of the file is_public: Whether the file should be publicly accessible from_address: Address changing the access """ - tx_hash = self.contract.functions.changePublicAccess(file_id, is_public).transact({'from': from_address}) + tx_hash = self.contract.functions.changePublicAccess(file_id, is_public).transact({"from": from_address}) self.web3.eth.wait_for_transaction_receipt(tx_hash) def get_file_access_info(self, file_id: bytes) -> Tuple[HexAddress, bool]: """Gets access information for a file. - + Args: file_id: ID of the file - + Returns: Tuple containing (policy contract address, is public) """ @@ -193,10 +140,10 @@ def get_file_access_info(self, file_id: bytes) -> Tuple[HexAddress, bool]: def get_policy(self, file_id: bytes) -> HexAddress: """Gets the policy contract address for a file. - + Args: file_id: ID of the file - + Returns: Address of the policy contract """ @@ -204,18 +151,18 @@ def get_policy(self, file_id: bytes) -> HexAddress: def set_policy(self, file_id: bytes, policy_contract: HexAddress, from_address: HexAddress) -> None: """Sets the policy contract for a file. - + Args: file_id: ID of the file policy_contract: Address of the policy contract from_address: Address setting the policy """ - tx_hash = self.contract.functions.setPolicy(file_id, policy_contract).transact({'from': from_address}) + tx_hash = self.contract.functions.setPolicy(file_id, policy_contract).transact({"from": from_address}) self.web3.eth.wait_for_transaction_receipt(tx_hash) def get_storage_contract(self) -> HexAddress: """Gets the address of the associated storage contract. - + Returns: Address of the storage contract """ diff --git a/private/ipc/contracts/akave_token.py b/private/ipc/contracts/akave_token.py index 0008595..159110e 100644 --- a/private/ipc/contracts/akave_token.py +++ b/private/ipc/contracts/akave_token.py @@ -43,13 +43,17 @@ def __init__(self, contract: Contract, web3: Web3): self.contract = contract self.web3 = web3 - def grant_role(self, account: LocalAccount, role: bytes, grantee: Address, tx_params: Optional[Dict[str, Any]] = None) -> HexStr: + def grant_role( + self, account: LocalAccount, role: bytes, grantee: Address, tx_params: Optional[Dict[str, Any]] = None + ) -> HexStr: params = {"from": account.address, "gasPrice": self.web3.eth.gas_price} if tx_params: params.update(tx_params) tx = self.contract.functions.grantRole(role, grantee).build_transaction(params) signed = account.sign_transaction(tx) - tx_hash = self.web3.eth.send_raw_transaction(getattr(signed, "raw_transaction", getattr(signed, "rawTransaction"))) + tx_hash = self.web3.eth.send_raw_transaction( + getattr(signed, "raw_transaction", getattr(signed, "rawTransaction")) + ) return tx_hash.hex() @@ -67,5 +71,3 @@ def address(self) -> Address: def new_akave_token(w3: Web3, address: Address) -> AkaveToken: return AkaveToken(w3, address) - - diff --git a/private/ipc/contracts/erc1967_proxy.py b/private/ipc/contracts/erc1967_proxy.py index bb3f99c..b19e6fc 100644 --- a/private/ipc/contracts/erc1967_proxy.py +++ b/private/ipc/contracts/erc1967_proxy.py @@ -15,109 +15,88 @@ class ERC1967ProxyMetaData: """Metadata for the ERC1967Proxy contract.""" - + ABI = [ { "inputs": [ {"internalType": "address", "name": "implementation", "type": "address"}, - {"internalType": "bytes", "name": "_data", "type": "bytes"} + {"internalType": "bytes", "name": "_data", "type": "bytes"}, ], "stateMutability": "nonpayable", - "type": "constructor" + "type": "constructor", }, { - "inputs": [ - {"internalType": "address", "name": "target", "type": "address"} - ], + "inputs": [{"internalType": "address", "name": "target", "type": "address"}], "name": "AddressEmptyCode", - "type": "error" + "type": "error", }, { - "inputs": [ - {"internalType": "address", "name": "implementation", "type": "address"} - ], + "inputs": [{"internalType": "address", "name": "implementation", "type": "address"}], "name": "ERC1967InvalidImplementation", - "type": "error" - }, - { - "inputs": [], - "name": "ERC1967NonPayable", - "type": "error" - }, - { - "inputs": [], - "name": "FailedCall", - "type": "error" + "type": "error", }, + {"inputs": [], "name": "ERC1967NonPayable", "type": "error"}, + {"inputs": [], "name": "FailedCall", "type": "error"}, { "anonymous": False, - "inputs": [ - {"indexed": True, "internalType": "address", "name": "implementation", "type": "address"} - ], + "inputs": [{"indexed": True, "internalType": "address", "name": "implementation", "type": "address"}], "name": "Upgraded", - "type": "event" + "type": "event", }, - { - "stateMutability": "payable", - "type": "fallback" - } + {"stateMutability": "payable", "type": "fallback"}, ] - + # Compiled bytecode for deploying new contracts BIN = "0x608060405234801561000f575f5ffd5b506040516103df3803806103df83398101604081905261002e9161024b565b818161003a8282610043565b50505050610330565b61004c826100a1565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561009557610090828261011c565b505050565b61009d61018f565b5050565b806001600160a01b03163b5f036100db57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f846001600160a01b031684604051610138919061031a565b5f60405180830381855af49150503d805f8114610170576040519150601f19603f3d011682016040523d82523d5f602084013e610175565b606091505b5090925090506101868583836101b0565b95945050505050565b34156101ae5760405163b398979f60e01b815260040160405180910390fd5b565b6060826101c5576101c08261020f565b610208565b81511580156101dc57506001600160a01b0384163b155b1561020557604051639996b31560e01b81526001600160a01b03851660048201526024016100d2565b50805b9392505050565b80511561021e57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b634e487b7160e01b5f52604160045260245ffd5b5f5f6040838503121561025c575f5ffd5b82516001600160a01b0381168114610272575f5ffd5b60208401519092506001600160401b0381111561028d575f5ffd5b8301601f8101851361029d575f5ffd5b80516001600160401b038111156102b6576102b6610237565b604051601f8201601f19908116603f011681016001600160401b03811182821017156102e4576102e4610237565b6040528181528282016020018710156102fb575f5ffd5b8160208401602083015e5f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b60a38061033c5f395ff3fe6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156069573d5ff35b3d5ffdfea2646970667358221220be6558ccf6c789c169daef452fea1c8eb936331c00f69faa3606b7da6fbbb3e464736f6c634300081c0033" class ERC1967ProxyCaller: """Read-only binding to the ERC1967Proxy contract.""" - + def __init__(self, contract: Contract): self.contract = contract - + # Note: ERC1967Proxy is primarily a proxy contract with minimal read-only functions # Most functionality is delegated to the implementation contract class ERC1967ProxyTransactor: """Write-only binding to the ERC1967Proxy contract.""" - + def __init__(self, contract: Contract, web3: Web3): self.contract = contract self.web3 = web3 - + def fallback(self, account: LocalAccount, calldata: bytes, tx_params: Optional[Dict[str, Any]] = None) -> HexStr: """ Execute the fallback function with custom calldata. - + Args: account: Account to sign the transaction calldata: Bytes to send to the fallback function tx_params: Additional transaction parameters - + Returns: Transaction hash """ - params = { - "from": account.address, - "gasPrice": self.web3.eth.gas_price, - "data": calldata - } + params = {"from": account.address, "gasPrice": self.web3.eth.gas_price, "data": calldata} if tx_params: params.update(tx_params) - + # Build transaction manually since this is a fallback call tx = { "to": self.contract.address, "from": account.address, "data": calldata, - "gasPrice": self.web3.eth.gas_price + "gasPrice": self.web3.eth.gas_price, } if tx_params: tx.update(tx_params) - + # Estimate gas if not provided if "gas" not in tx: tx["gas"] = self.web3.eth.estimate_gas(tx) - + # Sign and send transaction signed_tx = account.sign_transaction(tx) tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) @@ -126,210 +105,200 @@ def fallback(self, account: LocalAccount, calldata: bytes, tx_params: Optional[D class ERC1967ProxyFilterer: """Log filtering binding for ERC1967Proxy contract events.""" - + def __init__(self, contract: Contract): self.contract = contract - - def filter_upgraded(self, from_block: int = 0, to_block: str = "latest", - implementation: Optional[List[Address]] = None): + + def filter_upgraded( + self, from_block: int = 0, to_block: str = "latest", implementation: Optional[List[Address]] = None + ): """ Filter for Upgraded events. - + Args: from_block: Starting block number to_block: Ending block number or "latest" implementation: List of implementation addresses to filter by - + Returns: Event filter object """ argument_filters = {} if implementation: argument_filters["implementation"] = implementation - + return self.contract.events.Upgraded.create_filter( - fromBlock=from_block, - toBlock=to_block, - argument_filters=argument_filters + fromBlock=from_block, toBlock=to_block, argument_filters=argument_filters ) - - def get_upgraded_events(self, from_block: int = 0, to_block: str = "latest", - implementation: Optional[List[Address]] = None): + + def get_upgraded_events( + self, from_block: int = 0, to_block: str = "latest", implementation: Optional[List[Address]] = None + ): """ Get all past Upgraded events. - + Args: from_block: Starting block number to_block: Ending block number or "latest" implementation: List of implementation addresses to filter by - + Returns: List of event dictionaries """ argument_filters = {} if implementation: argument_filters["implementation"] = implementation - + return self.contract.events.Upgraded.get_logs( - fromBlock=from_block, - toBlock=to_block, - argument_filters=argument_filters + fromBlock=from_block, toBlock=to_block, argument_filters=argument_filters ) class ERC1967Proxy: """Auto-generated Python binding for the ERC1967Proxy contract.""" - + def __init__(self, web3: Web3, contract_address: HexAddress): """ Initialize the ERC1967Proxy contract interface. - + Args: web3: Web3 instance contract_address: Address of the deployed proxy contract """ self.web3 = web3 self.contract_address = contract_address - self.contract = web3.eth.contract( - address=contract_address, - abi=ERC1967ProxyMetaData.ABI - ) - + self.contract = web3.eth.contract(address=contract_address, abi=ERC1967ProxyMetaData.ABI) + # Initialize component bindings self.caller = ERC1967ProxyCaller(self.contract) self.transactor = ERC1967ProxyTransactor(self.contract, web3) self.filterer = ERC1967ProxyFilterer(self.contract) - + @classmethod - def deploy(cls, web3: Web3, account: LocalAccount, implementation: Address, - data: bytes, tx_params: Optional[Dict[str, Any]] = None) -> 'ERC1967Proxy': + def deploy( + cls, + web3: Web3, + account: LocalAccount, + implementation: Address, + data: bytes, + tx_params: Optional[Dict[str, Any]] = None, + ) -> "ERC1967Proxy": """ Deploy a new ERC1967Proxy contract. - + Args: web3: Web3 instance account: Account to deploy the contract implementation: Address of the implementation contract data: Initialization data to send to the implementation tx_params: Additional transaction parameters - + Returns: ERC1967Proxy instance for the deployed contract """ - contract_factory = web3.eth.contract( - abi=ERC1967ProxyMetaData.ABI, - bytecode=ERC1967ProxyMetaData.BIN - ) - + contract_factory = web3.eth.contract(abi=ERC1967ProxyMetaData.ABI, bytecode=ERC1967ProxyMetaData.BIN) + # Build deployment transaction - params = { - "from": account.address, - "gasPrice": web3.eth.gas_price - } + params = {"from": account.address, "gasPrice": web3.eth.gas_price} if tx_params: params.update(tx_params) - + tx = contract_factory.constructor(implementation, data).build_transaction(params) - + # Estimate gas if not provided if "gas" not in tx: tx["gas"] = web3.eth.estimate_gas(tx) - + # Sign and send transaction signed_tx = account.sign_transaction(tx) tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction) - + # Wait for transaction receipt tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash) - + return cls(web3, tx_receipt.contractAddress) - - def call_implementation(self, method_sig: str, args: List[Any] = None, - caller: Optional[Address] = None) -> Any: + + def call_implementation(self, method_sig: str, args: List[Any] = None, caller: Optional[Address] = None) -> Any: """ Call a method on the implementation contract through the proxy. - + Args: method_sig: Method signature (e.g., "balanceOf(address)") args: Method arguments caller: Address to use for the call - + Returns: Result of the method call """ if args is None: args = [] - + # Encode function call method_id = self.web3.keccak(text=method_sig)[:4] - encoded_args = self.web3.codec.encode( - ['address'] + [type(arg).__name__ for arg in args], - args - ) if args else b'' + encoded_args = self.web3.codec.encode(["address"] + [type(arg).__name__ for arg in args], args) if args else b"" calldata = method_id + encoded_args - + # Make call through proxy call_params = {"to": self.contract_address, "data": calldata} if caller: call_params["from"] = caller - + result = self.web3.eth.call(call_params) return result - - def transact_implementation(self, account: LocalAccount, method_sig: str, - args: List[Any] = None, tx_params: Optional[Dict[str, Any]] = None) -> HexStr: + + def transact_implementation( + self, account: LocalAccount, method_sig: str, args: List[Any] = None, tx_params: Optional[Dict[str, Any]] = None + ) -> HexStr: """ Send a transaction to a method on the implementation contract through the proxy. - + Args: account: Account to sign the transaction method_sig: Method signature (e.g., "transfer(address,uint256)") args: Method arguments tx_params: Additional transaction parameters - + Returns: Transaction hash """ if args is None: args = [] - + # Encode function call method_id = self.web3.keccak(text=method_sig)[:4] - encoded_args = self.web3.codec.encode( - ['address'] + [type(arg).__name__ for arg in args], - args - ) if args else b'' + encoded_args = self.web3.codec.encode(["address"] + [type(arg).__name__ for arg in args], args) if args else b"" calldata = method_id + encoded_args - + return self.transactor.fallback(account, calldata, tx_params) def new_erc1967_proxy(web3: Web3, contract_address: HexAddress) -> ERC1967Proxy: """ Create a new instance of ERC1967Proxy bound to a specific deployed contract. - + Args: web3: Web3 instance contract_address: Address of the deployed contract - + Returns: ERC1967Proxy instance """ return ERC1967Proxy(web3, contract_address) -def deploy_erc1967_proxy(web3: Web3, account: LocalAccount, implementation: Address, - data: bytes, tx_params: Optional[Dict[str, Any]] = None) -> ERC1967Proxy: +def deploy_erc1967_proxy( + web3: Web3, account: LocalAccount, implementation: Address, data: bytes, tx_params: Optional[Dict[str, Any]] = None +) -> ERC1967Proxy: """ Deploy a new ERC1967Proxy contract. - + Args: web3: Web3 instance account: Account to deploy the contract implementation: Address of the implementation contract data: Initialization data to send to the implementation tx_params: Additional transaction parameters - + Returns: ERC1967Proxy instance for the deployed contract """ diff --git a/private/ipc/contracts/errors.py b/private/ipc/contracts/errors.py index 42c244f..ea5b1fa 100644 --- a/private/ipc/contracts/errors.py +++ b/private/ipc/contracts/errors.py @@ -1,20 +1,21 @@ import web3 from typing import Dict, Optional, List -from sdk.config import KNOWN_ERROR_STRINGS,validate_hex_string +from sdk.config import KNOWN_ERROR_STRINGS, validate_hex_string # Dictionary to store the mapping from error hash (selector) to error string _error_hash_to_error_map: Dict[str, str] = {} + def parse_errors_to_hashes() -> None: """ - Computes the Keccak256 hash for known error strings and populates the + Computes the Keccak256 hash for known error strings and populates the _error_hash_to_error_map. This should be called once on initialization. - - The hash calculation mimics Solidity's `keccak256(bytes("Error(string)"))`, + + The hash calculation mimics Solidity's `keccak256(bytes("Error(string)"))`, taking the first 4 bytes as the selector. """ global _error_hash_to_error_map - if _error_hash_to_error_map: # Avoid re-parsing if already done + if _error_hash_to_error_map: # Avoid re-parsing if already done return temp_map = {} @@ -27,9 +28,10 @@ def parse_errors_to_hashes() -> None: selector = hash_bytes[:4].hex() # Store the mapping (e.g., '0xabcdef12' -> "Storage: bucket exists") temp_map[selector] = error_string - + _error_hash_to_error_map = temp_map - print(f"Parsed {len(_error_hash_to_error_map)} error strings into hashes.") # Optional: logging + print(f"Parsed {len(_error_hash_to_error_map)} error strings into hashes.") # Optional: logging + def error_hash_to_error(error_data: str) -> Optional[str]: """ @@ -37,7 +39,7 @@ def error_hash_to_error(error_data: str) -> Optional[str]: to a known human-readable error string. Args: - error_data: The error data string, usually starting with '0x' followed + error_data: The error data string, usually starting with '0x' followed by the 4-byte error selector (e.g., "0x08c379a0..."). Returns: @@ -45,25 +47,26 @@ def error_hash_to_error(error_data: str) -> Optional[str]: """ if not _error_hash_to_error_map: # Ensure hashes are parsed if accessed before explicit call - print("Warning: Error hashes not parsed yet. Parsing now.") # Optional: logging + print("Warning: Error hashes not parsed yet. Parsing now.") # Optional: logging parse_errors_to_hashes() if not isinstance(error_data, str) or not validate_hex_string(error_data): return None # Extract the selector (first 4 bytes after '0x') - selector = error_data[:10] # Takes '0x' + 8 hex characters + selector = error_data[:10] # Takes '0x' + 8 hex characters return _error_hash_to_error_map.get(selector) + # Automatically parse errors when the module is imported parse_errors_to_hashes() # --- How to use with web3.py --- -# +# # from web3.exceptions import ContractLogicError # from .errors import error_hash_to_error -# +# # try: # # ... make a contract call that might revert ... # tx_hash = contract.functions.someFunction().transact({'from': ..., ...}) @@ -79,7 +82,7 @@ def error_hash_to_error(error_data: str) -> Optional[str]: # # The error data might be in cle.args or similar, requires inspection # # Example: cle.args[0] might contain 'execution reverted: 0x...' # error_data = str(cle) # Or parse from args -# human_readable_error = error_hash_to_error(error_data) +# human_readable_error = error_hash_to_error(error_data) # if human_readable_error: # print(f"Known revert reason: {human_readable_error}") # else: @@ -87,7 +90,7 @@ def error_hash_to_error(error_data: str) -> Optional[str]: # except Exception as call_exc: # print(f"Could not get revert reason: {call_exc}") # print("Transaction failed, but couldn't determine exact reason.") -# +# # except ContractLogicError as e: # print(f"ContractLogicError on send/call: {e}") # # Process error_hash_to_error(str(e)) or e.args as above @@ -98,4 +101,4 @@ def error_hash_to_error(error_data: str) -> Optional[str]: # print(f"Unknown revert reason: {e}") # except Exception as e: # print(f"An unexpected error occurred: {e}") -# \ No newline at end of file +# diff --git a/private/ipc/contracts/list_policy.py b/private/ipc/contracts/list_policy.py index 4218027..891ef6d 100644 --- a/private/ipc/contracts/list_policy.py +++ b/private/ipc/contracts/list_policy.py @@ -15,66 +15,50 @@ class ListPolicyMetaData: """Metadata for the ListPolicy contract.""" - + ABI = [ - { - "inputs": [], - "name": "AlreadyWhitelisted", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidAddress", - "type": "error" - }, - { - "inputs": [], - "name": "NotThePolicyOwner", - "type": "error" - }, - { - "inputs": [], - "name": "NotWhitelisted", - "type": "error" - }, + {"inputs": [], "name": "AlreadyWhitelisted", "type": "error"}, + {"inputs": [], "name": "InvalidAddress", "type": "error"}, + {"inputs": [], "name": "NotThePolicyOwner", "type": "error"}, + {"inputs": [], "name": "NotWhitelisted", "type": "error"}, { "inputs": [{"internalType": "address", "name": "user", "type": "address"}], "name": "assignRole", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "address", "name": "_owner", "type": "address"}], "name": "initialize", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [], "name": "owner", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "address", "name": "user", "type": "address"}], "name": "revokeRole", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "address", "name": "user", "type": "address"}, - {"internalType": "bytes", "name": "data", "type": "bytes"} + {"internalType": "bytes", "name": "data", "type": "bytes"}, ], "name": "validateAccess", "outputs": [{"internalType": "bool", "name": "hasAccess", "type": "bool"}], "stateMutability": "view", - "type": "function" - } + "type": "function", + }, ] BIN = "0x6080604052348015600e575f5ffd5b506103a78061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610055575f3560e01c80635c110a741461005957806365e88c5a1461008157806380e52e3f146100965780638da5cb5b146100a9578063c4d66de8146100d3575b5f5ffd5b61006c6100673660046102d3565b6100e6565b60405190151581526020015b60405180910390f35b61009461008f366004610351565b61012e565b005b6100946100a4366004610351565b6101bd565b5f546100bb906001600160a01b031681565b6040516001600160a01b039091168152602001610078565b6100946100e1366004610351565b610245565b5f6001600160a01b03841661010e5760405163e6c4247b60e01b815260040160405180910390fd5b5050506001600160a01b03165f9081526001602052604090205460ff1690565b5f546001600160a01b03163314610158576040516351604ff560e11b815260040160405180910390fd5b6001600160a01b0381165f9081526001602081905260409091205460ff16151590036101975760405163b73e95e160e01b815260040160405180910390fd5b6001600160a01b03165f908152600160208190526040909120805460ff19169091179055565b5f546001600160a01b031633146101e7576040516351604ff560e11b815260040160405180910390fd5b6001600160a01b0381165f9081526001602081905260409091205460ff1615151461022557604051630b094f2760e31b815260040160405180910390fd5b6001600160a01b03165f908152600160205260409020805460ff19169055565b5f546001600160a01b0316156102975760405162461bcd60e51b8152602060048201526013602482015272105b1c9958591e481a5b9a5d1a585b1a5e9959606a1b604482015260640160405180910390fd5b5f80546001600160a01b0319166001600160a01b0392909216919091179055565b80356001600160a01b03811681146102ce575f5ffd5b919050565b5f5f5f604084860312156102e5575f5ffd5b6102ee846102b8565b9250602084013567ffffffffffffffff811115610309575f5ffd5b8401601f81018613610319575f5ffd5b803567ffffffffffffffff81111561032f575f5ffd5b866020828401011115610340575f5ffd5b939660209190910195509293505050565b5f60208284031215610361575f5ffd5b61036a826102b8565b939250505056fea264697066735822122047472e4c391eccdb2c4a9389b8901ff8ee4eac8e7f69a48928e3085c565ad9aa64736f6c634300081c0033" @@ -82,58 +66,61 @@ class ListPolicyMetaData: class ListPolicyContract: """Main class for interacting with the ListPolicy contract.""" - + def __init__(self, w3: Web3, address: str): """Initialize ListPolicy contract interface.""" self.w3 = w3 self.address = Web3.to_checksum_address(address) - self.contract = w3.eth.contract( - address=self.address, - abi=ListPolicyMetaData.ABI - ) - + self.contract = w3.eth.contract(address=self.address, abi=ListPolicyMetaData.ABI) + # View functions def owner(self) -> str: """Get the owner of the policy.""" return self.contract.functions.owner().call() - + def validate_access(self, user: str, data: bytes) -> bool: """Validate access for a user.""" return self.contract.functions.validateAccess(user, data).call() - + # Transaction functions def initialize(self, account: LocalAccount, owner: str, gas_limit: int = 500000) -> str: """Initialize the policy with an owner.""" - tx = self.contract.functions.initialize(owner).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + tx = self.contract.functions.initialize(owner).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - + def assign_role(self, account: LocalAccount, user: str, gas_limit: int = 500000) -> str: """Assign role (whitelist) to a user.""" - tx = self.contract.functions.assignRole(user).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + tx = self.contract.functions.assignRole(user).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - + def revoke_role(self, account: LocalAccount, user: str, gas_limit: int = 500000) -> str: """Revoke role (remove from whitelist) from a user.""" - tx = self.contract.functions.revokeRole(user).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + tx = self.contract.functions.revokeRole(user).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() @@ -147,25 +134,24 @@ def new_list_policy(w3: Web3, address: str) -> ListPolicyContract: def deploy_list_policy(w3: Web3, account: LocalAccount, gas_limit: int = 2000000) -> Tuple[str, str]: """ Deploy a new ListPolicy contract. - + Returns: Tuple of (contract_address, transaction_hash) """ - contract_factory = w3.eth.contract( - abi=ListPolicyMetaData.ABI, - bytecode=ListPolicyMetaData.BIN + contract_factory = w3.eth.contract(abi=ListPolicyMetaData.ABI, bytecode=ListPolicyMetaData.BIN) + + tx = contract_factory.constructor().build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": w3.eth.get_transaction_count(account.address), + } ) - - tx = contract_factory.constructor().build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': w3.eth.get_transaction_count(account.address), - }) - + signed_tx = account.sign_transaction(tx) tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) - + # Wait for transaction receipt receipt = w3.eth.wait_for_transaction_receipt(tx_hash) - - return receipt.contractAddress, tx_hash.hex() \ No newline at end of file + + return receipt.contractAddress, tx_hash.hex() diff --git a/private/ipc/contracts/pdp_verifier.py b/private/ipc/contracts/pdp_verifier.py index 429468f..adbdd74 100644 --- a/private/ipc/contracts/pdp_verifier.py +++ b/private/ipc/contracts/pdp_verifier.py @@ -31,58 +31,58 @@ def __init__(self, root_id: int, offset: int): self.offset = offset -class PDPVerifierMetaData: +class PDPVerifierMetaData: ABI = [ { "inputs": [{"internalType": "uint256", "name": "_challengeFinality", "type": "uint256"}], "stateMutability": "nonpayable", - "type": "constructor" + "type": "constructor", }, { "inputs": [ {"internalType": "uint256", "name": "idx", "type": "uint256"}, - {"internalType": "string", "name": "msg", "type": "string"} + {"internalType": "string", "name": "msg", "type": "string"}, ], "name": "IndexedError", - "type": "error" + "type": "error", }, { "inputs": [{"internalType": "address", "name": "owner", "type": "address"}], "name": "OwnableInvalidOwner", - "type": "error" + "type": "error", }, { "inputs": [{"internalType": "address", "name": "account", "type": "address"}], "name": "OwnableUnauthorizedAccount", - "type": "error" + "type": "error", }, { "anonymous": False, "inputs": [ {"indexed": False, "internalType": "string", "name": "message", "type": "string"}, - {"indexed": False, "internalType": "uint256", "name": "value", "type": "uint256"} + {"indexed": False, "internalType": "uint256", "name": "value", "type": "uint256"}, ], "name": "Debug", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}, {"indexed": False, "internalType": "uint256", "name": "challengeEpoch", "type": "uint256"}, - {"indexed": False, "internalType": "uint256", "name": "leafCount", "type": "uint256"} + {"indexed": False, "internalType": "uint256", "name": "leafCount", "type": "uint256"}, ], "name": "NextProvingPeriod", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "address", "name": "previousOwner", "type": "address"}, - {"indexed": True, "internalType": "address", "name": "newOwner", "type": "address"} + {"indexed": True, "internalType": "address", "name": "newOwner", "type": "address"}, ], "name": "OwnershipTransferred", - "type": "event" + "type": "event", }, { "anonymous": False, @@ -91,16 +91,16 @@ class PDPVerifierMetaData: { "components": [ {"internalType": "uint256", "name": "rootId", "type": "uint256"}, - {"internalType": "uint256", "name": "offset", "type": "uint256"} + {"internalType": "uint256", "name": "offset", "type": "uint256"}, ], "indexed": False, "internalType": "struct PDPVerifier.RootIdAndOffset[]", "name": "challenges", - "type": "tuple[]" - } + "type": "tuple[]", + }, ], "name": "PossessionProven", - "type": "event" + "type": "event", }, { "anonymous": False, @@ -108,120 +108,118 @@ class PDPVerifierMetaData: {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}, {"indexed": False, "internalType": "uint256", "name": "fee", "type": "uint256"}, {"indexed": False, "internalType": "uint64", "name": "price", "type": "uint64"}, - {"indexed": False, "internalType": "int32", "name": "expo", "type": "int32"} + {"indexed": False, "internalType": "int32", "name": "expo", "type": "int32"}, ], "name": "ProofFeePaid", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}, - {"indexed": True, "internalType": "address", "name": "owner", "type": "address"} + {"indexed": True, "internalType": "address", "name": "owner", "type": "address"}, ], "name": "ProofSetCreated", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}, - {"indexed": False, "internalType": "uint256", "name": "deletedLeafCount", "type": "uint256"} + {"indexed": False, "internalType": "uint256", "name": "deletedLeafCount", "type": "uint256"}, ], "name": "ProofSetDeleted", - "type": "event" + "type": "event", }, { "anonymous": False, - "inputs": [ - {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"} - ], + "inputs": [{"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "ProofSetEmpty", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}, {"indexed": True, "internalType": "address", "name": "oldOwner", "type": "address"}, - {"indexed": True, "internalType": "address", "name": "newOwner", "type": "address"} + {"indexed": True, "internalType": "address", "name": "newOwner", "type": "address"}, ], "name": "ProofSetOwnerChanged", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}, - {"indexed": False, "internalType": "uint256[]", "name": "rootIds", "type": "uint256[]"} + {"indexed": False, "internalType": "uint256[]", "name": "rootIds", "type": "uint256[]"}, ], "name": "RootsAdded", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "uint256", "name": "setId", "type": "uint256"}, - {"indexed": False, "internalType": "uint256[]", "name": "rootIds", "type": "uint256[]"} + {"indexed": False, "internalType": "uint256[]", "name": "rootIds", "type": "uint256[]"}, ], "name": "RootsRemoved", - "type": "event" + "type": "event", }, { "inputs": [], "name": "EXTRA_DATA_MAX_SIZE", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "LEAF_SIZE", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "MAX_ENQUEUED_REMOVALS", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "MAX_ROOT_SIZE", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "NO_CHALLENGE_SCHEDULED", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "NO_PROVEN_EPOCH", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "RANDOMNESS_PRECOMPILE", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "SECONDS_IN_DAY", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ @@ -229,267 +227,261 @@ class PDPVerifierMetaData: { "components": [ { - "components": [ - {"internalType": "bytes", "name": "data", "type": "bytes"} - ], + "components": [{"internalType": "bytes", "name": "data", "type": "bytes"}], "internalType": "struct Cids.Cid", "name": "root", - "type": "tuple" + "type": "tuple", }, - {"internalType": "uint256", "name": "rawSize", "type": "uint256"} + {"internalType": "uint256", "name": "rawSize", "type": "uint256"}, ], "internalType": "struct PDPVerifier.RootData[]", "name": "rootData", - "type": "tuple[]" + "type": "tuple[]", }, - {"internalType": "bytes", "name": "extraData", "type": "bytes"} + {"internalType": "bytes", "name": "extraData", "type": "bytes"}, ], "name": "addRoots", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "claimProofSetOwnership", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "address", "name": "listenerAddr", "type": "address"}, - {"internalType": "bytes", "name": "extraData", "type": "bytes"} + {"internalType": "bytes", "name": "extraData", "type": "bytes"}, ], "name": "createProofSet", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "payable", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "bytes", "name": "extraData", "type": "bytes"} + {"internalType": "bytes", "name": "extraData", "type": "bytes"}, ], "name": "deleteProofSet", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "uint256[]", "name": "leafIndexs", "type": "uint256[]"} + {"internalType": "uint256[]", "name": "leafIndexs", "type": "uint256[]"}, ], "name": "findRootIds", "outputs": [ { "components": [ {"internalType": "uint256", "name": "rootId", "type": "uint256"}, - {"internalType": "uint256", "name": "offset", "type": "uint256"} + {"internalType": "uint256", "name": "offset", "type": "uint256"}, ], "internalType": "struct PDPVerifier.RootIdAndOffset[]", "name": "", - "type": "tuple[]" + "type": "tuple[]", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "getChallengeFinality", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getChallengeRange", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getNextChallengeEpoch", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "getNextProofSetId", "outputs": [{"internalType": "uint64", "name": "", "type": "uint64"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getNextRootId", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getProofSetLastProvenEpoch", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getProofSetLeafCount", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getProofSetListener", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getProofSetOwner", "outputs": [ {"internalType": "address", "name": "", "type": "address"}, - {"internalType": "address", "name": "", "type": "address"} + {"internalType": "address", "name": "", "type": "address"}, ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "epoch", "type": "uint256"}], "name": "getRandomness", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "uint256", "name": "rootId", "type": "uint256"} + {"internalType": "uint256", "name": "rootId", "type": "uint256"}, ], "name": "getRootCid", "outputs": [ { - "components": [ - {"internalType": "bytes", "name": "data", "type": "bytes"} - ], + "components": [{"internalType": "bytes", "name": "data", "type": "bytes"}], "internalType": "struct Cids.Cid", "name": "", - "type": "tuple" + "type": "tuple", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "uint256", "name": "rootId", "type": "uint256"} + {"internalType": "uint256", "name": "rootId", "type": "uint256"}, ], "name": "getRootLeafCount", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "getScheduledRemovals", "outputs": [{"internalType": "uint256[]", "name": "", "type": "uint256[]"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "uint256", "name": "rootId", "type": "uint256"} + {"internalType": "uint256", "name": "rootId", "type": "uint256"}, ], "name": "getSumTreeCounts", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "index", "type": "uint256"}], "name": "heightFromIndex", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "pure", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "heightOfTree", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "bytes32[][]", "name": "tree", "type": "bytes32[][]"}, - {"internalType": "uint256", "name": "leafCount", "type": "uint256"} + {"internalType": "uint256", "name": "leafCount", "type": "uint256"}, ], "name": "makeRoot", "outputs": [ { "components": [ { - "components": [ - {"internalType": "bytes", "name": "data", "type": "bytes"} - ], + "components": [{"internalType": "bytes", "name": "data", "type": "bytes"}], "internalType": "struct Cids.Cid", "name": "root", - "type": "tuple" + "type": "tuple", }, - {"internalType": "uint256", "name": "rawSize", "type": "uint256"} + {"internalType": "uint256", "name": "rawSize", "type": "uint256"}, ], "internalType": "struct PDPVerifier.RootData", "name": "", - "type": "tuple" + "type": "tuple", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, {"internalType": "uint256", "name": "challengeEpoch", "type": "uint256"}, - {"internalType": "bytes", "name": "extraData", "type": "bytes"} + {"internalType": "bytes", "name": "extraData", "type": "bytes"}, ], "name": "nextProvingPeriod", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [], "name": "owner", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "uint256", "name": "setId", "type": "uint256"}], "name": "proofSetLive", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "address", "name": "newOwner", "type": "address"} + {"internalType": "address", "name": "newOwner", "type": "address"}, ], "name": "proposeProofSetOwner", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ @@ -497,73 +489,67 @@ class PDPVerifierMetaData: { "components": [ {"internalType": "bytes32", "name": "leaf", "type": "bytes32"}, - {"internalType": "bytes32[]", "name": "proof", "type": "bytes32[]"} + {"internalType": "bytes32[]", "name": "proof", "type": "bytes32[]"}, ], "internalType": "struct PDPVerifier.Proof[]", "name": "proofs", - "type": "tuple[]" - } + "type": "tuple[]", + }, ], "name": "provePossession", "outputs": [], "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, + {"inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "uint256", "name": "rootId", "type": "uint256"} + {"internalType": "uint256", "name": "rootId", "type": "uint256"}, ], "name": "rootChallengable", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, - {"internalType": "uint256", "name": "rootId", "type": "uint256"} + {"internalType": "uint256", "name": "rootId", "type": "uint256"}, ], "name": "rootLive", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "setId", "type": "uint256"}, {"internalType": "uint256[]", "name": "rootIds", "type": "uint256[]"}, - {"internalType": "bytes", "name": "extraData", "type": "bytes"} + {"internalType": "bytes", "name": "extraData", "type": "bytes"}, ], "name": "scheduleRemovals", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ {"internalType": "uint256", "name": "", "type": "uint256"}, - {"internalType": "uint256", "name": "", "type": "uint256"} + {"internalType": "uint256", "name": "", "type": "uint256"}, ], "name": "sumTreeCounts", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [{"internalType": "address", "name": "newOwner", "type": "address"}], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", - "type": "function" - } + "type": "function", + }, ] BIN = "0x6080604052348015600e575f5ffd5b50604051613bc9380380613bc9833981016040819052602b9160ad565b3380604f57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b605681605e565b5060015560c3565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f6020828403121560bc575f5ffd5b5051919050565b613af9806100d05f395ff3fe60806040526004361061023e575f3560e01c806371cf2a16116101345780639f8cb3bd116100b3578063f178b1be11610078578063f178b1be146103db578063f2fde38b146106ec578063f58f952b1461070b578063f5cac1ba1461071e578063f83758fe1461073d578063faa6716314610751575f5ffd5b80639f8cb3bd1461064f578063c0e1594914610664578063cbb147cd14610678578063d49245c1146106ae578063ee3dac65146106cd575f5ffd5b80638d101c68116100f95780638d101c68146105985780638da5cb5b146105b75780638ea417e5146105d35780639153e64b146105fa5780639a9a330f14610619575f5ffd5b806371cf2a16146104f0578063831604e11461050f578063847d1d061461053b578063869888841461055a57806389208ba914610579575f5ffd5b8063453f4f62116101c057806361a52a361161018557806361a52a361461045c5780636ba4608f146104725780636cb55c16146104915780636fa44692146104b0578063715018a6146104dc575f5ffd5b8063453f4f621461039f57806345c0b92d146103bc578063462dd449146103db5780634726075b146103ee578063473310501461042d575f5ffd5b806316e2bcd51161020657806316e2bcd5146102fa57806331601226146103145780633b68e4e9146103335780633b7ae913146103545780633f84135f14610380575f5ffd5b8063029b4646146102425780630528a55b1461026a5780630a4d79321461029657806311c0ee4a146102a957806315b17570146102c8575b5f5ffd5b34801561024d575f5ffd5b5061025761080081565b6040519081526020015b60405180910390f35b348015610275575f5ffd5b50610289610284366004613022565b610770565b6040516102619190613069565b6102576102a436600461310f565b610857565b3480156102b4575f5ffd5b506102576102c3366004613150565b610a47565b3480156102d3575f5ffd5b506102e26006607f60991b0181565b6040516001600160a01b039091168152602001610261565b348015610305575f5ffd5b50610257660400000000000081565b34801561031f575f5ffd5b506102e261032e3660046131c7565b610cf5565b34801561033e575f5ffd5b5061035261034d366004613150565b610d36565b005b34801561035f575f5ffd5b5061037361036e3660046131de565b610fe9565b6040516102619190613236565b34801561038b575f5ffd5b5061025761039a3660046131c7565b6110d9565b3480156103aa575f5ffd5b506102576103b93660046131c7565b90565b3480156103c7575f5ffd5b506103526103d6366004613248565b611111565b3480156103e6575f5ffd5b506102575f81565b3480156103f9575f5ffd5b5061040d6104083660046131c7565b61150c565b604080516001600160a01b03938416815292909116602083015201610261565b348015610438575f5ffd5b5061044c6104473660046131de565b611560565b6040519015158152602001610261565b348015610467575f5ffd5b506102576201518081565b34801561047d575f5ffd5b5061025761048c3660046131c7565b6115a8565b34801561049c575f5ffd5b506103526104ab366004613296565b6115e0565b3480156104bb575f5ffd5b506104cf6104ca3660046131c7565b6116e6565b60405161026191906132c0565b3480156104e7575f5ffd5b506103526117b5565b3480156104fb575f5ffd5b5061044c61050a3660046131de565b6117c8565b34801561051a575f5ffd5b5061052e61052936600461335d565b6118da565b6040516102619190613476565b348015610546575f5ffd5b506103526105553660046134a7565b611976565b348015610565575f5ffd5b506102576105743660046131c7565b611b5b565b348015610584575f5ffd5b506102576105933660046131c7565b611b6f565b3480156105a3575f5ffd5b506102576105b23660046131c7565b611ba7565b3480156105c2575f5ffd5b505f546001600160a01b03166102e2565b3480156105de575f5ffd5b506002546040516001600160401b039091168152602001610261565b348015610605575f5ffd5b506102576106143660046131de565b611bca565b348015610624575f5ffd5b506102576106333660046131de565b600560209081525f928352604080842090915290825290205481565b34801561065a575f5ffd5b506102576107d081565b34801561066f575f5ffd5b50610257602081565b348015610683575f5ffd5b506102576106923660046131de565b5f91825260056020908152604080842092845291905290205490565b3480156106b9575f5ffd5b506102576106c83660046131c7565b611c0d565b3480156106d8575f5ffd5b506103526106e73660046131c7565b611c45565b3480156106f7575f5ffd5b506103526107063660046134d5565b611d50565b610352610719366004613022565b611d8d565b348015610729575f5ffd5b5061044c6107383660046131c7565b612269565b348015610748575f5ffd5b50600154610257565b34801561075c575f5ffd5b5061025761076b3660046131c7565b61229d565b5f838152600660205260408120546060919061078b906122d5565b61079790610100613502565b90505f836001600160401b038111156107b2576107b26132f7565b6040519080825280602002602001820160405280156107f657816020015b604080518082019091525f80825260208201528152602001906001900390816107d05790505b5090505f5b8481101561084b576108268787878481811061081957610819613515565b90506020020135856123cc565b82828151811061083857610838613515565b60209081029190910101526001016107fb565b509150505b9392505050565b5f6108008211156108835760405162461bcd60e51b815260040161087a90613529565b60405180910390fd5b5f61088c6125ac565b9050803410156108d25760405162461bcd60e51b81526020600482015260116024820152701cde589a5b08199959481b9bdd081b595d607a1b604482015260640161087a565b8034111561090f57336108fc6108e88334613502565b6040518115909202915f818181858888f1935050505015801561090d573d5f5f3e3d5ffd5b505b600280545f916001600160401b03909116908261092b83613557565b82546001600160401b039182166101009390930a928302928202191691909117909155165f81815260076020908152604080832083905560088252808320839055600c825280832080546001600160a01b031990811633179091556009835281842080546001600160a01b038d16921682179055600e90925282209190915590915015610a1257604051634a6a0d9b60e11b81526001600160a01b038716906394d41b36906109e490849033908a908a906004016135a9565b5f604051808303815f87803b1580156109fb575f5ffd5b505af1158015610a0d573d5f5f3e3d5ffd5b505050505b604051339082907f017f0b33d96e8f9968590172013032c2346cf047787a5e17a44b0a1bb3cd0f01905f90a395945050505050565b5f610800821115610a6a5760405162461bcd60e51b815260040161087a90613529565b610a7386612269565b610a8f5760405162461bcd60e51b815260040161087a906135dd565b83610adc5760405162461bcd60e51b815260206004820152601a60248201527f4d75737420616464206174206c65617374206f6e6520726f6f74000000000000604482015260640161087a565b5f868152600c60205260409020546001600160a01b03163314610b415760405162461bcd60e51b815260206004820152601c60248201527f4f6e6c7920746865206f776e65722063616e2061646420726f6f747300000000604482015260640161087a565b5f8681526006602052604081205490856001600160401b03811115610b6857610b686132f7565b604051908082528060200260200182016040528015610b91578160200160208202803683370190505b5090505f5b86811015610c2e57610bfe89828a8a85818110610bb557610bb5613515565b9050602002810190610bc79190613609565b610bd19080613627565b8b8b86818110610be357610be3613515565b9050602002810190610bf59190613609565b602001356125d1565b50610c09818461363b565b828281518110610c1b57610c1b613515565b6020908102919091010152600101610b96565b50877f5ce51a8003915c377679ba533d9dafa0792058b254965697e674272f13f4fdd382604051610c5f91906132c0565b60405180910390a25f888152600960205260409020546001600160a01b03168015610ce8576040516312d5d66f60e01b81526001600160a01b038216906312d5d66f90610cba908c9087908d908d908d908d9060040161364e565b5f604051808303815f87803b158015610cd1575f5ffd5b505af1158015610ce3573d5f5f3e3d5ffd5b505050505b5090979650505050505050565b5f610cff82612269565b610d1b5760405162461bcd60e51b815260040161087a906135dd565b505f908152600960205260409020546001600160a01b031690565b610800811115610d585760405162461bcd60e51b815260040161087a90613529565b610d6185612269565b610d7d5760405162461bcd60e51b815260040161087a906135dd565b5f858152600c60205260409020546001600160a01b03163314610df75760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f776e65722063616e207363686564756c652072656d6f60448201526b76616c206f6620726f6f747360a01b606482015260840161087a565b5f858152600b60205260409020546107d090610e13908561363b565b1115610e875760405162461bcd60e51b815260206004820152603a60248201527f546f6f206d616e792072656d6f76616c73207761697420666f72206e6578742060448201527f70726f76696e6720706572696f6420746f207363686564756c65000000000000606482015260840161087a565b5f5b83811015610f61575f86815260066020526040902054858583818110610eb157610eb1613515565b9050602002013510610f195760405162461bcd60e51b815260206004820152602b60248201527f43616e206f6e6c79207363686564756c652072656d6f76616c206f662065786960448201526a7374696e6720726f6f747360a81b606482015260840161087a565b5f868152600b60205260409020858583818110610f3857610f38613515565b8354600180820186555f9586526020958690209290950293909301359201919091555001610e89565b505f858152600960205260409020546001600160a01b03168015610fe15760405163257be8e960e11b81526001600160a01b03821690634af7d1d290610fb39089908990899089908990600401613755565b5f604051808303815f87803b158015610fca575f5ffd5b505af1158015610fdc573d5f5f3e3d5ffd5b505050505b505050505050565b60408051602081019091526060815261100183612269565b61101d5760405162461bcd60e51b815260040161087a906135dd565b5f8381526003602090815260408083208584528252918290208251918201909252815490919082908290611050906137ad565b80601f016020809104026020016040519081016040528092919081815260200182805461107c906137ad565b80156110c75780601f1061109e576101008083540402835291602001916110c7565b820191905f5260205f20905b8154815290600101906020018083116110aa57829003601f168201915b50505050508152505090505b92915050565b5f6110e382612269565b6110ff5760405162461bcd60e51b815260040161087a906135dd565b505f9081526007602052604090205490565b6108008111156111335760405162461bcd60e51b815260040161087a90613529565b5f848152600c60205260409020546001600160a01b031633146111685760405162461bcd60e51b815260040161087a906137e5565b5f848152600760205260409020546111d75760405162461bcd60e51b815260206004820152602c60248201527f63616e206f6e6c792073746172742070726f76696e67206f6e6365206c65617660448201526b195cc8185c9948185919195960a21b606482015260840161087a565b5f848152600e60205260409020546111fa575f848152600e602052604090204390555b5f848152600b6020526040812080549091906001600160401b03811115611223576112236132f7565b60405190808252806020026020018201604052801561124c578160200160208202803683370190505b5090505f5b81518110156112cf578254839061126a90600190613502565b8154811061127a5761127a613515565b905f5260205f20015482828151811061129557611295613515565b602002602001018181525050828054806112b1576112b1613833565b5f8281526020812082015f1990810191909155019055600101611251565b506112da8682612794565b857fd22bb0ee05b8ca92312459c76223d3b9bc1bd96fb6c9b18e637ededf92d811748260405161130a91906132c0565b60405180910390a25f86815260076020908152604080832054600a90925290912055600154611339904361363b565b8510156113be5760405162461bcd60e51b815260206004820152604760248201527f6368616c6c656e67652065706f6368206d757374206265206174206c6561737460448201527f206368616c6c656e676546696e616c6974792065706f63687320696e207468656064820152662066757475726560c81b608482015260a40161087a565b5f868152600860209081526040808320889055600790915281205490036114255760405186907f323c29bc8d678a5d987b90a321982d10b9a91bcad071a9e445879497bf0e68e7905f90a25f868152600e6020908152604080832083905560089091528120555b5f868152600960205260409020546001600160a01b031680156114bb575f87815260086020908152604080832054600790925291829020549151632a89faf360e21b81526001600160a01b0384169263aa27ebcc9261148d928c92908b908b90600401613847565b5f604051808303815f87803b1580156114a4575f5ffd5b505af11580156114b6573d5f5f3e3d5ffd5b505050505b5f878152600760209081526040918290205482518981529182015288917fc099ffec4e3e773644a4d1dda368c46af853a0eeb15babde217f53a657396e1e910160405180910390a250505050505050565b5f5f61151783612269565b6115335760405162461bcd60e51b815260040161087a906135dd565b50505f908152600c6020908152604080832054600d909252909120546001600160a01b0391821692911690565b5f61156a83612269565b801561158257505f8381526006602052604090205482105b80156108505750505f918252600460209081526040808420928452919052902054151590565b5f6115b282612269565b6115ce5760405162461bcd60e51b815260040161087a906135dd565b505f9081526008602052604090205490565b6115e982612269565b6116055760405162461bcd60e51b815260040161087a906135dd565b5f828152600c60205260409020546001600160a01b03163381146116825760405162461bcd60e51b815260206004820152602e60248201527f4f6e6c79207468652063757272656e74206f776e65722063616e2070726f706f60448201526d39b29030903732bb9037bbb732b960911b606482015260840161087a565b816001600160a01b0316816001600160a01b0316036116ba5750505f908152600d6020526040902080546001600160a01b0319169055565b5f838152600d6020526040902080546001600160a01b0319166001600160a01b0384161790555b505050565b60606116f182612269565b61170d5760405162461bcd60e51b815260040161087a906135dd565b5f828152600b6020526040812080549091906001600160401b03811115611736576117366132f7565b60405190808252806020026020018201604052801561175f578160200160208202803683370190505b5090505f5b82548110156117ad5782818154811061177f5761177f613515565b905f5260205f20015482828151811061179a5761179a613515565b6020908102919091010152600101611764565b509392505050565b6117bd612824565b6117c65f612850565b565b5f8281526006602052604081205481906117e1906122d5565b6117ed90610100613502565b5f858152600a60205260408120549192509061181790869061181190600190613502565b846123cc565b5f8681526004602090815260408083208451845290915290205490915061184090600190613502565b8160200151146118ba576040805162461bcd60e51b81526020600482015260248101919091527f6368616c6c656e676552616e6765202d312073686f756c6420616c69676e207760448201527f697468207468652076657279206c617374206c656166206f66206120726f6f74606482015260840161087a565b6118c48585611560565b80156118d1575080518411155b95945050505050565b60408051606080820183529181019182529081525f60208201525f6040518060400160405280600381526020016210d25160ea1b8152509050604051806040016040528061195a83875f8151811061193457611934613515565b60200260200101515f8151811061194d5761194d613515565b602002602001015161289f565b815260200184602061196c9190613877565b9052949350505050565b6108008111156119985760405162461bcd60e51b815260040161087a90613529565b6002546001600160401b031683106119f25760405162461bcd60e51b815260206004820152601a60248201527f70726f6f6620736574206964206f7574206f6620626f756e6473000000000000604482015260640161087a565b5f838152600c60205260409020546001600160a01b03163314611a635760405162461bcd60e51b8152602060048201526024808201527f4f6e6c7920746865206f776e65722063616e2064656c6574652070726f6f66206044820152637365747360e01b606482015260840161087a565b5f838152600760209081526040808320805490849055600c835281842080546001600160a01b031916905560088352818420849055600e83528184208490556009909252909120546001600160a01b03168015611b1a576040516326c249e360e01b81526001600160a01b038216906326c249e390611aec90889086908990899060040161388e565b5f604051808303815f87803b158015611b03575f5ffd5b505af1158015611b15573d5f5f3e3d5ffd5b505050505b847f589e9a441b5bddda77c4ab647b0108764a9cc1a7f655aa9b7bc50b8bdfab867383604051611b4c91815260200190565b60405180910390a25050505050565b5f6110d3611b6a83600161363b565b6129c5565b5f611b7982612269565b611b955760405162461bcd60e51b815260040161087a906135dd565b505f908152600a602052604090205490565b5f81815260066020526040812054611bbe906122d5565b6110d390610100613502565b5f611bd483612269565b611bf05760405162461bcd60e51b815260040161087a906135dd565b505f91825260046020908152604080842092845291905290205490565b5f611c1782612269565b611c335760405162461bcd60e51b815260040161087a906135dd565b505f9081526006602052604090205490565b611c4e81612269565b611c6a5760405162461bcd60e51b815260040161087a906135dd565b5f818152600d60205260409020546001600160a01b03163314611ce35760405162461bcd60e51b815260206004820152602b60248201527f4f6e6c79207468652070726f706f736564206f776e65722063616e20636c616960448201526a06d206f776e6572736869760ac1b606482015260840161087a565b5f818152600c602090815260408083208054336001600160a01b03198083168217909355600d909452828520805490921690915590516001600160a01b0390911692839185917fd3273037b635678293ef0c076bd77af13760e75e12806d1db237616d03c3a76691a45050565b611d58612824565b6001600160a01b038116611d8157604051631e4fbdf760e01b81525f600482015260240161087a565b611d8a81612850565b50565b5f5a5f858152600c60205260409020549091506001600160a01b03163314611dc75760405162461bcd60e51b815260040161087a906137e5565b5f8481526008602052604090205443811115611e175760405162461bcd60e51b815260206004820152600f60248201526e383932b6b0ba3ab93290383937b7b360891b604482015260640161087a565b82611e525760405162461bcd60e51b815260206004820152600b60248201526a32b6b83a3c90383937b7b360a91b604482015260640161087a565b80611e985760405162461bcd60e51b81526020600482015260166024820152751b9bc818da185b1b195b99d9481cd8da19591d5b195960521b604482015260640161087a565b5f836001600160401b03811115611eb157611eb16132f7565b604051908082528060200260200182016040528015611ef557816020015b604080518082019091525f8082526020820152815260200190600190039081611ecf5790505b5090505f611f0287612be5565b5f888152600a6020908152604080832054600690925282205492935091611f28906122d5565b611f3490610100613502565b90505f5b6001600160401b03811688111561213f5760408051602081018690529081018b90526001600160c01b031960c083901b1660608201525f9060680160405160208183030381529060405290505f8482805190602001205f1c611f9a91906138c1565b9050611fa78c82866123cc565b87846001600160401b031681518110611fc257611fc2613515565b60200260200101819052505f6120056120008e8a876001600160401b031681518110611ff057611ff0613515565b60200260200101515f0151610fe9565b612bf8565b90505f6120e28d8d876001600160401b031681811061202657612026613515565b90506020028101906120389190613609565b6120469060208101906138d4565b808060200260200160405190810160405280939291908181526020018383602002808284375f81840152601f19601f82011690508083019250505050505050838f8f896001600160401b03168181106120a1576120a1613515565b90506020028101906120b39190613609565b5f01358c896001600160401b0316815181106120d1576120d1613515565b602002602001015160200151612ce2565b9050806121285760405162461bcd60e51b815260206004820152601460248201527370726f6f6620646964206e6f742076657269667960601b604482015260640161087a565b50505050808061213790613557565b915050611f38565b505f61214b8989612cf9565b61215690602061363b565b61216290610514613877565b5a61216d9089613502565b612177919061363b565b5f8b8152600960205260409020549091506001600160a01b03168015612212575f8b8152600760205260409081902054905163356de02b60e01b8152600481018d9052602481019190915260448101869052606481018a90526001600160a01b0382169063356de02b906084015f604051808303815f87803b1580156121fb575f5ffd5b505af115801561220d573d5f5f3e3d5ffd5b505050505b5f8b8152600e602052604090819020439055518b907f1acf7df9f0c1b0208c23be6178950c0273f89b766805a2c0bd1e53d25c700e5090612254908990613069565b60405180910390a25050505050505050505050565b6002545f906001600160401b0316821080156110d35750505f908152600c60205260409020546001600160a01b0316151590565b5f6122a782612269565b6122c35760405162461bcd60e51b815260040161087a906135dd565b505f908152600e602052604090205490565b5f610100608083901c80156122f5576122ef608083613502565b91508093505b50604083901c80156123125761230c604083613502565b91508093505b50602083901c801561232f57612329602083613502565b91508093505b50601083901c801561234c57612346601083613502565b91508093505b50600883901c801561236957612363600883613502565b91508093505b50600483901c801561238657612380600483613502565b91508093505b50600283901c80156123a35761239d600283613502565b91508093505b50600183901c80156123c2576123ba600283613502565b949350505050565b6123ba8483613502565b604080518082019091525f80825260208201525f84815260076020526040902054831061243b5760405162461bcd60e51b815260206004820152601860248201527f4c65616620696e646578206f7574206f6620626f756e64730000000000000000604482015260640161087a565b5f612449600180851b613502565b90505f80845b8015612521575f88815260066020526040902054841061248957612474600182613502565b612482906001901b85613502565b935061250f565b5f8881526005602090815260408083208784529091529020546124ac908461363b565b91508682116124f3575f8881526005602090815260408083208784529091529020546124d8908461363b565b92506124e5600182613502565b612482906001901b8561363b565b6124fe600182613502565b61250c906001901b85613502565b93505b8061251981613919565b91505061244f565b505f878152600560209081526040808320868452909152902054612545908361363b565b9050858111612582576040518060400160405280846001612566919061363b565b81526020016125758389613502565b8152509350505050610850565b6040518060400160405280848152602001838861259f9190613502565b9052979650505050505050565b5f600a6125c26001670de0b6b3a7640000613877565b6125cc919061392e565b905090565b5f6125dd6020836138c1565b15612634578360405163c7b67cf360e01b815260040161087a918152604060208201819052601d908201527f53697a65206d7573742062652061206d756c7469706c65206f66203332000000606082015260800190565b815f0361268d578360405163c7b67cf360e01b815260040161087a918152604060208201819052601b908201527f53697a65206d7573742062652067726561746572207468616e20300000000000606082015260800190565b66040000000000008211156126ed578360405163c7b67cf360e01b815260040161087a91815260406020808301829052908201527f526f6f742073697a65206d757374206265206c657373207468616e20325e3530606082015260800190565b5f6126f960208461392e565b5f87815260066020526040812080549293509091908261271883613941565b919050559050612729878383612d62565b5f8781526003602090815260408083208484529091529020859061274d828261399d565b50505f878152600460209081526040808320848452825280832085905589835260079091528120805484929061278490849061363b565b9091555090979650505050505050565b61279d82612269565b6127b95760405162461bcd60e51b815260040161087a906135dd565b5f805b82518110156127fc576127e8848483815181106127db576127db613515565b6020026020010151612ddd565b6127f2908361363b565b91506001016127bc565b505f838152600760205260408120805483929061281a908490613502565b9091555050505050565b5f546001600160a01b031633146117c65760405163118cdaa760e01b815233600482015260240161087a565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6040805160208101909152606081525f835160206128bd919061363b565b6001600160401b038111156128d4576128d46132f7565b6040519080825280601f01601f1916602001820160405280156128fe576020820181803683370190505b5090505f5b84518110156129595784818151811061291e5761291e613515565b602001015160f81c60f81b82828151811061293b5761293b613515565b60200101906001600160f81b03191690815f1a905350600101612903565b505f5b60208110156129af57612970816008613877565b84901b82865183612981919061363b565b8151811061299157612991613515565b60200101906001600160f81b03191690815f1a90535060010161295c565b5060408051602081019091529081529392505050565b5f6001600160ff1b03821115612a285760405162461bcd60e51b815260206004820152602260248201527f496e7075742065786365656473206d6178696d756d20696e743235362076616c604482015261756560f01b606482015260840161087a565b6101005f612a3584613a86565b841690508015612a4d5781612a4981613919565b9250505b6fffffffffffffffffffffffffffffffff811615612a7357612a70608083613502565b91505b77ffffffffffffffff0000000000000000ffffffffffffffff811615612aa157612a9e604083613502565b91505b7bffffffff00000000ffffffff00000000ffffffff00000000ffffffff811615612ad357612ad0602083613502565b91505b7dffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff811615612b0757612b04601083613502565b91505b7eff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff811615612b3c57612b39600883613502565b91505b7f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f811615612b7257612b6f600483613502565b91505b7f3333333333333333333333333333333333333333333333333333333333333333811615612ba857612ba5600283613502565b91505b7f5555555555555555555555555555555555555555555555555555555555555555811615612bde57612bdb600183613502565b91505b5092915050565b5f818152600860205260408120546110d3565b5f6020825f0151511015612c465760405162461bcd60e51b815260206004820152601560248201527410da590819185d18481a5cc81d1bdbc81cda1bdc9d605a1b604482015260640161087a565b6040805160208082528183019092525f916020820181803683370190505090505f5b6020811015612cd857835180518290612c8390602090613502565b612c8d919061363b565b81518110612c9d57612c9d613515565b602001015160f81c60f81b828281518110612cba57612cba613515565b60200101906001600160f81b03191690815f1a905350600101612c68565b5061085081613aa0565b5f83612cef868585612e43565b1495945050505050565b5f80805b838110156117ad57848482818110612d1757612d17613515565b9050602002810190612d299190613609565b612d379060208101906138d4565b612d4391506020613877565b612d4e90604061363b565b612d58908361363b565b9150600101612cfd565b805f612d6d82611b5b565b9050835f5b82811015612db9575f612d886001831b86613502565b5f898152600560209081526040808320848452909152902054909150612dae908461363b565b925050600101612d72565b505f9586526005602090815260408088209588529490529290942091909155505050565b5f828152600460209081526040808320848452909152812054612e01848483612ebd565b5f848152600460209081526040808320868452825280832083905586835260038252808320868452909152812090612e398282612f91565b5090949350505050565b5f82815b8551811015612eb4575f868281518110612e6357612e63613515565b60200260200101519050600285612e7a91906138c1565b5f03612e9157612e8a8382612f64565b9250612e9e565b612e9b8184612f64565b92505b612ea960028661392e565b945050600101612e47565b50949350505050565b5f83815260066020526040812054612ed4906122d5565b612ee090610100613502565b90505f612eec84611b5b565b90505b818111158015612f0b57505f8581526006602052604090205484105b15612f5d575f85815260056020908152604080832087845290915281208054859290612f38908490613502565b90915550612f4b90506001821b8561363b565b9350612f5684611b5b565b9050612eef565b5050505050565b5f61085083835f825f528160205260205f60405f60025afa612f84575f5ffd5b50505f5160c01916919050565b508054612f9d906137ad565b5f825580601f10612fac575050565b601f0160209004905f5260205f2090810190611d8a91905b80821115612fd7575f8155600101612fc4565b5090565b5f5f83601f840112612feb575f5ffd5b5081356001600160401b03811115613001575f5ffd5b6020830191508360208260051b850101111561301b575f5ffd5b9250929050565b5f5f5f60408486031215613034575f5ffd5b8335925060208401356001600160401b03811115613050575f5ffd5b61305c86828701612fdb565b9497909650939450505050565b602080825282518282018190525f918401906040840190835b818110156130ac578351805184526020908101518185015290930192604090920191600101613082565b509095945050505050565b80356001600160a01b03811681146130cd575f5ffd5b919050565b5f5f83601f8401126130e2575f5ffd5b5081356001600160401b038111156130f8575f5ffd5b60208301915083602082850101111561301b575f5ffd5b5f5f5f60408486031215613121575f5ffd5b61312a846130b7565b925060208401356001600160401b03811115613144575f5ffd5b61305c868287016130d2565b5f5f5f5f5f60608688031215613164575f5ffd5b8535945060208601356001600160401b03811115613180575f5ffd5b61318c88828901612fdb565b90955093505060408601356001600160401b038111156131aa575f5ffd5b6131b6888289016130d2565b969995985093965092949392505050565b5f602082840312156131d7575f5ffd5b5035919050565b5f5f604083850312156131ef575f5ffd5b50508035926020909101359150565b5f81516020845280518060208601528060208301604087015e5f604082870101526040601f19601f8301168601019250505092915050565b602081525f61085060208301846131fe565b5f5f5f5f6060858703121561325b575f5ffd5b843593506020850135925060408501356001600160401b0381111561327e575f5ffd5b61328a878288016130d2565b95989497509550505050565b5f5f604083850312156132a7575f5ffd5b823591506132b7602084016130b7565b90509250929050565b602080825282518282018190525f918401906040840190835b818110156130ac5783518352602093840193909201916001016132d9565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f191681016001600160401b0381118282101715613333576133336132f7565b604052919050565b5f6001600160401b03821115613353576133536132f7565b5060051b60200190565b5f5f6040838503121561336e575f5ffd5b82356001600160401b03811115613383575f5ffd5b8301601f81018513613393575f5ffd5b80356133a66133a18261333b565b61330b565b8082825260208201915060208360051b8501019250878311156133c7575f5ffd5b602084015b838110156134645780356001600160401b038111156133e9575f5ffd5b8501603f81018a136133f9575f5ffd5b602081013561340a6133a18261333b565b808282526020820191506020808460051b8601010192508c83111561342d575f5ffd5b6040840193505b8284101561344f578335825260209384019390910190613434565b865250506020938401939190910190506133cc565b50976020969096013596505050505050565b602081525f82516040602084015261349160608401826131fe565b9050602084015160408401528091505092915050565b5f5f5f604084860312156134b9575f5ffd5b8335925060208401356001600160401b03811115613144575f5ffd5b5f602082840312156134e5575f5ffd5b610850826130b7565b634e487b7160e01b5f52601160045260245ffd5b818103818111156110d3576110d36134ee565b634e487b7160e01b5f52603260045260245ffd5b6020808252601490820152734578747261206461746120746f6f206c6172676560601b604082015260600190565b5f6001600160401b0382166001600160401b038103613578576135786134ee565b60010192915050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b8481526001600160a01b03841660208201526060604082018190525f906135d39083018486613581565b9695505050505050565b60208082526012908201527150726f6f6620736574206e6f74206c69766560701b604082015260600190565b5f8235603e1983360301811261361d575f5ffd5b9190910192915050565b5f8235601e1983360301811261361d575f5ffd5b808201808211156110d3576110d36134ee565b5f60808201888352876020840152608060408401528086825260a08401905060a08760051b8501019150875f603e198a3603015b8982101561373157868503609f1901845282358181126136a0575f5ffd5b8b01803536829003601e190181126136b6575f5ffd5b604087528101803536829003601e190181126136d0575f5ffd5b016020810190356001600160401b038111156136ea575f5ffd5b8036038213156136f8575f5ffd5b6020604089015261370d606089018284613581565b60209384013598840198909852505093840193929092019160019190910190613682565b505050508281036060840152613748818587613581565b9998505050505050505050565b85815260606020820181905281018490525f6001600160fb1b0385111561377a575f5ffd5b8460051b808760808501378201828103608090810160408501526137a19082018587613581565b98975050505050505050565b600181811c908216806137c157607f821691505b6020821081036137df57634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602e908201527f6f6e6c7920746865206f776e65722063616e206d6f766520746f206e6578742060408201526d1c1c9bdd9a5b99c81c195c9a5bd960921b606082015260800190565b634e487b7160e01b5f52603160045260245ffd5b858152846020820152836040820152608060608201525f61386c608083018486613581565b979650505050505050565b80820281158282048414176110d3576110d36134ee565b848152836020820152606060408201525f6135d3606083018486613581565b634e487b7160e01b5f52601260045260245ffd5b5f826138cf576138cf6138ad565b500690565b5f5f8335601e198436030181126138e9575f5ffd5b8301803591506001600160401b03821115613902575f5ffd5b6020019150600581901b360382131561301b575f5ffd5b5f81613927576139276134ee565b505f190190565b5f8261393c5761393c6138ad565b500490565b5f60018201613952576139526134ee565b5060010190565b601f8211156116e157805f5260205f20601f840160051c8101602085101561397e5750805b601f840160051c820191505b81811015612f5d575f815560010161398a565b8135601e198336030181126139b0575f5ffd5b820180356001600160401b03811180156139c8575f5ffd5b8136036020840113156139d9575f5ffd5b5f9050506139f1816139eb85546137ad565b85613959565b5f601f821160018114613a25575f8315613a0e5750838201602001355b5f19600385901b1c1916600184901b178555610fe1565b5f85815260208120601f198516915b82811015613a5657602085880181013583559485019460019092019101613a34565b5084821015613a75575f1960f88660031b161c19602085880101351681555b50505050600190811b019091555050565b5f600160ff1b8201613a9a57613a9a6134ee565b505f0390565b805160208083015191908110156137df575f1960209190910360031b1b1691905056fea26469706673582212201e3514b1c3fabe754ed0d7e9cc3d506f10369864d5ab3e8889771c58b041754564736f6c634300081c0033" @@ -573,221 +559,242 @@ class PDPVerifier: def __init__(self, w3: Web3, address: str): self.w3 = w3 self.address = Web3.to_checksum_address(address) - self.contract = w3.eth.contract( - address=self.address, - abi=PDPVerifierMetaData.ABI - ) - + self.contract = w3.eth.contract(address=self.address, abi=PDPVerifierMetaData.ABI) + # Constants def extra_data_max_size(self) -> int: return self.contract.functions.EXTRA_DATA_MAX_SIZE().call() - + def leaf_size(self) -> int: return self.contract.functions.LEAF_SIZE().call() - + def max_enqueued_removals(self) -> int: return self.contract.functions.MAX_ENQUEUED_REMOVALS().call() - + def max_root_size(self) -> int: return self.contract.functions.MAX_ROOT_SIZE().call() - + def no_challenge_scheduled(self) -> int: return self.contract.functions.NO_CHALLENGE_SCHEDULED().call() - + def no_proven_epoch(self) -> int: return self.contract.functions.NO_PROVEN_EPOCH().call() - + def randomness_precompile(self) -> str: return self.contract.functions.RANDOMNESS_PRECOMPILE().call() - + def seconds_in_day(self) -> int: return self.contract.functions.SECONDS_IN_DAY().call() - + def get_challenge_finality(self) -> int: return self.contract.functions.getChallengeFinality().call() - + def get_challenge_range(self, set_id: int) -> int: return self.contract.functions.getChallengeRange(set_id).call() - + def get_next_challenge_epoch(self, set_id: int) -> int: return self.contract.functions.getNextChallengeEpoch(set_id).call() - + def get_next_proof_set_id(self) -> int: return self.contract.functions.getNextProofSetId().call() - + def get_next_root_id(self, set_id: int) -> int: return self.contract.functions.getNextRootId(set_id).call() - + def get_proof_set_last_proven_epoch(self, set_id: int) -> int: return self.contract.functions.getProofSetLastProvenEpoch(set_id).call() - + def get_proof_set_leaf_count(self, set_id: int) -> int: return self.contract.functions.getProofSetLeafCount(set_id).call() - + def get_proof_set_listener(self, set_id: int) -> str: return self.contract.functions.getProofSetListener(set_id).call() - + def get_proof_set_owner(self, set_id: int) -> Tuple[str, str]: return self.contract.functions.getProofSetOwner(set_id).call() - + def get_randomness(self, epoch: int) -> int: return self.contract.functions.getRandomness(epoch).call() - + def get_root_cid(self, set_id: int, root_id: int) -> Tuple[bytes]: return self.contract.functions.getRootCid(set_id, root_id).call() - + def get_root_leaf_count(self, set_id: int, root_id: int) -> int: return self.contract.functions.getRootLeafCount(set_id, root_id).call() - + def get_scheduled_removals(self, set_id: int) -> List[int]: return self.contract.functions.getScheduledRemovals(set_id).call() - + def get_sum_tree_counts(self, set_id: int, root_id: int) -> int: return self.contract.functions.getSumTreeCounts(set_id, root_id).call() - + def height_from_index(self, index: int) -> int: return self.contract.functions.heightFromIndex(index).call() - + def height_of_tree(self, set_id: int) -> int: return self.contract.functions.heightOfTree(set_id).call() - + def owner(self) -> str: return self.contract.functions.owner().call() - + def proof_set_live(self, set_id: int) -> bool: return self.contract.functions.proofSetLive(set_id).call() - + def root_challengable(self, set_id: int, root_id: int) -> bool: return self.contract.functions.rootChallengable(set_id, root_id).call() - + def root_live(self, set_id: int, root_id: int) -> bool: return self.contract.functions.rootLive(set_id, root_id).call() - + def sum_tree_counts(self, set_id: int, root_id: int) -> int: return self.contract.functions.sumTreeCounts(set_id, root_id).call() - + def find_root_ids(self, set_id: int, leaf_indices: List[int]) -> List[Tuple[int, int]]: return self.contract.functions.findRootIds(set_id, leaf_indices).call() - + def make_root(self, tree: List[List[bytes]], leaf_count: int) -> Tuple[Tuple[bytes], int]: return self.contract.functions.makeRoot(tree, leaf_count).call() - - def add_roots(self, account: LocalAccount, set_id: int, root_data: List[Tuple], extra_data: bytes, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.addRoots( - set_id, root_data, extra_data - ).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + + def add_roots( + self, account: LocalAccount, set_id: int, root_data: List[Tuple], extra_data: bytes, gas_limit: int = 1000000 + ) -> str: + tx = self.contract.functions.addRoots(set_id, root_data, extra_data).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - + def claim_proof_set_ownership(self, account: LocalAccount, set_id: int, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.claimProofSetOwnership(set_id).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + tx = self.contract.functions.claimProofSetOwnership(set_id).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - - def create_proof_set(self, account: LocalAccount, listener_addr: str, extra_data: bytes, value: int = 0, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.createProofSet( - listener_addr, extra_data - ).build_transaction({ - 'from': account.address, - 'value': value, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + + def create_proof_set( + self, account: LocalAccount, listener_addr: str, extra_data: bytes, value: int = 0, gas_limit: int = 1000000 + ) -> str: + tx = self.contract.functions.createProofSet(listener_addr, extra_data).build_transaction( + { + "from": account.address, + "value": value, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - + def delete_proof_set(self, account: LocalAccount, set_id: int, extra_data: bytes, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.deleteProofSet(set_id, extra_data).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + tx = self.contract.functions.deleteProofSet(set_id, extra_data).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - - def next_proving_period(self, account: LocalAccount, set_id: int, challenge_epoch: int, extra_data: bytes, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.nextProvingPeriod( - set_id, challenge_epoch, extra_data - ).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + + def next_proving_period( + self, account: LocalAccount, set_id: int, challenge_epoch: int, extra_data: bytes, gas_limit: int = 1000000 + ) -> str: + tx = self.contract.functions.nextProvingPeriod(set_id, challenge_epoch, extra_data).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - - def propose_proof_set_owner(self, account: LocalAccount, set_id: int, new_owner: str, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.proposeProofSetOwner(set_id, new_owner).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + + def propose_proof_set_owner( + self, account: LocalAccount, set_id: int, new_owner: str, gas_limit: int = 1000000 + ) -> str: + tx = self.contract.functions.proposeProofSetOwner(set_id, new_owner).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - - def prove_possession(self, account: LocalAccount, set_id: int, proofs: List[Tuple], value: int = 0, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.provePossession(set_id, proofs).build_transaction({ - 'from': account.address, - 'value': value, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + + def prove_possession( + self, account: LocalAccount, set_id: int, proofs: List[Tuple], value: int = 0, gas_limit: int = 1000000 + ) -> str: + tx = self.contract.functions.provePossession(set_id, proofs).build_transaction( + { + "from": account.address, + "value": value, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - + def renounce_ownership(self, account: LocalAccount, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.renounceOwnership().build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + tx = self.contract.functions.renounceOwnership().build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - - def schedule_removals(self, account: LocalAccount, set_id: int, root_ids: List[int], extra_data: bytes, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.scheduleRemovals( - set_id, root_ids, extra_data - ).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + + def schedule_removals( + self, account: LocalAccount, set_id: int, root_ids: List[int], extra_data: bytes, gas_limit: int = 1000000 + ) -> str: + tx = self.contract.functions.scheduleRemovals(set_id, root_ids, extra_data).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() - + def transfer_ownership(self, account: LocalAccount, new_owner: str, gas_limit: int = 1000000) -> str: - tx = self.contract.functions.transferOwnership(new_owner).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - }) - + tx = self.contract.functions.transferOwnership(new_owner).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + } + ) + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() @@ -797,22 +804,23 @@ def new_pdp_verifier(w3: Web3, address: str) -> PDPVerifier: return PDPVerifier(w3, address) -def deploy_pdp_verifier(w3: Web3, account: LocalAccount, challenge_finality: int, gas_limit: int = 3000000) -> Tuple[str, str]: - - contract_factory = w3.eth.contract( - abi=PDPVerifierMetaData.ABI, - bytecode=PDPVerifierMetaData.BIN +def deploy_pdp_verifier( + w3: Web3, account: LocalAccount, challenge_finality: int, gas_limit: int = 3000000 +) -> Tuple[str, str]: + + contract_factory = w3.eth.contract(abi=PDPVerifierMetaData.ABI, bytecode=PDPVerifierMetaData.BIN) + + tx = contract_factory.constructor(challenge_finality).build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": w3.eth.get_transaction_count(account.address), + } ) - - tx = contract_factory.constructor(challenge_finality).build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': w3.eth.get_transaction_count(account.address), - }) - + signed_tx = account.sign_transaction(tx) tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) - + receipt = w3.eth.wait_for_transaction_receipt(tx_hash) - + return receipt.contractAddress, tx_hash.hex() diff --git a/private/ipc/contracts/sink.py b/private/ipc/contracts/sink.py index 5d85ed5..ddd81f0 100644 --- a/private/ipc/contracts/sink.py +++ b/private/ipc/contracts/sink.py @@ -15,41 +15,33 @@ class SinkMetaData: """Metadata for the Sink contract.""" - - ABI = [ - { - "stateMutability": "nonpayable", - "type": "fallback" - } - ] + + ABI = [{"stateMutability": "nonpayable", "type": "fallback"}] BIN = "0x6080604052348015600e575f5ffd5b50604680601a5f395ff3fe6080604052348015600e575f5ffd5b00fea2646970667358221220f43799cb6e28e32500f5eb3784cc07778d26ab2be04f4ee9fd27d581ad2138f464736f6c634300081c0033" class SinkContract: """Main class for interacting with the Sink contract.""" - + def __init__(self, w3: Web3, address: str): """Initialize Sink contract interface.""" self.w3 = w3 self.address = Web3.to_checksum_address(address) - self.contract = w3.eth.contract( - address=self.address, - abi=SinkMetaData.ABI - ) - + self.contract = w3.eth.contract(address=self.address, abi=SinkMetaData.ABI) + # Fallback function - def fallback(self, account: LocalAccount, data: bytes = b'', value: int = 0, gas_limit: int = 500000) -> str: + def fallback(self, account: LocalAccount, data: bytes = b"", value: int = 0, gas_limit: int = 500000) -> str: """Call the fallback function with optional data and value.""" tx = { - 'to': self.address, - 'from': account.address, - 'value': value, - 'gas': gas_limit, - 'nonce': self.w3.eth.get_transaction_count(account.address), - 'data': data.hex() if isinstance(data, bytes) else data + "to": self.address, + "from": account.address, + "value": value, + "gas": gas_limit, + "nonce": self.w3.eth.get_transaction_count(account.address), + "data": data.hex() if isinstance(data, bytes) else data, } - + signed_tx = account.sign_transaction(tx) tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) return tx_hash.hex() @@ -63,25 +55,24 @@ def new_sink(w3: Web3, address: str) -> SinkContract: def deploy_sink(w3: Web3, account: LocalAccount, gas_limit: int = 500000) -> Tuple[str, str]: """ Deploy a new Sink contract. - + Returns: Tuple of (contract_address, transaction_hash) """ - contract_factory = w3.eth.contract( - abi=SinkMetaData.ABI, - bytecode=SinkMetaData.BIN + contract_factory = w3.eth.contract(abi=SinkMetaData.ABI, bytecode=SinkMetaData.BIN) + + tx = contract_factory.constructor().build_transaction( + { + "from": account.address, + "gas": gas_limit, + "nonce": w3.eth.get_transaction_count(account.address), + } ) - - tx = contract_factory.constructor().build_transaction({ - 'from': account.address, - 'gas': gas_limit, - 'nonce': w3.eth.get_transaction_count(account.address), - }) - + signed_tx = account.sign_transaction(tx) tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) - + # Wait for transaction receipt receipt = w3.eth.wait_for_transaction_receipt(tx_hash) - - return receipt.contractAddress, tx_hash.hex() \ No newline at end of file + + return receipt.contractAddress, tx_hash.hex() diff --git a/private/ipc/contracts/storage.py b/private/ipc/contracts/storage.py index 078da5e..fbef4f5 100644 --- a/private/ipc/contracts/storage.py +++ b/private/ipc/contracts/storage.py @@ -4,2072 +4,837 @@ from web3.contract import Contract from eth_account import Account + def get_raw_transaction(signed_tx): - if hasattr(signed_tx, 'raw_transaction'): + if hasattr(signed_tx, "raw_transaction"): return signed_tx.raw_transaction # web3 v7+ - elif hasattr(signed_tx, 'rawTransaction'): - return signed_tx.rawTransaction # web3 v6 + elif hasattr(signed_tx, "rawTransaction"): + return signed_tx.rawTransaction # web3 v6 else: raise AttributeError("SignedTransaction has neither raw_transaction nor rawTransaction attribute") + + import json + class StorageContract: """Python bindings for the Storage smart contract.""" - + def __init__(self, web3: Web3, contract_address: HexAddress): """Initialize the Storage contract interface. - + Args: web3: Web3 instance contract_address: Address of the deployed Storage contract """ self.web3 = web3 self.contract_address = contract_address - + # Contract ABI from the Go bindings self.abi = [ + {"inputs": [], "stateMutability": "nonpayable", "type": "constructor"}, { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], + "inputs": [{"internalType": "address", "name": "target", "type": "address"}], "name": "AddressEmptyCode", - "type": "error" - }, - { - "inputs": [], - "name": "BlockAlreadyExists", - "type": "error" - }, - { - "inputs": [], - "name": "BlockAlreadyFilled", - "type": "error" - }, - { - "inputs": [], - "name": "BlockInvalid", - "type": "error" - }, - { - "inputs": [], - "name": "BlockNonexists", - "type": "error" - }, - { - "inputs": [], - "name": "BucketAlreadyExists", - "type": "error" - }, - { - "inputs": [], - "name": "BucketInvalid", - "type": "error" - }, - { - "inputs": [], - "name": "BucketInvalidOwner", - "type": "error" - }, - { - "inputs": [], - "name": "BucketNonempty", - "type": "error" - }, - { - "inputs": [], - "name": "BucketNonexists", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "fileCID", - "type": "bytes" - } - ], + "type": "error", + }, + {"inputs": [], "name": "BlockAlreadyExists", "type": "error"}, + {"inputs": [], "name": "BlockAlreadyFilled", "type": "error"}, + {"inputs": [], "name": "BlockInvalid", "type": "error"}, + {"inputs": [], "name": "BlockNonexists", "type": "error"}, + {"inputs": [], "name": "BucketAlreadyExists", "type": "error"}, + {"inputs": [], "name": "BucketInvalid", "type": "error"}, + {"inputs": [], "name": "BucketInvalidOwner", "type": "error"}, + {"inputs": [], "name": "BucketNonempty", "type": "error"}, + {"inputs": [], "name": "BucketNonexists", "type": "error"}, + { + "inputs": [{"internalType": "bytes", "name": "fileCID", "type": "bytes"}], "name": "ChunkCIDMismatch", - "type": "error" + "type": "error", }, + {"inputs": [], "name": "ECDSAInvalidSignature", "type": "error"}, { - "inputs": [], - "name": "ECDSAInvalidSignature", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "length", - "type": "uint256" - } - ], + "inputs": [{"internalType": "uint256", "name": "length", "type": "uint256"}], "name": "ECDSAInvalidSignatureLength", - "type": "error" + "type": "error", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "s", "type": "bytes32"}], "name": "ECDSAInvalidSignatureS", - "type": "error" - }, - { - "inputs": [], - "name": "FileAlreadyExists", - "type": "error" - }, - { - "inputs": [], - "name": "FileChunkDuplicate", - "type": "error" - }, - { - "inputs": [], - "name": "FileFullyUploaded", - "type": "error" - }, - { - "inputs": [], - "name": "FileInvalid", - "type": "error" - }, - { - "inputs": [], - "name": "FileNameDuplicate", - "type": "error" - }, - { - "inputs": [], - "name": "FileNonempty", - "type": "error" - }, - { - "inputs": [], - "name": "FileNotExists", - "type": "error" - }, - { - "inputs": [], - "name": "FileNotFilled", - "type": "error" - }, - { - "inputs": [], - "name": "IndexMismatch", - "type": "error" - }, + "type": "error", + }, + {"inputs": [], "name": "FileAlreadyExists", "type": "error"}, + {"inputs": [], "name": "FileChunkDuplicate", "type": "error"}, + {"inputs": [], "name": "FileFullyUploaded", "type": "error"}, + {"inputs": [], "name": "FileInvalid", "type": "error"}, + {"inputs": [], "name": "FileNameDuplicate", "type": "error"}, + {"inputs": [], "name": "FileNonempty", "type": "error"}, + {"inputs": [], "name": "FileNotExists", "type": "error"}, + {"inputs": [], "name": "FileNotFilled", "type": "error"}, + {"inputs": [], "name": "IndexMismatch", "type": "error"}, { "inputs": [ - { - "internalType": "uint256", - "name": "cidsLength", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sizesLength", - "type": "uint256" - } + {"internalType": "uint256", "name": "cidsLength", "type": "uint256"}, + {"internalType": "uint256", "name": "sizesLength", "type": "uint256"}, ], "name": "InvalidArrayLength", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidBlockIndex", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidBlocksAmount", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidEncodedSize", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidFileBlocksCount", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidFileCID", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidLastBlockSize", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidShortString", - "type": "error" - }, - { - "inputs": [], - "name": "LastChunkDuplicate", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "str", - "type": "string" - } - ], + "type": "error", + }, + {"inputs": [], "name": "InvalidBlockIndex", "type": "error"}, + {"inputs": [], "name": "InvalidBlocksAmount", "type": "error"}, + {"inputs": [], "name": "InvalidEncodedSize", "type": "error"}, + {"inputs": [], "name": "InvalidFileBlocksCount", "type": "error"}, + {"inputs": [], "name": "InvalidFileCID", "type": "error"}, + {"inputs": [], "name": "InvalidLastBlockSize", "type": "error"}, + {"inputs": [], "name": "InvalidShortString", "type": "error"}, + {"inputs": [], "name": "LastChunkDuplicate", "type": "error"}, + { + "inputs": [{"internalType": "string", "name": "str", "type": "string"}], "name": "StringTooLong", - "type": "error" + "type": "error", }, { "anonymous": False, "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "indexed": False, - "internalType": "address", - "name": "owner", - "type": "address" - } + {"indexed": True, "internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"indexed": True, "internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"indexed": True, "internalType": "string", "name": "name", "type": "string"}, + {"indexed": False, "internalType": "address", "name": "owner", "type": "address"}, ], "name": "AddFile", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "indexed": True, - "internalType": "address", - "name": "owner", - "type": "address" - } + {"indexed": True, "internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"indexed": True, "internalType": "string", "name": "name", "type": "string"}, + {"indexed": True, "internalType": "address", "name": "owner", "type": "address"}, ], "name": "CreateBucket", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "indexed": False, - "internalType": "address", - "name": "owner", - "type": "address" - } + {"indexed": True, "internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"indexed": True, "internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"indexed": True, "internalType": "string", "name": "name", "type": "string"}, + {"indexed": False, "internalType": "address", "name": "owner", "type": "address"}, ], "name": "CreateFile", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "indexed": True, - "internalType": "address", - "name": "owner", - "type": "address" - } + {"indexed": True, "internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"indexed": True, "internalType": "string", "name": "name", "type": "string"}, + {"indexed": True, "internalType": "address", "name": "owner", "type": "address"}, ], "name": "DeleteBucket", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "indexed": False, - "internalType": "address", - "name": "owner", - "type": "address" - } + {"indexed": True, "internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"indexed": True, "internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"indexed": True, "internalType": "string", "name": "name", "type": "string"}, + {"indexed": False, "internalType": "address", "name": "owner", "type": "address"}, ], "name": "DeleteFile", - "type": "event" + "type": "event", }, { "anonymous": False, "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "blockId", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "bytes", - "name": "peerId", - "type": "bytes" - } + {"indexed": True, "internalType": "bytes32", "name": "blockId", "type": "bytes32"}, + {"indexed": True, "internalType": "bytes", "name": "peerId", "type": "bytes"}, ], "name": "DeletePeerBlock", - "type": "event" - }, - { - "anonymous": False, - "inputs": [], - "name": "EIP712DomainChanged", - "type": "event" + "type": "event", }, + {"anonymous": False, "inputs": [], "name": "EIP712DomainChanged", "type": "event"}, { "anonymous": False, "inputs": [ - { - "indexed": True, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "indexed": True, - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "indexed": False, - "internalType": "address", - "name": "owner", - "type": "address" - } + {"indexed": True, "internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"indexed": True, "internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"indexed": True, "internalType": "string", "name": "name", "type": "string"}, + {"indexed": False, "internalType": "address", "name": "owner", "type": "address"}, ], "name": "FileUploaded", - "type": "event" + "type": "event", }, { "inputs": [], "name": "MAX_BLOCKS_PER_FILE", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], + "outputs": [{"internalType": "uint64", "name": "", "type": "uint64"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "MAX_BLOCK_SIZE", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], + "outputs": [{"internalType": "uint64", "name": "", "type": "uint64"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "accessManager", - "outputs": [ - { - "internalType": "contract IAccessManager", - "name": "", - "type": "address" - } - ], + "outputs": [{"internalType": "contract IAccessManager", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes", - "name": "cid", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "encodedChunkSize", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "cids", - "type": "bytes32[]" - }, - { - "internalType": "uint256[]", - "name": "chunkBlocksSizes", - "type": "uint256[]" - }, - { - "internalType": "uint256", - "name": "chunkIndex", - "type": "uint256" - } + {"internalType": "bytes", "name": "cid", "type": "bytes"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "encodedChunkSize", "type": "uint256"}, + {"internalType": "bytes32[]", "name": "cids", "type": "bytes32[]"}, + {"internalType": "uint256[]", "name": "chunkBlocksSizes", "type": "uint256[]"}, + {"internalType": "uint256", "name": "chunkIndex", "type": "uint256"}, ], "name": "addFileChunk", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], + "outputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes", - "name": "peerId", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "cid", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "isReplica", - "type": "bool" - } + {"internalType": "bytes", "name": "peerId", "type": "bytes"}, + {"internalType": "bytes32", "name": "cid", "type": "bytes32"}, + {"internalType": "bool", "name": "isReplica", "type": "bool"}, ], "name": "addPeerBlock", - "outputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], + "outputs": [{"internalType": "bytes32", "name": "id", "type": "bytes32"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], + "inputs": [{"internalType": "string", "name": "name", "type": "string"}], "name": "createBucket", - "outputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], + "outputs": [{"internalType": "bytes32", "name": "id", "type": "bytes32"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, ], "name": "createFile", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], + "outputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "encodedFileSize", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "actualSize", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "fileCID", - "type": "bytes" - } + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "encodedFileSize", "type": "uint256"}, + {"internalType": "uint256", "name": "actualSize", "type": "uint256"}, + {"internalType": "bytes", "name": "fileCID", "type": "bytes"}, ], "name": "commitFile", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], + "outputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "index", "type": "uint256"}, ], "name": "deleteBucket", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "fileID", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } + {"internalType": "bytes32", "name": "fileID", "type": "bytes32"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "index", "type": "uint256"}, ], "name": "deleteFile", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], + "inputs": [{"internalType": "string", "name": "name", "type": "string"}], "name": "getBucketByName", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "bytes32[]", - "name": "files", - "type": "bytes32[]" - } + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "address", "name": "owner", "type": "address"}, + {"internalType": "bytes32[]", "name": "files", "type": "bytes32[]"}, ], "internalType": "struct IStorage.Bucket", "name": "bucket", - "type": "tuple" + "type": "tuple", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "string", - "name": "bucketName", - "type": "string" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fileOffset", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fileLimit", - "type": "uint256" - } + {"internalType": "string", "name": "bucketName", "type": "string"}, + {"internalType": "address", "name": "owner", "type": "address"}, + {"internalType": "uint256", "name": "fileOffset", "type": "uint256"}, + {"internalType": "uint256", "name": "fileLimit", "type": "uint256"}, ], "name": "getBucketByName", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "bytes32[]", - "name": "files", - "type": "bytes32[]" - } + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "address", "name": "owner", "type": "address"}, + {"internalType": "bytes32[]", "name": "files", "type": "bytes32[]"}, ], "internalType": "struct IStorage.Bucket", "name": "bucket", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "name": "getFileByName", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "fileCID", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "encodedSize", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "actualSize", - "type": "uint256" - }, + "type": "tuple", + } + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + ], + "name": "getFileByName", + "outputs": [ + { + "components": [ + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "bytes", "name": "fileCID", "type": "bytes"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "encodedSize", "type": "uint256"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "uint256", "name": "actualSize", "type": "uint256"}, { "components": [ - { - "internalType": "bytes[]", - "name": "chunkCIDs", - "type": "bytes[]" - }, - { - "internalType": "uint256[]", - "name": "chunkSize", - "type": "uint256[]" - } + {"internalType": "bytes[]", "name": "chunkCIDs", "type": "bytes[]"}, + {"internalType": "uint256[]", "name": "chunkSize", "type": "uint256[]"}, ], "internalType": "struct IStorage.Chunk", "name": "chunks", - "type": "tuple" - } + "type": "tuple", + }, ], "internalType": "struct IStorage.File", "name": "file", - "type": "tuple" + "type": "tuple", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], + "inputs": [{"internalType": "string", "name": "name", "type": "string"}], "name": "getFileByName", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "fileCID", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "encodedSize", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "actualSize", - "type": "uint256" - }, + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "bytes", "name": "fileCID", "type": "bytes"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "encodedSize", "type": "uint256"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "uint256", "name": "actualSize", "type": "uint256"}, { "components": [ - { - "internalType": "bytes[]", - "name": "chunkCIDs", - "type": "bytes[]" - }, - { - "internalType": "uint256[]", - "name": "chunkSize", - "type": "uint256[]" - } + {"internalType": "bytes[]", "name": "chunkCIDs", "type": "bytes[]"}, + {"internalType": "uint256[]", "name": "chunkSize", "type": "uint256[]"}, ], "internalType": "struct IStorage.Chunk", "name": "chunks", - "type": "tuple" - } + "type": "tuple", + }, ], "internalType": "struct IStorage.File", "name": "file", - "type": "tuple" + "type": "tuple", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - } + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "bytes32", "name": "fileId", "type": "bytes32"}, ], "name": "getFileIndexById", - "outputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], + "outputs": [{"internalType": "uint256", "name": "index", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "fileId", "type": "bytes32"}], "name": "isFileFilled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "fileId", "type": "bytes32"}], "name": "isFileFilledV2", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "UPGRADE_INTERFACE_VERSION", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], + "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes[]", - "name": "cids", - "type": "bytes[]" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - }, - { - "internalType": "uint256[]", - "name": "encodedChunkSizes", - "type": "uint256[]" - }, - { - "internalType": "bytes32[][]", - "name": "chunkBlocksCIDs", - "type": "bytes32[][]" - }, - { - "internalType": "uint256[][]", - "name": "chunkBlockSizes", - "type": "uint256[][]" - }, - { - "internalType": "uint256", - "name": "startingChunkIndex", - "type": "uint256" - } + {"internalType": "bytes[]", "name": "cids", "type": "bytes[]"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "fileName", "type": "string"}, + {"internalType": "uint256[]", "name": "encodedChunkSizes", "type": "uint256[]"}, + {"internalType": "bytes32[][]", "name": "chunkBlocksCIDs", "type": "bytes32[][]"}, + {"internalType": "uint256[][]", "name": "chunkBlockSizes", "type": "uint256[][]"}, + {"internalType": "uint256", "name": "startingChunkIndex", "type": "uint256"}, ], "name": "addFileChunks", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "id", "type": "bytes32"}], "name": "getFileById", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "fileCID", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "encodedSize", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "actualSize", - "type": "uint256" - }, + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "bytes", "name": "fileCID", "type": "bytes"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "encodedSize", "type": "uint256"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "uint256", "name": "actualSize", "type": "uint256"}, { "components": [ - { - "internalType": "bytes[]", - "name": "chunkCIDs", - "type": "bytes[]" - }, - { - "internalType": "uint256[]", - "name": "chunkSize", - "type": "uint256[]" - } + {"internalType": "bytes[]", "name": "chunkCIDs", "type": "bytes[]"}, + {"internalType": "uint256[]", "name": "chunkSize", "type": "uint256[]"}, ], "internalType": "struct IStorage.Chunk", "name": "chunks", - "type": "tuple" - } + "type": "tuple", + }, ], "internalType": "struct IStorage.File", "name": "file", - "type": "tuple" + "type": "tuple", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "string", - "name": "bucketName", - "type": "string" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - } + {"internalType": "string", "name": "bucketName", "type": "string"}, + {"internalType": "address", "name": "owner", "type": "address"}, ], "name": "getBucketIndexByName", "outputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "exists", - "type": "bool" - } + {"internalType": "uint256", "name": "index", "type": "uint256"}, + {"internalType": "bool", "name": "exists", "type": "bool"}, ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "chunkIndex", - "type": "uint256" - } + {"internalType": "bytes32", "name": "fileId", "type": "bytes32"}, + {"internalType": "uint256", "name": "chunkIndex", "type": "uint256"}, ], "name": "isChunkFilled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "fileId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "blockIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "chunkIndex", - "type": "uint256" - } + {"internalType": "bytes32", "name": "fileId", "type": "bytes32"}, + {"internalType": "uint256", "name": "blockIndex", "type": "uint256"}, + {"internalType": "uint256", "name": "chunkIndex", "type": "uint256"}, ], "name": "isBlockFilled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], + "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "pure", - "type": "function" + "type": "function", }, { "inputs": [], "name": "getChainID", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "id", "type": "bytes32"}], "name": "getFileOwner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], + "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32[]", - "name": "ids", - "type": "bytes32[]" - } - ], + "inputs": [{"internalType": "bytes32[]", "name": "ids", "type": "bytes32[]"}], "name": "getBucketsByIds", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "bytes32[]", - "name": "files", - "type": "bytes32[]" - } + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "address", "name": "owner", "type": "address"}, + {"internalType": "bytes32[]", "name": "files", "type": "bytes32[]"}, ], "internalType": "struct IStorage.Bucket[]", "name": "", - "type": "tuple[]" + "type": "tuple[]", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "uint256", - "name": "offset", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fileOffset", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fileLimit", - "type": "uint256" - } + {"internalType": "address", "name": "owner", "type": "address"}, + {"internalType": "uint256", "name": "offset", "type": "uint256"}, + {"internalType": "uint256", "name": "limit", "type": "uint256"}, + {"internalType": "uint256", "name": "fileOffset", "type": "uint256"}, + {"internalType": "uint256", "name": "fileLimit", "type": "uint256"}, ], "name": "getOwnerBuckets", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "bytes32[]", - "name": "files", - "type": "bytes32[]" - } + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "address", "name": "owner", "type": "address"}, + {"internalType": "bytes32[]", "name": "files", "type": "bytes32[]"}, ], "internalType": "struct IStorage.Bucket[]", "name": "buckets", - "type": "tuple[]" + "type": "tuple[]", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - } - ], + "inputs": [{"internalType": "address", "name": "tokenAddress", "type": "address"}], "name": "initialize", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [], "name": "timestamp", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "token", - "outputs": [ - { - "internalType": "contractIAkaveToken", - "name": "", - "type": "address" - } - ], + "outputs": [{"internalType": "contractIAkaveToken", "name": "", "type": "address"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "peerId", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "cid", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - }, - { - "internalType": "bool", - "name": "isReplica", - "type": "bool" - } + {"internalType": "bytes32", "name": "peerId", "type": "bytes32"}, + {"internalType": "bytes32", "name": "cid", "type": "bytes32"}, + {"internalType": "string", "name": "fileName", "type": "string"}, + {"internalType": "bool", "name": "isReplica", "type": "bool"}, ], "name": "addPeerBlock", - "outputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], + "outputs": [{"internalType": "bytes32", "name": "id", "type": "bytes32"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "peerId", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "cid", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "bytes32", "name": "peerId", "type": "bytes32"}, + {"internalType": "bytes32", "name": "cid", "type": "bytes32"}, + {"internalType": "string", "name": "fileName", "type": "string"}, + {"internalType": "uint256", "name": "index", "type": "uint256"}, ], "name": "deletePeerBlock", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "blockCID", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "nodeId", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "chunkIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "blockIndex", - "type": "uint256" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - } + {"internalType": "bytes32", "name": "blockCID", "type": "bytes32"}, + {"internalType": "bytes32", "name": "nodeId", "type": "bytes32"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "uint256", "name": "chunkIndex", "type": "uint256"}, + {"internalType": "uint256", "name": "nonce", "type": "uint256"}, + {"internalType": "uint256", "name": "blockIndex", "type": "uint256"}, + {"internalType": "string", "name": "fileName", "type": "string"}, + {"internalType": "bytes", "name": "signature", "type": "bytes"}, + {"internalType": "uint256", "name": "deadline", "type": "uint256"}, ], "internalType": "struct IStorage.FillChunkBlockArgs", "name": "args", - "type": "tuple" + "type": "tuple", } ], "name": "fillChunkBlock", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "blockCID", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "nodeId", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "chunkIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "blockIndex", - "type": "uint256" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - } + {"internalType": "bytes32", "name": "blockCID", "type": "bytes32"}, + {"internalType": "bytes32", "name": "nodeId", "type": "bytes32"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "uint256", "name": "chunkIndex", "type": "uint256"}, + {"internalType": "uint256", "name": "nonce", "type": "uint256"}, + {"internalType": "uint256", "name": "blockIndex", "type": "uint256"}, + {"internalType": "string", "name": "fileName", "type": "string"}, + {"internalType": "bytes", "name": "signature", "type": "bytes"}, + {"internalType": "uint256", "name": "deadline", "type": "uint256"}, ], "internalType": "struct IStorage.FillChunkBlockArgs[]", "name": "args", - "type": "tuple[]" + "type": "tuple[]", } ], "name": "fillChunkBlocks", "outputs": [], "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } + "type": "function", + }, + { + "inputs": [ + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "uint256", "name": "index", "type": "uint256"}, ], "name": "getChunkByIndex", "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + {"internalType": "bytes", "name": "", "type": "bytes"}, + {"internalType": "uint256", "name": "", "type": "uint256"}, ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "string", - "name": "bucketName", - "type": "string" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - } + {"internalType": "string", "name": "bucketName", "type": "string"}, + {"internalType": "string", "name": "fileName", "type": "string"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "address", "name": "owner", "type": "address"}, ], "name": "getFullFileInfo", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "fileCID", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "bucketId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "encodedSize", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "actualSize", - "type": "uint256" - }, + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "bytes", "name": "fileCID", "type": "bytes"}, + {"internalType": "bytes32", "name": "bucketId", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "encodedSize", "type": "uint256"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "uint256", "name": "actualSize", "type": "uint256"}, { "components": [ - { - "internalType": "bytes[]", - "name": "chunkCIDs", - "type": "bytes[]" - }, - { - "internalType": "uint256[]", - "name": "chunkSize", - "type": "uint256[]" - } + {"internalType": "bytes[]", "name": "chunkCIDs", "type": "bytes[]"}, + {"internalType": "uint256[]", "name": "chunkSize", "type": "uint256[]"}, ], "internalType": "struct IStorage.Chunk", "name": "chunks", - "type": "tuple" - } + "type": "tuple", + }, ], "internalType": "struct IStorage.File", "name": "file", - "type": "tuple" + "type": "tuple", }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "exists", - "type": "bool" - } + {"internalType": "uint256", "name": "index", "type": "uint256"}, + {"internalType": "bool", "name": "exists", "type": "bool"}, ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32[]", - "name": "ids", - "type": "bytes32[]" - }, - { - "internalType": "uint256", - "name": "bucketOffset", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "bucketLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fileOffset", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fileLimit", - "type": "uint256" - } + {"internalType": "bytes32[]", "name": "ids", "type": "bytes32[]"}, + {"internalType": "uint256", "name": "bucketOffset", "type": "uint256"}, + {"internalType": "uint256", "name": "bucketLimit", "type": "uint256"}, + {"internalType": "uint256", "name": "fileOffset", "type": "uint256"}, + {"internalType": "uint256", "name": "fileLimit", "type": "uint256"}, ], "name": "getBucketsByIdsWithFiles", "outputs": [ { "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "bytes32[]", - "name": "files", - "type": "bytes32[]" - } + {"internalType": "bytes32", "name": "id", "type": "bytes32"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "uint256", "name": "createdAt", "type": "uint256"}, + {"internalType": "address", "name": "owner", "type": "address"}, + {"internalType": "bytes32[]", "name": "files", "type": "bytes32[]"}, ], "internalType": "struct IStorage.Bucket[]", "name": "buckets", - "type": "tuple[]" + "type": "tuple[]", } ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "peerId", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "cid", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - } + {"internalType": "bytes32", "name": "peerId", "type": "bytes32"}, + {"internalType": "bytes32", "name": "cid", "type": "bytes32"}, + {"internalType": "string", "name": "fileName", "type": "string"}, ], "name": "getPeerBlockIndexById", "outputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "exists", - "type": "bool" - } + {"internalType": "uint256", "name": "index", "type": "uint256"}, + {"internalType": "bool", "name": "exists", "type": "bool"}, ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32[]", - "name": "cids", - "type": "bytes32[]" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - } + {"internalType": "bytes32[]", "name": "cids", "type": "bytes32[]"}, + {"internalType": "string", "name": "fileName", "type": "string"}, ], "name": "getPeersArrayByPeerBlockCid", - "outputs": [ - { - "internalType": "bytes32[][]", - "name": "peers", - "type": "bytes32[][]" - } - ], + "outputs": [{"internalType": "bytes32[][]", "name": "peers", "type": "bytes32[][]"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "cid", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "fileName", - "type": "string" - } + {"internalType": "bytes32", "name": "cid", "type": "bytes32"}, + {"internalType": "string", "name": "fileName", "type": "string"}, ], "name": "getPeersByPeerBlockCid", - "outputs": [ - { - "internalType": "bytes32[]", - "name": "peers", - "type": "bytes32[]" - } - ], + "outputs": [{"internalType": "bytes32[]", "name": "peers", "type": "bytes32[]"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "cid", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "peerId", - "type": "bytes32" - } + {"internalType": "bytes32", "name": "cid", "type": "bytes32"}, + {"internalType": "bytes32", "name": "peerId", "type": "bytes32"}, ], "name": "isPeerBlockReplica", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], "name": "fileFillCounter", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], + "outputs": [{"internalType": "uint16", "name": "", "type": "uint16"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], + "inputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], "name": "fileRewardClaimed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + {"internalType": "bytes32", "name": "", "type": "bytes32"}, + {"internalType": "uint256", "name": "", "type": "uint256"}, ], "name": "fulfilledBlocks", - "outputs": [ - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], + "outputs": [{"internalType": "uint32", "name": "", "type": "uint32"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "eip712Domain", "outputs": [ - { - "internalType": "bytes1", - "name": "fields", - "type": "bytes1" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "string", - "name": "version", - "type": "string" - }, - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "verifyingContract", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "salt", - "type": "bytes32" - }, - { - "internalType": "uint256[]", - "name": "extensions", - "type": "uint256[]" - } + {"internalType": "bytes1", "name": "fields", "type": "bytes1"}, + {"internalType": "string", "name": "name", "type": "string"}, + {"internalType": "string", "name": "version", "type": "string"}, + {"internalType": "uint256", "name": "chainId", "type": "uint256"}, + {"internalType": "address", "name": "verifyingContract", "type": "address"}, + {"internalType": "bytes32", "name": "salt", "type": "bytes32"}, + {"internalType": "uint256[]", "name": "extensions", "type": "uint256[]"}, ], "stateMutability": "view", - "type": "function" + "type": "function", }, { "inputs": [], "name": "proxiableUUID", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], + "outputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"}], "stateMutability": "view", - "type": "function" + "type": "function", }, { - "inputs": [ - { - "internalType": "address", - "name": "accessManagerAddress", - "type": "address" - } - ], + "inputs": [{"internalType": "address", "name": "accessManagerAddress", "type": "address"}], "name": "setAccessManager", "outputs": [], "stateMutability": "nonpayable", - "type": "function" + "type": "function", }, { "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } + {"internalType": "address", "name": "newImplementation", "type": "address"}, + {"internalType": "bytes", "name": "data", "type": "bytes"}, ], "name": "upgradeToAndCall", "outputs": [], "stateMutability": "payable", - "type": "function" - } + "type": "function", + }, ] - + try: self.contract = web3.eth.contract(address=contract_address, abi=self.abi) except Exception as e: @@ -2078,11 +843,13 @@ def __init__(self, web3: Web3, contract_address: HexAddress): if "Could not format invalid value" in error_msg and "as field 'abi'" in error_msg: raise ValueError(f"Failed to initialize storage contract at {contract_address}: Invalid ABI format") else: - raise ValueError(f"Failed to initialize storage contract at {contract_address}: {type(e).__name__}")from e + raise ValueError( + f"Failed to initialize storage contract at {contract_address}: {type(e).__name__}" + ) from e def get_access_manager(self) -> HexAddress: """Gets the address of the associated access manager contract. - + Returns: Address of the access manager contract """ @@ -2090,7 +857,7 @@ def get_access_manager(self) -> HexAddress: def get_max_blocks_per_file(self) -> int: """Gets the maximum number of blocks per file. - + Returns: Maximum blocks per file """ @@ -2098,42 +865,44 @@ def get_max_blocks_per_file(self) -> int: def get_max_block_size(self) -> int: """Gets the maximum block size. - + Returns: Maximum block size in bytes """ return self.contract.functions.MAX_BLOCK_SIZE().call() - def create_bucket(self, bucket_name: str, from_address: HexAddress, private_key: str, gas_limit: int = None, nonce_manager=None) -> HexStr: + def create_bucket( + self, bucket_name: str, from_address: HexAddress, private_key: str, gas_limit: int = None, nonce_manager=None + ) -> HexStr: """Creates a new bucket. - + Args: bucket_name: Name of the bucket to create from_address: Address creating the bucket private_key: Private key for signing the transaction gas_limit: Optional gas limit for the transaction. If not provided, will use default. nonce_manager: Optional nonce manager for coordinated transactions - + Returns: Transaction hash of the create operation """ # Build transaction tx_params = { - 'from': from_address, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + if gas_limit: - tx_params['gas'] = gas_limit + tx_params["gas"] = gas_limit else: - tx_params['gas'] = 500000 # Default gas limit - + tx_params["gas"] = 500000 # Default gas limit + tx = self.contract.functions.createBucket(bucket_name).build_transaction(tx_params) - + # Sign transaction signed_tx = Account.sign_transaction(tx, private_key) - + # Send raw transaction try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) @@ -2141,47 +910,47 @@ def create_bucket(self, bucket_name: str, from_address: HexAddress, private_key: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + # Wait for receipt receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: # Get revert reason if possible try: - self.contract.functions.createBucket(bucket_name).call({ - 'from': from_address - }) + self.contract.functions.createBucket(bucket_name).call({"from": from_address}) except Exception as e: raise Exception(f"Transaction reverted: {str(e)}") raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def create_file(self, from_address: HexAddress, private_key: str, bucket_id: bytes, file_name: str, nonce_manager=None) -> HexStr: + def create_file( + self, from_address: HexAddress, private_key: str, bucket_id: bytes, file_name: str, nonce_manager=None + ) -> HexStr: """Creates a new file entry in the specified bucket. - + Args: from_address: Address creating the file private_key: Private key for signing the transaction bucket_id: ID of the bucket to create the file in (bytes32) file_name: Name of the file nonce_manager: Optional nonce manager for coordinated transactions - + Returns: Transaction hash of the create operation """ # Build transaction with signature: createFile(bucketId, name) tx_params = { - 'from': from_address, - 'gas': 500000, # Gas limit - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 500000, # Gas limit + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + tx = self.contract.functions.createFile(bucket_id, file_name).build_transaction(tx_params) - + # Sign transaction signed_tx = Account.sign_transaction(tx, private_key) - + # Send raw transaction try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) @@ -2189,24 +958,34 @@ def create_file(self, from_address: HexAddress, private_key: str, bucket_id: byt if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + # Wait for receipt receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: # Get revert reason if possible try: - self.contract.functions.createFile(bucket_id, file_name).call({ - 'from': from_address - }) + self.contract.functions.createFile(bucket_id, file_name).call({"from": from_address}) except Exception as e: raise Exception(f"Transaction reverted: {str(e)}") raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def add_file_chunk(self, from_address: HexAddress, private_key: str, cid: bytes, bucket_id: bytes, name: str, encoded_chunk_size: int, cids: list, chunk_blocks_sizes: list, chunk_index: int, nonce_manager=None) -> HexStr: + def add_file_chunk( + self, + from_address: HexAddress, + private_key: str, + cid: bytes, + bucket_id: bytes, + name: str, + encoded_chunk_size: int, + cids: list, + chunk_blocks_sizes: list, + chunk_index: int, + nonce_manager=None, + ) -> HexStr: """Adds a chunk to a file. - + Args: from_address: Address adding the chunk private_key: Private key for signing the transaction @@ -2218,25 +997,25 @@ def add_file_chunk(self, from_address: HexAddress, private_key: str, cid: bytes, chunk_blocks_sizes: List of block sizes chunk_index: Index of the chunk nonce_manager: Optional nonce manager for coordinated transactions - + Returns: Transaction hash of the add operation """ # Build transaction tx_params = { - 'from': from_address, - 'gas': 1000000, # Higher gas limit for chunk operations - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 1000000, # Higher gas limit for chunk operations + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + tx = self.contract.functions.addFileChunk( cid, bucket_id, name, encoded_chunk_size, cids, chunk_blocks_sizes, chunk_index ).build_transaction(tx_params) - + # Sign transaction signed_tx = Account.sign_transaction(tx, private_key) - + # Send raw transaction try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) @@ -2244,7 +1023,7 @@ def add_file_chunk(self, from_address: HexAddress, private_key: str, cid: bytes, if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + # Wait for receipt receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: @@ -2252,16 +1031,25 @@ def add_file_chunk(self, from_address: HexAddress, private_key: str, cid: bytes, try: self.contract.functions.addFileChunk( cid, bucket_id, name, encoded_chunk_size, cids, chunk_blocks_sizes, chunk_index - ).call({'from': from_address}) + ).call({"from": from_address}) except Exception as e: raise Exception(f"Transaction reverted: {str(e)}") raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def commit_file(self, bucket_id: bytes, file_name: str, encoded_size: int, actual_size: int, root_cid: bytes, from_address: HexAddress, private_key: str) -> None: + def commit_file( + self, + bucket_id: bytes, + file_name: str, + encoded_size: int, + actual_size: int, + root_cid: bytes, + from_address: HexAddress, + private_key: str, + ) -> None: """Updates the file metadata after upload using new ABI signature. - + Args: bucket_id: ID of the bucket (bytes32) file_name: Name of the file @@ -2273,28 +1061,32 @@ def commit_file(self, bucket_id: bytes, file_name: str, encoded_size: int, actua """ # Ensure bucket_id is bytes32 if isinstance(bucket_id, str): - if bucket_id.startswith('0x'): + if bucket_id.startswith("0x"): bucket_id = bytes.fromhex(bucket_id[2:]) else: bucket_id = bytes.fromhex(bucket_id) - + if len(bucket_id) != 32: raise ValueError(f"bucket_id must be 32 bytes, got {len(bucket_id)}") - + # commitFile signature: commitFile(bucketId, name, encodedFileSize, actualSize, fileCID) - tx = self.contract.functions.commitFile(bucket_id, file_name, encoded_size, actual_size, root_cid).build_transaction({ - 'from': from_address, - 'gas': 500000, # Gas limit (adjust as needed) - 'gasPrice': self.web3.eth.gas_price, - 'nonce': self.web3.eth.get_transaction_count(from_address) - }) - + tx = self.contract.functions.commitFile( + bucket_id, file_name, encoded_size, actual_size, root_cid + ).build_transaction( + { + "from": from_address, + "gas": 500000, # Gas limit (adjust as needed) + "gasPrice": self.web3.eth.gas_price, + "nonce": self.web3.eth.get_transaction_count(from_address), + } + ) + # Sign transaction signed_tx = Account.sign_transaction(tx, private_key) - + # Send raw transaction tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) - + # Wait for receipt receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: @@ -2308,20 +1100,22 @@ def commit_file(self, bucket_id: bytes, file_name: str, encoded_size: int, actua print(f"[COMMIT_FILE_ERROR] Root CID: {root_cid.hex()}") raise Exception(f"Transaction failed for commitFile. Status: {receipt.status}, Gas used: {receipt.gasUsed}") - def delete_bucket(self, bucket_name: str, from_address: HexAddress, private_key: str, bucket_id_hex: str = None) -> HexStr: + def delete_bucket( + self, bucket_name: str, from_address: HexAddress, private_key: str, bucket_id_hex: str = None + ) -> HexStr: if not bucket_id_hex: raise Exception("bucket_id_hex is required - get it from IPC BucketView response") - + try: - if bucket_id_hex.startswith('0x'): + if bucket_id_hex.startswith("0x"): bucket_id_hex = bucket_id_hex[2:] - + if len(bucket_id_hex) != 64: - bucket_id_hex = bucket_id_hex.ljust(64, '0') # Pad with zeros if needed - + bucket_id_hex = bucket_id_hex.ljust(64, "0") # Pad with zeros if needed + bucket_id_bytes = bytes.fromhex(bucket_id_hex) print(f"Using bucket_id from IPC: 0x{bucket_id_hex}") - + try: result = self.contract.functions.getBucketIndexByName(bucket_name, from_address).call() bucket_index = result[0] if isinstance(result, (list, tuple)) else result @@ -2330,105 +1124,109 @@ def delete_bucket(self, bucket_name: str, from_address: HexAddress, private_key: except Exception as e: print(f"getBucketIndexByName failed: {e}") raise Exception(f"Failed to get bucket index: {str(e)}") - + except Exception as e: raise Exception(f"Failed to prepare bucket deletion: {str(e)}") # Build transaction parameters - use standard legacy transaction tx_params = { - 'from': from_address, - 'gas': 500000, # Gas limit - 'gasPrice': self.web3.eth.gas_price, - 'nonce': self.web3.eth.get_transaction_count(from_address), + "from": from_address, + "gas": 500000, # Gas limit + "gasPrice": self.web3.eth.gas_price, + "nonce": self.web3.eth.get_transaction_count(from_address), } - + try: print(f"Calling deleteBucket with:") print(f" bucket_id: 0x{bucket_id_hex}") print(f" bucket_name: {bucket_name}") print(f" bucket_index: {bucket_index}") print(f" from_address: {from_address}") - + # Try to call the function first to see if it would revert try: self.contract.functions.deleteBucket( - bucket_id_bytes, # bytes32 id (from IPC BucketView response) - bucket_name, # string name - bucket_index # uint256 index - ).call({'from': from_address}) + bucket_id_bytes, # bytes32 id (from IPC BucketView response) + bucket_name, # string name + bucket_index, # uint256 index + ).call({"from": from_address}) print("deleteBucket call simulation succeeded") except Exception as call_error: print(f"deleteBucket call simulation failed: {call_error}") - + # Try to decode the revert reason error_str = str(call_error) if "execution reverted" in error_str.lower(): # Extract any hex data from the error import re - hex_match = re.search(r'0x[a-fA-F0-9]+', error_str) + + hex_match = re.search(r"0x[a-fA-F0-9]+", error_str) if hex_match: error_data = hex_match.group() print(f"Revert data: {error_data}") - + # Try to decode common error selectors - if error_data.startswith('0x938a92b7'): + if error_data.startswith("0x938a92b7"): print("Error: Bucket not found or doesn't exist") - elif error_data.startswith('0x08c379a0'): + elif error_data.startswith("0x08c379a0"): # Standard revert reason try: from eth_abi import decode_single - reason = decode_single('string', bytes.fromhex(error_data[10:])) + + reason = decode_single("string", bytes.fromhex(error_data[10:])) print(f"Revert reason: {reason}") except: print(f"Could not decode revert reason from: {error_data}") - + raise Exception(f"Contract call simulation failed: {str(call_error)}") - + # Build and send the transaction tx = self.contract.functions.deleteBucket( - bucket_id_bytes, # bytes32 id (from IPC BucketView response) - bucket_name, # string name - bucket_index # uint256 index + bucket_id_bytes, # bytes32 id (from IPC BucketView response) + bucket_name, # string name + bucket_index, # uint256 index ).build_transaction(tx_params) - + print(f"Built transaction") - + # Sign transaction signed_tx = self.web3.eth.account.sign_transaction(tx, private_key) - + # Send transaction tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) print(f"Transaction sent: {tx_hash.hex()}") - + # Wait for receipt receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) print(f"Transaction receipt: status={receipt.status}, gasUsed={receipt.gasUsed}") - + if receipt.status != 1: raise Exception(f"Transaction failed with status: {receipt.status}") - + return tx_hash.hex() - + except Exception as e: raise Exception(f"Failed to delete bucket: {str(e)}") def delete_file(self, auth, file_id: bytes, bucket_id: bytes, file_name: str, file_index: int) -> str: - + # Build transaction - tx = self.contract.functions.deleteFile(file_id, bucket_id, file_name, file_index).build_transaction({ - 'from': auth.address, - 'gas': 500000, # Gas limit - 'gasPrice': self.web3.eth.gas_price, - 'nonce': self.web3.eth.get_transaction_count(auth.address) - }) + tx = self.contract.functions.deleteFile(file_id, bucket_id, file_name, file_index).build_transaction( + { + "from": auth.address, + "gas": 500000, # Gas limit + "gasPrice": self.web3.eth.gas_price, + "nonce": self.web3.eth.get_transaction_count(auth.address), + } + ) signed_tx = Account.sign_transaction(tx, auth.key) - + tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception("Transaction failed") - + return tx_hash.hex() def get_bucket(self, bucket_name: str, owner_address: str = None) -> Tuple[str, int, HexAddress]: @@ -2440,20 +1238,24 @@ def get_bucket(self, bucket_name: str, owner_address: str = None) -> Tuple[str, def get_file(self, bucket_name: str, file_name: str, owner_address: str = None) -> Tuple[str, bytes, int, int]: if owner_address is None: raise ValueError("Owner address must be provided for bucket lookup") - + bucket = self.contract.functions.getBucketByName(bucket_name, owner_address, 0, 10).call() bucket_id = bucket[0] # bytes32 id file_info = self.contract.functions.getFileByName(bucket_id, file_name).call() return (file_info[3], file_info[0], file_info[4], file_info[5]) # (name, id, encodedSize, createdAt) - def get_bucket_by_name(self, call_opts: dict, bucket_name: str, owner_address: str = None, file_offset: int = 0, file_limit: int = 10): - if owner_address is None and call_opts and 'from' in call_opts: - owner_address = call_opts['from'] + def get_bucket_by_name( + self, call_opts: dict, bucket_name: str, owner_address: str = None, file_offset: int = 0, file_limit: int = 10 + ): + if owner_address is None and call_opts and "from" in call_opts: + owner_address = call_opts["from"] elif owner_address is None: raise ValueError("Owner address must be provided either as parameter or in call_opts['from']") - + if call_opts: - return self.contract.functions.getBucketByName(bucket_name, owner_address, file_offset, file_limit).call(call_opts) + return self.contract.functions.getBucketByName(bucket_name, owner_address, file_offset, file_limit).call( + call_opts + ) else: return self.contract.functions.getBucketByName(bucket_name, owner_address, file_offset, file_limit).call() @@ -2471,10 +1273,10 @@ def get_file_index_by_id(self, call_opts: dict, bucket_name: str, file_id: bytes def is_file_filled(self, file_id: bytes) -> bool: """Returns info about file status. - + Args: file_id: ID of the file to check - + Returns: True if file is filled, False otherwise """ @@ -2482,50 +1284,60 @@ def is_file_filled(self, file_id: bytes) -> bool: def is_file_filled_v2(self, file_id: bytes) -> bool: """Returns info about file status (V2 - uses loop to iterate through each chunk). - + Args: file_id: ID of the file to check - + Returns: True if file is filled, False otherwise """ return self.contract.functions.isFileFilledV2(file_id).call() def get_upgrade_interface_version(self) -> str: - + return self.contract.functions.UPGRADE_INTERFACE_VERSION().call() - def add_file_chunks(self, from_address: HexAddress, private_key: str, cids: List[bytes], bucket_id: bytes, - file_name: str, encoded_chunk_sizes: List[int], chunk_blocks_cids: List[List[bytes]], - chunk_block_sizes: List[List[int]], starting_chunk_index: int, nonce_manager=None) -> HexStr: - + def add_file_chunks( + self, + from_address: HexAddress, + private_key: str, + cids: List[bytes], + bucket_id: bytes, + file_name: str, + encoded_chunk_sizes: List[int], + chunk_blocks_cids: List[List[bytes]], + chunk_block_sizes: List[List[int]], + starting_chunk_index: int, + nonce_manager=None, + ) -> HexStr: + tx_params = { - 'from': from_address, - 'gas': 2000000, # Higher gas limit for multiple chunks - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 2000000, # Higher gas limit for multiple chunks + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + tx = self.contract.functions.addFileChunks( cids, bucket_id, file_name, encoded_chunk_sizes, chunk_blocks_cids, chunk_block_sizes, starting_chunk_index ).build_transaction(tx_params) - + signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def get_file_by_id(self, file_id: bytes): + def get_file_by_id(self, file_id: bytes): return self.contract.functions.getFileById(file_id).call() def get_bucket_index_by_name(self, bucket_name: str, owner_address: HexAddress) -> Tuple[int, bool]: @@ -2549,32 +1361,35 @@ def get_file_owner(self, file_id: bytes) -> HexAddress: def get_buckets_by_ids(self, bucket_ids: List[bytes]): return self.contract.functions.getBucketsByIds(bucket_ids).call() - def get_owner_buckets(self, owner_address: HexAddress, offset: int = 0, limit: int = 10, - file_offset: int = 0, file_limit: int = 10): + def get_owner_buckets( + self, owner_address: HexAddress, offset: int = 0, limit: int = 10, file_offset: int = 0, file_limit: int = 10 + ): return self.contract.functions.getOwnerBuckets(owner_address, offset, limit, file_offset, file_limit).call() - def initialize_contract(self, from_address: HexAddress, private_key: str, token_address: HexAddress, nonce_manager=None) -> HexStr: + def initialize_contract( + self, from_address: HexAddress, private_key: str, token_address: HexAddress, nonce_manager=None + ) -> HexStr: tx_params = { - 'from': from_address, - 'gas': 500000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 500000, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + tx = self.contract.functions.initialize(token_address).build_transaction(tx_params) signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() def get_timestamp(self) -> int: @@ -2583,129 +1398,152 @@ def get_timestamp(self) -> int: def get_token(self) -> HexAddress: return self.contract.functions.token().call() - def add_peer_block(self, from_address: HexAddress, private_key: str, peer_id: bytes, cid: bytes, - file_name: str, is_replica: bool, nonce_manager=None) -> HexStr: + def add_peer_block( + self, + from_address: HexAddress, + private_key: str, + peer_id: bytes, + cid: bytes, + file_name: str, + is_replica: bool, + nonce_manager=None, + ) -> HexStr: tx_params = { - 'from': from_address, - 'gas': 500000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 500000, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + tx = self.contract.functions.addPeerBlock(peer_id, cid, file_name, is_replica).build_transaction(tx_params) signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def delete_peer_block(self, from_address: HexAddress, private_key: str, block_id: bytes, - peer_id: bytes, cid: bytes, file_name: str, index: int, nonce_manager=None) -> HexStr: + def delete_peer_block( + self, + from_address: HexAddress, + private_key: str, + block_id: bytes, + peer_id: bytes, + cid: bytes, + file_name: str, + index: int, + nonce_manager=None, + ) -> HexStr: tx_params = { - 'from': from_address, - 'gas': 500000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 500000, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - - tx = self.contract.functions.deletePeerBlock(block_id, peer_id, cid, file_name, index).build_transaction(tx_params) + + tx = self.contract.functions.deletePeerBlock(block_id, peer_id, cid, file_name, index).build_transaction( + tx_params + ) signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def fill_chunk_block(self, from_address: HexAddress, private_key: str, fill_args: dict, nonce_manager=None) -> HexStr: + def fill_chunk_block( + self, from_address: HexAddress, private_key: str, fill_args: dict, nonce_manager=None + ) -> HexStr: tx_params = { - 'from': from_address, - 'gas': 1000000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 1000000, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + args_tuple = ( - fill_args['blockCID'], - fill_args['nodeId'], - fill_args['bucketId'], - fill_args['chunkIndex'], - fill_args['nonce'], - fill_args['blockIndex'], - fill_args['fileName'], - fill_args['signature'], - fill_args['deadline'] + fill_args["blockCID"], + fill_args["nodeId"], + fill_args["bucketId"], + fill_args["chunkIndex"], + fill_args["nonce"], + fill_args["blockIndex"], + fill_args["fileName"], + fill_args["signature"], + fill_args["deadline"], ) - + tx = self.contract.functions.fillChunkBlock(args_tuple).build_transaction(tx_params) signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def fill_chunk_blocks(self, from_address: HexAddress, private_key: str, fill_args_list: List[dict], nonce_manager=None) -> HexStr: + def fill_chunk_blocks( + self, from_address: HexAddress, private_key: str, fill_args_list: List[dict], nonce_manager=None + ) -> HexStr: tx_params = { - 'from': from_address, - 'gas': 2000000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 2000000, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + args_tuples = [] for fill_args in fill_args_list: args_tuple = ( - fill_args['blockCID'], - fill_args['nodeId'], - fill_args['bucketId'], - fill_args['chunkIndex'], - fill_args['nonce'], - fill_args['blockIndex'], - fill_args['fileName'], - fill_args['signature'], - fill_args['deadline'] + fill_args["blockCID"], + fill_args["nodeId"], + fill_args["bucketId"], + fill_args["chunkIndex"], + fill_args["nonce"], + fill_args["blockIndex"], + fill_args["fileName"], + fill_args["signature"], + fill_args["deadline"], ) args_tuples.append(args_tuple) - + tx = self.contract.functions.fillChunkBlocks(args_tuples).build_transaction(tx_params) signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() def get_chunk_by_index(self, file_id: bytes, index: int) -> Tuple[bytes, int]: @@ -2714,8 +1552,14 @@ def get_chunk_by_index(self, file_id: bytes, index: int) -> Tuple[bytes, int]: def get_full_file_info(self, bucket_name: str, file_name: str, bucket_id: bytes, owner_address: HexAddress): return self.contract.functions.getFullFileInfo(bucket_name, file_name, bucket_id, owner_address).call() - def get_buckets_by_ids_with_files(self, bucket_ids: List[bytes], bucket_offset: int = 0, - bucket_limit: int = 10, file_offset: int = 0, file_limit: int = 10): + def get_buckets_by_ids_with_files( + self, + bucket_ids: List[bytes], + bucket_offset: int = 0, + bucket_limit: int = 10, + file_offset: int = 0, + file_limit: int = 10, + ): return self.contract.functions.getBucketsByIdsWithFiles( bucket_ids, bucket_offset, bucket_limit, file_offset, file_limit ).call() @@ -2747,52 +1591,59 @@ def get_eip712_domain(self): def get_proxiable_uuid(self) -> bytes: return self.contract.functions.proxiableUUID().call() - def set_access_manager(self, from_address: HexAddress, private_key: str, access_manager_address: HexAddress, - nonce_manager=None) -> HexStr: + def set_access_manager( + self, from_address: HexAddress, private_key: str, access_manager_address: HexAddress, nonce_manager=None + ) -> HexStr: tx_params = { - 'from': from_address, - 'gas': 500000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 500000, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + tx = self.contract.functions.setAccessManager(access_manager_address).build_transaction(tx_params) signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() - def upgrade_to_and_call(self, from_address: HexAddress, private_key: str, new_implementation: HexAddress, - data: bytes, nonce_manager=None) -> HexStr: + def upgrade_to_and_call( + self, + from_address: HexAddress, + private_key: str, + new_implementation: HexAddress, + data: bytes, + nonce_manager=None, + ) -> HexStr: tx_params = { - 'from': from_address, - 'gas': 1000000, - 'gasPrice': self.web3.eth.gas_price, - 'nonce': nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address) + "from": from_address, + "gas": 1000000, + "gasPrice": self.web3.eth.gas_price, + "nonce": nonce_manager.get_nonce() if nonce_manager else self.web3.eth.get_transaction_count(from_address), } - + tx = self.contract.functions.upgradeToAndCall(new_implementation, data).build_transaction(tx_params) signed_tx = Account.sign_transaction(tx, private_key) - + try: tx_hash = self.web3.eth.send_raw_transaction(get_raw_transaction(signed_tx)) except Exception as e: if nonce_manager and "nonce too low" in str(e): nonce_manager.reset_nonce() raise - + receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise Exception(f"Transaction failed. Receipt: {receipt}") - + return tx_hash.hex() diff --git a/private/ipc/errors.py b/private/ipc/errors.py index c0eb090..4e92520 100644 --- a/private/ipc/errors.py +++ b/private/ipc/errors.py @@ -1,23 +1,23 @@ - from typing import Optional, Any, Dict from web3.exceptions import ContractLogicError def error_hash_to_error(error_data: Any) -> Exception: - if hasattr(error_data, 'args') and error_data.args: + if hasattr(error_data, "args") and error_data.args: error_str = str(error_data.args[0]) if error_data.args else str(error_data) else: error_str = str(error_data) hash_code = None if isinstance(error_str, str): import re - hex_match = re.search(r'0x[a-fA-F0-9]{8}', error_str) + + hex_match = re.search(r"0x[a-fA-F0-9]{8}", error_str) if hex_match: hash_code = hex_match.group(0).lower() - + error_map = { "0x497ef2c2": "BucketAlreadyExists", - "0x4f4b202a": "BucketInvalid", + "0x4f4b202a": "BucketInvalid", "0xdc64d0ad": "BucketInvalidOwner", "0x938a92b7": "BucketNonexists", "0x89fddc00": "BucketNonempty", @@ -62,9 +62,9 @@ def error_hash_to_error(error_data: Any) -> Exception: "0x923b8cbb": "NonceAlreadyUsed", "0x9605a010": "OffsetOutOfBounds", } - + if hash_code and hash_code in error_map: - return Exception(error_map[hash_code]) + return Exception(error_map[hash_code]) return error_data if isinstance(error_data, Exception) else Exception(str(error_data)) @@ -76,4 +76,4 @@ def ignore_offset_error(error: Exception) -> Optional[Exception]: def parse_errors_to_hashes() -> None: - pass \ No newline at end of file + pass diff --git a/private/ipc/ipc.py b/private/ipc/ipc.py index 41f7871..635c0f3 100644 --- a/private/ipc/ipc.py +++ b/private/ipc/ipc.py @@ -8,6 +8,7 @@ try: from multiformats import CID + MULTIFORMATS_AVAILABLE = True except ImportError: MULTIFORMATS_AVAILABLE = False @@ -18,6 +19,7 @@ except ImportError: import sys import os + sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from eip712 import Domain as EIP712Domain, TypedData as EIP712TypedData, sign as eip712_sign @@ -25,13 +27,13 @@ @dataclass class StorageData: chunk_cid: bytes - block_cid: bytes + block_cid: bytes chunk_index: int - block_index: int - node_id: bytes + block_index: int + node_id: bytes nonce: int deadline: int - bucket_id: bytes + bucket_id: bytes def to_message_dict(self) -> Dict[str, Any]: return { @@ -53,40 +55,40 @@ def generate_nonce() -> int: def calculate_file_id(bucket_id: bytes, name: str) -> bytes: if not isinstance(bucket_id, (bytes, bytearray)): raise TypeError("bucket_id must be bytes") - - data = bucket_id + name.encode('utf-8') + + data = bucket_id + name.encode("utf-8") return keccak(data) def calculate_bucket_id(bucket_name: str, address: str) -> bytes: - data = bucket_name.encode('utf-8') - + data = bucket_name.encode("utf-8") + addr = address.lower() if addr.startswith("0x"): addr = addr[2:] if len(addr) != 40: raise ValueError("address must be a 20-byte hex string") - + address_bytes = bytes.fromhex(addr) data += address_bytes - + return keccak(data) -def from_byte_array_cid(data: bytes) -> 'CID': +def from_byte_array_cid(data: bytes) -> "CID": if not MULTIFORMATS_AVAILABLE: raise ImportError("multiformats library is required") - + if len(data) != 32: raise ValueError(f"expected 32 bytes, got {len(data)}") - + # CID v1, dag-pb (0x70), sha2-256 (0x12) # Construct CID bytes: version + codec + multihash # multihash format: hash_type (0x12 for sha2-256) + length (0x20 = 32) + hash_data multicodec_dagpb = bytes([0x70]) multihash_sha256_header = bytes([0x12, 0x20]) cid_bytes = bytes([0x01]) + multicodec_dagpb + multihash_sha256_header + data - + return CID.decode(cid_bytes) @@ -120,4 +122,4 @@ def sign_block(private_key_hex: str, storage_address: str, chain_id: int, data: message = data.to_message_dict() - return eip712_sign(private_key_bytes, domain, "StorageData", storage_data_types, message) \ No newline at end of file + return eip712_sign(private_key_bytes, domain, "StorageData", storage_data_types, message) diff --git a/private/ipc/ipc_test.py b/private/ipc/ipc_test.py index 694c0f9..38d54f2 100644 --- a/private/ipc/ipc_test.py +++ b/private/ipc/ipc_test.py @@ -6,6 +6,7 @@ try: from multiformats import CID + MULTIFORMATS_AVAILABLE = True except ImportError: MULTIFORMATS_AVAILABLE = False @@ -15,15 +16,15 @@ def test_generate_nonce(): for i in range(10): nonce = generate_nonce() - + if i > 0: print(f"retrying to sample nonce {i}") - - nonce_bytes = nonce.to_bytes((nonce.bit_length() + 7) // 8, byteorder='big') + + nonce_bytes = nonce.to_bytes((nonce.bit_length() + 7) // 8, byteorder="big") if len(nonce_bytes) == 32: break - - nonce_bytes = nonce.to_bytes((nonce.bit_length() + 7) // 8, byteorder='big') + + nonce_bytes = nonce.to_bytes((nonce.bit_length() + 7) // 8, byteorder="big") assert len(nonce_bytes) == 32 @@ -45,7 +46,7 @@ def test_calculate_file_id(): "expected": bytes.fromhex("3eb92385cd986662e9885c47364fa5b2f154cd6fca8d99f4aed68160064991cb"), }, ] - + for tc in test_cases: file_id = calculate_file_id(tc["bucket_id"], tc["name"]) assert file_id == tc["expected"] @@ -69,7 +70,7 @@ def test_calculate_bucket_id(): "expected": "8f92db9fde643ed88b4dc2e238e329bafdff4a172b34d0501c2f46a0d2c36696", }, ] - + for tc in test_cases: bucket_id = calculate_bucket_id(tc["bucket_name"], tc["address"]) assert bucket_id.hex() == tc["expected"] @@ -78,16 +79,17 @@ def test_calculate_bucket_id(): def test_from_byte_array_cid(): if not MULTIFORMATS_AVAILABLE: pytest.skip("multiformats library not available") - + import secrets + data = secrets.token_bytes(32) - + reconstructed_cid = from_byte_array_cid(data) assert reconstructed_cid is not None - + # Check that the CID is valid assert reconstructed_cid.version == 1 - assert reconstructed_cid.codec.name == 'dag-pb' + assert reconstructed_cid.codec.name == "dag-pb" if __name__ == "__main__": diff --git a/private/ipc/pdp_test.py b/private/ipc/pdp_test.py index cc15247..803195d 100644 --- a/private/ipc/pdp_test.py +++ b/private/ipc/pdp_test.py @@ -10,7 +10,6 @@ from ..ipctest.ipctest import new_funded_account, to_wei, wait_for_tx - DIAL_URI = os.getenv("DIAL_URI", "") PRIVATE_KEY = os.getenv("PRIVATE_KEY", "") @@ -28,13 +27,8 @@ def pick_private_key() -> str: def fill_blocks(dial_uri: str): - payload = { - "jsonrpc": "2.0", - "method": "anvil_mine", - "params": [], - "id": 1 - } - + payload = {"jsonrpc": "2.0", "method": "anvil_mine", "params": [], "id": 1} + response = requests.post(dial_uri, json=payload, headers={"Content-Type": "application/json"}) response.raise_for_status() @@ -42,9 +36,9 @@ def fill_blocks(dial_uri: str): def pad_left(data: bytes, size: int) -> bytes: if len(data) >= size: return data - + padded = bytearray(size) - padded[size - len(data):] = data + padded[size - len(data) :] = data return bytes(padded) @@ -52,63 +46,344 @@ def pad_left(data: bytes, size: int) -> bytes: def test_contract_pdp(): dial_uri = pick_dial_uri() private_key = pick_private_key() - + pk = new_funded_account(private_key, dial_uri, to_wei(10)) - + web3 = Web3(Web3.HTTPProvider(dial_uri)) assert web3.is_connected() - + chain_id = web3.eth.chain_id - + challenge_finality = 3 - + from .contracts.pdp_verifier import deploy_pdp_verifier, PDPVerifierMetaData from .contracts.sink import deploy_sink - - verifier_address, tx_hash = deploy_pdp_verifier( - web3, pk, challenge_finality - ) + + verifier_address, tx_hash = deploy_pdp_verifier(web3, pk, challenge_finality) wait_for_tx(web3, tx_hash) - + pdp_verifier = web3.eth.contract(address=verifier_address, abi=PDPVerifierMetaData.ABI) - + sink_address, tx_hash = deploy_sink(web3, pk) wait_for_tx(web3, tx_hash) - - value_wei = web3.to_wei(1, 'ether') - - tx = pdp_verifier.functions.createProofSet(sink_address, b'').build_transaction({ - 'from': pk.address, - 'value': value_wei, - 'gas': 500000, - 'gasPrice': web3.eth.gas_price, - 'nonce': web3.eth.get_transaction_count(pk.address), - }) - + + value_wei = web3.to_wei(1, "ether") + + tx = pdp_verifier.functions.createProofSet(sink_address, b"").build_transaction( + { + "from": pk.address, + "value": value_wei, + "gas": 500000, + "gasPrice": web3.eth.gas_price, + "nonce": web3.eth.get_transaction_count(pk.address), + } + ) + signed_tx = pk.sign_transaction(tx) tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction) receipt = wait_for_tx(web3, tx_hash) - + set_id = 0 - for log in receipt['logs']: - if log['address'].lower() == verifier_address.lower(): - set_id = int.from_bytes(log['topics'][1], byteorder='big') + for log in receipt["logs"]: + if log["address"].lower() == verifier_address.lower(): + set_id = int.from_bytes(log["topics"][1], byteorder="big") break - - data = bytes([ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 7, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - ]) - + + data = bytes( + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 1, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 2, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 3, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 4, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 5, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 6, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 7, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 8, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + ] + ) + assert len(data) == 288 - + print(f"PDP test completed with set_id: {set_id}") diff --git a/private/ipc/transactiondata_parser.py b/private/ipc/transactiondata_parser.py index 2ee4abd..a281db1 100644 --- a/private/ipc/transactiondata_parser.py +++ b/private/ipc/transactiondata_parser.py @@ -6,6 +6,7 @@ try: from multiformats import CID + MULTIFORMATS_AVAILABLE = True except ImportError: MULTIFORMATS_AVAILABLE = False @@ -17,68 +18,69 @@ @dataclass class AddChunkTransactionData: - cid: 'CID' + cid: "CID" bucket_id: bytes file_name: str encoded_size: int - block_cids: List['CID'] + block_cids: List["CID"] block_sizes: List[int] index: int -def from_byte_array_cid(data: bytes) -> 'CID': +def from_byte_array_cid(data: bytes) -> "CID": if not MULTIFORMATS_AVAILABLE: raise ImportError("multiformats library is required") - + if len(data) != 32: raise ValueError(f"expected 32 bytes, got {len(data)}") - + multicodec_dagpb = bytes([0x70]) multihash_sha256_header = bytes([0x12, 0x20]) cid_bytes = bytes([0x01]) + multicodec_dagpb + multihash_sha256_header + data - + return CID.decode(cid_bytes) def parse_add_chunk_tx(storage_contract_abi: dict, tx_data: bytes) -> AddChunkTransactionData: if len(tx_data) < CONTRACT_METHOD_SIGNATURE_LEN: raise ValueError("invalid transaction data length") - + method_abi = None for item in storage_contract_abi: - if item.get('type') == 'function' and item.get('name') == 'addFileChunk': + if item.get("type") == "function" and item.get("name") == "addFileChunk": method_abi = item break - + if not method_abi: raise ValueError("method addFileChunk not found in ABI") - + from web3 import Web3 + contract = Web3().eth.contract(abi=[method_abi]) - + fn, params = contract.decode_function_input(tx_data) - - chunk_cid_bytes = params['chunkCID'] + + chunk_cid_bytes = params["chunkCID"] if not MULTIFORMATS_AVAILABLE: raise ImportError("multiformats library is required") chunk_cid = CID.decode(chunk_cid_bytes) - - bucket_id = params['bucketId'] - file_name = params['fileName'] - encoded_size = int(params['encodedChunkSize']) - block_cid_arrays = params['cids'] - block_sizes_bigint = params['chunkBlocksSizes'] - chunk_index = int(params['chunkIndex']) - + + bucket_id = params["bucketId"] + file_name = params["fileName"] + encoded_size = int(params["encodedChunkSize"]) + block_cid_arrays = params["cids"] + block_sizes_bigint = params["chunkBlocksSizes"] + chunk_index = int(params["chunkIndex"]) + block_cids = [] for b in block_cid_arrays: block_cids.append(from_byte_array_cid(b)) - + block_sizes = [int(size) for size in block_sizes_bigint] - + if len(block_cids) != len(block_sizes): raise ValueError("mismatched block CIDs and sizes lengths") - + return AddChunkTransactionData( cid=chunk_cid, bucket_id=bucket_id, @@ -93,54 +95,57 @@ def parse_add_chunk_tx(storage_contract_abi: dict, tx_data: bytes) -> AddChunkTr def parse_add_chunks_tx(storage_contract_abi: dict, tx_data: bytes) -> List[AddChunkTransactionData]: if len(tx_data) < CONTRACT_METHOD_SIGNATURE_LEN: raise ValueError("invalid transaction data length") - + method_abi = None for item in storage_contract_abi: - if item.get('type') == 'function' and item.get('name') == 'addFileChunks': + if item.get("type") == "function" and item.get("name") == "addFileChunks": method_abi = item break - + if not method_abi: raise ValueError("method addFileChunks not found in ABI") - + from web3 import Web3 + contract = Web3().eth.contract(abi=[method_abi]) - + fn, params = contract.decode_function_input(tx_data) - - chunk_cids_bytes = params['cids'] - bucket_id = params['bucketId'] - file_name = params['fileName'] - encoded_chunk_sizes = params['encodedChunkSizes'] - chunk_blocks_cids = params['chunkBlocksCIDs'] - chunk_blocks_sizes = params['chunkBlockSizes'] - starting_chunk_index = int(params['startingChunkIndex']) - + + chunk_cids_bytes = params["cids"] + bucket_id = params["bucketId"] + file_name = params["fileName"] + encoded_chunk_sizes = params["encodedChunkSizes"] + chunk_blocks_cids = params["chunkBlocksCIDs"] + chunk_blocks_sizes = params["chunkBlockSizes"] + starting_chunk_index = int(params["startingChunkIndex"]) + num_chunks = len(chunk_cids_bytes) chunks = [] - + for i in range(num_chunks): if not MULTIFORMATS_AVAILABLE: raise ImportError("multiformats library is required") chunk_cid = CID.decode(chunk_cids_bytes[i]) - + block_cids = [] for b in chunk_blocks_cids[i]: block_cids.append(from_byte_array_cid(b)) - + block_sizes = [int(size) for size in chunk_blocks_sizes[i]] - + if len(block_cids) != len(block_sizes): raise ValueError(f"mismatched block CIDs and sizes for chunk {i}") - - chunks.append(AddChunkTransactionData( - cid=chunk_cid, - bucket_id=bucket_id, - file_name=file_name, - encoded_size=int(encoded_chunk_sizes[i]), - block_cids=block_cids, - block_sizes=block_sizes, - index=starting_chunk_index + i, - )) - + + chunks.append( + AddChunkTransactionData( + cid=chunk_cid, + bucket_id=bucket_id, + file_name=file_name, + encoded_size=int(encoded_chunk_sizes[i]), + block_cids=block_cids, + block_sizes=block_sizes, + index=starting_chunk_index + i, + ) + ) + return chunks diff --git a/private/ipctest/__init__.py b/private/ipctest/__init__.py index 7e50829..fb131e7 100644 --- a/private/ipctest/__init__.py +++ b/private/ipctest/__init__.py @@ -11,7 +11,7 @@ __all__ = [ "IPCTestError", - "TransactionFailedError", + "TransactionFailedError", "NonceTooLowError", "ReplaceUnderpricedError", "new_funded_account", diff --git a/private/ipctest/ipctest.py b/private/ipctest/ipctest.py index 7a4876a..3774847 100644 --- a/private/ipctest/ipctest.py +++ b/private/ipctest/ipctest.py @@ -10,49 +10,50 @@ _nonce_lock = threading.Lock() + class IPCTestError(Exception): pass + class TransactionFailedError(IPCTestError): pass + class NonceTooLowError(IPCTestError): pass + class ReplaceUnderpricedError(IPCTestError): pass + def new_funded_account( - source_private_key: str, - dial_uri: str, - amount_wei: int, - max_retries: int = 10, - retry_delay: float = 0.01 + source_private_key: str, dial_uri: str, amount_wei: int, max_retries: int = 10, retry_delay: float = 0.01 ) -> LocalAccount: try: - if source_private_key.startswith('0x'): + if source_private_key.startswith("0x"): source_private_key = source_private_key[2:] source_account = Account.from_key(source_private_key) except Exception as e: raise IPCTestError(f"Failed to load private key: {e}") - + try: dest_account = Account.create() except Exception as e: raise IPCTestError(f"Failed to generate private key: {e}") - + try: web3 = Web3(Web3.HTTPProvider(dial_uri)) if not web3.is_connected(): raise IPCTestError(f"Failed to connect to {dial_uri}") except Exception as e: raise IPCTestError(f"Failed to connect to {dial_uri}: {e}") - + try: chain_id = web3.eth.chain_id except Exception as e: raise IPCTestError(f"Failed to get network ID: {e}") - + for attempt in range(max_retries): try: deposit( @@ -60,7 +61,7 @@ def new_funded_account( dest_address=dest_account.address, source_account=source_account, amount_wei=amount_wei, - chain_id=chain_id + chain_id=chain_id, ) return dest_account except (NonceTooLowError, ReplaceUnderpricedError) as e: @@ -70,51 +71,47 @@ def new_funded_account( raise IPCTestError(f"Failed to deposit after {max_retries} attempts: {e}") except Exception as e: raise IPCTestError(f"Failed to deposit: {e}") - + raise IPCTestError(f"Failed to deposit account {max_retries} times") -def deposit( - web3: Web3, - dest_address: str, - source_account: LocalAccount, - amount_wei: int, - chain_id: int -) -> None: + +def deposit(web3: Web3, dest_address: str, source_account: LocalAccount, amount_wei: int, chain_id: int) -> None: source_address = source_account.address - + with _nonce_lock: try: - nonce = web3.eth.get_transaction_count(source_address, 'pending') + nonce = web3.eth.get_transaction_count(source_address, "pending") except Exception as e: raise IPCTestError(f"Failed to get nonce: {e}") - + try: gas_price = web3.eth.gas_price gas_limit = 21000 - + transaction = { - 'chainId': chain_id, - 'nonce': nonce, - 'maxFeePerGas': gas_price * 2, - 'maxPriorityFeePerGas': gas_price, - 'gas': gas_limit, - 'to': dest_address, - 'value': amount_wei, - 'data': b'', + "chainId": chain_id, + "nonce": nonce, + "maxFeePerGas": gas_price * 2, + "maxPriorityFeePerGas": gas_price, + "gas": gas_limit, + "to": dest_address, + "value": amount_wei, + "data": b"", } - signed_txn = source_account.sign_transaction(transaction) + signed_txn = source_account.sign_transaction(transaction) tx_hash = web3.eth.send_raw_transaction(signed_txn.rawTransaction) wait_for_tx(web3, tx_hash) - + except Exception as e: error_msg = str(e).lower() - if 'nonce too low' in error_msg: + if "nonce too low" in error_msg: raise NonceTooLowError(f"Nonce too low: {e}") - elif 'replacement transaction underpriced' in error_msg: + elif "replacement transaction underpriced" in error_msg: raise ReplaceUnderpricedError(f"Replacement transaction underpriced: {e}") else: raise IPCTestError(f"Transaction failed: {e}") + def to_wei(amount: Union[int, float]) -> int: return int(amount * 10**18) @@ -124,40 +121,35 @@ def private_key_to_hex(private_key: LocalAccount) -> str: return pk_bytes.hex() -def wait_for_tx( - web3: Web3, - tx_hash: Union[str, bytes], - timeout: float = 120.0, - poll_interval: float = 0.2 -): - +def wait_for_tx(web3: Web3, tx_hash: Union[str, bytes], timeout: float = 120.0, poll_interval: float = 0.2): + if isinstance(tx_hash, str): - if tx_hash.startswith('0x'): + if tx_hash.startswith("0x"): tx_hash = bytes.fromhex(tx_hash[2:]) else: tx_hash = bytes.fromhex(tx_hash) - + start_time = time.time() - + try: receipt = web3.eth.get_transaction_receipt(tx_hash) - if receipt['status'] == 1: + if receipt["status"] == 1: return receipt else: raise IPCTestError("Transaction failed") except TransactionNotFound: - pass + pass except Exception as e: raise IPCTestError(f"Error checking transaction receipt: {e}") - + while True: current_time = time.time() if current_time - start_time > timeout: raise IPCTestError(f"Timeout waiting for transaction {tx_hash.hex()}") - + try: receipt = web3.eth.get_transaction_receipt(tx_hash) - if receipt['status'] == 1: + if receipt["status"] == 1: return receipt else: raise IPCTestError("Transaction failed") diff --git a/private/memory/__init__.py b/private/memory/__init__.py index 05e1e31..a861484 100644 --- a/private/memory/__init__.py +++ b/private/memory/__init__.py @@ -4,4 +4,4 @@ from .memory import Size -__all__ = ['Size'] +__all__ = ["Size"] diff --git a/private/memory/memory.py b/private/memory/memory.py index b88c823..d1bb0c3 100644 --- a/private/memory/memory.py +++ b/private/memory/memory.py @@ -23,10 +23,10 @@ def __str__(self) -> str: def to_int(self) -> int: return self.size - def mul_int(self, n: int) -> 'Size': + def mul_int(self, n: int) -> "Size": return Size(self.size * n) - def div_int(self, n: int) -> 'Size': + def div_int(self, n: int) -> "Size": return Size(self.size // n) def format_size(self) -> str: diff --git a/private/pdptest/pdptest.py b/private/pdptest/pdptest.py index 9e272c9..c27f9c5 100644 --- a/private/pdptest/pdptest.py +++ b/private/pdptest/pdptest.py @@ -22,21 +22,22 @@ def pick_server_url() -> str: pytest.skip("PDP server URL flag missing, example: -pdp-server-url=") return PDP_SERVER_URL -#temporary solution to calculate piece CID + +# temporary solution to calculate piece CID def calculate_piece_cid(data: bytes) -> str: try: import subprocess import tempfile import os - - with tempfile.NamedTemporaryFile(mode='wb', delete=False) as f: + + with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f: f.write(data) temp_path = f.name - + try: result = subprocess.run( - ['go', 'run', '-', temp_path], - input=''' + ["go", "run", "-", temp_path], + input=""" package main import ( "fmt" @@ -62,19 +63,19 @@ def calculate_piece_cid(data: bytes) -> str: } fmt.Print(pieceCid.String()) } -''', +""", capture_output=True, text=True, - timeout=30 + timeout=30, ) - + if result.returncode != 0: raise RuntimeError(f"Failed to calculate piece CID: {result.stderr}") - + return result.stdout.strip() finally: os.unlink(temp_path) - + except FileNotFoundError: raise RuntimeError( "Go toolchain not found. To calculate piece CIDs, you need:\n" diff --git a/private/retry/retry.py b/private/retry/retry.py index 153bfe9..db2459d 100644 --- a/private/retry/retry.py +++ b/private/retry/retry.py @@ -11,43 +11,43 @@ class WithRetry: def __init__(self, max_attempts: int, base_delay: float): self.max_attempts = max_attempts self.base_delay = base_delay - + def do(self, f: Callable[[], Tuple[bool, Exception]]) -> Exception: for attempt in range(self.max_attempts + 1): needs_retry, err = f() if err is None: return None - + if not needs_retry or attempt >= self.max_attempts: return err - - backoff = self.base_delay * (2 ** attempt) + + backoff = self.base_delay * (2**attempt) jitter = random.uniform(0, self.base_delay) delay = backoff + jitter - + time.sleep(delay) - + return err - + async def do_async(self, ctx, f: Callable[[], Tuple[bool, Exception]]) -> Exception: for attempt in range(self.max_attempts + 1): if ctx and ctx.done(): return Exception(f"retry aborted: context cancelled") - + needs_retry, err = f() if err is None: return None - + if not needs_retry or attempt >= self.max_attempts: return err - - backoff = self.base_delay * (2 ** attempt) + + backoff = self.base_delay * (2**attempt) jitter = random.uniform(0, self.base_delay) delay = backoff + jitter - + try: await asyncio.wait_for(asyncio.sleep(delay), timeout=delay + 1) except asyncio.CancelledError: return Exception(f"retry aborted: {err}") - + return err diff --git a/private/retry/retry_test.py b/private/retry/retry_test.py index a75b1d6..feabec1 100644 --- a/private/retry/retry_test.py +++ b/private/retry/retry_test.py @@ -9,12 +9,12 @@ def test_success_on_first_attempt(): retry = WithRetry(max_attempts=3, base_delay=0.01) call_count = 0 - + def f(): nonlocal call_count call_count += 1 return False, None - + err = retry.do(f) assert err is None assert call_count == 1 @@ -24,12 +24,12 @@ def test_failure_without_retry(): retry = WithRetry(max_attempts=3, base_delay=0.01) call_count = 0 test_err = Exception("test error") - + def f(): nonlocal call_count call_count += 1 return False, test_err - + err = retry.do(f) assert err is not None assert str(err) == "test error" @@ -40,14 +40,14 @@ def test_retry_and_success(): retry = WithRetry(max_attempts=3, base_delay=0.01) call_count = 0 test_err = Exception("test error") - + def f(): nonlocal call_count call_count += 1 if call_count < 3: return True, test_err return False, None - + err = retry.do(f) assert err is None assert call_count == 3 @@ -57,12 +57,12 @@ def test_retry_exceeds_max_attempts(): retry = WithRetry(max_attempts=2, base_delay=0.01) call_count = 0 test_err = Exception("test error") - + def f(): nonlocal call_count call_count += 1 return True, test_err - + err = retry.do(f) assert err is not None assert str(err) == "test error" @@ -73,12 +73,12 @@ def test_max_attempts_zero(): retry = WithRetry(max_attempts=0, base_delay=0.01) call_count = 0 test_err = Exception("test error") - + def f(): nonlocal call_count call_count += 1 return True, test_err - + err = retry.do(f) assert err is not None assert str(err) == "test error" diff --git a/sdk/__init__.py b/sdk/__init__.py index 6822ee5..647a4fd 100644 --- a/sdk/__init__.py +++ b/sdk/__init__.py @@ -10,17 +10,15 @@ try: # Import WithRetry from retry module from private.retry.retry import WithRetry - + from .sdk import ( # Core SDK class SDK, - # Data classes BucketCreateResult, Bucket, MonkitStats, AkaveContractFetcher, - # SDK Options SDKOption, WithMetadataEncryption, @@ -32,7 +30,6 @@ WithBatchSize, WithCustomHttpClient, WithoutRetry, - # Utility functions get_monkit_stats, extract_block_data, @@ -40,12 +37,11 @@ is_retryable_tx_error, skip_to_position, parse_timestamp, - # Constants ENCRYPTION_OVERHEAD, MIN_FILE_SIZE, ) - + # Import model classes from .model import ( IPCFileUpload, @@ -67,169 +63,170 @@ ArchivalChunk, ArchivalBlock, PDPBlockData, - ErrMissingArchivalBlock + ErrMissingArchivalBlock, ) - + _SDK_AVAILABLE = True except ImportError as e: print(f"Warning: Could not import SDK core modules: {e}") _SDK_AVAILABLE = False - + class SDK: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class BucketCreateResult: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class Bucket: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class MonkitStats: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithRetry: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class AkaveContractFetcher: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class SDKOption: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithMetadataEncryption: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithEncryptionKey: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithPrivateKey: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithStreamingMaxBlocksInChunk: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithErasureCoding: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithChunkBuffer: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class WithoutRetry: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + def get_monkit_stats(*args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + def extract_block_data(*args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + def encryption_key_derivation(*args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + def is_retryable_tx_error(*args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + def skip_to_position(*args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + def parse_timestamp(*args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + ENCRYPTION_OVERHEAD = 28 MIN_FILE_SIZE = 127 - + class IPCFileUpload: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + def new_ipc_file_upload(*args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class UploadState: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class TxWaitSignal: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class IPCFileChunkUploadV2: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class IPCFileMetaV2: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class IPCBucketCreateResult: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class IPCBucket: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class IPCFileMeta: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class IPCFileListItem: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class IPCFileDownload: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class FileChunkDownload: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class Chunk: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class FileBlockUpload: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class FileBlockDownload: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class ArchivalMetadata: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class ArchivalChunk: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class ArchivalBlock: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class PDPBlockData: def __init__(self, *args, **kwargs): raise ImportError("SDK not available due to missing dependencies") - + class ErrMissingArchivalBlock(Exception): pass + try: from .config import SDKConfig, SDKError, Config except ImportError as e: @@ -246,78 +243,73 @@ class ErrMissingArchivalBlock(Exception): try: from private.cids import verify_raw, verify, CIDError except ImportError: + def verify_raw(*args, **kwargs): raise ImportError("CID utilities not available due to missing dependencies") + def verify(*args, **kwargs): raise ImportError("CID utilities not available due to missing dependencies") + CIDError = Exception __all__ = [ # Core SDK - 'SDK', - + "SDK", # Data classes - 'BucketCreateResult', - 'Bucket', - 'MonkitStats', - 'WithRetry', - 'AkaveContractFetcher', - + "BucketCreateResult", + "Bucket", + "MonkitStats", + "WithRetry", + "AkaveContractFetcher", # SDK Options - 'SDKOption', - 'WithMetadataEncryption', - 'WithEncryptionKey', - 'WithPrivateKey', - 'WithStreamingMaxBlocksInChunk', - 'WithErasureCoding', - 'WithChunkBuffer', - 'WithoutRetry', - + "SDKOption", + "WithMetadataEncryption", + "WithEncryptionKey", + "WithPrivateKey", + "WithStreamingMaxBlocksInChunk", + "WithErasureCoding", + "WithChunkBuffer", + "WithoutRetry", # Utility functions - 'get_monkit_stats', - 'extract_block_data', - 'encryption_key_derivation', - 'is_retryable_tx_error', - 'skip_to_position', - 'parse_timestamp', - + "get_monkit_stats", + "extract_block_data", + "encryption_key_derivation", + "is_retryable_tx_error", + "skip_to_position", + "parse_timestamp", # Constants - 'ENCRYPTION_OVERHEAD', - 'MIN_FILE_SIZE', - + "ENCRYPTION_OVERHEAD", + "MIN_FILE_SIZE", # Configuration - 'SDKConfig', - 'SDKError', - 'Config', - + "SDKConfig", + "SDKError", + "Config", # APIs - 'IPC', - + "IPC", # CID utilities - 'verify_raw', - 'verify', - 'CIDError', - + "verify_raw", + "verify", + "CIDError", # Model classes - 'IPCFileUpload', - 'new_ipc_file_upload', - 'UploadState', - 'TxWaitSignal', - 'IPCFileChunkUploadV2', - 'IPCFileMetaV2', - 'IPCBucketCreateResult', - 'IPCBucket', - 'IPCFileMeta', - 'IPCFileListItem', - 'IPCFileDownload', - 'FileChunkDownload', - 'Chunk', - 'FileBlockUpload', - 'FileBlockDownload', - 'ArchivalMetadata', - 'ArchivalChunk', - 'ArchivalBlock', - 'PDPBlockData', - 'ErrMissingArchivalBlock', + "IPCFileUpload", + "new_ipc_file_upload", + "UploadState", + "TxWaitSignal", + "IPCFileChunkUploadV2", + "IPCFileMetaV2", + "IPCBucketCreateResult", + "IPCBucket", + "IPCFileMeta", + "IPCFileListItem", + "IPCFileDownload", + "FileChunkDownload", + "Chunk", + "FileBlockUpload", + "FileBlockDownload", + "ArchivalMetadata", + "ArchivalChunk", + "ArchivalBlock", + "PDPBlockData", + "ErrMissingArchivalBlock", ] diff --git a/sdk/common.py b/sdk/common.py index db790aa..c207c6a 100644 --- a/sdk/common.py +++ b/sdk/common.py @@ -12,14 +12,14 @@ ) __all__ = [ - 'SDKError', - 'BLOCK_SIZE', - 'MIN_BUCKET_NAME_LENGTH', - 'ENCRYPTION_OVERHEAD', - 'MIN_FILE_SIZE', - 'BlockSize', - 'EncryptionOverhead', - 'DEFAULT_CID_VERSION', - 'DAG_PB_CODEC', - 'RAW_CODEC', + "SDKError", + "BLOCK_SIZE", + "MIN_BUCKET_NAME_LENGTH", + "ENCRYPTION_OVERHEAD", + "MIN_FILE_SIZE", + "BlockSize", + "EncryptionOverhead", + "DEFAULT_CID_VERSION", + "DAG_PB_CODEC", + "RAW_CODEC", ] diff --git a/sdk/config.py b/sdk/config.py index 412b733..90a3675 100644 --- a/sdk/config.py +++ b/sdk/config.py @@ -8,7 +8,6 @@ MIN_FILE_SIZE = 127 # 127 bytes - # Default CID version and codecs for IPFS # used in the DAG operations @@ -22,12 +21,19 @@ EncryptionOverhead = 16 # 16 bytes overhead from encryption - ## [Base Config Class] + class Config: """Configuration for the Ethereum storage contract client.""" - def __init__(self, dial_uri: str, private_key: str, storage_contract_address: str, access_contract_address: Optional[str] = None): + + def __init__( + self, + dial_uri: str, + private_key: str, + storage_contract_address: str, + access_contract_address: Optional[str] = None, + ): self.dial_uri = dial_uri self.private_key = private_key self.storage_contract_address = storage_contract_address @@ -40,13 +46,14 @@ def default(): ## [SDK Error Class] -class SDKError(Exception): - pass +class SDKError(Exception): + pass ## [Validation Functions] + # Basic validation: expect hex string like '0x' + 8 hex chars (4 bytes) minimum def validate_hex_string(hex_string: str) -> bool: if not hex_string.startswith("0x"): @@ -59,16 +66,16 @@ def validate_hex_string(hex_string: str) -> bool: ## [Test Configurations] DEFAULT_CONFIG_TEST_STREAMING_CONN = { - 'AKAVE_SDK_NODE': 'connect.akave.ai:5000', - 'ENCRYPTION_KEY': '', + "AKAVE_SDK_NODE": "connect.akave.ai:5000", + "ENCRYPTION_KEY": "", } DEFAULT_CONFIG_TEST_SDK_CONN = { - 'AKAVE_SDK_NODE': 'connect.akave.ai:5000', # For streaming operations - 'AKAVE_IPC_NODE': 'connect.akave.ai:5500', # For IPC operations - 'ETHEREUM_NODE_URL': 'https://n3-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc', - 'STORAGE_CONTRACT_ADDRESS': '0x9Aa8ff1604280d66577ecB5051a3833a983Ca3aF', # Will be obtained from node - 'ACCESS_CONTRACT_ADDRESS': '', # Will be obtained from node + "AKAVE_SDK_NODE": "connect.akave.ai:5000", # For streaming operations + "AKAVE_IPC_NODE": "connect.akave.ai:5500", # For IPC operations + "ETHEREUM_NODE_URL": "https://n3-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc", + "STORAGE_CONTRACT_ADDRESS": "0x9Aa8ff1604280d66577ecB5051a3833a983Ca3aF", # Will be obtained from node + "ACCESS_CONTRACT_ADDRESS": "", # Will be obtained from node } @@ -87,6 +94,7 @@ def validate_hex_string(hex_string: str) -> bool: # Add all other known error strings here... ] + @dataclass class SDKConfig: address: str diff --git a/sdk/connection.py b/sdk/connection.py index 9c2a783..66c927e 100644 --- a/sdk/connection.py +++ b/sdk/connection.py @@ -8,12 +8,14 @@ class ConnectionPool: - + def __init__(self): self._lock = threading.RLock() self._connections: Dict[str, grpc.Channel] = {} - def create_ipc_client(self, addr: str, pooled: bool) -> Tuple[ipcnodeapi_pb2_grpc.IPCNodeAPIStub, Optional[Callable[[], None]], Optional[Exception]]: + def create_ipc_client( + self, addr: str, pooled: bool + ) -> Tuple[ipcnodeapi_pb2_grpc.IPCNodeAPIStub, Optional[Callable[[], None]], Optional[Exception]]: try: if pooled: conn, err = self._get(addr) @@ -22,21 +24,23 @@ def create_ipc_client(self, addr: str, pooled: bool) -> Tuple[ipcnodeapi_pb2_grp return ipcnodeapi_pb2_grpc.IPCNodeAPIStub(conn), None, None options = [ - ('grpc.max_receive_message_length', 100 * 1024 * 1024), - ('grpc.max_send_message_length', 100 * 1024 * 1024), - ('grpc.keepalive_time_ms', 30000), - ('grpc.keepalive_timeout_ms', 10000), - ('grpc.keepalive_permit_without_calls', 1), - ('grpc.http2.max_pings_without_data', 0), - ('grpc.http2.min_time_between_pings_ms', 10000), + ("grpc.max_receive_message_length", 100 * 1024 * 1024), + ("grpc.max_send_message_length", 100 * 1024 * 1024), + ("grpc.keepalive_time_ms", 30000), + ("grpc.keepalive_timeout_ms", 10000), + ("grpc.keepalive_permit_without_calls", 1), + ("grpc.http2.max_pings_without_data", 0), + ("grpc.http2.min_time_between_pings_ms", 10000), ] conn = grpc.insecure_channel(addr, options=options) return ipcnodeapi_pb2_grpc.IPCNodeAPIStub(conn), conn.close, None - + except Exception as e: return None, None, SDKError(f"Failed to create IPC client: {str(e)}") - def create_archival_client(self, addr: str, pooled: bool) -> Tuple[ipcnodeapi_pb2_grpc.IPCArchivalAPIStub, Optional[Callable[[], None]], Optional[Exception]]: + def create_archival_client( + self, addr: str, pooled: bool + ) -> Tuple[ipcnodeapi_pb2_grpc.IPCArchivalAPIStub, Optional[Callable[[], None]], Optional[Exception]]: try: if pooled: conn, err = self._get(addr) @@ -45,17 +49,17 @@ def create_archival_client(self, addr: str, pooled: bool) -> Tuple[ipcnodeapi_pb return ipcnodeapi_pb2_grpc.IPCArchivalAPIStub(conn), None, None options = [ - ('grpc.max_receive_message_length', 100 * 1024 * 1024), - ('grpc.max_send_message_length', 100 * 1024 * 1024), - ('grpc.keepalive_time_ms', 30000), - ('grpc.keepalive_timeout_ms', 10000), - ('grpc.keepalive_permit_without_calls', 1), - ('grpc.http2.max_pings_without_data', 0), - ('grpc.http2.min_time_between_pings_ms', 10000), + ("grpc.max_receive_message_length", 100 * 1024 * 1024), + ("grpc.max_send_message_length", 100 * 1024 * 1024), + ("grpc.keepalive_time_ms", 30000), + ("grpc.keepalive_timeout_ms", 10000), + ("grpc.keepalive_permit_without_calls", 1), + ("grpc.http2.max_pings_without_data", 0), + ("grpc.http2.min_time_between_pings_ms", 10000), ] conn = grpc.insecure_channel(addr, options=options) return ipcnodeapi_pb2_grpc.IPCArchivalAPIStub(conn), conn.close, None - + except Exception as e: return None, None, SDKError(f"Failed to create archival client: {str(e)}") @@ -70,44 +74,44 @@ def _get(self, addr: str) -> Tuple[Optional[grpc.Channel], Optional[Exception]]: try: options = [ - ('grpc.max_receive_message_length', 100 * 1024 * 1024), - ('grpc.max_send_message_length', 100 * 1024 * 1024), - ('grpc.keepalive_time_ms', 30000), - ('grpc.keepalive_timeout_ms', 10000), - ('grpc.keepalive_permit_without_calls', 1), - ('grpc.http2.max_pings_without_data', 0), - ('grpc.http2.min_time_between_pings_ms', 10000), + ("grpc.max_receive_message_length", 100 * 1024 * 1024), + ("grpc.max_send_message_length", 100 * 1024 * 1024), + ("grpc.keepalive_time_ms", 30000), + ("grpc.keepalive_timeout_ms", 10000), + ("grpc.keepalive_permit_without_calls", 1), + ("grpc.http2.max_pings_without_data", 0), + ("grpc.http2.min_time_between_pings_ms", 10000), ] conn = grpc.insecure_channel(addr, options=options) - + try: grpc.channel_ready_future(conn).result(timeout=5) except grpc.FutureTimeoutError: logging.warning(f"Connection to {addr} not ready within timeout, proceeding anyway") - + self._connections[addr] = conn return conn, None - + except Exception as e: return None, SDKError(f"Failed to connect to {addr}: {str(e)}") def close(self) -> Optional[Exception]: with self._lock: errors = [] - + for addr, conn in self._connections.items(): try: conn.close() except Exception as e: errors.append(f"failed to close connection to {addr}: {str(e)}") - + self._connections.clear() - + if errors: return SDKError(f"encountered errors while closing connections: {errors}") - + return None def new_connection_pool() -> ConnectionPool: - return ConnectionPool() \ No newline at end of file + return ConnectionPool() diff --git a/sdk/dag.py b/sdk/dag.py index b9188b2..868375a 100644 --- a/sdk/dag.py +++ b/sdk/dag.py @@ -7,34 +7,38 @@ from multiformats import CID, multihash from multiformats.multicodec import multicodec from ipld_dag_pb import PBNode, PBLink, encode, decode, prepare, code as dag_pb_code + IPLD_AVAILABLE = True except ImportError: IPLD_AVAILABLE = False + class CID: def __init__(self, cid_str): self.cid_str = cid_str - + @classmethod def decode(cls, cid_str): return cls(cid_str) - + def __str__(self): return self.cid_str - + def string(self): return self.cid_str - + def bytes(self): return self.cid_str.encode() - + def type(self): - if self.cid_str.startswith('bafybeig'): + if self.cid_str.startswith("bafybeig"): return 0x70 # dag-pb else: return 0x55 # raw + try: from ipld_dag_pb import decode as decode_dag_pb + DAG_PB_AVAILABLE = True except ImportError: DAG_PB_AVAILABLE = False @@ -44,28 +48,30 @@ def type(self): CID_VERSION = 1 + class DAGError(Exception): pass -class DAGRoot: + +class DAGRoot: def __init__(self): self.node = None # Will store PBNode self.fs_node_data = b"" # UnixFS data self.links = [] # Store links for the node self.total_file_size = 0 # Track total file size for UnixFS - - @classmethod + + @classmethod def new(cls): return cls() - + def add_link(self, chunk_cid, raw_data_size: int, proto_node_size: int) -> None: - if hasattr(chunk_cid, 'string'): + if hasattr(chunk_cid, "string"): cid_str = chunk_cid.string() - elif hasattr(chunk_cid, '__str__'): + elif hasattr(chunk_cid, "__str__"): cid_str = str(chunk_cid) else: cid_str = chunk_cid - + if IPLD_AVAILABLE: try: cid_obj = CID.decode(cid_str) if isinstance(cid_str, str) else chunk_cid @@ -73,37 +79,33 @@ def add_link(self, chunk_cid, raw_data_size: int, proto_node_size: int) -> None: cid_obj = cid_str else: cid_obj = cid_str - + self.total_file_size += raw_data_size - - self.links.append({ - "cid": cid_obj, - "cid_str": cid_str, - "name": "", - "size": proto_node_size - }) - + + self.links.append({"cid": cid_obj, "cid_str": cid_str, "name": "", "size": proto_node_size}) + def build(self): if len(self.links) == 0: raise DAGError("no chunks added") - + if len(self.links) == 1: link_cid = self.links[0]["cid"] - if hasattr(link_cid, 'string'): + if hasattr(link_cid, "string"): return link_cid.string() elif "cid_str" in self.links[0]: return self.links[0]["cid_str"] return str(link_cid) - + if not IPLD_AVAILABLE: import base64 + combined_data = "".join([link["cid_str"] for link in self.links]).encode() hash_digest = hashlib.sha256(combined_data).digest() - b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip('=') - char_map = str.maketrans('01', 'ab') + b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip("=") + char_map = str.maketrans("01", "ab") b32_hash = b32_hash.translate(char_map) return f"bafybeig{b32_hash[:50]}" - + try: pb_links = [] for link in self.links: @@ -113,269 +115,262 @@ def build(self): link_cid = CID.decode(link_cid) except Exception: continue - - pb_link = PBLink( - hash=link_cid, - name=link["name"], - size=link["size"] - ) + + pb_link = PBLink(hash=link_cid, name=link["name"], size=link["size"]) pb_links.append(pb_link) - + if not pb_links: raise DAGError("no valid CIDs found for DAG links") - + unixfs_data = self._create_unixfs_file_data() pb_node = PBNode(data=unixfs_data, links=pb_links) encoded_bytes = encode(pb_node) digest = multihash.digest(encoded_bytes, "sha2-256") root_cid = CID("base32", CID_VERSION, dag_pb_code, digest) - + return root_cid - + except Exception as e: raise DAGError(f"failed to build DAG root: {str(e)}") - + def _create_unixfs_file_data(self) -> bytes: unixfs_data = bytes([0x08, 0x02]) # Type = File - + if self.total_file_size > 0: unixfs_data += bytes([0x18]) + self._encode_varint(self.total_file_size) - + return unixfs_data - + def _encode_varint(self, value: int) -> bytes: - result = b'' + result = b"" while value > 127: result += bytes([(value & 127) | 128]) value >>= 7 result += bytes([value & 127]) return result -@dataclass + +@dataclass class ChunkDAG: - cid: Any # Chunk CID - raw_data_size: int # size of data read from disk - encoded_size: int # encoded size (was proto_node_size) - blocks: List[FileBlockUpload] # Blocks in the chunk + cid: Any # Chunk CID + raw_data_size: int # size of data read from disk + encoded_size: int # encoded size (was proto_node_size) + blocks: List[FileBlockUpload] # Blocks in the chunk + def build_dag(ctx: Any, reader: BinaryIO, block_size: int) -> ChunkDAG: try: data = reader.read() if not data: raise DAGError("empty data") - + raw_data_size = len(data) - + blocks = [] - + if len(data) <= block_size: chunk_cid, encoded_data = _create_unixfs_file_node(data) proto_node_size = len(encoded_data) - - blocks = [FileBlockUpload( - cid=str(chunk_cid) if hasattr(chunk_cid, '__str__') else chunk_cid, - data=encoded_data - )] + + blocks = [ + FileBlockUpload(cid=str(chunk_cid) if hasattr(chunk_cid, "__str__") else chunk_cid, data=encoded_data) + ] raw_size, encoded_size = node_sizes(encoded_data) - + else: offset = 0 pb_links = [] - + while offset < len(data): end_offset = min(offset + block_size, len(data)) block_data = data[offset:end_offset] - + block_cid, block_encoded_data = _create_unixfs_file_node(block_data) - + block = FileBlockUpload( - cid=str(block_cid) if hasattr(block_cid, '__str__') else block_cid, - data=block_encoded_data + cid=str(block_cid) if hasattr(block_cid, "__str__") else block_cid, data=block_encoded_data ) blocks.append(block) - + if IPLD_AVAILABLE: try: cid_obj = CID.decode(block.cid) if isinstance(block.cid, str) else block_cid - pb_links.append(PBLink( - hash=cid_obj, - name="", - size=len(block_encoded_data) - )) + pb_links.append(PBLink(hash=cid_obj, name="", size=len(block_encoded_data))) except: pass - + offset = end_offset - + chunk_cid, encoded_size = _create_chunk_dag_root_node(blocks, pb_links) raw_size = raw_data_size - + if not blocks: raise DAGError("no blocks created") - return ChunkDAG( - cid=chunk_cid, - raw_data_size=raw_size, - encoded_size=encoded_size, - blocks=blocks - ) - + return ChunkDAG(cid=chunk_cid, raw_data_size=raw_size, encoded_size=encoded_size, blocks=blocks) + except Exception as e: raise DAGError(f"failed to build chunk DAG: {str(e)}") + def _create_unixfs_file_node(data: bytes): if not IPLD_AVAILABLE: hash_digest = hashlib.sha256(data).digest() import base64 - b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip('=') - char_map = str.maketrans('01', 'ab') + + b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip("=") + char_map = str.maketrans("01", "ab") b32_hash = b32_hash.translate(char_map) return f"bafybeig{b32_hash[:50]}", data - + try: - unixfs_data = bytes([0x08, 0x02]) - + unixfs_data = bytes([0x08, 0x02]) + if len(data) > 0: unixfs_data += bytes([0x22]) + _encode_varint(len(data)) + data - + pb_node = PBNode(data=unixfs_data, links=[]) encoded_bytes = encode(pb_node) - + digest = multihash.digest(encoded_bytes, "sha2-256") cid = CID("base32", CID_VERSION, dag_pb_code, digest) - + return cid, encoded_bytes - + except Exception as e: hash_digest = hashlib.sha256(data).digest() import base64 - b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip('=') - char_map = str.maketrans('01', 'ab') + + b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip("=") + char_map = str.maketrans("01", "ab") b32_hash = b32_hash.translate(char_map) return f"bafybeig{b32_hash[:50]}", data + def _create_chunk_dag_root_node(blocks: List[FileBlockUpload], pb_links: List = None): if not IPLD_AVAILABLE: combined_data = b"".join([block.data for block in blocks]) hash_digest = hashlib.sha256(combined_data).digest() import base64 - b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip('=') - char_map = str.maketrans('01', 'ab') + + b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip("=") + char_map = str.maketrans("01", "ab") b32_hash = b32_hash.translate(char_map) return f"bafybeig{b32_hash[:50]}", len(combined_data) - + try: if not pb_links: pb_links = [] for block in blocks: try: block_cid = CID.decode(block.cid) if isinstance(block.cid, str) else block.cid - pb_link = PBLink( - hash=block_cid, - name="", - size=len(block.data) - ) + pb_link = PBLink(hash=block_cid, name="", size=len(block.data)) pb_links.append(pb_link) except: continue - - unixfs_data = bytes([0x08, 0x02]) - + + unixfs_data = bytes([0x08, 0x02]) + pb_node = PBNode(data=unixfs_data, links=pb_links) encoded_bytes = encode(pb_node) - + digest = multihash.digest(encoded_bytes, "sha2-256") cid = CID("base32", CID_VERSION, dag_pb_code, digest) - + total_size = sum(len(block.data) for block in blocks) return cid, total_size - + except Exception as e: combined_data = b"".join([block.data for block in blocks]) hash_digest = hashlib.sha256(combined_data).digest() import base64 - b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip('=') - char_map = str.maketrans('01', 'ab') + + b32_hash = base64.b32encode(hash_digest).decode().lower().rstrip("=") + char_map = str.maketrans("01", "ab") b32_hash = b32_hash.translate(char_map) return f"bafybeig{b32_hash[:50]}", len(combined_data) + def node_sizes(node_data: bytes) -> Tuple[int, int]: if not IPLD_AVAILABLE: encoded_size = len(node_data) return encoded_size, encoded_size - + try: - pb_node = decode(node_data) - + pb_node = decode(node_data) + if not pb_node.data: if len(pb_node.links) == 0: return len(node_data), len(node_data) - + encoded_size = sum(link.size for link in pb_node.links) - return 0, encoded_size - + return 0, encoded_size + raw_data_size = _extract_unixfs_data_size(pb_node.data) - + if len(pb_node.links) == 0: encoded_size = len(node_data) else: encoded_size = sum(link.size for link in pb_node.links) - + return raw_data_size, encoded_size - + except Exception as e: encoded_size = len(node_data) return encoded_size, encoded_size + def _extract_unixfs_data_size(unixfs_bytes: bytes) -> int: try: offset = 0 while offset < len(unixfs_bytes): if offset >= len(unixfs_bytes): break - + field_tag = unixfs_bytes[offset] offset += 1 - - field_number = (field_tag >> 3) + + field_number = field_tag >> 3 wire_type = field_tag & 0x07 - - if field_number == 3 and wire_type == 0: + + if field_number == 3 and wire_type == 0: file_size, bytes_read = _decode_varint(unixfs_bytes[offset:]) return file_size - elif field_number == 4 and wire_type == 2: + elif field_number == 4 and wire_type == 2: length, bytes_read = _decode_varint(unixfs_bytes[offset:]) - return length - elif wire_type == 2: + return length + elif wire_type == 2: length, bytes_read = _decode_varint(unixfs_bytes[offset:]) offset += bytes_read + length - elif wire_type == 0: + elif wire_type == 0: value, bytes_read = _decode_varint(unixfs_bytes[offset:]) offset += bytes_read - elif wire_type == 1: + elif wire_type == 1: offset += 8 - elif wire_type == 5: + elif wire_type == 5: offset += 4 else: offset += 1 - + return 0 - + except Exception: return 0 + def _encode_varint(value: int) -> bytes: - result = b'' + result = b"" while value > 127: result += bytes([(value & 127) | 128]) value >>= 7 result += bytes([value & 127]) return result + def _decode_varint(data: bytes) -> tuple[int, int]: value = 0 shift = 0 bytes_read = 0 - + for byte in data: bytes_read += 1 value |= (byte & 0x7F) << shift @@ -384,23 +379,24 @@ def _decode_varint(data: bytes) -> tuple[int, int]: shift += 7 if shift >= 64: raise ValueError("varint too long") - + return value, bytes_read + def extract_block_data(cid_str: str, data: bytes) -> bytes: try: if not IPLD_AVAILABLE: return _extract_unixfs_data_fallback(data) - + try: cid_obj = CID.decode(cid_str) cid_type = cid_obj.codec except: - if cid_str.startswith('bafkreig'): + if cid_str.startswith("bafkreig"): cid_type = RAW_CODEC else: cid_type = DAG_PB_CODEC - + if cid_type == DAG_PB_CODEC: try: pb_node = decode(data) @@ -417,76 +413,78 @@ def extract_block_data(cid_str: str, data: bytes) -> bytes: return data else: raise DAGError(f"unknown CID type: {cid_type}") - + except Exception: return _extract_unixfs_data_fallback(data) + def _extract_unixfs_data_fallback(data: bytes) -> bytes: try: - offset = 0 + offset = 0 # DAG-PB structure: # Field 1 (Data): The UnixFS data # Field 2 (Links): Array of links to other blocks - + while offset < len(data): if offset >= len(data): break - + field_tag = data[offset] offset += 1 - - field_number = (field_tag >> 3) + + field_number = field_tag >> 3 wire_type = field_tag & 0x07 - + if field_number == 1 and wire_type == 2: length, bytes_read = _decode_varint(data[offset:]) offset += bytes_read - + if offset + length <= len(data): - unixfs_data = data[offset:offset + length] + unixfs_data = data[offset : offset + length] extracted = _extract_unixfs_data(unixfs_data) if extracted: return extracted offset += length else: break - elif wire_type == 2: + elif wire_type == 2: length, bytes_read = _decode_varint(data[offset:]) offset += bytes_read + length - elif wire_type == 0: + elif wire_type == 0: value, bytes_read = _decode_varint(data[offset:]) offset += bytes_read - elif wire_type == 1: + elif wire_type == 1: offset += 8 - elif wire_type == 5: + elif wire_type == 5: offset += 4 else: offset += 1 - + return data - + except Exception: return data + def _extract_unixfs_data(unixfs_bytes: bytes) -> bytes: try: offset = 0 while offset < len(unixfs_bytes): if offset >= len(unixfs_bytes): break - + field_tag = unixfs_bytes[offset] offset += 1 - - field_number = (field_tag >> 3) + + field_number = field_tag >> 3 wire_type = field_tag & 0x07 - + if field_number == 4 and wire_type == 2: # Field 4 (Data) with length-delimited wire type length, bytes_read = _decode_varint(unixfs_bytes[offset:]) offset += bytes_read - + if offset + length <= len(unixfs_bytes): - return unixfs_bytes[offset:offset + length] + return unixfs_bytes[offset : offset + length] else: break elif wire_type == 2: # Length-delimited field @@ -501,12 +499,13 @@ def _extract_unixfs_data(unixfs_bytes: bytes) -> bytes: offset += 4 else: offset += 1 - + return b"" - + except Exception: return b"" + def bytes_to_node(node_data: bytes): try: pb_node = decode(node_data) @@ -514,26 +513,28 @@ def bytes_to_node(node_data: bytes): except Exception as e: raise DAGError(f"failed to decode node data: {str(e)}") + def get_node_links(node_data: bytes) -> List[dict]: try: pb_node = bytes_to_node(node_data) - + links = [] for pb_link in pb_node.links: link_info = { - 'cid': str(pb_link.hash) if hasattr(pb_link.hash, '__str__') else pb_link.hash, - 'name': pb_link.name if pb_link.name else "", - 'size': pb_link.size + "cid": str(pb_link.hash) if hasattr(pb_link.hash, "__str__") else pb_link.hash, + "name": pb_link.name if pb_link.name else "", + "size": pb_link.size, } links.append(link_info) - + return links - + except Exception as e: raise DAGError(f"failed to extract links from node: {str(e)}") + def block_by_cid(blocks: List[FileBlockUpload], cid_str: str) -> tuple[FileBlockUpload, bool]: for block in blocks: if block.cid == cid_str: return block, True - return FileBlockUpload(cid="", data=b""), False \ No newline at end of file + return FileBlockUpload(cid="", data=b""), False diff --git a/sdk/model.py b/sdk/model.py index 6c25a61..549eecb 100644 --- a/sdk/model.py +++ b/sdk/model.py @@ -7,9 +7,11 @@ TimestampType = Union[datetime, float, int] + @dataclass class BucketCreateResult: """Result of bucket creation.""" + name: str created_at: TimestampType @@ -17,6 +19,7 @@ class BucketCreateResult: @dataclass class Bucket: """A bucket.""" + name: str created_at: TimestampType @@ -24,6 +27,7 @@ class Bucket: @dataclass class Chunk: """A piece of metadata of some file.""" + cid: str encoded_size: int size: int @@ -33,6 +37,7 @@ class Chunk: @dataclass class FileBlockUpload: """A piece of metadata of some file used for upload.""" + cid: str data: bytes permit: str = "" @@ -43,19 +48,19 @@ class FileBlockUpload: @property def CID(self): return self.cid - + @property def Data(self): return self.data - + @property def NodeAddress(self): return self.node_address - + @property def NodeID(self): return self.node_id - + @property def Permit(self): return self.permit @@ -64,6 +69,7 @@ def Permit(self): @dataclass class FileBlockDownload: """A piece of metadata of some file used for download.""" + cid: str data: bytes permit: str = "" @@ -74,6 +80,7 @@ class FileBlockDownload: @dataclass class FileListItem: """Contains bucket file list file meta information.""" + root_cid: str name: str size: int @@ -85,6 +92,7 @@ class FileListItem: @dataclass class FileUpload: """Contains single file meta information.""" + bucket_name: str name: str stream_id: str @@ -96,17 +104,19 @@ class FileUpload: @dataclass class FileChunkUpload: """Contains single file chunk meta information.""" + stream_id: str index: int chunk_cid: CIDType raw_data_size: int # uint64 in Go - encoded_size: int # uint64 in Go + encoded_size: int # uint64 in Go blocks: List[FileBlockUpload] @dataclass class FileDownload: """Contains single file meta information.""" + stream_id: str bucket_name: str name: str @@ -118,6 +128,7 @@ class FileDownload: @dataclass class FileChunkDownload: """Contains single file chunk meta information.""" + cid: str index: int encoded_size: int @@ -128,6 +139,7 @@ class FileChunkDownload: @dataclass class FileMeta: """Contains single file meta information.""" + stream_id: str root_cid: str bucket_name: str @@ -143,6 +155,7 @@ class FileMeta: @dataclass class IPCBucketCreateResult: """Result of IPC bucket creation.""" + id: str name: str created_at: TimestampType @@ -151,6 +164,7 @@ class IPCBucketCreateResult: @dataclass class IPCBucket: """An IPC bucket.""" + id: str name: str created_at: TimestampType @@ -159,6 +173,7 @@ class IPCBucket: @dataclass class IPCFileDownload: """Represents an IPC file download and some metadata.""" + bucket_name: str name: str chunks: List[Chunk] @@ -167,6 +182,7 @@ class IPCFileDownload: @dataclass class IPCFileListItem: """Contains IPC bucket file list file meta information.""" + root_cid: str name: str encoded_size: int @@ -177,6 +193,7 @@ class IPCFileListItem: @dataclass class IPCFileMeta: """Contains single IPC file meta information.""" + root_cid: str name: str bucket_name: str @@ -189,6 +206,7 @@ class IPCFileMeta: @dataclass class IPCFileMetaV2: """Contains single file meta information.""" + root_cid: str bucket_name: str name: str @@ -201,11 +219,12 @@ class IPCFileMetaV2: @dataclass class IPCFileChunkUploadV2: """Contains single file chunk meta information.""" + index: int chunk_cid: CIDType actual_size: int raw_data_size: int # uint64 in Go - encoded_size: int # uint64 in Go + encoded_size: int # uint64 in Go blocks: List[FileBlockUpload] bucket_id: bytes # 32-byte array in Go, using bytes in Python file_name: str @@ -214,12 +233,13 @@ class IPCFileChunkUploadV2: @dataclass class TxWaitSignal: file_upload_chunk: IPCFileChunkUploadV2 - transaction: Any + transaction: Any -class UploadState: +class UploadState: def __init__(self, dag_root): from threading import RLock + self.dag_root = dag_root self.mutex = RLock() self.pre_created_chunks = {} # map[int]chunkWithTx @@ -227,24 +247,21 @@ def __init__(self, dag_root): self.chunk_count = 0 self.actual_file_size = 0 self.encoded_file_size = 0 - + def pre_create_chunk(self, chunk: IPCFileChunkUploadV2, tx) -> None: with self.mutex: - self.pre_created_chunks[chunk.index] = { - 'chunk': chunk, - 'tx': tx - } + self.pre_created_chunks[chunk.index] = {"chunk": chunk, "tx": tx} self.chunk_count += 1 self.actual_file_size += chunk.actual_size self.encoded_file_size += chunk.encoded_size - if hasattr(self.dag_root, 'add_link'): + if hasattr(self.dag_root, "add_link"): self.dag_root.add_link(chunk.chunk_cid, chunk.raw_data_size, chunk.encoded_size) - + def chunk_uploaded(self, chunk: IPCFileChunkUploadV2) -> None: with self.mutex: if chunk.index in self.pre_created_chunks: del self.pre_created_chunks[chunk.index] - + def list_pre_created_chunks(self) -> List[dict]: with self.mutex: return list(self.pre_created_chunks.values()) @@ -262,45 +279,45 @@ class IPCFileUpload: def new_ipc_file_upload(bucket_name: str, name: str) -> IPCFileUpload: from .dag import DAGRoot + dag_root = DAGRoot.new() state = UploadState(dag_root) - + return IPCFileUpload( - bucket_name=bucket_name, - name=name, - state=state, - blocks_counter=0, - bytes_counter=0, - chunks_counter=0 + bucket_name=bucket_name, name=name, state=state, blocks_counter=0, bytes_counter=0, chunks_counter=0 ) @dataclass class ArchivalMetadata: """Contains file metadata with chunks and blocks including PDP data.""" + bucket_name: str name: str - chunks: List['ArchivalChunk'] + chunks: List["ArchivalChunk"] @dataclass class ArchivalChunk: """Contains chunk metadata with blocks.""" + chunk: Chunk - blocks: List['ArchivalBlock'] + blocks: List["ArchivalBlock"] @dataclass class ArchivalBlock: """Contains block metadata with PDP data.""" + cid: str size: int - pdp_data: Optional['PDPBlockData'] = None + pdp_data: Optional["PDPBlockData"] = None @dataclass class PDPBlockData: """Contains PDP data for a block.""" + url: str offset: int size: int @@ -309,7 +326,7 @@ class PDPBlockData: class ErrMissingArchivalBlock(Exception): """Error returned when archival block metadata is missing.""" - + def __init__(self, block_cid: str): self.block_cid = block_cid super().__init__(f"missing archival block metadata for block CID {block_cid}") diff --git a/sdk/sdk.py b/sdk/sdk.py index 93c50ae..edc6a71 100644 --- a/sdk/sdk.py +++ b/sdk/sdk.py @@ -16,6 +16,7 @@ try: import requests + HTTP_CLIENT_AVAILABLE = True except ImportError: HTTP_CLIENT_AVAILABLE = False @@ -23,12 +24,13 @@ ENCRYPTION_OVERHEAD = 28 # 16 bytes for AES-GCM tag, 12 bytes for nonce MIN_FILE_SIZE = 127 # 127 bytes + class AkaveContractFetcher: def __init__(self, node_address: str): self.node_address = node_address self.channel = None self.stub = None - + def connect(self) -> bool: """Connect to the Akave node""" try: @@ -36,35 +38,35 @@ def connect(self) -> bool: self.channel = grpc.insecure_channel(self.node_address) self.stub = ipcnodeapi_pb2_grpc.IPCNodeAPIStub(self.channel) return True - + except grpc.RpcError as e: logging.error(f"❌ gRPC error: {e.code()} - {e.details()}") return False except Exception as e: logging.error(f"❌ Connection error: {type(e).__name__}: {str(e)}") return False - + def fetch_contract_addresses(self) -> Optional[dict]: if not self.stub: return None - + try: request = ipcnodeapi_pb2.ConnectionParamsRequest() response = self.stub.ConnectionParams(request) - + contract_info = { - 'dial_uri': response.dial_uri if hasattr(response, 'dial_uri') else None, - 'contract_address': response.storage_address if hasattr(response, 'storage_address') else None, + "dial_uri": response.dial_uri if hasattr(response, "dial_uri") else None, + "contract_address": response.storage_address if hasattr(response, "storage_address") else None, } - - if hasattr(response, 'access_address'): - contract_info['access_address'] = response.access_address - + + if hasattr(response, "access_address"): + contract_info["access_address"] = response.access_address + return contract_info except Exception as e: logging.error(f"❌ Error fetching contract info: {e}") return None - + def close(self): """Close the gRPC connection""" if self.channel: @@ -73,6 +75,7 @@ def close(self): T = TypeVar("T") # Generic return type for gRPC calls + @dataclass class BucketCreateResult: name: str @@ -84,78 +87,89 @@ class Bucket: name: str created_at: datetime + @dataclass class MonkitStats: name: str successes: int - errors: Dict[str, int] - highwater: int - success_times: Optional[List[float]] = None - failure_times: Optional[List[float]] = None + errors: Dict[str, int] + highwater: int + success_times: Optional[List[float]] = None + failure_times: Optional[List[float]] = None class SDKOption: - def apply(self, sdk: 'SDK'): + def apply(self, sdk: "SDK"): pass + class WithMetadataEncryption(SDKOption): - def apply(self, sdk: 'SDK'): + def apply(self, sdk: "SDK"): sdk.use_metadata_encryption = True + class WithEncryptionKey(SDKOption): def __init__(self, key: bytes): self.key = key - - def apply(self, sdk: 'SDK'): + + def apply(self, sdk: "SDK"): sdk.encryption_key = self.key + class WithPrivateKey(SDKOption): def __init__(self, key: str): self.key = key - - def apply(self, sdk: 'SDK'): + + def apply(self, sdk: "SDK"): sdk.private_key = self.key + class WithStreamingMaxBlocksInChunk(SDKOption): def __init__(self, max_blocks_in_chunk: int): self.max_blocks_in_chunk = max_blocks_in_chunk - - def apply(self, sdk: 'SDK'): + + def apply(self, sdk: "SDK"): sdk.streaming_max_blocks_in_chunk = self.max_blocks_in_chunk + class WithErasureCoding(SDKOption): def __init__(self, parity_blocks: int): self.parity_blocks = parity_blocks - - def apply(self, sdk: 'SDK'): + + def apply(self, sdk: "SDK"): sdk.parity_blocks_count = self.parity_blocks + class WithChunkBuffer(SDKOption): def __init__(self, buffer_size: int): self.buffer_size = buffer_size - - def apply(self, sdk: 'SDK'): + + def apply(self, sdk: "SDK"): sdk.chunk_buffer = self.buffer_size + class WithBatchSize(SDKOption): def __init__(self, batch_size: int): self.batch_size = max(1, batch_size) # Ensure at least 1 - - def apply(self, sdk: 'SDK'): + + def apply(self, sdk: "SDK"): sdk.batch_size = self.batch_size + class WithCustomHttpClient(SDKOption): def __init__(self, client): self.client = client - - def apply(self, sdk: 'SDK'): + + def apply(self, sdk: "SDK"): sdk.http_client = self.client + class WithoutRetry(SDKOption): - def apply(self, sdk: 'SDK'): + def apply(self, sdk: "SDK"): sdk.with_retry = WithRetry(max_attempts=0, base_delay=0.1) -class SDK(): + +class SDK: def __init__(self, config: SDKConfig): self.conn = None self.ipc_conn = None @@ -166,13 +180,13 @@ def __init__(self, config: SDKConfig): self.config.encryption_key = config.encryption_key or [] self.ipc_address = config.ipc_address or config.address # Use provided IPC address or fallback to main address self._contract_info = None - + # Initialize HTTP client if HTTP_CLIENT_AVAILABLE: self.http_client = requests.Session() else: self.http_client = None - + # Initialize batch size and retry settings self.batch_size = 1 self.with_retry = WithRetry(max_attempts=5, base_delay=0.1) @@ -181,17 +195,17 @@ def __init__(self, config: SDKConfig): raise SDKError(f"Invalid blockPartSize: {config.block_part_size}. Valid range is 1-{BLOCK_SIZE}") self.conn = grpc.insecure_channel(config.address) - + if self.ipc_address == config.address: self.ipc_conn = self.conn else: self.ipc_conn = grpc.insecure_channel(self.ipc_address) - + self.ipc_client = ipcnodeapi_pb2_grpc.IPCNodeAPIStub(self.ipc_conn) if len(self.config.encryption_key) != 0 and len(self.config.encryption_key) != 32: raise SDKError("Encryption key length should be 32 bytes long") - + # Sanitize retry params if self.with_retry.max_attempts < 0: self.with_retry.max_attempts = 0 @@ -202,24 +216,24 @@ def _fetch_contract_info(self) -> Optional[dict]: """Dynamically fetch contract information using multiple endpoints""" if self._contract_info: return self._contract_info - - endpoints = [ - 'connect.akave.ai:5500' - ] - + + endpoints = ["connect.akave.ai:5500"] + for endpoint in endpoints: logging.info(f"πŸ”„ Trying endpoint: {endpoint}") fetcher = AkaveContractFetcher(endpoint) - + if fetcher.connect(): logging.info("βœ… Connected successfully!") - + info = fetcher.fetch_contract_addresses() fetcher.close() - - if info and info.get('contract_address') and info.get('dial_uri'): + + if info and info.get("contract_address") and info.get("dial_uri"): logging.info("βœ… Successfully fetched contract information!") - logging.info(f"πŸ“ Contract Details: dial_uri={info.get('dial_uri')}, contract_address={info.get('contract_address')}") + logging.info( + f"πŸ“ Contract Details: dial_uri={info.get('dial_uri')}, contract_address={info.get('contract_address')}" + ) self._contract_info = info return info else: @@ -227,7 +241,7 @@ def _fetch_contract_info(self) -> Optional[dict]: else: logging.warning(f"❌ Failed to connect to {endpoint}") fetcher.close() - + logging.error("❌ All endpoints failed for contract fetching") return None @@ -245,25 +259,25 @@ def ipc(self): try: # Get connection parameters dynamically conn_params = self._fetch_contract_info() - + if not conn_params: raise SDKError("Could not fetch contract information from any Akave node") - + if not self.config.private_key: raise SDKError("Private key is required for IPC operations") - + config = Config( - dial_uri=conn_params['dial_uri'], + dial_uri=conn_params["dial_uri"], private_key=self.config.private_key, - storage_contract_address=conn_params['contract_address'], - access_contract_address=conn_params.get('access_address', '') + storage_contract_address=conn_params["contract_address"], + access_contract_address=conn_params.get("access_address", ""), ) - + # Create IPC instance with retries max_retries = 3 - retry_delay = 1 + retry_delay = 1 last_error = None - + for attempt in range(max_retries): try: ipc_instance = Client.dial(config) @@ -279,7 +293,7 @@ def ipc(self): continue else: raise SDKError(f"Failed to dial IPC client after {max_retries} attempts: {str(last_error)}") - + return IPC( client=self.ipc_client, conn=self.ipc_conn, # Use the IPC connection @@ -287,29 +301,31 @@ def ipc(self): config=self.config, http_client=self.http_client, batch_size=self.batch_size, - with_retry=self.with_retry + with_retry=self.with_retry, ) except Exception as e: raise SDKError(f"Failed to initialize IPC API: {str(e)}") - def get_monkit_stats() -> List[MonkitStats]: stats = [] return stats + def extract_block_data(id_str: str, data: bytes) -> bytes: try: from multiformats import CID + cid = CID.decode(id_str) except Exception as e: raise ValueError(f"failed to decode CID: {e}") - - codec_name = getattr(cid.codec, 'name', str(cid.codec)) - + + codec_name = getattr(cid.codec, "name", str(cid.codec)) + if codec_name == "dag-pb": try: from .dag import extract_block_data as dag_extract + return dag_extract(id_str, data) except Exception as e: raise ValueError(f"failed to decode DAG-PB node: {e}") @@ -318,10 +334,11 @@ def extract_block_data(id_str: str, data: bytes) -> bytes: else: raise ValueError(f"unknown cid type: {codec_name}") + def encryption_key_derivation(parent_key: bytes, *info_data: str) -> bytes: if not parent_key: return b"" - + info = "/".join(info_data) try: key = derive_key(parent_key, info.encode()) @@ -329,29 +346,27 @@ def encryption_key_derivation(parent_key: bytes, *info_data: str) -> bytes: except Exception as e: raise SDKError(f"failed to derive key: {e}") + def is_retryable_tx_error(err: Exception) -> bool: if err is None: return False - + msg = str(err).lower() - retryable_errors = [ - "nonce too low", - "replacement transaction underpriced", - "eof" - ] - + retryable_errors = ["nonce too low", "replacement transaction underpriced", "eof"] + return any(error in msg for error in retryable_errors) + def skip_to_position(reader: io.IOBase, position: int) -> None: if position > 0: - if hasattr(reader, 'seek'): + if hasattr(reader, "seek"): try: reader.seek(position, io.SEEK_SET) return except (OSError, io.UnsupportedOperation): pass - - if hasattr(reader, 'read'): + + if hasattr(reader, "read"): remaining = position while remaining > 0: chunk_size = min(remaining, 8192) # Read in 8KB chunks @@ -362,35 +377,23 @@ def skip_to_position(reader: io.IOBase, position: int) -> None: else: raise SDKError("reader does not support seek or read operations") + def parse_timestamp(ts) -> Optional[datetime]: if ts is None: return None return ts.AsTime() if hasattr(ts, "AsTime") else ts + def get_monkit_stats() -> List[MonkitStats]: stats = [] - + placeholder_stats = [ - MonkitStats( - name="sdk.upload", - successes=0, - errors={}, - highwater=0, - success_times=None, - failure_times=None - ), - MonkitStats( - name="sdk.download", - successes=0, - errors={}, - highwater=0, - success_times=None, - failure_times=None - ) + MonkitStats(name="sdk.upload", successes=0, errors={}, highwater=0, success_times=None, failure_times=None), + MonkitStats(name="sdk.download", successes=0, errors={}, highwater=0, success_times=None, failure_times=None), ] - + for stat in placeholder_stats: if stat.successes > 0 or len(stat.errors) > 0: stats.append(stat) - - return stats \ No newline at end of file + + return stats diff --git a/sdk/sdk_ipc.py b/sdk/sdk_ipc.py index 41a230b..fad1ef3 100644 --- a/sdk/sdk_ipc.py +++ b/sdk/sdk_ipc.py @@ -11,22 +11,35 @@ from typing import List, Optional, Callable, Dict, Any, Union, Tuple from google.protobuf.timestamp_pb2 import Timestamp from datetime import datetime -import grpc +import grpc from .config import MIN_BUCKET_NAME_LENGTH, SDKError, SDKConfig, BLOCK_SIZE, ENCRYPTION_OVERHEAD from .dag import DAGRoot, build_dag, extract_block_data from .connection import ConnectionPool from .model import ( - IPCBucketCreateResult, IPCBucket, IPCFileMeta, IPCFileListItem, - IPCFileMetaV2, IPCFileChunkUploadV2, FileBlockUpload, - FileBlockDownload, Chunk, IPCFileDownload, FileChunkDownload, - IPCFileUpload, new_ipc_file_upload, UploadState + IPCBucketCreateResult, + IPCBucket, + IPCFileMeta, + IPCFileListItem, + IPCFileMetaV2, + IPCFileChunkUploadV2, + FileBlockUpload, + FileBlockDownload, + Chunk, + IPCFileDownload, + FileChunkDownload, + IPCFileUpload, + new_ipc_file_upload, + UploadState, ) + class TxWaitSignal: def __init__(self, FileUploadChunk, Transaction): self.FileUploadChunk = FileUploadChunk self.Transaction = Transaction + + from private.encryption import encrypt, derive_key, decrypt from private.pb import ipcnodeapi_pb2, ipcnodeapi_pb2_grpc @@ -46,17 +59,19 @@ def __init__(self, FileUploadChunk, Transaction): BlockSize = BLOCK_SIZE EncryptionOverhead = ENCRYPTION_OVERHEAD + def encryption_key(parent_key: bytes, *info_data: str): if len(parent_key) == 0: - return b'' - + return b"" + info = "/".join(info_data) return derive_key(parent_key, info.encode()) + def maybe_encrypt_metadata(value: str, derivation_path: str, encryption_key: bytes) -> str: if len(encryption_key) == 0: return value - + try: file_enc_key = derive_key(encryption_key, derivation_path.encode()) encrypted_data = encrypt(file_enc_key, value.encode(), b"metadata") @@ -64,71 +79,65 @@ def maybe_encrypt_metadata(value: str, derivation_path: str, encryption_key: byt except Exception as e: raise SDKError(f"failed to encrypt metadata: {str(e)}") + def to_ipc_proto_chunk(chunk_cid, index: int, size: int, blocks): from private.pb import ipcnodeapi_pb2 - + cids = [] sizes = [] - - if hasattr(chunk_cid, '__str__'): + + if hasattr(chunk_cid, "__str__"): chunk_cid_str = str(chunk_cid) else: chunk_cid_str = chunk_cid - + pb_blocks = [] for block in blocks: - block_cid = block.cid if hasattr(block, 'cid') else block["cid"] - block_data = block.data if hasattr(block, 'data') else block["data"] - - if hasattr(block_cid, '__str__'): + block_cid = block.cid if hasattr(block, "cid") else block["cid"] + block_data = block.data if hasattr(block, "data") else block["data"] + + if hasattr(block_cid, "__str__"): block_cid_str = str(block_cid) else: block_cid_str = block_cid - - pb_block = ipcnodeapi_pb2.IPCChunk.Block( - cid=block_cid_str, - size=len(block_data) - ) + + pb_block = ipcnodeapi_pb2.IPCChunk.Block(cid=block_cid_str, size=len(block_data)) pb_blocks.append(pb_block) - + try: try: - if hasattr(block_cid, '__bytes__'): + if hasattr(block_cid, "__bytes__"): cid_bytes = bytes(block_cid) else: c = CID.decode(block_cid_str) cid_bytes = bytes(c) except Exception as e: raise SDKError(f"failed to get CID bytes for block {block_cid_str}: {str(e)}") - + bcid = bytearray(32) - + if isinstance(cid_bytes, str): cid_bytes = cid_bytes.encode() elif not isinstance(cid_bytes, (bytes, bytearray)): cid_bytes = bytes(cid_bytes) - + if len(cid_bytes) > 4: copy_len = min(len(cid_bytes) - 4, 32) - bcid[:copy_len] = cid_bytes[4:4+copy_len] + bcid[:copy_len] = cid_bytes[4 : 4 + copy_len] else: copy_len = min(len(cid_bytes), 32) bcid[:copy_len] = cid_bytes[:copy_len] - + cids.append(bytes(bcid)) sizes.append(len(block_data)) except Exception as e: return None, None, None, SDKError(f"failed to process CID: {str(e)}") - - proto_chunk = ipcnodeapi_pb2.IPCChunk( - cid=chunk_cid_str, - index=index, - size=size, - blocks=pb_blocks - ) - + + proto_chunk = ipcnodeapi_pb2.IPCChunk(cid=chunk_cid_str, index=index, size=size, blocks=pb_blocks) + return cids, sizes, proto_chunk, None + class IPC: def __init__(self, client, conn, ipc_instance, config: SDKConfig, http_client=None, batch_size=1, with_retry=None): self.client = client @@ -137,13 +146,14 @@ def __init__(self, client, conn, ipc_instance, config: SDKConfig, http_client=No self.max_concurrency = config.max_concurrency self.block_part_size = config.block_part_size self.use_connection_pool = config.use_connection_pool - self.encryption_key = config.encryption_key if config.encryption_key else b'' + self.encryption_key = config.encryption_key if config.encryption_key else b"" self.max_blocks_in_chunk = config.streaming_max_blocks_in_chunk self.chunk_buffer = config.chunk_buffer self.http_client = http_client self.batch_size = batch_size - + from private.retry.retry import WithRetry + self.with_retry = with_retry if with_retry is not None else WithRetry(max_attempts=5, base_delay=0.1) def create_bucket(self, ctx, name: str) -> IPCBucketCreateResult: @@ -156,24 +166,20 @@ def create_bucket(self, ctx, name: str) -> IPCBucketCreateResult: from_address=self.ipc.auth.address, private_key=self.ipc.auth.key, gas_limit=500000, - nonce_manager=None + nonce_manager=None, ) receipt = self.ipc.eth.eth.wait_for_transaction_receipt(tx) - + if receipt.status != 1: raise SDKError("bucket creation transaction failed") - + block = self.ipc.eth.eth.get_block(receipt.blockNumber) created_at = block.timestamp - + bucket_id = receipt.transactionHash.hex() if receipt.transactionHash else "unknown" - - return IPCBucketCreateResult( - id=bucket_id, - name=name, - created_at=created_at - ) - + + return IPCBucketCreateResult(id=bucket_id, name=name, created_at=created_at) + except Exception as e: logging.error(f"IPC create_bucket failed: {e}") raise SDKError(f"bucket creation failed: {e}") @@ -182,22 +188,19 @@ def view_bucket(self, ctx, bucket_name: str) -> Optional[IPCBucket]: if not bucket_name: raise SDKError("empty bucket name") try: - request = ipcnodeapi_pb2.IPCBucketViewRequest( - name=bucket_name, - address=self.ipc.auth.address.lower() - ) + request = ipcnodeapi_pb2.IPCBucketViewRequest(name=bucket_name, address=self.ipc.auth.address.lower()) response = self.client.BucketView(request) - + if not response: return None created_at = 0 - if hasattr(response, 'created_at') and response.created_at: + if hasattr(response, "created_at") and response.created_at: created_at = int(response.created_at.seconds) return IPCBucket( - id=response.id if hasattr(response, 'id') else '', - name=response.name if hasattr(response, 'name') else bucket_name, - created_at=created_at + id=response.id if hasattr(response, "id") else "", + name=response.name if hasattr(response, "name") else bucket_name, + created_at=created_at, ) except grpc.RpcError as e: if e.code() == grpc.StatusCode.NOT_FOUND: @@ -217,35 +220,31 @@ def view_bucket(self, ctx, bucket_name: str) -> Optional[IPCBucket]: logging.error(f"IPC view_bucket unexpected error: {err}") raise SDKError(f"failed to get bucket: {err}") - def list_buckets(self, ctx, offset: int = 0, limit: int = 0) -> list[IPCBucket]: + def list_buckets(self, ctx, offset: int = 0, limit: int = 0) -> list[IPCBucket]: try: actual_limit = limit if limit > 0 else 10000 - + request = ipcnodeapi_pb2.IPCBucketListRequest( - address=self.ipc.auth.address, - offset=offset, - limit=actual_limit + address=self.ipc.auth.address, offset=offset, limit=actual_limit + ) + logging.info( + f"Sending BucketList request - address: {self.ipc.auth.address}, offset: {offset}, limit: {actual_limit} (user limit: {limit})" ) - logging.info(f"Sending BucketList request - address: {self.ipc.auth.address}, offset: {offset}, limit: {actual_limit} (user limit: {limit})") response = self.client.BucketList(request) - + buckets = [] - if response and hasattr(response, 'buckets'): + if response and hasattr(response, "buckets"): logging.info(f"Received BucketList response with {len(response.buckets)} buckets") for bucket in response.buckets: created_at = 0 - if hasattr(bucket, 'created_at') and bucket.created_at: + if hasattr(bucket, "created_at") and bucket.created_at: created_at = int(bucket.created_at.seconds) - - bucket_name = bucket.name if hasattr(bucket, 'name') else '' - buckets.append(IPCBucket( - name=bucket_name, - created_at=created_at, - id='' - )) + + bucket_name = bucket.name if hasattr(bucket, "name") else "" + buckets.append(IPCBucket(name=bucket_name, created_at=created_at, id="")) else: logging.info("BucketList response is empty or has no 'buckets' field") - + return buckets except grpc.RpcError as e: logging.error(f"IPC list_buckets gRPC failed: {e.code()} - {e.details()}") @@ -258,10 +257,7 @@ def delete_bucket(self, ctx, name: str) -> None: if not name: raise SDKError("empty bucket name") try: - request = ipcnodeapi_pb2.IPCBucketViewRequest( - name=name, - address=self.ipc.auth.address.lower() - ) + request = ipcnodeapi_pb2.IPCBucketViewRequest(name=name, address=self.ipc.auth.address.lower()) try: response = self.client.BucketView(request) if not response: @@ -275,27 +271,27 @@ def delete_bucket(self, ctx, name: str) -> None: raise SDKError(f"failed to check bucket existence: {e.details()}") # If we get here, bucket exists - proceed with deletion - bucket_id_hex = response.id if hasattr(response, 'id') and response.id else None + bucket_id_hex = response.id if hasattr(response, "id") and response.id else None if not bucket_id_hex: logging.error(f"No bucket ID returned from IPC for bucket '{name}'") raise SDKError(f"bucket ID not available from IPC response") - + logging.info(f"Got bucket ID from IPC: {bucket_id_hex}") - + try: tx_hash = self.ipc.storage.delete_bucket( bucket_name=name, from_address=self.ipc.auth.address, private_key=self.ipc.auth.key, - bucket_id_hex=bucket_id_hex # bucket ID from IPC response + bucket_id_hex=bucket_id_hex, # bucket ID from IPC response ) logging.info(f"IPC delete_bucket transaction sent for '{name}', tx_hash: {tx_hash}") return None - + except Exception as e: logging.error(f"Failed to delete bucket '{name}' on blockchain: {str(e)}") raise SDKError(f"blockchain transaction failed: {str(e)}") - + except Exception as err: logging.error(f"IPC delete_bucket failed: {err}") raise SDKError(f"failed to delete bucket: {err}") @@ -303,34 +299,32 @@ def delete_bucket(self, ctx, name: str) -> None: def file_info(self, ctx, bucket_name: str, file_name: str) -> Optional[IPCFileMeta]: if not bucket_name: raise SDKError("empty bucket name") - + if not file_name: raise SDKError("empty file name") try: request = ipcnodeapi_pb2.IPCFileViewRequest( - file_name=file_name, - bucket_name=bucket_name, - address=self.ipc.auth.address.lower() + file_name=file_name, bucket_name=bucket_name, address=self.ipc.auth.address.lower() ) response = self.client.FileView(request) - + if not response: logging.info(f"File '{file_name}' in bucket '{bucket_name}' not found.") return None - + created_at = 0 - if hasattr(response, 'created_at') and response.created_at: + if hasattr(response, "created_at") and response.created_at: created_at = int(response.created_at.seconds) - + return IPCFileMeta( - root_cid=response.root_cid if hasattr(response, 'root_cid') else '', - name=response.file_name if hasattr(response, 'file_name') else file_name, - bucket_name=response.bucket_name if hasattr(response, 'bucket_name') else bucket_name, - encoded_size=response.encoded_size if hasattr(response, 'encoded_size') else 0, - actual_size=response.actual_size if hasattr(response, 'actual_size') else 0, - is_public=response.is_public if hasattr(response, 'is_public') else False, - created_at=created_at + root_cid=response.root_cid if hasattr(response, "root_cid") else "", + name=response.file_name if hasattr(response, "file_name") else file_name, + bucket_name=response.bucket_name if hasattr(response, "bucket_name") else bucket_name, + encoded_size=response.encoded_size if hasattr(response, "encoded_size") else 0, + actual_size=response.actual_size if hasattr(response, "actual_size") else 0, + is_public=response.is_public if hasattr(response, "is_public") else False, + created_at=created_at, ) except grpc.RpcError as e: if e.code() == grpc.StatusCode.NOT_FOUND: @@ -351,35 +345,39 @@ def list_files(self, ctx, bucket_name: str) -> list[IPCFileListItem]: bucket_name=bucket_name, address=self.ipc.auth.address.lower(), offset=0, - limit=1000 # Default limit to get up to 1000 files + limit=1000, # Default limit to get up to 1000 files ) response = self.client.FileList(request) - + files = [] - if response and hasattr(response, 'list'): + if response and hasattr(response, "list"): logging.info(f"Received FileList response with {len(response.list)} files") for file_item in response.list: created_at = 0 - if hasattr(file_item, 'created_at') and file_item.created_at: + if hasattr(file_item, "created_at") and file_item.created_at: created_at = int(file_item.created_at.seconds) - - file_name = file_item.name if hasattr(file_item, 'name') else '' - root_cid = file_item.root_cid if hasattr(file_item, 'root_cid') else '' - encoded_size = file_item.encoded_size if hasattr(file_item, 'encoded_size') else 0 - actual_size = file_item.actual_size if hasattr(file_item, 'actual_size') else 0 - - logging.info(f"Processing file: name={file_name}, root_cid={root_cid}, encoded_size={encoded_size}, actual_size={actual_size}, created_at={created_at}") - - files.append(IPCFileListItem( - name=file_name, - root_cid=root_cid, - encoded_size=encoded_size, - actual_size=actual_size, - created_at=created_at - )) + + file_name = file_item.name if hasattr(file_item, "name") else "" + root_cid = file_item.root_cid if hasattr(file_item, "root_cid") else "" + encoded_size = file_item.encoded_size if hasattr(file_item, "encoded_size") else 0 + actual_size = file_item.actual_size if hasattr(file_item, "actual_size") else 0 + + logging.info( + f"Processing file: name={file_name}, root_cid={root_cid}, encoded_size={encoded_size}, actual_size={actual_size}, created_at={created_at}" + ) + + files.append( + IPCFileListItem( + name=file_name, + root_cid=root_cid, + encoded_size=encoded_size, + actual_size=actual_size, + created_at=created_at, + ) + ) else: logging.warning("FileList response has no 'list' field or is empty") - + return files except grpc.RpcError as e: logging.error(f"IPC list_files gRPC failed: {e.code()} - {e.details()}") @@ -390,7 +388,7 @@ def list_files(self, ctx, bucket_name: str) -> list[IPCFileListItem]: def file_delete(self, ctx, bucket_name: str, file_name: str) -> None: """Delete a file by bucket name and file name. - + This implementation matches the Go SDK's IPC FileDelete method. """ if not bucket_name.strip() or not file_name.strip(): @@ -401,7 +399,7 @@ def file_delete(self, ctx, bucket_name: str, file_name: str) -> None: # For now, using the names as-is like in the current implementation encrypted_file_name = file_name encrypted_bucket_name = bucket_name - + # Step 1: Get bucket by name from storage contract # Go SDK: bucket, err := sdk.ipc.Storage.GetBucketByName(...) bucket = self.ipc.storage.get_bucket_by_name( @@ -409,37 +407,33 @@ def file_delete(self, ctx, bucket_name: str, file_name: str) -> None: encrypted_bucket_name, self.ipc.auth.address, 0, # file_offset - no need to fetch file ids - 0 # file_limit - no need to fetch file ids + 0, # file_limit - no need to fetch file ids ) if not bucket: raise SDKError(f"bucket '{bucket_name}' not found") - + bucket_id = bucket[0] # bytes32 bucket ID bucket_name_from_contract = bucket[1] # Bucket name from contract - + # Step 2: Get file by name from storage contract # Go SDK: file, err := sdk.ipc.Storage.GetFileByName(&bind.CallOpts{Context: ctx}, bucket.Id, fileName) file_info = self.ipc.storage.get_file_by_name( - {"from": self.ipc.auth.address}, - bucket_id, - encrypted_file_name + {"from": self.ipc.auth.address}, bucket_id, encrypted_file_name ) if not file_info: raise SDKError(f"failed to retrieve file - file does not exist") - - file_id = file_info[0] + + file_id = file_info[0] file_index = None try: file_index = self.ipc.storage.get_file_index_by_id( - {"from": self.ipc.auth.address}, - encrypted_bucket_name, - file_id + {"from": self.ipc.auth.address}, encrypted_bucket_name, file_id ) logging.info(f"Got file index from contract: {file_index}") except Exception as index_err: logging.warning(f"Failed to get file index from contract: {index_err}") logging.info("Falling back to manual index calculation via file listing...") - + try: files = self.list_files(ctx, bucket_name) for i, f in enumerate(files): @@ -447,35 +441,35 @@ def file_delete(self, ctx, bucket_name: str, file_name: str) -> None: file_index = i logging.info(f"Calculated manual file index: {file_index} (position in list)") break - + if file_index is None: raise SDKError(f"file '{file_name}' not found in bucket file list") - + except Exception as list_err: logging.error(f"Failed to calculate manual index: {list_err}") - raise SDKError(f"failed to determine file index: contract method failed and manual calculation failed: {index_err}") - + raise SDKError( + f"failed to determine file index: contract method failed and manual calculation failed: {index_err}" + ) + # Validate that we got a valid index if file_index is None or (isinstance(file_index, int) and file_index < 0): raise SDKError(f"invalid file index: {file_index}") - + # Step 4: Delete file via storage contract transaction # Go SDK: tx, err := sdk.ipc.Storage.DeleteFile(sdk.ipc.Auth, file.Id, bucket.Id, fileName, fileIdx.Index) - logging.info(f"Deleting file with file_id: {file_id.hex() if isinstance(file_id, bytes) else file_id}, bucket_id: {bucket_id.hex() if isinstance(bucket_id, bytes) else bucket_id}, name: {encrypted_file_name}, index: {file_index}") - - tx_hash = self.ipc.storage.delete_file( - self.ipc.auth, - file_id, - bucket_id, - encrypted_file_name, - file_index + logging.info( + f"Deleting file with file_id: {file_id.hex() if isinstance(file_id, bytes) else file_id}, bucket_id: {bucket_id.hex() if isinstance(bucket_id, bytes) else bucket_id}, name: {encrypted_file_name}, index: {file_index}" ) - + + tx_hash = self.ipc.storage.delete_file(self.ipc.auth, file_id, bucket_id, encrypted_file_name, file_index) + # Go SDK waits for transaction: return errSDK.Wrap(sdk.ipc.WaitForTx(ctx, tx.Hash())) # Python SDK doesn't have WaitForTx implemented yet, so we just return - logging.info(f"IPC file_delete transaction successful for '{file_name}' in bucket '{bucket_name}', tx_hash: {tx_hash}") + logging.info( + f"IPC file_delete transaction successful for '{file_name}' in bucket '{bucket_name}', tx_hash: {tx_hash}" + ) return None - + except SDKError: # Re-raise SDKError as-is raise @@ -486,71 +480,71 @@ def file_delete(self, ctx, bucket_name: str, file_name: str) -> None: def create_file_upload(self, ctx, bucket_name: str, file_name: str) -> IPCFileUpload: if not bucket_name: raise SDKError("empty bucket name") - + if not file_name: raise SDKError("empty file name") - + try: encrypted_file_name = maybe_encrypt_metadata(file_name, bucket_name + "/" + file_name, self.encryption_key) encrypted_bucket_name = maybe_encrypt_metadata(bucket_name, file_name, self.encryption_key) - + file_upload = new_ipc_file_upload(bucket_name, file_name) - + bucket_info = self.view_bucket(None, encrypted_bucket_name) if not bucket_info: raise SDKError(f"bucket '{bucket_name}' not found") - + bucket_id_hex = bucket_info.id - if bucket_id_hex.startswith('0x'): + if bucket_id_hex.startswith("0x"): bucket_id_hex = bucket_id_hex[2:] bucket_id = bytes.fromhex(bucket_id_hex) - + tx_hash = None max_retries = 3 for attempt in range(max_retries): try: tx_hash = self.ipc.storage.create_file( - self.ipc.auth.address, - self.ipc.auth.key, - bucket_id, - encrypted_file_name, - nonce_manager=None + self.ipc.auth.address, self.ipc.auth.key, bucket_id, encrypted_file_name, nonce_manager=None ) break except Exception as e: error_msg = str(e).lower() - + if "0x6891dde0" in error_msg or "filealreadyexists" in error_msg: logging.info(f"File '{file_name}' already exists in bucket '{bucket_name}'") raise SDKError(f"file already exists") - + if "transaction reverted" in error_msg: from private.ipc.errors import error_hash_to_error + parsed_error = error_hash_to_error(e) error_name = str(parsed_error) if error_name != str(e): logging.error(f"Transaction reverted: {error_name}") raise SDKError(f"upload failed: {error_name}") - - is_retryable = any(retry_phrase in error_msg for retry_phrase in [ - "nonce too low", "replacement transaction underpriced", "eof" - ]) + + is_retryable = any( + retry_phrase in error_msg + for retry_phrase in ["nonce too low", "replacement transaction underpriced", "eof"] + ) if is_retryable and attempt < max_retries - 1: - time.sleep(0.5 * (2 ** attempt)) # Exponential backoff + time.sleep(0.5 * (2**attempt)) # Exponential backoff continue else: raise SDKError(f"failed to create file transaction: {str(e)}") - - if hasattr(self.ipc, 'wait_for_tx') and tx_hash: + + if hasattr(self.ipc, "wait_for_tx") and tx_hash: self.ipc.wait_for_tx(tx_hash) - elif hasattr(self.ipc, 'web3') and hasattr(self.ipc.eth.eth, 'wait_for_transaction_receipt') and tx_hash: + elif hasattr(self.ipc, "web3") and hasattr(self.ipc.eth.eth, "wait_for_transaction_receipt") and tx_hash: receipt = self.ipc.eth.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise SDKError("CreateFile transaction failed") - - logging.info(f"IPC create_file_upload completed successfully for '{file_name}' in bucket '{bucket_name}', tx_hash: {tx_hash}") + + logging.info( + f"IPC create_file_upload completed successfully for '{file_name}' in bucket '{bucket_name}', tx_hash: {tx_hash}" + ) return file_upload - + except Exception as err: logging.error(f"IPC create_file_upload failed: {err}") raise SDKError(f"failed to create file upload: {err}") @@ -561,8 +555,12 @@ def upload(self, ctx, bucket_name: str, file_name: str, reader: io.IOBase) -> IP is_continuation = file_upload.state.chunk_count > 0 - encrypted_file_name = maybe_encrypt_metadata(file_upload.name, file_upload.bucket_name + "/" + file_upload.name, self.encryption_key) - encrypted_bucket_name = maybe_encrypt_metadata(file_upload.bucket_name, file_upload.name, self.encryption_key) + encrypted_file_name = maybe_encrypt_metadata( + file_upload.name, file_upload.bucket_name + "/" + file_upload.name, self.encryption_key + ) + encrypted_bucket_name = maybe_encrypt_metadata( + file_upload.bucket_name, file_upload.name, self.encryption_key + ) bucket = None @@ -570,11 +568,11 @@ def get_bucket_call(): nonlocal bucket try: bucket = self.ipc.storage.get_bucket_by_name( - call_opts={'from': self.ipc.auth.address}, + call_opts={"from": self.ipc.auth.address}, bucket_name=encrypted_bucket_name, owner_address=self.ipc.auth.address, file_offset=0, - file_limit=0 + file_limit=0, ) return (False, None) except Exception as e: @@ -599,18 +597,35 @@ def get_bucket_call(): if is_continuation: from .sdk import skip_to_position + skip_to_position(reader, file_upload.state.actual_file_size) - return self._upload_with_comprehensive_debug(ctx, file_upload, reader, bucket_id, encrypted_file_name, - encrypted_bucket_name, file_enc_key, buffer_size) + return self._upload_with_comprehensive_debug( + ctx, + file_upload, + reader, + bucket_id, + encrypted_file_name, + encrypted_bucket_name, + file_enc_key, + buffer_size, + ) except Exception as err: logging.error(f"IPC upload failed: {err}") raise SDKError(f"upload failed: {str(err)}") - def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, reader: io.IOBase, bucket_id: bytes, - encrypted_file_name: str, encrypted_bucket_name: str, file_enc_key: bytes, - buffer_size: int) -> IPCFileMetaV2: + def _upload_with_comprehensive_debug( + self, + ctx, + file_upload: IPCFileUpload, + reader: io.IOBase, + bucket_id: bytes, + encrypted_file_name: str, + encrypted_bucket_name: str, + file_enc_key: bytes, + buffer_size: int, + ) -> IPCFileMetaV2: pool = ConnectionPool() try: chunk_index = file_upload.state.chunk_count @@ -635,14 +650,14 @@ def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, read if chunk_index == 0: raise SDKError("empty file") break - - chunk_upload = self.create_chunk_upload(ctx, chunk_index, file_enc_key, - chunk_data[:bytes_read], bucket_id, - encrypted_file_name) + + chunk_upload = self.create_chunk_upload( + ctx, chunk_index, file_enc_key, chunk_data[:bytes_read], bucket_id, encrypted_file_name + ) cids, sizes, proto_chunk, error = to_ipc_proto_chunk( - chunk_upload.chunk_cid, chunk_upload.index, chunk_upload.actual_size, - chunk_upload.blocks) + chunk_upload.chunk_cid, chunk_upload.index, chunk_upload.actual_size, chunk_upload.blocks + ) if error: raise error @@ -651,30 +666,39 @@ def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, read for attempt in range(max_retries): try: tx_hash = self.ipc.storage.add_file_chunk( - self.ipc.auth.address, self.ipc.auth.key, + self.ipc.auth.address, + self.ipc.auth.key, self._convert_cid_to_bytes(chunk_upload.chunk_cid), - bucket_id, encrypted_file_name, chunk_upload.encoded_size, - cids, sizes, chunk_upload.index, nonce_manager=None) + bucket_id, + encrypted_file_name, + chunk_upload.encoded_size, + cids, + sizes, + chunk_upload.index, + nonce_manager=None, + ) break except Exception as e: error_msg = str(e).lower() - is_retryable = any(retry_phrase in error_msg for retry_phrase in [ - "nonce too low", "replacement transaction underpriced", "eof"]) + is_retryable = any( + retry_phrase in error_msg + for retry_phrase in ["nonce too low", "replacement transaction underpriced", "eof"] + ) if is_retryable and attempt < max_retries - 1: - time.sleep(0.5 * (2 ** attempt)) + time.sleep(0.5 * (2**attempt)) continue else: raise SDKError(f"failed to add file chunk: {str(e)}") - if hasattr(self.ipc, 'wait_for_tx') and tx_hash: + if hasattr(self.ipc, "wait_for_tx") and tx_hash: self.ipc.wait_for_tx(tx_hash) - elif hasattr(self.ipc, 'web3') and tx_hash: + elif hasattr(self.ipc, "web3") and tx_hash: receipt = self.ipc.eth.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise SDKError(f"AddFileChunk transaction failed: {tx_hash}") file_upload.state.pre_create_chunk(chunk_upload, tx_hash) - + self.upload_chunk(ctx, chunk_upload, pool) file_upload.state.chunk_uploaded(chunk_upload) @@ -685,7 +709,8 @@ def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, read root_cid = file_upload.state.dag_root.build() file_meta = self.ipc.storage.get_file_by_name( - {"from": self.ipc.auth.address}, bucket_id, encrypted_file_name) + {"from": self.ipc.auth.address}, bucket_id, encrypted_file_name + ) file_id = self._calculate_file_id(bucket_id, encrypted_file_name) is_filled = False @@ -704,7 +729,7 @@ def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, read if time.time() - wait_start > max_wait_time: raise SDKError("timeout waiting for file to be filled") - + time.sleep(1) root_cid_bytes = self._convert_cid_to_bytes(root_cid) @@ -713,17 +738,20 @@ def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, read raise SDKError(f"bucket '{file_upload.bucket_name}' not found during commit") bucket_id_bytes = bytes.fromhex(bucket_info.id) if isinstance(bucket_info.id, str) else bucket_info.id - + tx_hash = self.ipc.storage.commit_file( - bucket_id_bytes, encrypted_file_name, + bucket_id_bytes, + encrypted_file_name, file_upload.state.encoded_file_size, file_upload.state.actual_file_size, root_cid_bytes, - self.ipc.auth.address, self.ipc.auth.key) + self.ipc.auth.address, + self.ipc.auth.key, + ) - if hasattr(self.ipc, 'wait_for_tx') and tx_hash: + if hasattr(self.ipc, "wait_for_tx") and tx_hash: self.ipc.wait_for_tx(tx_hash) - elif hasattr(self.ipc, 'web3') and tx_hash: + elif hasattr(self.ipc, "web3") and tx_hash: receipt = self.ipc.eth.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise SDKError("CommitFile transaction failed") @@ -737,7 +765,7 @@ def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, read encoded_size=file_upload.state.encoded_file_size, size=file_upload.state.actual_file_size, created_at=time.time() if file_meta else None, - committed_at=time.time() + committed_at=time.time(), ) except Exception as err: @@ -747,89 +775,87 @@ def _upload_with_comprehensive_debug(self, ctx, file_upload: IPCFileUpload, read pool.close() except Exception as e: logging.warning(f"Error closing connection pool: {str(e)}") - + def _calculate_file_id(self, bucket_id: bytes, file_name: str) -> bytes: try: from Crypto.Hash import keccak + combined = bucket_id + file_name.encode() hash_obj = keccak.new(digest_bits=256) hash_obj.update(combined) return hash_obj.digest() except ImportError: - raise SDKError("Failed to import required modules for file ID calculation") + raise SDKError("Failed to import required modules for file ID calculation") except Exception as e: raise SDKError(f"Failed to calculate file ID: {str(e)}") def _convert_cid_to_bytes(self, cid_input) -> bytes: - if hasattr(cid_input, '__bytes__'): + if hasattr(cid_input, "__bytes__"): try: return bytes(cid_input) except Exception as e: logging.warning(f"Failed to call __bytes__ on CID object: {e}") - + # Convert to string if needed if not isinstance(cid_input, str): cid_str = str(cid_input) else: cid_str = cid_input - + try: from multiformats import CID as CIDLib + cid_obj = CIDLib.decode(cid_str) return bytes(cid_obj) - + except Exception as e: logging.error(f"Failed to decode CID using multiformats library: {e}") raise SDKError(f"Failed to convert CID to binary format: {e}") - def create_chunk_upload(self, ctx, index: int, file_encryption_key: bytes, data: bytes, bucket_id: bytes, file_name: str) -> IPCFileChunkUploadV2: + def create_chunk_upload( + self, ctx, index: int, file_encryption_key: bytes, data: bytes, bucket_id: bytes, file_name: str + ) -> IPCFileChunkUploadV2: try: if len(file_encryption_key) > 0: data = encrypt(file_encryption_key, data, str(index).encode()) - + size = len(data) - + block_size = BlockSize - + chunk_dag = build_dag(ctx, io.BytesIO(data), block_size) if chunk_dag is None: raise SDKError("build_dag returned None") - + chunk_cid_str = chunk_dag.cid if isinstance(chunk_dag.cid, str) else str(chunk_dag.cid) - - cids, sizes, proto_chunk, error = to_ipc_proto_chunk( - chunk_cid_str, - index, - size, - chunk_dag.blocks - ) - + + cids, sizes, proto_chunk, error = to_ipc_proto_chunk(chunk_cid_str, index, size, chunk_dag.blocks) + if error is not None: raise error if proto_chunk is None: raise SDKError("to_ipc_proto_chunk returned None proto_chunk") - + request = ipcnodeapi_pb2.IPCFileUploadChunkCreateRequest( - chunk=proto_chunk, - bucket_id=bucket_id, # bytes - file_name=file_name + chunk=proto_chunk, bucket_id=bucket_id, file_name=file_name # bytes ) - + response = self.client.FileUploadChunkCreate(request) if response is None: raise SDKError("gRPC response is None") - + if len(response.blocks) != len(chunk_dag.blocks): - raise SDKError(f"received unexpected amount of blocks {len(response.blocks)}, expected {len(chunk_dag.blocks)}") - + raise SDKError( + f"received unexpected amount of blocks {len(response.blocks)}, expected {len(chunk_dag.blocks)}" + ) + for i, upload in enumerate(response.blocks): if chunk_dag.blocks[i].cid != upload.cid: raise SDKError(f"block CID mismatch at position {i}") chunk_dag.blocks[i].node_address = upload.node_address chunk_dag.blocks[i].node_id = upload.node_id chunk_dag.blocks[i].permit = upload.permit - - + return IPCFileChunkUploadV2( index=index, chunk_cid=chunk_dag.cid, @@ -838,7 +864,7 @@ def create_chunk_upload(self, ctx, index: int, file_encryption_key: bytes, data: encoded_size=chunk_dag.encoded_size, # proto_node_size maps to encoded_size blocks=chunk_dag.blocks, bucket_id=bucket_id, - file_name=file_name + file_name=file_name, ) except Exception as err: raise SDKError(f"failed to create chunk upload: {str(err)}") @@ -846,62 +872,66 @@ def create_chunk_upload(self, ctx, index: int, file_encryption_key: bytes, data: def upload_chunk(self, ctx, file_chunk_upload: IPCFileChunkUploadV2, pool: ConnectionPool) -> None: try: chunk_cid_str = file_chunk_upload.chunk_cid - if hasattr(file_chunk_upload.chunk_cid, 'string'): + if hasattr(file_chunk_upload.chunk_cid, "string"): chunk_cid_str = file_chunk_upload.chunk_cid.string() - elif hasattr(file_chunk_upload.chunk_cid, 'toString'): + elif hasattr(file_chunk_upload.chunk_cid, "toString"): chunk_cid_str = file_chunk_upload.chunk_cid.toString() elif not isinstance(file_chunk_upload.chunk_cid, str): chunk_cid_str = str(file_chunk_upload.chunk_cid) - + cids, sizes, proto_chunk, err = to_ipc_proto_chunk( - chunk_cid_str, - file_chunk_upload.index, - file_chunk_upload.actual_size, - file_chunk_upload.blocks + chunk_cid_str, file_chunk_upload.index, file_chunk_upload.actual_size, file_chunk_upload.blocks ) if err: raise err - + # Upload all blocks in parallel with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_concurrency) as executor: futures = {} - + for i, block in enumerate(file_chunk_upload.blocks): future = executor.submit( self._upload_block, - ctx, pool, i, block, proto_chunk, - file_chunk_upload.bucket_id, file_chunk_upload.file_name + ctx, + pool, + i, + block, + proto_chunk, + file_chunk_upload.bucket_id, + file_chunk_upload.file_name, ) futures[future] = i - + for future in concurrent.futures.as_completed(futures): try: - future.result() + future.result() except Exception as e: for f in futures: f.cancel() raise e - + except Exception as err: raise SDKError(f"failed to upload chunk: {str(err)}") - def _upload_block(self, ctx, pool: ConnectionPool, block_index: int, block, proto_chunk, bucket_id, file_name: str) -> None: + def _upload_block( + self, ctx, pool: ConnectionPool, block_index: int, block, proto_chunk, bucket_id, file_name: str + ) -> None: try: - node_address = block.node_address if hasattr(block, 'node_address') else block["node_address"] - block_data_bytes = block.data if hasattr(block, 'data') else block["data"] - block_cid = block.cid if hasattr(block, 'cid') else block["cid"] - node_id = block.node_id if hasattr(block, 'node_id') else block["node_id"] - + node_address = block.node_address if hasattr(block, "node_address") else block["node_address"] + block_data_bytes = block.data if hasattr(block, "data") else block["data"] + block_cid = block.cid if hasattr(block, "cid") else block["cid"] + node_id = block.node_id if hasattr(block, "node_id") else block["node_id"] + if isinstance(block_data_bytes, memoryview): block_data_bytes = bytes(block_data_bytes) - + if not node_address or not node_address.strip(): raise SDKError(f"Invalid node address for block {block_cid}: '{node_address}'") - + logging.debug(f"Uploading block {block_index}: CID={block_cid}, node={node_address}") - + result = pool.create_ipc_client(node_address, self.use_connection_pool) - + if len(result) == 3: client, closer, err = result if err: @@ -910,17 +940,20 @@ def _upload_block(self, ctx, pool: ConnectionPool, block_index: int, block, prot raise SDKError(f"failed to create client: client is None") else: raise SDKError(f"Unexpected return format from create_ipc_client: {result}") - + try: + def upload_streaming(): import random + nonce_bytes = random.randbytes(32) - nonce = int.from_bytes(nonce_bytes, byteorder='big') + nonce = int.from_bytes(nonce_bytes, byteorder="big") deadline = int(time.time() + 24 * 60 * 60) try: import base58 - if node_id.startswith('12D3'): + + if node_id.startswith("12D3"): full_node_id = base58.b58decode(node_id) else: full_node_id = node_id.encode() if isinstance(node_id, str) else node_id @@ -928,8 +961,7 @@ def upload_streaming(): full_node_id = node_id.encode() if isinstance(node_id, str) else node_id signature_hex, _ = self._create_storage_signature( - proto_chunk.cid, block_cid, proto_chunk.index, - block_index, node_id, nonce, deadline, bucket_id + proto_chunk.cid, block_cid, proto_chunk.index, block_index, node_id, nonce, deadline, bucket_id ) data_len = len(block_data_bytes) @@ -957,138 +989,146 @@ def message_generator(): signature=signature_hex, nonce=nonce_bytes, node_id=full_node_id, - deadline=deadline + deadline=deadline, ) yield block_data i = end is_first_part = False - + try: response = client.FileUploadBlock(message_generator()) return response except Exception as e: if "BlockAlreadyFilled" in str(e): - return None + return None raise SDKError(f"streaming upload failed: {str(e)}") - + upload_streaming() - + finally: if closer: try: closer() except Exception as e: logging.warning(f"Failed to close connection for block {block_cid}: {e}") - + except Exception as err: - block_cid = block.cid if hasattr(block, 'cid') else block["cid"] + block_cid = block.cid if hasattr(block, "cid") else block["cid"] raise SDKError(f"failed to upload block {block_cid}: {str(err)}") - - def _create_storage_signature(self, chunk_cid: str, block_cid: str, chunk_index: int, - block_index: int, node_id: str, nonce: int, deadline: int, bucket_id: bytes) -> tuple: + + def _create_storage_signature( + self, + chunk_cid: str, + block_cid: str, + chunk_index: int, + block_index: int, + node_id: str, + nonce: int, + deadline: int, + bucket_id: bytes, + ) -> tuple: try: from private.eip712 import sign, Domain, TypedData - nonce_bytes = nonce.to_bytes(32, byteorder='big') + + nonce_bytes = nonce.to_bytes(32, byteorder="big") chunk_cid_obj = CID.decode(chunk_cid) block_cid_obj = CID.decode(block_cid) - + chunk_cid_bytes = self._convert_cid_to_bytes(chunk_cid_obj) block_cid_bytes = self._convert_cid_to_bytes(block_cid_obj) - + bcid = bytearray(32) if len(block_cid_bytes) > 4: copy_len = min(len(block_cid_bytes) - 4, 32) - bcid[:copy_len] = block_cid_bytes[4:4+copy_len] + bcid[:copy_len] = block_cid_bytes[4 : 4 + copy_len] else: copy_len = min(len(block_cid_bytes), 32) bcid[:copy_len] = block_cid_bytes[:copy_len] - + try: import base58 - if node_id.startswith('12D3'): + + if node_id.startswith("12D3"): decoded = base58.b58decode(node_id) node_id_bytes = decoded else: node_id_bytes = node_id.encode() if isinstance(node_id, str) else node_id except ImportError: node_id_bytes = node_id.encode() if isinstance(node_id, str) else node_id - + chain_id = self.ipc.storage.get_chain_id() contract_address = self.ipc.storage.contract_address - - domain = Domain( - name="Storage", - version="1", - chain_id=chain_id, - verifying_contract=contract_address - ) - + + domain = Domain(name="Storage", version="1", chain_id=chain_id, verifying_contract=contract_address) + data_types = { "StorageData": [ TypedData("chunkCID", "bytes"), - TypedData("blockCID", "bytes32"), + TypedData("blockCID", "bytes32"), TypedData("chunkIndex", "uint256"), TypedData("blockIndex", "uint8"), TypedData("nodeId", "bytes32"), TypedData("nonce", "uint256"), TypedData("deadline", "uint256"), - TypedData("bucketId", "bytes32") + TypedData("bucketId", "bytes32"), ] } - + from eth_utils import to_int - + node_id_32 = bytearray(32) if len(node_id_bytes) > 6: copy_len = min(len(node_id_bytes) - 6, 32) - node_id_32[:copy_len] = node_id_bytes[6:6+copy_len] - + node_id_32[:copy_len] = node_id_bytes[6 : 6 + copy_len] + data_message = { - "chunkCID": bytes(chunk_cid_bytes), - "blockCID": bytes(bcid), + "chunkCID": bytes(chunk_cid_bytes), + "blockCID": bytes(bcid), "chunkIndex": to_int(chunk_index), "blockIndex": int(block_index), - "nodeId": bytes(node_id_32), + "nodeId": bytes(node_id_32), "nonce": to_int(nonce), "deadline": to_int(deadline), - "bucketId": bytes(bucket_id) if len(bucket_id) == 32 else bytes(bucket_id) + b'\x00' * (32 - len(bucket_id)) + "bucketId": ( + bytes(bucket_id) if len(bucket_id) == 32 else bytes(bucket_id) + b"\x00" * (32 - len(bucket_id)) + ), } - + if isinstance(self.ipc.auth.key, bytes): private_key_bytes = self.ipc.auth.key else: - key_str = str(self.ipc.auth.key).replace('0x', '') + key_str = str(self.ipc.auth.key).replace("0x", "") private_key_bytes = bytes.fromhex(key_str) - + signature_bytes = sign(private_key_bytes, domain, "StorageData", data_types, data_message) signature_hex = signature_bytes.hex() - + return signature_hex, nonce_bytes - + except Exception as e: raise SDKError(f"signature creation failed: {str(e)}") def fetch_block_data( - self, - ctx, + self, + ctx, pool: ConnectionPool, - chunk_cid: str, - bucket_name: str, - file_name: str, + chunk_cid: str, + bucket_name: str, + file_name: str, address: str, - chunk_index: int, + chunk_index: int, block_index: int, - block + block, ) -> bytes: try: - if not hasattr(block, 'node_address') or not block.node_address: + if not hasattr(block, "node_address") or not block.node_address: raise SDKError("missing block metadata") - + client, closer, err = pool.create_ipc_client(block.node_address, self.use_connection_pool) if err: raise SDKError(f"failed to create client: {str(err)}") - + try: download_req = ipcnodeapi_pb2.IPCFileDownloadBlockRequest( chunk_cid=chunk_cid, @@ -1097,20 +1137,20 @@ def fetch_block_data( block_index=block_index, bucket_name=bucket_name, file_name=file_name, - address=address + address=address, ) - + download_client = client.FileDownloadBlock(download_req) if not download_client: raise SDKError("failed to get download client") - + buffer = io.BytesIO() - + for block_data in download_client: if not block_data: break buffer.write(block_data.data) - + return buffer.getvalue() finally: if closer: @@ -1125,73 +1165,53 @@ def create_file_download(self, ctx, bucket_name: str, file_name: str): try: if not bucket_name: raise SDKError("empty bucket name") - + if not file_name: raise SDKError("empty file name") - + file_name = maybe_encrypt_metadata(file_name, bucket_name + "/" + file_name, self.encryption_key) bucket_name = maybe_encrypt_metadata(bucket_name, bucket_name, self.encryption_key) - + request = ipcnodeapi_pb2.IPCFileDownloadCreateRequest( - bucket_name=bucket_name, - file_name=file_name, - address=self.ipc.auth.address + bucket_name=bucket_name, file_name=file_name, address=self.ipc.auth.address ) - + response = self.client.FileDownloadCreate(request) - + chunks = [] for i, chunk in enumerate(response.chunks): - chunks.append(Chunk( - cid=chunk.cid, - encoded_size=chunk.encoded_size, - size=chunk.size, - index=i - )) - - return IPCFileDownload( - bucket_name=response.bucket_name, - name=file_name, - chunks=chunks - ) + chunks.append(Chunk(cid=chunk.cid, encoded_size=chunk.encoded_size, size=chunk.size, index=i)) + + return IPCFileDownload(bucket_name=response.bucket_name, name=file_name, chunks=chunks) except Exception as err: raise SDKError(f"failed to create file download: {str(err)}") - + def create_range_file_download(self, ctx, bucket_name: str, file_name: str, start: int, end: int): try: if not bucket_name: raise SDKError("empty bucket name") - + if not file_name: raise SDKError("empty file name") - + file_name = maybe_encrypt_metadata(file_name, bucket_name + "/" + file_name, self.encryption_key) bucket_name = maybe_encrypt_metadata(bucket_name, bucket_name, self.encryption_key) - + request = ipcnodeapi_pb2.IPCFileDownloadRangeCreateRequest( bucket_name=bucket_name, file_name=file_name, address=self.ipc.auth.address, start_index=start, - end_index=end + end_index=end, ) - + response = self.client.FileDownloadRangeCreate(request) - + chunks = [] for i, chunk in enumerate(response.chunks): - chunks.append(Chunk( - cid=chunk.cid, - encoded_size=chunk.encoded_size, - size=chunk.size, - index=i + start - )) - - return IPCFileDownload( - bucket_name=response.bucket_name, - name=file_name, - chunks=chunks - ) + chunks.append(Chunk(cid=chunk.cid, encoded_size=chunk.encoded_size, size=chunk.size, index=i + start)) + + return IPCFileDownload(bucket_name=response.bucket_name, name=file_name, chunks=chunks) except Exception as err: raise SDKError(f"failed to create range file download: {str(err)}") @@ -1201,80 +1221,67 @@ def file_set_public_access(self, ctx, bucket_name: str, file_name: str, is_publi raise SDKError("empty bucket name") if not file_name: raise SDKError("empty file name") - + original_file_name = file_name original_bucket_name = bucket_name - + file_name = maybe_encrypt_metadata(file_name, bucket_name + "/" + file_name, self.encryption_key) bucket_name = maybe_encrypt_metadata(bucket_name, bucket_name, self.encryption_key) - + bucket_info = self.view_bucket(None, bucket_name) if not bucket_info: raise SDKError(f"bucket '{bucket_name}' not found") - + bucket_id_hex = bucket_info.id - if bucket_id_hex.startswith('0x'): + if bucket_id_hex.startswith("0x"): bucket_id_hex = bucket_id_hex[2:] bucket_id = bytes.fromhex(bucket_id_hex) - + try: - file = self.ipc.storage.get_file_by_name( - {}, - bucket_id, # bucket ID - file_name - ) + file = self.ipc.storage.get_file_by_name({}, bucket_id, file_name) # bucket ID if not file: raise SDKError("failed to retrieve file") except Exception as e: raise SDKError(f"failed to get file: {str(e)}") - - if not hasattr(self.ipc, 'access_manager') or self.ipc.access_manager is None: + + if not hasattr(self.ipc, "access_manager") or self.ipc.access_manager is None: raise SDKError("access manager not available") - + try: tx_hash = self.ipc.access_manager.change_public_access( - self.ipc.auth, # auth object - file[0], # file ID - is_public # is_public flag + self.ipc.auth, file[0], is_public # auth object # file ID # is_public flag ) - - if hasattr(self.ipc, 'wait_for_tx'): + + if hasattr(self.ipc, "wait_for_tx"): self.ipc.wait_for_tx(tx_hash) - elif hasattr(self.ipc, 'web3') and hasattr(self.ipc.eth.eth, 'wait_for_transaction_receipt'): + elif hasattr(self.ipc, "web3") and hasattr(self.ipc.eth.eth, "wait_for_transaction_receipt"): receipt = self.ipc.eth.eth.wait_for_transaction_receipt(tx_hash) if receipt.status != 1: raise SDKError("ChangePublicAccess transaction failed") - - logging.info(f"Successfully {'enabled' if is_public else 'disabled'} public access for file '{original_file_name}' in bucket '{original_bucket_name}', tx_hash: {tx_hash}") + + logging.info( + f"Successfully {'enabled' if is_public else 'disabled'} public access for file '{original_file_name}' in bucket '{original_bucket_name}', tx_hash: {tx_hash}" + ) return None - + except Exception as e: raise SDKError(f"failed to change public access: {str(e)}") - + except Exception as err: logging.error(f"IPC file_set_public_access failed: {err}") raise SDKError(f"failed to set public access: {str(err)}") - + def download(self, ctx, file_download, writer: io.IOBase): pool = ConnectionPool() try: - file_enc_key = encryption_key( - self.encryption_key, - file_download.bucket_name, - file_download.name - ) - + file_enc_key = encryption_key(self.encryption_key, file_download.bucket_name, file_download.name) + for chunk in file_download.chunks: - if hasattr(ctx, 'done') and ctx.done(): + if hasattr(ctx, "done") and ctx.done(): raise SDKError("context cancelled") - - chunk_download = self.create_chunk_download( - ctx, - file_download.bucket_name, - file_download.name, - chunk - ) - + + chunk_download = self.create_chunk_download(ctx, file_download.bucket_name, file_download.name, chunk) + self.download_chunk_blocks( ctx, pool, @@ -1283,9 +1290,9 @@ def download(self, ctx, file_download, writer: io.IOBase): self.ipc.auth.address, chunk_download, file_enc_key, - writer + writer, ) - + return None except Exception as err: raise SDKError(f"failed to download file: {str(err)}") @@ -1294,69 +1301,84 @@ def download(self, ctx, file_download, writer: io.IOBase): pool.close() except Exception as e: logging.warning(f"Error closing connection pool: {str(e)}") - + def create_chunk_download(self, ctx, bucket_name: str, file_name: str, chunk): try: request = ipcnodeapi_pb2.IPCFileDownloadChunkCreateRequest( - bucket_name=bucket_name, - file_name=file_name, - chunk_cid=chunk.cid, - address=self.ipc.auth.address + bucket_name=bucket_name, file_name=file_name, chunk_cid=chunk.cid, address=self.ipc.auth.address ) - + response = self.client.FileDownloadChunkCreate(request) - + blocks = [] for block in response.blocks: - blocks.append(FileBlockDownload( - cid=block.cid, - data=b"", - permit=block.permit, - node_address=block.node_address, - node_id=block.node_id - )) - + blocks.append( + FileBlockDownload( + cid=block.cid, + data=b"", + permit=block.permit, + node_address=block.node_address, + node_id=block.node_id, + ) + ) + return FileChunkDownload( - cid=chunk.cid, - index=chunk.index, - encoded_size=chunk.encoded_size, - size=chunk.size, - blocks=blocks + cid=chunk.cid, index=chunk.index, encoded_size=chunk.encoded_size, size=chunk.size, blocks=blocks ) except Exception as err: raise SDKError(f"failed to create chunk download: {str(err)}") - - def download_chunk_blocks(self, ctx, pool: ConnectionPool, bucket_name: str, file_name: str, address: str, - chunk_download, file_encryption_key: bytes, writer: io.IOBase): + + def download_chunk_blocks( + self, + ctx, + pool: ConnectionPool, + bucket_name: str, + file_name: str, + address: str, + chunk_download, + file_encryption_key: bytes, + writer: io.IOBase, + ): try: with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_concurrency) as executor: futures = {} - + for i, block in enumerate(chunk_download.blocks): - futures[executor.submit( - self.fetch_block_data, - ctx, pool, chunk_download.cid, bucket_name, file_name, - address, chunk_download.index, i, block - )] = i - + futures[ + executor.submit( + self.fetch_block_data, + ctx, + pool, + chunk_download.cid, + bucket_name, + file_name, + address, + chunk_download.index, + i, + block, + ) + ] = i + blocks = [None] * len(chunk_download.blocks) for future in concurrent.futures.as_completed(futures): index = futures[future] try: data = future.result() from .dag import extract_block_data + blocks[index] = extract_block_data(chunk_download.blocks[index].cid, data) except Exception as e: raise SDKError(f"failed to download block: {str(e)}") - + data = b"".join([b for b in blocks if b is not None]) - + if file_encryption_key: from private.encryption import decrypt + data = decrypt(file_encryption_key, data, str(chunk_download.index).encode()) - + writer.write(data) - + return None except Exception as err: - raise SDKError(f"failed to download chunk blocks: {str(err)}") \ No newline at end of file + raise SDKError(f"failed to download chunk blocks: {str(err)}") diff --git a/sdk/shared/__init__.py b/sdk/shared/__init__.py index db9ee38..775e5a0 100644 --- a/sdk/shared/__init__.py +++ b/sdk/shared/__init__.py @@ -1,5 +1,4 @@ # Shared utilities for SDK from .grpc_base import GrpcClientBase -__all__ = ['GrpcClientBase'] - +__all__ = ["GrpcClientBase"] diff --git a/sdk/shared/grpc_base.py b/sdk/shared/grpc_base.py index 9c4c522..9b4f005 100644 --- a/sdk/shared/grpc_base.py +++ b/sdk/shared/grpc_base.py @@ -3,10 +3,10 @@ from ..config import SDKError -class GrpcClientBase: +class GrpcClientBase: def __init__(self, connection_timeout: int = None): self.connection_timeout = connection_timeout - + def _handle_grpc_error(self, method_name: str, error: grpc.RpcError) -> None: status_code = error.code() details = error.details() or "No details provided" @@ -16,9 +16,7 @@ def _handle_grpc_error(self, method_name: str, error: grpc.RpcError) -> None: logging.warning(f"{method_name} timed out after {self.connection_timeout}s") raise SDKError(f"{method_name} request timed out after {self.connection_timeout}s") from error - logging.error( - f"gRPC call {method_name} failed: {status_code.name} ({status_code.value}) - {details}" - ) + logging.error(f"gRPC call {method_name} failed: {status_code.name} ({status_code.value}) - {details}") raise SDKError( f"gRPC call {method_name} failed: {status_code.name} ({status_code.value}) - {details}" ) from error diff --git a/tests/__init__.py b/tests/__init__.py index f60d0bd..73d90cd 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,2 +1 @@ # Test package initialization - diff --git a/tests/conftest.py b/tests/conftest.py index 57235c7..658d0e2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) + @pytest.fixture(scope="session") def test_config(): """Global test configuration.""" @@ -14,13 +15,15 @@ def test_config(): "test_node_address": "connect.akave.ai:5500", "test_bucket_name": "pytest-test-bucket", "test_timeout": 30, - "max_retries": 3 + "max_retries": 3, } + @pytest.fixture(scope="session") def project_root_path(): return project_root + @pytest.fixture def sample_data(): return { @@ -29,15 +32,17 @@ def sample_data(): "large_data": b"y" * 1024 * 1024, # 1MB "binary_data": bytes(range(256)), "text_data": "The quick brown fox jumps over the lazy dog".encode(), - "unicode_data": "Hello δΈ–η•Œ! 🌍".encode("utf-8") + "unicode_data": "Hello δΈ–η•Œ! 🌍".encode("utf-8"), } + @pytest.fixture def temp_file(tmp_path, sample_data): file_path = tmp_path / "test_file.bin" file_path.write_bytes(sample_data["medium_data"]) return file_path + @pytest.fixture(autouse=True) def setup_test_environment(): os.environ["PYTEST_RUNNING"] = "1" @@ -45,4 +50,5 @@ def setup_test_environment(): if "PYTEST_RUNNING" in os.environ: del os.environ["PYTEST_RUNNING"] + pytest_plugins = [] diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py index 08f17eb..2d11b4e 100644 --- a/tests/e2e/__init__.py +++ b/tests/e2e/__init__.py @@ -1,2 +1 @@ # End-to-end tests package - diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 9a9c14b..a265048 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -1,2 +1 @@ # Integration tests package - diff --git a/tests/mocks/__init__.py b/tests/mocks/__init__.py index 4a1cbaf..3d5688b 100644 --- a/tests/mocks/__init__.py +++ b/tests/mocks/__init__.py @@ -1,2 +1 @@ # Test mocks package - diff --git a/tests/mocks/mock_ipc.py b/tests/mocks/mock_ipc.py index 68ef684..3046def 100644 --- a/tests/mocks/mock_ipc.py +++ b/tests/mocks/mock_ipc.py @@ -1,102 +1,109 @@ from unittest.mock import Mock, MagicMock from typing import Any, Dict, List, Optional + class MockIPCClient: - + def __init__(self): self.auth = Mock() self.auth.address = "0x1234567890123456789012345678901234567890" self.auth.key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - + self.storage = MockStorageContract() self.access_manager = Mock() self.eth = Mock() - + # Mock wait methods self.wait_for_tx = Mock() - + def close(self): pass + class MockStorageContract: - + def __init__(self): self.contract_address = "0x1111111111111111111111111111111111111111" self._chain_id = 21207 self._buckets = {} self._files = {} self._filled_files = set() - + def get_chain_id(self) -> int: return self._chain_id - + def create_bucket(self, **kwargs) -> str: bucket_id = f"bucket_{len(self._buckets)}" - self._buckets[kwargs.get('bucket_name', 'test')] = { - 'id': bucket_id, - 'name': kwargs.get('bucket_name', 'test'), - 'owner': kwargs.get('from_address', self.contract_address) + self._buckets[kwargs.get("bucket_name", "test")] = { + "id": bucket_id, + "name": kwargs.get("bucket_name", "test"), + "owner": kwargs.get("from_address", self.contract_address), } return f"0x{bucket_id}" - + def get_bucket_by_name(self, call_opts: dict, bucket_name: str, **kwargs): if bucket_name in self._buckets: bucket = self._buckets[bucket_name] return ( - bytes.fromhex(bucket['id'].replace('bucket_', '').zfill(64)), - bucket['name'], + bytes.fromhex(bucket["id"].replace("bucket_", "").zfill(64)), + bucket["name"], 1234567890, # created_at - bucket['owner'], - [] # files + bucket["owner"], + [], # files ) raise Exception(f"Bucket '{bucket_name}' not found") - - def create_file(self, from_address: str, private_key: str, bucket_id: bytes, - file_name: str, **kwargs) -> str: + + def create_file(self, from_address: str, private_key: str, bucket_id: bytes, file_name: str, **kwargs) -> str: file_id = f"file_{len(self._files)}" - self._files[file_name] = { - 'id': file_id, - 'name': file_name, - 'bucket_id': bucket_id, - 'created_at': 1234567890 - } + self._files[file_name] = {"id": file_id, "name": file_name, "bucket_id": bucket_id, "created_at": 1234567890} return f"0x{file_id}" - - def add_file_chunk(self, from_address: str, private_key: str, cid: bytes, - bucket_id: bytes, name: str, encoded_chunk_size: int, - cids: list, chunk_blocks_sizes: list, chunk_index: int, - **kwargs) -> str: + + def add_file_chunk( + self, + from_address: str, + private_key: str, + cid: bytes, + bucket_id: bytes, + name: str, + encoded_chunk_size: int, + cids: list, + chunk_blocks_sizes: list, + chunk_index: int, + **kwargs, + ) -> str: return f"0xchunk_{chunk_index}" - + def is_file_filled(self, file_id: bytes) -> bool: return file_id in self._filled_files - + def mark_file_filled(self, file_id: bytes): self._filled_files.add(file_id) - + def get_file_by_name(self, call_opts: dict, bucket_id: bytes, file_name: str): if file_name in self._files: file_info = self._files[file_name] return ( - bytes.fromhex(file_info['id'].replace('file_', '').zfill(64)), + bytes.fromhex(file_info["id"].replace("file_", "").zfill(64)), b"mock_cid", bucket_id, file_name, 1024, # encoded_size - file_info['created_at'], - 1000 # actual_size + file_info["created_at"], + 1000, # actual_size ) raise Exception(f"File '{file_name}' not found") - - def commit_file(self, bucket_name: str, file_name: str, size: int, - root_cid: bytes, from_address: str, private_key: str) -> str: + + def commit_file( + self, bucket_name: str, file_name: str, size: int, root_cid: bytes, from_address: str, private_key: str + ) -> str: if file_name in self._files: - file_id = bytes.fromhex(self._files[file_name]['id'].replace('file_', '').zfill(64)) + file_id = bytes.fromhex(self._files[file_name]["id"].replace("file_", "").zfill(64)) self.mark_file_filled(file_id) return "0xcommit_tx" + class MockGRPCClient: - + def __init__(self): self.BucketView = Mock() self.BucketList = Mock() @@ -107,31 +114,32 @@ def __init__(self): self.FileDownloadCreate = Mock() self.FileDownloadChunkCreate = Mock() self.FileDownloadBlock = Mock() - + self._setup_default_responses() - + def _setup_default_responses(self): mock_bucket = Mock() mock_bucket.id = "test_bucket_id" mock_bucket.name = "test-bucket" mock_bucket.created_at.seconds = 1234567890 self.BucketView.return_value = mock_bucket - + mock_chunk_response = Mock() mock_chunk_response.blocks = [] self.FileUploadChunkCreate.return_value = mock_chunk_response + class MockConnectionPool: - + def __init__(self): self.connections = {} self.closed = False - + def create_ipc_client(self, node_address: str, use_pool: bool = True): mock_client = Mock() mock_closer = Mock() return (mock_client, mock_closer, None) - + def close(self): self.closed = True return None diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index 04a283c..4a5d263 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -1,2 +1 @@ # Unit tests package - diff --git a/tests/unit/test_sdk_ipc.py b/tests/unit/test_sdk_ipc.py index 1847c6e..6ff4cb6 100644 --- a/tests/unit/test_sdk_ipc.py +++ b/tests/unit/test_sdk_ipc.py @@ -8,7 +8,7 @@ class TestCreateBucket: """Test create bucket functionality.""" - + def setup_method(self): self.mock_client = Mock() self.mock_conn = Mock() @@ -19,15 +19,12 @@ def setup_method(self): self.mock_ipc.storage = Mock() self.mock_ipc.eth = Mock() self.mock_ipc.eth.eth = Mock() - + self.config = SDKConfig( - address="test:5500", - max_concurrency=10, - block_part_size=1048576, - use_connection_pool=True + address="test:5500", max_concurrency=10, block_part_size=1048576, use_connection_pool=True ) self.ipc = IPC(self.mock_client, self.mock_conn, self.mock_ipc, self.config) - + def test_create_bucket_success(self): """Test successful bucket creation.""" mock_receipt = Mock() @@ -35,16 +32,16 @@ def test_create_bucket_success(self): mock_receipt.blockNumber = 100 mock_receipt.transactionHash = Mock() mock_receipt.transactionHash.hex.return_value = "0xabc" - + mock_block = Mock() mock_block.timestamp = 1234567890 - + self.mock_ipc.storage.create_bucket.return_value = "0xtx" self.mock_ipc.eth.eth.wait_for_transaction_receipt.return_value = mock_receipt self.mock_ipc.eth.eth.get_block.return_value = mock_block - + result = self.ipc.create_bucket(None, "test-bucket") - + assert isinstance(result, IPCBucketCreateResult) assert result.name == "test-bucket" assert result.created_at == 1234567890 diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index abaeded..f616c2d 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1,2 +1 @@ # Test utilities package - diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index f7d7065..cb4e831 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -4,18 +4,22 @@ from typing import Any, Dict, List from unittest.mock import Mock, MagicMock + def calculate_sha256(data: bytes) -> str: return hashlib.sha256(data).hexdigest() + def generate_test_data(size: int) -> bytes: return secrets.token_bytes(size) + def create_mock_response(data: Dict[str, Any]) -> Mock: mock_response = Mock() for key, value in data.items(): setattr(mock_response, key, value) return mock_response + def wait_for_condition(condition_func, timeout: int = 10, interval: float = 0.1) -> bool: start_time = time.time() while time.time() - start_time < timeout: @@ -24,14 +28,16 @@ def wait_for_condition(condition_func, timeout: int = 10, interval: float = 0.1) time.sleep(interval) return False + def assert_eventually(condition_func, timeout: int = 5, message: str = "Condition not met"): if not wait_for_condition(condition_func, timeout): raise AssertionError(f"{message} within {timeout} seconds") + def create_mock_cid(cid_string: str = None) -> Mock: if cid_string is None: cid_string = "bafybeigweriqysuigpnsu3jmndgonrihee4dmx27rctlsd5mfn5arrnxyi" - + mock_cid = Mock() mock_cid.__str__ = Mock(return_value=cid_string) mock_cid.string = Mock(return_value=cid_string) @@ -39,6 +45,7 @@ def create_mock_cid(cid_string: str = None) -> Mock: mock_cid.bytes = Mock(return_value=b"mock_cid_bytes") return mock_cid + def create_mock_dag_node(cid: str = None, data: bytes = None) -> Mock: mock_node = Mock() mock_node.cid = create_mock_cid(cid) if cid else create_mock_cid() @@ -47,6 +54,7 @@ def create_mock_dag_node(cid: str = None, data: bytes = None) -> Mock: mock_node.encoded_size = len(mock_node.data) + 100 # Add some overhead return mock_node + def create_mock_file_block(cid: str = None, data: bytes = None, node_address: str = None) -> Mock: mock_block = Mock() mock_block.cid = cid or "bafybeia3uzxlpmpopl6tsafgwfgygzibbgqhpnvvizg2acnjdtq" @@ -56,32 +64,34 @@ def create_mock_file_block(cid: str = None, data: bytes = None, node_address: st mock_block.permit = "mock_permit" return mock_block + def assert_mock_called_with_partial(mock_obj: Mock, expected_args: Dict[str, Any]): assert mock_obj.called, "Mock was not called" - + last_call = mock_obj.call_args if last_call is None: raise AssertionError("Mock was not called") - + args, kwargs = last_call - + for key, expected_value in expected_args.items(): if key in kwargs: assert kwargs[key] == expected_value, f"Expected {key}={expected_value}, got {kwargs[key]}" else: raise AssertionError(f"Expected argument '{key}' not found in call") + class MockContextManager: - + def __init__(self, return_value=None): self.return_value = return_value self.entered = False self.exited = False - + def __enter__(self): self.entered = True return self.return_value - + def __exit__(self, exc_type, exc_val, exc_tb): self.exited = True return False From 1f4cdbf0476ecf8c38c4f966d64bbd2fe284c44f Mon Sep 17 00:00:00 2001 From: Amit Pandey Date: Mon, 23 Feb 2026 02:43:32 +0530 Subject: [PATCH 3/3] removed private files from security tests Signed-off-by: Amit Pandey --- .github/workflows/code-quality.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index ca120c4..392685e 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -70,8 +70,8 @@ jobs: - name: Security check with Bandit run: | - bandit -r akavesdk/ private/ sdk/ -ll -f json -o bandit-report.json - bandit -r akavesdk/ private/ sdk/ -ll + bandit -r akavesdk/ sdk/ -ll -f json -o bandit-report.json + bandit -r akavesdk/ sdk/ -ll - name: Upload Bandit report uses: actions/upload-artifact@v4