def _add_block_metadata(self, headerhash, block_timestamp, parent_headerhash, batch): block_metadata = self._state.get_block_metadata(headerhash) if not block_metadata: block_metadata = BlockMetadata.create() parent_metadata = self._state.get_block_metadata(parent_headerhash) parent_block_difficulty = parent_metadata.block_difficulty parent_cumulative_difficulty = parent_metadata.cumulative_difficulty block_metadata.update_last_headerhashes( parent_metadata.last_N_headerhashes, parent_headerhash) measurement = self._state.get_measurement(block_timestamp, parent_headerhash, parent_metadata) block_difficulty, _ = DifficultyTracker.get( measurement=measurement, parent_difficulty=parent_block_difficulty) block_cumulative_difficulty = StringToUInt256( str( int(UInt256ToString(block_difficulty)) + int(UInt256ToString(parent_cumulative_difficulty)))) block_metadata.set_block_difficulty(block_difficulty) block_metadata.set_cumulative_difficulty(block_cumulative_difficulty) parent_metadata.add_child_headerhash(headerhash) self._state.put_block_metadata(parent_headerhash, parent_metadata, batch) self._state.put_block_metadata(headerhash, block_metadata, batch) return block_metadata
def prepare_next_unmined_block_template(self, mining_address, tx_pool, parent_block: Block, parent_difficulty, dev_config: DevConfig): miner = self.get_miner(parent_block.block_number + 1, dev_config) try: logger.debug('Miner-Try - prepare_next_unmined_block_template') with self.lock: logger.debug('Miner-Locked - prepare_next_unmined_block_template') logger.debug('Miner-TryCancel - prepare_next_unmined_block_template') miner.cancel() logger.debug('Miner-Cancel - prepare_next_unmined_block_template') self._mining_block = self.create_block(last_block=parent_block, mining_nonce=0, tx_pool=tx_pool, miner_address=mining_address) parent_metadata = self._chain_manager.get_block_metadata(parent_block.headerhash) self._measurement = self._chain_manager.get_measurement(dev_config, self._mining_block.timestamp, self._mining_block.prev_headerhash, parent_metadata) self._current_difficulty, self._current_target = DifficultyTracker.get( measurement=self._measurement, parent_difficulty=parent_difficulty, dev_config=dev_config) except Exception as e: logger.warning("Exception in start_mining") logger.exception(e)
def validate_mining_nonce(self, block, enable_logging=False): parent_metadata = self.state.get_block_metadata(block.prev_headerhash) parent_block = self.state.get_block(block.prev_headerhash) measurement = self.state.get_measurement(block.timestamp, block.prev_headerhash, parent_metadata) diff, target = DifficultyTracker.get( measurement=measurement, parent_difficulty=parent_metadata.block_difficulty) if enable_logging: logger.debug('-----------------START--------------------') logger.debug('Validate #%s', block.block_number) logger.debug('block.timestamp %s', block.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 | target : %s', UInt256ToString(diff), target) logger.debug('-------------------END--------------------') if not self.verify_input_cached(block.mining_blob, target): if enable_logging: logger.warning("PoW verification failed") qn = Qryptonight() tmp_hash = qn.hash(block.mining_blob) logger.warning("{}".format(tmp_hash)) logger.debug('%s', block.to_json()) return False return True
def prepare_next_unmined_block_template(self, tx_pool, parent_block: Block, parent_difficulty): mining_xmss = self.get_mining_xmss() if not mining_xmss: logger.warning('No Mining XMSS Found') return try: self.cancel() self._mining_block = self.create_block( last_block=parent_block, mining_nonce=0, tx_pool=tx_pool, signing_xmss=self._mining_xmss, master_address=self._master_address) parent_metadata = self.state.get_block_metadata( parent_block.headerhash) self._measurement = self.state.get_measurement( self._mining_block.timestamp, self._mining_block.prev_headerhash, parent_metadata) self._current_difficulty, self._current_target = DifficultyTracker.get( measurement=self._measurement, parent_difficulty=parent_difficulty) except Exception as e: logger.warning("Exception in start_mining") logger.exception(e)
def load(self, genesis_block): height = self.state.get_mainchain_height() if height == -1: self.state.put_block(genesis_block, None) block_number_mapping = qrl_pb2.BlockNumberMapping(headerhash=genesis_block.headerhash, prev_headerhash=genesis_block.prev_headerhash) self.state.put_block_number_mapping(genesis_block.block_number, block_number_mapping, None) parent_difficulty = StringToUInt256(str(config.dev.genesis_difficulty)) self.current_difficulty, _ = DifficultyTracker.get( measurement=config.dev.mining_setpoint_blocktime, parent_difficulty=parent_difficulty) block_metadata = BlockMetadata.create() block_metadata.set_orphan(False) block_metadata.set_block_difficulty(self.current_difficulty) block_metadata.set_cumulative_difficulty(self.current_difficulty) self.state.put_block_metadata(genesis_block.headerhash, block_metadata, None) addresses_state = dict() for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address addresses_state[bytes_addr] = AddressState.get_default(bytes_addr) addresses_state[bytes_addr]._data.balance = genesis_balance.balance self.state.state_objects.update_current_state(addresses_state) self.state.state_objects.push(genesis_block.headerhash) else: self.last_block = self.get_block_by_number(height) self.current_difficulty = self.state.get_block_metadata(self.last_block.headerhash).block_difficulty
def load(self, genesis_block): height = self.state.get_mainchain_height() if height == -1: self.state.put_block(genesis_block, None) block_number_mapping = qrl_pb2.BlockNumberMapping(headerhash=genesis_block.headerhash, prev_headerhash=genesis_block.prev_headerhash) self.state.put_block_number_mapping(genesis_block.block_number, block_number_mapping, None) parent_difficulty = StringToUInt256(str(config.dev.genesis_difficulty)) self.current_difficulty, _ = DifficultyTracker.get( measurement=config.dev.mining_setpoint_blocktime, parent_difficulty=parent_difficulty) block_metadata = BlockMetadata.create() block_metadata.set_orphan(False) block_metadata.set_block_difficulty(self.current_difficulty) block_metadata.set_cumulative_difficulty(self.current_difficulty) self.state.put_block_metadata(genesis_block.headerhash, block_metadata, None) addresses_state = dict() for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address addresses_state[bytes_addr] = AddressState.get_default(bytes_addr) addresses_state[bytes_addr]._data.balance = genesis_balance.balance for tx_idx in range(1, len(genesis_block.transactions)): tx = Transaction.from_pbdata(genesis_block.transactions[tx_idx]) for addr in tx.addrs_to: addresses_state[addr] = AddressState.get_default(addr) coinbase_tx = Transaction.from_pbdata(genesis_block.transactions[0]) if not isinstance(coinbase_tx, CoinBase): return False addresses_state[coinbase_tx.addr_to] = AddressState.get_default(coinbase_tx.addr_to) if not coinbase_tx.validate_extended(): return False coinbase_tx.apply_on_state(addresses_state) for tx_idx in range(1, len(genesis_block.transactions)): tx = Transaction.from_pbdata(genesis_block.transactions[tx_idx]) tx.apply_on_state(addresses_state) self.state.state_objects.update_current_state(addresses_state) self.state.state_objects.update_tx_metadata(genesis_block, None) self.state.state_objects.push(genesis_block.headerhash) else: self.last_block = self.get_block_by_number(height) self.current_difficulty = self.state.get_block_metadata(self.last_block.headerhash).block_difficulty
def add_block_metadata(self, headerhash, block_timestamp, parent_headerhash, batch): block_metadata = self.state.get_block_metadata(headerhash) if not block_metadata: block_metadata = BlockMetadata.create() parent_metadata = self.state.get_block_metadata(parent_headerhash) block_difficulty = (0, ) * 32 # 32 bytes to represent 256 bit of 0 block_cumulative_difficulty = ( 0, ) * 32 # 32 bytes to represent 256 bit of 0 if not parent_metadata: parent_metadata = BlockMetadata.create() else: parent_block = self.state.get_block(parent_headerhash) if parent_block: parent_block_difficulty = parent_metadata.block_difficulty parent_cumulative_difficulty = parent_metadata.cumulative_difficulty if not parent_metadata.is_orphan: block_metadata.update_last_headerhashes( parent_metadata.last_N_headerhashes, parent_headerhash) measurement = self.state.get_measurement( block_timestamp, parent_headerhash, parent_metadata) block_difficulty, _ = DifficultyTracker.get( measurement=measurement, parent_difficulty=parent_block_difficulty) block_cumulative_difficulty = StringToUInt256( str( int(UInt256ToString(block_difficulty)) + int(UInt256ToString(parent_cumulative_difficulty))) ) block_metadata.set_orphan(parent_metadata.is_orphan) block_metadata.set_block_difficulty(block_difficulty) block_metadata.set_cumulative_difficulty(block_cumulative_difficulty) parent_metadata.add_child_headerhash(headerhash) self.state.put_block_metadata(parent_headerhash, parent_metadata, batch) self.state.put_block_metadata(headerhash, block_metadata, batch) # Call once to populate the cache self.state.get_block_datapoint(headerhash)
def prepare_next_unmined_block_template(self, tx_pool, parent_block: Block, parent_difficulty): try: self.cancel() self._mining_block = self.create_block(last_block=parent_block, mining_nonce=0, tx_pool=tx_pool, miner_address=self._mining_credit_wallet) parent_metadata = self.state.get_block_metadata(parent_block.headerhash) self._measurement = self.state.get_measurement(self._mining_block.timestamp, self._mining_block.prev_headerhash, parent_metadata) self._current_difficulty, self._current_target = DifficultyTracker.get( measurement=self._measurement, parent_difficulty=parent_difficulty) except Exception as e: logger.warning("Exception in start_mining") logger.exception(e)
def validate_mining_nonce(self, blockheader: BlockHeader, enable_logging=True): with self.lock: parent_metadata = self.get_block_metadata( blockheader.prev_headerhash) parent_block = self._state.get_block(blockheader.prev_headerhash) measurement = self.get_measurement(blockheader.timestamp, blockheader.prev_headerhash, parent_metadata) diff, target = DifficultyTracker.get( measurement=measurement, parent_difficulty=parent_metadata.block_difficulty) 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('-------------------END--------------------') if not PoWValidator().verify_input(blockheader.mining_blob, target): if enable_logging: logger.warning("PoW verification failed") qn = Qryptonight() tmp_hash = qn.hash(blockheader.mining_blob) logger.warning("{}".format(bin2hstr(tmp_hash))) logger.debug('%s', blockheader.to_json()) return False return True
def load(self, genesis_block): # load() has the following tasks: # Write Genesis Block into State immediately # Register block_number <-> blockhash mapping # Calculate difficulty Metadata for Genesis Block # Generate AddressStates from Genesis Block balances # Apply Genesis Block's transactions to the state # Detect if we are forked from genesis block and if so initiate recovery. height = self._state.get_mainchain_height() if height == -1: self._state.put_block(genesis_block, None) block_number_mapping = qrl_pb2.BlockNumberMapping( headerhash=genesis_block.headerhash, prev_headerhash=genesis_block.prev_headerhash) self._state.put_block_number_mapping(genesis_block.block_number, block_number_mapping, None) parent_difficulty = StringToUInt256( str(config.user.genesis_difficulty)) self.current_difficulty, _ = DifficultyTracker.get( measurement=config.dev.mining_setpoint_blocktime, parent_difficulty=parent_difficulty) block_metadata = BlockMetadata.create() block_metadata.set_block_difficulty(self.current_difficulty) block_metadata.set_cumulative_difficulty(self.current_difficulty) self._state.put_block_metadata(genesis_block.headerhash, block_metadata, None) addresses_state = dict() for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address addresses_state[bytes_addr] = AddressState.get_default( bytes_addr) addresses_state[ bytes_addr]._data.balance = genesis_balance.balance for tx_idx in range(1, len(genesis_block.transactions)): tx = Transaction.from_pbdata( genesis_block.transactions[tx_idx]) for addr in tx.addrs_to: addresses_state[addr] = AddressState.get_default(addr) coinbase_tx = Transaction.from_pbdata( genesis_block.transactions[0]) if not isinstance(coinbase_tx, CoinBase): return False addresses_state[coinbase_tx.addr_to] = AddressState.get_default( coinbase_tx.addr_to) if not coinbase_tx.validate_extended(genesis_block.block_number): return False coinbase_tx.apply_state_changes(addresses_state) for tx_idx in range(1, len(genesis_block.transactions)): tx = Transaction.from_pbdata( genesis_block.transactions[tx_idx]) tx.apply_state_changes(addresses_state) self._state.put_addresses_state(addresses_state) self._state.update_tx_metadata(genesis_block, None) self._state.update_mainchain_height(0, None) else: self._last_block = self.get_block_by_number(height) self.current_difficulty = self._state.get_block_metadata( self._last_block.headerhash).block_difficulty fork_state = self._state.get_fork_state() if fork_state: block = self._state.get_block(fork_state.initiator_headerhash) self._fork_recovery(block, fork_state)
def test_verify(self): class CustomQMiner(Qryptominer): def __init__(self): Qryptominer.__init__(self) self._solution_lock = threading.Lock() self.nonce = None self.solution_blob = None def start(self, input, nonceOffset, target, thread_count): self.cancel() try: self._solution_lock.release() except RuntimeError: pass self._solution_lock.acquire(blocking=False) super().start(input, nonceOffset, target, thread_count) def wait_for_solution(self): self._solution_lock.acquire(blocking=True) self._solution_lock.release() def handleEvent(self, event): if event.type == SOLUTION: self.nonce = event.nonce self.solution_blob = self.solutionInput() self._solution_lock.release() block_timestamp = 1515443508 parent_block_timestamp = 1515443508 # This could be the average of last N blocks measurement = block_timestamp - parent_block_timestamp parent_difficulty = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4) new_diff, new_target = DifficultyTracker.get( measurement, parent_difficulty=parent_difficulty) self.assertEqual(new_diff, (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5)) self.assertEqual(new_target, ( 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51)) block_json = read_data_file('core/example_block_mining.json') block = Block.from_json(block_json) expected_blob = (0, 231, 90, 101, 142, 20, 245, 183, 96, 5, 216, 159, 111, 239, 93, 217, 138, 10, 227, 159, 198, 207, 109, 238, 83, 220, 167, 148, 247, 200, 197, 41, 37, 36, 150, 12, 116, 85, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, 198, 40, 62, 106, 139, 108, 83, 216, 206, 161, 148, 50, 65, 212, 137, 94, 102, 124, 45, 51, 57, 43, 19, 51) self.assertEqual(expected_blob, tuple(block.mining_blob)) custom_qminer = CustomQMiner() custom_qminer.start(input=block.mining_blob, nonceOffset=block.mining_nonce_offset, target=new_target, thread_count=2) custom_qminer.wait_for_solution() expected_mined_blob = bytearray(expected_blob) tmp_offset = config.dev.mining_nonce_offset expected_mined_blob[tmp_offset:tmp_offset + 4] = custom_qminer.nonce.to_bytes(4, byteorder='big', signed=False) print(custom_qminer.nonce) self.assertEqual(tuple(expected_mined_blob), custom_qminer.solution_blob) self.assertTrue(PoWHelper().verifyInput(custom_qminer.solution_blob, new_target))
def test_verify(self): class CustomQMiner(Qryptominer): def __init__(self): Qryptominer.__init__(self) self._solution_lock = threading.Lock() self.nonce = None self.solution_blob = None def start(self, input, nonceOffset, target, thread_count): self.cancel() try: self._solution_lock.release() except RuntimeError: pass self._solution_lock.acquire(blocking=False) super().start(input, nonceOffset, target, thread_count) def wait_for_solution(self): self._solution_lock.acquire(blocking=True) self._solution_lock.release() def solutionEvent(self, nonce): print('Solution Found %s', nonce) self.nonce = nonce self.solution_blob = self.solutionInput() self._solution_lock.release() block_timestamp = 1515443508 parent_block_timestamp = 1515443508 # This could be the average of last N blocks measurement = block_timestamp - parent_block_timestamp parent_difficulty = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4) new_diff, new_target = DifficultyTracker.get( measurement, parent_difficulty=parent_difficulty) self.assertEqual(new_diff, (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5)) self.assertEqual( new_target, (51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51)) block_json = read_data_file('core/example_block_mining.json') block = Block.from_json(block_json) expected_blob = tuple([ 241, 93, 178, 239, 171, 183, 27, 87, 2, 191, 178, 157, 32, 74, 254, 207, 242, 82, 128, 197, 58, 86, 24, 90, 106, 33, 58, 82, 160, 251, 118, 174, 45, 182, 72, 157, 142, 141, 219, 0, 0, 0, 15, 61, 11, 20, 166, 132, 14, 29, 248, 65, 55, 56, 226, 12, 57, 60, 37, 64, 123, 44, 48, 172, 218, 221, 26, 8, 143, 110, 38, 215, 83, 248, 227, 87, 148, 88, 237, 48, 203, 111, 245, 31, 125, 45, 14, 111, 109, 0, 87, 13, 154, 252, 49, 160 ]) self.assertEqual(expected_blob, tuple(block.mining_blob)) custom_qminer = CustomQMiner() custom_qminer.start(input=block.mining_blob, nonceOffset=block.mining_nonce_offset, target=new_target, thread_count=2) custom_qminer.wait_for_solution() expected_mined_blob = bytearray(expected_blob) tmp_offset = config.dev.mining_nonce_offset expected_mined_blob[tmp_offset:tmp_offset + 4] = custom_qminer.nonce.to_bytes(4, byteorder='big', signed=False) print(custom_qminer.nonce) self.assertEqual(tuple(expected_mined_blob), custom_qminer.solution_blob) self.assertTrue(PoWHelper().verifyInput(custom_qminer.solution_blob, new_target))
class ChainManager: def __init__(self, state): self.state = state self.tx_pool = TransactionPool() # TODO: Move to some pool manager self.last_block = Block.from_json(GenesisBlock().to_json()) self.current_difficulty = StringToUInt256(str(config.dev.genesis_difficulty)) self._difficulty_tracker = DifficultyTracker() self.trigger_miner = False @property def height(self): return self.last_block.block_number def get_last_block(self) -> Block: return self.last_block def get_cumulative_difficulty(self): last_block_metadata = self.state.get_block_metadata(self.last_block.headerhash) return last_block_metadata.cumulative_difficulty def load(self, genesis_block): height = self.state.get_mainchain_height() if height == -1: self.state.put_block(genesis_block, None) block_number_mapping = qrl_pb2.BlockNumberMapping(headerhash=genesis_block.headerhash, prev_headerhash=genesis_block.prev_headerhash) self.state.put_block_number_mapping(genesis_block.block_number, block_number_mapping, None) parent_difficulty = StringToUInt256(str(config.dev.genesis_difficulty)) self.current_difficulty, _ = self._difficulty_tracker.get( measurement=config.dev.mining_setpoint_blocktime, parent_difficulty=parent_difficulty) block_metadata = BlockMetadata.create() block_metadata.set_orphan(False) block_metadata.set_block_difficulty(self.current_difficulty) block_metadata.set_cumulative_difficulty(self.current_difficulty) self.state.put_block_metadata(genesis_block.headerhash, block_metadata, None) addresses_state = dict() for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address.encode() addresses_state[bytes_addr] = AddressState.get_default(bytes_addr) addresses_state[bytes_addr]._data.balance = genesis_balance.balance self.state.state_objects.update_current_state(addresses_state) self.state.state_objects.push(genesis_block.headerhash) else: self.last_block = self.get_block_by_number(height) self.current_difficulty = self.state.get_block_metadata(self.last_block.headerhash).block_difficulty def validate_mining_nonce(self, block, enable_logging=False): parent_metadata = self.state.get_block_metadata(block.prev_headerhash) parent_block = self.state.get_block(block.prev_headerhash) input_bytes = StringToUInt256(str(block.mining_nonce))[-4:] + tuple(block.mining_hash) measurement = self.state.get_measurement(block.timestamp, block.prev_headerhash) diff, target = self._difficulty_tracker.get( measurement=measurement, parent_difficulty=parent_metadata.block_difficulty) if enable_logging: logger.debug('-----------------START--------------------') logger.debug('Validate #%s', block.block_number) logger.debug('block.timestamp %s', block.timestamp) logger.debug('parent_block.timestamp %s', parent_block.timestamp) logger.debug('parent_block.difficulty %s', UInt256ToString(parent_metadata.block_difficulty)) logger.debug('input_bytes %s', UInt256ToString(input_bytes)) logger.debug('diff : %s | target : %s', UInt256ToString(diff), target) logger.debug('-------------------END--------------------') if not PoWHelper.verifyInput(input_bytes, target): if enable_logging: logger.warning("PoW verification failed") qn = Qryptonight() tmp_hash = qn.hash(input_bytes) logger.warning("{}".format(tmp_hash)) logger.debug('%s', block.to_json()) return False return True def validate_block(self, block, address_txn) -> bool: len_transactions = len(block.transactions) if len_transactions < 1: return False coinbase_tx = Transaction.from_pbdata(block.transactions[0]) coinbase_tx.validate() if not self.validate_mining_nonce(block): return False if coinbase_tx.subtype != qrl_pb2.Transaction.COINBASE: return False if not coinbase_tx.validate(): return False coinbase_tx.apply_on_state(address_txn) addr_from_pk_state = address_txn[coinbase_tx.txto] addr_from_pk = Transaction.get_slave(coinbase_tx) if addr_from_pk: addr_from_pk_state = address_txn[addr_from_pk] if not coinbase_tx.validate_extended(address_txn[coinbase_tx.txto], addr_from_pk_state, []): return False # TODO: check block reward must be equal to coinbase amount for tx_idx in range(1, len_transactions): tx = Transaction.from_pbdata(block.transactions[tx_idx]) if tx.subtype == qrl_pb2.Transaction.COINBASE: return False if not tx.validate(): # TODO: Move this validation, before adding txn to pool return False addr_from_pk_state = address_txn[tx.txfrom] 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.txfrom], addr_from_pk_state, []): return False expected_nonce = address_txn[tx.txfrom].nonce + 1 if tx.nonce != expected_nonce: logger.warning('nonce incorrect, invalid tx') logger.warning('subtype: %s', tx.subtype) logger.warning('%s actual: %s expected: %s', tx.txfrom, tx.nonce, expected_nonce) return False if tx.ots_key_reuse(address_txn[tx.txfrom], tx.ots_key): logger.warning('pubkey reuse detected: invalid tx %s', tx.txhash) logger.warning('subtype: %s', tx.subtype) return False tx.apply_on_state(address_txn) return True def _pre_check(self, block, ignore_duplicate): if block.block_number < 1: return False if not block.validate(): return False if (not ignore_duplicate) and self.state.get_block(block.headerhash): # Duplicate block check logger.info('Duplicate block %s %s', block.block_number, bin2hstr(block.headerhash)) return False return True def _try_orphan_add_block(self, block, batch): prev_block_metadata = self.state.get_block_metadata(block.prev_headerhash) self.trigger_miner = False if prev_block_metadata is None or prev_block_metadata.is_orphan: self.state.put_block(block, batch) self.add_block_metadata(block.headerhash, block.timestamp, block.prev_headerhash, batch) return True return False def _try_branch_add_block(self, block, batch=None) -> bool: parent_block = self.state.get_block(block.prev_headerhash) if not block.validate_parent_child_relation(parent_block): logger.warning('Failed to validate blocks parent child relation') return False address_set = self.state.prepare_address_list(block) # Prepare list for current block if self.last_block.headerhash == block.prev_headerhash: address_txn = self.state.get_state_mainchain(address_set) else: address_txn = self.state.get_state(block.prev_headerhash, address_set) if self.validate_block(block, address_txn): self.state.put_block(block, None) self.add_block_metadata(block.headerhash, block.timestamp, block.prev_headerhash, None) last_block_metadata = self.state.get_block_metadata(self.last_block.headerhash) new_block_metadata = self.state.get_block_metadata(block.headerhash) last_block_difficulty = int(UInt256ToString(last_block_metadata.cumulative_difficulty)) new_block_difficulty = int(UInt256ToString(new_block_metadata.cumulative_difficulty)) self.trigger_miner = False if new_block_difficulty > last_block_difficulty: if self.last_block.headerhash != block.prev_headerhash: self.rollback(block) return True self.state.update_mainchain_state(address_txn, block.block_number, block.headerhash) self.last_block = block self._update_mainchain(block, batch) self.tx_pool.remove_tx_in_block_from_pool(block) self.state.update_mainchain_height(block.block_number, batch) self.state.update_tx_metadata(block, batch) self.trigger_miner = True return True return False def rollback(self, block): hash_path = [] while True: if self.state.state_objects.contains(block.headerhash): break hash_path.append(block.headerhash) new_block = self.state.get_block(block.prev_headerhash) if not new_block: logger.warning('No block found %s', block.prev_headerhash) break block = new_block if block.block_number == 0: del hash_path[-1] # Skip replaying Genesis Block break self.state.state_objects.destroy_current_state(None) block = self.state.get_block(hash_path[-1]) self.state.state_objects.destroy_fork_states(block.block_number, block.headerhash) for header_hash in hash_path[-1::-1]: block = self.state.get_block(header_hash) address_set = self.state.prepare_address_list(block) # Prepare list for current block address_txn = self.state.get_state_mainchain(address_set) self.state.update_mainchain_state(address_txn, block.block_number, block.headerhash) self.last_block = block self._update_mainchain(block, None) self.tx_pool.remove_tx_in_block_from_pool(block) self.state.update_mainchain_height(block.block_number, None) self.state.update_tx_metadata(block, None) self.trigger_miner = True def _add_block(self, block, ignore_duplicate=False, batch=None): block_size_limit = self.state.get_block_size_limit(block) if block_size_limit and block.size > block_size_limit: logger.info('Block Size greater than threshold limit %s > %s', block.size, block_size_limit) return False if not self._pre_check(block, ignore_duplicate): logger.debug('Failed pre_check') return False if self._try_orphan_add_block(block, batch): return True if self._try_branch_add_block(block, batch): return True return False def add_block(self, block: Block) -> bool: if block.block_number < self.height - config.dev.reorg_limit: logger.debug('Skipping block #%s as beyond re-org limit', block.block_number) return False batch = self.state.get_batch() if self._add_block(block, batch=batch): self.state.write_batch(batch) self.update_child_metadata(block.headerhash) return True return False def update_child_metadata(self, headerhash): block_metadata = self.state.get_block_metadata(headerhash) childs = list(block_metadata.child_headerhashes) while childs: child_headerhash = childs.pop(0) block = self.state.get_block(child_headerhash) if not block: continue if not self._add_block(block, True): self._prune([block.headerhash], None) continue block_metadata = self.state.get_block_metadata(child_headerhash) childs += block_metadata.child_headerhashes def _prune(self, childs, batch): while childs: child_headerhash = childs.pop(0) block_metadata = self.state.get_block_metadata(child_headerhash) childs += block_metadata.child_headerhashes self.state.delete(bin2hstr(child_headerhash).encode(), batch) self.state.delete(b'metadata_' + bin2hstr(child_headerhash).encode(), batch) def add_block_metadata(self, headerhash, block_timestamp, parent_headerhash, batch): parent_metadata = self.state.get_block_metadata(parent_headerhash) block_difficulty = (0,) * 32 # 32 bytes to represent 256 bit of 0 block_cumulative_difficulty = (0,) * 32 # 32 bytes to represent 256 bit of 0 if not parent_metadata: parent_metadata = BlockMetadata.create() else: parent_block = self.state.get_block(parent_headerhash) if parent_block: parent_block_difficulty = parent_metadata.block_difficulty parent_cumulative_difficulty = parent_metadata.cumulative_difficulty if not parent_metadata.is_orphan: measurement = self.state.get_measurement(block_timestamp, parent_headerhash) block_difficulty, _ = self._difficulty_tracker.get( measurement=measurement, parent_difficulty=parent_block_difficulty) block_cumulative_difficulty = StringToUInt256(str( int(UInt256ToString(block_difficulty)) + int(UInt256ToString(parent_cumulative_difficulty)))) block_metadata = self.state.get_block_metadata(headerhash) if not block_metadata: block_metadata = BlockMetadata.create() block_metadata.set_orphan(parent_metadata.is_orphan) block_metadata.set_block_difficulty(block_difficulty) block_metadata.set_cumulative_difficulty(block_cumulative_difficulty) parent_metadata.add_child_headerhash(headerhash) self.state.put_block_metadata(parent_headerhash, parent_metadata, batch) self.state.put_block_metadata(headerhash, block_metadata, batch) def _update_mainchain(self, block, batch): measurement = self.state.get_measurement(block.timestamp, block.prev_headerhash) self.current_difficulty, _ = self._difficulty_tracker.get( measurement=measurement, parent_difficulty=self.current_difficulty) block_number_mapping = None while block_number_mapping is None or block.headerhash != block_number_mapping.headerhash: block_number_mapping = qrl_pb2.BlockNumberMapping(headerhash=block.headerhash, prev_headerhash=block.prev_headerhash) self.state.put_block_number_mapping(block.block_number, block_number_mapping, batch) block = self.state.get_block(block.prev_headerhash) block_number_mapping = self.state.get_block_number_mapping(block.block_number) def get_block_by_number(self, block_number) -> Optional[Block]: return self.state.get_block_by_number(block_number) def get_state(self, headerhash): return self.state.get_state(headerhash, set()) def get_address(self, address): return self.state.get_address(address) def get_transaction(self, transaction_hash) -> list: for tx in self.tx_pool.transaction_pool: if tx.txhash == transaction_hash: return [tx, None] return self.state.get_tx_metadata(transaction_hash) def get_headerhashes(self, start_blocknumber): start_blocknumber = max(0, start_blocknumber) end_blocknumber = min(self.last_block.block_number, start_blocknumber + config.dev.reorg_limit) node_header_hash = qrl_pb2.NodeHeaderHash() node_header_hash.block_number = start_blocknumber for i in range(start_blocknumber, end_blocknumber + 1): block = self.state.get_block_by_number(i) node_header_hash.headerhashes.append(block.headerhash) return node_header_hash def add_ephemeral_message(self, encrypted_ephemeral: EncryptedEphemeralMessage): self.state.update_ephemeral(encrypted_ephemeral)
class Miner(Qryptominer): def __init__(self, pre_block_logic, slaves: list, state: State, add_unprocessed_txn_fn): super().__init__() self.pre_block_logic = pre_block_logic # FIXME: Circular dependency with node.py self._mining_block = None self._slaves = slaves self._mining_xmss = None self._reward_address = None self.state = state self._difficulty_tracker = DifficultyTracker() self._add_unprocessed_txn_fn = add_unprocessed_txn_fn @staticmethod def _get_mining_data(block): input_bytes = [0x00, 0x00, 0x00, 0x00] + list(block.mining_hash) nonce_offset = 0 return input_bytes, nonce_offset @staticmethod def calc_hash(input_bytes): qn = Qryptonight() return qn.hash(input_bytes) def set_unused_ots_key(self, xmss, addr_state, start=0): for i in range(start, 2 ** xmss.height): if not Transaction.ots_key_reuse(addr_state, i): xmss.set_index(i) return True return False def valid_mining_permission(self): if self._master_address == self._mining_xmss.get_address(): return True addr_state = self.state.get_address(self._master_address) access_type = addr_state.get_slave_permission(self._mining_xmss.pk()) if access_type == -1: logger.warning('Slave is not authorized yet for mining') logger.warning('Added Slave Txn') slave_tx = Transaction.from_json(self._slaves[2]) self._add_unprocessed_txn_fn(slave_tx, None) return None return True def get_mining_xmss(self): if self._mining_xmss: addr_state = self.state.get_address(self._mining_xmss.get_address()) if self.set_unused_ots_key(self._mining_xmss, addr_state, self._mining_xmss.get_index()): if self.valid_mining_permission(): return self._mining_xmss else: self._mining_xmss = None return None if not self._mining_xmss: self._master_address = self._slaves[0].encode() unused_ots_found = False for slave_seed in self._slaves[1]: xmss = Wallet.get_new_address(seed=slave_seed).xmss addr_state = self.state.get_address(xmss.get_address()) if self.set_unused_ots_key(xmss, addr_state): # Unused ots_key_found self._mining_xmss = xmss unused_ots_found = True break if not unused_ots_found: # Unused ots_key_found logger.warning('No OTS-KEY left for mining') return None if self._master_address == self._mining_xmss.get_address(): return self._mining_xmss if not self.valid_mining_permission(): return None return self._mining_xmss def start_mining(self, tx_pool, parent_block, parent_difficulty, thread_count=config.user.mining_thread_count): mining_xmss = self.get_mining_xmss() if not mining_xmss: logger.warning('No Mining XMSS Found') return try: self.cancel() self._mining_block = self.create_block(last_block=parent_block, mining_nonce=0, tx_pool=tx_pool, signing_xmss=self._mining_xmss, master_address=self._master_address) measurement = self.state.get_measurement(self._mining_block.timestamp, self._mining_block.prev_headerhash) current_difficulty, current_target = self._difficulty_tracker.get( measurement=measurement, parent_difficulty=parent_difficulty) input_bytes, nonce_offset = self._get_mining_data(self._mining_block) logger.debug('!!! Mine #{} | {} ({}) | {} -> {} | {}'.format( self._mining_block.block_number, measurement, self._mining_block.timestamp - parent_block.timestamp, UInt256ToString(parent_difficulty), UInt256ToString(current_difficulty), current_target )) logger.debug('!!! {}'.format(current_target)) self.start(input=input_bytes, nonceOffset=nonce_offset, target=current_target, thread_count=thread_count) except Exception as e: logger.warning("Exception in start_mining") logger.exception(e) def solutionEvent(self, nonce): # NOTE: This function usually runs in the context of a C++ thread try: logger.debug('Solution Found %s', nonce) self._mining_block.set_mining_nonce(nonce) logger.info('Block #%s nonce: %s', self._mining_block.block_number, StringToUInt256(str(nonce))[-4:]) logger.info('Hash Rate: %s H/s', self.hashRate()) cloned_block = copy.deepcopy(self._mining_block) self.pre_block_logic(cloned_block) except Exception as e: logger.warning("Exception in solutionEvent") logger.exception(e) def create_block(self, last_block, mining_nonce, tx_pool, signing_xmss, master_address) -> Optional[Block]: # TODO: Persistence will move to rocksdb # FIXME: Difference between this and create block????????????? # FIXME: Break encapsulation dummy_block = Block.create(mining_nonce=mining_nonce, block_number=last_block.block_number + 1, prevblock_headerhash=last_block.headerhash, transactions=[], signing_xmss=signing_xmss, master_address=master_address, nonce=0) dummy_block.set_mining_nonce(mining_nonce) signing_xmss.set_index(signing_xmss.get_index() - 1) t_pool2 = copy.deepcopy(tx_pool.transaction_pool) del tx_pool.transaction_pool[:] ###### # recreate the transaction pool as in the tx_hash_list, ordered by txhash.. total_txn = len(t_pool2) txnum = 0 addresses_set = set() while txnum < total_txn: tx = t_pool2[txnum] tx.set_effected_address(addresses_set) txnum += 1 addresses_state = dict() for address in addresses_set: addresses_state[address] = self.state.get_address(address) block_size = dummy_block.size block_size_limit = self.state.get_block_size_limit(last_block) txnum = 0 while txnum < total_txn: tx = t_pool2[txnum] # Skip Transactions for later, which doesn't fit into block if block_size + tx.size + config.dev.tx_extra_overhead > block_size_limit: txnum += 1 continue addr_from_pk_state = addresses_state[tx.txfrom] addr_from_pk = Transaction.get_slave(tx) if addr_from_pk: addr_from_pk_state = addresses_state[addr_from_pk] if tx.ots_key_reuse(addr_from_pk_state, tx.ots_key): del t_pool2[txnum] total_txn -= 1 continue if tx.subtype == qrl_pb2.Transaction.TRANSFER: if addresses_state[tx.txfrom].balance < tx.amount + tx.fee: logger.warning('%s %s exceeds balance, invalid tx', tx, tx.txfrom) logger.warning('subtype: %s', tx.subtype) logger.warning('Buffer State Balance: %s Transfer Amount %s', addresses_state[tx.txfrom].balance, tx.amount) del t_pool2[txnum] total_txn -= 1 continue if tx.subtype == qrl_pb2.Transaction.MESSAGE: if addresses_state[tx.txfrom].balance < tx.fee: logger.warning('%s %s exceeds balance, invalid message tx', tx, tx.txfrom) logger.warning('subtype: %s', tx.subtype) logger.warning('Buffer State Balance: %s Free %s', addresses_state[tx.txfrom].balance, tx.fee) total_txn -= 1 continue if tx.subtype == qrl_pb2.Transaction.TOKEN: if addresses_state[tx.txfrom].balance < tx.fee: logger.warning('%s %s exceeds balance, invalid tx', tx, tx.txfrom) logger.warning('subtype: %s', tx.subtype) logger.warning('Buffer State Balance: %s Fee %s', addresses_state[tx.txfrom].balance, tx.fee) del t_pool2[txnum] total_txn -= 1 continue if tx.subtype == qrl_pb2.Transaction.TRANSFERTOKEN: if addresses_state[tx.txfrom].balance < tx.fee: logger.warning('%s %s exceeds balance, invalid tx', tx, tx.txfrom) logger.warning('subtype: %s', tx.subtype) logger.warning('Buffer State Balance: %s Transfer Amount %s', addresses_state[tx.txfrom].balance, tx.fee) del t_pool2[txnum] total_txn -= 1 continue if bin2hstr(tx.token_txhash).encode() not in addresses_state[tx.txfrom].tokens: logger.warning('%s doesnt own any token with token_txnhash %s', tx.txfrom, bin2hstr(tx.token_txhash).encode()) del t_pool2[txnum] total_txn -= 1 continue if addresses_state[tx.txfrom].tokens[bin2hstr(tx.token_txhash).encode()] < tx.amount: logger.warning('Token Transfer amount exceeds available token') logger.warning('Token Txhash %s', bin2hstr(tx.token_txhash).encode()) logger.warning('Available Token Amount %s', addresses_state[tx.txfrom].tokens[bin2hstr(tx.token_txhash).encode()]) logger.warning('Transaction Amount %s', tx.amount) del t_pool2[txnum] total_txn -= 1 continue if tx.subtype == qrl_pb2.Transaction.LATTICE: if addresses_state[tx.txfrom].balance < tx.fee: logger.warning('Lattice TXN %s %s exceeds balance, invalid tx', tx, tx.txfrom) logger.warning('subtype: %s', tx.subtype) logger.warning('Buffer State Balance: %s Transfer Amount %s', addresses_state[tx.txfrom].balance, tx.fee) del t_pool2[txnum] total_txn -= 1 continue if tx.subtype == qrl_pb2.Transaction.SLAVE: if addresses_state[tx.txfrom].balance < tx.fee: logger.warning('Slave TXN %s %s exceeds balance, invalid tx', tx, tx.txfrom) logger.warning('subtype: %s', tx.subtype) logger.warning('Buffer State Balance: %s Transfer Amount %s', addresses_state[tx.txfrom].balance, tx.fee) del t_pool2[txnum] total_txn -= 1 continue tx.apply_on_state(addresses_state) tx_pool.add_tx_to_pool(tx) tx._data.nonce = addresses_state[tx.txfrom].nonce txnum += 1 block_size += tx.size + config.dev.tx_extra_overhead coinbase_nonce = self.state.get_address(signing_xmss.get_address()).nonce if signing_xmss.get_address() in addresses_state: coinbase_nonce = addresses_state[signing_xmss.get_address()].nonce + 1 block = Block.create(mining_nonce=mining_nonce, block_number=last_block.block_number + 1, prevblock_headerhash=last_block.headerhash, transactions=t_pool2, signing_xmss=signing_xmss, master_address=master_address, nonce=coinbase_nonce) return block