Пример #1
0
 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()
Пример #2
0
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)
Пример #3
0
    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()
Пример #4
0
    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]
Пример #5
0
    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)
Пример #6
0
    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)
Пример #8
0
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)
Пример #9
0
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
Пример #10
0
    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)
Пример #11
0
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()
Пример #12
0
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)
Пример #13
0
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]))
Пример #14
0
 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]
Пример #15
0
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)
Пример #16
0
    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)
Пример #17
0
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)
Пример #18
0
 def setUp(self):
     super().setUp(TransactionMemoryStorage())
Пример #19
0
 def setUp(self):
     super().setUp()
     self.storage = TransactionMemoryStorage()
Пример #20
0
 def setUp(self):
     store = TransactionMemoryStorage()
     reactor = Clock()
     super().setUp(TransactionCacheStorage(store, reactor, capacity=5))
Пример #21
0
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)