def test_send_transaction(carrier): tx = create_commitment_tx() txid = bitcoin_cli.decoderawtransaction(tx).get("txid") receipt = carrier.send_transaction(tx, txid) assert receipt.delivered is True
def test_add_appointment_trigger_on_cache_cannot_decrypt(teosd): _, teos_id = teosd commitment_tx, _, penalty_tx = create_txs() # Let's send the commitment to the network and mine a block generate_block_with_transactions(commitment_tx) sleep(1) # The appointment data is built using a random 32-byte value. appointment_data = build_appointment_data(get_random_value_hex(32), penalty_tx) # We cannot use teos_client.add_appointment here since it computes the locator internally, so let's do it manually. appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")) appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32)) appointment = Appointment.from_dict(appointment_data) signature = Cryptographer.sign(appointment.serialize(), user_sk) data = {"appointment": appointment.to_dict(), "signature": signature} # Send appointment to the server. response = teos_client.post_request(data, teos_add_appointment_endpoint) response_json = teos_client.process_post_response(response) # Check that the server has accepted the appointment tower_signature = response_json.get("signature") appointment_receipt = receipts.create_appointment_receipt(signature, response_json.get("start_block")) rpk = Cryptographer.recover_pk(appointment_receipt, tower_signature) assert teos_id == Cryptographer.get_compressed_pk(rpk) assert response_json.get("locator") == appointment.locator # The appointment should should have been immediately dropped with pytest.raises(TowerResponseError): get_appointment_info(teos_id, appointment_data["locator"])
def test_send_transaction_bitcoind_crash(carrier): # Trying to send a transaction if bitcoind is unreachable should block the thread until it becomes reachable again tx = create_commitment_tx() txid = bitcoin_cli.decoderawtransaction(tx).get("txid") run_test_blocking_command_bitcoind_crash( carrier.bitcoind_reachable, lambda: carrier.send_transaction(tx, txid), )
def _generate_dummy_tracker(commitment_tx=None): if not commitment_tx: commitment_tx = create_commitment_tx() decoded_commitment_tx = bitcoin_cli.decoderawtransaction(commitment_tx) penalty_tx = create_penalty_tx(decoded_commitment_tx) locator = decoded_commitment_tx.get("txid")[:LOCATOR_LEN_HEX] tracker_data = dict( locator=locator, dispute_txid=bitcoin_cli.decoderawtransaction(commitment_tx).get( "txid"), penalty_txid=bitcoin_cli.decoderawtransaction(penalty_tx).get( "txid"), penalty_rawtx=penalty_tx, user_id="02" + get_random_value_hex(32), ) return TransactionTracker.from_dict(tracker_data)
def test_send_double_spending_transaction(carrier): # We can test what happens if the same transaction is sent twice tx = create_commitment_tx() txid = bitcoin_cli.decoderawtransaction(tx).get("txid") receipt = carrier.send_transaction(tx, txid) sent_txs.append(txid) # Wait for a block to be mined. Issued receipts are 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, 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_two_appointment_same_locator_different_penalty_different_users(teosd): _, teos_id = teosd # This tests sending an appointment with two valid transaction with the same locator from different users commitment_tx, commitment_txid, penalty_tx1 = create_txs() # We need to create a second penalty spending from the same commitment decoded_commitment_tx = bitcoin_cli.decoderawtransaction(commitment_tx) new_addr = bitcoin_cli.getnewaddress() penalty_tx2 = create_penalty_tx(decoded_commitment_tx, new_addr) appointment1_data = build_appointment_data(commitment_txid, penalty_tx1) appointment2_data = build_appointment_data(commitment_txid, penalty_tx2) locator = compute_locator(commitment_txid) # tmp keys for a different user tmp_user_sk = PrivateKey() tmp_user_id = Cryptographer.get_compressed_pk(tmp_user_sk.public_key) teos_client.register(tmp_user_id, teos_id, teos_base_endpoint) appointment_1 = teos_client.create_appointment(appointment1_data) add_appointment(teos_id, appointment_1) appointment_2 = teos_client.create_appointment(appointment2_data) add_appointment(teos_id, appointment_2, sk=tmp_user_sk) # Broadcast the commitment transaction and mine a block generate_block_with_transactions(commitment_tx) # One of the transactions must have made it to the Responder while the other must have been dropped for # double-spending. That means that one of the responses from the tower should fail appointment_info = None with pytest.raises(TowerResponseError): appointment_info = get_appointment_info(teos_id, locator) appointment2_info = get_appointment_info(teos_id, locator, sk=tmp_user_sk) if appointment_info is None: appointment_info = appointment2_info appointment1_data = appointment2_data assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED assert appointment_info.get("locator") == appointment1_data.get("locator") assert appointment_info.get("appointment").get("penalty_tx") == appointment1_data.get("penalty_tx")
def test_appointment_wrong_decryption_key(teosd): _, teos_id = teosd # This tests an appointment encrypted with a key that has not been derived from the same source as the locator. # Therefore the tower won't be able to decrypt the blob once the appointment is triggered. commitment_tx, _, penalty_tx = create_txs() # The appointment data is built using a random 32-byte value. appointment_data = build_appointment_data(get_random_value_hex(32), penalty_tx) # We cannot use teos_client.add_appointment here since it computes the locator internally, so let's do it manually. # We will encrypt the blob using the random value and derive the locator from the commitment tx. appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")) appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32)) appointment = Appointment.from_dict(appointment_data) signature = Cryptographer.sign(appointment.serialize(), user_sk) data = {"appointment": appointment.to_dict(), "signature": signature} # Send appointment to the server. response = teos_client.post_request(data, teos_add_appointment_endpoint) response_json = teos_client.process_post_response(response) # Check that the server has accepted the appointment tower_signature = response_json.get("signature") appointment_receipt = receipts.create_appointment_receipt(signature, response_json.get("start_block")) rpk = Cryptographer.recover_pk(appointment_receipt, tower_signature) assert teos_id == Cryptographer.get_compressed_pk(rpk) assert response_json.get("locator") == appointment.locator # Trigger the appointment generate_block_with_transactions(commitment_tx) # The appointment should have been removed since the decryption failed. with pytest.raises(TowerResponseError): get_appointment_info(teos_id, appointment.locator)
def test_appointment_life_cycle(teosd): global appointments_in_watcher, appointments_in_responder, available_slots, subscription_expiry _, teos_id = teosd # First of all we need to register available_slots, subscription_expiry = teos_client.register(user_id, teos_id, teos_base_endpoint) # After that we can build an appointment and send it to the tower commitment_tx, commitment_txid, penalty_tx = create_txs() appointment_data = build_appointment_data(commitment_txid, penalty_tx) locator = compute_locator(commitment_txid) appointment = teos_client.create_appointment(appointment_data) add_appointment(teos_id, appointment) appointments_in_watcher += 1 # Get the information from the tower to check that it matches appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED assert appointment_info.get("locator") == locator assert appointment_info.get("appointment") == appointment.to_dict() rpc_client = RPCClient(config.get("RPC_BIND"), config.get("RPC_PORT")) # Check also the get_all_appointments endpoint all_appointments = json.loads(rpc_client.get_all_appointments()) watching = all_appointments.get("watcher_appointments") responding = all_appointments.get("responder_trackers") assert len(watching) == appointments_in_watcher and len(responding) == 0 # Trigger a breach and check again generate_block_with_transactions(commitment_tx) appointment_info = get_appointment_info(teos_id, locator) assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED assert appointment_info.get("locator") == locator appointments_in_watcher -= 1 appointments_in_responder += 1 all_appointments = json.loads(rpc_client.get_all_appointments()) watching = all_appointments.get("watcher_appointments") responding = all_appointments.get("responder_trackers") assert len(watching) == appointments_in_watcher and len(responding) == appointments_in_responder # It can be also checked by ensuring that the penalty transaction made it to the network penalty_tx_id = bitcoin_cli.decoderawtransaction(penalty_tx).get("txid") try: bitcoin_cli.getrawtransaction(penalty_tx_id) assert True except JSONRPCException: # If the transaction is not found. assert False # Now let's mine some blocks so the appointment reaches its end. We need 100 + EXPIRY_DELTA -1 generate_blocks(100 + config.get("EXPIRY_DELTA") - 1) appointments_in_responder -= 1 # The appointment is no longer in the tower with pytest.raises(TowerResponseError): get_appointment_info(teos_id, locator) assert get_subscription_info(teos_id).get("available_slots") == available_slots