Adjust fulfillment (de)serialization

This commit is contained in:
tim 2016-09-14 13:46:17 +02:00 committed by Sylvain Bellemare
parent 331150b9c2
commit 2ae5d49783
2 changed files with 54 additions and 58 deletions

View File

@ -46,7 +46,15 @@ class Fulfillment(object):
try: try:
fulfillment = self.fulfillment.serialize_uri() fulfillment = self.fulfillment.serialize_uri()
except (TypeError, AttributeError): except (TypeError, AttributeError):
fulfillment = None # NOTE: When a non-signed transaction is casted to a dict,
# `self.fulfillments` value is lost, as in the node's
# transaction model that is saved to the database, does not
# account for its dictionary form but just for its signed uri
# form.
# Hence, when a non-signed fulfillment is to be cast to a
# dict, we just call its internal `to_dict` method here and
# its `from_dict` method in `Fulfillment.from_dict`.
fulfillment = self.fulfillment.to_dict()
try: try:
# NOTE: `self.tx_input` can be `None` and that's fine # NOTE: `self.tx_input` can be `None` and that's fine
@ -70,13 +78,16 @@ class Fulfillment(object):
try: try:
fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) fulfillment = CCFulfillment.from_uri(ffill['fulfillment'])
except TypeError: except TypeError:
fulfillment = None # NOTE: See comment about this special case in
# `Fulfillment.to_dict`
fulfillment = CCFulfillment.from_dict(ffill['fulfillment'])
return cls(fulfillment, ffill['owners_before'], TransactionLink.from_dict(ffill['input'])) return cls(fulfillment, ffill['owners_before'], TransactionLink.from_dict(ffill['input']))
class TransactionLink(object): class TransactionLink(object):
# NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an # NOTE: In an IPLD implementation, this class is not necessary anymore,
# object, as well as an objects properties. So instead of having a (de)serializable class, we can have a # as an IPLD link can simply point to an object, as well as an objects
# properties. So instead of having a (de)serializable class, we can have a
# simple IPLD link of the form: `/<tx_id>/transaction/conditions/<cid>/` # simple IPLD link of the form: `/<tx_id>/transaction/conditions/<cid>/`
def __init__(self, txid=None, cid=None): def __init__(self, txid=None, cid=None):
self.txid = txid self.txid = txid
@ -111,8 +122,8 @@ class Condition(object):
"""Create a new condition for a fulfillment """Create a new condition for a fulfillment
Args Args
owners_after (Optional(list)): base58 encoded public key of the owner of the digital asset after owners_after (Optional(list)): base58 encoded public key of the
this transaction. owner of the digital asset after this transaction.
""" """
self.fulfillment = fulfillment self.fulfillment = fulfillment
@ -234,48 +245,9 @@ class Transaction(object):
ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS) ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS)
VERSION = 1 VERSION = 1
def __init__(self, operation, fulfillments=None, conditions=None, data=None, timestamp=None, version=None): def __init__(self, operation, fulfillments=None, conditions=None,
data=None, timestamp=None, version=None):
# TODO: Update this comment # TODO: Update this comment
"""Create a new transaction in memory
A transaction in BigchainDB is a transfer of a digital asset between two entities represented
by public keys.
Currently BigchainDB supports two types of operations:
`CREATE` - Only federation nodes are allowed to use this operation. In a create operation
a federation node creates a digital asset in BigchainDB and assigns that asset to a public
key. The owner of the private key can then decided to transfer this digital asset by using the
`transaction id` of the transaction as an input in a `TRANSFER` transaction.
`TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities.
If a transaction is initialized with the inputs being `None` a `operation` `CREATE` is
chosen. Otherwise the transaction is of `operation` `TRANSFER`.
Args:
# TODO: Write a description here
fulfillments
conditions
operation
data (Optional[dict]): dictionary with information about asset.
Raises:
TypeError: if the optional ``data`` argument is not a ``dict``.
# TODO: Incorporate this text somewhere better in the docs of this class
Some use cases for this class:
1. Create a new `CREATE` transaction:
- This means `inputs` is empty
2. Create a new `TRANSFER` transaction:
- This means `inputs` is a filled list (one to multiple transactions)
3. Written transactions must be managed somehow in the user's program: use `from_dict`
"""
self.timestamp = timestamp if timestamp is not None else gen_timestamp() self.timestamp = timestamp if timestamp is not None else gen_timestamp()
self.version = version if version is not None else Transaction.VERSION self.version = version if version is not None else Transaction.VERSION
@ -397,7 +369,11 @@ class Transaction(object):
return cls(cls.TRANSFER, inputs, conditions, data) return cls(cls.TRANSFER, inputs, conditions, data)
def __eq__(self, other): def __eq__(self, other):
return self.to_dict() == other.to_dict() try:
other = other.to_dict()
except AttributeError:
return False
return self.to_dict() == other
def to_inputs(self, condition_indices=None): def to_inputs(self, condition_indices=None):
inputs = [] inputs = []
@ -428,6 +404,8 @@ class Transaction(object):
self.conditions.append(condition) self.conditions.append(condition)
def sign(self, private_keys): def sign(self, private_keys):
# TODO: Singing should be possible with at least one of all private
# keys supplied to this method.
""" Signs a transaction """ Signs a transaction
Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API to the outside. Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API to the outside.
""" """
@ -460,6 +438,9 @@ class Transaction(object):
self._sign_simple_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) self._sign_simple_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs)
elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment):
self._sign_threshold_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) self._sign_threshold_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs)
else:
raise ValueError("Fulfillment couldn't be matched to "
'Cryptocondition fulfillment type.')
def _sign_simple_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): def _sign_simple_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs):
# NOTE: To eliminate the dangers of accidentially signing a condition by reference, # NOTE: To eliminate the dangers of accidentially signing a condition by reference,
@ -574,12 +555,14 @@ class Transaction(object):
@staticmethod @staticmethod
def _remove_signatures(tx_dict): def _remove_signatures(tx_dict):
# NOTE: We remove the reference since we need `tx_dict` only for the transaction's hash # NOTE: We remove the reference since we need `tx_dict` only for the
# transaction's hash
tx_dict = deepcopy(tx_dict) tx_dict = deepcopy(tx_dict)
for fulfillment in tx_dict['transaction']['fulfillments']: for fulfillment in tx_dict['transaction']['fulfillments']:
# NOTE: Not all Cryptoconditions return a `signature` key (e.g. ThresholdSha256Fulfillment), so setting it # NOTE: Not all Cryptoconditions return a `signature` key (e.g.
# to `None` in any case could yield incorrect signatures. This is why we only set it to `None` if # ThresholdSha256Fulfillment), so setting it to `None` in any case
# it's set in the dict. # could yield incorrect signatures. This is why we only set it to
# `None` if it's set in the dict.
fulfillment['fulfillment'] = None fulfillment['fulfillment'] = None
return tx_dict return tx_dict
@ -599,7 +582,8 @@ class Transaction(object):
return serialize(value) return serialize(value)
def __str__(self): def __str__(self):
return Transaction._to_str(self.to_dict()) tx = Transaction._remove_signatures(self.to_dict())
return Transaction._to_str(tx)
@classmethod @classmethod
# TODO: Make this method more pretty # TODO: Make this method more pretty

12
util.py
View File

@ -31,3 +31,15 @@ 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):
"""Deserialize a JSON formatted string into a dict.
Args:
data (str): JSON formatted string.
Returns:
dict: dict resulting from the serialization of a JSON formatted string.
"""
return rapidjson.loads(data)