diff --git a/duniterpy/documents/block.py b/duniterpy/documents/block.py index 09f2095eaec1bae47edda45f1dd5eeb004383ad1..9a01fdecf101c6dba722e2438bbaf0bb91d51d7a 100644 --- a/duniterpy/documents/block.py +++ b/duniterpy/documents/block.py @@ -194,6 +194,13 @@ The class Block handles Block documents. :param list[str] signatures: the block signaturezs """ super().__init__(version, currency, [signature]) + documents_versions = max(max([1] + [i.version for i in identities]), + max([1] + [m.version for m in actives + leavers + joiners + excluded + actives]), + max([1] + [r.version for r in revokations]), + max([1] + [c.version for c in certifications]), + max([1] + [t.version for t in transactions])) + if self.version < documents_versions: + raise MalformedDocumentError("Block version is too low : {0} < {1}".format(self.version, documents_versions)) self.number = number self.powmin = powmin self.time = time @@ -344,13 +351,19 @@ The class Block handles Block documents. header_data = Transaction.re_header.match(lines[n]) if header_data is None: raise MalformedDocumentError("Compact transaction ({0})".format(lines[n])) - version = int(header_data.group(1)) + tx_version = int(header_data.group(1)) issuers_num = int(header_data.group(2)) inputs_num = int(header_data.group(3)) unlocks_num = int(header_data.group(4)) outputs_num = int(header_data.group(5)) has_comment = int(header_data.group(6)) - tx_max = n + 1 + issuers_num * 2 + inputs_num + unlocks_num + outputs_num + has_comment + if version > 2: + if tx_version <= 2: + raise MalformedDocumentError("TX document is using wrong version") + sup_lines = 2 + else: + sup_lines = 1 + tx_max = n + sup_lines + issuers_num * 2 + inputs_num + unlocks_num + outputs_num + has_comment for i in range(n, tx_max): tx_lines += lines[n] n += 1 diff --git a/duniterpy/documents/transaction.py b/duniterpy/documents/transaction.py index 4bb3391a1c25fc0dd141e970aa2b6266d506c0a8..c882d5e670d891f95ddb83f90584141380d9188d 100644 --- a/duniterpy/documents/transaction.py +++ b/duniterpy/documents/transaction.py @@ -1,5 +1,5 @@ from .document import Document, MalformedDocumentError -from .constants import pubkey_regex, transaction_hash_regex, block_id_regex +from .constants import pubkey_regex, transaction_hash_regex, block_id_regex, block_uid_regex from ..grammars import output import pypeg2 import re @@ -63,6 +63,8 @@ class Transaction(Document): re_type = re.compile("Type: (Transaction)\n") re_header = re.compile("TX:([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):(0|1):([0-9]+)\n") + re_compact_blockstamp = re.compile("({block_uid_regex})\n".format(block_uid_regex=block_uid_regex)) + re_blockstamp = re.compile("Blockstamp: ({block_uid_regex})\n".format(block_uid_regex=block_uid_regex)) re_locktime = re.compile("Locktime: ([0-9]+)\n") re_issuers = re.compile("Issuers:\n") re_inputs = re.compile("Inputs:\n") @@ -74,6 +76,8 @@ class Transaction(Document): fields_parsers = {**Document.fields_parsers, **{ "Type": re_type, + "Blockstamp": re_blockstamp, + "CompactBlockstamp": re_compact_blockstamp, "Locktime": re_locktime, "TX": re_header, "Issuers": re_issuers, @@ -86,13 +90,23 @@ class Transaction(Document): } } - def __init__(self, version, currency, locktime, issuers, inputs, unlocks, outputs, + def __init__(self, version, currency, blockstamp, locktime, issuers, inputs, unlocks, outputs, comment, signatures): """ - Constructor + + :param int version: + :param str currency: + :param BlockUID blockstamp: + :param int locktime: + :param str issuers: + :param inputs: + :param unlocks: + :param outputs: + :param comment: + :param signatures: """ super().__init__(version, currency, signatures) - + self.blockstamp = blockstamp self.locktime = locktime self.issuers = issuers self.inputs = inputs @@ -102,6 +116,7 @@ class Transaction(Document): @classmethod def from_compact(cls, currency, compact): + from .block import BlockUID lines = compact.splitlines(True) n = 0 @@ -117,6 +132,12 @@ class Transaction(Document): locktime = int(header_data.group(7)) n += 1 + if version >= 3: + blockstamp = BlockUID.from_str(Transaction.parse_field("CompactBlockstamp", lines[n])) + n += 1 + else: + blockstamp = None + issuers = [] inputs = [] unlocks = [] @@ -128,7 +149,7 @@ class Transaction(Document): n += 1 for i in range(0, inputs_num): - input_source = InputSource.from_inline(lines[n]) + input_source = InputSource.from_inline(version, lines[n]) inputs.append(input_source) n += 1 @@ -157,10 +178,11 @@ class Transaction(Document): else: raise MalformedDocumentError("Compact TX Signatures") - return cls(version, currency, locktime, issuers, inputs, unlocks, outputs, comment, signatures) + return cls(version, currency, blockstamp, locktime, issuers, inputs, unlocks, outputs, comment, signatures) @classmethod def from_signed_raw(cls, raw): + from .block import BlockUID lines = raw.splitlines(True) n = 0 @@ -173,6 +195,12 @@ class Transaction(Document): currency = Transaction.parse_field("Currency", lines[n]) n += 1 + if version >= 3: + blockstamp = BlockUID.from_str(Transaction.parse_field("Blockstamp", lines[n])) + n += 1 + else: + blockstamp = None + locktime = Transaction.parse_field("Locktime", lines[n]) n += 1 @@ -192,7 +220,7 @@ class Transaction(Document): if Transaction.re_inputs.match(lines[n]): n += 1 while Transaction.re_unlocks.match(lines[n]) is None: - input_source = InputSource.from_inline(lines[n]) + input_source = InputSource.from_inline(version, lines[n]) inputs.append(input_source) n += 1 @@ -219,25 +247,28 @@ class Transaction(Document): signatures.append(sign) n += 1 - return cls(version, currency, locktime, issuers, inputs, unlocks, outputs, + return cls(version, currency, blockstamp, locktime, issuers, inputs, unlocks, outputs, comment, signatures) def raw(self): doc = """Version: {0} Type: Transaction Currency: {1} -Locktime: {2} -Issuers: """.format(self.version, - self.currency, - self.locktime) + self.currency) + if self.version >= 3: + doc += "Blockstamp: {0}\n".format(self.blockstamp) + + doc += "Locktime: {0}\n".format(self.locktime) + + doc += "Issuers:\n" for p in self.issuers: doc += "{0}\n".format(p) doc += "Inputs:\n" for i in self.inputs: - doc += "{0}\n".format(i.inline()) + doc += "{0}\n".format(i.inline(self.version)) doc += "Unlocks:\n" for u in self.unlocks: @@ -272,10 +303,13 @@ COMMENT len(self.outputs), '1' if self.comment != "" else '0', self.locktime) + if self.version >= 3: + doc += "{0}\n".format(self.blockstamp) + for pubkey in self.issuers: doc += "{0}\n".format(pubkey) for i in self.inputs: - doc += "{0}\n".format(i.inline()) + doc += "{0}\n".format(i.inline(self.version)) for u in self.unlocks: doc += "{0}\n".format(u.inline()) for o in self.outputs: @@ -340,40 +374,66 @@ class InputSource: .format(pubkey_regex=pubkey_regex, block_id_regex=block_id_regex, transaction_hash_regex=transaction_hash_regex)) - re_compact = re.compile("([0-9]+):(D|T):([0-9a-fA-F]{5,40}):([0-9]+)\n") + re_inline_v3 = re.compile("([0-9]+):([0-9]+):(?:(?:(D):({pubkey_regex}):({block_id_regex}))|(?:(T):({transaction_hash_regex}):([0-9]+)))\n" + .format(pubkey_regex=pubkey_regex, + block_id_regex=block_id_regex, + transaction_hash_regex=transaction_hash_regex)) - def __init__(self, source, origin_id, index): + def __init__(self, amount, base, source, origin_id, index): """ An input source can come from a dividend or a transaction. + :param int amount: amount of the input + :param int base: base of the input :param str source: D if dividend, T if transaction :param str origin_id: a Public key if a dividend, a tx hash if a transaction :param int index: a block id if a dividend, an tx index if a transaction :return: """ + self.amount = amount + self.base = base self.source = source self.origin_id = origin_id self.index = index @classmethod - def from_inline(cls, inline): - data = InputSource.re_inline.match(inline) - if data is None: - raise MalformedDocumentError("Inline input") - if data.group(1): - source = data.group(1) - origin_id = data.group(2) - index = int(data.group(3)) + def from_inline(cls, tx_version, inline): + if tx_version == 2: + data = InputSource.re_inline.match(inline) + if data is None: + raise MalformedDocumentError("Inline input") + source_offset = 0 + amount = 0 + base = 0 + else: + data = InputSource.re_inline_v3.match(inline) + if data is None: + raise MalformedDocumentError("Inline input") + source_offset = 2 + amount = data.group(1) + base = data.group(2) + if data.group(1 + source_offset): + source = data.group(1 + source_offset) + origin_id = data.group(2 + source_offset) + index = int(data.group(3 + source_offset)) else: - source = data.group(4) - origin_id = data.group(5) - index = int(data.group(6)) - return cls(source, origin_id, index) + source = data.group(4 + source_offset) + origin_id = data.group(5 + source_offset) + index = int(data.group(6 + source_offset)) - def inline(self): - return "{0}:{1}:{2}".format(self.source, - self.origin_id, - self.index) + return cls(amount, base, source, origin_id, index) + + def inline(self, tx_version): + if tx_version == 2: + return "{0}:{1}:{2}".format(self.source, + self.origin_id, + self.index) + else: + return "{0}:{1}:{2}:{3}:{4}".format(self.amount, + self.base, + self.source, + self.origin_id, + self.index) class UnlockParameter: diff --git a/tests/documents/test_block.py b/tests/documents/test_block.py index 99533d89f813e63e5de5b6ea0c4d8bd3245eb2dc..2442f754fecbefb6d2bccb6429b960341b3d56ef 100644 --- a/tests/documents/test_block.py +++ b/tests/documents/test_block.py @@ -160,6 +160,68 @@ Nonce: 9906 5LZCFSnm5FkFihPBTpmsPyILEdvu8MXfJOp6OR4d1s+/e2jVWg4J6YSDfO2KBBPgubASyr2QwQuiBlYD2918Bw== """ +raw_block_with_tx_v3 = """Version: 3 +Type: Block +Currency: meta_brouzouf +Number: 34436 +PoWMin: 5 +Time: 1443896211 +MedianTime: 1443881811 +Issuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk +PreviousHash: 000002B06C990DEBD5C1D947289C2CF4F4396FB2 +PreviousIssuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk +MembersCount: 19 +Identities: +Joiners: +Actives: +ATkjQPa4sn4LBF69jqEPzFtRdHYJs6MJQjvP8JdN7MtN:QTowsupV+uXrcomL44WCxbu3LQoJM2C2VPMet5Xg6gXGAHEtGRp47FfQLb2ok1+/588JiIHskCyazj3UOsmKDw==:34434-00000D21F80687248A8C02F16BB19A975B4F983D:34432-00000D21F80687248A8C02F16BB19A975B4F983D:urodelus +Leavers: +Revoked: +Excluded: +Certifications: +5ocqzyDMMWf1V8bsoNhWb1iNwax1e9M7VTUN6navs8of:ATkjQPa4sn4LBF69jqEPzFtRdHYJs6MJQjvP8JdN7MtN:0:6TuxRcARnpo13l3cXtgPTkjJlv8DZOUvsAzmZJMbjHZbbZfDQ6MJpH9DIuH0eyG3WGc0EX/046mbMGBrKKg9DQ== +ATkjQPa4sn4LBF69jqEPzFtRdHYJs6MJQjvP8JdN7MtN:2qwGeLWoPG7db72bKXPfJpAtj67FYDnPaJn2JB7tyXxJ:0:LusTbb7CgwrqqacDKjtldw60swwvDBH8wVUIJN4SWRb2pZPJSpDxgqaGyjC5P9i/DendfyQWc7cfzPDqSZmZAg== +Transactions: +TX:3:1:3:3:1:0:0 +32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD +HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY +5:0:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0 +1:1:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:10 +35:0:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:88 +0:SIG(0) +1:SIG(0) +2:SIG(0) +30:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g) +42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r +TX:3:3:6:6:3:1:0 +3-DB30D958EE5CB75186972286ED3F4686B8A1C2CD +HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY +CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp +9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB +30:0:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2 +25:0:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8 +5:1:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:46 +10:1:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3 +60:0:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5 +50:0:D:9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:46 +0:SIG(0) +1:XHX(7665798292) +2:SIG(0) +3:SIG(0) SIG(2) +4:SIG(0) SIG(1) SIG(2) +5:SIG(2) +120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g) +146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx) +49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) OR XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85)) +-----@@@----- (why not this comment?) +42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r +2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX +2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk +InnerHash: DB30D958EE5CB75186972286ED3F4686B8A1C2CD +Nonce: 581 +nY/MsFU2luiohLmSiOOimL1RIqbriOBgc22ua03Z2dhxtSJxKZeGNGDvl1jaXgmEBRnXU87yXbZ7ioOS/AAVCA== +""" + class Test_Block(unittest.TestCase): def test_fromraw(self): @@ -282,6 +344,31 @@ class Test_Block(unittest.TestCase): self.assertEqual(block.signed_raw(), raw_block_with_tx) + def test_raw_with_tx_v3(self): + block = Block.from_signed_raw(raw_block_with_tx_v3) + rendered_raw = block.signed_raw() + from_rendered_raw = block.from_signed_raw(rendered_raw) + + self.assertEqual(from_rendered_raw.version, 3) + self.assertEqual(from_rendered_raw.currency, "meta_brouzouf") + self.assertEqual(from_rendered_raw.noonce, 581) + self.assertEqual(from_rendered_raw.number, 34436) + self.assertEqual(from_rendered_raw.powmin, 5) + self.assertEqual(from_rendered_raw.time, 1443896211) + self.assertEqual(from_rendered_raw.mediantime, 1443881811) + self.assertEqual(from_rendered_raw.issuer, "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk") + self.assertEqual(from_rendered_raw.parameters, None) + self.assertEqual(from_rendered_raw.members_count, 19) + self.assertEqual(from_rendered_raw.identities, []) + self.assertEqual(from_rendered_raw.joiners, []) + self.assertEqual(len(from_rendered_raw.actives), 1) + self.assertEqual(from_rendered_raw.leavers, []) + self.assertEqual(from_rendered_raw.excluded, []) + self.assertEqual(len(from_rendered_raw.certifications), 2) + self.assertEqual(len(from_rendered_raw.transactions), 2) + + self.assertEqual(block.signed_raw(), raw_block_with_tx_v3) + def test_raw_with_leavers(self): block = Block.from_signed_raw(raw_block_with_leavers) rendered_raw = block.signed_raw() diff --git a/tests/documents/test_transaction.py b/tests/documents/test_transaction.py index a80cbc3553b5ab1d95bdf2461412845267131515..24246811b86b4df7bc74b78d6262515c2da111c3 100644 --- a/tests/documents/test_transaction.py +++ b/tests/documents/test_transaction.py @@ -43,6 +43,32 @@ D:GNPdPNwSJAYw7ixkDeibo3YpdELgLmrZ2Q86HF4cyg92:471 XDQeEMcJDd+XVGaFIZc8d4kKRJgsPuWAPVNG5UKNk8mDZx2oE1kTP/hbxiFx6yDouBELCswuf/X6POK9ES7JCA== """ +tx_compact_v3 = """TX:3:3:6:6:3:1:0 +32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD +HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY +CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp +9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB +30:0:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2 +25:0:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8 +5:1:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:46 +10:1:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3 +60:0:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5 +50:0:D:9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:46 +0:SIG(0) +1:XHX(7665798292) +2:SIG(0) +3:SIG(0) SIG(2) +4:SIG(0) SIG(1) SIG(2) +5:SIG(2) +120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g) +146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx) +49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) OR XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85)) +-----@@@----- (why not this comment?) +42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r +2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX +2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk +""" + tx_raw = """Version: 2 Type: Transaction Currency: beta_brousouf @@ -75,6 +101,38 @@ Comment: -----@@@----- (why not this comment?) 2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk """ +tx_raw_v3 = """Version: 3 +Type: Transaction +Currency: beta_brousouf +Blockstamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD +Locktime: 0 +Issuers: +HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY +CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp +9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB +Inputs: +30:0:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2 +25:0:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8 +5:1:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:46 +10:1:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3 +60:0:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5 +50:0:D:9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:46 +Unlocks: +0:SIG(0) +1:XHX(7665798292) +2:SIG(0) +3:SIG(0) SIG(2) +4:SIG(0) SIG(1) SIG(2) +5:SIG(2) +Outputs: +120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g) +146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx) +49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) OR XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85)) +Comment: -----@@@----- (why not this comment?) +42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r +2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX +2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk +""" class Test_Transaction(unittest.TestCase): def test_fromcompact(self): @@ -169,6 +227,72 @@ class Test_Transaction(unittest.TestCase): self.assertEqual(type(tx.outputs[1].conditions.left), output.SIG) self.assertEqual(tx.signatures[0], "XDQeEMcJDd+XVGaFIZc8d4kKRJgsPuWAPVNG5UKNk8mDZx2oE1kTP/hbxiFx6yDouBELCswuf/X6POK9ES7JCA==") + def test_fromcompact_v3(self): + tx = Transaction.from_compact("zeta_brousouf", tx_compact_v3) + self.assertEqual(tx.version, 3) + self.assertEqual(tx.currency, "zeta_brousouf") + self.assertEqual(tx.blockstamp.number, 32) + self.assertEqual(tx.blockstamp.sha_hash, "DB30D958EE5CB75186972286ED3F4686B8A1C2CD") + self.assertEqual(len(tx.issuers), 3) + self.assertEqual(len(tx.inputs), 6) + self.assertEqual(len(tx.unlocks), 6) + self.assertEqual(len(tx.outputs), 3) + + self.assertEqual(tx.issuers[0], "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY") + self.assertEqual(tx.issuers[1], "CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp") + self.assertEqual(tx.issuers[2], "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB") + + self.assertEqual(tx.inputs[0].source, 'T') + self.assertEqual(tx.inputs[0].origin_id, "6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3") + self.assertEqual(tx.inputs[0].index, 2) + self.assertEqual(tx.inputs[1].source, 'T') + self.assertEqual(tx.inputs[1].origin_id, "3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435") + self.assertEqual(tx.inputs[1].index, 8) + self.assertEqual(tx.inputs[2].source, 'D') + self.assertEqual(tx.inputs[2].origin_id, "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY") + self.assertEqual(tx.inputs[2].index, 46) + self.assertEqual(tx.inputs[3].source, 'T') + self.assertEqual(tx.inputs[3].origin_id, "A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956") + self.assertEqual(tx.inputs[3].index, 3) + self.assertEqual(tx.inputs[4].source, 'T') + self.assertEqual(tx.inputs[4].origin_id, "67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B") + self.assertEqual(tx.inputs[4].index, 5) + self.assertEqual(tx.inputs[5].source, 'D') + self.assertEqual(tx.inputs[5].origin_id, "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB") + self.assertEqual(tx.inputs[5].index, 46) + + self.assertEqual(tx.unlocks[0].index, 0) + self.assertEqual(str(tx.unlocks[0].parameters[0]), "SIG(0)") + self.assertEqual(tx.unlocks[1].index, 1) + self.assertEqual(str(tx.unlocks[1].parameters[0]), "XHX(7665798292)") + self.assertEqual(tx.unlocks[2].index, 2) + self.assertEqual(str(tx.unlocks[2].parameters[0]), "SIG(0)") + self.assertEqual(tx.unlocks[3].index, 3) + self.assertEqual(str(tx.unlocks[3].parameters[0]), "SIG(0)") + self.assertEqual(str(tx.unlocks[3].parameters[1]), "SIG(2)") + self.assertEqual(tx.unlocks[4].index, 4) + self.assertEqual(str(tx.unlocks[4].parameters[0]), "SIG(0)") + self.assertEqual(str(tx.unlocks[4].parameters[1]), "SIG(1)") + self.assertEqual(str(tx.unlocks[4].parameters[2]), "SIG(2)") + self.assertEqual(tx.unlocks[5].index, 5) + self.assertEqual(str(tx.unlocks[5].parameters[0]), "SIG(2)") + + self.assertEqual(tx.outputs[0].amount, 120) + self.assertEqual(tx.outputs[0].base, 2) + self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)") + self.assertEqual(tx.outputs[1].amount, 146) + self.assertEqual(tx.outputs[1].base, 2) + self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)") + self.assertEqual(tx.outputs[2].amount, 49) + self.assertEqual(tx.outputs[2].base, 2) + self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) OR XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85))") + + self.assertEqual(tx.comment, "-----@@@----- (why not this comment?)") + + self.assertEqual(tx.signatures[0], "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r") + self.assertEqual(tx.signatures[1], "2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX") + self.assertEqual(tx.signatures[2], "2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk") + def test_fromraw(self): tx = Transaction.from_signed_raw(tx_raw) self.assertEqual(tx.version, 2) @@ -300,6 +424,75 @@ class Test_Transaction(unittest.TestCase): self.assertEqual(tx.signatures[1], "2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX") self.assertEqual(tx.signatures[2], "2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk") + def test_fromraw_toraw_v3(self): + tx = Transaction.from_signed_raw(tx_raw_v3) + rendered_tx = tx.signed_raw() + from_rendered_tx = Transaction.from_signed_raw(rendered_tx) + + self.assertEqual(tx.version, 3) + self.assertEqual(tx.currency, "beta_brousouf") + self.assertEqual(tx.blockstamp.number, 32) + self.assertEqual(tx.blockstamp.sha_hash, "DB30D958EE5CB75186972286ED3F4686B8A1C2CD") + self.assertEqual(len(tx.issuers), 3) + self.assertEqual(len(tx.inputs), 6) + self.assertEqual(len(tx.unlocks), 6) + self.assertEqual(len(tx.outputs), 3) + + self.assertEqual(tx.issuers[0], "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY") + self.assertEqual(tx.issuers[1], "CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp") + self.assertEqual(tx.issuers[2], "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB") + + self.assertEqual(tx.inputs[0].source, 'T') + self.assertEqual(tx.inputs[0].origin_id, "6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3") + self.assertEqual(tx.inputs[0].index, 2) + self.assertEqual(tx.inputs[1].source, 'T') + self.assertEqual(tx.inputs[1].origin_id, "3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435") + self.assertEqual(tx.inputs[1].index, 8) + self.assertEqual(tx.inputs[2].source, 'D') + self.assertEqual(tx.inputs[2].origin_id, "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY") + self.assertEqual(tx.inputs[2].index, 46) + self.assertEqual(tx.inputs[3].source, 'T') + self.assertEqual(tx.inputs[3].origin_id, "A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956") + self.assertEqual(tx.inputs[3].index, 3) + self.assertEqual(tx.inputs[4].source, 'T') + self.assertEqual(tx.inputs[4].origin_id, "67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B") + self.assertEqual(tx.inputs[4].index, 5) + self.assertEqual(tx.inputs[5].source, 'D') + self.assertEqual(tx.inputs[5].origin_id, "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB") + self.assertEqual(tx.inputs[5].index, 46) + + self.assertEqual(tx.unlocks[0].index, 0) + self.assertEqual(str(tx.unlocks[0].parameters[0]), "SIG(0)") + self.assertEqual(tx.unlocks[1].index, 1) + self.assertEqual(str(tx.unlocks[1].parameters[0]), "XHX(7665798292)") + self.assertEqual(tx.unlocks[2].index, 2) + self.assertEqual(str(tx.unlocks[2].parameters[0]), "SIG(0)") + self.assertEqual(tx.unlocks[3].index, 3) + self.assertEqual(str(tx.unlocks[3].parameters[0]), "SIG(0)") + self.assertEqual(str(tx.unlocks[3].parameters[1]), "SIG(2)") + self.assertEqual(tx.unlocks[4].index, 4) + self.assertEqual(str(tx.unlocks[4].parameters[0]), "SIG(0)") + self.assertEqual(str(tx.unlocks[4].parameters[1]), "SIG(1)") + self.assertEqual(str(tx.unlocks[4].parameters[2]), "SIG(2)") + self.assertEqual(tx.unlocks[5].index, 5) + self.assertEqual(str(tx.unlocks[5].parameters[0]), "SIG(2)") + + self.assertEqual(tx.outputs[0].amount, 120) + self.assertEqual(tx.outputs[0].base, 2) + self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), "SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)") + self.assertEqual(tx.outputs[1].amount, 146) + self.assertEqual(tx.outputs[1].base, 2) + self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), "SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)") + self.assertEqual(tx.outputs[2].amount, 49) + self.assertEqual(tx.outputs[2].base, 2) + self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition), "(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) OR XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85))") + + self.assertEqual(tx.comment, "-----@@@----- (why not this comment?)") + + self.assertEqual(tx.signatures[0], "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r") + self.assertEqual(tx.signatures[1], "2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX") + self.assertEqual(tx.signatures[2], "2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk") + def test_reduce_base(self): amount = 1200 base = 0