예제 #1
0
    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
예제 #2
0
    def test_create(self, time_mock):
        b = BlockHeader.create(dev_config=config.dev,
                               blocknumber=1,
                               prev_headerhash=b'headerhash',
                               prev_timestamp=10,
                               hashedtransactions=b'some_data',
                               fee_reward=1,
                               seed_height=0,
                               seed_hash=None)
        self.assertIsNotNone(b)

        b = BlockHeader.create(dev_config=config.dev,
                               blocknumber=1,
                               prev_headerhash=b'headerhash',
                               prev_timestamp=10,
                               hashedtransactions=b'some_data',
                               fee_reward=1,
                               seed_height=0,
                               seed_hash=None)
예제 #3
0
 def test_create_fails_when_prev_timestamp_is_negative(self, time_mock):
     # The only way to get it to fail in this mode is to pass a negative timestamp. Which should never happen IRL.
     time_mock.return_value = 0
     b = BlockHeader.create(config.dev,
                            1,
                            sha256(b'prev'),
                            -10,
                            sha256(b'txs'),
                            10,
                            seed_height=0,
                            seed_hash=None)
     self.assertIsNone(b)
예제 #4
0
    def test_GetLastBlockHeader(self):
        self.block_header_params["blocknumber"] = 20
        block_header = BlockHeader.create(**self.block_header_params)
        self.chain_manager.height = 200
        self.xrdnode.get_blockheader_and_metadata = MagicMock(
            return_value=[block_header, BlockMetadata()])

        req = xrdmining_pb2.GetLastBlockHeaderReq(height=20)
        answer = self.service.GetLastBlockHeader(request=req, context=None)

        self.assertEqual(180, answer.depth)
        self.assertEqual(20, answer.height)
예제 #5
0
    def setUp(self):
        with mock.patch('xrd.core.misc.ntp.getTime',
                        return_value=1615270948) as time_mock:
            self.block_header = BlockHeader.create(config.dev, 1,
                                                   sha256(b'prev'),
                                                   time_mock.return_value,
                                                   sha256(b'txs'), 10, 0, None)

        self.fee_reward = 10
        self.coinbase_amount = self.block_header.block_reward + self.fee_reward

        # this variable is for validate_parent_child_relation()
        self.m_parent_block = Mock(
            name='mock Parent Block',
            block_number=self.block_header.block_number - 1,
            headerhash=self.block_header.prev_headerhash,
            timestamp=self.block_header.timestamp - 1)
예제 #6
0
    def test_GetBlockMiningCompatible(self):
        block_header = BlockHeader.create(**self.block_header_params)
        self.xrdnode.get_blockheader_and_metadata = MagicMock(
            return_value=[block_header, BlockMetadata()])

        req = xrdmining_pb2.GetBlockMiningCompatibleReq(height=10)
        answer = self.service.GetBlockMiningCompatible(request=req,
                                                       context=None)

        self.assertEqual(10, answer.blockheader.block_number)
        self.assertEqual(1, answer.blockheader.reward_fee)

        # if xrdNode responds with None, None, the GRPC response should be blank too
        self.xrdnode.get_blockheader_and_metadata = MagicMock(
            return_value=[None, None])
        answer = self.service.GetBlockMiningCompatible(request=req,
                                                       context=None)
        self.assertEqual(0, answer.blockheader.block_number)
        self.assertEqual(0, answer.blockheader.reward_fee)
예제 #7
0
    def __init__(self, protobuf_block=None):
        self._data = protobuf_block
        if protobuf_block is None:
            self._data = xrd_pb2.Block()

        self.blockheader = BlockHeader(self._data.header)
예제 #8
0
class Block(object):
    def __init__(self, protobuf_block=None):
        self._data = protobuf_block
        if protobuf_block is None:
            self._data = xrd_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: xrd_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 = xrd_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 = xrd_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

        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 = xrd_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)
예제 #9
0
 def test_init(self, time_mock):
     block_header = BlockHeader()
     self.assertIsNotNone(block_header)  # just to avoid warnings
예제 #10
0
 def test_block_reward_calc_genesis_is_total_coin_supply(self, time_mock):
     genesis = BlockHeader.create(config.dev, 0,
                                  sha256(b'Random Scifi Book Title'),
                                  time_mock.return_value, sha256(b'txs'),
                                  10, 0, None)
     self.assertEqual(config.dev.supplied_coins, genesis.block_reward)
예제 #11
0
 def test_create_uses_prev_timestamp_when_genesis_block(self, time_mock):
     genesis = BlockHeader.create(config.dev, 0,
                                  sha256(b'Random Scifi Book Title'),
                                  time_mock.return_value, sha256(b'txs'),
                                  10, 0, None)
     self.assertEqual(genesis.timestamp, time_mock.return_value)