Skip to content

Commit

Permalink
Add post-deployment verification
Browse files Browse the repository at this point in the history
- check that the `deployment_[CHAIN_NAME].json` file created post deployment contains correct information, compared to the chain
- check that deployed bytecode matches the one from the compiled `contracts.json` file for each contract
- add Readme information to run the verification separate from the deployment: `python -m raiden_contracts.deploy verify --rpc-provider http://127.0.0.1:8545`
  • Loading branch information
loredanacirstea committed Oct 10, 2018
1 parent 7d0b9b3 commit 0bfea73
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 6 deletions.
10 changes: 10 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,13 @@ Deploying a token for testing purposes (please DO NOT use this for production pu
Registering a token with the ``TokenNetworkRegistry`` contract, so it can be used by the Raiden Network, with the ``register`` command::

python -m raiden_contracts.deploy register --rpc-provider http://127.0.0.1:8545 --private-key /path/to/your/private_key/file --gas-price 10 --token-address TOKEN_TO_BE_REGISTERED_ADDRESS --registry-address TOKEN_NETWORK_REGISTRY_ADDRESS


Deployment information is stored in a ``deployment_[CHAIN_NAME].json`` file corresponding to the chain on which it was deployed. To verify that the deployed contracts match the compiled data in ``contracts.json`` and also match the deployment information in the file, we can run:

::

python -m raiden_contracts.deploy verify --rpc-provider http://127.0.0.1:8545

# Based on the network id, the script verifies the corresponding deployment_[CHAIN_NAME].json file
# using the chain name-id mapping from constants.py
170 changes: 164 additions & 6 deletions raiden_contracts/deploy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@

from logging import getLogger
from typing import Dict
from pathlib import Path
from json import JSONDecodeError

from eth_utils import denoms, encode_hex, is_address, to_checksum_address
from web3 import HTTPProvider, Web3
from web3.middleware import geth_poa_middleware

from raiden_contracts.constants import (
CONTRACTS_VERSION,
CONTRACT_CUSTOM_TOKEN,
CONTRACT_ENDPOINT_REGISTRY,
CONTRACT_SECRET_REGISTRY,
Expand All @@ -35,6 +38,10 @@
log = getLogger(__name__)


class DeploymentVerificationError(RuntimeError):
pass


def validate_address(ctx, param, value):
if not value:
return None
Expand Down Expand Up @@ -296,6 +303,30 @@ def register(
)


@main.command()
@click.option(
'--rpc-provider',
default='http://127.0.0.1:8545',
help='Address of the Ethereum RPC provider',
)
@click.pass_context
def verify(
ctx,
rpc_provider,
):
web3 = Web3(HTTPProvider(rpc_provider, request_kwargs={'timeout': 60}))
web3.middleware_stack.inject(geth_poa_middleware, layer=0)
print('Web3 provider is', web3.providers[0])

contract_manager = ContractManager(CONTRACTS_PRECOMPILED_PATH)

verify_deployed_contracts(
web3,
contract_manager,
get_deployment_path(int(web3.version.network))
)


def deploy_raiden_contracts(
deployer: ContractDeployer,
):
Expand Down Expand Up @@ -348,9 +379,10 @@ def deploy_raiden_contracts(
}

chain_id = int(deployer.web3.version.network)
chain_name = ID_TO_NETWORKNAME[chain_id] if chain_id in ID_TO_NETWORKNAME else 'private_net'
deployment_file_path = get_deployment_path(chain_id)

store_deployment_info(deployed_contracts_info, chain_name)
store_deployment_info(deployed_contracts_info, deployment_file_path)
verify_deployed_contracts(deployer.web3, deployer.contract_manager, deployment_file_path)

return deployed_contracts

Expand Down Expand Up @@ -414,15 +446,141 @@ def register_token_network(
)


def store_deployment_info(deployment_info: dict, chain_name):
deployment_file_path = CONTRACTS_PRECOMPILED_PATH.parents[0].joinpath(
f'deployment_{chain_name}.json'
)
def store_deployment_info(deployment_info: dict, deployment_file_path: Path):
with deployment_file_path.open(mode='w') as target_file:
target_file.write(json.dumps(deployment_info))

print(f'Deployment information has been updated at {deployment_file_path}.')


def verify_deployed_contracts(web3, contract_manager, deployment_file_path: Path):
try:
with deployment_file_path.open() as deployment_file:
deployment_data = json.load(deployment_file)
except (JSONDecodeError, UnicodeDecodeError) as ex:
raise DeploymentVerificationError(
f'Cannot load deployment data file: {ex}'
) from ex

contracts = deployment_data['contracts']

assert contract_manager.contracts_version == deployment_data['contracts_version']

endpoint_registry_address = contracts[CONTRACT_ENDPOINT_REGISTRY]['address']
endpoint_registry_abi = contract_manager.get_contract_abi(CONTRACT_ENDPOINT_REGISTRY)
endpoint_registry = web3.eth.contract(
abi=endpoint_registry_abi,
address=endpoint_registry_address,
)
endpoint_registry = PrivateContract(endpoint_registry)

# Check that the deployed bytecode matches the precompiled data
blockchain_bytecode = web3.eth.getCode(endpoint_registry_address).hex()
compiled_bytecode = contract_manager.contracts[CONTRACT_ENDPOINT_REGISTRY]['bin']
# Compiled code contains some additional initial data compare to the blockchain bytecode
compiled_bytecode = compiled_bytecode[-len(blockchain_bytecode):]
compiled_bytecode = hex(int(compiled_bytecode, 16))
assert blockchain_bytecode == compiled_bytecode

# Check blockchain transaction hash & block information
receipt = web3.eth.getTransactionReceipt(
contracts[CONTRACT_ENDPOINT_REGISTRY]['transaction_hash']
)
assert receipt['blockNumber'] == contracts[CONTRACT_ENDPOINT_REGISTRY]['block_number']
assert receipt['gasUsed'] == contracts[CONTRACT_ENDPOINT_REGISTRY]['gas_cost']
assert receipt['contractAddress'] == contracts[CONTRACT_ENDPOINT_REGISTRY]['address']

# Check the contract version
assert endpoint_registry.functions.contract_version().call().decode() == deployment_data['contracts_version']

print(
f'{CONTRACT_ENDPOINT_REGISTRY} at {endpoint_registry_address} '
f'matches the compiled data from contracts.json',
)

secret_registry_address = contracts[CONTRACT_SECRET_REGISTRY]['address']
secret_registry_abi = contract_manager.get_contract_abi(CONTRACT_SECRET_REGISTRY)
secret_registry = web3.eth.contract(
abi=secret_registry_abi,
address=secret_registry_address,
)
secret_registry = PrivateContract(secret_registry)

# Check that the deployed bytecode matches the precompiled data
blockchain_bytecode = web3.eth.getCode(secret_registry_address).hex()
compiled_bytecode = contract_manager.contracts[CONTRACT_SECRET_REGISTRY]['bin']
compiled_bytecode = compiled_bytecode[-len(blockchain_bytecode):]
compiled_bytecode = hex(int(compiled_bytecode, 16))
assert blockchain_bytecode == compiled_bytecode

# Check blockchain transaction hash & block information
receipt = web3.eth.getTransactionReceipt(
contracts[CONTRACT_SECRET_REGISTRY]['transaction_hash']
)
assert receipt['blockNumber'] == contracts[CONTRACT_SECRET_REGISTRY]['block_number']
assert receipt['gasUsed'] == contracts[CONTRACT_SECRET_REGISTRY]['gas_cost']
assert receipt['contractAddress'] == contracts[CONTRACT_SECRET_REGISTRY]['address']

# Check the contract version
assert secret_registry.functions.contract_version().call().decode() == deployment_data['contracts_version']

print(
f'{CONTRACT_SECRET_REGISTRY} at {secret_registry_address} '
f'matches the compiled data from contracts.json',
)

token_registry_address = contracts[CONTRACT_TOKEN_NETWORK_REGISTRY]['address']
token_registry_abi = contract_manager.get_contract_abi(
CONTRACT_TOKEN_NETWORK_REGISTRY,
)
token_network_registry = web3.eth.contract(
abi=token_registry_abi,
address=token_registry_address,
)
token_network_registry = PrivateContract(token_network_registry)

# Check that the deployed bytecode matches the precompiled data
blockchain_bytecode = web3.eth.getCode(token_registry_address).hex()
compiled_bytecode = contract_manager.contracts[CONTRACT_TOKEN_NETWORK_REGISTRY]['bin']
compiled_bytecode = compiled_bytecode[-len(blockchain_bytecode):]
compiled_bytecode = hex(int(compiled_bytecode, 16))
assert blockchain_bytecode == compiled_bytecode

# Check blockchain transaction hash & block information
receipt = web3.eth.getTransactionReceipt(
contracts[CONTRACT_TOKEN_NETWORK_REGISTRY]['transaction_hash']
)
assert receipt['blockNumber'] == contracts[CONTRACT_TOKEN_NETWORK_REGISTRY]['block_number']
assert receipt['gasUsed'] == contracts[CONTRACT_TOKEN_NETWORK_REGISTRY]['gas_cost']
assert receipt['contractAddress'] == contracts[CONTRACT_TOKEN_NETWORK_REGISTRY]['address']

# Check the contract version
assert token_network_registry.functions.contract_version().call().decode() == deployment_data['contracts_version']

# Check constructor parameters
constructor_arguments = contracts[CONTRACT_TOKEN_NETWORK_REGISTRY]['constructor_arguments']
assert to_checksum_address(
token_network_registry.functions.secret_registry_address().call(),
) == secret_registry_address
assert secret_registry_address == constructor_arguments[0]
assert token_network_registry.functions.chain_id().call() == constructor_arguments[1]
assert token_network_registry.functions.settlement_timeout_min().call() == constructor_arguments[2]
assert token_network_registry.functions.settlement_timeout_max().call() == constructor_arguments[3]

print(
f'{CONTRACT_TOKEN_NETWORK_REGISTRY} at {token_registry_address} '
f'matches the compiled data from contracts.json',
)


def get_deployment_path(chain_id: int):
chain_name = ID_TO_NETWORKNAME[chain_id] if chain_id in ID_TO_NETWORKNAME else 'private_net'

deployment_file_path = CONTRACTS_PRECOMPILED_PATH.parents[0].joinpath(
f'deployment_{chain_name}.json'
)
return deployment_file_path


if __name__ == '__main__':
main()

0 comments on commit 0bfea73

Please sign in to comment.