def setUp(self): super().setUp() self.directory = tempfile.mkdtemp(dir='/tmp/') self.storage = TransactionMemoryStorage() self.manager = self.create_peer('testnet', unlock_wallet=True) # read genesis keys self.genesis_private_key = get_genesis_key() self.genesis_public_key = self.genesis_private_key.public_key()
class GenesisTest(unittest.TestCase): def setUp(self): super().setUp() self.storage = TransactionMemoryStorage() def test_pow(self): genesis = self.storage.get_all_genesis() for g in genesis: self.assertEqual(g.calculate_hash(), g.hash) self.assertIsNone(g.verify_pow()) def test_verify(self): genesis = self.storage.get_all_genesis() for g in genesis: g.verify_without_storage() def test_output(self): # Test if block output is valid genesis = self.storage.get_all_genesis() for g in genesis: if g.is_block: for output in g.outputs: self.assertEqual(output.script.hex(), get_genesis_output()) def test_genesis_tokens(self): genesis = self.storage.get_all_genesis() genesis_blocks = [tx for tx in genesis if tx.is_block] genesis_block = genesis_blocks[0] self.assertEqual( settings.GENESIS_TOKENS, sum([output.value for output in genesis_block.outputs])) 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] network = 'testnet' manager = self.create_peer(network, unlock_wallet=True) # Validate the block and tx weight # in test mode weight is always 1 self.assertEqual(manager.calculate_block_difficulty(genesis_block), 1) self.assertEqual(manager.minimum_tx_weight(genesis_tx), 1) manager.test_mode = TestMode.DISABLED self.assertEqual(manager.calculate_block_difficulty(genesis_block), genesis_block.weight) self.assertEqual(manager.minimum_tx_weight(genesis_tx), genesis_tx.weight)
def setUp(self): super().setUp() tx_storage = TransactionMemoryStorage() self.genesis_blocks = [ tx for tx in tx_storage.get_all_genesis() if tx.is_block ] self.genesis_txs = [ tx for tx in tx_storage.get_all_genesis() if not tx.is_block ] # read genesis keys self.genesis_private_key = get_genesis_key() self.genesis_public_key = self.genesis_private_key.public_key()
def setUp(self): super().setUp() self.wallet = Wallet() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] # read genesis keys self.genesis_private_key = get_genesis_key() self.genesis_public_key = self.genesis_private_key.public_key() # this makes sure we can spend the genesis outputs self.manager = self.create_peer('testnet', tx_storage=self.tx_storage, unlock_wallet=True) blocks = add_blocks_unlock_reward(self.manager) self.last_block = blocks[-1]
def test_manager_connections(self): tx_storage = TransactionMemoryStorage() tmpdir = tempfile.mkdtemp() wallet = Wallet(directory=tmpdir) wallet.unlock(b'teste') manager = HathorManager(self.clock, tx_storage=tx_storage, wallet=wallet) endpoint = 'tcp://127.0.0.1:8005' manager.connections.connect_to(endpoint, use_ssl=True) self.assertFalse(endpoint in manager.connections.connecting_peers) self.assertFalse(endpoint in manager.connections.handshaking_peers) self.assertFalse(endpoint in manager.connections.connected_peers) manager.stop() manager.stop() shutil.rmtree(tmpdir)
def test_manager_connections(self): tx_storage = TransactionMemoryStorage() tmpdir = tempfile.mkdtemp() wallet = Wallet(directory=tmpdir) wallet.unlock(b'teste') manager = HathorManager(self.clock, tx_storage=tx_storage, wallet=wallet) endpoint = 'tcp://127.0.0.1:8005' manager.connections.connect_to(endpoint, use_ssl=True) self.assertNotIn(endpoint, manager.connections.iter_not_ready_endpoints()) self.assertNotIn(endpoint, manager.connections.iter_ready_connections()) self.assertNotIn(endpoint, manager.connections.iter_all_connections()) shutil.rmtree(tmpdir)
def setUp(self): super().setUp() store = TransactionMemoryStorage() self.cache_storage = TransactionCacheStorage(store, self.clock, capacity=5) self.cache_storage._manually_initialize() self.cache_storage.pre_init() self.genesis = self.cache_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] # Save genesis metadata self.cache_storage.save_transaction(self.genesis_txs[0], only_metadata=True) self.manager = self.create_peer('testnet', tx_storage=self.cache_storage, unlock_wallet=True)
class BaseAccumulatedWeightTestCase(unittest.TestCase): __test__ = False def setUp(self): super().setUp() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] def test_accumulated_weight_indirect_block(self): """ All new blocks belong to case (i). """ self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # Mine 3 blocks in a row with no transaction but the genesis blocks = add_new_blocks(manager, 3, advance_clock=15) add_blocks_unlock_reward(manager) # Add some transactions between blocks tx_list = add_new_transactions(manager, 20, advance_clock=15) # Mine more 2 blocks in a row with no transactions between them blocks = add_new_blocks(manager, 2, weight=8) tx0 = tx_list[0] for block in blocks: self.assertNotIn(tx0.hash, block.parents) # All transactions and blocks should be verifying tx_list[0] directly or # indirectly. expected = 0 for tx in tx_list: expected = sum_weights(expected, tx.weight) for block in blocks: expected = sum_weights(expected, block.weight) meta = tx0.update_accumulated_weight() self.assertAlmostEqual(meta.accumulated_weight, expected)
def start_remote_storage(tx_storage=None): """ Starts a remote storage :param tx_storage: storage to run in the remote storage :type tx_storage: :py:class:`hathor.transaction.storage.TransactionStorage` :return: Remote tx storage and the remote server :rtype: Tuple[:py:class:`hathor.transaction.storage.TransactionRemoteStorage`, grpc server] """ if not tx_storage: tx_storage = TransactionMemoryStorage() _server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) tx_storage._manually_initialize() _servicer, port = create_transaction_storage_server(_server, tx_storage) _server.start() tx_storage = TransactionRemoteStorage() tx_storage.connect_to(port) return tx_storage, _server
def prepare(self, args: Namespace) -> None: import hathor from hathor.cli.util import check_or_exit from hathor.conf import HathorSettings from hathor.conf.get_settings import get_settings_module from hathor.daa import TestMode, _set_test_mode from hathor.manager import HathorManager from hathor.p2p.peer_discovery import BootstrapPeerDiscovery, DNSPeerDiscovery from hathor.p2p.peer_id import PeerId from hathor.p2p.utils import discover_hostname from hathor.transaction import genesis from hathor.transaction.storage import ( TransactionCacheStorage, TransactionCompactStorage, TransactionMemoryStorage, TransactionRocksDBStorage, TransactionStorage, ) from hathor.wallet import HDWallet, Wallet settings = HathorSettings() settings_module = get_settings_module() # only used for logging its location self.log = logger.new() from setproctitle import setproctitle setproctitle('{}hathor-core'.format(args.procname_prefix)) if args.recursion_limit: sys.setrecursionlimit(args.recursion_limit) else: sys.setrecursionlimit(5000) try: import resource except ModuleNotFoundError: pass else: (nofile_soft, _) = resource.getrlimit(resource.RLIMIT_NOFILE) if nofile_soft < 256: print('Maximum number of open file descriptors is too low. Minimum required is 256.') sys.exit(-2) if not args.peer: peer_id = PeerId() else: data = json.load(open(args.peer, 'r')) peer_id = PeerId.create_from_json(data) python = f'{platform.python_version()}-{platform.python_implementation()}' self.check_unsafe_arguments(args) self.log.info( 'hathor-core v{hathor}', hathor=hathor.__version__, pid=os.getpid(), genesis=genesis.GENESIS_HASH.hex()[:7], my_peer_id=str(peer_id.id), python=python, platform=platform.platform(), settings=settings_module.__file__, ) def create_wallet(): if args.wallet == 'hd': kwargs = { 'words': args.words, } if args.passphrase: wallet_passphrase = getpass.getpass(prompt='HD Wallet passphrase:') kwargs['passphrase'] = wallet_passphrase.encode() if args.data: kwargs['directory'] = args.data return HDWallet(**kwargs) elif args.wallet == 'keypair': print('Using KeyPairWallet') if args.data: wallet = Wallet(directory=args.data) else: wallet = Wallet() wallet.flush_to_disk_interval = 5 # seconds if args.unlock_wallet: wallet_passwd = getpass.getpass(prompt='Wallet password:'******'Invalid type for wallet') tx_storage: TransactionStorage if args.memory_storage: check_or_exit(not args.data, '--data should not be used with --memory-storage') # if using MemoryStorage, no need to have cache tx_storage = TransactionMemoryStorage() assert not args.x_rocksdb_indexes, 'RocksDB indexes require RocksDB data' self.log.info('with storage', storage_class=type(tx_storage).__name__) elif args.json_storage: check_or_exit(args.data, '--data is expected') assert not args.x_rocksdb_indexes, 'RocksDB indexes require RocksDB data' tx_storage = TransactionCompactStorage(path=args.data, with_index=(not args.cache)) else: check_or_exit(args.data, '--data is expected') if args.rocksdb_storage: self.log.warn('--rocksdb-storage is now implied, no need to specify it') cache_capacity = args.rocksdb_cache use_memory_indexes = not args.x_rocksdb_indexes tx_storage = TransactionRocksDBStorage(path=args.data, with_index=(not args.cache), cache_capacity=cache_capacity, use_memory_indexes=use_memory_indexes) self.log.info('with storage', storage_class=type(tx_storage).__name__, path=args.data) if args.cache: check_or_exit(not args.memory_storage, '--cache should not be used with --memory-storage') tx_storage = TransactionCacheStorage(tx_storage, reactor) if args.cache_size: tx_storage.capacity = args.cache_size if args.cache_interval: tx_storage.interval = args.cache_interval self.log.info('with cache', capacity=tx_storage.capacity, interval=tx_storage.interval) self.tx_storage = tx_storage self.log.info('with indexes', indexes_class=type(tx_storage.indexes).__name__) if args.wallet: self.wallet = create_wallet() self.log.info('with wallet', wallet=self.wallet, path=args.data) else: self.wallet = None if args.hostname and args.auto_hostname: print('You cannot use --hostname and --auto-hostname together.') sys.exit(-1) if not args.auto_hostname: hostname = args.hostname else: print('Trying to discover your hostname...') hostname = discover_hostname() if not hostname: print('Aborting because we could not discover your hostname.') print('Try again or run without --auto-hostname.') sys.exit(-1) print('Hostname discovered and set to {}'.format(hostname)) network = settings.NETWORK_NAME enable_sync_v1 = not args.x_sync_v2_only enable_sync_v2 = args.x_sync_v2_only or args.x_sync_bridge self.manager = HathorManager( reactor, peer_id=peer_id, network=network, hostname=hostname, tx_storage=self.tx_storage, wallet=self.wallet, wallet_index=args.wallet_index, stratum_port=args.stratum, ssl=True, checkpoints=settings.CHECKPOINTS, enable_sync_v1=enable_sync_v1, enable_sync_v2=enable_sync_v2, soft_voided_tx_ids=set(settings.SOFT_VOIDED_TX_IDS), ) if args.allow_mining_without_peers: self.manager.allow_mining_without_peers() if args.x_localhost_only: self.manager.connections.localhost_only = True dns_hosts = [] if settings.BOOTSTRAP_DNS: dns_hosts.extend(settings.BOOTSTRAP_DNS) if args.dns: dns_hosts.extend(args.dns) if dns_hosts: self.manager.add_peer_discovery(DNSPeerDiscovery(dns_hosts)) if args.bootstrap: self.manager.add_peer_discovery(BootstrapPeerDiscovery(args.bootstrap)) if args.test_mode_tx_weight: _set_test_mode(TestMode.TEST_TX_WEIGHT) if self.wallet: self.wallet.test_mode = True if args.x_full_verification: self.manager._full_verification = True if args.x_fast_init_beta: self.log.warn('--x-fast-init-beta is now the default, no need to specify it') for description in args.listen: self.manager.add_listen_address(description) self.start_manager(args) self.register_resources(args)
class BasicTransaction(unittest.TestCase): def setUp(self): super().setUp() self.wallet = Wallet() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] # read genesis keys self.genesis_private_key = get_genesis_key() self.genesis_public_key = self.genesis_private_key.public_key() # this makes sure we can spend the genesis outputs self.manager = self.create_peer('testnet', tx_storage=self.tx_storage, unlock_wallet=True, wallet_index=True) blocks = add_blocks_unlock_reward(self.manager) self.last_block = blocks[-1] def test_input_output_match(self): genesis_block = self.genesis_blocks[0] _input = TxInput(genesis_block.hash, 0, b'') # spend less than what was generated value = genesis_block.outputs[0].value - 1 address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) tx = Transaction(inputs=[_input], outputs=[output], storage=self.tx_storage) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) with self.assertRaises(InputOutputMismatch): tx.verify_sum() def test_script(self): genesis_block = self.genesis_blocks[0] # random keys to be used random_priv = 'MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgMnAHVIyj7Hym2yI' \ 'w+JcKEfdCHByIp+FHfPoIkcnjqGyhRANCAATX76SGshGeoacUcZDhXEzERt' \ 'AHbd30CVpUg8RRnAIhaFcuMY3G+YFr/mReAPRuiLKCnolWz3kCltTtNj36rJyd' private_key_random = get_private_key_from_bytes( base64.b64decode(random_priv)) # create input data with incorrect private key _input = TxInput(genesis_block.hash, 0, b'') value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) tx = Transaction(inputs=[_input], outputs=[output], storage=self.tx_storage, timestamp=self.last_block.timestamp + 1) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, private_key_random) data_wrong = P2PKH.create_input_data(public_bytes, signature) _input.data = data_wrong with self.assertRaises(InvalidInputData): tx.verify_inputs() def test_too_many_inputs(self): random_bytes = bytes.fromhex( '0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') _input = TxInput(random_bytes, 0, random_bytes) inputs = [_input] * (MAX_NUM_INPUTS + 1) tx = Transaction(inputs=inputs, storage=self.tx_storage) with self.assertRaises(TooManyInputs): tx.verify_number_of_inputs() def test_no_inputs(self): tx = Transaction(inputs=[], storage=self.tx_storage) with self.assertRaises(NoInputError): tx.verify_number_of_inputs() def test_too_many_outputs(self): random_bytes = bytes.fromhex( '0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') output = TxOutput(1, random_bytes) outputs = [output] * (MAX_NUM_OUTPUTS + 1) tx = Transaction(outputs=outputs, storage=self.tx_storage) with self.assertRaises(TooManyOutputs): tx.verify_number_of_outputs() def _gen_tx_spending_genesis_block(self): parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] _input = TxInput(genesis_block.hash, 0, b'') value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) tx = Transaction(nonce=100, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx.update_hash() return tx def test_struct(self): tx = self._gen_tx_spending_genesis_block() data = tx.get_struct() tx_read = Transaction.create_from_struct(data) self.assertEqual(tx, tx_read) def test_children_update(self): tx = self._gen_tx_spending_genesis_block() # get info before update children_len = [] for parent in tx.get_parents(): metadata = parent.get_metadata() children_len.append(len(metadata.children)) # update metadata tx.update_initial_metadata() # genesis transactions should have only this tx in their children set for old_len, parent in zip(children_len, tx.get_parents()): metadata = parent.get_metadata() self.assertEqual(len(metadata.children) - old_len, 1) self.assertEqual(metadata.children.pop(), tx.hash) def test_block_inputs(self): # a block with inputs should be invalid parents = [tx.hash for tx in self.genesis] genesis_block = self.genesis_blocks[0] tx_inputs = [TxInput(genesis_block.hash, 0, b'')] address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.tx_storage) block.inputs = tx_inputs block.resolve() with self.assertRaises(BlockWithInputs): block.verify() def test_block_outputs(self): from hathor.transaction import MAX_NUM_OUTPUTS from hathor.transaction.exceptions import TooManyOutputs # a block should have no more than MAX_NUM_OUTPUTS outputs parents = [tx.hash for tx in self.genesis] address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] * (MAX_NUM_OUTPUTS + 1) block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.tx_storage) with self.assertRaises(TooManyOutputs): block.verify_outputs() def test_tx_number_parents(self): genesis_block = self.genesis_blocks[0] _input = TxInput(genesis_block.hash, 0, b'') value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) parents = [self.genesis_txs[0].hash] tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 1) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) # in first test, only with 1 parent tx.resolve() with self.assertRaises(IncorrectParents): tx.verify() # test with 3 parents parents = [tx.hash for tx in self.genesis] tx.parents = parents tx.resolve() with self.assertRaises(IncorrectParents): tx.verify() # 2 parents, 1 tx and 1 block parents = [self.genesis_txs[0].hash, self.genesis_blocks[0].hash] tx.parents = parents tx.resolve() with self.assertRaises(IncorrectParents): tx.verify() def test_block_unknown_parent(self): address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] # Random unknown parent parents = [hashlib.sha256().digest()] block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.tx_storage) block.resolve() with self.assertRaises(ParentDoesNotExist): block.verify() def test_block_number_parents(self): address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] parents = [tx.hash for tx in self.genesis_txs] block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.tx_storage) block.resolve() with self.assertRaises(IncorrectParents): block.verify() def test_tx_inputs_out_of_range(self): # we'll try to spend output 3 from genesis transaction, which does not exist parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) _input = TxInput(genesis_block.hash, len(genesis_block.outputs) + 1, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) data = P2PKH.create_input_data(public_bytes, signature) tx.inputs[0].data = data # test with an inexistent index tx.resolve() with self.assertRaises(InexistentInput): tx.verify() # now with index equals of len of outputs _input = [ TxInput(genesis_block.hash, len(genesis_block.outputs), data) ] tx.inputs = _input # test with an inexistent index tx.resolve() with self.assertRaises(InexistentInput): tx.verify() # now with inexistent tx hash random_bytes = bytes.fromhex( '0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') _input = [TxInput(random_bytes, 3, data)] tx.inputs = _input tx.resolve() with self.assertRaises(InexistentInput): tx.verify() def test_tx_inputs_conflict(self): # the new tx inputs will try to spend the same output parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) # We can't only duplicate the value because genesis is using the max value possible outputs = [TxOutput(value, script), TxOutput(value, script)] _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input, _input], outputs=outputs, parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 1) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() with self.assertRaises(ConflictingInputs): tx.verify() def test_regular_tx(self): # this should succeed parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 1) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() tx.verify() def test_weight_nan(self): # this should succeed parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage) tx.weight = float('NaN') data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) tx.update_hash() self.assertTrue(isnan(tx.weight)) with self.assertRaises(WeightError): tx.verify() def test_weight_inf(self): # this should succeed parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage) tx.weight = float('inf') data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) tx.update_hash() self.assertTrue(isinf(tx.weight)) with self.assertRaises(WeightError): tx.verify() def test_tx_duplicated_parents(self): # the new tx will confirm the same tx twice parents = [self.genesis_txs[0].hash, self.genesis_txs[0].hash] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 1) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() with self.assertRaises(DuplicatedParents): tx.verify() def test_update_timestamp(self): parents = [tx for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) # update based on input _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=[p.hash for p in parents], storage=self.tx_storage) input_timestamp = genesis_block.timestamp max_ts = max(input_timestamp, parents[0].timestamp, parents[1].timestamp) tx.update_timestamp(0) self.assertEquals(tx.timestamp, max_ts + 1) ts = max_ts + 20 tx.update_timestamp(ts) self.assertEquals(tx.timestamp, ts) def test_propagation_error(self): manager = self.create_peer('testnet', unlock_wallet=True) manager.test_mode = TestMode.DISABLED # 1. propagate genesis genesis_block = self.genesis_blocks[0] genesis_block.storage = manager.tx_storage self.assertFalse(manager.propagate_tx(genesis_block)) # 2. propagate block with weight 1 block = manager.generate_mining_block() block.weight = 1 block.resolve() self.assertFalse(manager.propagate_tx(block)) # 3. propagate block with wrong amount of tokens block = manager.generate_mining_block() output = TxOutput(1, block.outputs[0].script) block.outputs = [output] block.resolve() self.assertFalse(manager.propagate_tx(block)) # 4. propagate block from the future block = manager.generate_mining_block() block.timestamp = int( self.clock.seconds()) + settings.MAX_FUTURE_TIMESTAMP_ALLOWED + 100 block.resolve(update_time=False) self.assertFalse(manager.propagate_tx(block)) def test_tx_methods(self): blocks = add_new_blocks(self.manager, 2, advance_clock=1) add_blocks_unlock_reward(self.manager) txs = add_new_transactions(self.manager, 2, advance_clock=1) # Validate __str__, __bytes__, __eq__ tx = txs[0] tx2 = txs[1] str_tx = str(tx) self.assertTrue(isinstance(str_tx, str)) self.assertEqual(bytes(tx), tx.get_struct()) tx_equal = Transaction.create_from_struct(tx.get_struct()) self.assertTrue(tx == tx_equal) self.assertFalse(tx == tx2) tx2_hash = tx2.hash tx2.hash = None self.assertFalse(tx == tx2) tx2.hash = tx2_hash # Validate is_genesis without storage tx_equal.storage = None self.assertFalse(tx_equal.is_genesis) # Pow error tx2.verify_pow() tx2.weight = 100 with self.assertRaises(PowError): tx2.verify_pow() # Verify parent timestamps tx2.verify_parents() tx2_timestamp = tx2.timestamp tx2.timestamp = 2 with self.assertRaises(TimestampError): tx2.verify_parents() tx2.timestamp = tx2_timestamp # Verify inputs timestamps tx2.verify_inputs() tx2.timestamp = 2 with self.assertRaises(TimestampError): tx2.verify_inputs() tx2.timestamp = tx2_timestamp # Validate maximum distance between blocks block = blocks[0] block2 = blocks[1] block2.timestamp = block.timestamp + settings.MAX_DISTANCE_BETWEEN_BLOCKS block2.verify_parents() block2.timestamp += 1 with self.assertRaises(TimestampError): block2.verify_parents() def test_block_big_nonce(self): block = self.genesis_blocks[0] # Integer with more than 4 bytes of representation start = 1 << (8 * 12) end = start + 1 << (8 * 4) hash = block.start_mining(start, end) assert hash is not None block.hash = hash cloned_block = block.clone() assert cloned_block == block def test_block_data(self): def add_block_with_data(data: bytes = b'') -> None: add_new_blocks(self.manager, 1, advance_clock=1, block_data=data)[0] add_block_with_data() add_block_with_data(b'Testing, testing 1, 2, 3...') add_block_with_data(100 * b'a') with self.assertRaises(TransactionDataError): add_block_with_data(101 * b'a') def test_output_serialization(self): from hathor.transaction.base_transaction import ( _MAX_OUTPUT_VALUE_32, MAX_OUTPUT_VALUE, bytes_to_output_value, output_value_to_bytes, ) max_32 = output_value_to_bytes(_MAX_OUTPUT_VALUE_32) self.assertEqual(len(max_32), 4) value, buf = bytes_to_output_value(max_32) self.assertEqual(value, _MAX_OUTPUT_VALUE_32) over_32 = output_value_to_bytes(_MAX_OUTPUT_VALUE_32 + 1) self.assertEqual(len(over_32), 8) value, buf = bytes_to_output_value(over_32) self.assertEqual(value, _MAX_OUTPUT_VALUE_32 + 1) max_64 = output_value_to_bytes(MAX_OUTPUT_VALUE) self.assertEqual(len(max_64), 8) value, buf = bytes_to_output_value(max_64) self.assertEqual(value, MAX_OUTPUT_VALUE) def test_output_value(self): from hathor.transaction.base_transaction import bytes_to_output_value # first test using a small output value with 8 bytes. It should fail parents = [tx.hash for tx in self.genesis_txs] outputs = [TxOutput(1, b'')] tx = Transaction(outputs=outputs, parents=parents) original_struct = tx.get_struct() struct_bytes = tx.get_funds_struct() # we'll get the struct without the last output bytes and add it ourselves struct_bytes = struct_bytes[:-7] # add small value using 8 bytes and expect failure when trying to deserialize struct_bytes += (-1).to_bytes(8, byteorder='big', signed=True) struct_bytes += int_to_bytes(0, 1) struct_bytes += int_to_bytes(0, 2) struct_bytes += tx.get_graph_struct() struct_bytes += int_to_bytes(tx.nonce, tx.SERIALIZATION_NONCE_SIZE) len_difference = len(struct_bytes) - len(original_struct) assert len_difference == 4, 'new struct is incorrect, len difference={}'.format( len_difference) with self.assertRaises(ValueError): Transaction.create_from_struct(struct_bytes) # now use 8 bytes and make sure it's working outputs = [TxOutput(MAX_OUTPUT_VALUE, b'')] tx = Transaction(outputs=outputs, parents=parents) tx.update_hash() original_struct = tx.get_struct() tx2 = Transaction.create_from_struct(original_struct) tx2.update_hash() assert tx == tx2 # Validating that all output values must be positive value = 1 address = decode_address('WUDtnw3GYjvUnZmiHAmus6hhs9GoSUSJMG') script = P2PKH.create_output_script(address) output = TxOutput(value, script) output.value = -1 random_bytes = bytes.fromhex( '0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') _input = TxInput(random_bytes, 0, random_bytes) tx = Transaction(inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage) with self.assertRaises(InvalidOutputValue): tx.resolve() # 'Manually resolving', to validate verify method tx.hash = bytes.fromhex( '012cba011be3c29f1c406f9015e42698b97169dbc6652d1f5e4d5c5e83138858') with self.assertRaises(InvalidOutputValue): tx.verify() # Invalid output value invalid_output = bytes.fromhex('ffffffff') with self.assertRaises(InvalidOutputValue): bytes_to_output_value(invalid_output) # Can't instantiate an output with negative value with self.assertRaises(AssertionError): TxOutput(-1, script) def test_tx_version(self): from hathor.transaction.base_transaction import TxVersion # test the 1st byte of version field is ignored version = TxVersion(0xFF00) self.assertEqual(version.get_cls(), Block) version = TxVersion(0xFF01) self.assertEqual(version.get_cls(), Transaction) # test serialization doesn't mess up with version block = Block(version=0xFF00, nonce=100, weight=1) block2 = block.clone() self.assertEqual(block.version, block2.version) def test_output_sum_ignore_authority(self): # sum of tx outputs should ignore authority outputs address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output1 = TxOutput(5, script) # regular utxo output2 = TxOutput(30, script, 0b10000001) # authority utxo output3 = TxOutput(3, script) # regular utxo tx = Transaction(outputs=[output1, output2, output3], storage=self.tx_storage) self.assertEqual(8, tx.sum_outputs) def _spend_reward_tx(self, manager, reward_block): value = reward_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) input_ = TxInput(reward_block.hash, 0, b'') output = TxOutput(value, script) tx = Transaction( weight=1, timestamp=int(manager.reactor.seconds()) + 1, inputs=[input_], outputs=[output], parents=manager.get_new_tx_parents(), storage=manager.tx_storage, ) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) input_.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() return tx def test_reward_lock(self): from hathor.transaction.exceptions import RewardLocked # add block with a reward we can spend reward_block = self.manager.generate_mining_block( address=get_address_from_public_key(self.genesis_public_key)) reward_block.resolve() self.assertTrue(self.manager.propagate_tx(reward_block)) # reward cannot be spent while not enough blocks are added for _ in range(settings.REWARD_SPEND_MIN_BLOCKS): tx = self._spend_reward_tx(self.manager, reward_block) with self.assertRaises(RewardLocked): tx.verify() add_new_blocks(self.manager, 1, advance_clock=1) # now it should be spendable tx = self._spend_reward_tx(self.manager, reward_block) self.assertTrue(self.manager.propagate_tx(tx, fails_silently=False)) def test_reward_lock_timestamp(self): from hathor.transaction.exceptions import RewardLocked # add block with a reward we can spend reward_block = self.manager.generate_mining_block( address=get_address_from_public_key(self.genesis_public_key)) reward_block.resolve() self.assertTrue(self.manager.propagate_tx(reward_block)) # we add enough blocks that this output could be spent based on block height blocks = add_blocks_unlock_reward(self.manager) # tx timestamp is equal to the block that unlock the spent rewards. It should # be greater, so it'll fail tx = self._spend_reward_tx(self.manager, reward_block) tx.timestamp = blocks[-1].timestamp tx.resolve() with self.assertRaises(RewardLocked): tx.verify() # we can fix it be incrementing the timestamp tx._height_cache = None tx.timestamp = blocks[-1].timestamp + 1 tx.resolve() tx.verify() def test_wallet_index(self): # First transaction: send tokens to output with address=address_b58 parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) address_b58 = parse_address_script(script).address # Get how many transactions wallet index already has for this address wallet_index_count = len( self.tx_storage.wallet_index.index[address_b58]) _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 1) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() self.manager.propagate_tx(tx) # This transaction has an output to address_b58, so we need one more element on the index self.assertEqual(len(self.tx_storage.wallet_index.index[address_b58]), wallet_index_count + 1) # Second transaction: spend tokens from output with address=address_b58 and # send tokens to 2 outputs, one with address=address_b58 and another one # with address=new_address_b58, which is an address of a random wallet new_address_b58 = self.get_address(0) new_address = decode_address(new_address_b58) output1 = TxOutput(value - 100, script) script2 = P2PKH.create_output_script(new_address) output2 = TxOutput(100, script2) input1 = TxInput(tx.hash, 0, b'') tx2 = Transaction(weight=1, inputs=[input1], outputs=[output1, output2], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 2) data_to_sign = tx2.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) input1.data = P2PKH.create_input_data(public_bytes, signature) tx2.resolve() self.manager.propagate_tx(tx2) # tx2 has two outputs, for address_b58 and new_address_b58 # So we must have one more element on address_b58 index and only one on new_address_b58 self.assertEqual(len(self.tx_storage.wallet_index.index[address_b58]), wallet_index_count + 2) self.assertEqual( len(self.tx_storage.wallet_index.index[new_address_b58]), 1) # Third transaction: spend tokens from output with address=address_b58 and send # tokens to a new address = output3_address_b58, which is from a random wallet output3_address_b58 = self.get_address(1) output3_address = decode_address(output3_address_b58) script3 = P2PKH.create_output_script(output3_address) output3 = TxOutput(value - 100, script3) input2 = TxInput(tx2.hash, 0, b'') tx3 = Transaction(weight=1, inputs=[input2], outputs=[output3], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 3) data_to_sign = tx3.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) input2.data = P2PKH.create_input_data(public_bytes, signature) tx3.resolve() self.manager.propagate_tx(tx3) # tx3 has one output, for another new address (output3_address_b58) and it's spending an output of address_b58 # So address_b58 index must have one more element and output3_address_b58 should have one element also # new_address_b58 was not spent neither received tokens, so didn't change self.assertEqual(len(self.tx_storage.wallet_index.index[address_b58]), wallet_index_count + 3) self.assertEqual( len(self.tx_storage.wallet_index.index[output3_address_b58]), 1) self.assertEqual( len(self.tx_storage.wallet_index.index[new_address_b58]), 1) def test_sighash_cache(self): from unittest import mock address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(5, script) tx = Transaction(outputs=[output], storage=self.tx_storage) with mock.patch('hathor.transaction.transaction.bytearray') as mocked: for _ in range(10): tx.get_sighash_all() mocked.assert_called_once() def test_sighash_data_cache(self): from unittest import mock address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(5, script) tx = Transaction(outputs=[output], storage=self.tx_storage) with mock.patch('hathor.transaction.transaction.hashlib') as mocked: for _ in range(10): tx.get_sighash_all_data() mocked.sha256.assert_called_once()
class ConsensusTestCase(unittest.TestCase): def setUp(self): super().setUp() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] def test_revert_block_high_weight(self): """ A conflict transaction will be propagated. At first, it will be voided. But, a new block with high weight will verify it, which will flip it to executed. """ self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # Mine a few blocks in a row with no transaction but the genesis blocks = add_new_blocks(manager, 3, advance_clock=15) add_blocks_unlock_reward(manager) # Add some transactions between blocks add_new_transactions(manager, 5, advance_clock=15) # Create a double spending transaction. conflicting_tx = add_new_double_spending(manager, use_same_parents=True) # Add a few transactions. add_new_transactions(manager, 10, advance_clock=15) meta = conflicting_tx.get_metadata() self.assertEqual(meta.voided_by, {conflicting_tx.hash}) for parent_hash in conflicting_tx.parents: self.assertNotIn(parent_hash, meta.conflict_with) # These blocks will be voided later. blocks2 = add_new_blocks(manager, 2, advance_clock=15) # This block verifies the conflicting transaction and has a high weight. # So, it will be executed and previous blocks and transactions will be voided. tb0 = manager.make_custom_block_template(blocks[-1].hash, [conflicting_tx.hash, conflicting_tx.parents[0]]) b0 = tb0.generate_mining_block(storage=manager.tx_storage) b0.weight = 10 b0.resolve() b0.verify() manager.propagate_tx(b0, fails_silently=False) b1 = add_new_block(manager, advance_clock=15) b2 = add_new_block(manager, advance_clock=15) # from hathor.graphviz import GraphvizVisualizer # dot = GraphvizVisualizer(manager.tx_storage, include_verifications=True, include_funds=True).dot() # dot.render('dot0') self.assertEqual(b1.parents[0], b0.hash) self.assertEqual(b2.parents[0], b1.hash) meta = conflicting_tx.get_metadata() self.assertIsNone(meta.voided_by) # Find the other transaction voiding the blocks. tmp_tx = manager.tx_storage.get_transaction(blocks2[0].parents[1]) tmp_tx_meta = tmp_tx.get_metadata() self.assertEqual(len(tmp_tx_meta.voided_by), 1) other_tx_hash = list(tmp_tx_meta.voided_by)[0] for block in blocks2: meta = block.get_metadata() self.assertEqual(meta.voided_by, {other_tx_hash, block.hash}) self.assertConsensusValid(manager) def test_dont_revert_block_low_weight(self): """ A conflict transaction will be propagated and voided. A new block with low weight will verify it, which won't be enough to flip to executed. So, it will remain voided. """ self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # Mine a few blocks in a row with no transaction but the genesis blocks = add_new_blocks(manager, 3, advance_clock=15) add_blocks_unlock_reward(manager) # Add some transactions between blocks add_new_transactions(manager, 5, advance_clock=15) # Create a double spending transaction. conflicting_tx = add_new_double_spending(manager, use_same_parents=True) # Add a few transactions. add_new_transactions(manager, 10, advance_clock=15) meta = conflicting_tx.get_metadata() self.assertEqual(meta.voided_by, {conflicting_tx.hash}) for parent_hash in conflicting_tx.parents: self.assertNotIn(parent_hash, meta.conflict_with) # These blocks will be voided later. add_new_blocks(manager, 2, advance_clock=15) # This block verifies the conflicting transaction and has a low weight. # So, it is not enough to revert and this block will be voided as well. b0 = manager.generate_mining_block() b0.parents = [blocks[-1].hash, conflicting_tx.hash, conflicting_tx.parents[0]] b0.resolve() b0.verify() manager.propagate_tx(b0, fails_silently=False) b1 = add_new_block(manager, advance_clock=15) b2 = add_new_block(manager, advance_clock=15) # dot = GraphvizVisualizer(manager.tx_storage, include_verifications=True, include_funds=True).dot() # dot.render('dot1') self.assertNotEqual(b1.parents[0], b0.hash) self.assertEqual(b2.parents[0], b1.hash) meta = conflicting_tx.get_metadata() self.assertEqual(meta.voided_by, {conflicting_tx.hash}) b0_meta = b0.get_metadata() self.assertEqual(b0_meta.voided_by, {b0.hash, conflicting_tx.hash}) self.assertConsensusValid(manager) def test_dont_revert_block_high_weight_transaction_verify_other(self): """ A conflict transaction will be propagated and voided. But this transaction verifies its conflicting transaction. So, its accumulated weight will always be smaller than the others and it will never be executed. """ self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # Mine a few blocks in a row with no transaction but the genesis blocks = add_new_blocks(manager, 3, advance_clock=15) add_blocks_unlock_reward(manager) # Add some transactions between blocks add_new_transactions(manager, 5, advance_clock=15) # Create a double spending transaction. conflicting_tx = add_new_double_spending(manager) meta = conflicting_tx.get_metadata() self.assertEqual(len(meta.conflict_with), 1) self.assertIn(list(meta.conflict_with)[0], conflicting_tx.parents) # Add a few transactions. add_new_transactions(manager, 10, advance_clock=15) meta = conflicting_tx.get_metadata() self.assertEqual(meta.voided_by, {conflicting_tx.hash}) # These blocks will be voided later. blocks2 = add_new_blocks(manager, 2, advance_clock=15) # This block verifies the conflicting transaction and has a high weight. tb0 = manager.make_custom_block_template(blocks[-1].hash, [conflicting_tx.hash, conflicting_tx.parents[0]]) b0 = tb0.generate_mining_block(storage=manager.tx_storage) b0.weight = 10 b0.resolve() b0.verify() manager.propagate_tx(b0, fails_silently=False) b1 = add_new_block(manager, advance_clock=15) b2 = add_new_block(manager, advance_clock=15) # dot = GraphvizVisualizer(manager.tx_storage, include_verifications=True, include_funds=True).dot() # dot.render('dot2') self.assertNotEqual(b1.parents[0], b0.hash) self.assertEqual(b2.parents[0], b1.hash) meta = conflicting_tx.get_metadata() self.assertEqual(meta.voided_by, {conflicting_tx.hash}) for block in blocks2: meta = block.get_metadata() self.assertIsNone(meta.voided_by) self.assertConsensusValid(manager) def test_dont_revert_block_high_weight_verify_both(self): """ A conflicting transaction will be propagated and voided. But the block with high weight verifies both the conflicting transactions, so this block will always be voided. """ self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # Mine a few blocks in a row with no transaction but the genesis add_new_blocks(manager, 3, advance_clock=15) add_blocks_unlock_reward(manager) # Add some transactions between blocks add_new_transactions(manager, 5, advance_clock=15) # Create a double spending transaction. conflicting_tx = add_new_double_spending(manager, use_same_parents=True) # Add a few transactions. add_new_transactions(manager, 10, advance_clock=15) meta = conflicting_tx.get_metadata() self.assertEqual(meta.voided_by, {conflicting_tx.hash}) for parent_hash in conflicting_tx.parents: self.assertNotIn(parent_hash, meta.conflict_with) # Add two blocks. blocks2 = add_new_blocks(manager, 2, advance_clock=15) # This block verifies the conflicting transaction and has a high weight. b0 = manager.generate_mining_block() b0.parents = [b0.parents[0], conflicting_tx.hash, conflicting_tx.parents[0]] b0.weight = 10 b0.resolve() b0.verify() manager.propagate_tx(b0, fails_silently=False) b1 = add_new_block(manager, advance_clock=15) b2 = add_new_block(manager, advance_clock=15) # dot = GraphvizVisualizer(manager.tx_storage, include_verifications=True, include_funds=True).dot() # dot.render('dot3') self.assertNotEqual(b1.parents[0], b0.hash) self.assertEqual(b2.parents[0], b1.hash) meta = conflicting_tx.get_metadata() self.assertEqual(meta.voided_by, {conflicting_tx.hash}) for block in blocks2: meta = block.get_metadata() self.assertIsNone(meta.voided_by) self.assertConsensusValid(manager)
class BasicWallet(unittest.TestCase): def setUp(self): super().setUp() self.directory = tempfile.mkdtemp(dir='/tmp/') self.storage = TransactionMemoryStorage() self.manager = self.create_peer('testnet', unlock_wallet=True) # read genesis keys self.genesis_private_key = get_genesis_key() self.genesis_public_key = self.genesis_private_key.public_key() def tearDown(self): super().tearDown() shutil.rmtree(self.directory) def test_wallet_keys_storage(self): w = Wallet(directory=self.directory) # Testing password error not in bytes with self.assertRaises(ValueError): w.unlock('testpass') w.unlock(b'testpass') w.generate_keys() # Using one address to save used/unused addresses in the file w.get_unused_address() w._write_keys_to_file() # wallet 2 will read from saved file w2 = Wallet(directory=self.directory) w2._manually_initialize() for address, key in w.keys.items(): key2 = w2.keys.pop(address) self.assertEqual(key, key2) def test_wallet_create_transaction(self): genesis_private_key_bytes = get_private_key_bytes( self.genesis_private_key, encryption_algorithm=serialization.BestAvailableEncryption( PASSWORD)) genesis_address = get_address_b58_from_public_key( self.genesis_public_key) # create wallet with genesis block key key_pair = KeyPair(private_key_bytes=genesis_private_key_bytes, address=genesis_address, used=True) keys = {} keys[key_pair.address] = key_pair w = Wallet(keys=keys, directory=self.directory) w.unlock(PASSWORD) genesis_blocks = [ tx for tx in get_genesis_transactions(None) if tx.is_block ] genesis_block = genesis_blocks[0] genesis_value = sum([output.value for output in genesis_block.outputs]) # wallet will receive genesis block and store in unspent_tx w.on_new_tx(genesis_block) for index in range(len(genesis_block.outputs)): utxo = w.unspent_txs[settings.HATHOR_TOKEN_UID].get( (genesis_block.hash, index)) self.assertIsNotNone(utxo) self.assertEqual(w.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, genesis_value)) # create transaction spending this value, but sending to same wallet new_address = w.get_unused_address() out = WalletOutputInfo(decode_address(new_address), 100, timelock=None) tx1 = w.prepare_transaction_compute_inputs(Transaction, outputs=[out]) tx1.storage = self.storage tx1.update_hash() self.storage.save_transaction(tx1) w.on_new_tx(tx1) self.assertEqual(len(w.spent_txs), 1) self.assertEqual(w.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, genesis_value)) # pass inputs and outputs to prepare_transaction, but not the input keys # spend output last transaction input_info = WalletInputInfo(tx1.hash, 1, None) new_address = w.get_unused_address() key2 = w.keys[new_address] out = WalletOutputInfo(decode_address(key2.address), 100, timelock=None) tx2 = w.prepare_transaction_incomplete_inputs(Transaction, inputs=[input_info], outputs=[out], tx_storage=self.storage) tx2.storage = self.storage tx2.update_hash() self.storage.save_transaction(tx2) w.on_new_tx(tx2) self.assertEqual(len(w.spent_txs), 2) self.assertEqual(w.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, genesis_value)) # test keypair exception with self.assertRaises(WalletLocked): key_pair.get_private_key(None) def test_block_increase_balance(self): # generate a new block and check if we increase balance w = Wallet(directory=self.directory) w.unlock(PASSWORD) new_address = w.get_unused_address() key = w.keys[new_address] out = WalletOutputInfo(decode_address(key.address), BLOCK_REWARD, timelock=None) tx = w.prepare_transaction(Transaction, inputs=[], outputs=[out]) tx.update_hash() w.on_new_tx(tx) utxo = w.unspent_txs[settings.HATHOR_TOKEN_UID].get((tx.hash, 0)) self.assertIsNotNone(utxo) self.assertEqual(w.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, BLOCK_REWARD)) def test_locked(self): # generate a new block and check if we increase balance w = Wallet(directory=self.directory) with self.assertRaises(OutOfUnusedAddresses): w.get_unused_address() # now it should work w.unlock(PASSWORD) w.get_unused_address() # lock wallet and fake that there are no more unused keys w.unused_keys = set() w.lock() with self.assertRaises(OutOfUnusedAddresses): w.get_unused_address() with self.assertRaises(WalletLocked): w.generate_keys() def test_insuficient_funds(self): w = Wallet(directory=self.directory) w.unlock(PASSWORD) # create transaction spending some value new_address = w.get_unused_address() out = WalletOutputInfo(decode_address(new_address), 100, timelock=None) with self.assertRaises(InsufficientFunds): w.prepare_transaction_compute_inputs(Transaction, outputs=[out]) def test_invalid_address(self): w = Wallet(directory=self.directory) w.unlock(PASSWORD) # creating valid address valid_address = '15d14K5jMqsN2uwUEFqiPG5SoD7Vr1BfnH' WalletOutputInfo(decode_address(valid_address), 100, None) # creating invalid address invalid_address = '5d14K5jMqsN2uwUEFqiPG5SoD7Vr1BfnH' with self.assertRaises(InvalidAddress): WalletOutputInfo(decode_address(invalid_address), 100, None) # invalid address (checksum invalid) invalid_address2 = '15d14K5jMqsN2uwUEFqiPG5SoD7Vr1Bfnq' with self.assertRaises(InvalidAddress): WalletOutputInfo(decode_address(invalid_address2), 100, None) def test_separate_inputs(self): block = add_new_block(self.manager, advance_clock=5) my_input = TxInput(block.hash, 0, b'') genesis_blocks = [ tx for tx in get_genesis_transactions(None) if tx.is_block ] genesis_block = genesis_blocks[0] other_input = TxInput(genesis_block.hash, 0, b'') my_inputs, other_inputs = self.manager.wallet.separate_inputs( [my_input, other_input], self.manager.tx_storage) self.assertEqual(len(my_inputs), 1) self.assertEqual(my_inputs[0], my_input) self.assertEqual(len(other_inputs), 1) self.assertEqual(other_inputs[0], other_input) def test_create_token_transaction(self): add_new_block(self.manager, advance_clock=5) add_blocks_unlock_reward(self.manager) tx = create_tokens(self.manager) tokens_created = tx.outputs[0].value token_uid = tx.tokens[0] address_b58 = self.manager.wallet.get_unused_address() address = decode_address(address_b58) _, hathor_balance = self.manager.wallet.balance[ settings.HATHOR_TOKEN_UID] # prepare tx with hathors and another token # hathor tx hathor_out = WalletOutputInfo(address, hathor_balance, None) # token tx token_out = WalletOutputInfo(address, tokens_created - 20, None, token_uid.hex()) tx2 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, [hathor_out, token_out]) tx2.storage = self.manager.tx_storage tx2.timestamp = tx.timestamp + 1 tx2.parents = self.manager.get_new_tx_parents() tx2.resolve() tx2.verify() self.assertNotEqual(len(tx2.inputs), 0) token_dict = defaultdict(int) for _input in tx2.inputs: output_tx = self.manager.tx_storage.get_transaction(_input.tx_id) output = output_tx.outputs[_input.index] token_uid = output_tx.get_token_uid(output.get_token_index()) token_dict[token_uid] += output.value # make sure balance is the same and we've checked both balances did_enter = 0 for token_uid, value in token_dict.items(): if token_uid == settings.HATHOR_TOKEN_UID: self.assertEqual(value, hathor_balance) did_enter += 1 elif token_uid == token_uid: self.assertEqual(value, tokens_created) did_enter += 1 self.assertEqual(did_enter, 2) def test_prepare_transaction(self): block = add_new_block(self.manager, advance_clock=5) w = self.manager.wallet new_address = w.get_unused_address() out = WalletOutputInfo(decode_address(new_address), 1, timelock=None) with self.assertRaises(InsufficientFunds): w.prepare_transaction_compute_inputs(Transaction, outputs=[out], timestamp=block.timestamp) def test_maybe_spent_txs(self): add_new_block(self.manager, advance_clock=15) blocks = add_blocks_unlock_reward(self.manager) w = self.manager.wallet new_address = w.get_unused_address() out = WalletOutputInfo(decode_address(new_address), 1, timelock=None) tx1 = w.prepare_transaction_compute_inputs(Transaction, outputs=[out]) self.assertEqual(len(tx1.inputs), 1) _input = tx1.inputs[0] key = (_input.tx_id, _input.index) self.assertNotIn(key, w.unspent_txs[settings.HATHOR_TOKEN_UID]) self.assertIn(key, w.maybe_spent_txs[settings.HATHOR_TOKEN_UID]) self.run_to_completion() self.assertIn(key, w.unspent_txs[settings.HATHOR_TOKEN_UID]) self.assertEqual(0, len(w.maybe_spent_txs[settings.HATHOR_TOKEN_UID])) # when we receive the new tx it will remove from maybe_spent tx2 = w.prepare_transaction_compute_inputs(Transaction, outputs=[out]) tx2.storage = self.manager.tx_storage tx2.timestamp = max( tx2.get_spent_tx(txin).timestamp for txin in tx2.inputs) + 1 tx2.parents = self.manager.get_new_tx_parents(tx2.timestamp) tx2.weight = 1 tx2.timestamp = blocks[-1].timestamp + 1 tx2.resolve() self.assertTrue(self.manager.on_new_tx(tx2, fails_silently=False)) self.clock.advance(2) self.assertEqual(0, len(w.maybe_spent_txs[settings.HATHOR_TOKEN_UID]))
def setUp(self): super().setUp() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block]
class BlockchainTestCase(unittest.TestCase): """ Thus, there are eight cases to be handled when a new block arrives, which are: (i) Single best chain, connected to the head of the best chain (ii) Single best chain, connected to the tail of the best chain (iii) Single best chain, connected to the head of a side chain (iv) Single best chain, connected to the tail of a side chain (v) Multiple best chains, connected to the head of a best chain (vi) Multiple best chains, connected to the tail of a best chain (vii) Multiple best chains, connected to the head of a side chain (viii) Multiple best chains, connected to the tail of a side chain """ def setUp(self): super().setUp() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] def test_block_template_after_genesis(self): manager = self.create_peer('testnet', tx_storage=self.tx_storage) block_templates = manager.get_block_templates() self.assertEqual(len(block_templates), 1) self.assertEqual(block_templates[0], BlockTemplate( versions={0, 3}, reward=settings.INITIAL_TOKEN_UNITS_PER_BLOCK * 100, weight=1.0, timestamp_now=int(manager.reactor.seconds()), timestamp_min=settings.GENESIS_TIMESTAMP + 3, timestamp_max=0xffffffff, # no limit for next block after genesis # parents=[tx.hash for tx in self.genesis_blocks + self.genesis_txs], parents=block_templates[0].parents, parents_any=[], height=1, # genesis is 0 score=sum_weights(self.genesis_blocks[0].weight, 1.0), )) def test_regular_block_template(self): manager = self.create_peer('testnet', tx_storage=self.tx_storage) # add 100 blocks blocks = add_new_blocks(manager, 100, advance_clock=15) block_templates = manager.get_block_templates() self.assertEqual(len(block_templates), 1) self.assertEqual(block_templates[0], BlockTemplate( versions={0, 3}, reward=settings.INITIAL_TOKEN_UNITS_PER_BLOCK * 100, weight=1.0, timestamp_now=int(manager.reactor.seconds()), timestamp_min=blocks[-1].timestamp + 1, timestamp_max=blocks[-1].timestamp + settings.MAX_DISTANCE_BETWEEN_BLOCKS - 1, # parents=[blocks[-1].hash, self.genesis_txs[-1].hash, self.genesis_txs[-2].hash], parents=block_templates[0].parents, parents_any=[], height=101, # genesis is 0 score=sum_weights(blocks[-1].get_metadata().score, 1.0), )) self.assertConsensusValid(manager)
def prepare(self, args: Namespace) -> None: import hathor from hathor.conf import HathorSettings from hathor.manager import HathorManager, TestMode from hathor.p2p.peer_discovery import BootstrapPeerDiscovery, DNSPeerDiscovery from hathor.p2p.peer_id import PeerId from hathor.p2p.utils import discover_hostname from hathor.transaction import genesis from hathor.transaction.storage import ( TransactionStorage, TransactionCacheStorage, TransactionCompactStorage, TransactionMemoryStorage, ) from hathor.wallet import HDWallet, Wallet settings = HathorSettings() if args.recursion_limit: sys.setrecursionlimit(args.recursion_limit) if not args.peer: peer_id = PeerId() else: data = json.load(open(args.peer, 'r')) peer_id = PeerId.create_from_json(data) print('Hathor v{} (genesis {})'.format(hathor.__version__, genesis.GENESIS_HASH.hex()[:7])) print('My peer id is', peer_id.id) def create_wallet(): if args.wallet == 'hd': print('Using HDWallet') kwargs = { 'words': args.words, } if args.passphrase: wallet_passphrase = getpass.getpass( prompt='HD Wallet passphrase:') kwargs['passphrase'] = wallet_passphrase.encode() if args.data: kwargs['directory'] = args.data return HDWallet(**kwargs) elif args.wallet == 'keypair': print('Using KeyPairWallet') if args.data: wallet = Wallet(directory=args.data) else: wallet = Wallet() wallet.flush_to_disk_interval = 5 # seconds if args.unlock_wallet: wallet_passwd = getpass.getpass(prompt='Wallet password:'******'Invalid type for wallet') tx_storage: TransactionStorage if args.data: wallet_dir = args.data print('Using Wallet at {}'.format(wallet_dir)) if args.rocksdb_storage: from hathor.transaction.storage import TransactionRocksDBStorage tx_dir = os.path.join(args.data, 'tx.db') tx_storage = TransactionRocksDBStorage( path=tx_dir, with_index=(not args.cache)) print('Using TransactionRocksDBStorage at {}'.format(tx_dir)) else: tx_dir = os.path.join(args.data, 'tx') tx_storage = TransactionCompactStorage( path=tx_dir, with_index=(not args.cache)) print('Using TransactionCompactStorage at {}'.format(tx_dir)) if args.cache: tx_storage = TransactionCacheStorage(tx_storage, reactor) if args.cache_size: tx_storage.capacity = args.cache_size if args.cache_interval: tx_storage.interval = args.cache_interval print( 'Using TransactionCacheStorage, capacity {}, interval {}s'. format(tx_storage.capacity, tx_storage.interval)) tx_storage.start() else: # if using MemoryStorage, no need to have cache tx_storage = TransactionMemoryStorage() print('Using TransactionMemoryStorage') self.tx_storage = tx_storage if args.wallet: self.wallet = create_wallet() else: self.wallet = None if args.hostname and args.auto_hostname: print('You cannot use --hostname and --auto-hostname together.') sys.exit(-1) if not args.auto_hostname: hostname = args.hostname else: print('Trying to discover your hostname...') hostname = discover_hostname() if not hostname: print('Aborting because we could not discover your hostname.') print('Try again or run without --auto-hostname.') sys.exit(-1) print('Hostname discovered and set to {}'.format(hostname)) network = settings.NETWORK_NAME self.manager = HathorManager(reactor, peer_id=peer_id, network=network, hostname=hostname, tx_storage=self.tx_storage, wallet=self.wallet, wallet_index=args.wallet_index, stratum_port=args.stratum, min_block_weight=args.min_block_weight, ssl=True) if args.allow_mining_without_peers: self.manager.allow_mining_without_peers() dns_hosts = [] if settings.BOOTSTRAP_DNS: dns_hosts.extend(settings.BOOTSTRAP_DNS) if args.dns: dns_hosts.extend(args.dns) if dns_hosts: self.manager.add_peer_discovery(DNSPeerDiscovery(dns_hosts)) if args.bootstrap: self.manager.add_peer_discovery( BootstrapPeerDiscovery(args.bootstrap)) if args.test_mode_tx_weight: self.manager.test_mode = TestMode.TEST_TX_WEIGHT if self.wallet: self.wallet.test_mode = True for description in args.listen: self.manager.add_listen_address(description) self.start_manager() self.register_resources(args)
class BlockchainTestCase(unittest.TestCase): """ Thus, there are eight cases to be handled when a new block arrives, which are: (i) Single best chain, connected to the head of the best chain (ii) Single best chain, connected to the tail of the best chain (iii) Single best chain, connected to the head of a side chain (iv) Single best chain, connected to the tail of a side chain (v) Multiple best chains, connected to the head of a best chain (vi) Multiple best chains, connected to the tail of a best chain (vii) Multiple best chains, connected to the head of a side chain (viii) Multiple best chains, connected to the tail of a side chain """ def setUp(self): super().setUp() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] def test_single_chain(self): """ All new blocks belong to case (i). """ self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # The initial score is the sum of the genesis score = self.genesis_blocks[0].weight for tx in self.genesis_txs: score = sum_weights(score, tx.weight) # Mine 100 blocks in a row with no transaction but the genesis blocks = add_new_blocks(manager, 100, advance_clock=15) for i, block in enumerate(blocks): meta = block.get_metadata(force_reload=True) score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) # Add some transactions between blocks txs = add_new_transactions(manager, 30, advance_clock=15) for tx in txs: score = sum_weights(score, tx.weight) # Mine 50 more blocks in a row with no transactions between them blocks = add_new_blocks(manager, 50) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) self.assertAlmostEqual( manager.consensus_algorithm.block_algorithm.calculate_score( block), meta.score) # Mine 15 more blocks with 10 transactions between each block for _ in range(15): txs = add_new_transactions(manager, 10, advance_clock=15) for tx in txs: score = sum_weights(score, tx.weight) blocks = add_new_blocks(manager, 1) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) self.assertAlmostEqual( manager.consensus_algorithm.block_algorithm. calculate_score(block), meta.score) self.assertConsensusValid(manager) def test_single_fork_not_best(self): """ New blocks belong to cases (i), (ii), (iii), and (iv). The best chain never changes. All other chains are side chains. """ self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # The initial score is the sum of the genesis score = self.genesis_blocks[0].weight for tx in self.genesis_txs: score = sum_weights(score, tx.weight) # Mine 30 blocks in a row with no transactions blocks = add_new_blocks(manager, 30, advance_clock=15) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) # Add some transactions between blocks txs = add_new_transactions(manager, 5, advance_clock=15) for tx in txs: score = sum_weights(score, tx.weight) # Mine 1 blocks blocks = add_new_blocks(manager, 1, advance_clock=15) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) # Generate a block which will be a fork in the middle of the chain # Change the order of the transactions to change the hash fork_block1 = manager.generate_mining_block() fork_block1.parents = [fork_block1.parents[0] ] + fork_block1.parents[:0:-1] fork_block1.resolve() fork_block1.verify() # Mine 8 blocks in a row blocks = add_new_blocks(manager, 8, advance_clock=15) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) # Fork block must have the same parents as blocks[0] as well as the same score self.assertEqual(set(blocks[0].parents), set(fork_block1.parents)) # Propagate fork block. # This block belongs to case (ii). self.assertTrue(manager.propagate_tx(fork_block1)) fork_meta1 = fork_block1.get_metadata() self.assertEqual(fork_meta1.voided_by, {fork_block1.hash}) # Add some transactions between blocks txs = add_new_transactions(manager, 5, advance_clock=15) for tx in txs: score = sum_weights(score, tx.weight) # Mine 5 blocks in a row # These blocks belong to case (i). blocks = add_new_blocks(manager, 5, advance_clock=15) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) # Add some transactions between blocks txs = add_new_transactions(manager, 2, advance_clock=15) for tx in txs: score = sum_weights(score, tx.weight) # Propagate a block connected to the voided chain # These blocks belongs to case (iii). sidechain1 = add_new_blocks(manager, 3, parent_block_hash=fork_block1.hash) for block in sidechain1: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) # Add some transactions between blocks txs = add_new_transactions(manager, 2, advance_clock=15) for tx in txs: score = sum_weights(score, tx.weight) # Propagate a block connected to the voided chain # This block belongs to case (iv). fork_block3 = manager.generate_mining_block( parent_block_hash=fork_block1.hash) fork_block3.resolve() fork_block3.verify() self.assertTrue(manager.propagate_tx(fork_block3)) fork_meta3 = fork_block3.get_metadata() self.assertEqual(fork_meta3.voided_by, {fork_block3.hash}) self.assertConsensusValid(manager) def test_multiple_forks(self): self.assertEqual(len(self.genesis_blocks), 1) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # The initial score is the sum of the genesis score = self.genesis_blocks[0].weight for tx in self.genesis_txs: score = sum_weights(score, tx.weight) # Mine 30 blocks in a row with no transactions, case (i). blocks = add_new_blocks(manager, 30, advance_clock=15) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) # Add some transactions between blocks txs1 = add_new_transactions(manager, 5, advance_clock=15) for tx in txs1: score = sum_weights(score, tx.weight) # Mine 1 blocks, case (i). blocks = add_new_blocks(manager, 1, advance_clock=15) block_before_fork = blocks[0] for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) for tx in txs1: meta = tx.get_metadata(force_reload=True) self.assertEqual(meta.first_block, blocks[0].hash) # Add some transactions between blocks txs2 = add_new_transactions(manager, 3, advance_clock=15) for tx in txs2: score = sum_weights(score, tx.weight) # Mine 5 blocks in a row, case (i). blocks = add_new_blocks(manager, 5, advance_clock=15) for i, block in enumerate(blocks): meta = block.get_metadata() score = sum_weights(score, block.weight) self.assertAlmostEqual(score, meta.score) # Mine 4 blocks, starting a fork. # All these blocks belong to case (ii). sidechain = add_new_blocks(manager, 4, advance_clock=15, parent_block_hash=blocks[0].parents[0]) # Fork block must have the same parents as blocks[0] as well as the same score self.assertEqual(set(blocks[0].parents), set(sidechain[0].parents)) for block in blocks: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, None) for block in sidechain: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) # Propagate a block connected to the voided chain, case (iii). fork_block2 = manager.generate_mining_block( parent_block_hash=sidechain[-1].hash) fork_block2.resolve() fork_block2.verify() self.assertTrue(manager.propagate_tx(fork_block2)) sidechain.append(fork_block2) # Now, both chains have the same score. for block in blocks: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) for block in sidechain: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) for tx in txs1: meta = tx.get_metadata(force_reload=True) self.assertEqual(meta.first_block, block_before_fork.hash) for tx in txs2: meta = tx.get_metadata(force_reload=True) self.assertIsNone(meta.first_block) # Mine 1 block, starting another fork. # This block belongs to case (vi). sidechain2 = add_new_blocks(manager, 1, advance_clock=15, parent_block_hash=sidechain[0].hash) for block in sidechain2: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) # Mine 2 more blocks in the new fork. # These blocks belong to case (vii). sidechain2 += add_new_blocks(manager, 2, advance_clock=15, parent_block_hash=sidechain2[-1].hash) for block in sidechain2: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) # Mine 1 block, starting another fork from sidechain2. # This block belongs to case (viii). sidechain3 = add_new_blocks(manager, 1, advance_clock=15, parent_block_hash=sidechain2[-2].hash) for block in sidechain3: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) # Propagate a block connected to the side chain, case (v). fork_block3 = manager.generate_mining_block( parent_block_hash=fork_block2.hash) fork_block3.resolve() fork_block3.verify() self.assertTrue(manager.propagate_tx(fork_block3)) sidechain.append(fork_block3) # The side chains have exceeded the score (after it has the same score) for block in blocks: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) for block in sidechain: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, None) # from hathor.graphviz import GraphvizVisualizer # dot = GraphvizVisualizer(manager.tx_storage, include_verifications=True, include_funds=True).dot() # dot.render('dot0') for tx in txs2: meta = tx.get_metadata(force_reload=True) self.assertEqual(meta.first_block, sidechain[0].hash) # Propagate a block connected to the side chain, case (v). # Another side chain has direcly exceeded the best score. fork_block4 = manager.generate_mining_block( parent_block_hash=sidechain3[-1].hash) fork_block4.weight = 10 fork_block4.resolve() fork_block4.verify() self.assertTrue(manager.propagate_tx(fork_block4)) sidechain3.append(fork_block4) for block in blocks: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) for block in sidechain[1:]: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) for block in sidechain2[-1:]: meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, {block.hash}) for block in chain(sidechain[:1], sidechain2[:-1], sidechain3): meta = block.get_metadata(force_reload=True) self.assertEqual(meta.voided_by, None) for tx in txs2: meta = tx.get_metadata(force_reload=True) self.assertEqual(meta.first_block, sidechain[0].hash) # dot = manager.tx_storage.graphviz(format='pdf') # dot.render('test_fork') self.assertConsensusValid(manager) def test_block_height(self): genesis_block = self.genesis_blocks[0] self.assertEqual(genesis_block.get_metadata().height, 0) manager = self.create_peer('testnet', tx_storage=self.tx_storage) # Mine 50 blocks in a row with no transaction but the genesis blocks = add_new_blocks(manager, 50, advance_clock=15) for i, block in enumerate(blocks): expected_height = i + 1 self.assertEqual(block.get_metadata().height, expected_height) def test_tokens_issued_per_block(self): manager = self.create_peer('testnet', tx_storage=self.tx_storage) # this test is pretty dumb in that it test every possible height until halving has long stopped initial_reward = settings.INITIAL_TOKENS_PER_BLOCK final_reward = settings.MINIMUM_TOKENS_PER_BLOCK expected_reward = initial_reward height = 1 # check that there are BLOCKS_PER_HALVING with each reward, starting at the first rewardable block (height=1) for _i_halving in range(0, settings.MAXIMUM_NUMBER_OF_HALVINGS): for _i_block in range(0, settings.BLOCKS_PER_HALVING): reward = manager.get_tokens_issued_per_block(height) self.assertEqual(reward, expected_reward, f'reward at height {height}') height += 1 expected_reward /= 2 self.assertEqual(expected_reward, final_reward) # check that halving stops, for at least two "halving rounds" for _i_block in range(0, 2 * settings.BLOCKS_PER_HALVING): reward = manager.get_tokens_issued_per_block(height) self.assertEqual(reward, expected_reward, f'reward at height {height}') height += 1 def test_block_rewards(self): # even dumber test that only check if manager.get_tokens_issued_per_block was used correctly for a really large # number of blocks, probably not worth running all the time manager = self.create_peer('testnet', tx_storage=self.tx_storage) block_count = (settings.MAXIMUM_NUMBER_OF_HALVINGS + 1) * settings.BLOCKS_PER_HALVING blocks = add_new_blocks(manager, block_count, advance_clock=block_count * 30) for block in blocks: outputs = block.outputs self.assertEqual(len(outputs), 1) output = outputs[0] height = block.get_metadata().height self.assertEqual(output.value, manager.get_tokens_issued_per_block(height)) def test_daa_sanity(self): # sanity test the DAA manager = self.create_peer('testnet', tx_storage=self.tx_storage) manager.test_mode = 0 N = settings.BLOCK_DIFFICULTY_N_BLOCKS T = settings.AVG_TIME_BETWEEN_BLOCKS manager.avg_time_between_blocks = T # stabilize weight on 2 and lower the minimum to 1, so it can vary around 2 manager.min_block_weight = 2 add_new_blocks(manager, N * 2, advance_clock=T) manager.min_block_weight = 1 for i in range(N): # decreasing solvetime should increase weight base_weight = manager.generate_mining_block().weight add_new_blocks(manager, i, advance_clock=T) add_new_blocks(manager, 1, advance_clock=T * 0.9) add_new_blocks(manager, N - i, advance_clock=T) new_weight = manager.generate_mining_block().weight self.assertGreater(new_weight, base_weight) add_new_blocks(manager, N, advance_clock=T) # increasing solvetime should decrease weight base_weight = manager.generate_mining_block().weight add_new_blocks(manager, i, advance_clock=T) add_new_blocks(manager, 1, advance_clock=T * 1.1) add_new_blocks(manager, N - i, advance_clock=T) new_weight = manager.generate_mining_block().weight self.assertLess(new_weight, base_weight) def test_daa_weight_decay_amount(self): manager = self.create_peer('testnet', tx_storage=self.tx_storage) manager.test_mode = 0 amount = settings.WEIGHT_DECAY_AMOUNT for distance in range(0, settings.WEIGHT_DECAY_ACTIVATE_DISTANCE, 10): self.assertEqual(manager.get_weight_decay_amount(distance), 0) distance = settings.WEIGHT_DECAY_ACTIVATE_DISTANCE - 1 self.assertAlmostEqual(manager.get_weight_decay_amount(distance), 0) distance = settings.WEIGHT_DECAY_ACTIVATE_DISTANCE for k in range(1, 11): for _ in range(settings.WEIGHT_DECAY_WINDOW_SIZE): self.assertAlmostEqual( manager.get_weight_decay_amount(distance), k * amount) distance += 1 self.assertAlmostEqual(manager.get_weight_decay_amount(distance), 11 * amount) def test_daa_weight_decay_blocks(self): manager = self.create_peer('testnet', tx_storage=self.tx_storage) manager.test_mode = 0 amount = settings.WEIGHT_DECAY_AMOUNT manager.avg_time_between_blocks = settings.AVG_TIME_BETWEEN_BLOCKS manager.min_block_weight = 2 + 2 * settings.WEIGHT_DECAY_AMOUNT add_new_blocks(manager, 2 * settings.BLOCK_DIFFICULTY_N_BLOCKS, advance_clock=settings.AVG_TIME_BETWEEN_BLOCKS) manager.min_block_weight = 1 base_weight = manager.generate_mining_block().weight self.assertGreater(base_weight, manager.min_block_weight) add_new_blocks(manager, 20, advance_clock=settings.AVG_TIME_BETWEEN_BLOCKS) dt = settings.AVG_TIME_BETWEEN_BLOCKS # the latest call to add_new_blocks will advance the clock while dt < settings.WEIGHT_DECAY_ACTIVATE_DISTANCE: weight = manager.generate_mining_block().weight self.assertAlmostEqual(weight, base_weight) manager.reactor.advance(1) dt += 1 dt = 0 while dt < settings.WEIGHT_DECAY_WINDOW_SIZE: weight = manager.generate_mining_block().weight self.assertAlmostEqual(weight, base_weight - amount) manager.reactor.advance(1) dt += 1 dt = 0 while dt < settings.WEIGHT_DECAY_WINDOW_SIZE: weight = manager.generate_mining_block().weight self.assertAlmostEqual(weight, base_weight - 2 * amount) manager.reactor.advance(1) dt += 1 manager.reactor.advance(1) weight = manager.generate_mining_block().weight self.assertAlmostEqual(weight, manager.min_block_weight)
def setUp(self): super().setUp(TransactionMemoryStorage())
def setUp(self): super().setUp() self.storage = TransactionMemoryStorage()
def setUp(self): store = TransactionMemoryStorage() reactor = Clock() super().setUp(TransactionCacheStorage(store, reactor, capacity=5))
class BaseIndexesTest(unittest.TestCase): __test__ = False def setUp(self): super().setUp() self.wallet = Wallet() self.tx_storage = TransactionMemoryStorage() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] # read genesis keys self.genesis_private_key = get_genesis_key() self.genesis_public_key = self.genesis_private_key.public_key() # this makes sure we can spend the genesis outputs self.manager = self.create_peer('testnet', tx_storage=self.tx_storage, unlock_wallet=True, wallet_index=True) blocks = add_blocks_unlock_reward(self.manager) self.last_block = blocks[-1] def test_tx_tips_with_conflict(self): from hathor.wallet.base_wallet import WalletOutputInfo add_new_blocks(self.manager, 5, advance_clock=15) add_blocks_unlock_reward(self.manager) address = self.get_address(0) value = 500 outputs = [ WalletOutputInfo(address=decode_address(address), value=value, timelock=None) ] tx1 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager.tx_storage) tx1.weight = 2.0 tx1.parents = self.manager.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() self.assertTrue(self.manager.propagate_tx(tx1, False)) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, {tx1.hash}) outputs = [ WalletOutputInfo(address=decode_address(address), value=value, timelock=None) ] tx2 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager.tx_storage) tx2.weight = 2.0 tx2.parents = [tx1.hash] + self.manager.get_new_tx_parents()[1:] self.assertIn(tx1.hash, tx2.parents) tx2.timestamp = int(self.clock.seconds()) + 1 tx2.resolve() self.assertTrue(self.manager.propagate_tx(tx2, False)) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, {tx2.hash}) tx3 = Transaction.create_from_struct(tx2.get_struct()) tx3.timestamp = tx2.timestamp + 1 self.assertIn(tx1.hash, tx3.parents) tx3.resolve() self.assertNotEqual(tx2.hash, tx3.hash) self.assertTrue(self.manager.propagate_tx(tx3, False)) self.assertIn(tx3.hash, tx2.get_metadata().conflict_with) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, # XXX: what should we expect here? I don't think we should exclude both tx2 and tx3, but maybe let the # function using the index decide # {tx1.hash, tx3.hash} {tx1.hash}) def test_tx_tips_voided(self): from hathor.wallet.base_wallet import WalletOutputInfo add_new_blocks(self.manager, 5, advance_clock=15) add_blocks_unlock_reward(self.manager) address1 = self.get_address(0) address2 = self.get_address(1) address3 = self.get_address(2) output1 = WalletOutputInfo(address=decode_address(address1), value=123, timelock=None) output2 = WalletOutputInfo(address=decode_address(address2), value=234, timelock=None) output3 = WalletOutputInfo(address=decode_address(address3), value=345, timelock=None) outputs = [output1, output2, output3] tx1 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager.tx_storage) tx1.weight = 2.0 tx1.parents = self.manager.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() self.assertTrue(self.manager.propagate_tx(tx1, False)) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, {tx1.hash}) tx2 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager.tx_storage) tx2.weight = 2.0 tx2.parents = [tx1.hash] + self.manager.get_new_tx_parents()[1:] self.assertIn(tx1.hash, tx2.parents) tx2.timestamp = int(self.clock.seconds()) + 1 tx2.resolve() self.assertTrue(self.manager.propagate_tx(tx2, False)) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, {tx2.hash}) tx3 = Transaction.create_from_struct(tx2.get_struct()) tx3.weight = 3.0 # tx3.timestamp = tx2.timestamp + 1 tx3.parents = tx1.parents # self.assertIn(tx1.hash, tx3.parents) tx3.resolve() self.assertNotEqual(tx2.hash, tx3.hash) self.assertTrue(self.manager.propagate_tx(tx3, False)) # self.assertIn(tx3.hash, tx2.get_metadata().voided_by) self.assertIn(tx3.hash, tx2.get_metadata().conflict_with) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, # XXX: what should we expect here? I don't think we should exclude both tx2 and tx3, but maybe let the # function using the index decide {tx1.hash, tx3.hash}) def test_genesis_not_in_mempool(self): mempool_txs = list( self.tx_storage.indexes.mempool_tips.iter_all(self.tx_storage)) for tx in self.genesis_txs: self.assertNotIn(tx, mempool_txs)