Skip to content
Snippets Groups Projects
Commit e2313e7d authored by Vincent Texier's avatar Vincent Texier
Browse files

[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
No related branches found
No related tags found
1 merge request!778Release 0.51.0
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment