コード例 #1
0
def test_cheating_client(
    doggo_proxy,
    web3,
    default_http_client: DefaultHTTPClient,
    wait_for_blocks,
    http_doggo_url: str,
):
    patch_on_http_response(default_http_client, abort_on=[502])
    balance = web3.eth.getBalance(doggo_proxy.channel_manager.receiver)
    assert balance > 0
    # remove all receiver's eth
    web3.eth.sendTransaction({
        'from': doggo_proxy.channel_manager.receiver,
        'to': FAUCET_ADDRESS,
        'value': balance - 90000
    })
    wait_for_blocks(1)
    default_http_client.get(http_doggo_url)
    # proxy is expected to return 502 - it has no funds
    assert default_http_client.last_response.status_code == 502
    web3.eth.sendTransaction({
        'from': FAUCET_ADDRESS,
        'to': doggo_proxy.channel_manager.receiver,
        'value': balance
    })
    wait_for_blocks(1)
    default_http_client.get(http_doggo_url)
    # now it should proceed normally
    assert default_http_client.last_response.status_code == 200
コード例 #2
0
def test_default_http_client_topup(
        doggo_proxy,
        default_http_client: DefaultHTTPClient,
        http_doggo_url: str
):

    # Create a channel that has just enough capacity for one transfer.
    default_http_client.initial_deposit = lambda x: 0
    check_response(default_http_client.get(http_doggo_url))

    client = default_http_client.client
    open_channels = client.get_open_channels()
    assert len(open_channels) == 1
    channel1 = open_channels[0]
    assert channel1 == default_http_client.get_channel(http_doggo_url)
    assert channel1.balance_sig
    assert channel1.balance == channel1.deposit

    # Do another payment. Topup should occur.
    check_response(default_http_client.get(http_doggo_url))
    open_channels = client.get_open_channels()
    assert len(open_channels) == 1
    channel2 = open_channels[0]
    assert channel2 == default_http_client.get_channel(http_doggo_url)
    assert channel2.balance_sig
    assert channel2.balance < channel2.deposit
    assert channel1 == channel2
コード例 #3
0
def test_status_codes(doggo_proxy, default_http_client: DefaultHTTPClient,
                      http_doggo_url: str):
    patch_on_http_response(default_http_client, abort_on=[404])
    default_http_client.get(http_doggo_url)
    assert default_http_client.last_response.status_code == 200
    default_http_client.get(http_doggo_url[:-1])
    assert default_http_client.last_response.status_code == 404
コード例 #4
0
def test_ssl_client(
        doggo_proxy,
        default_http_client: DefaultHTTPClient,
        https_doggo_url: str
):
    with disable_ssl_check():
        check_response(default_http_client.get(https_doggo_url))
    with pytest.raises(SSLError):
        check_response(default_http_client.get(https_doggo_url))
コード例 #5
0
def test_cheating_client(
        doggo_proxy,
        default_http_client: DefaultHTTPClient,
        http_doggo_url: str
):
    """this test scenario where client sends less funds than what is requested
        by the server. In such case, a "RDN-Invalid-Amount=1" header should
        be sent in a server's reply
    """
    def patched_payment(
            self: DefaultHTTPClient,
            method: str,
            url: str,
            response: Response,
            **kwargs
    ):
        response.headers[HTTPHeaders.PRICE] = str(
            int(response.headers[HTTPHeaders.PRICE]) + self.price_adjust
        )
        return DefaultHTTPClient.on_payment_requested(self, method, url, response, **kwargs)

    def patched_on_invalid_amount(self, method: str, url: str, response: Response, **kwargs):
        self.invalid_amount_received += 1
        price = int(response.headers[HTTPHeaders.PRICE])
        DefaultHTTPClient.on_invalid_amount(self, method, url, response, **kwargs)
        # on_invalid_amount will already prepare the next payment which we don't execute anymore,
        # so revert that.
        channel = self.get_channel(url)
        channel.update_balance(channel.balance - price)
        return False

    default_http_client.on_invalid_amount = types.MethodType(
        patched_on_invalid_amount,
        default_http_client
    )
    default_http_client.on_payment_requested = types.MethodType(
        patched_payment,
        default_http_client
    )

    default_http_client.invalid_amount_received = 0

    # correct amount
    default_http_client.price_adjust = 0
    response = default_http_client.get(http_doggo_url)
    check_response(response)
    assert default_http_client.invalid_amount_received == 0
    # underpay
    default_http_client.price_adjust = -1
    response = default_http_client.get(http_doggo_url)
    assert response is None
    assert default_http_client.invalid_amount_received == 1
    # overpay
    default_http_client.price_adjust = 1
    response = default_http_client.get(http_doggo_url)
    assert response is None
    assert default_http_client.invalid_amount_received == 2
コード例 #6
0
class ETHTickerClient(ttk.Frame):
    def __init__(self,
                 sender_privkey: str,
                 httpclient: DefaultHTTPClient = None,
                 poll_interval: float = 5) -> None:
        self.poll_interval = poll_interval

        self.root = tkinter.Tk()
        ttk.Frame.__init__(self, self.root)
        self.root.title('µRaiden ETH Ticker')
        self.root.protocol('WM_DELETE_WINDOW', self.close)
        self.pack()
        self.pricevar = tkinter.StringVar(value='0.00 USD')
        ttk.Label(self, textvariable=self.pricevar,
                  font=('Helvetica', '72')).pack()

        if httpclient:
            self.httpclient = httpclient
            self.client = httpclient.client
        else:
            self.client = Client(sender_privkey)
            self.httpclient = DefaultHTTPClient(
                self.client,
                initial_deposit=lambda x: 10 * x,
                topup_deposit=lambda x: 5 * x)

        self.active_query = False
        self.running = False

    def run(self):
        self.running = True
        self.root.after(0, self.query_price)
        self.root.mainloop()

    def query_price(self):
        if not self.running:
            return
        self.active_query = True

        response = self.httpclient.get('http://localhost:5000/ETHUSD')
        if response:
            price = float(response.json()['last_price'])
            log.info('New price received: {:.2f} USD'.format(price))
            self.pricevar.set('{:.2f} USD'.format(price))
        else:
            log.warning('No response.')

        if self.running:
            self.root.after(int(self.poll_interval * 1000), self.query_price)
        self.active_query = False

    def close(self):
        log.info('Shutting down gracefully.')
        self.running = False
        self.root.destroy()
        # Sloppy handling of thread joining but works for this small demo.
        while self.active_query:
            gevent.sleep(1)

        self.httpclient.close_active_channel('http://localhost:5000')
コード例 #7
0
def test_default_http_client_existing_channel(
        doggo_proxy, default_http_client: DefaultHTTPClient,
        receiver_address: str, http_doggo_url: str):
    client = default_http_client.client
    channel = client.open_channel(receiver_address, 50)
    check_response(default_http_client.get(http_doggo_url))
    assert channel.balance == 2
    assert channel.deposit == 50
コード例 #8
0
def test_default_http_client_close(doggo_proxy,
                                   default_http_client: DefaultHTTPClient,
                                   http_doggo_url: str):
    client = default_http_client.client
    check_response(default_http_client.get(http_doggo_url))
    default_http_client.close_active_channel(http_doggo_url)
    open_channels = client.get_open_channels()
    assert len(open_channels) == 0
コード例 #9
0
def test_full_cycle_error_500(
        default_http_client: DefaultHTTPClient,
        api_endpoint_address: str,
        token_contract_address,
        channel_manager_contract_address,
        receiver_address
):
    default_http_client.initial_deposit = lambda x: x

    with requests_mock.mock() as server_mock:
        headers1 = Munch()
        headers1.token_address = token_contract_address
        headers1.contract_address = channel_manager_contract_address
        headers1.receiver_address = receiver_address
        headers1.price = '3'

        headers2 = Munch()
        headers2.cost = '3'

        headers1 = HTTPHeaders.serialize(headers1)
        headers2 = HTTPHeaders.serialize(headers2)

        url = 'http://{}/something'.format(api_endpoint_address)
        server_mock.get(url, [
            {'status_code': 402, 'headers': headers1},
            {'status_code': 500, 'headers': {}},
            {'status_code': 200, 'headers': headers2, 'text': 'success'}
        ])
        response = default_http_client.get(url)

    # First cycle, request price.
    request = server_mock.request_history[0]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_contract_address

    # Second cycle, pay price but receive error.
    request = server_mock.request_history[1]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_contract_address
    assert request.headers['RDN-Balance'] == '3'

    channel = default_http_client.get_channel(url)
    assert channel.balance == 3
    balance_sig_hex = encode_hex(channel.balance_sig)
    assert request.headers['RDN-Balance-Signature'] == balance_sig_hex

    # Third cycle, retry naively.
    request = server_mock.request_history[2]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_contract_address
    assert request.headers['RDN-Balance'] == '3'
    assert channel.balance == 3
    assert request.headers['RDN-Balance-Signature'] == balance_sig_hex
    assert channel.balance_sig
    assert response.text == 'success'
コード例 #10
0
def test_error_handling(default_http_client: DefaultHTTPClient,
                        api_endpoint_address: str, token_address: str,
                        channel_manager_address: str, receiver_address: str):
    nonexisting_channel_mock = mock.patch.object(
        default_http_client,
        'on_nonexisting_channel',
        wraps=default_http_client.on_nonexisting_channel).start()
    insufficient_confirmations_mock = mock.patch.object(
        default_http_client,
        'on_insufficient_confirmations',
        wraps=default_http_client.on_insufficient_confirmations).start()
    insufficient_funds_mock = mock.patch.object(
        default_http_client,
        'on_insufficient_funds',
        wraps=default_http_client.on_insufficient_funds).start()
    invalid_contract_address_mock = mock.patch.object(
        default_http_client,
        'on_invalid_contract_address',
        wraps=default_http_client.on_invalid_contract_address).start()

    with requests_mock.mock() as server_mock:
        headers = {
            HTTPHeaders.TOKEN_ADDRESS: token_address,
            HTTPHeaders.CONTRACT_ADDRESS: channel_manager_address,
            HTTPHeaders.RECEIVER_ADDRESS: receiver_address,
            HTTPHeaders.PRICE: '3'
        }
        headers = [headers.copy() for _ in range(5)]
        headers[1][HTTPHeaders.NONEXISTING_CHANNEL] = '1'
        headers[2][HTTPHeaders.INSUF_CONFS] = '1'
        headers[3][HTTPHeaders.INSUF_FUNDS] = '1'
        headers[4][HTTPHeaders.CONTRACT_ADDRESS] = '0x' + '12' * 20

        url = 'http://{}/something'.format(api_endpoint_address)
        server_mock.get(url, [{
            'status_code': 402,
            'headers': headers[0]
        }, {
            'status_code': 402,
            'headers': headers[1]
        }, {
            'status_code': 402,
            'headers': headers[2]
        }, {
            'status_code': 402,
            'headers': headers[3]
        }, {
            'status_code': 402,
            'headers': headers[4]
        }])
        response = default_http_client.get(url)

    assert response is None
    assert nonexisting_channel_mock.call_count == 1
    assert insufficient_confirmations_mock.call_count == 1
    assert insufficient_funds_mock.call_count == 1
    assert invalid_contract_address_mock.call_count == 1
コード例 #11
0
def test_custom_headers(default_http_client: DefaultHTTPClient,
                        api_endpoint_address: str, token_address: str,
                        channel_manager_address: str, receiver_address: str):
    default_http_client.initial_deposit = lambda x: x

    with requests_mock.mock() as server_mock:
        headers1 = Munch()
        headers1.token_address = token_address
        headers1.contract_address = channel_manager_address
        headers1.receiver_address = receiver_address
        headers1.price = '7'

        headers2 = Munch()
        headers2.cost = '7'

        headers1 = HTTPHeaders.serialize(headers1)
        headers2 = HTTPHeaders.serialize(headers2)

        url = 'http://{}/something'.format(api_endpoint_address)
        server_mock.get(url, [{
            'status_code': 402,
            'headers': headers1
        }, {
            'status_code': 200,
            'headers': headers2,
            'text': 'success'
        }])
        response = default_http_client.get(
            url,
            headers={
                'someheader': 'somevalue',
                # This should override the actual balance but doesn't actually make sense.
                'RDN-Balance': '5'
            })

    # First cycle, request price.
    request = server_mock.request_history[0]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_address
    assert request.headers['RDN-Balance'] == '5'
    assert request.headers['someheader'] == 'somevalue'

    # Second cycle, pay price.
    request = server_mock.request_history[1]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_address
    assert request.headers['RDN-Balance'] == '5'
    assert request.headers['someheader'] == 'somevalue'

    channel = default_http_client.get_channel(url)
    assert channel.balance == 7
    balance_sig_hex = encode_hex(channel.balance_sig)
    assert request.headers['RDN-Balance-Signature'] == balance_sig_hex
    assert channel.balance_sig
    assert response.text == 'success'
コード例 #12
0
def test_default_http_client_existing_channel_topup(
        doggo_proxy,
        default_http_client: DefaultHTTPClient,
        receiver_address,
        http_doggo_url: str
):
    client = default_http_client.client
    default_http_client.topup_deposit = lambda x: 13
    channel = client.open_channel(receiver_address, 1)
    check_response(default_http_client.get(http_doggo_url))
    assert channel.balance == 2
    assert channel.deposit == 13
コード例 #13
0
def test_default_http_client(doggo_proxy,
                             default_http_client: DefaultHTTPClient,
                             sender_address: str, receiver_address: str,
                             http_doggo_url: str):
    check_response(default_http_client.get(http_doggo_url))

    client = default_http_client.client
    open_channels = client.get_open_channels()
    assert len(open_channels) == 1

    channel = open_channels[0]
    assert channel == default_http_client.get_channel(http_doggo_url)
    assert channel.balance_sig
    assert channel.balance < channel.deposit
    assert channel.sender == sender_address
    assert channel.receiver == receiver_address
コード例 #14
0
def test_cooperative_close_denied(default_http_client: DefaultHTTPClient,
                                  api_endpoint_address: str,
                                  token_address: str,
                                  channel_manager_address: str,
                                  receiver_address: str):
    cooperative_close_denied_mock = mock.patch.object(
        default_http_client,
        'on_cooperative_close_denied',
        wraps=default_http_client.on_cooperative_close_denied).start()

    with requests_mock.mock() as server_mock:
        headers = {
            HTTPHeaders.TOKEN_ADDRESS: token_address,
            HTTPHeaders.CONTRACT_ADDRESS: channel_manager_address,
            HTTPHeaders.RECEIVER_ADDRESS: receiver_address,
            HTTPHeaders.PRICE: '3'
        }
        headers = [headers.copy() for _ in range(2)]
        headers[1][HTTPHeaders.COST] = '3'

        url = 'http://{}/something'.format(api_endpoint_address)
        server_mock.get(url, [
            {
                'status_code': 402,
                'headers': headers[0]
            },
            {
                'status_code': 200,
                'headers': headers[1],
                'text': 'success'
            },
        ])
        channel_url = re.compile(
            'http://{}/api/1/channels/0x.{{40}}/\d+'.format(
                api_endpoint_address))
        server_mock.delete(channel_url, [{'status_code': 403}])
        response = default_http_client.get(url)
        default_http_client.close_active_channel('http://' +
                                                 api_endpoint_address)

    assert response.text == 'success'
    assert cooperative_close_denied_mock.call_count == 1
    assert default_http_client.client.channels[
        0].state == Channel.State.settling
コード例 #15
0
def test_coop_close(doggo_proxy, default_http_client: DefaultHTTPClient,
                    http_doggo_url: str):
    check_response(default_http_client.get(http_doggo_url))

    client = default_http_client.client
    open_channels = client.get_open_channels()
    assert len(open_channels) == 1

    channel = open_channels[0]
    import requests
    reply = requests.get('http://localhost:5000/api/1/channels/%s/%s' %
                         (channel.sender, channel.block))
    assert reply.status_code == 200
    json_reply = json.loads(reply.text)

    request_data = {'balance': json_reply['balance']}
    reply = requests.delete('http://localhost:5000/api/1/channels/%s/%s' %
                            (channel.sender, channel.block),
                            data=request_data)

    assert reply.status_code == 200
コード例 #16
0
def test_full_cycle_adapt_balance(
        default_http_client: DefaultHTTPClient,
        api_endpoint_address: str,
        token_contract_address,
        channel_manager_contract_address,
        receiver_address
):
    # Simulate a lost balance signature.
    client = default_http_client.client
    channel = client.get_suitable_channel(receiver_address, 10, initial_deposit=lambda x: 2 * x)
    channel.create_transfer(3)
    lost_balance_sig = channel.balance_sig
    channel.update_balance(0)

    with requests_mock.mock() as server_mock:
        headers1 = Munch()
        headers1.token_address = token_contract_address
        headers1.contract_address = channel_manager_contract_address
        headers1.receiver_address = receiver_address
        headers1.price = '7'

        headers2 = headers1.copy()
        headers2.invalid_amount = '1'
        headers2.sender_balance = '3'
        headers2.balance_signature = encode_hex(lost_balance_sig)

        headers3 = Munch()
        headers3.cost = '7'

        headers1 = HTTPHeaders.serialize(headers1)
        headers2 = HTTPHeaders.serialize(headers2)
        headers3 = HTTPHeaders.serialize(headers3)

        url = 'http://{}/something'.format(api_endpoint_address)
        server_mock.get(url, [
            {'status_code': 402, 'headers': headers1},
            {'status_code': 402, 'headers': headers2},
            {'status_code': 200, 'headers': headers3, 'text': 'success'}
        ])

        response = default_http_client.get(url)

    # First cycle, request price.
    request = server_mock.request_history[0]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_contract_address

    # Second cycle, pay price based on outdated balance.
    request = server_mock.request_history[1]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_contract_address
    assert request.headers['RDN-Balance'] == '7'
    assert request.headers['RDN-Balance-Signature']

    # Third cycle, adapt new balance and pay price again.
    request = server_mock.request_history[2]
    assert request.path == '/something'
    assert request.method == 'GET'
    assert request.headers['RDN-Contract-Address'] == channel_manager_contract_address
    assert request.headers['RDN-Balance'] == '10'

    channel = default_http_client.get_channel(url)
    assert channel.balance == 10
    balance_sig_hex = encode_hex(channel.balance_sig)
    assert request.headers['RDN-Balance-Signature'] == balance_sig_hex
    assert channel.balance_sig
    assert response.text == 'success'