Skip to content
Snippets Groups Projects
Select Git revision
  • b1f231738e8e53926dad0925c7be8067a95932c2
  • main default protected
  • release/1.1
  • encrypt_comments
  • mnemonic_dewif
  • authors_rules
  • 0.14
  • rtd
  • 1.2.1 protected
  • 1.2.0 protected
  • 1.1.1 protected
  • 1.1.0 protected
  • 1.0.0 protected
  • 1.0.0rc1 protected
  • 1.0.0rc0 protected
  • 1.0.0-rc protected
  • 0.62.0 protected
  • 0.61.0 protected
  • 0.60.1 protected
  • 0.58.1 protected
  • 0.60.0 protected
  • 0.58.0 protected
  • 0.57.0 protected
  • 0.56.0 protected
  • 0.55.1 protected
  • 0.55.0 protected
  • 0.54.3 protected
  • 0.54.2 protected
28 results

request_data_graphql.py

Blame
  • block.py 19.29 KiB
    import base64
    import hashlib
    import re
    from typing import TypeVar, Type, Optional, List, Sequence
    from .block_uid import BlockUID
    from .certification import Certification
    from .revocation import Revocation
    from .identity import Identity
    from .document import Document, MalformedDocumentError
    from .membership import Membership
    from .transaction import Transaction
    from ..constants import PUBKEY_REGEX, BLOCK_HASH_REGEX
    
    
    # required to type hint cls in classmethod
    BlockType = TypeVar('BlockType', bound='Block')
    
    
    class Block(Document):
        """
    The class Block handles Block documents.
    
    .. note:: A block document is specified by the following format :
    
        | Version: VERSION
        | Type: Block
        | Currency: CURRENCY
        | Nonce: NONCE
        | Number: BLOCK_NUMBER
        | PoWMin: NUMBER_OF_ZEROS
        | Time: GENERATED_ON
        | MedianTime: MEDIAN_DATE
        | UniversalDividend: DIVIDEND_AMOUNT
        | Issuer: ISSUER_KEY
        | PreviousHash: PREVIOUS_HASH
        | PreviousIssuer: PREVIOUS_ISSUER_KEY
        | Parameters: PARAMETERS
        | MembersCount: WOT_MEM_COUNT
        | Identities:
        | PUBLIC_KEY:SIGNATURE:TIMESTAMP:USER_ID
        | ...
        | Joiners:
        | PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID
        | ...
        | Actives:
        | PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID
        | ...
        | Leavers:
        | PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID
        | ...
        | Excluded:
        | PUBLIC_KEY
        | ...
        | Certifications:
        | PUBKEY_FROM:PUBKEY_TO:BLOCK_NUMBER:SIGNATURE
        | ...
        | Transactions:
        | COMPACT_TRANSACTION
        | ...
        | BOTTOM_SIGNATURE
    
        """
    
        re_type = re.compile("Type: (Block)\n")
        re_number = re.compile("Number: ([0-9]+)\n")
        re_powmin = re.compile("PoWMin: ([0-9]+)\n")
        re_time = re.compile("Time: ([0-9]+)\n")
        re_mediantime = re.compile("MedianTime: ([0-9]+)\n")
        re_universaldividend = re.compile("UniversalDividend: ([0-9]+)\n")
        re_unitbase = re.compile("UnitBase: ([0-9]+)\n")
        re_issuer = re.compile("Issuer: ({pubkey_regex})\n".format(pubkey_regex=PUBKEY_REGEX))
        re_issuers_frame = re.compile("IssuersFrame: ([0-9]+)\n")
        re_issuers_frame_var = re.compile("IssuersFrameVar: (0|-?[1-9]\\d{0,18})\n")
        re_different_issuers_count = re.compile("DifferentIssuersCount: ([0-9]+)\n")
        re_previoushash = re.compile("PreviousHash: ({block_hash_regex})\n".format(block_hash_regex=BLOCK_HASH_REGEX))
        re_previousissuer = re.compile("PreviousIssuer: ({pubkey_regex})\n".format(pubkey_regex=PUBKEY_REGEX))
        re_parameters = re.compile("Parameters: ([0-9]+\\.[0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):\
    ([0-9]+):([0-9]+):([0-9]+\\.[0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+\\.[0-9]+)\n")
        re_parameters_v10 = re.compile("Parameters: ([0-9]+\\.[0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):\
    ([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+\\.[0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+\\.[0-9]+):\
    ([0-9]+):([0-9]+):([0-9]+)\n")
        re_memberscount = re.compile("MembersCount: ([0-9]+)\n")
        re_identities = re.compile("Identities:\n")
        re_joiners = re.compile("Joiners:\n")
        re_actives = re.compile("Actives:\n")
        re_leavers = re.compile("Leavers:\n")
        re_revoked = re.compile("Revoked:\n")
        re_excluded = re.compile("Excluded:\n")
        re_exclusion = re.compile("({pubkey_regex})\n".format(pubkey_regex=PUBKEY_REGEX))
        re_certifications = re.compile("Certifications:\n")
        re_transactions = re.compile("Transactions:\n")
        re_hash = re.compile("InnerHash: ({block_hash_regex})\n".format(block_hash_regex=BLOCK_HASH_REGEX))
        re_noonce = re.compile("Nonce: ([0-9]+)\n")
    
        fields_parsers = {**Document.fields_parsers, **{
            'Type': re_type,
            'Number': re_number,
            'PoWMin': re_powmin,
            'Time': re_time,
            'MedianTime': re_mediantime,
            'UD': re_universaldividend,
            'UnitBase': re_unitbase,
            'Issuer': re_issuer,
            'IssuersFrame': re_issuers_frame,
            'IssuersFrameVar': re_issuers_frame_var,
            'DifferentIssuersCount': re_different_issuers_count,
            'PreviousIssuer': re_previousissuer,
            'PreviousHash': re_previoushash,
            'Parameters': re_parameters,
            'MembersCount': re_memberscount,
            'Identities': re_identities,
            'Joiners': re_joiners,
            'Actives': re_actives,
            'Leavers': re_leavers,
            'Revoked': re_revoked,
            'Excluded': re_excluded,
            'Certifications': re_certifications,
            'Transactions': re_transactions,
            'InnerHash': re_hash,
            'Noonce': re_noonce,
        }
                          }
    
        def __init__(self,
                     version: int,
                     currency: str,
                     number: int,
                     powmin: int,
                     time: int,
                     mediantime: int,
                     ud: Optional[int],
                     unit_base: int,
                     issuer: str,
                     issuers_frame: int,
                     issuers_frame_var: int,
                     different_issuers_count: int,
                     prev_hash: Optional[str],
                     prev_issuer: Optional[str],
                     parameters: Optional[Sequence[str]],
                     members_count: int,
                     identities: List[Identity],
                     joiners: List[Membership],
                     actives: List[Membership],
                     leavers: List[Membership],
                     revokations: List[Revocation],
                     excluded: List[str],
                     certifications: List[Certification],
                     transactions: List[Transaction],
                     inner_hash: str,
                     noonce: int,
                     signature: str
                     ) -> None:
            """
            Constructor
    
            :param version: duniter protocol version
            :param currency: the block currency
            :param number: the number of the block
            :param powmin: the powmin value of this block
            :param time: the timestamp of this block
            :param mediantime: the timestamp of the median time of this block
            :param ud: the dividend amount, or None if no dividend present in this block
            :param unit_base: the unit_base of the dividend, or None if no dividend present in this block
            :param issuer: the pubkey of the issuer of the block
            :param issuers_frame:
            :param issuers_frame_var:
            :param different_issuers_count: the count of issuers
            :param prev_hash: the previous block hash
            :param prev_issuer: the previous block issuer
            :param parameters: the parameters of the currency. Should only be present in block 0.
            :param members_count: the number of members found in this block
            :param identities: the self certifications declared in this block
            :param joiners: the joiners memberships via "IN" documents
            :param actives: renewed memberships via "IN" documents
            :param leavers: the leavers memberships via "OUT" documents
            :param revokations: revokations
            :param excluded: members excluded because of missing certifications
            :param certifications: certifications documents
            :param transactions: transactions documents
            :param inner_hash: the block hah
            :param noonce: the noonce value of the block
            :param signature: the block signature
            """
            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]),
                                     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
            self.mediantime = mediantime
            self.ud = ud
            self.unit_base = unit_base
            self.issuer = issuer
            self.issuers_frame = issuers_frame
            self.issuers_frame_var = issuers_frame_var
            self.different_issuers_count = different_issuers_count
            self.prev_hash = prev_hash
            self.prev_issuer = prev_issuer
            self.parameters = parameters
            self.members_count = members_count
            self.identities = identities
            self.joiners = joiners
            self.actives = actives
            self.leavers = leavers
            self.revoked = revokations
            self.excluded = excluded
            self.certifications = certifications
            self.transactions = transactions
            self.inner_hash = inner_hash
            self.noonce = noonce
    
        @property
        def blockUID(self) -> BlockUID:
            return BlockUID(self.number, self.proof_of_work())
    
        @classmethod
        def from_signed_raw(cls: Type[BlockType], signed_raw: str) -> BlockType:
            lines = signed_raw.splitlines(True)
            n = 0
    
            version = int(Block.parse_field("Version", lines[n]))
            n += 1
    
            Block.parse_field("Type", lines[n])
            n += 1
    
            currency = Block.parse_field("Currency", lines[n])
            n += 1
    
            number = int(Block.parse_field("Number", lines[n]))
            n += 1
    
            powmin = int(Block.parse_field("PoWMin", lines[n]))
            n += 1
    
            time = int(Block.parse_field("Time", lines[n]))
            n += 1
    
            mediantime = int(Block.parse_field("MedianTime", lines[n]))
            n += 1
    
            ud_match = Block.re_universaldividend.match(lines[n])
            ud = None
            unit_base = 0
            if ud_match is not None:
                ud = int(Block.parse_field("UD", lines[n]))
                n += 1
    
            if version >= 3 or ud:
                unit_base = int(Block.parse_field("UnitBase", lines[n]))
                n += 1
    
            issuer = Block.parse_field("Issuer", lines[n])
            n += 1
    
            if version >= 3:
                issuers_frame = Block.parse_field("IssuersFrame", lines[n])
                n += 1
                issuers_frame_var = Block.parse_field("IssuersFrameVar", lines[n])
                n += 1
                different_issuers_count = Block.parse_field("DifferentIssuersCount", lines[n])
                n += 1
            else:
                issuers_frame = None
                issuers_frame_var = None
                different_issuers_count = None
    
            prev_hash = None
            prev_issuer = None
            if number > 0:
                prev_hash = str(Block.parse_field("PreviousHash", lines[n]))
                n += 1
    
                prev_issuer = str(Block.parse_field("PreviousIssuer", lines[n]))
                n += 1
    
            parameters = None
            if number == 0:
                try:
                    if version >= 10:
                        params_match = Block.re_parameters_v10.match(lines[n])
                        if params_match is None:
                            raise MalformedDocumentError("Parameters")
                        parameters = params_match.groups()
                    else:
                        params_match = Block.re_parameters.match(lines[n])
                        if params_match is None:
                            raise MalformedDocumentError("Parameters")
                        parameters = params_match.groups()
                    n += 1
                except AttributeError:
                    raise MalformedDocumentError("Parameters")
    
            members_count = int(Block.parse_field("MembersCount", lines[n]))
            n += 1
    
            identities = []
            joiners = []
            actives = []
            leavers = []
            revoked = []
            excluded = []
            certifications = []
            transactions = []
    
            if Block.re_identities.match(lines[n]) is not None:
                n += 1
                while Block.re_joiners.match(lines[n]) is None:
                    selfcert = Identity.from_inline(version, currency, lines[n])
                    identities.append(selfcert)
                    n += 1
    
            if Block.re_joiners.match(lines[n]):
                n += 1
                while Block.re_actives.match(lines[n]) is None:
                    membership = Membership.from_inline(version, currency, "IN", lines[n])
                    joiners.append(membership)
                    n += 1
    
            if Block.re_actives.match(lines[n]):
                n += 1
                while Block.re_leavers.match(lines[n]) is None:
                    membership = Membership.from_inline(version, currency, "IN", lines[n])
                    actives.append(membership)
                    n += 1
    
            if Block.re_leavers.match(lines[n]):
                n += 1
                while Block.re_revoked.match(lines[n]) is None:
                    membership = Membership.from_inline(version, currency, "OUT", lines[n])
                    leavers.append(membership)
                    n += 1
    
            if Block.re_revoked.match(lines[n]):
                n += 1
                while Block.re_excluded.match(lines[n]) is None:
                    revokation = Revocation.from_inline(version, currency, lines[n])
                    revoked.append(revokation)
                    n += 1
    
            if Block.re_excluded.match(lines[n]):
                n += 1
                while Block.re_certifications.match(lines[n]) is None:
                    exclusion_match = Block.re_exclusion.match(lines[n])
                    if exclusion_match is not None:
                        exclusion = exclusion_match.group(1)
                        excluded.append(exclusion)
                    n += 1
    
            if Block.re_certifications.match(lines[n]):
                n += 1
                while Block.re_transactions.match(lines[n]) is None:
                    certification = Certification.from_inline(version, currency,
                                                              prev_hash, lines[n])
                    certifications.append(certification)
                    n += 1
    
            if Block.re_transactions.match(lines[n]):
                n += 1
                while not Block.re_hash.match(lines[n]):
                    tx_lines = ""
                    header_data = Transaction.re_header.match(lines[n])
                    if header_data is None:
                        raise MalformedDocumentError("Compact transaction ({0})".format(lines[n]))
                    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))
                    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
                    transaction = Transaction.from_compact(currency, tx_lines)
                    transactions.append(transaction)
    
            inner_hash = Block.parse_field("InnerHash", lines[n])
            n += 1
    
            noonce = int(Block.parse_field("Noonce", lines[n]))
            n += 1
    
            signature = Block.parse_field("Signature", lines[n])
    
            return cls(version, currency, number, powmin, time,
                       mediantime, ud, unit_base, issuer, issuers_frame, issuers_frame_var,
                       different_issuers_count, prev_hash, prev_issuer,
                       parameters, members_count, identities, joiners,
                       actives, leavers, revoked, excluded, certifications,
                       transactions, inner_hash, noonce, signature)
    
        def raw(self) -> str:
            doc = """Version: {version}
    Type: Block
    Currency: {currency}
    Number: {number}
    PoWMin: {powmin}
    Time: {time}
    MedianTime: {mediantime}
    """.format(version=self.version,
               currency=self.currency,
               number=self.number,
               powmin=self.powmin,
               time=self.time,
               mediantime=self.mediantime)
            if self.ud:
                doc += "UniversalDividend: {0}\n".format(self.ud)
    
            if self.version >= 3 or self.ud:
                doc += "UnitBase: {0}\n".format(self.unit_base)
    
            doc += "Issuer: {0}\n".format(self.issuer)
    
            if self.version >= 3:
                doc += """IssuersFrame: {0}
    IssuersFrameVar: {1}
    DifferentIssuersCount: {2}
    """.format(self.issuers_frame, self.issuers_frame_var, self.different_issuers_count)
    
            if self.number == 0 and self.parameters is not None:
                str_params = ":".join([str(p) for p in self.parameters])
                doc += "Parameters: {0}\n".format(str_params)
            else:
                doc += "PreviousHash: {0}\n\
    PreviousIssuer: {1}\n".format(self.prev_hash, self.prev_issuer)
    
            doc += "MembersCount: {0}\n".format(self.members_count)
    
            doc += "Identities:\n"
            for identity in self.identities:
                doc += "{0}\n".format(identity.inline())
    
            doc += "Joiners:\n"
            for joiner in self.joiners:
                doc += "{0}\n".format(joiner.inline())
    
            doc += "Actives:\n"
            for active in self.actives:
                doc += "{0}\n".format(active.inline())
    
            doc += "Leavers:\n"
            for leaver in self.leavers:
                doc += "{0}\n".format(leaver.inline())
    
            doc += "Revoked:\n"
            for revokation in self.revoked:
                doc += "{0}\n".format(revokation.inline())
    
            doc += "Excluded:\n"
            for exclude in self.excluded:
                doc += "{0}\n".format(exclude)
    
            doc += "Certifications:\n"
            for cert in self.certifications:
                doc += "{0}\n".format(cert.inline())
    
            doc += "Transactions:\n"
            for transaction in self.transactions:
                doc += "{0}".format(transaction.compact())
    
            doc += "InnerHash: {0}\n".format(self.inner_hash)
    
            doc += "Nonce: {0}\n".format(self.noonce)
    
            return doc
    
        def proof_of_work(self) -> str:
            doc_str = """InnerHash: {inner_hash}
    Nonce: {nonce}
    {signature}
    """.format(inner_hash=self.inner_hash, nonce=self.noonce, signature=self.signatures[0])
            return hashlib.sha256(doc_str.encode('ascii')).hexdigest().upper()
    
        def computed_inner_hash(self) -> str:
            doc = self.signed_raw()
            inner_doc = '\n'.join(doc.split('\n')[:-2]) + '\n'
            return hashlib.sha256(inner_doc.encode("ascii")).hexdigest().upper()
    
        def sign(self, keys):
            """
            Sign the current document.
            Warning : current signatures will be replaced with the new ones.
            """
            key = keys[0]
            signed = self.raw()[-2:]
            signing = base64.b64encode(key.signature(bytes(signed, 'ascii')))
            self.signatures = [signing.decode("ascii")]
    
        def __eq__(self, other: object) -> bool:
            if not isinstance(other, Block):
                return NotImplemented
            return self.blockUID == other.blockUID
    
        def __lt__(self, other: object) -> bool:
            if not isinstance(other, Block):
                return False
            return self.blockUID < other.blockUID
    
        def __gt__(self, other: object) -> bool:
            if not isinstance(other, Block):
                return False
            return self.blockUID > other.blockUID
    
        def __le__(self, other: object) -> bool:
            if not isinstance(other, Block):
                return False
            return self.blockUID <= other.blockUID
    
        def __ge__(self, other: object) -> bool:
            if not isinstance(other, Block):
                return False
            return self.blockUID >= other.blockUID