mirror of
https://github.com/planetmint/planetmint.git
synced 2025-11-24 06:25:45 +00:00
added initial interfaces for backend, refactored Asset and MetaData logic
Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
parent
713bd5267c
commit
2694974a37
70
planetmint/backend/interfaces.py
Normal file
70
planetmint/backend/interfaces.py
Normal 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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]})
|
||||
|
||||
@ -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__)
|
||||
|
||||
@ -148,21 +149,16 @@ 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,
|
||||
)
|
||||
|
||||
metadata = backend.convert.prepare_metadata(
|
||||
self.connection, transaction_id=transaction["id"], metadata=metadata
|
||||
)
|
||||
|
||||
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 = 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))
|
||||
|
||||
2
setup.py
2
setup.py
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user