diff --git a/README.md b/README.md new file mode 100644 index 0000000..be10ebb --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Chainwalker Utilities + +This repo is home to Flipsides chainwalkers python utilities package. A central repo that packeges up various python modules that can be used across chainwalkers projects. + +## Modules + +### Tendermint Utils + +Collection of common methods used to interact with tendermint based chains (Cosmos, Kava, Binance) + +### Eth utils + +Collection of common methods used across eth based chains + +## Install + +`pip install git+ssh://git@github.com/FlipsideCrypto/chainwalkers-utils.git` \ No newline at end of file diff --git a/build/lib/eth_utils/__init__.py b/build/lib/eth_utils/__init__.py new file mode 100644 index 0000000..cdd4ae2 --- /dev/null +++ b/build/lib/eth_utils/__init__.py @@ -0,0 +1 @@ +from eth_utils.jsonrpc import JsonRpcCaller diff --git a/build/lib/eth_utils/decimals.py b/build/lib/eth_utils/decimals.py new file mode 100644 index 0000000..2f05b5a --- /dev/null +++ b/build/lib/eth_utils/decimals.py @@ -0,0 +1,7 @@ +def hex_to_decimal(hex_value): + if hex_value == '0x': + return 0 + return int(hex_value, 16) + +def decimal_to_hex(decimal): + return hex(decimal) \ No newline at end of file diff --git a/build/lib/eth_utils/jsonrpc.py b/build/lib/eth_utils/jsonrpc.py new file mode 100644 index 0000000..f452cd6 --- /dev/null +++ b/build/lib/eth_utils/jsonrpc.py @@ -0,0 +1,62 @@ +import requests +import json +import base64 +import time + +class RpcCallFailedException(Exception): + pass + +class JsonRpcCaller(object): + + def __init__(self, node_url, user=None, password=None, tls=False, tlsVerify=False): + self.url = node_url + self.user = user + self.password = password + self.tls = tls + self.tlsVerify = tlsVerify + + def _make_rpc_call(self, headers, payload, json): + try: + response = requests.post( + self.url, + headers=headers, + data=payload, + json=json, + verify=(self.tls and self.tlsVerify) + ) + except Exception as e: + raise RpcCallFailedException(e) + + if response.status_code != 200: + raise RpcCallFailedException("Invalid status code: %s" % response.status_code) + + responseJson = response.json(parse_float=lambda f: f) + + if type(responseJson) != list: + if "error" in responseJson and responseJson["error"] is not None: + raise RpcCallFailedException("RPC call error: %s" % responseJson["error"]) + else: + return responseJson.get('result') + else: + result = [] + for subResult in responseJson: + if "error" in subResult and subResult["error"] is not None: + raise RpcCallFailedException("RPC call error: %s" % subResult["error"]) + else: + result.append(subResult["result"]) + return result + + def call(self, method, params=None, query=None): + if params is None: + params = [] + headers = {'content-type': 'application/json'} + payload = json.dumps({"jsonrpc": "2.0", "id": "0", "method": method, "params": params}) + if query: # GQL Hack + return self._make_rpc_call(headers, payload=None, json={'query': query}) + return self._make_rpc_call(headers, payload, json=None) + + def bulk_call(self, methodParamsTuples): + headers = {'content-type': 'application/json'} + payload = json.dumps([{"jsonrpc": "2.0", "id": "0", "method": method, "params": params} + for method, params in methodParamsTuples]) + return self._make_rpc_call(headers, payload) \ No newline at end of file diff --git a/tendermint/__init__.py b/build/lib/tendermint_utils/__init__.py similarity index 100% rename from tendermint/__init__.py rename to build/lib/tendermint_utils/__init__.py diff --git a/tendermint/rpc.py b/build/lib/tendermint_utils/rpc.py similarity index 100% rename from tendermint/rpc.py rename to build/lib/tendermint_utils/rpc.py diff --git a/chainwalkers_utils.egg-info/PKG-INFO b/chainwalkers_utils.egg-info/PKG-INFO index fc8c369..da02d7e 100644 --- a/chainwalkers_utils.egg-info/PKG-INFO +++ b/chainwalkers_utils.egg-info/PKG-INFO @@ -1,4 +1,4 @@ -Metadata-Version: 1.0 +Metadata-Version: 2.1 Name: chainwalkers-utils Version: 0.0.4 Summary: Collection of utilities to be used across chainwalkers repos @@ -6,5 +6,22 @@ Home-page: git@github.com:FlipsideCrypto/chainwalkers-utils.git Author: Brian Ford Author-email: brian@flipsidecrypto.com License: unlicense -Description: UNKNOWN +Description: # Chainwalker Utilities + + This repo is home to Flipsides chainwalkers python utilities package. A central repo that packeges up various python modules that can be used across chainwalkers projects. + + ## Modules + + ### Tendermint Utils + + Collection of common methods used to interact with tendermint based chains (Cosmos, Kava, Binance) + + ### Eth utils + + Collection of common methods used across eth based chains + + ## Install + + `pip install git+ssh://git@github.com/FlipsideCrypto/chainwalkers-utils.git` Platform: UNKNOWN +Description-Content-Type: text/markdown diff --git a/chainwalkers_utils.egg-info/SOURCES.txt b/chainwalkers_utils.egg-info/SOURCES.txt index d9842ea..334b411 100644 --- a/chainwalkers_utils.egg-info/SOURCES.txt +++ b/chainwalkers_utils.egg-info/SOURCES.txt @@ -1,8 +1,12 @@ +README.md setup.py chainwalkers_utils.egg-info/PKG-INFO chainwalkers_utils.egg-info/SOURCES.txt chainwalkers_utils.egg-info/dependency_links.txt chainwalkers_utils.egg-info/not-zip-safe chainwalkers_utils.egg-info/top_level.txt -tendermint/__init__.py -tendermint/rpc.py \ No newline at end of file +eth_utils/__init__.py +eth_utils/decimals.py +eth_utils/jsonrpc.py +tendermint_utils/__init__.py +tendermint_utils/rpc.py \ No newline at end of file diff --git a/chainwalkers_utils.egg-info/top_level.txt b/chainwalkers_utils.egg-info/top_level.txt index 9059c68..4c82be1 100644 --- a/chainwalkers_utils.egg-info/top_level.txt +++ b/chainwalkers_utils.egg-info/top_level.txt @@ -1 +1,2 @@ -tendermint +eth_utils +tendermint_utils diff --git a/dist/chainwalkers_utils-0.0.4-py3.7.egg b/dist/chainwalkers_utils-0.0.4-py3.7.egg index 1e52836..fc2dba8 100644 Binary files a/dist/chainwalkers_utils-0.0.4-py3.7.egg and b/dist/chainwalkers_utils-0.0.4-py3.7.egg differ diff --git a/eth_utils/__init__.py b/eth_utils/__init__.py new file mode 100644 index 0000000..cdd4ae2 --- /dev/null +++ b/eth_utils/__init__.py @@ -0,0 +1 @@ +from eth_utils.jsonrpc import JsonRpcCaller diff --git a/eth_utils/decimals.py b/eth_utils/decimals.py new file mode 100644 index 0000000..2f05b5a --- /dev/null +++ b/eth_utils/decimals.py @@ -0,0 +1,7 @@ +def hex_to_decimal(hex_value): + if hex_value == '0x': + return 0 + return int(hex_value, 16) + +def decimal_to_hex(decimal): + return hex(decimal) \ No newline at end of file diff --git a/eth_utils/jsonrpc.py b/eth_utils/jsonrpc.py new file mode 100644 index 0000000..f452cd6 --- /dev/null +++ b/eth_utils/jsonrpc.py @@ -0,0 +1,62 @@ +import requests +import json +import base64 +import time + +class RpcCallFailedException(Exception): + pass + +class JsonRpcCaller(object): + + def __init__(self, node_url, user=None, password=None, tls=False, tlsVerify=False): + self.url = node_url + self.user = user + self.password = password + self.tls = tls + self.tlsVerify = tlsVerify + + def _make_rpc_call(self, headers, payload, json): + try: + response = requests.post( + self.url, + headers=headers, + data=payload, + json=json, + verify=(self.tls and self.tlsVerify) + ) + except Exception as e: + raise RpcCallFailedException(e) + + if response.status_code != 200: + raise RpcCallFailedException("Invalid status code: %s" % response.status_code) + + responseJson = response.json(parse_float=lambda f: f) + + if type(responseJson) != list: + if "error" in responseJson and responseJson["error"] is not None: + raise RpcCallFailedException("RPC call error: %s" % responseJson["error"]) + else: + return responseJson.get('result') + else: + result = [] + for subResult in responseJson: + if "error" in subResult and subResult["error"] is not None: + raise RpcCallFailedException("RPC call error: %s" % subResult["error"]) + else: + result.append(subResult["result"]) + return result + + def call(self, method, params=None, query=None): + if params is None: + params = [] + headers = {'content-type': 'application/json'} + payload = json.dumps({"jsonrpc": "2.0", "id": "0", "method": method, "params": params}) + if query: # GQL Hack + return self._make_rpc_call(headers, payload=None, json={'query': query}) + return self._make_rpc_call(headers, payload, json=None) + + def bulk_call(self, methodParamsTuples): + headers = {'content-type': 'application/json'} + payload = json.dumps([{"jsonrpc": "2.0", "id": "0", "method": method, "params": params} + for method, params in methodParamsTuples]) + return self._make_rpc_call(headers, payload) \ No newline at end of file diff --git a/setup.py b/setup.py index 04aad3c..45b3a7f 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,14 @@ import setuptools +with open("README.md", "r") as fh: + long_description = fh.read() + setuptools.setup( name='chainwalkers_utils', version='0.0.4', description='Collection of utilities to be used across chainwalkers repos', + long_description=long_description, + long_description_content_type="text/markdown", url='git@github.com:FlipsideCrypto/chainwalkers-utils.git', author='Brian Ford', author_email='brian@flipsidecrypto.com', diff --git a/tendermint_utils/__init__.py b/tendermint_utils/__init__.py new file mode 100644 index 0000000..6e9328b --- /dev/null +++ b/tendermint_utils/__init__.py @@ -0,0 +1 @@ +from tendermint.rpc import TendermintRPC \ No newline at end of file diff --git a/tendermint_utils/rpc.py b/tendermint_utils/rpc.py new file mode 100644 index 0000000..2d874d3 --- /dev/null +++ b/tendermint_utils/rpc.py @@ -0,0 +1,85 @@ +import json +import requests + +class TendermintRPC: + + def __init__(self, node_url): + self.node_url = node_url + + def get_block_height(self): + try: + response = requests.get(self.node_url+ '/abci_info?') + response.raise_for_status() + data = response.json() + return data['result']['response']['last_block_height'] + except Exception as err: + print(f'An error occured retrieving the latest block height: {err}') + + def get_block(self, height): + try: + response = requests.get(self.node_url + '/block?height=' + str(height)) + response.raise_for_status() + data = response.json() + block = self.init_block(data['result']) + + block_results = self.get_block_results(height) + + # Capture transactions and underlying events + block_transactions = self.get_transactions_by_block(height) + if block_transactions['txs']: + for tx in block_transactions['txs']: + block_tx = self.get_transactions_by_hash(tx['hash']) + block.add_transaction(block_tx) + + # Capture begin block events () + if block_results['results']['begin_block']: + for event in block_results['results']['begin_block']['events']: + block.begin_block.append(event) + + block.end_block = block_results['results']['end_block'] + + block_validators = self.get_block_validators(height) + for validator in block_validators['validators']: + block.validators.append(validator) + + return block + except Exception as err: + print(f'An error occured retrieving block: {err}') + + def get_block_results(self, height): + try: + response = requests.get(self.node_url + '/block_results?height=' + str(height)) + response.raise_for_status() + data = response.json() + return data['result'] + except Exception as err: + print(f'An error occured retrieving the results of block height: {err}') + + # Need ANKR to turn on indexing at node level for this call to work properly + def get_transactions_by_block(self, height): + try: + response = requests.get(self.node_url + '/tx_search?query=\"tx.height=' + str(height) + '\"&prove=true') + response.raise_for_status() + data = response.json() + return data['result'] + except Exception as err: + print(f'An error occured retrieving the transactions in block: {err}') + + def get_transactions_by_hash(self, tx_hash): + try: + response = requests.get(self.node_url + '/tx?hash=' + tx_hash) + response.raise_for_status() + data = response.json() + return data + except Exception as err: + print(f'An error occured retrieving the transaction by hash: {err}') + + # Currently returning the following response "Height must be less than or equal to the current blockchain height" + def get_block_validators(self, height): + try: + response = requests.get(self.node_url + '/validators?height=' + str(height)) + response.raise_for_status() + data = response.json() + return data['result'] + except Exception as err: + print(f'An error occured retrieving the validators for block height: {err}') \ No newline at end of file