diff --git a/blockscout b/blockscout index 60d60cb3..3a1ba51e 160000 --- a/blockscout +++ b/blockscout @@ -1 +1 @@ -Subproject commit 60d60cb331b0289d39c7480379a14a86100af37a +Subproject commit 3a1ba51edf359b893050c25de5a1bb26c245c942 diff --git a/docker-compose.yaml b/docker-compose.yaml index 0f1e5501..2ec63237 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -166,7 +166,15 @@ services: - "l1keystore:/home/user/l1keystore" - "config:/config" - "tokenbridge-data:/tokenbridge-data" - command: --conf.file /config/sequencer_config.json --node.feed.output.enable --node.feed.output.port 9642 --http.api net,web3,eth,txpool,debug --node.seq-coordinator.my-url ws://sequencer:8548 --graphql.enable --graphql.vhosts * --graphql.corsdomain * + command: + - --conf.file=/config/sequencer_config.json + - --node.feed.output.enable + - --node.feed.output.port=9642 + - --http.api=net,web3,eth,txpool,debug,timeboost,auctioneer + - --node.seq-coordinator.my-url=http://sequencer:8547 + - --graphql.enable + - --graphql.vhosts=* + - --graphql.corsdomain=* depends_on: - geth @@ -180,7 +188,10 @@ services: volumes: - "seqdata_b:/home/user/.arbitrum/local/nitro" - "config:/config" - command: --conf.file /config/sequencer_config.json --node.seq-coordinator.my-url ws://sequencer_b:8548 + command: + - --conf.file=/config/sequencer_config.json + - --node.seq-coordinator.my-url=http://sequencer_b:8547 + - --http.api=net,web3,eth,txpool,debug,timeboost,auctioneer depends_on: - geth - redis @@ -195,7 +206,10 @@ services: volumes: - "seqdata_c:/home/user/.arbitrum/local/nitro" - "config:/config" - command: --conf.file /config/sequencer_config.json --node.seq-coordinator.my-url ws://sequencer_c:8548 + command: + - --conf.file=/config/sequencer_config.json + - --node.seq-coordinator.my-url=http://sequencer_c:8547 + - --http.api=net,web3,eth,txpool,debug,timeboost,auctioneer depends_on: - geth - redis @@ -210,7 +224,10 @@ services: volumes: - "seqdata_d:/home/user/.arbitrum/local/nitro" - "config:/config" - command: --conf.file /config/sequencer_config.json --node.seq-coordinator.my-url ws://sequencer_d:8548 + command: + - --conf.file=/config/sequencer_config.json + - --node.seq-coordinator.my-url=http://sequencer_d:8547 + - --http.api=net,web3,eth,txpool,debug,timeboost,auctioneer depends_on: - geth - redis @@ -434,6 +451,37 @@ services: command: - --conf.file=/config/l2_das_mirror.json + timeboost-auctioneer: + pid: host # allow debugging + image: nitro-node-dev-testnode + entrypoint: /usr/local/bin/autonomous-auctioneer + volumes: + - "config:/config" + - "timeboost-auctioneer-data:/data" + - "l1keystore:/home/user/l1keystore" + command: + - --conf.file=/config/autonomous_auctioneer_config.json + depends_on: + - redis + + timeboost-bid-validator: + pid: host # allow debugging + image: nitro-node-dev-testnode + entrypoint: /usr/local/bin/autonomous-auctioneer + ports: + - "127.0.0.1:9372:8547" + volumes: + - "config:/config" + command: + - --conf.file=/config/bid_validator_config.json + - --http.addr=0.0.0.0 + - --http.vhosts=* + - --http.corsdomain=* + - --http.api=auctioneer + - --log-level=INFO + depends_on: + - redis + volumes: l1data: consensus: @@ -453,4 +501,5 @@ volumes: das-committee-a-data: das-committee-b-data: das-mirror-data: + timeboost-auctioneer-data: boldupgrader-data: diff --git a/scripts/Dockerfile b/scripts/Dockerfile index ca2c677e..88bf9dcb 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -1,7 +1,15 @@ -FROM node:18-bullseye-slim +# Stage 1: Base build environment +FROM node:18-bullseye-slim AS base WORKDIR /workspace COPY ./package.json ./yarn.lock ./ RUN yarn + +# Stage 2: Copy files and run build +FROM base AS pre-build COPY ./*.ts ./tsconfig.json ./ +RUN echo "Intermediate image created before yarn build" + +# Stage 3: Final build +FROM pre-build AS final RUN yarn build ENTRYPOINT ["node", "index.js"] diff --git a/scripts/accounts.ts b/scripts/accounts.ts index 20c1cbbe..233d5f49 100644 --- a/scripts/accounts.ts +++ b/scripts/accounts.ts @@ -5,7 +5,7 @@ import * as crypto from "crypto"; import { runStress } from "./stress"; const path = require("path"); -const specialAccounts = 6; +const specialAccounts = 7; async function writeAccounts() { for (let i = 0; i < specialAccounts; i++) { @@ -47,6 +47,9 @@ export function namedAccount( if (name == "l2owner") { return specialAccount(5); } + if (name == "auctioneer") { + return specialAccount(6); + } if (name.startsWith("user_")) { return new ethers.Wallet( ethers.utils.sha256(ethers.utils.toUtf8Bytes(name)) @@ -85,7 +88,8 @@ export function namedAddress( export const namedAccountHelpString = "Valid account names:\n" + - " funnel | sequencer | validator | l2owner - known keys used by l2\n" + + " funnel | sequencer | validator | l2owner\n" + + " | auctioneer - known keys used by l2\n" + " l3owner | l3sequencer - known keys used by l3\n" + " user_[Alphanumeric] - key will be generated from username\n" + " threaduser_[Alphanumeric] - same as user_[Alphanumeric]_thread_[thread-id]\n" + diff --git a/scripts/config.ts b/scripts/config.ts index e10f5718..33cde21a 100644 --- a/scripts/config.ts +++ b/scripts/config.ts @@ -187,6 +187,7 @@ function writeConfigs(argv: any) { const valJwtSecret = path.join(consts.configpath, "val_jwt.hex") const chainInfoFile = path.join(consts.configpath, "l2_chain_info.json") let baseConfig = { + "ensure-rollup-deployment": false, "parent-chain": { "connection": { "url": argv.l1url, @@ -201,7 +202,7 @@ function writeConfigs(argv: any) { "dangerous": { "without-block-validator": false }, - "parent-chain-wallet" : { + "parent-chain-wallet": { "account": namedAddress("validator"), "password": consts.l1passphrase, "pathname": consts.l1keystore, @@ -235,7 +236,7 @@ function writeConfigs(argv: any) { "redis-url": argv.redisUrl, "max-delay": "30s", "l1-block-bound": "ignore", - "parent-chain-wallet" : { + "parent-chain-wallet": { "account": namedAddress("sequencer"), "password": consts.l1passphrase, "pathname": consts.l1keystore, @@ -267,7 +268,7 @@ function writeConfigs(argv: any) { }, "execution": { "sequencer": { - "enable": false, + "enable": false }, "forwarding-target": "null", }, @@ -319,6 +320,13 @@ function writeConfigs(argv: any) { sequencerConfig.node["seq-coordinator"].enable = true sequencerConfig.execution["sequencer"].enable = true sequencerConfig.node["delayed-sequencer"].enable = true + if (argv.timeboost) { + sequencerConfig.execution.sequencer.dangerous = {}; + sequencerConfig.execution.sequencer.dangerous.timeboost = { + "enable": false, // Create it false initially, turn it on with sed in test-node.bash after contract setup. + "redis-url": argv.redisUrl + }; + } fs.writeFileSync(path.join(consts.configpath, "sequencer_config.json"), JSON.stringify(sequencerConfig)) let posterConfig = JSON.parse(baseConfJSON) @@ -395,7 +403,7 @@ function writeL2ChainConfig(argv: any) { "EnableArbOS": true, "AllowDebugPrecompiles": true, "DataAvailabilityCommittee": argv.anytrust, - "InitialArbOSVersion": 32, + "InitialArbOSVersion": 32, // TODO For Timeboost, this still needs to be set to 31 "InitialChainOwner": argv.l2owner, "GenesisBlockNum": 0 } @@ -524,14 +532,67 @@ function dasBackendsJsonConfig(argv: any) { return backends } +export const writeTimeboostConfigsCommand = { + command: "write-timeboost-configs", + describe: "writes configs for the timeboost autonomous auctioneer and bid validator", + builder: { + "auction-contract": { + string: true, + describe: "auction contract address", + demandOption: true + }, + }, + handler: (argv: any) => { + writeAutonomousAuctioneerConfig(argv) + writeBidValidatorConfig(argv) + } +} + +function writeAutonomousAuctioneerConfig(argv: any) { + const autonomousAuctioneerConfig = { + "auctioneer-server": { + "auction-contract-address": argv.auctionContract, + "db-directory": "/data", + "redis-url": "redis://redis:6379", + "use-redis-coordinator": true, + "redis-coordinator-url": "redis://redis:6379", + "wallet": { + "account": namedAddress("auctioneer"), + "password": consts.l1passphrase, + "pathname": consts.l1keystore + }, + }, + "bid-validator": { + "enable": false + } + } + const autonomousAuctioneerConfigJSON = JSON.stringify(autonomousAuctioneerConfig) + fs.writeFileSync(path.join(consts.configpath, "autonomous_auctioneer_config.json"), autonomousAuctioneerConfigJSON) +} + +function writeBidValidatorConfig(argv: any) { + const bidValidatorConfig = { + "auctioneer-server": { + "enable": false + }, + "bid-validator": { + "auction-contract-address": argv.auctionContract, + "redis-url": "redis://redis:6379", + "sequencer-endpoint": "http://sequencer:8547" + } + } + const bidValidatorConfigJSON = JSON.stringify(bidValidatorConfig) + fs.writeFileSync(path.join(consts.configpath, "bid_validator_config.json"), bidValidatorConfigJSON) +} + export const writeConfigCommand = { command: "write-config", describe: "writes config files", builder: { simple: { - boolean: true, - describe: "simple config (sequencer is also poster, validator)", - default: false, + boolean: true, + describe: "simple config (sequencer is also poster, validator)", + default: false, }, anytrust: { boolean: true, @@ -548,8 +609,12 @@ export const writeConfigCommand = { describe: "DAS committee member B BLS pub key", default: "" }, - - }, + timeboost: { + boolean: true, + describe: "run sequencer in timeboost mode", + default: false + }, + }, handler: (argv: any) => { writeConfigs(argv) } @@ -625,8 +690,9 @@ export const writeL2DASKeysetConfigCommand = { describe: "DAS committee member B BLS pub key", default: "" }, - }, + }, handler: (argv: any) => { - writeL2DASKeysetConfig(argv) + writeL2DASKeysetConfig(argv) } } + diff --git a/scripts/ethcommands.ts b/scripts/ethcommands.ts index a7a0720d..27984cb5 100644 --- a/scripts/ethcommands.ts +++ b/scripts/ethcommands.ts @@ -8,6 +8,8 @@ import * as ERC20 from "@openzeppelin/contracts/build/contracts/ERC20.json"; import * as TestWETH9 from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/test/TestWETH9.sol/TestWETH9.json"; import * as fs from "fs"; import { ARB_OWNER } from "./consts"; +import * as TransparentUpgradeableProxy from "@openzeppelin/contracts/build/contracts/TransparentUpgradeableProxy.json" +import * as ExpressLaneAuctionContract from "@arbitrum/nitro-contracts/build/contracts/src/express-lane-auction/ExpressLaneAuction.sol/ExpressLaneAuction.json" const path = require("path"); async function sendTransaction(argv: any, threadId: number) { @@ -337,7 +339,8 @@ export const createERC20Command = { builder: { deployer: { string: true, - describe: "account (see general help)" + describe: "account (see general help)", + demandOption: true }, bridgeable: { boolean: true, @@ -440,6 +443,63 @@ export const createFeeTokenPricerCommand = { argv.provider.destroy(); }, }; + +export const deployExpressLaneAuctionContractCommand = { + command: "deploy-express-lane-auction", + describe: "Deploy the ExpressLaneAuction contract", + builder: { + "bidding-token": { + string: true, + describe: "bidding token address", + demandOption: true + }, + "auctioneer": { + string: true, + describe: "account name to set as auctioneer and admin on contract (default auctioneer)", + default: "auctioneer" + } + }, + handler: async (argv: any) => { + console.log("deploy ExpressLaneAuction contract"); + argv.provider = new ethers.providers.WebSocketProvider(argv.l2url); + const l2OwnerWallet = namedAccount("l2owner").connect(argv.provider) + const contractFactory = new ContractFactory(ExpressLaneAuctionContract.abi, ExpressLaneAuctionContract.bytecode, l2OwnerWallet) + + const contract = await contractFactory.deploy(); + await contract.deployTransaction.wait(); + console.log("ExpressLaneAuction contract deployed at address:", contract.address); + + const auctioneerAddr = namedAddress(argv.auctioneer) + const initIface = new ethers.utils.Interface(["function initialize((address,address,address,(int64,uint64,uint64,uint64),uint256,address,address,address,address,address,address,address))"]) + const initData = initIface.encodeFunctionData("initialize", [[ + auctioneerAddr, //_auctioneer + argv.biddingToken, //_biddingToken + auctioneerAddr, //_beneficiary + [ + Math.round(Date.now() / 60000) * 60, // offsetTimestamp - most recent minute + 60, // roundDurationSeconds + 15, // auctionClosingSeconds + 15 // reserveSubmissionSeconds + ],// RoundTiminginfo + 1, // _minReservePrice + auctioneerAddr, //_auctioneerAdmin + auctioneerAddr, //_minReservePriceSetter, + auctioneerAddr, //_reservePriceSetter, + auctioneerAddr, //_reservePriceSetterAdmin, + auctioneerAddr, //_beneficiarySetter, + auctioneerAddr, //_roundTimingSetter, + auctioneerAddr //_masterAdmin + ]]); + + const proxyFactory = new ethers.ContractFactory(TransparentUpgradeableProxy.abi, TransparentUpgradeableProxy.bytecode, l2OwnerWallet) + const proxy = await proxyFactory.deploy(contract.address, namedAddress("l2owner"), initData) + await proxy.deployed() + console.log("Proxy(ExpressLaneAuction) contract deployed at address:", proxy.address); + + argv.provider.destroy(); + } +}; + // Will revert if the keyset is already valid. async function setValidKeyset(argv: any, upgradeExecutorAddr: string, sequencerInboxAddr: string, keyset: string){ const innerIface = new ethers.utils.Interface(["function setValidKeyset(bytes)"]) diff --git a/scripts/index.ts b/scripts/index.ts index 80253b59..55a85b7b 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -2,7 +2,17 @@ import { hideBin } from "yargs/helpers"; import Yargs from "yargs/yargs"; import { stressOptions } from "./stress"; import { redisReadCommand, redisInitCommand } from "./redis"; -import { writeConfigCommand, writeGethGenesisCommand, writePrysmCommand, writeL2ChainConfigCommand, writeL3ChainConfigCommand, writeL2DASCommitteeConfigCommand, writeL2DASMirrorConfigCommand, writeL2DASKeysetConfigCommand } from "./config"; +import { + writeConfigCommand, + writeGethGenesisCommand, + writePrysmCommand, + writeL2ChainConfigCommand, + writeL3ChainConfigCommand, + writeL2DASCommitteeConfigCommand, + writeL2DASMirrorConfigCommand, + writeL2DASKeysetConfigCommand, + writeTimeboostConfigsCommand +} from "./config"; import { printAddressCommand, namedAccountHelpString, @@ -14,6 +24,7 @@ import { bridgeNativeTokenToL3Command, bridgeToL3Command, createERC20Command, + deployExpressLaneAuctionContractCommand, createWETHCommand, transferERC20Command, sendL1Command, @@ -43,6 +54,7 @@ async function main() { .command(bridgeNativeTokenToL3Command) .command(createERC20Command) .command(createFeeTokenPricerCommand) + .command(deployExpressLaneAuctionContractCommand) .command(createWETHCommand) .command(transferERC20Command) .command(sendL1Command) @@ -60,6 +72,7 @@ async function main() { .command(writeL2DASKeysetConfigCommand) .command(writePrysmCommand) .command(writeAccountsCommand) + .command(writeTimeboostConfigsCommand) .command(printAddressCommand) .command(printPrivateKeyCommand) .command(redisReadCommand) diff --git a/scripts/package.json b/scripts/package.json index d1de3706..c2dc9db9 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -6,6 +6,7 @@ "author": "Offchain Labs, Inc.", "license": "Apache-2.0", "dependencies": { + "@arbitrum/nitro-contracts": "^2.1.1", "@arbitrum/token-bridge-contracts": "1.2.0", "@node-redis/client": "^1.0.4", "@openzeppelin/contracts": "^4.9.3", diff --git a/scripts/redis.ts b/scripts/redis.ts index 1d13c21d..1282368a 100644 --- a/scripts/redis.ts +++ b/scripts/redis.ts @@ -40,14 +40,14 @@ async function writeRedisPriorities(redisUrl: string, priorities: number) { let prio_sequencers = "bcd"; let priostring = ""; if (priorities == 0) { - priostring = "ws://sequencer:8548"; + priostring = "http://sequencer:8547"; } if (priorities > prio_sequencers.length) { priorities = prio_sequencers.length; } for (let index = 0; index < priorities; index++) { const this_prio = - "ws://sequencer_" + prio_sequencers.charAt(index) + ":8548"; + "http://sequencer_" + prio_sequencers.charAt(index) + ":8547"; if (index != 0) { priostring = priostring + ","; } diff --git a/test-node.bash b/test-node.bash index 643a82fe..1e666899 100755 --- a/test-node.bash +++ b/test-node.bash @@ -2,7 +2,7 @@ set -eu -NITRO_NODE_VERSION=offchainlabs/nitro-node:v3.2.1-d81324d-dev +NITRO_NODE_VERSION=offchainlabs/nitro-node:v3.5.3-rc.3-653b078 BLOCKSCOUT_VERSION=offchainlabs/blockscout:v1.1.0-0e716c8 DEFAULT_NITRO_CONTRACTS_VERSION="v2.1.1-beta.0" @@ -60,10 +60,12 @@ devprivkey=b6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 l1chainid=1337 simple=true l2anytrust=false +l2timeboost=false # Use the dev versions of nitro/blockscout dev_nitro=false dev_blockscout=false +dev_contracts=false # Rebuild docker images build_dev_nitro=false @@ -117,6 +119,9 @@ while [[ $# -gt 0 ]]; do done fi ;; + --dev-contracts) + dev_contracts=true + ;; --ci) ci=true shift @@ -161,6 +166,7 @@ while [[ $# -gt 0 ]]; do ;; --force-build-utils) force_build_utils=true + build_utils=true shift ;; --validate) @@ -260,6 +266,10 @@ while [[ $# -gt 0 ]]; do l2anytrust=true shift ;; + --l2-timeboost) + l2timeboost=true + shift + ;; --redundantsequencers) simple=false redundantsequencers=$2 @@ -286,6 +296,7 @@ while [[ $# -gt 0 ]]; do echo --build rebuild docker images echo --no-build don\'t rebuild docker images echo --dev build nitro and blockscout dockers from source instead of pulling them. Disables simple mode + echo --dev-contracts build scripts with local development version of contracts echo --init remove all data, rebuild, deploy new rollup echo --pos l1 is a proof-of-stake chain \(using prysm for consensus\) echo --validate heavy computation, validating all blocks in WASM @@ -294,6 +305,7 @@ while [[ $# -gt 0 ]]; do echo --l3-fee-token-decimals Number of decimals to use for custom fee token. Only valid if also '--l3-fee-token' is provided echo --l3-token-bridge Deploy L2-L3 token bridge. Only valid if also '--l3node' is provided echo --l2-anytrust run the L2 as an AnyTrust chain + echo --l2-timeboost run the L2 with Timeboost enabled, including auctioneer and bid validator echo --batchposters batch posters [0-3] echo --redundantsequencers redundant sequencers [0-3] echo --detach detach from nodes after running them @@ -356,6 +368,10 @@ if $blockscout; then NODES="$NODES blockscout" fi +if $l2timeboost; then + NODES="$NODES timeboost-auctioneer timeboost-bid-validator" +fi + if $dev_nitro && $build_dev_nitro; then echo == Building Nitro if ! [ -n "${NITRO_SRC+set}" ]; then @@ -484,11 +500,16 @@ if $force_init; then echo == Deploying L2 chain docker compose run -e PARENT_CHAIN_RPC="http://geth:8545" -e DEPLOYER_PRIVKEY=$l2ownerKey -e PARENT_CHAIN_ID=$l1chainid -e CHILD_CHAIN_NAME="arb-dev-test" -e MAX_DATA_SIZE=117964 -e OWNER_ADDRESS=$l2ownerAddress -e WASM_MODULE_ROOT=$wasmroot -e SEQUENCER_ADDRESS=$sequenceraddress -e AUTHORIZE_VALIDATORS=10 -e CHILD_CHAIN_CONFIG_PATH="/config/l2_chain_config.json" -e CHAIN_DEPLOYMENT_INFO="/config/deployment.json" -e CHILD_CHAIN_INFO="/config/deployed_chain_info.json" rollupcreator create-rollup-testnode - docker compose run --entrypoint sh rollupcreator -c "jq [.[]] /config/deployed_chain_info.json > /config/l2_chain_info.json" + if $l2timeboost; then + docker compose run --entrypoint sh rollupcreator -c 'jq ".[] | .\"track-block-metadata-from\"=1 | [.]" /config/deployed_chain_info.json > /config/l2_chain_info.json' + else + docker compose run --entrypoint sh rollupcreator -c "jq [.[]] /config/deployed_chain_info.json > /config/l2_chain_info.json" + fi fi # $force_init anytrustNodeConfigLine="" +timeboostNodeConfigLine="" # Remaining init may require AnyTrust committee/mirrors to have been started if $l2anytrust; then @@ -518,12 +539,15 @@ if $l2anytrust; then fi if $force_init; then + if $l2timeboost; then + timeboostNodeConfigLine="--timeboost" + fi if $simple; then echo == Writing configs - docker compose run scripts write-config --simple $anytrustNodeConfigLine + docker compose run scripts write-config --simple $anytrustNodeConfigLine $timeboostNodeConfigLine else echo == Writing configs - docker compose run scripts write-config $anytrustNodeConfigLine + docker compose run scripts write-config $anytrustNodeConfigLine $timeboostNodeConfigLine echo == Initializing redis docker compose up --wait redis @@ -536,6 +560,26 @@ if $force_init; then docker compose run scripts send-l2 --ethamount 100 --to l2owner --wait rollupAddress=`docker compose run --entrypoint sh poster -c "jq -r '.[0].rollup.rollup' /config/deployed_chain_info.json | tail -n 1 | tr -d '\r\n'"` + if $l2timeboost; then + docker compose run scripts send-l2 --ethamount 100 --to auctioneer --wait + biddingTokenAddress=`docker compose run scripts create-erc20 --deployer auctioneer | tail -n 1 | awk '{ print $NF }'` + auctionContractAddress=`docker compose run scripts deploy-express-lane-auction --bidding-token $biddingTokenAddress | tail -n 1 | awk '{ print $NF }'` + auctioneerAddress=`docker compose run scripts print-address --account auctioneer | tail -n1 | tr -d '\r\n'` + echo == Starting up Timeboost auctioneer and bid validator. + echo == Bidding token: $biddingTokenAddress, auction contract $auctionContractAddress + docker compose run scripts write-timeboost-configs --auction-contract $auctionContractAddress + docker compose run --user root --entrypoint sh timeboost-auctioneer -c "chown -R 1000:1000 /data" + + echo == Funding alice and bob user accounts for timeboost testing + docker compose run scripts send-l2 --ethamount 10 --to user_alice --wait + docker compose run scripts send-l2 --ethamount 10 --to user_bob --wait + docker compose run scripts transfer-erc20 --token $biddingTokenAddress --amount 10000 --from auctioneer --to user_alice + docker compose run scripts transfer-erc20 --token $biddingTokenAddress --amount 10000 --from auctioneer --to user_bob + + docker compose run --entrypoint sh scripts -c "sed -i 's/\(\"execution\":{\"sequencer\":{\"enable\":true,\"dangerous\":{\"timeboost\":{\"enable\":\)false/\1true,\"auction-contract-address\":\"$auctionContractAddress\",\"auctioneer-address\":\"$auctioneerAddress\"/' /config/sequencer_config.json" --wait + docker compose restart $INITIAL_SEQ_NODES + fi + if $tokenbridge; then echo == Deploying L1-L2 token bridge sleep 10 # no idea why this sleep is needed but without it the deploy fails randomly