Skip to content

Commit 7787695

Browse files
Improve base + blockfrost module maintainability (#120)
* UPDATE. including base.py and blockfrost.py in mypy check * UPDATE. converting dataclass DTOs to have concrete values at all times * REFACTOR. provide all required values for ProtocolParameters in the instantiation step * ADD. adding ogmios specific integration tests * ADD. adding a stub extra_entropy value for FixedChainContext * REFACTOR. pulling out JSON type to a dedicated types module * ADD. adding type hints for BlockFrostChainContext attributes * UPDATE. explicitly specifying type information of Blockfrost API's epoch value * ADD. adding min_pool_cost to blockfrost ProtocolParameters instantiation * UPDATE. lovelace_amount variable should be an integer value * ADD. adding a nested Nativescript test case before attempting to improve readability of NativeScript.from_dict() class method * ADD. adding ogmios parsing integration test UPDATE. improve NativeScript.from_dict() by pulling out primitive list building step * UPDATE. renaming script_json serializing method name * UPDATE. enforcing immutability for GenesisParameters and ProtocolParameters * REFACTOR. renaming JSON type to JsonDict to infer JSON object is represented
1 parent 374b505 commit 7787695

File tree

9 files changed

+181
-82
lines changed

9 files changed

+181
-82
lines changed

integration-test/test/test_ogmios.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from retry import retry
2+
3+
from .base import TEST_RETRIES, TestBase
4+
5+
6+
class TestProtocolParam(TestBase):
7+
@retry(tries=TEST_RETRIES, backoff=1.5, delay=6, jitter=(0, 4))
8+
def test_protocol_param_cost_models(self):
9+
protocol_param = self.chain_context.protocol_param
10+
11+
cost_models = protocol_param.cost_models
12+
for _, cost_model in cost_models.items():
13+
assert "addInteger-cpu-arguments-intercept" in cost_model
14+
assert "addInteger-cpu-arguments-slope" in cost_model

pycardano/backend/base.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,90 +19,90 @@
1919
ALONZO_COINS_PER_UTXO_WORD = 34482
2020

2121

22-
@dataclass
22+
@dataclass(frozen=True)
2323
class GenesisParameters:
2424
"""Cardano genesis parameters"""
2525

26-
active_slots_coefficient: float = None
26+
active_slots_coefficient: float
2727

28-
update_quorum: int = None
28+
update_quorum: int
2929

30-
max_lovelace_supply: int = None
30+
max_lovelace_supply: int
3131

32-
network_magic: int = None
32+
network_magic: int
3333

34-
epoch_length: int = None
34+
epoch_length: int
3535

36-
system_start: int = None
36+
system_start: int
3737

38-
slots_per_kes_period: int = None
38+
slots_per_kes_period: int
3939

40-
slot_length: int = None
40+
slot_length: int
4141

42-
max_kes_evolutions: int = None
42+
max_kes_evolutions: int
4343

44-
security_param: int = None
44+
security_param: int
4545

4646

47-
@dataclass
47+
@dataclass(frozen=True)
4848
class ProtocolParameters:
4949
"""Cardano protocol parameters"""
5050

51-
min_fee_constant: int = None
51+
min_fee_constant: int
5252

53-
min_fee_coefficient: int = None
53+
min_fee_coefficient: int
5454

55-
max_block_size: int = None
55+
max_block_size: int
5656

57-
max_tx_size: int = None
57+
max_tx_size: int
5858

59-
max_block_header_size: int = None
59+
max_block_header_size: int
6060

61-
key_deposit: int = None
61+
key_deposit: int
6262

63-
pool_deposit: int = None
63+
pool_deposit: int
6464

65-
pool_influence: float = None
65+
pool_influence: float
6666

67-
monetary_expansion: float = None
67+
monetary_expansion: float
6868

69-
treasury_expansion: float = None
69+
treasury_expansion: float
7070

71-
decentralization_param: float = None
71+
decentralization_param: float
7272

73-
extra_entropy: str = None
73+
extra_entropy: str
7474

75-
protocol_major_version: int = None
75+
protocol_major_version: int
7676

77-
protocol_minor_version: int = None
77+
protocol_minor_version: int
7878

79-
min_utxo: int = None
79+
min_utxo: int
8080

81-
min_pool_cost: int = None
81+
min_pool_cost: int
8282

83-
price_mem: float = None
83+
price_mem: float
8484

85-
price_step: float = None
85+
price_step: float
8686

87-
max_tx_ex_mem: int = None
87+
max_tx_ex_mem: int
8888

89-
max_tx_ex_steps: int = None
89+
max_tx_ex_steps: int
9090

91-
max_block_ex_mem: int = None
91+
max_block_ex_mem: int
9292

93-
max_block_ex_steps: int = None
93+
max_block_ex_steps: int
9494

95-
max_val_size: int = None
95+
max_val_size: int
9696

97-
collateral_percent: int = None
97+
collateral_percent: int
9898

99-
max_collateral_inputs: int = None
99+
max_collateral_inputs: int
100100

101-
coins_per_utxo_word: int = None
101+
coins_per_utxo_word: int
102102

103-
coins_per_utxo_byte: int = None
103+
coins_per_utxo_byte: int
104104

105-
cost_models: Dict[str, Dict[str, int]] = None
105+
cost_models: Dict[str, Dict[str, int]]
106106
"""A dict contains cost models for Plutus. The key will be "PlutusV1", "PlutusV2", etc.
107107
The value will be a dict of cost model parameters."""
108108

pycardano/backend/blockfrost.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
22
import tempfile
33
import time
4-
from typing import Dict, List, Union
4+
from typing import Dict, List, Optional, Union
55

66
import cbor2
77
from blockfrost import ApiUrls, BlockFrostApi
8+
from blockfrost.utils import Namespace
89

910
from pycardano.address import Address
1011
from pycardano.backend.base import (
@@ -28,6 +29,7 @@
2829
UTxO,
2930
Value,
3031
)
32+
from pycardano.types import JsonDict
3133

3234
__all__ = ["BlockFrostChainContext"]
3335

@@ -40,6 +42,12 @@ class BlockFrostChainContext(ChainContext):
4042
network (Network): Network to use.
4143
"""
4244

45+
api: BlockFrostApi
46+
_epoch_info: Namespace
47+
_epoch: Optional[int] = None
48+
_genesis_param: Optional[GenesisParameters] = None
49+
_protocol_param: Optional[ProtocolParameters] = None
50+
4351
def __init__(
4452
self, project_id: str, network: Network = Network.TESTNET, base_url: str = None
4553
):
@@ -72,7 +80,8 @@ def network(self) -> Network:
7280
@property
7381
def epoch(self) -> int:
7482
if not self._epoch or self._check_epoch_and_update():
75-
self._epoch = self.api.epoch_latest().epoch
83+
new_epoch: int = self.api.epoch_latest().epoch
84+
self._epoch = new_epoch
7685
return self._epoch
7786

7887
@property
@@ -107,6 +116,7 @@ def protocol_param(self) -> ProtocolParameters:
107116
protocol_major_version=int(params.protocol_major_ver),
108117
protocol_minor_version=int(params.protocol_minor_ver),
109118
min_utxo=int(params.min_utxo),
119+
min_pool_cost=int(params.min_pool_cost),
110120
price_mem=float(params.price_mem),
111121
price_step=float(params.price_step),
112122
max_tx_ex_mem=int(params.max_tx_ex_mem),
@@ -138,9 +148,10 @@ def _get_script(
138148
cbor2.loads(bytes.fromhex(self.api.script_cbor(script_hash).cbor))
139149
)
140150
else:
141-
return NativeScript.from_dict(
142-
self.api.script_json(script_hash, return_type="json")["json"]
143-
)
151+
script_json: JsonDict = self.api.script_json(
152+
script_hash, return_type="json"
153+
)["json"]
154+
return NativeScript.from_dict(script_json)
144155

145156
def utxos(self, address: str) -> List[UTxO]:
146157
results = self.api.address_utxos(address, gather_pages=True)
@@ -152,7 +163,7 @@ def utxos(self, address: str) -> List[UTxO]:
152163
[result.tx_hash, result.output_index]
153164
)
154165
amount = result.amount
155-
lovelace_amount = None
166+
lovelace_amount = 0
156167
multi_assets = MultiAsset()
157168
for item in amount:
158169
if item.unit == "lovelace":

pycardano/backend/ogmios.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,11 @@
2929
UTxO,
3030
Value,
3131
)
32+
from pycardano.types import JsonDict
3233

3334
__all__ = ["OgmiosChainContext"]
3435

3536

36-
JSON = Dict[str, Any]
37-
38-
3937
class OgmiosQueryType(str, Enum):
4038
Query = "Query"
4139
SubmitTx = "SubmitTx"
@@ -66,7 +64,7 @@ def __init__(
6664
self._genesis_param = None
6765
self._protocol_param = None
6866

69-
def _request(self, method: OgmiosQueryType, args: JSON) -> Any:
67+
def _request(self, method: OgmiosQueryType, args: JsonDict) -> Any:
7068
ws = websocket.WebSocket()
7169
ws.connect(self._ws_url)
7270
request = json.dumps(
@@ -88,27 +86,27 @@ def _request(self, method: OgmiosQueryType, args: JSON) -> Any:
8886
)
8987
return json.loads(response)["result"]
9088

91-
def _query_current_protocol_params(self) -> JSON:
89+
def _query_current_protocol_params(self) -> JsonDict:
9290
args = {"query": "currentProtocolParameters"}
9391
return self._request(OgmiosQueryType.Query, args)
9492

95-
def _query_genesis_config(self) -> JSON:
93+
def _query_genesis_config(self) -> JsonDict:
9694
args = {"query": "genesisConfig"}
9795
return self._request(OgmiosQueryType.Query, args)
9896

9997
def _query_current_epoch(self) -> int:
10098
args = {"query": "currentEpoch"}
10199
return self._request(OgmiosQueryType.Query, args)
102100

103-
def _query_chain_tip(self) -> JSON:
101+
def _query_chain_tip(self) -> JsonDict:
104102
args = {"query": "chainTip"}
105103
return self._request(OgmiosQueryType.Query, args)
106104

107-
def _query_utxos_by_address(self, address: str) -> List[List[JSON]]:
105+
def _query_utxos_by_address(self, address: str) -> List[List[JsonDict]]:
108106
args = {"query": {"utxo": [address]}}
109107
return self._request(OgmiosQueryType.Query, args)
110108

111-
def _query_utxos_by_tx_id(self, tx_id: str, index: int) -> List[List[JSON]]:
109+
def _query_utxos_by_tx_id(self, tx_id: str, index: int) -> List[List[JsonDict]]:
112110
args = {"query": {"utxo": [{"txId": tx_id, "index": index}]}}
113111
return self._request(OgmiosQueryType.Query, args)
114112

@@ -151,6 +149,7 @@ def _fetch_protocol_param(self) -> ProtocolParameters:
151149
extra_entropy=result.get("extraEntropy", ""),
152150
protocol_major_version=result["protocolVersion"]["major"],
153151
protocol_minor_version=result["protocolVersion"]["minor"],
152+
min_utxo=self._get_min_utxo(),
154153
min_pool_cost=result["minPoolCost"],
155154
price_mem=self._fraction_parser(result["prices"]["memory"]),
156155
price_step=self._fraction_parser(result["prices"]["steps"]),
@@ -165,17 +164,24 @@ def _fetch_protocol_param(self) -> ProtocolParameters:
165164
"coinsPerUtxoWord", ALONZO_COINS_PER_UTXO_WORD
166165
),
167166
coins_per_utxo_byte=result.get("coinsPerUtxoByte", 0),
168-
cost_models=result.get("costModels", {}),
167+
cost_models=self._parse_cost_models(result),
169168
)
170169

171-
if "plutus:v1" in param.cost_models:
172-
param.cost_models["PlutusV1"] = param.cost_models.pop("plutus:v1")
173-
if "plutus:v2" in param.cost_models:
174-
param.cost_models["PlutusV2"] = param.cost_models.pop("plutus:v2")
170+
return param
175171

172+
def _get_min_utxo(self) -> int:
176173
result = self._query_genesis_config()
177-
param.min_utxo = result["protocolParameters"]["minUtxoValue"]
178-
return param
174+
return result["protocolParameters"]["minUtxoValue"]
175+
176+
def _parse_cost_models(self, ogmios_result: JsonDict) -> Dict[str, Dict[str, int]]:
177+
ogmios_cost_models = ogmios_result.get("costModels", {})
178+
179+
cost_models = {}
180+
if "plutus:v1" in ogmios_cost_models:
181+
cost_models["PlutusV1"] = ogmios_cost_models["plutus:v1"].copy()
182+
if "plutus:v2" in ogmios_cost_models:
183+
cost_models["PlutusV2"] = ogmios_cost_models["plutus:v2"].copy()
184+
return cost_models
179185

180186
@property
181187
def genesis_param(self) -> GenesisParameters:

0 commit comments

Comments
 (0)