Skip to content

Commit

Permalink
Merge branch 'feat/ogmios-path' of https://github.com/theeldermilleni…
Browse files Browse the repository at this point in the history
…al/pycardano into bugfix/datum
  • Loading branch information
theeldermillenial committed Nov 16, 2024
2 parents f1e3f80 + 9938b37 commit f203fc7
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 82 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,5 @@ You can support us by 1) sponsoring through Github, or 2) donating ADA to our AD

<p align="left">
<a href="https://github.com/KtorZ"><img src="https://avatars.githubusercontent.com/u/5680256?s=50&v=4"/></a>
<a href="https://github.com/CardanoDur"><img width="50" src="https://avatars.githubusercontent.com/u/1000466?s=50&v=4"/></a>
<a href="https://github.com/huths0lo"><img width="50" src="https://avatars.githubusercontent.com/u/78839856?s=50&v=4"/></a>
<a href="https://github.com/markrufino"><img width="50" src="https://avatars.githubusercontent.com/u/30117352?v=4"/></a>
<a href="https://github.com/OpShin"><img width="50" src="https://avatars.githubusercontent.com/u/102762047?s=200&v=4"/></a>
<a href="https://github.com/aada-finance"><img width="50" src="https://avatars.githubusercontent.com/u/89693711?v=4"/></a>
<a href="https://github.com/blockfrost"><img src="https://avatars.githubusercontent.com/u/70073210?s=50&v=4"/></a>
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5901e9010000323232323232323232323232322232323232323232323232323374a90001bb1498c8c8ccccd400c01001401840084004030030488888c8c8c94ccd5cd19180d88009980c180aa8012400c2930992999ab9a32301c100133019301650034801052615333573464603820029404c00452613263357389201136e6f7420612076616c696420707572706f7365004988c00852624984004c0454004405840584c98cd5ce2481104e616d654572726f723a207e626f6f6c004984c98cd5ce2481144e616d654572726f723a2076616c696461746f72004984c98cd5ce2481124e616d654572726f723a20707572706f7365004984c98cd5ce2481104e616d654572726f723a20646174756d004984c98cd5ce2481124e616d654572726f723a20636f6e74657874004984c98cd5ce2481144e616d654572726f723a20526577617264696e67004984c98cd5ce2481154e616d654572726f723a2043657274696679696e67004980080088c010c0200048c0140040108c018c94ccd55cf800899319ab9c49010a496e6465784572726f72004984d5d1000800919000a80091aab9d3754002e1c8d55cf1baa00123253335573e002264c66ae712410a496e6465784572726f72004984d5d0800800919ba548018cd5d028009bb14988cdd2a400866ae814004dd8a4c1
2 changes: 1 addition & 1 deletion integration-test/test/test_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_stake_delegation(self):
builder = TransactionBuilder(self.chain_context)

builder.add_input_address(giver_address)
builder.add_output(TransactionOutput(address, 440000000000))
builder.add_output(TransactionOutput(address, 44000000000))

signed_tx = builder.build_and_sign([self.payment_skey], giver_address)

Expand Down
97 changes: 97 additions & 0 deletions integration-test/test/test_certificate_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import os
import time

import cbor2
from retry import retry

from pycardano import *

from .base import TEST_RETRIES, TestBase


class TestDelegation(TestBase):
@retry(tries=TEST_RETRIES, backoff=1.5, delay=6, jitter=(0, 4))
def test_stake_delegation(self):
with open("./plutus_scripts/pass_certifying_and_rewarding.plutus", "r") as f:
script_hex = f.read()
stake_script = PlutusV2Script(bytes.fromhex(script_hex))
cert_script_hash = plutus_script_hash(stake_script)
address = Address(
self.payment_key_pair.verification_key.hash(),
cert_script_hash,
self.NETWORK,
)

utxos = self.chain_context.utxos(address)

if not utxos:
giver_address = Address(self.payment_vkey.hash(), network=self.NETWORK)

builder = TransactionBuilder(self.chain_context)

builder.add_input_address(giver_address)
builder.add_output(TransactionOutput(address, 44000000000))

signed_tx = builder.build_and_sign([self.payment_skey], giver_address)

print("############### Transaction created ###############")
print(signed_tx)
print(signed_tx.to_cbor_hex())
print("############### Submitting transaction ###############")
self.chain_context.submit_tx(signed_tx)

time.sleep(3)

stake_credential = StakeCredential(cert_script_hash)
stake_registration = StakeRegistration(stake_credential)
pool_hash = PoolKeyHash(bytes.fromhex(os.environ.get("POOL_ID").strip()))
stake_delegation = StakeDelegation(stake_credential, pool_keyhash=pool_hash)

builder = TransactionBuilder(self.chain_context)

builder.add_input_address(address)
builder.add_output(TransactionOutput(address, 35000000))
builder.certificates = [stake_registration, stake_delegation]
redeemer = Redeemer(0)
builder.add_certificate_script(stake_script, redeemer=redeemer)

signed_tx = builder.build_and_sign(
[self.payment_key_pair.signing_key],
address,
)

print("############### Transaction created ###############")
print(signed_tx)
print(signed_tx.to_cbor_hex())
print("############### Submitting transaction ###############")
self.chain_context.submit_tx(signed_tx)


# time.sleep(8)
#
# builder = TransactionBuilder(self.chain_context)
#
# builder.add_input_address(address)
#
# stake_address = Address(
# staking_part=cert_script_hash,
# network=self.NETWORK,
# )
#
# builder.withdrawals = Withdrawals({bytes(stake_address): 0})
#
# builder.add_output(TransactionOutput(address, 1000000))
# redeemer = Redeemer(0)
# builder.add_withdrawal_script(stake_script, redeemer=redeemer)
#
# signed_tx = builder.build_and_sign(
# [self.payment_key_pair.signing_key],
# address,
# )
#
# print("############### Transaction created ###############")
# print(signed_tx)
# print(signed_tx.to_cbor_hex())
# print("############### Submitting transaction ###############")
# self.chain_context.submit_tx(signed_tx)
#
7 changes: 6 additions & 1 deletion pycardano/backend/blockfrost.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,13 @@ def evaluate_tx_cbor(self, cbor: Union[bytes, str]) -> Dict[str, ExecutionUnits]
cbor = cbor.hex()
with tempfile.NamedTemporaryFile(delete=False, mode="w") as f:
f.write(cbor)
result = self.api.transaction_evaluate(f.name).result

result = self.api.transaction_evaluate(f.name)
os.remove(f.name)
if not hasattr(result, "result"):
raise TransactionFailedException(result)
else:
result = result.result
return_val = {}
if not hasattr(result, "EvaluationResult"):
raise TransactionFailedException(result)
Expand Down
20 changes: 11 additions & 9 deletions pycardano/backend/ogmios_v6.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def __init__(
self,
host: str = "localhost",
port: int = 1337,
path: str = "",
secure: bool = False,
refetch_chain_tip_interval: Optional[float] = None,
utxo_cache_size: int = 10000,
Expand All @@ -67,6 +68,7 @@ def __init__(
):
self.host = host
self.port = port
self.path = path
self.secure = secure
self._network = network
self._service_name = "ogmios"
Expand All @@ -86,26 +88,26 @@ def __init__(
self._datum_cache = LRUCache(maxsize=datum_cache_size)

def _query_current_era(self) -> OgmiosEra:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
return get_current_era(client)

def _query_current_epoch(self) -> int:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
epoch, _ = client.query_epoch.execute()
return epoch

def _query_chain_tip(self) -> OgmiosTip:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
tip, _ = client.query_network_tip.execute()
return tip

def _query_utxos_by_address(self, address: Address) -> List[OgmiosUtxo]:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
utxos, _ = client.query_utxo.execute([address])
return utxos

def _query_utxos_by_tx_id(self, tx_id: str, index: int) -> List[OgmiosUtxo]:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
utxos, _ = client.query_utxo.execute(
[OgmiosTxOutputReference(tx_id, index)]
)
Expand Down Expand Up @@ -135,7 +137,7 @@ def protocol_param(self) -> ProtocolParameters:
return self._protocol_param

def _fetch_protocol_param(self) -> ProtocolParameters:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
protocol_parameters, _ = client.query_protocol_parameters.execute()
pyc_protocol_params = ProtocolParameters(
min_fee_constant=protocol_parameters.min_fee_constant.lovelace,
Expand Down Expand Up @@ -205,7 +207,7 @@ def genesis_param(self) -> GenesisParameters:
return self._genesis_param # type: ignore[return-value]

def _fetch_genesis_param(self) -> OgmiosGenesisParameters:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
return OgmiosGenesisParameters(client, self._query_current_era())

@property
Expand Down Expand Up @@ -311,13 +313,13 @@ def utxo_by_tx_id(self, tx_id: str, index: int) -> Optional[UTxO]:
def submit_tx_cbor(self, cbor: Union[bytes, str]):
if isinstance(cbor, bytes):
cbor = cbor.hex()
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
client.submit_transaction.execute(cbor)

def evaluate_tx_cbor(self, cbor: Union[bytes, str]) -> Dict[str, ExecutionUnits]:
if isinstance(cbor, bytes):
cbor = cbor.hex()
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
result, _ = client.evaluate_transaction.execute(cbor)
result_dict = {}
for res in result:
Expand Down
2 changes: 1 addition & 1 deletion pycardano/plutus.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ class RedeemerTag(CBORSerializable, Enum):

SPEND = 0
MINT = 1
CERT = 2
CERTIFICATE = 2
WITHDRAWAL = 3

def to_primitive(self) -> int:
Expand Down
53 changes: 53 additions & 0 deletions pycardano/txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ class TransactionBuilder:
field(init=False, default_factory=lambda: [])
)

_certificate_script_to_redeemers: List[Tuple[ScriptType, Optional[Redeemer]]] = (
field(init=False, default_factory=lambda: [])
)

_inputs_to_scripts: Dict[UTxO, ScriptType] = field(
init=False, default_factory=lambda: {}
)
Expand Down Expand Up @@ -385,6 +389,49 @@ def add_withdrawal_script(
self._withdrawal_script_to_redeemers.append((script, redeemer))
return self

def add_certificate_script(
self,
script: Union[
UTxO, NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script
],
redeemer: Optional[Redeemer] = None,
) -> TransactionBuilder:
"""Add a certificate script along with its redeemer to this transaction.
WARNING: The order of operations matters.
The index of the redeemer will be set to the index of the last certificate added.
Args:
script (Union[UTxO, PlutusV1Script, PlutusV2Script, PlutusV3Script]): A plutus script.
redeemer (Optional[Redeemer]): A plutus redeemer to unlock the UTxO.
Returns:
TransactionBuilder: Current transaction builder.
"""
if redeemer:
if redeemer.tag is not None and redeemer.tag != RedeemerTag.CERTIFICATE:
raise InvalidArgumentException(
f"Expect the redeemer tag's type to be {RedeemerTag.CERTIFICATE}, "
f"but got {redeemer.tag} instead."
)
assert self.certificates is not None and len(self.certificates) >= 1, (
"self.certificates is None. redeemer.index needs to be set to the index of the corresponding"
"certificate (defaulting to the last certificate) however no certificates could be found"
)
redeemer.index = len(self.certificates) - 1
redeemer.tag = RedeemerTag.CERTIFICATE
self._consolidate_redeemer(redeemer)

if isinstance(script, UTxO):
assert script.output.script is not None
self._certificate_script_to_redeemers.append(
(script.output.script, redeemer)
)
self.reference_inputs.add(script)
self._reference_scripts.append(script.output.script)
else:
self._certificate_script_to_redeemers.append((script, redeemer))
return self

def add_input_address(self, address: Union[Address, str]) -> TransactionBuilder:
"""Add an address to transaction's input address.
Unlike :meth:`add_input`, which deterministically adds a UTxO to the transaction's inputs, `add_input_address`
Expand Down Expand Up @@ -473,6 +520,9 @@ def all_scripts(self) -> List[ScriptType]:
for s, _ in self._withdrawal_script_to_redeemers:
scripts[script_hash(s)] = s

for s, _ in self._certificate_script_to_redeemers:
scripts[script_hash(s)] = s

return list(scripts.values())

@property
Expand All @@ -498,6 +548,7 @@ def _redeemer_list(self) -> List[Redeemer]:
[r for r in self._inputs_to_redeemers.values() if r is not None]
+ [r for _, r in self._minting_script_to_redeemers if r is not None]
+ [r for _, r in self._withdrawal_script_to_redeemers if r is not None]
+ [r for _, r in self._certificate_script_to_redeemers if r is not None]
)

def redeemers(self) -> Redeemers:
Expand Down Expand Up @@ -880,6 +931,8 @@ def _dfs(script: NativeScript):
def _set_redeemer_index(self):
# Set redeemers' index according to section 4.1 in
# https://hydra.iohk.io/build/13099856/download/1/alonzo-changes.pdf
#
# There is no way to determine certificate index here

if self.mint:
sorted_mint_policies = sorted(self.mint.keys(), key=lambda x: x.to_cbor())
Expand Down
65 changes: 0 additions & 65 deletions pycardano/witness.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,11 @@
from pycardano.key import ExtendedVerificationKey, VerificationKey
from pycardano.nativescript import NativeScript
from pycardano.plutus import (
ExecutionUnits,
PlutusV1Script,
PlutusV2Script,
PlutusV3Script,
RawPlutusData,
Redeemer,
RedeemerKey,
RedeemerMap,
Redeemers,
RedeemerTag,
RedeemerValue,
)
from pycardano.serialization import (
ArrayCBORSerializable,
Expand Down Expand Up @@ -94,62 +88,3 @@ class TransactionWitnessSet(MapCBORSerializable):
plutus_v3_script: Optional[List[PlutusV3Script]] = field(
default=None, metadata={"optional": True, "key": 7}
)

@classmethod
@limit_primitive_type(dict, list)
def from_primitive(
cls: Type[TransactionWitnessSet], values: Union[dict, list, tuple]
) -> TransactionWitnessSet | None:
def _get_vkey_witnesses(data: Any):
return (
[VerificationKeyWitness.from_primitive(witness) for witness in data]
if data
else None
)

def _get_native_scripts(data: Any):
return (
[NativeScript.from_primitive(script) for script in data]
if data
else None
)

def _get_plutus_v1_scripts(data: Any):
return [PlutusV1Script(script) for script in data] if data else None

def _get_plutus_v2_scripts(data: Any):
return [PlutusV2Script(script) for script in data] if data else None

def _get_redeemers(data: Any):
if not data:
return None
if isinstance(data, dict):
redeemer_map = RedeemerMap()
for (tag, index), value in data.items():
key = RedeemerKey(RedeemerTag(tag), index)
redeemer_value = RedeemerValue(value[0], ExecutionUnits(*value[1]))
redeemer_map[key] = redeemer_value
return redeemer_map
elif isinstance(data, list):
return [Redeemer.from_primitive(redeemer) for redeemer in data]
else:
raise ValueError(f"Unexpected redeemer data format: {type(data)}")

def _get_cls(data: Any):
return cls(
vkey_witnesses=_get_vkey_witnesses(data.get(0)),
native_scripts=_get_native_scripts(data.get(1)),
bootstrap_witness=data.get(2),
plutus_v1_script=_get_plutus_v1_scripts(data.get(3)),
plutus_data=data.get(4),
redeemer=_get_redeemers(data.get(5)),
plutus_v2_script=_get_plutus_v2_scripts(data.get(6)),
)

if isinstance(values, dict):
return _get_cls(values)
elif isinstance(values, list):
# TODO: May need to handle this differently
values = dict(values)
return _get_cls(values)
return None
Loading

0 comments on commit f203fc7

Please sign in to comment.