added initial interfaces for backend, refactored Asset and MetaData logic

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
Lorenz Herzberger 2022-11-14 14:43:14 +01:00
parent 713bd5267c
commit 2694974a37
No known key found for this signature in database
GPG Key ID: FA5EE906EB55316A
7 changed files with 132 additions and 82 deletions

View File

@ -0,0 +1,70 @@
# 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
from dataclasses import dataclass
# Asset should represent a single asset (e.g.: tarantool tuple (data, tx_id, asset_id))
# If multiple assets are stored at once this should remain the same.
# For Create ({'data': 'values'}, c_tx_id, c_tx_id), For Transfer ({'id': c_tx_id}, tx_id, c_tx_id)
@dataclass
class Asset:
id: str = None
tx_id: str = None
data: dict = None
@dataclass
class MetaData:
id: str = None
metadata: str = None
@dataclass
class Input:
id: str = None
@dataclass
class Output:
id: str = None
@dataclass
class Block:
id: str = None
app_hash: str = None
height: int = None
@dataclass
class Script:
id: str = None
script = None
@dataclass
class UTXO:
id: str = None
output_index: int = None
utxo: dict = None
@dataclass
class Transaction:
id: str = None
assets: list[Asset] = None
metadata: MetaData = None
version: str = None # TODO: make enum
operation: str = None # TODO: make enum
inputs: list[Input] = None
outputs: list[Output] = None
script: str = None
@dataclass
class Validator:
id: str = None
height: int = None
validators = None
@dataclass
class ABCIChain:
height: str = None
is_synced: bool = None
chain_id: str = None

View File

@ -7,11 +7,10 @@
from functools import singledispatch
from planetmint.backend.exceptions import OperationError
from planetmint.backend.interfaces import Asset, MetaData
# FIXME ADD HERE HINT FOR RETURNING TYPE
@singledispatch
def store_asset(asset: dict, connection):
def store_asset(connection, asset: dict) -> Asset:
"""Write an asset to the asset table.
Args:
@ -25,7 +24,7 @@ def store_asset(asset: dict, connection):
@singledispatch
def store_assets(assets: list, connection):
def store_assets(connection, assets: list) -> list[Asset]:
"""Write a list of assets to the assets table.
backend
Args:
@ -39,7 +38,7 @@ def store_assets(assets: list, connection):
@singledispatch
def store_metadatas(connection, metadata):
def store_metadatas(connection, metadata) -> MetaData:
"""Write a list of metadata to metadata table.
Args:
@ -88,7 +87,7 @@ def get_transactions(connection, transaction_ids):
@singledispatch
def get_asset(connection, asset_id):
def get_asset(connection, asset_id) -> Asset:
"""Get an asset from the assets table.
Args:
@ -191,7 +190,7 @@ def get_metadata(connection, transaction_ids):
@singledispatch
def get_assets(connection, asset_ids) -> list:
def get_assets(connection, asset_ids) -> list[Asset]:
"""Get a list of assets from the assets table.
Args:
asset_ids (list): a list of ids for the assets to be retrieved from

View File

@ -12,10 +12,10 @@ from operator import itemgetter
from tarantool.error import DatabaseError
from planetmint.backend import query
from planetmint.backend.utils import module_dispatch_registrar
from planetmint.backend.interfaces import Asset, MetaData
from planetmint.backend.tarantool.connection import TarantoolDBConnection
from planetmint.backend.tarantool.transaction.tools import TransactionCompose, TransactionDecompose
register_query = module_dispatch_registrar(query)
@ -91,11 +91,11 @@ def get_transactions(connection, transactions_ids: list):
@register_query(TarantoolDBConnection)
def store_metadatas(connection, metadata: list):
def store_metadatas(connection, metadata: list[MetaData]):
for meta in metadata:
connection.run(
connection.space("meta_data").insert(
(meta["id"], json.dumps(meta["data"] if not "metadata" in meta else meta["metadata"]))
(meta.id, json.dumps(meta.metadata))
) # noqa: E713
)
@ -115,17 +115,10 @@ def get_metadata(connection, transaction_ids: list):
@register_query(TarantoolDBConnection)
def store_asset(connection, asset):
def convert(obj):
if isinstance(obj, tuple):
obj = list(obj)
obj[0] = json.dumps(obj[0])
return tuple(obj)
else:
return (json.dumps(obj), obj["id"], obj["id"])
def store_asset(connection, asset: Asset):
asset = (json.dumps(asset.data), asset.tx_id, asset.id)
try:
return connection.run(connection.space("assets").insert(convert(asset)), only_data=False)
return connection.run(connection.space("assets").insert(asset), only_data=False)
except DatabaseError:
pass
@ -137,21 +130,20 @@ def store_assets(connection, assets: list):
@register_query(TarantoolDBConnection)
def get_asset(connection, asset_id: str):
def get_asset(connection, asset_id: str) -> Asset:
_data = connection.run(connection.space("assets").select(asset_id, index="txid_search"))
return json.loads(_data[0][0]) if len(_data) > 0 else []
return Asset(_data[0][2], _data[0][1], json.loads(_data[0][0]))
@register_query(TarantoolDBConnection)
def get_assets(connection, assets_ids: list) -> list:
def get_assets(connection, assets_ids: list) -> list[Asset]:
_returned_data = []
for _id in list(set(assets_ids)):
res = connection.run(connection.space("assets").select(_id, index="txid_search"))
_returned_data.append(res[0])
sorted_assets = sorted(_returned_data, key=lambda k: k[1], reverse=False)
return [(json.loads(asset[0]), asset[1]) for asset in sorted_assets]
return [Asset(asset[2], asset[1], json.loads(asset[0])) for asset in sorted_assets]
@register_query(TarantoolDBConnection)
@ -238,7 +230,7 @@ def text_search(conn, search, table="assets", limit=0):
if len(res[0]): # NEEDS BEAUTIFICATION
if table == "assets":
for result in res[0]:
to_return.append({"data": json.loads(result[0])[0]["data"], "id": result[1]})
to_return.append({"data": json.loads(result[0])["data"], "id": result[1]})
else:
for result in res[0]:
to_return.append({"metadata": json.loads(result[1]), "id": result[0]})

View File

@ -54,6 +54,7 @@ from planetmint.tendermint_utils import (
)
from planetmint import exceptions as core_exceptions
from planetmint.validation import BaseValidationRules
from planetmint.backend.interfaces import Asset, MetaData
logger = logging.getLogger(__name__)
@ -149,20 +150,15 @@ class Planetmint(object):
tx_assets = transaction.pop("assets")
metadata = transaction.pop("metadata")
tx_assets = backend.convert.prepare_asset(
self.connection,
transaction_type=transaction["operation"],
transaction_id=transaction["id"],
filter_operation=[t.CREATE, t.VALIDATOR_ELECTION, t.CHAIN_MIGRATION_ELECTION],
assets=tx_assets,
)
if tx_assets is not None:
for asset in tx_assets:
id = transaction["id"] if "id" not in asset else asset["id"]
tx_asset = Asset(id, transaction["id"], asset)
assets.append(tx_asset)
metadata = backend.convert.prepare_metadata(
self.connection, transaction_id=transaction["id"], metadata=metadata
)
metadata = MetaData(transaction["id"], metadata)
txn_metadatas.append(metadata)
assets.append(tx_assets)
txns.append(transaction)
backend.query.store_metadatas(self.connection, txn_metadatas)
@ -258,10 +254,7 @@ class Planetmint(object):
if transaction:
assets = backend.query.get_assets(self.connection, [transaction_id])
metadata = backend.query.get_metadata(self.connection, [transaction_id])
# NOTE: assets must not be replaced for transfer transactions
# NOTE: assets should be appended for all txs that define new assets otherwise the ids are already stored in tx
if transaction["operation"] != "TRANSFER" and transaction["operation"] != "VOTE" and assets:
transaction["assets"] = assets[0][0]
transaction["assets"] = [asset.data for asset in assets]
if "metadata" not in transaction:
metadata = metadata[0] if metadata else None
@ -323,6 +316,8 @@ class Planetmint(object):
raise DoubleSpend('tx "{}" spends inputs twice'.format(txid))
elif transactions:
transaction = backend.query.get_transaction(self.connection, transactions[0]["id"])
assets = backend.query.get_assets(self.connection, [transaction["id"]])
transaction["assets"] = [asset.data for asset in assets]
transaction = Transaction.from_dict(transaction, False)
elif current_spent_transactions:
transaction = current_spent_transactions[0]
@ -476,7 +471,7 @@ class Planetmint(object):
"""
return backend.query.text_search(self.connection, search, limit=limit, table=table)
def get_assets(self, asset_ids):
def get_assets(self, asset_ids) -> list[Asset]:
"""Return a list of assets that match the asset_ids
Args:
@ -597,12 +592,12 @@ class Planetmint(object):
tx_map[tx["id"]] = tx
tx_ids.append(tx["id"])
assets = list(self.get_assets(tx_ids))
# TODO: this will break if more than one asset is used
assets = self.get_assets(tx_ids)
for asset in assets:
if asset is not None:
# This is tarantool specific behaviour needs to be addressed
tx = tx_map[asset[1]]
tx["asset"] = asset[0]
tx = tx_map[asset.id]
tx["assets"] = [asset.data]
tx_ids = list(tx_map.keys())
metadata_list = list(self.get_metadata(tx_ids))

View File

@ -137,7 +137,7 @@ install_requires = [
"pyasn1>=0.4.8",
"cryptography==3.4.7",
"python-decouple",
"planetmint-transactions==0.2.0",
"planetmint-transactions==0.2.1",
]
setup(

View File

@ -10,6 +10,7 @@ import json
from transactions.common.transaction import Transaction
from transactions.types.assets.create import Create
from transactions.types.assets.transfer import Transfer
from planetmint.backend.interfaces import Asset, MetaData
pytestmark = pytest.mark.bdb
@ -44,11 +45,11 @@ def test_write_assets(db_conn):
from planetmint.backend.tarantool import query
assets = [
{"id": "1", "data": "1"},
{"id": "2", "data": "2"},
{"id": "3", "data": "3"},
Asset("1", "1", "1"),
Asset("2", "2", "2"),
Asset("3", "3", "3"),
# Duplicated id. Should not be written to the database
{"id": "1", "data": "1"},
Asset("1", "1", "1")
]
# write the assets
@ -56,25 +57,25 @@ def test_write_assets(db_conn):
query.store_asset(connection=db_conn, asset=asset)
# check that 3 assets were written to the database
documents = query.get_assets(assets_ids=[asset["id"] for asset in assets], connection=db_conn)
documents = query.get_assets(assets_ids=[asset.id for asset in assets], connection=db_conn)
assert len(documents) == 3
assert list(documents)[0][0] == assets[:-1][0]
assert list(documents)[0] == assets[:-1][0]
def test_get_assets(db_conn):
from planetmint.backend.tarantool import query
assets = [
("1", "1", "1"),
("2", "2", "2"),
("3", "3", "3"),
Asset("1", "1"),
Asset("2", "2"),
Asset("3", "3"),
]
query.store_assets(assets=assets, connection=db_conn)
for asset in assets:
assert query.get_asset(asset_id=asset[2], connection=db_conn)
assert query.get_asset(asset_id=asset.id, connection=db_conn)
@pytest.mark.parametrize("table", ["assets", "metadata"])
@ -164,17 +165,17 @@ def test_text_search(table):
def test_write_metadata(db_conn):
from planetmint.backend.tarantool import query
metadata = [{"id": "1", "data": "1"}, {"id": "2", "data": "2"}, {"id": "3", "data": "3"}]
metadata = [MetaData("1", "1"), MetaData("2", "2"), MetaData("3", "3")]
# write the assets
query.store_metadatas(connection=db_conn, metadata=metadata)
# check that 3 assets were written to the database
metadatas = []
for meta in metadata:
_data = db_conn.run(db_conn.space("meta_data").select(meta["id"]))[0]
metadatas.append({"id": _data[0], "data": json.loads(_data[1])})
_data = db_conn.run(db_conn.space("meta_data").select(meta.id))[0]
metadatas.append(MetaData(_data[0], json.loads(_data[1])))
metadatas = sorted(metadatas, key=lambda k: k["id"])
metadatas = sorted(metadatas, key=lambda k: k.id)
assert len(metadatas) == 3
assert list(metadatas) == metadata
@ -184,15 +185,15 @@ def test_get_metadata(db_conn):
from planetmint.backend.tarantool import query
metadata = [
{"id": "dd86682db39e4b424df0eec1413cfad65488fd48712097c5d865ca8e8e059b64", "metadata": None},
{"id": "55a2303e3bcd653e4b5bd7118d39c0e2d48ee2f18e22fbcf64e906439bdeb45d", "metadata": {"key": "value"}},
MetaData("dd86682db39e4b424df0eec1413cfad65488fd48712097c5d865ca8e8e059b64", None),
MetaData("55a2303e3bcd653e4b5bd7118d39c0e2d48ee2f18e22fbcf64e906439bdeb45d", {"key": "value"})
]
# conn.db.metadata.insert_many(deepcopy(metadata), ordered=False)
query.store_metadatas(connection=db_conn, metadata=metadata)
for meta in metadata:
_m = query.get_metadata(connection=db_conn, transaction_ids=[meta["id"]])
_m = query.get_metadata(connection=db_conn, transaction_ids=[meta.id])
assert _m

View File

@ -20,6 +20,7 @@ from transactions.common.transaction_mode_types import (
BROADCAST_TX_SYNC,
)
from planetmint.lib import Block
from planetmint.backend.interfaces import Asset, MetaData
from ipld import marshal, multihash
@ -195,12 +196,12 @@ def test_store_transaction(mocker, b, signed_create_tx, signed_transfer_tx, db_c
)
else:
mocked_store_asset.assert_called_once_with(
b.connection, [(signed_create_tx.assets, signed_create_tx.id, signed_create_tx.id)]
b.connection, [Asset(signed_create_tx.id, signed_create_tx.id, signed_create_tx.assets[0])]
)
mocked_store_metadata.assert_called_once_with(
b.connection,
[{"id": signed_create_tx.id, "metadata": signed_create_tx.metadata}],
[MetaData(signed_create_tx.id, signed_create_tx.metadata)]
)
mocked_store_transaction.assert_called_once_with(
b.connection,
@ -245,7 +246,7 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx, signed_transfer_tx,
if isinstance(b.connection, TarantoolDBConnection):
mocked_store_assets.assert_called_once_with(
b.connection, # signed_create_tx.asset['data'] this was before
[(signed_create_tx.assets, signed_create_tx.id, signed_create_tx.id)],
[Asset(signed_create_tx.id, signed_create_tx.id, signed_create_tx.assets[0])]
)
else:
mocked_store_assets.assert_called_once_with(
@ -254,7 +255,7 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx, signed_transfer_tx,
)
mocked_store_metadata.assert_called_once_with(
b.connection,
[{"id": signed_create_tx.id, "metadata": signed_create_tx.metadata}],
[MetaData(signed_create_tx.id, signed_create_tx.metadata)]
)
mocked_store_transactions.assert_called_once_with(
b.connection,
@ -513,28 +514,20 @@ def test_migrate_abci_chain_generates_new_chains(b, chain, block_height, expecte
@pytest.mark.bdb
def test_get_spent_key_order(b, user_pk, user_sk, user2_pk, user2_sk):
from planetmint import backend
from transactions.common.crypto import generate_key_pair
from transactions.common.exceptions import DoubleSpend
alice = generate_key_pair()
bob = generate_key_pair()
tx1 = Create.generate([user_pk], [([alice.public_key], 3), ([user_pk], 2)], assets=None).sign([user_sk])
tx1 = Create.generate([user_pk], [([alice.public_key], 3), ([user_pk], 2)]).sign([user_sk])
b.store_bulk_transactions([tx1])
inputs = tx1.to_inputs()
tx2 = Transfer.generate([inputs[1]], [([user2_pk], 2)], [tx1.id]).sign([user_sk])
assert b.validate_transaction(tx2)
tx2_dict = tx2.to_dict()
fulfills = tx2_dict["inputs"][0]["fulfills"]
tx2_dict["inputs"][0]["fulfills"] = {
"output_index": fulfills["output_index"],
"transaction_id": fulfills["transaction_id"],
}
backend.query.store_transactions(b.connection, [tx2_dict])
b.store_bulk_transactions([tx2])
tx3 = Transfer.generate([inputs[1]], [([bob.public_key], 2)], [tx1.id]).sign([user_sk])