def test_session_topup( doggo_proxy, session: Session, http_doggo_url: str ): # Create a channel that has just enough capacity for one transfer. session.initial_deposit = lambda x: 0 check_response(session.get(http_doggo_url)) client = session.client open_channels = client.get_open_channels() assert len(open_channels) == 1 channel1 = open_channels[0] assert channel1 == session.channel assert channel1.balance_sig assert channel1.balance == channel1.deposit # Do another payment. Topup should occur. check_response(session.get(http_doggo_url)) open_channels = client.get_open_channels() assert len(open_channels) == 1 channel2 = open_channels[0] assert channel2 == session.channel assert channel2.balance_sig assert channel2.balance < channel2.deposit assert channel1 == channel2
def run(private_key: str, password_path: str, resource: str, channel_manager_address: str = None, web3: Web3 = None, retry_interval: float = 5, endpoint_url: str = 'http://localhost:5000'): # Create the client session. session = Session(endpoint_url=endpoint_url, private_key=private_key, key_password_path=password_path, channel_manager_address=channel_manager_address, web3=web3, retry_interval=retry_interval) # Get the resource. If payment is required, client will attempt to create # a channel or will use existing one. response = session.get('{}/{}'.format(endpoint_url, resource)) if response.status_code == requests.codes.OK: if re.match('^text/', response.headers['Content-Type']): logging.info("Got the resource {} type={}:\n{}".format( resource, response.headers.get('Content-Type', '???'), response.text)) else: logging.info("Got the resource {} type={} (not echoed)".format( resource, response.headers.get('Content-Type', '???'))) else: logging.error("Error getting the resource. Code={} body={}".format( response.status_code, response.text)) return response
class ETHTickerClient(ttk.Frame): def __init__( self, sender_privkey: str, session: Session = 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 session is None: self.session = Session( private_key=sender_privkey, close_channel_on_exit=True, endpoint_url='http://localhost:5000' ) else: self.session = session 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.session.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.session.close()
def __init__( self, sender_privkey: str, session: Session = 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 session is None: self.session = Session( private_key=sender_privkey, close_channel_on_exit=True, endpoint_url='http://localhost:5000' ) else: self.session = session self.active_query = False self.running = False
def test_cheating_client( doggo_proxy, web3, session: Session, wait_for_blocks, http_doggo_url: str, ): patch_on_http_response(session, 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) session.get(http_doggo_url) # proxy is expected to return 502 - it has no funds assert session.last_response.status_code == 502 web3.eth.sendTransaction({ 'from': FAUCET_ADDRESS, 'to': doggo_proxy.channel_manager.receiver, 'value': balance }) wait_for_blocks(1) session.get(http_doggo_url) # now it should proceed normally assert session.last_response.status_code == 200
def test_full_cycle_error_500(session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str): session.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 = '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 = session.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_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_address assert request.headers['RDN-Balance'] == '3' assert session.channel.balance == 3 balance_sig_hex = encode_hex(session.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_address assert request.headers['RDN-Balance'] == '3' assert session.channel.balance == 3 assert request.headers['RDN-Balance-Signature'] == balance_sig_hex assert session.channel.balance_sig assert response.text == 'success'
def patched_on_invalid_amount(self, method: str, url: str, response: Response, **kwargs): self.invalid_amount_received += 1 price = int(response.headers[HTTPHeaders.PRICE]) Session.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. self.channel.update_balance(self.channel.balance - price) return False
def test_ssl_client( doggo_proxy, session: Session, https_doggo_url: str ): with pytest.raises(SSLError): check_response(session.get(https_doggo_url)) check_response(session.get(https_doggo_url, verify=False))
def test_status_codes( doggo_proxy, session: Session, http_doggo_url: str ): response = session.get(http_doggo_url) assert response.status_code == 200 response = session.get(http_doggo_url[:-1]) assert response.status_code == 404
def test_custom_headers( session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str ): session.initial_deposit = lambda x: x with requests_mock.mock(real_http=True) 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 = session.get(url, headers={ 'someheader': 'somevalue', # This should override the actual balance but doesn't actually make sense. 'RDN-Balance': '5' }) # Filter out any requests made to the ethereum node. request_history = [request for request in server_mock.request_history if request.port == 5000] # First cycle, request price. request = 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 = 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' assert session.channel.balance == 7 balance_sig_hex = encode_hex(session.channel.balance_sig) assert request.headers['RDN-Balance-Signature'] == balance_sig_hex assert session.channel.balance_sig assert response.text == 'success'
def test_session_existing_channel_topup(doggo_proxy, session: Session, receiver_address: str, http_doggo_url: str): client = session.client session.topup_deposit = lambda x: 13 channel = client.open_channel(receiver_address, 1) check_response(session.get(http_doggo_url)) assert channel.balance == 2 assert channel.deposit == 13
def test_session_close( doggo_proxy, session: Session, http_doggo_url: str ): client = session.client check_response(session.get(http_doggo_url)) session.close_channel() open_channels = client.get_open_channels() assert len(open_channels) == 0
def test_status_codes( doggo_proxy, session: Session, http_doggo_url: str ): patch_on_http_response(session, abort_on=[404]) session.get(http_doggo_url) assert session.last_response.status_code == 200 session.get(http_doggo_url[:-1]) assert session.last_response.status_code == 404
def close_channels(private_key: str, password_path: str, resource: str, channel_manager_address: str = None, web3: Web3 = None, retry_interval: float = 5, endpoint_url: str = 'http://0.0.0.0:5010', close_channel: bool = False): # Create the client session. session = Session(endpoint_url=endpoint_url, private_key=private_key, key_password_path=password_path, channel_manager_address=channel_manager_address, web3=web3, retry_interval=retry_interval, close_channel_on_exit=close_channel) #conn = Client(monitor_address) #conn.send('share') ##print(conn.recv()) #conn.close() print("Private Key:", private_key) addr = privkey_to_addr(private_key) print("Address:", addr) # response = requests.get('http://0.0.0.0:5000/api/1/channels/{}'.format(addr)) #response = session.get('{}/api/1/channels/{}'.format('http://0.0.0.0:5000', addr)) #print(response) response = session.get('{}/{}'.format('http://0.0.0.0:5010', resource)) print(response) print(response.text) print(response.content) print(response.headers) # time.sleep(4) response = session.get('{}/{}'.format('http://0.0.0.0:5010', resource)) print(response) print(response.text) print(response.content) print(response.headers) time.sleep(4) response = session.get('{}/{}'.format('http://0.0.0.0:5010', resource)) print(response) print(response.text) print(response.content) print(response.headers) # time.sleep(10) session.channel.close(balance=session.channel.balance - 1)
def test_session_existing_channel_topup( doggo_proxy, session: Session, receiver_address: str, http_doggo_url: str ): client = session.client session.topup_deposit = lambda x: 13 channel = client.open_channel(receiver_address, 1) check_response(session.get(http_doggo_url)) assert channel.balance == 2 assert channel.deposit == 13
def test_full_cycle_error_500(session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str): session.initial_deposit = lambda x: x with requests_mock.mock(real_http=True) as server_mock: headers1 = Munch() headers1.token_address = token_address headers1.contract_address = channel_manager_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': {} }]) response = session.get(url) # Filter out any requests made to the ethereum node. request_history = [ request for request in server_mock.request_history if request.port == 5000 ] # First cycle, request price. request = request_history[0] assert request.path == '/something' assert request.method == 'GET' assert request.headers['RDN-Contract-Address'] == channel_manager_address # Second cycle, pay price but receive error. request = 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'] == '3' assert session.channel.balance == 3 balance_sig_hex = encode_hex(session.channel.balance_sig) assert request.headers['RDN-Balance-Signature'] == balance_sig_hex assert response.status_code == 500
def test_cheating_client( doggo_proxy, session: Session, 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: Session, method: str, url: str, response: Response, **kwargs ): response.headers[HTTPHeaders.PRICE] = str( int(response.headers[HTTPHeaders.PRICE]) + self.price_adjust ) return Session.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]) Session.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. self.channel.update_balance(self.channel.balance - price) return False session.on_invalid_amount = types.MethodType( patched_on_invalid_amount, session ) session.on_payment_requested = types.MethodType( patched_payment, session ) session.invalid_amount_received = 0 # correct amount session.price_adjust = 0 response = session.get(http_doggo_url) check_response(response) assert session.invalid_amount_received == 0 # underpay session.price_adjust = -1 response = session.get(http_doggo_url) assert response.status_code == 402 assert session.invalid_amount_received == 1 # overpay session.price_adjust = 1 response = session.get(http_doggo_url) assert response.status_code == 402 assert session.invalid_amount_received == 2
def test_cheating_client( doggo_proxy, session: Session, 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: Session, method: str, url: str, response: Response, **kwargs ): response.headers[HTTPHeaders.PRICE] = str( int(response.headers[HTTPHeaders.PRICE]) + self.price_adjust ) return Session.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]) Session.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. self.channel.update_balance(self.channel.balance - price) return False session.on_invalid_amount = types.MethodType( patched_on_invalid_amount, session ) session.on_payment_requested = types.MethodType( patched_payment, session ) session.invalid_amount_received = 0 # correct amount session.price_adjust = 0 response = session.get(http_doggo_url) check_response(response) assert session.invalid_amount_received == 0 # underpay session.price_adjust = -1 response = session.get(http_doggo_url) assert response is None assert session.invalid_amount_received == 1 # overpay session.price_adjust = 1 response = session.get(http_doggo_url) assert response is None assert session.invalid_amount_received == 2
def test_full_cycle_success_post(session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str): session.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.post(url, [{ 'status_code': 402, 'headers': headers1 }, { 'status_code': 200, 'headers': headers2, 'text': 'success' }]) response = session.post(url, json={'somefield': 'somevalue'}) # First cycle, request price. request = server_mock.request_history[0] assert request.path == '/something' assert request.method == 'POST' assert request.headers['RDN-Contract-Address'] == channel_manager_address assert request.json()['somefield'] == 'somevalue' # Second cycle, pay price. request = server_mock.request_history[1] assert request.path == '/something' assert request.method == 'POST' assert request.headers['RDN-Contract-Address'] == channel_manager_address assert request.headers['RDN-Balance'] == '7' assert request.json()['somefield'] == 'somevalue' assert session.channel.balance == 7 balance_sig_hex = encode_hex(session.channel.balance_sig) assert request.headers['RDN-Balance-Signature'] == balance_sig_hex assert session.channel.balance_sig assert response.text == 'success'
def test_full_cycle_error_500( session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str ): session.initial_deposit = lambda x: x with requests_mock.mock(real_http=True) as server_mock: headers1 = Munch() headers1.token_address = token_address headers1.contract_address = channel_manager_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': {}} ]) response = session.get(url) # Filter out any requests made to the ethereum node. request_history = [request for request in server_mock.request_history if request.port == 5000] # First cycle, request price. request = request_history[0] assert request.path == '/something' assert request.method == 'GET' assert request.headers['RDN-Contract-Address'] == channel_manager_address # Second cycle, pay price but receive error. request = 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'] == '3' assert session.channel.balance == 3 balance_sig_hex = encode_hex(session.channel.balance_sig) assert request.headers['RDN-Balance-Signature'] == balance_sig_hex assert response.status_code == 500
def run(api_endpoint, api_port, **kwargs): exclude_kwargs = {'close_channels'} kwargs_client = { key: value for key, value in kwargs.items() if value and key not in exclude_kwargs } with Client(**kwargs_client) as client: m2mclient = Session(client, api_endpoint, api_port) resource = m2mclient.run('doggo.jpg') log.info('Response: {}'.format(resource)) if kwargs['close_channels'] is True: for channel in client.channels: if channel.state == Channel.State.open: channel.close_channel()
def run(private_key: str, password_path: str, channel_manager_address: str = None, reqs: int = 20, web3: Web3 = None, retry_interval: float = 5, endpoint_url: str = 'http://localhost:5000'): # Create the client session. global session session = Session(endpoint_url=endpoint_url, private_key=private_key, key_password_path=password_path, channel_manager_address=channel_manager_address, web3=web3, initial_deposit=lambda price: 10 * 1000 * 1000 * price, topup_deposit=lambda price: 10 * 1000 * 1000 * price, close_channel_on_exit=True, retry_interval=retry_interval) resource = "/test/1" # Get the resource. If payment is required, client will attempt to create # a channel or will use existing one. last_time = time.time() global rqs last_reqs = 0 for i in range(0, reqs): response = session.get('{}/{}'.format(endpoint_url, resource)) if response.status_code == requests.codes.OK: rqs = rqs + 1 t = time.time() if (t - last_time) >= 1: print("transaction rate: ", rqs - last_reqs, " req/sec") last_time = t last_reqs = rqs continue if re.match('^text/', response.headers['Content-Type']): logging.info("Got the resource {} type={}:\n{}".format( resource, response.headers.get('Content-Type', '???'), response.text)) else: logging.info("Got the resource {} type={} (not echoed)".format( resource, response.headers.get('Content-Type', '???'))) else: logging.error("Error getting the resource. Code={} body={}".format( response.status_code, response.text)) print(rqs, " requests processed, closing session.") session.close() return response
def test_eth_ticker( empty_proxy: PaywalledProxy, session: Session, sender_privkey: str, receiver_privkey: str, monkeypatch: MonkeyPatch ): def get_patched(*args, **kwargs): body = { 'mid': '682.435', 'bid': '682.18', 'ask': '682.69', 'last_price': '683.16', 'low': '532.97', 'high': '684.0', 'volume': '724266.25906224', 'timestamp': '1513167820.721733' } return jsonify(body) monkeypatch.setattr(PaywalledProxyUrl, 'get', get_patched) ETHTickerProxy(receiver_privkey, proxy=empty_proxy) ticker = ETHTickerClient(sender_privkey, session=session, poll_interval=0.5) def post(): ticker.close() assert ticker.pricevar.get() == '683.16 USD' assert len(session.client.get_open_channels()) == 0 ticker.success = True session.close_channel_on_exit = True ticker.success = False ticker.root.after(1500, post) ticker.run() assert ticker.success
def patch_on_http_response(default_http_client: Session, abort_on=[]): def patched(self, method: str, url: str, response: Response, **kwargs): self.last_response = response return (response.status_code not in abort_on) default_http_client.on_http_response = types.MethodType( patched, default_http_client)
def run( private_key: str, password_path: str, resource: str, channel_manager_address: str = None, web3: Web3 = None, retry_interval: float = 5, endpoint_url: str = 'http://localhost:5000' ): # Create the client session. session = Session( endpoint_url=endpoint_url, private_key=private_key, key_password_path=password_path, channel_manager_address=channel_manager_address, web3=web3, retry_interval=retry_interval ) # Get the resource. If payment is required, client will attempt to create # a channel or will use existing one. response = session.get('{}/{}'.format(endpoint_url, resource)) if response.status_code == requests.codes.OK: if re.match('^text/', response.headers['Content-Type']): logging.info( "Got the resource {} type={}:\n{}".format( resource, response.headers.get('Content-Type', '???'), response.text ) ) else: logging.info( "Got the resource {} type={} (not echoed)".format( resource, response.headers.get('Content-Type', '???') ) ) else: logging.error( "Error getting the resource. Code={} body={}".format( response.status_code, response.text ) ) return response
def request(method: str, url: str, **kwargs) -> Response: session_kwargs = pop_function_kwargs(kwargs, Session.__init__) client_kwargs = pop_function_kwargs(kwargs, Client.__init__) session_kwargs.update(client_kwargs) with Session(**session_kwargs) as session: return session.request(method, url, **kwargs)
def test_resource_request(doggo_proxy, http_doggo_url: str, session: Session): n_requests = 10 session.initial_deposit = lambda x: (n_requests + 1) * x # First transfer creates channel on-chain => exclude from profiling. response = session.get(http_doggo_url) assert response.text == 'HI I AM A DOGGO' t_start = time.time() for i in range(n_requests): log.debug('Transfer {}'.format(i)) response = session.get(http_doggo_url) assert response.text == 'HI I AM A DOGGO' t_diff = time.time() - t_start log.info("{} requests in {} ({} rps)".format( n_requests, datetime.timedelta(seconds=t_diff), n_requests / t_diff))
def test_cache_cleanup(empty_proxy: PaywalledProxy, usession: uSession, api_endpoint_address: str): now = time.time() es_mock = ElasticsearchBackend(None) api_path = 'http://' + api_endpoint_address resource_url = '/some_index/some_type/_search' url = api_path + resource_url bodies = [ { 'query': 'query something' }, { 'query': 'query some other thing' }, { 'query': 'query some third thing' }, ] requests = [ Request.from_values(method='GET', base_url=api_path, path=resource_url, content_type='application/json', data=json.dumps(bodies[0])), Request.from_values(method='GET', base_url=api_path, path=resource_url, content_type='application/json', data=json.dumps(bodies[1])), Request.from_values(method='GET', base_url=api_path, path=resource_url, content_type='application/json', data=json.dumps(bodies[2])) ] resources = [ Resource(content='success1', price=4, expires_at=now - 100), Resource(content='success2', price=4, expires_at=now + 100), Resource(content='success3', price=4, expires_at=now - 100), ] es_mock.search = mock.Mock(return_value=resources[1]) server = APIServer(empty_proxy, es=es_mock) server.resource_cache.update({ ExpensiveElasticsearch.get_request_key(requests[0]): resources[0], ExpensiveElasticsearch.get_request_key(requests[1]): resources[1], ExpensiveElasticsearch.get_request_key(requests[2]): resources[2] }) assert len(server.resource_cache) == 3 response = usession.get(url, json=bodies[1]) assert response.json() == 'success2' assert len(server.resource_cache) == 1
def test_error_handling(session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str): nonexisting_channel_mock = mock.patch.object( session, 'on_nonexisting_channel', wraps=session.on_nonexisting_channel).start() insufficient_confirmations_mock = mock.patch.object( session, 'on_insufficient_confirmations', wraps=session.on_insufficient_confirmations).start() insufficient_funds_mock = mock.patch.object( session, 'on_invalid_balance_proof', wraps=session.on_invalid_balance_proof).start() invalid_contract_address_mock = mock.patch.object( session, 'on_invalid_contract_address', wraps=session.on_invalid_contract_address).start() with requests_mock.mock(real_http=True) 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.INVALID_PROOF] = '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 = session.get(url) assert response.status_code == 402 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
def session(client: Client, use_tester: bool, api_endpoint_address: str): # patch request_resource of this instance in order to advance blocks when doing requests def request_patched(self: Session, method: str, url: str, **kwargs): if use_tester: log.info('Mining new block.') self.client.context.web3.testing.mine(1) return Session._request_resource(self, method, url, **kwargs) kwargs = {} if use_tester: kwargs['retry_interval'] = 0.1 session = Session(client, endpoint_url='http://' + api_endpoint_address, **kwargs) session._request_resource = types.MethodType(request_patched, session) yield session session.close()
def test_resource_request(doggo_proxy, http_doggo_url: str, session: Session): n_requests = 10 session.initial_deposit = lambda x: (n_requests + 1) * x # First transfer creates channel on-chain => exclude from profiling. response = session.get(http_doggo_url) assert response.text == 'HI I AM A DOGGO' t_start = time.time() for i in range(n_requests): log.debug('Transfer {}'.format(i)) response = session.get(http_doggo_url) assert response.text == 'HI I AM A DOGGO' t_diff = time.time() - t_start log.info("{} requests in {} ({} rps)".format( n_requests, datetime.timedelta(seconds=t_diff), n_requests / t_diff) )
def test_error_handling( session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str ): nonexisting_channel_mock = mock.patch.object( session, 'on_nonexisting_channel', wraps=session.on_nonexisting_channel ).start() insufficient_confirmations_mock = mock.patch.object( session, 'on_insufficient_confirmations', wraps=session.on_insufficient_confirmations ).start() insufficient_funds_mock = mock.patch.object( session, 'on_invalid_balance_proof', wraps=session.on_invalid_balance_proof ).start() invalid_contract_address_mock = mock.patch.object( session, 'on_invalid_contract_address', wraps=session.on_invalid_contract_address ).start() with requests_mock.mock(real_http=True) 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.INVALID_PROOF] = '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 = session.get(url) assert response.status_code == 402 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
def run(key_path, key_password_path, resource): # create the client with Client(key_path=key_path, key_password_path=key_password_path) as client: m2mclient = Session(client, 'localhost', 5000) # Get the resource. If payment is required, client will attempt to create # a channel or will use existing one. status, headers, body = m2mclient.run(resource) if status == requests.codes.OK: if re.match('^text\/', headers['Content-Type']): logging.info( "got the resource %s type=%s\n%s" % (resource, headers.get('Content-Type', '???'), body)) else: logging.info("got the resource %s type=%s" % (resource, headers.get('Content-Type', '???'))) else: logging.error("error getting the resource. code=%d body=%s" % (status, body.decode().strip()))
def patched_payment( self: Session, method: str, url: str, response: Response, **kwargs ): response.headers[HTTPHeaders.PRICE] = str( int(response.headers[HTTPHeaders.PRICE]) + self.price_adjust ) return Session.on_payment_requested(self, method, url, response, **kwargs)
def session(client: Client, use_tester: bool, api_endpoint_address: str): # patch request_resource of this instance in order to advance blocks when doing requests def request_patched(self: Session, method: str, url: str, **kwargs): if use_tester: log.info('Mining new block.') self.client.context.web3.testing.mine(1) return Session._request_resource(self, method, url, **kwargs) kwargs = {} if use_tester: kwargs['retry_interval'] = 0.1 session = Session( client, endpoint_url='http://' + api_endpoint_address, **kwargs ) session._request_resource = types.MethodType(request_patched, session) yield session session.close()
def on_payment_requested(self, method: str, url: str, response: Response, **kwargs) -> bool: price = int(response.headers[HTTPHeaders.PRICE]) if price > self.max_rei_per_request: log.error( 'Requested price exceeds configured maximum price per request ({} > {}). ' 'Aborting'.format(price, self.max_rei_per_request)) return False return uSession.on_payment_requested(self, method, url, response, **kwargs)
def test_cooperative_close_denied(session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str): cooperative_close_denied_mock = mock.patch.object( session, 'on_cooperative_close_denied', wraps=session.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 = session.get(url) session.close_channel() assert response.text == 'success' assert cooperative_close_denied_mock.call_count == 1 assert session.client.channels[0].state == Channel.State.settling
def test_cooperative_close_denied( session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str ): cooperative_close_denied_mock = mock.patch.object( session, 'on_cooperative_close_denied', wraps=session.on_cooperative_close_denied ).start() with requests_mock.mock(real_http=True) 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 = session.get(url) session.close_channel() assert response.text == 'success' assert cooperative_close_denied_mock.call_count == 1 assert session.channel.state == Channel.State.settling
def test_cheating_client( doggo_proxy, web3, session: Session, wait_for_transaction, http_doggo_url: str, faucet_private_key: str, faucet_address: str, receiver_privkey: str, receiver_address: str ): balance = web3.eth.getBalance(doggo_proxy.channel_manager.receiver) assert balance > 0 # remove all receiver's eth tx = create_signed_transaction( receiver_privkey, web3, faucet_address, balance - NETWORK_CFG.GAS_PRICE * NETWORK_CFG.POT_GAS_LIMIT ) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash) response = session.get(http_doggo_url) # proxy is expected to return 502 - it has no funds assert response.status_code == 502 tx = create_signed_transaction( faucet_private_key, web3, receiver_address, balance - NETWORK_CFG.GAS_PRICE * NETWORK_CFG.POT_GAS_LIMIT ) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash) response = session.get(http_doggo_url) # now it should proceed normally assert response.status_code == 200
def test_session( doggo_proxy, session: Session, sender_address: str, receiver_address: str, http_doggo_url: str ): check_response(session.get(http_doggo_url)) client = session.client open_channels = client.get_open_channels() assert len(open_channels) == 1 channel = open_channels[0] assert channel == session.channel assert channel.balance_sig assert channel.balance < channel.deposit assert is_same_address(channel.sender, sender_address) assert is_same_address(channel.receiver, receiver_address)
def test_coop_close( doggo_proxy, session: Session, http_doggo_url: str ): check_response(session.get(http_doggo_url)) client = session.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
def request_patched(self: Session, method: str, url: str, **kwargs): if use_tester: log.info('Mining new block.') self.client.context.web3.testing.mine(1) return Session._request_resource(self, method, url, **kwargs)
def test_full_cycle_adapt_balance( session: Session, api_endpoint_address: str, token_address: str, channel_manager_address: str, receiver_address: str ): # Simulate a lost balance signature. client = session.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(real_http=True) 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 = 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 = session.get(url) # Filter out any requests made to the ethereum node. request_history = [request for request in server_mock.request_history if request.port == 5000] # First cycle, request price. request = request_history[0] assert request.path == '/something' assert request.method == 'GET' assert request.headers['RDN-Contract-Address'] == channel_manager_address # Second cycle, pay price based on outdated balance. request = 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'] == '7' assert request.headers['RDN-Balance-Signature'] # Third cycle, adapt new balance and pay price again. request = request_history[2] assert request.path == '/something' assert request.method == 'GET' assert request.headers['RDN-Contract-Address'] == channel_manager_address assert request.headers['RDN-Balance'] == '10' assert session.channel.balance == 10 balance_sig_hex = encode_hex(session.channel.balance_sig) assert request.headers['RDN-Balance-Signature'] == balance_sig_hex assert session.channel.balance_sig assert response.text == 'success'