mirror of
https://github.com/planetmint/planetmint.git
synced 2025-06-07 14:46:38 +00:00
Compose & Decompose Support (#304)
Added the newest transaction package and support for Compose and Decompose as specified in PRP-5 https://github.com/planetmint/PRPs/tree/main/5
This commit is contained in:
parent
cfa3b6dcd4
commit
599f64f68c
@ -25,6 +25,9 @@ For reference, the possible headings are:
|
||||
* **Known Issues**
|
||||
* **Notes**
|
||||
|
||||
## [2.1.0] - 2023-26-01
|
||||
* **Added** validation for compose and decompose transaction types
|
||||
|
||||
## [2.0.0] - 2023-12-01
|
||||
* **Changed** changed tarantool db schema
|
||||
* **Removed** removed text_search routes
|
||||
|
@ -374,20 +374,70 @@ class Planetmint(object):
|
||||
return False
|
||||
|
||||
if transaction.operation == Transaction.CREATE:
|
||||
duplicates = any(txn for txn in current_transactions if txn.id == transaction.id)
|
||||
if self.is_committed(transaction.id) or duplicates:
|
||||
raise DuplicateTransaction("transaction `{}` already exists".format(transaction.id))
|
||||
self.validate_create_inputs(transaction, current_transactions)
|
||||
elif transaction.operation in [Transaction.TRANSFER, Transaction.VOTE]:
|
||||
self.validate_transfer_inputs(transaction, current_transactions)
|
||||
elif transaction.operation in [Transaction.COMPOSE]:
|
||||
self.validate_compose_inputs(transaction, current_transactions)
|
||||
|
||||
return transaction
|
||||
|
||||
def validate_transfer_inputs(self, tx, current_transactions=[]):
|
||||
def validate_create_inputs(self, tx, current_transactions=[]) -> bool:
|
||||
duplicates = any(txn for txn in current_transactions if txn.id == tx.id)
|
||||
if self.is_committed(tx.id) or duplicates:
|
||||
raise DuplicateTransaction("transaction `{}` already exists".format(tx.id))
|
||||
|
||||
fulfilling_inputs = [i for i in tx.inputs if i.fulfills is not None and i.fulfills.txid is not None]
|
||||
|
||||
if len(fulfilling_inputs) > 0:
|
||||
input_txs, input_conditions = self.get_input_txs_and_conditions(fulfilling_inputs, current_transactions)
|
||||
create_asset = tx.assets[0]
|
||||
input_asset = input_txs[0].assets[tx.inputs[0].fulfills.output]["data"]
|
||||
if create_asset != input_asset:
|
||||
raise ValidationError("CREATE must have matching asset description with input transaction")
|
||||
if input_txs[0].operation != Transaction.DECOMPOSE:
|
||||
raise SchemaValidationError("CREATE can only consume DECOMPOSE outputs")
|
||||
|
||||
return True
|
||||
|
||||
def validate_transfer_inputs(self, tx, current_transactions=[]) -> bool:
|
||||
input_txs, input_conditions = self.get_input_txs_and_conditions(tx.inputs, current_transactions)
|
||||
|
||||
self.validate_input_conditions(tx, input_conditions)
|
||||
|
||||
self.validate_asset_id(tx, input_txs)
|
||||
|
||||
self.validate_inputs_distinct(tx)
|
||||
|
||||
input_amount = sum([input_condition.amount for input_condition in input_conditions])
|
||||
output_amount = sum([output_condition.amount for output_condition in tx.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)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def validate_compose_inputs(self, tx, current_transactions=[]) -> bool:
|
||||
input_txs, input_conditions = self.get_input_txs_and_conditions(tx.inputs, current_transactions)
|
||||
|
||||
self.validate_input_conditions(tx, input_conditions)
|
||||
|
||||
self.validate_asset_id(tx, input_txs)
|
||||
|
||||
self.validate_inputs_distinct(tx)
|
||||
|
||||
return True
|
||||
|
||||
def get_input_txs_and_conditions(self, inputs, current_transactions=[]):
|
||||
# store the inputs so that we can check if the asset ids match
|
||||
input_txs = []
|
||||
input_conditions = []
|
||||
|
||||
for input_ in tx.inputs:
|
||||
for input_ in inputs:
|
||||
input_txid = input_.fulfills.txid
|
||||
input_tx = self.get_transaction(input_txid)
|
||||
_output = self.get_outputs_by_tx_id(input_txid)
|
||||
@ -416,16 +466,9 @@ class Planetmint(object):
|
||||
pm_transaction = Transaction.from_dict(tx_dict, False)
|
||||
input_txs.append(pm_transaction)
|
||||
|
||||
# Validate that all inputs are distinct
|
||||
links = [i.fulfills.to_uri() for i in tx.inputs]
|
||||
if len(links) != len(set(links)):
|
||||
raise DoubleSpend('tx "{}" spends inputs twice'.format(tx.id))
|
||||
|
||||
# validate asset id
|
||||
asset_id = tx.get_asset_id(input_txs)
|
||||
if asset_id != Transaction.read_out_asset_id(tx):
|
||||
raise AssetIdMismatch(("The asset id of the input does not" " match the asset id of the" " transaction"))
|
||||
return (input_txs, input_conditions)
|
||||
|
||||
def validate_input_conditions(self, tx, input_conditions):
|
||||
# convert planetmint.Output objects to transactions.common.Output objects
|
||||
input_conditions_dict = Output.list_to_dict(input_conditions)
|
||||
input_conditions_converted = []
|
||||
@ -435,17 +478,24 @@ class Planetmint(object):
|
||||
if not tx.inputs_valid(input_conditions_converted):
|
||||
raise InvalidSignature("Transaction signature is invalid.")
|
||||
|
||||
input_amount = sum([input_condition.amount for input_condition in input_conditions])
|
||||
output_amount = sum([output_condition.amount for output_condition in tx.outputs])
|
||||
def validate_asset_id(self, tx: Transaction, input_txs: list):
|
||||
# validate asset
|
||||
if tx.operation != Transaction.COMPOSE:
|
||||
asset_id = tx.get_asset_id(input_txs)
|
||||
if asset_id != Transaction.read_out_asset_id(tx):
|
||||
raise AssetIdMismatch(
|
||||
("The asset id of the input does not" " match the asset id of the" " transaction")
|
||||
)
|
||||
else:
|
||||
asset_ids = Transaction.get_asset_ids(input_txs)
|
||||
if Transaction.read_out_asset_id(tx) in asset_ids:
|
||||
raise AssetIdMismatch(("The asset ID of the compose must be different to all of its input asset IDs"))
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
return True
|
||||
def validate_inputs_distinct(self, tx):
|
||||
# Validate that all inputs are distinct
|
||||
links = [i.fulfills.to_uri() for i in tx.inputs]
|
||||
if len(links) != len(set(links)):
|
||||
raise DoubleSpend('tx "{}" spends inputs twice'.format(tx.id))
|
||||
|
||||
def is_valid_transaction(self, tx, current_transactions=[]):
|
||||
# NOTE: the function returns the Transaction object in case
|
||||
|
@ -3,8 +3,8 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||
|
||||
__version__ = "2.0.0"
|
||||
__short_version__ = "2.0"
|
||||
__version__ = "2.1.0"
|
||||
__short_version__ = "2.1"
|
||||
|
||||
# Supported Tendermint versions
|
||||
__tm_supported_versions__ = ["0.34.15"]
|
||||
|
@ -1,7 +1,7 @@
|
||||
[pytest]
|
||||
testpaths = tests/
|
||||
norecursedirs = .* *.egg *.egg-info env* devenv* docs
|
||||
#addopts = -m "not abci"
|
||||
addopts = -m "abci"
|
||||
looponfailroots = planetmint tests
|
||||
asyncio_mode = strict
|
||||
markers =
|
||||
|
2
setup.py
2
setup.py
@ -129,7 +129,7 @@ install_requires = [
|
||||
"planetmint-ipld>=0.0.3",
|
||||
"pyasn1>=0.4.8",
|
||||
"python-decouple",
|
||||
"planetmint-transactions>=0.5.0",
|
||||
"planetmint-transactions>=0.6.0",
|
||||
]
|
||||
|
||||
setup(
|
||||
|
@ -7,6 +7,8 @@ import pytest
|
||||
|
||||
from transactions.types.assets.create import Create
|
||||
from transactions.types.assets.transfer import Transfer
|
||||
from transactions.types.assets.compose import Compose
|
||||
from transactions.types.assets.decompose import Decompose
|
||||
|
||||
|
||||
def test_asset_transfer(b, signed_create_tx, user_pk, user_sk, _bdb):
|
||||
@ -69,6 +71,59 @@ def test_asset_id_mismatch(alice, user_pk):
|
||||
Transaction.get_asset_id([tx1, tx2])
|
||||
|
||||
|
||||
def test_compose_valid_transactions(b, user_pk, user_sk, alice, signed_create_tx, _bdb):
|
||||
validated = b.validate_transaction(signed_create_tx)
|
||||
b.store_bulk_transactions([validated])
|
||||
|
||||
inputs = signed_create_tx.to_inputs()
|
||||
assets = [signed_create_tx.id, "QmW5GVMW98D3mktSDfWHS8nX2UiCd8gP1uCiujnFX4yK8n"]
|
||||
compose_transaction = Compose.generate(inputs=inputs, recipients=[([user_pk], 1)], assets=assets)
|
||||
compose_transaction.sign([user_sk])
|
||||
assert b.validate_transaction(compose_transaction)
|
||||
|
||||
|
||||
def test_decompose_valid_transactions(b, user_pk, user_sk, alice, signed_create_tx, _bdb):
|
||||
validated = b.validate_transaction(signed_create_tx)
|
||||
b.store_bulk_transactions([validated])
|
||||
|
||||
inputs = signed_create_tx.to_inputs()
|
||||
assets = [
|
||||
signed_create_tx.id,
|
||||
"bafkreiawyk3ou5qzqec4ggbvrs56dv5ske2viwprf6he5wj5gr4yv5orsu",
|
||||
"bafkreibncbonglm6mi3znbrqbchk56wmgftk4gfevxqlgeif3g5jdotcka",
|
||||
"bafkreibkokzihpnnyqf3xslcievqkadf2ozkdi72wyibijih447vq42kjm",
|
||||
]
|
||||
decompose_transaction = Decompose.generate(
|
||||
inputs=inputs, recipients=[([user_pk], 1), ([user_pk], 2), ([user_pk], 3)], assets=assets
|
||||
)
|
||||
decompose_transaction.sign([user_sk])
|
||||
assert b.validate_transaction(decompose_transaction)
|
||||
|
||||
|
||||
def test_create_decompose_output(b, user_pk, user_sk, signed_create_tx, _bdb):
|
||||
validated = b.validate_transaction(signed_create_tx)
|
||||
b.store_bulk_transactions([validated])
|
||||
|
||||
inputs = signed_create_tx.to_inputs()
|
||||
assets = [
|
||||
"bafkreiawyk3ou5qzqec4ggbvrs56dv5ske2viwprf6he5wj5gr4yv5orsu",
|
||||
"bafkreibncbonglm6mi3znbrqbchk56wmgftk4gfevxqlgeif3g5jdotcka",
|
||||
"bafkreibkokzihpnnyqf3xslcievqkadf2ozkdi72wyibijih447vq42kjm",
|
||||
signed_create_tx.id,
|
||||
]
|
||||
decompose_transaction = Decompose.generate(
|
||||
inputs=inputs, recipients=[([user_pk], 1), ([user_pk], 2), ([user_pk], 3)], assets=assets
|
||||
)
|
||||
decompose_transaction.sign([user_sk])
|
||||
validated_decompose = b.validate_transaction(decompose_transaction)
|
||||
b.store_bulk_transactions([validated_decompose])
|
||||
|
||||
create_inputs = decompose_transaction.to_inputs([0])
|
||||
create_tx = Create.generate([user_pk], recipients=[([user_pk], 1)], assets=[assets[0]], inputs=create_inputs)
|
||||
signed_decompose_create_tx = create_tx.sign([user_sk])
|
||||
assert b.validate_transaction(signed_decompose_create_tx)
|
||||
|
||||
|
||||
def test_create_valid_divisible_asset(b, user_pk, user_sk, _bdb):
|
||||
tx = Create.generate([user_pk], [([user_pk], 2)])
|
||||
tx_signed = tx.sign([user_sk])
|
||||
|
@ -1,261 +0,0 @@
|
||||
# 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(b, 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(b, 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(b, 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(b, create_tx, alice):
|
||||
create_tx.assets[0]["data"] = multihash(marshal({"a": ""}))
|
||||
signed_tx = create_tx.sign([alice.private_key])
|
||||
validate(signed_tx)
|
||||
# validate_raises(signed_tx)
|
||||
|
||||
|
||||
def test_create_tx_no_asset_data(b, create_tx, alice):
|
||||
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(b, create_tx, alice):
|
||||
create_tx.inputs = []
|
||||
signed_tx = create_tx.sign([alice.private_key])
|
||||
validate_raises(signed_tx)
|
||||
|
||||
|
||||
def test_create_single_input(b, 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_create_tx_no_fulfills(b, create_tx, alice):
|
||||
from transactions.common.transaction import Transaction
|
||||
|
||||
tx = create_tx.to_dict()
|
||||
tx["inputs"][0]["fulfills"] = {"transaction_id": "a" * 64, "output_index": 0}
|
||||
tx = Transaction.from_dict(tx).sign([alice.private_key]).to_dict()
|
||||
validate_raises(tx)
|
||||
|
||||
|
||||
def test_transfer_has_inputs(user_sk, signed_transfer_tx, alice):
|
||||
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(b, 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(b, 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(b, 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)
|
10
tests/validation/test_validation.py
Normal file
10
tests/validation/test_validation.py
Normal file
@ -0,0 +1,10 @@
|
||||
# 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
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_something():
|
||||
return True
|
@ -15,12 +15,21 @@ from hashlib import sha3_256
|
||||
from transactions.common import crypto
|
||||
from transactions.common.transaction import Transaction
|
||||
from transactions.types.assets.create import Create
|
||||
from transactions.types.assets.compose import Compose
|
||||
from transactions.types.assets.decompose import Decompose
|
||||
from transactions.types.assets.transfer import Transfer
|
||||
from transactions.common.transaction_mode_types import (
|
||||
BROADCAST_TX_COMMIT,
|
||||
BROADCAST_TX_ASYNC,
|
||||
BROADCAST_TX_SYNC,
|
||||
)
|
||||
from transactions.common.transaction import (
|
||||
Input,
|
||||
TransactionLink,
|
||||
)
|
||||
from transactions.common.utils import _fulfillment_from_details
|
||||
from transactions.common.crypto import generate_key_pair
|
||||
|
||||
|
||||
TX_ENDPOINT = "/api/v1/transactions/"
|
||||
|
||||
@ -448,8 +457,6 @@ def test_transactions_get_list_bad(client):
|
||||
],
|
||||
)
|
||||
def test_post_transaction_valid_modes(mock_post, client, mode):
|
||||
from transactions.common.crypto import generate_key_pair
|
||||
|
||||
def _mock_post(*args, **kwargs):
|
||||
return Mock(json=Mock(return_value={"result": {"code": 0}}))
|
||||
|
||||
@ -465,11 +472,90 @@ def test_post_transaction_valid_modes(mock_post, client, mode):
|
||||
|
||||
@pytest.mark.abci
|
||||
def test_post_transaction_invalid_mode(client):
|
||||
from transactions.common.crypto import generate_key_pair
|
||||
|
||||
alice = generate_key_pair()
|
||||
tx = Create.generate([alice.public_key], [([alice.public_key], 1)], assets=None).sign([alice.private_key])
|
||||
mode_endpoint = TX_ENDPOINT + "?mode=nope"
|
||||
response = client.post(mode_endpoint, data=json.dumps(tx.to_dict()))
|
||||
assert "400 BAD REQUEST" in response.status
|
||||
assert 'Mode must be "async", "sync" or "commit"' == json.loads(response.data.decode("utf8"))["message"]["mode"]
|
||||
|
||||
|
||||
def test_post_transaction_compose_valid_wo_abci(b, _bdb):
|
||||
alice = generate_key_pair()
|
||||
tx = Create.generate(
|
||||
[alice.public_key],
|
||||
[([alice.public_key], 1)],
|
||||
assets=[{"data": "QmW5GVMW98D3mktSDfWHS8nX2UiCd8gP1uCiujnFX4yK97"}],
|
||||
).sign([alice.private_key])
|
||||
validated = b.validate_transaction(tx)
|
||||
b.store_bulk_transactions([validated])
|
||||
|
||||
tx_obj = tx
|
||||
tx = tx.to_dict()
|
||||
compose_asset_cid = "bafkreignwcoye67vn6edp23mj4llhpzzkgyuefu7xesjzjxcv2bz3p4nfm"
|
||||
inputs_ = tx_obj.to_inputs()
|
||||
|
||||
assets_ = [tx["id"], compose_asset_cid]
|
||||
compose_transaction = Compose.generate(inputs=inputs_, recipients=[([alice.public_key], 1)], assets=assets_)
|
||||
signed_compose_tx = compose_transaction.sign([alice.private_key])
|
||||
compose_dict = signed_compose_tx.to_dict()
|
||||
compose_obj = Transaction.from_dict(compose_dict)
|
||||
validated_compose = b.validate_transaction(compose_obj)
|
||||
b.store_bulk_transactions([validated_compose])
|
||||
|
||||
|
||||
@pytest.mark.abci
|
||||
def test_post_transaction_compose_valid(client, b):
|
||||
mode = ("?mode=commit", BROADCAST_TX_COMMIT)
|
||||
alice = generate_key_pair()
|
||||
tx = Create.generate(
|
||||
[alice.public_key],
|
||||
[([alice.public_key], 1)],
|
||||
assets=[{"data": "QmW5GVMW98D3mktSDfWHS8nX2UiCd8gP1uCiujnFX4yK97"}],
|
||||
).sign([alice.private_key])
|
||||
mode_endpoint = TX_ENDPOINT + mode[0]
|
||||
response = client.post(mode_endpoint, data=json.dumps(tx.to_dict()))
|
||||
assert "202 ACCEPTED" in response.status
|
||||
tx_obj = tx
|
||||
tx = tx.to_dict()
|
||||
compose_asset_cid = "bafkreignwcoye67vn6edp23mj4llhpzzkgyuefu7xesjzjxcv2bz3p4nfm"
|
||||
inputs_ = tx_obj.to_inputs()
|
||||
|
||||
assets_ = [tx["id"], compose_asset_cid]
|
||||
compose_transaction = Compose.generate(inputs=inputs_, recipients=[([alice.public_key], 1)], assets=assets_)
|
||||
signed_tx = compose_transaction.sign([alice.private_key])
|
||||
validated_compose = b.validate_transaction(signed_tx)
|
||||
mode_endpoint = TX_ENDPOINT + "?mode=commit"
|
||||
response = client.post(mode_endpoint, data=json.dumps(signed_tx.to_dict()))
|
||||
assert "202 ACCEPTED" in response.status
|
||||
|
||||
|
||||
@pytest.mark.abci
|
||||
def test_post_transaction_decompose_valid(client, b):
|
||||
mode = ("?mode=commit", BROADCAST_TX_COMMIT)
|
||||
alice = generate_key_pair()
|
||||
tx = Create.generate(
|
||||
[alice.public_key],
|
||||
[([alice.public_key], 1)],
|
||||
assets=[{"data": "QmW5GVMW98D3mktSDfWHS8nX2UiCd8gP1uCiujnFX4yK97"}],
|
||||
).sign([alice.private_key])
|
||||
mode_endpoint = TX_ENDPOINT + mode[0]
|
||||
response = client.post(mode_endpoint, data=json.dumps(tx.to_dict()))
|
||||
assert "202 ACCEPTED" in response.status
|
||||
tx_obj = tx
|
||||
tx = tx.to_dict()
|
||||
inputs_ = tx_obj.to_inputs()
|
||||
|
||||
assets = [
|
||||
tx["id"],
|
||||
"bafkreiawyk3ou5qzqec4ggbvrs56dv5ske2viwprf6he5wj5gr4yv5orsu",
|
||||
"bafkreibncbonglm6mi3znbrqbchk56wmgftk4gfevxqlgeif3g5jdotcka",
|
||||
"bafkreibkokzihpnnyqf3xslcievqkadf2ozkdi72wyibijih447vq42kjm",
|
||||
]
|
||||
recipients = [([alice.public_key], 1), ([alice.public_key], 2), ([alice.public_key], 3)]
|
||||
decompose_transaction = Decompose.generate(inputs=inputs_, recipients=recipients, assets=assets)
|
||||
signed_tx = decompose_transaction.sign([alice.private_key])
|
||||
validated_decompose = b.validate_transaction(signed_tx)
|
||||
mode_endpoint = TX_ENDPOINT + "?mode=commit"
|
||||
response = client.post(mode_endpoint, data=json.dumps(signed_tx.to_dict()))
|
||||
assert "202 ACCEPTED" in response.status
|
||||
|
Loading…
x
Reference in New Issue
Block a user