Ejemplo n.º 1
0
class Block(object):
    def __init__(self, protobuf_block=None):
        self._data = protobuf_block
        if protobuf_block is None:
            self._data = qrl_pb2.Block()

        self.blockheader = BlockHeader(self._data.header)

    @property
    def size(self):
        return self._data.ByteSize()

    @property
    def pbdata(self):
        """
        Returns a protobuf object that contains persistable data representing this object
        :return: A protobuf Block object
        :rtype: qrl_pb2.Block
        """
        return self._data

    @property
    def block_number(self):
        return self.blockheader.block_number

    @property
    def epoch(self):
        return int(self.block_number // config.dev.blocks_per_epoch)

    @property
    def headerhash(self):
        return self.blockheader.headerhash

    @property
    def prev_headerhash(self):
        return self.blockheader.prev_blockheaderhash

    @property
    def transactions(self):
        return self._data.transactions

    @property
    def mining_nonce(self):
        return self.blockheader.mining_nonce

    @property
    def PK(self):
        return self.blockheader.PK

    @property
    def block_reward(self):
        return self.blockheader.block_reward

    @property
    def fee_reward(self):
        return self.blockheader.fee_reward

    @property
    def timestamp(self):
        return self.blockheader.timestamp

    @property
    def mining_blob(self) -> bytes:
        return self.blockheader.mining_blob

    @property
    def mining_nonce_offset(self) -> bytes:
        return self.blockheader.nonce_offset

    @staticmethod
    def from_json(json_data):
        pbdata = qrl_pb2.Block()
        Parse(json_data, pbdata)
        return Block(pbdata)

    def verify_blob(self, blob: bytes) -> bool:
        return self.blockheader.verify_blob(blob)

    def set_mining_nonce(self, mining_nonce):
        self.blockheader.set_mining_nonce(mining_nonce)

    def to_json(self) -> str:
        # FIXME: Remove once we move completely to protobuf
        return MessageToJson(self._data)

    @staticmethod
    def create(block_number: int, prevblock_headerhash: bytes,
               transactions: list, signing_xmss: XMSS, master_address: bytes,
               nonce: int):

        block = Block()
        block._data.transactions.extend([qrl_pb2.Transaction()
                                         ])  # FIXME: Empty for coinbase?

        # Process transactions
        hashedtransactions = []
        fee_reward = 0

        for tx in transactions:
            fee_reward += tx.fee
            hashedtransactions.append(tx.txhash)
            block._data.transactions.extend(
                [tx.pbdata])  # copy memory rather than sym link

        if not hashedtransactions:
            hashedtransactions = [sha256(b'')]

        txs_hash = merkle_tx_hash(
            hashedtransactions)  # FIXME: Find a better name, type changes

        tmp_blockheader = BlockHeader.create(
            blocknumber=block_number,
            PK=signing_xmss.pk,
            prev_blockheaderhash=prevblock_headerhash,
            hashedtransactions=txs_hash,
            fee_reward=fee_reward)

        block._data.header.MergeFrom(tmp_blockheader.pbdata)

        # Prepare coinbase tx
        coinbase_tx = CoinBase.create(tmp_blockheader, signing_xmss,
                                      master_address)
        coinbase_tx.pbdata.nonce = nonce
        coinbase_tx.sign(signing_xmss)  # Sign after nonce has been set

        # Replace first tx
        block._data.transactions[0].CopyFrom(coinbase_tx.pbdata)

        return block

    def validate(self) -> bool:
        fee_reward = 0
        for index in range(1, len(self.transactions)):
            fee_reward += self.transactions[index].fee

        if len(self.transactions) == 0:
            return False

        try:
            coinbase_txn = Transaction.from_pbdata(self.transactions[0])
            coinbase_amount = coinbase_txn.amount
        except Exception as e:
            logger.warning('Exception %s', e)
            return False

        return self.blockheader.validate(fee_reward, coinbase_amount)

    def validate_parent_child_relation(self, parent_block) -> bool:
        return self.blockheader.validate_parent_child_relation(parent_block)

    def add_transaction(self, tx: Transaction):
        # TODO: Verify something basic here?
        self._data.transactions.extend(tx.pbdata)
Ejemplo n.º 2
0
class Block(object):
    def __init__(self, protobuf_block=None):
        self._data = protobuf_block
        if protobuf_block is None:
            self._data = qrl_pb2.Block()

        self.blockheader = BlockHeader(self._data.header)

    @property
    def size(self):
        return self._data.ByteSize()

    @property
    def pbdata(self):
        """
        Returns a protobuf object that contains persistable data representing this object
        :return: A protobuf Block object
        :rtype: qrl_pb2.Block
        """
        return self._data

    @property
    def block_number(self):
        return self.blockheader.block_number

    @property
    def epoch(self):
        return int(self.block_number // config.dev.blocks_per_epoch)

    @property
    def headerhash(self):
        return self.blockheader.headerhash

    @property
    def prev_headerhash(self):
        return self.blockheader.prev_blockheaderhash

    @property
    def transactions(self):
        return self._data.transactions

    @property
    def mining_nonce(self):
        return self.blockheader.mining_nonce

    @property
    def PK(self):
        return self.blockheader.PK

    @property
    def block_reward(self):
        return self.blockheader.block_reward

    @property
    def fee_reward(self):
        return self.blockheader.fee_reward

    @property
    def timestamp(self):
        return self.blockheader.timestamp

    @property
    def mining_blob(self) -> bytes:
        return self.blockheader.mining_blob

    @property
    def mining_nonce_offset(self) -> bytes:
        return self.blockheader.nonce_offset

    @staticmethod
    def from_json(json_data):
        pbdata = qrl_pb2.Block()
        Parse(json_data, pbdata)
        return Block(pbdata)

    def verify_blob(self, blob: bytes) -> bool:
        return self.blockheader.verify_blob(blob)

    def set_nonces(self, mining_nonce, extra_nonce=0):
        self.blockheader.set_nonces(mining_nonce, extra_nonce)

    def to_json(self) -> str:
        # FIXME: Remove once we move completely to protobuf
        return MessageToJson(self._data)

    @staticmethod
    def create(block_number: int, prevblock_headerhash: bytes,
               transactions: list, miner_address: bytes):

        block = Block()

        # Process transactions
        hashedtransactions = []
        fee_reward = 0

        for tx in transactions:
            fee_reward += tx.fee

        # Prepare coinbase tx
        total_reward_amount = BlockHeader.block_reward_calc(
            block_number) + fee_reward
        coinbase_tx = CoinBase.create(total_reward_amount, miner_address,
                                      block_number)
        hashedtransactions.append(coinbase_tx.txhash)
        block._data.transactions.extend([coinbase_tx.pbdata
                                         ])  # copy memory rather than sym link

        for tx in transactions:
            hashedtransactions.append(tx.txhash)
            block._data.transactions.extend(
                [tx.pbdata])  # copy memory rather than sym link

        txs_hash = merkle_tx_hash(
            hashedtransactions)  # FIXME: Find a better name, type changes

        tmp_blockheader = BlockHeader.create(
            blocknumber=block_number,
            prev_blockheaderhash=prevblock_headerhash,
            hashedtransactions=txs_hash,
            fee_reward=fee_reward)

        block._data.header.MergeFrom(tmp_blockheader.pbdata)

        block.set_nonces(0, 0)

        return block

    def update_mining_address(self, mining_address: bytes):
        self.transactions[0].update_mining_address(mining_address)
        hashedtransactions = []

        for tx in self.transactions:
            hashedtransactions.append(tx.transaction_hash)

        tmp_blockheader = BlockHeader.create(
            blocknumber=self.block_number,
            prev_blockheaderhash=self.prev_headerhash,
            hashedtransactions=merkle_tx_hash(hashedtransactions),
            fee_reward=self.fee_reward)

        self._data.header.MergeFrom(tmp_blockheader.pbdata)

    def validate(self) -> bool:
        fee_reward = 0
        for index in range(1, len(self.transactions)):
            fee_reward += self.transactions[index].fee

        if len(self.transactions) == 0:
            return False

        try:
            coinbase_txn = Transaction.from_pbdata(self.transactions[0])
            coinbase_amount = coinbase_txn.amount
        except Exception as e:
            logger.warning('Exception %s', e)
            return False

        return self.blockheader.validate(fee_reward, coinbase_amount)

    def validate_parent_child_relation(self, parent_block) -> bool:
        return self.blockheader.validate_parent_child_relation(parent_block)
Ejemplo n.º 3
0
class Block(object):
    def __init__(self, protobuf_block=None):
        self._data = protobuf_block
        if protobuf_block is None:
            self._data = qrl_pb2.Block()

        self.blockheader = BlockHeader(self._data.header)

    def __eq__(self, other):
        equality = (self.block_number == other.block_number) and (self.headerhash == other.headerhash) and (
                self.prev_headerhash == other.prev_headerhash) and (self.timestamp == other.timestamp) and (
                           self.mining_nonce == other.mining_nonce)
        return equality

    @property
    def size(self):
        return self._data.ByteSize()

    @property
    def pbdata(self):
        """
        Returns a protobuf object that contains persistable data representing this object
        :return: A protobuf Block object
        :rtype: qrl_pb2.Block
        """
        return self._data

    @property
    def block_number(self):
        return self.blockheader.block_number

    @property
    def headerhash(self):
        return self.blockheader.headerhash

    @property
    def prev_headerhash(self):
        return self.blockheader.prev_headerhash

    @property
    def transactions(self):
        return self._data.transactions

    @property
    def mining_nonce(self):
        return self.blockheader.mining_nonce

    @property
    def block_reward(self):
        return self.blockheader.block_reward

    @property
    def fee_reward(self):
        return self.blockheader.fee_reward

    @property
    def timestamp(self):
        return self.blockheader.timestamp

    def mining_blob(self, dev_config: DevConfig) -> bytes:
        return self.blockheader.mining_blob(dev_config)

    def mining_nonce_offset(self, dev_config: DevConfig) -> bytes:
        return self.blockheader.nonce_offset(dev_config)

    @staticmethod
    def from_json(json_data):
        pbdata = qrl_pb2.Block()
        Parse(json_data, pbdata)
        return Block(pbdata)

    def verify_blob(self, blob: bytes, dev_config: DevConfig) -> bool:
        return self.blockheader.verify_blob(blob, dev_config)

    def set_nonces(self, dev_config: DevConfig, mining_nonce: int, extra_nonce: int = 0):
        self.blockheader.set_nonces(dev_config, mining_nonce, extra_nonce)
        self._data.header.MergeFrom(self.blockheader.pbdata)

    def to_json(self) -> str:
        # FIXME: Remove once we move completely to protobuf
        return MessageToJson(self._data, sort_keys=True)

    def serialize(self) -> str:
        return self._data.SerializeToString()

    @staticmethod
    def deserialize(data):
        pbdata = qrl_pb2.Block()
        pbdata.ParseFromString(bytes(data))
        block = Block(pbdata)
        return block

    @staticmethod
    def _copy_tx_pbdata_into_block(block, tx):
        block._data.transactions.extend([tx.pbdata])

    @staticmethod
    def create(dev_config: DevConfig,
               block_number: int,
               prev_headerhash: bytes,
               prev_timestamp: int,
               transactions: list,
               miner_address: bytes,
               seed_height: Optional[int],
               seed_hash: Optional[bytes]):

        block = Block()

        # Process transactions
        hashedtransactions = []
        fee_reward = 0

        for tx in transactions:
            fee_reward += tx.fee

        # Prepare coinbase tx
        total_reward_amount = BlockHeader.block_reward_calc(block_number, dev_config) + fee_reward
        coinbase_tx = CoinBase.create(dev_config, total_reward_amount, miner_address, block_number)
        hashedtransactions.append(coinbase_tx.txhash)
        Block._copy_tx_pbdata_into_block(block, coinbase_tx)  # copy memory rather than sym link

        for tx in transactions:
            hashedtransactions.append(tx.txhash)
            Block._copy_tx_pbdata_into_block(block, tx)  # copy memory rather than sym link

        txs_hash = merkle_tx_hash(hashedtransactions)  # FIXME: Find a better name, type changes

        tmp_blockheader = BlockHeader.create(dev_config=dev_config,
                                             blocknumber=block_number,
                                             prev_headerhash=prev_headerhash,
                                             prev_timestamp=prev_timestamp,
                                             hashedtransactions=txs_hash,
                                             fee_reward=fee_reward,
                                             seed_height=seed_height,
                                             seed_hash=seed_hash)

        block.blockheader = tmp_blockheader

        block._data.header.MergeFrom(tmp_blockheader.pbdata)

        block.set_nonces(dev_config, 0, 0)

        return block

    def update_mining_address(self, dev_config: DevConfig, mining_address: bytes):
        coinbase_tx = Transaction.from_pbdata(self.transactions[0])
        coinbase_tx.update_mining_address(mining_address)
        hashedtransactions = []

        for tx in self.transactions:
            hashedtransactions.append(tx.transaction_hash)

        self.blockheader.update_merkle_root(dev_config, merkle_tx_hash(hashedtransactions))

        self._data.header.MergeFrom(self.blockheader.pbdata)

    def validate(self, chain_manager, future_blocks: OrderedDict) -> bool:
        if chain_manager.get_block_is_duplicate(self):
            logger.warning('Duplicate Block #%s %s', self.block_number, bin2hstr(self.headerhash))
            return False

        parent_block = chain_manager.get_block(self.prev_headerhash)
        dev_config = chain_manager.get_config_by_block_number(self.block_number)

        # If parent block not found in state, then check if its in the future block list
        if not parent_block:
            try:
                parent_block = future_blocks[self.prev_headerhash]
            except KeyError:
                logger.warning('Parent block not found')
                logger.warning('Parent block headerhash %s', bin2hstr(self.prev_headerhash))
                return False

        if not self._validate_parent_child_relation(parent_block):
            logger.warning('Failed to validate blocks parent child relation')
            return False

        if not chain_manager.validate_mining_nonce(self.blockheader, dev_config):
            logger.warning('Failed PoW Validation')
            return False

        if len(self.transactions) == 0:
            return False

        try:
            state_container = chain_manager.new_state_container(set(),
                                                                self.block_number,
                                                                False,
                                                                None)

            coinbase_txn = Transaction.from_pbdata(self.transactions[0])
            coinbase_amount = coinbase_txn.amount

            if not coinbase_txn.validate_all(state_container):
                return False

            if self.block_number != 2078158:
                for proto_tx in self.transactions[1:]:
                    if proto_tx.WhichOneof('transactionType') == 'coinbase':
                        logger.warning("Multiple coinbase transaction found")
                        return False

        except Exception as e:
            logger.warning('Exception %s', e)
            return False

        # Build transaction merkle tree, calculate fee reward, and then see if BlockHeader also agrees.
        hashedtransactions = []

        for tx in self.transactions:
            tx = Transaction.from_pbdata(tx)
            hashedtransactions.append(tx.txhash)

        fee_reward = 0
        for index in range(1, len(self.transactions)):
            fee_reward += self.transactions[index].fee

        qn = Qryptonight()
        seed_block = chain_manager.get_block_by_number(qn.get_seed_height(self.block_number))

        self.blockheader._seed_height = seed_block.block_number
        self.blockheader._seed_hash = seed_block.headerhash

        if not self.blockheader.validate(fee_reward,
                                         coinbase_amount,
                                         merkle_tx_hash(hashedtransactions),
                                         dev_config):
            return False

        return True

    def is_future_block(self, dev_config: DevConfig) -> bool:
        if self.timestamp > ntp.getTime() + dev_config.block_max_drift:
            return True

        return False

    def _validate_parent_child_relation(self, parent_block) -> bool:
        return self.blockheader.validate_parent_child_relation(parent_block)

    @staticmethod
    def put_block(state: State, block, batch):
        state._db.put_raw(block.headerhash, block.serialize(), batch)

    @staticmethod
    def get_block(state: State, header_hash: bytes):
        try:
            data = state._db.get_raw(header_hash)
            return Block.deserialize(data)
        except KeyError:
            logger.debug('[get_block] Block header_hash %s not found', bin2hstr(header_hash).encode())
        except Exception as e:
            logger.error('[get_block] %s', e)

        return None

    @staticmethod
    def get_block_size_limit(state: State, block, dev_config: DevConfig):
        # NOTE: Miner
        block_size_list = []
        for _ in range(0, 10):
            block = Block.get_block(state, block.prev_headerhash)
            if not block:
                return None
            block_size_list.append(block.size)
            if block.block_number == 0:
                break
        return max(dev_config.block_min_size_limit_in_bytes, dev_config.size_multiplier * median(block_size_list))

    @staticmethod
    def remove_blocknumber_mapping(state: State, block_number, batch):
        state._db.delete(str(block_number).encode(), batch)

    @staticmethod
    def put_block_number_mapping(state: State, block_number: int, block_number_mapping, batch):
        state._db.put_raw(str(block_number).encode(), MessageToJson(block_number_mapping, sort_keys=True).encode(), batch)

    @staticmethod
    def get_block_number_mapping(state: State, block_number: int):
        try:
            data = state._db.get_raw(str(block_number).encode())
            block_number_mapping = qrl_pb2.BlockNumberMapping()
            return Parse(data, block_number_mapping)
        except KeyError:
            logger.debug('[get_block_number_mapping] Block #%s not found', block_number)
        except Exception as e:
            logger.error('[get_block_number_mapping] %s', e)

        return None

    @staticmethod
    def get_block_by_number(state: State, block_number: int):
        block_number_mapping = Block.get_block_number_mapping(state, block_number)
        if not block_number_mapping:
            return None
        return Block.get_block(state, block_number_mapping.headerhash)

    @staticmethod
    def get_block_header_hash_by_number(state: State, block_number: int):
        block_number_mapping = Block.get_block_number_mapping(state, block_number)
        if not block_number_mapping:
            return None
        return block_number_mapping.headerhash

    @staticmethod
    def last_block(state: State):
        block_number = state.get_mainchain_height()
        return Block.get_block_by_number(state, block_number)
Ejemplo n.º 4
0
class Block(object):
    def __init__(self, protobuf_block=None):
        self._data = protobuf_block
        if protobuf_block is None:
            self._data = qrl_pb2.Block()

        self.blockheader = BlockHeader(self._data.header)

    @property
    def size(self):
        return self._data.ByteSize()

    @property
    def pbdata(self):
        """
        Returns a protobuf object that contains persistable data representing this object
        :return: A protobuf Block object
        :rtype: qrl_pb2.Block
        """
        return self._data

    @property
    def block_number(self):
        return self.blockheader.block_number

    @property
    def epoch(self):
        return int(self.block_number // config.dev.blocks_per_epoch)

    @property
    def headerhash(self):
        return self.blockheader.headerhash

    @property
    def prev_headerhash(self):
        return self.blockheader.prev_blockheaderhash

    @property
    def transactions(self):
        return self._data.transactions

    @property
    def mining_nonce(self):
        return self.blockheader.mining_nonce

    @property
    def PK(self):
        return self.blockheader.PK

    @property
    def block_reward(self):
        return self.blockheader.block_reward

    @property
    def fee_reward(self):
        return self.blockheader.fee_reward

    @property
    def timestamp(self):
        return self.blockheader.timestamp

    @property
    def mining_blob(self) -> bytes:
        return self.blockheader.mining_blob

    @property
    def mining_nonce_offset(self) -> bytes:
        return self.blockheader.nonce_offset

    @staticmethod
    def from_json(json_data):
        pbdata = qrl_pb2.Block()
        Parse(json_data, pbdata)
        return Block(pbdata)

    def verify_blob(self, blob: bytes) -> bool:
        return self.blockheader.verify_blob(blob)

    def set_nonces(self, mining_nonce, extra_nonce=0):
        self.blockheader.set_nonces(mining_nonce, extra_nonce)
        self._data.header.MergeFrom(self.blockheader.pbdata)

    def to_json(self) -> str:
        # FIXME: Remove once we move completely to protobuf
        return MessageToJson(self._data, sort_keys=True)

    @staticmethod
    def create(block_number: int, prev_block_headerhash: bytes,
               prev_block_timestamp: int, transactions: list,
               miner_address: bytes):

        block = Block()

        # Process transactions
        hashedtransactions = []
        fee_reward = 0

        for tx in transactions:
            fee_reward += tx.fee

        # Prepare coinbase tx
        total_reward_amount = BlockHeader.block_reward_calc(
            block_number) + fee_reward
        coinbase_tx = CoinBase.create(total_reward_amount, miner_address,
                                      block_number)
        hashedtransactions.append(coinbase_tx.txhash)
        block._data.transactions.extend([coinbase_tx.pbdata
                                         ])  # copy memory rather than sym link

        for tx in transactions:
            hashedtransactions.append(tx.txhash)
            block._data.transactions.extend(
                [tx.pbdata])  # copy memory rather than sym link

        txs_hash = merkle_tx_hash(
            hashedtransactions)  # FIXME: Find a better name, type changes

        tmp_blockheader = BlockHeader.create(
            blocknumber=block_number,
            prev_block_headerhash=prev_block_headerhash,
            prev_block_timestamp=prev_block_timestamp,
            hashedtransactions=txs_hash,
            fee_reward=fee_reward)

        block.blockheader = tmp_blockheader

        block._data.header.MergeFrom(tmp_blockheader.pbdata)

        block.set_nonces(0, 0)

        return block

    def update_mining_address(self, mining_address: bytes):
        coinbase_tx = Transaction.from_pbdata(self.transactions[0])
        coinbase_tx.update_mining_address(mining_address)
        hashedtransactions = [coinbase_tx.txhash]

        for tx in self.transactions:
            hashedtransactions.append(tx.transaction_hash)

        self.blockheader.update_merkle_root(merkle_tx_hash(hashedtransactions))

        self._data.header.MergeFrom(self.blockheader.pbdata)

    def validate(self, state, future_blocks: OrderedDict) -> bool:
        if self.is_duplicate(state):
            logger.warning('Duplicate Block #%s %s', self.block_number,
                           bin2hstr(self.headerhash))
            return False

        parent_block = state.get_block(self.prev_headerhash)

        # If parent block not found in state, then check if its in the future block list
        if not parent_block:
            try:
                parent_block = future_blocks[self.prev_headerhash]
            except KeyError:
                logger.warning('Parent block not found')
                logger.warning('Parent block headerhash %s',
                               bin2hstr(self.prev_headerhash))
                return False

        if not self.validate_parent_child_relation(parent_block):
            logger.warning('Failed to validate blocks parent child relation')
            return False

        if not PoWValidator().validate_mining_nonce(state, self.blockheader):
            logger.warning('Failed PoW Validation')
            return False

        fee_reward = 0
        for index in range(1, len(self.transactions)):
            fee_reward += self.transactions[index].fee

        if len(self.transactions) == 0:
            return False

        try:
            coinbase_txn = Transaction.from_pbdata(self.transactions[0])
            coinbase_amount = coinbase_txn.amount

            if not coinbase_txn.validate_extended():
                return False

        except Exception as e:
            logger.warning('Exception %s', e)
            return False

        if not self.blockheader.validate(fee_reward, coinbase_amount):
            return False

        return True

    def apply_state_changes(self, address_txn) -> bool:
        coinbase_tx = Transaction.from_pbdata(self.transactions[0])

        if not coinbase_tx.validate_extended():
            logger.warning('Coinbase transaction failed')
            return False

        coinbase_tx.apply_state_changes(address_txn)

        len_transactions = len(self.transactions)
        for tx_idx in range(1, len_transactions):
            tx = Transaction.from_pbdata(self.transactions[tx_idx])

            if isinstance(tx, CoinBase):
                logger.warning('Found another coinbase transaction')
                return False

            if not tx.validate():
                return False

            addr_from_pk_state = address_txn[tx.addr_from]
            addr_from_pk = Transaction.get_slave(tx)
            if addr_from_pk:
                addr_from_pk_state = address_txn[addr_from_pk]

            if not tx.validate_extended(address_txn[tx.addr_from],
                                        addr_from_pk_state):
                return False

            expected_nonce = addr_from_pk_state.nonce + 1

            if tx.nonce != expected_nonce:
                logger.warning('nonce incorrect, invalid tx')
                logger.warning('subtype: %s', tx.type)
                logger.warning('%s actual: %s expected: %s', tx.addr_from,
                               tx.nonce, expected_nonce)
                return False

            if addr_from_pk_state.ots_key_reuse(tx.ots_key):
                logger.warning('pubkey reuse detected: invalid tx %s',
                               bin2hstr(tx.txhash))
                logger.warning('subtype: %s', tx.type)
                return False

            tx.apply_state_changes(address_txn)

        return True

    def is_duplicate(self, state) -> bool:
        if state.get_block(self.headerhash):
            return True

        return False

    def is_future_block(self) -> bool:
        if self.timestamp > ntp.getTime() + config.dev.block_max_drift:
            return True

        return False

    def validate_parent_child_relation(self, parent_block) -> bool:
        return self.blockheader.validate_parent_child_relation(parent_block)
Ejemplo n.º 5
0
Archivo: Block.py Proyecto: fanff/QRL
class Block(object):
    def __init__(self, protobuf_block=None):
        self._data = protobuf_block
        if protobuf_block is None:
            self._data = qrl_pb2.Block()

        self.blockheader = BlockHeader(self._data.header)

    @property
    def size(self):
        return self._data.ByteSize()

    @property
    def pbdata(self):
        """
        Returns a protobuf object that contains persistable data representing this object
        :return: A protobuf Block object
        :rtype: qrl_pb2.Block
        """
        return self._data

    @property
    def block_number(self):
        return self.blockheader.block_number

    @property
    def epoch(self):
        return int(self.block_number // config.dev.blocks_per_epoch)

    @property
    def headerhash(self):
        return self.blockheader.headerhash

    @property
    def prev_headerhash(self):
        return self.blockheader.prev_blockheaderhash

    @property
    def transactions(self):
        return self._data.transactions

    @property
    def mining_nonce(self):
        return self.blockheader.mining_nonce

    @property
    def PK(self):
        return self.blockheader.PK

    @property
    def block_reward(self):
        return self.blockheader.block_reward

    @property
    def timestamp(self):
        return self.blockheader.timestamp

    @property
    def mining_hash(self):
        return self.blockheader.mining_hash

    @staticmethod
    def from_json(json_data):
        pbdata = qrl_pb2.Block()
        Parse(json_data, pbdata)
        return Block(pbdata)

    def set_mining_nonce(self, mining_nonce):
        self.blockheader.set_mining_nonce(mining_nonce)

    def to_json(self)->str:
        # FIXME: Remove once we move completely to protobuf
        return MessageToJson(self._data)

    @staticmethod
    def create(mining_nonce: int,
               block_number: int,
               prevblock_headerhash: bytes,
               transactions: list,
               signing_xmss: XMSS,
               master_address: bytes,
               nonce: int):

        block = Block()
        block._data.transactions.extend([qrl_pb2.Transaction()])  # FIXME: Empty for coinbase?

        # Process transactions
        hashedtransactions = []
        fee_reward = 0

        for tx in transactions:
            fee_reward += tx.fee
            hashedtransactions.append(tx.txhash)
            block._data.transactions.extend([tx.pbdata])  # copy memory rather than sym link

        if not hashedtransactions:
            hashedtransactions = [sha256(b'')]

        txs_hash = merkle_tx_hash(hashedtransactions)           # FIXME: Find a better name, type changes

        tmp_blockheader = BlockHeader.create(blocknumber=block_number,
                                             mining_nonce=mining_nonce,
                                             PK=signing_xmss.pk(),
                                             prev_blockheaderhash=prevblock_headerhash,
                                             hashedtransactions=txs_hash,
                                             fee_reward=fee_reward)

        block._data.header.MergeFrom(tmp_blockheader.pbdata)

        # Prepare coinbase tx
        coinbase_tx = CoinBase.create(tmp_blockheader, signing_xmss, master_address)
        coinbase_tx.pbdata.nonce = nonce
        coinbase_tx.sign(signing_xmss)  # Sign after nonce has been set

        # Replace first tx
        block._data.transactions[0].CopyFrom(coinbase_tx.pbdata)

        return block

    def validate(self) -> bool:
        fee_reward = 0
        for index in range(1, len(self.transactions)):
            fee_reward += self.transactions[index].fee

        if len(self.transactions) == 0:
            return False

        try:
            coinbase_txn = Transaction.from_pbdata(self.transactions[0])
            coinbase_amount = coinbase_txn.amount
        except Exception as e:
            logger.warning('Exception %s', e)
            return False

        return self.blockheader.validate(fee_reward, coinbase_amount)

    def validate_parent_child_relation(self, parent_block) -> bool:
        return self.blockheader.validate_parent_child_relation(parent_block)

    def add_transaction(self, tx: Transaction):
        # TODO: Verify something basic here?
        self._data.transactions.extend(tx.pbdata)