Refactor utxo (#375)

* adjusted utxo space to resemble outputs

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added update_utxoset, removed deprecated test utils

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed test_update_utxoset

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed deprecated query and test cases

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed delete_unspent_outputs tests

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* moved get_merkget_utxoset_merkle_root to dataaccessor and fixed test cases

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed delete_transactions query

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed deprecated fixtures

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* blackified

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* added get_outputs_by_owner query and adjusted dataaccessor

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* removed fastquery class

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed api test case

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed TestMultipleInputs

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed get_outputs_filtered test cases

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* fixed get_spent naming issue

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* blackified

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

* updated changelog and version bump

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>

---------

Signed-off-by: Lorenz Herzberger <lorenzherzberger@gmail.com>
This commit is contained in:
Lorenz Herzberger 2023-04-11 15:18:44 +02:00 committed by GitHub
parent dbf4e9085c
commit 6a3c655e3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 280 additions and 606 deletions

View File

@ -25,6 +25,11 @@ For reference, the possible headings are:
* **Known Issues** * **Known Issues**
* **Notes** * **Notes**
## [2.4.1] - 2023-11-04
* **Removed** Fastquery class
* **Changed** UTXO space updated to resemble outputs
* **Changed** updated UTXO querying
## [2.4.0] - 2023-29-03 ## [2.4.0] - 2023-29-03
* **Added** Zenroom script validation * **Added** Zenroom script validation
* **Changed** adjusted zenroom testing for new transaction script structure * **Changed** adjusted zenroom testing for new transaction script structure

View File

@ -77,7 +77,7 @@ def get_assets(conn, asset_ids):
@register_query(LocalMongoDBConnection) @register_query(LocalMongoDBConnection)
def get_spent(conn, transaction_id, output): def get_spending_transaction(conn, transaction_id, output):
query = { query = {
"inputs": { "inputs": {
"$elemMatch": {"$and": [{"fulfills.transaction_id": transaction_id}, {"fulfills.output_index": output}]} "$elemMatch": {"$and": [{"fulfills.transaction_id": transaction_id}, {"fulfills.output_index": output}]}
@ -167,21 +167,6 @@ def delete_transactions(conn, txn_ids):
conn.run(conn.collection("transactions").delete_many({"id": {"$in": txn_ids}})) conn.run(conn.collection("transactions").delete_many({"id": {"$in": txn_ids}}))
@register_query(LocalMongoDBConnection)
def store_unspent_outputs(conn, *unspent_outputs):
if unspent_outputs:
try:
return conn.run(
conn.collection("utxos").insert_many(
unspent_outputs,
ordered=False,
)
)
except DuplicateKeyError:
# TODO log warning at least
pass
@register_query(LocalMongoDBConnection) @register_query(LocalMongoDBConnection)
def delete_unspent_outputs(conn, *unspent_outputs): def delete_unspent_outputs(conn, *unspent_outputs):
if unspent_outputs: if unspent_outputs:

View File

@ -133,7 +133,7 @@ def get_asset(connection, asset_id) -> Asset:
@singledispatch @singledispatch
def get_spent(connection, transaction_id, condition_id): def get_spending_transaction(connection, transaction_id, condition_id):
"""Check if a `txid` was already used as an input. """Check if a `txid` was already used as an input.
A transaction can be used as an input for another transaction. Bigchain A transaction can be used as an input for another transaction. Bigchain
@ -208,7 +208,7 @@ def get_block_with_transaction(connection, txid):
@singledispatch @singledispatch
def store_transaction_outputs(connection, output: Output, index: int): def store_transaction_outputs(connection, output: Output, index: int, table: str):
"""Store the transaction outputs. """Store the transaction outputs.
Args: Args:
@ -264,13 +264,6 @@ def store_block(conn, block):
raise NotImplementedError raise NotImplementedError
@singledispatch
def store_unspent_outputs(connection, unspent_outputs):
"""Store unspent outputs in ``utxo_set`` table."""
raise NotImplementedError
@singledispatch @singledispatch
def delete_unspent_outputs(connection, unspent_outputs): def delete_unspent_outputs(connection, unspent_outputs):
"""Delete unspent outputs in ``utxo_set`` table.""" """Delete unspent outputs in ``utxo_set`` table."""
@ -455,6 +448,12 @@ def get_outputs_by_tx_id(connection, tx_id: str) -> list[Output]:
raise NotImplementedError raise NotImplementedError
@singledispatch
def get_outputs_by_owner(connection, public_key: str, table: str) -> list[Output]:
"""Retrieve an owners outputs by public key"""
raise NotImplementedError
@singledispatch @singledispatch
def get_metadata(conn, transaction_ids): def get_metadata(conn, transaction_ids):
"""Retrieve metadata for a list of transactions by their ids""" """Retrieve metadata for a list of transactions by their ids"""

View File

@ -171,9 +171,11 @@ function init()
utxos = box.schema.create_space('utxos', { if_not_exists = true }) utxos = box.schema.create_space('utxos', { if_not_exists = true })
utxos:format({ utxos:format({
{ name = 'id', type = 'string' }, { name = 'id', type = 'string' },
{ name = 'transaction_id', type = 'string' }, { name = 'amount' , type = 'unsigned' },
{ name = 'output_index', type = 'unsigned' }, { name = 'public_keys', type = 'array' },
{ name = 'utxo', type = 'map' } { name = 'condition', type = 'map' },
{ name = 'output_index', type = 'number' },
{ name = 'transaction_id' , type = 'string' }
}) })
utxos:create_index('id', { utxos:create_index('id', {
if_not_exists = true, if_not_exists = true,
@ -189,7 +191,13 @@ function init()
parts = { parts = {
{ field = 'transaction_id', type = 'string' }, { field = 'transaction_id', type = 'string' },
{ field = 'output_index', type = 'unsigned' } { field = 'output_index', type = 'unsigned' }
}}) }
})
utxos:create_index('public_keys', {
if_not_exists = true,
unique = false,
parts = {{field = 'public_keys[*]', type = 'string' }}
})
-- Elections -- Elections

View File

@ -127,11 +127,12 @@ def get_transactions_by_metadata(connection, metadata: str, limit: int = 1000) -
return get_complete_transactions_by_ids(connection, tx_ids) return get_complete_transactions_by_ids(connection, tx_ids)
@register_query(TarantoolDBConnection)
@catch_db_exception @catch_db_exception
def store_transaction_outputs(connection, output: Output, index: int) -> str: def store_transaction_outputs(connection, output: Output, index: int, table=TARANT_TABLE_OUTPUT) -> str:
output_id = uuid4().hex output_id = uuid4().hex
connection.connect().insert( connection.connect().insert(
TARANT_TABLE_OUTPUT, table,
( (
output_id, output_id,
int(output.amount), int(output.amount),
@ -220,7 +221,9 @@ def get_assets(connection, assets_ids: list) -> list[Asset]:
@register_query(TarantoolDBConnection) @register_query(TarantoolDBConnection)
@catch_db_exception @catch_db_exception
def get_spent(connection, fullfil_transaction_id: str, fullfil_output_index: str) -> list[DbTransaction]: def get_spending_transaction(
connection, fullfil_transaction_id: str, fullfil_output_index: str
) -> list[DbTransaction]:
_inputs = ( _inputs = (
connection.connect() connection.connect()
.select( .select(
@ -300,7 +303,7 @@ def get_spending_transactions(connection, inputs):
_transactions = [] _transactions = []
for inp in inputs: for inp in inputs:
_trans_list = get_spent( _trans_list = get_spending_transaction(
fullfil_transaction_id=inp["transaction_id"], fullfil_transaction_id=inp["transaction_id"],
fullfil_output_index=inp["output_index"], fullfil_output_index=inp["output_index"],
connection=connection, connection=connection,
@ -337,6 +340,9 @@ def delete_transactions(connection, txn_ids: list):
_outputs = get_outputs_by_tx_id(connection, _id) _outputs = get_outputs_by_tx_id(connection, _id)
for x in range(len(_outputs)): for x in range(len(_outputs)):
connection.connect().call("delete_output", (_outputs[x].id)) connection.connect().call("delete_output", (_outputs[x].id))
connection.connect().delete(
TARANT_TABLE_UTXOS, (_id, _outputs[x].index), index="utxo_by_transaction_id_and_output_index"
)
for _id in txn_ids: for _id in txn_ids:
connection.connect().delete(TARANT_TABLE_TRANSACTION, _id) connection.connect().delete(TARANT_TABLE_TRANSACTION, _id)
connection.connect().delete(TARANT_TABLE_GOVERNANCE, _id) connection.connect().delete(TARANT_TABLE_GOVERNANCE, _id)
@ -344,26 +350,7 @@ def delete_transactions(connection, txn_ids: list):
@register_query(TarantoolDBConnection) @register_query(TarantoolDBConnection)
@catch_db_exception @catch_db_exception
def store_unspent_outputs(connection, *unspent_outputs: list): def delete_unspent_outputs(connection, unspent_outputs: list):
result = []
if unspent_outputs:
for utxo in unspent_outputs:
try:
output = (
connection.connect()
.insert(TARANT_TABLE_UTXOS, (uuid4().hex, utxo["transaction_id"], utxo["output_index"], utxo))
.data
)
result.append(output)
except Exception as e:
logger.info(f"Could not insert unspent output: {e}")
raise OperationDataInsertionError()
return result
@register_query(TarantoolDBConnection)
@catch_db_exception
def delete_unspent_outputs(connection, *unspent_outputs: list):
result = [] result = []
if unspent_outputs: if unspent_outputs:
for utxo in unspent_outputs: for utxo in unspent_outputs:
@ -383,8 +370,8 @@ def delete_unspent_outputs(connection, *unspent_outputs: list):
@register_query(TarantoolDBConnection) @register_query(TarantoolDBConnection)
@catch_db_exception @catch_db_exception
def get_unspent_outputs(connection, query=None): # for now we don't have implementation for 'query'. def get_unspent_outputs(connection, query=None): # for now we don't have implementation for 'query'.
_utxos = connection.connect().select(TARANT_TABLE_UTXOS, []).data utxos = connection.connect().select(TARANT_TABLE_UTXOS, []).data
return [utx[3] for utx in _utxos] return [{"transaction_id": utxo[5], "output_index": utxo[4]} for utxo in utxos]
@register_query(TarantoolDBConnection) @register_query(TarantoolDBConnection)
@ -522,3 +509,10 @@ def get_latest_abci_chain(connection) -> Union[dict, None]:
return None return None
_chain = sorted(_all_chains, key=itemgetter(1), reverse=True)[0] _chain = sorted(_all_chains, key=itemgetter(1), reverse=True)[0]
return {"chain_id": _chain[0], "height": _chain[1], "is_synced": _chain[2]} return {"chain_id": _chain[0], "height": _chain[1], "is_synced": _chain[2]}
@register_query(TarantoolDBConnection)
@catch_db_exception
def get_outputs_by_owner(connection, public_key: str, table=TARANT_TABLE_OUTPUT) -> list[Output]:
outputs = connection.connect().select(table, public_key, index="public_keys")
return [Output.from_tuple(output) for output in outputs]

View File

@ -1,5 +1,6 @@
import rapidjson import rapidjson
from itertools import chain from itertools import chain
from hashlib import sha3_256
from transactions import Transaction from transactions import Transaction
from transactions.common.exceptions import DoubleSpend from transactions.common.exceptions import DoubleSpend
@ -8,10 +9,14 @@ from transactions.common.exceptions import InputDoesNotExist
from planetmint import config_utils, backend from planetmint import config_utils, backend
from planetmint.const import GOVERNANCE_TRANSACTION_TYPES from planetmint.const import GOVERNANCE_TRANSACTION_TYPES
from planetmint.model.fastquery import FastQuery from planetmint.abci.utils import key_from_base64, merkleroot
from planetmint.abci.utils import key_from_base64
from planetmint.backend.connection import Connection from planetmint.backend.connection import Connection
from planetmint.backend.tarantool.const import TARANT_TABLE_TRANSACTION, TARANT_TABLE_GOVERNANCE from planetmint.backend.tarantool.const import (
TARANT_TABLE_TRANSACTION,
TARANT_TABLE_GOVERNANCE,
TARANT_TABLE_UTXOS,
TARANT_TABLE_OUTPUT,
)
from planetmint.backend.models.block import Block from planetmint.backend.models.block import Block
from planetmint.backend.models.output import Output from planetmint.backend.models.output import Output
from planetmint.backend.models.asset import Asset from planetmint.backend.models.asset import Asset
@ -37,6 +42,7 @@ class DataAccessor:
backend.query.store_transactions(self.connection, txns, TARANT_TABLE_TRANSACTION) backend.query.store_transactions(self.connection, txns, TARANT_TABLE_TRANSACTION)
backend.query.store_transactions(self.connection, gov_txns, TARANT_TABLE_GOVERNANCE) backend.query.store_transactions(self.connection, gov_txns, TARANT_TABLE_GOVERNANCE)
[self.update_utxoset(t) for t in txns + gov_txns]
def delete_transactions(self, txs): def delete_transactions(self, txs):
return backend.query.delete_transactions(self.connection, txs) return backend.query.delete_transactions(self.connection, txs)
@ -60,7 +66,7 @@ class DataAccessor:
def get_outputs_by_tx_id(self, txid): def get_outputs_by_tx_id(self, txid):
return backend.query.get_outputs_by_tx_id(self.connection, txid) return backend.query.get_outputs_by_tx_id(self.connection, txid)
def get_outputs_filtered(self, owner, spent=None): def get_outputs_filtered(self, owner, spent=None) -> list[Output]:
"""Get a list of output links filtered on some criteria """Get a list of output links filtered on some criteria
Args: Args:
@ -70,16 +76,23 @@ class DataAccessor:
not specified (``None``) return all outputs. not specified (``None``) return all outputs.
Returns: Returns:
:obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s :obj:`list` of Output: list of ``txid`` s and ``output`` s
pointing to another transaction's condition pointing to another transaction's condition
""" """
outputs = self.fastquery.get_outputs_by_public_key(owner) outputs = backend.query.get_outputs_by_owner(self.connection, owner)
if spent is None: unspent_outputs = backend.query.get_outputs_by_owner(self.connection, owner, TARANT_TABLE_UTXOS)
return outputs if spent is True:
elif spent is True: spent_outputs = []
return self.fastquery.filter_unspent_outputs(outputs) for output in outputs:
if not any(
utxo.transaction_id == output.transaction_id and utxo.index == output.index
for utxo in unspent_outputs
):
spent_outputs.append(output)
return spent_outputs
elif spent is False: elif spent is False:
return self.fastquery.filter_spent_outputs(outputs) return unspent_outputs
return outputs
def store_block(self, block): def store_block(self, block):
"""Create a new block.""" """Create a new block."""
@ -138,8 +151,8 @@ class DataAccessor:
return validators return validators
def get_spent(self, txid, output, current_transactions=[]) -> DbTransaction: def get_spending_transaction(self, txid, output, current_transactions=[]) -> DbTransaction:
transactions = backend.query.get_spent(self.connection, txid, output) transactions = backend.query.get_spending_transaction(self.connection, txid, output)
current_spent_transactions = [] current_spent_transactions = []
for ctxn in current_transactions: for ctxn in current_transactions:
@ -196,7 +209,7 @@ class DataAccessor:
if input_tx is None: if input_tx is None:
raise InputDoesNotExist("input `{}` doesn't exist".format(input_txid)) raise InputDoesNotExist("input `{}` doesn't exist".format(input_txid))
spent = self.get_spent(input_txid, input_.fulfills.output, current_transactions) spent = self.get_spending_transaction(input_txid, input_.fulfills.output, current_transactions)
if spent: if spent:
raise DoubleSpend("input `{}` was already spent".format(input_txid)) raise DoubleSpend("input `{}` was already spent".format(input_txid))
@ -277,6 +290,54 @@ class DataAccessor:
txns = backend.query.get_asset_tokens_for_public_key(self.connection, transaction_id, election_pk) txns = backend.query.get_asset_tokens_for_public_key(self.connection, transaction_id, election_pk)
return txns return txns
@property def update_utxoset(self, transaction):
def fastquery(self): spent_outputs = [
return FastQuery(self.connection) {"output_index": input["fulfills"]["output_index"], "transaction_id": input["fulfills"]["transaction_id"]}
for input in transaction["inputs"]
if input["fulfills"] != None
]
if spent_outputs:
backend.query.delete_unspent_outputs(self.connection, spent_outputs)
[
backend.query.store_transaction_outputs(
self.connection, Output.outputs_dict(output, transaction["id"]), index, TARANT_TABLE_UTXOS
)
for index, output in enumerate(transaction["outputs"])
]
def get_utxoset_merkle_root(self):
"""Returns the merkle root of the utxoset. This implies that
the utxoset is first put into a merkle tree.
For now, the merkle tree and its root will be computed each
time. This obviously is not efficient and a better approach
that limits the repetition of the same computation when
unnecesary should be sought. For instance, future optimizations
could simply re-compute the branches of the tree that were
affected by a change.
The transaction hash (id) and output index should be sufficient
to uniquely identify a utxo, and consequently only that
information from a utxo record is needed to compute the merkle
root. Hence, each node of the merkle tree should contain the
tuple (txid, output_index).
.. important:: The leaves of the tree will need to be sorted in
some kind of lexicographical order.
Returns:
str: Merkle root in hexadecimal form.
"""
utxoset = backend.query.get_unspent_outputs(self.connection)
# TODO Once ready, use the already pre-computed utxo_hash field.
# See common/transactions.py for details.
hashes = [
sha3_256("{}{}".format(utxo["transaction_id"], utxo["output_index"]).encode()).digest() for utxo in utxoset
]
print(sorted(hashes))
# TODO Notice the sorted call!
return merkleroot(sorted(hashes))

View File

@ -1,76 +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
from planetmint.backend import query
from transactions.common.transaction import TransactionLink
from planetmint.backend.models.output import ConditionDetails
class FastQuery:
"""Database queries that join on block results from a single node."""
def __init__(self, connection):
self.connection = connection
def get_outputs_by_public_key(self, public_key):
"""Get outputs for a public key"""
txs = query.get_owned_ids(self.connection, public_key)
return [
TransactionLink(tx.id, index)
for tx in txs
for index, output in enumerate(tx.outputs)
if condition_details_has_owner(output.condition.details, public_key)
]
def filter_spent_outputs(self, outputs):
"""Remove outputs that have been spent
Args:
outputs: list of TransactionLink
"""
links = [o.to_dict() for o in outputs]
txs = query.get_spending_transactions(self.connection, links)
spends = {TransactionLink.from_dict(input.fulfills.to_dict()) for tx in txs for input in tx.inputs}
return [ff for ff in outputs if ff not in spends]
def filter_unspent_outputs(self, outputs):
"""Remove outputs that have not been spent
Args:
outputs: list of TransactionLink
"""
links = [o.to_dict() for o in outputs]
txs = query.get_spending_transactions(self.connection, links)
spends = {TransactionLink.from_dict(input.fulfills.to_dict()) for tx in txs for input in tx.inputs}
return [ff for ff in outputs if ff in spends]
# TODO: Rename this function, it's handling fulfillments not conditions
def condition_details_has_owner(condition_details, owner):
"""Check if the public_key of owner is in the condition details
as an Ed25519Fulfillment.public_key
Args:
condition_details (dict): dict with condition details
owner (str): base58 public key of owner
Returns:
bool: True if the public key is found in the condition details, False otherwise
"""
if isinstance(condition_details, ConditionDetails) and condition_details.sub_conditions is not None:
result = condition_details_has_owner(condition_details.sub_conditions, owner)
if result:
return True
elif isinstance(condition_details, list):
for subcondition in condition_details:
result = condition_details_has_owner(subcondition, owner)
if result:
return True
else:
if condition_details.public_key is not None and owner == condition_details.public_key:
return True
return False

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "planetmint" name = "planetmint"
version = "2.4.0" version = "2.4.1"
description = "Planetmint: The Blockchain Database" description = "Planetmint: The Blockchain Database"
authors = ["Planetmint contributors"] authors = ["Planetmint contributors"]
license = "AGPLv3" license = "AGPLv3"

View File

@ -29,7 +29,7 @@ def test_schema(schema_func_name, args_qty):
("get_txids_filtered", 1), ("get_txids_filtered", 1),
("get_owned_ids", 1), ("get_owned_ids", 1),
("get_block", 1), ("get_block", 1),
("get_spent", 2), ("get_spending_transaction", 2),
("get_spending_transactions", 1), ("get_spending_transactions", 1),
("store_assets", 1), ("store_assets", 1),
("get_asset", 1), ("get_asset", 1),

View File

@ -484,50 +484,6 @@ def wsserver_base_url(wsserver_scheme, wsserver_host, wsserver_port):
return "{}://{}:{}".format(wsserver_scheme, wsserver_host, wsserver_port) return "{}://{}:{}".format(wsserver_scheme, wsserver_host, wsserver_port)
@pytest.fixture
def unspent_output_0():
return {
"amount": 1,
"asset_id": "e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d",
"condition_uri": "ni:///sha-256;RmovleG60-7K0CX60jjfUunV3lBpUOkiQOAnBzghm0w?fpt=ed25519-sha-256&cost=131072",
"fulfillment_message": '{"asset":{"data":{"hash":"06e47bcf9084f7ecfd2a2a2ad275444a"}},"id":"e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d","inputs":[{"fulfillment":"pGSAIIQT0Jm6LDlcSs9coJK4Q4W-SNtsO2EtMtQJ04EUjBMJgUAXKIqeaippbF-IClhhZNNaP6EIZ_OgrVQYU4mH6b-Vc3Tg-k6p-rJOlLGUUo_w8C5QgPHNRYFOqUk2f1q0Cs4G","fulfills":null,"owners_before":["9taLkHkaBXeSF8vrhDGFTAmcZuCEPqjQrKadfYGs4gHv"]}],"metadata":null,"operation":"CREATE","outputs":[{"amount":"1","condition":{"details":{"public_key":"6FDGsHrR9RZqNaEm7kBvqtxRkrvuWogBW2Uy7BkWc5Tz","type":"ed25519-sha-256"},"uri":"ni:///sha-256;RmovleG60-7K0CX60jjfUunV3lBpUOkiQOAnBzghm0w?fpt=ed25519-sha-256&cost=131072"},"public_keys":["6FDGsHrR9RZqNaEm7kBvqtxRkrvuWogBW2Uy7BkWc5Tz"]},{"amount":"2","condition":{"details":{"public_key":"AH9D7xgmhyLmVE944zvHvuvYWuj5DfbMBJhnDM4A5FdT","type":"ed25519-sha-256"},"uri":"ni:///sha-256;-HlYmgwwl-vXwE52IaADhvYxaL1TbjqfJ-LGn5a1PFc?fpt=ed25519-sha-256&cost=131072"},"public_keys":["AH9D7xgmhyLmVE944zvHvuvYWuj5DfbMBJhnDM4A5FdT"]},{"amount":"3","condition":{"details":{"public_key":"HpmSVrojHvfCXQbmoAs4v6Aq1oZiZsZDnjr68KiVtPbB","type":"ed25519-sha-256"},"uri":"ni:///sha-256;xfn8pvQkTCPtvR0trpHy2pqkkNTmMBCjWMMOHtk3WO4?fpt=ed25519-sha-256&cost=131072"},"public_keys":["HpmSVrojHvfCXQbmoAs4v6Aq1oZiZsZDnjr68KiVtPbB"]}],"version":"1.0"}', # noqa: E501
# noqa
"output_index": 0,
"transaction_id": "e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d",
}
@pytest.fixture
def unspent_output_1():
return {
"amount": 2,
"asset_id": "e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d",
"condition_uri": "ni:///sha-256;-HlYmgwwl-vXwE52IaADhvYxaL1TbjqfJ-LGn5a1PFc?fpt=ed25519-sha-256&cost=131072",
"fulfillment_message": '{"asset":{"data":{"hash":"06e47bcf9084f7ecfd2a2a2ad275444a"}},"id":"e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d","inputs":[{"fulfillment":"pGSAIIQT0Jm6LDlcSs9coJK4Q4W-SNtsO2EtMtQJ04EUjBMJgUAXKIqeaippbF-IClhhZNNaP6EIZ_OgrVQYU4mH6b-Vc3Tg-k6p-rJOlLGUUo_w8C5QgPHNRYFOqUk2f1q0Cs4G","fulfills":null,"owners_before":["9taLkHkaBXeSF8vrhDGFTAmcZuCEPqjQrKadfYGs4gHv"]}],"metadata":null,"operation":"CREATE","outputs":[{"amount":"1","condition":{"details":{"public_key":"6FDGsHrR9RZqNaEm7kBvqtxRkrvuWogBW2Uy7BkWc5Tz","type":"ed25519-sha-256"},"uri":"ni:///sha-256;RmovleG60-7K0CX60jjfUunV3lBpUOkiQOAnBzghm0w?fpt=ed25519-sha-256&cost=131072"},"public_keys":["6FDGsHrR9RZqNaEm7kBvqtxRkrvuWogBW2Uy7BkWc5Tz"]},{"amount":"2","condition":{"details":{"public_key":"AH9D7xgmhyLmVE944zvHvuvYWuj5DfbMBJhnDM4A5FdT","type":"ed25519-sha-256"},"uri":"ni:///sha-256;-HlYmgwwl-vXwE52IaADhvYxaL1TbjqfJ-LGn5a1PFc?fpt=ed25519-sha-256&cost=131072"},"public_keys":["AH9D7xgmhyLmVE944zvHvuvYWuj5DfbMBJhnDM4A5FdT"]},{"amount":"3","condition":{"details":{"public_key":"HpmSVrojHvfCXQbmoAs4v6Aq1oZiZsZDnjr68KiVtPbB","type":"ed25519-sha-256"},"uri":"ni:///sha-256;xfn8pvQkTCPtvR0trpHy2pqkkNTmMBCjWMMOHtk3WO4?fpt=ed25519-sha-256&cost=131072"},"public_keys":["HpmSVrojHvfCXQbmoAs4v6Aq1oZiZsZDnjr68KiVtPbB"]}],"version":"1.0"}', # noqa: E501
# noqa
"output_index": 1,
"transaction_id": "e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d",
}
@pytest.fixture
def unspent_output_2():
return {
"amount": 3,
"asset_id": "e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d",
"condition_uri": "ni:///sha-256;xfn8pvQkTCPtvR0trpHy2pqkkNTmMBCjWMMOHtk3WO4?fpt=ed25519-sha-256&cost=131072",
"fulfillment_message": '{"asset":{"data":{"hash":"06e47bcf9084f7ecfd2a2a2ad275444a"}},"id":"e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d","inputs":[{"fulfillment":"pGSAIIQT0Jm6LDlcSs9coJK4Q4W-SNtsO2EtMtQJ04EUjBMJgUAXKIqeaippbF-IClhhZNNaP6EIZ_OgrVQYU4mH6b-Vc3Tg-k6p-rJOlLGUUo_w8C5QgPHNRYFOqUk2f1q0Cs4G","fulfills":null,"owners_before":["9taLkHkaBXeSF8vrhDGFTAmcZuCEPqjQrKadfYGs4gHv"]}],"metadata":null,"operation":"CREATE","outputs":[{"amount":"1","condition":{"details":{"public_key":"6FDGsHrR9RZqNaEm7kBvqtxRkrvuWogBW2Uy7BkWc5Tz","type":"ed25519-sha-256"},"uri":"ni:///sha-256;RmovleG60-7K0CX60jjfUunV3lBpUOkiQOAnBzghm0w?fpt=ed25519-sha-256&cost=131072"},"public_keys":["6FDGsHrR9RZqNaEm7kBvqtxRkrvuWogBW2Uy7BkWc5Tz"]},{"amount":"2","condition":{"details":{"public_key":"AH9D7xgmhyLmVE944zvHvuvYWuj5DfbMBJhnDM4A5FdT","type":"ed25519-sha-256"},"uri":"ni:///sha-256;-HlYmgwwl-vXwE52IaADhvYxaL1TbjqfJ-LGn5a1PFc?fpt=ed25519-sha-256&cost=131072"},"public_keys":["AH9D7xgmhyLmVE944zvHvuvYWuj5DfbMBJhnDM4A5FdT"]},{"amount":"3","condition":{"details":{"public_key":"HpmSVrojHvfCXQbmoAs4v6Aq1oZiZsZDnjr68KiVtPbB","type":"ed25519-sha-256"},"uri":"ni:///sha-256;xfn8pvQkTCPtvR0trpHy2pqkkNTmMBCjWMMOHtk3WO4?fpt=ed25519-sha-256&cost=131072"},"public_keys":["HpmSVrojHvfCXQbmoAs4v6Aq1oZiZsZDnjr68KiVtPbB"]}],"version":"1.0"}', # noqa: E501
# noqa
"output_index": 2,
"transaction_id": "e897c7a0426461a02b4fca8ed73bc0debed7570cf3b40fb4f49c963434225a4d",
}
@pytest.fixture
def unspent_outputs(unspent_output_0, unspent_output_1, unspent_output_2):
return unspent_output_0, unspent_output_1, unspent_output_2
@pytest.fixture @pytest.fixture
def tarantool_client(db_context): # TODO Here add TarantoolConnectionClass def tarantool_client(db_context): # TODO Here add TarantoolConnectionClass
return TarantoolDBConnection(host=db_context.host, port=db_context.port) return TarantoolDBConnection(host=db_context.host, port=db_context.port)
@ -538,28 +494,6 @@ def utxo_collection(tarantool_client, _setup_database):
return tarantool_client.get_space("utxos") return tarantool_client.get_space("utxos")
@pytest.fixture
def dummy_unspent_outputs():
return [
{"transaction_id": "a", "output_index": 0},
{"transaction_id": "a", "output_index": 1},
{"transaction_id": "b", "output_index": 0},
]
@pytest.fixture
def utxoset(dummy_unspent_outputs, utxo_collection):
from uuid import uuid4
num_rows_before_operation = utxo_collection.select().rowcount
for utxo in dummy_unspent_outputs:
res = utxo_collection.insert((uuid4().hex, utxo["transaction_id"], utxo["output_index"], utxo))
assert res
num_rows_after_operation = utxo_collection.select().rowcount
assert num_rows_after_operation == num_rows_before_operation + 3
return dummy_unspent_outputs, utxo_collection
@pytest.fixture @pytest.fixture
def network_validators(node_keys): def network_validators(node_keys):
validator_pub_power = {} validator_pub_power = {}

View File

@ -7,10 +7,10 @@ from unittest.mock import patch
import pytest import pytest
from base58 import b58decode from base58 import b58decode
from ipld import marshal, multihash from ipld import marshal, multihash
from operator import attrgetter
from transactions.common import crypto from transactions.common import crypto
from transactions.common.transaction import TransactionLink from transactions.common.transaction import Transaction, TransactionLink, Input
from transactions.common.transaction import Transaction
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 planetmint.exceptions import CriticalDoubleSpend from planetmint.exceptions import CriticalDoubleSpend
@ -64,7 +64,6 @@ class TestBigchainApi(object):
def test_non_create_input_not_found(self, b, user_pk): def test_non_create_input_not_found(self, b, user_pk):
from planetmint_cryptoconditions import Ed25519Sha256 from planetmint_cryptoconditions import Ed25519Sha256
from transactions.common.exceptions import InputDoesNotExist from transactions.common.exceptions import InputDoesNotExist
from transactions.common.transaction import Input, TransactionLink
# Create an input for a non existing transaction # Create an input for a non existing transaction
input = Input( input = Input(
@ -104,14 +103,15 @@ class TestTransactionValidation(object):
def test_non_create_valid_input_wrong_owner(self, b, user_pk): def test_non_create_valid_input_wrong_owner(self, b, user_pk):
from transactions.common.crypto import generate_key_pair from transactions.common.crypto import generate_key_pair
from transactions.common.exceptions import InvalidSignature from transactions.common.exceptions import InvalidSignature
from transactions.common.transaction_link import TransactionLink
input_tx = b.models.fastquery.get_outputs_by_public_key(user_pk).pop() output = b.models.get_outputs_filtered(user_pk).pop()
input_transaction = b.models.get_transaction(input_tx.txid) input_transaction = b.models.get_transaction(output.transaction_id)
sk, pk = generate_key_pair() sk, pk = generate_key_pair()
tx = Create.generate([pk], [([user_pk], 1)]) tx = Create.generate([pk], [([user_pk], 1)])
tx.operation = "TRANSFER" tx.operation = "TRANSFER"
tx.assets = [{"id": input_transaction.id}] tx.assets = [{"id": input_transaction.id}]
tx.inputs[0].fulfills = input_tx tx.inputs[0].fulfills = TransactionLink(output.transaction_id, output.index)
with pytest.raises(InvalidSignature): with pytest.raises(InvalidSignature):
b.validate_transaction(tx) b.validate_transaction(tx)
@ -129,8 +129,8 @@ class TestTransactionValidation(object):
class TestMultipleInputs(object): class TestMultipleInputs(object):
def test_transfer_single_owner_single_input(self, b, inputs, user_pk, user_sk): def test_transfer_single_owner_single_input(self, b, inputs, user_pk, user_sk):
user2_sk, user2_pk = crypto.generate_key_pair() user2_sk, user2_pk = crypto.generate_key_pair()
tx_link = b.models.fastquery.get_outputs_by_public_key(user_pk).pop() tx_output = b.models.get_outputs_filtered(user_pk).pop()
input_tx = b.models.get_transaction(tx_link.txid) input_tx = b.models.get_transaction(tx_output.transaction_id)
tx_converted = Transaction.from_dict(input_tx.to_dict(), True) tx_converted = Transaction.from_dict(input_tx.to_dict(), True)
tx = Transfer.generate(tx_converted.to_inputs(), [([user2_pk], 1)], asset_ids=[input_tx.id]) tx = Transfer.generate(tx_converted.to_inputs(), [([user2_pk], 1)], asset_ids=[input_tx.id])
@ -144,9 +144,9 @@ class TestMultipleInputs(object):
def test_single_owner_before_multiple_owners_after_single_input(self, b, user_sk, user_pk, inputs): def test_single_owner_before_multiple_owners_after_single_input(self, b, user_sk, user_pk, inputs):
user2_sk, user2_pk = crypto.generate_key_pair() user2_sk, user2_pk = crypto.generate_key_pair()
user3_sk, user3_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair()
tx_link = b.models.fastquery.get_outputs_by_public_key(user_pk).pop() tx_output = b.models.get_outputs_filtered(user_pk).pop()
input_tx = b.models.get_transaction(tx_link.txid) input_tx = b.models.get_transaction(tx_output.transaction_id)
tx_converted = Transaction.from_dict(input_tx.to_dict(), True) tx_converted = Transaction.from_dict(input_tx.to_dict(), True)
tx = Transfer.generate(tx_converted.to_inputs(), [([user2_pk, user3_pk], 1)], asset_ids=[input_tx.id]) tx = Transfer.generate(tx_converted.to_inputs(), [([user2_pk, user3_pk], 1)], asset_ids=[input_tx.id])
@ -165,8 +165,8 @@ class TestMultipleInputs(object):
tx = tx.sign([alice.private_key]) tx = tx.sign([alice.private_key])
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
owned_input = b.models.fastquery.get_outputs_by_public_key(user_pk).pop() tx_output = b.models.get_outputs_filtered(user_pk).pop()
input_tx = b.models.get_transaction(owned_input.txid) input_tx = b.models.get_transaction(tx_output.transaction_id)
input_tx_converted = Transaction.from_dict(input_tx.to_dict(), True) input_tx_converted = Transaction.from_dict(input_tx.to_dict(), True)
transfer_tx = Transfer.generate(input_tx_converted.to_inputs(), [([user3_pk], 1)], asset_ids=[input_tx.id]) transfer_tx = Transfer.generate(input_tx_converted.to_inputs(), [([user3_pk], 1)], asset_ids=[input_tx.id])
@ -188,8 +188,8 @@ class TestMultipleInputs(object):
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
# get input # get input
tx_link = b.models.fastquery.get_outputs_by_public_key(user_pk).pop() tx_output = b.models.get_outputs_filtered(user_pk).pop()
tx_input = b.models.get_transaction(tx_link.txid) tx_input = b.models.get_transaction(tx_output.transaction_id)
input_tx_converted = Transaction.from_dict(tx_input.to_dict(), True) input_tx_converted = Transaction.from_dict(tx_input.to_dict(), True)
tx = Transfer.generate(input_tx_converted.to_inputs(), [([user3_pk, user4_pk], 1)], asset_ids=[tx_input.id]) tx = Transfer.generate(input_tx_converted.to_inputs(), [([user3_pk, user4_pk], 1)], asset_ids=[tx_input.id])
@ -206,20 +206,24 @@ class TestMultipleInputs(object):
tx = tx.sign([alice.private_key]) tx = tx.sign([alice.private_key])
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) stored_tx = b.models.get_transaction(tx.id)
owned_inputs_user2 = b.models.fastquery.get_outputs_by_public_key(user2_pk)
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
owned_inputs_user2 = b.models.get_outputs_filtered(user2_pk)
assert owned_inputs_user1 == [stored_tx.outputs[0]]
assert owned_inputs_user2 == [] assert owned_inputs_user2 == []
tx_transfer = Transfer.generate(tx.to_inputs(), [([user2_pk], 1)], asset_ids=[tx.id]) tx_transfer = Transfer.generate(tx.to_inputs(), [([user2_pk], 1)], asset_ids=[tx.id])
tx_transfer = tx_transfer.sign([user_sk]) tx_transfer = tx_transfer.sign([user_sk])
b.models.store_bulk_transactions([tx_transfer]) b.models.store_bulk_transactions([tx_transfer])
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
owned_inputs_user2 = b.models.fastquery.get_outputs_by_public_key(user2_pk) owned_inputs_user2 = b.models.get_outputs_filtered(user2_pk)
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] stored_tx_transfer = b.models.get_transaction(tx_transfer.id)
assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0)]
assert owned_inputs_user1 == [stored_tx.outputs[0]]
assert owned_inputs_user2 == [stored_tx_transfer.outputs[0]]
def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice): def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice):
user2_sk, user2_pk = crypto.generate_key_pair() user2_sk, user2_pk = crypto.generate_key_pair()
@ -230,11 +234,15 @@ class TestMultipleInputs(object):
b.models.store_bulk_transactions([tx_create_signed]) b.models.store_bulk_transactions([tx_create_signed])
# get input # get input
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
owned_inputs_user2 = b.models.fastquery.get_outputs_by_public_key(user2_pk) owned_inputs_user2 = b.models.get_outputs_filtered(user2_pk)
expected_owned_inputs_user1 = [TransactionLink(tx_create.id, 0), TransactionLink(tx_create.id, 1)] stored_tx = b.models.get_transaction(tx_create.id)
assert owned_inputs_user1 == expected_owned_inputs_user1
expected_owned_inputs_user1 = [stored_tx.outputs[0], stored_tx.outputs[1]]
assert sorted(owned_inputs_user1, key=attrgetter("index")) == sorted(
expected_owned_inputs_user1, key=attrgetter("index")
)
assert owned_inputs_user2 == [] assert owned_inputs_user2 == []
# transfer divisible asset divided in two outputs # transfer divisible asset divided in two outputs
@ -243,11 +251,16 @@ class TestMultipleInputs(object):
) )
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
b.models.store_bulk_transactions([tx_transfer_signed]) b.models.store_bulk_transactions([tx_transfer_signed])
stored_tx_transfer = b.models.get_transaction(tx_transfer.id)
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
owned_inputs_user2 = b.models.fastquery.get_outputs_by_public_key(user2_pk) owned_inputs_user2 = b.models.get_outputs_filtered(user2_pk)
assert owned_inputs_user1 == expected_owned_inputs_user1 assert sorted(owned_inputs_user1, key=attrgetter("index")) == sorted(
assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0), TransactionLink(tx_transfer.id, 1)] expected_owned_inputs_user1, key=attrgetter("index")
)
assert sorted(owned_inputs_user2, key=attrgetter("index")) == sorted(
[stored_tx_transfer.outputs[0], stored_tx_transfer.outputs[1]], key=attrgetter("index")
)
def test_get_owned_ids_multiple_owners(self, b, user_sk, user_pk, alice): def test_get_owned_ids_multiple_owners(self, b, user_sk, user_pk, alice):
user2_sk, user2_pk = crypto.generate_key_pair() user2_sk, user2_pk = crypto.generate_key_pair()
@ -257,10 +270,11 @@ class TestMultipleInputs(object):
tx = tx.sign([alice.private_key]) tx = tx.sign([alice.private_key])
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
stored_tx = b.models.get_transaction(tx.id)
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
owned_inputs_user2 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user2 = b.models.get_outputs_filtered(user_pk)
expected_owned_inputs_user1 = [TransactionLink(tx.id, 0)] expected_owned_inputs_user1 = [stored_tx.outputs[0]]
assert owned_inputs_user1 == owned_inputs_user2 assert owned_inputs_user1 == owned_inputs_user2
assert owned_inputs_user1 == expected_owned_inputs_user1 assert owned_inputs_user1 == expected_owned_inputs_user1
@ -269,9 +283,9 @@ class TestMultipleInputs(object):
tx = tx.sign([user_sk, user2_sk]) tx = tx.sign([user_sk, user2_sk])
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
owned_inputs_user2 = b.models.fastquery.get_outputs_by_public_key(user2_pk) owned_inputs_user2 = b.models.get_outputs_filtered(user2_pk)
spent_user1 = b.models.get_spent(tx.id, 0) spent_user1 = b.models.get_spending_transaction(tx.id, 0)
assert owned_inputs_user1 == owned_inputs_user2 assert owned_inputs_user1 == owned_inputs_user2
assert not spent_user1 assert not spent_user1
@ -283,11 +297,11 @@ class TestMultipleInputs(object):
tx = tx.sign([alice.private_key]) tx = tx.sign([alice.private_key])
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk).pop() owned_inputs_user1 = b.models.get_outputs_filtered(user_pk).pop()
# check spents # check spents
input_txid = owned_inputs_user1.txid input_txid = owned_inputs_user1.transaction_id
spent_inputs_user1 = b.models.get_spent(input_txid, 0) spent_inputs_user1 = b.models.get_spending_transaction(input_txid, 0)
assert spent_inputs_user1 is None assert spent_inputs_user1 is None
# create a transaction and send it # create a transaction and send it
@ -295,7 +309,7 @@ class TestMultipleInputs(object):
tx = tx.sign([user_sk]) tx = tx.sign([user_sk])
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
spent_inputs_user1 = b.models.get_spent(input_txid, 0) spent_inputs_user1 = b.models.get_spending_transaction(input_txid, 0)
assert spent_inputs_user1 == tx.to_dict() assert spent_inputs_user1 == tx.to_dict()
def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice): def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice):
@ -307,11 +321,11 @@ class TestMultipleInputs(object):
tx_create_signed = tx_create.sign([alice.private_key]) tx_create_signed = tx_create.sign([alice.private_key])
b.models.store_bulk_transactions([tx_create_signed]) b.models.store_bulk_transactions([tx_create_signed])
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
# check spents # check spents
for input_tx in owned_inputs_user1: for input_tx in owned_inputs_user1:
assert b.models.get_spent(input_tx.txid, input_tx.output) is None assert b.models.get_spending_transaction(input_tx.transaction_id, input_tx.index) is None
# transfer the first 2 inputs # transfer the first 2 inputs
tx_transfer = Transfer.generate( tx_transfer = Transfer.generate(
@ -322,12 +336,12 @@ class TestMultipleInputs(object):
# check that used inputs are marked as spent # check that used inputs are marked as spent
for ffill in tx_create.to_inputs()[:2]: for ffill in tx_create.to_inputs()[:2]:
spent_tx = b.models.get_spent(ffill.fulfills.txid, ffill.fulfills.output) spent_tx = b.models.get_spending_transaction(ffill.fulfills.txid, ffill.fulfills.output)
assert spent_tx == tx_transfer_signed.to_dict() assert spent_tx == tx_transfer_signed.to_dict()
# check if remaining transaction that was unspent is also perceived # check if remaining transaction that was unspent is also perceived
# spendable by Planetmint # spendable by Planetmint
assert b.models.get_spent(tx_create.to_inputs()[2].fulfills.txid, 2) is None assert b.models.get_spending_transaction(tx_create.to_inputs()[2].fulfills.txid, 2) is None
def test_get_spent_multiple_owners(self, b, user_sk, user_pk, alice): def test_get_spent_multiple_owners(self, b, user_sk, user_pk, alice):
user2_sk, user2_pk = crypto.generate_key_pair() user2_sk, user2_pk = crypto.generate_key_pair()
@ -342,10 +356,10 @@ class TestMultipleInputs(object):
b.models.store_bulk_transactions(transactions) b.models.store_bulk_transactions(transactions)
owned_inputs_user1 = b.models.fastquery.get_outputs_by_public_key(user_pk) owned_inputs_user1 = b.models.get_outputs_filtered(user_pk)
# check spents # check spents
for input_tx in owned_inputs_user1: for input_tx in owned_inputs_user1:
assert b.models.get_spent(input_tx.txid, input_tx.output) is None assert b.models.get_spending_transaction(input_tx.transaction_id, input_tx.index) is None
# create a transaction # create a transaction
tx = Transfer.generate(transactions[0].to_inputs(), [([user3_pk], 1)], asset_ids=[transactions[0].id]) tx = Transfer.generate(transactions[0].to_inputs(), [([user3_pk], 1)], asset_ids=[transactions[0].id])
@ -353,59 +367,49 @@ class TestMultipleInputs(object):
b.models.store_bulk_transactions([tx]) b.models.store_bulk_transactions([tx])
# check that used inputs are marked as spent # check that used inputs are marked as spent
assert b.models.get_spent(transactions[0].id, 0) == tx.to_dict() assert b.models.get_spending_transaction(transactions[0].id, 0) == tx.to_dict()
# check that the other remain marked as unspent # check that the other remain marked as unspent
for unspent in transactions[1:]: for unspent in transactions[1:]:
assert b.models.get_spent(unspent.id, 0) is None assert b.models.get_spending_transaction(unspent.id, 0) is None
def test_get_outputs_filtered_only_unspent(b): def test_get_outputs_filtered_only_unspent(b, alice):
from transactions.common.transaction import TransactionLink tx = Create.generate([alice.public_key], [([alice.public_key], 1), ([alice.public_key], 1)])
tx = tx.sign([alice.private_key])
b.models.store_bulk_transactions([tx])
go = "planetmint.model.fastquery.FastQuery.get_outputs_by_public_key" tx_transfer = Transfer.generate(tx.to_inputs([0]), [([alice.public_key], 1)], asset_ids=[tx.id])
with patch(go) as get_outputs: tx_transfer = tx_transfer.sign([alice.private_key])
get_outputs.return_value = [TransactionLink("a", 1), TransactionLink("b", 2)] b.models.store_bulk_transactions([tx_transfer])
fs = "planetmint.model.fastquery.FastQuery.filter_spent_outputs"
with patch(fs) as filter_spent: outputs = b.models.get_outputs_filtered(alice.public_key, spent=False)
filter_spent.return_value = [TransactionLink("b", 2)] assert len(outputs) == 2
out = b.models.get_outputs_filtered("abc", spent=False)
get_outputs.assert_called_once_with("abc")
assert out == [TransactionLink("b", 2)]
def test_get_outputs_filtered_only_spent(b): def test_get_outputs_filtered_only_spent(b, alice):
from transactions.common.transaction import TransactionLink tx = Create.generate([alice.public_key], [([alice.public_key], 1), ([alice.public_key], 1)])
tx = tx.sign([alice.private_key])
b.models.store_bulk_transactions([tx])
go = "planetmint.model.fastquery.FastQuery.get_outputs_by_public_key" tx_transfer = Transfer.generate(tx.to_inputs([0]), [([alice.public_key], 1)], asset_ids=[tx.id])
with patch(go) as get_outputs: tx_transfer = tx_transfer.sign([alice.private_key])
get_outputs.return_value = [TransactionLink("a", 1), TransactionLink("b", 2)] b.models.store_bulk_transactions([tx_transfer])
fs = "planetmint.model.fastquery.FastQuery.filter_unspent_outputs"
with patch(fs) as filter_spent: outputs = b.models.get_outputs_filtered(alice.public_key, spent=True)
filter_spent.return_value = [TransactionLink("b", 2)] assert len(outputs) == 1
out = b.models.get_outputs_filtered("abc", spent=True)
get_outputs.assert_called_once_with("abc")
assert out == [TransactionLink("b", 2)]
# @patch("planetmint.model.fastquery.FastQuery.filter_unspent_outputs") def test_get_outputs_filtered(b, alice):
# @patch("planetmint.model.fastquery.FastQuery.filter_spent_outputs") tx = Create.generate([alice.public_key], [([alice.public_key], 1), ([alice.public_key], 1)])
def test_get_outputs_filtered( tx = tx.sign([alice.private_key])
b, b.models.store_bulk_transactions([tx])
mocker,
):
from transactions.common.transaction import TransactionLink
mock_filter_spent_outputs = mocker.patch("planetmint.model.fastquery.FastQuery.filter_spent_outputs") tx_transfer = Transfer.generate(tx.to_inputs([0]), [([alice.public_key], 1)], asset_ids=[tx.id])
mock_filter_unspent_outputs = mocker.patch("planetmint.model.fastquery.FastQuery.filter_unspent_outputs") tx_transfer = tx_transfer.sign([alice.private_key])
b.models.store_bulk_transactions([tx_transfer])
go = "planetmint.model.fastquery.FastQuery.get_outputs_by_public_key" outputs = b.models.get_outputs_filtered(alice.public_key)
with patch(go) as get_outputs: assert len(outputs) == 3
get_outputs.return_value = [TransactionLink("a", 1), TransactionLink("b", 2)]
out = b.models.get_outputs_filtered("abc")
get_outputs.assert_called_once_with("abc")
mock_filter_spent_outputs.assert_not_called()
mock_filter_unspent_outputs.assert_not_called()
assert out == get_outputs.return_value
def test_cant_spend_same_input_twice_in_tx(b, alice): def test_cant_spend_same_input_twice_in_tx(b, alice):

View File

@ -1,134 +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
import pytest
from transactions.common.transaction import TransactionLink
from transactions.types.assets.create import Create
from transactions.types.assets.transfer import Transfer
pytestmark = pytest.mark.bdb
@pytest.fixture
def txns(b, user_pk, user_sk, user2_pk, user2_sk, test_models):
txs = [
Create.generate([user_pk], [([user2_pk], 1)]).sign([user_sk]),
Create.generate([user2_pk], [([user_pk], 1)]).sign([user2_sk]),
Create.generate([user_pk], [([user_pk], 1), ([user2_pk], 1)]).sign([user_sk]),
]
b.models.store_bulk_transactions(txs)
return txs
def test_get_outputs_by_public_key(b, user_pk, user2_pk, txns, test_models):
expected = [TransactionLink(txns[1].id, 0), TransactionLink(txns[2].id, 0)]
actual = test_models.fastquery.get_outputs_by_public_key(user_pk)
_all_txs = set([tx.txid for tx in expected + actual])
assert len(_all_txs) == 2
# assert b.models.fastquery.get_outputs_by_public_key(user_pk) == [ # OLD VERIFICATION
# TransactionLink(txns[1].id, 0),
# TransactionLink(txns[2].id, 0)
# ]
actual_1 = test_models.fastquery.get_outputs_by_public_key(user2_pk)
expected_1 = [
TransactionLink(txns[0].id, 0),
TransactionLink(txns[2].id, 1),
]
_all_tx_1 = set([tx.txid for tx in actual_1 + expected_1])
assert len(_all_tx_1) == 2
# assert b.models.fastquery.get_outputs_by_public_key(user2_pk) == [ # OLD VERIFICATION
# TransactionLink(txns[0].id, 0),
# TransactionLink(txns[2].id, 1),
# ]
def test_filter_spent_outputs(b, user_pk, user_sk, test_models):
out = [([user_pk], 1)]
tx1 = Create.generate([user_pk], out * 2)
tx1.sign([user_sk])
inputs = tx1.to_inputs()
tx2 = Transfer.generate([inputs[0]], out, [tx1.id])
tx2.sign([user_sk])
# tx2 produces a new unspent. inputs[1] remains unspent.
b.models.store_bulk_transactions([tx1, tx2])
outputs = test_models.fastquery.get_outputs_by_public_key(user_pk)
unspents = test_models.fastquery.filter_spent_outputs(outputs)
assert set(unsp for unsp in unspents) == {
inputs[1].fulfills,
tx2.to_inputs()[0].fulfills,
}
def test_filter_unspent_outputs(b, user_pk, user_sk, test_models):
out = [([user_pk], 1)]
tx1 = Create.generate([user_pk], out * 2)
tx1.sign([user_sk])
inputs = tx1.to_inputs()
tx2 = Transfer.generate([inputs[0]], out, [tx1.id])
tx2.sign([user_sk])
# tx2 produces a new unspent. input[1] remains unspent.
b.models.store_bulk_transactions([tx1, tx2])
outputs = test_models.fastquery.get_outputs_by_public_key(user_pk)
spents = test_models.fastquery.filter_unspent_outputs(outputs)
assert set(sp for sp in spents) == {
inputs[0].fulfills,
}
def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk, test_models, test_validator):
from planetmint import backend
from planetmint.backend.connection import Connection
from planetmint.backend import query
tx1 = Create.generate([user_pk], [([user_pk], 3), ([user_pk], 2), ([user_pk], 1)]).sign([user_sk])
b.models.store_bulk_transactions([tx1])
inputs = tx1.to_inputs()
tx2 = Transfer.generate([inputs[1]], [([user2_pk], 2)], [tx1.id]).sign([user_sk])
assert test_validator.validate_transaction(tx2)
tx2_dict = tx2.to_dict()
fulfills = tx2_dict["inputs"][0]["fulfills"]
tx2_dict["inputs"][0]["fulfills"] = {
"transaction_id": fulfills["transaction_id"],
"output_index": fulfills["output_index"],
}
backend.query.store_transactions(test_models.connection, [tx2_dict])
outputs = test_models.get_outputs_filtered(user_pk, spent=False)
assert len(outputs) == 2
outputs = test_models.get_outputs_filtered(user2_pk, spent=False)
assert len(outputs) == 1
# clean the transaction, metdata and asset collection
connection = Connection()
query.delete_transactions(test_models.connection, txn_ids=[tx1.id, tx2.id])
b.models.store_bulk_transactions([tx1])
tx2_dict = tx2.to_dict()
tx2_dict["inputs"][0]["fulfills"] = {
"output_index": fulfills["output_index"],
"transaction_id": fulfills["transaction_id"],
}
backend.query.store_transactions(test_models.connection, [tx2_dict])
outputs = test_models.get_outputs_filtered(user_pk, spent=False)
assert len(outputs) == 2
outputs = test_models.get_outputs_filtered(user2_pk, spent=False)
assert len(outputs) == 1

View File

@ -22,7 +22,6 @@ from ipld import marshal, multihash
from uuid import uuid4 from uuid import uuid4
from planetmint.abci.rpc import MODE_COMMIT, MODE_LIST from planetmint.abci.rpc import MODE_COMMIT, MODE_LIST
from tests.utils import delete_unspent_outputs, get_utxoset_merkle_root, store_unspent_outputs, update_utxoset
@pytest.mark.bdb @pytest.mark.bdb
@ -152,17 +151,17 @@ def test_post_transaction_invalid_mode(b, test_abci_rpc):
@pytest.mark.bdb @pytest.mark.bdb
def test_update_utxoset(b, signed_create_tx, signed_transfer_tx, db_conn): def test_update_utxoset(b, signed_create_tx, signed_transfer_tx, db_conn):
update_utxoset(b.models.connection, signed_create_tx) b.models.update_utxoset(signed_create_tx.to_dict())
utxoset = db_conn.get_space("utxos") utxoset = db_conn.get_space("utxos")
assert utxoset.select().rowcount == 1 assert utxoset.select().rowcount == 1
utxo = utxoset.select().data utxo = utxoset.select().data
assert utxo[0][1] == signed_create_tx.id assert utxo[0][5] == signed_create_tx.id
assert utxo[0][2] == 0 assert utxo[0][4] == 0
update_utxoset(b.models.connection, signed_transfer_tx) b.models.update_utxoset(signed_transfer_tx.to_dict())
assert utxoset.select().rowcount == 1 assert utxoset.select().rowcount == 1
utxo = utxoset.select().data utxo = utxoset.select().data
assert utxo[0][1] == signed_transfer_tx.id assert utxo[0][5] == signed_transfer_tx.id
assert utxo[0][2] == 0 assert utxo[0][4] == 0
@pytest.mark.bdb @pytest.mark.bdb
@ -184,107 +183,80 @@ def test_store_bulk_transaction(mocker, b, signed_create_tx, signed_transfer_tx)
@pytest.mark.bdb @pytest.mark.bdb
def test_delete_zero_unspent_outputs(b, utxoset): def test_delete_zero_unspent_outputs(b, alice):
unspent_outputs, utxo_collection = utxoset from planetmint.backend.tarantool.sync_io import query
num_rows_before_operation = utxo_collection.select().rowcount
delete_res = delete_unspent_outputs(b.models.connection) # noqa: F841 utxo_space = b.models.connection.get_space("utxos")
num_rows_after_operation = utxo_collection.select().rowcount
# assert delete_res is None tx = Create.generate([alice.public_key], [([alice.public_key], 8), ([alice.public_key], 1)]).sign(
[alice.private_key]
)
b.models.store_bulk_transactions([tx])
num_rows_before_operation = utxo_space.select().rowcount
query.delete_unspent_outputs(b.models.connection, []) # noqa: F841
num_rows_after_operation = utxo_space.select().rowcount
assert num_rows_before_operation == num_rows_after_operation assert num_rows_before_operation == num_rows_after_operation
@pytest.mark.bdb @pytest.mark.bdb
def test_delete_one_unspent_outputs(b, dummy_unspent_outputs): def test_delete_one_unspent_outputs(b, alice):
from planetmint.backend.tarantool.sync_io import query
utxo_space = b.models.connection.get_space("utxos") utxo_space = b.models.connection.get_space("utxos")
for utxo in dummy_unspent_outputs:
res = utxo_space.insert((uuid4().hex, utxo["transaction_id"], utxo["output_index"], utxo))
assert res
delete_unspent_outputs(b.models.connection, dummy_unspent_outputs[0]) tx = Create.generate([alice.public_key], [([alice.public_key], 8), ([alice.public_key], 1)]).sign(
res1 = utxo_space.select(["a", 1], index="utxo_by_transaction_id_and_output_index").data [alice.private_key]
res2 = utxo_space.select(["b", 0], index="utxo_by_transaction_id_and_output_index").data )
assert len(res1) + len(res2) == 2
res3 = utxo_space.select(["a", 0], index="utxo_by_transaction_id_and_output_index").data b.models.store_bulk_transactions([tx])
assert len(res3) == 0
query.delete_unspent_outputs(b.models.connection, [{"transaction_id": tx.id, "output_index": 0}])
res1 = utxo_space.select([tx.id, 1], index="utxo_by_transaction_id_and_output_index").data
res2 = utxo_space.select([tx.id, 0], index="utxo_by_transaction_id_and_output_index").data
assert len(res1) + len(res2) == 1
assert len(res2) == 0
@pytest.mark.bdb @pytest.mark.bdb
def test_delete_many_unspent_outputs(b, dummy_unspent_outputs): def test_delete_many_unspent_outputs(b, alice):
from planetmint.backend.tarantool.sync_io import query
utxo_space = b.models.connection.get_space("utxos") utxo_space = b.models.connection.get_space("utxos")
for utxo in dummy_unspent_outputs:
res = utxo_space.insert((uuid4().hex, utxo["transaction_id"], utxo["output_index"], utxo))
assert res
delete_unspent_outputs(b.models.connection, *dummy_unspent_outputs[::2]) tx = Create.generate(
res1 = utxo_space.select(["a", 0], index="utxo_by_transaction_id_and_output_index").data [alice.public_key], [([alice.public_key], 8), ([alice.public_key], 1), ([alice.public_key], 4)]
res2 = utxo_space.select(["b", 0], index="utxo_by_transaction_id_and_output_index").data ).sign([alice.private_key])
assert len(res1) + len(res2) == 0
res3 = utxo_space.select([], index="utxo_by_transaction_id_and_output_index").data
assert len(res3) == 1
b.models.store_bulk_transactions([tx])
@pytest.mark.bdb query.delete_unspent_outputs(
def test_store_zero_unspent_output(b): b.models.connection,
utxos = b.models.connection.get_space("utxos") [{"transaction_id": tx.id, "output_index": 0}, {"transaction_id": tx.id, "output_index": 2}],
num_rows_before_operation = utxos.select().rowcount )
res = store_unspent_outputs(b.models.connection) res1 = utxo_space.select([tx.id, 1], index="utxo_by_transaction_id_and_output_index").data
num_rows_after_operation = utxos.select().rowcount res2 = utxo_space.select([tx.id, 0], index="utxo_by_transaction_id_and_output_index").data
assert res is None assert len(res1) + len(res2) == 1
assert num_rows_before_operation == num_rows_after_operation
@pytest.mark.bdb
def test_store_one_unspent_output(b, unspent_output_1, utxo_collection):
from planetmint.backend.tarantool.sync_io.connection import TarantoolDBConnection
res = store_unspent_outputs(b.models.connection, unspent_output_1)
if not isinstance(b.models.connection, TarantoolDBConnection):
assert res.acknowledged
assert len(list(res)) == 1
assert (
utxo_collection.count_documents(
{
"transaction_id": unspent_output_1["transaction_id"],
"output_index": unspent_output_1["output_index"],
}
)
== 1
)
else:
utx_space = b.models.connection.get_space("utxos")
res = utx_space.select(
[unspent_output_1["transaction_id"], unspent_output_1["output_index"]],
index="utxo_by_transaction_id_and_output_index",
)
assert len(res.data) == 1
@pytest.mark.bdb
def test_store_many_unspent_outputs(b, unspent_outputs):
store_unspent_outputs(b.models.connection, *unspent_outputs)
utxo_space = b.models.connection.get_space("utxos")
res = utxo_space.select([unspent_outputs[0]["transaction_id"]], index="utxos_by_transaction_id")
assert len(res.data) == 3
def test_get_utxoset_merkle_root_when_no_utxo(b): def test_get_utxoset_merkle_root_when_no_utxo(b):
assert get_utxoset_merkle_root(b.models.connection) == sha3_256(b"").hexdigest() assert b.models.get_utxoset_merkle_root() == sha3_256(b"").hexdigest()
@pytest.mark.bdb @pytest.mark.bdb
def test_get_utxoset_merkle_root(b, dummy_unspent_outputs): def test_get_utxoset_merkle_root(b, user_sk, user_pk):
utxo_space = b.models.connection.get_space("utxos") tx = Create.generate([user_pk], [([user_pk], 8), ([user_pk], 1), ([user_pk], 4)]).sign([user_sk])
for utxo in dummy_unspent_outputs:
res = utxo_space.insert((uuid4().hex, utxo["transaction_id"], utxo["output_index"], utxo))
assert res
expected_merkle_root = "86d311c03115bf4d287f8449ca5828505432d69b82762d47077b1c00fe426eac" b.models.store_bulk_transactions([tx])
merkle_root = get_utxoset_merkle_root(b.models.connection)
assert merkle_root == expected_merkle_root expected_merkle_root = "e5fce6fed606b72744330b28b2f6d68f2eca570c4cf8e3c418b0c3150c75bfe2"
merkle_root = b.models.get_utxoset_merkle_root()
assert merkle_root in expected_merkle_root
@pytest.mark.bdb @pytest.mark.bdb
def test_get_spent_transaction_double_spend(b, alice, bob, carol): def test_get_spending_transaction_double_spend(b, alice, bob, carol):
from transactions.common.exceptions import DoubleSpend from transactions.common.exceptions import DoubleSpend
assets = [{"data": multihash(marshal({"test": "asset"}))}] assets = [{"data": multihash(marshal({"test": "asset"}))}]
@ -308,15 +280,15 @@ def test_get_spent_transaction_double_spend(b, alice, bob, carol):
with pytest.raises(DoubleSpend): with pytest.raises(DoubleSpend):
b.validate_transaction(same_input_double_spend) b.validate_transaction(same_input_double_spend)
assert b.models.get_spent(tx.id, tx_transfer.inputs[0].fulfills.output, [tx_transfer]) assert b.models.get_spending_transaction(tx.id, tx_transfer.inputs[0].fulfills.output, [tx_transfer])
with pytest.raises(DoubleSpend): with pytest.raises(DoubleSpend):
b.models.get_spent(tx.id, tx_transfer.inputs[0].fulfills.output, [tx_transfer, double_spend]) b.models.get_spending_transaction(tx.id, tx_transfer.inputs[0].fulfills.output, [tx_transfer, double_spend])
b.models.store_bulk_transactions([tx_transfer]) b.models.store_bulk_transactions([tx_transfer])
with pytest.raises(DoubleSpend): with pytest.raises(DoubleSpend):
b.models.get_spent(tx.id, tx_transfer.inputs[0].fulfills.output, [double_spend]) b.models.get_spending_transaction(tx.id, tx_transfer.inputs[0].fulfills.output, [double_spend])
def test_validation_with_transaction_buffer(b): def test_validation_with_transaction_buffer(b):

View File

@ -48,7 +48,7 @@ def test_bigchain_class_default_initialization(config):
@pytest.mark.bdb @pytest.mark.bdb
def test_get_spent_issue_1271(b, alice, bob, carol): def test_get_spending_transaction_issue_1271(b, alice, bob, carol):
tx_1 = Create.generate( tx_1 = Create.generate(
[carol.public_key], [carol.public_key],
[([carol.public_key], 8)], [([carol.public_key], 8)],
@ -88,7 +88,7 @@ def test_get_spent_issue_1271(b, alice, bob, carol):
assert b.validate_transaction(tx_5) assert b.validate_transaction(tx_5)
b.models.store_bulk_transactions([tx_5]) b.models.store_bulk_transactions([tx_5])
assert b.models.get_spent(tx_2.id, 0) == tx_5.to_dict() assert b.models.get_spending_transaction(tx_2.id, 0) == tx_5.to_dict()
assert not b.models.get_spent(tx_5.id, 0) assert not b.models.get_spending_transaction(tx_5.id, 0)
assert b.models.get_outputs_filtered(alice.public_key) assert b.models.get_outputs_filtered(alice.public_key)
assert b.models.get_outputs_filtered(alice.public_key, spent=False) assert b.models.get_outputs_filtered(alice.public_key, spent=False)

View File

@ -3,7 +3,6 @@
# 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
import multiprocessing import multiprocessing
from hashlib import sha3_256
import base58 import base58
import base64 import base64
@ -11,7 +10,6 @@ import random
from functools import singledispatch from functools import singledispatch
from planetmint import backend
from planetmint.backend.localmongodb.connection import LocalMongoDBConnection from planetmint.backend.localmongodb.connection import LocalMongoDBConnection
from planetmint.backend.tarantool.sync_io.connection import TarantoolDBConnection from planetmint.backend.tarantool.sync_io.connection import TarantoolDBConnection
from planetmint.backend.schema import TABLES from planetmint.backend.schema import TABLES
@ -20,7 +18,7 @@ from transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT
from transactions.types.assets.create import Create from transactions.types.assets.create import Create
from transactions.types.elections.vote import Vote from transactions.types.elections.vote import Vote
from transactions.types.elections.validator_utils import election_id_to_public_key from transactions.types.elections.validator_utils import election_id_to_public_key
from planetmint.abci.utils import merkleroot, key_to_base64 from planetmint.abci.utils import key_to_base64
from planetmint.abci.rpc import MODE_COMMIT, MODE_LIST from planetmint.abci.rpc import MODE_COMMIT, MODE_LIST
@ -127,78 +125,6 @@ def generate_election(b, cls, public_key, private_key, asset_data, voter_keys):
return election, votes return election, votes
def delete_unspent_outputs(connection, *unspent_outputs):
"""Deletes the given ``unspent_outputs`` (utxos).
Args:
*unspent_outputs (:obj:`tuple` of :obj:`dict`): Variable
length tuple or list of unspent outputs.
"""
if unspent_outputs:
return backend.query.delete_unspent_outputs(connection, *unspent_outputs)
def get_utxoset_merkle_root(connection):
"""Returns the merkle root of the utxoset. This implies that
the utxoset is first put into a merkle tree.
For now, the merkle tree and its root will be computed each
time. This obviously is not efficient and a better approach
that limits the repetition of the same computation when
unnecesary should be sought. For instance, future optimizations
could simply re-compute the branches of the tree that were
affected by a change.
The transaction hash (id) and output index should be sufficient
to uniquely identify a utxo, and consequently only that
information from a utxo record is needed to compute the merkle
root. Hence, each node of the merkle tree should contain the
tuple (txid, output_index).
.. important:: The leaves of the tree will need to be sorted in
some kind of lexicographical order.
Returns:
str: Merkle root in hexadecimal form.
"""
utxoset = backend.query.get_unspent_outputs(connection)
# TODO Once ready, use the already pre-computed utxo_hash field.
# See common/transactions.py for details.
hashes = [
sha3_256("{}{}".format(utxo["transaction_id"], utxo["output_index"]).encode()).digest() for utxo in utxoset
]
# TODO Notice the sorted call!
return merkleroot(sorted(hashes))
def store_unspent_outputs(connection, *unspent_outputs):
"""Store the given ``unspent_outputs`` (utxos).
Args:
*unspent_outputs (:obj:`tuple` of :obj:`dict`): Variable
length tuple or list of unspent outputs.
"""
if unspent_outputs:
return backend.query.store_unspent_outputs(connection, *unspent_outputs)
def update_utxoset(connection, transaction):
"""
Update the UTXO set given ``transaction``. That is, remove
the outputs that the given ``transaction`` spends, and add the
outputs that the given ``transaction`` creates.
Args:
transaction (:obj:`~planetmint.models.Transaction`): A new
transaction incoming into the system for which the UTXOF
set needs to be updated.
"""
spent_outputs = [spent_output for spent_output in transaction.spent_outputs]
if spent_outputs:
delete_unspent_outputs(connection, *spent_outputs)
store_unspent_outputs(connection, *[utxo._asdict() for utxo in transaction.unspent_outputs])
class ProcessGroup(object): class ProcessGroup(object):
def __init__(self, concurrency=None, group=None, target=None, name=None, args=None, kwargs=None, daemon=None): def __init__(self, concurrency=None, group=None, target=None, name=None, args=None, kwargs=None, daemon=None):
self.concurrency = concurrency or multiprocessing.cpu_count() self.concurrency = concurrency or multiprocessing.cpu_count()

View File

@ -23,10 +23,6 @@ from transactions.common.transaction_mode_types import (
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.utils import _fulfillment_from_details
from transactions.common.crypto import generate_key_pair from transactions.common.crypto import generate_key_pair