# Module `onionr.onionrblocks.onionrblockapi`

Onionr - P2P Anonymous Storage Network

This file contains the OnionrBlocks class which is a class for working with Onionr blocks

Source code

    '''
        Onionr - P2P Anonymous Storage Network

        This file contains the OnionrBlocks class which is a class for working with Onionr blocks
    '''
    '''
        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <https://www.gnu.org/licenses/>.
    '''
    import binascii
    import logger, config, onionrexceptions, nacl.exceptions
    import json, os, sys, datetime, base64, onionrstorage
    from onionrusers import onionrusers
    from onionrutils import stringvalidators, epoch
    from coredb import blockmetadb
    from onionrstorage import removeblock
    import onionrblocks
    from onionrcrypto import encryption, cryptoutils as cryptoutils, signing
    class Block:
        blockCacheOrder = list() # NEVER write your own code that writes to this!
        blockCache = dict() # should never be accessed directly, look at Block.getCache()

        def __init__(self, hash = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False):
            # take from arguments
            # sometimes people input a bytes object instead of str in `hash`
            if (not hash is None) and isinstance(hash, bytes):
                hash = hash.decode()

            self.hash = hash
            self.btype = type
            self.bcontent = content
            self.expire = expire
            self.bypassReplayCheck = bypassReplayCheck

            # initialize variables
            self.valid = True
            self.raw = None
            self.signed = False
            self.signature = None
            self.signedData = None
            self.blockFile = None
            self.bheader = {}
            self.bmetadata = {}
            self.isEncrypted = False
            self.decrypted = False
            self.signer = None
            self.validSig = False
            self.autoDecrypt = decrypt

            self.update()

        def decrypt(self, encodedData = True):
            '''
                Decrypt a block, loading decrypted data into their vars
            '''

            if self.decrypted:
                return True
            retData = False
            # decrypt data
            if self.getHeader('encryptType') == 'asym':
                try:
                    try:
                        self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=encodedData)
                    except (binascii.Error, ValueError) as e:
                        self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=False)
                    bmeta = encryption.pub_key_decrypt(self.bmetadata, encodedData=encodedData)
                    try:
                        bmeta = bmeta.decode()
                    except AttributeError:
                        # yet another bytes fix
                        pass
                    self.bmetadata = json.loads(bmeta)
                    self.signature = encryption.pub_key_decrypt(self.signature, encodedData=encodedData)
                    self.signer = encryption.pub_key_decrypt(self.signer, encodedData=encodedData)
                    self.bheader['signer'] = self.signer.decode()
                    self.signedData =  json.dumps(self.bmetadata).encode() + self.bcontent

                    if not self.signer is None:
                        if not self.verifySig():
                            raise onionrexceptions.SignatureError("Block has invalid signature")

                    # Check for replay attacks
                    try:
                        if epoch.get_epoch() - blockmetadb.get_block_date(self.hash) > 60:
                            if not cryptoutils.replay_validator(self.bmetadata['rply']): raise onionrexceptions.ReplayAttack
                    except (AssertionError, KeyError, TypeError, onionrexceptions.ReplayAttack) as e:
                        if not self.bypassReplayCheck:
                            # Zero out variables to prevent reading of replays
                            self.bmetadata = {}
                            self.signer = ''
                            self.bheader['signer'] = ''
                            self.signedData = ''
                            self.signature = ''
                            raise onionrexceptions.ReplayAttack('Signature is too old. possible replay attack')
                    try:
                        if not self.bmetadata['forwardEnc']: raise KeyError
                    except (AssertionError, KeyError) as e:
                        pass
                    else:
                        try:
                            self.bcontent = onionrusers.OnionrUser(self.signer).forwardDecrypt(self.bcontent)
                        except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e:
                            #logger.error(str(e))
                            pass
                except nacl.exceptions.CryptoError:
                    pass
                    #logger.debug('Could not decrypt block. Either invalid key or corrupted data')
                except onionrexceptions.ReplayAttack:
                    logger.warn('%s is possibly a replay attack' % (self.hash,))
                else:
                    retData = True
                    self.decrypted = True
            return retData

        def verifySig(self):
            '''
                Verify if a block's signature is signed by its claimed signer
            '''
            if self.signer is None or signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
                self.validSig = True
            else:
                self.validSig = False
            return self.validSig

        def update(self, data = None, file = None):
            '''
                Loads data from a block in to the current object.

                Inputs:
                - data (str):
                  - if None: will load from file by hash
                  - else: will load from `data` string
                - file (str):
                  - if None: will load from file specified in this parameter
                  - else: will load from wherever block is stored by hash

                Outputs:
                - (bool): indicates whether or not the operation was successful
            '''
            try:
                # import from string
                blockdata = data

                # import from file
                if blockdata is None:
                    try:
                        blockdata = onionrstorage.getData(self.getHash())#.decode()
                    except AttributeError:
                        raise onionrexceptions.NoDataAvailable('Block does not exist')
                else:
                    self.blockFile = None
                # parse block
                self.raw = blockdata
                self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
                self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
                if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
                    self.bmetadata = self.getHeader('meta', None)
                    self.isEncrypted = True
                else:
                    self.bmetadata = json.loads(self.getHeader('meta', None))
                self.btype = self.getMetadata('type', None)
                self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
                # TODO: detect if signer is hash of pubkey or not
                self.signer = self.getHeader('signer', None)
                self.signature = self.getHeader('sig', None)
                # signed data is jsonMeta + block content (no linebreak)
                self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
                self.date = blockmetadb.get_block_date(self.getHash())
                self.claimedTime = self.getHeader('time', None)

                if not self.getDate() is None:
                    self.date = datetime.datetime.fromtimestamp(self.getDate())

                self.valid = True

                if self.autoDecrypt:
                    self.decrypt()

                return True
            except Exception as e:
                logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)

                # if block can't be parsed, it's a waste of precious space. Throw it away.
                if not self.delete():
                    logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
                else:
                    logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)

            self.valid = False
            return False

        def delete(self):
            '''
                Deletes the block's file and records, if they exist

                Outputs:
                - (bool): whether or not the operation was successful
            '''

            if self.exists():
                try:
                    os.remove(self.getBlockFile())
                except TypeError:
                    pass
                b_hash = self.getHash()
                onionrstorage.deleteBlock(b_hash)
                removeblock.remove_block(b_hash)
                return True
            return False

        def save(self, sign = False, recreate = True):
            '''
                Saves a block to file and imports it into Onionr

                Inputs:
                - sign (bool): whether or not to sign the block before saving
                - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

                Outputs:
                - (bool): whether or not the operation was successful
            '''

            try:
                if self.isValid() is True:

                    self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
                    if self.hash != False:
                        self.update()

                    return self.getHash()
                else:
                    logger.warn('Not writing block; it is invalid.')
            except Exception as e:
                logger.error('Failed to save block.', error = e, timestamp = False)

            return False

        # getters

        def getExpire(self):
            '''
                Returns the expire time for a block

                Outputs:
                - (int): the expire time for a block, or None
            '''
            return self.expire

        def getHash(self):
            '''
                Returns the hash of the block if saved to file

                Outputs:
                - (str): the hash of the block, or None
            '''

            return self.hash

        def getType(self):
            '''
                Returns the type of the block

                Outputs:
                - (str): the type of the block
            '''
            return self.btype

        def getRaw(self):
            '''
                Returns the raw contents of the block, if saved to file

                Outputs:
                - (bytes): the raw contents of the block, or None
            '''

            return self.raw

        def getHeader(self, key = None, default = None):
            '''
                Returns the header information

                Inputs:
                - key (str): only returns the value of the key in the header

                Outputs:
                - (dict/str): either the whole header as a dict, or one value
            '''

            if not key is None:
                if key in self.getHeader():
                    return self.getHeader()[key]
                return default
            return self.bheader

        def getMetadata(self, key = None, default = None):
            '''
                Returns the metadata information

                Inputs:
                - key (str): only returns the value of the key in the metadata

                Outputs:
                - (dict/str): either the whole metadata as a dict, or one value
            '''

            if not key is None:
                if key in self.getMetadata():
                    return self.getMetadata()[key]
                return default
            return self.bmetadata

        def getContent(self):
            '''
                Returns the contents of the block

                Outputs:
                - (str): the contents of the block
            '''

            return str(self.bcontent)

        def getDate(self):
            '''
                Returns the date that the block was received, if loaded from file

                Outputs:
                - (datetime): the date that the block was received
            '''

            return self.date

        def getBlockFile(self):
            '''
                Returns the location of the block file if it is saved

                Outputs:
                - (str): the location of the block file, or None
            '''

            return self.blockFile

        def isValid(self):
            '''
                Checks if the block is valid

                Outputs:
                - (bool): whether or not the block is valid
            '''

            return self.valid

        def isSigned(self):
            '''
                Checks if the block was signed

                Outputs:
                - (bool): whether or not the block is signed
            '''

            return self.signed

        def getSignature(self):
            '''
                Returns the base64-encoded signature

                Outputs:
                - (str): the signature, or None
            '''

            return self.signature

        def getSignedData(self):
            '''
                Returns the data that was signed

                Outputs:
                - (str): the data that was signed, or None
            '''

            return self.signedData

        def isSigner(self, signer, encodedData = True):
            '''
                Checks if the block was signed by the signer inputted

                Inputs:
                - signer (str): the public key of the signer to check against
                - encodedData (bool): whether or not the `signer` argument is base64 encoded

                Outputs:
                - (bool): whether or not the signer of the block is the signer inputted
            '''

            try:
                if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
                    return False

                return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
            except:
                return False

        # setters

        def setType(self, btype):
            '''
                Sets the type of the block

                Inputs:
                - btype (str): the type of block to be set to

                Outputs:
                - (Block): the Block instance
            '''

            self.btype = btype
            return self

        def setMetadata(self, key, val):
            '''
                Sets a custom metadata value

                Metadata should not store block-specific data structures.

                Inputs:
                - key (str): the key
                - val: the value (type is irrelevant)

                Outputs:
                - (Block): the Block instance
            '''

            self.bmetadata[key] = val
            return self

        def setContent(self, bcontent):
            '''
                Sets the contents of the block

                Inputs:
                - bcontent (str): the contents to be set to

                Outputs:
                - (Block): the Block instance
            '''

            self.bcontent = str(bcontent)
            return self

        # static functions

        def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
            '''
                Returns a list of Block objects based on supplied filters

                Inputs:
                - type (str): filters by block type
                - signer (str/list): filters by signer (one in the list has to be a signer)
                - signed (bool): filters out by whether or not the block is signed
                - reverse (bool): reverses the list if True

                Outputs:
                - (list): a list of Block objects that match the input
            '''

            try:

                relevant_blocks = list()
                blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))

                for block in blocks:
                    if Block.exists(block):
                        block = Block(block)

                        relevant = True

                        if (not signed is None) and (block.isSigned() != bool(signed)):
                            relevant = False
                        if not signer is None:
                            if isinstance(signer, (str,)):
                                signer = [signer]
                            if isinstance(signer, (bytes,)):
                                signer = [signer.decode()]

                            isSigner = False
                            for key in signer:
                                if block.isSigner(key):
                                    isSigner = True
                                    break

                            if not isSigner:
                                relevant = False

                        if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
                            relevant_blocks.append(block)

                if bool(reverse):
                    relevant_blocks.reverse()

                return relevant_blocks
            except Exception as e:
                logger.debug('Failed to get blocks.', error = e)

            return list()

        def exists(bHash):
            '''
                Checks if a block is saved to file or not

                Inputs:
                - hash (str/Block):
                  - if (Block): check if this block is saved to file
                  - if (str): check if a block by this hash is in file

                Outputs:
                - (bool): whether or not the block file exists
            '''

            # no input data? scrap it.
            if bHash is None:
                return False

            if isinstance(bHash, Block):
                bHash = bHash.getHash()

            ret = isinstance(onionrstorage.getData(bHash), type(None))

            return not ret

## Classes

`class Block (hash=None, type=None, content=None, expire=None, decrypt=False, bypassReplayCheck=False)`
  Source code

      class Block:
          blockCacheOrder = list() # NEVER write your own code that writes to this!
          blockCache = dict() # should never be accessed directly, look at Block.getCache()

          def __init__(self, hash = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False):
              # take from arguments
              # sometimes people input a bytes object instead of str in `hash`
              if (not hash is None) and isinstance(hash, bytes):
                  hash = hash.decode()

              self.hash = hash
              self.btype = type
              self.bcontent = content
              self.expire = expire
              self.bypassReplayCheck = bypassReplayCheck

              # initialize variables
              self.valid = True
              self.raw = None
              self.signed = False
              self.signature = None
              self.signedData = None
              self.blockFile = None
              self.bheader = {}
              self.bmetadata = {}
              self.isEncrypted = False
              self.decrypted = False
              self.signer = None
              self.validSig = False
              self.autoDecrypt = decrypt

              self.update()

          def decrypt(self, encodedData = True):
              '''
                  Decrypt a block, loading decrypted data into their vars
              '''

              if self.decrypted:
                  return True
              retData = False
              # decrypt data
              if self.getHeader('encryptType') == 'asym':
                  try:
                      try:
                          self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=encodedData)
                      except (binascii.Error, ValueError) as e:
                          self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=False)
                      bmeta = encryption.pub_key_decrypt(self.bmetadata, encodedData=encodedData)
                      try:
                          bmeta = bmeta.decode()
                      except AttributeError:
                          # yet another bytes fix
                          pass
                      self.bmetadata = json.loads(bmeta)
                      self.signature = encryption.pub_key_decrypt(self.signature, encodedData=encodedData)
                      self.signer = encryption.pub_key_decrypt(self.signer, encodedData=encodedData)
                      self.bheader['signer'] = self.signer.decode()
                      self.signedData =  json.dumps(self.bmetadata).encode() + self.bcontent

                      if not self.signer is None:
                          if not self.verifySig():
                              raise onionrexceptions.SignatureError("Block has invalid signature")

                      # Check for replay attacks
                      try:
                          if epoch.get_epoch() - blockmetadb.get_block_date(self.hash) > 60:
                              if not cryptoutils.replay_validator(self.bmetadata['rply']): raise onionrexceptions.ReplayAttack
                      except (AssertionError, KeyError, TypeError, onionrexceptions.ReplayAttack) as e:
                          if not self.bypassReplayCheck:
                              # Zero out variables to prevent reading of replays
                              self.bmetadata = {}
                              self.signer = ''
                              self.bheader['signer'] = ''
                              self.signedData = ''
                              self.signature = ''
                              raise onionrexceptions.ReplayAttack('Signature is too old. possible replay attack')
                      try:
                          if not self.bmetadata['forwardEnc']: raise KeyError
                      except (AssertionError, KeyError) as e:
                          pass
                      else:
                          try:
                              self.bcontent = onionrusers.OnionrUser(self.signer).forwardDecrypt(self.bcontent)
                          except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e:
                              #logger.error(str(e))
                              pass
                  except nacl.exceptions.CryptoError:
                      pass
                      #logger.debug('Could not decrypt block. Either invalid key or corrupted data')
                  except onionrexceptions.ReplayAttack:
                      logger.warn('%s is possibly a replay attack' % (self.hash,))
                  else:
                      retData = True
                      self.decrypted = True
              return retData

          def verifySig(self):
              '''
                  Verify if a block's signature is signed by its claimed signer
              '''
              if self.signer is None or signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
                  self.validSig = True
              else:
                  self.validSig = False
              return self.validSig

          def update(self, data = None, file = None):
              '''
                  Loads data from a block in to the current object.

                  Inputs:
                  - data (str):
                    - if None: will load from file by hash
                    - else: will load from `data` string
                  - file (str):
                    - if None: will load from file specified in this parameter
                    - else: will load from wherever block is stored by hash

                  Outputs:
                  - (bool): indicates whether or not the operation was successful
              '''
              try:
                  # import from string
                  blockdata = data

                  # import from file
                  if blockdata is None:
                      try:
                          blockdata = onionrstorage.getData(self.getHash())#.decode()
                      except AttributeError:
                          raise onionrexceptions.NoDataAvailable('Block does not exist')
                  else:
                      self.blockFile = None
                  # parse block
                  self.raw = blockdata
                  self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
                  self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
                  if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
                      self.bmetadata = self.getHeader('meta', None)
                      self.isEncrypted = True
                  else:
                      self.bmetadata = json.loads(self.getHeader('meta', None))
                  self.btype = self.getMetadata('type', None)
                  self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
                  # TODO: detect if signer is hash of pubkey or not
                  self.signer = self.getHeader('signer', None)
                  self.signature = self.getHeader('sig', None)
                  # signed data is jsonMeta + block content (no linebreak)
                  self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
                  self.date = blockmetadb.get_block_date(self.getHash())
                  self.claimedTime = self.getHeader('time', None)

                  if not self.getDate() is None:
                      self.date = datetime.datetime.fromtimestamp(self.getDate())

                  self.valid = True

                  if self.autoDecrypt:
                      self.decrypt()

                  return True
              except Exception as e:
                  logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)

                  # if block can't be parsed, it's a waste of precious space. Throw it away.
                  if not self.delete():
                      logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
                  else:
                      logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)

              self.valid = False
              return False

          def delete(self):
              '''
                  Deletes the block's file and records, if they exist

                  Outputs:
                  - (bool): whether or not the operation was successful
              '''

              if self.exists():
                  try:
                      os.remove(self.getBlockFile())
                  except TypeError:
                      pass
                  b_hash = self.getHash()
                  onionrstorage.deleteBlock(b_hash)
                  removeblock.remove_block(b_hash)
                  return True
              return False

          def save(self, sign = False, recreate = True):
              '''
                  Saves a block to file and imports it into Onionr

                  Inputs:
                  - sign (bool): whether or not to sign the block before saving
                  - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

                  Outputs:
                  - (bool): whether or not the operation was successful
              '''

              try:
                  if self.isValid() is True:

                      self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
                      if self.hash != False:
                          self.update()

                      return self.getHash()
                  else:
                      logger.warn('Not writing block; it is invalid.')
              except Exception as e:
                  logger.error('Failed to save block.', error = e, timestamp = False)

              return False

          # getters

          def getExpire(self):
              '''
                  Returns the expire time for a block

                  Outputs:
                  - (int): the expire time for a block, or None
              '''
              return self.expire

          def getHash(self):
              '''
                  Returns the hash of the block if saved to file

                  Outputs:
                  - (str): the hash of the block, or None
              '''

              return self.hash

          def getType(self):
              '''
                  Returns the type of the block

                  Outputs:
                  - (str): the type of the block
              '''
              return self.btype

          def getRaw(self):
              '''
                  Returns the raw contents of the block, if saved to file

                  Outputs:
                  - (bytes): the raw contents of the block, or None
              '''

              return self.raw

          def getHeader(self, key = None, default = None):
              '''
                  Returns the header information

                  Inputs:
                  - key (str): only returns the value of the key in the header

                  Outputs:
                  - (dict/str): either the whole header as a dict, or one value
              '''

              if not key is None:
                  if key in self.getHeader():
                      return self.getHeader()[key]
                  return default
              return self.bheader

          def getMetadata(self, key = None, default = None):
              '''
                  Returns the metadata information

                  Inputs:
                  - key (str): only returns the value of the key in the metadata

                  Outputs:
                  - (dict/str): either the whole metadata as a dict, or one value
              '''

              if not key is None:
                  if key in self.getMetadata():
                      return self.getMetadata()[key]
                  return default
              return self.bmetadata

          def getContent(self):
              '''
                  Returns the contents of the block

                  Outputs:
                  - (str): the contents of the block
              '''

              return str(self.bcontent)

          def getDate(self):
              '''
                  Returns the date that the block was received, if loaded from file

                  Outputs:
                  - (datetime): the date that the block was received
              '''

              return self.date

          def getBlockFile(self):
              '''
                  Returns the location of the block file if it is saved

                  Outputs:
                  - (str): the location of the block file, or None
              '''

              return self.blockFile

          def isValid(self):
              '''
                  Checks if the block is valid

                  Outputs:
                  - (bool): whether or not the block is valid
              '''

              return self.valid

          def isSigned(self):
              '''
                  Checks if the block was signed

                  Outputs:
                  - (bool): whether or not the block is signed
              '''

              return self.signed

          def getSignature(self):
              '''
                  Returns the base64-encoded signature

                  Outputs:
                  - (str): the signature, or None
              '''

              return self.signature

          def getSignedData(self):
              '''
                  Returns the data that was signed

                  Outputs:
                  - (str): the data that was signed, or None
              '''

              return self.signedData

          def isSigner(self, signer, encodedData = True):
              '''
                  Checks if the block was signed by the signer inputted

                  Inputs:
                  - signer (str): the public key of the signer to check against
                  - encodedData (bool): whether or not the `signer` argument is base64 encoded

                  Outputs:
                  - (bool): whether or not the signer of the block is the signer inputted
              '''

              try:
                  if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
                      return False

                  return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
              except:
                  return False

          # setters

          def setType(self, btype):
              '''
                  Sets the type of the block

                  Inputs:
                  - btype (str): the type of block to be set to

                  Outputs:
                  - (Block): the Block instance
              '''

              self.btype = btype
              return self

          def setMetadata(self, key, val):
              '''
                  Sets a custom metadata value

                  Metadata should not store block-specific data structures.

                  Inputs:
                  - key (str): the key
                  - val: the value (type is irrelevant)

                  Outputs:
                  - (Block): the Block instance
              '''

              self.bmetadata[key] = val
              return self

          def setContent(self, bcontent):
              '''
                  Sets the contents of the block

                  Inputs:
                  - bcontent (str): the contents to be set to

                  Outputs:
                  - (Block): the Block instance
              '''

              self.bcontent = str(bcontent)
              return self

          # static functions

          def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
              '''
                  Returns a list of Block objects based on supplied filters

                  Inputs:
                  - type (str): filters by block type
                  - signer (str/list): filters by signer (one in the list has to be a signer)
                  - signed (bool): filters out by whether or not the block is signed
                  - reverse (bool): reverses the list if True

                  Outputs:
                  - (list): a list of Block objects that match the input
              '''

              try:

                  relevant_blocks = list()
                  blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))

                  for block in blocks:
                      if Block.exists(block):
                          block = Block(block)

                          relevant = True

                          if (not signed is None) and (block.isSigned() != bool(signed)):
                              relevant = False
                          if not signer is None:
                              if isinstance(signer, (str,)):
                                  signer = [signer]
                              if isinstance(signer, (bytes,)):
                                  signer = [signer.decode()]

                              isSigner = False
                              for key in signer:
                                  if block.isSigner(key):
                                      isSigner = True
                                      break

                              if not isSigner:
                                  relevant = False

                          if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
                              relevant_blocks.append(block)

                  if bool(reverse):
                      relevant_blocks.reverse()

                  return relevant_blocks
              except Exception as e:
                  logger.debug('Failed to get blocks.', error = e)

              return list()

          def exists(bHash):
              '''
                  Checks if a block is saved to file or not

                  Inputs:
                  - hash (str/Block):
                    - if (Block): check if this block is saved to file
                    - if (str): check if a block by this hash is in file

                  Outputs:
                  - (bool): whether or not the block file exists
              '''

              # no input data? scrap it.
              if bHash is None:
                  return False

              if isinstance(bHash, Block):
                  bHash = bHash.getHash()

              ret = isinstance(onionrstorage.getData(bHash), type(None))

              return not ret

  ### Class variables

  `var blockCache`
  `var blockCacheOrder`

  ### Methods

  `def decrypt(self, encodedData=True)`

    Decrypt a block, loading decrypted data into their vars

    Source code

        def decrypt(self, encodedData = True):
            '''
                Decrypt a block, loading decrypted data into their vars
            '''

            if self.decrypted:
                return True
            retData = False
            # decrypt data
            if self.getHeader('encryptType') == 'asym':
                try:
                    try:
                        self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=encodedData)
                    except (binascii.Error, ValueError) as e:
                        self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=False)
                    bmeta = encryption.pub_key_decrypt(self.bmetadata, encodedData=encodedData)
                    try:
                        bmeta = bmeta.decode()
                    except AttributeError:
                        # yet another bytes fix
                        pass
                    self.bmetadata = json.loads(bmeta)
                    self.signature = encryption.pub_key_decrypt(self.signature, encodedData=encodedData)
                    self.signer = encryption.pub_key_decrypt(self.signer, encodedData=encodedData)
                    self.bheader['signer'] = self.signer.decode()
                    self.signedData =  json.dumps(self.bmetadata).encode() + self.bcontent

                    if not self.signer is None:
                        if not self.verifySig():
                            raise onionrexceptions.SignatureError("Block has invalid signature")

                    # Check for replay attacks
                    try:
                        if epoch.get_epoch() - blockmetadb.get_block_date(self.hash) > 60:
                            if not cryptoutils.replay_validator(self.bmetadata['rply']): raise onionrexceptions.ReplayAttack
                    except (AssertionError, KeyError, TypeError, onionrexceptions.ReplayAttack) as e:
                        if not self.bypassReplayCheck:
                            # Zero out variables to prevent reading of replays
                            self.bmetadata = {}
                            self.signer = ''
                            self.bheader['signer'] = ''
                            self.signedData = ''
                            self.signature = ''
                            raise onionrexceptions.ReplayAttack('Signature is too old. possible replay attack')
                    try:
                        if not self.bmetadata['forwardEnc']: raise KeyError
                    except (AssertionError, KeyError) as e:
                        pass
                    else:
                        try:
                            self.bcontent = onionrusers.OnionrUser(self.signer).forwardDecrypt(self.bcontent)
                        except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e:
                            #logger.error(str(e))
                            pass
                except nacl.exceptions.CryptoError:
                    pass
                    #logger.debug('Could not decrypt block. Either invalid key or corrupted data')
                except onionrexceptions.ReplayAttack:
                    logger.warn('%s is possibly a replay attack' % (self.hash,))
                else:
                    retData = True
                    self.decrypted = True
            return retData

  `def delete(self)`

    Deletes the block's file and records, if they exist

    Outputs: - (bool): whether or not the operation was successful

    Source code

        def delete(self):
            '''
                Deletes the block's file and records, if they exist

                Outputs:
                - (bool): whether or not the operation was successful
            '''

            if self.exists():
                try:
                    os.remove(self.getBlockFile())
                except TypeError:
                    pass
                b_hash = self.getHash()
                onionrstorage.deleteBlock(b_hash)
                removeblock.remove_block(b_hash)
                return True
            return False

  `def exists(bHash)`

    Checks if a block is saved to file or not

    Inputs: - hash (str/Block): - if (Block): check if this block is saved to file - if (str): check if a block by this hash is in file

    Outputs: - (bool): whether or not the block file exists

    Source code

        def exists(bHash):
            '''
                Checks if a block is saved to file or not

                Inputs:
                - hash (str/Block):
                  - if (Block): check if this block is saved to file
                  - if (str): check if a block by this hash is in file

                Outputs:
                - (bool): whether or not the block file exists
            '''

            # no input data? scrap it.
            if bHash is None:
                return False

            if isinstance(bHash, Block):
                bHash = bHash.getHash()

            ret = isinstance(onionrstorage.getData(bHash), type(None))

            return not ret

  `def getBlockFile(self)`

    Returns the location of the block file if it is saved

    Outputs: - (str): the location of the block file, or None

    Source code

        def getBlockFile(self):
            '''
                Returns the location of the block file if it is saved

                Outputs:
                - (str): the location of the block file, or None
            '''

            return self.blockFile

  `def getBlocks(type=None, signer=None, signed=None, reverse=False, limit=None)`

    Returns a list of Block objects based on supplied filters

    Inputs: - type (str): filters by block type - signer (str/list): filters by signer (one in the list has to be a signer) - signed (bool): filters out by whether or not the block is signed - reverse (bool): reverses the list if True

    Outputs: - (list): a list of Block objects that match the input

    Source code

        def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
            '''
                Returns a list of Block objects based on supplied filters

                Inputs:
                - type (str): filters by block type
                - signer (str/list): filters by signer (one in the list has to be a signer)
                - signed (bool): filters out by whether or not the block is signed
                - reverse (bool): reverses the list if True

                Outputs:
                - (list): a list of Block objects that match the input
            '''

            try:

                relevant_blocks = list()
                blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))

                for block in blocks:
                    if Block.exists(block):
                        block = Block(block)

                        relevant = True

                        if (not signed is None) and (block.isSigned() != bool(signed)):
                            relevant = False
                        if not signer is None:
                            if isinstance(signer, (str,)):
                                signer = [signer]
                            if isinstance(signer, (bytes,)):
                                signer = [signer.decode()]

                            isSigner = False
                            for key in signer:
                                if block.isSigner(key):
                                    isSigner = True
                                    break

                            if not isSigner:
                                relevant = False

                        if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
                            relevant_blocks.append(block)

                if bool(reverse):
                    relevant_blocks.reverse()

                return relevant_blocks
            except Exception as e:
                logger.debug('Failed to get blocks.', error = e)

            return list()

  `def getContent(self)`

    Returns the contents of the block

    Outputs: - (str): the contents of the block

    Source code

        def getContent(self):
            '''
                Returns the contents of the block

                Outputs:
                - (str): the contents of the block
            '''

            return str(self.bcontent)

  `def getDate(self)`

    Returns the date that the block was received, if loaded from file

    Outputs: - (datetime): the date that the block was received

    Source code

        def getDate(self):
            '''
                Returns the date that the block was received, if loaded from file

                Outputs:
                - (datetime): the date that the block was received
            '''

            return self.date

  `def getExpire(self)`

    Returns the expire time for a block

    Outputs: - (int): the expire time for a block, or None

    Source code

        def getExpire(self):
            '''
                Returns the expire time for a block

                Outputs:
                - (int): the expire time for a block, or None
            '''
            return self.expire

  `def getHash(self)`

    Returns the hash of the block if saved to file

    Outputs: - (str): the hash of the block, or None

    Source code

        def getHash(self):
            '''
                Returns the hash of the block if saved to file

                Outputs:
                - (str): the hash of the block, or None
            '''

            return self.hash

  `def getHeader(self, key=None, default=None)`

    Returns the header information

    Inputs: - key (str): only returns the value of the key in the header

    Outputs: - (dict/str): either the whole header as a dict, or one value

    Source code

        def getHeader(self, key = None, default = None):
            '''
                Returns the header information

                Inputs:
                - key (str): only returns the value of the key in the header

                Outputs:
                - (dict/str): either the whole header as a dict, or one value
            '''

            if not key is None:
                if key in self.getHeader():
                    return self.getHeader()[key]
                return default
            return self.bheader

  `def getMetadata(self, key=None, default=None)`

    Returns the metadata information

    Inputs: - key (str): only returns the value of the key in the metadata

    Outputs: - (dict/str): either the whole metadata as a dict, or one value

    Source code

        def getMetadata(self, key = None, default = None):
            '''
                Returns the metadata information

                Inputs:
                - key (str): only returns the value of the key in the metadata

                Outputs:
                - (dict/str): either the whole metadata as a dict, or one value
            '''

            if not key is None:
                if key in self.getMetadata():
                    return self.getMetadata()[key]
                return default
            return self.bmetadata

  `def getRaw(self)`

    Returns the raw contents of the block, if saved to file

    Outputs: - (bytes): the raw contents of the block, or None

    Source code

        def getRaw(self):
            '''
                Returns the raw contents of the block, if saved to file

                Outputs:
                - (bytes): the raw contents of the block, or None
            '''

            return self.raw

  `def getSignature(self)`

    Returns the base64-encoded signature

    Outputs: - (str): the signature, or None

    Source code

        def getSignature(self):
            '''
                Returns the base64-encoded signature

                Outputs:
                - (str): the signature, or None
            '''

            return self.signature

  `def getSignedData(self)`

    Returns the data that was signed

    Outputs: - (str): the data that was signed, or None

    Source code

        def getSignedData(self):
            '''
                Returns the data that was signed

                Outputs:
                - (str): the data that was signed, or None
            '''

            return self.signedData

  `def getType(self)`

    Returns the type of the block

    Outputs: - (str): the type of the block

    Source code

        def getType(self):
            '''
                Returns the type of the block

                Outputs:
                - (str): the type of the block
            '''
            return self.btype

  `def isSigned(self)`

    Checks if the block was signed

    Outputs: - (bool): whether or not the block is signed

    Source code

        def isSigned(self):
            '''
                Checks if the block was signed

                Outputs:
                - (bool): whether or not the block is signed
            '''

            return self.signed

  `def isSigner(self, signer, encodedData=True)`

    Checks if the block was signed by the signer inputted

    Inputs: - signer (str): the public key of the signer to check against - encodedData (bool): whether or not the `signer` argument is base64 encoded

    Outputs: - (bool): whether or not the signer of the block is the signer inputted

    Source code

        def isSigner(self, signer, encodedData = True):
            '''
                Checks if the block was signed by the signer inputted

                Inputs:
                - signer (str): the public key of the signer to check against
                - encodedData (bool): whether or not the `signer` argument is base64 encoded

                Outputs:
                - (bool): whether or not the signer of the block is the signer inputted
            '''

            try:
                if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
                    return False

                return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
            except:
                return False

  `def isValid(self)`

    Checks if the block is valid

    Outputs: - (bool): whether or not the block is valid

    Source code

        def isValid(self):
            '''
                Checks if the block is valid

                Outputs:
                - (bool): whether or not the block is valid
            '''

            return self.valid

  `def save(self, sign=False, recreate=True)`

    Saves a block to file and imports it into Onionr

    Inputs: - sign (bool): whether or not to sign the block before saving - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

    Outputs: - (bool): whether or not the operation was successful

    Source code

        def save(self, sign = False, recreate = True):
            '''
                Saves a block to file and imports it into Onionr

                Inputs:
                - sign (bool): whether or not to sign the block before saving
                - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

                Outputs:
                - (bool): whether or not the operation was successful
            '''

            try:
                if self.isValid() is True:

                    self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
                    if self.hash != False:
                        self.update()

                    return self.getHash()
                else:
                    logger.warn('Not writing block; it is invalid.')
            except Exception as e:
                logger.error('Failed to save block.', error = e, timestamp = False)

            return False

  `def setContent(self, bcontent)`

    Sets the contents of the block

    Inputs: - bcontent (str): the contents to be set to

    Outputs: - (Block): the Block instance

    Source code

        def setContent(self, bcontent):
            '''
                Sets the contents of the block

                Inputs:
                - bcontent (str): the contents to be set to

                Outputs:
                - (Block): the Block instance
            '''

            self.bcontent = str(bcontent)
            return self

  `def setMetadata(self, key, val)`

    Sets a custom metadata value

    Metadata should not store block-specific data structures.

    Inputs: - key (str): the key - val: the value (type is irrelevant)

    Outputs: - (Block): the Block instance

    Source code

        def setMetadata(self, key, val):
            '''
                Sets a custom metadata value

                Metadata should not store block-specific data structures.

                Inputs:
                - key (str): the key
                - val: the value (type is irrelevant)

                Outputs:
                - (Block): the Block instance
            '''

            self.bmetadata[key] = val
            return self

  `def setType(self, btype)`

    Sets the type of the block

    Inputs: - btype (str): the type of block to be set to

    Outputs: - (Block): the Block instance

    Source code

        def setType(self, btype):
            '''
                Sets the type of the block

                Inputs:
                - btype (str): the type of block to be set to

                Outputs:
                - (Block): the Block instance
            '''

            self.btype = btype
            return self

  `def update(self, data=None, file=None)`

    Loads data from a block in to the current object.

    Inputs: - data (str): - if None: will load from file by hash - else: will load from `data` string - file (str): - if None: will load from file specified in this parameter - else: will load from wherever block is stored by hash

    Outputs: - (bool): indicates whether or not the operation was successful

    Source code

        def update(self, data = None, file = None):
            '''
                Loads data from a block in to the current object.

                Inputs:
                - data (str):
                  - if None: will load from file by hash
                  - else: will load from `data` string
                - file (str):
                  - if None: will load from file specified in this parameter
                  - else: will load from wherever block is stored by hash

                Outputs:
                - (bool): indicates whether or not the operation was successful
            '''
            try:
                # import from string
                blockdata = data

                # import from file
                if blockdata is None:
                    try:
                        blockdata = onionrstorage.getData(self.getHash())#.decode()
                    except AttributeError:
                        raise onionrexceptions.NoDataAvailable('Block does not exist')
                else:
                    self.blockFile = None
                # parse block
                self.raw = blockdata
                self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
                self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
                if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
                    self.bmetadata = self.getHeader('meta', None)
                    self.isEncrypted = True
                else:
                    self.bmetadata = json.loads(self.getHeader('meta', None))
                self.btype = self.getMetadata('type', None)
                self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
                # TODO: detect if signer is hash of pubkey or not
                self.signer = self.getHeader('signer', None)
                self.signature = self.getHeader('sig', None)
                # signed data is jsonMeta + block content (no linebreak)
                self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
                self.date = blockmetadb.get_block_date(self.getHash())
                self.claimedTime = self.getHeader('time', None)

                if not self.getDate() is None:
                    self.date = datetime.datetime.fromtimestamp(self.getDate())

                self.valid = True

                if self.autoDecrypt:
                    self.decrypt()

                return True
            except Exception as e:
                logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)

                # if block can't be parsed, it's a waste of precious space. Throw it away.
                if not self.delete():
                    logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
                else:
                    logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)

            self.valid = False
            return False

  `def verifySig(self)`

    Verify if a block's signature is signed by its claimed signer

    Source code

        def verifySig(self):
            '''
                Verify if a block's signature is signed by its claimed signer
            '''
            if self.signer is None or signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
                self.validSig = True
            else:
                self.validSig = False
            return self.validSig