def test_sendrawtransaction(genesis_block_hash): # sendrawtransaction should only allow txs that the simulator has not mined yet bitcoin_cli().sendrawtransaction( transaction.create_dummy_transaction().hex()) # Any tx with invalid format or that matches with an already mined transaction should fail try: # Trying to resend the coinbase tx of the genesis block genesis_tx = bitcoin_cli().getblock(genesis_block_hash).get("tx")[0] bitcoin_cli().sendrawtransaction(genesis_tx) assert False except JSONRPCException as e: assert True for v in MIXED_VALUES: # Sending random values try: bitcoin_cli().sendrawtransaction(v) assert False except JSONRPCException as e: assert True # Trying with a valid tx try: tx = transaction.create_dummy_transaction() bitcoin_cli().sendrawtransaction(tx.hex()) assert True except JSONRPCException as e: assert False
def test_decoderawtransaction(genesis_block_hash): # decoderawtransaction should only return if the given transaction is properly formatted (can be deserialized using # (TX.deserialize(raw_tx). block = bitcoin_cli().getblock(genesis_block_hash) coinbase_txid = block.get("tx")[0] coinbase_tx = bitcoin_cli().getrawtransaction(coinbase_txid).get("hex") tx = bitcoin_cli().decoderawtransaction(coinbase_tx) assert isinstance(tx, dict) assert isinstance(tx.get("txid"), str) assert check_hash_format(tx.get("txid")) # Therefore it should also work for a random transaction hex in our simulation random_tx = transaction.create_dummy_transaction() tx = bitcoin_cli().decoderawtransaction(random_tx.hex()) assert isinstance(tx, dict) assert isinstance(tx.get("txid"), str) assert check_hash_format(tx.get("txid")) # But it should fail for not proper formatted one for v in MIXED_VALUES: try: bitcoin_cli().decoderawtransaction(v) assert False except JSONRPCException as e: assert True
def test_add_tracker_already_confirmed(responder): for i in range(20): uuid = uuid4().hex confirmations = i + 1 locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data( penalty_rawtx=create_dummy_transaction().hex()) responder.add_tracker(uuid, locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end, confirmations) assert penalty_txid not in responder.unconfirmed_txs
def get_new_block_data(): """ Creates the data to be used for a mined block. Returns: :obj:`tuple`: A three item tuple (block_hash, coinbase_tx, coinbase_tx_hash) """ block_hash = utils.get_random_value_hex(32) coinbase_tx = transaction.create_dummy_transaction() return block_hash, coinbase_tx.hex(), coinbase_tx.tx_id.hex()
def generate_dummy_appointment_data(real_height=True, start_time_offset=5, end_time_offset=30): if real_height: current_height = bitcoin_cli(bitcoind_connect_params).getblockcount() else: current_height = 10 dispute_tx = create_dummy_transaction() dispute_txid = dispute_tx.tx_id.hex() penalty_tx = create_dummy_transaction(dispute_txid) dummy_appointment_data = { "tx": penalty_tx.hex(), "tx_id": dispute_txid, "start_time": current_height + start_time_offset, "end_time": current_height + end_time_offset, "to_self_delay": 20, } # dummy keys for this test client_sk, client_pk = generate_keypair() client_pk_hex = client_pk.format().hex() locator = compute_locator(dispute_txid) blob = Blob(dummy_appointment_data.get("tx")) encrypted_blob = Cryptographer.encrypt(blob, dummy_appointment_data.get("tx_id")) appointment_data = { "locator": locator, "start_time": dummy_appointment_data.get("start_time"), "end_time": dummy_appointment_data.get("end_time"), "to_self_delay": dummy_appointment_data.get("to_self_delay"), "encrypted_blob": encrypted_blob, } signature = Cryptographer.sign(Appointment.from_dict(appointment_data).serialize(), client_sk) data = {"appointment": appointment_data, "signature": signature, "public_key": client_pk_hex} return data, dispute_tx.hex()
def generate_dummy_appointment(): dispute_tx = create_dummy_transaction() dispute_txid = dispute_tx.tx_id.hex() penalty_tx = create_dummy_transaction(dispute_txid) locator = compute_locator(dispute_txid) dummy_appointment_data = { "tx": penalty_tx.hex(), "tx_id": dispute_txid, "to_self_delay": 20 } encrypted_blob = Cryptographer.encrypt(dummy_appointment_data.get("tx"), dummy_appointment_data.get("tx_id")) appointment_data = { "locator": locator, "to_self_delay": dummy_appointment_data.get("to_self_delay"), "encrypted_blob": encrypted_blob, "user_id": get_random_value_hex(16), } return ExtendedAppointment.from_dict(appointment_data), dispute_tx.hex()
def test_add_tracker_already_confirmed(responder): for i in range(20): uuid = uuid4().hex confirmations = i + 1 locator, dispute_txid, penalty_txid, penalty_rawtx, user_id = create_dummy_tracker_data( penalty_rawtx=create_dummy_transaction().hex()) responder.add_tracker(uuid, locator, dispute_txid, penalty_txid, penalty_rawtx, user_id, confirmations) assert penalty_txid not in responder.unconfirmed_txs assert (responder.trackers[uuid].get("penalty_txid") == penalty_txid and responder.trackers[uuid].get("locator") == locator and responder.trackers[uuid].get("user_id") == user_id)
def test_rebroadcast(db_manager, carrier, block_processor): responder = Responder(db_manager, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() txs_to_rebroadcast = [] # Rebroadcast calls add_response with retry=True. The tracker data is already in trackers. for i in range(20): uuid = uuid4().hex locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data( penalty_rawtx=create_dummy_transaction().hex()) tracker = TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end) responder.trackers[uuid] = { "locator": locator, "penalty_txid": penalty_txid, "appointment_end": appointment_end, } # We need to add it to the db too responder.db_manager.create_triggered_appointment_flag(uuid) responder.db_manager.store_responder_tracker(uuid, tracker.to_json()) responder.tx_tracker_map[penalty_txid] = [uuid] responder.unconfirmed_txs.append(penalty_txid) # Let's add some of the txs in the rebroadcast list if (i % 2) == 0: txs_to_rebroadcast.append(penalty_txid) # The block_hash passed to rebroadcast does not matter much now. It will in the future to deal with errors receipts = responder.rebroadcast(txs_to_rebroadcast) # All txs should have been delivered and the missed confirmation reset for txid, receipt in receipts: # Sanity check assert txid in txs_to_rebroadcast assert receipt.delivered is True assert responder.missed_confirmations[txid] == 0
def test_send_double_spending_transaction(carrier): # We can test what happens if the same transaction is sent twice tx = create_dummy_transaction() txid = tx.tx_id.hex() receipt = carrier.send_transaction(tx.hex(), txid) sent_txs.append(txid) # Wait for a block to be mined. Issued receipts is reset from the Responder every block, so we should do it too. generate_blocks(2) carrier.issued_receipts = {} # Try to send it again receipt2 = carrier.send_transaction(tx.hex(), txid) # The carrier should report delivered True for both, but in the second case the transaction was already delivered # (either by himself or someone else) assert receipt.delivered is True assert receipt2.delivered is True and receipt2.confirmations >= 1 and receipt2.reason == RPC_VERIFY_ALREADY_IN_CHAIN
def test_send_transaction_invalid_format(carrier): # Test sending a transaction that does not fits the format txid = create_dummy_transaction().tx_id.hex() receipt = carrier.send_transaction(txid, txid) assert receipt.delivered is False and receipt.reason == RPC_DESERIALIZATION_ERROR
def test_send_transaction(run_bitcoind, carrier): tx = create_dummy_transaction() receipt = carrier.send_transaction(tx.hex(), tx.tx_id.hex()) assert receipt.delivered is True
def test_get_completed_trackers(db_manager, carrier, block_processor): initial_height = bitcoin_cli(bitcoind_connect_params).getblockcount() responder = Responder(db_manager, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() # A complete tracker is a tracker that has reached the appointment end with enough confs (> MIN_CONFIRMATIONS) # We'll create three type of transactions: end reached + enough conf, end reached + no enough conf, end not reached trackers_end_conf = { uuid4().hex: create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(10) } trackers_end_no_conf = {} for _ in range(10): tracker = create_dummy_tracker( penalty_rawtx=create_dummy_transaction().hex()) responder.unconfirmed_txs.append(tracker.penalty_txid) trackers_end_no_conf[uuid4().hex] = tracker trackers_no_end = {} for _ in range(10): tracker = create_dummy_tracker( penalty_rawtx=create_dummy_transaction().hex()) tracker.appointment_end += 10 trackers_no_end[uuid4().hex] = tracker all_trackers = {} all_trackers.update(trackers_end_conf) all_trackers.update(trackers_end_no_conf) all_trackers.update(trackers_no_end) # Let's add all to the responder for uuid, tracker in all_trackers.items(): responder.trackers[uuid] = { "locator": tracker.locator, "penalty_txid": tracker.penalty_txid, "appointment_end": tracker.appointment_end, } for uuid, tracker in all_trackers.items(): bitcoin_cli(bitcoind_connect_params).sendrawtransaction( tracker.penalty_rawtx) # The dummy appointments have a end_appointment time of current + 2, but trackers need at least 6 confs by default generate_blocks(6) # And now let's check completed_trackers = responder.get_completed_trackers(initial_height + 6) completed_trackers_ids = [ tracker_id for tracker_id, confirmations in completed_trackers.items() ] ended_trackers_keys = list(trackers_end_conf.keys()) assert set(completed_trackers_ids) == set(ended_trackers_keys) # Generating 6 additional blocks should also confirm trackers_no_end generate_blocks(6) completed_trackers = responder.get_completed_trackers(initial_height + 12) completed_trackers_ids = [ tracker_id for tracker_id, confirmations in completed_trackers.items() ] ended_trackers_keys.extend(list(trackers_no_end.keys())) assert set(completed_trackers_ids) == set(ended_trackers_keys)
def test_do_watch(temp_db_manager, carrier, block_processor): # Create a fresh responder to simplify the test responder = Responder(temp_db_manager, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() trackers = [ create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(20) ] # Let's set up the trackers first for tracker in trackers: uuid = uuid4().hex responder.trackers[uuid] = { "locator": tracker.locator, "penalty_txid": tracker.penalty_txid, "appointment_end": tracker.appointment_end, } responder.tx_tracker_map[tracker.penalty_txid] = [uuid] responder.missed_confirmations[tracker.penalty_txid] = 0 responder.unconfirmed_txs.append(tracker.penalty_txid) # We also need to store the info in the db responder.db_manager.create_triggered_appointment_flag(uuid) responder.db_manager.store_responder_tracker(uuid, tracker.to_json()) # Let's start to watch Thread(target=responder.do_watch, daemon=True).start() # And broadcast some of the transactions broadcast_txs = [] for tracker in trackers[:5]: bitcoin_cli(bitcoind_connect_params).sendrawtransaction( tracker.penalty_rawtx) broadcast_txs.append(tracker.penalty_txid) # Mine a block generate_block() # The transactions we sent shouldn't be in the unconfirmed transaction list anymore assert not set(broadcast_txs).issubset(responder.unconfirmed_txs) # TODO: test that reorgs can be detected once data persistence is merged (new version of the simulator) # Generating 5 additional blocks should complete the 5 trackers generate_blocks(5) assert not set(broadcast_txs).issubset(responder.tx_tracker_map) # Do the rest broadcast_txs = [] for tracker in trackers[5:]: bitcoin_cli(bitcoind_connect_params).sendrawtransaction( tracker.penalty_rawtx) broadcast_txs.append(tracker.penalty_txid) # Mine a block generate_blocks(6) assert len(responder.tx_tracker_map) == 0
def test_get_expired_trackers(responder): # expired trackers are those who's subscription has reached the expiry block and have not been confirmed. # confirmed trackers that have reached their expiry will be kept until completed current_block = responder.block_processor.get_block_count() # Lets first register the a couple of users user1_id = get_random_value_hex(16) responder.gatekeeper.registered_users[user1_id] = UserInfo( available_slots=10, subscription_expiry=current_block + 15) user2_id = get_random_value_hex(16) responder.gatekeeper.registered_users[user2_id] = UserInfo( available_slots=10, subscription_expiry=current_block + 16) # And create some trackers and add them to the corresponding user in the Gatekeeper expired_unconfirmed_trackers_15 = {} expired_unconfirmed_trackers_16 = {} expired_confirmed_trackers_15 = {} for _ in range(10): uuid = uuid4().hex dummy_tracker = create_dummy_tracker( penalty_rawtx=create_dummy_transaction().hex()) dummy_tracker.user_id = user1_id expired_unconfirmed_trackers_15[uuid] = dummy_tracker responder.unconfirmed_txs.append(dummy_tracker.penalty_txid) # Assume the appointment only took a single slot responder.gatekeeper.registered_users[ dummy_tracker.user_id].appointments[uuid] = 1 for _ in range(10): uuid = uuid4().hex dummy_tracker = create_dummy_tracker( penalty_rawtx=create_dummy_transaction().hex()) dummy_tracker.user_id = user1_id expired_confirmed_trackers_15[uuid] = dummy_tracker # Assume the appointment only took a single slot responder.gatekeeper.registered_users[ dummy_tracker.user_id].appointments[uuid] = 1 for _ in range(10): uuid = uuid4().hex dummy_tracker = create_dummy_tracker( penalty_rawtx=create_dummy_transaction().hex()) dummy_tracker.user_id = user2_id expired_unconfirmed_trackers_16[uuid] = dummy_tracker responder.unconfirmed_txs.append(dummy_tracker.penalty_txid) # Assume the appointment only took a single slot responder.gatekeeper.registered_users[ dummy_tracker.user_id].appointments[uuid] = 1 all_trackers = {} all_trackers.update(expired_confirmed_trackers_15) all_trackers.update(expired_unconfirmed_trackers_15) all_trackers.update(expired_unconfirmed_trackers_16) # Add everything to the Responder for uuid, tracker in all_trackers.items(): responder.trackers[uuid] = tracker.get_summary() # Currently nothing should be expired assert responder.get_expired_trackers(current_block) == [] # 15 blocks (+ EXPIRY_DELTA) afterwards only user1's confirmed trackers should be expired assert responder.get_expired_trackers( current_block + 15 + config.get("EXPIRY_DELTA")) == list( expired_unconfirmed_trackers_15.keys()) # 1 (+ EXPIRY_DELTA) block after that user2's should be expired assert responder.get_expired_trackers( current_block + 16 + config.get("EXPIRY_DELTA")) == list( expired_unconfirmed_trackers_16.keys())
def test_get_completed_trackers(db_manager, gatekeeper, carrier, block_processor): responder = Responder(db_manager, gatekeeper, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() # A complete tracker is a tracker which penalty transaction has been irrevocably resolved (i.e. has reached 100 # confirmations) # We'll create 3 type of txs: irrevocably resolved, confirmed but not irrevocably resolved, and unconfirmed trackers_ir_resolved = { uuid4().hex: create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(10) } trackers_confirmed = { uuid4().hex: create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(10) } trackers_unconfirmed = {} for _ in range(10): tracker = create_dummy_tracker( penalty_rawtx=create_dummy_transaction().hex()) responder.unconfirmed_txs.append(tracker.penalty_txid) trackers_unconfirmed[uuid4().hex] = tracker all_trackers = {} all_trackers.update(trackers_ir_resolved) all_trackers.update(trackers_confirmed) all_trackers.update(trackers_unconfirmed) # Let's add all to the Responder for uuid, tracker in all_trackers.items(): responder.trackers[uuid] = tracker.get_summary() for uuid, tracker in trackers_ir_resolved.items(): bitcoin_cli(bitcoind_connect_params).sendrawtransaction( tracker.penalty_rawtx) generate_block_w_delay() for uuid, tracker in trackers_confirmed.items(): bitcoin_cli(bitcoind_connect_params).sendrawtransaction( tracker.penalty_rawtx) # ir_resolved have 100 confirmations and confirmed have 99 generate_blocks_w_delay(99) # Let's check completed_trackers = responder.get_completed_trackers() ended_trackers_keys = list(trackers_ir_resolved.keys()) assert set(completed_trackers) == set(ended_trackers_keys) # Generating 1 additional blocks should also include confirmed generate_block_w_delay() completed_trackers = responder.get_completed_trackers() ended_trackers_keys.extend(list(trackers_confirmed.keys())) assert set(completed_trackers) == set(ended_trackers_keys)
def test_do_watch(temp_db_manager, gatekeeper, carrier, block_processor): # Create a fresh responder to simplify the test responder = Responder(temp_db_manager, gatekeeper, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() trackers = [ create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(20) ] subscription_expiry = responder.block_processor.get_block_count() + 110 # Let's set up the trackers first for tracker in trackers: uuid = uuid4().hex # Simulate user registration so trackers can properly expire responder.gatekeeper.registered_users[tracker.user_id] = UserInfo( available_slots=10, subscription_expiry=subscription_expiry) # Add data to the Responder responder.trackers[uuid] = tracker.get_summary() responder.tx_tracker_map[tracker.penalty_txid] = [uuid] responder.missed_confirmations[tracker.penalty_txid] = 0 responder.unconfirmed_txs.append(tracker.penalty_txid) # Assuming the appointment only took a single slot responder.gatekeeper.registered_users[ tracker.user_id].appointments[uuid] = 1 # We also need to store the info in the db responder.db_manager.create_triggered_appointment_flag(uuid) responder.db_manager.store_responder_tracker(uuid, tracker.to_dict()) # Let's start to watch Thread(target=responder.do_watch, daemon=True).start() # And broadcast some of the transactions broadcast_txs = [] for tracker in trackers[:5]: bitcoin_cli(bitcoind_connect_params).sendrawtransaction( tracker.penalty_rawtx) broadcast_txs.append(tracker.penalty_txid) # Mine a block generate_block_w_delay() # The transactions we sent shouldn't be in the unconfirmed transaction list anymore assert not set(broadcast_txs).issubset(responder.unconfirmed_txs) # CONFIRMATIONS_BEFORE_RETRY+1 blocks after, the responder should rebroadcast the unconfirmed txs (15 remaining) generate_blocks_w_delay(CONFIRMATIONS_BEFORE_RETRY + 1) assert len(responder.unconfirmed_txs) == 0 assert len(responder.trackers) == 20 # Generating 100 - CONFIRMATIONS_BEFORE_RETRY -2 additional blocks should complete the first 5 trackers generate_blocks_w_delay(100 - CONFIRMATIONS_BEFORE_RETRY - 2) assert len(responder.unconfirmed_txs) == 0 assert len(responder.trackers) == 15 # Check they are not in the Gatekeeper either for tracker in trackers[:5]: assert len(responder.gatekeeper.registered_users[ tracker.user_id].appointments) == 0 # CONFIRMATIONS_BEFORE_RETRY additional blocks should complete the rest generate_blocks_w_delay(CONFIRMATIONS_BEFORE_RETRY) assert len(responder.unconfirmed_txs) == 0 assert len(responder.trackers) == 0 # Check they are not in the Gatekeeper either for tracker in trackers[5:]: assert len(responder.gatekeeper.registered_users[ tracker.user_id].appointments) == 0