diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a9c77b09f9657625994ae5354d04a5b8837b6f0..8b880b7144c75c9830863559d9ccb04ff086609d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## v0.54.3 (29th May 2019) +- Upload again to PyPi as previous release haven’t been uploaded thanks to the tag +- Transaction: fix `time` type + +## v0.54.2 (27th May 2019) +- fix Transaction document generation +- lock transaction document generation with a test + +## v0.54.1 (9th May 2019) +- `Transaction`: add __eq__() and __hash__() methods +- Transaction Unlock parameters: add __eq__() and __hash__() methods +- Transaction: add 'time' variable for read and write but not for doc generation +- output conditions: add __eq__() and __hash__() methods +- test transaction equality at all levels + +--- + +- Thanks @Moul, @vtexier + ## v0.54.0 (5th May 2019) ### Code/tests @@ -16,6 +35,8 @@ - setup.py: add classifiers: Python versions, Intended Audience - Add CHANGELOG.md from v0.53.1 +--- + - Thanks @Moul, @vtexier ## v0.53.1 (18 April 2019) diff --git a/docs/conf.py b/docs/conf.py index 2dc8df5a8aa38e8e307ce854c27228f6962ca4a7..de0cd0369be407c984d536a56a597a2ab6e8ee50 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,9 +72,9 @@ author = 'caner & inso & vit' # built documents. # # The short X.Y version. -version = '0.54.0' +version = '0.54.3' # The full version, including alpha/beta/rc tags. -release = '0.54.0' +release = '0.54.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/duniterpy/__init__.py b/duniterpy/__init__.py index ce53672d2fc36a4b80a67be14b51fe580d31ca40..c4b422a824c33ef9d9e2298ace3b014cda202bcd 100644 --- a/duniterpy/__init__.py +++ b/duniterpy/__init__.py @@ -18,7 +18,7 @@ __author__ = 'Caner Candan & inso & vit' -__version__ = '0.54.0' +__version__ = '0.54.3' __nonsense__ = 'duniter' from . import api, documents, key diff --git a/duniterpy/documents/transaction.py b/duniterpy/documents/transaction.py index 5545d16b7994dd597ced99a4c6dbaaeefd9bb01c..95417e49d388c9952c6902e2d7264dc82458746a 100644 --- a/duniterpy/documents/transaction.py +++ b/duniterpy/documents/transaction.py @@ -230,6 +230,19 @@ class SIGParameter: """ self.index = index + + def __eq__(self, other: Any) -> bool: + """ + Check SIGParameter instances equality + """ + if not isinstance(other, SIGParameter): + return NotImplemented + return self.index == other.index + + def __hash__(self) -> int: + return hash((self.index)) + + @classmethod def from_parameter(cls: Type[SIGParameterType], parameter: str) -> Optional[SIGParameterType]: """ @@ -272,6 +285,19 @@ class XHXParameter: """ self.integer = integer + + def __eq__(self, other: Any) -> bool: + """ + Check XHXParameter instances equality + """ + if not isinstance(other, XHXParameter): + return NotImplemented + return self.integer == other.integer + + def __hash__(self) -> int: + return hash((self.integer)) + + @classmethod def from_parameter(cls: Type[XHXParameterType], parameter: str) -> Optional[XHXParameterType]: """ @@ -348,6 +374,24 @@ class Unlock: self.index = index self.parameters = parameters + + def __eq__(self, other: Any) -> bool: + """ + Check Unlock instances equality + """ + if not isinstance(other, Unlock): + return NotImplemented + + params_equals = True + for spar, opar in zip(self.parameters, other.parameters): + if spar != opar: + params_equals = False + return self.index == other.index and params_equals + + def __hash__(self) -> int: + return hash((self.index, self.parameters)) + + @classmethod def from_inline(cls: Type[UnlockType], inline: str) -> UnlockType: """ @@ -448,7 +492,7 @@ class Transaction(Document): def __init__(self, version: int, currency: str, blockstamp: Optional[BlockUID], locktime: int, issuers: List[str], inputs: List[InputSource], unlocks: List[Unlock], outputs: List[OutputSource], - comment: str, signatures: List[str]) -> None: + comment: str, signatures: List[str], time: Optional[int] = None) -> None: """ Init Transaction instance @@ -461,6 +505,7 @@ class Transaction(Document): :param unlocks: List of Unlock instances :param outputs: List of OutputSource instances :param comment: Comment field + :param time: time when the transaction enters the blockchain :param signatures: List of signatures """ super().__init__(version, currency, signatures) @@ -471,6 +516,31 @@ class Transaction(Document): self.unlocks = unlocks self.outputs = outputs self.comment = comment + self.time = time + + + def __eq__(self, other: Any) -> bool: + """ + Check Transaction instances equality + """ + if not isinstance(other, Transaction): + return NotImplemented + return self.version == other.version and \ + self.currency == other.currency and \ + self.signatures == other.signatures and \ + self.blockstamp == other.blockstamp and \ + self.locktime == other.locktime and \ + self.issuers == other.issuers and \ + self.inputs == other.inputs and \ + self.unlocks == other.unlocks and \ + self.outputs == other.outputs and \ + self.comment == other.comment and \ + self.time == other.time + + + def __hash__(self) -> int: + return hash((self.version, self.currency, self.signatures, self.blockstamp, self.locktime, self.issuers, self.inputs, self.unlocks, self.outputs, self.comment, self.time)) + @classmethod def from_bma_history(cls: Type[TransactionType], currency: str, tx_data: Dict) -> TransactionType: @@ -501,7 +571,7 @@ Outputs: {multiline_outputs} Comment: {comment} {multiline_signatures} -""".format(**tx_data)) +""".format(**tx_data), tx_data["time"]) @classmethod def from_compact(cls: Type[TransactionType], currency: str, compact: str) -> TransactionType: @@ -575,11 +645,12 @@ Comment: {comment} return cls(version, currency, blockstamp, locktime, issuers, inputs, unlocks, outputs, comment, signatures) @classmethod - def from_signed_raw(cls: Type[TransactionType], raw: str) -> TransactionType: + def from_signed_raw(cls: Type[TransactionType], raw: str, time: int = 0) -> TransactionType: """ Return a Transaction instance from a raw string format :param raw: Raw string format + :param time: time when the transaction enters the blockchain :return: """ @@ -645,7 +716,7 @@ Comment: {comment} n += 1 return cls(version, currency, blockstamp, locktime, issuers, inputs, unlocks, outputs, - comment, signatures) + comment, signatures, time) def raw(self) -> str: """ @@ -732,7 +803,7 @@ class SimpleTransaction(Transaction): def __init__(self, version: int, currency: str, blockstamp: BlockUID, locktime: int, issuer: str, single_input: InputSource, unlocks: List[Unlock], outputs: List[OutputSource], comment: str, - signature: str) -> None: + signature: str, time: int) -> None: """ Init instance @@ -745,10 +816,11 @@ class SimpleTransaction(Transaction): :param unlocks: List of Unlock instances :param outputs: List of OutputSource instances :param comment: Comment field + :param time: time when the transaction enters the blockchain :param signature: Signature """ super().__init__(version, currency, blockstamp, locktime, [issuer], [single_input], unlocks, - outputs, comment, [signature]) + outputs, comment, [signature], time) @staticmethod def is_simple(tx: Transaction) -> bool: diff --git a/duniterpy/grammars/output.py b/duniterpy/grammars/output.py index c180f5307e809cd96d8833cc0078245c64180715..ef4b7f5bfc0985271e13ce5fad048ea3d91baaef 100644 --- a/duniterpy/grammars/output.py +++ b/duniterpy/grammars/output.py @@ -48,6 +48,19 @@ class SIG: def __str__(self) -> str: return self.value + def __eq__(self, other: Any) -> bool: + """ + Check SIG instances equality + """ + if not isinstance(other, SIG): + return NotImplemented + return self.value == other.value and \ + self.pubkey == other.pubkey + + def __hash__(self) -> int: + return hash((self.value, self.pubkey)) + + @classmethod def token(cls: Type[SIGType], pubkey: str) -> SIGType: """ @@ -94,6 +107,19 @@ class CSV: def __str__(self) -> str: return self.value + def __eq__(self, other: Any) -> bool: + """ + Check CSV instances equality + """ + if not isinstance(other, CSV): + return NotImplemented + return self.value == other.value and \ + self.time == other.time + + def __hash__(self) -> int: + return hash((self.value, self.time)) + + @classmethod def token(cls: Type[CSVType], time: int) -> CSVType: """ @@ -139,6 +165,19 @@ class CLTV: def __str__(self) -> str: return self.value + def __eq__(self, other: Any) -> bool: + """ + Check CLTV instances equality + """ + if not isinstance(other, CLTV): + return NotImplemented + return self.value == other.value and \ + self.timestamp == other.timestamp + + def __hash__(self) -> int: + return hash((self.value, self.timestamp)) + + @classmethod def token(cls: Type[CLTVType], timestamp: int) -> CLTVType: """ @@ -184,6 +223,19 @@ class XHX: def __str__(self) -> str: return self.value + def __eq__(self, other: Any) -> bool: + """ + Check XHX instances equality + """ + if not isinstance(other, XHX): + return NotImplemented + return self.value == other.value and \ + self.sha_hash == other.sha_hash + + def __hash__(self) -> int: + return hash((self.value, self.sha_hash)) + + @classmethod def token(cls: Type[XHXType], sha_hash: str) -> XHXType: """ @@ -262,6 +314,21 @@ class Condition: self.right = '' self.op = '' + + def __eq__(self, other: Any) -> bool: + """ + Check Condition instances equality + """ + if not isinstance(other, Condition): + return NotImplemented + return self.value == other.value and \ + self.left == other.left and \ + self.right == other.right and \ + self.op == other.op + + def __hash__(self) -> int: + return hash((self.value, self.left, self.right, self.op)) + def __str__(self) -> str: return self.value diff --git a/duniterpy/key/encryption_key.py b/duniterpy/key/encryption_key.py index 0ee82549e397d6f8b44cb79bb5f88d8ff4c75aa4..4c534a4e4a12f1d64b4e00ecc374d64abbc25538 100644 --- a/duniterpy/key/encryption_key.py +++ b/duniterpy/key/encryption_key.py @@ -25,7 +25,7 @@ class SecretKey(libnacl.public.SecretKey): :param salt: Salt credential :param password: Password credential - :param scrypt_params: Optional ScriptParams instance + :param scrypt_params: Optional ScryptParams instance """ if scrypt_params is None: scrypt_params = ScryptParams() diff --git a/tests/documents/test_transaction.py b/tests/documents/test_transaction.py index 415397522a0ff1099e03e5e1dd6f324517fb1bff..0901eb8db90115485999b3cb9426f12d1571f0f3 100644 --- a/tests/documents/test_transaction.py +++ b/tests/documents/test_transaction.py @@ -6,7 +6,8 @@ Created on 12 déc. 2014 import unittest import pypeg2 from duniterpy.grammars import output -from duniterpy.documents.transaction import Transaction, reduce_base, SimpleTransaction, InputSource, OutputSource +from duniterpy.documents import BlockUID +from duniterpy.documents.transaction import Transaction, reduce_base, SimpleTransaction, InputSource, OutputSource, Unlock, SIGParameter compact_change = """TX:10:1:1:1:1:1:0 13410-000041DF0CCA173F09B5FBA48F619D4BC934F12ADF1D0B798639EB2149C4A8CC @@ -104,6 +105,9 @@ Comment: -----@@@----- (why not this comment?) 2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk """ +input_source_str = "30:0:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2" + +output_source_str = "460:0:SIG(8kXygUHh1vLjmcRzXVM86t38EL8dfFJgfBeHmkaWLamu)" class TestTransaction(unittest.TestCase): def test_fromcompact(self): @@ -291,18 +295,59 @@ class TestTransaction(unittest.TestCase): def test_inputsource_from_inline(self): - input_source_str = "30:0:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2" i = InputSource.from_inline(input_source_str) self.assertEqual(i.inline(), input_source_str) def test_outputsource_from_inline(self): - output_source_str = "460:0:SIG(8kXygUHh1vLjmcRzXVM86t38EL8dfFJgfBeHmkaWLamu)" o = OutputSource.from_inline(output_source_str) self.assertEqual(o.inline(), output_source_str) def test_outputsource_inline_condition(self): - output_source_str = "460:0:SIG(8kXygUHh1vLjmcRzXVM86t38EL8dfFJgfBeHmkaWLamu)" o = OutputSource.from_inline(output_source_str) self.assertEqual(o.inline_condition(), output_source_str.split(":")[2]) + + + def test_transaction_equality(self): + t1 = Transaction.from_signed_raw(tx_raw) + t2 = Transaction.from_signed_raw(tx_raw) + + self.assertTrue(t1 == t2) + + t2.signatures = ["NSTN"] + self.assertFalse(t1 == t2) + + t2 = Transaction.from_signed_raw(tx_raw) + t2.issuers = ["NSTRNRST"] + self.assertFalse(t1 == t2) + + t2 = Transaction.from_signed_raw(tx_raw) + t2.time = 123 + self.assertFalse(t1 == t2) + + t2 = Transaction.from_signed_raw(tx_raw) + t2.inputs = InputSource.from_inline(input_source_str) + self.assertFalse(t1 == t2) + + t2 = Transaction.from_signed_raw(tx_raw) + t2.outputs = OutputSource.from_inline(output_source_str) + self.assertFalse(t1 == t2) + + + def test_transaction_document_generation(self): + transaction = Transaction( + version=10, + currency="gtest", + blockstamp=BlockUID(8979, "000041DF0CCA173F09B5FBA48F619D4BC934F12ADF1D0B798639EB2149C4A8CC"), + locktime=0, + issuers=list("8kXygUHh1vLjmcRzXVM86t38EL8dfFJgfBeHmkaWLamu"), + inputs=[InputSource.from_inline(input_source_str)], + unlocks=[Unlock(index=0, parameters=[SIGParameter(0)])], + outputs=[OutputSource.from_inline(output_source_str)], + comment='', + signatures=[] + ) + self.assertTrue(transaction.time == None) + self.assertTrue(transaction.currency == "gtest") + self.assertTrue(transaction.inputs[0].amount == 30)