def test_invalid_same_peer_id(self): manager3 = self.create_peer(self.network, peer_id=self.peer_id1) conn = FakeConnection(self.manager1, manager3) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR') self.assertTrue(conn.tr1.disconnecting)
def test_block_sync_new_blocks_and_txs(self): self._add_new_blocks(25) self._add_new_transactions(3) self._add_new_blocks(4) self._add_new_transactions(5) manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) for _ in range(1000): conn.run_one_step() self.clock.advance(0.1) # dot1 = self.manager1.tx_storage.graphviz(format='pdf') # dot1.render('dot1') # dot2 = manager2.tx_storage.graphviz(format='pdf') # dot2.render('dot2') node_sync = conn.proto1.state.sync_manager self.assertEqual(self.manager1.tx_storage.latest_timestamp, manager2.tx_storage.latest_timestamp) self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(self.manager1, manager2) self.assertConsensusEqual(self.manager1, manager2) self.assertConsensusValid(self.manager1) self.assertConsensusValid(manager2)
def setUp(self): super().setUp() self.network = 'testnet' self.peer_id1 = PeerId() self.peer_id2 = PeerId() self.manager1 = self.create_peer(self.network, peer_id=self.peer_id1) self.manager2 = self.create_peer(self.network, peer_id=self.peer_id2) self.conn = FakeConnection(self.manager1, self.manager2)
def _simulate_run(self, run_i, simulator): # XXX: the following was adapted from test_new_syncing_peer, it doesn't matter too much, but has good coverage # of different behaviors that can be affected by non-determinism on the fullnode implementation self.log.debug(f'run{run_i}: simulator{run_i}') nodes = [] miners = [] tx_generators = [] peer_id_pool = self.new_peer_id_pool() manager = self.create_simulator_peer(simulator, peer_id_pool) nodes.append(manager) miner = simulator.create_miner(manager, hashpower=10e6) miner.start() miners.append(miner) simulator.run(10) for i, hashpower in enumerate([10e6, 8e6, 5e6]): manager = self.create_simulator_peer(simulator, peer_id_pool) for node in nodes: conn = FakeConnection(manager, node, latency=0.085) simulator.add_connection(conn) nodes.append(manager) miner = simulator.create_miner(manager, hashpower=hashpower) miner.start() miners.append(miner) for i, rate in enumerate([5, 4, 3]): tx_gen = simulator.create_tx_generator(nodes[i], rate=rate * 1 / 60., hashpower=1e6, ignore_no_funds=True) tx_gen.start() tx_generators.append(tx_gen) simulator.run(10) self.log.debug(f'run{run_i}: adding late node') late_manager = self.create_simulator_peer(simulator, peer_id_pool) for node in nodes: conn = FakeConnection(late_manager, node, latency=0.300) simulator.add_connection(conn) nodes.append(late_manager) simulator.run(10) for tx_gen in tx_generators: tx_gen.stop() for miner in miners: miner.stop() simulator.run(10) return nodes
def test_tx_propagation_nat_peers(self): """ manager1 <- manager2 <- manager3 """ self._add_new_blocks(25) self.manager2 = self.create_peer(self.network) self.conn1 = FakeConnection(self.manager1, self.manager2) for _ in range(1000): if self.conn1.is_empty(): break self.conn1.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self._add_new_blocks(1) for _ in range(1000): if self.conn1.is_empty(): break self.conn1.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self.manager3 = self.create_peer(self.network) self.conn2 = FakeConnection(self.manager2, self.manager3) for _ in range(1000): if self.conn1.is_empty() and self.conn2.is_empty(): break self.conn1.run_one_step() self.conn2.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self.assertTipsEqual(self.manager1, self.manager3) self._add_new_transactions(1) for _ in range(1000): if self.conn1.is_empty() and self.conn2.is_empty(): break self.conn1.run_one_step() self.conn2.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self.assertTipsEqual(self.manager1, self.manager3) self.assertConsensusEqual(self.manager1, self.manager2) self.assertConsensusEqual(self.manager1, self.manager3) self.assertConsensusValid(self.manager1) self.assertConsensusValid(self.manager2) self.assertConsensusValid(self.manager3)
def test_new_syncing_peer(self): nodes = [] miners = [] tx_generators = [] manager = self.create_peer() nodes.append(manager) miner = self.simulator.create_miner(manager, hashpower=10e6) miner.start() miners.append(miner) self.simulator.run(600) for hashpower in [10e6, 8e6, 5e6]: manager = self.create_peer() for node in nodes: conn = FakeConnection(manager, node, latency=0.085) self.simulator.add_connection(conn) nodes.append(manager) miner = self.simulator.create_miner(manager, hashpower=hashpower) miner.start() miners.append(miner) for i, rate in enumerate([5, 4, 3]): tx_gen = self.simulator.create_tx_generator(nodes[i], rate=rate * 1 / 60., hashpower=1e6, ignore_no_funds=True) tx_gen.start() tx_generators.append(tx_gen) self.simulator.run(600) self.log.debug('adding late node') late_manager = self.create_peer() for node in nodes: conn = FakeConnection(late_manager, node, latency=0.300) self.simulator.add_connection(conn) self.simulator.run(600) for tx_gen in tx_generators: tx_gen.stop() for miner in miners: miner.stop() self.simulator.run_until_complete(600) for idx, node in enumerate(nodes): self.log.debug(f'checking node {idx}') self.assertConsensusValid(node) self.assertConsensusEqual(node, late_manager)
def test_capabilities(self): network = 'testnet' manager1 = self.create_peer( network, capabilities=[settings.CAPABILITY_WHITELIST]) manager2 = self.create_peer(network, capabilities=[]) conn = FakeConnection(manager1, manager2) # Run the p2p protocol. for _ in range(100): conn.run_one_step(debug=True) self.clock.advance(0.1) # Even if we don't have the capability we must connect because the whitelist url conf is None self.assertEqual(conn._proto1.state.state_name, 'READY') self.assertEqual(conn._proto2.state.state_name, 'READY') manager3 = self.create_peer( network, capabilities=[settings.CAPABILITY_WHITELIST]) manager4 = self.create_peer( network, capabilities=[settings.CAPABILITY_WHITELIST]) conn2 = FakeConnection(manager3, manager4) # Run the p2p protocol. for _ in range(100): conn2.run_one_step(debug=True) self.clock.advance(0.1) self.assertEqual(conn2._proto1.state.state_name, 'READY') self.assertEqual(conn2._proto2.state.state_name, 'READY')
def test_many_miners_since_beginning(self): nodes = [] miners = [] for hashpower in [10e6, 5e6, 1e6, 1e6, 1e6]: manager = self.create_peer() for node in nodes: conn = FakeConnection(manager, node, latency=0.085) self.simulator.add_connection(conn) nodes.append(manager) miner = self.simulator.create_miner(manager, hashpower=hashpower) miner.start() miners.append(miner) self.simulator.run(600) for miner in miners: miner.stop() self.simulator.run(15) for node in nodes[1:]: self.assertTipsEqual(nodes[0], node)
def test_two_nodes(self): manager1 = self.create_peer() manager2 = self.create_peer() miner1 = self.simulator.create_miner(manager1, hashpower=10e6) miner1.start() self.simulator.run(10) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(60) conn12 = FakeConnection(manager1, manager2, latency=0.150) self.simulator.add_connection(conn12) self.simulator.run(60) miner2 = self.simulator.create_miner(manager2, hashpower=100e6) miner2.start() self.simulator.run(120) gen_tx2 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx2.start() self.simulator.run(10 * 60) miner1.stop() miner2.stop() gen_tx1.stop() gen_tx2.stop() self.simulator.run(5 * 60) self.assertTrue(conn12.is_connected) self.assertTipsEqual(manager1, manager2)
def test_invalid_different_network(self): manager3 = self.create_peer(network='mainnet') conn = FakeConnection(self.manager1, manager3) conn.run_one_step() # HELLO self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR') self.assertTrue(conn.tr1.disconnecting) conn.run_one_step() # ERROR
def test_block_sync_many_new_blocks(self): self._add_new_blocks(150) manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) while not conn.is_empty(): conn.run_one_step(debug=True) self.clock.advance(0.1) node_sync = conn.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(self.manager1, manager2) self.assertConsensusEqual(self.manager1, manager2) self.assertConsensusValid(self.manager1) self.assertConsensusValid(manager2)
def test_mempool_basic(self): # 10 blocks self._add_new_blocks(2) # N blocks to unlock the reward add_blocks_unlock_reward(self.manager1) # 5 transactions to be confirmed by the next blocks self._add_new_transactions(5) # 2 more blocks self._add_new_blocks(2) # 30 transactions in the mempool self._add_new_transactions(30) debug_pdf = False if debug_pdf: dot1 = GraphvizVisualizer(self.manager1.tx_storage, include_verifications=True, include_funds=True).dot() dot1.render('mempool-test') manager2 = self.create_peer(self.network, enable_sync_v1=True) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) for _ in range(1000): if conn.is_empty(): break conn.run_one_step(debug=True) self.clock.advance(1) self.assertConsensusValid(self.manager1) self.assertConsensusValid(manager2) self.assertConsensusEqual(self.manager1, manager2) # 3 genesis # 25 blocks # Unlock reward blocks # 8 txs self.assertEqual(len(manager2.tx_storage.indexes.mempool_tips.get()), 1) self.assertEqual( len(self.manager1.tx_storage.indexes.mempool_tips.get()), 1)
def test_match_peer_id(self): network = 'testnet' peer_id1 = PeerId() peer_id2 = PeerId() manager1 = self.create_peer(network, peer_id=peer_id1) manager2 = self.create_peer(network, peer_id=peer_id2) conn = FakeConnection(manager1, manager2) self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.HELLO)) matcher = NetfilterMatchPeerId(str(peer_id1.id)) context = NetfilterContext(protocol=conn.proto2) self.assertFalse(matcher.match(context)) conn.run_one_step() self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.PEER_ID)) self.assertFalse(matcher.match(context)) # Success because the connection is ready and proto2 is connected to proto1. conn.run_one_step() conn.run_one_step() self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.READY)) self.assertTrue(matcher.match(context)) # Fail because proto1 is connected to proto2, and the peer id cannot match. context = NetfilterContext(protocol=conn.proto1) self.assertFalse(matcher.match(context))
def test_block_sync_only_genesis(self): manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID conn.run_one_step() # READY node_sync = conn.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(self.manager1, manager2)
def test_two_connections(self): self.conn.run_one_step() # HELLO self.conn.run_one_step() # PEER-ID self.conn.run_one_step() # READY self.conn.run_one_step() # GET-PEERS self.conn.run_one_step() # GET-TIPS manager3 = self.create_peer(self.network) conn = FakeConnection(self.manager1, manager3) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID conn.run_one_step() # READY conn.run_one_step() # GET-PEERS self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEERS') self.conn.run_one_step()
def test_split_brain(self): debug_pdf = False manager1 = self.create_peer() manager1.allow_mining_without_peers() manager2 = self.create_peer() manager2.allow_mining_without_peers() miner11 = self.simulator.create_miner(manager1, hashpower=10e6) miner11.start() gen_tx11 = self.simulator.create_tx_generator(manager1, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx11.start() gen_tx12 = self.simulator.create_tx_generator(manager1, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx12.enable_double_spending() gen_tx12.start() miner21 = self.simulator.create_miner(manager2, hashpower=10e6) miner21.start() gen_tx21 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx21.start() gen_tx22 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx22.enable_double_spending() gen_tx22.start() self.simulator.run(400) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True, include_funds=True).dot() dot1.render('dot1-pre') dot2 = GraphvizVisualizer(manager2.tx_storage, include_verifications=True, include_funds=True).dot() dot2.render('dot2-pre') self.assertTipsNotEqual(manager1, manager2) self.assertConsensusValid(manager1) self.assertConsensusValid(manager2) # input('Press enter to continue...') miner11.stop() gen_tx11.stop() gen_tx12.stop() miner21.stop() gen_tx21.stop() gen_tx22.stop() conn12 = FakeConnection(manager1, manager2) self.simulator.add_connection(conn12) self.simulator.run(300) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True).dot() dot1.render('dot1-post') dot2 = GraphvizVisualizer(manager2.tx_storage, include_verifications=True).dot() dot2.render('dot2-post') node_sync = conn12.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) node_sync = conn12.proto2.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(manager1, manager2) self.assertConsensusEqual(manager1, manager2) self.assertConsensusValid(manager1) self.assertConsensusValid(manager2)
def test_downloader(self): from hathor.p2p.node_sync import NodeSyncTimestamp blocks = self._add_new_blocks(3) manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) # Get to PEER-ID state only because when it gets to READY it will automatically sync conn.run_one_step() self.assertTrue(isinstance(conn.proto1.state, PeerIdState)) self.assertTrue(isinstance(conn.proto2.state, PeerIdState)) downloader = conn.proto2.connections._sync_factories[ SyncVersion.V1].downloader node_sync1 = NodeSyncTimestamp(conn.proto1, downloader, reactor=conn.proto1.node.reactor) node_sync1.start() node_sync2 = NodeSyncTimestamp(conn.proto2, downloader, reactor=conn.proto2.node.reactor) node_sync2.start() self.assertTrue(isinstance(conn.proto1.state, PeerIdState)) self.assertTrue(isinstance(conn.proto2.state, PeerIdState)) deferred1 = downloader.get_tx(blocks[0].hash, node_sync1) deferred1.addCallback(node_sync1.on_tx_success) self.assertEqual(len(downloader.pending_transactions), 1) details = downloader.pending_transactions[blocks[0].hash] self.assertEqual(len(details.connections), 1) self.assertEqual(len(downloader.downloading_deque), 1) deferred2 = downloader.get_tx(blocks[0].hash, node_sync2) deferred2.addCallback(node_sync2.on_tx_success) self.assertEqual(len(downloader.pending_transactions), 1) self.assertEqual( len(downloader.pending_transactions[blocks[0].hash].connections), 2) self.assertEqual(len(downloader.downloading_deque), 1) self.assertEqual(deferred1, deferred2) details.downloading_deferred.callback(blocks[0]) self.assertEqual(len(downloader.downloading_deque), 0) self.assertEqual(len(downloader.pending_transactions), 0) # Getting tx already downloaded downloader.get_tx(blocks[0].hash, node_sync1) self.assertEqual(len(downloader.downloading_deque), 0) # Adding fake tx_id to downloading deque downloader.downloading_deque.append('1') # Getting new tx downloader.get_tx(blocks[1].hash, node_sync1) self.assertEqual(len(downloader.pending_transactions), 1) details = downloader.pending_transactions[blocks[1].hash] self.assertEqual(len(details.connections), 1) self.assertEqual(len(downloader.downloading_deque), 2) details.downloading_deferred.callback(blocks[1]) # Still 2 elements because the first one is not downloaded yet self.assertEqual(len(downloader.downloading_deque), 2) # Remove it downloader.downloading_deque.popleft() # And try again downloader.check_downloading_queue() self.assertEqual(len(downloader.downloading_deque), 0)
def setUp(self): super().setUp() self.web = StubSite(StatusResource(self.manager)) self.manager2 = self.create_peer('testnet') self.conn1 = FakeConnection(self.manager, self.manager2)
def test_determinism_interleaved(self): # sanity assert as to not mess up with it on the setup self.assertEqual(self.simulator1.seed, self.simulator2.seed) # XXX: the following was adapted from test_new_syncing_peer, it doesn't matter too much, but has good coverage # of different behaviors that can be affected by non-determinism on the fullnode implementation nodes1 = [] nodes2 = [] miners1 = [] miners2 = [] tx_generators1 = [] tx_generators2 = [] peer_id_pool1 = self.new_peer_id_pool() peer_id_pool2 = self.new_peer_id_pool() self.log.debug('part1 simulator1') manager1 = self.create_simulator_peer(self.simulator1, peer_id_pool1) nodes1.append(manager1) miner1 = self.simulator1.create_miner(manager1, hashpower=10e6) miner1.start() miners1.append(miner1) self.log.debug('part1 simulator2') manager2 = self.create_simulator_peer(self.simulator2, peer_id_pool2) nodes2.append(manager2) miner2 = self.simulator2.create_miner(manager2, hashpower=10e6) miner2.start() miners2.append(miner2) for _ in range(3): self.simulator1.run(10) self.simulator2.run(10) for idx, (node1, node2) in enumerate(zip(nodes1, nodes2)): self.log.debug(f'checking node {idx}') self.assertConsensusEqual(node1, node2) for i, hashpower in enumerate([10e6, 8e6, 5e6]): self.log.debug(f'part2.{i} simulator1') manager1 = self.create_simulator_peer(self.simulator1, peer_id_pool1) for node in nodes1: conn = FakeConnection(manager1, node, latency=0.085) self.simulator1.add_connection(conn) nodes1.append(manager1) miner1 = self.simulator1.create_miner(manager1, hashpower=hashpower) miner1.start() miners1.append(miner1) self.log.debug(f'part2.{i} simulator2') manager2 = self.create_simulator_peer(self.simulator2, peer_id_pool2) for node in nodes2: conn = FakeConnection(manager2, node, latency=0.085) self.simulator2.add_connection(conn) nodes2.append(manager2) miner2 = self.simulator2.create_miner(manager2, hashpower=hashpower) miner2.start() miners2.append(miner2) for i, rate in enumerate([5, 4, 3]): self.log.debug(f'part3.{i} simulator1') tx_gen1 = self.simulator1.create_tx_generator(nodes1[i], rate=rate * 1 / 60., hashpower=1e6, ignore_no_funds=True) tx_gen1.start() tx_generators1.append(tx_gen1) self.log.debug(f'part3.{i} simulator2') tx_gen2 = self.simulator2.create_tx_generator(nodes2[i], rate=rate * 1 / 60., hashpower=1e6, ignore_no_funds=True) tx_gen2.start() tx_generators2.append(tx_gen2) for _ in range(3): self.simulator1.run(10) self.simulator2.run(10) for idx, (node1, node2) in enumerate(zip(nodes1, nodes2)): self.log.debug(f'checking node {idx}') self.assertConsensusEqual(node1, node2) self.log.debug('adding late node') self.log.debug('part4 simulator1') late_manager1 = self.create_simulator_peer(self.simulator1, peer_id_pool1) for node in nodes1: conn = FakeConnection(late_manager1, node, latency=0.300) self.simulator1.add_connection(conn) nodes1.append(late_manager1) self.log.debug('part4 simulator2') late_manager2 = self.create_simulator_peer(self.simulator2, peer_id_pool2) for node in nodes2: conn = FakeConnection(late_manager2, node, latency=0.300) self.simulator2.add_connection(conn) nodes2.append(late_manager2) for _ in range(3): self.simulator1.run(10) self.simulator2.run(10) for idx, (node1, node2) in enumerate(zip(nodes1, nodes2)): self.log.debug(f'checking node {idx}') self.assertConsensusEqual(node1, node2) self.log.debug('part5 simulator1') for tx_gen in tx_generators1: tx_gen.stop() for miner in miners1: miner.stop() self.log.debug('part5 simulator2') for tx_gen in tx_generators2: tx_gen.stop() for miner in miners2: miner.stop() for _ in range(3): self.simulator1.run(10) self.simulator2.run(10) for idx, (node1, node2) in enumerate(zip(nodes1, nodes2)): self.log.debug(f'checking node {idx}') self.assertConsensusEqual(node1, node2)
def test_soft_voided(self): txA_hash = bytes.fromhex( '4586c5428e8d666ea59684c1cd9286d2b9d9e89b4939207db47412eeaabc48b2') soft_voided_tx_ids = set([ txA_hash, ]) manager1 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager1.allow_mining_without_peers() miner1 = self.simulator.create_miner(manager1, hashpower=5e6) miner1.start() self.simulator.run(60) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(300) manager2 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager2.soft_voided_tx_ids = soft_voided_tx_ids graphviz = GraphvizVisualizer(manager2.tx_storage, include_verifications=True, include_funds=True) conn12 = FakeConnection(manager1, manager2, latency=0.001) self.simulator.add_connection(conn12) miner2 = self.simulator.create_miner(manager2, hashpower=10e6) miner2.start() gen_tx2 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx2.start() self.simulator.run(900) txA = manager2.tx_storage.get_transaction(txA_hash) metaA = txA.get_metadata() self.assertEqual({settings.SOFT_VOIDED_ID, txA.hash}, metaA.voided_by) graphviz.labels[txA.hash] = 'txA' txB = add_custom_tx(manager2, [(txA, 0)]) metaB = txB.get_metadata() self.assertEqual({txA.hash}, metaB.voided_by) graphviz.labels[txB.hash] = 'txB' txD1 = add_custom_tx(manager2, [(txB, 0)]) metaD1 = txD1.get_metadata() self.assertEqual({txA.hash}, metaD1.voided_by) graphviz.labels[txD1.hash] = 'txD1' txD2 = add_custom_tx(manager2, [(txB, 0)]) metaD2 = txD2.get_metadata() self.assertEqual({txA.hash, txD2.hash}, metaD2.voided_by) graphviz.labels[txD2.hash] = 'txD2' metaD1 = txD1.get_metadata() self.assertEqual({txA.hash, txD1.hash}, metaD1.voided_by) address = manager2.wallet.get_unused_address(mark_as_used=False) value = 1 txC = gen_new_tx(manager2, address, value) txC.parents[0] = txA.hash txC.timestamp = max(txC.timestamp, txA.timestamp + 1) txC.weight = 25 txC.update_hash() self.assertTrue(manager2.propagate_tx(txC, fails_silently=False)) metaC = txC.get_metadata() self.assertIsNone(metaC.voided_by) graphviz.labels[txC.hash] = 'txC' blk1 = manager2.generate_mining_block() self.assertNoParentsAreSoftVoided(blk1) blk1.parents[1] = txA.hash blk1.nonce = self.rng.getrandbits(32) blk1.update_hash() self.assertTrue(manager2.propagate_tx(blk1, fails_silently=False)) blk1meta = blk1.get_metadata() self.assertIsNone(blk1meta.voided_by) graphviz.labels[blk1.hash] = 'b1' blk2 = manager2.generate_mining_block() self.assertNoParentsAreSoftVoided(blk2) if txD1.hash not in blk2.parents: blk2.parents[1] = txD1.hash blk2.nonce = self.rng.getrandbits(32) blk2.update_hash() self.assertTrue(manager2.propagate_tx(blk2, fails_silently=False)) blk2meta = blk2.get_metadata() self.assertIsNone(blk2meta.voided_by) graphviz.labels[blk2.hash] = 'b2' blk3 = manager2.generate_mining_block() self.assertNoParentsAreSoftVoided(blk3) blk3.parents[1] = txD2.hash blk3.nonce = self.rng.getrandbits(32) blk3.update_hash() self.assertTrue(manager2.propagate_tx(blk3, fails_silently=False)) blk3meta = blk3.get_metadata() self.assertIsNone(blk3meta.voided_by) graphviz.labels[blk3.hash] = 'b3' for tx in manager1.tx_storage.get_all_transactions(): meta = tx.get_metadata() voided_by = meta.voided_by or set() if settings.SOFT_VOIDED_ID in voided_by: self.assertTrue({settings.SOFT_VOIDED_ID, tx.hash}.issubset(voided_by))
class BaseHathorSyncMethodsTestCase(unittest.TestCase): __test__ = False def setUp(self): super().setUp() # import sys # from twisted.python import log # log.startLogging(sys.stdout) self.network = 'testnet' self.manager1 = self.create_peer(self.network, unlock_wallet=True) self.manager1.avg_time_between_blocks = 4 self.genesis = self.manager1.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] def _add_new_tx(self, address, value): from hathor.transaction import Transaction from hathor.wallet.base_wallet import WalletOutputInfo outputs = [] outputs.append( WalletOutputInfo(address=decode_address(address), value=int(value), timelock=None)) tx = self.manager1.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager1.tx_storage) tx.timestamp = int(self.clock.seconds()) tx.storage = self.manager1.tx_storage tx.weight = 10 tx.parents = self.manager1.get_new_tx_parents() tx.resolve() tx.verify() self.manager1.propagate_tx(tx) self.clock.advance(10) return tx def _add_new_transactions(self, num_txs): txs = [] for _ in range(num_txs): address = self.get_address(0) value = random.choice([5, 10, 50, 100, 120]) tx = self._add_new_tx(address, value) txs.append(tx) return txs def _add_new_block(self, propagate=True): block = self.manager1.generate_mining_block() self.assertTrue(block.resolve()) block.verify() self.manager1.on_new_tx(block, propagate_to_peers=propagate) self.clock.advance(10) return block def _add_new_blocks(self, num_blocks, propagate=True): blocks = [] for _ in range(num_blocks): blocks.append(self._add_new_block(propagate=propagate)) return blocks def test_get_blocks_before(self): genesis_block = self.genesis_blocks[0] result = self.manager1.tx_storage.get_blocks_before(genesis_block.hash) self.assertEqual(0, len(result)) genesis_tx = [tx for tx in self.genesis if not tx.is_block][0] with self.assertRaises(TransactionIsNotABlock): self.manager1.tx_storage.get_blocks_before(genesis_tx.hash) blocks = self._add_new_blocks(20) num_blocks = 5 for i, block in enumerate(blocks): result = self.manager1.tx_storage.get_blocks_before( block.hash, num_blocks=num_blocks) expected_result = [genesis_block] + blocks[:i] expected_result = expected_result[-num_blocks:] expected_result = expected_result[::-1] self.assertEqual(result, expected_result) def test_block_sync_only_genesis(self): manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID conn.run_one_step() # READY node_sync = conn.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(self.manager1, manager2) def test_block_sync_new_blocks(self): self._add_new_blocks(15) manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) for _ in range(10000): if conn.is_empty(): break conn.run_one_step(debug=True) self.clock.advance(0.1) node_sync = conn.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(self.manager1, manager2) self.assertConsensusEqual(self.manager1, manager2) self.assertConsensusValid(self.manager1) self.assertConsensusValid(manager2) def test_block_sync_many_new_blocks(self): self._add_new_blocks(150) manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) while not conn.is_empty(): conn.run_one_step(debug=True) self.clock.advance(0.1) node_sync = conn.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(self.manager1, manager2) self.assertConsensusEqual(self.manager1, manager2) self.assertConsensusValid(self.manager1) self.assertConsensusValid(manager2) def test_block_sync_new_blocks_and_txs(self): self._add_new_blocks(25) self._add_new_transactions(3) self._add_new_blocks(4) self._add_new_transactions(5) manager2 = self.create_peer(self.network) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) for _ in range(1000): conn.run_one_step() self.clock.advance(0.1) # dot1 = self.manager1.tx_storage.graphviz(format='pdf') # dot1.render('dot1') # dot2 = manager2.tx_storage.graphviz(format='pdf') # dot2.render('dot2') node_sync = conn.proto1.state.sync_manager self.assertEqual(self.manager1.tx_storage.latest_timestamp, manager2.tx_storage.latest_timestamp) self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(self.manager1, manager2) self.assertConsensusEqual(self.manager1, manager2) self.assertConsensusValid(self.manager1) self.assertConsensusValid(manager2) def test_tx_propagation_nat_peers(self): """ manager1 <- manager2 <- manager3 """ self._add_new_blocks(25) self.manager2 = self.create_peer(self.network) self.conn1 = FakeConnection(self.manager1, self.manager2) for _ in range(1000): if self.conn1.is_empty(): break self.conn1.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self._add_new_blocks(1) for _ in range(1000): if self.conn1.is_empty(): break self.conn1.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self.manager3 = self.create_peer(self.network) self.conn2 = FakeConnection(self.manager2, self.manager3) for _ in range(1000): if self.conn1.is_empty() and self.conn2.is_empty(): break self.conn1.run_one_step() self.conn2.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self.assertTipsEqual(self.manager1, self.manager3) self._add_new_transactions(1) for _ in range(1000): if self.conn1.is_empty() and self.conn2.is_empty(): break self.conn1.run_one_step() self.conn2.run_one_step() self.clock.advance(0.1) self.assertTipsEqual(self.manager1, self.manager2) self.assertTipsEqual(self.manager1, self.manager3) self.assertConsensusEqual(self.manager1, self.manager2) self.assertConsensusEqual(self.manager1, self.manager3) self.assertConsensusValid(self.manager1) self.assertConsensusValid(self.manager2) self.assertConsensusValid(self.manager3)
def test_soft_voided(self): txA_hash = bytes.fromhex( '4586c5428e8d666ea59684c1cd9286d2b9d9e89b4939207db47412eeaabc48b2') soft_voided_tx_ids = set([ txA_hash, ]) manager1 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager1.allow_mining_without_peers() miner1 = self.simulator.create_miner(manager1, hashpower=5e6) miner1.start() self.simulator.run(60) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(300) manager2 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager2.soft_voided_tx_ids = soft_voided_tx_ids conn12 = FakeConnection(manager1, manager2, latency=0.001) self.simulator.add_connection(conn12) miner2 = self.simulator.create_miner(manager2, hashpower=10e6) miner2.start() gen_tx2 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx2.start() self.simulator.run(900) miner2.stop() gen_tx2.stop() txA = manager2.tx_storage.get_transaction(txA_hash) metaA = txA.get_metadata() self.assertEqual({settings.SOFT_VOIDED_ID, txA.hash}, metaA.voided_by) txB = add_custom_tx(manager2, [(txA, 0)]) metaB = txB.get_metadata() self.assertEqual({txA.hash}, metaB.voided_by) txD1 = add_custom_tx(manager2, [(txB, 0)]) metaD1 = txD1.get_metadata() self.assertEqual({txA.hash}, metaD1.voided_by) blk1 = manager2.generate_mining_block() self.assertNoParentsAreSoftVoided(blk1) if txD1.hash not in blk1.parents: blk1.parents[1] = txD1.hash blk1.timestamp = txD1.timestamp + 1 blk1.nonce = self.rng.getrandbits(32) blk1.update_hash() self.assertTrue(manager2.propagate_tx(blk1, fails_silently=False)) blk1meta = blk1.get_metadata() self.assertIsNone(blk1meta.voided_by) self.simulator.run(10) address = manager2.wallet.get_unused_address(mark_as_used=True) txC = gen_new_tx(manager2, address, 6400) if txD1.hash not in txC.parents: txC.parents[1] = txD1.hash txC.weight = 25 txC.update_hash() manager2.propagate_tx(txC, fails_silently=False) metaC = txC.get_metadata() self.assertIsNone(metaC.voided_by) txD2 = gen_custom_tx(manager2, [(txB, 0)]) txD2.timestamp = txD1.timestamp + 2 txD2.update_hash() manager2.propagate_tx(txD2, fails_silently=False) blk1meta = blk1.get_metadata() self.assertIsNone(blk1meta.voided_by) metaC = txC.get_metadata() self.assertIsNone(metaC.voided_by)
class BaseHathorProtocolTestCase(unittest.TestCase): __test__ = False def setUp(self): super().setUp() self.network = 'testnet' self.peer_id1 = PeerId() self.peer_id2 = PeerId() self.manager1 = self.create_peer(self.network, peer_id=self.peer_id1) self.manager2 = self.create_peer(self.network, peer_id=self.peer_id2) self.conn = FakeConnection(self.manager1, self.manager2) def assertIsConnected(self, conn=None): if conn is None: conn = self.conn self.assertFalse(conn.tr1.disconnecting) self.assertFalse(conn.tr2.disconnecting) def assertIsNotConnected(self, conn=None): if conn is None: conn = self.conn self.assertTrue(conn.tr1.disconnecting) self.assertTrue(conn.tr2.disconnecting) def _send_cmd(self, proto, cmd, payload=None): if not payload: line = '{}\r\n'.format(cmd) else: line = '{} {}\r\n'.format(cmd, payload) if isinstance(line, str): line = line.encode('utf-8') return proto.dataReceived(line) def _check_result_only_cmd(self, result, expected_cmd): cmd_list = [] for line in result.split(b'\r\n'): cmd, _, _ = line.partition(b' ') cmd_list.append(cmd) self.assertIn(expected_cmd, cmd_list) def _check_cmd_and_value(self, result, expected): result_list = [] for line in result.split(b'\r\n'): cmd, _, data = line.partition(b' ') result_list.append((cmd, data)) self.assertIn(expected, result_list) def test_on_connect(self): self._check_result_only_cmd(self.conn.peek_tr1_value(), b'HELLO') def test_invalid_command(self): self._send_cmd(self.conn.proto1, 'INVALID-CMD') self.conn.proto1.state.handle_error('') self.assertTrue(self.conn.tr1.disconnecting) def test_rate_limit(self): hits = 1 window = 60 self.conn.proto1.ratelimit.set_limit( HathorProtocol.RateLimitKeys.GLOBAL, hits, window) # first will be OK and reach the hits limit per window self.conn.run_one_step() # HELLO # second will fail and be throttled self.conn.run_one_step() # PEER-ID self._check_cmd_and_value( self.conn.peek_tr1_value(), (b'THROTTLE', 'global At most {} hits every {} seconds'.format( hits, window).encode('utf-8')), ) self.conn.proto1.state.handle_throttle(b'') # Test empty disconnect self.conn.proto1.state = None self.conn.proto1.connections = None self.conn.proto1.on_disconnect(Failure(Exception())) def test_invalid_size(self): self.conn.tr1.clear() # Creating big payload big_payload = '[' for x in range(65536): big_payload = '{}{}'.format(big_payload, x) big_payload = '{}]'.format(big_payload) self._send_cmd(self.conn.proto1, 'HELLO', big_payload) self.assertTrue(self.conn.tr1.disconnecting) def test_invalid_payload(self): self.conn.run_one_step() # HELLO self.conn.run_one_step() # PEER-ID self.conn.run_one_step() # READY with self.assertRaises(json.decoder.JSONDecodeError): self._send_cmd(self.conn.proto1, 'PEERS', 'abc') def test_invalid_hello1(self): self.conn.tr1.clear() self._send_cmd(self.conn.proto1, 'HELLO') self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR') self.assertTrue(self.conn.tr1.disconnecting) def test_invalid_hello2(self): self.conn.tr1.clear() self._send_cmd(self.conn.proto1, 'HELLO', 'invalid_payload') self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR') self.assertTrue(self.conn.tr1.disconnecting) def test_invalid_hello3(self): self.conn.tr1.clear() self._send_cmd(self.conn.proto1, 'HELLO', '{}') self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR') self.assertTrue(self.conn.tr1.disconnecting) def test_invalid_hello4(self): self.conn.tr1.clear() self._send_cmd( self.conn.proto1, 'HELLO', '{"app": 0, "remote_address": 1, "network": 2, "genesis_hash": "123", "settings_hash": "456"}' ) self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR') self.assertTrue(self.conn.tr1.disconnecting) def test_invalid_hello5(self): # hello with clocks too far apart self.conn.tr1.clear() data = self.conn.proto2.state._get_hello_data() data['timestamp'] = data[ 'timestamp'] + settings.MAX_FUTURE_TIMESTAMP_ALLOWED / 2 + 1 self._send_cmd(self.conn.proto1, 'HELLO', json.dumps(data)) self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR') self.assertTrue(self.conn.tr1.disconnecting) def test_valid_hello(self): self.conn.run_one_step() # HELLO self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEER-ID') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'PEER-ID') self.assertFalse(self.conn.tr1.disconnecting) self.assertFalse(self.conn.tr2.disconnecting) @inlineCallbacks def test_invalid_peer_id(self): self.conn.run_one_step() # HELLO self.conn.run_one_step() # PEER-ID self.conn.run_one_step() # READY self.conn.run_one_step() # GET-PEERS self.conn.run_one_step() # GET-TIPS self.conn.run_one_step() # PEERS self.conn.run_one_step() # TIPS invalid_payload = { 'id': '123', 'entrypoints': ['tcp://localhost:1234'] } yield self._send_cmd(self.conn.proto1, 'PEER-ID', json.dumps(invalid_payload)) self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR') self.assertTrue(self.conn.tr1.disconnecting) def test_invalid_same_peer_id(self): manager3 = self.create_peer(self.network, peer_id=self.peer_id1) conn = FakeConnection(self.manager1, manager3) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR') self.assertTrue(conn.tr1.disconnecting) def test_invalid_same_peer_id2(self): """ We connect nodes 1-2 and 1-3. Nodes 2 and 3 have the same peer_id. The connections are established simultaneously, so we do not detect a peer id duplication in PEER_ID state, only on READY state. """ # Disable idle timeout before creating any new peer because self.create_peer(...) # runs the main loop. self.conn.disable_idle_timeout() # Create new peer and disable idle timeout. manager3 = self.create_peer(self.network, peer_id=self.peer_id2) conn = FakeConnection(manager3, self.manager1) # Disable idle timeout. conn.disable_idle_timeout() # HELLO self.assertEqual(self.conn.peek_tr1_value().split()[0], b'HELLO') self.assertEqual(self.conn.peek_tr2_value().split()[0], b'HELLO') self.assertEqual(conn.peek_tr1_value().split()[0], b'HELLO') self.assertEqual(conn.peek_tr2_value().split()[0], b'HELLO') self.conn.run_one_step() conn.run_one_step() # PEER-ID self.assertEqual(self.conn.peek_tr1_value().split()[0], b'PEER-ID') self.assertEqual(self.conn.peek_tr2_value().split()[0], b'PEER-ID') self.assertEqual(conn.peek_tr1_value().split()[0], b'PEER-ID') self.assertEqual(conn.peek_tr2_value().split()[0], b'PEER-ID') self.conn.run_one_step() conn.run_one_step() # READY self.assertEqual(self.conn.peek_tr1_value().split()[0], b'READY') self.assertEqual(self.conn.peek_tr2_value().split()[0], b'READY') self.assertEqual(conn.peek_tr1_value().split()[0], b'READY') self.assertEqual(conn.peek_tr2_value().split()[0], b'READY') self.conn.run_one_step() conn.run_one_step() # continue until messages stop self.conn.run_until_complete() conn.run_until_complete() self.run_to_completion() # one of the peers will close the connection. We don't know which on, as it depends # on the peer ids conn1_value = self.conn.peek_tr1_value() + self.conn.peek_tr2_value() conn2_value = conn.peek_tr1_value() + conn.peek_tr2_value() if b'ERROR' in conn1_value: conn_dead = self.conn conn_alive = conn elif b'ERROR' in conn2_value: conn_dead = conn conn_alive = self.conn else: raise Exception('It should never happen.') self._check_result_only_cmd( conn_dead.peek_tr1_value() + conn_dead.peek_tr2_value(), b'ERROR') # at this point, the connection must be closing as the error was detected on READY state self.assertIn( True, [conn_dead.tr1.disconnecting, conn_dead.tr2.disconnecting]) # check connected_peers connected_peers = list( self.manager1.connections.connected_peers.values()) self.assertEquals(1, len(connected_peers)) self.assertIn(connected_peers[0], [conn_alive.proto1, conn_alive.proto2]) # connection is still up self.assertIsConnected(conn_alive) def test_invalid_different_network(self): manager3 = self.create_peer(network='mainnet') conn = FakeConnection(self.manager1, manager3) conn.run_one_step() # HELLO self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR') self.assertTrue(conn.tr1.disconnecting) conn.run_one_step() # ERROR def test_valid_hello_and_peer_id(self): self._check_result_only_cmd(self.conn.peek_tr1_value(), b'HELLO') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'HELLO') self.conn.run_one_step() # HELLO self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEER-ID') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'PEER-ID') self.conn.run_one_step() # PEER-ID self._check_result_only_cmd(self.conn.peek_tr1_value(), b'READY') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'READY') self.conn.run_one_step() # READY self._check_result_only_cmd(self.conn.peek_tr1_value(), b'GET-PEERS') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'GET-PEERS') self.conn.run_one_step() # GET-PEERS self._check_result_only_cmd(self.conn.peek_tr1_value(), b'GET-TIPS') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'GET-TIPS') self.conn.run_one_step() # GET-TIPS self.assertIsConnected() self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEERS') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'PEERS') self.conn.run_one_step() # PEERS self._check_result_only_cmd(self.conn.peek_tr1_value(), b'TIPS') self._check_result_only_cmd(self.conn.peek_tr2_value(), b'TIPS') self.conn.run_one_step() # TIPS self.assertIsConnected() def test_send_ping(self): self.conn.run_one_step() # HELLO self.conn.run_one_step() # PEER-ID self.conn.run_one_step() # READY self.conn.run_one_step() # GET-PEERS self.conn.run_one_step() # GET-TIPS self.conn.run_one_step() # PEERS self.conn.run_one_step() # TIPS self.assertIsConnected() self.clock.advance(5) self.assertEqual(b'PING\r\n', self.conn.peek_tr1_value()) self.assertEqual(b'PING\r\n', self.conn.peek_tr2_value()) self.conn.run_one_step() # PING self.conn.run_one_step() # GET-TIPS self.assertEqual(b'PONG\r\n', self.conn.peek_tr1_value()) self.assertEqual(b'PONG\r\n', self.conn.peek_tr2_value()) while b'PONG\r\n' in self.conn.peek_tr1_value(): self.conn.run_one_step() self.assertEqual(self.clock.seconds(), self.conn.proto1.last_message) def test_send_invalid_unicode(self): # \xff is an invalid unicode. self.conn.proto1.dataReceived(b'\xff\r\n') self.assertTrue(self.conn.tr1.disconnecting) def test_on_disconnect(self): self.assertIn(self.conn.proto1, self.manager1.connections.handshaking_peers) self.conn.disconnect(Failure(Exception('testing'))) self.assertNotIn(self.conn.proto1, self.manager1.connections.handshaking_peers) def test_on_disconnect_after_hello(self): self.conn.run_one_step() # HELLO self.assertIn(self.conn.proto1, self.manager1.connections.handshaking_peers) self.conn.disconnect(Failure(Exception('testing'))) self.assertNotIn(self.conn.proto1, self.manager1.connections.handshaking_peers) def test_on_disconnect_after_peer_id(self): self.conn.run_one_step() # HELLO self.assertIn(self.conn.proto1, self.manager1.connections.handshaking_peers) # No peer id in the peer_storage (known_peers) self.assertNotIn(self.peer_id2.id, self.manager1.connections.peer_storage) # The peer READY now depends on a message exchange from both peers, so we need one more step self.conn.run_one_step() # PEER-ID self.conn.run_one_step() # READY self.assertIn(self.conn.proto1, self.manager1.connections.connected_peers.values()) # Peer id 2 in the peer_storage (known_peers) after connection self.assertIn(self.peer_id2.id, self.manager1.connections.peer_storage) self.assertNotIn(self.conn.proto1, self.manager1.connections.handshaking_peers) self.conn.disconnect(Failure(Exception('testing'))) # Peer id 2 in the peer_storage (known_peers) after disconnection but before looping call self.assertIn(self.peer_id2.id, self.manager1.connections.peer_storage) self.assertNotIn(self.conn.proto1, self.manager1.connections.connected_peers.values()) self.clock.advance(10) # Peer id 2 removed from peer_storage (known_peers) after disconnection and after looping call self.assertNotIn(self.peer_id2.id, self.manager1.connections.peer_storage) def test_two_connections(self): self.conn.run_one_step() # HELLO self.conn.run_one_step() # PEER-ID self.conn.run_one_step() # READY self.conn.run_one_step() # GET-PEERS self.conn.run_one_step() # GET-TIPS manager3 = self.create_peer(self.network) conn = FakeConnection(self.manager1, manager3) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID conn.run_one_step() # READY conn.run_one_step() # GET-PEERS self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEERS') self.conn.run_one_step() def test_idle_connection(self): self.clock.advance(settings.PEER_IDLE_TIMEOUT - 10) self.assertIsConnected(self.conn) self.clock.advance(15) self.assertIsNotConnected(self.conn) @inlineCallbacks def test_get_data(self): self.conn.run_one_step() # HELLO self.conn.run_one_step() # PEER-ID self.conn.run_one_step() # READY self.conn.run_one_step() # GET-PEERS self.conn.run_one_step() # GET-TIPS self.conn.run_one_step() # PEERS self.conn.run_one_step() # TIPS self.assertIsConnected() missing_tx = '00000000228dfcd5dec1c9c6263f6430a5b4316bb9e3decb9441a6414bfd8697' yield self._send_cmd(self.conn.proto1, 'GET-DATA', missing_tx) self._check_result_only_cmd(self.conn.peek_tr1_value(), b'NOT-FOUND') self.conn.run_one_step()
def test_invalid_same_peer_id2(self): """ We connect nodes 1-2 and 1-3. Nodes 2 and 3 have the same peer_id. The connections are established simultaneously, so we do not detect a peer id duplication in PEER_ID state, only on READY state. """ # Disable idle timeout before creating any new peer because self.create_peer(...) # runs the main loop. self.conn.disable_idle_timeout() # Create new peer and disable idle timeout. manager3 = self.create_peer(self.network, peer_id=self.peer_id2) conn = FakeConnection(manager3, self.manager1) # Disable idle timeout. conn.disable_idle_timeout() # HELLO self.assertEqual(self.conn.peek_tr1_value().split()[0], b'HELLO') self.assertEqual(self.conn.peek_tr2_value().split()[0], b'HELLO') self.assertEqual(conn.peek_tr1_value().split()[0], b'HELLO') self.assertEqual(conn.peek_tr2_value().split()[0], b'HELLO') self.conn.run_one_step() conn.run_one_step() # PEER-ID self.assertEqual(self.conn.peek_tr1_value().split()[0], b'PEER-ID') self.assertEqual(self.conn.peek_tr2_value().split()[0], b'PEER-ID') self.assertEqual(conn.peek_tr1_value().split()[0], b'PEER-ID') self.assertEqual(conn.peek_tr2_value().split()[0], b'PEER-ID') self.conn.run_one_step() conn.run_one_step() # READY self.assertEqual(self.conn.peek_tr1_value().split()[0], b'READY') self.assertEqual(self.conn.peek_tr2_value().split()[0], b'READY') self.assertEqual(conn.peek_tr1_value().split()[0], b'READY') self.assertEqual(conn.peek_tr2_value().split()[0], b'READY') self.conn.run_one_step() conn.run_one_step() # continue until messages stop self.conn.run_until_complete() conn.run_until_complete() self.run_to_completion() # one of the peers will close the connection. We don't know which on, as it depends # on the peer ids conn1_value = self.conn.peek_tr1_value() + self.conn.peek_tr2_value() conn2_value = conn.peek_tr1_value() + conn.peek_tr2_value() if b'ERROR' in conn1_value: conn_dead = self.conn conn_alive = conn elif b'ERROR' in conn2_value: conn_dead = conn conn_alive = self.conn else: raise Exception('It should never happen.') self._check_result_only_cmd( conn_dead.peek_tr1_value() + conn_dead.peek_tr2_value(), b'ERROR') # at this point, the connection must be closing as the error was detected on READY state self.assertIn( True, [conn_dead.tr1.disconnecting, conn_dead.tr2.disconnecting]) # check connected_peers connected_peers = list( self.manager1.connections.connected_peers.values()) self.assertEquals(1, len(connected_peers)) self.assertIn(connected_peers[0], [conn_alive.proto1, conn_alive.proto2]) # connection is still up self.assertIsConnected(conn_alive)
def test_soft_voided(self): txA_hash = bytes.fromhex('4586c5428e8d666ea59684c1cd9286d2b9d9e89b4939207db47412eeaabc48b2') txB_hash = bytes.fromhex('d19bee149abdce63bbfd523c90c3cf110563970a34100b2a62583a1eba48dfc8') soft_voided_tx_ids = set([ txA_hash, txB_hash, ]) manager1 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager1.allow_mining_without_peers() miner1 = self.simulator.create_miner(manager1, hashpower=5e6) miner1.start() self.simulator.run(60) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(300) manager2 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager2.soft_voided_tx_ids = soft_voided_tx_ids self.graphviz = GraphvizVisualizer(manager2.tx_storage, include_verifications=True, include_funds=True) conn12 = FakeConnection(manager1, manager2, latency=0.001) self.simulator.add_connection(conn12) miner2 = self.simulator.create_miner(manager2, hashpower=10e6) miner2.start() gen_tx2 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx2.start() self.simulator.run(900) miner2.stop() gen_tx2.stop() txB = manager2.tx_storage.get_transaction(txB_hash) # Get the tx confirmed by the soft voided that will be voided tx_base = manager2.tx_storage.get_transaction(txB.parents[0]) txC = gen_new_double_spending(manager2, use_same_parents=False, tx=tx_base) txC.weight = 25 txC.parents = tx_base.parents txC.update_hash() self.graphviz.labels[txC.hash] = 'txC' self.assertTrue(manager2.propagate_tx(txC, fails_silently=False)) metaC = txC.get_metadata() self.assertIsNone(metaC.voided_by) meta_base = tx_base.get_metadata() self.assertEqual(meta_base.voided_by, {tx_base.hash}) # Create 2 blocks confirming C in order to keep this voidance when we add # the block confirming the soft voided tx blk1 = manager2.generate_mining_block() if txC.hash not in blk1.parents: blk1.parents[1] = txC.hash blk1.nonce = self.rng.getrandbits(32) blk1.update_hash() self.assertTrue(manager2.propagate_tx(blk1, fails_silently=False)) blk1meta = blk1.get_metadata() self.assertIsNone(blk1meta.voided_by) blk2 = manager2.generate_mining_block() if txC.hash not in blk2.parents: blk2.parents[1] = txC.hash blk2.nonce = self.rng.getrandbits(32) blk2.update_hash() self.assertTrue(manager2.propagate_tx(blk2, fails_silently=False)) blk2meta = blk2.get_metadata() self.assertIsNone(blk2meta.voided_by) # Create block that confirms soft voided blk3 = manager2.generate_mining_block() if txB.hash not in blk3.parents: blk3.parents[1] = txB.hash blk3.nonce = self.rng.getrandbits(32) blk3.update_hash() self.assertTrue(manager2.propagate_tx(blk3, fails_silently=False)) blk3meta = blk3.get_metadata() self.simulator.run(10) txD = add_custom_tx(manager2, [(txC, 0)], base_parent=txB) # dot = self.graphviz.dot() # dot.render('dot0') blk3meta = blk3.get_metadata() self.assertEqual(blk3meta.voided_by, {tx_base.hash, blk3meta.hash}) metaD = txD.get_metadata() self.assertEqual(metaD.voided_by, {tx_base.hash})
def test_split_brain(self): debug_pdf = False manager1 = self.create_peer(self.network, unlock_wallet=True) manager1.avg_time_between_blocks = 3 manager2 = self.create_peer(self.network, unlock_wallet=True) manager2.avg_time_between_blocks = 3 for _ in range(10): add_new_block(manager1, advance_clock=1) add_blocks_unlock_reward(manager1) add_new_block(manager2, advance_clock=1) add_blocks_unlock_reward(manager2) self.clock.advance(10) for _ in range(random.randint(3, 10)): add_new_transactions(manager1, random.randint(2, 4), advance_clock=1) add_new_transactions(manager2, random.randint(3, 7), advance_clock=1) add_new_double_spending(manager1) add_new_double_spending(manager2) self.clock.advance(10) self.clock.advance(20) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True).dot() dot1.render('dot1-pre') self.assertTipsNotEqual(manager1, manager2) self.assertConsensusValid(manager1) self.assertConsensusValid(manager2) # input('Press enter to continue...') conn = FakeConnection(manager1, manager2) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID conn.run_one_step() # READY conn.run_one_step() # GET-PEERS conn.run_one_step() # GET-TIPS conn.run_one_step() # PEERS conn.run_one_step() # TIPS empty_counter = 0 for i in range(2000): if conn.is_empty(): empty_counter += 1 if empty_counter > 10: break else: empty_counter = 0 conn.run_one_step() self.clock.advance(0.2) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True).dot() dot1.render('dot1-post') dot2 = GraphvizVisualizer(manager2.tx_storage, include_verifications=True).dot() dot2.render('dot2-post') node_sync = conn.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(manager1, manager2) self.assertConsensusEqual(manager1, manager2) self.assertConsensusValid(manager1) self.assertConsensusValid(manager2)
class BaseStatusTest(_BaseResourceTest._ResourceTest): __test__ = False def setUp(self): super().setUp() self.web = StubSite(StatusResource(self.manager)) self.manager2 = self.create_peer('testnet') self.conn1 = FakeConnection(self.manager, self.manager2) @inlineCallbacks def test_get(self): response = yield self.web.get("status") data = response.json_value() server_data = data.get('server') self.assertEqual(server_data['app_version'], 'Hathor v{}'.format(hathor.__version__)) self.assertEqual(server_data['network'], 'testnet') self.assertGreater(server_data['uptime'], 0) @inlineCallbacks def test_handshaking(self): response = yield self.web.get("status") data = response.json_value() server_data = data.get('server') known_peers = data.get('known_peers') connections = data.get('connections') self.assertEqual(server_data['app_version'], 'Hathor v{}'.format(hathor.__version__)) self.assertEqual(server_data['network'], 'testnet') self.assertGreater(server_data['uptime'], 0) handshake_peer = self.conn1.proto1.transport.getPeer() handshake_address = '{}:{}'.format(handshake_peer.host, handshake_peer.port) self.assertEqual(len(known_peers), 0) self.assertEqual(len(connections['connected_peers']), 0) self.assertEqual(len(connections['handshaking_peers']), 1) self.assertEqual(connections['handshaking_peers'][0]['address'], handshake_address) @inlineCallbacks def test_get_with_one_peer(self): self.conn1.run_one_step() # HELLO self.conn1.run_one_step() # PEER-ID self.conn1.run_one_step() # READY self.conn1.run_one_step() # BOTH PEERS ARE READY NOW response = yield self.web.get("status") data = response.json_value() server_data = data.get('server') known_peers = data.get('known_peers') connections = data.get('connections') self.assertEqual(server_data['app_version'], 'Hathor v{}'.format(hathor.__version__)) self.assertEqual(server_data['network'], 'testnet') self.assertGreater(server_data['uptime'], 0) self.assertEqual(len(known_peers), 1) self.assertEqual(known_peers[0]['id'], self.manager2.my_peer.id) self.assertEqual(len(connections['connected_peers']), 1) self.assertEqual(connections['connected_peers'][0]['id'], self.manager2.my_peer.id) @inlineCallbacks def test_connecting_peers(self): address = '192.168.1.1:54321' endpoint = endpoints.clientFromString(self.manager.reactor, 'tcp:{}'.format(address)) deferred = endpoint.connect self.manager.connections.connecting_peers[endpoint] = deferred response = yield self.web.get("status") data = response.json_value() connecting = data['connections']['connecting_peers'] self.assertEqual(len(connecting), 1) self.assertEqual(connecting[0]['address'], address) self.assertIsNotNone(connecting[0]['deferred'])