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:
Jürgen Eckel 2023-01-26 17:41:45 +01:00 committed by GitHub
parent cfa3b6dcd4
commit 599f64f68c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 236 additions and 293 deletions

View File

@ -25,6 +25,9 @@ For reference, the possible headings are:
* **Known Issues** * **Known Issues**
* **Notes** * **Notes**
## [2.1.0] - 2023-26-01
* **Added** validation for compose and decompose transaction types
## [2.0.0] - 2023-12-01 ## [2.0.0] - 2023-12-01
* **Changed** changed tarantool db schema * **Changed** changed tarantool db schema
* **Removed** removed text_search routes * **Removed** removed text_search routes

View File

@ -374,20 +374,70 @@ class Planetmint(object):
return False return False
if transaction.operation == Transaction.CREATE: if transaction.operation == Transaction.CREATE:
duplicates = any(txn for txn in current_transactions if txn.id == transaction.id) self.validate_create_inputs(transaction, current_transactions)
if self.is_committed(transaction.id) or duplicates:
raise DuplicateTransaction("transaction `{}` already exists".format(transaction.id))
elif transaction.operation in [Transaction.TRANSFER, Transaction.VOTE]: elif transaction.operation in [Transaction.TRANSFER, Transaction.VOTE]:
self.validate_transfer_inputs(transaction, current_transactions) self.validate_transfer_inputs(transaction, current_transactions)
elif transaction.operation in [Transaction.COMPOSE]:
self.validate_compose_inputs(transaction, current_transactions)
return transaction 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 # store the inputs so that we can check if the asset ids match
input_txs = [] input_txs = []
input_conditions = [] input_conditions = []
for input_ in tx.inputs: for input_ in inputs:
input_txid = input_.fulfills.txid input_txid = input_.fulfills.txid
input_tx = self.get_transaction(input_txid) input_tx = self.get_transaction(input_txid)
_output = self.get_outputs_by_tx_id(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) pm_transaction = Transaction.from_dict(tx_dict, False)
input_txs.append(pm_transaction) input_txs.append(pm_transaction)
# Validate that all inputs are distinct return (input_txs, input_conditions)
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"))
def validate_input_conditions(self, tx, input_conditions):
# convert planetmint.Output objects to transactions.common.Output objects # convert planetmint.Output objects to transactions.common.Output objects
input_conditions_dict = Output.list_to_dict(input_conditions) input_conditions_dict = Output.list_to_dict(input_conditions)
input_conditions_converted = [] input_conditions_converted = []
@ -435,17 +478,24 @@ class Planetmint(object):
if not tx.inputs_valid(input_conditions_converted): if not tx.inputs_valid(input_conditions_converted):
raise InvalidSignature("Transaction signature is invalid.") raise InvalidSignature("Transaction signature is invalid.")
input_amount = sum([input_condition.amount for input_condition in input_conditions]) def validate_asset_id(self, tx: Transaction, input_txs: list):
output_amount = sum([output_condition.amount for output_condition in tx.outputs]) # 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: def validate_inputs_distinct(self, tx):
raise AmountError( # Validate that all inputs are distinct
( links = [i.fulfills.to_uri() for i in tx.inputs]
"The amount used in the inputs `{}`" " needs to be same as the amount used" " in the outputs `{}`" if len(links) != len(set(links)):
).format(input_amount, output_amount) raise DoubleSpend('tx "{}" spends inputs twice'.format(tx.id))
)
return True
def is_valid_transaction(self, tx, current_transactions=[]): def is_valid_transaction(self, tx, current_transactions=[]):
# NOTE: the function returns the Transaction object in case # NOTE: the function returns the Transaction object in case

View File

@ -3,8 +3,8 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0 # Code is Apache-2.0 and docs are CC-BY-4.0
__version__ = "2.0.0" __version__ = "2.1.0"
__short_version__ = "2.0" __short_version__ = "2.1"
# Supported Tendermint versions # Supported Tendermint versions
__tm_supported_versions__ = ["0.34.15"] __tm_supported_versions__ = ["0.34.15"]

View File

@ -1,7 +1,7 @@
[pytest] [pytest]
testpaths = tests/ testpaths = tests/
norecursedirs = .* *.egg *.egg-info env* devenv* docs norecursedirs = .* *.egg *.egg-info env* devenv* docs
#addopts = -m "not abci" addopts = -m "abci"
looponfailroots = planetmint tests looponfailroots = planetmint tests
asyncio_mode = strict asyncio_mode = strict
markers = markers =

View File

@ -129,7 +129,7 @@ install_requires = [
"planetmint-ipld>=0.0.3", "planetmint-ipld>=0.0.3",
"pyasn1>=0.4.8", "pyasn1>=0.4.8",
"python-decouple", "python-decouple",
"planetmint-transactions>=0.5.0", "planetmint-transactions>=0.6.0",
] ]
setup( setup(

View File

@ -7,6 +7,8 @@ import pytest
from transactions.types.assets.create import Create from transactions.types.assets.create import Create
from transactions.types.assets.transfer import Transfer 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): 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]) 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): def test_create_valid_divisible_asset(b, user_pk, user_sk, _bdb):
tx = Create.generate([user_pk], [([user_pk], 2)]) tx = Create.generate([user_pk], [([user_pk], 2)])
tx_signed = tx.sign([user_sk]) tx_signed = tx.sign([user_sk])

View File

@ -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)

View 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

View File

@ -15,12 +15,21 @@ from hashlib import sha3_256
from transactions.common import crypto from transactions.common import crypto
from transactions.common.transaction import Transaction from transactions.common.transaction import Transaction
from transactions.types.assets.create import Create 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.types.assets.transfer import Transfer
from transactions.common.transaction_mode_types import ( from transactions.common.transaction_mode_types import (
BROADCAST_TX_COMMIT, BROADCAST_TX_COMMIT,
BROADCAST_TX_ASYNC, BROADCAST_TX_ASYNC,
BROADCAST_TX_SYNC, 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/" 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): def test_post_transaction_valid_modes(mock_post, client, mode):
from transactions.common.crypto import generate_key_pair
def _mock_post(*args, **kwargs): def _mock_post(*args, **kwargs):
return Mock(json=Mock(return_value={"result": {"code": 0}})) 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 @pytest.mark.abci
def test_post_transaction_invalid_mode(client): def test_post_transaction_invalid_mode(client):
from transactions.common.crypto import generate_key_pair
alice = generate_key_pair() alice = generate_key_pair()
tx = Create.generate([alice.public_key], [([alice.public_key], 1)], assets=None).sign([alice.private_key]) tx = Create.generate([alice.public_key], [([alice.public_key], 1)], assets=None).sign([alice.private_key])
mode_endpoint = TX_ENDPOINT + "?mode=nope" mode_endpoint = TX_ENDPOINT + "?mode=nope"
response = client.post(mode_endpoint, data=json.dumps(tx.to_dict())) response = client.post(mode_endpoint, data=json.dumps(tx.to_dict()))
assert "400 BAD REQUEST" in response.status assert "400 BAD REQUEST" in response.status
assert 'Mode must be "async", "sync" or "commit"' == json.loads(response.data.decode("utf8"))["message"]["mode"] 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