From 2b2cffdf8aab72fb4332521ee4ea9558baa42ca8 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 14:28:15 +0100 Subject: [PATCH 01/18] Update --- CHANGELOG.md | 12 ++++ flake.nix | 4 +- pocket_ic/pocket_ic.py | 103 ++++++++++++++++++++++------------ pocket_ic/pocket_ic_server.py | 60 +++++++++----------- pyproject.toml | 2 +- tests/pocket_ic_test.py | 15 +++-- 6 files changed, 119 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 287e641..dfdcef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 3.0.0 - 2024-11-19 + +### Added +- Support for PocketIC server version 7.0.0 +- Load a state directory for any subnet kind with `SubnetConfig.add_subnet_with_state` +- Verified Application subnet type + +### Removed +- `with_nns_state`. Use `SubnetConfig.add_subnet_with_state` instead + + + ## 2.1.0 - 2024-02-08 ### Added diff --git a/flake.nix b/flake.nix index 2e28ec0..f1bf0c2 100644 --- a/flake.nix +++ b/flake.nix @@ -5,11 +5,11 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; pocket-ic-darwin-gz = { - url = "https://github.com/dfinity/pocketic/releases/download/3.0.1/pocket-ic-x86_64-darwin.gz"; + url = "https://github.com/dfinity/pocketic/releases/download/7.0.0/pocket-ic-x86_64-darwin.gz"; flake = false; }; pocket-ic-linux-gz = { - url = "https://github.com/dfinity/pocketic/releases/download/3.0.1/pocket-ic-x86_64-linux.gz"; + url = "https://github.com/dfinity/pocketic/releases/download/7.0.0/pocket-ic-x86_64-linux.gz"; flake = false; }; }; diff --git a/pocket_ic/pocket_ic.py b/pocket_ic/pocket_ic.py index 69497e4..4007423 100644 --- a/pocket_ic/pocket_ic.py +++ b/pocket_ic/pocket_ic.py @@ -7,7 +7,7 @@ import ic from enum import Enum from ic.candid import Types -from typing import List, Optional, Any +from typing import Optional, Any from pocket_ic.pocket_ic_server import PocketICServer @@ -21,7 +21,7 @@ class SubnetKind(Enum): NNS = "NNS" SNS = "SNS" SYSTEM = "System" - + VERIFIED_APPLICATION = "VerifiedApplication" class SubnetConfig: """The configuration of subnets for a PocketIC instance.""" @@ -35,24 +35,25 @@ def __init__( nns=False, sns=False, system=0, + verified_application=0, ) -> None: - self.application = application - self.bitcoin = bitcoin - self.fiduciary = fiduciary - self.ii = ii - self.nns = nns - self.sns = sns - self.system = system + self.application = [{ "state_config": "New", "instruction_config": "Production" }] * application + self.bitcoin = { "state_config": "New", "instruction_config": "Production" } if bitcoin else None + self.fiduciary = { "state_config": "New", "instruction_config": "Production" } if fiduciary else None + self.ii = { "state_config": "New", "instruction_config": "Production" } if ii else None + self.nns = { "state_config": "New", "instruction_config": "Production" } if nns else None + self.sns = { "state_config": "New", "instruction_config": "Production" } if sns else None + self.system = [{ "state_config": "New", "instruction_config": "Production" }] * system + self.verified_application = [{ "state_config": "New", "instruction_config": "Production" }] * verified_application def __repr__(self) -> str: - return f"SubnetConfigSet(application={self.application}, bitcoin={self.bitcoin}, fiduciary={self.fiduciary}, ii={self.ii}, nns={self.nns}, sns={self.sns}, system={self.system})" + return f"SubnetConfigSet(application={self.application}, bitcoin={self.bitcoin}, fiduciary={self.fiduciary}, ii={self.ii}, nns={self.nns}, sns={self.sns}, system={self.system}, verified_application={self.verified_application})" def validate(self) -> None: """Validates the subnet configuration. Raises: - ValueError: if no subnet is configured or if the number of application or system - subnets is negative + ValueError: if no subnet is configured """ if not ( self.bitcoin @@ -62,34 +63,65 @@ def validate(self) -> None: or self.sns or self.system or self.application + or self.verified_application ): raise ValueError("At least one subnet must be configured.") - if self.application < 0 or self.system < 0: - raise ValueError( - "The number of application and system subnets must be non-negative." - ) - - def with_nns_state(self, state_dir_path: str, nns_subnet_id: ic.Principal): - """Provide an NNS state directory and a subnet id. """ - self.nns = (state_dir_path, nns_subnet_id) + def add_subnet_with_state(self, subnet_type: SubnetKind, state_dir_path: str, nns_subnet_id: ic.Principal): + """Add a subnet with state loaded form the given state directory. + Note that the provided path must be accessible for the PocketIC server process. + + `state_dir` should point to a directory which is expected to have the following structure: + + state_dir/ + |-- backups + |-- checkpoints + |-- diverged_checkpoints + |-- diverged_state_markers + |-- fs_tmp + |-- page_deltas + |-- states_metadata.pbuf + |-- tip + `-- tmp + + `subnet_id` should be the subnet ID of the subnet in the state to be loaded""" + + raw_subnet_id = base64.b64encode(nns_subnet_id.bytes).decode() + + match subnet_type: + case SubnetKind.APPLICATION: + self.application.append({"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" }) + case SubnetKind.BITCOIN: + self.bitcoin = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + case SubnetKind.FIDUCIARY: + self.fiduciary = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + case SubnetKind.II: + self.ii = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + case SubnetKind.NNS: + self.nns = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + case SubnetKind.SNS: + self.sns = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + case SubnetKind.SYSTEM: + self.system.append({"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" }) + case SubnetKind.VERIFIED_APPLICATION: + self.verified_application.append({"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" }) def _json(self) -> dict: - if isinstance(self.nns, tuple): - raw_subnet_id = base64.b64encode(self.nns[1].bytes).decode() - nns = {"FromPath": (self.nns[0], {"subnet_id": raw_subnet_id})} - elif self.nns: - nns = "New" - else: - nns = None return { - "application": self.application * ["New"], - "bitcoin": "New" if self.bitcoin else None, - "fiduciary": "New" if self.fiduciary else None, - "ii": "New" if self.ii else None, - "nns": nns, - "sns": "New" if self.sns else None, - "system": self.system * ["New"], + "subnet_config_set": { + "application": self.application, + "bitcoin": self.bitcoin, + "fiduciary": self.fiduciary, + "ii": self.ii, + "nns": self.nns, + "sns": self.sns, + "system": self.system, + "verified_application": self.verified_application, + }, + "state_dir": None, + "nonmainnet_features": False, + "log_level": None, + "bitcoind_addr": None } @@ -501,7 +533,8 @@ def _canister_call( def _generate_topology(self, topology): t = dict() - for subnet_id, config in topology.items(): + subnets = topology['subnet_configs'] + for subnet_id, config in subnets.items(): subnet_id = ic.Principal.from_str(subnet_id) subnet_kind = SubnetKind(config["subnet_kind"]) t.update({subnet_id: subnet_kind}) diff --git a/pocket_ic/pocket_ic_server.py b/pocket_ic/pocket_ic_server.py index b4bfe2e..132ef37 100644 --- a/pocket_ic/pocket_ic_server.py +++ b/pocket_ic/pocket_ic_server.py @@ -9,9 +9,6 @@ from tempfile import gettempdir -HEADERS = {"processing-timeout-ms": "300000"} - - class PocketICServer: """ An object of this class represents a running PocketIC server. During instantiation, @@ -46,12 +43,15 @@ def __init__(self) -> None: """ ) - # Attempt to start the PocketIC server if it's not already running. mute = ( "1> /dev/null 2> /dev/null" if "POCKET_IC_MUTE_SERVER" in os.environ else "" ) - os.system(f"{bin_path} --pid {pid} {mute} &") - self.url = self._get_url(pid) + # Attempt to start the PocketIC server if it's not already running. + tmp_dir = gettempdir() + port_file_path = f"{tmp_dir}/pocket_ic_{pid}.port" + + os.system(f"{bin_path} --port-file {port_file_path} {mute} &") + self.url = self._get_url(port_file_path) self.request_client = requests.session() def new_instance(self, subnet_config: dict) -> Tuple[int, dict]: @@ -61,7 +61,7 @@ def new_instance(self, subnet_config: dict) -> Tuple[int, dict]: str: the new instance ID """ url = f"{self.url}/instances" - response = self.request_client.post(url, headers=HEADERS, json=subnet_config) + response = self.request_client.post(url, json=subnet_config) res = self._check_response(response)["Created"] return res["instance_id"], res["topology"] @@ -72,7 +72,7 @@ def list_instances(self) -> List[str]: List[str]: a list of instance names """ url = f"{self.url}/instances" - response = self.request_client.get(url, headers=HEADERS) + response = self.request_client.get(url) response = self._check_response(response) return response @@ -83,18 +83,18 @@ def delete_instance(self, instance_id: int): instance_id (int): the ID of the instance to delete """ url = f"{self.url}/instances/{instance_id}" - self.request_client.delete(url, headers=HEADERS) + self.request_client.delete(url) def instance_get(self, endpoint: str, instance_id: int): """HTTP get requests for instance endpoints""" url = f"{self.url}/instances/{instance_id}/{endpoint}" - response = self.request_client.get(url, headers=HEADERS) + response = self.request_client.get(url) return self._check_response(response) def instance_post(self, endpoint: str, instance_id: int, body: Optional[dict]): """HTTP post requests for instance endpoints""" url = f"{self.url}/instances/{instance_id}/{endpoint}" - response = self.request_client.post(url, json=body, headers=HEADERS) + response = self.request_client.post(url, json=body) return self._check_response(response) def set_blob_store_entry(self, blob: bytes, compression: Optional[str]) -> str: @@ -109,9 +109,9 @@ def set_blob_store_entry(self, blob: bytes, compression: Optional[str]) -> str: """ url = f"{self.url}/blobstore" if compression is None: - response = self.request_client.post(url, data=blob, headers=HEADERS) + response = self.request_client.post(url, data=blob) elif compression == "gzip": - headers = HEADERS | {"Content-Encoding": "gzip"} + headers = {"Content-Encoding": "gzip"} response = self.request_client.post(url, data=blob, headers=headers) else: raise ValueError('only "gzip" compression is supported') @@ -119,34 +119,28 @@ def set_blob_store_entry(self, blob: bytes, compression: Optional[str]) -> str: self._check_status_code(response) return response.text - def _get_url(self, pid: int) -> str: - tmp_dir = gettempdir() - ready_file_path = f"{tmp_dir}/pocket_ic_{pid}.ready" - port_file_path = f"{tmp_dir}/pocket_ic_{pid}.port" - + def _get_url(self, port_file_path: int) -> str: stop_at = time.time() + 10 # Wait for the ready file for 10 seconds - while not os.path.exists(ready_file_path): - if time.time() < stop_at: - time.sleep(0.1) # 100ms - else: - raise TimeoutError("PocketIC failed to start") + while True: + if os.path.isfile(port_file_path): + with open(port_file_path, "r", encoding="utf-8") as port_file: + port = port_file.readline().strip() + if port: + return f"http://127.0.0.1:{port}" - if os.path.isfile(ready_file_path): - with open(port_file_path, "r", encoding="utf-8") as port_file: - port = port_file.readline().strip() - else: - raise ValueError(f"{ready_file_path} is not a file!") - - return f"http://127.0.0.1:{port}" + time.sleep(0.02) # wait for 20ms + + if time.time() > stop_at: + raise TimeoutError("PocketIC failed to start") - def _check_response(self, response): + def _check_response(self, response: requests.Response): self._check_status_code(response) res_json = response.json() return res_json - def _check_status_code(self, response): + def _check_status_code(self, response: requests.Response): if response.status_code not in [200, 201, 202]: raise ConnectionError( - f'PocketIC server returned status code {response.status_code}: "{response.reason}"' + f'PocketIC server returned status code {response.status_code}: "{response.text}"' ) diff --git a/pyproject.toml b/pyproject.toml index b75017f..c3c17d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pocket_ic" -version = "2.1.0" +version = "3.0.0" description = "PocketIC: A Canister Smart Contract Testing Platform" authors = [ "The Internet Computer Project Developers ", diff --git a/tests/pocket_ic_test.py b/tests/pocket_ic_test.py index 311cb7e..93147fa 100644 --- a/tests/pocket_ic_test.py +++ b/tests/pocket_ic_test.py @@ -34,9 +34,8 @@ def test_create_canister_with_id(self): # Creating a canister with an ID that is not hosted by any subnet fails. canister_id = ic.Principal.anonymous() - with self.assertRaises(ValueError) as ex: + with self.assertRaises(Exception) as ex: pic.create_canister(canister_id=canister_id) - self.assertIn("CanisterNotHostedBySubnet", ex.exception.args[0]) def test_large_config_and_deduplication(self): pic = PocketIC( @@ -147,12 +146,16 @@ def test_cycles_balance(self): pic.add_cycles(canister_id, 6_666) self.assertEqual(pic.get_cycles_balance(canister_id), initial_balance + 6_666) - def test_nns_state(self): - principal = "6gvjz-uotju-2ngtj-u2ngt-ju2ng-tju2n-gtju2-ngtjv" + def test_load_state(self): + principal = ic.Principal.from_str("6gvjz-uotju-2ngtj-u2ngt-ju2ng-tju2n-gtju2-ngtjv") tmp_dir = tempfile.mkdtemp() - pic = PocketIC(SubnetConfig(nns=(tmp_dir, ic.Principal.from_str(principal)))) + + config = SubnetConfig() + config.add_subnet_with_state(SubnetKind.NNS, tmp_dir, principal) + pic = PocketIC(subnet_config=config) + (k,v) = list(pic.topology.items())[0] - self.assertEqual(str(k), principal) + self.assertEqual(str(k), str(principal)) self.assertEqual(v, SubnetKind.NNS) os.rmdir(tmp_dir) From eb0b0f2a0f39f677808607b06549c7ae61eaef88 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 13:31:47 +0000 Subject: [PATCH 02/18] nix develop --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 53c7b9c..4ef57cc 100644 --- a/flake.lock +++ b/flake.lock @@ -37,25 +37,25 @@ "pocket-ic-darwin-gz": { "flake": false, "locked": { - "narHash": "sha256-paPv910O9u7JriOWZDSZ1UEy5qqVDjA6ESz5PfvFpmg=", + "narHash": "sha256-tAxziSPN43Utb7Bq6aJheN/uZafOIdWFaq395kw97HE=", "type": "file", - "url": "https://github.com/dfinity/pocketic/releases/download/3.0.1/pocket-ic-x86_64-darwin.gz" + "url": "https://github.com/dfinity/pocketic/releases/download/7.0.0/pocket-ic-x86_64-darwin.gz" }, "original": { "type": "file", - "url": "https://github.com/dfinity/pocketic/releases/download/3.0.1/pocket-ic-x86_64-darwin.gz" + "url": "https://github.com/dfinity/pocketic/releases/download/7.0.0/pocket-ic-x86_64-darwin.gz" } }, "pocket-ic-linux-gz": { "flake": false, "locked": { - "narHash": "sha256-eaZJN4gebKFTdzB88hN0mLAbdslH1x1xbYqIi/VeRY8=", + "narHash": "sha256-oks9H8GARq8opVcy+0RH7lffG34lZDuY90iFS0bGhvI=", "type": "file", - "url": "https://github.com/dfinity/pocketic/releases/download/3.0.1/pocket-ic-x86_64-linux.gz" + "url": "https://github.com/dfinity/pocketic/releases/download/7.0.0/pocket-ic-x86_64-linux.gz" }, "original": { "type": "file", - "url": "https://github.com/dfinity/pocketic/releases/download/3.0.1/pocket-ic-x86_64-linux.gz" + "url": "https://github.com/dfinity/pocketic/releases/download/7.0.0/pocket-ic-x86_64-linux.gz" } }, "root": { From c52225f9150affc14244b8d8fb2886824422b3fd Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 14:36:44 +0100 Subject: [PATCH 03/18] Format --- pocket_ic/pocket_ic.py | 116 +++++++++++++++++++++++++++------- pocket_ic/pocket_ic_server.py | 4 +- tests/pocket_ic_test.py | 7 +- 3 files changed, 100 insertions(+), 27 deletions(-) diff --git a/pocket_ic/pocket_ic.py b/pocket_ic/pocket_ic.py index 4007423..5e9601f 100644 --- a/pocket_ic/pocket_ic.py +++ b/pocket_ic/pocket_ic.py @@ -3,6 +3,7 @@ It also contains 'SubnetConfig' and 'SubnetKind', which are used to configure the subnets of a PocketIC instance. """ + import base64 import ic from enum import Enum @@ -21,7 +22,8 @@ class SubnetKind(Enum): NNS = "NNS" SNS = "SNS" SYSTEM = "System" - VERIFIED_APPLICATION = "VerifiedApplication" + VERIFIED_APPLICATION = "VerifiedApplication" + class SubnetConfig: """The configuration of subnets for a PocketIC instance.""" @@ -37,14 +39,34 @@ def __init__( system=0, verified_application=0, ) -> None: - self.application = [{ "state_config": "New", "instruction_config": "Production" }] * application - self.bitcoin = { "state_config": "New", "instruction_config": "Production" } if bitcoin else None - self.fiduciary = { "state_config": "New", "instruction_config": "Production" } if fiduciary else None - self.ii = { "state_config": "New", "instruction_config": "Production" } if ii else None - self.nns = { "state_config": "New", "instruction_config": "Production" } if nns else None - self.sns = { "state_config": "New", "instruction_config": "Production" } if sns else None - self.system = [{ "state_config": "New", "instruction_config": "Production" }] * system - self.verified_application = [{ "state_config": "New", "instruction_config": "Production" }] * verified_application + self.application = [ + {"state_config": "New", "instruction_config": "Production"} + ] * application + self.bitcoin = ( + {"state_config": "New", "instruction_config": "Production"} + if bitcoin + else None + ) + self.fiduciary = ( + {"state_config": "New", "instruction_config": "Production"} + if fiduciary + else None + ) + self.ii = ( + {"state_config": "New", "instruction_config": "Production"} if ii else None + ) + self.nns = ( + {"state_config": "New", "instruction_config": "Production"} if nns else None + ) + self.sns = ( + {"state_config": "New", "instruction_config": "Production"} if sns else None + ) + self.system = [ + {"state_config": "New", "instruction_config": "Production"} + ] * system + self.verified_application = [ + {"state_config": "New", "instruction_config": "Production"} + ] * verified_application def __repr__(self) -> str: return f"SubnetConfigSet(application={self.application}, bitcoin={self.bitcoin}, fiduciary={self.fiduciary}, ii={self.ii}, nns={self.nns}, sns={self.sns}, system={self.system}, verified_application={self.verified_application})" @@ -67,12 +89,14 @@ def validate(self) -> None: ): raise ValueError("At least one subnet must be configured.") - def add_subnet_with_state(self, subnet_type: SubnetKind, state_dir_path: str, nns_subnet_id: ic.Principal): + def add_subnet_with_state( + self, subnet_type: SubnetKind, state_dir_path: str, nns_subnet_id: ic.Principal + ): """Add a subnet with state loaded form the given state directory. Note that the provided path must be accessible for the PocketIC server process. `state_dir` should point to a directory which is expected to have the following structure: - + state_dir/ |-- backups |-- checkpoints @@ -83,28 +107,74 @@ def add_subnet_with_state(self, subnet_type: SubnetKind, state_dir_path: str, nn |-- states_metadata.pbuf |-- tip `-- tmp - + `subnet_id` should be the subnet ID of the subnet in the state to be loaded""" raw_subnet_id = base64.b64encode(nns_subnet_id.bytes).decode() match subnet_type: case SubnetKind.APPLICATION: - self.application.append({"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" }) + self.application.append( + { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } + ) case SubnetKind.BITCOIN: - self.bitcoin = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + self.bitcoin = { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } case SubnetKind.FIDUCIARY: - self.fiduciary = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + self.fiduciary = { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } case SubnetKind.II: - self.ii = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + self.ii = { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } case SubnetKind.NNS: - self.nns = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + self.nns = { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } case SubnetKind.SNS: - self.sns = {"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" } + self.sns = { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } case SubnetKind.SYSTEM: - self.system.append({"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" }) + self.system.append( + { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } + ) case SubnetKind.VERIFIED_APPLICATION: - self.verified_application.append({"state_config": {"FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}]}, "instruction_config": "Production" }) + self.verified_application.append( + { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } + ) def _json(self) -> dict: return { @@ -121,7 +191,7 @@ def _json(self) -> dict: "state_dir": None, "nonmainnet_features": False, "log_level": None, - "bitcoind_addr": None + "bitcoind_addr": None, } @@ -480,7 +550,7 @@ def create_and_install_canister_with_candid( arg = [{"type": canister_arguments[0], "value": init_args}] else: raise ValueError("The candid file appears to be malformed") - + self.add_cycles(canister_id, 2_000_000_000_000) self.install_code(canister_id, wasm_module, arg) return canister @@ -533,7 +603,7 @@ def _canister_call( def _generate_topology(self, topology): t = dict() - subnets = topology['subnet_configs'] + subnets = topology["subnet_configs"] for subnet_id, config in subnets.items(): subnet_id = ic.Principal.from_str(subnet_id) subnet_kind = SubnetKind(config["subnet_kind"]) diff --git a/pocket_ic/pocket_ic_server.py b/pocket_ic/pocket_ic_server.py index 132ef37..e56c7a4 100644 --- a/pocket_ic/pocket_ic_server.py +++ b/pocket_ic/pocket_ic_server.py @@ -129,8 +129,8 @@ def _get_url(self, port_file_path: int) -> str: if port: return f"http://127.0.0.1:{port}" - time.sleep(0.02) # wait for 20ms - + time.sleep(0.02) # wait for 20ms + if time.time() > stop_at: raise TimeoutError("PocketIC failed to start") diff --git a/tests/pocket_ic_test.py b/tests/pocket_ic_test.py index 93147fa..c128724 100644 --- a/tests/pocket_ic_test.py +++ b/tests/pocket_ic_test.py @@ -147,17 +147,20 @@ def test_cycles_balance(self): self.assertEqual(pic.get_cycles_balance(canister_id), initial_balance + 6_666) def test_load_state(self): - principal = ic.Principal.from_str("6gvjz-uotju-2ngtj-u2ngt-ju2ng-tju2n-gtju2-ngtjv") + principal = ic.Principal.from_str( + "6gvjz-uotju-2ngtj-u2ngt-ju2ng-tju2n-gtju2-ngtjv" + ) tmp_dir = tempfile.mkdtemp() config = SubnetConfig() config.add_subnet_with_state(SubnetKind.NNS, tmp_dir, principal) pic = PocketIC(subnet_config=config) - (k,v) = list(pic.topology.items())[0] + (k, v) = list(pic.topology.items())[0] self.assertEqual(str(k), str(principal)) self.assertEqual(v, SubnetKind.NNS) os.rmdir(tmp_dir) + if __name__ == "__main__": unittest.main() From 8e8224e17466a27afd1e6cfaa0fe16d435488724 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 14:46:54 +0100 Subject: [PATCH 04/18] DRY code --- pocket_ic/pocket_ic.py | 110 ++++++++++------------------------------- 1 file changed, 26 insertions(+), 84 deletions(-) diff --git a/pocket_ic/pocket_ic.py b/pocket_ic/pocket_ic.py index 5e9601f..a00d1b4 100644 --- a/pocket_ic/pocket_ic.py +++ b/pocket_ic/pocket_ic.py @@ -39,34 +39,15 @@ def __init__( system=0, verified_application=0, ) -> None: - self.application = [ - {"state_config": "New", "instruction_config": "Production"} - ] * application - self.bitcoin = ( - {"state_config": "New", "instruction_config": "Production"} - if bitcoin - else None - ) - self.fiduciary = ( - {"state_config": "New", "instruction_config": "Production"} - if fiduciary - else None - ) - self.ii = ( - {"state_config": "New", "instruction_config": "Production"} if ii else None - ) - self.nns = ( - {"state_config": "New", "instruction_config": "Production"} if nns else None - ) - self.sns = ( - {"state_config": "New", "instruction_config": "Production"} if sns else None - ) - self.system = [ - {"state_config": "New", "instruction_config": "Production"} - ] * system - self.verified_application = [ - {"state_config": "New", "instruction_config": "Production"} - ] * verified_application + new = {"state_config": "New", "instruction_config": "Production"} + self.application = [new] * application + self.bitcoin = new if bitcoin else None + self.fiduciary = new if fiduciary else None + self.ii = new if ii else None + self.nns = new if nns else None + self.sns = new if sns else None + self.system = [new] * system + self.verified_application = [new] * verified_application def __repr__(self) -> str: return f"SubnetConfigSet(application={self.application}, bitcoin={self.bitcoin}, fiduciary={self.fiduciary}, ii={self.ii}, nns={self.nns}, sns={self.sns}, system={self.system}, verified_application={self.verified_application})" @@ -90,7 +71,7 @@ def validate(self) -> None: raise ValueError("At least one subnet must be configured.") def add_subnet_with_state( - self, subnet_type: SubnetKind, state_dir_path: str, nns_subnet_id: ic.Principal + self, subnet_type: SubnetKind, state_dir_path: str, subnet_id: ic.Principal ): """Add a subnet with state loaded form the given state directory. Note that the provided path must be accessible for the PocketIC server process. @@ -110,71 +91,32 @@ def add_subnet_with_state( `subnet_id` should be the subnet ID of the subnet in the state to be loaded""" - raw_subnet_id = base64.b64encode(nns_subnet_id.bytes).decode() + raw_subnet_id = base64.b64encode(subnet_id.bytes).decode() + + new_from_path = { + "state_config": { + "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] + }, + "instruction_config": "Production", + } match subnet_type: case SubnetKind.APPLICATION: - self.application.append( - { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } - ) + self.application.append(new_from_path) case SubnetKind.BITCOIN: - self.bitcoin = { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } + self.bitcoin = new_from_path case SubnetKind.FIDUCIARY: - self.fiduciary = { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } + self.fiduciary = new_from_path case SubnetKind.II: - self.ii = { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } + self.ii = new_from_path case SubnetKind.NNS: - self.nns = { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } + self.nns = new_from_path case SubnetKind.SNS: - self.sns = { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } + self.sns = new_from_path case SubnetKind.SYSTEM: - self.system.append( - { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } - ) + self.system.append(new_from_path) case SubnetKind.VERIFIED_APPLICATION: - self.verified_application.append( - { - "state_config": { - "FromPath": [state_dir_path, {"subnet_id": raw_subnet_id}] - }, - "instruction_config": "Production", - } - ) + self.verified_application.append(new_from_path) def _json(self) -> dict: return { From 145a7fef0ad3fc01a39834e20c6735f84c069806 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 16:02:35 +0100 Subject: [PATCH 05/18] Fix linux pipeline --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9c9f3a..3484649 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,10 @@ jobs: - macos-latest runs-on: ${{ matrix.os }} steps: + - if: matrix.os == 'ubuntu-latest' + name: Install certificates + run: sudo apt-get install ca-certificates + - uses: actions/checkout@v3 - uses: cachix/install-nix-action@v22 From dad8d74a441d0199e296d84077e50cbb3007267e Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 16:11:04 +0100 Subject: [PATCH 06/18] Update --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3484649..f93a2e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: steps: - if: matrix.os == 'ubuntu-latest' name: Install certificates - run: sudo apt-get install ca-certificates + run: sudo apt install ca-certificates - uses: actions/checkout@v3 From d8d0c11e361d4db5c1b34bc22ad1781bfefc1829 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 16:42:28 +0100 Subject: [PATCH 07/18] add cacert to nix --- flake.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index f1bf0c2..564213d 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "PocketIC Python Libary"; + description = "PocketIC Python Library"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -98,6 +98,7 @@ pkgs.poetry pocketic-py pytest + pkgs.cacert # Here we added ca-certificates ]; }; From 6de852798f8d0cd3e21890441ed8cde6fbbdf8bb Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 16:48:03 +0100 Subject: [PATCH 08/18] add it in another place --- flake.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 564213d..cee9da7 100644 --- a/flake.nix +++ b/flake.nix @@ -86,7 +86,7 @@ ''; checks.default = pkgs.runCommand "pocketic-py-tests" { - nativeBuildInputs = [ pytest pocketic-py]; + nativeBuildInputs = [ pytest pocketic-py cacert]; POCKET_IC_BIN = "${pocket-ic}/bin/pocket-ic"; inherit projectDir; } '' @@ -98,7 +98,6 @@ pkgs.poetry pocketic-py pytest - pkgs.cacert # Here we added ca-certificates ]; }; From 56f2edfc1969d01e5458e9c57677c02eab400770 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 16:49:26 +0100 Subject: [PATCH 09/18] pkgs --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index cee9da7..7761d0f 100644 --- a/flake.nix +++ b/flake.nix @@ -86,7 +86,7 @@ ''; checks.default = pkgs.runCommand "pocketic-py-tests" { - nativeBuildInputs = [ pytest pocketic-py cacert]; + nativeBuildInputs = [ pytest pocketic-py pkgs.cacert]; POCKET_IC_BIN = "${pocket-ic}/bin/pocket-ic"; inherit projectDir; } '' From b07095db9a3cff592adadfc9f3cf3ccefa5eb0d3 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 16:52:09 +0100 Subject: [PATCH 10/18] Remove CI step --- .github/workflows/build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f93a2e2..a9c9f3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,10 +14,6 @@ jobs: - macos-latest runs-on: ${{ matrix.os }} steps: - - if: matrix.os == 'ubuntu-latest' - name: Install certificates - run: sudo apt install ca-certificates - - uses: actions/checkout@v3 - uses: cachix/install-nix-action@v22 From c07dac9806a97192aa65d9948f5538e272c81223 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 15:54:22 +0000 Subject: [PATCH 11/18] Run nix flake update --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 4ef57cc..edc1955 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1692799911, - "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1693158576, - "narHash": "sha256-aRTTXkYvhXosGx535iAFUaoFboUrZSYb1Ooih/auGp0=", + "lastModified": 1731676054, + "narHash": "sha256-OZiZ3m8SCMfh3B6bfGC/Bm4x3qc1m2SVEAlkV6iY7Yg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a999c1cc0c9eb2095729d5aa03e0d8f7ed256780", + "rev": "5e4fbfb6b3de1aa2872b76d49fafc942626e2add", "type": "github" }, "original": { From ace290bfeb49231bff068645393809fb179953ef Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 17:01:13 +0100 Subject: [PATCH 12/18] Revert "Run nix flake update" This reverts commit c07dac9806a97192aa65d9948f5538e272c81223. --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index edc1955..4ef57cc 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", "owner": "numtide", "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1731676054, - "narHash": "sha256-OZiZ3m8SCMfh3B6bfGC/Bm4x3qc1m2SVEAlkV6iY7Yg=", + "lastModified": 1693158576, + "narHash": "sha256-aRTTXkYvhXosGx535iAFUaoFboUrZSYb1Ooih/auGp0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5e4fbfb6b3de1aa2872b76d49fafc942626e2add", + "rev": "a999c1cc0c9eb2095729d5aa03e0d8f7ed256780", "type": "github" }, "original": { From ad078be7862be59e65f4c41f6536ef374f732aff Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 17:05:42 +0100 Subject: [PATCH 13/18] Remove unused var --- tests/pocket_ic_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pocket_ic_test.py b/tests/pocket_ic_test.py index c128724..03da69c 100644 --- a/tests/pocket_ic_test.py +++ b/tests/pocket_ic_test.py @@ -34,7 +34,7 @@ def test_create_canister_with_id(self): # Creating a canister with an ID that is not hosted by any subnet fails. canister_id = ic.Principal.anonymous() - with self.assertRaises(Exception) as ex: + with self.assertRaises(Exception): pic.create_canister(canister_id=canister_id) def test_large_config_and_deduplication(self): From 4b45304255e0336ecd64c336bc516e0f0a13d989 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 19:11:26 +0100 Subject: [PATCH 14/18] Check for newline in port file --- pocket_ic/pocket_ic_server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pocket_ic/pocket_ic_server.py b/pocket_ic/pocket_ic_server.py index e56c7a4..2da42a6 100644 --- a/pocket_ic/pocket_ic_server.py +++ b/pocket_ic/pocket_ic_server.py @@ -120,14 +120,14 @@ def set_blob_store_entry(self, blob: bytes, compression: Optional[str]) -> str: return response.text def _get_url(self, port_file_path: int) -> str: - stop_at = time.time() + 10 # Wait for the ready file for 10 seconds + stop_at = time.time() + 10 # Wait for the port file for 10 seconds while True: if os.path.isfile(port_file_path): with open(port_file_path, "r", encoding="utf-8") as port_file: - port = port_file.readline().strip() - if port: - return f"http://127.0.0.1:{port}" + port = port_file.readline() + if "\n" in port: + return f"http://127.0.0.1:{port.strip()}" time.sleep(0.02) # wait for 20ms From 20498cd8b3e0c9fc4c97db0b5c52aa3a217a4e23 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 19:20:45 +0100 Subject: [PATCH 15/18] Dont timeout --- pocket_ic/pocket_ic_server.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pocket_ic/pocket_ic_server.py b/pocket_ic/pocket_ic_server.py index 2da42a6..336e2f2 100644 --- a/pocket_ic/pocket_ic_server.py +++ b/pocket_ic/pocket_ic_server.py @@ -120,20 +120,14 @@ def set_blob_store_entry(self, blob: bytes, compression: Optional[str]) -> str: return response.text def _get_url(self, port_file_path: int) -> str: - stop_at = time.time() + 10 # Wait for the port file for 10 seconds - while True: if os.path.isfile(port_file_path): with open(port_file_path, "r", encoding="utf-8") as port_file: port = port_file.readline() if "\n" in port: return f"http://127.0.0.1:{port.strip()}" - time.sleep(0.02) # wait for 20ms - if time.time() > stop_at: - raise TimeoutError("PocketIC failed to start") - def _check_response(self, response: requests.Response): self._check_status_code(response) res_json = response.json() From 7b3955bce08372a8f45784e3b514ea28cc209fa0 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 19:44:57 +0100 Subject: [PATCH 16/18] Fetch topology every time as it can change --- CHANGELOG.md | 1 + pocket_ic/pocket_ic.py | 25 +++++++++++++------------ pocket_ic/pocket_ic_server.py | 8 ++++---- tests/pocket_ic_test.py | 25 ++++++++++++++----------- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfdcef8..b065ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `with_nns_state`. Use `SubnetConfig.add_subnet_with_state` instead +- `PocketIC.topology`. Use `PocketIC.topology()` instead diff --git a/pocket_ic/pocket_ic.py b/pocket_ic/pocket_ic.py index a00d1b4..548d1b2 100644 --- a/pocket_ic/pocket_ic.py +++ b/pocket_ic/pocket_ic.py @@ -155,8 +155,7 @@ def __init__(self, subnet_config: Optional[SubnetConfig] = None) -> None: self.server = PocketICServer() subnet_config = subnet_config if subnet_config else SubnetConfig(application=1) subnet_config.validate() - self.instance_id, topology = self.server.new_instance(subnet_config._json()) - self.topology = self._generate_topology(topology) + self.instance_id = self.server.new_instance(subnet_config._json()) self.sender = ic.Principal.anonymous() def __del__(self) -> None: @@ -175,13 +174,24 @@ def set_sender(self, principal: ic.Principal) -> None: """ self.sender = principal + def topology(self): + """Returns the current topology of the PocketIC instance.""" + res = self._instance_get("read/topology") + t = dict() + subnets = res["subnet_configs"] + for subnet_id, config in subnets.items(): + subnet_id = ic.Principal.from_str(subnet_id) + subnet_kind = SubnetKind(config["subnet_kind"]) + t.update({subnet_id: subnet_kind}) + return t + def get_root_key(self) -> Optional[bytes]: """Get the root key of the IC. If there is no NNS subnet, returns `None`. Returns: Optional[bytes]: the root key of the IC """ - nns_subnet = [k for k, v in self.topology.items() if v == SubnetKind.NNS] + nns_subnet = [k for k, v in self.topology().items() if v == SubnetKind.NNS] if not nns_subnet: return None body = { @@ -543,15 +553,6 @@ def _canister_call( res = self._instance_post(endpoint, body) return self._get_ok_reply(res) - def _generate_topology(self, topology): - t = dict() - subnets = topology["subnet_configs"] - for subnet_id, config in subnets.items(): - subnet_id = ic.Principal.from_str(subnet_id) - subnet_kind = SubnetKind(config["subnet_kind"]) - t.update({subnet_id: subnet_kind}) - return t - def _get_ok_reply(self, request_result): if "Ok" in request_result: if "Reply" not in request_result["Ok"]: diff --git a/pocket_ic/pocket_ic_server.py b/pocket_ic/pocket_ic_server.py index 336e2f2..0c006ca 100644 --- a/pocket_ic/pocket_ic_server.py +++ b/pocket_ic/pocket_ic_server.py @@ -54,16 +54,16 @@ def __init__(self) -> None: self.url = self._get_url(port_file_path) self.request_client = requests.session() - def new_instance(self, subnet_config: dict) -> Tuple[int, dict]: - """Creates a new PocketIC instance. + def new_instance(self, subnet_config: dict) -> int: + """Creates a new PocketIC instance ID. Returns: - str: the new instance ID + int: the new instance ID """ url = f"{self.url}/instances" response = self.request_client.post(url, json=subnet_config) res = self._check_response(response)["Created"] - return res["instance_id"], res["topology"] + return res["instance_id"] def list_instances(self) -> List[str]: """Lists the currently running instances on the PocketIC Server. diff --git a/tests/pocket_ic_test.py b/tests/pocket_ic_test.py index 03da69c..b9156af 100644 --- a/tests/pocket_ic_test.py +++ b/tests/pocket_ic_test.py @@ -32,10 +32,11 @@ def test_create_canister_with_id(self): new_canister_id = pic.create_canister() self.assertNotEqual(new_canister_id.bytes, canister_id.bytes) - # Creating a canister with an ID that is not hosted by any subnet fails. - canister_id = ic.Principal.anonymous() - with self.assertRaises(Exception): - pic.create_canister(canister_id=canister_id) + # Creating a canister with an ID that is not hosted by any subnet creates a new subnet containing the canister. + canister_id = ic.Principal.from_str("zzztf-6qaaa-aaaah-qfsaa-cai") + self.assertEqual(len(pic.topology()), 1) + pic.create_canister(canister_id=canister_id) + self.assertEqual(len(pic.topology()), 2) def test_large_config_and_deduplication(self): pic = PocketIC( @@ -50,23 +51,25 @@ def test_large_config_and_deduplication(self): ) ) app_subnets = [ - k for k, v in pic.topology.items() if v == SubnetKind.APPLICATION + k for k, v in pic.topology().items() if v == SubnetKind.APPLICATION ] self.assertEqual(len(app_subnets), 2) - nns_subnets = [k for k, v in pic.topology.items() if v == SubnetKind.NNS] + nns_subnets = [k for k, v in pic.topology().items() if v == SubnetKind.NNS] self.assertEqual(len(nns_subnets), 1) bitcoin_subnets = [ - k for k, v in pic.topology.items() if v == SubnetKind.BITCOIN + k for k, v in pic.topology().items() if v == SubnetKind.BITCOIN ] self.assertEqual(len(bitcoin_subnets), 1) - system_subnets = [k for k, v in pic.topology.items() if v == SubnetKind.SYSTEM] + system_subnets = [ + k for k, v in pic.topology().items() if v == SubnetKind.SYSTEM + ] self.assertEqual(len(system_subnets), 3) def test_install_canister_on_subnet_and_get_subnet_of_canister(self): pic = PocketIC(SubnetConfig(nns=True, application=1)) - nns_subnet = next(k for k, v in pic.topology.items() if v == SubnetKind.NNS) + nns_subnet = next(k for k, v in pic.topology().items() if v == SubnetKind.NNS) app_subnet = next( - k for k, v in pic.topology.items() if v == SubnetKind.APPLICATION + k for k, v in pic.topology().items() if v == SubnetKind.APPLICATION ) nns_canister = pic.create_canister(subnet=nns_subnet) app_canister = pic.create_canister(subnet=app_subnet) @@ -156,7 +159,7 @@ def test_load_state(self): config.add_subnet_with_state(SubnetKind.NNS, tmp_dir, principal) pic = PocketIC(subnet_config=config) - (k, v) = list(pic.topology.items())[0] + (k, v) = list(pic.topology().items())[0] self.assertEqual(str(k), str(principal)) self.assertEqual(v, SubnetKind.NNS) os.rmdir(tmp_dir) From 3f24cbc32e0e74acfc7e6b652d239cd5c0f7cc19 Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 19:48:18 +0100 Subject: [PATCH 17/18] Add comment --- pocket_ic/pocket_ic.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pocket_ic/pocket_ic.py b/pocket_ic/pocket_ic.py index 548d1b2..006e66c 100644 --- a/pocket_ic/pocket_ic.py +++ b/pocket_ic/pocket_ic.py @@ -91,7 +91,10 @@ def add_subnet_with_state( `subnet_id` should be the subnet ID of the subnet in the state to be loaded""" - raw_subnet_id = base64.b64encode(subnet_id.bytes).decode() + raw_subnet_id_bytes = base64.b64encode( + subnet_id.bytes + ) # convert bytes to base64 bytes + raw_subnet_id = raw_subnet_id_bytes.decode() # convert bytes to str new_from_path = { "state_config": { From 5600239e75424a8951945985fd604c9e541eb69b Mon Sep 17 00:00:00 2001 From: Elias Datler Date: Tue, 19 Nov 2024 19:52:46 +0100 Subject: [PATCH 18/18] Fix doc string --- pocket_ic/pocket_ic_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pocket_ic/pocket_ic_server.py b/pocket_ic/pocket_ic_server.py index 0c006ca..b946fe7 100644 --- a/pocket_ic/pocket_ic_server.py +++ b/pocket_ic/pocket_ic_server.py @@ -55,7 +55,7 @@ def __init__(self) -> None: self.request_client = requests.session() def new_instance(self, subnet_config: dict) -> int: - """Creates a new PocketIC instance ID. + """Creates a new PocketIC instance. Returns: int: the new instance ID