From 4b0225e8d9456b0fe4f9b9b8afd9deba19cc417f Mon Sep 17 00:00:00 2001 From: Inso <insomniak.fr@gmail.com> Date: Fri, 12 Dec 2014 17:29:36 +0100 Subject: [PATCH] More tests and working on documents parsing --- _ucoinpy_test/documents/test_block.py | 1 - _ucoinpy_test/documents/test_certification.py | 17 +- _ucoinpy_test/documents/test_membership.py | 16 ++ _ucoinpy_test/documents/test_transaction.py | 48 +++++ ucoinpy/documents/__init__.py | 7 +- ucoinpy/documents/block.py | 36 ++-- ucoinpy/documents/certification.py | 24 ++- ucoinpy/documents/membership.py | 18 +- ucoinpy/documents/peer.py | 9 +- ucoinpy/documents/status.py | 11 +- ucoinpy/documents/transaction.py | 184 +++++++++++++++--- 11 files changed, 276 insertions(+), 95 deletions(-) create mode 100644 _ucoinpy_test/documents/test_membership.py create mode 100644 _ucoinpy_test/documents/test_transaction.py diff --git a/_ucoinpy_test/documents/test_block.py b/_ucoinpy_test/documents/test_block.py index 0dcc8d74..da3f788f 100644 --- a/_ucoinpy_test/documents/test_block.py +++ b/_ucoinpy_test/documents/test_block.py @@ -7,7 +7,6 @@ import pytest from ucoinpy.documents.block import Block from mock import Mock - raw_block = "Version: 1\nType: \ Block\nCurrency: zeta_brouzouf\n\ Nonce: 45079\nNumber: 15\nPoWMin: 4\n\ diff --git a/_ucoinpy_test/documents/test_certification.py b/_ucoinpy_test/documents/test_certification.py index 0d53c3b3..62499b97 100644 --- a/_ucoinpy_test/documents/test_certification.py +++ b/_ucoinpy_test/documents/test_certification.py @@ -6,20 +6,9 @@ Created on 6 déc. 2014 import pytest from ucoinpy.documents.certification import SelfCertification -from ucoinpy.key import Base58Encoder from mock import Mock -from nacl.signing import SigningKey - -uid = "lolcat" -timestamp = 1409990782.24 -correct_certification = """UID:lolcat -META:TS:1409990782 -J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci -""" - -key = SigningKey() - +inline_selfcert = "" class Test_SelfCertification: ''' @@ -27,5 +16,5 @@ class Test_SelfCertification: ''' def test_certification(self): - cert = SelfCertification(timestamp, uid) - assert cert.signed(key) == correct_certification + selfcert = SelfCertification.from_inline(version, inline_selfcert) + diff --git a/_ucoinpy_test/documents/test_membership.py b/_ucoinpy_test/documents/test_membership.py new file mode 100644 index 00000000..b9b6c022 --- /dev/null +++ b/_ucoinpy_test/documents/test_membership.py @@ -0,0 +1,16 @@ +''' +Created on 12 déc. 2014 + +@author: inso +''' +import pytest +from ucoinpy.documents.transaction import Transaction +from mock import Mock + +inline_membership = "" + +class Test_Membership: + def test_frominline(self): + membership = Membership.from_inline(inline_membership) + + \ No newline at end of file diff --git a/_ucoinpy_test/documents/test_transaction.py b/_ucoinpy_test/documents/test_transaction.py new file mode 100644 index 00000000..1b6b8951 --- /dev/null +++ b/_ucoinpy_test/documents/test_transaction.py @@ -0,0 +1,48 @@ +''' +Created on 12 déc. 2014 + +@author: inso +''' +import pytest +from ucoinpy.documents.transaction import Transaction +from mock import Mock + + +compact_transaction = """TX:1:1:3:1:0 +HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY +0:T:65:D717FEC1993554F8EAE4CEA88DE5FBB6887CFAE8:4 +0:T:77:F80993776FB55154A60B3E58910C942A347964AD:15 +0:D:88:F4A47E39BC2A20EE69DCD5CAB0A9EB3C92FD8F7B:11 +BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g:30 +42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r +""" + + +class Test_Transaction: + def test_fromraw(self): + tx = Transaction.from_compact(compact_transaction) + assert tx.version == 1 + assert tx.issuers.len == 1 + assert tx.inputs.len == 1 + assert tx.outputs.len == 3 + + assert tx.inputs[0].index == 0 + assert tx.inputs[0].source == 'T' + assert tx.inputs[0].number == 65 + assert tx.inputs[0].pubkey == "D717FEC1993554F8EAE4CEA88DE5FBB6887CFAE8" + assert tx.inputs[0].amount == 4 + + assert tx.inputs[1].index == 0 + assert tx.inputs[1].source == 'T' + assert tx.inputs[1].number == 77 + assert tx.inputs[1].pubkey == "F80993776FB55154A60B3E58910C942A347964AD" + assert tx.inputs[1].amount == 15 + + assert tx.inputs[2].index == 0 + assert tx.inputs[2].source == 'D' + assert tx.inputs[2].number == 88 + assert tx.inputs[2].pubkey == "F4A47E39BC2A20EE69DCD5CAB0A9EB3C92FD8F7B" + assert tx.inputs[2].amount == 11 + + assert tx.outputs[0].pubkey == "BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g" + assert tx.outputs[1].amount == 30 diff --git a/ucoinpy/documents/__init__.py b/ucoinpy/documents/__init__.py index 4e993849..37411ace 100644 --- a/ucoinpy/documents/__init__.py +++ b/ucoinpy/documents/__init__.py @@ -9,7 +9,10 @@ from ..key import Base58Encoder class Document: - RE_VERSION = re.compile("Version: ([0-9]+)\n") + re_version = re.compile("Version: ([0-9]+)\n") + re_currency = re.compile("Currency: ([^\n]+)\n") - def __init__(self, version): + def __init__(self, version, currency, signatures): self.version = version + self.currency = currency + self.signatures = signatures diff --git a/ucoinpy/documents/block.py b/ucoinpy/documents/block.py index 55bb26a2..34db3545 100644 --- a/ucoinpy/documents/block.py +++ b/ucoinpy/documents/block.py @@ -78,12 +78,11 @@ BOTTOM_SIGNATURE mediantime, ud, issuer, prev_hash, prev_issuer, parameters, members_count, identities, joiners, actives, leavers, excluded, certifications, - transactions, sign): + transactions, signature): ''' Constructor ''' - super(version) - self.currency = currency + super(version, currency, [signature]) self.noonce = noonce self.number = number self.powmin = powmin @@ -102,13 +101,10 @@ BOTTOM_SIGNATURE self.excluded = excluded self.certifications = certifications self.transactions = transactions - self.sign = sign @classmethod def from_raw(cls, raw): - #TODO : Parsing lines = raw.splitlines(True) - n = 0 version = Block.re_version.match(lines[n]).group(1) @@ -170,50 +166,56 @@ BOTTOM_SIGNATURE identities.append(selfcert) lines = lines + 1 - if Block.re_joiners.match(lines[n]) is not None: + if Block.re_joiners.match(lines[n]): + lines = lines + 1 while Block.re_actives.match(lines[n]) is None: membership = Membership.from_inline(version, currency, "IN", lines[n]) joiners.append(membership) lines = lines + 1 - if Block.re_actives.match(lines[n]) is not None: + if Block.re_actives.match(lines[n]): + lines = lines + 1 while Block.re_leavers.match(lines[n]) is None: membership = Membership.from_inline(version, currency, "IN", lines[n]) actives.append(membership) lines = lines + 1 - if Block.re_leavers.match(lines[n]) is not None: + if Block.re_leavers.match(lines[n]): + lines = lines + 1 while Block.re_excluded.match(lines[n]) is None: membership = Membership.from_inline(version, currency, "OUT", lines[n]) leavers.append(membership) lines = lines + 1 - if Block.re_excluded.match(lines[n]) is not None: + if Block.re_excluded.match(lines[n]): + lines = lines + 1 while Block.re_certifications.match(lines[n]) is None: membership = Membership.from_inline(version, currency, "OUT", lines[n]) excluded.append(membership) lines = lines + 1 - if Block.re_certifications.match(lines[n]) is not None: + if Block.re_certifications.match(lines[n]): + lines = lines + 1 while Block.re_transactions.match(lines[n]) is None: certification = Certification.from_inline(version, lines[n]) certifications.append(certification) lines = lines + 1 - if Block.re_transactions.match(lines[n]) is not None: - while Block.re_transactions.match(lines[n]) is None: + if Block.re_transactions.match(lines[n]): + lines = lines + 1 + while Block.re_sign.match(lines[n]) is None: transaction = Transaction.from_compact(version, lines[n]) transactions.append(transaction) lines = lines + 1 - sign = Block.re_sign.match(lines[n]) + signature = Block.re_sign.match(lines[n]) n = n + 1 return cls(version, currency, noonce, number, powmin, time, mediantime, ud, issuer, prev_hash, prev_issuer, parameters, members_count, identities, joiners, actives, leavers, excluded, certifications, - transactions, sign) + transactions, signature) def raw(self): doc = """ @@ -231,7 +233,7 @@ PreviousHash: {9} PreviousIssuer: {10} Parameters: {11} MembersCount: {12} -Identities:""".format(PROTOCOL_VERSION, +Identities:""".format(self.version, self.currency, self.noonce, self.number, @@ -272,4 +274,4 @@ Identities:""".format(PROTOCOL_VERSION, for transaction in self.transactions: doc += "{0}\n".format(transaction.inline()) - doc += self.sign + doc += self.signatures[0] diff --git a/ucoinpy/documents/certification.py b/ucoinpy/documents/certification.py index b9f821ea..66c9331e 100644 --- a/ucoinpy/documents/certification.py +++ b/ucoinpy/documents/certification.py @@ -15,21 +15,20 @@ class SelfCertification(Document): re_inline = re.compile("([1-9A-Za-z][^OIl]{43,45}):([A-Za-z0-9+/]+):([0-9]+):([^\n]+)\n") - def __init__(self, version, pubkey, ts, identifier, sign): - super(version) + def __init__(self, version, currency, pubkey, ts, identifier, signature): + super(version, currency, [signature]) self.pubkey = pubkey self.timestamp = ts self.identifier = identifier - self.sign = sign @classmethod def from_inline(cls, version, inline): selfcert_data = SelfCertification.re_inline.match(inline) pubkey = selfcert_data.group(1) - sign = selfcert_data.group(2) + signature = selfcert_data.group(2) ts = selfcert_data.group(3) identifier = selfcert_data.group(4) - return cls(version, pubkey, ts, identifier, sign) + return cls(version, pubkey, ts, identifier, signature) @classmethod def from_raw(cls, raw): @@ -43,7 +42,7 @@ class SelfCertification(Document): return "UID:{0}".format(self.identifier) def raw(self): - return "{0}\n{1}".format(self.uid(), self.ts()) + return "{0}\n{1}\n{2}".format(self.uid(), self.ts(), self.signatures[0]) class Certification(Document): @@ -55,17 +54,16 @@ class Certification(Document): ([A-Za-z0-9+/]+)(==)?:([0-9]+):([0-9a-fA-F]{5,40}):\ ([0-9]+):([^\n]+)\n") - def __init__(self, version, pubkey_from, pubkey_to, - blockhash, blocknumber, sign): + def __init__(self, version, currency, pubkey_from, pubkey_to, + blockhash, blocknumber, signature): ''' Constructor ''' - super(version) + super(version, currency, signature) self.pubkey_from = pubkey_from self.pubkey_to = pubkey_to self.blockhash = blockhash self.blocknumber = blocknumber - self.sign = sign @classmethod def from_inline(cls, version, blockhash, inline): @@ -73,12 +71,12 @@ class Certification(Document): pubkey_from = cert_data.group(1) pubkey_to = cert_data.group(2) blocknumber = cert_data.group(3) - sign = cert_data.group(4) + signature = cert_data.group(4) return cls(version, pubkey_from, pubkey_to, - blockhash, blocknumber, sign) + blockhash, blocknumber, signature) def ts(self): return "META:TS:{0}-{1}".format(self.blockhash, self.blocknumber) def raw(self, selfcert): - return "{0}\n{1}".format(selfcert.content(), self.ts()) + return "{0}\n{1}\n{2}".format(selfcert.raw(), self.ts(), self.signatures[0]) diff --git a/ucoinpy/documents/membership.py b/ucoinpy/documents/membership.py index 49eef154..392ae9f9 100644 --- a/ucoinpy/documents/membership.py +++ b/ucoinpy/documents/membership.py @@ -27,38 +27,36 @@ class Membership(Document): ([0-9]+):([0-9a-fA-F]{5,40}):([0-9]+):([^\n]+)\n") def __init__(self, version, currency, issuer, block_number, block_hash, - membership_type, userid, cert_ts, sign): + membership_type, userid, cert_ts, signature): ''' Constructor ''' - super(version) - self.currency = currency + super(version, currency, [signature]) self.issuer = issuer self.block_number = block_number self.block_hash = block_hash self.membership_type = membership_type self.userid = userid self.cert_ts = cert_ts - self.sign = sign @classmethod def from_inline(cls, version, currency, type, inline): data = Membership.re_inline.match(inline) issuer = data.group(1) - sign = data.group(2) + signature = data.group(2) block_number = data.group(3) block_hash = data.group(4) cert_ts = data.group(5) userid = data.group(6) return cls(version, currency, issuer, block_number, - block_hash, type, userid, cert_ts, sign) + block_hash, type, userid, cert_ts, signature) @classmethod def from_raw(cls, raw): #TODO : Parsing return cls() - def content(self): + def raw(self): return """ Version: {0} Type: Membership @@ -67,13 +65,15 @@ Issuer: {2} Block: {3}-{4} Membership: {5} UserID: {6} -CertTS: {7}""".format(PROTOCOL_VERSION, +CertTS: {7} +{8}""".format(self.version, self.currency, self.issuer, self.block_number, self.block_hash, self.membership_type, self.userid, - self.cert_ts) + self.cert_ts, + self.signatures[0]) def inline(self): return "{0}:{1}:{2}:{3}".format(self.issuer, diff --git a/ucoinpy/documents/peer.py b/ucoinpy/documents/peer.py index 0a387f79..30cafe51 100644 --- a/ucoinpy/documents/peer.py +++ b/ucoinpy/documents/peer.py @@ -24,7 +24,8 @@ class Peer(Document): [...] """ - def __init__(self, currency, pubkey, blockid, endpoints): + def __init__(self, version, pubkey, blockid, endpoints, signature): + super(version, currency, [signature]) self.currency = currency self.pubkey = pubkey self.blockid = blockid @@ -35,7 +36,7 @@ class Peer(Document): #TODO : Parsing return cls() - def content(self): + def raw(self): doc = """ Version: {0} Type: Peer @@ -43,10 +44,12 @@ Currency: {1} PublicKey: {2} Block: {3} Endpoints: -""".format(PROTOCOL_VERSION, self.currency, self.pubkey, self.blockid) +""".format(self.version, self.currency, self.pubkey, self.blockid) for endpoint in self.endpoints: doc += "{0}\n".format(endpoint.inline()) + + doc += "{0}\n".format(self.signatures[0]) return doc diff --git a/ucoinpy/documents/status.py b/ucoinpy/documents/status.py index 3913e051..a05c083a 100644 --- a/ucoinpy/documents/status.py +++ b/ucoinpy/documents/status.py @@ -19,11 +19,11 @@ class Status(Document): To: RECIPIENT ''' - def __init__(self, currency, status, blockid, sender, recipient): + def __init__(self, version, currency, status, blockid, sender, recipient, signature): ''' Constructor ''' - self.currency = currency + super(version, currency, [signature]) self.status = status self.blockid = blockid self.sender = sender @@ -34,7 +34,7 @@ class Status(Document): #TODO : Parsing return cls() - def content(self): + def raw(self): return ''' Version: {0} Type: Status @@ -43,5 +43,6 @@ Status: {2} Block: {3} From: {4} To: {5} -'''.format(PROTOCOL_VERSION, self.currency, self.status, - self.blockid, self.sender, self.recipient) +{6} +'''.format(self.version, self.currency, self.status, + self.blockid, self.sender, self.recipient, self.signatures[0]) diff --git a/ucoinpy/documents/transaction.py b/ucoinpy/documents/transaction.py index ee5a824b..38190895 100644 --- a/ucoinpy/documents/transaction.py +++ b/ucoinpy/documents/transaction.py @@ -10,6 +10,7 @@ from .. import PROTOCOL_VERSION class Transaction(Document): ''' +Document format : Version: VERSION Type: Transaction Currency: CURRENCY_NAME @@ -23,38 +24,130 @@ Outputs: PUBLIC_KEY:AMOUNT ... Comment: COMMENT -SIGNATURES +... + + +Compact format : +TX:VERSION:NB_ISSUERS:NB_INPUTS:NB_OUTPUTS:HAS_COMMENT +PUBLIC_KEY:INDEX +... +INDEX:SOURCE:FINGERPRINT:AMOUNT +... +PUBLIC_KEY:AMOUNT +... +COMMENT +SIGNATURE ... ''' - def __init__(self, currency, pubkeys, inputs, outputs, comment=None): + re_type = re.compile("Type: Transaction\n") + re_header = re.compile("TX:([0-9])+:([0-9])+:([0-9])+:([0-9])+:(0|1)\n") + re_issuers = re.compile("Issuers:\n") + re_inputs = re.compile("Inputs:\n") + re_outputs = re.compile("Outputs:\n") + re_pubkey = re.compile("([1-9A-Za-z][^OIl]{43,45})\n") + + def __init__(self, version, currency, issuers, inputs, outputs, signatures): ''' Constructor ''' - self.currency = currency - self.pubkeys = pubkeys + super(version, currency, signatures) + self.issuers = issuers self.inputs = inputs self.outputs = outputs self.comment = comment @classmethod - def from_compact(cls, compact): - return None + def from_compact(cls, currency, number, compact): + lines = raw.splitlines(True) + n = 0 + + header_data = re_header.match(lines[n]) + version = header_data.group(2) + issuers_num = int(header_data.group(3)) + inputs_num = int(header_data.group(3)) + outputs_num = int(header_data.group(3)) + n = n + 1 + + issuers = [] + inputs = [] + outputs = [] + signatures = [] + + for i in range(0, issuers_num): + issuer = re_pubkey.match(lines[n]).group(1) + issuers.append(issuer) + n = n + 1 + + for i in range(0, inputs_num): + input = InputSource.from_compact(lines[n]) + inputs.append(issuer) + n = n + 1 + + for i in range(0, outputs_num): + output = OutputSource.from_inline(lines[n]) + outputs.append(output) + n = n + 1 + + return cls(version, currency, issuers, inputs, outputs, signatures) + @classmethod def from_raw(cls, raw): - #TODO : Parsing - return cls() + n = 0 + + version = Transaction.re_version.match(lines[n]).group(1) + n = n + 1 + + type = Transaction.re_type.match(lines[n]).group(1) + n = n + 1 + + currency = Transaction.re_currency.match(lines[n]).group(1) + n = n + 1 + + issuers = [] + inputs = [] + outputs = [] + signatures = [] + + if Transaction.re_issuers.match(lines[n]): + lines = lines + 1 + while Transaction.re_inputs.match(lines[n]) is None: + issuer = Transaction.re_pubkey.match(lines[n]).group(1) + issuers.append(issuer) + lines = lines + 1 + + if Transaction.re_inputs.match(lines[n]): + lines = lines + 1 + while Transaction.re_outputs.match(lines[n]) is None: + input = InputSource.from_compact(number, lines[n]) + inputs.append(input) + lines = lines + 1 - def content(self): + if Transaction.re_outputs.match(lines[n]) is not None: + while Transaction.re_sign.match(lines[n]) is None: + output = OutputSource.from_inline(lines[n]) + outputs.append(output) + lines = lines + 1 + + + if Transaction.re_sign.match(lines[n]) is not None: + while n < lines.len: + sign = re_sign.match(lines[n]).group(1) + signatures.append(sign) + lines = lines + 1 + + return cls(version, currency, issuers, inputs, outputs, signatures) + + def raw(self): doc = """ Version: {0} Type: Transaction Currency: {1} -Issuers:""".format(PROTOCOL_VERSION, +Issuers:""".format(self.version, self.currency) - for p in self.pubkeys: + for p in self.issuers: doc += "{0}\n".format(p) doc += "Inputs:\n" @@ -70,6 +163,9 @@ COMMENT: {0} """.format(self.comment) + for signature in self.signatures: + doc += "{0}\n".format(signature) + return doc def compact(self): @@ -85,12 +181,12 @@ PUBLIC_KEY:AMOUNT ... COMMENT """ - doc = "TX:{0}:{1}:{2}:{3}:{4}".format(PROTOCOL_VERSION, - self.pubkeys.len, + doc = "TX:{0}:{1}:{2}:{3}:{4}".format(self.version, + self.issuers.len, self.inputs.len, self.outputs.len, '1' if self.Comment else '0') - for pubkey in self.pubkeys: + for pubkey in self.issuers: doc += "{0}\n".format(pubkey) for i in self.inputs: doc += "{0}\n".format(i.compact()) @@ -98,53 +194,71 @@ COMMENT doc += "{0}\n".format(o.inline()) if self.comment: doc += "{0}\n".format(self.comment) - return doc - - def sign(self, keys): - signatures = "" - for k in keys: - signatures += "{0}\n".format(super().sign(k)) - return signatures + for s in self.signatures: + doc += "{0}\n".format(s) + return doc class SimpleTransaction(Transaction): ''' As transaction class, but for only one issuer. ... ''' - def __init__(self, currency, pubkey, single_input, outputs, comment): + def __init__(self, version, currency, issuer, single_input, outputs, comment, signature): ''' Constructor ''' - self.currency = currency - self.pubkeys = [pubkey] - self.inputs = [single_input] - self.outputs = outputs - self.comment = comment + super(version, currency, [issuer], [single_input], outputs, comment, [signature]) class InputSource(): ''' A Transaction INPUT + + Compact : + INDEX:SOURCE:FINGERPRINT:AMOUNT ''' - def __init__(self, index, source, number, fingerprint, amount): + re_inline = re.compile("([0-9]+):(D|T):([0-9]+):([0-9a-fA-F]{5,40}):([0-9]+)") + re_compact = re.compile("([0-9]+):(D|T):([0-9a-fA-F]{5,40}):([0-9]+)") + + def __init__(self, index, source, number, txhash, amount): self.index = index self.source = source self.number = number - self.fingerprint = fingerprint + self.txhash = txhash self.amount = amount + @classmethod + def from_inline(cls, inline): + data = re_inline.match(inline) + index = data.group(1) + source = data.group(2) + number = data.group(3) + txhash = data.group(4) + amount = data;group(5) + return cls(data, index, source, number, txhash, amount) + + @classmethod + def from_compact(cls, number, compact): + data = re_compact.match(inline) + index = data.group(1) + source = data.group(2) + txhash = data.group(3) + amount = data;group(4) + return cls(data, index, source, number, txhash, amount) + + def inline(self): return "{0}:{1}:{2}:{3}:{4}".format(self.index, self.source, self.number, - self.fingerprint, + self.txhash, self.amount) def compact(self): return "{0}:{1}:{2}:{3}".format(self.index, self.source, - self.fingerprint, + self.txhash, self.amount) @@ -152,9 +266,17 @@ class OutputSource(): ''' A Transaction OUTPUT ''' + re_inline = "([1-9A-Za-z][^OIl]{43,45}):([0-9]+)" def __init__(self, pubkey, amount): self.pubkey = pubkey self.amount = amount + + @lassmethod + def from_inline(cls, inline): + data = re_inline.match(inline) + pubkey = data.group(1) + amount = data.group(2) + return cls(pubkey, amount) def inline(self): return "{0}:{1}".format(self.pubkey, self.amount) -- GitLab