Formalize upgrade, rollback, and deploy as deploy CLI actions, dehydrate tests and check every upgrade / rollback opportunity.

pull/1040/head
Kieran Prasch 2019-03-26 11:34:47 -07:00
parent 7d64d7c15c
commit 28e5c78e49
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
2 changed files with 133 additions and 91 deletions

View File

@ -34,7 +34,6 @@ from nucypher.config.constants import DEFAULT_CONFIG_ROOT
@click.argument('action')
@click.option('--force', is_flag=True)
@click.option('--poa', help="Inject POA middleware", is_flag=True)
@click.option('--upgrade', help="Upgrade an already deployed contract", is_flag=True)
@click.option('--no-compile', help="Disables solidity contract compilation", is_flag=True)
@click.option('--provider-uri', help="Blockchain provider's URI", type=click.STRING)
@click.option('--config-root', help="Custom configuration directory", type=click.Path())
@ -50,7 +49,6 @@ from nucypher.config.constants import DEFAULT_CONFIG_ROOT
def deploy(click_config,
action,
poa,
upgrade,
provider_uri,
deployer_address,
contract_name,
@ -95,7 +93,7 @@ def deploy(click_config,
# Upgrade
#
if upgrade:
if action == 'upgrade':
if not contract_name:
raise click.BadArgumentUsage(message="--contract-name is required when using --upgrade")
existing_secret = click.prompt('Enter existing contract upgrade secret', hide_input=True, confirmation_prompt=True)
@ -103,26 +101,31 @@ def deploy(click_config,
deployer.upgrade_contract(contract_name=contract_name, existing_secret=existing_secret, new_plaintext_secret=new_secret)
return
#
# Deploy Single Contract
#
if contract_name:
try:
deployer_func = deployer.deployers[contract_name]
except KeyError:
message = "No such contract {}. Available contracts are {}".format(contract_name, deployer.deployers.keys())
click.secho(message, fg='red', bold=True)
raise click.Abort()
else:
_txs, _agent = deployer_func()
elif action == 'rollback':
existing_secret = click.prompt('Enter existing contract upgrade secret', hide_input=True, confirmation_prompt=True)
new_secret = click.prompt('Enter new contract upgrade secret', hide_input=True, confirmation_prompt=True)
deployer.rollback_contract(contract_name=contract_name, existing_plaintext_secret=existing_secret, new_plaintext_secret=new_secret)
return
# The Big Three
if action == "contracts":
elif action == "deploy":
#
# Deploy Single Contract
#
if contract_name:
try:
deployer_func = deployer.deployers[contract_name]
except KeyError:
message = "No such contract {}. Available contracts are {}".format(contract_name,
deployer.deployers.keys())
click.secho(message, fg='red', bold=True)
raise click.Abort()
else:
_txs, _agent = deployer_func()
return
secrets = click_config.collect_deployment_secrets()
@ -169,6 +172,7 @@ def deploy(click_config,
click.secho("Block #{} | {}\n".format(receipt['blockNumber'], receipt['blockHash'].hex()))
click.secho("Cumulative Gas Consumption: {} gas\n".format(total_gas_used), bold=True, fg='blue')
return
elif action == "allocations":
if not allocation_infile:
@ -176,6 +180,7 @@ def deploy(click_config,
click.confirm("Continue deploying and allocating?", abort=True)
deployer.deploy_beneficiaries_from_file(allocation_data_filepath=allocation_infile,
allocation_outfile=allocation_outfile)
return
elif action == "transfer":
token_agent = NucypherTokenAgent(blockchain=blockchain)
@ -189,6 +194,7 @@ def deploy(click_config,
click.confirm(f"Are you absolutely sure you want to destroy the contract registry at {registry_filepath}?", abort=True)
os.remove(registry_filepath)
click.secho(f"Successfully destroyed {registry_filepath}", fg='red')
return
else:
raise click.BadArgumentUsage(message=f"Unknown action '{action}'")

View File

@ -25,7 +25,7 @@ def test_nucypher_deploy_contracts(testerchain, click_runner, mock_primary_regis
# We start with a blockchain node, and nothing else...
assert not os.path.isfile(mock_primary_registry_filepath)
command = ('contracts',
command = ('deploy',
'--registry-outfile', mock_primary_registry_filepath,
'--provider-uri', TEST_PROVIDER_URI,
'--poa')
@ -74,86 +74,122 @@ def test_nucypher_deploy_contracts(testerchain, click_runner, mock_primary_regis
def test_upgrade_contracts(click_runner):
contracts_to_upgrade = ('MinersEscrow', # Initial upgrades (version 2)
'PolicyManager',
'MiningAdjudicator',
'UserEscrowProxy',
miner_agent = MinerAgent()
contract_name = miner_agent.contract_name
bound_miner_escrow_address = miner_agent.contract_address
# Additional Upgrades
'MinersEscrow', # v3
'MinersEscrow', # v4
'MiningAdjudicator', # v3
'PolicyAgent,', # v3
'UserEscrowProxy' # v3
)
command = ('contracts',
'--upgrade',
'--contract-name', contract_name,
'--registry-infile', MOCK_REGISTRY_FILEPATH,
'--provider-uri', TEST_PROVIDER_URI,
'--poa')
user_input = 'Y\n' \
+ f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2 \
+ f'{INSECURE_DEVELOPMENT_PASSWORD[::-1]}\n' * 2
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
# original bound address is the same
assert bound_miner_escrow_address == miner_agent.contract_address
with open(MOCK_REGISTRY_FILEPATH, 'r') as file:
# Ensure every contract's name was written to the file, somehow
raw_registry_data = file.read()
for registry_name in Deployer.contract_names:
assert registry_name in raw_registry_data
assert raw_registry_data.count(contract_name) == 2
registry_data = json.loads(raw_registry_data)
assert len(registry_data) == 10
executed_upgrades = {name: 0 for name in set(contracts_to_upgrade)}
blockchain = Blockchain.connect(registry=EthereumContractRegistry(registry_filepath=MOCK_REGISTRY_FILEPATH))
records = blockchain.interface.registry.search(contract_name=contract_name)
assert len(records) == 2
old, new = records
assert old[1] != new[1] # deployments are different addresses
# Ensure the dispatcher targets the new deployment
dispatcher = blockchain.interface.get_proxy(target_address=new[1], proxy_name='Dispatcher')
targeted_address = dispatcher.functions.target().call()
assert targeted_address != old[1]
assert targeted_address == new[1]
yes = 'Y\n'
version_1_secret = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
version_2_secret = f'{INSECURE_DEVELOPMENT_PASSWORD[::-1]}\n' * 2
version_3_secret = f'{INSECURE_DEVELOPMENT_PASSWORD[2:-2:-1]}\n' * 2
version_4_secret = f'{INSECURE_DEVELOPMENT_PASSWORD[1:-3:-1]}\n' * 2
command = ('contracts',
'--upgrade',
'--contract-name', 'PolicyManager',
'--registry-infile', MOCK_REGISTRY_FILEPATH,
'--provider-uri', TEST_PROVIDER_URI,
'--poa')
user_input_1_to_2 = yes + version_1_secret + version_2_secret
user_input_2_to_3 = yes + version_2_secret + version_3_secret
user_input_3_to_4 = yes + version_3_secret + version_4_secret
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
records = blockchain.interface.registry.search(contract_name='PolicyManager')
assert len(records) == 2
user_inputs = {0: user_input_1_to_2,
1: user_input_2_to_3,
2: user_input_3_to_4}
command = ('contracts',
'--upgrade',
'--contract-name', 'MiningAdjudicator',
'--registry-infile', MOCK_REGISTRY_FILEPATH,
'--provider-uri', TEST_PROVIDER_URI,
'--poa')
expected_registrations = 9
with open(MOCK_REGISTRY_FILEPATH, 'r') as file:
raw_registry_data = file.read()
registry_data = json.loads(raw_registry_data)
assert len(registry_data) == expected_registrations
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
records = blockchain.interface.registry.search(contract_name='MiningAdjudicator')
assert len(records) == 2
for contract_name in contracts_to_upgrade:
command = ('contracts',
'--upgrade',
'--contract-name', 'UserEscrowProxy',
'--registry-infile', MOCK_REGISTRY_FILEPATH,
'--provider-uri', TEST_PROVIDER_URI,
'--poa')
command = ('upgrade',
'--contract-name', contract_name,
'--registry-infile', MOCK_REGISTRY_FILEPATH,
'--provider-uri', TEST_PROVIDER_URI,
'--poa')
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
records = blockchain.interface.registry.search(contract_name='UserEscrowProxy')
assert len(records) == 2
user_input = user_inputs[executed_upgrades[contract_name]]
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
executed_upgrades[contract_name] += 1
expected_registrations += 1
with open(MOCK_REGISTRY_FILEPATH, 'r') as file:
raw_registry_data = file.read()
registry_data = json.loads(raw_registry_data)
assert len(registry_data) == expected_registrations
# Check that there is more than one entry, since we've deployed a "version 2"
registered_names = [r[0] for r in registry_data]
assert registered_names.count(contract_name) == 2
# Ensure deployments are different addresses
records = blockchain.interface.registry.search(contract_name=contract_name)
assert len(records) == executed_upgrades[contract_name] + 1
# Get the last two entries
old, new = records[-2:]
old_name, old_address, *abi = old
new_name, new_address, *abi = new
assert old_name == new_name
assert old_address != new_address
# Ensure the proxy targets the new deployment
proxy_name = 'Dispatcher' if contract_name != "UserEscrowProxy" else "UserEscrowLibraryLinker"
proxy = blockchain.interface.get_proxy(target_address=new_address, proxy_name=proxy_name)
targeted_address = proxy.functions.target().call()
assert targeted_address != old_address
assert targeted_address == new_address
def test_rollback(click_runner):
"""Roll 'em all back!"""
contracts_to_rollback = ('MinersEscrow', 'PolicyManager', 'MiningAdjudicator')
user_input = 'Y\n' \
+ f'{INSECURE_DEVELOPMENT_PASSWORD[::-1]}\n' * 2 \
+ f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
blockchain = Blockchain.connect(registry=EthereumContractRegistry(registry_filepath=MOCK_REGISTRY_FILEPATH))
for contract_name in contracts_to_rollback:
command = ('rollback',
'--contract-name', contract_name,
'--registry-infile', MOCK_REGISTRY_FILEPATH,
'--provider-uri', TEST_PROVIDER_URI,
'--poa')
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
assert result.exit_code == 0
records = blockchain.interface.registry.search(contract_name='UserEscrowProxy')
assert len(records) == 2
old, new = records
_name, old_address, *abi = old
_name, new_address, *abi = new
assert old_address != new_address
# Ensure the proxy targets the old deployment
proxy_name = 'Dispatcher'
proxy = blockchain.interface.get_proxy(target_address=new_address, proxy_name=proxy_name)
targeted_address = proxy.functions.target().call()
assert targeted_address != new_address
assert targeted_address == old_address
def test_nucypher_deploy_allocations(testerchain, click_runner, mock_allocation_infile, token_economics):