This is an automated email from the ASF dual-hosted git repository.
aroy pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/incubator-resilientdb-python-sdk.git
The following commit(s) were added to refs/heads/main by this push:
new a0d19ce Format with black line-length 88
a0d19ce is described below
commit a0d19cee919f3e44946a23f18cc97fab98c76e48
Author: AlphaRoy14 <[email protected]>
AuthorDate: Thu Oct 3 00:48:15 2024 -0400
Format with black
line-length 88
---
resdb_driver/connection.py | 11 +-
resdb_driver/crypto.py | 7 +-
resdb_driver/driver.py | 21 +-
resdb_driver/driver_experiment.py | 6 +-
resdb_driver/exceptions.py | 6 +-
resdb_driver/offchain.py | 32 +-
resdb_driver/pool.py | 9 +-
resdb_driver/transaction.py | 62 +-
resdb_driver/transport.py | 9 +-
resdb_driver/utils.py | 12 +-
resdb_driver/validate.py | 9 +-
service/pybind_sample/endpoint_test.py | 4 +-
service/pybind_sample/print_sample.py | 2 +-
service/pybind_sample/validator_example.py | 16 +-
service/sdk_validator/resdb_validator/__init__.py | 4 +-
service/sdk_validator/resdb_validator/crypto.py | 27 +-
.../sdk_validator/resdb_validator/exceptions.py | 8 +-
service/sdk_validator/resdb_validator/lib.py | 176 ++--
service/sdk_validator/resdb_validator/memoize.py | 14 +-
service/sdk_validator/resdb_validator/models.py | 33 +-
.../sdk_validator/resdb_validator/transaction.py | 1049 +++++++++++---------
service/sdk_validator/resdb_validator/utils.py | 127 +--
service/sdk_validator/validator.py | 58 +-
test_driver.py | 4 +-
test_driver_2.py | 3 +-
test_update_metadata.py | 19 +-
26 files changed, 914 insertions(+), 814 deletions(-)
diff --git a/resdb_driver/connection.py b/resdb_driver/connection.py
index a99ff41..a42c6eb 100644
--- a/resdb_driver/connection.py
+++ b/resdb_driver/connection.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
import time
@@ -42,7 +42,7 @@ class Connection:
@param node_url (str): Url of the node to connect to.
@param headers (dict): Optional headers to send with each request.
- @return An instance of the Connection class
+ @return An instance of the Connection class
"""
self.node_url = node_url
self.session = Session()
@@ -115,8 +115,7 @@ class Connection:
connExc = err
raise err
finally:
- self.update_backoff_time(
- success=connExc is None, backoff_cap=backoff_cap)
+ self.update_backoff_time(success=connExc is None,
backoff_cap=backoff_cap)
return response
def get_backoff_timedelta(self) -> float:
diff --git a/resdb_driver/crypto.py b/resdb_driver/crypto.py
index 28ddaec..7f656e8 100644
--- a/resdb_driver/crypto.py
+++ b/resdb_driver/crypto.py
@@ -5,16 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
-
+# under the License.
from collections import namedtuple
diff --git a/resdb_driver/driver.py b/resdb_driver/driver.py
index e7af548..b76880d 100644
--- a/resdb_driver/driver.py
+++ b/resdb_driver/driver.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
from crypt import methods
@@ -71,8 +71,7 @@ class Resdb:
@property
def nodes(self):
- """! :obj:`tuple` of :obj:`str`: URLs of connected nodes.
- """
+ """! :obj:`tuple` of :obj:`str`: URLs of connected nodes."""
return self._nodes
@property
@@ -111,7 +110,7 @@ class Resdb:
def metadata(self):
"""! :class:`~resdb_driver.driver.MetadataEndpoint`:
Exposes functionalities of the ``'/metadata'`` endpoint.
- TODO: check
+ TODO: check
"""
return self._metadata
@@ -142,7 +141,7 @@ class Resdb:
"""! Retrieves information provided by the API root endpoint
``'/api/v1'``.
- TODO: implement the endpoint in the node
+ TODO: implement the endpoint in the node
@param headers (dict): Optional headers to pass to the request.
@@ -222,7 +221,7 @@ class TransactionsEndpoint(NamespacedDriver):
operations. Defaults to ``None``.
@param recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
One or more public keys representing the new recipients(s)
of the asset being created or transferred. Defaults to
``None``.
- @param asset (:obj:`list` | :obj:`tuple` | :obj:`str`, optional): The
asset to be created or transferred. MUST be supplied
+ @param asset (:obj:`list` | :obj:`tuple` | :obj:`str`, optional): The
asset to be created or transferred. MUST be supplied
for ``'TRANSFER'`` operations. Defaults to ``None``.
@param metadata (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
Metadata associated with the transaction. Defaults to ``None``.
@param inputs (:obj:`dict` | :obj:`list` | :obj:`tuple`, optional):
One or more inputs holding the condition(s) that this
@@ -285,10 +284,10 @@ class TransactionsEndpoint(NamespacedDriver):
"""! Fulfills the given transaction.
@param transaction (dict): The transaction to be fulfilled.
- @param private_keys (:obj:`str` | :obj:`list` | :obj:`tuple`): One or
more private keys to be
+ @param private_keys (:obj:`str` | :obj:`list` | :obj:`tuple`): One or
more private keys to be
used for fulfilling the transaction.
- @return The fulfilled transaction payload, ready to
+ @return The fulfilled transaction payload, ready to
be sent to a Resdb federation.
@exception :exc:`~.exceptions.MissingPrivateKeyError`: If a private
@@ -489,7 +488,7 @@ class BlocksEndpoint(NamespacedDriver):
class AssetsEndpoint(NamespacedDriver):
"""! Exposes functionality of the ``'/assets'`` endpoint.
- path (str): The path of the endpoint.
+ path (str): The path of the endpoint.
"""
PATH = "/assets/"
diff --git a/resdb_driver/driver_experiment.py
b/resdb_driver/driver_experiment.py
index fb43ef2..6fe777a 100644
--- a/resdb_driver/driver_experiment.py
+++ b/resdb_driver/driver_experiment.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
#%%[markdown]
diff --git a/resdb_driver/exceptions.py b/resdb_driver/exceptions.py
index b59e1ad..2636840 100644
--- a/resdb_driver/exceptions.py
+++ b/resdb_driver/exceptions.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
class ResdbException(Exception):
diff --git a/resdb_driver/offchain.py b/resdb_driver/offchain.py
index 2824393..8d9190f 100644
--- a/resdb_driver/offchain.py
+++ b/resdb_driver/offchain.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
"""
@@ -76,19 +76,19 @@ def prepare_transaction(
@param operation (str): The operation to perform. Must be ``'CREATE'``
or ``'TRANSFER'``. Case insensitive. Defaults to ``'CREATE'``.
- @param signers (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
+ @param signers (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
One or more public keys representing the issuer(s) of
the asset being created. Only applies for ``'CREATE'``
operations. Defaults to ``None``.
- @param recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
+ @param recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
One or more public keys representing the new recipients(s)
of the asset being created or transferred.
Defaults to ``None``.
- @param asset (:obj:`dict`, optional):
- The asset to be created orctransferred.
+ @param asset (:obj:`dict`, optional):
+ The asset to be created orctransferred.
MUST be supplied for ``'TRANSFER'`` operations.
Defaults to ``None``.
- @param metadata (:obj:`dict`, optional):
+ @param metadata (:obj:`dict`, optional):
Metadata associated with the
transaction. Defaults to ``None``.
@param inputs (:obj:`dict` | :obj:`list` | :obj:`tuple`, optional):
@@ -151,11 +151,11 @@ def prepare_create_transaction(*, signers,
recipients=None, asset=None, metadata
"""! Prepares a ``"CREATE"`` transaction payload, ready to be
fulfilled.
- @param signers (:obj:`list` | :obj:`tuple` | :obj:`str`):
- One or more public keys representing
+ @param signers (:obj:`list` | :obj:`tuple` | :obj:`str`):
+ One or more public keys representing
the issuer(s) of the asset being created.
- @param recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
- One or more public keys representing
+ @param recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional):
+ One or more public keys representing
the new recipients(s) of the asset being created. Defaults to
``None``.
@param asset (:obj:`dict`, optional): The asset to be created. Defaults to
``None``.
@param metadata (:obj:`dict`, optional): Metadata associated with the
transaction. Defaults to ``None``.
@@ -208,12 +208,12 @@ def prepare_transfer_transaction(*, inputs, recipients,
asset, metadata=None):
"""! Prepares a ``"TRANSFER"`` transaction payload, ready to be
fulfilled.
- @param inputs (:obj:`dict` | :obj:`list` | :obj:`tuple`):
+ @param inputs (:obj:`dict` | :obj:`list` | :obj:`tuple`):
One or more inputs holding the condition(s) that this
transaction
intends to fulfill. Each input is expected to be a
:obj:`dict`.
- @param recipients (:obj:`str` | :obj:`list` | :obj:`tuple`):
- One or more public keys representing the
+ @param recipients (:obj:`str` | :obj:`list` | :obj:`tuple`):
+ One or more public keys representing the
new recipients(s) of the
asset being transferred.
@param asset (:obj:`dict`): A single-key dictionary holding the ``id``
@@ -327,7 +327,7 @@ def fulfill_transaction(transaction, *, private_keys) ->
dict:
"""! Fulfills the given transaction.
@param transaction The transaction to be fulfilled.
- @param private_keys One or more private keys to be
+ @param private_keys One or more private keys to be
used for fulfilling the transaction.
@return The fulfilled transaction payload, ready to be sent to a
diff --git a/resdb_driver/pool.py b/resdb_driver/pool.py
index 9ff68b7..6a30392 100644
--- a/resdb_driver/pool.py
+++ b/resdb_driver/pool.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
from abc import ABCMeta, abstractmethod
@@ -60,8 +60,7 @@ class RoundRobinPicker(AbstractPicker):
class Pool:
- """! Pool of connections.
- """
+ """! Pool of connections."""
def __init__(self, connections: list[Connection],
picker_class=RoundRobinPicker):
"""! Initializes a :class:`~resdb_driver.pool.Pool` instance.
diff --git a/resdb_driver/transaction.py b/resdb_driver/transaction.py
index 610acf9..47e9343 100644
--- a/resdb_driver/transaction.py
+++ b/resdb_driver/transaction.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
"""!
@@ -190,7 +190,7 @@ def _fulfillment_to_details(fulfillment):
def _fulfillment_from_details(data, _depth=0):
"""! Load a fulfillment for a signing spec dictionary
- @param data tx.output[].condition.details dictionary
+ @param data tx.output[].condition.details dictionary
"""
if _depth == 100:
raise ThresholdTooDeep()
@@ -247,9 +247,9 @@ class TransactionLink(object):
def from_dict(cls, link):
"""! Transforms a Python dictionary to a TransactionLink object.
- @param link (dict): The link to be transformed.
+ @param link (dict): The link to be transformed.
- @return :class:`~resdb.transaction.TransactionLink`
+ @return :class:`~resdb.transaction.TransactionLink`
"""
try:
return cls(link["transaction_id"], link["output_index"])
@@ -258,7 +258,7 @@ class TransactionLink(object):
def to_dict(self):
"""! Transforms the object to a Python dictionary.
- @return The link as an alternative serialization format.
+ @return The link as an alternative serialization format.
"""
if self.txid is None and self.output is None:
return None
@@ -369,20 +369,17 @@ class Output(object):
if not isinstance(public_keys, list):
raise TypeError("`public_keys` must be an instance of list")
if len(public_keys) == 0:
- raise ValueError(
- "`public_keys` needs to contain at least one" "owner")
+ raise ValueError("`public_keys` needs to contain at least one"
"owner")
elif len(public_keys) == 1 and not isinstance(public_keys[0], list):
if isinstance(public_keys[0], Fulfillment):
ffill = public_keys[0]
else:
- ffill = Ed25519Sha256(
- public_key=base58.b58decode(public_keys[0]))
+ ffill =
Ed25519Sha256(public_key=base58.b58decode(public_keys[0]))
return cls(ffill, public_keys, amount=amount)
else:
# Threshold conditions not supported by resdb yet
initial_cond = ThresholdSha256(threshold=threshold)
- threshold_cond = reduce(
- cls._gen_condition, public_keys, initial_cond)
+ threshold_cond = reduce(cls._gen_condition, public_keys,
initial_cond)
return cls(threshold_cond, public_keys, amount=amount)
@classmethod
@@ -422,8 +419,7 @@ class Output(object):
if isinstance(new_public_keys, Fulfillment):
ffill = new_public_keys
else:
- ffill = Ed25519Sha256(
- public_key=base58.b58decode(new_public_keys))
+ ffill =
Ed25519Sha256(public_key=base58.b58decode(new_public_keys))
initial.add_subfulfillment(ffill)
return initial
@@ -440,8 +436,7 @@ class Output(object):
@return :class:`~resdb.transaction.Output`
"""
try:
- fulfillment = _fulfillment_from_details(
- data["condition"]["details"])
+ fulfillment =
_fulfillment_from_details(data["condition"]["details"])
except KeyError:
# NOTE: Hashlock condition case
fulfillment = data["condition"]["uri"]
@@ -503,8 +498,7 @@ class Transaction(object):
"""
if operation not in Transaction.ALLOWED_OPERATIONS:
allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
- raise ValueError(
- "`operation` must be one of {}".format(allowed_ops))
+ raise ValueError("`operation` must be one of
{}".format(allowed_ops))
# Asset payloads for 'CREATE' operations must be None or
# dicts holding a `data` property. Asset payloads for 'TRANSFER'
@@ -845,16 +839,14 @@ class Transaction(object):
message = sha3_256(message.encode())
if input_.fulfills:
message.update(
- "{}{}".format(input_.fulfills.txid,
- input_.fulfills.output).encode()
+ "{}{}".format(input_.fulfills.txid,
input_.fulfills.output).encode()
)
try:
# cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings
input_.fulfillment.sign(
- message.digest(), base58.b58decode(
- key_pairs[public_key].encode())
+ message.digest(),
base58.b58decode(key_pairs[public_key].encode())
)
except KeyError:
raise KeypairMismatchException(
@@ -875,8 +867,7 @@ class Transaction(object):
message = sha3_256(message.encode())
if input_.fulfills:
message.update(
- "{}{}".format(input_.fulfills.txid,
- input_.fulfills.output).encode()
+ "{}{}".format(input_.fulfills.txid,
input_.fulfills.output).encode()
)
for owner_before in set(input_.owners_before):
@@ -890,8 +881,7 @@ class Transaction(object):
# TODO FOR CC: `get_subcondition` is singular. One would not
# expect to get a list back.
ccffill = input_.fulfillment
- subffills = ccffill.get_subcondition_from_vk(
- base58.b58decode(owner_before))
+ subffills =
ccffill.get_subcondition_from_vk(base58.b58decode(owner_before))
if not subffills:
raise KeypairMismatchException(
"Public key {} cannot be found "
@@ -908,8 +898,7 @@ class Transaction(object):
# cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings
for subffill in subffills:
- subffill.sign(message.digest(),
- base58.b58decode(private_key.encode()))
+ subffill.sign(message.digest(),
base58.b58decode(private_key.encode()))
return input_
def inputs_valid(self, outputs=None):
@@ -937,8 +926,7 @@ class Transaction(object):
)
else:
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):
"""!Validates an Input against a given set of Outputs.
@@ -999,8 +987,7 @@ class Transaction(object):
message = sha3_256(message.encode())
if input_.fulfills:
message.update(
- "{}{}".format(input_.fulfills.txid,
- input_.fulfills.output).encode()
+ "{}{}".format(input_.fulfills.txid,
input_.fulfills.output).encode()
)
# NOTE: We pass a timestamp to `.validate`, as in case of a timeout
@@ -1197,8 +1184,7 @@ class Transaction(object):
Transaction.type_registry[tx_type] = tx_class
def resolve_class(operation):
- """! For the given `tx` based on the `operation` key return its
implementation class
- """
+ """! For the given `tx` based on the `operation` key return its
implementation class"""
create_txn_class = Transaction.type_registry.get(Transaction.CREATE)
return Transaction.type_registry.get(operation, create_txn_class)
@@ -1223,15 +1209,13 @@ class Transaction(object):
input_tx = ctxn
if input_tx is None:
- raise InputDoesNotExist(
- "input `{}` doesn't exist".format(input_txid))
+ raise InputDoesNotExist("input `{}` doesn't
exist".format(input_txid))
spent = resdb.get_spent(
input_txid, input_.fulfills.output, current_transactions
)
if spent:
- raise DoubleSpend(
- "input `{}` was already spent".format(input_txid))
+ raise DoubleSpend("input `{}` was already
spent".format(input_txid))
output = input_tx.outputs[input_.fulfills.output]
input_conditions.append(output)
diff --git a/resdb_driver/transport.py b/resdb_driver/transport.py
index ce3f563..4606cd1 100644
--- a/resdb_driver/transport.py
+++ b/resdb_driver/transport.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
from time import time
from requests import request, Response
@@ -29,8 +29,7 @@ NO_TIMEOUT_BACKOFF_CAP = 10 # seconds
class Transport:
- """! Transport class.
- """
+ """! Transport class."""
def __init__(self, *nodes: list, timeout: int = None):
"""! Initializes an instance of
diff --git a/resdb_driver/utils.py b/resdb_driver/utils.py
index c0741d3..d2916cf 100644
--- a/resdb_driver/utils.py
+++ b/resdb_driver/utils.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
from urllib.parse import urlparse, urlunparse
@@ -56,13 +56,11 @@ DEFAULT_NODE = "http://localhost:9984"
class CreateOperation:
- """! Class representing the ``'CREATE'`` transaction operation.
- """
+ """! Class representing the ``'CREATE'`` transaction operation."""
class TransferOperation:
- """! Class representing the ``'TRANSFER'`` transaction operation.
- """
+ """! Class representing the ``'TRANSFER'`` transaction operation."""
ops_map = {
diff --git a/resdb_driver/validate.py b/resdb_driver/validate.py
index a696898..fa62091 100644
--- a/resdb_driver/validate.py
+++ b/resdb_driver/validate.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
# from resdb_driver.backend.schema import validate_language_key
@@ -43,8 +43,7 @@ class Transaction(Transaction):
input_conditions = []
if self.operation == Transaction.CREATE:
- duplicates = any(
- txn for txn in current_transactions if txn.id == self.id)
+ duplicates = any(txn for txn in current_transactions if txn.id ==
self.id)
# TODO check if id already committed
# if resdb.is_committed(self.id) or duplicates:
# raise DuplicateTransaction('transaction `{}` already exists'
diff --git a/service/pybind_sample/endpoint_test.py
b/service/pybind_sample/endpoint_test.py
index 3058bb3..14945ee 100644
--- a/service/pybind_sample/endpoint_test.py
+++ b/service/pybind_sample/endpoint_test.py
@@ -1,8 +1,8 @@
import requests
-print('Example of calling NexRes endpoint from Python')
+print("Example of calling NexRes endpoint from Python")
-url = 'http://localhost:8000/v1/transactions'
+url = "http://localhost:8000/v1/transactions"
response = requests.get(url)
print(response)
diff --git a/service/pybind_sample/print_sample.py
b/service/pybind_sample/print_sample.py
index 0eeea40..24ecaf6 100644
--- a/service/pybind_sample/print_sample.py
+++ b/service/pybind_sample/print_sample.py
@@ -1 +1 @@
-print('Printing from print_sample.py')
+print("Printing from print_sample.py")
diff --git a/service/pybind_sample/validator_example.py
b/service/pybind_sample/validator_example.py
index d2a553b..9c70373 100644
--- a/service/pybind_sample/validator_example.py
+++ b/service/pybind_sample/validator_example.py
@@ -5,17 +5,18 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
import json
+
# sample valid transaction:
# txn = {
# "asset": {
@@ -24,9 +25,9 @@ import json
# "id": "12490124812041",
# }
-# todo: take in string, convert to
+# todo: take in string, convert to
def validate(transaction):
- print(f'Validating {transaction}')
+ print(f"Validating {transaction}")
if (not transaction) or type(transaction) is not dict:
return False
@@ -40,10 +41,9 @@ def validate(transaction):
return False
return True
+
txn = {
- "asset": {
- "id": "ageuiagh93421941a"
- },
+ "asset": {"id": "ageuiagh93421941a"},
"id": "12490124812041",
}
diff --git a/service/sdk_validator/resdb_validator/__init__.py
b/service/sdk_validator/resdb_validator/__init__.py
index 5e0b3b5..29c0a75 100644
--- a/service/sdk_validator/resdb_validator/__init__.py
+++ b/service/sdk_validator/resdb_validator/__init__.py
@@ -1,5 +1,5 @@
-from service.sdk_validator.resdb_validator.transaction import Transaction
-from service.sdk_validator.resdb_validator import models
+from service.sdk_validator.resdb_validator.transaction import Transaction
+from service.sdk_validator.resdb_validator import models
Transaction.register_type(Transaction.CREATE, models.Transaction)
Transaction.register_type(Transaction.TRANSFER, models.Transaction)
diff --git a/service/sdk_validator/resdb_validator/crypto.py
b/service/sdk_validator/resdb_validator/crypto.py
index 13490c7..462b59b 100644
--- a/service/sdk_validator/resdb_validator/crypto.py
+++ b/service/sdk_validator/resdb_validator/crypto.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
# Separate all crypto code so that we can easily test several implementations
from collections import namedtuple
@@ -26,7 +26,7 @@ except ImportError:
from cryptoconditions import crypto
-CryptoKeypair = namedtuple('CryptoKeypair', ('private_key', 'public_key'))
+CryptoKeypair = namedtuple("CryptoKeypair", ("private_key", "public_key"))
def hash_data(data):
@@ -45,8 +45,7 @@ def generate_key_pair():
"""
# TODO FOR CC: Adjust interface so that this function becomes unnecessary
- return CryptoKeypair(
- *(k.decode() for k in crypto.ed25519_generate_key_pair()))
+ return CryptoKeypair(*(k.decode() for k in
crypto.ed25519_generate_key_pair()))
PrivateKey = crypto.Ed25519SigningKey
@@ -55,13 +54,19 @@ PublicKey = crypto.Ed25519VerifyingKey
def key_pair_from_ed25519_key(hex_private_key):
"""Generate base58 encode public-private key pair from a hex encoded
private key"""
- priv_key = crypto.Ed25519SigningKey(bytes.fromhex(hex_private_key)[:32],
encoding='bytes')
+ priv_key = crypto.Ed25519SigningKey(
+ bytes.fromhex(hex_private_key)[:32], encoding="bytes"
+ )
public_key = priv_key.get_verifying_key()
- return
CryptoKeypair(private_key=priv_key.encode(encoding='base58').decode('utf-8'),
-
public_key=public_key.encode(encoding='base58').decode('utf-8'))
+ return CryptoKeypair(
+ private_key=priv_key.encode(encoding="base58").decode("utf-8"),
+ public_key=public_key.encode(encoding="base58").decode("utf-8"),
+ )
def public_key_from_ed25519_key(hex_public_key):
"""Generate base58 public key from hex encoded public key"""
- public_key = crypto.Ed25519VerifyingKey(bytes.fromhex(hex_public_key),
encoding='bytes')
- return public_key.encode(encoding='base58').decode('utf-8')
+ public_key = crypto.Ed25519VerifyingKey(
+ bytes.fromhex(hex_public_key), encoding="bytes"
+ )
+ return public_key.encode(encoding="base58").decode("utf-8")
diff --git a/service/sdk_validator/resdb_validator/exceptions.py
b/service/sdk_validator/resdb_validator/exceptions.py
index f579483..1986f4f 100644
--- a/service/sdk_validator/resdb_validator/exceptions.py
+++ b/service/sdk_validator/resdb_validator/exceptions.py
@@ -5,20 +5,22 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
"""
Custom exceptions used in the `resdb_validator` package.
"""
+
+
class ResDBError(Exception):
"""Base class for ResDB exceptions."""
diff --git a/service/sdk_validator/resdb_validator/lib.py
b/service/sdk_validator/resdb_validator/lib.py
index 235ed3a..c6ed7b9 100644
--- a/service/sdk_validator/resdb_validator/lib.py
+++ b/service/sdk_validator/resdb_validator/lib.py
@@ -22,10 +22,11 @@ except ImportError:
import requests
from resdb_validator.models import Transaction
-from resdb_validator.exceptions import (SchemaValidationError,
- ValidationError,
- DoubleSpend)
-
+from resdb_validator.exceptions import (
+ SchemaValidationError,
+ ValidationError,
+ DoubleSpend,
+)
logger = logging.getLogger(__name__)
@@ -36,7 +37,9 @@ class ResDB(object):
Create, read, sign, write transactions to the database
"""
- backend=None
+
+ backend = None
+
def __init__(self, connection=None):
"""Initialize the ResDB instance
@@ -44,19 +47,21 @@ class ResDB(object):
As of now the validator does not directly interact with any database.
"""
-
def post_transaction(self, transaction, mode):
"""Submit a valid transaction to the mempool."""
if not mode or mode not in self.mode_list:
- raise ValidationError('Mode must be one of the following {}.'
- .format(', '.join(self.mode_list)))
+ raise ValidationError(
+ "Mode must be one of the following {}.".format(
+ ", ".join(self.mode_list)
+ )
+ )
tx_dict = transaction.tx_dict if transaction.tx_dict else
transaction.to_dict()
payload = {
- 'method': mode,
- 'jsonrpc': '2.0',
- 'params': [encode_transaction(tx_dict)],
- 'id': str(uuid4())
+ "method": mode,
+ "jsonrpc": "2.0",
+ "params": [encode_transaction(tx_dict)],
+ "id": str(uuid4()),
}
# TODO: handle connection errors!
return requests.post(self.endpoint, json=payload)
@@ -70,44 +75,47 @@ class ResDB(object):
def _process_post_response(self, response, mode):
logger.debug(response)
- error = response.get('error')
+ error = response.get("error")
if error:
status_code = 500
- message = error.get('message', 'Internal Error')
- data = error.get('data', '')
+ message = error.get("message", "Internal Error")
+ data = error.get("data", "")
- if 'Tx already exists in cache' in data:
+ if "Tx already exists in cache" in data:
status_code = 400
- return (status_code, message + ' - ' + data)
+ return (status_code, message + " - " + data)
- result = response['result']
+ result = response["result"]
if mode == self.mode_commit:
- check_tx_code = result.get('check_tx', {}).get('code', 0)
- deliver_tx_code = result.get('deliver_tx', {}).get('code', 0)
+ check_tx_code = result.get("check_tx", {}).get("code", 0)
+ deliver_tx_code = result.get("deliver_tx", {}).get("code", 0)
error_code = check_tx_code or deliver_tx_code
else:
- error_code = result.get('code', 0)
+ error_code = result.get("code", 0)
if error_code:
- return (500, 'Transaction validation failed')
+ return (500, "Transaction validation failed")
- return (202, '')
+ return (202, "")
def store_bulk_transactions(self, transactions):
txns = []
assets = []
txn_metadatas = []
for t in transactions:
- transaction = t.tx_dict if t.tx_dict else
rapidjson.loads(rapidjson.dumps(t.to_dict()))
- if transaction['operation'] == t.CREATE:
- asset = transaction.pop('asset')
- asset['id'] = transaction['id']
+ transaction = (
+ t.tx_dict
+ if t.tx_dict
+ else rapidjson.loads(rapidjson.dumps(t.to_dict()))
+ )
+ if transaction["operation"] == t.CREATE:
+ asset = transaction.pop("asset")
+ asset["id"] = transaction["id"]
assets.append(asset)
- metadata = transaction.pop('metadata')
- txn_metadatas.append({'id': transaction['id'],
- 'metadata': metadata})
+ metadata = transaction.pop("metadata")
+ txn_metadatas.append({"id": transaction["id"], "metadata":
metadata})
txns.append(transaction)
backend.query.store_metadatas(self.connection, txn_metadatas)
@@ -128,9 +136,7 @@ class ResDB(object):
transaction incoming into the system for which the UTXO
set needs to be updated.
"""
- spent_outputs = [
- spent_output for spent_output in transaction.spent_outputs
- ]
+ spent_outputs = [spent_output for spent_output in
transaction.spent_outputs]
if spent_outputs:
self.delete_unspent_outputs(*spent_outputs)
self.store_unspent_outputs(
@@ -146,7 +152,8 @@ class ResDB(object):
"""
if unspent_outputs:
return backend.query.store_unspent_outputs(
- self.connection, *unspent_outputs)
+ self.connection, *unspent_outputs
+ )
def get_utxoset_merkle_root(self):
"""Returns the merkle root of the utxoset. This implies that
@@ -176,8 +183,9 @@ class ResDB(object):
# See common/transactions.py for details.
hashes = [
sha3_256(
- '{}{}'.format(utxo['transaction_id'],
utxo['output_index']).encode()
- ).digest() for utxo in utxoset
+ "{}{}".format(utxo["transaction_id"],
utxo["output_index"]).encode()
+ ).digest()
+ for utxo in utxoset
]
# TODO Notice the sorted call!
return merkleroot(sorted(hashes))
@@ -200,7 +208,8 @@ class ResDB(object):
"""
if unspent_outputs:
return backend.query.delete_unspent_outputs(
- self.connection, *unspent_outputs)
+ self.connection, *unspent_outputs
+ )
def is_committed(self, transaction_id):
transaction = backend.query.get_transaction(self.connection,
transaction_id)
@@ -213,14 +222,14 @@ class ResDB(object):
asset = backend.query.get_asset(self.connection, transaction_id)
metadata = backend.query.get_metadata(self.connection,
[transaction_id])
if asset:
- transaction['asset'] = asset
+ transaction["asset"] = asset
- if 'metadata' not in transaction:
+ if "metadata" not in transaction:
metadata = metadata[0] if metadata else None
if metadata:
- metadata = metadata.get('metadata')
+ metadata = metadata.get("metadata")
- transaction.update({'metadata': metadata})
+ transaction.update({"metadata": metadata})
transaction = Transaction.from_dict(transaction)
@@ -230,10 +239,10 @@ class ResDB(object):
return backend.query.get_transactions(self.connection, txn_ids)
def get_transactions_filtered(self, asset_id, operation=None,
last_tx=None):
- """Get a list of transactions filtered on some criteria
- """
- txids = backend.query.get_txids_filtered(self.connection, asset_id,
- operation, last_tx)
+ """Get a list of transactions filtered on some criteria"""
+ txids = backend.query.get_txids_filtered(
+ self.connection, asset_id, operation, last_tx
+ )
for txid in txids:
yield self.get_transaction(txid)
@@ -259,20 +268,22 @@ class ResDB(object):
return self.fastquery.filter_spent_outputs(outputs)
def get_spent(self, txid, output, current_transactions=[]):
- transactions = backend.query.get_spent(self.connection, txid,
- output)
+ transactions = backend.query.get_spent(self.connection, txid, output)
transactions = list(transactions) if transactions else []
if len(transactions) > 1:
raise core_exceptions.CriticalDoubleSpend(
- '`{}` was spent more than once. There is a problem'
- ' with the chain'.format(txid))
+ "`{}` was spent more than once. There is a problem"
+ " with the chain".format(txid)
+ )
current_spent_transactions = []
for ctxn in current_transactions:
for ctxn_input in ctxn.inputs:
- if ctxn_input.fulfills and\
- ctxn_input.fulfills.txid == txid and\
- ctxn_input.fulfills.output == output:
+ if (
+ ctxn_input.fulfills
+ and ctxn_input.fulfills.txid == txid
+ and ctxn_input.fulfills.output == output
+ ):
current_spent_transactions.append(ctxn)
transaction = None
@@ -307,17 +318,20 @@ class ResDB(object):
block = backend.query.get_block(self.connection, block_id)
latest_block = self.get_latest_block()
- latest_block_height = latest_block['height'] if latest_block else 0
+ latest_block_height = latest_block["height"] if latest_block else 0
if not block and block_id > latest_block_height:
return
- result = {'height': block_id,
- 'transactions': []}
+ result = {"height": block_id, "transactions": []}
if block:
- transactions = backend.query.get_transactions(self.connection,
block['transactions'])
- result['transactions'] = [t.to_dict() for t in
Transaction.from_db(self, transactions)]
+ transactions = backend.query.get_transactions(
+ self.connection, block["transactions"]
+ )
+ result["transactions"] = [
+ t.to_dict() for t in Transaction.from_db(self, transactions)
+ ]
return result
@@ -333,9 +347,9 @@ class ResDB(object):
"""
blocks =
list(backend.query.get_block_with_transaction(self.connection, txid))
if len(blocks) > 1:
- logger.critical('Transaction id %s exists in multiple blocks',
txid)
+ logger.critical("Transaction id %s exists in multiple blocks",
txid)
- return [block['height'] for block in blocks]
+ return [block["height"] for block in blocks]
def validate_transaction(self, tx, current_transactions=[]):
"""Validate a transaction against the current status of the
database."""
@@ -349,10 +363,10 @@ class ResDB(object):
try:
transaction = Transaction.from_dict(tx)
except SchemaValidationError as e:
- logger.warning('Invalid transaction schema: %s',
e.__cause__.message)
+ logger.warning("Invalid transaction schema: %s",
e.__cause__.message)
return False
except ValidationError as e:
- logger.warning('Invalid transaction (%s): %s',
type(e).__name__, e)
+ logger.warning("Invalid transaction (%s): %s",
type(e).__name__, e)
return False
return transaction.validate(self, current_transactions)
@@ -362,10 +376,10 @@ class ResDB(object):
try:
return self.validate_transaction(tx, current_transactions)
except ValidationError as e:
- logger.warning('Invalid transaction (%s): %s', type(e).__name__, e)
+ logger.warning("Invalid transaction (%s): %s", type(e).__name__, e)
return False
- def text_search(self, search, *, limit=0, table='assets'):
+ def text_search(self, search, *, limit=0, table="assets"):
"""Return an iterator of assets that match the text search
Args:
@@ -375,8 +389,9 @@ class ResDB(object):
Returns:
iter: An iterator of assets that match the text search.
"""
- return backend.query.text_search(self.connection, search, limit=limit,
- table=table)
+ return backend.query.text_search(
+ self.connection, search, limit=limit, table=table
+ )
def get_assets(self, asset_ids):
"""Return a list of assets that match the asset_ids
@@ -411,7 +426,7 @@ class ResDB(object):
def get_validators(self, height=None):
result = self.get_validator_change(height)
- return [] if result is None else result['validators']
+ return [] if result is None else result["validators"]
def get_election(self, election_id):
return backend.query.get_election(self.connection, election_id)
@@ -424,18 +439,20 @@ class ResDB(object):
def store_validator_set(self, height, validators):
"""Store validator set at a given `height`.
- NOTE: If the validator set already exists at that `height` then an
- exception will be raised.
+ NOTE: If the validator set already exists at that `height` then an
+ exception will be raised.
"""
- return backend.query.store_validator_set(self.connection, {'height':
height,
-
'validators': validators})
+ return backend.query.store_validator_set(
+ self.connection, {"height": height, "validators": validators}
+ )
def delete_validator_set(self, height):
return backend.query.delete_validator_set(self.connection, height)
def store_abci_chain(self, height, chain_id, is_synced=True):
- return backend.query.store_abci_chain(self.connection, height,
- chain_id, is_synced)
+ return backend.query.store_abci_chain(
+ self.connection, height, chain_id, is_synced
+ )
def delete_abci_chain(self, height):
return backend.query.delete_abci_chain(self.connection, height)
@@ -460,16 +477,17 @@ class ResDB(object):
block = self.get_latest_block()
- suffix = '-migrated-at-height-'
- chain_id = latest_chain['chain_id']
- block_height_str = str(block['height'])
+ suffix = "-migrated-at-height-"
+ chain_id = latest_chain["chain_id"]
+ block_height_str = str(block["height"])
new_chain_id = chain_id.split(suffix)[0] + suffix + block_height_str
- self.store_abci_chain(block['height'] + 1, new_chain_id, False)
+ self.store_abci_chain(block["height"] + 1, new_chain_id, False)
def store_election(self, election_id, height, is_concluded):
- return backend.query.store_election(self.connection, election_id,
- height, is_concluded)
+ return backend.query.store_election(
+ self.connection, election_id, height, is_concluded
+ )
def store_elections(self, elections):
return backend.query.store_elections(self.connection, elections)
@@ -478,4 +496,4 @@ class ResDB(object):
return backend.query.delete_elections(self.connection, height)
-Block = namedtuple('Block', ('app_hash', 'height', 'transactions'))
+Block = namedtuple("Block", ("app_hash", "height", "transactions"))
diff --git a/service/sdk_validator/resdb_validator/memoize.py
b/service/sdk_validator/resdb_validator/memoize.py
index b273921..3c332ab 100644
--- a/service/sdk_validator/resdb_validator/memoize.py
+++ b/service/sdk_validator/resdb_validator/memoize.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
import functools
@@ -23,7 +23,7 @@ from functools import lru_cache
class HDict(dict):
def __hash__(self):
- return hash(codecs.decode(self['id'], 'hex'))
+ return hash(codecs.decode(self["id"], "hex"))
@lru_cache(maxsize=16384)
@@ -32,11 +32,10 @@ def from_dict(func, *args, **kwargs):
def memoize_from_dict(func):
-
@functools.wraps(func)
def memoized_func(*args, **kwargs):
- if args[1].get('id', None):
+ if args[1].get("id", None):
args = list(args)
args[1] = HDict(args[1])
new_args = tuple(args)
@@ -47,7 +46,7 @@ def memoize_from_dict(func):
return memoized_func
-class ToDictWrapper():
+class ToDictWrapper:
def __init__(self, tx):
self.tx = tx
@@ -64,7 +63,6 @@ def to_dict(func, tx_wrapped):
def memoize_to_dict(func):
-
@functools.wraps(func)
def memoized_func(*args, **kwargs):
diff --git a/service/sdk_validator/resdb_validator/models.py
b/service/sdk_validator/resdb_validator/models.py
index c013511..e5e4e99 100644
--- a/service/sdk_validator/resdb_validator/models.py
+++ b/service/sdk_validator/resdb_validator/models.py
@@ -5,27 +5,29 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
-from service.sdk_validator.resdb_validator.exceptions import (InvalidSignature,
- DuplicateTransaction)
+from service.sdk_validator.resdb_validator.exceptions import (
+ InvalidSignature,
+ DuplicateTransaction,
+)
from service.sdk_validator.resdb_validator.transaction import Transaction
-from service.sdk_validator.resdb_validator.utils import (validate_txn_obj,
validate_key)
+from service.sdk_validator.resdb_validator.utils import validate_txn_obj,
validate_key
class Transaction(Transaction):
- ASSET = 'asset'
- METADATA = 'metadata'
- DATA = 'data'
+ ASSET = "asset"
+ METADATA = "metadata"
+ DATA = "data"
def validate(self, resdb=None, current_transactions=[]):
"""Validate transaction spend
@@ -42,12 +44,13 @@ class Transaction(Transaction):
if self.operation == Transaction.CREATE:
duplicates = any(txn for txn in current_transactions if txn.id ==
self.id)
- if resdb and resdb.is_committed(self.id) or duplicates:
- raise DuplicateTransaction('transaction `{}` already exists'
- .format(self.id))
+ if resdb and resdb.is_committed(self.id) or duplicates:
+ raise DuplicateTransaction(
+ "transaction `{}` already exists".format(self.id)
+ )
if not self.inputs_valid(input_conditions):
- raise InvalidSignature('Transaction signature is invalid.')
+ raise InvalidSignature("Transaction signature is invalid.")
elif self.operation == Transaction.TRANSFER:
self.validate_transfer_inputs(resdb, current_transactions)
@@ -56,7 +59,7 @@ class Transaction(Transaction):
@classmethod
def from_dict(cls, tx_body):
- #TODO schema validation
+ # TODO schema validation
return super().from_dict(tx_body, True)
# @classmethod
@@ -81,7 +84,7 @@ class FastTransaction:
@property
def id(self):
- return self.data['id']
+ return self.data["id"]
def to_dict(self):
return self.data
diff --git a/service/sdk_validator/resdb_validator/transaction.py
b/service/sdk_validator/resdb_validator/transaction.py
index b76aab0..f0b9669 100644
--- a/service/sdk_validator/resdb_validator/transaction.py
+++ b/service/sdk_validator/resdb_validator/transaction.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
"""Transaction related models to parse and construct transaction
@@ -32,32 +32,43 @@ import rapidjson
import base58
from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256
from cryptoconditions.exceptions import (
- ParsingError, ASN1DecodeError, ASN1EncodeError, UnsupportedTypeError)
+ ParsingError,
+ ASN1DecodeError,
+ ASN1EncodeError,
+ UnsupportedTypeError,
+)
+
try:
from hashlib import sha3_256
except ImportError:
from sha3 import sha3_256
from service.sdk_validator.resdb_validator.crypto import PrivateKey, hash_data
-from service.sdk_validator.resdb_validator.exceptions import
(KeypairMismatchException,
- InputDoesNotExist, DoubleSpend,
- InvalidHash, InvalidSignature,
- AmountError, AssetIdMismatch,
- ThresholdTooDeep)
+from service.sdk_validator.resdb_validator.exceptions import (
+ KeypairMismatchException,
+ InputDoesNotExist,
+ DoubleSpend,
+ InvalidHash,
+ InvalidSignature,
+ AmountError,
+ AssetIdMismatch,
+ ThresholdTooDeep,
+)
from service.sdk_validator.resdb_validator.utils import serialize
from .memoize import memoize_from_dict, memoize_to_dict
UnspentOutput = namedtuple(
- 'UnspentOutput', (
+ "UnspentOutput",
+ (
# TODO 'utxo_hash': sha3_256(f'{txid}{output_index}'.encode())
# 'utxo_hash', # noqa
- 'transaction_id',
- 'output_index',
- 'amount',
- 'asset_id',
- 'condition_uri',
- )
+ "transaction_id",
+ "output_index",
+ "amount",
+ "asset_id",
+ "condition_uri",
+ ),
)
@@ -79,19 +90,19 @@ class Input(object):
def __init__(self, fulfillment, owners_before, fulfills=None):
"""Create an instance of an :class:`~.Input`.
- Args:
- fulfillment (:class:`cryptoconditions.Fulfillment`): A
- Fulfillment to be signed with a private key.
- owners_before (:obj:`list` of :obj:`str`): A list of owners
- after a Transaction was confirmed.
- fulfills (:class:`~resdb_validator.transaction.
- TransactionLink`, optional): A link representing the input
- of a `TRANSFER` Transaction.
+ Args:
+ fulfillment (:class:`cryptoconditions.Fulfillment`): A
+ Fulfillment to be signed with a private key.
+ owners_before (:obj:`list` of :obj:`str`): A list of owners
+ after a Transaction was confirmed.
+ fulfills (:class:`~resdb_validator.transaction.
+ TransactionLink`, optional): A link representing the input
+ of a `TRANSFER` Transaction.
"""
if fulfills is not None and not isinstance(fulfills, TransactionLink):
- raise TypeError('`fulfills` must be a TransactionLink instance')
+ raise TypeError("`fulfills` must be a TransactionLink instance")
if not isinstance(owners_before, list):
- raise TypeError('`owners_before` must be a list instance')
+ raise TypeError("`owners_before` must be a list instance")
self.fulfillment = fulfillment
self.fulfills = fulfills
@@ -109,12 +120,12 @@ class Input(object):
def to_dict(self):
"""Transforms the object to a Python dictionary.
- Note:
- If an Input hasn't been signed yet, this method returns a
- dictionary representation.
+ Note:
+ If an Input hasn't been signed yet, this method returns a
+ dictionary representation.
- Returns:
- dict: The Input as an alternative serialization format.
+ Returns:
+ dict: The Input as an alternative serialization format.
"""
try:
fulfillment = self.fulfillment.serialize_uri()
@@ -128,9 +139,9 @@ class Input(object):
fulfills = None
input_ = {
- 'owners_before': self.owners_before,
- 'fulfills': fulfills,
- 'fulfillment': fulfillment,
+ "owners_before": self.owners_before,
+ "fulfills": fulfills,
+ "fulfillment": fulfillment,
}
return input_
@@ -146,23 +157,23 @@ class Input(object):
def from_dict(cls, data):
"""Transforms a Python dictionary to an Input object.
- Note:
- Optionally, this method can also serialize a Cryptoconditions-
- Fulfillment that is not yet signed.
+ Note:
+ Optionally, this method can also serialize a Cryptoconditions-
+ Fulfillment that is not yet signed.
- Args:
- data (dict): The Input to be transformed.
+ Args:
+ data (dict): The Input to be transformed.
- Returns:
- :class:`~resdb_validator.transaction.Input`
+ Returns:
+ :class:`~resdb_validator.transaction.Input`
- Raises:
- InvalidSignature: If an Input's URI couldn't be parsed.
+ Raises:
+ InvalidSignature: If an Input's URI couldn't be parsed.
"""
- fulfillment = data['fulfillment']
+ fulfillment = data["fulfillment"]
if not isinstance(fulfillment, (Fulfillment, type(None))):
try:
- fulfillment = Fulfillment.from_uri(data['fulfillment'])
+ fulfillment = Fulfillment.from_uri(data["fulfillment"])
except ASN1DecodeError:
# TODO Remove as it is legacy code, and simply fall back on
# ASN1DecodeError
@@ -170,9 +181,9 @@ class Input(object):
except TypeError:
# NOTE: See comment about this special case in
# `Input.to_dict`
- fulfillment = _fulfillment_from_details(data['fulfillment'])
- fulfills = TransactionLink.from_dict(data['fulfills'])
- return cls(fulfillment, data['owners_before'], fulfills)
+ fulfillment = _fulfillment_from_details(data["fulfillment"])
+ fulfills = TransactionLink.from_dict(data["fulfills"])
+ return cls(fulfillment, data["owners_before"], fulfills)
def _fulfillment_to_details(fulfillment):
@@ -182,21 +193,20 @@ def _fulfillment_to_details(fulfillment):
fulfillment: Crypto-conditions Fulfillment object
"""
- if fulfillment.type_name == 'ed25519-sha-256':
+ if fulfillment.type_name == "ed25519-sha-256":
return {
- 'type': 'ed25519-sha-256',
- 'public_key': base58.b58encode(fulfillment.public_key).decode(),
+ "type": "ed25519-sha-256",
+ "public_key": base58.b58encode(fulfillment.public_key).decode(),
}
- if fulfillment.type_name == 'threshold-sha-256':
+ if fulfillment.type_name == "threshold-sha-256":
subconditions = [
- _fulfillment_to_details(cond['body'])
- for cond in fulfillment.subconditions
+ _fulfillment_to_details(cond["body"]) for cond in
fulfillment.subconditions
]
return {
- 'type': 'threshold-sha-256',
- 'threshold': fulfillment.threshold,
- 'subconditions': subconditions,
+ "type": "threshold-sha-256",
+ "threshold": fulfillment.threshold,
+ "subconditions": subconditions,
}
raise UnsupportedTypeError(fulfillment.type_name)
@@ -211,43 +221,43 @@ def _fulfillment_from_details(data, _depth=0):
if _depth == 100:
raise ThresholdTooDeep()
- if data['type'] == 'ed25519-sha-256':
- public_key = base58.b58decode(data['public_key'])
+ if data["type"] == "ed25519-sha-256":
+ public_key = base58.b58decode(data["public_key"])
return Ed25519Sha256(public_key=public_key)
- if data['type'] == 'threshold-sha-256':
- threshold = ThresholdSha256(data['threshold'])
- for cond in data['subconditions']:
- cond = _fulfillment_from_details(cond, _depth+1)
+ if data["type"] == "threshold-sha-256":
+ threshold = ThresholdSha256(data["threshold"])
+ for cond in data["subconditions"]:
+ cond = _fulfillment_from_details(cond, _depth + 1)
threshold.add_subfulfillment(cond)
return threshold
- raise UnsupportedTypeError(data.get('type'))
+ raise UnsupportedTypeError(data.get("type"))
class TransactionLink(object):
"""An object for unidirectional linking to a Transaction's Output.
- Attributes:
- txid (str, optional): A Transaction to link to.
- output (int, optional): An output's index in a Transaction with id
- `txid`.
+ Attributes:
+ txid (str, optional): A Transaction to link to.
+ output (int, optional): An output's index in a Transaction with id
+ `txid`.
"""
def __init__(self, txid=None, output=None):
"""Create an instance of a :class:`~.TransactionLink`.
- Note:
- In an IPLD implementation, this class is not necessary anymore,
- 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/outputs/<output>/`.
+ Note:
+ In an IPLD implementation, this class is not necessary anymore,
+ 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/outputs/<output>/`.
- Args:
- txid (str, optional): A Transaction to link to.
- output (int, optional): An Outputs's index in a Transaction
with
- id `txid`.
+ Args:
+ txid (str, optional): A Transaction to link to.
+ output (int, optional): An Outputs's index in a Transaction with
+ id `txid`.
"""
self.txid = txid
self.output = output
@@ -266,36 +276,35 @@ class TransactionLink(object):
def from_dict(cls, link):
"""Transforms a Python dictionary to a TransactionLink object.
- Args:
- link (dict): The link to be transformed.
+ Args:
+ link (dict): The link to be transformed.
- Returns:
- :class:`~resdb_validator.transaction.TransactionLink`
+ Returns:
+ :class:`~resdb_validator.transaction.TransactionLink`
"""
try:
- return cls(link['transaction_id'], link['output_index'])
+ return cls(link["transaction_id"], link["output_index"])
except TypeError:
return cls()
def to_dict(self):
"""Transforms the object to a Python dictionary.
- Returns:
- (dict|None): The link as an alternative serialization format.
+ Returns:
+ (dict|None): The link as an alternative serialization format.
"""
if self.txid is None and self.output is None:
return None
else:
return {
- 'transaction_id': self.txid,
- 'output_index': self.output,
+ "transaction_id": self.txid,
+ "output_index": self.output,
}
- def to_uri(self, path=''):
+ def to_uri(self, path=""):
if self.txid is None and self.output is None:
return None
- return '{}/transactions/{}/outputs/{}'.format(path, self.txid,
- self.output)
+ return "{}/transactions/{}/outputs/{}".format(path, self.txid,
self.output)
class Output(object):
@@ -310,30 +319,30 @@ class Output(object):
owners before a Transaction was confirmed.
"""
- MAX_AMOUNT = 9 * 10 ** 18
+ MAX_AMOUNT = 9 * 10**18
def __init__(self, fulfillment, public_keys=None, amount=1):
"""Create an instance of a :class:`~.Output`.
- Args:
- fulfillment (:class:`cryptoconditions.Fulfillment`): A
- Fulfillment to extract a Condition from.
- public_keys (:obj:`list` of :obj:`str`, optional): A list of
- owners before a Transaction was confirmed.
- amount (int): The amount of Assets to be locked with this
- Output.
-
- Raises:
- TypeError: if `public_keys` is not instance of `list`.
+ Args:
+ fulfillment (:class:`cryptoconditions.Fulfillment`): A
+ Fulfillment to extract a Condition from.
+ public_keys (:obj:`list` of :obj:`str`, optional): A list of
+ owners before a Transaction was confirmed.
+ amount (int): The amount of Assets to be locked with this
+ Output.
+
+ Raises:
+ TypeError: if `public_keys` is not instance of `list`.
"""
if not isinstance(public_keys, list) and public_keys is not None:
- raise TypeError('`public_keys` must be a list instance or None')
+ raise TypeError("`public_keys` must be a list instance or None")
if not isinstance(amount, int):
- raise TypeError('`amount` must be an int')
+ raise TypeError("`amount` must be an int")
if amount < 1:
- raise AmountError('`amount` must be greater than 0')
+ raise AmountError("`amount` must be greater than 0")
if amount > self.MAX_AMOUNT:
- raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT)
+ raise AmountError("`amount` must be <= %s" % self.MAX_AMOUNT)
self.fulfillment = fulfillment
self.amount = amount
@@ -346,30 +355,30 @@ class Output(object):
def to_dict(self):
"""Transforms the object to a Python dictionary.
- Note:
- A dictionary serialization of the Input the Output was
- derived from is always provided.
+ Note:
+ A dictionary serialization of the Input the Output was
+ derived from is always provided.
- Returns:
- dict: The Output as an alternative serialization format.
+ Returns:
+ dict: The Output as an alternative serialization format.
"""
# TODO FOR CC: It must be able to recognize a hashlock condition
# and fulfillment!
condition = {}
try:
- condition['details'] = _fulfillment_to_details(self.fulfillment)
+ condition["details"] = _fulfillment_to_details(self.fulfillment)
except AttributeError:
pass
try:
- condition['uri'] = self.fulfillment.condition_uri
+ condition["uri"] = self.fulfillment.condition_uri
except AttributeError:
- condition['uri'] = self.fulfillment
+ condition["uri"] = self.fulfillment
output = {
- 'public_keys': self.public_keys,
- 'condition': condition,
- 'amount': str(self.amount),
+ "public_keys": self.public_keys,
+ "condition": condition,
+ "amount": str(self.amount),
}
return output
@@ -377,66 +386,63 @@ class Output(object):
def generate(cls, public_keys, amount):
"""Generates a Output from a specifically formed tuple or list.
- Note:
- If a ThresholdCondition has to be generated where the threshold
- is always the number of subconditions it is split between, a
- list of the following structure is sufficient:
+ Note:
+ If a ThresholdCondition has to be generated where the threshold
+ is always the number of subconditions it is split between, a
+ list of the following structure is sufficient:
- [(address|condition)*, [(address|condition)*, ...], ...]
+ [(address|condition)*, [(address|condition)*, ...], ...]
- Args:
- public_keys (:obj:`list` of :obj:`str`): The public key of
- the users that should be able to fulfill the Condition
- that is being created.
- amount (:obj:`int`): The amount locked by the Output.
+ Args:
+ public_keys (:obj:`list` of :obj:`str`): The public key of
+ the users that should be able to fulfill the Condition
+ that is being created.
+ amount (:obj:`int`): The amount locked by the Output.
- Returns:
- An Output that can be used in a Transaction.
+ Returns:
+ An Output that can be used in a Transaction.
- Raises:
- TypeError: If `public_keys` is not an instance of `list`.
- ValueError: If `public_keys` is an empty list.
+ Raises:
+ TypeError: If `public_keys` is not an instance of `list`.
+ ValueError: If `public_keys` is an empty list.
"""
threshold = len(public_keys)
if not isinstance(amount, int):
- raise TypeError('`amount` must be a int')
+ raise TypeError("`amount` must be a int")
if amount < 1:
- raise AmountError('`amount` needs to be greater than zero')
+ raise AmountError("`amount` needs to be greater than zero")
if not isinstance(public_keys, list):
- raise TypeError('`public_keys` must be an instance of list')
+ raise TypeError("`public_keys` must be an instance of list")
if len(public_keys) == 0:
- raise ValueError('`public_keys` needs to contain at least one'
- 'owner')
+ raise ValueError("`public_keys` needs to contain at least one"
"owner")
elif len(public_keys) == 1 and not isinstance(public_keys[0], list):
if isinstance(public_keys[0], Fulfillment):
ffill = public_keys[0]
else:
- ffill = Ed25519Sha256(
- public_key=base58.b58decode(public_keys[0]))
+ ffill =
Ed25519Sha256(public_key=base58.b58decode(public_keys[0]))
return cls(ffill, public_keys, amount=amount)
else:
initial_cond = ThresholdSha256(threshold=threshold)
- threshold_cond = reduce(cls._gen_condition, public_keys,
- initial_cond)
+ threshold_cond = reduce(cls._gen_condition, public_keys,
initial_cond)
return cls(threshold_cond, public_keys, amount=amount)
@classmethod
def _gen_condition(cls, initial, new_public_keys):
"""Generates ThresholdSha256 conditions from a list of new owners.
- Note:
- This method is intended only to be used with a reduce function.
- For a description on how to use this method, see
- :meth:`~.Output.generate`.
+ Note:
+ This method is intended only to be used with a reduce function.
+ For a description on how to use this method, see
+ :meth:`~.Output.generate`.
- Args:
- initial (:class:`cryptoconditions.ThresholdSha256`):
- A Condition representing the overall root.
- new_public_keys (:obj:`list` of :obj:`str`|str): A list of new
- owners or a single new owner.
+ Args:
+ initial (:class:`cryptoconditions.ThresholdSha256`):
+ A Condition representing the overall root.
+ new_public_keys (:obj:`list` of :obj:`str`|str): A list of new
+ owners or a single new owner.
- Returns:
- :class:`cryptoconditions.ThresholdSha256`:
+ Returns:
+ :class:`cryptoconditions.ThresholdSha256`:
"""
try:
threshold = len(new_public_keys)
@@ -447,7 +453,7 @@ class Output(object):
ffill = ThresholdSha256(threshold=threshold)
reduce(cls._gen_condition, new_public_keys, ffill)
elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1:
- raise ValueError('Sublist cannot contain single owner')
+ raise ValueError("Sublist cannot contain single owner")
else:
try:
new_public_keys = new_public_keys.pop()
@@ -462,8 +468,7 @@ class Output(object):
if isinstance(new_public_keys, Fulfillment):
ffill = new_public_keys
else:
- ffill = Ed25519Sha256(
- public_key=base58.b58decode(new_public_keys))
+ ffill =
Ed25519Sha256(public_key=base58.b58decode(new_public_keys))
initial.add_subfulfillment(ffill)
return initial
@@ -471,104 +476,124 @@ class Output(object):
def from_dict(cls, data):
"""Transforms a Python dictionary to an Output object.
- Note:
- To pass a serialization cycle multiple times, a
- Cryptoconditions Fulfillment needs to be present in the
- passed-in dictionary, as Condition URIs are not serializable
- anymore.
+ Note:
+ To pass a serialization cycle multiple times, a
+ Cryptoconditions Fulfillment needs to be present in the
+ passed-in dictionary, as Condition URIs are not serializable
+ anymore.
- Args:
- data (dict): The dict to be transformed.
+ Args:
+ data (dict): The dict to be transformed.
- Returns:
- :class:`~resdb_validator.transaction.Output`
+ Returns:
+ :class:`~resdb_validator.transaction.Output`
"""
try:
- fulfillment =
_fulfillment_from_details(data['condition']['details'])
+ fulfillment =
_fulfillment_from_details(data["condition"]["details"])
except KeyError:
# NOTE: Hashlock condition case
- fulfillment = data['condition']['uri']
+ fulfillment = data["condition"]["uri"]
try:
- amount = int(data['amount'])
+ amount = int(data["amount"])
except ValueError:
- raise AmountError('Invalid amount: %s' % data['amount'])
- return cls(fulfillment, data['public_keys'], amount)
+ raise AmountError("Invalid amount: %s" % data["amount"])
+ return cls(fulfillment, data["public_keys"], amount)
class Transaction(object):
"""A Transaction is used to create and transfer assets.
+ Note:
+ For adding Inputs and Outputs, this class provides methods
+ to do so.
+
+ Attributes:
+ operation (str): Defines the operation of the Transaction.
+ inputs (:obj:`list` of :class:`~resdb_validator.
+ transaction.Input`, optional): Define the assets to
+ spend.
+ outputs (:obj:`list` of :class:`~resdb_validator.
+ transaction.Output`, optional): Define the assets to lock.
+ asset (dict): Asset payload for this Transaction. ``CREATE``
+ Transactions require a dict with a ``data``
+ property while ``TRANSFER`` Transactions require a dict with a
+ ``id`` property.
+ metadata (dict):
+ Metadata to be stored along with the Transaction.
+ version (string): Defines the version number of a Transaction.
+ """
+
+ CREATE = "CREATE"
+ TRANSFER = "TRANSFER"
+ ALLOWED_OPERATIONS = (CREATE, TRANSFER)
+ VERSION = "2.0"
+
+ def __init__(
+ self,
+ operation,
+ asset,
+ inputs=None,
+ outputs=None,
+ metadata=None,
+ version=None,
+ hash_id=None,
+ tx_dict=None,
+ ):
+ """The constructor allows to create a customizable Transaction.
+
Note:
- For adding Inputs and Outputs, this class provides methods
- to do so.
+ When no `version` is provided, one is being
+ generated by this method.
- Attributes:
+ Args:
operation (str): Defines the operation of the Transaction.
+ asset (dict): Asset payload for this Transaction.
inputs (:obj:`list` of :class:`~resdb_validator.
transaction.Input`, optional): Define the assets to
- spend.
outputs (:obj:`list` of :class:`~resdb_validator.
- transaction.Output`, optional): Define the assets to lock.
- asset (dict): Asset payload for this Transaction. ``CREATE``
- Transactions require a dict with a ``data``
- property while ``TRANSFER`` Transactions require a dict with a
- ``id`` property.
- metadata (dict):
- Metadata to be stored along with the Transaction.
+ transaction.Output`, optional): Define the assets to
+ lock.
+ metadata (dict): Metadata to be stored along with the
+ Transaction.
version (string): Defines the version number of a Transaction.
- """
-
- CREATE = 'CREATE'
- TRANSFER = 'TRANSFER'
- ALLOWED_OPERATIONS = (CREATE, TRANSFER)
- VERSION = '2.0'
-
- def __init__(self, operation, asset, inputs=None, outputs=None,
- metadata=None, version=None, hash_id=None, tx_dict=None):
- """The constructor allows to create a customizable Transaction.
-
- Note:
- When no `version` is provided, one is being
- generated by this method.
-
- Args:
- operation (str): Defines the operation of the Transaction.
- asset (dict): Asset payload for this Transaction.
- inputs (:obj:`list` of :class:`~resdb_validator.
- transaction.Input`, optional): Define the assets to
- outputs (:obj:`list` of :class:`~resdb_validator.
- transaction.Output`, optional): Define the assets to
- lock.
- metadata (dict): Metadata to be stored along with the
- Transaction.
- version (string): Defines the version number of a Transaction.
- hash_id (string): Hash id of the transaction.
+ hash_id (string): Hash id of the transaction.
"""
if operation not in self.ALLOWED_OPERATIONS:
- allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
- raise ValueError('`operation` must be one of {}'
- .format(allowed_ops))
+ allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
+ raise ValueError("`operation` must be one of
{}".format(allowed_ops))
# Asset payloads for 'CREATE' operations must be None or
# dicts holding a `data` property. Asset payloads for 'TRANSFER'
# operations must be dicts holding an `id` property.
- if (operation == self.CREATE and
- asset is not None and not (isinstance(asset, dict) and 'data'
in asset)):
- raise TypeError(('`asset` must be None or a dict holding a `data` '
- " property instance for '{}'
Transactions".format(operation)))
- elif (operation == self.TRANSFER and
- not (isinstance(asset, dict) and 'id' in asset)):
- raise TypeError(('`asset` must be a dict holding an `id` property '
- 'for \'TRANSFER\' Transactions'))
+ if (
+ operation == self.CREATE
+ and asset is not None
+ and not (isinstance(asset, dict) and "data" in asset)
+ ):
+ raise TypeError(
+ (
+ "`asset` must be None or a dict holding a `data` "
+ " property instance for '{}'
Transactions".format(operation)
+ )
+ )
+ elif operation == self.TRANSFER and not (
+ isinstance(asset, dict) and "id" in asset
+ ):
+ raise TypeError(
+ (
+ "`asset` must be a dict holding an `id` property "
+ "for 'TRANSFER' Transactions"
+ )
+ )
if outputs and not isinstance(outputs, list):
- raise TypeError('`outputs` must be a list instance or None')
+ raise TypeError("`outputs` must be a list instance or None")
if inputs and not isinstance(inputs, list):
- raise TypeError('`inputs` must be a list instance or None')
+ raise TypeError("`inputs` must be a list instance or None")
if metadata is not None and not isinstance(metadata, dict):
- raise TypeError('`metadata` must be a dict or None')
+ raise TypeError("`metadata` must be a dict or None")
self.version = version if version is not None else self.VERSION
self.operation = operation
@@ -588,14 +613,17 @@ class Transaction(object):
if self.operation == self.CREATE:
self._asset_id = self._id
elif self.operation == self.TRANSFER:
- self._asset_id = self.asset['id']
- return (UnspentOutput(
- transaction_id=self._id,
- output_index=output_index,
- amount=output.amount,
- asset_id=self._asset_id,
- condition_uri=output.fulfillment.condition_uri,
- ) for output_index, output in enumerate(self.outputs))
+ self._asset_id = self.asset["id"]
+ return (
+ UnspentOutput(
+ transaction_id=self._id,
+ output_index=output_index,
+ amount=output.amount,
+ asset_id=self._asset_id,
+ condition_uri=output.fulfillment.condition_uri,
+ )
+ for output_index, output in enumerate(self.outputs)
+ )
@property
def spent_outputs(self):
@@ -603,10 +631,7 @@ class Transaction(object):
is represented as a dictionary containing a transaction id and
output index.
"""
- return (
- input_.fulfills.to_dict()
- for input_ in self.inputs if input_.fulfills
- )
+ return (input_.fulfills.to_dict() for input_ in self.inputs if
input_.fulfills)
@property
def serialized(self):
@@ -618,17 +643,17 @@ class Transaction(object):
@classmethod
def validate_create(cls, tx_signers, recipients, asset, metadata):
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):
- raise TypeError('`recipients` must be a list instance')
+ raise TypeError("`recipients` must be a list instance")
if len(tx_signers) == 0:
- raise ValueError('`tx_signers` list cannot be empty')
+ raise ValueError("`tx_signers` list cannot be empty")
if len(recipients) == 0:
- raise ValueError('`recipients` list cannot be empty')
+ raise ValueError("`recipients` list cannot be empty")
if not (asset is None or isinstance(asset, dict)):
- raise TypeError('`asset` must be a dict or None')
+ raise TypeError("`asset` must be a dict or None")
if not (metadata is None or isinstance(metadata, dict)):
- raise TypeError('`metadata` must be a dict or None')
+ raise TypeError("`metadata` must be a dict or None")
inputs = []
outputs = []
@@ -636,9 +661,13 @@ class Transaction(object):
# generate_outputs
for recipient in recipients:
if not isinstance(recipient, tuple) or len(recipient) != 2:
- raise ValueError(('Each `recipient` in the list must be a'
- ' tuple of `([<list of public keys>],'
- ' <amount>)`'))
+ raise ValueError(
+ (
+ "Each `recipient` in the list must be a"
+ " tuple of `([<list of public keys>],"
+ " <amount>)`"
+ )
+ )
pub_keys, amount = recipient
outputs.append(Output.generate(pub_keys, amount))
@@ -651,56 +680,60 @@ class Transaction(object):
def create(cls, tx_signers, recipients, metadata=None, asset=None):
"""A simple way to generate a `CREATE` transaction.
- Note:
- This method currently supports the following Cryptoconditions
- use cases:
- - Ed25519
- - ThresholdSha256
+ Note:
+ This method currently supports the following Cryptoconditions
+ use cases:
+ - Ed25519
+ - ThresholdSha256
- Additionally, it provides support for the following ResDB
- use cases:
- - Multiple inputs and outputs.
+ Additionally, it provides support for the following ResDB
+ use cases:
+ - Multiple inputs and outputs.
- Args:
- tx_signers (:obj:`list` of :obj:`str`): A list of keys that
- represent the signers of the CREATE Transaction.
- recipients (:obj:`list` of :obj:`tuple`): A list of
- ([keys],amount) that represent the recipients of this
- Transaction.
- metadata (dict): The metadata to be stored along with the
- Transaction.
- asset (dict): The metadata associated with the asset that will
- be created in this Transaction.
+ Args:
+ tx_signers (:obj:`list` of :obj:`str`): A list of keys that
+ represent the signers of the CREATE Transaction.
+ recipients (:obj:`list` of :obj:`tuple`): A list of
+ ([keys],amount) that represent the recipients of this
+ Transaction.
+ metadata (dict): The metadata to be stored along with the
+ Transaction.
+ asset (dict): The metadata associated with the asset that will
+ be created in this Transaction.
- Returns:
- :class:`~resdb_validator.transaction.Transaction`
+ Returns:
+ :class:`~resdb_validator.transaction.Transaction`
"""
(inputs, outputs) = cls.validate_create(tx_signers, recipients, asset,
metadata)
- return cls(cls.CREATE, {'data': asset}, inputs, outputs, metadata)
+ return cls(cls.CREATE, {"data": asset}, inputs, outputs, metadata)
@classmethod
def validate_transfer(cls, inputs, recipients, asset_id, metadata):
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:
- raise ValueError('`inputs` must contain at least one item')
+ raise ValueError("`inputs` must contain at least one item")
if not isinstance(recipients, list):
- raise TypeError('`recipients` must be a list instance')
+ raise TypeError("`recipients` must be a list instance")
if len(recipients) == 0:
- raise ValueError('`recipients` list cannot be empty')
+ raise ValueError("`recipients` list cannot be empty")
outputs = []
for recipient in recipients:
if not isinstance(recipient, tuple) or len(recipient) != 2:
- raise ValueError(('Each `recipient` in the list must be a'
- ' tuple of `([<list of public keys>],'
- ' <amount>)`'))
+ raise ValueError(
+ (
+ "Each `recipient` in the list must be a"
+ " tuple of `([<list of public keys>],"
+ " <amount>)`"
+ )
+ )
pub_keys, amount = recipient
outputs.append(Output.generate(pub_keys, amount))
if not isinstance(asset_id, str):
- raise TypeError('`asset_id` must be a string')
+ raise TypeError("`asset_id` must be a string")
return (deepcopy(inputs), outputs)
@@ -708,43 +741,45 @@ class Transaction(object):
def transfer(cls, inputs, recipients, asset_id, metadata=None):
"""A simple way to generate a `TRANSFER` transaction.
- Note:
- Different cases for threshold conditions:
+ Note:
+ Different cases for threshold conditions:
- Combining multiple `inputs` with an arbitrary number of
- `recipients` can yield interesting cases for the creation of
- threshold conditions we'd like to support. The following
- notation is proposed:
+ Combining multiple `inputs` with an arbitrary number of
+ `recipients` can yield interesting cases for the creation of
+ threshold conditions we'd like to support. The following
+ notation is proposed:
- 1. The index of a `recipient` corresponds to the index of
- an input:
- e.g. `transfer([input1], [a])`, means `input1` would now be
- owned by user `a`.
+ 1. The index of a `recipient` corresponds to the index of
+ an input:
+ e.g. `transfer([input1], [a])`, means `input1` would now be
+ owned by user `a`.
- 2. `recipients` can (almost) get arbitrary deeply nested,
- creating various complex threshold conditions:
- e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means
- `a`'s signature would have a 50% weight on `inp1`
- compared to `b` and `c` that share 25% of the leftover
- weight respectively. `inp2` is owned completely by `d`.
+ 2. `recipients` can (almost) get arbitrary deeply nested,
+ creating various complex threshold conditions:
+ e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means
+ `a`'s signature would have a 50% weight on `inp1`
+ compared to `b` and `c` that share 25% of the leftover
+ weight respectively. `inp2` is owned completely by `d`.
- Args:
- inputs (:obj:`list` of :class:`~resdb_validator.transaction.
- Input`): Converted `Output`s, intended to
- be used as inputs in the transfer to generate.
- recipients (:obj:`list` of :obj:`tuple`): A list of
- ([keys],amount) that represent the recipients of this
- Transaction.
- asset_id (str): The asset ID of the asset to be transferred in
- this Transaction.
- metadata (dict): Python dictionary to be stored along with the
- Transaction.
+ Args:
+ inputs (:obj:`list` of :class:`~resdb_validator.transaction.
+ Input`): Converted `Output`s, intended to
+ be used as inputs in the transfer to generate.
+ recipients (:obj:`list` of :obj:`tuple`): A list of
+ ([keys],amount) that represent the recipients of this
+ Transaction.
+ asset_id (str): The asset ID of the asset to be transferred in
+ this Transaction.
+ metadata (dict): Python dictionary to be stored along with the
+ Transaction.
- Returns:
- :class:`~resdb_validator.transaction.Transaction`
+ Returns:
+ :class:`~resdb_validator.transaction.Transaction`
"""
- (inputs, outputs) = cls.validate_transfer(inputs, recipients,
asset_id, metadata)
- return cls(cls.TRANSFER, {'id': asset_id}, inputs, outputs, metadata)
+ (inputs, outputs) = cls.validate_transfer(
+ inputs, recipients, asset_id, metadata
+ )
+ return cls(cls.TRANSFER, {"id": asset_id}, inputs, outputs, metadata)
def __eq__(self, other):
try:
@@ -756,80 +791,82 @@ class Transaction(object):
def to_inputs(self, indices=None):
"""Converts a Transaction's outputs to spendable inputs.
- Note:
- Takes the Transaction's outputs and derives inputs
- from that can then be passed into `Transaction.transfer` as
- `inputs`.
- A list of integers can be passed to `indices` that
- defines which outputs should be returned as inputs.
- If no `indices` are passed (empty list or None) all
- outputs of the Transaction are returned.
+ Note:
+ Takes the Transaction's outputs and derives inputs
+ from that can then be passed into `Transaction.transfer` as
+ `inputs`.
+ A list of integers can be passed to `indices` that
+ defines which outputs should be returned as inputs.
+ If no `indices` are passed (empty list or None) all
+ outputs of the Transaction are returned.
- Args:
- indices (:obj:`list` of int): Defines which
- outputs should be returned as inputs.
+ Args:
+ indices (:obj:`list` of int): Defines which
+ outputs should be returned as inputs.
- Returns:
- :obj:`list` of :class:`~resdb_validator.transaction.
- Input`
+ Returns:
+ :obj:`list` of :class:`~resdb_validator.transaction.
+ Input`
"""
# NOTE: If no indices are passed, we just assume to take all outputs
# as inputs.
indices = indices or range(len(self.outputs))
return [
- Input(self.outputs[idx].fulfillment,
- self.outputs[idx].public_keys,
- TransactionLink(self.id, idx))
+ Input(
+ self.outputs[idx].fulfillment,
+ self.outputs[idx].public_keys,
+ TransactionLink(self.id, idx),
+ )
for idx in indices
]
def add_input(self, input_):
"""Adds an input to a Transaction's list of inputs.
- Args:
- input_ (:class:`~resdb_validator.transaction.
- Input`): An Input to be added to the Transaction.
+ Args:
+ input_ (:class:`~resdb_validator.transaction.
+ Input`): An Input to be added to the Transaction.
"""
if not isinstance(input_, Input):
- raise TypeError('`input_` must be a Input instance')
+ raise TypeError("`input_` must be a Input instance")
self.inputs.append(input_)
def add_output(self, output):
"""Adds an output to a Transaction's list of outputs.
- Args:
- output (:class:`~resdb_validator.transaction.
- Output`): An Output to be added to the
- Transaction.
+ Args:
+ output (:class:`~resdb_validator.transaction.
+ Output`): An Output to be added to the
+ Transaction.
"""
if not isinstance(output, Output):
- raise TypeError('`output` must be an Output instance or None')
+ raise TypeError("`output` must be an Output instance or None")
self.outputs.append(output)
def sign(self, private_keys):
"""Fulfills a previous Transaction's Output by signing Inputs.
- Note:
- This method works only for the following Cryptoconditions
- currently:
- - Ed25519Fulfillment
- - ThresholdSha256
- Furthermore, note that all keys required to fully sign the
- Transaction have to be passed to this method. A subset of all
- will cause this method to fail.
+ Note:
+ This method works only for the following Cryptoconditions
+ currently:
+ - Ed25519Fulfillment
+ - ThresholdSha256
+ Furthermore, note that all keys required to fully sign the
+ Transaction have to be passed to this method. A subset of all
+ will cause this method to fail.
- Args:
- private_keys (:obj:`list` of :obj:`str`): A complete list of
- all private keys needed to sign all Fulfillments of this
- Transaction.
+ Args:
+ private_keys (:obj:`list` of :obj:`str`): A complete list of
+ all private keys needed to sign all Fulfillments of this
+ Transaction.
- Returns:
- :class:`~resdb_validator.transaction.Transaction`
+ Returns:
+ :class:`~resdb_validator.transaction.Transaction`
"""
# TODO: Singing should be possible with at least one of all private
# keys supplied to this method.
if private_keys is None or not isinstance(private_keys, list):
- raise TypeError('`private_keys` must be a list instance')
+ raise TypeError("`private_keys` must be a list instance")
# NOTE: Generate public keys from private keys and match them in a
# dictionary:
@@ -846,8 +883,10 @@ class Transaction(object):
# to decode to convert the bytestring into a python str
return public_key.decode()
- key_pairs = {gen_public_key(PrivateKey(private_key)):
- PrivateKey(private_key) for private_key in private_keys}
+ key_pairs = {
+ gen_public_key(PrivateKey(private_key)): PrivateKey(private_key)
+ for private_key in private_keys
+ }
tx_dict = self.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict)
@@ -863,38 +902,37 @@ class Transaction(object):
def _sign_input(cls, input_, message, key_pairs):
"""Signs a single Input.
- Note:
- This method works only for the following Cryptoconditions
- currently:
- - Ed25519Fulfillment
- - ThresholdSha256.
+ Note:
+ This method works only for the following Cryptoconditions
+ currently:
+ - Ed25519Fulfillment
+ - ThresholdSha256.
- Args:
- input_ (:class:`~resdb_validator.transaction.
- Input`) The Input to be signed.
- message (str): The message to be signed
- key_pairs (dict): The keys to sign the Transaction with.
+ Args:
+ input_ (:class:`~resdb_validator.transaction.
+ Input`) The Input to be signed.
+ message (str): The message to be signed
+ key_pairs (dict): The keys to sign the Transaction with.
"""
if isinstance(input_.fulfillment, Ed25519Sha256):
- return cls._sign_simple_signature_fulfillment(input_, message,
- key_pairs)
+ return cls._sign_simple_signature_fulfillment(input_, message,
key_pairs)
elif isinstance(input_.fulfillment, ThresholdSha256):
- return cls._sign_threshold_signature_fulfillment(input_, message,
- key_pairs)
+ return cls._sign_threshold_signature_fulfillment(input_, message,
key_pairs)
else:
raise ValueError(
- 'Fulfillment couldn\'t be matched to '
- 'Cryptocondition fulfillment type.')
+ "Fulfillment couldn't be matched to "
+ "Cryptocondition fulfillment type."
+ )
@classmethod
def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs):
"""Signs a Ed25519Fulfillment.
- Args:
- input_ (:class:`~resdb_validator.transaction.
- Input`) The input to be signed.
- message (str): The message to be signed
- key_pairs (dict): The keys to sign the Transaction with.
+ Args:
+ input_ (:class:`~resdb_validator.transaction.
+ Input`) The input to be signed.
+ message (str): The message to be signed
+ key_pairs (dict): The keys to sign the Transaction with.
"""
# NOTE: To eliminate the dangers of accidentally signing a condition by
# reference, we remove the reference of input_ here
@@ -904,35 +942,39 @@ class Transaction(object):
public_key = input_.owners_before[0]
message = sha3_256(message.encode())
if input_.fulfills:
- message.update('{}{}'.format(
- input_.fulfills.txid, input_.fulfills.output).encode())
+ message.update(
+ "{}{}".format(input_.fulfills.txid,
input_.fulfills.output).encode()
+ )
try:
# cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings
input_.fulfillment.sign(
- message.digest(),
base58.b58decode(key_pairs[public_key].encode()))
+ message.digest(),
base58.b58decode(key_pairs[public_key].encode())
+ )
except KeyError:
- raise KeypairMismatchException('Public key {} is not a pair to '
- 'any of the private keys'
- .format(public_key))
+ raise KeypairMismatchException(
+ "Public key {} is not a pair to "
+ "any of the private keys".format(public_key)
+ )
return input_
@classmethod
def _sign_threshold_signature_fulfillment(cls, input_, message, key_pairs):
"""Signs a ThresholdSha256.
- Args:
- input_ (:class:`~resdb_validator.transaction.
- Input`) The Input to be signed.
- message (str): The message to be signed
- key_pairs (dict): The keys to sign the Transaction with.
+ Args:
+ input_ (:class:`~resdb_validator.transaction.
+ Input`) The Input to be signed.
+ message (str): The message to be signed
+ key_pairs (dict): The keys to sign the Transaction with.
"""
input_ = deepcopy(input_)
message = sha3_256(message.encode())
if input_.fulfills:
- message.update('{}{}'.format(
- input_.fulfills.txid, input_.fulfills.output).encode())
+ message.update(
+ "{}{}".format(input_.fulfills.txid,
input_.fulfills.output).encode()
+ )
for owner_before in set(input_.owners_before):
# TODO: CC should throw a KeypairMismatchException, instead of
@@ -945,24 +987,24 @@ class Transaction(object):
# TODO FOR CC: `get_subcondition` is singular. One would not
# expect to get a list back.
ccffill = input_.fulfillment
- subffills = ccffill.get_subcondition_from_vk(
- base58.b58decode(owner_before))
+ subffills =
ccffill.get_subcondition_from_vk(base58.b58decode(owner_before))
if not subffills:
- raise KeypairMismatchException('Public key {} cannot be found '
- 'in the fulfillment'
- .format(owner_before))
+ raise KeypairMismatchException(
+ "Public key {} cannot be found "
+ "in the fulfillment".format(owner_before)
+ )
try:
private_key = key_pairs[owner_before]
except KeyError:
- raise KeypairMismatchException('Public key {} is not a pair '
- 'to any of the private keys'
- .format(owner_before))
+ raise KeypairMismatchException(
+ "Public key {} is not a pair "
+ "to any of the private keys".format(owner_before)
+ )
# cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings
for subffill in subffills:
- subffill.sign(
- message.digest(), base58.b58decode(private_key.encode()))
+ subffill.sign(message.digest(),
base58.b58decode(private_key.encode()))
return input_
def inputs_valid(self, outputs=None):
@@ -987,72 +1029,71 @@ class Transaction(object):
# to check for outputs, we're just submitting dummy
# values to the actual method. This simplifies it's logic
# greatly, as we do not have to check against `None` values.
- return self._inputs_valid(['dummyvalue'
- for _ in self.inputs])
+ return self._inputs_valid(["dummyvalue" for _ in self.inputs])
elif self.operation == self.TRANSFER:
- return self._inputs_valid([output.fulfillment.condition_uri
- for output in outputs])
+ return self._inputs_valid(
+ [output.fulfillment.condition_uri for output in outputs]
+ )
else:
- allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
- raise TypeError('`operation` must be one of {}'
- .format(allowed_ops))
+ allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
+ raise TypeError("`operation` must be one of
{}".format(allowed_ops))
def _inputs_valid(self, output_condition_uris):
"""Validates an Input against a given set of Outputs.
- Note:
- The number of `output_condition_uris` must be equal to the
- number of Inputs a Transaction has.
+ Note:
+ The number of `output_condition_uris` must be equal to the
+ number of Inputs a Transaction has.
- Args:
- output_condition_uris (:obj:`list` of :obj:`str`): A list of
- Outputs to check the Inputs against.
+ Args:
+ output_condition_uris (:obj:`list` of :obj:`str`): A list of
+ Outputs to check the Inputs against.
- Returns:
- bool: If all Outputs are valid.
+ Returns:
+ bool: If all Outputs are valid.
"""
if len(self.inputs) != len(output_condition_uris):
- raise ValueError('Inputs and '
- 'output_condition_uris must have the same count')
+ raise ValueError(
+ "Inputs and " "output_condition_uris must have the same count"
+ )
tx_dict = self.tx_dict if self.tx_dict else self.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict)
- tx_dict['id'] = None
+ tx_dict["id"] = None
tx_serialized = Transaction._to_str(tx_dict)
def validate(i, output_condition_uri=None):
"""Validate input against output condition URI"""
- return self._input_valid(self.inputs[i], self.operation,
- tx_serialized, output_condition_uri)
+ return self._input_valid(
+ self.inputs[i], self.operation, tx_serialized,
output_condition_uri
+ )
- 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)
def _input_valid(self, input_, operation, message,
output_condition_uri=None):
"""Validates a single Input against a single Output.
- Note:
- In case of a `CREATE` Transaction, this method
- does not validate against `output_condition_uri`.
+ Note:
+ In case of a `CREATE` Transaction, this method
+ does not validate against `output_condition_uri`.
- Args:
- input_ (:class:`~resdb_validator.transaction.
- Input`) The Input to be signed.
- operation (str): The type of Transaction.
- message (str): The fulfillment message.
- output_condition_uri (str, optional): An Output to check the
- Input against.
+ Args:
+ input_ (:class:`~resdb_validator.transaction.
+ Input`) The Input to be signed.
+ operation (str): The type of Transaction.
+ message (str): The fulfillment message.
+ output_condition_uri (str, optional): An Output to check the
+ Input against.
- Returns:
- bool: If the Input is valid.
+ Returns:
+ bool: If the Input is valid.
"""
ccffill = input_.fulfillment
try:
parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri())
- except (TypeError, ValueError,
- ParsingError, ASN1DecodeError, ASN1EncodeError):
+ except (TypeError, ValueError, ParsingError, ASN1DecodeError,
ASN1EncodeError):
return False
if operation == self.CREATE:
@@ -1064,8 +1105,9 @@ class Transaction(object):
message = sha3_256(message.encode())
if input_.fulfills:
- message.update('{}{}'.format(
- input_.fulfills.txid, input_.fulfills.output).encode())
+ message.update(
+ "{}{}".format(input_.fulfills.txid,
input_.fulfills.output).encode()
+ )
# NOTE: We pass a timestamp to `.validate`, as in case of a timeout
# condition we'll have to validate against it
@@ -1083,17 +1125,17 @@ class Transaction(object):
def to_dict(self):
"""Transforms the object to a Python dictionary.
- Returns:
- dict: The Transaction as an alternative serialization format.
+ Returns:
+ dict: The Transaction as an alternative serialization format.
"""
return {
- 'inputs': [input_.to_dict() for input_ in self.inputs],
- 'outputs': [output.to_dict() for output in self.outputs],
- 'operation': str(self.operation),
- 'metadata': self.metadata,
- 'asset': self.asset,
- 'version': self.version,
- 'id': self._id,
+ "inputs": [input_.to_dict() for input_ in self.inputs],
+ "outputs": [output.to_dict() for output in self.outputs],
+ "operation": str(self.operation),
+ "metadata": self.metadata,
+ "asset": self.asset,
+ "version": self.version,
+ "id": self._id,
}
@staticmethod
@@ -1101,22 +1143,22 @@ class Transaction(object):
def _remove_signatures(tx_dict):
"""Takes a Transaction dictionary and removes all signatures.
- Args:
- tx_dict (dict): The Transaction to remove all signatures from.
+ Args:
+ tx_dict (dict): The Transaction to remove all signatures from.
- Returns:
- dict
+ Returns:
+ dict
"""
# NOTE: We remove the reference since we need `tx_dict` only for the
# transaction's hash
tx_dict = deepcopy(tx_dict)
- for input_ in tx_dict['inputs']:
+ for input_ in tx_dict["inputs"]:
# NOTE: Not all Cryptoconditions return a `signature` key (e.g.
# ThresholdSha256), so setting it to `None` in any
# case could yield incorrect signatures. This is why we only
# set it to `None` if it's set in the dict.
- input_['fulfillment'] = None
+ input_["fulfillment"] = None
return tx_dict
@staticmethod
@@ -1128,7 +1170,7 @@ class Transaction(object):
return self._id
def to_hash(self):
- return self.to_dict()['id']
+ return self.to_dict()["id"]
@staticmethod
def _to_str(value):
@@ -1164,40 +1206,47 @@ class Transaction(object):
transactions = [transactions]
# create a set of the transactions' asset ids
- asset_ids = {tx.id if tx.operation == tx.CREATE
- else tx.asset['id']
- for tx in transactions}
+ asset_ids = {
+ tx.id if tx.operation == tx.CREATE else tx.asset["id"]
+ for tx in transactions
+ }
# check that all the transasctions have the same asset id
if len(asset_ids) > 1:
- raise AssetIdMismatch(('All inputs of all transactions passed'
- ' need to have the same asset id'))
+ raise AssetIdMismatch(
+ (
+ "All inputs of all transactions passed"
+ " need to have the same asset id"
+ )
+ )
return asset_ids.pop()
@staticmethod
def validate_id(tx_body):
"""Validate the transaction ID of a transaction
- Args:
- tx_body (dict): The Transaction to be transformed.
+ Args:
+ tx_body (dict): The Transaction to be transformed.
"""
# NOTE: Remove reference to avoid side effects
# tx_body = deepcopy(tx_body)
tx_body = rapidjson.loads(rapidjson.dumps(tx_body))
try:
- proposed_tx_id = tx_body['id']
+ proposed_tx_id = tx_body["id"]
except KeyError:
- raise InvalidHash('No transaction id found!')
+ raise InvalidHash("No transaction id found!")
- tx_body['id'] = None
+ tx_body["id"] = None
tx_body_serialized = Transaction._to_str(tx_body)
valid_tx_id = Transaction._to_hash(tx_body_serialized)
if proposed_tx_id != valid_tx_id:
- err_msg = ("The transaction's id '{}' isn't equal to "
- "the hash of its body, i.e. it's not valid.")
+ err_msg = (
+ "The transaction's id '{}' isn't equal to "
+ "the hash of its body, i.e. it's not valid."
+ )
raise InvalidHash(err_msg.format(proposed_tx_id))
@classmethod
@@ -1205,13 +1254,17 @@ class Transaction(object):
def from_dict(cls, tx, skip_schema_validation=True):
"""Transforms a Python dictionary to a Transaction object.
- Args:
- tx_body (dict): The Transaction to be transformed.
+ Args:
+ tx_body (dict): The Transaction to be transformed.
- Returns:
- :class:`~resdb_validator.transaction.Transaction`
+ Returns:
+ :class:`~resdb_validator.transaction.Transaction`
"""
- operation = tx.get('operation', Transaction.CREATE) if isinstance(tx,
dict) else Transaction.CREATE
+ operation = (
+ tx.get("operation", Transaction.CREATE)
+ if isinstance(tx, dict)
+ else Transaction.CREATE
+ )
cls = Transaction.resolve_class(operation)
# print(f"{operation=} {cls=}")
@@ -1219,10 +1272,18 @@ class Transaction(object):
cls.validate_id(tx)
cls.validate_schema(tx)
- inputs = [Input.from_dict(input_) for input_ in tx['inputs']]
- outputs = [Output.from_dict(output) for output in tx['outputs']]
- return cls(tx['operation'], tx['asset'], inputs, outputs,
- tx['metadata'], tx['version'], hash_id=tx['id'], tx_dict=tx)
+ inputs = [Input.from_dict(input_) for input_ in tx["inputs"]]
+ outputs = [Output.from_dict(output) for output in tx["outputs"]]
+ return cls(
+ tx["operation"],
+ tx["asset"],
+ inputs,
+ outputs,
+ tx["metadata"],
+ tx["version"],
+ hash_id=tx["id"],
+ tx_dict=tx,
+ )
@classmethod
def from_db(cls, resdb, tx_dict_list):
@@ -1248,22 +1309,22 @@ class Transaction(object):
tx_map = {}
tx_ids = []
for tx in tx_dict_list:
- tx.update({'metadata': None})
- tx_map[tx['id']] = tx
- tx_ids.append(tx['id'])
+ tx.update({"metadata": None})
+ tx_map[tx["id"]] = tx
+ tx_ids.append(tx["id"])
assets = list(resdb.get_assets(tx_ids))
for asset in assets:
if asset is not None:
- tx = tx_map[asset['id']]
- del asset['id']
- tx['asset'] = asset
+ tx = tx_map[asset["id"]]
+ del asset["id"]
+ tx["asset"] = asset
tx_ids = list(tx_map.keys())
metadata_list = list(resdb.get_metadata(tx_ids))
for metadata in metadata_list:
- tx = tx_map[metadata['id']]
- tx.update({'metadata': metadata.get('metadata')})
+ tx = tx_map[metadata["id"]]
+ tx.update({"metadata": metadata.get("metadata")})
if return_list:
tx_list = []
@@ -1304,14 +1365,13 @@ class Transaction(object):
input_tx = ctxn
if input_tx is None:
- raise InputDoesNotExist("input `{}` doesn't exist"
- .format(input_txid))
+ raise InputDoesNotExist("input `{}` doesn't
exist".format(input_txid))
- spent = resdb.get_spent(input_txid, input_.fulfills.output,
- current_transactions)
+ spent = resdb.get_spent(
+ input_txid, input_.fulfills.output, current_transactions
+ )
if spent:
- raise DoubleSpend('input `{}` was already spent'
- .format(input_txid))
+ raise DoubleSpend("input `{}` was already
spent".format(input_txid))
output = input_tx.outputs[input_.fulfills.output]
input_conditions.append(output)
@@ -1324,21 +1384,32 @@ class Transaction(object):
# validate asset id
asset_id = self.get_asset_id(input_txs)
- if asset_id != self.asset['id']:
- raise AssetIdMismatch(('The asset id of the input does not'
- ' match the asset id of the'
- ' transaction'))
-
- input_amount = sum([input_condition.amount for input_condition in
input_conditions])
- output_amount = sum([output_condition.amount for output_condition in
self.outputs])
+ if asset_id != self.asset["id"]:
+ raise AssetIdMismatch(
+ (
+ "The asset id of the input does not"
+ " match the asset id of the"
+ " transaction"
+ )
+ )
+
+ input_amount = sum(
+ [input_condition.amount for input_condition in input_conditions]
+ )
+ output_amount = sum(
+ [output_condition.amount for output_condition in self.outputs]
+ )
if output_amount != input_amount:
- raise AmountError(('The amount used in the inputs `{}`'
- ' needs to be same as the amount used'
- ' in the outputs `{}`')
- .format(input_amount, output_amount))
+ raise AmountError(
+ (
+ "The amount used in the inputs `{}`"
+ " needs to be same as the amount used"
+ " in the outputs `{}`"
+ ).format(input_amount, output_amount)
+ )
if not self.inputs_valid(input_conditions):
- raise InvalidSignature('Transaction signature is invalid.')
+ raise InvalidSignature("Transaction signature is invalid.")
return True
diff --git a/service/sdk_validator/resdb_validator/utils.py
b/service/sdk_validator/resdb_validator/utils.py
index 1884042..59c585f 100644
--- a/service/sdk_validator/resdb_validator/utils.py
+++ b/service/sdk_validator/resdb_validator/utils.py
@@ -5,15 +5,15 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
import time
@@ -25,10 +25,10 @@ from service.sdk_validator.resdb_validator.exceptions
import ValidationError
def gen_timestamp():
"""The Unix time, rounded to the nearest second.
- See https://en.wikipedia.org/wiki/Unix_time
+ See https://en.wikipedia.org/wiki/Unix_time
- Returns:
- str: the Unix time
+ Returns:
+ str: the Unix time
"""
return str(round(time.time()))
@@ -36,34 +36,33 @@ def gen_timestamp():
def serialize(data):
"""Serialize a dict into a JSON formatted string.
- This function enforces rules like the separator and order of keys.
- This ensures that all dicts are serialized in the same way.
+ This function enforces rules like the separator and order of keys.
+ This ensures that all dicts are serialized in the same way.
- This is specially important for hashing data. We need to make sure that
- everyone serializes their data in the same way so that we do not have
- hash mismatches for the same structure due to serialization
- differences.
+ This is specially important for hashing data. We need to make sure that
+ everyone serializes their data in the same way so that we do not have
+ hash mismatches for the same structure due to serialization
+ differences.
- Args:
- data (dict): dict to serialize
+ Args:
+ data (dict): dict to serialize
- Returns:
- str: JSON formatted string
+ Returns:
+ str: JSON formatted string
"""
- 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.
+ Args:
+ data (str): JSON formatted string.
- Returns:
- dict: dict resulting from the serialization of a JSON formatted
- string.
+ Returns:
+ dict: dict resulting from the serialization of a JSON formatted
+ string.
"""
return rapidjson.loads(data)
@@ -71,24 +70,24 @@ def deserialize(data):
def validate_txn_obj(obj_name, obj, key, validation_fun):
"""Validate value of `key` in `obj` using `validation_fun`.
- Args:
- obj_name (str): name for `obj` being validated.
- obj (dict): dictionary object.
- key (str): key to be validated in `obj`.
- validation_fun (function): function used to validate the value
- of `key`.
+ Args:
+ obj_name (str): name for `obj` being validated.
+ obj (dict): dictionary object.
+ key (str): key to be validated in `obj`.
+ validation_fun (function): function used to validate the value
+ of `key`.
- Returns:
- None: indicates validation successful
+ Returns:
+ None: indicates validation successful
- Raises:
- ValidationError: `validation_fun` will raise exception on failure
+ Raises:
+ ValidationError: `validation_fun` will raise exception on failure
"""
print("No backend yet")
# backend = resdb_validator.config['database']['backend']
backend = None
- if backend == 'localmongodb':
+ if backend == "localmongodb":
data = obj.get(key, {})
if isinstance(data, dict):
validate_all_keys_in_obj(obj_name, data, validation_fun)
@@ -107,17 +106,17 @@ def validate_all_items_in_list(obj_name, data,
validation_fun):
def validate_all_keys_in_obj(obj_name, obj, validation_fun):
"""Validate all (nested) keys in `obj` by using `validation_fun`.
- Args:
- obj_name (str): name for `obj` being validated.
- obj (dict): dictionary object.
- validation_fun (function): function used to validate the value
- of `key`.
+ Args:
+ obj_name (str): name for `obj` being validated.
+ obj (dict): dictionary object.
+ validation_fun (function): function used to validate the value
+ of `key`.
- Returns:
- None: indicates validation successful
+ Returns:
+ None: indicates validation successful
- Raises:
- ValidationError: `validation_fun` will raise this error on failure
+ Raises:
+ ValidationError: `validation_fun` will raise this error on failure
"""
for key, value in obj.items():
validation_fun(obj_name, key)
@@ -129,16 +128,16 @@ def validate_all_keys_in_obj(obj_name, obj,
validation_fun):
def validate_all_values_for_key_in_obj(obj, key, validation_fun):
"""Validate value for all (nested) occurrence of `key` in `obj`
- using `validation_fun`.
+ using `validation_fun`.
- Args:
- obj (dict): dictionary object.
- key (str): key whose value is to be validated.
- validation_fun (function): function used to validate the value
- of `key`.
+ Args:
+ obj (dict): dictionary object.
+ key (str): key whose value is to be validated.
+ validation_fun (function): function used to validate the value
+ of `key`.
- Raises:
- ValidationError: `validation_fun` will raise this error on failure
+ Raises:
+ ValidationError: `validation_fun` will raise this error on failure
"""
for vkey, value in obj.items():
if vkey == key:
@@ -160,20 +159,22 @@ def validate_all_values_for_key_in_list(input_list, key,
validation_fun):
def validate_key(obj_name, key):
"""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
- Args:
- obj_name (str): object name to use when raising exception
- key (str): key to validated
+ Args:
+ obj_name (str): object name to use when raising exception
+ key (str): key to validated
- Returns:
- None: validation successful
+ Returns:
+ None: validation successful
- Raises:
- ValidationError: will raise exception in case of regex match.
+ Raises:
+ ValidationError: will raise exception in case of regex match.
"""
- if re.search(r'^[$]|\.|\x00', key):
- error_str = ('Invalid key name "{}" in {} object. The '
- 'key name cannot contain characters '
- '".", "$" or null characters').format(key, obj_name)
+ if re.search(r"^[$]|\.|\x00", key):
+ error_str = (
+ 'Invalid key name "{}" in {} object. The '
+ "key name cannot contain characters "
+ '".", "$" or null characters'
+ ).format(key, obj_name)
raise ValidationError(error_str)
diff --git a/service/sdk_validator/validator.py
b/service/sdk_validator/validator.py
index 949d796..7b96e91 100644
--- a/service/sdk_validator/validator.py
+++ b/service/sdk_validator/validator.py
@@ -5,23 +5,24 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
#%%
from service.sdk_validator.resdb_validator.models import Transaction
from service.sdk_validator.resdb_validator.exceptions import InvalidSignature
+
def is_valid_tx(tx_dict: dict) -> Transaction:
- tx_obj : Transaction = Transaction.from_dict(tx_dict)
+ tx_obj: Transaction = Transaction.from_dict(tx_dict)
try:
tx_obj.validate()
@@ -31,24 +32,41 @@ def is_valid_tx(tx_dict: dict) -> Transaction:
return (1, None)
-
# %%
-if __name__ == '__main__':
- tx_dict = {'inputs': [{'owners_before':
['9zC37hhowLSeHjknaqsZRAfakvuTR1piuin42AAvL7sG'],
- 'fulfills': None,
- 'fulfillment':
'pGSAIIWEFJxPvs4ClXvcd4Rnwy2h7GVmb3JME7xH5n2oBE3jgUDLke3SK_3x333Dg3Gd-1co64LWgMenHLAam3Bo48-VCjboQO0GZQJdA_5DbvgxVmoKJYc3mK7o9jiHMnb5WscL'}],
- 'outputs': [{'public_keys':
['EuLjsaa21zXzf3kQ25AbwNrymdk9iMuKtrBgqkGM2uVB'],
- 'condition': {'details': {'type': 'ed25519-sha-256',
- 'public_key':
'EuLjsaa21zXzf3kQ25AbwNrymdk9iMuKtrBgqkGM2uVB'},
- 'uri':
'ni:///sha-256;s9hZhqQ61Vt2jcfJSqHnPhGwtuS6zlJHRypyEoyrZfY?fpt=ed25519-sha-256&cost=131072'},
- 'amount': '1'}],
- 'operation': 'CREATE',
- 'metadata': None,
- 'asset': {'data': {'token_for': {'game_boy': {'serial_number':
'LR35902'}},
- 'description': 'Time share token. Each token equals one hour
of usage.'}},
- 'version': '2.0',
- 'id':
'523db618299340e10b5c779600563285cf174aeb1362603bca66d67371584cb8'}
+if __name__ == "__main__":
+ tx_dict = {
+ "inputs": [
+ {
+ "owners_before":
["9zC37hhowLSeHjknaqsZRAfakvuTR1piuin42AAvL7sG"],
+ "fulfills": None,
+ "fulfillment":
"pGSAIIWEFJxPvs4ClXvcd4Rnwy2h7GVmb3JME7xH5n2oBE3jgUDLke3SK_3x333Dg3Gd-1co64LWgMenHLAam3Bo48-VCjboQO0GZQJdA_5DbvgxVmoKJYc3mK7o9jiHMnb5WscL",
+ }
+ ],
+ "outputs": [
+ {
+ "public_keys":
["EuLjsaa21zXzf3kQ25AbwNrymdk9iMuKtrBgqkGM2uVB"],
+ "condition": {
+ "details": {
+ "type": "ed25519-sha-256",
+ "public_key":
"EuLjsaa21zXzf3kQ25AbwNrymdk9iMuKtrBgqkGM2uVB",
+ },
+ "uri":
"ni:///sha-256;s9hZhqQ61Vt2jcfJSqHnPhGwtuS6zlJHRypyEoyrZfY?fpt=ed25519-sha-256&cost=131072",
+ },
+ "amount": "1",
+ }
+ ],
+ "operation": "CREATE",
+ "metadata": None,
+ "asset": {
+ "data": {
+ "token_for": {"game_boy": {"serial_number": "LR35902"}},
+ "description": "Time share token. Each token equals one hour
of usage.",
+ }
+ },
+ "version": "2.0",
+ "id":
"523db618299340e10b5c779600563285cf174aeb1362603bca66d67371584cb8",
+ }
ret = is_valid_tx(tx_dict)
# %%
diff --git a/test_driver.py b/test_driver.py
index bed96bd..0322d7f 100644
--- a/test_driver.py
+++ b/test_driver.py
@@ -3,7 +3,7 @@
from resdb_driver import Resdb
db_root_url = "http://127.0.0.1:18000"
-#db_root_url = "https://resdb.free.beeceptor.com"
+# db_root_url = "https://resdb.free.beeceptor.com"
db = Resdb(db_root_url)
from resdb_driver.crypto import generate_keypair
@@ -71,7 +71,7 @@ sent_transfer_tx =
db.transactions.send_commit(fulfilled_transfer_tx)
# %%
# sent_transfer_tx is in the form "id: ac98e91d01591156008e23de4b5564909228..."
-print(db.transactions.retrieve(txid=sent_transfer_tx[len("id: "):]))
+print(db.transactions.retrieve(txid=sent_transfer_tx[len("id: ") :]))
#%%
# TODO valide a tx object
diff --git a/test_driver_2.py b/test_driver_2.py
index f6e19e0..d743424 100644
--- a/test_driver_2.py
+++ b/test_driver_2.py
@@ -2,6 +2,7 @@
from resdb_driver import Resdb
import json
+
db_root_url = "http://127.0.0.1:18000"
db = Resdb(db_root_url)
@@ -71,7 +72,7 @@ sent_transfer_tx =
db.transactions.send_commit(fulfilled_transfer_tx)
# %%
# sent_transfer_tx is in the form "id: ac98e91d01591156008e23de4b5564909228..."
-print(db.transactions.retrieve(txid=sent_transfer_tx[len("id: "):]))
+print(db.transactions.retrieve(txid=sent_transfer_tx[len("id: ") :]))
#%%
# TODO valide a tx object
diff --git a/test_update_metadata.py b/test_update_metadata.py
index fba18fd..d5a24d0 100644
--- a/test_update_metadata.py
+++ b/test_update_metadata.py
@@ -39,7 +39,7 @@ prepared_token_tx = db.transactions.prepare(
asset=game_boy_token,
)
-prepared_token_tx["metadata"] = {"price": "100"} #metadata has to be a dict
+prepared_token_tx["metadata"] = {"price": "100"} # metadata has to be a dict
#%%
# fulfill the tnx
fulfilled_token_tx = db.transactions.fulfill(
@@ -74,14 +74,17 @@ fulfilled_transfer_tx = db.transactions.fulfill(
)
# stage 3
-# Change the metadata
+# Change the metadata
#%%
transfer_asset = {"id": fulfilled_token_tx["id"]}
output_index = 0
output = fulfilled_transfer_tx["outputs"][output_index]
transfer_input = {
"fulfillment": output["condition"]["details"],
- "fulfills": {"output_index": output_index, "transaction_id":
fulfilled_transfer_tx["id"]},
+ "fulfills": {
+ "output_index": output_index,
+ "transaction_id": fulfilled_transfer_tx["id"],
+ },
"owners_before": output["public_keys"],
}
@@ -105,7 +108,10 @@ output_index = 0
output = fulfilled_transfer_tx["outputs"][output_index]
transfer_input = {
"fulfillment": output["condition"]["details"],
- "fulfills": {"output_index": output_index, "transaction_id":
fulfilled_transfer_tx["id"]},
+ "fulfills": {
+ "output_index": output_index,
+ "transaction_id": fulfilled_transfer_tx["id"],
+ },
"owners_before": output["public_keys"],
}
@@ -122,5 +128,6 @@ fulfilled_transfer_tx = db.transactions.fulfill(
#%%
-sent_transfer_tx = db.transactions.send_commit(fulfilled_transfer_tx) # use
this to commit whenever required
-
+sent_transfer_tx = db.transactions.send_commit(
+ fulfilled_transfer_tx
+) # use this to commit whenever required