Skip to content

Commit

Permalink
Add compose & decompose class (#11)
Browse files Browse the repository at this point in the history
Integration of PRP-5 https://github.com/planetmint/PRPs/tree/main/5

Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
Co-authored-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
  • Loading branch information
eckelj and LaurentMontBlanc authored Jan 26, 2023
1 parent 9ba96f9 commit 2c13371
Show file tree
Hide file tree
Showing 16 changed files with 730 additions and 32 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [0.6.0] - 2022-01-26
### Added
- Transactional types: Compose and Decompose
- structural tests for Transaction class

## [0.5.0] - 2022-12-12
### Fixed
- renamed method from _simple_ to _ed25519_ (a more expressive and speaking name)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "planetmint-transactions"
version = "0.5.0"
version = "0.6.0"
description = "Python implementation of the planetmint transactions spec"
authors = ["Lorenz Herzberger <lorenzherzberger@gmail.com>"]
readme = "README.md"
Expand Down
Empty file added tests/__init__.py
Empty file.
251 changes: 251 additions & 0 deletions tests/common/test_transaction_structure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0

"""All tests of transaction structure. The concern here is that transaction
structural / schematic issues are caught when reading a transaction
(ie going from dict -> transaction).
"""
import json
import pytest
import hashlib as sha3

from unittest.mock import MagicMock
from transactions.common.exceptions import AmountError, SchemaValidationError, ThresholdTooDeep
from transactions.common.transaction import Transaction
from transactions.common.utils import _fulfillment_to_details, _fulfillment_from_details
from ipld import marshal, multihash

################################################################################
# Helper functions


def validate(tx):
if isinstance(tx, Transaction):
tx = tx.to_dict()
Transaction.from_dict(tx, False)


def validate_raises(tx, exc=SchemaValidationError):
with pytest.raises(exc):
validate(tx)


# We should test that validation works when we expect it to
def test_validation_passes(signed_create_tx):
Transaction.from_dict(signed_create_tx.to_dict(), False)


################################################################################
# ID


def test_tx_serialization_hash_function(signed_create_tx):
tx = signed_create_tx.to_dict()
tx["id"] = None
payload = json.dumps(tx, skipkeys=False, sort_keys=True, separators=(",", ":"))
assert sha3.sha3_256(payload.encode()).hexdigest() == signed_create_tx.id


def test_tx_serialization_with_incorrect_hash(signed_create_tx):
from transactions.common.exceptions import InvalidHash

tx = signed_create_tx.to_dict()
tx["id"] = "a" * 64
with pytest.raises(InvalidHash):
Transaction.validate_id(tx)


def test_tx_serialization_with_no_hash(signed_create_tx):
from transactions.common.exceptions import InvalidHash

tx = signed_create_tx.to_dict()
del tx["id"]
with pytest.raises(InvalidHash):
Transaction.from_dict(tx, False)


################################################################################
# Operation


def test_validate_invalid_operation(create_tx, alice):
create_tx.operation = "something invalid"
signed_tx = create_tx.sign([alice.private_key])
validate_raises(signed_tx)


################################################################################
# Metadata


def test_validate_fails_metadata_empty_dict(create_tx, alice):
create_tx.metadata = multihash(marshal({"a": 1}))
signed_tx = create_tx.sign([alice.private_key])
validate(signed_tx)

create_tx._id = None
create_tx.fulfillment = None
create_tx.metadata = None
signed_tx = create_tx.sign([alice.private_key])
validate(signed_tx)

create_tx._id = None
create_tx.fulfillment = None
create_tx.metadata = {}
signed_tx = create_tx.sign([alice.private_key])
validate_raises(signed_tx)


################################################################################
# Asset


def test_transfer_asset_schema(user_sk, signed_transfer_tx):
from transactions.common.transaction import Transaction

tx = signed_transfer_tx.to_dict()
validate(tx)
tx["id"] = None
tx["assets"][0]["data"] = {}
tx = Transaction.from_dict(tx).sign([user_sk]).to_dict()
validate_raises(tx)
tx["id"] = None
del tx["assets"][0]["data"]
tx["assets"][0]["id"] = "b" * 63
tx = Transaction.from_dict(tx).sign([user_sk]).to_dict()
validate_raises(tx)


def test_create_tx_no_asset_id(create_tx, alice):
create_tx.assets[0]["id"] = "b" * 64
signed_tx = create_tx.sign([alice.private_key])
validate_raises(signed_tx)


def test_create_tx_asset_type(create_tx, alice):
create_tx.assets[0]["data"] = multihash(marshal({"a": ""}))
signed_tx = create_tx.sign([alice.private_key])
validate(signed_tx)


def test_create_tx_no_asset_data(create_tx):
tx_body = create_tx.to_dict()
del tx_body["assets"][0]["data"]
tx_serialized = json.dumps(tx_body, skipkeys=False, sort_keys=True, separators=(",", ":"))
tx_body["id"] = sha3.sha3_256(tx_serialized.encode()).hexdigest()
validate_raises(tx_body)


################################################################################
# Inputs


def test_no_inputs(create_tx, alice):
create_tx.inputs = []
signed_tx = create_tx.sign([alice.private_key])
validate_raises(signed_tx)


def test_create_single_input(create_tx, alice):
from transactions.common.transaction import Transaction

tx = create_tx.to_dict()
tx["inputs"] += tx["inputs"]
tx = Transaction.from_dict(tx).sign([alice.private_key]).to_dict()
validate_raises(tx)
tx["id"] = None
tx["inputs"] = []
tx = Transaction.from_dict(tx).sign([alice.private_key]).to_dict()
validate_raises(tx)


def test_transfer_has_inputs(user_sk, signed_transfer_tx):
signed_transfer_tx.inputs = []
signed_transfer_tx._id = None
signed_transfer_tx.sign([user_sk])
validate_raises(signed_transfer_tx)


################################################################################
# Outputs


def test_low_amounts(user_sk, create_tx, signed_transfer_tx, alice):
for sk, tx in [(alice.private_key, create_tx), (user_sk, signed_transfer_tx)]:
tx.outputs[0].amount = 0
tx._id = None
tx.sign([sk])
validate_raises(tx, AmountError)
tx.outputs[0].amount = -1
tx._id = None
tx.sign([sk])
validate_raises(tx)


def test_high_amounts(create_tx, alice):
# Should raise a SchemaValidationError - don't want to allow ridiculously
# large numbers to get converted to int
create_tx.outputs[0].amount = 10**21
create_tx.sign([alice.private_key])
validate_raises(create_tx)
# Should raise AmountError
create_tx.outputs[0].amount = 9 * 10**18 + 1
create_tx._id = None
create_tx.sign([alice.private_key])
validate_raises(create_tx, AmountError)
# Should pass
create_tx.outputs[0].amount -= 1
create_tx._id = None
create_tx.sign([alice.private_key])
validate(create_tx)


################################################################################
# Conditions


def test_handle_threshold_overflow():
cond = {
"type": "ed25519-sha-256",
"public_key": "a" * 43,
}
for i in range(1000):
cond = {
"type": "threshold-sha-256",
"threshold": 1,
"subconditions": [cond],
}
with pytest.raises(ThresholdTooDeep):
_fulfillment_from_details(cond)


def test_unsupported_condition_type():
from planetmint_cryptoconditions.exceptions import UnsupportedTypeError

with pytest.raises(UnsupportedTypeError):
_fulfillment_from_details({"type": "a"})

with pytest.raises(UnsupportedTypeError):
_fulfillment_to_details(MagicMock(type_name="a"))


################################################################################
# Version


def test_validate_version(create_tx, alice):
create_tx.version = "3.0"
create_tx.sign([alice.private_key])
validate(create_tx)

create_tx.version = "0.10"
create_tx._id = None
create_tx.sign([alice.private_key])
validate_raises(create_tx)

create_tx.version = "110"
create_tx._id = None
create_tx.sign([alice.private_key])
validate_raises(create_tx)
22 changes: 14 additions & 8 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from planetmint_cryptoconditions import ThresholdSha256, Ed25519Sha256
from ipld import marshal, multihash
from transactions.types.assets.create import Create
from transactions.common.crypto import generate_key_pair


USER_PRIVATE_KEY = "8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie"
USER_PUBLIC_KEY = "JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE"
Expand Down Expand Up @@ -326,29 +328,21 @@ def user_pk():

@pytest.fixture
def alice():
from transactions.common.crypto import generate_key_pair

return generate_key_pair()


@pytest.fixture
def bob():
from transactions.common.crypto import generate_key_pair

return generate_key_pair()


@pytest.fixture
def carol():
from transactions.common.crypto import generate_key_pair

return generate_key_pair()


@pytest.fixture
def merlin():
from transactions.common.crypto import generate_key_pair

return generate_key_pair()


Expand All @@ -359,11 +353,23 @@ def create_tx(alice, user_pk):
return Create.generate([alice.public_key], [([user_pk], 1)], assets=assets)


@pytest.fixture
def create_tx_2(alice, user_pk):
name = f"I am created by the create_tx fixture. My random identifier is {random.random()}."
assets = [{"data": multihash(marshal({"name": name}))}]
return Create.generate([alice.public_key], [([user_pk], 1)], assets=assets)


@pytest.fixture
def signed_create_tx(alice, create_tx):
return create_tx.sign([alice.private_key])


@pytest.fixture
def signed_create_tx_2(alice, create_tx_2):
return create_tx_2.sign([alice.private_key])


@pytest.fixture
def signed_transfer_tx(signed_create_tx, user_pk, user_sk):
from transactions.types.assets.transfer import Transfer
Expand Down
Empty file added tests/types/__init__.py
Empty file.
Loading

0 comments on commit 2c13371

Please sign in to comment.