mirror of
https://github.com/planetmint/planetmint.git
synced 2025-11-25 15:05:49 +00:00
added type hints to transactions module
Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
parent
cddfe862ef
commit
a7d302e94d
@ -17,7 +17,7 @@ def hash_data(data):
|
|||||||
return sha3_256(data.encode()).hexdigest()
|
return sha3_256(data.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def generate_key_pair():
|
def generate_key_pair() -> CryptoKeypair:
|
||||||
"""Generates a cryptographic key pair.
|
"""Generates a cryptographic key pair.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
from cryptoconditions import Fulfillment
|
from cryptoconditions import Fulfillment
|
||||||
from cryptoconditions.exceptions import ASN1DecodeError, ASN1EncodeError
|
from cryptoconditions.exceptions import ASN1DecodeError, ASN1EncodeError
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ class Input(object):
|
|||||||
Transaction.
|
Transaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fulfillment, owners_before, fulfills=None):
|
def __init__(self, fulfillment: Fulfillment, owners_before: list[str], fulfills: Optional[TransactionLink] = None):
|
||||||
"""Create an instance of an :class:`~.Input`.
|
"""Create an instance of an :class:`~.Input`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -86,7 +87,7 @@ class Input(object):
|
|||||||
return input_
|
return input_
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, public_keys):
|
def generate(cls, public_keys: list[str]):
|
||||||
# TODO: write docstring
|
# TODO: write docstring
|
||||||
# The amount here does not really matter. It is only use on the
|
# The amount here does not really matter. It is only use on the
|
||||||
# output data model but here we only care about the fulfillment
|
# output data model but here we only care about the fulfillment
|
||||||
@ -94,7 +95,7 @@ class Input(object):
|
|||||||
return cls(output.fulfillment, public_keys)
|
return cls(output.fulfillment, public_keys)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data):
|
def from_dict(cls, data: dict):
|
||||||
"""Transforms a Python dictionary to an Input object.
|
"""Transforms a Python dictionary to an Input object.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import functools
|
import functools
|
||||||
import codecs
|
import codecs
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
class HDict(dict):
|
class HDict(dict):
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
@ -9,11 +9,11 @@ class HDict(dict):
|
|||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=16384)
|
@lru_cache(maxsize=16384)
|
||||||
def from_dict(func, *args, **kwargs):
|
def from_dict(func: Callable, *args, **kwargs):
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def memoize_from_dict(func):
|
def memoize_from_dict(func: Callable):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def memoized_func(*args, **kwargs):
|
def memoized_func(*args, **kwargs):
|
||||||
if args[1] is None:
|
if args[1] is None:
|
||||||
@ -45,7 +45,7 @@ def to_dict(func, tx_wrapped):
|
|||||||
return func(tx_wrapped.tx)
|
return func(tx_wrapped.tx)
|
||||||
|
|
||||||
|
|
||||||
def memoize_to_dict(func):
|
def memoize_to_dict(func: Callable):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def memoized_func(*args, **kwargs):
|
def memoized_func(*args, **kwargs):
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from typing import Union, Optional
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256
|
from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256
|
||||||
@ -27,7 +28,7 @@ class Output(object):
|
|||||||
|
|
||||||
MAX_AMOUNT = 9 * 10**18
|
MAX_AMOUNT = 9 * 10**18
|
||||||
|
|
||||||
def __init__(self, fulfillment, public_keys=None, amount=1):
|
def __init__(self, fulfillment: type[Fulfillment], public_keys: Optional[list[str]] = None, amount: int = 1):
|
||||||
"""Create an instance of a :class:`~.Output`.
|
"""Create an instance of a :class:`~.Output`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -90,7 +91,7 @@ class Output(object):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, public_keys, amount):
|
def generate(cls, public_keys: list[str], amount: int):
|
||||||
"""Generates a Output from a specifically formed tuple or list.
|
"""Generates a Output from a specifically formed tuple or list.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -136,7 +137,7 @@ class Output(object):
|
|||||||
return cls(threshold_cond, public_keys, amount=amount)
|
return cls(threshold_cond, public_keys, amount=amount)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _gen_condition(cls, initial, new_public_keys):
|
def _gen_condition(cls, initial: type[ThresholdSha256], new_public_keys: Union[list[str],str]) -> type[ThresholdSha256]:
|
||||||
"""Generates ThresholdSha256 conditions from a list of new owners.
|
"""Generates ThresholdSha256 conditions from a list of new owners.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -165,7 +166,8 @@ class Output(object):
|
|||||||
raise ValueError("Sublist cannot contain single owner")
|
raise ValueError("Sublist cannot contain single owner")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
new_public_keys = new_public_keys.pop()
|
if isinstance(new_public_keys, list):
|
||||||
|
new_public_keys = new_public_keys.pop()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
# NOTE: Instead of submitting base58 encoded addresses, a user
|
# NOTE: Instead of submitting base58 encoded addresses, a user
|
||||||
@ -182,7 +184,7 @@ class Output(object):
|
|||||||
return initial
|
return initial
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data):
|
def from_dict(cls, data: dict):
|
||||||
"""Transforms a Python dictionary to an Output object.
|
"""Transforms a Python dictionary to an Output object.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|||||||
@ -14,6 +14,7 @@ Attributes:
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from typing import Optional
|
||||||
import rapidjson
|
import rapidjson
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
@ -83,16 +84,16 @@ class Transaction(object):
|
|||||||
version (string): Defines the version number of a Transaction.
|
version (string): Defines the version number of a Transaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CREATE = "CREATE"
|
CREATE: str = "CREATE"
|
||||||
TRANSFER = "TRANSFER"
|
TRANSFER: str = "TRANSFER"
|
||||||
VALIDATOR_ELECTION = VALIDATOR_ELECTION
|
VALIDATOR_ELECTION: str = VALIDATOR_ELECTION
|
||||||
CHAIN_MIGRATION_ELECTION = CHAIN_MIGRATION_ELECTION
|
CHAIN_MIGRATION_ELECTION: str = CHAIN_MIGRATION_ELECTION
|
||||||
VOTE = VOTE
|
VOTE: str = VOTE
|
||||||
ALLOWED_OPERATIONS = (CREATE, TRANSFER)
|
ALLOWED_OPERATIONS: tuple[str, ...] = (CREATE, TRANSFER)
|
||||||
ASSET = "asset"
|
ASSET: str = "asset"
|
||||||
METADATA = "metadata"
|
METADATA: str = "metadata"
|
||||||
DATA = "data"
|
DATA: str = "data"
|
||||||
VERSION = "2.0"
|
VERSION: str = "2.0"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -250,7 +251,7 @@ class Transaction(object):
|
|||||||
return False
|
return False
|
||||||
return self.to_dict() == other
|
return self.to_dict() == other
|
||||||
|
|
||||||
def to_inputs(self, indices=None):
|
def to_inputs(self, indices: Optional[list[int]] = None) -> list[Input]:
|
||||||
"""Converts a Transaction's outputs to spendable inputs.
|
"""Converts a Transaction's outputs to spendable inputs.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -272,17 +273,17 @@ class Transaction(object):
|
|||||||
"""
|
"""
|
||||||
# NOTE: If no indices are passed, we just assume to take all outputs
|
# NOTE: If no indices are passed, we just assume to take all outputs
|
||||||
# as inputs.
|
# as inputs.
|
||||||
indices = indices or range(len(self.outputs))
|
iterable_indices = indices or range(len(self.outputs))
|
||||||
return [
|
return [
|
||||||
Input(
|
Input(
|
||||||
self.outputs[idx].fulfillment,
|
self.outputs[idx].fulfillment,
|
||||||
self.outputs[idx].public_keys,
|
self.outputs[idx].public_keys,
|
||||||
TransactionLink(self.id, idx),
|
TransactionLink(self.id, idx),
|
||||||
)
|
)
|
||||||
for idx in indices
|
for idx in iterable_indices
|
||||||
]
|
]
|
||||||
|
|
||||||
def add_input(self, input_):
|
def add_input(self, input_: Input) -> None:
|
||||||
"""Adds an input to a Transaction's list of inputs.
|
"""Adds an input to a Transaction's list of inputs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -293,7 +294,7 @@ class Transaction(object):
|
|||||||
raise TypeError("`input_` must be a Input instance")
|
raise TypeError("`input_` must be a Input instance")
|
||||||
self.inputs.append(input_)
|
self.inputs.append(input_)
|
||||||
|
|
||||||
def add_output(self, output):
|
def add_output(self, output: Output) -> None:
|
||||||
"""Adds an output to a Transaction's list of outputs.
|
"""Adds an output to a Transaction's list of outputs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -305,7 +306,7 @@ class Transaction(object):
|
|||||||
raise TypeError("`output` must be an Output instance or None")
|
raise TypeError("`output` must be an Output instance or None")
|
||||||
self.outputs.append(output)
|
self.outputs.append(output)
|
||||||
|
|
||||||
def sign(self, private_keys):
|
def sign(self, private_keys: list[str]):
|
||||||
"""Fulfills a previous Transaction's Output by signing Inputs.
|
"""Fulfills a previous Transaction's Output by signing Inputs.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -359,7 +360,7 @@ class Transaction(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _sign_input(cls, input_, message, key_pairs):
|
def _sign_input(cls, input_: Input, message: str, key_pairs: dict) -> Input:
|
||||||
"""Signs a single Input.
|
"""Signs a single Input.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -384,7 +385,7 @@ class Transaction(object):
|
|||||||
raise ValueError("Fulfillment couldn't be matched to " "Cryptocondition fulfillment type.")
|
raise ValueError("Fulfillment couldn't be matched to " "Cryptocondition fulfillment type.")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _sign_zenroom_fulfillment(cls, input_, message, key_pairs):
|
def _sign_zenroom_fulfillment(cls, input_: Input, message: str, key_pairs: dict) -> Input:
|
||||||
"""Signs a Zenroomful.
|
"""Signs a Zenroomful.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -399,14 +400,14 @@ class Transaction(object):
|
|||||||
# this should never happen, but then again, never say never.
|
# this should never happen, but then again, never say never.
|
||||||
input_ = deepcopy(input_)
|
input_ = deepcopy(input_)
|
||||||
public_key = input_.owners_before[0]
|
public_key = input_.owners_before[0]
|
||||||
message = sha3_256(message.encode())
|
sha3_message = sha3_256(message.encode())
|
||||||
if input_.fulfills:
|
if input_.fulfills:
|
||||||
message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
sha3_message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# cryptoconditions makes no assumptions of the encoding of the
|
# cryptoconditions makes no assumptions of the encoding of the
|
||||||
# message to sign or verify. It only accepts bytestrings
|
# message to sign or verify. It only accepts bytestrings
|
||||||
input_.fulfillment.sign(message.digest(), base58.b58decode(key_pairs[public_key].encode()))
|
input_.fulfillment.sign(sha3_message.digest(), base58.b58decode(key_pairs[public_key].encode()))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeypairMismatchException(
|
raise KeypairMismatchException(
|
||||||
"Public key {} is not a pair to " "any of the private keys".format(public_key)
|
"Public key {} is not a pair to " "any of the private keys".format(public_key)
|
||||||
@ -414,7 +415,7 @@ class Transaction(object):
|
|||||||
return input_
|
return input_
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs):
|
def _sign_simple_signature_fulfillment(cls, input_: Input, message: str, key_pairs: dict) -> Input:
|
||||||
"""Signs a Ed25519Fulfillment.
|
"""Signs a Ed25519Fulfillment.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -429,14 +430,14 @@ class Transaction(object):
|
|||||||
# this should never happen, but then again, never say never.
|
# this should never happen, but then again, never say never.
|
||||||
input_ = deepcopy(input_)
|
input_ = deepcopy(input_)
|
||||||
public_key = input_.owners_before[0]
|
public_key = input_.owners_before[0]
|
||||||
message = sha3_256(message.encode())
|
sha3_message = sha3_256(message.encode())
|
||||||
if input_.fulfills:
|
if input_.fulfills:
|
||||||
message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
sha3_message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# cryptoconditions makes no assumptions of the encoding of the
|
# cryptoconditions makes no assumptions of the encoding of the
|
||||||
# message to sign or verify. It only accepts bytestrings
|
# message to sign or verify. It only accepts bytestrings
|
||||||
input_.fulfillment.sign(message.digest(), base58.b58decode(key_pairs[public_key].encode()))
|
input_.fulfillment.sign(sha3_message.digest(), base58.b58decode(key_pairs[public_key].encode()))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeypairMismatchException(
|
raise KeypairMismatchException(
|
||||||
"Public key {} is not a pair to " "any of the private keys".format(public_key)
|
"Public key {} is not a pair to " "any of the private keys".format(public_key)
|
||||||
@ -444,7 +445,7 @@ class Transaction(object):
|
|||||||
return input_
|
return input_
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _sign_threshold_signature_fulfillment(cls, input_, message, key_pairs):
|
def _sign_threshold_signature_fulfillment(cls, input_: Input, message: str, key_pairs: dict) -> Input:
|
||||||
"""Signs a ThresholdSha256.
|
"""Signs a ThresholdSha256.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -454,9 +455,9 @@ class Transaction(object):
|
|||||||
key_pairs (dict): The keys to sign the Transaction with.
|
key_pairs (dict): The keys to sign the Transaction with.
|
||||||
"""
|
"""
|
||||||
input_ = deepcopy(input_)
|
input_ = deepcopy(input_)
|
||||||
message = sha3_256(message.encode())
|
sha3_message = sha3_256(message.encode())
|
||||||
if input_.fulfills:
|
if input_.fulfills:
|
||||||
message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
sha3_message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
||||||
|
|
||||||
for owner_before in set(input_.owners_before):
|
for owner_before in set(input_.owners_before):
|
||||||
# TODO: CC should throw a KeypairMismatchException, instead of
|
# TODO: CC should throw a KeypairMismatchException, instead of
|
||||||
@ -484,10 +485,10 @@ class Transaction(object):
|
|||||||
# cryptoconditions makes no assumptions of the encoding of the
|
# cryptoconditions makes no assumptions of the encoding of the
|
||||||
# message to sign or verify. It only accepts bytestrings
|
# message to sign or verify. It only accepts bytestrings
|
||||||
for subffill in subffills:
|
for subffill in subffills:
|
||||||
subffill.sign(message.digest(), base58.b58decode(private_key.encode()))
|
subffill.sign(sha3_message.digest(), base58.b58decode(private_key.encode()))
|
||||||
return input_
|
return input_
|
||||||
|
|
||||||
def inputs_valid(self, outputs=None):
|
def inputs_valid(self, outputs=None) -> bool:
|
||||||
"""Validates the Inputs in the Transaction against given
|
"""Validates the Inputs in the Transaction against given
|
||||||
Outputs.
|
Outputs.
|
||||||
|
|
||||||
@ -520,7 +521,7 @@ class Transaction(object):
|
|||||||
allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
|
allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
|
||||||
raise TypeError("`operation` must be one of {}".format(allowed_ops))
|
raise TypeError("`operation` must be one of {}".format(allowed_ops))
|
||||||
|
|
||||||
def _inputs_valid(self, output_condition_uris):
|
def _inputs_valid(self, output_condition_uris: list[str]) -> bool:
|
||||||
"""Validates an Input against a given set of Outputs.
|
"""Validates an Input against a given set of Outputs.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -550,7 +551,7 @@ class Transaction(object):
|
|||||||
return all(validate(i, cond) for i, cond in enumerate(output_condition_uris))
|
return all(validate(i, cond) for i, cond in enumerate(output_condition_uris))
|
||||||
|
|
||||||
@lru_cache(maxsize=16384)
|
@lru_cache(maxsize=16384)
|
||||||
def _input_valid(self, input_, operation, message, output_condition_uri=None):
|
def _input_valid(self, input_: Input, operation: str, message: str, output_condition_uri: Optional[str] = None) -> bool:
|
||||||
"""Validates a single Input against a single Output.
|
"""Validates a single Input against a single Output.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -601,16 +602,16 @@ class Transaction(object):
|
|||||||
msg = json.loads(message)
|
msg = json.loads(message)
|
||||||
ffill_valid = parsed_ffill.validate(message=json.dumps(msg["script"]))
|
ffill_valid = parsed_ffill.validate(message=json.dumps(msg["script"]))
|
||||||
else:
|
else:
|
||||||
message = sha3_256(message.encode())
|
sha3_message = sha3_256(message.encode())
|
||||||
if input_.fulfills:
|
if input_.fulfills:
|
||||||
message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
sha3_message.update("{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode())
|
||||||
|
|
||||||
# NOTE: We pass a timestamp to `.validate`, as in case of a timeout
|
# NOTE: We pass a timestamp to `.validate`, as in case of a timeout
|
||||||
# condition we'll have to validate against it
|
# condition we'll have to validate against it
|
||||||
|
|
||||||
# cryptoconditions makes no assumptions of the encoding of the
|
# cryptoconditions makes no assumptions of the encoding of the
|
||||||
# message to sign or verify. It only accepts bytestrings
|
# message to sign or verify. It only accepts bytestrings
|
||||||
ffill_valid = parsed_ffill.validate(message=message.digest())
|
ffill_valid = parsed_ffill.validate(message=sha3_message.digest())
|
||||||
return output_valid and ffill_valid
|
return output_valid and ffill_valid
|
||||||
|
|
||||||
# This function is required by `lru_cache` to create a key for memoization
|
# This function is required by `lru_cache` to create a key for memoization
|
||||||
@ -618,7 +619,7 @@ class Transaction(object):
|
|||||||
return hash(self.id)
|
return hash(self.id)
|
||||||
|
|
||||||
@memoize_to_dict
|
@memoize_to_dict
|
||||||
def to_dict(self):
|
def to_dict(self) -> dict:
|
||||||
"""Transforms the object to a Python dictionary.
|
"""Transforms the object to a Python dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -639,7 +640,7 @@ class Transaction(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
# TODO: Remove `_dict` prefix of variable.
|
# TODO: Remove `_dict` prefix of variable.
|
||||||
def _remove_signatures(tx_dict):
|
def _remove_signatures(tx_dict: dict) -> dict:
|
||||||
"""Takes a Transaction dictionary and removes all signatures.
|
"""Takes a Transaction dictionary and removes all signatures.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -716,7 +717,7 @@ class Transaction(object):
|
|||||||
return asset_ids.pop()
|
return asset_ids.pop()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_id(tx_body):
|
def validate_id(tx_body: dict):
|
||||||
"""Validate the transaction ID of a transaction
|
"""Validate the transaction ID of a transaction
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -741,7 +742,7 @@ class Transaction(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@memoize_from_dict
|
@memoize_from_dict
|
||||||
def from_dict(cls, tx, skip_schema_validation=True):
|
def from_dict(cls, tx: dict, skip_schema_validation=True):
|
||||||
"""Transforms a Python dictionary to a Transaction object.
|
"""Transforms a Python dictionary to a Transaction object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -848,7 +849,7 @@ class Transaction(object):
|
|||||||
tx = list(tx_map.values())[0]
|
tx = list(tx_map.values())[0]
|
||||||
return cls.from_dict(tx)
|
return cls.from_dict(tx)
|
||||||
|
|
||||||
type_registry = {}
|
type_registry: dict[type, type] = {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def register_type(tx_type, tx_class):
|
def register_type(tx_type, tx_class):
|
||||||
|
|||||||
@ -4,6 +4,9 @@
|
|||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
|
||||||
class TransactionLink(object):
|
class TransactionLink(object):
|
||||||
"""An object for unidirectional linking to a Transaction's Output.
|
"""An object for unidirectional linking to a Transaction's Output.
|
||||||
|
|
||||||
@ -13,7 +16,7 @@ class TransactionLink(object):
|
|||||||
`txid`.
|
`txid`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, txid=None, output=None):
|
def __init__(self, txid: Optional[str] = None, output: Optional[int] = None):
|
||||||
"""Create an instance of a :class:`~.TransactionLink`.
|
"""Create an instance of a :class:`~.TransactionLink`.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -42,7 +45,7 @@ class TransactionLink(object):
|
|||||||
return hash((self.txid, self.output))
|
return hash((self.txid, self.output))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, link):
|
def from_dict(cls, link: dict):
|
||||||
"""Transforms a Python dictionary to a TransactionLink object.
|
"""Transforms a Python dictionary to a TransactionLink object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -56,7 +59,7 @@ class TransactionLink(object):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> Union[dict,None]:
|
||||||
"""Transforms the object to a Python dictionary.
|
"""Transforms the object to a Python dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -70,7 +73,7 @@ class TransactionLink(object):
|
|||||||
"output_index": self.output,
|
"output_index": self.output,
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_uri(self, path=""):
|
def to_uri(self, path: str = "") -> Union[str,None]:
|
||||||
if self.txid is None and self.output is None:
|
if self.txid is None and self.output is None:
|
||||||
return None
|
return None
|
||||||
return "{}/transactions/{}/outputs/{}".format(path, self.txid, self.output)
|
return "{}/transactions/{}/outputs/{}".format(path, self.txid, self.output)
|
||||||
|
|||||||
@ -7,15 +7,16 @@ import base58
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import rapidjson
|
import rapidjson
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
from planetmint.config import Config
|
from planetmint.config import Config
|
||||||
from planetmint.transactions.common.exceptions import ValidationError
|
from planetmint.transactions.common.exceptions import ValidationError
|
||||||
from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256
|
from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256, Fulfillment
|
||||||
from planetmint.transactions.common.exceptions import ThresholdTooDeep
|
from planetmint.transactions.common.exceptions import ThresholdTooDeep
|
||||||
from cryptoconditions.exceptions import UnsupportedTypeError
|
from cryptoconditions.exceptions import UnsupportedTypeError
|
||||||
|
|
||||||
|
|
||||||
def gen_timestamp():
|
def gen_timestamp() -> str:
|
||||||
"""The Unix time, rounded to the nearest second.
|
"""The Unix time, rounded to the nearest second.
|
||||||
See https://en.wikipedia.org/wiki/Unix_time
|
See https://en.wikipedia.org/wiki/Unix_time
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ def gen_timestamp():
|
|||||||
return str(round(time.time()))
|
return str(round(time.time()))
|
||||||
|
|
||||||
|
|
||||||
def serialize(data):
|
def serialize(data: dict) -> str:
|
||||||
"""Serialize a dict into a JSON formatted string.
|
"""Serialize a dict into a JSON formatted string.
|
||||||
|
|
||||||
This function enforces rules like the separator and order of keys.
|
This function enforces rules like the separator and order of keys.
|
||||||
@ -46,7 +47,7 @@ def serialize(data):
|
|||||||
return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True)
|
return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
def deserialize(data):
|
def deserialize(data: str) -> dict:
|
||||||
"""Deserialize a JSON formatted string into a dict.
|
"""Deserialize a JSON formatted string into a dict.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -59,7 +60,7 @@ def deserialize(data):
|
|||||||
return rapidjson.loads(data)
|
return rapidjson.loads(data)
|
||||||
|
|
||||||
|
|
||||||
def validate_txn_obj(obj_name, obj, key, validation_fun):
|
def validate_txn_obj(obj_name: str, obj: dict, key: str, validation_fun: Callable) -> None:
|
||||||
"""Validate value of `key` in `obj` using `validation_fun`.
|
"""Validate value of `key` in `obj` using `validation_fun`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -85,7 +86,7 @@ def validate_txn_obj(obj_name, obj, key, validation_fun):
|
|||||||
validate_all_items_in_list(obj_name, data, validation_fun)
|
validate_all_items_in_list(obj_name, data, validation_fun)
|
||||||
|
|
||||||
|
|
||||||
def validate_all_items_in_list(obj_name, data, validation_fun):
|
def validate_all_items_in_list(obj_name: str, data: list, validation_fun: Callable) -> None:
|
||||||
for item in data:
|
for item in data:
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
validate_all_keys_in_obj(obj_name, item, validation_fun)
|
validate_all_keys_in_obj(obj_name, item, validation_fun)
|
||||||
@ -93,7 +94,7 @@ def validate_all_items_in_list(obj_name, data, validation_fun):
|
|||||||
validate_all_items_in_list(obj_name, item, validation_fun)
|
validate_all_items_in_list(obj_name, item, validation_fun)
|
||||||
|
|
||||||
|
|
||||||
def validate_all_keys_in_obj(obj_name, obj, validation_fun):
|
def validate_all_keys_in_obj(obj_name: str, obj: dict, validation_fun: Callable) -> None:
|
||||||
"""Validate all (nested) keys in `obj` by using `validation_fun`.
|
"""Validate all (nested) keys in `obj` by using `validation_fun`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -116,7 +117,7 @@ def validate_all_keys_in_obj(obj_name, obj, validation_fun):
|
|||||||
validate_all_items_in_list(obj_name, value, validation_fun)
|
validate_all_items_in_list(obj_name, value, validation_fun)
|
||||||
|
|
||||||
|
|
||||||
def validate_all_values_for_key_in_obj(obj, key, validation_fun):
|
def validate_all_values_for_key_in_obj(obj: dict, key: str, validation_fun: Callable) -> None:
|
||||||
"""Validate value for all (nested) occurrence of `key` in `obj`
|
"""Validate value for all (nested) occurrence of `key` in `obj`
|
||||||
using `validation_fun`.
|
using `validation_fun`.
|
||||||
|
|
||||||
@ -138,7 +139,7 @@ def validate_all_values_for_key_in_obj(obj, key, validation_fun):
|
|||||||
validate_all_values_for_key_in_list(value, key, validation_fun)
|
validate_all_values_for_key_in_list(value, key, validation_fun)
|
||||||
|
|
||||||
|
|
||||||
def validate_all_values_for_key_in_list(input_list, key, validation_fun):
|
def validate_all_values_for_key_in_list(input_list: list, key: str, validation_fun: Callable) -> None:
|
||||||
for item in input_list:
|
for item in input_list:
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
validate_all_values_for_key_in_obj(item, key, validation_fun)
|
validate_all_values_for_key_in_obj(item, key, validation_fun)
|
||||||
@ -146,7 +147,7 @@ def validate_all_values_for_key_in_list(input_list, key, validation_fun):
|
|||||||
validate_all_values_for_key_in_list(item, key, validation_fun)
|
validate_all_values_for_key_in_list(item, key, validation_fun)
|
||||||
|
|
||||||
|
|
||||||
def validate_key(obj_name, key):
|
def validate_key(obj_name: str, key: str) -> None:
|
||||||
"""Check if `key` contains ".", "$" or null characters.
|
"""Check if `key` contains ".", "$" or null characters.
|
||||||
|
|
||||||
https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
|
https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
|
||||||
@ -170,7 +171,7 @@ def validate_key(obj_name, key):
|
|||||||
raise ValidationError(error_str)
|
raise ValidationError(error_str)
|
||||||
|
|
||||||
|
|
||||||
def _fulfillment_to_details(fulfillment):
|
def _fulfillment_to_details(fulfillment: type[Fulfillment]) -> dict:
|
||||||
"""Encode a fulfillment as a details dictionary
|
"""Encode a fulfillment as a details dictionary
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -201,7 +202,7 @@ def _fulfillment_to_details(fulfillment):
|
|||||||
raise UnsupportedTypeError(fulfillment.type_name)
|
raise UnsupportedTypeError(fulfillment.type_name)
|
||||||
|
|
||||||
|
|
||||||
def _fulfillment_from_details(data, _depth=0):
|
def _fulfillment_from_details(data: dict, _depth: int = 0):
|
||||||
"""Load a fulfillment for a signing spec dictionary
|
"""Load a fulfillment for a signing spec dictionary
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@ -3,11 +3,10 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
from cid import is_cid
|
from cid import is_cid
|
||||||
|
|
||||||
from planetmint.transactions.common.transaction import Transaction
|
from planetmint.transactions.common.transaction import Transaction
|
||||||
from planetmint.transactions.common.input import Input
|
|
||||||
from planetmint.transactions.common.output import Output
|
|
||||||
|
|
||||||
|
|
||||||
class Create(Transaction):
|
class Create(Transaction):
|
||||||
@ -16,7 +15,7 @@ class Create(Transaction):
|
|||||||
ALLOWED_OPERATIONS = (OPERATION,)
|
ALLOWED_OPERATIONS = (OPERATION,)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_create(self, tx_signers, recipients, asset, metadata):
|
def validate_create(self, tx_signers: list[str], recipients: list[tuple[list[str],int]], asset: Optional[dict], metadata: Optional[dict]):
|
||||||
if not isinstance(tx_signers, list):
|
if not isinstance(tx_signers, list):
|
||||||
raise TypeError("`tx_signers` must be a list instance")
|
raise TypeError("`tx_signers` must be a list instance")
|
||||||
if not isinstance(recipients, list):
|
if not isinstance(recipients, list):
|
||||||
@ -36,7 +35,7 @@ class Create(Transaction):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, tx_signers, recipients, metadata=None, asset=None):
|
def generate(cls, tx_signers: list[str], recipients: list[tuple[list[str],int]], metadata: Optional[dict] = None, asset: Optional[dict] = None):
|
||||||
"""A simple way to generate a `CREATE` transaction.
|
"""A simple way to generate a `CREATE` transaction.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|||||||
@ -3,7 +3,10 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from planetmint.transactions.common.transaction import Transaction
|
from planetmint.transactions.common.transaction import Transaction
|
||||||
|
from planetmint.transactions.common.input import Input
|
||||||
from planetmint.transactions.common.output import Output
|
from planetmint.transactions.common.output import Output
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
@ -14,7 +17,7 @@ class Transfer(Transaction):
|
|||||||
ALLOWED_OPERATIONS = (OPERATION,)
|
ALLOWED_OPERATIONS = (OPERATION,)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_transfer(cls, inputs, recipients, asset_id, metadata):
|
def validate_transfer(cls, inputs: list[Input], recipients: list[tuple[list[str],int]], asset_id: str, metadata: Optional[dict]):
|
||||||
if not isinstance(inputs, list):
|
if not isinstance(inputs, list):
|
||||||
raise TypeError("`inputs` must be a list instance")
|
raise TypeError("`inputs` must be a list instance")
|
||||||
if len(inputs) == 0:
|
if len(inputs) == 0:
|
||||||
@ -39,7 +42,7 @@ class Transfer(Transaction):
|
|||||||
return (deepcopy(inputs), outputs)
|
return (deepcopy(inputs), outputs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, inputs, recipients, asset_id, metadata=None):
|
def generate(cls, inputs: list[Input], recipients: list[tuple[list[str],int]], asset_id: str, metadata: Optional[dict] = None):
|
||||||
"""A simple way to generate a `TRANSFER` transaction.
|
"""A simple way to generate a `TRANSFER` transaction.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
import base58
|
import base58
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from planetmint import backend
|
from planetmint import backend
|
||||||
from planetmint.transactions.types.assets.create import Create
|
from planetmint.transactions.types.assets.create import Create
|
||||||
@ -32,13 +33,13 @@ class Election(Transaction):
|
|||||||
set to (OPERATION,), CREATE set to OPERATION.
|
set to (OPERATION,), CREATE set to OPERATION.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
OPERATION = None
|
OPERATION: Optional[str] = None
|
||||||
# Custom validation schema
|
# Custom validation schema
|
||||||
TX_SCHEMA_CUSTOM = None
|
TX_SCHEMA_CUSTOM = None
|
||||||
# Election Statuses:
|
# Election Statuses:
|
||||||
ONGOING = "ongoing"
|
ONGOING: str = "ongoing"
|
||||||
CONCLUDED = "concluded"
|
CONCLUDED: str = "concluded"
|
||||||
INCONCLUSIVE = "inconclusive"
|
INCONCLUSIVE: str = "inconclusive"
|
||||||
# Vote ratio to approve an election
|
# Vote ratio to approve an election
|
||||||
ELECTION_THRESHOLD = 2 / 3
|
ELECTION_THRESHOLD = 2 / 3
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user