<commit_before># coding=utf-8
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import sys
import threading
from collections import OrderedDict
from typing import Optional, Tuple
from math import ceil

import functools
from pyqrllib.pyqrllib import bin2hstr
from pyqryptonight.pyqryptonight import StringToUInt256, UInt256ToString

from qrl.core import config, BlockHeader
from qrl.core.config import DevConfig
from qrl.core.formulas import block_reward
from qrl.core.StateContainer import StateContainer
from qrl.core.StateMigration import StateMigration
from qrl.core.AddressState import AddressState
from qrl.core.VoteStats import VoteStats
from qrl.core.LastTransactions import LastTransactions
from qrl.core.TransactionMetadata import TransactionMetadata
from qrl.core.OptimizedAddressState import OptimizedAddressState
from qrl.core.PaginatedData import PaginatedData
from qrl.core.PaginatedBitfield import PaginatedBitfield
from qrl.core.Indexer import Indexer
from qrl.core.Block import Block
from qrl.core.BlockMetadata import BlockMetadata
from qrl.core.DifficultyTracker import DifficultyTracker
from qrl.core.GenesisBlock import GenesisBlock
from qrl.core.MultiSigAddressState import MultiSigAddressState
from qrl.core.PoWValidator import PoWValidator
from qrl.core.txs.Transaction import Transaction
from qrl.core.txs.CoinBase import CoinBase
from qrl.core.txs.LatticeTransaction import LatticeTransaction
from qrl.core.txs.SlaveTransaction import SlaveTransaction
from qrl.core.txs.TransferTokenTransaction import TransferTokenTransaction
from qrl.core.txs.multisig.MultiSigVote import MultiSigVote
from qrl.core.TransactionPool import TransactionPool
from qrl.core.misc import logger
from qrl.crypto.Qryptonight import Qryptonight
from qrl.generated import qrl_pb2, qrlstateinfo_pb2


class ChainManager:
    def __init__(self, state):
        self._state = state
        self.tx_pool = TransactionPool(None)
        self._last_block = Block.deserialize(GenesisBlock().serialize())
        self.current_difficulty = StringToUInt256(str(config.user.genesis_difficulty))

        self.trigger_miner = False
        self.lock = threading.RLock()

    @property
    def re_org_limit(self):
        with self.lock:
            return self._state.get_re_org_limit()

    @property
    def height(self):
        with self.lock:
            if not self._last_block:
                return -1
            return self._last_block.block_number

    @property
    def last_block(self) -> Block:
        with self.lock:
            return self._last_block

    @property
    def total_coin_supply(self):
        with self.lock:
            return self._state.total_coin_supply

    def get_cumulative_difficulty(self):
        with self.lock:
            last_block_metadata = BlockMetadata.get_block_metadata(self._state,
                                                                   self._last_block.headerhash)
            return last_block_metadata.cumulative_difficulty

    def get_block_by_number(self, block_number) -> Optional[Block]:
        with self.lock:
            return Block.get_block_by_number(self._state, block_number)

    def get_block_header_hash_by_number(self, block_number) -> Optional[bytes]:
        with self.lock:
            return Block.get_block_header_hash_by_number(self._state,
                                                         block_number)

    def get_block(self, header_hash: bytes) -> Optional[Block]:
        with self.lock:
            return Block.get_block(self._state, header_hash)

    def get_address_balance(self, address: bytes) -> int:
        with self.lock:
            return self.get_optimized_address_state(address).balance

    def get_address_is_used(self, address: bytes) -> bool:
        with self.lock:
            return self._state.get_address_is_used(address)

    def get_address_state(self,
                          address: bytes,
                          exclude_ots_bitfield: bool = False,
                          exclude_transaction_hashes: bool = False) -> AddressState:
        """
        Transform Optimized Address State into Older Address State format
        This should only be used by API.
        """
        optimized_address_state = self.get_optimized_address_state(address)
        ots_bitfield = []
        transaction_hashes = list()
        tokens = OrderedDict()
        slave_pks_access_type = OrderedDict()

        max_bitfield_page = ceil((2 ** optimized_address_state.height) / config.dev.ots_tracking_per_page)
        if not exclude_ots_bitfield:
            ots_bitfield = [b'\x00'] * max(1024, int(ceil((2 ** optimized_address_state.height) / 8)))
            for page in range(1, max_bitfield_page + 1):
                offset = (page - 1) * config.dev.ots_tracking_per_page
                page_data = self.get_bitfield(address, page)
                for data in page_data:
                    if offset >= len(ots_bitfield):
                        break
                    ots_bitfield[offset] = data
                    offset += 1

        if not exclude_transaction_hashes:
            max_transaction_hash_page = ceil(optimized_address_state.transaction_hash_count() / config.dev.data_per_page)

            for page in range(0, max_transaction_hash_page + 1):
                page_data = self.get_transaction_hashes(address, page * config.dev.data_per_page)
                transaction_hashes.extend(page_data)

            max_token_page = ceil(optimized_address_state.tokens_count() / config.dev.data_per_page)

            for page in range(0, max_token_page + 1):
                page_data = self.get_token_transaction_hashes(address, page * config.dev.data_per_page)
                for token_txn_hash in page_data:
                    token_balance = self.get_token(address, token_txn_hash)
                    # token_balance None is only possible when the token transaction
                    # is done by a QRL address as an owner, which has not been
                    # assigned any token balance.
                    if token_balance is None:
                        continue
                    tokens[token_txn_hash] = token_balance.balance

            max_slave_page = ceil(optimized_address_state.slaves_count() / config.dev.data_per_page)

            for page in range(0, max_slave_page + 1):
                page_data = self.get_slave_transaction_hashes(address, page * config.dev.data_per_page)
                for slave_txn_hash in page_data:
                    tx, _ = self.get_tx_metadata(slave_txn_hash)
                    for slave_pk in tx.slave_pks:
                        slave_meta_data = self.get_slave_pk_access_type(address, slave_pk)
                        slave_pks_access_type[str(slave_pk)] = slave_meta_data.access_type

        addr_state = AddressState.create(address=optimized_address_state.address,
                                         nonce=optimized_address_state.nonce,
                                         balance=optimized_address_state.balance,
                                         ots_bitfield=ots_bitfield,
                                         tokens=tokens,
                                         slave_pks_access_type=slave_pks_access_type,
                                         ots_counter=0)
        addr_state.transaction_hashes.extend(transaction_hashes)

        return addr_state

    def get_optimized_address_state(self, address: bytes) -> OptimizedAddressState:
        with self.lock:
            return OptimizedAddressState.get_optimized_address_state(self._state, address)

    def get_multi_sig_address_state(self, address: bytes) -> MultiSigAddressState:
        with self.lock:
            return MultiSigAddressState.get_multi_sig_address_state_by_address(self._state._db, address)

    def get_bitfield(self, address: bytes, page: int):
        with self.lock:
            p = PaginatedBitfield(False, self._state._db)
            return p.get_paginated_data(address, page)

    def is_slave(self, master_address: bytes, slave_pk: bytes) -> bool:
        with self.lock:
            slave_meta_data = self._state.get_slave_pk_access_type(master_address, slave_pk)
            if slave_meta_data:
                return slave_meta_data.access_type == 0
            return False

    def get_slave_pk_access_type(self, address: bytes, slave_pk: bytes) -> qrl_pb2.SlaveMetadata:
        with self.lock:
            return self._state.get_slave_pk_access_type(address, slave_pk)

    def get_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_tx_hash', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_multi_sig_spend_txn_hashes(self, multi_sig_address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_multi_sig_spend', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(multi_sig_address, item_index)

    def get_token_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_tokens', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_slave_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_slaves', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_lattice_pks_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_lattice_pk', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_multi_sig_addresses(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_multisig_address', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_inbox_message_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_inbox_message', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_vote_stats(self, multi_sig_spend_txn_hash: bytes) -> VoteStats:
        with self.lock:
            return VoteStats.get_state(state=self._state, shared_key=multi_sig_spend_txn_hash)

    def get_token(self, address: bytes, token_txhash: bytes) -> qrl_pb2.TokenBalance:
        with self.lock:
            return self._state.get_token(address, token_txhash)

    def validate_all(self, tx: Transaction, check_nonce: bool) -> bool:
        with self.lock:
            addresses_set = set()
            tx.set_affected_address(addresses_set)
            state_container = self.new_state_container(addresses_set, self.height, True, None)
            if state_container is None:
                return False
            if not self.update_state_container(tx, state_container):
                return False
            return tx.validate_all(state_container, check_nonce)

    def validate_tx(self,
                    tx: Transaction,
                    addresses_state: dict,
                    addresses_bitfield: dict,
                    tokens: dict,
                    slaves: dict) -> bool:
        with self.lock:
            return self._state.validate_tx(tx, addresses_state, addresses_bitfield, tokens, slaves)

    def apply_txn(self, tx: Transaction, state_container: StateContainer) -> bool:
        with self.lock:
            return tx.apply(self._state, state_container)

    def get_tx_metadata(self, transaction_hash) -> list:
        with self.lock:
            return TransactionMetadata.get_tx_metadata(self._state, transaction_hash)

    def get_last_transactions(self):
        with self.lock:
            return LastTransactions.get_last_txs(self._state)

    def get_unconfirmed_transaction(self, transaction_hash) -> list:
        with self.lock:
            for tx_set in self.tx_pool.transactions:
                tx = tx_set[1].transaction
                if tx.txhash == transaction_hash:
                    return [tx, tx_set[1].timestamp]
            if transaction_hash in self.tx_pool.pending_tx_pool_hash:
                for tx_set in self.tx_pool.pending_tx_pool:
                    tx = tx_set[1].transaction
                    if tx.txhash == transaction_hash:
                        return [tx, tx_set[1].timestamp]

            return []

    def get_block_metadata(self, header_hash: bytes) -> Optional[BlockMetadata]:
        with self.lock:
            return BlockMetadata.get_block_metadata(self._state, header_hash)

    def get_blockheader_and_metadata(self, block_number=0) -> Tuple:
        with self.lock:
            block_number = block_number or self.height  # if both are non-zero, then block_number takes priority

            result = (None, None)
            block = self.get_block_by_number(block_number)
            if block:
                blockheader = block.blockheader
                blockmetadata = self.get_block_metadata(blockheader.headerhash)
                result = (blockheader, blockmetadata)

            return result

    def get_block_to_mine(self, miner, wallet_address) -> list:
        with miner.lock:  # Trying to acquire miner.lock to make sure pre_block_logic is not running
            with self.lock:
                last_block = self.last_block
                last_block_metadata = self.get_block_metadata(last_block.headerhash)
                return miner.get_block_to_mine(wallet_address,
                                               self.tx_pool,
                                               last_block,
                                               last_block_metadata.block_difficulty)

    def get_block_size_limit(self, block: Block, dev_config: DevConfig):
        with self.lock:
            return Block.get_block_size_limit(self._state, block, dev_config)

    def get_block_is_duplicate(self, block: Block) -> bool:
        with self.lock:
            return block.get_block(self._state, block.headerhash) is not None

    def get_config_by_block_number(self, block_number: int) -> config.DevConfig:
        dev_config = config.dev
        if block_number == 0:
            return dev_config

        with self.lock:
            while dev_config.activation_block_number >= block_number:
                dev_config_pb_data = self._state.get_dev_config_state(dev_config.prev_state_key)
                dev_config = config.DevConfig(dev_config_pb_data, True, True)
            return dev_config

    def validate_mining_nonce(self, blockheader: BlockHeader, dev_config: config.DevConfig, enable_logging=True):
        with self.lock:
            parent_metadata = BlockMetadata.get_block_metadata(self._state, blockheader.prev_headerhash)
            parent_block = Block.get_block(self._state, blockheader.prev_headerhash)

            measurement = self.get_measurement(dev_config,
                                               blockheader.timestamp,
                                               blockheader.prev_headerhash,
                                               parent_metadata)
            diff, target = DifficultyTracker.get(
                measurement=measurement,
                parent_difficulty=parent_metadata.block_difficulty,
                dev_config=dev_config)

            mining_blob = blockheader.mining_blob(dev_config)

            if enable_logging:
                logger.debug('-----------------START--------------------')
                logger.debug('Validate                #%s', blockheader.block_number)
                logger.debug('block.timestamp         %s', blockheader.timestamp)
                logger.debug('parent_block.timestamp  %s', parent_block.timestamp)
                logger.debug('parent_block.difficulty %s', UInt256ToString(parent_metadata.block_difficulty))
                logger.debug('diff                    %s', UInt256ToString(diff))
                logger.debug('target                  %s', bin2hstr(target))
                logger.debug('mining blob             %s', bin2hstr(mining_blob))
                logger.debug('-------------------END--------------------')

            qn = Qryptonight()
            seed_block = self.get_block_by_number(qn.get_seed_height(blockheader.block_number))
            if not PoWValidator().verify_input(blockheader.block_number,
                                               seed_block.block_number,
                                               seed_block.headerhash,
                                               mining_blob,
                                               target):
                if enable_logging:
                    logger.warning("PoW verification failed")
                    tmp_hash = qn.hash(blockheader.block_number,
                                       seed_block.block_number,
                                       seed_block.headerhash,
                                       blockheader.mining_blob(dev_config))
                    logger.warning("{}".format(bin2hstr(tmp_hash)))
                    logger.debug('%s', blockheader.to_json())
                return False

            return True

    def get_headerhashes(self, start_blocknumber):
        with self.lock:
            start_blocknumber = max(0, start_blocknumber)
            end_blocknumber = min(self._last_block.block_number,
                                  start_blocknumber + 2 * config.dev.reorg_limit)

            total_expected_headerhash = end_blocknumber - start_blocknumber + 1

            node_header_hash = qrl_pb2.NodeHeaderHash()
            node_header_hash.block_number = start_blocknumber<commit_msg>Added code to get seed_block for alt chain<commit_after># coding=utf-8
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import sys
import threading
from collections import OrderedDict
from typing import Optional, Tuple
from math import ceil

import functools
from pyqrllib.pyqrllib import bin2hstr
from pyqryptonight.pyqryptonight import StringToUInt256, UInt256ToString

from qrl.core import config, BlockHeader
from qrl.core.config import DevConfig
from qrl.core.formulas import block_reward
from qrl.core.StateContainer import StateContainer
from qrl.core.StateMigration import StateMigration
from qrl.core.AddressState import AddressState
from qrl.core.VoteStats import VoteStats
from qrl.core.LastTransactions import LastTransactions
from qrl.core.TransactionMetadata import TransactionMetadata
from qrl.core.OptimizedAddressState import OptimizedAddressState
from qrl.core.PaginatedData import PaginatedData
from qrl.core.PaginatedBitfield import PaginatedBitfield
from qrl.core.Indexer import Indexer
from qrl.core.Block import Block
from qrl.core.BlockMetadata import BlockMetadata
from qrl.core.DifficultyTracker import DifficultyTracker
from qrl.core.GenesisBlock import GenesisBlock
from qrl.core.MultiSigAddressState import MultiSigAddressState
from qrl.core.PoWValidator import PoWValidator
from qrl.core.txs.Transaction import Transaction
from qrl.core.txs.CoinBase import CoinBase
from qrl.core.txs.LatticeTransaction import LatticeTransaction
from qrl.core.txs.SlaveTransaction import SlaveTransaction
from qrl.core.txs.TransferTokenTransaction import TransferTokenTransaction
from qrl.core.txs.multisig.MultiSigVote import MultiSigVote
from qrl.core.TransactionPool import TransactionPool
from qrl.core.misc import logger
from qrl.crypto.Qryptonight import Qryptonight
from qrl.generated import qrl_pb2, qrlstateinfo_pb2


class ChainManager:
    def __init__(self, state):
        self._state = state
        self.tx_pool = TransactionPool(None)
        self._last_block = Block.deserialize(GenesisBlock().serialize())
        self.current_difficulty = StringToUInt256(str(config.user.genesis_difficulty))

        self.trigger_miner = False
        self.lock = threading.RLock()

    @property
    def re_org_limit(self):
        with self.lock:
            return self._state.get_re_org_limit()

    @property
    def height(self):
        with self.lock:
            if not self._last_block:
                return -1
            return self._last_block.block_number

    @property
    def last_block(self) -> Block:
        with self.lock:
            return self._last_block

    @property
    def total_coin_supply(self):
        with self.lock:
            return self._state.total_coin_supply

    def get_cumulative_difficulty(self):
        with self.lock:
            last_block_metadata = BlockMetadata.get_block_metadata(self._state,
                                                                   self._last_block.headerhash)
            return last_block_metadata.cumulative_difficulty

    def get_block_by_number(self, block_number) -> Optional[Block]:
        with self.lock:
            return Block.get_block_by_number(self._state, block_number)

    def get_block_header_hash_by_number(self, block_number) -> Optional[bytes]:
        with self.lock:
            return Block.get_block_header_hash_by_number(self._state,
                                                         block_number)

    def get_block(self, header_hash: bytes) -> Optional[Block]:
        with self.lock:
            return Block.get_block(self._state, header_hash)

    def get_address_balance(self, address: bytes) -> int:
        with self.lock:
            return self.get_optimized_address_state(address).balance

    def get_address_is_used(self, address: bytes) -> bool:
        with self.lock:
            return self._state.get_address_is_used(address)

    def get_address_state(self,
                          address: bytes,
                          exclude_ots_bitfield: bool = False,
                          exclude_transaction_hashes: bool = False) -> AddressState:
        """
        Transform Optimized Address State into Older Address State format
        This should only be used by API.
        """
        optimized_address_state = self.get_optimized_address_state(address)
        ots_bitfield = []
        transaction_hashes = list()
        tokens = OrderedDict()
        slave_pks_access_type = OrderedDict()

        max_bitfield_page = ceil((2 ** optimized_address_state.height) / config.dev.ots_tracking_per_page)
        if not exclude_ots_bitfield:
            ots_bitfield = [b'\x00'] * max(1024, int(ceil((2 ** optimized_address_state.height) / 8)))
            for page in range(1, max_bitfield_page + 1):
                offset = (page - 1) * config.dev.ots_tracking_per_page
                page_data = self.get_bitfield(address, page)
                for data in page_data:
                    if offset >= len(ots_bitfield):
                        break
                    ots_bitfield[offset] = data
                    offset += 1

        if not exclude_transaction_hashes:
            max_transaction_hash_page = ceil(optimized_address_state.transaction_hash_count() / config.dev.data_per_page)

            for page in range(0, max_transaction_hash_page + 1):
                page_data = self.get_transaction_hashes(address, page * config.dev.data_per_page)
                transaction_hashes.extend(page_data)

            max_token_page = ceil(optimized_address_state.tokens_count() / config.dev.data_per_page)

            for page in range(0, max_token_page + 1):
                page_data = self.get_token_transaction_hashes(address, page * config.dev.data_per_page)
                for token_txn_hash in page_data:
                    token_balance = self.get_token(address, token_txn_hash)
                    # token_balance None is only possible when the token transaction
                    # is done by a QRL address as an owner, which has not been
                    # assigned any token balance.
                    if token_balance is None:
                        continue
                    tokens[token_txn_hash] = token_balance.balance

            max_slave_page = ceil(optimized_address_state.slaves_count() / config.dev.data_per_page)

            for page in range(0, max_slave_page + 1):
                page_data = self.get_slave_transaction_hashes(address, page * config.dev.data_per_page)
                for slave_txn_hash in page_data:
                    tx, _ = self.get_tx_metadata(slave_txn_hash)
                    for slave_pk in tx.slave_pks:
                        slave_meta_data = self.get_slave_pk_access_type(address, slave_pk)
                        slave_pks_access_type[str(slave_pk)] = slave_meta_data.access_type

        addr_state = AddressState.create(address=optimized_address_state.address,
                                         nonce=optimized_address_state.nonce,
                                         balance=optimized_address_state.balance,
                                         ots_bitfield=ots_bitfield,
                                         tokens=tokens,
                                         slave_pks_access_type=slave_pks_access_type,
                                         ots_counter=0)
        addr_state.transaction_hashes.extend(transaction_hashes)

        return addr_state

    def get_optimized_address_state(self, address: bytes) -> OptimizedAddressState:
        with self.lock:
            return OptimizedAddressState.get_optimized_address_state(self._state, address)

    def get_multi_sig_address_state(self, address: bytes) -> MultiSigAddressState:
        with self.lock:
            return MultiSigAddressState.get_multi_sig_address_state_by_address(self._state._db, address)

    def get_bitfield(self, address: bytes, page: int):
        with self.lock:
            p = PaginatedBitfield(False, self._state._db)
            return p.get_paginated_data(address, page)

    def is_slave(self, master_address: bytes, slave_pk: bytes) -> bool:
        with self.lock:
            slave_meta_data = self._state.get_slave_pk_access_type(master_address, slave_pk)
            if slave_meta_data:
                return slave_meta_data.access_type == 0
            return False

    def get_slave_pk_access_type(self, address: bytes, slave_pk: bytes) -> qrl_pb2.SlaveMetadata:
        with self.lock:
            return self._state.get_slave_pk_access_type(address, slave_pk)

    def get_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_tx_hash', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_multi_sig_spend_txn_hashes(self, multi_sig_address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_multi_sig_spend', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(multi_sig_address, item_index)

    def get_token_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_tokens', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_slave_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_slaves', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_lattice_pks_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_lattice_pk', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_multi_sig_addresses(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_multisig_address', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_inbox_message_transaction_hashes(self, address: bytes, item_index: int) -> list:
        p = PaginatedData(b'p_inbox_message', False, self._state._db)
        with self.lock:
            return p.get_paginated_data(address, item_index)

    def get_vote_stats(self, multi_sig_spend_txn_hash: bytes) -> VoteStats:
        with self.lock:
            return VoteStats.get_state(state=self._state, shared_key=multi_sig_spend_txn_hash)

    def get_token(self, address: bytes, token_txhash: bytes) -> qrl_pb2.TokenBalance:
        with self.lock:
            return self._state.get_token(address, token_txhash)

    def validate_all(self, tx: Transaction, check_nonce: bool) -> bool:
        with self.lock:
            addresses_set = set()
            tx.set_affected_address(addresses_set)
            state_container = self.new_state_container(addresses_set, self.height, True, None)
            if state_container is None:
                return False
            if not self.update_state_container(tx, state_container):
                return False
            return tx.validate_all(state_container, check_nonce)

    def validate_tx(self,
                    tx: Transaction,
                    addresses_state: dict,
                    addresses_bitfield: dict,
                    tokens: dict,
                    slaves: dict) -> bool:
        with self.lock:
            return self._state.validate_tx(tx, addresses_state, addresses_bitfield, tokens, slaves)

    def apply_txn(self, tx: Transaction, state_container: StateContainer) -> bool:
        with self.lock:
            return tx.apply(self._state, state_container)

    def get_tx_metadata(self, transaction_hash) -> list:
        with self.lock:
            return TransactionMetadata.get_tx_metadata(self._state, transaction_hash)

    def get_last_transactions(self):
        with self.lock:
            return LastTransactions.get_last_txs(self._state)

    def get_unconfirmed_transaction(self, transaction_hash) -> list:
        with self.lock:
            for tx_set in self.tx_pool.transactions:
                tx = tx_set[1].transaction
                if tx.txhash == transaction_hash:
                    return [tx, tx_set[1].timestamp]
            if transaction_hash in self.tx_pool.pending_tx_pool_hash:
                for tx_set in self.tx_pool.pending_tx_pool:
                    tx = tx_set[1].transaction
                    if tx.txhash == transaction_hash:
                        return [tx, tx_set[1].timestamp]

            return []

    def get_block_metadata(self, header_hash: bytes) -> Optional[BlockMetadata]:
        with self.lock:
            return BlockMetadata.get_block_metadata(self._state, header_hash)

    def get_blockheader_and_metadata(self, block_number=0) -> Tuple:
        with self.lock:
            block_number = block_number or self.height  # if both are non-zero, then block_number takes priority

            result = (None, None)
            block = self.get_block_by_number(block_number)
            if block:
                blockheader = block.blockheader
                blockmetadata = self.get_block_metadata(blockheader.headerhash)
                result = (blockheader, blockmetadata)

            return result

    def get_block_to_mine(self, miner, wallet_address) -> list:
        with miner.lock:  # Trying to acquire miner.lock to make sure pre_block_logic is not running
            with self.lock:
                last_block = self.last_block
                last_block_metadata = self.get_block_metadata(last_block.headerhash)
                return miner.get_block_to_mine(wallet_address,
                                               self.tx_pool,
                                               last_block,
                                               last_block_metadata.block_difficulty)

    def get_block_size_limit(self, block: Block, dev_config: DevConfig):
        with self.lock:
            return Block.get_block_size_limit(self._state, block, dev_config)

    def get_block_is_duplicate(self, block: Block) -> bool:
        with self.lock:
            return block.get_block(self._state, block.headerhash) is not None

    def get_config_by_block_number(self, block_number: int) -> config.DevConfig:
        dev_config = config.dev
        if block_number == 0:
            return dev_config

        with self.lock:
            while dev_config.activation_block_number >= block_number:
                dev_config_pb_data = self._state.get_dev_config_state(dev_config.prev_state_key)
                dev_config = config.DevConfig(dev_config_pb_data, True, True)
            return dev_config

    def get_seed_block(self, blockheader: BlockHeader):
        qn = Qryptonight()
        seed_height = qn.get_seed_height(blockheader.block_number)

        # If parent block belongs to main chain, then seed block will also be in the main chain
        prev_mainchain_block = self.get_block_by_number(blockheader.block_number - 1)
        if prev_mainchain_block.headerhash == blockheader.prev_headerhash:
            return self.get_block_by_number(seed_height)

        prev_block = self.get_block(blockheader.prev_headerhash)
        while prev_block.block_number > seed_height:
            prev_mainchain_block = self.get_block_by_number(prev_block.block_number)
            if prev_mainchain_block.headerhash == prev_block.headerhash:
                return self.get_block_by_number(seed_height)
            prev_block = self.get_block(prev_block.prev_headerhash)

        return prev_block

    def validate_mining_nonce(self, blockheader: BlockHeader, dev_config: config.DevConfig, enable_logging=True):
        with self.lock:
            parent_metadata = BlockMetadata.get_block_metadata(self._state, blockheader.prev_headerhash)
            parent_block = Block.get_block(self._state, blockheader.prev_headerhash)

            measurement = self.get_measurement(dev_config,
                                               blockheader.timestamp,
                                               blockheader.prev_headerhash,
                                               parent_metadata)
            diff, target = DifficultyTracker.get(
                measurement=measurement,
                parent_difficulty=parent_metadata.block_difficulty,
                dev_config=dev_config)

            mining_blob = blockheader.mining_blob(dev_config)
            qn = Qryptonight()
            seed_block = self.get_seed_block(blockheader)

            if enable_logging:
                logger.debug('-----------------START--------------------')
                logger.debug('Validate                #%s', blockheader.block_number)
                logger.debug('block.timestamp         %s', blockheader.timestamp)
                logger.debug('parent_block.timestamp  %s', parent_block.timestamp)
                logger.debug('parent_block.difficulty %s', UInt256ToString(parent_metadata.block_difficulty))
                logger.debug('diff                    %s', UInt256ToString(diff))
                logger.debug('target                  %s', bin2hstr(target))
                logger.debug('mining blob             %s', bin2hstr(mining_blob))
                logger.debug('seed_block              #%s', seed_block.block_number)
                logger.debug('seed_block hash         %s', bin2hstr(seed_block.headerhash))
                logger.debug('-------------------END--------------------')

            if not PoWValidator().verify_input(blockheader.block_number,
                                               seed_block.block_number,
                                               seed_block.headerhash,
                                               mining_blob,
                                               target):
                if enable_logging:
                    logger.warning("PoW verification failed")
                    tmp_hash = qn.hash(blockheader.block_number,
                                       seed_block.block_number,
                                       seed_block.headerhash,
                                       blockheader.mining_blob(dev_config))
                    logger.warning("{}".format(bin2hstr(tmp_hash)))
                    logger.debug('%s', blockheader.to_json())
                return False

            return True

    def get_headerhashes(self, start_blocknumber):
        with self.lock:
            start_blocknumber = max(0, start_blocknumber)
            end_blocknumber = min(self._last_block.block_number,
                                  start_blocknumber + 2 * config.dev.reorg_limit)

            total_expected_headerhash = end_blocknumber - start_blocknumber + 1

            node_header_hash = qrl_pb2.NodeHeaderHash()
            node_header_hash.block_number = start_blocknumber
{'fraction_non_alphanumeric': 0.07484418800860028, 'fraction_numerical': 0.003102631793811066, 'mean_word_length': 2.1917998610145935, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 68, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 80, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2240869', 'n_tokens_mistral': 10233, 'n_tokens_neox': 9828, 'n_words': 2042}