Commit d5674c3d authored by Vincent Texier's avatar Vincent Texier

[fix] #798 add condition evaluation on automatic sources when creating a transaction

This is to avoid using locked sources

Need all the tx referenced by the sources in the db !

Fix the previous commit that add complex and potentially locked sources in db
parent e2313e7d
......@@ -111,18 +111,21 @@ class Transaction:
Transaction entity
:param str currency: the currency of the transaction
:param str pubkey: the pubkey of the issuer
:param str sha_hash: the hash of the transaction
:param int written_block: the number of the block
:param duniterpy.documents.BlockUID blockstamp: the blockstamp of the transaction
:param int timestamp: the timestamp of the transaction
:param str signature: the signature
:param str issuer: the pubkey of the issuer
:param str receiver: the pubkey of the receiver
:param str signatures: the signature
:param tuple issuers: tuple of pubkey of the issuers
:param tuple receivers: tuple of pubkey of the receivers
:param int amount: the amount
:param int amount_base: the amount base
:param str comment: a comment
:param str txid: the transaction id to sort transctions
:param int txid: the transaction id to sort transctions
:param int state: the state of the transaction
:param bool local: is the transaction local
:param str raw: the raw string of the transaction
"""
TO_SEND = 0
......
......@@ -92,7 +92,7 @@ class TransactionsProcessor:
except sqlite3.IntegrityError:
self._repo.update(tx)
def find_by_hash(self, pubkey, sha_hash):
def find_by_hash(self, pubkey: str, sha_hash: str) -> Transaction:
return self._repo.get_one(pubkey=pubkey, sha_hash=sha_hash)
def awaiting(self, currency):
......
......@@ -75,7 +75,7 @@ class TransactionsRepo:
def get_one(self, **search):
"""
Get an existing transaction in the database
:param dict search: the criterions of the lookup
:param ** search: the criterions of the lookup
:rtype: sakia.data.entities.Transaction
"""
filters = []
......
from hashlib import sha256
import jsonschema
import attr
import logging
......@@ -18,7 +20,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.output import Condition, Operator, SIG, CSV
from duniterpy.grammars.output import Condition, Operator, SIG, CSV, CLTV, XHX
from duniterpy.api import bma
from sakia.data.entities import Identity, Transaction, Source
from sakia.data.processors import (
......@@ -298,13 +300,13 @@ class DocumentsService:
return document.signed_raw(), identity
def tx_sources(self, amount, amount_base, currency, pubkey):
def tx_sources(self, amount, amount_base, currency, key: SigningKey):
"""
Get inputs to generate a transaction with a given amount of money
:param int amount: The amount target value
:param int amount_base: The amount base target value
:param str currency: The community target of the transaction
:param str pubkey: The pubkey owning the sources
:param str key: The key owning the sources
:return: The list of inputs to use in the transaction document
"""
......@@ -321,7 +323,7 @@ class DocumentsService:
return i
amount, amount_base = reduce_base(amount, amount_base)
available_sources = self._sources_processor.available(currency, pubkey)
available_sources = self._sources_processor.available(currency, key.pubkey)
if available_sources:
current_base = max([src.base for src in available_sources])
value = 0
......@@ -331,6 +333,12 @@ class DocumentsService:
buf_sources = list(available_sources)
while current_base >= 0:
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(
currency, condition, [key], [], s.identifier
):
continue
test_sources = sources + [s]
val = current_value(test_sources, overheads)
# if we have to compute an overhead
......@@ -503,7 +511,7 @@ class DocumentsService:
forged_tx = []
sources = [None] * 41
while len(sources) > 40:
result = self.tx_sources(int(amount), amount_base, currency, key.pubkey)
result = self.tx_sources(int(amount), amount_base, currency, key)
sources = result[0]
computed_outputs = result[1]
overheads = result[2]
......@@ -626,3 +634,97 @@ 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
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