Commit 22e93520 authored by Vincent Texier's avatar Vincent Texier

[enh] #798 add errors in result of evaluate_condition

Refactor tests of evaluate_condition in test_sources.py
Update tests
parent 64f5842b
......@@ -92,9 +92,12 @@ class TransactionsProcessor:
except sqlite3.IntegrityError:
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)
def find_one_by_hash(self, sha_hash: str) -> Transaction:
return self._repo.get_one(sha_hash=sha_hash)
def awaiting(self, currency):
return self._repo.get_all(currency=currency, state=Transaction.AWAITING)
......
import re
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 duniterpy.constants import PUBKEY_REGEX
from duniterpy.documents import CRCPubkey
from duniterpy.grammars.output import Condition
from sakia.data.processors import ConnectionsProcessor
from sakia.decorators import asyncify
from sakia.gui.sub.password_input import PasswordInputController
......@@ -13,6 +16,7 @@ from sakia.gui.sub.search_user.controller import SearchUserController
from sakia.gui.sub.user_information.controller import UserInformationController
from sakia.money import Quantitative
from sakia.gui.widgets.dialogs import dialog_async_exec
from sakia.services import sources
from .model import TransferModel
from .view import TransferView
......@@ -327,6 +331,41 @@ class TransferController(QObject):
self.refresh()
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.setText("The source has been checked.")
qmessagebox.setWindowTitle("Check source condition")
qmessagebox.setText(message)
qmessagebox.exec()
......@@ -331,9 +331,10 @@ class DocumentsService:
for s in [src for src in available_sources if src.base == current_base]:
condition = pypeg2.parse(s.conditions, Condition)
# evaluate the condition
if not self._sources_services.evaluate_condition(
result, _ = self._sources_services.evaluate_condition(
currency, condition, [key], [], s.identifier
):
)
if not result:
continue
test_sources = sources + [s]
val = current_value(test_sources, overheads)
......
from hashlib import sha256
from PyQt5.QtCore import QObject
from PyQt5.QtCore import QObject, QT_TRANSLATE_NOOP
from duniterpy.api import bma, errors
from duniterpy.documents import Transaction as TransactionDoc
from duniterpy.grammars.output import Condition, SIG, CSV, CLTV, XHX
......@@ -10,6 +8,17 @@ import pypeg2
from sakia.data.entities import Source, Transaction
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):
"""
......@@ -268,23 +277,25 @@ class SourcesServices(QObject):
def evaluate_condition(
self,
currency,
currency: str,
condition: Condition,
keys: list,
passwords,
passwords: list,
identifier: str,
result: bool = True,
) -> bool:
result: bool = False,
_errors: list = None,
) -> tuple:
"""
Evaluate a source lock condition
Support multiple signatures and passwords
:param passwords:
:param str currency: Name of currency
: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 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:
"""
left = False
......@@ -292,70 +303,162 @@ class SourcesServices(QObject):
# if left param is a condition...
if isinstance(condition.left, Condition):
# evaluate condition
left = self.evaluate_condition(
currency, condition.left, keys, passwords, identifier, result
left, _errors = self.evaluate_condition(
currency, condition.left, keys, passwords, identifier, result, _errors
)
# if right param is a condition...
if isinstance(condition.right, Condition):
# evaluate condition
right = self.evaluate_condition(
currency, condition.right, keys, passwords, identifier, result
right, _errors = self.evaluate_condition(
currency, condition.right, keys, passwords, identifier, result, _errors
)
# if left param is a SIG...
if isinstance(condition.left, SIG) and condition.left.pubkey in (
key.pubkey for key in keys
):
left = True
if isinstance(condition.left, SIG):
if condition.left.pubkey in (key.pubkey for key in keys):
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 isinstance(condition.left, CSV):
# 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:
# capture current blockchain time
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
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 isinstance(condition.left, CLTV):
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
locked_until = int(condition.left.timestamp)
# 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 isinstance(condition.left, XHX):
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 not condition.op:
return left
return left, _errors
# if right param is a SIG...
if isinstance(condition.right, SIG) and condition.right.pubkey in (
key.pubkey for key in keys
):
right = True
if isinstance(condition.right, SIG):
if condition.right.pubkey in (key.pubkey for key in keys):
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 isinstance(condition.right, CSV):
# 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:
# capture current blockchain time
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
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 isinstance(condition.right, CLTV):
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
# param is true if CLTV value <= blockchain time
right = int(condition.right.timestamp) <= median_time
locked_until = int(condition.right.timestamp)
# 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 isinstance(condition.right, XHX):
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 condition.op == "&&":
return left & right
return left & right, _errors
# operator OR
return left | right
return left | right, _errors
......@@ -53,7 +53,9 @@ class TransactionsService(QObject):
:param sakia.data.entities.Transaction transaction: The transaction to parse
: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)
tx = parse_transaction_doc(
transaction.txdoc(),
......@@ -123,7 +125,7 @@ class TransactionsService(QObject):
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(
connection.pubkey, tx_doc.sha_hash
) and SimpleTransaction.is_simple(tx_doc):
tx = parse_transaction_doc(
......@@ -233,7 +235,7 @@ class TransactionsService(QObject):
)
for tx_data in history_data["history"]["received"]:
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
) and SimpleTransaction.is_simple(tx_doc):
tx = parse_transaction_doc(
......
This diff is collapsed.
This diff is collapsed.
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