Skip to content

Commit

Permalink
Merge pull request #459 from 0xPolygon/anvil_l1
Browse files Browse the repository at this point in the history
Anvil l1
  • Loading branch information
xavier-romero authored Feb 4, 2025
2 parents 85f0dc2 + 7ed8f96 commit ace83c6
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 36 deletions.
4 changes: 4 additions & 0 deletions .github/tests/anvil/rollup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
args:
verbosity: debug
l1_engine: anvil
consensus_contract_type: rollup
44 changes: 43 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -841,5 +841,47 @@ jobs:
with:
name: dump_attach_sovereign_${{ github.run_id }}
path: ./dump
env :
env:
agglayer_prover_sp1_key: ${{ secrets.SP1_PRIVATE_KEY }}

deploy-to-anvil_l1:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# This step will only execute if the necessary secrets are available, preventing failures
# on pull requests from forked repositories.
if: ${{ env.DOCKERHUB_USERNAME && env.DOCKERHUB_TOKEN }}
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Install Kurtosis CDK tools
uses: ./.github/actions/setup-kurtosis-cdk

- name: Deploy stack with anvil
run: kurtosis run --enclave=${{ env.ENCLAVE_NAME }} --args-file=./.github/tests/anvil/rollup.yml .

- name: Monitor CDK chain verified batches (CDK Erigon Permissionless RPC)
working-directory: .github/scripts
run: |
./monitor-cdk-chain.sh \
--enclave ${{ env.ENCLAVE_NAME }} \
--rpc-url "$(kurtosis port print ${{ env.ENCLAVE_NAME }} cdk-erigon-rpc-001 rpc)"
- name: Dump enclave
if: ${{ !cancelled() }}
run: kurtosis enclave dump ${{ env.ENCLAVE_NAME }} ./dump

- name: Upload enclave dump
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: dump_deploy_to_anvil_l1_${{ github.run_id }}
path: ./dump
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ package-lock.json
package.json

# prod values
*secret*
*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
55 changes: 55 additions & 0 deletions anvil.star
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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(args["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" + args["deployment_suffix"],
config=ServiceConfig(
image=args["anvil_image"],
ports={
"rpc": PortSpec(args["anvil_port"], application_protocol="http"),
},
files=service_files,
cmd=[cmd],
),
description="Anvil",
)
53 changes: 30 additions & 23 deletions cdk_erigon.star
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
Expand Down Expand Up @@ -49,42 +73,24 @@ 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"
)


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"]
)
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 1 addition & 4 deletions deploy_zkevm_contracts.star
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,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",
Expand Down
91 changes: 91 additions & 0 deletions docs/anvil.md
Original file line number Diff line number Diff line change
@@ -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-001:8545
- l1_ws_url is automatically set to ws://anvil-001: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 expected there for use_previously_deployed_contracts=True
docker cp contracts-001--$contracts_uuid:/opt/zkevm/$file ./templates/contract-deploy/$file
done

# Dump Anvil state (L1)
anvil_uuid=$(kurtosis enclave inspect --full-uuids $ENCLAVE | grep anvil | awk '{ print $1 }')
docker cp anvil-001--$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
```
41 changes: 37 additions & 4 deletions input_parser.star
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ DEFAULT_IMAGES = {
"zkevm_pool_manager_image": "hermeznetwork/zkevm-pool-manager:v0.1.2", # https://hub.docker.com/r/hermeznetwork/zkevm-pool-manager/tags
"zkevm_prover_image": "hermeznetwork/zkevm-prover:v8.0.0-RC14-fork.12", # https://hub.docker.com/r/hermeznetwork/zkevm-prover/tags
"zkevm_sequence_sender_image": "hermeznetwork/zkevm-sequence-sender:v0.2.4", # https://hub.docker.com/r/hermeznetwork/zkevm-sequence-sender/tags
"anvil_image": "ghcr.io/foundry-rs/foundry:v1.0.0-rc", # https://github.com/foundry-rs/foundry/pkgs/container/foundry/versions?filters%5Bversion_type%5D=tagged
}

DEFAULT_PORTS = {
Expand All @@ -69,6 +70,7 @@ DEFAULT_PORTS = {
"zkevm_rpc_ws_port": 8133,
"zkevm_cdk_node_port": 5576,
"blockscout_frontend_port": 3000,
"anvil_port": 8545,
}

DEFAULT_STATIC_PORTS = {
Expand Down Expand Up @@ -157,6 +159,8 @@ DEFAULT_ACCOUNTS = {
}

DEFAULT_L1_ARGS = {
# The L1 engine to use, either "geth" or "anvil".
"l1_engine": "geth",
# The L1 network identifier.
"l1_chain_id": 271828,
# This mnemonic will:
Expand Down Expand Up @@ -217,6 +221,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 = {
Expand Down Expand Up @@ -383,11 +388,39 @@ DEFAULT_ARGS = (
SUPPORTED_FORK_IDS = [9, 11, 12, 13]


def parse_args(plan, args):
def parse_args(plan, user_args):
# Merge the provided args with defaults.
deployment_stages = DEFAULT_DEPLOYMENT_STAGES | args.get("deployment_stages", {})
op_stack_args = args.get("optimism_package", {})
args = DEFAULT_ARGS | args.get("args", {})
deployment_stages = DEFAULT_DEPLOYMENT_STAGES | user_args.get(
"deployment_stages", {}
)
op_stack_args = user_args.get("optimism_package", {})
args = DEFAULT_ARGS | user_args.get("args", {})

if args["anvil_state_file"] != None:
args["l1_engine"] = "anvil"

if args["l1_engine"] == "anvil":
# We override only is user did not provide explicit values
if not user_args.get("args", {}).get("l1_rpc_url"):
args["l1_rpc_url"] = (
"http://anvil"
+ args["deployment_suffix"]
+ ":"
+ str(DEFAULT_PORTS.get("anvil_port"))
)
if not user_args.get("args", {}).get("l1_ws_url"):
args["l1_ws_url"] = (
"ws://anvil"
+ args["deployment_suffix"]
+ ":"
+ str(DEFAULT_PORTS.get("anvil_port"))
)
elif args["l1_engine"] != "geth":
fail(
"Unsupported L1 engine: '{}', please use 'geth' or 'anvil'".format(
args["l1_engine"]
)
)

# Validation step.
verbosity = args.get("verbosity", "")
Expand Down
Loading

0 comments on commit ace83c6

Please sign in to comment.