def _call(self, method: str, *args: Any) -> Any: """Call the remote bitcoin node RPC with a method and parameters Args: method: The bitcoin json rpc method to call args: The arbitrary arguments for the method (in order) Returns: The result from the rpc call Raises: exceptions.InterchainConnectionError: If the remote call returned an error """ # Note: Even though sending json, documentation still says to use text/plain content type header # https://bitcoin.org/en/developer-reference#remote-procedure-calls-rpcs r = requests.post( self.rpc_address, json={ "method": method, "params": list(args), "id": "1", "jsonrpc": "1.0" }, headers={ "Authorization": f"Basic {self.authorization}", "Content-Type": "text/plain" }, timeout=20, ) if r.status_code != 200: raise exceptions.InterchainConnectionError( f"Error from bitcoin node with http status code {r.status_code} | {r.text}" ) response = r.json() if response.get("error") or response.get("errors"): raise exceptions.InterchainConnectionError( f"The RPC call got an error response: {response}") return response["result"]
def ping(self) -> None: """Ping this network to check if the given node is reachable and authorization is correct (raises exception if not)""" response_rpc = self._call_node_rpc("status", {}).json() response_api = self._call_node_api("tokens/BNB").json() if response_rpc.get("error") or response_api.get("error"): raise exceptions.InterchainConnectionError( f"[BINANCE] Node ping checks failed!")
def _call_node_api(self, path: str) -> Any: full_address = f"{self.node_url}:{self.api_port}/api/v1/{path}" try: response = requests.get(full_address, timeout=10) except Exception as e: raise exceptions.InterchainConnectionError(f"Error sending get request to binance node: {e}") _log.debug(f"Binance <- {response.status_code} {response.text}") return response
def _call_node_rpc(self, method: str, params: Dict[str, Any]) -> Any: full_address = f"{self.node_url}:{self.rpc_port}/" body = {"method": method, "jsonrpc": "2.0", "params": params, "id": "dontcare"} _log.debug(f"Binance RPC: -> {full_address} {body}") try: response = requests.post(full_address, json=body, timeout=10) except Exception as e: raise exceptions.InterchainConnectionError(f"Error sending post request to binance node: {e}") _log.debug(f"Binance <- {response.status_code} {response.text}") return response
def _fetch_account(self): """Fetch the account metadata for an address Returns: response containing account metadata """ _log.info(f"[BINANCE] Fetching address metadata for {self.address}") path = f"account/{self.address}" # params expected inside path string try: response = self._call_node_api(path) if response.status_code == 404: _log.warning("[BINANCE] 404 response from Binance node:") _log.warning("[BINANCE] Address not found -- likely has zero funds.") raise exceptions.BadRequest("[BINANCE] Error fetching metadata from 'account' endpoint.") return response.json() except exceptions.InterchainConnectionError: _log.warning("[BINANCE] Non 200 response from Binance node.") _log.warning("[BINANCE] May have been a 500 Bad Request or a 404 Not Found.") raise exceptions.InterchainConnectionError("[BINANCE] Error fetching metadata from 'account' endpoint.")