279 lines
8.8 KiB
Bash
Executable file
279 lines
8.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
root="${EVERY_CHANNEL_OP_STACK_ROOT:?set EVERY_CHANNEL_OP_STACK_ROOT}"
|
|
private_key_file="${EVERY_CHANNEL_OP_STACK_PRIVATE_KEY_FILE:?set EVERY_CHANNEL_OP_STACK_PRIVATE_KEY_FILE}"
|
|
l1_rpc_url="${EVERY_CHANNEL_OP_STACK_L1_RPC_URL:-https://ethereum-sepolia-rpc.publicnode.com}"
|
|
l1_beacon_url="${EVERY_CHANNEL_OP_STACK_L1_BEACON_URL:-https://ethereum-sepolia-beacon-api.publicnode.com}"
|
|
chain_id="${EVERY_CHANNEL_OP_STACK_CHAIN_ID:-245245}"
|
|
p2p_advertise_ip="${EVERY_CHANNEL_OP_STACK_P2P_ADVERTISE_IP:-127.0.0.1}"
|
|
l2_rpc_url="${EVERY_CHANNEL_OP_STACK_L2_RPC_URL:-http://127.0.0.1:28545}"
|
|
rollup_rpc_url="${EVERY_CHANNEL_OP_STACK_ROLLUP_RPC_URL:-http://127.0.0.1:28547}"
|
|
op_deployer_bin="${EVERY_CHANNEL_OP_DEPLOYER_BIN:-${root}/bin/op-deployer}"
|
|
download_script="${EVERY_CHANNEL_OP_DEPLOYER_DOWNLOAD_SCRIPT:-}"
|
|
download_tag="${EVERY_CHANNEL_OP_DEPLOYER_TAG:-op-deployer/v0.6.0-rc.3}"
|
|
skip_apply="${EVERY_CHANNEL_OP_STACK_SKIP_APPLY:-false}"
|
|
challenger_prestate_file="${EVERY_CHANNEL_OP_STACK_CHALLENGER_PRESTATE_FILE:-}"
|
|
|
|
deployer_dir="${root}/deployer"
|
|
sequencer_dir="${root}/sequencer"
|
|
batcher_dir="${root}/batcher"
|
|
proposer_dir="${root}/proposer"
|
|
challenger_dir="${root}/challenger"
|
|
dispute_mon_dir="${root}/dispute-mon"
|
|
challenger_prestate_name=""
|
|
|
|
log() {
|
|
printf '[op-stack] %s\n' "$*" >&2
|
|
}
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || {
|
|
log "missing required command: $1"
|
|
exit 2
|
|
}
|
|
}
|
|
|
|
trimmed_file_contents() {
|
|
tr -d '\r\n' <"$1"
|
|
}
|
|
|
|
normalize_rollup_config() {
|
|
local path="$1"
|
|
python - "$path" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
path = Path(sys.argv[1])
|
|
data = json.loads(path.read_text())
|
|
system_config = data.setdefault("genesis", {}).setdefault("system_config", {})
|
|
system_config.pop("daFootprintGasScalar", None)
|
|
|
|
chain_op_config = data.get("chain_op_config", {})
|
|
denominator = chain_op_config.get("eip1559DenominatorCanyon") or chain_op_config.get("eip1559Denominator")
|
|
elasticity = chain_op_config.get("eip1559Elasticity")
|
|
if (
|
|
isinstance(denominator, int)
|
|
and isinstance(elasticity, int)
|
|
and system_config.get("eip1559Params") in (None, "0x", "0x0000000000000000")
|
|
):
|
|
system_config["eip1559Params"] = f"0x{denominator:08x}{elasticity:08x}"
|
|
|
|
path.write_text(json.dumps(data, indent=2, sort_keys=False) + "\n")
|
|
PY
|
|
}
|
|
|
|
set_toml_value() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local file="$3"
|
|
python - "$key" "$value" "$file" <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
key, value, path = sys.argv[1:]
|
|
text = Path(path).read_text()
|
|
needle = f"{key} = "
|
|
out = []
|
|
replaced = False
|
|
for line in text.splitlines():
|
|
stripped = line.lstrip()
|
|
if stripped.startswith(needle):
|
|
indent = line[:len(line) - len(stripped)]
|
|
out.append(f'{indent}{key} = "{value}"')
|
|
replaced = True
|
|
else:
|
|
out.append(line)
|
|
if not replaced:
|
|
raise SystemExit(f"missing key {key} in {path}")
|
|
Path(path).write_text("\n".join(out) + "\n")
|
|
PY
|
|
}
|
|
|
|
mkdir -p "${root}/bin" "$deployer_dir" "$sequencer_dir" "$batcher_dir" "$proposer_dir" \
|
|
"$challenger_dir" "$challenger_dir/data" "$dispute_mon_dir"
|
|
|
|
require_cmd cast
|
|
require_cmd curl
|
|
require_cmd jq
|
|
require_cmd openssl
|
|
|
|
if [[ ! -x "$op_deployer_bin" ]]; then
|
|
if [[ -z "$download_script" ]]; then
|
|
log "op-deployer missing and no download script configured"
|
|
exit 2
|
|
fi
|
|
log "downloading op-deployer via ${download_script}"
|
|
EVERY_CHANNEL_OP_DEPLOYER_TAG="$download_tag" \
|
|
EVERY_CHANNEL_OP_DEPLOYER_OUT="$op_deployer_bin" \
|
|
"$download_script"
|
|
fi
|
|
|
|
private_key="$(trimmed_file_contents "$private_key_file")"
|
|
private_key="${private_key#0x}"
|
|
if [[ ${#private_key} -ne 64 ]]; then
|
|
log "private key file must contain exactly 32 bytes of hex"
|
|
exit 2
|
|
fi
|
|
|
|
operator_address="$(cast wallet address --private-key "0x${private_key}")"
|
|
|
|
cat > "${deployer_dir}/.env" <<EOF
|
|
L1_RPC_URL=${l1_rpc_url}
|
|
PRIVATE_KEY=${private_key}
|
|
EOF
|
|
|
|
if [[ ! -d "${deployer_dir}/.deployer" ]]; then
|
|
"$op_deployer_bin" init \
|
|
--l1-chain-id 11155111 \
|
|
--l2-chain-ids "${chain_id}" \
|
|
--workdir "${deployer_dir}/.deployer" \
|
|
--intent-type standard-overrides
|
|
fi
|
|
|
|
intent="${deployer_dir}/.deployer/intent.toml"
|
|
chain_id_hex="$(printf '0x%064x' "${chain_id}")"
|
|
set_toml_value "id" "${chain_id_hex}" "${intent}"
|
|
for key in \
|
|
baseFeeVaultRecipient \
|
|
l1FeeVaultRecipient \
|
|
sequencerFeeVaultRecipient \
|
|
operatorFeeVaultRecipient \
|
|
chainFeesRecipient \
|
|
systemConfigOwner \
|
|
unsafeBlockSigner \
|
|
batcher \
|
|
proposer \
|
|
challenger
|
|
do
|
|
set_toml_value "${key}" "${operator_address}" "${intent}"
|
|
done
|
|
|
|
python - "${intent}" <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
path = Path(sys.argv[1])
|
|
text = path.read_text()
|
|
if 'fundDevAccounts = false' not in text:
|
|
text = text.replace('fundDevAccounts = true', 'fundDevAccounts = false')
|
|
path.write_text(text)
|
|
PY
|
|
|
|
state_json="${deployer_dir}/.deployer/state.json"
|
|
if [[ "${skip_apply}" != "true" ]]; then
|
|
if [[ ! -f "${state_json}" ]] || ! jq -e \
|
|
'.appliedIntent != null and .opChainDeployments != null' \
|
|
<"${state_json}" >/dev/null 2>&1
|
|
then
|
|
"$op_deployer_bin" apply \
|
|
--workdir "${deployer_dir}/.deployer" \
|
|
--l1-rpc-url "${l1_rpc_url}" \
|
|
--private-key "${private_key}"
|
|
fi
|
|
fi
|
|
|
|
if [[ ! -f "${state_json}" ]] || ! jq -e \
|
|
'.appliedIntent != null and .opChainDeployments != null' \
|
|
<"${state_json}" >/dev/null 2>&1
|
|
then
|
|
log "state.json missing or unapplied; bootstrap did not complete"
|
|
exit 1
|
|
fi
|
|
|
|
"$op_deployer_bin" inspect genesis --workdir "${deployer_dir}/.deployer" "${chain_id_hex}" >"${sequencer_dir}/genesis.json"
|
|
"$op_deployer_bin" inspect rollup --workdir "${deployer_dir}/.deployer" "${chain_id_hex}" >"${sequencer_dir}/rollup.json"
|
|
normalize_rollup_config "${sequencer_dir}/rollup.json"
|
|
openssl rand -hex 32 >"${sequencer_dir}/jwt.txt"
|
|
chmod 0600 "${sequencer_dir}/jwt.txt"
|
|
|
|
system_config_proxy="$(jq -r '.opChainDeployments[0].systemConfigProxyAddress // .opChainDeployments[0].SystemConfigProxy // empty' <"${state_json}")"
|
|
dispute_game_factory="$(jq -r '.opChainDeployments[0].disputeGameFactoryProxyAddress // .opChainDeployments[0].DisputeGameFactoryProxy // empty' <"${state_json}")"
|
|
l1_standard_bridge="$(jq -r '.opChainDeployments[0].l1StandardBridgeProxyAddress // .opChainDeployments[0].L1StandardBridgeProxy // empty' <"${state_json}")"
|
|
|
|
if [[ -z "${system_config_proxy}" || "${system_config_proxy}" == "null" ]]; then
|
|
log "failed to extract systemConfigProxyAddress from state.json"
|
|
exit 1
|
|
fi
|
|
if [[ -z "${dispute_game_factory}" || "${dispute_game_factory}" == "null" ]]; then
|
|
log "failed to extract disputeGameFactoryProxyAddress from state.json"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "${challenger_prestate_file}" ]]; then
|
|
if [[ ! -f "${challenger_prestate_file}" ]]; then
|
|
log "challenger prestate file does not exist: ${challenger_prestate_file}"
|
|
exit 1
|
|
fi
|
|
challenger_prestate_name="$(basename "${challenger_prestate_file}")"
|
|
cp "${challenger_prestate_file}" "${challenger_dir}/${challenger_prestate_name}"
|
|
chmod 0640 "${challenger_dir}/${challenger_prestate_name}"
|
|
fi
|
|
|
|
cat > "${sequencer_dir}/.env" <<EOF
|
|
L1_RPC_URL=${l1_rpc_url}
|
|
L1_BEACON_URL=${l1_beacon_url}
|
|
PRIVATE_KEY=${private_key}
|
|
P2P_ADVERTISE_IP=${p2p_advertise_ip}
|
|
L2_CHAIN_ID=${chain_id}
|
|
EOF
|
|
|
|
cat > "${batcher_dir}/.env" <<EOF
|
|
L1_RPC_URL=${l1_rpc_url}
|
|
L2_RPC_URL=${l2_rpc_url}
|
|
ROLLUP_RPC_URL=${rollup_rpc_url}
|
|
PRIVATE_KEY=${private_key}
|
|
EOF
|
|
|
|
cat > "${proposer_dir}/.env" <<EOF
|
|
L1_RPC_URL=${l1_rpc_url}
|
|
ROLLUP_RPC_URL=${rollup_rpc_url}
|
|
GAME_FACTORY_ADDRESS=${dispute_game_factory}
|
|
PRIVATE_KEY=${private_key}
|
|
PROPOSAL_INTERVAL=3600s
|
|
EOF
|
|
|
|
cp "${sequencer_dir}/genesis.json" "${challenger_dir}/genesis.json"
|
|
cp "${sequencer_dir}/rollup.json" "${challenger_dir}/rollup.json"
|
|
cat > "${challenger_dir}/.env" <<EOF
|
|
L1_RPC_URL=${l1_rpc_url}
|
|
L1_BEACON_URL=${l1_beacon_url}
|
|
L2_RPC_URL=${l2_rpc_url}
|
|
ROLLUP_RPC_URL=${rollup_rpc_url}
|
|
GAME_FACTORY_ADDRESS=${dispute_game_factory}
|
|
PRIVATE_KEY=${private_key}
|
|
EOF
|
|
|
|
if [[ -n "${challenger_prestate_name}" ]]; then
|
|
cat >> "${challenger_dir}/.env" <<EOF
|
|
CANNON_PRESTATE=/workspace/${challenger_prestate_name}
|
|
EOF
|
|
fi
|
|
|
|
cat > "${dispute_mon_dir}/.env" <<EOF
|
|
OP_DISPUTE_MON_L1_ETH_RPC=${l1_rpc_url}
|
|
OP_DISPUTE_MON_ROLLUP_RPC=${rollup_rpc_url}
|
|
OP_DISPUTE_MON_HONEST_ACTORS=${operator_address}
|
|
OP_DISPUTE_MON_GAME_FACTORY_ADDRESS=${dispute_game_factory}
|
|
OP_DISPUTE_MON_MONITOR_INTERVAL=10s
|
|
OP_DISPUTE_MON_NETWORK=ecp-sepolia
|
|
OP_DISPUTE_MON_LOG_LEVEL=debug
|
|
OP_DISPUTE_MON_LOG_FORMAT=logfmt
|
|
OP_DISPUTE_MON_METRICS_ADDR=0.0.0.0
|
|
OP_DISPUTE_MON_METRICS_ENABLED=true
|
|
OP_DISPUTE_MON_METRICS_PORT=7300
|
|
EOF
|
|
|
|
cat > "${root}/deployment.json" <<EOF
|
|
{
|
|
"chain_id": ${chain_id},
|
|
"operator_address": "${operator_address}",
|
|
"l1_rpc_url": "${l1_rpc_url}",
|
|
"l1_beacon_url": "${l1_beacon_url}",
|
|
"p2p_advertise_ip": "${p2p_advertise_ip}",
|
|
"system_config_proxy": "${system_config_proxy}",
|
|
"dispute_game_factory": "${dispute_game_factory}",
|
|
"l1_standard_bridge": "${l1_standard_bridge}"
|
|
}
|
|
EOF
|
|
|
|
log "wrote bootstrap outputs under ${root}"
|