mirror of
https://github.com/planetmint/planetmint.git
synced 2025-03-30 15:08:31 +00:00
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:
parent
dbf4e9085c
commit
6a3c655e3b
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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"""
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
|
@ -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"
|
||||||
|
@ -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),
|
||||||
|
@ -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 = {}
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user