Commit e2313e7d authored by Vincent Texier's avatar Vincent Texier

[enh] #798 consume and restore sources with the used_by property

Dropping sources and recreate them from tx inputs was loosing source conditions.
This consume method allow to not loose source and their conditions
parent 9adf9705
......@@ -11,4 +11,4 @@ class Source:
amount = attr.ib(converter=int, hash=False)
base = attr.ib(converter=int, hash=False)
conditions = attr.ib(converter=str, hash=False)
used_by = attr.ib(converter=str, hash=False, default=None)
used_by = attr.ib(hash=False, default=None)
......@@ -53,15 +53,27 @@ class SourcesProcessor:
"""
return self._repo.get_all(currency=currency, pubkey=pubkey)
def consume(self, sources):
def consume(self, sources, tx_sha_hash):
"""
Consume sources in db by setting the used_by column to tx hash
:param List(Source) sources: Source instances list
:param str tx_sha_hash: Hash of tx
:param currency:
:param sources:
:return:
"""
for s in sources:
self._repo.drop(s)
self._repo.consume(s, tx_sha_hash)
def restore_all(self, tx_sha_hash):
"""
Restore sources in db by setting the used_by column to null
:param str tx_sha_hash: Hash of tx
:return:
"""
self._repo.restore_all(tx_sha_hash)
def insert(self, source):
try:
......
......@@ -29,7 +29,8 @@ class SourcesRepo:
def get_one(self, **search):
"""
Get an existing source in the database
Get an existing not consumed source in the database
:param ** search: the criterions of the lookup
:rtype: sakia.data.entities.Source
"""
......@@ -39,7 +40,7 @@ class SourcesRepo:
filters.append("{k}=?".format(k=k))
values.append(v)
request = "SELECT * FROM sources WHERE {filters}".format(
request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format(
filters=" AND ".join(filters)
)
......@@ -50,7 +51,8 @@ class SourcesRepo:
def get_all(self, **search):
"""
Get all existing source in the database corresponding to the search
Get all existing not consumed source in the database corresponding to the search
:param ** search: the criterions of the lookup
:rtype: [sakia.data.entities.Source]
"""
......@@ -61,7 +63,7 @@ class SourcesRepo:
filters.append("{key} = ?".format(key=k))
values.append(value)
request = "SELECT * FROM sources WHERE {filters}".format(
request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format(
filters=" AND ".join(filters)
)
......@@ -101,3 +103,40 @@ class SourcesRepo:
filters=" AND ".join(filters)
)
self._conn.execute(request, tuple(values))
def consume(self, source, tx_hash):
"""
Consume a source by setting the used_by column with the tx hash
:param Source source: Source instance to consume
:param str tx_hash: Hash of tx
:return:
"""
where_fields = attr.astuple(
source, filter=attr.filters.include(*SourcesRepo._primary_keys)
)
fields = (tx_hash,) + where_fields
self._conn.execute(
"""UPDATE sources SET used_by=?
WHERE
currency=? AND
pubkey=? AND
identifier=? AND
noffset=?""",
fields,
)
def restore_all(self, tx_hash):
"""
Restore all sources released by tx_hash setting the used_by column with null
:param Source source: Source instance to consume
:param str tx_hash: Hash of tx
:return:
"""
self._conn.execute(
"""UPDATE sources SET used_by=NULL
WHERE
used_by=?""",
(tx_hash,),
)
......@@ -121,12 +121,9 @@ class TransferModel(QObject):
)
for conn in self._connections_processor.connections():
if conn.pubkey == recipient:
# fixme: do not drop sources from input cause we can not restore source conditions
# from inputs if needed... May cause side effects.
# Add a state field in sources table if needed.
# self.app.sources_service.parse_transaction_inputs(
# recipient, transaction
# )
self.app.sources_service.consume_sources_from_transaction_inputs(
recipient, transaction
)
new_tx = self.app.transactions_service.parse_sent_transaction(
recipient, transaction
)
......
......@@ -409,18 +409,14 @@ class DocumentsService:
"""
lock_modes = {
# Receiver
0: pypeg2.compose(
Condition.token(SIG.token(receiver)), Condition
),
0: pypeg2.compose(Condition.token(SIG.token(receiver)), Condition),
# Receiver or (issuer and delay of one week)
1: pypeg2.compose(
Condition.token(
SIG.token(receiver),
Operator.token("||"),
Condition.token(
SIG.token(issuer),
Operator.token("&&"),
CSV.token(604800),
SIG.token(issuer), Operator.token("&&"), CSV.token(604800),
),
),
Condition,
......@@ -527,7 +523,6 @@ class DocumentsService:
currency,
)
forged_tx += chained_tx
self._sources_processor.consume(sources)
logging.debug("Inputs: {0}".format(sources))
inputs = self.tx_inputs(sources)
......@@ -570,6 +565,9 @@ class DocumentsService:
raw=txdoc.signed_raw(),
)
forged_tx.append(tx)
self._sources_processor.consume(sources, tx.sha_hash)
return forged_tx
async def send_money(
......
......@@ -5,7 +5,7 @@ from duniterpy.grammars.output import Condition, SIG
from duniterpy.documents import BlockUID
import logging
import pypeg2
from sakia.data.entities import Source, Transaction, Dividend
from sakia.data.entities import Source, Transaction
import hashlib
......@@ -68,27 +68,28 @@ class SourcesServices(QObject):
)
self._sources_processor.insert(source)
# def parse_transaction_inputs(self, pubkey, transaction):
# """
# Parse a transaction to drop sources used in inputs
#
# :param str pubkey: Receiver pubkey
# :param sakia.data.entities.Transaction transaction:
# """
# txdoc = TransactionDoc.from_signed_raw(transaction.raw)
# for index, input in enumerate(txdoc.inputs):
# source = Source(
# currency=self.currency,
# pubkey=txdoc.issuers[0],
# identifier=input.origin_id,
# type=input.source,
# noffset=input.index,
# amount=input.amount,
# base=input.base,
# conditions=""
# )
# if source.pubkey == pubkey:
# self._sources_processor.drop(source)
def consume_sources_from_transaction_inputs(self, pubkey, transaction):
"""
Parse a transaction to drop sources used in inputs
:param str pubkey: Receiver pubkey
:param sakia.data.entities.Transaction transaction:
"""
txdoc = TransactionDoc.from_signed_raw(transaction.raw)
for index, input in enumerate(txdoc.inputs):
source = Source(
currency=self.currency,
pubkey=txdoc.issuers[0],
identifier=input.origin_id,
type=input.source,
noffset=input.index,
amount=input.amount,
base=input.base,
conditions="",
used_by=None,
)
if source.pubkey == pubkey:
self._sources_processor.consume((source,), transaction.sha_hash)
def _parse_ud(self, pubkey, dividend):
"""
......@@ -192,40 +193,27 @@ class SourcesServices(QObject):
def restore_sources(self, pubkey, tx):
"""
Restore the sources of a cancelled tx
Restore consumed sources and drop created sources of a cancelled tx
:param sakia.entities.Transaction tx:
:param str pubkey: Pubkey
:param Transaction tx: Instance of tx entity
"""
txdoc = TransactionDoc.from_signed_raw(tx.raw)
for offset, output in enumerate(txdoc.outputs):
if output.condition.left.pubkey == pubkey:
source = Source(
currency=self.currency,
pubkey=pubkey,
identifier=txdoc.sha_hash,
type="T",
noffset=offset,
amount=output.amount,
base=output.base,
conditions=pypeg2.compose(output.condition, Condition),
)
self._sources_processor.drop(source)
# fixme: do not restore sources from input as it is impossible to retrieve conditions from the referenced tx.
# Sources are not dropped now by parse_transaction_inputs(). If a state field is added to source table.
# Then update it back here.
# for index, input in enumerate(txdoc.inputs):
# source = Source(
# currency=self.currency,
# pubkey=txdoc.issuers[0],
# identifier=input.origin_id,
# type=input.source,
# noffset=input.index,
# amount=input.amount,
# base=input.base,
# conditions=""
# )
# if source.pubkey == pubkey:
# self._sources_processor.insert(source)
source = Source(
currency=self.currency,
pubkey=pubkey,
identifier=txdoc.sha_hash,
type="T",
noffset=offset,
amount=output.amount,
base=output.base,
conditions=pypeg2.compose(output.condition, Condition),
)
# drop sources created by the canceled tx
self._sources_processor.drop(source)
# restore consumed sources
self._sources_processor.restore_all(tx.sha_hash)
def find_signature_in_condition(self, _condition, pubkey, result=False):
"""
......
......@@ -77,3 +77,41 @@ def test_add_get_multiple_source(meta_repo):
assert "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843" in [
s.identifier for s in sources
]
def test_consume_restore_source(meta_repo):
tx_hash = "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843"
sources_repo = SourcesRepo(meta_repo.conn)
sources_repo.insert(
Source(
"testcurrency",
"FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn",
tx_hash,
3,
"T",
1565,
1,
"SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)",
None,
)
)
source = sources_repo.get_one(identifier=tx_hash)
assert source.currency == "testcurrency"
assert source.pubkey == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
assert source.type == "T"
assert source.amount == 1565
assert source.base == 1
assert source.noffset == 3
sources_repo.consume(source, tx_hash)
source = sources_repo.get_one(identifier=tx_hash)
assert source is None
sources_repo.restore_all(tx_hash)
source = sources_repo.get_one(identifier=tx_hash)
assert source.currency == "testcurrency"
assert source.pubkey == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
assert source.type == "T"
assert source.amount == 1565
assert source.base == 1
assert source.noffset == 3
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment