eth-gas-proxy

Middleware to selectively override EVM gas heuristics
git clone git://holbrook.no/eth-gas-proxy.git
Log | Files | Refs

commit 5c6b65dd50842e5a3aa26fbfbd28639e8d1e5fc7
parent e06ab63d8e7285d2428c00e2e0b71f1bd66c3b0c
Author: nolash <dev@holbrook.no>
Date:   Tue, 26 Jan 2021 11:38:13 +0100

Add web3 middleware

Diffstat:
Mgas_proxy/proxy.py | 24+++++++++++++++---------
Mgas_proxy/runnable/server.py | 19++++++++++++++++---
Arequirements.txt | 2++
Ascripts/test.py | 25+++++++++++++++++++++++++
Atests/test_middleware.py | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 131 insertions(+), 12 deletions(-)

diff --git a/gas_proxy/proxy.py b/gas_proxy/proxy.py @@ -3,7 +3,6 @@ import logging import websocket -logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -12,30 +11,36 @@ class Oracle: def get(self): raise NotImplementedError -class Web3WebsocketOracle(Oracle): +class JSONRPCOracle(Oracle): - def __init__(self, connection_string, cache=None): - logg.debug('connecting to {}'.format(connection_string)) - self.ws = websocket.create_connection(connection_string) + def __init__(self, socket, cache=None): self.query = { "jsonrpc": "2.0", "method": "eth_gasPrice", "params": [], "id": 0, } + self.id_seq = 0 + self.socket = socket self.cache = cache def __del__(self): - self.ws.close() + self.socket.close() - def get(self): + def get(self, request_id=None): o = None + if request_id == None: + request_id = str(self.id_seq) + self.id_seq += 1 + + self.query['id'] = request_id + try: - self.ws.send(json.dumps(self.query)) - result = self.ws.recv() + self.socket.send(json.dumps(self.query)) + result = self.socket.recv() o = json.loads(result) result_hex = o['result'][2:] if len(result_hex) % 2 != 0: @@ -43,6 +48,7 @@ class Web3WebsocketOracle(Oracle): result_bytes = bytes.fromhex(result_hex) result_num = int.from_bytes(result_bytes, 'big') except Exception as e: + logg.exception('backend error') if self.cache == None: raise(e) result_num = self.cache.get() diff --git a/gas_proxy/runnable/server.py b/gas_proxy/runnable/server.py @@ -1,13 +1,16 @@ +import re import os import argparse import logging import socket import json -from gas_proxy.proxy import Web3WebsocketOracle as Oracle +import websocket + +from gas_proxy.proxy import JSONRPCOracle as Oracle from gas_proxy.cache.mem import MemCache -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() @@ -26,9 +29,19 @@ if args.vv: elif args.v: logging.getLogger().setLevel(logging.INFO) +re_websocket = r'^wss?://(.+):(\d+)/?$' +m = re.match(re_websocket, args.provider) +if m == None: + raise ValueError('websocket providers only') + +logg.debug('using websocket host {} port {}'.format(m.group(1), m.group(2))) +connection_string = args.provider + if __name__ == '__main__': memcache = MemCache() - ws = Oracle(args.provider, cache=memcache) + + ws = websocket.create_connection(connection_string) + ws = Oracle(ws, cache=memcache) s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) s.bind((args.host, args.port)) diff --git a/requirements.txt b/requirements.txt @@ -0,0 +1,2 @@ +web3==5.12.2 +websocket-client==0.57.0 diff --git a/scripts/test.py b/scripts/test.py @@ -0,0 +1,25 @@ +import logging +import time + +import web3 +import websocket + +from gas_proxy.web3 import GasMiddleware + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +re_websocket = r'wss?://' + +def socket_constructor(): + return websocket.create_connection('ws://localhost:8545') + +GasMiddleware.socket_constructor = socket_constructor +GasMiddleware.last_gas = "0x1000" + +w3 = web3.Web3(web3.Web3.WebsocketProvider('ws://localhost:8545')) +w3.middleware_onion.add(GasMiddleware) + +while True: + print(w3.eth.gas_price()) + time.sleep(1) diff --git a/tests/test_middleware.py b/tests/test_middleware.py @@ -0,0 +1,73 @@ +import json +import logging + +import unittest + +from gas_proxy.web3 import GasMiddleware + +logging.basicConfig(level=logging.DEBUG) + + +class Mocket: + + + def __init__(self): + self.last_id = None + self.response = None + self.active = True + + + def toggle(self): + self.active = not self.active + + + def send(self, d): + if not self.active: + raise ConnectionError('socket deactivated') + o = json.loads(d) + self.last_id = o['id'] + r = { + "jsonrpc": "2.0", + "id": self.last_id, + "result": "0x1324", + } + self.response = json.dumps(r) + + def recv(self, c=0): + return self.response + + +class MiddlewareTest(unittest.TestCase): + + + def setUp(self): + self.socket = Mocket() + + def socket_constructor(): + return self.socket + + GasMiddleware.socket_constructor = socket_constructor + + + def tearDown(self): + pass + + + def test_middleware(self): + GasMiddleware.last_gas = "0x2a" + middleware = GasMiddleware(None, None) + + self.socket.toggle() + r = middleware.__call__("eth_gasPrice", []) + self.assertEqual(r['result'], "0x2a") + + self.socket.toggle() + r = middleware.__call__("eth_gasPrice", []) + self.assertEqual(r['result'], "0x1324") + + self.socket.toggle() + r = middleware.__call__("eth_gasPrice", []) + self.assertEqual(r['result'], "0x1324") + +if __name__ == '__main__': + unittest.main()