def verify_weight(self) -> None: """Validate minimum tx difficulty.""" min_tx_weight = daa.minimum_tx_weight(self) if self.weight < min_tx_weight - settings.WEIGHT_TOL: raise InvalidNewTransaction( f'Invalid new tx {self.hash_hex}: weight ({self.weight}) is ' f'smaller than the minimum weight ({min_tx_weight})')
def test_genesis_weight(self): genesis = self.storage.get_all_genesis() genesis_blocks = [tx for tx in genesis if tx.is_block] genesis_block = genesis_blocks[0] genesis_txs = [tx for tx in genesis if not tx.is_block] genesis_tx = genesis_txs[0] # Validate the block and tx weight # in test mode weight is always 1 _set_test_mode(TestMode.TEST_ALL_WEIGHT) self.assertEqual(calculate_block_difficulty(genesis_block), 1) self.assertEqual(minimum_tx_weight(genesis_tx), 1) _set_test_mode(TestMode.DISABLED) self.assertEqual(calculate_block_difficulty(genesis_block), genesis_block.weight) self.assertEqual(minimum_tx_weight(genesis_tx), genesis_tx.weight)
def new_tx_step1(self): """ Generate a new transaction and schedule the mining part of the transaction. """ balance = self.manager.wallet.balance[settings.HATHOR_TOKEN_UID] if balance.available == 0 and self.ignore_no_funds: self.delayedcall = self.clock.callLater( 0, self.schedule_next_transaction) return if not self.send_to: address = self.manager.wallet.get_unused_address( mark_as_used=False) else: address = self.rng.choice(self.send_to) value = self.rng.randint(1, balance.available) self.log.debug('randomized step: send to', address=address, amount=value / 100) if not self.double_spending_only: try: tx = gen_new_tx(self.manager, address, value) except (InsufficientFunds, RewardLocked): self.delayedcall = self.clock.callLater( 0, self.schedule_next_transaction) return else: try: tx = gen_new_double_spending(self.manager) tx.nonce = self.rng.getrandbits(32) except NoCandidatesError: self.delayedcall = self.clock.callLater( 0, self.schedule_next_transaction) return tx.weight = daa.minimum_tx_weight(tx) tx.update_hash() geometric_p = 2**(-tx.weight) trials = self.rng.geometric(geometric_p) dt = 1.0 * trials / self.hashpower self.tx = tx self.delayedcall = self.clock.callLater(dt, self.schedule_next_transaction) self.log.debug('randomized step: schedule next transaction', dt=dt, hash=tx.hash_hex)
def render_POST(self, request): """ Post request /create_tx/ that returns an encoded tx, if valid Expects {"inputs":[{"tx_id": <hex encoded>, "index": <int>, "data": <optional base64 encoded>}], "outputs":[{"value": <int, 1.00 HTR = 100>, "token_uid": <optional omit for HTR, hex encoded>, "address" or "script"}]} as POST data """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'POST') body_content = json_loadb(request.content.read()) raw_inputs = body_content.get('inputs', []) raw_outputs = body_content.get('outputs', []) inputs = [TxInput.create_from_dict(i) for i in raw_inputs] tokens = [] outputs = [from_raw_output(i, tokens) for i in raw_outputs] timestamp = int(max(self.manager.tx_storage.latest_timestamp, self.manager.reactor.seconds())) parents = self.manager.get_new_tx_parents(timestamp) # this tx will have to be mined by tx-mining-server or equivalent tx = Transaction( timestamp=timestamp, inputs=inputs, outputs=outputs, parents=parents, storage=self.manager.tx_storage, ) fake_signed_tx = tx.clone() for tx_input in fake_signed_tx.inputs: # conservative estimate of the input data size to estimate a valid weight tx_input.data = b'\0' * 107 tx.weight = minimum_tx_weight(fake_signed_tx) tx.verify_unsigned_skip_pow() if tx.is_double_spending(): raise InvalidNewTransaction('At least one of your inputs has already been spent.') hex_data = bytes(tx).hex() data = tx.to_json() data.pop('hash', None) data.pop('nonce', None) return json_dumpb({ 'success': True, 'hex_data': hex_data, 'data': data, })
def _render_POST_thread(self, values: Dict[str, Any], request: Request) -> Union[bytes, Transaction]: tx = self.manager.wallet.prepare_transaction(Transaction, values['inputs'], values['outputs'], values['timestamp']) tx.storage = values['storage'] tx.parents = values['parents'] weight = values['weight'] if weight is None: weight = minimum_tx_weight(tx) tx.weight = weight tx.resolve() tx.verify() return tx
def render_GET(self, request): """ Get request /decode_tx/ that returns the signed tx, if success Expects 'hex_tx' as GET parameter :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'GET') if b'hex_tx' in request.args: requested_decode = request.args[b'hex_tx'][0].decode('utf-8') else: return get_missing_params_msg('hex_tx') pattern = r'[a-fA-F\d]+' if re.match(pattern, requested_decode) and len(requested_decode) % 2 == 0: tx_bytes = bytes.fromhex(requested_decode) prepare_to_send = False if b'prepare_to_send' in request.args: _prepare_to_send = request.args[b'prepare_to_send'][0].decode('utf-8') prepare_to_send = _prepare_to_send == 'true' try: tx = Transaction.create_from_struct(tx_bytes) tx.storage = self.manager.tx_storage self.manager.wallet.sign_transaction(tx, self.manager.tx_storage) if prepare_to_send: tx.parents = self.manager.get_new_tx_parents() tx.update_timestamp(int(self.manager.reactor.seconds())) tx.weight = minimum_tx_weight(tx) tx.resolve() data = {'hex_tx': tx.get_struct().hex(), 'success': True} except struct.error: data = {'success': False, 'message': 'Transaction invalid'} else: data = {'success': False, 'message': 'Transaction invalid'} return json.dumps(data).encode('utf-8')
def test_post(self): # Unlocking wallet self.manager.wallet.unlock(b'MYPASS') blocks = add_new_blocks(self.manager, 3, advance_clock=1) add_blocks_unlock_reward(self.manager) blocks_tokens = [ sum(txout.value for txout in blk.outputs) for blk in blocks ] self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID].available, sum(blocks_tokens)) # Options yield self.web.options('thin_wallet/send_tokens') tx_id = blocks[0].hash output = blocks[0].outputs[0] script_type_out = parse_address_script(output.script) address = script_type_out.address private_key = self.manager.wallet.get_private_key(address) output_address = decode_address(self.get_address(0)) value = blocks_tokens[0] o = TxOutput(value, create_output_script(output_address, None)) o_invalid_amount = TxOutput(value - 1, create_output_script(output_address, None)) i = TxInput(tx_id, 0, b'') # wrong weight tx = Transaction(inputs=[i], outputs=[o]) data_to_sign = tx.get_sighash_all() public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data( data_to_sign, private_key) i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes) tx.inputs = [i] tx.timestamp = int(self.clock.seconds()) tx.weight = 0 response = yield self.web.post('thin_wallet/send_tokens', {'tx_hex': tx.get_struct().hex()}) data = response.json_value() self.assertFalse(data['success']) # Error wrong amount tx2 = Transaction(inputs=[i], outputs=[o_invalid_amount]) data_to_sign = tx2.get_sighash_all() public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data( data_to_sign, private_key) i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes) tx2.inputs = [i] tx2.timestamp = int(self.clock.seconds()) tx2.weight = minimum_tx_weight(tx2) response_wrong_amount = yield self.web.post( 'thin_wallet/send_tokens', {'tx_hex': tx2.get_struct().hex()}) data_wrong_amount = response_wrong_amount.json_value() self.assertFalse(data_wrong_amount['success']) # successful tx tx3 = Transaction(inputs=[i], outputs=[o]) data_to_sign = tx3.get_sighash_all() public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data( data_to_sign, private_key) i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes) tx3.inputs = [i] tx3.timestamp = int(self.clock.seconds()) tx3.weight = minimum_tx_weight(tx3) # Then send tokens response = yield self.web.post('thin_wallet/send_tokens', {'tx_hex': tx3.get_struct().hex()}) data = response.json_value() self.assertTrue(data['success']) # Trying to send a double spending will not have success self.clock.advance(5) tx3.timestamp = int(self.clock.seconds()) response = yield self.web.post('thin_wallet/send_tokens', {'tx_hex': tx3.get_struct().hex()}) data_error = response.json_value() self.assertFalse(data_error['success']) self.clock.advance(5) # Check if tokens were really sent self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID].available, sum(blocks_tokens[:-1])) response_history = yield self.web_address_history.get( 'thin_wallet/address_history', { b'addresses[]': address.encode(), }) response_data = response_history.json_value()['history'] self.assertIn(data['tx']['hash'], [x['tx_id'] for x in response_data]) # Create token tx tx4 = create_tokens(self.manager, address, mint_amount=100, propagate=False) tx4.nonce = 0 tx4.timestamp = int(self.clock.seconds()) response = yield self.web.post('thin_wallet/send_tokens', {'tx_hex': tx4.get_struct().hex()}) data = response.json_value() self.assertTrue(data['success'])
def test_token_history(self): self.manager.wallet.unlock(b'MYPASS') resource = StubSite(TokenHistoryResource(self.manager)) add_new_blocks(self.manager, 1, advance_clock=1) add_blocks_unlock_reward(self.manager) tx = create_tokens(self.manager, mint_amount=100, token_name='Teste', token_symbol='TST') token_uid = tx.tokens[0] response = yield resource.get('thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': b'3' }) data = response.json_value() # Success returning the token creation tx self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(1, len(data['transactions'])) self.assertEqual(tx.hash.hex(), data['transactions'][0]['tx_id']) response = yield resource.get('thin_wallet/token_history', { b'id': b'123', b'count': b'3' }) data = response.json_value() # Fail because token is unknown self.assertFalse(data['success']) # Create a tx with this token, so we can have more tx in the history output = tx.outputs[0] script_type_out = parse_address_script(output.script) address = script_type_out.address private_key = self.manager.wallet.get_private_key(address) output_address = decode_address(self.get_address(0)) o = TxOutput(100, create_output_script(output_address, None), 1) i = TxInput(tx.hash, 0, b'') tx2 = Transaction(inputs=[i], outputs=[o], tokens=[token_uid]) data_to_sign = tx2.get_sighash_all() public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data( data_to_sign, private_key) i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes) tx2.inputs = [i] tx2.timestamp = int(self.clock.seconds()) tx2.weight = minimum_tx_weight(tx2) tx2.parents = self.manager.get_new_tx_parents() tx2.resolve() self.manager.propagate_tx(tx2) # Now we have 2 txs with this token response = yield resource.get('thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': b'3' }) data = response.json_value() # Success returning the token creation tx and newly created tx self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(2, len(data['transactions'])) self.assertEqual(tx2.hash.hex(), data['transactions'][0]['tx_id']) self.assertEqual(tx.hash.hex(), data['transactions'][1]['tx_id']) response = yield resource.get('thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': b'1' }) data = response.json_value() # Testing has_more self.assertTrue(data['success']) self.assertTrue(data['has_more']) self.assertEqual(1, len(data['transactions'])) response = yield resource.get( 'thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': b'10', b'page': b'next', b'hash': tx2.hash.hex().encode(), b'timestamp': str(tx2.timestamp).encode(), }) data = response.json_value() # Testing next self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(1, len(data['transactions'])) self.assertEqual(tx.hash.hex(), data['transactions'][0]['tx_id']) response = yield resource.get( 'thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': b'10', b'page': b'previous', b'hash': tx.hash.hex().encode(), b'timestamp': str(tx.timestamp).encode(), }) data = response.json_value() # Testing previous self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(1, len(data['transactions'])) self.assertEqual(tx2.hash.hex(), data['transactions'][0]['tx_id']) response = yield resource.get( 'thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': b'10', b'page': b'previous', b'hash': tx2.hash.hex().encode(), b'timestamp': str(tx2.timestamp).encode(), }) data = response.json_value() # Testing previous from first self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(0, len(data['transactions']))