From e0ff8d8f8a3d864b300e86d2031b0aebb2a5df7b Mon Sep 17 00:00:00 2001 From: Xavier Romero Date: Mon, 3 Feb 2025 19:30:30 +0100 Subject: [PATCH] Anvil dump and restore --- .gitignore | 9 +++- anvil.star | 73 +++++++++++++------------- cdk_erigon.star | 53 ++++++++++--------- deploy_zkevm_contracts.star | 5 +- docs/anvil.md | 91 +++++++++++++++++++++++++++++++++ input_parser.star | 8 ++- lib/cdk_erigon.star | 17 +++++- templates/cdk-erigon/config.yml | 3 +- test_anvil.sh | 7 --- 9 files changed, 190 insertions(+), 76 deletions(-) create mode 100644 docs/anvil.md delete mode 100755 test_anvil.sh diff --git a/.gitignore b/.gitignore index d3d4a7ddb..67681feb8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,11 @@ package-lock.json package.json # prod values -*secret* \ No newline at end of file +*secret* + +# Files used for existing L1 +templates/contract-deploy/combined.json +templates/contract-deploy/dynamic-kurtosis-allocs.json +templates/contract-deploy/dynamic-kurtosis-conf.json +templates/contract-deploy/genesis.json +anvil_state.json \ No newline at end of file diff --git a/anvil.star b/anvil.star index 1630e40f5..d82f6282c 100644 --- a/anvil.star +++ b/anvil.star @@ -4,53 +4,54 @@ ANVIL_BLOCK_TIME = 1 ANVIL_SLOTS_IN_AN_EPOCH = ( 2 # Setting to X leads to block N-(X+1) being finalized, being N latest block ) +STATE_PATH = "/tmp" def run(plan, args): chain_id = str(args["l1_chain_id"]) + service_files = {} + mnemonic = args.get("l1_preallocated_mnemonic") + + cmd = ( + "anvil --block-time " + + str(ANVIL_BLOCK_TIME) + + " --slots-in-an-epoch " + + str(ANVIL_SLOTS_IN_AN_EPOCH) + + " --chain-id " + + chain_id + + " --host 0.0.0.0 --port " + + str(ANVIL_PORT) + + " --dump-state " + + STATE_PATH + + "/state_out.json" + + " --balance 1000000000" + + ' --mnemonic "' + + mnemonic + + '"' + ) + + load_state = bool(args.get("anvil_state_file")) + + if load_state: + anvil_state = plan.upload_files( + name="anvil-state", + src=args["anvil_state_file"], + description="Uploading Anvil State", + ) + service_files = { + STATE_PATH: anvil_state, + } + cmd += " --load-state " + STATE_PATH + "/" + args["anvil_state_file"] plan.add_service( - name="anvil", + name="anvil" + args["deployment_suffix"], config=ServiceConfig( image=ANVIL_IMAGE, ports={ "rpc": PortSpec(ANVIL_PORT, application_protocol="http"), }, - cmd=[ - "anvil --block-time " - + str(ANVIL_BLOCK_TIME) - + " --slots-in-an-epoch " - + str(ANVIL_SLOTS_IN_AN_EPOCH) - + " --chain-id " - + chain_id - + " --host 0.0.0.0 --port " - + str(ANVIL_PORT) - ], + files=service_files, + cmd=[cmd], ), description="Anvil", ) - - mnemonic = args.get("l1_preallocated_mnemonic") - cmd = ( - 'cast rpc anvil_setBalance $(cast wallet addr --mnemonic "' - + mnemonic - + '") 0x33b2e3c9fd0803ce8000000' - ) - plan.exec( - description="Funding L1 account", - service_name="anvil", - recipe=ExecRecipe(command=["/bin/sh", "-c", cmd]), - ) - - # Check balance - plan.exec( - description="Checking L1 account balance", - service_name="anvil", - recipe=ExecRecipe( - command=[ - "/bin/sh", - "-c", - 'cast balance $(cast wallet addr --mnemonic "' + mnemonic + '")', - ] - ), - ) diff --git a/cdk_erigon.star b/cdk_erigon.star index 4b588b92d..a61797422 100644 --- a/cdk_erigon.star +++ b/cdk_erigon.star @@ -3,6 +3,29 @@ zkevm_prover_package = import_module("./lib/zkevm_prover.star") def run_sequencer(plan, args, contract_setup_addresses): + # Start the zkevm stateless executor if strict mode is enabled. + if args["erigon_strict_mode"]: + stateless_configs = {} + stateless_configs["stateless_executor"] = True + stateless_executor_config_template = read_file( + src="./templates/trusted-node/prover-config.json" + ) + stateless_executor_config_artifact = plan.render_templates( + name="stateless-executor-config-artifact", + config={ + "stateless-executor-config.json": struct( + template=stateless_executor_config_template, + data=args | stateless_configs, + ) + }, + ) + zkevm_prover_package.start_stateless_executor( + plan, + args, + stateless_executor_config_artifact, + "zkevm_stateless_executor_start_port", + ) + cdk_erigon_config_template = read_file(src="./templates/cdk-erigon/config.yml") cdk_erigon_sequencer_config_artifact = plan.render_templates( name="cdk-erigon-sequencer-config-artifact", @@ -13,6 +36,7 @@ def run_sequencer(plan, args, contract_setup_addresses): "zkevm_data_stream_port": args["zkevm_data_streamer_port"], "is_sequencer": True, "consensus_contract_type": args["consensus_contract_type"], + "l1_sync_start_block": 1 if args["anvil_state_file"] else 0, } | args | contract_setup_addresses, @@ -49,12 +73,17 @@ def run_sequencer(plan, args, contract_setup_addresses): name="cdk-erigon-chain-first-batch", ) + cdk_erigon_datadir = Directory( + persistent_key="cdk-erigon-datadir" + args["deployment_suffix"], + ) + config_artifacts = struct( config=cdk_erigon_sequencer_config_artifact, chain_spec=cdk_erigon_chain_spec_artifact, chain_config=cdk_erigon_chain_config_artifact, chain_allocs=cdk_erigon_chain_allocs_artifact, chain_first_batch=cdk_erigon_chain_first_batch_artifact, + datadir=cdk_erigon_datadir, ) cdk_erigon_package.start_cdk_erigon_sequencer( plan, args, config_artifacts, "cdk_erigon_sequencer_start_port" @@ -62,29 +91,6 @@ def run_sequencer(plan, args, contract_setup_addresses): def run_rpc(plan, args, contract_setup_addresses): - # Start the zkevm stateless executor if strict mode is enabled. - if args["erigon_strict_mode"]: - stateless_configs = {} - stateless_configs["stateless_executor"] = True - stateless_executor_config_template = read_file( - src="./templates/trusted-node/prover-config.json" - ) - stateless_executor_config_artifact = plan.render_templates( - name="stateless-executor-config-artifact", - config={ - "stateless-executor-config.json": struct( - template=stateless_executor_config_template, - data=args | stateless_configs, - ) - }, - ) - zkevm_prover_package.start_stateless_executor( - plan, - args, - stateless_executor_config_artifact, - "zkevm_stateless_executor_start_port", - ) - zkevm_sequencer_service = plan.get_service( name=args["sequencer_name"] + args["deployment_suffix"] ) @@ -115,6 +121,7 @@ def run_rpc(plan, args, contract_setup_addresses): "is_sequencer": False, "pool_manager_url": pool_manager_url, "consensus_contract_type": args["consensus_contract_type"], + "l1_sync_start_block": 0, } | args | contract_setup_addresses, diff --git a/deploy_zkevm_contracts.star b/deploy_zkevm_contracts.star index aa28a5003..43db87eb2 100644 --- a/deploy_zkevm_contracts.star +++ b/deploy_zkevm_contracts.star @@ -36,10 +36,7 @@ def run(plan, args): artifact_paths = list(ARTIFACTS) # If we are configured to use a previous deployment, we'll # dynamically add artifacts for the genesis and combined outputs. - if ( - "use_previously_deployed_contracts" in args - and args["use_previously_deployed_contracts"] - ): + if args.get("use_previously_deployed_contracts"): artifact_paths.append( { "name": "genesis.json", diff --git a/docs/anvil.md b/docs/anvil.md new file mode 100644 index 000000000..43704b7fa --- /dev/null +++ b/docs/anvil.md @@ -0,0 +1,91 @@ +# Anvil L1 + +You can configure the stack to use Anvil as L1 by setting +``` +l1_engine: anvil +``` + +Please, understand that by doing: +- l1_chain_id is taken into account +- l1_rpc_url is automatically set to http://anvil:8545" +- l1_ws_url is automatically set to ws://anvil:8545 +- These params are ignored: + - l1_beacon_url + - l1_additional_services + - l1_preset + - l1_seconds_per_slot + - l1_participants_count + + +## State dump and recover +By using Anvil as L1, you can dump L1 network state, totally remove the network, and recreate again with the same L1 state. +This procedure has been tested with zkEVM rollup mode, for other scenarios you could need to perform additional steps (like dumping/restoring DAC database) or could be even not supported. + +### Procedure +Deploy the network. +```bash +ENCLAVE=cdk +kurtosis run --enclave $ENCLAVE . '{ + "args": { + "l1_engine": "anvil", + "consensus_contract_type": "rollup", + } +}' +``` + +Once deployed, save the required files to recreate again later. These are the files you need: +- ./anvil_state.json +- ./templates/contract-deploy/ +- ./templates/contract-deploy/combined.json +- ./templates/contract-deploy/genesis.json +- ./templates/contract-deploy/dynamic-kurtosis-conf.json +- ./templates/contract-deploy/dynamic-kurtosis-allocs.json + +Let's get them: + +```bash +STATE_FILE=anvil_state.json +DEPLOYMENT_FILES="combined.json genesis.json dynamic-kurtosis-conf.json dynamic-kurtosis-allocs.json" + +contracts_uuid=$(kurtosis enclave inspect --full-uuids $ENCLAVE | grep contracts | awk '{ print $1 }') +for file in $DEPLOYMENT_FILES; do + # Save each file on ./templates/contract-deploy, as they are expecte there for use_previously_deployed_contracts=True + docker cp contracts-001--$contracts_uuid:/opt/zkevm/$file ./templates/contract-deploy/$file +done + +# Dump Anvilstate (L1) +anvil_uuid=$(kurtosis enclave inspect --full-uuids $ENCLAVE | grep anvil | awk '{ print $1 }') +docker cp anvil--$anvil_uuid:/tmp/state_out.json $STATE_FILE +``` + +At that point you have all you need, you can totally remove the network. +```bash +kurtosis enclave stop $ENCLAVE +kurtosis enclave rm $ENCLAVE +``` + +To recreate the network, run kurtosis from scratch like this: +```bash +time kurtosis run --enclave $ENCLAVE . '{ + "args": { + "anvil_state_file": '$STATE_FILE', + "use_previously_deployed_contracts": true, + "consensus_contract_type": "rollup", + } +}' +``` + +This will perform the required steps to load the previous state, however, the sequencer needs to recover the state from L1 so it has been set with a specific param, that won't allow it to resume generating new blocks. So you need to manually unlock it: + +```bash +# Check cdk-erigon-sequencer logs until you see lines like this before proceeding. +# "L1 block sync recovery has completed!"" + +# Disable L1 recovery mode +kurtosis service exec $ENCLAVE cdk-erigon-sequencer-001 \ + "sed -i 's/zkevm.l1-sync-start-block: 1/zkevm.l1-sync-start-block: 0/' /etc/cdk-erigon/config.yaml" + +# Restart sequenccer +kurtosis service stop $ENCLAVE cdk-erigon-sequencer-001 +kurtosis service start $ENCLAVE cdk-erigon-sequencer-001 +``` diff --git a/input_parser.star b/input_parser.star index 4ad198454..4f77f2a0a 100644 --- a/input_parser.star +++ b/input_parser.star @@ -219,6 +219,7 @@ DEFAULT_L1_ARGS = { # TODO at some point it would be nice if erigon could recover itself, but this is not going to be easy if there's a DAC "use_previously_deployed_contracts": False, "erigon_datadir_archive": None, + "anvil_state_file": None, } DEFAULT_L2_ARGS = { @@ -391,9 +392,12 @@ def parse_args(plan, args): op_stack_args = args.get("optimism_package", {}) args = DEFAULT_ARGS | args.get("args", {}) + if args["anvil_state_file"] != None: + args["l1_engine"] = "anvil" + if args["l1_engine"] == "anvil": - args["l1_rpc_url"] = "http://anvil:8545" - args["l1_ws_url"] = "ws://anvil:8545" + args["l1_rpc_url"] = "http://anvil" + args["deployment_suffix"] + ":8545" + args["l1_ws_url"] = "ws://anvil" + args["deployment_suffix"] + ":8545" elif args["l1_engine"] != "geth": fail( "Unsupported L1 engine: '{}', please use 'geth' or 'anvil'".format( diff --git a/lib/cdk_erigon.star b/lib/cdk_erigon.star index 8f4e6b63a..807a96b77 100644 --- a/lib/cdk_erigon.star +++ b/lib/cdk_erigon.star @@ -4,6 +4,7 @@ CDK_ERIGON_TYPE = struct( sequencer="sequencer", rpc="rpc", ) +CDK_ERIGON_CMD = "cdk-erigon --config /etc/cdk-erigon/config.yaml" def start_cdk_erigon_sequencer(plan, args, config_artifact, start_port_name): @@ -29,7 +30,13 @@ def start_cdk_erigon_rpc(plan, args, config_artifact, start_port_name): def _start_service( - plan, type, args, config_artifact, start_port_name, additional_ports={}, env_vars={} + plan, + type, + args, + config_artifact, + start_port_name, + additional_ports={}, + env_vars={}, ): cdk_erigon_chain_artifact_names = [ config_artifact.chain_spec, @@ -37,6 +44,7 @@ def _start_service( config_artifact.chain_allocs, config_artifact.chain_first_batch, ] + plan_files = { "/etc/cdk-erigon": Directory( artifact_names=[config_artifact.config] + cdk_erigon_chain_artifact_names, @@ -46,6 +54,11 @@ def _start_service( ), } + if hasattr(config_artifact, "datadir"): + plan_files[ + "/home/erigon/data/dynamic-" + args["chain_name"] + "-sequencer" + ] = config_artifact.datadir + proc_runner_file_artifact = plan.upload_files( name="cdk-erigon-" + type + "-proc-runner", src="../templates/proc-runner.sh", @@ -72,7 +85,7 @@ def _start_service( public_ports=public_ports, files=plan_files, entrypoint=["/usr/local/share/proc-runner/proc-runner.sh"], - cmd=["cdk-erigon --config /etc/cdk-erigon/config.yaml"], + cmd=[CDK_ERIGON_CMD], env_vars=env_vars, ), ) diff --git a/templates/cdk-erigon/config.yml b/templates/cdk-erigon/config.yml index 5e8b338da..9a9e88292 100644 --- a/templates/cdk-erigon/config.yml +++ b/templates/cdk-erigon/config.yml @@ -430,7 +430,7 @@ zkevm.address-ger-manager: "{{.zkevm_global_exit_root_address}}" # Used for network recovery from L1 batch data. # Set to 0 to use the datastream instead. # Default: 0 -zkevm.l1-sync-start-block: 0 +zkevm.l1-sync-start-block: {{.l1_sync_start_block}} # Limits the number of batches to sync from L1. # Useful for debugging or partial recoveries. @@ -999,3 +999,4 @@ db.size.limit: 8TB # - config # - lightclient.x # - sentinel.x + diff --git a/test_anvil.sh b/test_anvil.sh deleted file mode 100755 index b36933c42..000000000 --- a/test_anvil.sh +++ /dev/null @@ -1,7 +0,0 @@ -ENCLAVE=cdk - -kurtosis run --enclave $ENCLAVE . '{ - "args": { - "l1_engine": "anvil", - } -}'