diff --git a/.clabot b/.clabot new file mode 100644 index 00000000..55cb5e61 --- /dev/null +++ b/.clabot @@ -0,0 +1,5 @@ +{ + "contributors": "https://api.github.com/repos/OffchainLabs/clabot-config/contents/nitro-contributors.json", + "message": "We require contributors to sign our Contributor License Agreement. In order for us to review and merge your code, please sign the linked documents below to get yourself added. https://na3.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=b15c81cc-b5ea-42a6-9107-3992526f2898&env=na3&acct=6e152afc-6284-44af-a4c1-d8ef291db402&v=2", + "label": "s" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a78e451c..fd2eb2a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,36 +2,40 @@ name: CI run-name: CI triggered from @${{ github.actor }} of ${{ github.head_ref }} on: - workflow_dispatch: - merge_group: - pull_request: - push: - branches: - - master - - develop - + workflow_dispatch: + merge_group: + pull_request: + push: + branches: + - master + - develop jobs: build_and_run: runs-on: ubuntu-8 + strategy: + matrix: + pos: [pos, no-pos] + l3node: [l3node, l3node-token-6, no-l3node] + tokenbridge: [tokenbridge, no-tokenbridge] steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: network=host + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host - - name: Cache Docker layers - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }} - restore-keys: ${{ runner.os }}-buildx- - - - name: Startup Nitro testnode - run: ${{ github.workspace }}/.github/workflows/testnode.bash + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }} + restore-keys: ${{ runner.os }}-buildx- + + - name: Startup Nitro testnode + run: ${{ github.workspace }}/.github/workflows/testnode.bash --init-force ${{ (matrix.l3node == 'l3node' && '--l3node') || (matrix.l3node == 'l3node-token-6' && '--l3node --l3-fee-token --l3-token-bridge --l3-fee-token-decimals 6') || '' }} ${{ matrix.tokenbridge == 'tokenbridge' && '--tokenbridge' || '--no-tokenbridge' }} --no-simple --detach ${{ matrix.pos == 'pos' && '--pos' || '' }} diff --git a/.github/workflows/testnode.bash b/.github/workflows/testnode.bash index 0e57e13d..be7e6961 100755 --- a/.github/workflows/testnode.bash +++ b/.github/workflows/testnode.bash @@ -5,24 +5,36 @@ # Start the test node and get PID, to terminate it once send-l2 is done. cd ${GITHUB_WORKSPACE} -# TODO once develop is merged into nitro-contract's master, remove the NITRO_CONTRACTS_BRANCH env var -./test-node.bash --init-force --l3node --no-simple --detach +./test-node.bash "$@" + +if [ $? -ne 0 ]; then + echo "test-node.bash failed" + docker compose logs --tail=1000 + exit 1 +fi + START=$(date +%s) L2_TRANSACTION_SUCCEEDED=false -L3_TRANSACTION_SUCCEEDED=false +# if we're not running an l3node then we just set l3 to success by default +L3_TRANSACTION_SUCCEEDED=true +for arg in "$@"; do + if [ "$arg" = "--l3node" ]; then + L3_TRANSACTION_SUCCEEDED=false + fi +done SUCCEEDED=false while true; do if [ "$L2_TRANSACTION_SUCCEEDED" = false ]; then - if ${GITHUB_WORKSPACE}/test-node.bash script send-l2 --ethamount 100 --to user_l2user --wait; then + if ${GITHUB_WORKSPACE}/test-node.bash script send-l2 --ethamount 2 --to user_l2user --wait; then echo "Sending l2 transaction succeeded" L2_TRANSACTION_SUCCEEDED=true fi fi if [ "$L3_TRANSACTION_SUCCEEDED" = false ]; then - if ${GITHUB_WORKSPACE}/test-node.bash script send-l3 --ethamount 100 --to user_l3user --wait; then + if ${GITHUB_WORKSPACE}/test-node.bash script send-l3 --ethamount 2 --to user_l3user --wait; then echo "Sending l3 transaction succeeded" L3_TRANSACTION_SUCCEEDED=true fi @@ -44,10 +56,10 @@ while true; do sleep 10 done -docker-compose stop +docker compose stop if [ "$SUCCEEDED" = false ]; then - docker-compose logs + docker compose logs exit 1 fi diff --git a/docker-compose.yaml b/docker-compose.yaml index 08f87711..d2d828e0 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,7 +5,7 @@ services: - sequencer image: blockscout-testnode restart: always - container_name: 'blockscout' + container_name: "blockscout" links: - postgres:database command: @@ -16,11 +16,11 @@ services: node init/install.js postgres 5432 bin/blockscout start extra_hosts: - - 'host.docker.internal:host-gateway' + - "host.docker.internal:host-gateway" env_file: - ./blockscout/nitro.env environment: - ETHEREUM_JSONRPC_VARIANT: 'geth' + ETHEREUM_JSONRPC_VARIANT: "geth" ETHEREUM_JSONRPC_HTTP_URL: http://sequencer:8547/ INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: "true" DATABASE_URL: postgresql://postgres:@postgres:5432/blockscout @@ -31,11 +31,11 @@ services: postgres: image: postgres:13.6 restart: always - container_name: 'postgres' + container_name: "postgres" environment: - POSTGRES_PASSWORD: '' - POSTGRES_USER: 'postgres' - POSTGRES_HOST_AUTH_METHOD: 'trust' + POSTGRES_PASSWORD: "" + POSTGRES_USER: "postgres" + POSTGRES_HOST_AUTH_METHOD: "trust" volumes: - "postgres-data:/var/lib/postgresql/data" ports: @@ -47,7 +47,7 @@ services: - "127.0.0.1:6379:6379" geth: - image: ethereum/client-go:v1.10.23 + image: ethereum/client-go:latest ports: - "127.0.0.1:8545:8545" - "127.0.0.1:8551:8551" @@ -77,6 +77,7 @@ services: - --authrpc.jwtsecret=/config/jwt.hex - --nodiscover - --syncmode=full + - --state.scheme=hash - --dev - --dev.period=1 - --mine @@ -91,9 +92,13 @@ services: command: - testnet - generate-genesis + - --fork=deneb - --num-validators=64 + - --genesis-time-delay=15 - --output-ssz=/consensus/genesis.ssz - --chain-config-file=/config/prysm.yaml + - --geth-genesis-json-in=/config/geth_genesis.json + - --geth-genesis-json-out=/config/geth_genesis.json volumes: - "consensus:/consensus" - "config:/config" @@ -108,16 +113,17 @@ services: - --datadir=/consensus/beacondata - --rpc-port=5000 - --min-sync-peers=0 - - --interop-genesis-state=/consensus/genesis.ssz + - --genesis-state=/consensus/genesis.ssz - --interop-eth1data-votes - --bootstrap-node= - --chain-config-file=/config/prysm.yaml - --rpc-host=0.0.0.0 - --grpc-gateway-host=0.0.0.0 - - --chain-id=32382 + - --chain-id=1337 - --execution-endpoint=http://geth:8551 - --accept-terms-of-use - --jwt-secret=/config/jwt.hex + - --suggested-fee-recipient=0x000000000000000000000000000000000000dEaD depends_on: geth: condition: service_started @@ -169,7 +175,7 @@ services: pid: host # allow debugging image: espresso-integration-testnode entrypoint: /usr/local/bin/nitro - ports: + ports: - "127.0.0.1:8547:8547" - "127.0.0.1:8548:8548" - "127.0.0.1:9642:9642" @@ -181,7 +187,7 @@ services: 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 * depends_on: - geth - + sequencer-espresso-finality: pid: host # allow debugging image: nitro-node-dev-testnode @@ -198,7 +204,7 @@ services: command: --conf.file /config/espresso_finality_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 * depends_on: - geth - + sequencer_b: pid: host # allow debugging image: nitro-node-dev-testnode @@ -429,7 +435,11 @@ services: - geth - sequencer healthcheck: - test: ["CMD-SHELL", "curl -fL http://localhost:$ESPRESSO_DEV_NODE_PORT || exit 1"] + test: + [ + "CMD-SHELL", + "curl -fL http://localhost:$ESPRESSO_DEV_NODE_PORT || exit 1", + ] interval: 30s timeout: 10s retries: 5 @@ -459,6 +469,53 @@ services: timeout: 10s retries: 5 start_period: 40s + datool: + image: nitro-node-dev-testnode + entrypoint: /usr/local/bin/datool + volumes: + - "config:/config" + - "das-committee-a-data:/das-committee-a" + - "das-committee-b-data:/das-committee-b" + - "das-mirror-data:/das-mirror" + command: + + das-committee-a: + pid: host # allow debugging + image: nitro-node-dev-testnode + entrypoint: /usr/local/bin/daserver + ports: + - "127.0.0.1:9876:9876" + - "127.0.0.1:9877:9877" + volumes: + - "config:/config" + - "das-committee-a-data:/das" + command: + - --conf.file=/config/l2_das_committee.json + + das-committee-b: + pid: host # allow debugging + image: nitro-node-dev-testnode + entrypoint: /usr/local/bin/daserver + ports: + - "127.0.0.1:8876:9876" + - "127.0.0.1:8877:9877" + volumes: + - "config:/config" + - "das-committee-b-data:/das" + command: + - --conf.file=/config/l2_das_committee.json + + das-mirror: + pid: host # allow debugging + image: nitro-node-dev-testnode + entrypoint: /usr/local/bin/daserver + ports: + - "127.0.0.1:7877:9877" + volumes: + - "config:/config" + - "das-mirror-data:/das" + command: + - --conf.file=/config/l2_das_mirror.json volumes: l1data: @@ -478,3 +535,6 @@ volumes: espresso-config: postgres-data: tokenbridge-data: + das-committee-a-data: + das-committee-b-data: + das-mirror-data: diff --git a/scripts/config.ts b/scripts/config.ts index 6b80e692..1efeb8c8 100644 --- a/scripts/config.ts +++ b/scripts/config.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as consts from './consts' +import { ethers } from "ethers"; import { namedAccount, namedAddress } from './accounts' const path = require("path"); @@ -13,16 +14,28 @@ PRESET_BASE: interop GENESIS_FORK_VERSION: 0x20000089 # Altair -ALTAIR_FORK_EPOCH: 1 +ALTAIR_FORK_EPOCH: 0 ALTAIR_FORK_VERSION: 0x20000090 # Merge -BELLATRIX_FORK_EPOCH: 2 +BELLATRIX_FORK_EPOCH: 0 BELLATRIX_FORK_VERSION: 0x20000091 TERMINAL_TOTAL_DIFFICULTY: 50 +# Capella +CAPELLA_FORK_EPOCH: 0 +CAPELLA_FORK_VERSION: 0x20000092 +MAX_WITHDRAWALS_PER_PAYLOAD: 16 + +# DENEB +DENEB_FORK_EPOCH: 0 +DENEB_FORK_VERSION: 0x20000093 + +# ELECTRA +ELECTRA_FORK_VERSION: 0x20000094 + # Time parameters -SECONDS_PER_SLOT: 12 +SECONDS_PER_SLOT: 2 SLOTS_PER_EPOCH: 6 # Deposit contract @@ -36,8 +49,7 @@ function writeGethGenesisConfig(argv: any) { { "config": { "ChainName": "l1_chain", - "chainId": 32382, - "consensus": "clique", + "chainId": 1337, "homesteadBlock": 0, "daoForkSupport": true, "eip150Block": 0, @@ -54,13 +66,12 @@ function writeGethGenesisConfig(argv: any) { "terminalBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "arrowGlacierBlock": 0, "grayGlacierBlock": 0, - "clique": { - "period": 5, - "epoch": 30000 - }, - "terminalTotalDifficulty": 50 + "shanghaiTime": 0, + "cancunTime": 1706778826, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true }, - "difficulty": "1", + "difficulty": "0", "extradata": "0x00000000000000000000000000000000000000000000000000000000000000003f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E0B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x42", "timestamp": "0x0", @@ -150,10 +161,22 @@ function writeGethGenesisConfig(argv: any) { fs.writeFileSync(path.join(consts.configpath, "val_jwt.hex"), val_jwt) } +type ChainInfo = { + [key: string]: any; +}; + +// Define a function to return ChainInfo +function getChainInfo(): ChainInfo { + const filePath = path.join(consts.configpath, "l2_chain_info.json"); + const fileContents = fs.readFileSync(filePath).toString(); + const chainInfo: ChainInfo = JSON.parse(fileContents); + return chainInfo; +} + function writeConfigs(argv: any) { const valJwtSecret = path.join(consts.configpath, "val_jwt.hex") const chainInfoFile = path.join(consts.configpath, "l2_chain_info.json") - const baseConfig = { + let baseConfig = { "parent-chain": { "connection": { "url": argv.l1url, @@ -171,7 +194,7 @@ function writeConfigs(argv: any) { "parent-chain-wallet" : { "account": namedAddress("validator"), "password": consts.l1passphrase, - "pathname": consts.l1keystore, + "pathname": consts.l1keystore, }, "disable-challenge": false, "enable": false, @@ -205,7 +228,7 @@ function writeConfigs(argv: any) { "parent-chain-wallet" : { "account": namedAddress("sequencer"), "password": consts.l1passphrase, - "pathname": consts.l1keystore, + "pathname": consts.l1keystore, }, "data-poster": { "redis-signer": { @@ -230,6 +253,17 @@ function writeConfigs(argv: any) { "signed": false, "addr": "0.0.0.0", }, + }, + "data-availability": { + "enable": argv.anytrust, + "rpc-aggregator": dasBackendsJsonConfig(argv), + "rest-aggregator": { + "enable": true, + "urls": ["http://das-mirror:9877"], + }, + // TODO Fix das config to not need this redundant config + "parent-chain-node-url": argv.l1url, + "sequencer-inbox-address": "not_set" } }, "execution": { @@ -252,6 +286,7 @@ function writeConfigs(argv: any) { }, } +<<<<<<< HEAD if (argv.espresso) { let config = baseConfig as any config.node['block-validator']["espresso"] = false @@ -265,6 +300,9 @@ function writeConfigs(argv: any) { } } +======= + baseConfig.node["data-availability"]["sequencer-inbox-address"] = ethers.utils.hexlify(getChainInfo()[0]["rollup"]["sequencer-inbox"]); +>>>>>>> upstream/master const baseConfJSON = JSON.stringify(baseConfig) @@ -284,6 +322,9 @@ function writeConfigs(argv: any) { } simpleConfig.execution["sequencer"].enable = true + if (argv.anytrust) { + simpleConfig.node["data-availability"]["rpc-aggregator"].enable = true + } if (argv.espresso) { simpleConfig.node.feed.output.enable = true simpleConfig.node["batch-poster"]["hotshot-url"] = argv.espressoUrl @@ -384,6 +425,9 @@ function writeConfigs(argv: any) { posterConfig.node["seq-coordinator"].enable = true } posterConfig.node["batch-poster"].enable = true + if (argv.anytrust) { + posterConfig.node["data-availability"]["rpc-aggregator"].enable = true + } fs.writeFileSync(path.join(consts.configpath, "poster_config.json"), JSON.stringify(posterConfig)) } @@ -456,8 +500,8 @@ function writeL2ChainConfig(argv: any) { "arbitrum": { "EnableArbOS": true, "AllowDebugPrecompiles": true, - "DataAvailabilityCommittee": false, - "InitialArbOSVersion": 30, + "DataAvailabilityCommittee": argv.anytrust, + "InitialArbOSVersion": 32, "InitialChainOwner": argv.l2owner, "GenesisBlockNum": 0, } @@ -495,7 +539,7 @@ function writeL3ChainConfig(argv: any) { "EnableArbOS": true, "AllowDebugPrecompiles": true, "DataAvailabilityCommittee": false, - "InitialArbOSVersion": 30, + "InitialArbOSVersion": 31, "InitialChainOwner": argv.l2owner, "GenesisBlockNum": 0, } @@ -509,6 +553,93 @@ function writeL3ChainConfig(argv: any) { fs.writeFileSync(path.join(consts.configpath, "l3_chain_config.json"), l3ChainConfigJSON) } +function writeL2DASCommitteeConfig(argv: any) { + const sequencerInboxAddr = ethers.utils.hexlify(getChainInfo()[0]["rollup"]["sequencer-inbox"]); + const l2DASCommitteeConfig = { + "data-availability": { + "key": { + "key-dir": "/das/keys" + }, + "local-file-storage": { + "data-dir": "/das/data", + "enable": true, + "enable-expiry": true + }, + "sequencer-inbox-address": sequencerInboxAddr, + "parent-chain-node-url": argv.l1url + }, + "enable-rest": true, + "enable-rpc": true, + "log-level": "INFO", + "rest-addr": "0.0.0.0", + "rest-port": "9877", + "rpc-addr": "0.0.0.0", + "rpc-port": "9876" + } + const l2DASCommitteeConfigJSON = JSON.stringify(l2DASCommitteeConfig) + + fs.writeFileSync(path.join(consts.configpath, "l2_das_committee.json"), l2DASCommitteeConfigJSON) +} + +function writeL2DASMirrorConfig(argv: any, sequencerInboxAddr: string) { + const l2DASMirrorConfig = { + "data-availability": { + "local-file-storage": { + "data-dir": "/das/data", + "enable": true, + "enable-expiry": false + }, + "sequencer-inbox-address": sequencerInboxAddr, + "parent-chain-node-url": argv.l1url, + "rest-aggregator": { + "enable": true, + "sync-to-storage": { + "eager": false, + "ignore-write-errors": false, + "state-dir": "/das/metadata", + "sync-expired-data": true + }, + "urls": ["http://das-committee-a:9877", "http://das-committee-b:9877"], + } + }, + "enable-rest": true, + "enable-rpc": false, + "log-level": "INFO", + "rest-addr": "0.0.0.0", + "rest-port": "9877" + } + const l2DASMirrorConfigJSON = JSON.stringify(l2DASMirrorConfig) + + fs.writeFileSync(path.join(consts.configpath, "l2_das_mirror.json"), l2DASMirrorConfigJSON) +} + +function writeL2DASKeysetConfig(argv: any) { + const l2DASKeysetConfig = { + "keyset": dasBackendsJsonConfig(argv) + } + const l2DASKeysetConfigJSON = JSON.stringify(l2DASKeysetConfig) + + fs.writeFileSync(path.join(consts.configpath, "l2_das_keyset.json"), l2DASKeysetConfigJSON) +} + +function dasBackendsJsonConfig(argv: any) { + const backends = { + "enable": false, + "assumed-honest": 1, + "backends": [ + { + "url": "http://das-committee-a:9876", + "pubkey": argv.dasBlsA + }, + { + "url": "http://das-committee-b:9876", + "pubkey": argv.dasBlsB + } + ] + } + return backends +} + export const writeConfigCommand = { command: "write-config", describe: "writes config files", @@ -523,7 +654,23 @@ export const writeConfigCommand = { describe: "simple config but with real validator", default: false, }, - }, + anytrust: { + boolean: true, + describe: "run nodes in anytrust mode", + default: false + }, + dasBlsA: { + string: true, + describe: "DAS committee member A BLS pub key", + default: "" + }, + dasBlsB: { + string: true, + describe: "DAS committee member B BLS pub key", + default: "" + }, + + }, handler: (argv: any) => { writeConfigs(argv) } @@ -548,6 +695,13 @@ export const writeGethGenesisCommand = { export const writeL2ChainConfigCommand = { command: "write-l2-chain-config", describe: "writes l2 chain config file", + builder: { + anytrust: { + boolean: true, + describe: "enable anytrust in chainconfig", + default: false + }, + }, handler: (argv: any) => { writeL2ChainConfig(argv) } @@ -559,4 +713,41 @@ export const writeL3ChainConfigCommand = { handler: (argv: any) => { writeL3ChainConfig(argv) } -} \ No newline at end of file +} + +export const writeL2DASCommitteeConfigCommand = { + command: "write-l2-das-committee-config", + describe: "writes daserver committee member config file", + handler: (argv: any) => { + writeL2DASCommitteeConfig(argv) + } +} + +export const writeL2DASMirrorConfigCommand = { + command: "write-l2-das-mirror-config", + describe: "writes daserver mirror config file", + handler: (argv: any) => { + const sequencerInboxAddr = ethers.utils.hexlify(getChainInfo()[0]["rollup"]["sequencer-inbox"]); + writeL2DASMirrorConfig(argv, sequencerInboxAddr) + } +} + +export const writeL2DASKeysetConfigCommand = { + command: "write-l2-das-keyset-config", + describe: "writes DAS keyset config", + builder: { + dasBlsA: { + string: true, + describe: "DAS committee member A BLS pub key", + default: "" + }, + dasBlsB: { + string: true, + describe: "DAS committee member B BLS pub key", + default: "" + }, + }, + handler: (argv: any) => { + writeL2DASKeysetConfig(argv) + } +} diff --git a/scripts/consts.ts b/scripts/consts.ts index ff322260..edfcedd6 100644 --- a/scripts/consts.ts +++ b/scripts/consts.ts @@ -5,3 +5,5 @@ export const tokenbridgedatapath = "/tokenbridge-data"; // Not secure. Do not use for production purposes export const l1mnemonic = "indoor dish desk flag debris potato excuse depart ticket judge file exit"; + +export const ARB_OWNER = "0x0000000000000000000000000000000000000070"; \ No newline at end of file diff --git a/scripts/ethcommands.ts b/scripts/ethcommands.ts index 39254d25..06285e79 100644 --- a/scripts/ethcommands.ts +++ b/scripts/ethcommands.ts @@ -3,8 +3,10 @@ import { BigNumber, ContractFactory, ethers, Wallet } from "ethers"; import * as consts from "./consts"; import { namedAccount, namedAddress } from "./accounts"; import * as L1GatewayRouter from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol/L1GatewayRouter.json"; +import * as L1AtomicTokenBridgeCreator from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol/L1AtomicTokenBridgeCreator.json"; import * as ERC20 from "@openzeppelin/contracts/build/contracts/ERC20.json"; import * as fs from "fs"; +import { ARB_OWNER } from "./consts"; const path = require("path"); async function sendTransaction(argv: any, threadId: number) { @@ -235,6 +237,56 @@ export const bridgeNativeTokenToL3Command = { }, }; +export const transferL3ChainOwnershipCommand = { + command: "transfer-l3-chain-ownership", + describe: "transfer L3 chain ownership to upgrade executor", + builder: { + creator: { + string: true, + describe: "address of the token bridge creator", + }, + wait: { + boolean: true, + describe: "wait till ownership is transferred", + default: false, + }, + }, + handler: async (argv: any) => { + // get inbox address from config file + const deploydata = JSON.parse( + fs + .readFileSync(path.join(consts.configpath, "l3deployment.json")) + .toString() + ); + const inboxAddr = ethers.utils.hexlify(deploydata.inbox); + + // get L3 upgrade executor address from token bridge creator + const l2provider = new ethers.providers.WebSocketProvider(argv.l2url); + const tokenBridgeCreator = new ethers.Contract(argv.creator, L1AtomicTokenBridgeCreator.abi, l2provider); + const [,,,,,,,l3UpgradeExecutorAddress,] = await tokenBridgeCreator.inboxToL2Deployment(inboxAddr); + + // set TX params + argv.provider = new ethers.providers.WebSocketProvider(argv.l3url); + argv.to = "address_" + ARB_OWNER; + argv.from = "l3owner"; + argv.ethamount = "0"; + + // add L3 UpgradeExecutor to chain owners + const arbOwnerIface = new ethers.utils.Interface([ + "function addChainOwner(address newOwner) external", + "function removeChainOwner(address ownerToRemove) external" + ]) + argv.data = arbOwnerIface.encodeFunctionData("addChainOwner", [l3UpgradeExecutorAddress]); + await runStress(argv, sendTransaction); + + // remove L3 owner from chain owners + argv.data = arbOwnerIface.encodeFunctionData("removeChainOwner", [namedAccount("l3owner").address]); + await runStress(argv, sendTransaction); + + argv.provider.destroy(); + } +}; + export const createERC20Command = { command: "create-erc20", describe: "creates simple ERC20 on L2", @@ -279,8 +331,10 @@ export const createERC20Command = { const l1GatewayRouter = new ethers.Contract(l1l2tokenbridge.l2Network.tokenBridge.l1GatewayRouter, L1GatewayRouter.abi, deployerWallet); await (await token.functions.approve(l1l2tokenbridge.l2Network.tokenBridge.l1ERC20Gateway, ethers.constants.MaxUint256)).wait(); const supply = await token.totalSupply(); + // transfer 90% of supply to l2 + const transferAmount = supply.mul(9).div(10); await (await l1GatewayRouter.functions.outboundTransfer( - token.address, deployerWallet.address, supply, 100000000, 1000000000, "0x000000000000000000000000000000000000000000000000000fffffffffff0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", { + token.address, deployerWallet.address, transferAmount, 100000000, 1000000000, "0x000000000000000000000000000000000000000000000000000fffffffffff0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", { value: ethers.utils.parseEther("1"), } )).wait(); @@ -318,6 +372,25 @@ export const createERC20Command = { }, }; +// 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)"]) + const innerData = innerIface.encodeFunctionData("setValidKeyset", [keyset]); + + // The Executor contract is the owner of the SequencerInbox so calls must be made + // through it. + const outerIface = new ethers.utils.Interface(["function executeCall(address,bytes)"]) + argv.data = outerIface.encodeFunctionData("executeCall", [sequencerInboxAddr, innerData]); + + argv.from = "l2owner"; + argv.to = "address_" + upgradeExecutorAddr + argv.ethamount = "0" + + await sendTransaction(argv, 0); + + argv.provider.destroy(); +} + export const transferERC20Command = { command: "transfer-erc20", describe: "transfers ERC20 token", @@ -471,3 +544,43 @@ export const sendRPCCommand = { await rpcProvider.send(argv.method, argv.params) } } + +export const setValidKeysetCommand = { + command: "set-valid-keyset", + describe: "sets the anytrust keyset", + handler: async (argv: any) => { + argv.provider = new ethers.providers.WebSocketProvider(argv.l1url); + const deploydata = JSON.parse( + fs + .readFileSync(path.join(consts.configpath, "deployment.json")) + .toString() + ); + const sequencerInboxAddr = ethers.utils.hexlify(deploydata["sequencer-inbox"]); + const upgradeExecutorAddr = ethers.utils.hexlify(deploydata["upgrade-executor"]); + + const keyset = fs + .readFileSync(path.join(consts.configpath, "l2_das_keyset.hex")) + .toString() + + await setValidKeyset(argv, upgradeExecutorAddr, sequencerInboxAddr, keyset) + } +}; + +export const waitForSyncCommand = { + command: "wait-for-sync", + describe: "wait for rpc to sync", + builder: { + url: { string: true, describe: "url to send rpc call", default: "http://sequencer:8547"}, + }, + handler: async (argv: any) => { + const rpcProvider = new ethers.providers.JsonRpcProvider(argv.url) + let syncStatus; + do { + syncStatus = await rpcProvider.send("eth_syncing", []) + if (syncStatus !== false) { + // Wait for a short interval before checking again + await new Promise(resolve => setTimeout(resolve, 5000)) + } + } while (syncStatus !== false) + }, +}; diff --git a/scripts/index.ts b/scripts/index.ts index 3de0835c..59e03d96 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -2,7 +2,7 @@ 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 } from "./config"; +import { writeConfigCommand, writeGethGenesisCommand, writePrysmCommand, writeL2ChainConfigCommand, writeL3ChainConfigCommand, writeL2DASCommitteeConfigCommand, writeL2DASMirrorConfigCommand, writeL2DASKeysetConfigCommand } from "./config"; import { printAddressCommand, namedAccountHelpString, @@ -19,6 +19,9 @@ import { sendL2Command, sendL3Command, sendRPCCommand, + setValidKeysetCommand, + waitForSyncCommand, + transferL3ChainOwnershipCommand, } from "./ethcommands"; async function main() { @@ -30,6 +33,7 @@ async function main() { l3url: { string: true, default: "ws://l3node:3348" }, validationNodeUrl: { string: true, default: "ws://validation_node:8549" }, l2owner: { string: true, default: "0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E" }, + committeeMember: { string: true, default: "not_set" }, }) .options(stressOptions) .options({ @@ -47,16 +51,22 @@ async function main() { .command(sendL2Command) .command(sendL3Command) .command(sendRPCCommand) + .command(setValidKeysetCommand) + .command(transferL3ChainOwnershipCommand) .command(writeConfigCommand) .command(writeGethGenesisCommand) .command(writeL2ChainConfigCommand) .command(writeL3ChainConfigCommand) + .command(writeL2DASCommitteeConfigCommand) + .command(writeL2DASMirrorConfigCommand) + .command(writeL2DASKeysetConfigCommand) .command(writePrysmCommand) .command(writeAccountsCommand) .command(printAddressCommand) .command(printPrivateKeyCommand) .command(redisReadCommand) .command(redisInitCommand) + .command(waitForSyncCommand) .strict() .demandCommand(1, "a command must be specified") .epilogue(namedAccountHelpString) diff --git a/test-node.bash b/test-node.bash index 3bb88336..3d4c6551 100755 --- a/test-node.bash +++ b/test-node.bash @@ -1,11 +1,12 @@ #!/usr/bin/env bash -set -e - -NITRO_NODE_VERSION=offchainlabs/nitro-node:v3.1.0-7d1d84c-dev -BLOCKSCOUT_VERSION=offchainlabs/blockscout:v1.0.0-c8db5b1 +set -eu DEFAULT_NITRO_CONTRACTS_REPO="https://github.com/OffchainLabs/nitro-contracts.git" +NITRO_NODE_VERSION=offchainlabs/nitro-node:v3.2.1-d81324d-dev +BLOCKSCOUT_VERSION=offchainlabs/blockscout:v1.1.0-0e716c8 + +# This commit matches v2.1.0 release of nitro-contracts, with additional support to set arb owner through upgrade executor DEFAULT_NITRO_CONTRACTS_VERSION="99c07a7db2fcce75b751c5a2bd4936e898cda065" DEFAULT_TOKEN_BRIDGE_VERSION="v1.2.2" @@ -40,7 +41,6 @@ else fi run=true -force_build=false validate=false detach=false blockscout=false @@ -63,6 +63,19 @@ devprivkey=b6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 l1chainid=1337 simple=true simple_with_validator=false +l2anytrust=false + +# Use the dev versions of nitro/blockscout +dev_nitro=false +dev_blockscout=false + +# Rebuild docker images +build_dev_nitro=false +build_dev_blockscout=false +build_utils=false +force_build_utils=false +build_node_images=false + while [[ $# -gt 0 ]]; do case $1 in --init) @@ -71,6 +84,8 @@ while [[ $# -gt 0 ]]; do read -p "are you sure? [y/n]" -n 1 response if [[ $response == "y" ]] || [[ $response == "Y" ]]; then force_init=true + build_utils=true + build_node_images=true echo else exit 0 @@ -80,6 +95,8 @@ while [[ $# -gt 0 ]]; do ;; --init-force) force_init=true + build_utils=true + build_node_images=true shift ;; --dev) @@ -87,14 +104,18 @@ while [[ $# -gt 0 ]]; do shift if [[ $# -eq 0 || $1 == -* ]]; then # If no argument after --dev, set both flags to true - dev_build_nitro=true - dev_build_blockscout=true + dev_nitro=true + build_dev_nitro=true + dev_blockscout=true + build_dev_blockscout=true else while [[ $# -gt 0 && $1 != -* ]]; do if [[ $1 == "nitro" ]]; then - dev_build_nitro=true + dev_nitro=true + build_dev_nitro=true elif [[ $1 == "blockscout" ]]; then - dev_build_blockscout=true + dev_blockscout=true + build_dev_blockscout=true fi shift done @@ -114,7 +135,45 @@ while [[ $# -gt 0 ]]; do shift ;; --build) - force_build=true + build_dev_nitro=true + build_dev_blockscout=true + build_utils=true + build_node_images=true + shift + ;; + --no-build) + build_dev_nitro=false + build_dev_blockscout=false + build_utils=false + build_node_images=false + shift + ;; + --build-dev-nitro) + build_dev_nitro=true + shift + ;; + --no-build-dev-nitro) + build_dev_nitro=false + shift + ;; + --build-dev-blockscout) + build_dev_blockscout=true + shift + ;; + --no-build-dev-blockscout) + build_dev_blockscout=false + shift + ;; + --build-utils) + build_utils=true + shift + ;; + --no-build-utils) + build_utils=false + shift + ;; + --force-build-utils) + force_build_utils=true shift ;; --validate) @@ -154,7 +213,7 @@ while [[ $# -gt 0 ]]; do ;; --pos) consensusclient=true - l1chainid=32382 + l1chainid=1337 shift ;; --l3node) @@ -190,6 +249,10 @@ while [[ $# -gt 0 ]]; do l3_token_bridge=true shift ;; + --l2-anytrust) + l2anytrust=true + shift + ;; --redundantsequencers) simple=false redundantsequencers=$2 @@ -219,6 +282,7 @@ while [[ $# -gt 0 ]]; do echo echo OPTIONS: 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 --init remove all data, rebuild, deploy new rollup echo --pos l1 is a proof-of-stake chain \(using prysm for consensus\) @@ -227,6 +291,7 @@ while [[ $# -gt 0 ]]; do echo --l3-fee-token L3 chain is set up to use custom fee token. Only valid if also '--l3node' is provided 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 --batchposters batch posters [0-3] echo --redundantsequencers redundant sequencers [0-3] echo --detach detach from nodes after running them @@ -238,6 +303,13 @@ while [[ $# -gt 0 ]]; do echo --no-run does not launch nodes \(useful with build or init\) echo --no-simple run a full configuration with separate sequencer/batch-poster/validator/relayer echo --enable-finality-node enable espresso finality node + echo --build-dev-nitro rebuild dev nitro docker image + echo --no-build-dev-nitro don\'t rebuild dev nitro docker image + echo --build-dev-blockscout rebuild dev blockscout docker image + echo --no-build-dev-blockscout don\'t rebuild dev blockscout docker image + echo --build-utils rebuild scripts, rollupcreator, token bridge docker images + echo --no-build-utils don\'t rebuild scripts, rollupcreator, token bridge docker images + echo --force-build-utils force rebuilding utils, useful if NITRO_CONTRACTS_ or TOKEN_BRIDGE_BRANCH changes echo echo script runs inside a separate docker. For SCRIPT-ARGS, run $0 script --help exit 0 @@ -254,22 +326,6 @@ if $espresso; then echo "Using NITRO_CONTRACTS_BRANCH: $NITRO_CONTRACTS_BRANCH" fi -if $force_init; then - force_build=true -fi - -if $dev_build_nitro; then - if [[ "$(docker images -q nitro-node-dev:latest 2> /dev/null)" == "" ]]; then - force_build=true - fi -fi - -if $dev_build_blockscout; then - if [[ "$(docker images -q blockscout:latest 2> /dev/null)" == "" ]]; then - force_build=true - fi -fi - NODES="sequencer" INITIAL_SEQ_NODES="sequencer" @@ -324,40 +380,46 @@ if $espresso; then # l2 node will run without `espresso` mode. l2_espresso=false fi - if $force_build && $l2_espresso; then + if $build_node_images && $l2_espresso; then INITIAL_SEQ_NODES="$INITIAL_SEQ_NODES espresso-dev-node" else NODES="$NODES espresso-dev-node" fi fi -if $force_build; then - echo == Building.. - if $dev_build_nitro; then - if ! [ -n "${NITRO_SRC+set}" ]; then - NITRO_SRC=`dirname $PWD` - fi - if ! grep ^FROM "${NITRO_SRC}/Dockerfile" | grep nitro-node 2>&1 > /dev/null; then - echo nitro source not found in "$NITRO_SRC" - echo execute from a sub-directory of nitro or use NITRO_SRC environment variable - exit 1 - fi - docker build "$NITRO_SRC" -t nitro-node-dev --target nitro-node-dev + +if $dev_nitro && $build_dev_nitro; then + echo == Building Nitro + if ! [ -n "${NITRO_SRC+set}" ]; then + NITRO_SRC=`dirname $PWD` fi - if $dev_build_blockscout; then - if $blockscout; then - docker build blockscout -t blockscout -f blockscout/docker/Dockerfile - fi + if ! grep ^FROM "${NITRO_SRC}/Dockerfile" | grep nitro-node 2>&1 > /dev/null; then + echo nitro source not found in "$NITRO_SRC" + echo execute from a sub-directory of nitro or use NITRO_SRC environment variable + exit 1 fi + docker build "$NITRO_SRC" -t nitro-node-dev --target nitro-node-dev +fi +if $dev_blockscout && $build_dev_blockscout; then + if $blockscout; then + echo == Building Blockscout + docker build blockscout -t blockscout -f blockscout/docker/Dockerfile + fi +fi +if $build_utils; then LOCAL_BUILD_NODES="scripts rollupcreator" if $tokenbridge || $l3_token_bridge; then LOCAL_BUILD_NODES="$LOCAL_BUILD_NODES tokenbridge" fi - docker compose build --no-rm $LOCAL_BUILD_NODES + UTILS_NOCACHE="" + if $force_build_utils; then + UTILS_NOCACHE="--no-cache" + fi + docker compose build --no-rm $UTILS_NOCACHE $LOCAL_BUILD_NODES fi -if $dev_build_nitro; then +if $dev_nitro; then docker tag nitro-node-dev:latest nitro-node-dev-testnode else if $latest_espresso_image; then @@ -369,18 +431,16 @@ else fi fi -if $dev_build_blockscout; then - if $blockscout; then +if $blockscout; then + if $dev_blockscout; then docker tag blockscout:latest blockscout-testnode - fi -else - if $blockscout; then + else docker pull $BLOCKSCOUT_VERSION docker tag $BLOCKSCOUT_VERSION blockscout-testnode fi fi -if $force_build; then +if $build_node_images; then docker compose build --no-rm $NODES scripts fi @@ -403,31 +463,32 @@ if $force_init; then docker compose run --entrypoint sh geth -c "chown -R 1000:1000 /keystore" docker compose run --entrypoint sh geth -c "chown -R 1000:1000 /config" + echo == Writing geth configs + docker compose run scripts write-geth-genesis-config if $consensusclient; then - echo == Writing configs - docker compose run scripts write-geth-genesis-config - - echo == Writing configs + echo == Writing prysm configs docker compose run scripts write-prysm-config - echo == Initializing go-ethereum genesis configuration - docker compose run geth init --datadir /datadir/ /config/geth_genesis.json - - echo == Starting geth - docker compose up --wait geth - echo == Creating prysm genesis - docker compose up create_beacon_chain_genesis + docker compose run create_beacon_chain_genesis + fi + echo == Initializing go-ethereum genesis configuration + docker compose run geth init --state.scheme hash --datadir /datadir/ /config/geth_genesis.json + + if $consensusclient; then echo == Running prysm docker compose up --wait prysm_beacon_chain docker compose up --wait prysm_validator - else - docker compose up --wait geth - docker compose run scripts write-geth-genesis-config fi + echo == Starting geth + docker compose up --wait geth + + echo == Waiting for geth to sync + docker compose run scripts wait-for-sync --url http://geth:8545 + echo == Funding validator, sequencer and l2owner docker compose run scripts send-l1 --ethamount 1000 --to validator --wait docker compose run scripts send-l1 --ethamount 1000 --to sequencer --wait @@ -441,8 +502,13 @@ if $force_init; then l2ownerAddress=`docker compose run scripts print-address --account l2owner | tail -n 1 | tr -d '\r\n'` echo $l2ownerAddress - echo == Writing l2 chain config - docker compose run scripts --l2owner $l2ownerAddress write-l2-chain-config --espresso $l2_espresso + if $l2anytrust; then + echo "== Writing l2 chain config (anytrust enabled)" + docker compose run scripts --l2owner $l2ownerAddress write-l2-chain-config --anytrust --espresso $l2_espresso + else + echo == Writing l2 chain config + docker compose run scripts --l2owner $l2ownerAddress write-l2-chain-config --espresso $l2_espresso + fi sequenceraddress=`docker compose run scripts print-address --account sequencer | tail -n 1 | tr -d '\r\n'` l2ownerKey=`docker compose run scripts print-private-key --account l2owner | tail -n 1 | tr -d '\r\n'` @@ -454,16 +520,48 @@ if $force_init; then docker compose run --entrypoint sh rollupcreator -c "jq [.[]] /config/deployed_chain_info.json > /espresso-config/l2_chain_info.json" docker compose run --entrypoint sh rollupcreator -c "cat /config/l2_chain_info.json" +fi # $force_init + +anytrustNodeConfigLine="" + +# Remaining init may require AnyTrust committee/mirrors to have been started +if $l2anytrust; then + if $force_init; then + echo == Generating AnyTrust Config + docker compose run --user root --entrypoint sh datool -c "mkdir /das-committee-a/keys /das-committee-a/data /das-committee-a/metadata /das-committee-b/keys /das-committee-b/data /das-committee-b/metadata /das-mirror/data /das-mirror/metadata" + docker compose run --user root --entrypoint sh datool -c "chown -R 1000:1000 /das*" + docker compose run datool keygen --dir /das-committee-a/keys + docker compose run datool keygen --dir /das-committee-b/keys + docker compose run scripts write-l2-das-committee-config + docker compose run scripts write-l2-das-mirror-config + + das_bls_a=`docker compose run --entrypoint sh datool -c "cat /das-committee-a/keys/das_bls.pub"` + das_bls_b=`docker compose run --entrypoint sh datool -c "cat /das-committee-b/keys/das_bls.pub"` + + docker compose run scripts write-l2-das-keyset-config --dasBlsA $das_bls_a --dasBlsB $das_bls_b + docker compose run --entrypoint sh datool -c "/usr/local/bin/datool dumpkeyset --conf.file /config/l2_das_keyset.json | grep 'Keyset: ' | awk '{ printf \"%s\", \$2 }' > /config/l2_das_keyset.hex" + docker compose run scripts set-valid-keyset + + anytrustNodeConfigLine="--anytrust --dasBlsA $das_bls_a --dasBlsB $das_bls_b" + fi + + if $run; then + echo == Starting AnyTrust committee and mirror + docker compose up --wait das-committee-a das-committee-b das-mirror + fi +fi + +if $force_init; then if $simple; then echo == Writing configs for simple - docker compose run scripts write-config --simple --simpleWithValidator $simple_with_validator --espresso $l2_espresso --lightClientAddress $lightClientAddr + docker compose run scripts write-config --simple $anytrustNodeConfigLine --simpleWithValidator $simple_with_validator --espresso $l2_espresso --lightClientAddress $lightClientAddr else echo == Writing configs - docker compose run scripts write-config --espresso $l2_espresso --lightClientAddress $lightClientAddr + docker compose run scripts write-config $anytrustNodeConfigLine --espresso $l2_espresso --lightClientAddress $lightClientAddr if $enableEspressoFinalityNode; then echo == Writing configs for finality node - docker compose run scripts write-config --espresso $l2_espresso --enableEspressoFinalityNode --lightClientAddress $lightClientAddr + docker compose run scripts write-config $anytrustNodeConfigLine --espresso $l2_espresso --enableEspressoFinalityNode --lightClientAddress $lightClientAddr fi echo == Initializing redis docker compose up --wait redis @@ -512,6 +610,7 @@ if $force_init; then echo l3owneraddress $l3owneraddress docker compose run scripts --l2owner $l3owneraddress write-l3-chain-config --espresso $espresso + EXTRA_L3_DEPLOY_FLAG="" if $l3_custom_fee_token; then echo == Deploying custom fee token nativeTokenAddress=`docker compose run scripts create-erc20 --deployer user_fee_token_deployer --bridgeable $tokenbridge --decimals $l3_custom_fee_token_decimals | tail -n 1 | awk '{ print $NF }'` @@ -542,6 +641,11 @@ if $force_init; then fi docker compose run -e PARENT_WETH_OVERRIDE=$l2Weth -e ROLLUP_OWNER_KEY=$l3ownerkey -e ROLLUP_ADDRESS=$rollupAddress -e PARENT_RPC=http://sequencer:8547 -e PARENT_KEY=$deployer_key -e CHILD_RPC=http://l3node:3347 -e CHILD_KEY=$deployer_key tokenbridge deploy:local:token-bridge docker compose run --entrypoint sh tokenbridge -c "cat network.json && cp network.json l2l3_network.json" + + # set L3 UpgradeExecutor, deployed by token bridge creator in previous step, to be the L3 chain owner. L3owner (EOA) and alias of L2 UpgradeExectuor have the executor role on the L3 UpgradeExecutor + echo == Set L3 UpgradeExecutor to be chain owner + tokenBridgeCreator=`docker compose run --entrypoint sh tokenbridge -c "cat l2l3_network.json" | jq -r '.l1TokenBridgeCreator'` + docker compose run scripts transfer-l3-chain-ownership --creator $tokenBridgeCreator echo fi