Commit 64f5842b authored by Vincent Texier's avatar Vincent Texier

[enh] #798 refactor evaluate_condition in sources service

Fix tests
parent 8d3f2bd9
......@@ -2,7 +2,7 @@ import re
import logging
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QCoreApplication
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QMessageBox
from duniterpy.constants import PUBKEY_REGEX
from duniterpy.documents import CRCPubkey
......@@ -34,7 +34,7 @@ class TransferController(QObject):
"""
super().__init__()
self.view = view
self.model = model
self.model = model # type: TransferModel
self.search_user = search_user
self.user_information = user_information
self.password_input = password_input
......@@ -49,6 +49,7 @@ class TransferController(QObject):
)
self.view.spinbox_amount.valueChanged.connect(self.handle_amount_change)
self.view.spinbox_relative.valueChanged.connect(self.handle_relative_change)
self.view.button_source_check.clicked.connect(self.check_source)
@classmethod
def create(cls, parent, app):
......@@ -324,3 +325,8 @@ class TransferController(QObject):
self.model.set_connection(index)
self.password_input.set_connection(self.model.connection)
self.refresh()
def check_source(self):
qmessagebox = QMessageBox(self.view)
qmessagebox.setText("The source has been checked.")
qmessagebox.exec()
from hashlib import sha256
import jsonschema
import attr
import logging
......@@ -15,12 +13,10 @@ from duniterpy.documents import (
SIGParameter,
Unlock,
block_uid,
BlockUID,
)
from duniterpy.documents import Identity as IdentityDoc
from duniterpy.documents import Transaction as TransactionDoc
from duniterpy.documents.transaction import reduce_base
from duniterpy.grammars.output import Condition, Operator, SIG, CSV, CLTV, XHX
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 (
......@@ -335,7 +331,7 @@ 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.evaluate_condition(
if not self._sources_services.evaluate_condition(
currency, condition, [key], [], s.identifier
):
continue
......@@ -644,97 +640,3 @@ class DocumentsService:
return result
except NotEnoughChangeError as e:
return (False, str(e)), tx_entities
def evaluate_condition(
self,
currency,
condition: Condition,
keys: list,
passwords,
identifier: str,
result: bool = True,
) -> bool:
"""
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 str identifier: Source transaction identifier
:param bool result: result accumulator
:return:
"""
left = False
right = False
# if left param is a condition...
if isinstance(condition.left, Condition):
# evaluate condition
left = self.evaluate_condition(
currency, condition.left, keys, passwords, identifier, result
)
# if right param is a condition...
if isinstance(condition.right, Condition):
# evaluate condition
right = self.evaluate_condition(
currency, condition.right, keys, passwords, identifier, result
)
# 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 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)
if tx:
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
# param is true if tx time + CSV delay <= blockchain time
left = tx.timestamp + int(condition.left.time) <= median_time
# if left param is a CLTV value...
if isinstance(condition.left, CLTV):
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
# param is true if CL:TV value <= blockchain time
left = int(condition.left.timestamp) <= median_time
# 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
]
# if no op then stop evaluation...
if not condition.op:
return left
# 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 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)
if tx:
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
# param is true if tx time + CSV delay <= blockchain time
right = tx.timestamp + int(condition.right.time) <= median_time
# 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
# if right param is a XHX value...
if isinstance(condition.right, XHX):
right = condition.right.sha_hash in [
sha256(password).upper() for password in passwords
]
# if operator AND...
if condition.op == "&&":
return left & right
# operator OR
return left | right
from hashlib import sha256
from PyQt5.QtCore import QObject
from duniterpy.api import bma, errors
from duniterpy.documents import Transaction as TransactionDoc
from duniterpy.grammars.output import Condition, SIG
from duniterpy.grammars.output import Condition, SIG, CSV, CLTV, XHX
from duniterpy.documents import BlockUID
import logging
import pypeg2
......@@ -263,3 +265,97 @@ class SourcesServices(QObject):
self._sources_processor.insert(entity)
except AttributeError as e:
self._logger.error(str(e))
def evaluate_condition(
self,
currency,
condition: Condition,
keys: list,
passwords,
identifier: str,
result: bool = True,
) -> bool:
"""
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 str identifier: Source transaction identifier
:param bool result: result accumulator
:return:
"""
left = False
right = False
# if left param is a condition...
if isinstance(condition.left, Condition):
# evaluate condition
left = self.evaluate_condition(
currency, condition.left, keys, passwords, identifier, result
)
# if right param is a condition...
if isinstance(condition.right, Condition):
# evaluate condition
right = self.evaluate_condition(
currency, condition.right, keys, passwords, identifier, result
)
# 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 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)
if tx:
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
# param is true if tx time + CSV delay <= blockchain time
left = tx.timestamp + int(condition.left.time) <= median_time
# if left param is a CLTV value...
if isinstance(condition.left, CLTV):
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
# param is true if CL:TV value <= blockchain time
left = int(condition.left.timestamp) <= median_time
# 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
]
# if no op then stop evaluation...
if not condition.op:
return left
# 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 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)
if tx:
# capture current blockchain time
median_time = self._blockchain_processor.time(currency)
# param is true if tx time + CSV delay <= blockchain time
right = tx.timestamp + int(condition.right.time) <= median_time
# 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
# 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
]
# if operator AND...
if condition.op == "&&":
return left & right
# operator OR
return left | right
......@@ -44,6 +44,7 @@ async def test_send_more_than_40_sources(
0,
"Test comment",
0,
None,
)
assert transactions[0].comment == "[CHAINED]"
assert transactions[1].comment == "Test comment"
......
......@@ -83,7 +83,7 @@ async def test_send_tx_then_cancel(
)
fake_server_with_blockchain.reject_next_post = True
await application_with_one_connection.documents_service.send_money(
bob_connection, bob.salt, bob.password, alice.key.pubkey, 10, 0, None, 0
bob_connection, bob.salt, bob.password, alice.key.pubkey, 10, 0, None, 0, None
)
tx_after_send = application_with_one_connection.transactions_service.transfers(
bob.key.pubkey
......
from hashlib import sha256
import pypeg2
import pytest
from duniterpy.grammars import output
from duniterpy.grammars.output import Condition
from sakia.data.entities import Transaction, Source
from sakia.data.repositories import TransactionsRepo, SourcesRepo
from sakia.data.entities import Transaction
from sakia.data.repositories import TransactionsRepo
@pytest.mark.asyncio
......@@ -34,7 +32,7 @@ async def test_lock_mode_0(application_with_one_connection, fake_server, bob, al
_,
sakia_tx_list,
) = await application_with_one_connection.documents_service.send_money(
bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 0
bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 0, None
)
assert len(sakia_tx_list) == 1
......@@ -69,7 +67,7 @@ async def test_lock_mode_1(application_with_one_connection, fake_server, bob, al
_,
sakia_tx_list,
) = await application_with_one_connection.documents_service.send_money(
bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 1
bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 1, None
)
assert len(sakia_tx_list) == 1
......@@ -132,14 +130,14 @@ def test_evaluate_condition_source_lock_mode_0(
condition = output.Condition.token(output.SIG.token(bob.key.pubkey),)
# bob can spend this source
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency, condition, [bob.key], [], tx_hash
)
is True
)
# alice can not
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
condition,
[alice.key],
......@@ -212,14 +210,14 @@ def test_evaluate_condition_source_lock_mode_1(
)
# bob try to spend his source
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency, condition, [bob.key], [], tx_hash
)
is True
)
# alice try to get back this source before the CSV delay
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
condition,
[alice.key],
......@@ -271,14 +269,14 @@ def test_evaluate_condition_source_lock_mode_1(
# bob try to spend his source
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency, condition, [bob.key], [], tx_hash
)
is True
)
# alice can get back this source after the CSV delay
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
condition,
[alice.key],
......@@ -347,14 +345,14 @@ def test_evaluate_condition_source_multisig(
)
# bob can not spend this source alone
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency, condition, [bob.key], [], tx_hash
)
is False
)
# alice can not spend this source alone
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
condition,
[alice.key],
......@@ -365,7 +363,7 @@ def test_evaluate_condition_source_multisig(
)
# alice && bob together only can spend this source
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
condition,
[alice.key, bob.key],
......@@ -519,7 +517,7 @@ def test_evaluate_condition_source_atomic_swap(
# alice spend the source from tx2 with the password
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx2_condition,
[alice.key],
......@@ -532,7 +530,7 @@ def test_evaluate_condition_source_atomic_swap(
# the password is revealed in the unlock of the tx3 spending tx2
# bob can now spend tx1 using the password
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx1_condition,
[bob.key],
......@@ -544,7 +542,7 @@ def test_evaluate_condition_source_atomic_swap(
# alice and bob can sign together to spend tx1
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx1_condition,
[bob.key, alice.key],
......@@ -556,7 +554,7 @@ def test_evaluate_condition_source_atomic_swap(
# alice and bob can sign together to spend tx2
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx2_condition,
[bob.key, alice.key],
......@@ -568,7 +566,7 @@ def test_evaluate_condition_source_atomic_swap(
# alice can not spend the source from tx2 without the password
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx2_condition,
[alice.key],
......@@ -580,7 +578,7 @@ def test_evaluate_condition_source_atomic_swap(
# bob can not spend tx1 without the password
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx1_condition,
[bob.key],
......@@ -672,7 +670,7 @@ def test_evaluate_condition_source_atomic_swap(
# alice can get back the source from tx1 without the password after 48h
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx1_condition,
[alice.key],
......@@ -684,7 +682,7 @@ def test_evaluate_condition_source_atomic_swap(
# bob can spend tx2 without the password after 24h
assert (
application_with_one_connection.documents_service.evaluate_condition(
application_with_one_connection.sources_service.evaluate_condition(
application_with_one_connection.currency,
tx2_condition,
[bob.key],
......
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