From c0bcec6d1a9f4e950c3b60bf6a8dd0101b9f8c7a Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Fri, 28 Oct 2022 22:34:20 -0700 Subject: [PATCH 1/8] REFACTOR. pull out current protocol param query --- pycardano/backend/ogmios.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index 163a3e6d..ce3e0ce6 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -88,6 +88,10 @@ def _request(self, method: OgmiosQueryType, args: JSON) -> Any: ) return json.loads(response)["result"] + def _query_current_protocol_params(self) -> JSON: + args = {"query": "currentProtocolParameters"} + return self._request(OgmiosQueryType.Query, args) + def _check_chain_tip_and_update(self): slot = self.last_block_slot if self._last_known_block_slot != slot: @@ -104,9 +108,8 @@ def _fraction_parser(fraction: str) -> float: @property def protocol_param(self) -> ProtocolParameters: """Get current protocol parameters""" - args = {"query": "currentProtocolParameters"} if not self._protocol_param or self._check_chain_tip_and_update(): - result = self._request(OgmiosQueryType.Query, args) + result = self._query_current_protocol_params() param = ProtocolParameters( min_fee_constant=result["minFeeConstant"], min_fee_coefficient=result["minFeeCoefficient"], From 1e5df37172bbaf6f4b05cd1f803f2c4f3ed16c1a Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Fri, 28 Oct 2022 22:36:18 -0700 Subject: [PATCH 2/8] REFACTOR. pull out genesis config query --- pycardano/backend/ogmios.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index ce3e0ce6..b8c15f64 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -92,6 +92,10 @@ def _query_current_protocol_params(self) -> JSON: args = {"query": "currentProtocolParameters"} return self._request(OgmiosQueryType.Query, args) + def _query_genesis_config(self) -> JSON: + args = {"query": "genesisConfig"} + return self._request(OgmiosQueryType.Query, args) + def _check_chain_tip_and_update(self): slot = self.last_block_slot if self._last_known_block_slot != slot: @@ -149,8 +153,7 @@ def protocol_param(self) -> ProtocolParameters: if "plutus:v2" in param.cost_models: param.cost_models["PlutusV2"] = param.cost_models.pop("plutus:v2") - args = {"query": "genesisConfig"} - result = self._request(OgmiosQueryType.Query, args) + result = self._query_genesis_config() param.min_utxo = result["protocolParameters"]["minUtxoValue"] self._protocol_param = param @@ -159,9 +162,8 @@ def protocol_param(self) -> ProtocolParameters: @property def genesis_param(self) -> GenesisParameters: """Get chain genesis parameters""" - args = {"query": "genesisConfig"} if not self._genesis_param or self._check_chain_tip_and_update(): - result = self._request(OgmiosQueryType.Query, args) + result = self._query_genesis_config() system_start_unix = int( calendar.timegm( time.strptime( From 08809825754b1c4518f2935a0b458cbfa3ba547d Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Fri, 28 Oct 2022 22:38:25 -0700 Subject: [PATCH 3/8] REFACTOR. pull out current epoch query --- pycardano/backend/ogmios.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index b8c15f64..7adb01dd 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -96,6 +96,10 @@ def _query_genesis_config(self) -> JSON: args = {"query": "genesisConfig"} return self._request(OgmiosQueryType.Query, args) + def _query_current_epoch(self) -> int: + args = {"query": "currentEpoch"} + return self._request(OgmiosQueryType.Query, args) + def _check_chain_tip_and_update(self): slot = self.last_block_slot if self._last_known_block_slot != slot: @@ -195,8 +199,7 @@ def network(self) -> Network: @property def epoch(self) -> int: """Current epoch number""" - args = {"query": "currentEpoch"} - return self._request(OgmiosQueryType.Query, args) + return self._query_current_epoch() @property def last_block_slot(self) -> int: From 1c724ed3bc7ec25c71ca10c4792265e7869e4104 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Fri, 28 Oct 2022 22:41:44 -0700 Subject: [PATCH 4/8] UPDATE. running black formatter UPDATE. including black format check in ci/cd static analyses part --- Makefile | 1 + pycardano/address.py | 2 +- pycardano/backend/ogmios.py | 6 ++++-- pycardano/hash.py | 2 +- pycardano/metadata.py | 2 +- pycardano/nativescript.py | 2 +- pycardano/plutus.py | 2 +- pycardano/transaction.py | 10 +++++++--- 8 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 4b3cbf67..e2195782 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ test-single: ## runs tests with "single" markers qa: ## runs static analyses poetry run flake8 pycardano poetry run mypy --install-types --non-interactive pycardano + poetry run black --check . format: ## runs code style and formatter poetry run isort . diff --git a/pycardano/address.py b/pycardano/address.py index cbe07fef..3905b1c3 100644 --- a/pycardano/address.py +++ b/pycardano/address.py @@ -9,7 +9,7 @@ from __future__ import annotations from enum import Enum -from typing import Union, Type +from typing import Type, Union from pycardano.crypto.bech32 import decode, encode from pycardano.exception import ( diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index 7adb01dd..32793f75 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -2,7 +2,7 @@ import json import time from enum import Enum -from typing import Any, Dict, List, Optional, Union, Tuple +from typing import Any, Dict, List, Optional, Tuple, Union import cbor2 import requests @@ -242,7 +242,9 @@ def _utxos_kupo(self, address: str) -> List[UTxO]: List[UTxO]: A list of UTxOs. """ if self._kupo_url is None: - raise AssertionError("kupo_url object attribute has not been assigned properly.") + raise AssertionError( + "kupo_url object attribute has not been assigned properly." + ) address_url = self._kupo_url + "/" + address results = requests.get(address_url).json() diff --git a/pycardano/hash.py b/pycardano/hash.py index df072905..975da671 100644 --- a/pycardano/hash.py +++ b/pycardano/hash.py @@ -1,6 +1,6 @@ """All type of hashes in Cardano ledger spec.""" -from typing import TypeVar, Union, Type +from typing import Type, TypeVar, Union from pycardano.serialization import CBORSerializable diff --git a/pycardano/metadata.py b/pycardano/metadata.py index d8ceaf70..d11ea642 100644 --- a/pycardano/metadata.py +++ b/pycardano/metadata.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any, ClassVar, List, Union, Type +from typing import Any, ClassVar, List, Type, Union from cbor2 import CBORTag from nacl.encoding import RawEncoder diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index 5c9c4353..c71d482c 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import ClassVar, List, Union, Type +from typing import ClassVar, List, Type, Union from nacl.encoding import RawEncoder from nacl.hash import blake2b diff --git a/pycardano/plutus.py b/pycardano/plutus.py index 6a7eae36..179f0936 100644 --- a/pycardano/plutus.py +++ b/pycardano/plutus.py @@ -6,7 +6,7 @@ import json from dataclasses import dataclass, field, fields from enum import Enum -from typing import Any, ClassVar, List, Optional, Union, Type +from typing import Any, ClassVar, List, Optional, Type, Union import cbor2 from cbor2 import CBORTag diff --git a/pycardano/transaction.py b/pycardano/transaction.py index db5cac40..d870aaa0 100644 --- a/pycardano/transaction.py +++ b/pycardano/transaction.py @@ -5,7 +5,7 @@ from copy import deepcopy from dataclasses import dataclass, field from pprint import pformat -from typing import Any, Callable, List, Optional, Union, Type +from typing import Any, Callable, List, Optional, Type, Union import cbor2 from cbor2 import CBORTag @@ -311,7 +311,9 @@ def to_shallow_primitive(self) -> List[Primitive]: return [self._TYPE, data] @classmethod - def from_primitive(cls: Type[_DatumOption], values: List[Primitive]) -> _DatumOption: + def from_primitive( + cls: Type[_DatumOption], values: List[Primitive] + ) -> _DatumOption: if values[0] == 0: return _DatumOption(DatumHash(values[1])) else: @@ -418,7 +420,9 @@ def to_primitive(self) -> Primitive: ).to_primitive() @classmethod - def from_primitive(cls: Type[TransactionOutput], value: Primitive) -> TransactionOutput: + def from_primitive( + cls: Type[TransactionOutput], value: Primitive + ) -> TransactionOutput: if isinstance(value, list): output = _TransactionOutputLegacy.from_primitive(value) return cls(output.address, output.amount, datum=output.datum_hash) From 8f6700d1baad1cc1a2c30499e5f210c27674007d Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Fri, 28 Oct 2022 22:42:51 -0700 Subject: [PATCH 5/8] REFACTOR. pulling out chain tip query --- pycardano/backend/ogmios.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index 32793f75..84dbe0b8 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -100,6 +100,10 @@ def _query_current_epoch(self) -> int: args = {"query": "currentEpoch"} return self._request(OgmiosQueryType.Query, args) + def _query_chain_tip(self) -> JSON: + args = {"query": "chainTip"} + return self._request(OgmiosQueryType.Query, args)["slot"] + def _check_chain_tip_and_update(self): slot = self.last_block_slot if self._last_known_block_slot != slot: @@ -204,8 +208,8 @@ def epoch(self) -> int: @property def last_block_slot(self) -> int: """Slot number of last block""" - args = {"query": "chainTip"} - return self._request(OgmiosQueryType.Query, args)["slot"] + result = self._query_chain_tip() + return result["slot"] def _extract_asset_info(self, asset_hash: str) -> Tuple[str, ScriptHash, AssetName]: policy_hex, asset_name_hex = asset_hash.split(".") From 40e83dce86d1131cc8b5382f2656a7f7f8b15774 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Fri, 28 Oct 2022 23:26:09 -0700 Subject: [PATCH 6/8] FIX. return JSON result from query chain tip --- pycardano/backend/ogmios.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index 84dbe0b8..c444e89a 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -102,7 +102,7 @@ def _query_current_epoch(self) -> int: def _query_chain_tip(self) -> JSON: args = {"query": "chainTip"} - return self._request(OgmiosQueryType.Query, args)["slot"] + return self._request(OgmiosQueryType.Query, args) def _check_chain_tip_and_update(self): slot = self.last_block_slot From 6f85d496150506426a95f3c4edf20374a2056178 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Tue, 1 Nov 2022 00:41:11 -0700 Subject: [PATCH 7/8] REFACTOR. pull out utxo ogmios query methods with explicit return type REFACTOR. re-order methods so that helper functions are right beneath the main function for readability --- pycardano/backend/ogmios.py | 75 ++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index c444e89a..47962846 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -104,6 +104,14 @@ def _query_chain_tip(self) -> JSON: args = {"query": "chainTip"} return self._request(OgmiosQueryType.Query, args) + def _query_utxos_by_address(self, address: str) -> List[List[JSON]]: + args = {"query": {"utxo": [address]}} + return self._request(OgmiosQueryType.Query, args) + + def _query_utxos_by_tx_id(self, tx_id: str, index: int) -> List[List[JSON]]: + args = {"query": {"utxo": [{"txId": tx_id, "index": index}]}} + return self._request(OgmiosQueryType.Query, args) + def _check_chain_tip_and_update(self): slot = self.last_block_slot if self._last_known_block_slot != slot: @@ -211,28 +219,21 @@ def last_block_slot(self) -> int: result = self._query_chain_tip() return result["slot"] - def _extract_asset_info(self, asset_hash: str) -> Tuple[str, ScriptHash, AssetName]: - policy_hex, asset_name_hex = asset_hash.split(".") - policy = ScriptHash.from_primitive(policy_hex) - asset_name = AssetName.from_primitive(asset_name_hex) - - return policy_hex, policy, asset_name - - def _check_utxo_unspent(self, tx_id: str, index: int) -> bool: - """Check whether an UTxO is unspent with Ogmios. + def utxos(self, address: str) -> List[UTxO]: + """Get all UTxOs associated with an address. Args: - tx_id (str): transaction id. - index (int): transaction index. - """ - - args = {"query": {"utxo": [{"txId": tx_id, "index": index}]}} - results = self._request(OgmiosQueryType.Query, args) + address (str): An address encoded with bech32. - if results: - return True + Returns: + List[UTxO]: A list of UTxOs. + """ + if self._kupo_url: + utxos = self._utxos_kupo(address) else: - return False + utxos = self._utxos_ogmios(address) + + return utxos def _utxos_kupo(self, address: str) -> List[UTxO]: """Get all UTxOs associated with an address with Kupo. @@ -302,6 +303,23 @@ def _utxos_kupo(self, address: str) -> List[UTxO]: return utxos + def _check_utxo_unspent(self, tx_id: str, index: int) -> bool: + """Check whether an UTxO is unspent with Ogmios. + + Args: + tx_id (str): transaction id. + index (int): transaction index. + """ + results = self._query_utxos_by_tx_id(tx_id, index) + return len(results) > 0 + + def _extract_asset_info(self, asset_hash: str) -> Tuple[str, ScriptHash, AssetName]: + policy_hex, asset_name_hex = asset_hash.split(".") + policy = ScriptHash.from_primitive(policy_hex) + asset_name = AssetName.from_primitive(asset_name_hex) + + return policy_hex, policy, asset_name + def _utxos_ogmios(self, address: str) -> List[UTxO]: """Get all UTxOs associated with an address with Ogmios. @@ -311,12 +329,9 @@ def _utxos_ogmios(self, address: str) -> List[UTxO]: Returns: List[UTxO]: A list of UTxOs. """ - - args = {"query": {"utxo": [address]}} - results = self._request(OgmiosQueryType.Query, args) + results = self._query_utxos_by_address(address) utxos = [] - for result in results: in_ref = result[0] output = result[1] @@ -374,22 +389,6 @@ def _utxos_ogmios(self, address: str) -> List[UTxO]: return utxos - def utxos(self, address: str) -> List[UTxO]: - """Get all UTxOs associated with an address. - - Args: - address (str): An address encoded with bech32. - - Returns: - List[UTxO]: A list of UTxOs. - """ - if self._kupo_url: - utxos = self._utxos_kupo(address) - else: - utxos = self._utxos_ogmios(address) - - return utxos - def submit_tx(self, cbor: Union[bytes, str]): """Submit a transaction to the blockchain. From bae4341d3a53548cb96f49d7f9b9bf23408a5ca8 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Tue, 1 Nov 2022 01:07:18 -0700 Subject: [PATCH 8/8] REFACTOR. pull out genesis and protocol param fetching logic to be independently testable REFACTOR. renaming chain tip status checking helper method --- pycardano/backend/ogmios.py | 137 ++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/pycardano/backend/ogmios.py b/pycardano/backend/ogmios.py index 47962846..4fa2a47c 100644 --- a/pycardano/backend/ogmios.py +++ b/pycardano/backend/ogmios.py @@ -112,7 +112,7 @@ def _query_utxos_by_tx_id(self, tx_id: str, index: int) -> List[List[JSON]]: args = {"query": {"utxo": [{"txId": tx_id, "index": index}]}} return self._request(OgmiosQueryType.Query, args) - def _check_chain_tip_and_update(self): + def _is_chain_tip_updated(self): slot = self.last_block_slot if self._last_known_block_slot != slot: self._last_known_block_slot = slot @@ -128,81 +128,84 @@ def _fraction_parser(fraction: str) -> float: @property def protocol_param(self) -> ProtocolParameters: """Get current protocol parameters""" - if not self._protocol_param or self._check_chain_tip_and_update(): - result = self._query_current_protocol_params() - param = ProtocolParameters( - min_fee_constant=result["minFeeConstant"], - min_fee_coefficient=result["minFeeCoefficient"], - max_block_size=result["maxBlockBodySize"], - max_tx_size=result["maxTxSize"], - max_block_header_size=result["maxBlockHeaderSize"], - key_deposit=result["stakeKeyDeposit"], - pool_deposit=result["poolDeposit"], - pool_influence=self._fraction_parser(result["poolInfluence"]), - monetary_expansion=self._fraction_parser(result["monetaryExpansion"]), - treasury_expansion=self._fraction_parser(result["treasuryExpansion"]), - decentralization_param=self._fraction_parser( - result.get("decentralizationParameter", "0/1") - ), - extra_entropy=result.get("extraEntropy", ""), - protocol_major_version=result["protocolVersion"]["major"], - protocol_minor_version=result["protocolVersion"]["minor"], - min_pool_cost=result["minPoolCost"], - price_mem=self._fraction_parser(result["prices"]["memory"]), - price_step=self._fraction_parser(result["prices"]["steps"]), - max_tx_ex_mem=result["maxExecutionUnitsPerTransaction"]["memory"], - max_tx_ex_steps=result["maxExecutionUnitsPerTransaction"]["steps"], - max_block_ex_mem=result["maxExecutionUnitsPerBlock"]["memory"], - max_block_ex_steps=result["maxExecutionUnitsPerBlock"]["steps"], - max_val_size=result["maxValueSize"], - collateral_percent=result["collateralPercentage"], - max_collateral_inputs=result["maxCollateralInputs"], - coins_per_utxo_word=result.get( - "coinsPerUtxoWord", ALONZO_COINS_PER_UTXO_WORD - ), - coins_per_utxo_byte=result.get("coinsPerUtxoByte", 0), - cost_models=result.get("costModels", {}), - ) + if not self._protocol_param or self._is_chain_tip_updated(): + self._protocol_param = self._fetch_protocol_param() + return self._protocol_param - if "plutus:v1" in param.cost_models: - param.cost_models["PlutusV1"] = param.cost_models.pop("plutus:v1") - if "plutus:v2" in param.cost_models: - param.cost_models["PlutusV2"] = param.cost_models.pop("plutus:v2") + def _fetch_protocol_param(self) -> ProtocolParameters: + result = self._query_current_protocol_params() + param = ProtocolParameters( + min_fee_constant=result["minFeeConstant"], + min_fee_coefficient=result["minFeeCoefficient"], + max_block_size=result["maxBlockBodySize"], + max_tx_size=result["maxTxSize"], + max_block_header_size=result["maxBlockHeaderSize"], + key_deposit=result["stakeKeyDeposit"], + pool_deposit=result["poolDeposit"], + pool_influence=self._fraction_parser(result["poolInfluence"]), + monetary_expansion=self._fraction_parser(result["monetaryExpansion"]), + treasury_expansion=self._fraction_parser(result["treasuryExpansion"]), + decentralization_param=self._fraction_parser( + result.get("decentralizationParameter", "0/1") + ), + extra_entropy=result.get("extraEntropy", ""), + protocol_major_version=result["protocolVersion"]["major"], + protocol_minor_version=result["protocolVersion"]["minor"], + min_pool_cost=result["minPoolCost"], + price_mem=self._fraction_parser(result["prices"]["memory"]), + price_step=self._fraction_parser(result["prices"]["steps"]), + max_tx_ex_mem=result["maxExecutionUnitsPerTransaction"]["memory"], + max_tx_ex_steps=result["maxExecutionUnitsPerTransaction"]["steps"], + max_block_ex_mem=result["maxExecutionUnitsPerBlock"]["memory"], + max_block_ex_steps=result["maxExecutionUnitsPerBlock"]["steps"], + max_val_size=result["maxValueSize"], + collateral_percent=result["collateralPercentage"], + max_collateral_inputs=result["maxCollateralInputs"], + coins_per_utxo_word=result.get( + "coinsPerUtxoWord", ALONZO_COINS_PER_UTXO_WORD + ), + coins_per_utxo_byte=result.get("coinsPerUtxoByte", 0), + cost_models=result.get("costModels", {}), + ) - result = self._query_genesis_config() - param.min_utxo = result["protocolParameters"]["minUtxoValue"] + if "plutus:v1" in param.cost_models: + param.cost_models["PlutusV1"] = param.cost_models.pop("plutus:v1") + if "plutus:v2" in param.cost_models: + param.cost_models["PlutusV2"] = param.cost_models.pop("plutus:v2") - self._protocol_param = param - return self._protocol_param + result = self._query_genesis_config() + param.min_utxo = result["protocolParameters"]["minUtxoValue"] + return param @property def genesis_param(self) -> GenesisParameters: """Get chain genesis parameters""" - if not self._genesis_param or self._check_chain_tip_and_update(): - result = self._query_genesis_config() - system_start_unix = int( - calendar.timegm( - time.strptime( - result["systemStart"].split(".")[0], "%Y-%m-%dT%H:%M:%S" - ), - ) - ) - self._genesis_param = GenesisParameters( - active_slots_coefficient=self._fraction_parser( - result["activeSlotsCoefficient"] - ), - update_quorum=result["updateQuorum"], - max_lovelace_supply=result["maxLovelaceSupply"], - network_magic=result["networkMagic"], - epoch_length=result["epochLength"], - system_start=system_start_unix, - slots_per_kes_period=result["slotsPerKesPeriod"], - slot_length=result["slotLength"], - max_kes_evolutions=result["maxKesEvolutions"], - security_param=result["securityParameter"], - ) + if not self._genesis_param or self._is_chain_tip_updated(): + self._genesis_param = self._fetch_genesis_param() return self._genesis_param + def _fetch_genesis_param(self) -> GenesisParameters: + result = self._query_genesis_config() + system_start_unix = int( + calendar.timegm( + time.strptime(result["systemStart"].split(".")[0], "%Y-%m-%dT%H:%M:%S"), + ) + ) + return GenesisParameters( + active_slots_coefficient=self._fraction_parser( + result["activeSlotsCoefficient"] + ), + update_quorum=result["updateQuorum"], + max_lovelace_supply=result["maxLovelaceSupply"], + network_magic=result["networkMagic"], + epoch_length=result["epochLength"], + system_start=system_start_unix, + slots_per_kes_period=result["slotsPerKesPeriod"], + slot_length=result["slotLength"], + max_kes_evolutions=result["maxKesEvolutions"], + security_param=result["securityParameter"], + ) + @property def network(self) -> Network: """Get current network"""