Skip to content

Commit

Permalink
Transaction validation 2 0 fix (#12)
Browse files Browse the repository at this point in the history
* fixed tx validation issues with v2.0 transactions/elections/votes
* added changelog bumped version
* blackified
* using the newest black now
* added method to abstract v2.0, v3.0   differences away
* Added an object version of get_assets
---------

Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
  • Loading branch information
eckelj authored Feb 14, 2023
1 parent 2c13371 commit 3e4ec1f
Show file tree
Hide file tree
Showing 16 changed files with 532 additions and 430 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
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.7.0] - 2023-02-14
### Fixed
- Inconsistent TX validation: made it consistent.

## [0.6.0] - 2022-01-26
### Added
Expand Down
735 changes: 374 additions & 361 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "planetmint-transactions"
version = "0.6.0"
version = "0.7.0"
description = "Python implementation of the planetmint transactions spec"
authors = ["Lorenz Herzberger <lorenzherzberger@gmail.com>"]
readme = "README.md"
Expand All @@ -19,7 +19,7 @@ jsonschema = "^4.16.0"
PyYAML = "^6.0"

[tool.poetry.group.dev.dependencies]
black = {version = "^22.10.0", allow-prereleases = true}
black = "23.1.0"
hypothesis = "^6.54.6"
pytest = "^7.2.0"

Expand Down
3 changes: 0 additions & 3 deletions tests/common/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,6 @@ def test_invalid_input_initialization(user_input, user_pub):


def test_transaction_link_serialization():

tx_id = "a transaction id"
expected = {
"transaction_id": tx_id,
Expand Down Expand Up @@ -625,7 +624,6 @@ def test_create_create_transaction_single_io(user_output, user_pub, data):


def test_validate_single_io_create_transaction(user_pub, user_priv, data, asset_definition):

tx = Create.generate([user_pub], [([user_pub], 1)], metadata=data)
tx = tx.sign([user_priv])
assert tx.inputs_valid() is True
Expand Down Expand Up @@ -851,7 +849,6 @@ def test_create_transfer_with_invalid_parameters(tx, user_pub):


def test_cant_add_empty_output():

tx = Transaction(Transaction.CREATE, None)

with raises(TypeError):
Expand Down
86 changes: 86 additions & 0 deletions tests/common/test_transaction_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,92 @@ def test_validation_passes(signed_create_tx):
Transaction.from_dict(signed_create_tx.to_dict(), False)


def test_election_validation_v2_0():
tx_dict = {
"inputs": [
{
"owners_before": ["HY2gviZVW1fvsshx21kRCcGUJFBKQwf37xA959cTNFzP"],
"fulfills": None,
"fulfillment": "pGSAIPWt56ud3H3z0qf6hPKcMsJtOSc_goPAo3AXkH8T4mzAgUBuGTrhGQQ0oePAgxC_9pHNWzHiykddAuhMSnSDoatPExkDp2nAmq8D6pvc3ECqMUUj04GBEq2aROmAylDBKrgG",
}
],
"outputs": [
{
"public_keys": ["6G8TmxcbRgYuQviBg9Us5h4B8D94fNJ4kbMMKRwmY5sy"],
"condition": {
"details": {
"type": "ed25519-sha-256",
"public_key": "6G8TmxcbRgYuQviBg9Us5h4B8D94fNJ4kbMMKRwmY5sy",
},
"uri": "ni:///sha-256;jf_gG_7GnA7bTzelkz_Du2kMHzw7OS20TVNHD8XZSq4?fpt=ed25519-sha-256&cost=131072",
},
"amount": "10",
},
{
"public_keys": ["CwHvbpvaWQu9wBuVVLRsc2rxUcGM7j1tR3RsVPWtgbXj"],
"condition": {
"details": {
"type": "ed25519-sha-256",
"public_key": "CwHvbpvaWQu9wBuVVLRsc2rxUcGM7j1tR3RsVPWtgbXj",
},
"uri": "ni:///sha-256;4VrA0c8Pvb4DA7Bvrx7or0JBKExRCfWyKSFiLYxpr7A?fpt=ed25519-sha-256&cost=131072",
},
"amount": "10",
},
{
"public_keys": ["HY2gviZVW1fvsshx21kRCcGUJFBKQwf37xA959cTNFzP"],
"condition": {
"details": {
"type": "ed25519-sha-256",
"public_key": "HY2gviZVW1fvsshx21kRCcGUJFBKQwf37xA959cTNFzP",
},
"uri": "ni:///sha-256;-uhNj2-VQAytXDoz0z2n9PHUbJrzME4syF1EJCQ9cKw?fpt=ed25519-sha-256&cost=131072",
},
"amount": "10",
},
{
"public_keys": ["2mxYGVViSHfTFJNydAKQQZaz8HAUjUSCDobPCF2q1hjk"],
"condition": {
"details": {
"type": "ed25519-sha-256",
"public_key": "2mxYGVViSHfTFJNydAKQQZaz8HAUjUSCDobPCF2q1hjk",
},
"uri": "ni:///sha-256;fTF2N2feWDvfEeMoTXss5KlWkYwnP4g2jUWKF2cmFRI?fpt=ed25519-sha-256&cost=131072",
},
"amount": "10",
},
{
"public_keys": ["Dxy87xVCbGueayzmzo1csFFp47mhsvCYqX7dJJSTZmvB"],
"condition": {
"details": {
"type": "ed25519-sha-256",
"public_key": "Dxy87xVCbGueayzmzo1csFFp47mhsvCYqX7dJJSTZmvB",
},
"uri": "ni:///sha-256;Ga2y8alLY9f1tQ-Jvly68UZZ7csLLWbL7v7240_-uvo?fpt=ed25519-sha-256&cost=131072",
},
"amount": "10",
},
],
"operation": "VALIDATOR_ELECTION",
"metadata": None,
"asset": {
"data": {
"public_key": {
"value": "EEE57EEEE18BCC60B951C0672B09D70F52B20AC8C8DE9A191F956BB09083BF96",
"type": "ed25519-base16",
},
"power": 10,
"node_id": "a4ee5afed56efbfbc0d08c1d030b1d0291451c59",
"seed": "99430bfa-26e7-422d-a816-37166a05818c",
}
},
"version": "2.0",
"id": "d2a58e4bb788e6594b4e8570b881b8b9859be34881fd4cba40b49481b0af2e98",
}
tx = Transaction.from_dict(tx_dict, False)
print(tx)


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

Expand Down
1 change: 0 additions & 1 deletion transactions/common/memoize.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ def to_dict(func, tx_wrapped):
def memoize_to_dict(func: Callable):
@functools.wraps(func)
def memoized_func(*args, **kwargs):

if len(args) > 0 and args[0] and args[0].id:
return to_dict(func, ToDictWrapper(args[0]))
else:
Expand Down
44 changes: 28 additions & 16 deletions transactions/common/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,23 @@ def _load_schema(name, version, path=__file__):
return path, (schema, fast_schema)


# TODO: make this an env var from a config file
TX_SCHEMA_VERSION = "v3.0"
TX_SCHEMA_VERSION_2_0 = "v2.0"

TX_SCHEMA_PATH, TX_SCHEMA_COMMON = _load_schema("transaction", TX_SCHEMA_VERSION)
_, TX_SCHEMA_CREATE = _load_schema("transaction_create", TX_SCHEMA_VERSION)
_, TX_SCHEMA_TRANSFER = _load_schema("transaction_transfer", TX_SCHEMA_VERSION)

_, TX_SCHEMA_VALIDATOR_ELECTION = _load_schema("transaction_validator_election", TX_SCHEMA_VERSION)

_, TX_SCHEMA_CHAIN_MIGRATION_ELECTION = _load_schema("transaction_chain_migration_election", TX_SCHEMA_VERSION)

_, TX_SCHEMA_VOTE = _load_schema("transaction_vote", TX_SCHEMA_VERSION)

_, TX_SCHEMA_COMPOSE = _load_schema("transaction_compose", TX_SCHEMA_VERSION)

_, TX_SCHEMA_DECOMPOSE = _load_schema("transaction_decompose", TX_SCHEMA_VERSION)

TX_SCHEMA_PATH_2_0, TX_SCHEMA_COMMON_2_0 = _load_schema("transaction", TX_SCHEMA_VERSION_2_0)
_, TX_SCHEMA_CREATE_2_0 = _load_schema("transaction_create", TX_SCHEMA_VERSION_2_0)
_, TX_SCHEMA_TRANSFER_2_0 = _load_schema("transaction_transfer", TX_SCHEMA_VERSION_2_0)

_, TX_SCHEMA_VALIDATOR_ELECTION_2_0 = _load_schema("transaction_validator_election", TX_SCHEMA_VERSION_2_0)

_, TX_SCHEMA_CHAIN_MIGRATION_ELECTION_2_0 = _load_schema("transaction_chain_migration_election", TX_SCHEMA_VERSION_2_0)

_, TX_SCHEMA_VOTE_2_0 = _load_schema("transaction_vote", TX_SCHEMA_VERSION_2_0)


Expand Down Expand Up @@ -80,28 +71,49 @@ def _validate_schema(schema, body):
raise SchemaValidationError(str(exc)) from exc


def validate_transaction_schema(tx):
def validate_transaction_schema(tx: dict):
"""Validate a transaction dict.
TX_SCHEMA_COMMON contains properties that are common to all types of
transaction. TX_SCHEMA_[TRANSFER|CREATE] add additional constraints on top.
"""
try:
if tx["version"] == "3.0":
# generic validation
_validate_schema(TX_SCHEMA_COMMON, tx)
if tx["operation"] == "TRANSFER":
_validate_schema(TX_SCHEMA_TRANSFER, tx)
elif tx["operation"] == "CREATE":

# special validation
if tx["operation"] == "CREATE":
_validate_schema(TX_SCHEMA_CREATE, tx)
elif tx["operation"] == "TRANSFER":
_validate_schema(TX_SCHEMA_TRANSFER, tx)
elif tx["operation"] == "VALIDATOR_ELECTION":
_validate_schema(TX_SCHEMA_VALIDATOR_ELECTION, tx)
elif tx["operation"] == "CHAIN_MIGRATION_ELECTION":
_validate_schema(TX_SCHEMA_CHAIN_MIGRATION_ELECTION, tx)
elif tx["operation"] == "VOTE":
_validate_schema(TX_SCHEMA_TRANSFER, tx)
_validate_schema(TX_SCHEMA_VOTE, tx)
elif tx["operation"] == "COMPOSE":
_validate_schema(TX_SCHEMA_COMPOSE, tx)
elif tx["operation"] == "DECOMPOSE":
_validate_schema(TX_SCHEMA_DECOMPOSE, tx)

else:
# generic validation
_validate_schema(TX_SCHEMA_COMMON_2_0, tx)
if tx["operation"] == "TRANSFER":
_validate_schema(TX_SCHEMA_TRANSFER_2_0, tx)
else:

# special validation
if tx["operation"] == "CREATE":
_validate_schema(TX_SCHEMA_CREATE_2_0, tx)
elif tx["operation"] == "TRANSFER":
_validate_schema(TX_SCHEMA_TRANSFER_2_0, tx)
elif tx["operation"] == "VALIDATOR_ELECTION":
_validate_schema(TX_SCHEMA_VALIDATOR_ELECTION_2_0, tx)
elif tx["operation"] == "CHAIN_MIGRATION_ELECTION":
_validate_schema(TX_SCHEMA_CHAIN_MIGRATION_ELECTION_2_0, tx)
elif tx["operation"] == "VOTE":
_validate_schema(TX_SCHEMA_TRANSFER_2_0, tx)
_validate_schema(TX_SCHEMA_VOTE_2_0, tx)
except KeyError:
raise SchemaValidationError()
27 changes: 27 additions & 0 deletions transactions/common/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,33 @@ def get_asset_obj(tx: dict):
asset_obj = tx["assets"]
return asset_obj

# This method returns a an array of assets for all types of transactions version schema
# This is to have an unique way of accessing the asset object in the business logic
@staticmethod
def get_asset_array(tx: dict):
asset_obj = None
if tx["version"] != "2.0":
asset_obj = tx["assets"]
else:
try:
asset_obj = [tx["asset"]]
except KeyError:
asset_obj = tx["assets"]
return asset_obj

# This method returns a an array of assets for all types of transactions version schema
# This is to have an unique way of accessing the asset object in the business logic
def get_assets(self):
asset_obj = None
if self.version != "2.0":
asset_obj = self.assets
else:
try:
asset_obj = [self.assets]
except KeyError:
asset_obj = self.assets
return asset_obj

@staticmethod
def read_out_asset_id(tx):
if tx.operation in (tx.CREATE, tx.COMPOSE, tx.VALIDATOR_ELECTION):
Expand Down
9 changes: 2 additions & 7 deletions transactions/types/assets/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
from transactions.common.transaction import Transaction
from transactions.common.input import Input
from transactions.common.output import Output
from transactions.common.schema import _validate_schema, TX_SCHEMA_COMMON, TX_SCHEMA_COMPOSE
from transactions.common.schema import validate_transaction_schema
from transactions.common.exceptions import SchemaValidationError


class Compose(Transaction):
OPERATION = "COMPOSE"
ALLOWED_OPERATIONS = (OPERATION,)
TX_SCHEMA_CUSTOM = TX_SCHEMA_COMPOSE

@classmethod
def validate_compose(
Expand Down Expand Up @@ -53,11 +52,7 @@ def validate_compose(

@classmethod
def validate_schema(cls, tx):
try:
_validate_schema(TX_SCHEMA_COMMON, tx)
_validate_schema(cls.TX_SCHEMA_CUSTOM, tx)
except KeyError:
raise SchemaValidationError()
validate_transaction_schema(tx)

@classmethod
def generate(
Expand Down
1 change: 0 additions & 1 deletion transactions/types/assets/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@


class Create(Transaction):

OPERATION = "CREATE"
ALLOWED_OPERATIONS = (OPERATION,)

Expand Down
9 changes: 2 additions & 7 deletions transactions/types/assets/decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
from transactions.common.transaction import Transaction
from transactions.common.input import Input
from transactions.common.output import Output
from transactions.common.schema import _validate_schema, TX_SCHEMA_COMMON, TX_SCHEMA_DECOMPOSE
from transactions.common.schema import validate_transaction_schema
from transactions.common.exceptions import SchemaValidationError


class Decompose(Transaction):
OPERATION = "DECOMPOSE"
ALLOWED_OPERATIONS = (OPERATION,)
TX_SCHEMA_CUSTOM = TX_SCHEMA_DECOMPOSE

@classmethod
def validate_decompose(
Expand Down Expand Up @@ -62,11 +61,7 @@ def validate_decompose(

@classmethod
def validate_schema(cls, tx):
try:
_validate_schema(TX_SCHEMA_COMMON, tx)
_validate_schema(cls.TX_SCHEMA_CUSTOM, tx)
except KeyError:
raise SchemaValidationError()
validate_transaction_schema(tx)

@classmethod
def generate(
Expand Down
1 change: 0 additions & 1 deletion transactions/types/assets/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@


class Transfer(Transaction):

OPERATION = "TRANSFER"
ALLOWED_OPERATIONS = (OPERATION,)

Expand Down
2 changes: 0 additions & 2 deletions transactions/types/elections/chain_migration_election.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,5 @@


class ChainMigrationElection(Election):

OPERATION = CHAIN_MIGRATION_ELECTION
ALLOWED_OPERATIONS = (OPERATION,)
TX_SCHEMA_CUSTOM = TX_SCHEMA_CHAIN_MIGRATION_ELECTION
12 changes: 2 additions & 10 deletions transactions/types/elections/election.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Optional

from transactions.common.transaction import Transaction
from transactions.common.schema import _validate_schema, TX_SCHEMA_COMMON, TX_SCHEMA_COMMON_2_0
from transactions.common.schema import validate_transaction_schema, SchemaValidationError


class Election(Transaction):
Expand All @@ -19,8 +19,6 @@ class Election(Transaction):
"""

OPERATION: Optional[str] = None
# Custom validation schema
TX_SCHEMA_CUSTOM = None
# Election Statuses:
ONGOING: str = "ongoing"
CONCLUDED: str = "concluded"
Expand Down Expand Up @@ -64,12 +62,6 @@ def validate_schema(cls, tx):
`CREATE` transaction should be inherited
"""
try:
if tx["version"] == "3.0":
_validate_schema(TX_SCHEMA_COMMON, tx)
else:
_validate_schema(TX_SCHEMA_COMMON_2_0, tx)
validate_transaction_schema(tx)
except KeyError:
raise SchemaValidationError()

if cls.TX_SCHEMA_CUSTOM:
_validate_schema(cls.TX_SCHEMA_CUSTOM, tx)
Loading

0 comments on commit 3e4ec1f

Please sign in to comment.