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

[enh] #798 add errors in result of evaluate_condition

Refactor tests of evaluate_condition in test_sources.py
Update tests
parent 64f5842b
Branches
Tags
1 merge request!778Release 0.51.0
...@@ -92,9 +92,12 @@ class TransactionsProcessor: ...@@ -92,9 +92,12 @@ class TransactionsProcessor:
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
self._repo.update(tx) self._repo.update(tx)
def find_by_hash(self, pubkey: str, sha_hash: str) -> Transaction: def find_by_pubkey_and_hash(self, pubkey: str, sha_hash: str) -> Transaction:
return self._repo.get_one(pubkey=pubkey, sha_hash=sha_hash) return self._repo.get_one(pubkey=pubkey, sha_hash=sha_hash)
def find_one_by_hash(self, sha_hash: str) -> Transaction:
return self._repo.get_one(sha_hash=sha_hash)
def awaiting(self, currency): def awaiting(self, currency):
return self._repo.get_all(currency=currency, state=Transaction.AWAITING) return self._repo.get_all(currency=currency, state=Transaction.AWAITING)
......
import re import re
import logging import logging
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QCoreApplication import pypeg2
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QCoreApplication, QLocale, QDateTime
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QMessageBox from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QMessageBox
from duniterpy.constants import PUBKEY_REGEX from duniterpy.constants import PUBKEY_REGEX
from duniterpy.documents import CRCPubkey from duniterpy.documents import CRCPubkey
from duniterpy.grammars.output import Condition
from sakia.data.processors import ConnectionsProcessor from sakia.data.processors import ConnectionsProcessor
from sakia.decorators import asyncify from sakia.decorators import asyncify
from sakia.gui.sub.password_input import PasswordInputController from sakia.gui.sub.password_input import PasswordInputController
...@@ -13,6 +16,7 @@ from sakia.gui.sub.search_user.controller import SearchUserController ...@@ -13,6 +16,7 @@ from sakia.gui.sub.search_user.controller import SearchUserController
from sakia.gui.sub.user_information.controller import UserInformationController from sakia.gui.sub.user_information.controller import UserInformationController
from sakia.money import Quantitative from sakia.money import Quantitative
from sakia.gui.widgets.dialogs import dialog_async_exec from sakia.gui.widgets.dialogs import dialog_async_exec
from sakia.services import sources
from .model import TransferModel from .model import TransferModel
from .view import TransferView from .view import TransferView
...@@ -327,6 +331,41 @@ class TransferController(QObject): ...@@ -327,6 +331,41 @@ class TransferController(QObject):
self.refresh() self.refresh()
def check_source(self): def check_source(self):
source = self.model.current_source
condition = pypeg2.parse(source.conditions, Condition)
result, _errors = self.model.app.sources_service.evaluate_condition(
self.model.app.currency, condition, [], [], source.identifier
)
if result:
message = QCoreApplication.translate(
"TransferController", "Check is successful!"
)
else:
message = QCoreApplication.translate(
"TransferController", "<p><b>Condition</b></p>{}"
).format(source.conditions)
message += QCoreApplication.translate(
"TransferController", "<p><b>Errors</b><p>"
)
message += "<ul>"
# add error messages
for (_condition, _error, _param) in _errors:
if isinstance(_param, int):
_param = (
QLocale.toString(
QLocale(),
QDateTime.fromTime_t(_param),
QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat),
)
+ " BAT"
)
message += "\n\n" + QCoreApplication.translate(
"TransferController",
'<li>Error in {}: <span style="color: red">{} {}</span></li>',
).format(_condition, _error, _param)
message += "</ul>"
# open message box displaying source check result
qmessagebox = QMessageBox(self.view) qmessagebox = QMessageBox(self.view)
qmessagebox.setText("The source has been checked.") qmessagebox.setWindowTitle("Check source condition")
qmessagebox.setText(message)
qmessagebox.exec() qmessagebox.exec()
...@@ -331,9 +331,10 @@ class DocumentsService: ...@@ -331,9 +331,10 @@ class DocumentsService:
for s in [src for src in available_sources if src.base == current_base]: for s in [src for src in available_sources if src.base == current_base]:
condition = pypeg2.parse(s.conditions, Condition) condition = pypeg2.parse(s.conditions, Condition)
# evaluate the condition # evaluate the condition
if not self._sources_services.evaluate_condition( result, _ = self._sources_services.evaluate_condition(
currency, condition, [key], [], s.identifier currency, condition, [key], [], s.identifier
): )
if not result:
continue continue
test_sources = sources + [s] test_sources = sources + [s]
val = current_value(test_sources, overheads) val = current_value(test_sources, overheads)
......
from hashlib import sha256 from PyQt5.QtCore import QObject, QT_TRANSLATE_NOOP
from PyQt5.QtCore import QObject
from duniterpy.api import bma, errors from duniterpy.api import bma, errors
from duniterpy.documents import Transaction as TransactionDoc from duniterpy.documents import Transaction as TransactionDoc
from duniterpy.grammars.output import Condition, SIG, CSV, CLTV, XHX from duniterpy.grammars.output import Condition, SIG, CSV, CLTV, XHX
...@@ -10,6 +8,17 @@ import pypeg2 ...@@ -10,6 +8,17 @@ import pypeg2
from sakia.data.entities import Source, Transaction from sakia.data.entities import Source, Transaction
import hashlib import hashlib
EVALUATE_CONDITION_ERROR_SIG = QT_TRANSLATE_NOOP(
"SourcesServices", "missing secret key for public key"
)
EVALUATE_CONDITION_ERROR_XHX = QT_TRANSLATE_NOOP(
"SourcesServices", "missing password for hash"
)
EVALUATE_CONDITION_ERROR_CSV = QT_TRANSLATE_NOOP(
"SourcesServices", "locked by a delay until"
)
EVALUATE_CONDITION_ERROR_CLTV = QT_TRANSLATE_NOOP("SourcesServices", "locked until")
class SourcesServices(QObject): class SourcesServices(QObject):
""" """
...@@ -268,23 +277,25 @@ class SourcesServices(QObject): ...@@ -268,23 +277,25 @@ class SourcesServices(QObject):
def evaluate_condition( def evaluate_condition(
self, self,
currency, currency: str,
condition: Condition, condition: Condition,
keys: list, keys: list,
passwords, passwords: list,
identifier: str, identifier: str,
result: bool = True, result: bool = False,
) -> bool: _errors: list = None,
) -> tuple:
""" """
Evaluate a source lock condition Evaluate a source lock condition
Support multiple signatures and passwords Support multiple signatures and passwords
:param passwords:
:param str currency: Name of currency :param str currency: Name of currency
:param Condition condition: Condition instance :param Condition condition: Condition instance
:param [SigningKey] keys: Keys to unlock condition (first key is the source owner key) :param [SigningKey] keys: Keys to unlock condition
:param [str] passwords: List of passwords
:param str identifier: Source transaction identifier :param str identifier: Source transaction identifier
:param bool result: result accumulator :param bool result: Evaluation result accumulator
:param [tuple] _errors: List of tuple with parameters returning false (parameter: str, message: str, param: int)
:return: :return:
""" """
left = False left = False
...@@ -292,70 +303,162 @@ class SourcesServices(QObject): ...@@ -292,70 +303,162 @@ class SourcesServices(QObject):
# if left param is a condition... # if left param is a condition...
if isinstance(condition.left, Condition): if isinstance(condition.left, Condition):
# evaluate condition # evaluate condition
left = self.evaluate_condition( left, _errors = self.evaluate_condition(
currency, condition.left, keys, passwords, identifier, result currency, condition.left, keys, passwords, identifier, result, _errors
) )
# if right param is a condition... # if right param is a condition...
if isinstance(condition.right, Condition): if isinstance(condition.right, Condition):
# evaluate condition # evaluate condition
right = self.evaluate_condition( right, _errors = self.evaluate_condition(
currency, condition.right, keys, passwords, identifier, result currency, condition.right, keys, passwords, identifier, result, _errors
) )
# if left param is a SIG... # if left param is a SIG...
if isinstance(condition.left, SIG) and condition.left.pubkey in ( if isinstance(condition.left, SIG):
key.pubkey for key in keys if condition.left.pubkey in (key.pubkey for key in keys):
):
left = True left = True
else:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.left),
EVALUATE_CONDITION_ERROR_SIG,
condition.left.pubkey,
)
)
# if left param is a CSV value... # if left param is a CSV value...
if isinstance(condition.left, CSV): if isinstance(condition.left, CSV):
# capture transaction of the source # capture transaction of the source
tx = self._transactions_processor.find_by_hash(keys[0].pubkey, identifier) tx = self._transactions_processor.find_one_by_hash(identifier)
if tx: if tx:
# capture current blockchain time # capture current blockchain time
median_time = self._blockchain_processor.time(currency) median_time = self._blockchain_processor.time(currency)
locked_until = tx.timestamp + int(condition.left.time)
# param is true if tx time + CSV delay <= blockchain time # param is true if tx time + CSV delay <= blockchain time
left = tx.timestamp + int(condition.left.time) <= median_time left = locked_until <= median_time
if left is False:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.left),
EVALUATE_CONDITION_ERROR_CSV,
locked_until,
)
)
# if left param is a CLTV value... # if left param is a CLTV value...
if isinstance(condition.left, CLTV): if isinstance(condition.left, CLTV):
# capture current blockchain time # capture current blockchain time
median_time = self._blockchain_processor.time(currency) median_time = self._blockchain_processor.time(currency)
locked_until = int(condition.left.timestamp)
# param is true if CL:TV value <= blockchain time # param is true if CL:TV value <= blockchain time
left = int(condition.left.timestamp) <= median_time left = locked_until <= median_time
if left is False:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.left),
EVALUATE_CONDITION_ERROR_CLTV,
locked_until,
)
)
# if left param is a XHX value... # if left param is a XHX value...
if isinstance(condition.left, XHX): if isinstance(condition.left, XHX):
left = condition.left.sha_hash in [ left = condition.left.sha_hash in [
sha256(password).hexdigest().upper() for password in passwords hashlib.sha256(password).hexdigest().upper() for password in passwords
] ]
if left is False:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.left),
EVALUATE_CONDITION_ERROR_XHX,
condition.left.sha_hash,
)
)
# if no op then stop evaluation... # if no op then stop evaluation...
if not condition.op: if not condition.op:
return left return left, _errors
# if right param is a SIG... # if right param is a SIG...
if isinstance(condition.right, SIG) and condition.right.pubkey in ( if isinstance(condition.right, SIG):
key.pubkey for key in keys if condition.right.pubkey in (key.pubkey for key in keys):
):
right = True right = True
else:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.right),
EVALUATE_CONDITION_ERROR_SIG,
condition.right.pubkey,
)
)
# if right param is a CSV value... # if right param is a CSV value...
if isinstance(condition.right, CSV): if isinstance(condition.right, CSV):
# capture transaction of the source # capture transaction of the source
tx = self._transactions_processor.find_by_hash(keys[0].pubkey, identifier) tx = self._transactions_processor.find_one_by_hash(identifier)
if tx: if tx:
# capture current blockchain time # capture current blockchain time
median_time = self._blockchain_processor.time(currency) median_time = self._blockchain_processor.time(currency)
locked_until = tx.timestamp + int(condition.right.time)
# param is true if tx time + CSV delay <= blockchain time # param is true if tx time + CSV delay <= blockchain time
right = tx.timestamp + int(condition.right.time) <= median_time right = locked_until <= median_time
if right is False:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.right),
EVALUATE_CONDITION_ERROR_CSV,
locked_until,
)
)
# if right param is a CLTV value... # if right param is a CLTV value...
if isinstance(condition.right, CLTV): if isinstance(condition.right, CLTV):
# capture current blockchain time # capture current blockchain time
median_time = self._blockchain_processor.time(currency) median_time = self._blockchain_processor.time(currency)
# param is true if CLTV value <= blockchain time locked_until = int(condition.right.timestamp)
right = int(condition.right.timestamp) <= median_time # param is true if CL:TV value <= blockchain time
right = locked_until <= median_time
if right is False:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.right),
EVALUATE_CONDITION_ERROR_CLTV,
locked_until,
)
)
# if right param is a XHX value... # if right param is a XHX value...
if isinstance(condition.right, XHX): if isinstance(condition.right, XHX):
right = condition.right.sha_hash in [ right = condition.right.sha_hash in [
sha256(password).hexdigest().upper() for password in passwords hashlib.sha256(password).hexdigest().upper() for password in passwords
] ]
if right is False:
if _errors is None:
_errors = []
_errors.append(
(
pypeg2.compose(condition.right),
EVALUATE_CONDITION_ERROR_XHX,
condition.right.sha_hash,
)
)
# if operator AND... # if operator AND...
if condition.op == "&&": if condition.op == "&&":
return left & right return left & right, _errors
# operator OR # operator OR
return left | right return left | right, _errors
...@@ -53,7 +53,9 @@ class TransactionsService(QObject): ...@@ -53,7 +53,9 @@ class TransactionsService(QObject):
:param sakia.data.entities.Transaction transaction: The transaction to parse :param sakia.data.entities.Transaction transaction: The transaction to parse
:return: The list of transfers sent :return: The list of transfers sent
""" """
if not self._transactions_processor.find_by_hash(pubkey, transaction.sha_hash): if not self._transactions_processor.find_by_pubkey_and_hash(
pubkey, transaction.sha_hash
):
txid = self._transactions_processor.next_txid(transaction.currency, -1) txid = self._transactions_processor.next_txid(transaction.currency, -1)
tx = parse_transaction_doc( tx = parse_transaction_doc(
transaction.txdoc(), transaction.txdoc(),
...@@ -123,7 +125,7 @@ class TransactionsService(QObject): ...@@ -123,7 +125,7 @@ class TransactionsService(QObject):
tx_doc = TransactionDoc.from_bma_history( tx_doc = TransactionDoc.from_bma_history(
history_data["currency"], tx_data history_data["currency"], tx_data
) )
if not self._transactions_processor.find_by_hash( if not self._transactions_processor.find_by_pubkey_and_hash(
connection.pubkey, tx_doc.sha_hash connection.pubkey, tx_doc.sha_hash
) and SimpleTransaction.is_simple(tx_doc): ) and SimpleTransaction.is_simple(tx_doc):
tx = parse_transaction_doc( tx = parse_transaction_doc(
...@@ -233,7 +235,7 @@ class TransactionsService(QObject): ...@@ -233,7 +235,7 @@ class TransactionsService(QObject):
) )
for tx_data in history_data["history"]["received"]: for tx_data in history_data["history"]["received"]:
tx_doc = TransactionDoc.from_bma_history(history_data["currency"], tx_data) tx_doc = TransactionDoc.from_bma_history(history_data["currency"], tx_data)
if not self._transactions_processor.find_by_hash( if not self._transactions_processor.find_by_pubkey_and_hash(
pubkey, tx_doc.sha_hash pubkey, tx_doc.sha_hash
) and SimpleTransaction.is_simple(tx_doc): ) and SimpleTransaction.is_simple(tx_doc):
tx = parse_transaction_doc( tx = parse_transaction_doc(
......
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment