Skip to content

Commit 7da59df

Browse files
committed
Ogmios chain context
1 parent 97c7c8a commit 7da59df

File tree

8 files changed

+419
-3
lines changed

8 files changed

+419
-3
lines changed

docs/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" a
33
attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
44
babel==2.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
55
black==22.1.0; python_full_version >= "3.6.2"
6-
blockfrost-python==0.4.2; python_version >= "3.7" and python_version < "4"
6+
blockfrost-python==0.4.3; python_version >= "3.7" and python_version < "4"
77
cbor2==5.4.2.post1; python_version >= "3.6"
88
certifi==2021.10.8; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.7" and python_version < "4" and python_full_version >= "3.6.0"
99
cffi==1.15.0; python_version >= "3.6"
@@ -52,4 +52,5 @@ typed-ast==1.5.2; python_version < "3.8" and implementation_name == "cpython" an
5252
typeguard==2.13.3; python_full_version >= "3.5.3"
5353
typing-extensions==4.1.1; python_version < "3.8" and python_full_version >= "3.6.2" and python_version >= "3.6"
5454
urllib3==1.26.8; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.7"
55+
websocket-client==1.2.3; python_version >= "3.6"
5556
zipp==3.7.0; python_version < "3.8" and python_version >= "3.7"

docs/source/api/pycardano.backend.base.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ Backend
99
.. automodule:: pycardano.backend.blockfrost
1010
:members:
1111
:undoc-members:
12-
:show-inheritance:
12+
:show-inheritance:
13+
14+
.. automodule:: pycardano.backend.ogmios
15+
:members:
16+
:undoc-members:
17+
:show-inheritance:

poetry.lock

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pycardano/backend/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
from .base import *
44
from .blockfrost import *
5+
from .ogmios import *

pycardano/backend/ogmios.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import json
2+
import time
3+
from typing import List, Union
4+
5+
import websocket
6+
7+
from pycardano.address import Address
8+
from pycardano.backend.base import ChainContext, GenesisParameters, ProtocolParameters
9+
from pycardano.exception import TransactionFailedException
10+
from pycardano.hash import DatumHash, ScriptHash
11+
from pycardano.network import Network
12+
from pycardano.transaction import (
13+
Asset,
14+
AssetName,
15+
MultiAsset,
16+
TransactionInput,
17+
TransactionOutput,
18+
UTxO,
19+
Value,
20+
)
21+
22+
__all__ = ["OgmiosChainContext"]
23+
24+
25+
class OgmiosChainContext(ChainContext):
26+
def __init__(self, ws_url: str, network: Network, compact_result=True):
27+
self._ws_url = ws_url
28+
self._network = network
29+
self._service_name = "ogmios.v1:compact" if compact_result else "ogmios"
30+
self._last_known_block_slot = 0
31+
self._genesis_param = None
32+
self._protocol_param = None
33+
34+
def _request(self, method: str, args: dict) -> dict:
35+
ws = websocket.WebSocket()
36+
ws.connect(self._ws_url)
37+
request = json.dumps(
38+
{
39+
"type": "jsonwsp/request",
40+
"version": "1.0",
41+
"servicename": self._service_name,
42+
"methodname": method,
43+
"args": args,
44+
},
45+
separators=(",", ":"),
46+
)
47+
ws.send(request)
48+
response = ws.recv()
49+
ws.close()
50+
return json.loads(response)["result"]
51+
52+
def _check_chain_tip_and_update(self):
53+
slot = self.last_block_slot
54+
if self._last_known_block_slot != slot:
55+
self._last_known_block_slot = slot
56+
return True
57+
else:
58+
return False
59+
60+
@staticmethod
61+
def _fraction_parser(fraction: str) -> float:
62+
x, y = fraction.split("/")
63+
return int(x) / int(y)
64+
65+
@property
66+
def protocol_param(self) -> ProtocolParameters:
67+
"""Get current protocol parameters"""
68+
method = "Query"
69+
args = {"query": "currentProtocolParameters"}
70+
if not self._protocol_param or self._check_chain_tip_and_update():
71+
result = self._request(method, args)
72+
param = ProtocolParameters(
73+
min_fee_constant=result["minFeeConstant"],
74+
min_fee_coefficient=result["minFeeCoefficient"],
75+
max_block_size=result["maxBlockBodySize"],
76+
max_tx_size=result["maxTxSize"],
77+
max_block_header_size=result["maxBlockHeaderSize"],
78+
key_deposit=result["stakeKeyDeposit"],
79+
pool_deposit=result["poolDeposit"],
80+
pool_influence=self._fraction_parser(result["poolInfluence"]),
81+
monetary_expansion=self._fraction_parser(result["monetaryExpansion"]),
82+
treasury_expansion=self._fraction_parser(result["treasuryExpansion"]),
83+
decentralization_param=self._fraction_parser(
84+
result["decentralizationParameter"]
85+
),
86+
extra_entropy=result["extraEntropy"],
87+
protocol_major_version=result["protocolVersion"]["major"],
88+
protocol_minor_version=result["protocolVersion"]["minor"],
89+
min_pool_cost=result["minPoolCost"],
90+
price_mem=self._fraction_parser(result["prices"]["memory"]),
91+
price_step=self._fraction_parser(result["prices"]["steps"]),
92+
max_tx_ex_mem=result["maxExecutionUnitsPerTransaction"]["memory"],
93+
max_tx_ex_steps=result["maxExecutionUnitsPerTransaction"]["steps"],
94+
max_block_ex_mem=result["maxExecutionUnitsPerBlock"]["memory"],
95+
max_block_ex_steps=result["maxExecutionUnitsPerBlock"]["steps"],
96+
max_val_size=result["maxValueSize"],
97+
collateral_percent=result["collateralPercentage"],
98+
max_collateral_inputs=result["maxCollateralInputs"],
99+
coins_per_utxo_word=result["coinsPerUtxoWord"],
100+
)
101+
self._protocol_param = param
102+
return self._protocol_param
103+
104+
@property
105+
def genesis_param(self) -> GenesisParameters:
106+
"""Get chain genesis parameters"""
107+
method = "Query"
108+
args = {"query": "genesisConfig"}
109+
if not self._genesis_param or self._check_chain_tip_and_update():
110+
result = self._request(method, args)
111+
system_start_unix = int(
112+
time.mktime(
113+
time.strptime(
114+
result["systemStart"].split(".")[0], "%Y-%m-%dT%H:%M:%S"
115+
)
116+
)
117+
)
118+
self._genesis_param = GenesisParameters(
119+
active_slots_coefficient=self._fraction_parser(
120+
result["activeSlotsCoefficient"]
121+
),
122+
update_quorum=result["updateQuorum"],
123+
max_lovelace_supply=result["maxLovelaceSupply"],
124+
network_magic=result["networkMagic"],
125+
epoch_length=result["epochLength"],
126+
system_start=system_start_unix,
127+
slots_per_kes_period=result["slotsPerKesPeriod"],
128+
slot_length=result["slotLength"],
129+
max_kes_evolutions=result["maxKesEvolutions"],
130+
security_param=result["securityParameter"],
131+
)
132+
return self._genesis_param
133+
134+
@property
135+
def network(self) -> Network:
136+
"""Cet current network"""
137+
raise NotImplementedError()
138+
139+
@property
140+
def epoch(self) -> int:
141+
"""Current epoch number"""
142+
raise NotImplementedError()
143+
144+
@property
145+
def last_block_slot(self) -> int:
146+
"""Slot number of last block"""
147+
method = "Query"
148+
args = {"query": "chainTip"}
149+
slot = self._request(method, args)["result"]["slot"]
150+
return slot
151+
152+
def utxos(self, address: str) -> List[UTxO]:
153+
"""Get all UTxOs associated with an address.
154+
155+
Args:
156+
address (str): An address encoded with bech32.
157+
158+
Returns:
159+
List[UTxO]: A list of UTxOs.
160+
"""
161+
method = "Query"
162+
args = {"query": {"utxo": [address]}}
163+
results = self._request(method, args)
164+
165+
utxos = []
166+
167+
for result in results:
168+
in_ref = result[0]
169+
output = result[1]
170+
tx_in = TransactionInput.from_primitive([in_ref["txId"], in_ref["index"]])
171+
172+
lovelace_amount = output["value"]["coins"]
173+
174+
datum_hash = (
175+
DatumHash.from_primitive(output["datum"]) if output["datum"] else None
176+
)
177+
178+
if not output["value"]["assets"]:
179+
tx_out = TransactionOutput(
180+
Address.from_primitive(address),
181+
amount=lovelace_amount,
182+
datum_hash=datum_hash,
183+
)
184+
else:
185+
multi_assets = MultiAsset()
186+
187+
for asset in output["value"]["assets"]:
188+
quantity = output["value"]["assets"][asset]
189+
policy_hex, asset_name_hex = asset.split(".")
190+
policy = ScriptHash.from_primitive(policy_hex)
191+
asset_name_hex = AssetName.from_primitive(asset_name_hex)
192+
multi_assets.setdefault(policy, Asset())[asset_name_hex] = quantity
193+
194+
tx_out = TransactionOutput(
195+
Address.from_primitive(address),
196+
amount=Value(lovelace_amount, multi_assets),
197+
datum_hash=datum_hash,
198+
)
199+
utxos.append(UTxO(tx_in, tx_out))
200+
201+
return utxos
202+
203+
def submit_tx(self, cbor: Union[bytes, str]):
204+
"""Submit a transaction to the blockchain.
205+
206+
Args:
207+
cbor (Union[bytes, str]): The transaction to be submitted.
208+
209+
Raises:
210+
:class:`InvalidArgumentException`: When the transaction is invalid.
211+
:class:`TransactionFailedException`: When fails to submit the transaction to blockchain.
212+
"""
213+
if isinstance(cbor, bytes):
214+
cbor = cbor.hex()
215+
216+
method = "SubmitTx"
217+
args = {"bytes": cbor}
218+
result = self._request(method, args)
219+
if "SubmitFail" in result:
220+
raise TransactionFailedException(result["SubmitFail"])

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ PyNaCl = "^1.4.0"
2626
cbor2 = "^5.4.2"
2727
typeguard = "^2.13.3"
2828
blockfrost-python = "^0.4.3"
29+
websocket-client = "^1.2.3"
2930

3031
[tool.poetry.dev-dependencies]
3132
Sphinx = "^4.3.2"

test/pycardano/backend/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)