Support dynamicly added, lazy, multiple blockchain providers

pull/380/head
Kieran Prasch 2018-08-10 12:46:09 -07:00 committed by Kieran R. Prasch
parent 674410d1f1
commit b5e029a4ca
2 changed files with 55 additions and 72 deletions

View File

@ -1,6 +1,6 @@
import json import json
import os import os
from typing import Tuple from typing import Tuple, Union
from urllib.parse import urlparse from urllib.parse import urlparse
from constant_sorrow import constants from constant_sorrow import constants
@ -8,6 +8,7 @@ from eth_keys.datatypes import PublicKey, Signature
from eth_utils import to_canonical_address from eth_utils import to_canonical_address
from web3 import Web3, WebsocketProvider, HTTPProvider, IPCProvider from web3 import Web3, WebsocketProvider, HTTPProvider, IPCProvider
from web3.contract import Contract from web3.contract import Contract
from web3.providers import BaseProvider
from web3.providers.eth_tester.main import EthereumTesterProvider from web3.providers.eth_tester.main import EthereumTesterProvider
from nucypher.blockchain.eth.sol.compile import SolidityCompiler from nucypher.blockchain.eth.sol.compile import SolidityCompiler
@ -215,15 +216,22 @@ class ControlCircumflex:
# #
# Providers # Providers
# #
self.w3 = constants.NO_BLOCKCHAIN_CONNECTION
self.__providers = providers if providers is not None else constants.NO_BLOCKCHAIN_CONNECTION
if provider_uri and providers: if provider_uri and providers:
raise self.InterfaceError("Pass a provider URI string, or a list of provider instances.") raise self.InterfaceError("Pass a provider URI string, or a list of provider instances.")
elif provider_uri:
self.provider_uri = provider_uri self.provider_uri = provider_uri
self._providers = list() if providers is None else providers self.add_provider(provider_uri=provider_uri)
elif providers:
# If custom __providers are not injected... self.provider_uri = constants.MANUAL_PROVIDERS_SET
self.w3 = constants.NO_BLOCKCHAIN_CONNECTION for provider in providers:
if autoconnect is True: self.add_provider(provider)
self.connect(provider_uri=self.provider_uri) else:
# TODO: Emit a warning / log: No provider supplied for blockchain interface
pass
# if a SolidityCompiler class instance was passed, compile from solidity source code # if a SolidityCompiler class instance was passed, compile from solidity source code
recompile = True if compiler is not None else False recompile = True if compiler is not None else False
@ -231,7 +239,7 @@ class ControlCircumflex:
self.__sol_compiler = compiler self.__sol_compiler = compiler
# Setup the registry and base contract factory cache # Setup the registry and base contract factory cache
registry = registry if registry is not None else EthereumContractRegistry() registry = registry if registry is not None else EthereumContractRegistry().from_config()
self._registry = registry self._registry = registry
if self.__recompile is True: if self.__recompile is True:
@ -239,64 +247,38 @@ class ControlCircumflex:
interfaces = self.__sol_compiler.compile() interfaces = self.__sol_compiler.compile()
self.__raw_contract_cache = interfaces self.__raw_contract_cache = interfaces
def connect(self, # Auto-connect
provider_uri: str = None, self.autoconnect = autoconnect
providers: list = None): if self.autoconnect is True:
self.connect()
if provider_uri is None and not providers: def connect(self):
raise self.InterfaceError("No URI supplied.")
if provider_uri and not providers: if self.__providers is constants.NO_BLOCKCHAIN_CONNECTION:
uri_breakdown = urlparse(provider_uri) raise self.InterfaceError("There are no configured blockchain providers")
elif providers and not provider_uri:
raise NotImplementedError
else:
raise self.InterfaceError("Pass a provider URI string or a list of providers, not both.")
# stub
if providers is None:
providers = list()
# IPC
if uri_breakdown.scheme == 'ipc':
provider = IPCProvider(ipc_path=uri_breakdown.path, timeout=self.timeout)
# Websocket
elif uri_breakdown.scheme == 'ws':
provider = WebsocketProvider(endpoint_uri=provider_uri)
raise NotImplementedError
# HTTP
elif uri_breakdown.scheme in ('http', 'https'):
provider = HTTPProvider(endpoint_uri=provider_uri)
raise NotImplementedError
else:
raise self.InterfaceError("'{}' is not a blockchain provider protocol".format(uri_breakdown.scheme))
providers.append(provider)
# Connect # Connect
self._providers = providers web3_instance = Web3(providers=self.__providers) # Instantiate Web3 object with provider
web3_instance = Web3(providers=self._providers) # Instantiate Web3 object with provider
self.w3 = web3_instance self.w3 = web3_instance
# Check connection # Check connection
if not self.is_connected: if not self.is_connected:
raise self.InterfaceError('Failed to connect to {}'.format(provider_uri)) raise self.InterfaceError('Failed to connect to providers: {}'.format(self.__providers))
return True return True
@classmethod @classmethod
def from_config(cls, filepath=None, registry_filepath: str=None) -> 'ControlCircumflex': def from_config(cls, filepath=None) -> 'ControlCircumflex':
# Parse
filepath = filepath if filepath is None else DEFAULT_INI_FILEPATH filepath = filepath if filepath is None else DEFAULT_INI_FILEPATH
payload = parse_blockchain_config(filepath=filepath) payload = parse_blockchain_config(filepath=filepath)
# Init deps
compiler = SolidityCompiler() if payload['compile'] else None compiler = SolidityCompiler() if payload['compile'] else None
registry = EthereumContractRegistry.from_config(filepath=filepath) registry = EthereumContractRegistry.from_config(filepath=filepath)
interface_class = ControlCircumflex if not payload['deploy'] else DeployerCircumflex interface_class = ControlCircumflex if not payload['deploy'] else DeployerCircumflex
# init class
circumflex = interface_class(timeout=payload['timeout'], circumflex = interface_class(timeout=payload['timeout'],
provider_uri=payload['provider_uri'], provider_uri=payload['provider_uri'],
compiler=compiler, compiler=compiler,
@ -320,35 +302,36 @@ class ControlCircumflex:
"""Return node version information""" """Return node version information"""
return self.w3.version.node # type of connected node return self.w3.version.node # type of connected node
def add_provider(self, provider=None, endpoint_uri: str=None, def add_provider(self,
websocket=False, ipc_path=None, timeout=None) -> None: provider: Union[IPCProvider, WebsocketProvider, HTTPProvider] = None,
provider_uri: str = None,
timeout: int = None) -> None:
if provider is None: if not provider_uri and not provider:
raise self.InterfaceError("No URI or provider instances supplied.")
# Validate parameters if provider_uri and not provider:
if websocket and not endpoint_uri: uri_breakdown = urlparse(provider_uri)
if ipc_path is not None:
raise self.InterfaceError("Use either HTTP/Websocket or IPC params, not both.")
raise self.InterfaceError('Must pass endpoint_uri when using websocket __providers.')
if ipc_path is not None: # IPC
if endpoint_uri or websocket: if uri_breakdown.scheme == 'ipc':
raise self.InterfaceError("Use either HTTP/Websocket or IPC params, not both.") provider = IPCProvider(ipc_path=uri_breakdown.path, timeout=timeout)
# Websocket
elif uri_breakdown.scheme == 'ws':
provider = WebsocketProvider(endpoint_uri=provider_uri)
# HTTP
elif uri_breakdown.scheme in ('http', 'https'):
provider = HTTPProvider(endpoint_uri=provider_uri)
# HTTP / Websocket Provider
if endpoint_uri is not None:
if websocket is True:
provider = WebsocketProvider(endpoint_uri)
else: else:
provider = HTTPProvider(endpoint_uri) raise self.InterfaceError("'{}' is not a blockchain provider protocol".format(uri_breakdown.scheme))
# IPC Provider # lazy
elif ipc_path: if self.__providers is constants.NO_BLOCKCHAIN_CONNECTION:
provider = IPCProvider(ipc_path=ipc_path, testnet=False, timeout=timeout) self.__providers = list()
else: self.__providers.append(provider)
raise self.InterfaceError("Invalid interface parameters. Pass endpoint_uri or ipc_path")
self._providers.append(provider)
def get_contract_factory(self, contract_name) -> Contract: def get_contract_factory(self, contract_name) -> Contract:
"""Retrieve compiled interface data from the cache and return web3 contract""" """Retrieve compiled interface data from the cache and return web3 contract"""

View File

@ -29,7 +29,7 @@ from nucypher.utilities.network import MockRestMiddleware
# #
# Setup # Setup
# #
from nucypher_cli.main import BASE_DIR
test_contract_dir = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts') test_contract_dir = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts')
constants.TEST_CONTRACTS_DIR(test_contract_dir) constants.TEST_CONTRACTS_DIR(test_contract_dir)