예제 #1
0
def test_status_close_channel():
    """Test ability to get a channel's status and close it."""
    channel_server._db = DatabaseSQLite3(':memory:', db_dir='')
    test_client = _create_client_txs()

    # Test that channel close fails when no channel exists
    with pytest.raises(PaymentChannelNotFoundError):
        channel_server.close('fake', BAD_SIGNATURE)

    # Open the channel and make a payment
    deposit_txid = channel_server.open(test_client.deposit_tx,
                                       test_client.redeem_script)
    payment_txid = channel_server.receive_payment(deposit_txid,
                                                  test_client.payment_tx)
    channel_server.redeem(payment_txid)

    # Test that channel close fails without a valid signature
    with pytest.raises(TransactionVerificationError):
        closed = channel_server.close(deposit_txid, BAD_SIGNATURE)

    # Test that channel close succeeds
    good_signature = codecs.encode(
        cust_wallet._private_key.sign(deposit_txid).to_der(), 'hex_codec')
    closed = channel_server.close(deposit_txid, good_signature)
    assert closed
예제 #2
0
def test_channel_low_balance_message():
    """Test that the channel server returns a useful error when the balance is low."""
    channel_server._db = DatabaseSQLite3(':memory:', db_dir='')
    test_client = _create_client_txs()

    # Open the channel and make a payment
    deposit_txid = channel_server.open(test_client.deposit_tx,
                                       test_client.redeem_script)
    payment_txid = channel_server.receive_payment(deposit_txid,
                                                  test_client.payment_tx)
    channel_server.redeem(payment_txid)

    # Create a payment that almost completely drains the channel
    payment_tx2 = _create_client_payment(test_client, 17)
    payment_txid2 = channel_server.receive_payment(deposit_txid, payment_tx2)
    channel_server.redeem(payment_txid2)

    # Make a payment that spends more than the remaining channel balance
    payment_tx3 = _create_client_payment(test_client, 18)
    with pytest.raises(BadTransactionError) as exc:
        channel_server.receive_payment(deposit_txid, payment_tx3)

    assert 'Payment channel balance' in str(exc)

    # Test that channel close succeeds
    good_signature = codecs.encode(
        cust_wallet._private_key.sign(deposit_txid).to_der(), 'hex_codec')
    closed = channel_server.close(deposit_txid, good_signature)
    assert closed
예제 #3
0
def test_channel_server_open():
    """Test ability to open a payment channel."""
    channel_server._db = DatabaseSQLite3(':memory:', db_dir='')
    test_client = _create_client_txs()

    # Initialize the handshake and ensure that it returns successfully
    channel_server.open(test_client.deposit_tx, test_client.redeem_script)

    # Test for handshake failure when using the same refund twice
    with pytest.raises(PaymentServerError):
        channel_server.open(test_client.deposit_tx, test_client.redeem_script)
예제 #4
0
def test_identify():
    """Test ability to identify a payment server."""
    channel_server._db = DatabaseSQLite3(':memory:', db_dir='')
    pc_config = channel_server.identify()
    merchant_public_key = pc_config['public_key']
    test_public_key = codecs.encode(
        merch_wallet._private_key.public_key.compressed_bytes,
        'hex_codec').decode('utf-8')
    assert merchant_public_key == test_public_key
    assert pc_config['version'] == channel_server.PROTOCOL_VERSION
    assert pc_config['zeroconf'] is False
예제 #5
0
def test_channel_redeem_race_condition():
    """Test ability lock multiprocess redeems."""
    # Clear test database
    multiprocess_db = '/tmp/bitserv_test.sqlite3'
    with open(multiprocess_db, 'w') as f:
        f.write('')

    # Initialize test vectors
    channel_server._db = DatabaseSQLite3(multiprocess_db)
    test_client = _create_client_txs()
    deposit_txid = channel_server.open(test_client.deposit_tx,
                                       test_client.redeem_script)
    payment_txid = channel_server.receive_payment(deposit_txid,
                                                  test_client.payment_tx)

    # Cache channel result for later
    channel = channel_server._db.pc.lookup(deposit_txid)

    # This is a function that takes a long time
    def delayed_pc_lookup(deposit_txid):
        time.sleep(0.5)
        return channel

    # This is the normal function
    def normal_pc_lookup(deposit_txid):
        return channel

    # This function is called between the first lookup and the final record update
    # We make sure this function takes extra long the first time its called
    # in order to expose the race condition
    channel_server._db.pc.lookup = delayed_pc_lookup

    # Start the first redeem in its own process and allow time to begin
    p = multiprocessing.Process(target=channel_server.redeem,
                                args=(payment_txid, ))
    p.start()
    time.sleep(0.1)

    # After starting the first redeem, reset the function to take a normal amount of time
    channel_server._db.pc.lookup = normal_pc_lookup

    # To test the race, this redeem is called while the other redeem is still in-process
    # Because this call makes it to the final database update first, it should be successful
    channel_server.redeem(payment_txid)

    # The multiprocess redeem is intentionally made slow, and will finish after the redeem above
    # Because of this, the multiprocess redeem should throw and exception and exit with an error
    p.join()
    assert p.exitcode == 1
예제 #6
0
def test_receive_payment():
    """Test ability to receive a payment within a channel."""
    channel_server._db = DatabaseSQLite3(':memory:', db_dir='')
    test_client = _create_client_txs()

    # Test that payment receipt fails when no channel exists
    with pytest.raises(PaymentChannelNotFoundError):
        channel_server.receive_payment('fake', test_client.payment_tx)

    # Initiate and complete the payment channel handshake
    deposit_txid = channel_server.open(test_client.deposit_tx,
                                       test_client.redeem_script)

    # Test that payment receipt succeeds
    channel_server.receive_payment(deposit_txid, test_client.payment_tx)

    # Test that payment receipt fails with a duplicate payment
    with pytest.raises(PaymentServerError):
        channel_server.receive_payment(deposit_txid, test_client.payment_tx)
예제 #7
0
def test_redeem_payment():
    """Test ability to redeem a payment made within a channel."""
    channel_server._db = DatabaseSQLite3(':memory:', db_dir='')
    test_client = _create_client_txs()

    # Test that payment redeem fails when no channel exists
    with pytest.raises(PaymentChannelNotFoundError):
        channel_server.redeem('fake')

    # Test that payment redeem succeeds
    deposit_txid = channel_server.open(test_client.deposit_tx,
                                       test_client.redeem_script)
    payment_txid = channel_server.receive_payment(deposit_txid,
                                                  test_client.payment_tx)

    amount = channel_server.redeem(payment_txid)
    assert amount == TEST_PMT_AMOUNT

    # Test that payment redeem fails with a duplicate payment
    with pytest.raises(PaymentServerError):
        channel_server.redeem(payment_txid)
예제 #8
0
def test_channel_sync(monkeypatch):
    """Test ability to sync the status of all channels."""
    channel_server._db = DatabaseSQLite3(':memory:', db_dir='')

    # Seed the database with activity in Channel A
    test_client_a = _create_client_txs()
    deposit_txid_a = channel_server.open(test_client_a.deposit_tx,
                                         test_client_a.redeem_script)
    payment_txid = channel_server.receive_payment(deposit_txid_a,
                                                  test_client_a.payment_tx)
    amount = channel_server.redeem(payment_txid)
    assert amount == TEST_PMT_AMOUNT

    # Seed the database with activity in Channel B
    cust_wallet._private_key = PrivateKey.from_random()
    test_client_b = _create_client_txs()
    deposit_txid_b = channel_server.open(test_client_b.deposit_tx,
                                         test_client_b.redeem_script)
    payment_txid = channel_server.receive_payment(deposit_txid_b,
                                                  test_client_b.payment_tx)
    amount = channel_server.redeem(payment_txid)
    payment_tx1 = _create_client_payment(test_client_b, 2)
    payment_tx2 = _create_client_payment(test_client_b, 3)
    payment_tx3 = _create_client_payment(test_client_b, 4)
    payment_txid1 = channel_server.receive_payment(deposit_txid_b, payment_tx1)
    payment_txid2 = channel_server.receive_payment(deposit_txid_b, payment_tx2)
    payment_txid3 = channel_server.receive_payment(deposit_txid_b, payment_tx3)
    amount1 = channel_server.redeem(payment_txid1)
    amount2 = channel_server.redeem(payment_txid3)
    amount3 = channel_server.redeem(payment_txid2)
    assert amount1 == TEST_PMT_AMOUNT
    assert amount2 == TEST_PMT_AMOUNT
    assert amount3 == TEST_PMT_AMOUNT

    # Both channels should be `ready` since our channel is zeroconf by default
    channels = channel_server._db.pc.lookup()
    assert channels, 'Channel lookup with no args should return a list of all channels.'
    for channel in channels:
        assert channel.state == ChannelSQLite3.READY, 'Channel should be READY.'

    # Change Channel A to `confirming` for testing purposes
    channel_server._db.pc.update_state(deposit_txid_a,
                                       ChannelSQLite3.CONFIRMING)
    test_state = channel_server._db.pc.lookup(deposit_txid_a).state
    assert test_state == ChannelSQLite3.CONFIRMING, 'Channel should be CONFIRMING'

    # Change Channel B's expiration to be very close to allowable expiration
    new_expiry = int(time.time() + 3600)
    update = 'UPDATE payment_channel SET expires_at=? WHERE deposit_txid=?'
    channel_server._db.pc.c.execute(update, (new_expiry, deposit_txid_b))
    channel_server._db.pc.c.connection.commit()
    test_expiry = channel_server._db.pc.lookup(deposit_txid_b).expires_at
    assert test_expiry == new_expiry, 'Channel should closing soon.'

    # Sync all of the server's payment channels
    channel_server.sync()

    # Test that Channel A is `ready` after a sync
    test_state = channel_server._db.pc.lookup(deposit_txid_a).state
    assert test_state == ChannelSQLite3.READY, 'Channel should be READY'

    # Test that Channel B is `closed` after a sync
    test_state = channel_server._db.pc.lookup(deposit_txid_b).state
    assert test_state == ChannelSQLite3.CLOSED, 'Channel should be CLOSED'

    # Test that Channel B payment is fully signed after a sync
    test_payment = channel_server._db.pc.lookup(deposit_txid_b).payment_tx
    goodsig_1 = Script.validate_template(test_payment.inputs[0].script,
                                         [bytes, bytes, 'OP_1', bytes])
    goodsig_true = Script.validate_template(test_payment.inputs[0].script,
                                            [bytes, bytes, 'OP_TRUE', bytes])
    assert goodsig_1 or goodsig_true, 'Payment should be in a fully signed format'

    # Test that Channel A remains `ready` after another sync
    channel_server.sync()
    test_state = channel_server._db.pc.lookup(deposit_txid_a).state
    assert test_state == ChannelSQLite3.READY, 'Channel should be READY'

    # Modify `lookup_spend_txid` to return a txid, as if the tx were spent
    monkeypatch.setattr(MockBlockchain, 'lookup_spend_txid',
                        mock_lookup_spent_txid)

    # Test that Channel A is `closed` after a sync where it finds a spent txid
    channel_server.sync()
    test_state = channel_server._db.pc.lookup(deposit_txid_a).state
    assert test_state == ChannelSQLite3.CLOSED, 'Channel should be CLOSED'