Skip to content

Commit efbc2d2

Browse files
KINGH242HareemAtWavenielstroncffls
authored
Pool certificates (#321)
* feat: add cardano-cli chain context * fix: allow instances of str to submit_tx_cbor * fix: cast to int for asset amount and check for None in get_min_utxo * test: add test for cardano-cli chain context * Black formatting * Fix some QA issues * refactor: use `--out-file /dev/stdout` to get utxo data as json * fix: remove unused offline/online mode code * fix: remove unused fraction parser method * fix: add docker configuration to use cardano-cli in a Docker container and network args method to use custom networks * test: add integration tests for cardano-cli * test: fix cardano-node container name * feat: add initial functionality for pool certificates * test: add some tests for pool certificates * refactor: use built in fractions module * fix: output PoolRegistration as flat list * fix: clean up some code * test: add tests for pool params * Add more integration tests for cardano cli context * feat: add stake pool key pairs * fix: resolve mypy and black linting issues * feat: add witness count override for fee estimation add initial stake pool registration flag and deposit add pool vkey hashes if certificate exists * chore: add integration test temporary folders to ignore * test: add test for pool certificate related code * Simplify Certificate deserialization * Fix failing test cases for python<3.10 Syntax "Optional[type1 | type2]" is not supported in version <= 3.9 * Simplify relay parsing * Remove unused import --------- Co-authored-by: Hareem Adderley <[email protected]> Co-authored-by: Niels Mündler <[email protected]> Co-authored-by: Jerry <[email protected]>
1 parent d4ec506 commit efbc2d2

21 files changed

+1144
-47
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ dist
66

77
# IDE
88
.idea
9-
.code
9+
.code
10+
/integration-test/.env
11+
/integration-test/tmp_configs/*

pycardano/backend/cardano_cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from pycardano.hash import DatumHash, ScriptHash
3232
from pycardano.nativescript import NativeScript
3333
from pycardano.network import Network
34-
from pycardano.plutus import PlutusV1Script, PlutusV2Script, RawPlutusData, Datum
34+
from pycardano.plutus import Datum, PlutusV1Script, PlutusV2Script, RawPlutusData
3535
from pycardano.serialization import RawCBOR
3636
from pycardano.transaction import (
3737
Asset,

pycardano/certificate.py

+125-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1+
from __future__ import annotations
2+
13
from dataclasses import dataclass, field
2-
from typing import Optional, Union
4+
from typing import Optional, Tuple, Type, Union
35

6+
from pycardano.exception import DeserializeException
47
from pycardano.hash import PoolKeyHash, ScriptHash, VerificationKeyHash
5-
from pycardano.serialization import ArrayCBORSerializable
8+
from pycardano.serialization import ArrayCBORSerializable, limit_primitive_type
69

710
__all__ = [
811
"Certificate",
912
"StakeCredential",
1013
"StakeRegistration",
1114
"StakeDeregistration",
1215
"StakeDelegation",
16+
"PoolRegistration",
17+
"PoolRetirement",
1318
]
1419

20+
from pycardano.pool_params import PoolParams
21+
22+
unit_interval = Tuple[int, int]
23+
1524

1625
@dataclass(repr=False)
1726
class StakeCredential(ArrayCBORSerializable):
@@ -25,20 +34,55 @@ def __post_init__(self):
2534
else:
2635
self._CODE = 1
2736

37+
@classmethod
38+
@limit_primitive_type(list)
39+
def from_primitive(
40+
cls: Type[StakeCredential], values: Union[list, tuple]
41+
) -> StakeCredential:
42+
if values[0] == 0:
43+
return cls(VerificationKeyHash(values[1]))
44+
elif values[0] == 1:
45+
return cls(ScriptHash(values[1]))
46+
else:
47+
raise DeserializeException(f"Invalid StakeCredential type {values[0]}")
48+
2849

2950
@dataclass(repr=False)
3051
class StakeRegistration(ArrayCBORSerializable):
3152
_CODE: int = field(init=False, default=0)
3253

3354
stake_credential: StakeCredential
3455

56+
def __post_init__(self):
57+
self._CODE = 0
58+
59+
@classmethod
60+
@limit_primitive_type(list)
61+
def from_primitive(
62+
cls: Type[StakeRegistration], values: Union[list, tuple]
63+
) -> StakeRegistration:
64+
return cls(stake_credential=StakeCredential.from_primitive(values[1]))
65+
3566

3667
@dataclass(repr=False)
3768
class StakeDeregistration(ArrayCBORSerializable):
3869
_CODE: int = field(init=False, default=1)
3970

4071
stake_credential: StakeCredential
4172

73+
def __post_init__(self):
74+
self._CODE = 1
75+
76+
@classmethod
77+
@limit_primitive_type(list)
78+
def from_primitive(
79+
cls: Type[StakeDeregistration], values: Union[list, tuple]
80+
) -> StakeDeregistration:
81+
if values[0] == 1:
82+
return cls(StakeCredential.from_primitive(values[1]))
83+
else:
84+
raise DeserializeException(f"Invalid StakeDeregistration type {values[0]}")
85+
4286

4387
@dataclass(repr=False)
4488
class StakeDelegation(ArrayCBORSerializable):
@@ -48,5 +92,83 @@ class StakeDelegation(ArrayCBORSerializable):
4892

4993
pool_keyhash: PoolKeyHash
5094

95+
def __post_init__(self):
96+
self._CODE = 2
97+
98+
@classmethod
99+
@limit_primitive_type(list)
100+
def from_primitive(
101+
cls: Type[StakeDelegation], values: Union[list, tuple]
102+
) -> StakeDelegation:
103+
if values[0] == 2:
104+
return cls(
105+
stake_credential=StakeCredential.from_primitive(values[1]),
106+
pool_keyhash=PoolKeyHash.from_primitive(values[2]),
107+
)
108+
else:
109+
raise DeserializeException(f"Invalid StakeDelegation type {values[0]}")
110+
111+
112+
@dataclass(repr=False)
113+
class PoolRegistration(ArrayCBORSerializable):
114+
_CODE: int = field(init=False, default=3)
115+
116+
pool_params: PoolParams
117+
118+
def __post_init__(self):
119+
self._CODE = 3
120+
121+
def to_primitive(self):
122+
pool_params = self.pool_params.to_primitive()
123+
if isinstance(pool_params, list):
124+
return [self._CODE, *pool_params]
125+
return super().to_primitive()
126+
127+
@classmethod
128+
@limit_primitive_type(list)
129+
def from_primitive(
130+
cls: Type[PoolRegistration], values: Union[list, tuple]
131+
) -> PoolRegistration:
132+
if values[0] == 3:
133+
if isinstance(values[1], list):
134+
return cls(
135+
pool_params=PoolParams.from_primitive(values[1]),
136+
)
137+
else:
138+
return cls(
139+
pool_params=PoolParams.from_primitive(values[1:]),
140+
)
141+
else:
142+
raise DeserializeException(f"Invalid PoolRegistration type {values[0]}")
143+
144+
145+
@dataclass(repr=False)
146+
class PoolRetirement(ArrayCBORSerializable):
147+
_CODE: int = field(init=False, default=4)
148+
149+
pool_keyhash: PoolKeyHash
150+
epoch: int
151+
152+
def __post_init__(self):
153+
self._CODE = 4
154+
155+
@classmethod
156+
@limit_primitive_type(list)
157+
def from_primitive(
158+
cls: Type[PoolRetirement], values: Union[list, tuple]
159+
) -> PoolRetirement:
160+
if values[0] == 4:
161+
return cls(
162+
pool_keyhash=PoolKeyHash.from_primitive(values[1]), epoch=values[2]
163+
)
164+
else:
165+
raise DeserializeException(f"Invalid PoolRetirement type {values[0]}")
166+
51167

52-
Certificate = Union[StakeRegistration, StakeDeregistration, StakeDelegation]
168+
Certificate = Union[
169+
StakeRegistration,
170+
StakeDeregistration,
171+
StakeDelegation,
172+
PoolRegistration,
173+
PoolRetirement,
174+
]

pycardano/cip/cip14.py

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from nacl.encoding import RawEncoder
44
from nacl.hash import blake2b
5+
56
from pycardano.crypto.bech32 import encode
67
from pycardano.hash import ScriptHash
78
from pycardano.transaction import AssetName

pycardano/hash.py

+28-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
"AUXILIARY_DATA_HASH_SIZE",
1313
"POOL_KEY_HASH_SIZE",
1414
"SCRIPT_DATA_HASH_SIZE",
15+
"VRF_KEY_HASH_SIZE",
16+
"POOL_METADATA_HASH_SIZE",
17+
"REWARD_ACCOUNT_HASH_SIZE",
1518
"ConstrainedBytes",
1619
"VerificationKeyHash",
1720
"ScriptHash",
@@ -20,6 +23,9 @@
2023
"DatumHash",
2124
"AuxiliaryDataHash",
2225
"PoolKeyHash",
26+
"PoolMetadataHash",
27+
"VrfKeyHash",
28+
"RewardAccountHash",
2329
]
2430

2531
VERIFICATION_KEY_HASH_SIZE = 28
@@ -29,6 +35,9 @@
2935
DATUM_HASH_SIZE = 32
3036
AUXILIARY_DATA_HASH_SIZE = 32
3137
POOL_KEY_HASH_SIZE = 28
38+
POOL_METADATA_HASH_SIZE = 32
39+
VRF_KEY_HASH_SIZE = 32
40+
REWARD_ACCOUNT_HASH_SIZE = 29
3241

3342

3443
T = TypeVar("T", bound="ConstrainedBytes")
@@ -124,7 +133,25 @@ class AuxiliaryDataHash(ConstrainedBytes):
124133
MAX_SIZE = MIN_SIZE = AUXILIARY_DATA_HASH_SIZE
125134

126135

127-
class PoolKeyHash(ConstrainedBytes):
136+
class PoolKeyHash(VerificationKeyHash):
128137
"""Hash of a stake pool"""
129138

130139
MAX_SIZE = MIN_SIZE = POOL_KEY_HASH_SIZE
140+
141+
142+
class PoolMetadataHash(ConstrainedBytes):
143+
"""Hash of a stake pool metadata"""
144+
145+
MAX_SIZE = MIN_SIZE = POOL_METADATA_HASH_SIZE
146+
147+
148+
class VrfKeyHash(ConstrainedBytes):
149+
"""Hash of a Cardano VRF key."""
150+
151+
MAX_SIZE = MIN_SIZE = VRF_KEY_HASH_SIZE
152+
153+
154+
class RewardAccountHash(ConstrainedBytes):
155+
"""Hash of a Cardano VRF key."""
156+
157+
MAX_SIZE = MIN_SIZE = REWARD_ACCOUNT_HASH_SIZE

pycardano/key.py

+37
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
"StakeSigningKey",
3333
"StakeVerificationKey",
3434
"StakeKeyPair",
35+
"StakePoolSigningKey",
36+
"StakePoolVerificationKey",
37+
"StakePoolKeyPair",
3538
]
3639

3740

@@ -314,3 +317,37 @@ def from_signing_key(
314317
cls: Type[StakeKeyPair], signing_key: SigningKey
315318
) -> StakeKeyPair:
316319
return cls(signing_key, StakeVerificationKey.from_signing_key(signing_key))
320+
321+
322+
class StakePoolSigningKey(SigningKey):
323+
KEY_TYPE = "StakePoolSigningKey_ed25519"
324+
DESCRIPTION = "Stake Pool Operator Signing Key"
325+
326+
327+
class StakePoolVerificationKey(VerificationKey):
328+
KEY_TYPE = "StakePoolVerificationKey_ed25519"
329+
DESCRIPTION = "Stake Pool Operator Verification Key"
330+
331+
332+
class StakePoolKeyPair:
333+
def __init__(self, signing_key: SigningKey, verification_key: VerificationKey):
334+
self.signing_key = signing_key
335+
self.verification_key = verification_key
336+
337+
@classmethod
338+
def generate(cls: Type[StakePoolKeyPair]) -> StakePoolKeyPair:
339+
signing_key = StakePoolSigningKey.generate()
340+
return cls.from_signing_key(signing_key)
341+
342+
@classmethod
343+
def from_signing_key(
344+
cls: Type[StakePoolKeyPair], signing_key: SigningKey
345+
) -> StakePoolKeyPair:
346+
return cls(signing_key, StakePoolVerificationKey.from_signing_key(signing_key))
347+
348+
def __eq__(self, other):
349+
if isinstance(other, StakePoolKeyPair):
350+
return (
351+
other.signing_key == self.signing_key
352+
and other.verification_key == self.verification_key
353+
)

0 commit comments

Comments
 (0)