def test_calc_allowed_decimals(self): decimal = Transaction.calc_allowed_decimals(10000000000000000000) self.assertEqual(decimal, 0) decimal = Transaction.calc_allowed_decimals(1) self.assertEqual(decimal, 19) decimal = Transaction.calc_allowed_decimals(2) self.assertEqual(decimal, 18)
def rollback_tx_metadata(state: State, block, batch): fee_reward = 0 for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) fee_reward += txn.fee TransactionMetadata.remove_tx_metadata(state, txn, batch) txn = Transaction.from_pbdata(block.transactions[0]) # Coinbase Transaction state._update_total_coin_supply(fee_reward - txn.amount, batch) LastTransactions._remove_last_tx(state, block, batch)
def validate(self, chain_manager, future_blocks: OrderedDict) -> bool: if chain_manager.get_block_is_duplicate(self): logger.warning('Duplicate Block #%s %s', self.block_number, bin2hstr(self.headerhash)) return False parent_block = chain_manager.get_block(self.prev_headerhash) # If parent block not found in state, then check if its in the future block list if not parent_block: try: parent_block = future_blocks[self.prev_headerhash] except KeyError: logger.warning('Parent block not found') logger.warning('Parent block headerhash %s', bin2hstr(self.prev_headerhash)) return False if not self._validate_parent_child_relation(parent_block): logger.warning('Failed to validate blocks parent child relation') return False if not chain_manager.validate_mining_nonce(self.blockheader): 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(self.block_number): return False except Exception as e: logger.warning('Exception %s', e) return False hashedtransactions = [] for tx in self.transactions: tx = Transaction.from_pbdata(tx) hashedtransactions.append(tx.txhash) if not self.blockheader.validate(fee_reward, coinbase_amount, merkle_tx_hash(hashedtransactions)): return False return True
def PushTransaction(self, request: qrl_pb2.PushTransactionReq, context) -> qrl_pb2.PushTransactionResp: logger.debug("[PublicAPI] PushTransaction") answer = qrl_pb2.PushTransactionResp() try: tx = Transaction.from_pbdata(request.transaction_signed) tx.update_txhash() # FIXME: Full validation takes too much time. At least verify there is a signature # the validation happens later in the tx pool if len(tx.signature) > 1000: self.qrlnode.submit_send_tx(tx) answer.error_code = qrl_pb2.PushTransactionResp.SUBMITTED answer.tx_hash = tx.txhash else: answer.error_description = 'Signature too short' answer.error_code = qrl_pb2.PushTransactionResp.VALIDATION_FAILED except Exception as e: error_str = traceback.format_exception(None, e, e.__traceback__) answer.error_description = str(''.join(error_str)) answer.error_code = qrl_pb2.PushTransactionResp.ERROR return answer
def get_tx_metadata(state: State, txhash: bytes): try: tx_metadata = TransactionMetadata.deserialize(state._db.get_raw(txhash)) data, block_number = tx_metadata.transaction, tx_metadata.block_number return Transaction.from_pbdata(data), block_number except Exception: return None
def test_from_json(self, m_logger): tx = Transaction.from_json(test_json_TransferToken) tx.sign(self.alice) self.assertIsInstance(tx, TransferTokenTransaction) # Test that common Transaction components were copied over. self.assertEqual( '010300a1da274e68c88b0ccf448e0b1916fa789b01eb2ed4e9ad565ce264c9390782a9c61ac02f', bin2hstr(tx.addr_from)) self.assertEqual( '01030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada356' '5854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e', bin2hstr(tx.PK)) self.assertEqual(b'000000000000000', tx.token_txhash) self.assertEqual(200000, tx.total_amount) self.assertEqual( '390b159b34cffd29d4271a19679ff227df2ccd471078f177a7b58ca5f5d999f0', bin2hstr(tx.txhash)) self.assertEqual(10, tx.ots_key) # z = bin2hstr(tx.signature) # print('"', end='') # for i in range(len(z)): # print(z[i], end='') # if (i + 1) % 64 == 0: # print('" \\', end='') # print('') # print(' ' * len('test_signature_TransferToken = '), end='') # print('"', end='') self.assertEqual(test_signature_TransferToken, bin2hstr(tx.signature)) self.assertEqual(1, tx.fee)
def test_from_json(self, m_logger): tx = Transaction.from_json(test_json_Token) tx.sign(self.alice) self.assertIsInstance(tx, TokenTransaction) # Test that common Transaction components were copied over. self.assertEqual( '010300a1da274e68c88b0ccf448e0b1916fa789b01eb2ed4e9ad565ce264c9390782a9c61ac02f', bin2hstr(tx.addr_from)) self.assertEqual( '01030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada356' '5854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e', bin2hstr(tx.PK)) self.assertEqual(b'QRL', tx.symbol) self.assertEqual(b'Quantum Resistant Ledger', tx.name) self.assertEqual( '010317463dcd581b679b4754f46c6425125489a2826894e3c42a590efb6806450ce6bf52716c', bin2hstr(tx.owner)) self.assertEqual( 'ff84da605e9c9cd04d68503be7922110b4cc147837f8687ad18aa54b7bc5632d', bin2hstr(tx.txhash)) self.assertEqual(10, tx.ots_key) self.assertEqual(test_signature_Token, bin2hstr(tx.signature)) total_supply = 0 for initial_balance in tx.initial_balances: total_supply += initial_balance.amount self.assertEqual(600000000, total_supply) self.assertEqual(1, tx.fee)
def tx_push(ctx, txblob): """ Sends a signed transaction blob to a node """ tx = None try: txbin = parse_hexblob(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) stub = ctx.obj.get_stub_public_api() pushTransactionReq = qrl_pb2.PushTransactionReq( transaction_signed=tx.pbdata) pushTransactionResp = stub.PushTransaction(pushTransactionReq, timeout=CONNECTION_TIMEOUT) print(pushTransactionResp.error_code)
def test_from_json(self, m_logger): tx = Transaction.from_json(test_json_Simple) tx.sign(self.alice) self.assertIsInstance(tx, TransferTransaction) # Test that common Transaction components were copied over. self.assertEqual(0, tx.nonce) self.assertEqual( '010300a1da274e68c88b0ccf448e0b1916fa789b01eb2ed4e9ad565ce264c9390782a9c61ac02f', bin2hstr(tx.addr_from)) self.assertEqual( '01030038ea6375069f8272cc1a6601b3c76c21519455603d370036b97c779ada356' '5854e3983bd564298c49ae2e7fa6e28d4b954d8cd59398f1225b08d6144854aee0e', bin2hstr(tx.PK)) self.assertEqual( '554f546305d4aed6ec71c759942b721b904ab9d65eeac3c954c08c652181c4e8', bin2hstr(tx.txhash)) self.assertEqual(10, tx.ots_key) self.assertEqual(test_signature_Simple, bin2hstr(tx.signature)) # Test that specific content was copied over. self.assertEqual( '0103001d65d7e59aed5efbeae64246e0f3184d7c42411421eb385ba30f2c1c005a85ebc4419cfd', bin2hstr(tx.addrs_to[0])) self.assertEqual(100, tx.total_amount) self.assertEqual(1, tx.fee)
def apply_state_changes(self, address_txn) -> bool: coinbase_tx = Transaction.from_pbdata(self.transactions[0]) if not coinbase_tx.validate_extended(self.block_number): 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 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 update_tx_metadata(state: State, block, batch) -> bool: fee_reward = 0 for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) fee_reward += txn.fee if not TransactionMetadata.put_tx_metadata(state, txn, block.block_number, block.timestamp, batch): return False txn = Transaction.from_pbdata(block.transactions[0]) # Coinbase Transaction state._update_total_coin_supply(txn.amount - fee_reward, batch) LastTransactions._update_last_tx(state, block, batch) return True
def test_update_mining_address(self): self.block.update_mining_address(dev_config=config.dev, mining_address=bob.address) coinbase_tx = Transaction.from_pbdata(self.block.transactions[0]) self.assertTrue(isinstance(coinbase_tx, CoinBase)) self.assertEqual(coinbase_tx.addr_to, bob.address) hashedtransactions = [] for tx in self.block.transactions: hashedtransactions.append(tx.transaction_hash) self.assertEqual(self.block.blockheader.tx_merkle_root, merkle_tx_hash(hashedtransactions))
def create_block(self, last_block, mining_nonce, tx_pool: TransactionPool, miner_address) -> Optional[Block]: dummy_block = Block.create(block_number=last_block.block_number + 1, prev_headerhash=last_block.headerhash, prev_timestamp=last_block.timestamp, transactions=[], miner_address=miner_address) dummy_block.set_nonces(mining_nonce, 0) t_pool2 = tx_pool.transactions addresses_set = set() for tx_set in t_pool2: tx = tx_set[1].transaction tx.set_affected_address(addresses_set) addresses_state = dict() for address in addresses_set: addresses_state[address] = self._chain_manager.get_address_state( address) block_size = dummy_block.size block_size_limit = self._chain_manager.get_block_size_limit(last_block) transactions = [] for tx_set in t_pool2: tx = tx_set[1].transaction # Skip Transactions for later, which doesn't fit into block if block_size + tx.size + config.dev.tx_extra_overhead > block_size_limit: continue addr_from_pk_state = addresses_state[tx.addr_from] addr_from_pk = Transaction.get_slave(tx) if addr_from_pk: addr_from_pk_state = addresses_state[addr_from_pk] if not tx.validate_extended(addresses_state[tx.addr_from], addr_from_pk_state): logger.warning('Txn validation failed for tx in tx_pool') tx_pool.remove_tx_from_pool(tx) continue tx.apply_state_changes(addresses_state) tx._data.nonce = addr_from_pk_state.nonce block_size += tx.size + config.dev.tx_extra_overhead transactions.append(tx) block = Block.create(block_number=last_block.block_number + 1, prev_headerhash=last_block.headerhash, prev_timestamp=last_block.timestamp, transactions=transactions, miner_address=miner_address) return block
def update_mining_address(self, dev_config: DevConfig, mining_address: bytes): coinbase_tx = Transaction.from_pbdata(self.transactions[0]) coinbase_tx.update_mining_address(mining_address) hashedtransactions = [] for tx in self.transactions: hashedtransactions.append(tx.transaction_hash) self.blockheader.update_merkle_root(dev_config, merkle_tx_hash(hashedtransactions)) self._data.header.MergeFrom(self.blockheader.pbdata)
def test_validate_tx3(self, m_logger): tx = Transaction.from_json(test_json_MessageTransaction) tx.sign(self.alice) self.assertTrue(tx.validate_or_raise()) tx._data.transaction_hash = b'abc' # Should fail, as we have modified with invalid transaction_hash with self.assertRaises(ValueError): tx.validate_or_raise()
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 prepare_address_list(block) -> set: addresses = set() for proto_tx in block.transactions: tx = Transaction.from_pbdata(proto_tx) tx.set_affected_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 get_genesis_with_only_coin_base_txn(coin_base_reward_addr, dev_config): g = GenesisBlock() coin_base_tx = Transaction.from_pbdata(g.transactions[0]) coin_base_tx.update_mining_address(coin_base_reward_addr) # Remove all other transaction except CoinBase txn del g.transactions[:] g.pbdata.transactions.extend([coin_base_tx.pbdata]) g.blockheader.generate_headerhash(dev_config) return g
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, -1, -1): tx = Transaction.from_pbdata(block.transactions[tx_idx]) tx.revert_state_changes(addresses_state, self) 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 _remove_last_tx(self, block, batch): if len(block.transactions) == 0: return try: last_txn = LastTransactions.deserialize( self._db.get_raw(b'last_txn')) except: # noqa return for protobuf_txn in block.transactions: txn = Transaction.from_pbdata(protobuf_txn) i = 0 while i < len(last_txn.tx_metadata): tx = Transaction.from_pbdata( last_txn.tx_metadata[i].transaction) if txn.txhash == tx.txhash: del last_txn.tx_metadata[i] break i += 1 self._db.put_raw(b'last_txn', last_txn.serialize(), batch)
def valid_payment_permission(public_stub, master_address_state, payment_xmss, json_slave_txn): access_type = master_address_state.get_slave_permission(payment_xmss.pk) if access_type == -1: tx = Transaction.from_json(json_slave_txn) public_stub.PushTransaction(request=qrl_pb2.PushTransactionReq( transaction_signed=tx.pbdata)) return None if access_type == 0: return True return False
def get_last_txs(self): try: last_txn = LastTransactions.deserialize( self._db.get_raw(b'last_txn')) except: # noqa return [] txs = [] for tx_metadata in last_txn.tx_metadata: data = tx_metadata.transaction tx = Transaction.from_pbdata(data) txs.append(tx) return txs
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 test_update_mining_address(self): alice_xmss = get_alice_xmss() bob_xmss = get_bob_xmss() block = Block.create(block_number=5, prev_headerhash=bytes(sha2_256(b'test')), prev_timestamp=10, transactions=[], miner_address=alice_xmss.address) block.update_mining_address(mining_address=bob_xmss.address) coinbase_tx = Transaction.from_pbdata(block.transactions[0]) self.assertTrue(isinstance(coinbase_tx, CoinBase)) self.assertEqual(coinbase_tx.addr_to, bob_xmss.address) hashedtransactions = [] for tx in block.transactions: hashedtransactions.append(tx.transaction_hash) self.assertEqual(block.blockheader.tx_merkle_root, merkle_tx_hash(hashedtransactions))
def tx_inspect(ctx, txblob): """ Inspected a transaction blob """ tx = None try: txbin = parse_hexblob(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 get_last_txs(state: State): try: last_txn = LastTransactions.deserialize( state._db.get_raw(b'last_txn')) except KeyError: return [] except Exception as e: # noqa logger.warning("[get_last_txs] Exception during call %s", e) return [] txs = [] for tx_metadata in last_txn.tx_metadata: data = tx_metadata.transaction tx = Transaction.from_pbdata(data) txs.append(tx) return txs
def gen_blocks(block_count, state, miner_address): blocks = [] block = None with mock.patch('qrl.core.misc.ntp.getTime') as time_mock: time_mock.return_value = 1615270948 addresses_state = dict() for i in range(0, block_count): if i == 0: block = GenesisBlock() 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 else: block = Block.create(block_number=i, prev_headerhash=block.headerhash, prev_timestamp=block.timestamp, transactions=[], miner_address=miner_address) addresses_set = state.prepare_address_list(block) for address in addresses_set: addresses_state[address] = state.get_address_state(address) for tx_protobuf in block.transactions: tx = Transaction.from_pbdata(tx_protobuf) tx.apply_state_changes(addresses_state) block.set_nonces(10, 0) blocks.append(block) metadata = BlockMetadata() metadata.set_block_difficulty(StringToUInt256('256')) state.put_block_metadata(block.headerhash, metadata, None) state.put_block(block, None) bm = qrl_pb2.BlockNumberMapping( headerhash=block.headerhash, prev_headerhash=block.prev_headerhash) state.put_block_number_mapping(block.block_number, bm, None) state.update_mainchain_height(block.block_number, None) state.put_addresses_state(addresses_state) return blocks
def handle_tx(source, message: qrllegacy_pb2.LegacyMessage): """ Transaction Executed whenever a new TX type message is received. :return: """ P2PBaseObserver._validate_message(message, qrllegacy_pb2.LegacyMessage.TX) try: tx = Transaction.from_pbdata(message.txData) except Exception as e: logger.error('Message Txn rejected - unable to decode serialised data - closing connection') logger.exception(e) source.loseConnection() return # NOTE: Connects to MR if source.factory.master_mr.isRequested(tx.get_message_hash(), source): source.factory.add_unprocessed_txn(tx, source.peer.ip)
def _update_last_tx(self, block, batch): if len(block.transactions) == 0: return last_txn = LastTransactions() try: last_txn = LastTransactions.deserialize( self._db.get_raw(b'last_txn')) except: # noqa pass for protobuf_txn in block.transactions[-20:]: txn = Transaction.from_pbdata(protobuf_txn) if isinstance(txn, CoinBase): continue last_txn.add(txn, block.block_number, block.timestamp) self._db.put_raw(b'last_txn', last_txn.serialize(), batch)