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]) if not isinstance(coinbase_tx, CoinBase): return False if not coinbase_tx.validate_extended(): return False if not PoWValidator().validate_mining_nonce(self.state, block.blockheader): return False coinbase_tx.apply_on_state(address_txn) # 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 isinstance(tx, 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.addr_from] addr_from_pk = Transaction.get_slave(tx) if addr_from_pk: addr_from_pk_state = address_txn[addr_from_pk] if not tx.validate_extended(address_txn[tx.addr_from], addr_from_pk_state): return False expected_nonce = addr_from_pk_state.nonce + 1 if tx.nonce != expected_nonce: logger.warning('nonce incorrect, invalid tx') logger.warning('subtype: %s', tx.type) logger.warning('%s actual: %s expected: %s', tx.addr_from, tx.nonce, expected_nonce) return False if addr_from_pk_state.ots_key_reuse(tx.ots_key): logger.warning('pubkey reuse detected: invalid tx %s', tx.txhash) logger.warning('subtype: %s', tx.type) return False tx.apply_on_state(address_txn) return True
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 handle_slave(self, source, message: qrllegacy_pb2.LegacyMessage): """ Receives Lattice Public Key Transaction :param message: :return: """ P2PBaseObserver._validate_message(message, qrllegacy_pb2.LegacyMessage.SL) try: tx = Transaction.from_pbdata(message.slData) except Exception as e: logger.error( 'slave_txn rejected - unable to decode serialised data - closing connection' ) logger.exception(e) source.loseConnection() return if not source.factory.master_mr.isRequested(tx.get_message_hash(), source): return if not tx.validate(): logger.warning('>>>Slave Txn %s invalid state validation failed..', tx.hash) return source.factory.add_unprocessed_txn(tx, source.peer_ip)
def rollback(self, rollback_headerhash, hash_path, latest_block_number): while self.last_block.headerhash != rollback_headerhash: self.remove_block_from_mainchain(self.last_block, latest_block_number, None) self.last_block = self.state.get_block( self.last_block.prev_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 addresses_state = self.state.get_state_mainchain(address_set) for tx_idx in range(0, len(block.transactions)): tx = Transaction.from_pbdata(block.transactions[tx_idx]) tx.apply_state_changes(addresses_state) self.state.put_addresses_state(addresses_state) 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 validate(self, state) -> bool: if not PoWValidator().validate_mining_nonce(state, self.blockheader): logger.warning('Failed PoW Validation') return False fee_reward = 0 for index in range(1, len(self.transactions)): fee_reward += self.transactions[index].fee if len(self.transactions) == 0: return False try: coinbase_txn = Transaction.from_pbdata(self.transactions[0]) coinbase_amount = coinbase_txn.amount if not coinbase_txn.validate_extended(): return False except Exception as e: logger.warning('Exception %s', e) return False if not self.blockheader.validate(fee_reward, coinbase_amount): return False parent_block = state.get_block(self.prev_headerhash) if not self.validate_parent_child_relation(parent_block): logger.warning('Failed to validate blocks parent child relation') return False return True
def handle_message_transaction(self, source, message: qrllegacy_pb2.LegacyMessage): """ Message Transaction This function processes whenever a Transaction having subtype MESSAGE is received. :return: """ P2PBaseObserver._validate_message(message, qrllegacy_pb2.LegacyMessage.MT) try: tx = Transaction.from_pbdata(message.mtData) except Exception as e: logger.error( 'Message Txn rejected - unable to decode serialised data - closing connection' ) logger.exception(e) source.loseConnection() return if not source.factory.master_mr.isRequested(tx.get_message_hash(), source): return if tx.txhash in source.factory.buffered_chain.tx_pool.pending_tx_pool_hash: return source.factory.add_unprocessed_txn(tx, source.peer_ip)
def handle_transfer_token_transaction( source, message: qrllegacy_pb2.LegacyMessage): """ Transfer Token Transaction This function processes whenever a Transaction having subtype TRANSFERTOKEN is received. :return: """ P2PBaseObserver._validate_message(message, qrllegacy_pb2.LegacyMessage.TT) try: tx = Transaction.from_pbdata(message.ttData) except Exception as e: logger.error( 'Transfer Token Txn rejected - unable to decode serialised data - closing connection' ) logger.exception(e) source.loseConnection() return if not source.factory.master_mr.isRequested(tx.get_message_hash(), source): return source.factory.add_unprocessed_txn(tx, source.peer_ip)
def apply_state_changes(self, address_txn) -> bool: coinbase_tx = Transaction.from_pbdata(self.transactions[0]) if not coinbase_tx.validate_extended(): logger.warning('Coinbase transaction failed') return False coinbase_tx.apply_state_changes(address_txn) len_transactions = len(self.transactions) for tx_idx in range(1, len_transactions): tx = Transaction.from_pbdata(self.transactions[tx_idx]) if isinstance(tx, CoinBase): logger.warning('Found another coinbase transaction') return False if not tx.validate(): return False addr_from_pk_state = address_txn[tx.addr_from] addr_from_pk = Transaction.get_slave(tx) if addr_from_pk: addr_from_pk_state = address_txn[addr_from_pk] if not tx.validate_extended(address_txn[tx.addr_from], addr_from_pk_state): return False expected_nonce = addr_from_pk_state.nonce + 1 if tx.nonce != expected_nonce: logger.warning('nonce incorrect, invalid tx') logger.warning('subtype: %s', tx.type) logger.warning('%s actual: %s expected: %s', tx.addr_from, tx.nonce, expected_nonce) return False if addr_from_pk_state.ots_key_reuse(tx.ots_key): logger.warning('pubkey reuse detected: invalid tx %s', bin2hstr(tx.txhash)) logger.warning('subtype: %s', tx.type) return False tx.apply_state_changes(address_txn) return True
def slave_tx_generate(ctx, src, addr_from, number_of_slaves, access_type, fee, pk, otsidx): """ Generates Slave Transaction for the wallet """ try: address_src, src_xmss = _select_wallet(ctx, src) src_xmss.set_ots_index(otsidx) if len(addr_from.strip()) == 0: addr_from = address_src if src_xmss: address_src_pk = src_xmss.pk else: address_src_pk = pk.encode() fee_shor = int(fee * 1.e9) except Exception as e: click.echo("Error validating arguments") quit(1) slave_xmss = [] slave_pks = [] access_types = [] slave_xmss_seed = [] if number_of_slaves > 100: click.echo("Error: Max Limit for the number of slaves is 100") quit(1) for i in range(number_of_slaves): print("Generating Slave #" + str(i + 1)) xmss = XMSS.from_height(config.dev.xmss_tree_height) slave_xmss.append(xmss) slave_xmss_seed.append(xmss.extended_seed) slave_pks.append(xmss.pk) access_types.append(access_type) print("Successfully Generated Slave %s/%s" % (str(i + 1), number_of_slaves)) channel = grpc.insecure_channel(ctx.obj.node_public_address) stub = qrl_pb2_grpc.PublicAPIStub(channel) # FIXME: This could be problematic. Check slaveTxnReq = qrl_pb2.SlaveTxnReq(address_from=addr_from, slave_pks=slave_pks, access_types=access_types, fee=fee_shor, xmss_pk=address_src_pk, ) try: slaveTxnResp = stub.GetSlaveTxn(slaveTxnReq, timeout=5) tx = Transaction.from_pbdata(slaveTxnResp.transaction_unsigned) tx.sign(src_xmss) with open('slaves.json', 'w') as f: json.dump([bin2hstr(src_xmss.address), slave_xmss_seed, tx.to_json()], f) click.echo('Successfully created slaves.json') click.echo('Move slaves.json file from current directory to the mining node inside ~/.qrl/') except grpc.RpcError as e: click.echo(e.details()) quit(1) except Exception as e: click.echo("Unhandled error: {}".format(str(e))) quit(1)
def rollback_tx_metadata(self, block, batch): fee_reward = 0 for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) fee_reward -= txn.fee self.remove_tx_metadata(txn, batch) # FIXME: Being updated without batch, need to fix, if isinstance(txn, TransferTokenTransaction): self.remove_transfer_token_metadata(txn) elif isinstance(txn, TokenTransaction): self.remove_token_metadata(txn) self.decrease_txn_count(self.get_txn_count(txn.addr_from), txn.addr_from) txn = Transaction.from_pbdata(block.transactions[0]) # Coinbase Transaction self.update_total_coin_supply(fee_reward - txn.amount) self.remove_last_tx(block, batch)
def remove_tx_in_block_from_pool(self, block_obj: Block): for protobuf_tx in block_obj.transactions: tx = Transaction.from_pbdata(protobuf_tx) idx = self.get_tx_index_from_pool(tx.txhash) if idx > -1: del self.transaction_pool[idx] heapq.heapify(self.transaction_pool)
def update_vote_metadata(self, prev_stake_validators_tracker): self.total_stake_amount = prev_stake_validators_tracker.get_total_stake_amount( ) for vote_protobuf in self.block.vote: vote = Transaction.from_pbdata(vote_protobuf) if vote.headerhash == self.block.prev_headerhash: self.voted_weight += prev_stake_validators_tracker.get_stake_balance( vote.txfrom)
def GetObject(self, request: qrl_pb2.GetObjectReq, context) -> qrl_pb2.GetObjectResp: logger.debug("[PublicAPI] GetObject") answer = qrl_pb2.GetObjectResp() answer.found = False # FIXME: We need a unified way to access and validate data. query = bytes( request.query ) # query will be as a string, if Q is detected convert, etc. if AddressState.address_is_valid(query): if self.qrlnode.get_address_is_used(query): address_state = self.qrlnode.get_address_state(query) if address_state is not None: answer.found = True answer.address_state.CopyFrom(address_state.pbdata) return answer transaction, block_number = self.qrlnode.get_transaction(query) if transaction is not None: answer.found = True blockheader = None if block_number is not None: block = self.qrlnode.get_block_from_index(block_number) blockheader = block.blockheader.pbdata txextended = qrl_pb2.TransactionExtended( header=blockheader, tx=transaction.pbdata, addr_from=transaction.addr_from, size=transaction.size) answer.transaction.CopyFrom(txextended) return answer # NOTE: This is temporary, indexes are accepted for blocks try: block = self.qrlnode.get_block_from_hash(query) if block is None: query_str = query.decode() query_index = int(query_str) block = self.qrlnode.get_block_from_index(query_index) answer.found = True block_extended = qrl_pb2.BlockExtended() block_extended.header.CopyFrom(block.blockheader.pbdata) block_extended.size = block.size for transaction in block.transactions: tx = Transaction.from_pbdata(transaction) extended_tx = qrl_pb2.TransactionExtended( tx=transaction, addr_from=tx.addr_from, size=tx.size) block_extended.extended_transactions.extend([extended_tx]) answer.block_extended.CopyFrom(block_extended) return answer except Exception: pass return answer
def _parse_tx_object(source, message: qrllegacy_pb2.LegacyMessage, kind): tx = None try: tx = Transaction.from_pbdata(message.mtData) except Exception as e: logger.error('Message Txn rejected - unable to decode serialised data - closing connection') logger.exception(e) source.loseConnection() return tx
def search(self, query): # FIXME: Refactor this. Prepare a look up in the DB for block in self.blockchain: for protobuf_tx in block.transactions: tx = Transaction.from_pbdata(protobuf_tx) if tx.txhash == query or tx.txfrom == query or tx.txto == query: logger.info('%s found in block %s', query, str(block.block_number)) return tx return None
def PushTransaction(self, request: qrl_pb2.PushTransactionReq, context) -> qrl_pb2.PushTransactionResp: logger.debug("[PublicAPI] PushTransaction") tx = Transaction.from_pbdata(request.transaction_signed) submitted = self.qrlnode.submit_send_tx(tx) # FIXME: Improve response type # Prepare response answer = qrl_pb2.PushTransactionResp() answer.some_response = str(submitted) return answer
def update_tx_metadata(self, block, batch): fee_reward = 0 # TODO (cyyber): Move To State Cache, instead of writing directly for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) fee_reward += txn.fee self.put_tx_metadata(txn, block.block_number, block.timestamp, batch) # FIXME: Being updated without batch, need to fix, if isinstance(txn, TransferTokenTransaction): self.update_token_metadata(txn) elif isinstance(txn, TokenTransaction): self.create_token_metadata(txn) self.increase_txn_count(self.get_txn_count(txn.addr_from), txn.addr_from) txn = Transaction.from_pbdata( block.transactions[0]) # Coinbase Transaction self.update_total_coin_supply(txn.amount - fee_reward) self.update_last_tx(block, batch)
def update_mining_address(self, mining_address: bytes): coinbase_tx = Transaction.from_pbdata(self.transactions[0]) coinbase_tx.update_mining_address(mining_address) hashedtransactions = [coinbase_tx.txhash] for tx in self.transactions: hashedtransactions.append(tx.transaction_hash) self.blockheader.update_merkle_root(merkle_tx_hash(hashedtransactions)) self._data.header.MergeFrom(self.blockheader.pbdata)
def get_state(self, header_hash: bytes, addresses_set: set): tmp_header_hash = header_hash parent_headerhash = None addresses_state = dict() for address in addresses_set: addresses_state[address] = None while True: if self.state_objects.contains(header_hash): parent_headerhash = header_hash self.set_addresses_state(addresses_state, header_hash) break block = self.get_block(header_hash) if not block: logger.warning('[get_state] No Block Found %s', header_hash) break if block.block_number == 0: break header_hash = block.prev_headerhash for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address if not addresses_state[bytes_addr]: addresses_state[bytes_addr] = AddressState.get_default( bytes_addr) addresses_state[ bytes_addr]._data.balance = genesis_balance.balance for address in addresses_state: if not addresses_state[address]: addresses_state[address] = AddressState.get_default(address) header_hash = tmp_header_hash hash_path = [] while True: if parent_headerhash == header_hash: break block = self.get_block(header_hash) if not block: break hash_path.append(header_hash) header_hash = block.prev_headerhash if block.block_number == 0: break for header_hash in hash_path[-1::-1]: block = self.get_block(header_hash) for tx_pbdata in block.transactions: tx = Transaction.from_pbdata(tx_pbdata) tx.apply_on_state(addresses_state) return addresses_state
def remove_tx_in_block_from_pool(self, block_obj: Block): for protobuf_tx in block_obj.transactions: tx = Transaction.from_pbdata(protobuf_tx) idx = self.get_tx_index_from_pool(tx.txhash) if idx > -1: del self.transaction_pool[idx] addr_ots_hash = self.calc_addr_ots_hash(tx) self.address_ots_hash.remove(addr_ots_hash) heapq.heapify(self.transaction_pool)
def get_state(self, header_hash: bytes, addresses_set: set): tmp_header_hash = header_hash hash_path = [] while True: block = self.get_block(header_hash) if not block: raise Exception('[get_state] No Block Found %s, Initiator %s', header_hash, tmp_header_hash) mainchain_block = self.get_block_by_number(block.block_number) if mainchain_block and mainchain_block.headerhash == block.headerhash: break if block.block_number == 0: raise Exception( '[get_state] Alternate chain genesis is different, Initiator %s', tmp_header_hash) hash_path.append(header_hash) header_hash = block.prev_headerhash rollback_headerhash = header_hash addresses_state = dict() for address in addresses_set: addresses_state[address] = self.get_address_state(address) block = self.last_block while block.headerhash != rollback_headerhash: # Deplay transactions in reverse order, otherwise could result into negative value for tx_protobuf in block.transactions[-1::-1]: tx = Transaction.from_pbdata(tx_protobuf) tx.unapply_on_state(addresses_state, self) block = self.get_block(block.prev_headerhash) for header_hash in hash_path[-1::-1]: block = self.get_block(header_hash) for tx_pbdata in block.transactions: tx = Transaction.from_pbdata(tx_pbdata) tx.apply_on_state(addresses_state) return addresses_state, rollback_headerhash, hash_path
def prepare_address_list(block) -> set: addresses = set() for proto_tx in block.transactions: tx = Transaction.from_pbdata(proto_tx) tx.set_effected_address(addresses) for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address if bytes_addr not in addresses: addresses.add(bytes_addr) return addresses
def tx_transfer(ctx, src, master, dst, amounts, fee, ots_key_index): """ Transfer coins from src to dst """ if not ctx.obj.remote: click.echo('This command is unsupported for local wallets') return try: _, src_xmss = _select_wallet(ctx, src) if not src_xmss: click.echo("A local wallet is required to sign the transaction") quit(1) address_src_pk = src_xmss.pk src_xmss.set_ots_index(ots_key_index) addresses_dst = [] for addr in dst.split(' '): addresses_dst.append(bytes(hstr2bin(addr[1:]))) shor_amounts = [] for amount in amounts.split(' '): shor_amounts.append(int(float(amount) * 1.e9)) fee_shor = int(fee * 1.e9) except Exception: click.echo("Error validating arguments") quit(1) try: channel = grpc.insecure_channel(ctx.obj.node_public_address) stub = qrl_pb2_grpc.PublicAPIStub(channel) transferCoinsReq = qrl_pb2.TransferCoinsReq( addresses_to=addresses_dst, amounts=shor_amounts, fee=fee_shor, xmss_pk=address_src_pk, master_addr=master.encode()) transferCoinsResp = stub.TransferCoins(transferCoinsReq, timeout=5) tx = Transaction.from_pbdata( transferCoinsResp.extended_transaction_unsigned.tx) tx.sign(src_xmss) pushTransactionReq = qrl_pb2.PushTransactionReq( transaction_signed=tx.pbdata) pushTransactionResp = stub.PushTransaction(pushTransactionReq, timeout=5) print(pushTransactionResp) except Exception as e: print("Error {}".format(str(e)))
def remove_block_from_mainchain(self, block: Block, batch): addresses_set = self.state.prepare_address_list(block) addresses_state = self.state.get_state_mainchain(addresses_set) for tx_idx in range(len(block.transactions) - 1, 0, -1): tx = Transaction.from_pbdata(block.transactions[tx_idx]) tx.unapply_on_state(addresses_state, self.state) # TODO: Move txn from block to pool # self.tx_pool.add_tx_in_block_from_pool(block) self.state.update_mainchain_height(block.block_number - 1, batch) self.state.rollback_tx_metadata(block, batch) self.state.remove_blocknumber_mapping(block.block_number, batch)
def prepare_address_list(block) -> set: addresses = set() for proto_tx in block.transactions: tx = Transaction.from_pbdata(proto_tx) tx.set_effected_address(addresses) for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address.encode() if bytes_addr not in addresses: addresses.add(bytes_addr) return addresses
def update_tx_metadata(self, block, batch): if len(block.transactions) == 0: return token_list = [] # FIXME: Inconsistency in the keys/types for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) if txn.subtype in (qrl_pb2.Transaction.TRANSFER, qrl_pb2.Transaction.COINBASE, qrl_pb2.Transaction.MESSAGE, qrl_pb2.Transaction.TOKEN, qrl_pb2.Transaction.TRANSFERTOKEN): self._db.put(bin2hstr(txn.txhash), [txn.to_json(), block.block_number, block.timestamp], batch) if txn.subtype in (qrl_pb2.Transaction.TRANSFER, qrl_pb2.Transaction.MESSAGE, qrl_pb2.Transaction.TOKEN, qrl_pb2.Transaction.TRANSFERTOKEN): # FIXME: Being updated without batch, need to fix, # as its making get request, and batch get not possible # Thus cache is required to have only 1 time get self.update_address_tx_hashes(txn.txfrom, txn.txhash) if txn.subtype == qrl_pb2.Transaction.TOKEN: self.update_address_tx_hashes(txn.owner, txn.txhash) for initial_balance in txn.initial_balances: if initial_balance.address == txn.owner: continue self.update_address_tx_hashes(initial_balance.address, txn.txhash) if txn.subtype in (qrl_pb2.Transaction.TRANSFER, qrl_pb2.Transaction.COINBASE, qrl_pb2.Transaction.TRANSFERTOKEN): # FIXME: Being updated without batch, need to fix, if txn.subtype == qrl_pb2.Transaction.TRANSFERTOKEN: self.update_token_metadata(txn) self.update_address_tx_hashes(txn.txto, txn.txhash) self.increase_txn_count(txn.txto) if txn.subtype == qrl_pb2.Transaction.TOKEN: self.create_token_metadata(txn) token_list.append(txn.txhash) self.increase_txn_count(txn.txfrom) if token_list: self.update_token_list(token_list, batch)
def get_state(self, header_hash, addresses_set): tmp_header_hash = header_hash parent_headerhash = None addresses_state = dict() for address in addresses_set: addresses_state[address] = None while True: if self.state_objects.contains(header_hash): parent_headerhash = header_hash self.set_addresses_state(addresses_state, header_hash) break block = self.get_block(header_hash) if not block: logger.warning('[get_state] No Block Found %s', header_hash) break if block.block_number == 0: break header_hash = block.prev_headerhash for genesis_balance in GenesisBlock().genesis_balance: bytes_addr = genesis_balance.address.encode() if not addresses_state[bytes_addr]: addresses_state[bytes_addr] = AddressState.get_default(bytes_addr) addresses_state[bytes_addr]._data.balance = genesis_balance.balance for address in addresses_state: if not addresses_state[address]: addresses_state[address] = AddressState.get_default(address) header_hash = tmp_header_hash hash_path = [] while True: if parent_headerhash == header_hash: break block = self.get_block(header_hash) if not block: break hash_path.append(header_hash) header_hash = block.prev_headerhash if block.block_number == 0: break for header_hash in hash_path[-1::-1]: block = self.get_block(header_hash) for tx_pbdata in block.transactions: tx = Transaction.from_pbdata(tx_pbdata) tx.apply_on_state(addresses_state) return addresses_state
def remove_block_from_mainchain(self, block: Block, latest_block_number: int, batch): addresses_set = self.state.prepare_address_list(block) addresses_state = self.state.get_state_mainchain(addresses_set) for tx_idx in range(len(block.transactions) - 1, 0, -1): tx = Transaction.from_pbdata(block.transactions[tx_idx]) tx.revert_state_changes(addresses_state, self.state) self.tx_pool.add_tx_from_block_to_pool(block, latest_block_number) self.state.update_mainchain_height(block.block_number - 1, batch) self.state.rollback_tx_metadata(block, batch) self.state.remove_blocknumber_mapping(block.block_number, batch) self.state.put_addresses_state(addresses_state, batch)
def tx_sign(ctx, src, txblob): """ Sign a tx blob """ txbin = bytes(hstr2bin(txblob)) pbdata = qrl_pb2.Transaction() pbdata.ParseFromString(txbin) tx = Transaction.from_pbdata(pbdata) address_src, address_xmss = _select_wallet(ctx, src) tx.sign(address_xmss) txblob = bin2hstr(tx.pbdata.SerializeToString()) print(txblob)
def add_tx_from_block_to_pool(self, block: Block, current_block_number): """ Move all transactions from block to transaction pool. :param block: :return: """ for protobuf_tx in block.transactions[1:]: if not self.add_tx_to_pool(Transaction.from_pbdata(protobuf_tx), current_block_number): logger.warning( 'Failed to Add transaction into transaction pool') logger.warning('Block #%s %s', block.block_number, bin2hstr(block.headerhash)) return
def update_vote_metadata(self, block, batch): if len(block.transactions) == 0: return total_stake_amount = self.stake_validators_tracker.get_total_stake_amount() voted_weight = 0 # FIXME: Inconsistency in the keys/types for protobuf_txn in block.vote: vote = Transaction.from_pbdata(protobuf_txn) voted_weight += self.stake_validators_tracker.get_stake_balance(vote.txfrom) self._db.put(b'vote_'+str(block.block_number).encode(), [voted_weight, total_stake_amount], batch)
def validate(self, state, future_blocks: OrderedDict) -> bool: if self.is_duplicate(state): logger.warning('Duplicate Block #%s %s', self.block_number, bin2hstr(self.headerhash)) return False parent_block = state.get_block(self.prev_headerhash) # If parent block not found in state, then check if its in the future block list if not parent_block: try: parent_block = future_blocks[self.prev_headerhash] except KeyError: logger.warning('Parent block not found') logger.warning('Parent block headerhash %s', bin2hstr(self.prev_headerhash)) return False if not self.validate_parent_child_relation(parent_block): logger.warning('Failed to validate blocks parent child relation') return False if not PoWValidator().validate_mining_nonce(state, self.blockheader): logger.warning('Failed PoW Validation') return False fee_reward = 0 for index in range(1, len(self.transactions)): fee_reward += self.transactions[index].fee if len(self.transactions) == 0: return False try: coinbase_txn = Transaction.from_pbdata(self.transactions[0]) coinbase_amount = coinbase_txn.amount if not coinbase_txn.validate_extended(): return False except Exception as e: logger.warning('Exception %s', e) return False if not self.blockheader.validate(fee_reward, coinbase_amount): return False return True
def validate(self) -> bool: fee_reward = 0 for index in range(1, len(self.transactions)): fee_reward += self.transactions[index].fee if len(self.transactions) == 0: return False try: coinbase_txn = Transaction.from_pbdata(self.transactions[0]) coinbase_amount = coinbase_txn.amount except Exception as e: logger.warning('Exception %s', e) return False return self.blockheader.validate(fee_reward, coinbase_amount)
def tx_inspect(ctx, txblob): """ Inspected a transaction blob """ tx = None try: txbin = bytes(hstr2bin(txblob)) pbdata = qrl_pb2.Transaction() pbdata.ParseFromString(txbin) tx = Transaction.from_pbdata(pbdata) except Exception as e: click.echo("tx blob is not valid") quit(1) tmp_json = tx.to_json() # FIXME: binary fields are represented in base64. Improve output print(tmp_json)
def tx_transfer(ctx, src, dst, amount, fee): """ Transfer coins from src to dst """ if not ctx.obj.remote: click.echo('This command is unsupported for local wallets') return try: address_src, src_xmss = _select_wallet(ctx, src) if not src_xmss: click.echo("A local wallet is required to sign the transaction") quit(1) address_src_pk = src_xmss.pk() address_src_otsidx = src_xmss.get_index() address_dst = dst.encode() # FIXME: This could be problematic. Check amount_shor = int(amount * 1.e9) fee_shor = int(fee * 1.e9) except Exception as e: click.echo("Error validating arguments") quit(1) try: channel = grpc.insecure_channel(ctx.obj.node_public_address) stub = qrl_pb2_grpc.PublicAPIStub(channel) transferCoinsReq = qrl_pb2.TransferCoinsReq(address_from=address_src, address_to=address_dst, amount=amount_shor, fee=fee_shor, xmss_pk=address_src_pk, xmss_ots_index=address_src_otsidx) transferCoinsResp = stub.TransferCoins(transferCoinsReq, timeout=5) tx = Transaction.from_pbdata(transferCoinsResp.transaction_unsigned) tx.sign(src_xmss) pushTransactionReq = qrl_pb2.PushTransactionReq(transaction_signed=tx.pbdata) pushTransactionResp = stub.PushTransaction(pushTransactionReq, timeout=5) print(pushTransactionResp.some_response) except Exception as e: print("Error {}".format(str(e)))
def update_tx_metadata(self, block, batch): if len(block.transactions) == 0: return # TODO (cyyber): Move To State Cache, instead of writing directly for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) self._db.put(bin2hstr(txn.txhash), [txn.to_json(), block.block_number, block.timestamp], batch) # FIXME: Being updated without batch, need to fix, if txn.subtype == qrl_pb2.Transaction.TRANSFERTOKEN: self.update_token_metadata(txn) if txn.subtype == qrl_pb2.Transaction.TOKEN: self.create_token_metadata(txn) self.increase_txn_count(txn.txfrom) self.update_last_tx(block, batch)
def update_last_tx(self, block, batch): if len(block.transactions) == 0: return last_txn = [] try: last_txn = self._db.get('last_txn') except: # noqa pass for protobuf_txn in block.transactions[-20:]: txn = Transaction.from_pbdata(protobuf_txn) if txn.subtype == qrl_pb2.Transaction.COINBASE: continue last_txn.insert(0, [txn.to_json(), block.block_number, block.timestamp]) del last_txn[20:] self._db.put('last_txn', last_txn, batch)
def tx_push(ctx, txblob): tx = None try: txbin = bytes(hstr2bin(txblob)) pbdata = qrl_pb2.Transaction() pbdata.ParseFromString(txbin) tx = Transaction.from_pbdata(pbdata) except Exception as e: click.echo("tx blob is not valid") quit(1) tmp_json = tx.to_json() # FIXME: binary fields are represented in base64. Improve output print(tmp_json) if (len(tx.signature) == 0): click.echo('Signature missing') quit(1) channel = grpc.insecure_channel(ctx.obj.node_public_address) stub = qrl_pb2_grpc.PublicAPIStub(channel) pushTransactionReq = qrl_pb2.PushTransactionReq(transaction_signed=tx.pbdata) pushTransactionResp = stub.PushTransaction(pushTransactionReq, timeout=5) print(pushTransactionResp.some_response)
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 slave_tx_generate(ctx, src, addr_from, number_of_slaves, access_type, fee, pk, otsidx): """ Generates Slave Transaction for the wallet """ try: address_src, src_xmss = _select_wallet(ctx, src) if len(addr_from.strip()) == 0: addr_from = address_src if src_xmss: address_src_pk = src_xmss.pk() address_src_otsidx = src_xmss.get_index() else: address_src_pk = pk.encode() address_src_otsidx = int(otsidx) fee_shor = int(fee * 1.e9) except Exception as e: click.echo("Error validating arguments") quit(1) slave_xmss = [] slave_pks = [] access_types = [] slave_xmss_seed = [] if number_of_slaves > 100: click.echo("Error: Max Limit for the number of slaves is 100") quit(1) for i in range(number_of_slaves): print("Generating Slave #"+str(i+1)) xmss = XMSS(config.dev.xmss_tree_height) slave_xmss.append(xmss) slave_xmss_seed.append(xmss.get_seed()) slave_pks.append(xmss.pk()) access_types.append(access_type) print("Successfully Generated Slave %s/%s" % (str(i + 1), number_of_slaves)) channel = grpc.insecure_channel(ctx.obj.node_public_address) stub = qrl_pb2_grpc.PublicAPIStub(channel) # FIXME: This could be problematic. Check slaveTxnReq = qrl_pb2.SlaveTxnReq(address_from=addr_from, slave_pks=slave_pks, access_types=access_types, fee=fee_shor, xmss_pk=address_src_pk, xmss_ots_index=address_src_otsidx) try: slaveTxnResp = stub.GetSlaveTxn(slaveTxnReq, timeout=5) tx = Transaction.from_pbdata(slaveTxnResp.transaction_unsigned) tx.sign(src_xmss) with open('slaves.json', 'w') as f: json.dump([src_xmss.get_address(), slave_xmss_seed, tx.to_json()], f) click.echo('Successfully created slaves.json') click.echo('Move slaves.json file from current directory to the mining node inside ~/.qrl/') except grpc.RpcError as e: click.echo(e.details()) quit(1) except Exception as e: click.echo("Unhandled error: {}".format(str(e))) quit(1)
def remove_tx_in_block_from_pool(self, block_obj: Block): for protobuf_tx in block_obj.transactions: tx = Transaction.from_pbdata(protobuf_tx) for txn in self.transaction_pool: if tx.txhash == txn.txhash: self.remove_tx_from_pool(txn)