Example #1
0
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
Example #2
0
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"])
Example #3
0
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),
    )
Example #4
0
    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)
Example #5
0
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
Example #6
0
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")
Example #7
0
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)
Example #8
0
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