Commit eda2f50b authored by Vincent Texier's avatar Vincent Texier

[enh] #798 add conditions property in sources table and Source entity

Upgrade database automatically to version 8
Remove source drop and restore from inputs as it quite impossible to retrieve source conditions from inputs
parent 7fe07870
......@@ -10,3 +10,4 @@ class Source:
type = attr.ib(converter=str, validator=lambda i, a, s: s == "T" or s == "D")
amount = attr.ib(converter=int, hash=False)
base = attr.ib(converter=int, hash=False)
conditions = attr.ib(converter=str)
BEGIN TRANSACTION;
ALTER TABLE sources ADD COLUMN conditions VARCHAR(255);
COMMIT;
......@@ -89,6 +89,7 @@ class SakiaDatabase:
self.refactor_transactions,
self.drop_incorrect_nodes,
self.insert_last_mass_attribute,
self.add_sources_conditions_property,
]
def upgrade_database(self, to=0):
......@@ -211,6 +212,17 @@ class SakiaDatabase:
with self.conn:
self.conn.executescript(sql_file.read())
def add_sources_conditions_property(self):
self._logger.debug("Add sources conditions property")
sql_file = open(
os.path.join(
os.path.dirname(__file__), "006_add_sources_conditions_property.sql"
),
"r",
)
with self.conn:
self.conn.executescript(sql_file.read())
def version(self):
with self.conn:
c = self.conn.execute("SELECT * FROM meta WHERE id=1")
......
......@@ -106,7 +106,7 @@ CREATE TABLE IF NOT EXISTS nodes(
PRIMARY KEY (currency, pubkey)
);
-- Cnnections TABLE
-- CONNECTIONS TABLE
CREATE TABLE IF NOT EXISTS connections(
currency VARCHAR(30),
pubkey VARCHAR(50),
......@@ -118,7 +118,7 @@ CREATE TABLE IF NOT EXISTS connections(
PRIMARY KEY (currency, pubkey)
);
-- Cnnections TABLE
-- SOURCES TABLE
CREATE TABLE IF NOT EXISTS sources(
currency VARCHAR(30),
pubkey VARCHAR(50),
......@@ -130,6 +130,7 @@ CREATE TABLE IF NOT EXISTS sources(
PRIMARY KEY (currency, pubkey, identifier, noffset)
);
-- DIVIDENDS TABLE
CREATE TABLE IF NOT EXISTS dividends(
currency VARCHAR(30),
pubkey VARCHAR(50),
......
......@@ -30,7 +30,7 @@ class SourcesRepo:
def get_one(self, **search):
"""
Get an existing source in the database
:param dict search: the criterions of the lookup
:param ** search: the criterions of the lookup
:rtype: sakia.data.entities.Source
"""
filters = []
......@@ -51,8 +51,8 @@ class SourcesRepo:
def get_all(self, **search):
"""
Get all existing source in the database corresponding to the search
:param dict search: the criterions of the lookup
:rtype: sakia.data.entities.Source
:param ** search: the criterions of the lookup
:rtype: [sakia.data.entities.Source]
"""
filters = []
values = []
......
......@@ -121,9 +121,12 @@ class TransferModel(QObject):
)
for conn in self._connections_processor.connections():
if conn.pubkey == recipient:
self.app.sources_service.parse_transaction_inputs(
recipient, transaction
)
# 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
# )
new_tx = self.app.transactions_service.parse_sent_transaction(
recipient, transaction
)
......
......@@ -18,7 +18,7 @@ from duniterpy.documents import (
from duniterpy.documents import Identity as IdentityDoc
from duniterpy.documents import Transaction as TransactionDoc
from duniterpy.documents.transaction import reduce_base
from duniterpy.grammars import output
from duniterpy.grammars.output import Condition, Operator, SIG, CSV
from duniterpy.api import bma
from sakia.data.entities import Identity, Transaction, Source
from sakia.data.processors import (
......@@ -28,9 +28,11 @@ from sakia.data.processors import (
TransactionsProcessor,
SourcesProcessor,
CertificationsProcessor,
ConnectionsProcessor,
)
from sakia.data.connectors import BmaConnector, parse_bma_responses
from sakia.errors import NotEnoughChangeError
from sakia.services.sources import SourcesServices
@attr.s()
......@@ -52,6 +54,7 @@ class DocumentsService:
_certifications_processor = attr.ib()
_transactions_processor = attr.ib()
_sources_processor = attr.ib()
_sources_services = attr.ib() # type: SourcesServices
_logger = attr.ib(default=attr.Factory(lambda: logging.getLogger("sakia")))
@classmethod
......@@ -67,6 +70,14 @@ class DocumentsService:
CertificationsProcessor.instanciate(app),
TransactionsProcessor.instanciate(app),
SourcesProcessor.instanciate(app),
SourcesServices(
app.currency,
SourcesProcessor.instanciate(app),
ConnectionsProcessor.instanciate(app),
TransactionsProcessor.instanciate(app),
BlockchainProcessor.instanciate(app),
BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters),
),
)
def generate_identity(self, connection):
......@@ -399,20 +410,20 @@ class DocumentsService:
lock_modes = {
# Receiver
0: pypeg2.compose(
output.Condition.token(output.SIG.token(receiver)), output.Condition
Condition.token(SIG.token(receiver)), Condition
),
# Receiver or (issuer and delay of one week)
1: pypeg2.compose(
output.Condition.token(
output.SIG.token(receiver),
output.Operator.token("||"),
output.Condition.token(
output.SIG.token(issuer),
output.Operator.token("&&"),
output.CSV.token(604800),
Condition.token(
SIG.token(receiver),
Operator.token("||"),
Condition.token(
SIG.token(issuer),
Operator.token("&&"),
CSV.token(604800),
),
),
output.Condition,
Condition,
),
}
......@@ -439,9 +450,7 @@ class DocumentsService:
OutputSource(
overheads_sum,
base,
output.Condition.token(output.SIG.token(issuer)).compose(
output.Condition()
),
pypeg2.compose(Condition.token(SIG.token(issuer)), Condition),
)
)
......@@ -456,7 +465,9 @@ class DocumentsService:
:return:
"""
for offset, output in enumerate(txdoc.outputs):
if output.condition.left.pubkey == pubkey:
if self._sources_services.find_signature_in_condition(
output.condition, pubkey
):
source = Source(
currency=currency,
pubkey=pubkey,
......@@ -465,6 +476,7 @@ class DocumentsService:
noffset=offset,
amount=output.amount,
base=output.base,
conditions=pypeg2.compose(output.condition, Condition),
)
self._sources_processor.insert(source)
......
from PyQt5.QtCore import QObject
from duniterpy.api import bma, errors
from duniterpy.documents import Transaction as TransactionDoc
from duniterpy.grammars import output
from duniterpy.grammars.output import Condition
from duniterpy.grammars.output import Condition, SIG
from duniterpy.documents import BlockUID
import logging
import pypeg2
......@@ -49,12 +48,14 @@ class SourcesServices(QObject):
def parse_transaction_outputs(self, pubkey, transaction):
"""
Parse a transaction
Parse a transaction to extract sources
:param str pubkey: Receiver pubkey
:param sakia.data.entities.Transaction transaction:
"""
txdoc = TransactionDoc.from_signed_raw(transaction.raw)
for offset, output in enumerate(txdoc.outputs):
if output.condition.left.pubkey == pubkey:
if self.find_signature_in_condition(output.condition, pubkey):
source = Source(
currency=self.currency,
pubkey=pubkey,
......@@ -63,32 +64,38 @@ class SourcesServices(QObject):
noffset=offset,
amount=output.amount,
base=output.base,
conditions=pypeg2.compose(output.condition, Condition),
)
self._sources_processor.insert(source)
def parse_transaction_inputs(self, pubkey, transaction):
"""
Parse a transaction
: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,
)
if source.pubkey == pubkey:
self._sources_processor.drop(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 _parse_ud(self, pubkey, dividend):
"""
:param str pubkey:
:param sakia.data.entities.Dividend dividend:
Add source in db from UD
:param str pubkey: Pubkey of UD issuer
:param sakia.data.entities.Dividend dividend: Dividend instance
:return:
"""
source = Source(
......@@ -99,6 +106,7 @@ class SourcesServices(QObject):
noffset=dividend.block_number,
amount=dividend.amount,
base=dividend.base,
conditions=pypeg2.compose(Condition.token(SIG.token(pubkey)), Condition),
)
self._sources_processor.insert(source)
......@@ -185,6 +193,7 @@ class SourcesServices(QObject):
def restore_sources(self, pubkey, tx):
"""
Restore the sources of a cancelled tx
:param sakia.entities.Transaction tx:
"""
txdoc = TransactionDoc.from_signed_raw(tx.raw)
......@@ -198,38 +207,43 @@ class SourcesServices(QObject):
noffset=offset,
amount=output.amount,
base=output.base,
conditions=pypeg2.compose(output.condition, Condition),
)
self._sources_processor.drop(source)
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,
)
if source.pubkey == pubkey:
self._sources_processor.insert(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)
def find_signature_in_condition(self, _condition, pubkey, result=False):
"""
Recursive function to find a SIG(pubkey) in a Condition object
:param output.Condition _condition: Condition instance
:param Condition _condition: Condition instance
:param str pubkey: Pubkey to find
:param bool result: True if found
:return:
"""
if isinstance(_condition.left, output.Condition):
if isinstance(_condition.left, Condition):
result |= self.find_signature_in_condition(_condition.left, pubkey, result)
if isinstance(_condition.right, output.Condition):
if isinstance(_condition.right, Condition):
result |= self.find_signature_in_condition(_condition.right, pubkey, result)
if (
isinstance(_condition.left, output.SIG)
isinstance(_condition.left, SIG)
and _condition.left.pubkey == pubkey
or isinstance(_condition.right, output.SIG)
or isinstance(_condition.right, SIG)
and _condition.right.pubkey == pubkey
):
result |= True
......
import pypeg2
import pytest
import asyncio
import quamash
......@@ -6,6 +7,8 @@ import mirage
import sys
import os
from duniterpy.grammars import output
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
from sakia.constants import ROOT_SERVERS
......@@ -260,6 +263,10 @@ def application_with_one_connection(application, simple_blockchain_forge, bob):
type=s.source,
amount=s.amount,
base=s.base,
conditions=pypeg2.compose(
output.Condition.token(output.SIG.token(bob.key.pubkey)),
output.Condition,
),
)
)
bob_blockstamp = simple_blockchain_forge.user_identities[bob.key.pubkey].blockstamp
......@@ -314,6 +321,10 @@ def application_with_two_connections(
type=s.source,
amount=s.amount,
base=s.base,
conditions=pypeg2.compose(
output.Condition.token(output.SIG.token(bob.key.pubkey)),
output.Condition,
),
)
)
except sqlite3.IntegrityError:
......
......@@ -13,6 +13,7 @@ def test_add_get_drop_source(meta_repo):
"T",
1565,
1,
"SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)",
)
)
source = sources_repo.get_one(
......@@ -43,6 +44,7 @@ def test_add_get_multiple_source(meta_repo):
"T",
1565,
1,
"SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)",
)
)
sources_repo.insert(
......@@ -54,6 +56,7 @@ def test_add_get_multiple_source(meta_repo):
"D",
726946,
1,
"SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)",
)
)
sources = sources_repo.get_all(
......
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