Iterating on BlockchainInterface Composition for accuracy; Use interface cache.

pull/1029/head
Kieran Prasch 2019-06-19 11:40:18 -07:00 committed by David Núñez
parent aebac62dd8
commit b59d12d89c
5 changed files with 128 additions and 133 deletions

View File

@ -383,11 +383,11 @@ class UserEscrowAgent(EthereumContractAgent):
def __init__(self,
beneficiary: str,
blockchain: BlockchainInterface = None,
blockchain: BlockchainInterface,
allocation_registry: AllocationRegistry = None,
*args, **kwargs) -> None:
self.blockchain = blockchain or BlockchainInterface.connect()
self.blockchain = blockchain
self.__allocation_registry = allocation_registry or self.__allocation_registry()
self.__beneficiary = beneficiary

View File

@ -14,12 +14,11 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from eth_tester.exceptions import TransactionFailed
from eth_utils import is_checksum_address
from typing import Tuple, Dict
from web3.contract import Contract
from constant_sorrow.constants import CONTRACT_NOT_DEPLOYED, NO_DEPLOYER_CONFIGURED, NO_BENEFICIARY
from eth_utils import is_checksum_address
from web3.contract import Contract
from nucypher.blockchain.economics import TokenEconomics, SlashingEconomics
from nucypher.blockchain.eth.agents import (
@ -29,10 +28,8 @@ from nucypher.blockchain.eth.agents import (
PolicyAgent,
UserEscrowAgent,
AdjudicatorAgent)
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
from nucypher.blockchain.eth.registry import AllocationRegistry
from .interfaces import BlockchainInterface
class ContractDeployer:
@ -49,7 +46,7 @@ class ContractDeployer:
class ContractNotDeployed(ContractDeploymentError):
pass
def __init__(self, deployer_address: str, blockchain: BlockchainInterface) -> None:
def __init__(self, deployer_address: str, blockchain: BlockchainDeployerInterface) -> None:
self.blockchain = blockchain
self.deployment_transactions = CONTRACT_NOT_DEPLOYED
@ -326,7 +323,6 @@ class StakingEscrowDeployer(ContractDeployer):
# Raise if not all-systems-go
self.check_deployment_readiness()
origin_args = {'from': self.deployer_address, 'gas': 5000000} # TODO: Gas management
existing_bare_contract = self.blockchain.get_contract_by_name(name=self.contract_name,
proxy_name=self.__proxy_deployer.contract_name,
@ -347,13 +343,12 @@ class StakingEscrowDeployer(ContractDeployer):
self._contract = wrapped_escrow_contract
# 4 - Set the new Dispatcher target
upgrade_tx_hash = dispatcher_deployer.retarget(new_target=the_escrow_contract.address,
upgrade_receipt = dispatcher_deployer.retarget(new_target=the_escrow_contract.address,
existing_secret_plaintext=existing_secret_plaintext,
new_secret_hash=new_secret_hash)
_upgrade_receipt = self.blockchain.wait_for_receipt(upgrade_tx_hash)
# Respond
upgrade_transaction = {'deploy': deploy_txhash, 'retarget': upgrade_tx_hash}
upgrade_transaction = {'deploy': deploy_txhash, 'retarget': upgrade_receipt['transactionHash']}
return upgrade_transaction
def rollback(self, existing_secret_plaintext: bytes, new_secret_hash: bytes):
@ -365,11 +360,11 @@ class StakingEscrowDeployer(ContractDeployer):
deployer_address=self.deployer_address,
bare=True) # acquire agency for the dispatcher itself.
rollback_txhash = dispatcher_deployer.rollback(existing_secret_plaintext=existing_secret_plaintext,
rollback_receipt = dispatcher_deployer.rollback(existing_secret_plaintext=existing_secret_plaintext,
new_secret_hash=new_secret_hash)
_rollback_receipt = self.blockchain.wait_for_receipt(txhash=rollback_txhash)
return rollback_txhash
txhash = rollback_receipt['transactionHash']
return txhash
def make_agent(self) -> EthereumContractAgent:
self.__check_policy_manager() # Ensure the PolicyManager contract has already been initialized
@ -454,10 +449,9 @@ class PolicyManagerDeployer(ContractDeployer):
policy_manager_contract, deploy_txhash = self.blockchain.deploy_contract(self.contract_name,
self.staking_agent.contract_address)
upgrade_tx_hash = proxy_deployer.retarget(new_target=policy_manager_contract.address,
upgrade_receipt = proxy_deployer.retarget(new_target=policy_manager_contract.address,
existing_secret_plaintext=existing_secret_plaintext,
new_secret_hash=new_secret_hash)
_upgrade_receipt = self.blockchain.wait_for_receipt(upgrade_tx_hash)
# Wrap the escrow contract
wrapped_policy_manager_contract = self.blockchain._wrap_contract(proxy_deployer.contract,
@ -465,12 +459,9 @@ class PolicyManagerDeployer(ContractDeployer):
# Switch the contract for the wrapped one
policy_manager_contract = wrapped_policy_manager_contract
self._contract = policy_manager_contract
upgrade_transaction = {'deploy': deploy_txhash,
'retarget': upgrade_tx_hash}
upgrade_transaction = {'deploy': deploy_txhash, 'retarget': upgrade_receipt['transactionHash']}
return upgrade_transaction
def rollback(self, existing_secret_plaintext: bytes, new_secret_hash: bytes):
@ -482,10 +473,10 @@ class PolicyManagerDeployer(ContractDeployer):
deployer_address=self.deployer_address,
bare=True) # acquire agency for the dispatcher itself.
rollback_txhash = dispatcher_deployer.rollback(existing_secret_plaintext=existing_secret_plaintext,
rollback_receipt = dispatcher_deployer.rollback(existing_secret_plaintext=existing_secret_plaintext,
new_secret_hash=new_secret_hash)
_rollback_receipt = self.blockchain.wait_for_receipt(txhash=rollback_txhash)
rollback_txhash = rollback_receipt['transactionHash']
return rollback_txhash
@ -552,7 +543,7 @@ class UserEscrowProxyDeployer(ContractDeployer):
deployer_address=self.deployer_address,
target_contract=user_escrow_proxy_contract)
proxy_deployment_txhashes = proxy_deployer.deploy(secret_hash=secret_hash, gas_limit=gas_limit)
_proxy_deployment_txhashes = proxy_deployer.deploy(secret_hash=secret_hash, gas_limit=gas_limit)
deployment_transactions['proxy_deployment'] = proxy_deployment_txhash
return deployment_transactions
@ -603,10 +594,10 @@ class UserEscrowProxyDeployer(ContractDeployer):
deployer_address=self.deployer_address,
bare=True) # acquire agency for the dispatcher itself.
rollback_txhash = dispatcher_deployer.rollback(existing_secret_plaintext=existing_secret_plaintext,
_rollback_receipt = dispatcher_deployer.rollback(existing_secret_plaintext=existing_secret_plaintext,
new_secret_hash=new_secret_hash)
_rollback_receipt = self.blockchain.wait_for_receipt(txhash=rollback_txhash)
rollback_txhash = _rollback_receipt['transactionHash']
return rollback_txhash
@ -642,6 +633,7 @@ class UserEscrowDeployer(ContractDeployer):
"""Relinquish ownership of a UserEscrow deployment to the beneficiary"""
if not is_checksum_address(beneficiary_address):
raise self.ContractDeploymentError("{} is not a valid checksum address.".format(beneficiary_address))
# TODO: #413, #842 - Gas Management
payload = {'from': self.deployer_address, 'gas': 500_000, 'gasPrice': self.blockchain.client.gasPrice}
transfer_owner_function = self.contract.functions.transferOwnership(beneficiary_address)
transfer_owner_receipt = self.blockchain.send_transaction(transaction_function=transfer_owner_function,
@ -660,7 +652,7 @@ class UserEscrowDeployer(ContractDeployer):
allocation_receipts['approve'] = approve_receipt
# Deposit
# TODO: Gas management
# TODO: #413, #842 - Gas Management
args = {'from': self.deployer_address,
'gasPrice': self.blockchain.client.gasPrice,
'gas': 200_000}
@ -777,10 +769,9 @@ class AdjudicatorDeployer(ContractDeployer):
self.staking_agent.contract_address,
*self.__economics.deployment_parameters)
upgrade_tx_hash = proxy_deployer.retarget(new_target=adjudicator_contract.address,
upgrade_receipt = proxy_deployer.retarget(new_target=adjudicator_contract.address,
existing_secret_plaintext=existing_secret_plaintext,
new_secret_hash=new_secret_hash)
_upgrade_receipt = self.blockchain.wait_for_receipt(upgrade_tx_hash)
# Wrap the escrow contract
wrapped_adjudicator_contract = self.blockchain._wrap_contract(proxy_deployer.contract, target_contract=adjudicator_contract)
@ -790,7 +781,7 @@ class AdjudicatorDeployer(ContractDeployer):
self._contract = policy_manager_contract
upgrade_transaction = {'deploy': deploy_txhash, 'retarget': upgrade_tx_hash}
upgrade_transaction = {'deploy': deploy_txhash, 'retarget': upgrade_receipt['transactionHash']}
return upgrade_transaction
def rollback(self, existing_secret_plaintext: bytes, new_secret_hash: bytes):

View File

@ -49,6 +49,7 @@ from nucypher.blockchain.eth.providers import (
)
from nucypher.blockchain.eth.registry import EthereumContractRegistry
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
from nucypher.crypto.powers import BlockchainPower
Web3Providers = Union[IPCProvider, WebsocketProvider, HTTPProvider, EthereumTester]
@ -62,10 +63,11 @@ class BlockchainInterface:
TIMEOUT = 180 # seconds
NULL_ADDRESS = '0x' + '0' * 40
_instance = NO_BLOCKCHAIN_CONNECTION
process = NO_PROVIDER_PROCESS.bool_value(False)
Web3 = Web3
_contract_factory = ConciseContract
_contract_factory = Contract
class InterfaceError(Exception):
pass
@ -84,7 +86,7 @@ class BlockchainInterface:
sync_now: bool = True,
provider_process: NuCypherGethProcess = NO_PROVIDER_PROCESS,
provider_uri: str = NO_BLOCKCHAIN_CONNECTION,
transacting_power = READ_ONLY_INTERFACE,
transacting_power: BlockchainPower = READ_ONLY_INTERFACE,
provider: Web3Providers = NO_BLOCKCHAIN_CONNECTION,
registry: EthereumContractRegistry = None,
fetch_registry: bool = True):
@ -162,11 +164,13 @@ class BlockchainInterface:
self.transacting_power = transacting_power
self.registry = registry
self.__connect(provider=provider,
self.connect(provider=provider,
provider_uri=provider_uri,
fetch_registry=fetch_registry,
sync_now=sync_now)
BlockchainInterface._instance = self
def __repr__(self):
r = '{name}({uri})'.format(name=self.__class__.__name__, uri=self.provider_uri)
return r
@ -189,8 +193,13 @@ class BlockchainInterface:
def disconnect(self):
if self._provider_process:
self._provider_process.stop()
self._provider_process = NO_BLOCKCHAIN_CONNECTION
self._provider_process = NO_PROVIDER_PROCESS
self._provider = NO_BLOCKCHAIN_CONNECTION
BlockchainInterface._instance = NO_BLOCKCHAIN_CONNECTION
@classmethod
def reconnect(cls, *args, **kwargs) -> 'BlockchainInterface':
return cls._instance
def attach_middleware(self):
@ -199,7 +208,7 @@ class BlockchainInterface:
self.log.debug('Injecting POA middleware at layer 0')
self.client.inject_middleware(geth_poa_middleware, layer=0)
def __connect(self,
def connect(self,
provider: Web3Providers = None,
provider_uri: str = None,
fetch_registry: bool = True,
@ -239,6 +248,10 @@ class BlockchainInterface:
return self.is_connected
@property
def provider(self) -> Union[IPCProvider, WebsocketProvider, HTTPProvider]:
return self._provider
def _attach_provider(self, provider: Web3Providers = None, provider_uri: str = None) -> None:
"""
https://web3py.readthedocs.io/en/latest/providers.html#providers
@ -322,7 +335,7 @@ class BlockchainInterface:
if deployment_status is 0:
failure = f"Transaction transmitted, but receipt returned status code 0. " \
f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
raise self.DeploymentFailed(failure)
raise self.InterfaceError(failure)
if deployment_status is UNKNOWN_TX_STATUS:
self.log.info(f"Unknown transaction status for {txhash} (receipt did not contain a status field)")
@ -330,13 +343,71 @@ class BlockchainInterface:
# Secondary check TODO: Is this a sensible check?
tx = self.client.w3.eth.getTransaction(txhash)
if tx["gas"] == receipt["gasUsed"]:
raise self.DeploymentFailed(f"Deployment transaction consumed 100% of transaction gas."
raise self.InterfaceError(f"Transaction consumed 100% of transaction gas."
f"Full receipt: \n {pprint.pformat(receipt, indent=2)}")
return receipt
def read(self, query_function: ContractFunction):
raise NotImplementedError # TODO
def get_contract_by_name(self,
name: str,
proxy_name: str = None,
use_proxy_address: bool = True
) -> Union[Contract, List[tuple]]:
"""
Instantiate a deployed contract from registry data,
and assimilate it with it's proxy if it is upgradeable,
or return all registered records if use_proxy_address is False.
"""
target_contract_records = self.registry.search(contract_name=name)
if not target_contract_records:
raise self.UnknownContract(f"No such contract records with name {name}.")
if proxy_name: # It's upgradeable
# Lookup proxies; Search fot a published proxy that targets this contract record
proxy_records = self.registry.search(contract_name=proxy_name)
results = list()
for proxy_name, proxy_addr, proxy_abi in proxy_records:
proxy_contract = self.client.w3.eth.contract(abi=proxy_abi,
address=proxy_addr,
ContractFactoryClass=self._contract_factory)
# Read this dispatchers target address from the blockchain
proxy_live_target_address = proxy_contract.functions.target().call()
for target_name, target_addr, target_abi in target_contract_records:
if target_addr == proxy_live_target_address:
if use_proxy_address:
pair = (proxy_addr, target_abi)
else:
pair = (proxy_live_target_address, target_abi)
else:
continue
results.append(pair)
if len(results) > 1:
address, abi = results[0]
message = "Multiple {} deployments are targeting {}".format(proxy_name, address)
raise self.InterfaceError(message.format(name))
else:
selected_address, selected_abi = results[0]
else: # It's not upgradeable
if len(target_contract_records) != 1:
m = "Multiple records registered for non-upgradeable contract {}"
raise self.InterfaceError(m.format(name))
_target_contract_name, selected_address, selected_abi = target_contract_records[0]
# Create the contract from selected sources
unified_contract = self.client.w3.eth.contract(abi=selected_abi,
address=selected_address,
ContractFactoryClass=self._contract_factory)
return unified_contract
class BlockchainDeployerInterface(BlockchainInterface):
@ -460,9 +531,7 @@ class BlockchainDeployerInterface(BlockchainInterface):
ContractFactoryClass=Contract)
return contract
def _wrap_contract(self,
wrapper_contract: Contract,
target_contract: Contract) -> Contract:
def _wrap_contract(self, wrapper_contract: Contract, target_contract: Contract) -> Contract:
"""
Used for upgradeable contracts; Returns a new contract object assembled
with its own address but the abi of the other.
@ -474,7 +543,7 @@ class BlockchainDeployerInterface(BlockchainInterface):
ContractFactoryClass=self._contract_factory)
return wrapped_contract
def get_proxy(self, target_address: str, proxy_name: str):
def get_proxy(self, target_address: str, proxy_name: str) -> Contract:
# Lookup proxies; Search for a registered proxy that targets this contract record
records = self.registry.search(contract_name=proxy_name)
@ -499,64 +568,3 @@ class BlockchainDeployerInterface(BlockchainInterface):
return dispatchers[0]
except IndexError:
raise self.UnknownContract(f"No registered Dispatcher deployments target {target_address}")
def get_contract_by_name(self,
name: str,
proxy_name: str = None,
use_proxy_address: bool = True
) -> Union[Contract, List[tuple]]:
"""
Instantiate a deployed contract from registry data,
and assimilate it with it's proxy if it is upgradeable,
or return all registered records if use_proxy_address is False.
"""
target_contract_records = self.registry.search(contract_name=name)
if not target_contract_records:
raise self.UnknownContract(f"No such contract records with name {name}.")
if proxy_name: # It's upgradeable
# Lookup proxies; Search fot a published proxy that targets this contract record
proxy_records = self.registry.search(contract_name=proxy_name)
results = list()
for proxy_name, proxy_addr, proxy_abi in proxy_records:
proxy_contract = self.client.w3.eth.contract(abi=proxy_abi,
address=proxy_addr,
ContractFactoryClass=self._contract_factory)
# Read this dispatchers target address from the blockchain
proxy_live_target_address = proxy_contract.functions.target().call()
for target_name, target_addr, target_abi in target_contract_records:
if target_addr == proxy_live_target_address:
if use_proxy_address:
pair = (proxy_addr, target_abi)
else:
pair = (proxy_live_target_address, target_abi)
else:
continue
results.append(pair)
if len(results) > 1:
address, abi = results[0]
message = "Multiple {} deployments are targeting {}".format(proxy_name, address)
raise self.InterfaceError(message.format(name))
else:
selected_address, selected_abi = results[0]
else: # It's not upgradeable
if len(target_contract_records) != 1:
m = "Multiple records registered for non-upgradeable contract {}"
raise self.InterfaceError(m.format(name))
_target_contract_name, selected_address, selected_abi = target_contract_records[0]
# Create the contract from selected sources
unified_contract = self.client.w3.eth.contract(abi=selected_abi,
address=selected_address,
ContractFactoryClass=self._contract_factory)
return unified_contract

View File

@ -88,20 +88,18 @@ def deploy(click_config,
# Connect to Blockchain
#
# Establish a contract registry from disk if specified
registry, registry_filepath = None, (registry_outfile or registry_infile)
if registry_filepath is not None:
registry = EthereumContractRegistry(registry_filepath=registry_filepath)
if geth:
# Spawn geth child process
ETH_NODE = NuCypherGethDevnetProcess(config_root=config_root)
ETH_NODE.ensure_account_exists(password=click_config.get_password(confirm=True))
if not ETH_NODE.initialized:
ETH_NODE.initialize_blockchain()
ETH_NODE.start() # TODO: Graceful shutdown
provider_uri = ETH_NODE.provider_uri
# Establish a contract registry from disk if specified
registry, registry_filepath = None, (registry_outfile or registry_infile)
if registry_filepath is not None:
registry = EthereumContractRegistry(registry_filepath=registry_filepath)
# Deployment-tuned blockchain connection
blockchain = BlockchainDeployerInterface(provider_uri=provider_uri,
poa=poa,
@ -110,9 +108,9 @@ def deploy(click_config,
fetch_registry=False,
sync_now=sync)
# TODO: Integrate with Deployer Actor (Character)
blockchain.transacting_power = BlockchainPower(client=blockchain.client)
#
# Deployment Actor
#

View File

@ -65,6 +65,8 @@ class TesterBlockchain(BlockchainDeployerInterface):
Blockchain subclass with additional test utility methods and options.
"""
_instance = None
_PROVIDER_URI = 'tester://pyevm'
TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts')
_compiler = SolidityCompiler(test_contract_dir=TEST_CONTRACTS_DIR)
@ -202,17 +204,13 @@ class TesterBlockchain(BlockchainDeployerInterface):
f"| period {epoch_to_period(epoch=end_timestamp)} "
f"| epoch {end_timestamp}")
@property
def provider(self) -> Union[IPCProvider, WebsocketProvider, HTTPProvider]:
return self._provider
@classmethod
def bootstrap_network(cls) -> Tuple['TesterBlockchain', Dict[str, EthereumContractAgent]]:
testerchain = cls.connect()
testerchain = cls.connect() # FIXME
origin = testerchain.client.accounts[0]
deployer = Deployer(blockchain=testerchain, deployer_address=origin, bare=True)
_txhashes, agents = deployer.deploy_network_contracts(staker_secret=STAKING_ESCROW_DEPLOYMENT_SECRET,
policy_secret=POLICY_MANAGER_DEPLOYMENT_SECRET,
adjudicator_secret=ADJUDICATOR_DEPLOYMENT_SECRET,