From 6673f4c4f8866a5bd4110ce6afe0fbf7ceaa4c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:33:17 -0300 Subject: [PATCH 1/7] fix: extract keystore's password in independent step (#196) Fixes: #195 This PR fixes the `invalid character '\n' in string literal` error that sometimes happened in our CI. The error was caused by `egnkey generate` printing a "Private key Ignore" message whenever a generated key wasn't 32 bytes long. Since we generated the key and printed the password in the same step, the output of the first command got stored along with the second one. --- kurtosis_package/keys.star | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/kurtosis_package/keys.star b/kurtosis_package/keys.star index 241e31d3..e24646f7 100644 --- a/kurtosis_package/keys.star +++ b/kurtosis_package/keys.star @@ -63,8 +63,7 @@ def generate_keys(plan, egnkey_service_name, key_type, artifact_name): output_dir = "/_output" cmd = "set -e ; rm -rf {output} && \ - egnkey generate --key-type {type} --num-keys 1 --output-dir {output} ; \ - cat {output}/password.txt | tr -d '\n'".format( + egnkey generate --key-type {type} --num-keys 1 --output-dir {output}".format( output=output_dir, type=key_type ) @@ -73,7 +72,6 @@ def generate_keys(plan, egnkey_service_name, key_type, artifact_name): recipe=ExecRecipe(command=["sh", "-c", cmd]), description="Generating " + key_type + " key", ) - password = result["output"] artifact_name = plan.store_service_files( service_name=egnkey_service_name, @@ -82,6 +80,16 @@ def generate_keys(plan, egnkey_service_name, key_type, artifact_name): description="Storing " + key_type + " key", ) + # NOTE: we do this in another step to avoid egnkey's output from being stored + cmd = "set -e ; cat {}/password.txt | tr -d '\n'".format(output_dir) + + result = plan.exec( + service_name=egnkey_service_name, + recipe=ExecRecipe(command=["sh", "-c", cmd]), + description="Extracting keystore password", + ) + password = result["output"] + cmd = "set -e ; cat {}/private_key_hex.txt | tr -d '\n'".format(output_dir) result = plan.exec( From e790bbc273da62c572bed1268b7cdee9c69b2724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:35:02 -0300 Subject: [PATCH 2/7] ci: cache docker images (#186) Closes: #29 --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc0ce9a0..984aebf6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,11 @@ jobs: with: version: ${{ env.KURTOSIS_VERSION }} + - name: Cache Docker images + uses: ScribeMD/docker-cache@0.5.0 + with: + key: docker-${{ runner.os }}-${{ hashFiles('Makefile', 'examples', 'src/config/default_config.yaml') }} + - name: Run Package with default args run: | kurtosis run ./kurtosis_package @@ -50,6 +55,11 @@ jobs: with: version: ${{ env.KURTOSIS_VERSION }} + - name: Cache Docker images + uses: ScribeMD/docker-cache@0.5.0 + with: + key: docker-${{ runner.os }}-${{ hashFiles('Makefile', 'examples', 'src/config/default_config.yaml') }} + - name: Run example '${{ matrix.example }}' run: make ${{ matrix.example }} @@ -66,6 +76,11 @@ jobs: with: version: ${{ env.KURTOSIS_VERSION }} + - name: Cache Docker images + uses: ScribeMD/docker-cache@0.5.0 + with: + key: docker-${{ runner.os }}-${{ env.KURTOSIS_VERSION }} + - name: Kurtosis Lint run: make kurtosis_lint @@ -120,6 +135,11 @@ jobs: with: go-version: ${{ env.GO_VERSION }} + - name: Cache Docker images + uses: ScribeMD/docker-cache@0.5.0 + with: + key: docker-${{ runner.os }}-${{ hashFiles('Makefile', 'examples', 'src/config/default_config.yaml') }} + - name: Run go test run: make test @@ -144,14 +164,19 @@ jobs: - name: Install devnet run: make install - + + - name: Cache Docker images + uses: ScribeMD/docker-cache@0.5.0 + with: + key: docker-${{ runner.os }}-a294978-${{ hashFiles('examples/eigenda.yaml') }} + - name: Checkout EigenDA repo uses: actions/checkout@v4 with: repository: Layr-Labs/eigenda path: eigenda/ ref: a294978e346fe9d7ede0f1a57012c36f64f1212a - + - name: Download SRS points run: | cd eigenda/ From 5786a92032e59bc675229bd27e51235eccf76670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:39:03 -0300 Subject: [PATCH 3/7] feat!: unify cmd args (#194) Closes #90 This PR drops the config file flag and positional argument in all but the `init` and `start` subcommands. It also adds a `--name`/`-n` flag for specifying a name for the devnet (which defaults to `devnet`). --- cmd/avs-devnet/main.go | 35 ++++++++++++++++------------------- src/cmds/flags/flags.go | 11 +++++++---- src/cmds/get_address.go | 6 +----- src/cmds/get_ports.go | 6 ++---- src/cmds/init.go | 2 +- src/cmds/start.go | 3 ++- src/cmds/stop.go | 8 +++----- src/cmds/utils.go | 30 +++++++----------------------- 8 files changed, 39 insertions(+), 62 deletions(-) diff --git a/cmd/avs-devnet/main.go b/cmd/avs-devnet/main.go index 89e0cf86..0e9819d8 100644 --- a/cmd/avs-devnet/main.go +++ b/cmd/avs-devnet/main.go @@ -16,14 +16,12 @@ func main() { app.Name = "avs-devnet" app.Usage = "start an AVS devnet" app.Version = version - app.Flags = append(app.Flags, &flags.KurtosisPackageFlag) app.Commands = append(app.Commands, &cli.Command{ Name: "init", Usage: "Initialize a devnet configuration file", Args: true, - ArgsUsage: "[]", - Flags: []cli.Flag{}, + ArgsUsage: "[]", Action: cmds.InitCmd, }) @@ -31,18 +29,19 @@ func main() { Name: "start", Usage: "Start devnet from configuration file", Args: true, - ArgsUsage: "[]", - Flags: []cli.Flag{}, - Action: cmds.StartCmd, + ArgsUsage: "[]", + Flags: []cli.Flag{ + &flags.DevnetNameFlag, + &flags.KurtosisPackageFlag, + }, + Action: cmds.StartCmd, }) app.Commands = append(app.Commands, &cli.Command{ - Name: "stop", - Usage: "Stop devnet from configuration file", - Args: true, - ArgsUsage: "[]", - Flags: []cli.Flag{}, - Action: cmds.StopCmd, + Name: "stop", + Usage: "Stop devnet from configuration file", + Flags: []cli.Flag{&flags.DevnetNameFlag}, + Action: cmds.StopCmd, }) app.Commands = append(app.Commands, &cli.Command{ @@ -50,17 +49,15 @@ func main() { Usage: "Get a devnet contract or EOA address", Args: true, ArgsUsage: "...", - Flags: []cli.Flag{&flags.ConfigFilePathFlag}, + Flags: []cli.Flag{&flags.DevnetNameFlag}, Action: cmds.GetAddress, }) app.Commands = append(app.Commands, &cli.Command{ - Name: "get-ports", - Usage: "Get the published ports on the devnet", - Args: true, - ArgsUsage: "[]", - Flags: []cli.Flag{}, - Action: cmds.GetPorts, + Name: "get-ports", + Usage: "Get the published ports on the devnet", + Flags: []cli.Flag{&flags.DevnetNameFlag}, + Action: cmds.GetPorts, }) if err := app.Run(os.Args); err != nil { diff --git a/src/cmds/flags/flags.go b/src/cmds/flags/flags.go index aafa5ce1..635cf401 100644 --- a/src/cmds/flags/flags.go +++ b/src/cmds/flags/flags.go @@ -10,10 +10,13 @@ var DefaultKurtosisPackage string = "" //nolint:gochecknoglobals // these are constants var ( - ConfigFilePathFlag = cli.StringFlag{ - Name: "config-file", - Usage: "Path to the devnet configuration file", - Value: "devnet.yaml", + DevnetNameFlag = cli.StringFlag{ + Name: "name", + TakesFile: true, + Aliases: []string{"n"}, + Usage: "Assign a name to the devnet", + Value: "devnet", + DefaultText: "devnet", } // NOTE: this flag is for internal use. diff --git a/src/cmds/get_address.go b/src/cmds/get_address.go index cbf20e56..270c0563 100644 --- a/src/cmds/get_address.go +++ b/src/cmds/get_address.go @@ -14,11 +14,7 @@ import ( func GetAddress(ctx *cli.Context) error { args := ctx.Args() - configFileName := ctx.String(flags.ConfigFilePathFlag.Name) - devnetName, err := EnclaveNameFromFileName(configFileName) - if err != nil { - return cli.Exit(err, 1) - } + devnetName := flags.DevnetNameFlag.Get(ctx) kurtosisCtx, err := kurtosis.InitKurtosisContext() if err != nil { diff --git a/src/cmds/get_ports.go b/src/cmds/get_ports.go index 1731ce6f..3ffe5962 100644 --- a/src/cmds/get_ports.go +++ b/src/cmds/get_ports.go @@ -3,16 +3,14 @@ package cmds import ( "fmt" + "github.com/Layr-Labs/avs-devnet/src/cmds/flags" "github.com/Layr-Labs/avs-devnet/src/kurtosis" "github.com/urfave/cli/v2" "gopkg.in/yaml.v3" ) func GetPorts(ctx *cli.Context) error { - devnetName, _, err := parseArgs(ctx) - if err != nil { - return cli.Exit(err, 1) - } + devnetName := flags.DevnetNameFlag.Get(ctx) kurtosisCtx, err := kurtosis.InitKurtosisContext() if err != nil { diff --git a/src/cmds/init.go b/src/cmds/init.go index 89bca704..a490ab9b 100644 --- a/src/cmds/init.go +++ b/src/cmds/init.go @@ -11,7 +11,7 @@ import ( // Creates a new devnet configuration with the given context. func InitCmd(ctx *cli.Context) error { - _, configFileName, err := parseArgs(ctx) + configFileName, err := parseConfigFileName(ctx) if err != nil { return cli.Exit(err, 1) } diff --git a/src/cmds/start.go b/src/cmds/start.go index 990efe9b..b4f54b70 100644 --- a/src/cmds/start.go +++ b/src/cmds/start.go @@ -24,7 +24,8 @@ import ( // Starts the devnet with the given context. func StartCmd(ctx *cli.Context) error { pkgName := flags.KurtosisPackageFlag.Get(ctx) - devnetName, configPath, err := parseArgs(ctx) + devnetName := flags.DevnetNameFlag.Get(ctx) + configPath, err := parseConfigFileName(ctx) if err != nil { return cli.Exit(err, 1) } diff --git a/src/cmds/stop.go b/src/cmds/stop.go index 2710cd1e..03cd8554 100644 --- a/src/cmds/stop.go +++ b/src/cmds/stop.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/Layr-Labs/avs-devnet/src/cmds/flags" "github.com/Layr-Labs/avs-devnet/src/kurtosis" "github.com/urfave/cli/v2" ) @@ -13,12 +14,9 @@ var ErrEnclaveNotExists = errors.New("enclave doesn't exist") // Stops the devnet with the given context. func StopCmd(ctx *cli.Context) error { - devnetName, _, err := parseArgs(ctx) - if err != nil { - return cli.Exit(err, 1) - } + devnetName := flags.DevnetNameFlag.Get(ctx) fmt.Println("Stopping devnet...") - err = Stop(ctx.Context, devnetName) + err := Stop(ctx.Context, devnetName) if errors.Is(err, ErrEnclaveNotExists) { return cli.Exit("Failed to find '"+devnetName+"'. Maybe it's not running?", 1) } else if err != nil { diff --git a/src/cmds/utils.go b/src/cmds/utils.go index 6eac948e..7fc9364d 100644 --- a/src/cmds/utils.go +++ b/src/cmds/utils.go @@ -3,34 +3,25 @@ package cmds import ( "errors" "os" - "path/filepath" "regexp" "strings" "github.com/urfave/cli/v2" ) -// Parses the main arguments from the given context. -// Returns the devnet name and the configuration file name. -func parseArgs(ctx *cli.Context) (string, string, error) { +// Parses the configuration file path from the positional args. +// Fails if more than one positional arg is provided. +func parseConfigFileName(ctx *cli.Context) (string, error) { args := ctx.Args() if args.Len() > 1 { - return "", "", errors.New("expected exactly 1 argument: ") + return "", errors.New("expected none or 1 argument: []") } fileName := args.First() - var devnetName string - + // TODO: check file exists and support yml extension if fileName == "" { - fileName = "devnet.yaml" - devnetName = "devnet" - } else { - name, err := EnclaveNameFromFileName(fileName) - if err != nil { - return "", "", err - } - devnetName = name + return "devnet.yaml", nil } - return devnetName, fileName, nil + return fileName, nil } // Checks if a file exists at the given path. @@ -39,13 +30,6 @@ func fileExists(filePath string) bool { return !errors.Is(err, os.ErrNotExist) } -// Extracts the devnet name from the given configuration file name. -func EnclaveNameFromFileName(fileName string) (string, error) { - name := filepath.Base(fileName) - name = strings.Split(name, ".")[0] - return ToValidEnclaveName(name) -} - func ToValidEnclaveName(name string) (string, error) { for _, r := range []string{"_", "/", "."} { name = strings.ReplaceAll(name, r, "-") From aaf434de709dea6cde4d334cdc26c4af3d4a015f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:10:21 -0300 Subject: [PATCH 4/7] feat: add Aligned Layer's AVS example (#30) This PR adds an example that deploys Aligned Layer and starts an operator and aggregator. This isn't enabled in the CI due to images taking too long to build. What's left: - add balance on the `ServiceManager` for the `BatcherPaymentService` at startup. This is needed because the `ServiceManager` overcharges the sender by about 2x, but always reimburses any extra. Note that this was already added, but needs testing. --- Makefile | 4 +- examples/aligned_layer.yaml | 533 ++++++++++++++++++++++++++++++++++++ 2 files changed, 535 insertions(+), 2 deletions(-) create mode 100644 examples/aligned_layer.yaml diff --git a/Makefile b/Makefile index b08a4e77..da3846b2 100644 --- a/Makefile +++ b/Makefile @@ -103,8 +103,8 @@ HELLO_WORLD_REF:=4626e206fd119b26ebe98935b256daa7256e863b examples/hello-world-avs: @echo "Cloning hello-world-avs repo..." - @mkdir -p examples/hello-world-avs - @cd examples/hello-world-avs && \ + @mkdir -p $@ + @cd $@ && \ git init . && \ git remote add origin https://github.com/Layr-Labs/hello-world-avs.git && \ git fetch --depth 1 origin $(HELLO_WORLD_REF) && \ diff --git a/examples/aligned_layer.yaml b/examples/aligned_layer.yaml new file mode 100644 index 00000000..8f0e9a15 --- /dev/null +++ b/examples/aligned_layer.yaml @@ -0,0 +1,533 @@ +# yaml-language-server: $schema=../schema.json + +# Example devnet config file for Aligned Layer (https://github.com/yetanotherco/aligned_layer) + +# Things to note: +# - images need to be built manually, by running `make docker_build` in the Aigned Layer repo +# - the branch required is: https://github.com/yetanotherco/aligned_layer/pull/1708 +# - proof sending doesn't work yet +# - to try sending proofs, you need to: +# - replace 0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650 with the correct contract address in sdk +# - specify the correct RPC URL when sending proofs (look at the makefile for that) +# - pass batcher URL and proof sender PK with the `--batcher_url` and `--private_key` flags +deployments: + # TODO: use devnet EigenLayer deployer + - name: EigenLayer + repo: "https://github.com/yetanotherco/aligned_layer.git" + ref: "output-batcher-addresses-as-json" + contracts_path: "contracts" + script: script/deploy/EigenLayerDeployer.s.sol + extra_args: --sig 'run(string memory configFileName)' -- eigen.devnet.config.json + input: + script/deploy/config/devnet/: eigenlayer_deployment_config + output: + eigenlayer_addresses: "script/output/devnet/eigenlayer_deployment_output.json" + addresses: + delegationManager: "eigenlayer_addresses:.addresses.delegationManager" + mockStrategy: "eigenlayer_addresses:.addresses.strategies.MOCK" + + - name: Aligned + repo: "https://github.com/yetanotherco/aligned_layer.git" + ref: "output-batcher-addresses-as-json" + contracts_path: "contracts" + script: script/deploy/AlignedLayerDeployer.s.sol + extra_args: >- + --sig "run(string memory existingDeploymentInfoPath, string memory deployConfigPath, string memory outputPath)" + ./script/output/devnet/eigenlayer_deployment_output.json + ./script/deploy/config/devnet/aligned.devnet.config.json + ./script/output/devnet/alignedlayer_deployment_output.json + input: + script/deploy/config/devnet/: aligned_deployment_config + script/output/devnet/: eigenlayer_addresses + output: + avs_addresses: script/output/devnet/alignedlayer_deployment_output.json + addresses: + alignedLayerServiceManager: "avs_addresses:.addresses.alignedLayerServiceManager" + registryCoordinator: "avs_addresses:.addresses.registryCoordinator" + + - name: BatcherPaymentService + repo: "https://github.com/yetanotherco/aligned_layer.git" + ref: "output-batcher-addresses-as-json" + contracts_path: "contracts" + script: script/deploy/BatcherPaymentServiceDeployer.s.sol + extra_args: >- + --sig "run(string batcherConfigPath, string outputPath)" + ./script/deploy/config/devnet/batcher.devnet.config.json + ./script/output/devnet/batcher_deployment_output.json + input: + script/deploy/config/devnet/: batcher_deployment_config + output: + batcher_addresses: script/output/devnet/batcher_deployment_output.json + addresses: + batcherPaymentService: "batcher_addresses:.addresses.batcherPaymentService" + +services: + - name: localstack + image: localstack/localstack + ports: + gateway: + number: 4566 + transport_protocol: TCP + application_protocol: "http" + wait: 1m + env: + DEBUG: 1 + DEFAULT_REGION: us-east-2 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + input: + /etc/localstack/init/ready.d: localstack_init_script + + - name: "aggregator" + image: "ghcr.io/yetanotherco/aligned_layer/aggregator:latest" + ports: + rpc: + number: 8090 + transport_protocol: "TCP" + application_protocol: "http" + metrics: + number: 9091 + transport_protocol: "TCP" + application_protocol: "http" + input: + /aggregator/config-files/: aggregator_config + /aggregator/contracts/script/output/devnet/: + - eigenlayer_addresses + - avs_addresses + /aggregator/keystores/: + - aggregator_bls + - aggregator_ecdsa + cmd: + - "aligned-layer-aggregator" + - "--config" + - "./config-files/config-aggregator.yaml" + + # One-shot services to perform tasks + - name: "operator1-register-operator-eigenlayer" + image: ghcr.io/yetanotherco/aligned_layer/eigenlayer-cli:latest + input: + /go/config-files/: operator_config + /go/keystores/: operator1_ecdsa + env: + PASSWORD: "{{.keys.operator1_ecdsa.password}}" + cmd: + - "sh" + - "-c" + - 'echo "$PASSWORD" | eigenlayer operator register ./config-files/config-docker.yaml' + + - name: "operator1-mint-mock-tokens" + image: ghcr.io/yetanotherco/aligned_layer/foundry:latest + input: + /config-files/: operator_config + /keystores/: operator1_ecdsa + env: + DEPLOYER_PRIVATE_KEY: "{{.deployer_private_key}}" + RPC_URL: "{{.http_rpc_url}}" + OPERATOR_ADDRESS: "{{.keys.operator1_ecdsa.address}}" + cmd: + - "sh" + - "-c" + - >- + sleep 6 && + TOKEN_ADDR=$(cast call --rpc-url $RPC_URL {{.addresses.EigenLayer.mockStrategy}} 'underlyingToken()(address)') && + cast send $TOKEN_ADDR 'mint(address, uint256)' $OPERATOR_ADDRESS 100000000000000000 + --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $RPC_URL + + - name: "batcher-payment-service-deposit-initial-funds" + image: ghcr.io/yetanotherco/aligned_layer/foundry:latest + env: + PRIVATE_KEY: "{{.keys.batcher_ecdsa.private_key}}" + RPC_URL: "{{.http_rpc_url}}" + BATCHER_PAYMENT_SERVICE_ADDRESS: "{{.addresses.BatcherPaymentService.batcherPaymentService}}" + ALIGNED_SERVICE_MANAGER_ADDRESS: "{{.addresses.Aligned.alignedLayerServiceManager}}" + cmd: + - "sh" + - "-c" + - >- + cast send --value "1ether" + --private-key $PRIVATE_KEY --rpc-url $RPC_URL + $ALIGNED_SERVICE_MANAGER_ADDRESS 'depositToBatcher(address)' $BATCHER_PAYMENT_SERVICE_ADDRESS + + - name: "operator1-deposit-into-mock-strategy" + image: ghcr.io/yetanotherco/aligned_layer/operator:latest + input: + /aligned_layer/config-files/: operator_config + /aligned_layer/contracts/script/output/devnet/: + - eigenlayer_addresses + - avs_addresses + /aligned_layer/keystores/: + - operator1_bls + - operator1_ecdsa + env: + OPERATOR_ADDRESS: "{{.keys.operator1_ecdsa.address}}" + cmd: + - "sh" + - "-c" + - >- + sleep 12 && + aligned-layer-operator deposit-into-strategy --config ./config-files/config-docker.yaml + --strategy-address {{.addresses.EigenLayer.mockStrategy}} --amount 100000000000000000 + + - name: "operator1-whitelist-devnet" + image: ghcr.io/yetanotherco/aligned_layer/foundry:latest + input: + /config-files/: operator_config + /addresses/: avs_addresses + /keystores/: operator1_ecdsa + env: + DEPLOYER_PRIVATE_KEY: "{{.deployer_private_key}}" + RPC_URL: "{{.http_rpc_url}}" + OPERATOR_ADDRESS: "{{.keys.operator1_ecdsa.address}}" + REGISTRY_ADDR: "{{.addresses.Aligned.registryCoordinator}}" + cmd: + - "sh" + - "-c" + - >- + echo "Registry address: $REGISTRY_ADDR" && + sleep 18 && + cast send $REGISTRY_ADDR 'add(address)' $OPERATOR_ADDRESS --rpc-url $RPC_URL --private-key $DEPLOYER_PRIVATE_KEY + + - name: operator-register-with-aligned-layer + image: ghcr.io/yetanotherco/aligned_layer/operator:latest + input: + /aligned_layer/config-files/: operator_config + /aligned_layer/contracts/script/output/devnet/: + - eigenlayer_addresses + - avs_addresses + /aligned_layer/keystores/: + - operator1_bls + - operator1_ecdsa + cmd: + - "sh" + - "-c" + - "sleep 24 && aligned-layer-operator register --config ./config-files/config-docker.yaml" + + - name: "operator1" + image: "ghcr.io/yetanotherco/aligned_layer/operator:latest" + input: + /aligned_layer/config-files/: operator_config + /aligned_layer/contracts/script/output/devnet/: + - eigenlayer_addresses + - avs_addresses + /aligned_layer/keystores/: + - operator1_bls + - operator1_ecdsa + cmd: + - "sh" + - "-c" + - "sleep 30 && aligned-layer-operator start --config ./config-files/config-docker.yaml" + + - name: batcher + image: ghcr.io/yetanotherco/aligned_layer/batcher:latest + ports: + metrics: + number: 9093 + transport_protocol: "TCP" + application_protocol: "http" + rpc: + number: 8080 + transport_protocol: "TCP" + application_protocol: "http" + input: + /config-files/: + - batcher_config + - batcher_ecdsa + /deployment_output/: + - eigenlayer_addresses + - avs_addresses_merged + env: + AWS_SECRET_ACCESS_KEY: test + AWS_REGION: us-east-2 + AWS_ACCESS_KEY_ID: test + AWS_BUCKET_NAME: aligned.storage + UPLOAD_ENDPOINT: http://{{.services.localstack.ip_address}}:4566 + DOWNLOAD_ENDPOINT: http://{{.services.localstack.ip_address}}:4566/aligned.storage + RUST_LOG: info + RUST_BACKTRACE: 1 + cmd: + [ + "aligned-batcher", + "--config", + "./config-files/config-batcher-docker.yaml", + "--addr", + "0.0.0.0", + "--port", + "8080", + ] + +keys: + - name: "aggregator_bls" + type: "bls" + - name: "aggregator_ecdsa" + type: "ecdsa" + - name: "operator1_bls" + type: "bls" + - name: "operator1_ecdsa" + type: "ecdsa" + - name: "batcher_ecdsa" + type: "ecdsa" + # Account to be used when sending proofs + - name: "proof_sender" + type: "ecdsa" + +artifacts: + localstack_init_script: + files: + init-s3.py: + static_file: https://raw.githubusercontent.com/yetanotherco/aligned_layer/refs/heads/testnet/scripts/init-s3.py + + eigenlayer_deployment_config: + files: + eigen.devnet.config.json: + template: | + { + "chainInfo": { + "chainId": 31337 + }, + "multisig_addresses": { + "pauserMultisig": "{{.deployer_address}}", + "communityMultisig": "{{.deployer_address}}", + "operationsMultisig": "{{.deployer_address}}", + "executorMultisig": "{{.deployer_address}}", + "timelock": "{{.deployer_address}}" + }, + "strategies": { + "numStrategies": 0, + "MAX_PER_DEPOSIT": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "MAX_TOTAL_DEPOSITS": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "strategiesToDeploy": [] + }, + "strategyManager": { + "init_strategy_whitelister": "{{.deployer_address}}", + "init_paused_status": 0 + }, + "delegationManager": { + "init_paused_status": 0, + "init_minWithdrawalDelayBlocks": 10 + }, + "rewardsCoordinator": { + "init_paused_status": 0, + "CALCULATION_INTERVAL_SECONDS": 604800, + "MAX_REWARDS_DURATION": 6048000, + "MAX_RETROACTIVE_LENGTH": 7776000, + "MAX_FUTURE_LENGTH": 2592000, + "GENESIS_REWARDS_TIMESTAMP": 1710979200, + "rewards_updater_address": "{{.deployer_address}}", + "activation_delay": 7200, + "calculation_interval_seconds": 604800, + "global_operator_commission_bips": 1000 + }, + "avsDirectory": { + "init_paused_status": 0 + }, + "slasher": { + "init_paused_status": 0 + }, + "eigenPod": { + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000, + "GENESIS_TIME": 1695902400 + }, + "eigenPodManager": { + "init_paused_status": 0, + "deneb_fork_timestamp": "1707305664" + }, + "delayedWithdrawalRouter": { + "init_paused_status": 0, + "init_withdrawalDelayBlocks": 10 + }, + "ethPOSDepositAddress": "0x4242424242424242424242424242424242424242", + "beaconOracleAddress": "0x4C116BB629bff7A8373c2378bBd919f8349B8f25" + } + + aligned_deployment_config: + files: + aligned.devnet.config.json: + template: | + { + "chainInfo": { + "chainId": 31337 + }, + "permissions": { + "owner": "{{.deployer_address}}", + "pauser": "{{.deployer_address}}", + "aggregator": "{{.keys.aggregator_ecdsa.address}}", + "upgrader": "{{.deployer_address}}", + "churner": "{{.deployer_address}}", + "ejector": "{{.deployer_address}}", + "deployer": "{{.deployer_address}}", + "initalPausedStatus": 0 + }, + "minimumStakes": [ + 1 + ], + "strategyWeights": [ + [ + { + "0_strategy": "{{.addresses.EigenLayer.mockStrategy}}", + "1_multiplier": 1e+18 + } + ] + ], + "operatorSetParams": [ + { + "0_maxOperatorCount": 200, + "1_kickBIPsOfOperatorStake": 11000, + "2_kickBIPsOfTotalStake": 50 + } + ], + "uri": "" + } + + batcher_deployment_config: + files: + batcher.devnet.config.json: + template: | + { + "address": { + "batcherWallet": "{{.keys.batcher_ecdsa.address}}", + "batcherPrivateKey": "{{.keys.batcher_ecdsa.private_key}}", + "alignedLayerServiceManager": "{{.addresses.Aligned.alignedLayerServiceManager}}" + }, + "amounts": { + "gasForAggregator": "300000", + "gasPerProof": "21000" + }, + "permissions": { + "owner": "{{.deployer_address}}" + } + } + + aggregator_config: + files: + config-aggregator.yaml: + template: | + # Common variables for all the services + # 'production' only prints info and above. 'development' also prints debug + environment: "production" + aligned_layer_deployment_config_file_path: "./contracts/script/output/devnet/alignedlayer_deployment_output.json" + eigen_layer_deployment_config_file_path: "./contracts/script/output/devnet/eigenlayer_deployment_output.json" + eth_rpc_url: "{{.http_rpc_url}}" + eth_rpc_url_fallback: "{{.http_rpc_url}}" + eth_ws_url: "{{.ws_rpc_url}}" + eth_ws_url_fallback: "{{.ws_rpc_url}}" + eigen_metrics_ip_port_address: "localhost:9090" + + ## ECDSA Configurations + ecdsa: + private_key_store_path: "keystores/keys/1.ecdsa.key.json" + private_key_store_password: "{{.keys.aggregator_ecdsa.password}}" + + ## BLS Configurations + bls: + private_key_store_path: "keystores/keys/1.bls.key.json" + private_key_store_password: "{{.keys.aggregator_bls.password}}" + + ## Aggregator Configurations + aggregator: + server_ip_port_address: 0.0.0.0:8090 + bls_public_key_compendium_address: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + avs_service_manager_address: "{{.addresses.Aligned.alignedLayerServiceManager}}" + enable_metrics: true + metrics_ip_port_address: 0.0.0.0:9091 + telemetry_ip_port_address: localhost:4001 + garbage_collector_period: 2m #The period of the GC process. Suggested value for Prod: '168h' (7 days) + garbage_collector_tasks_age: 20 #The age of tasks that will be removed by the GC, in blocks. Suggested value for prod: '216000' (30 days) + garbage_collector_tasks_interval: 10 #The interval of queried blocks to get an old batch. Suggested value for prod: '900' (3 hours) + + operator_config: + files: + config-docker.yaml: + template: | + # Common variables for all the services + # 'production' only prints info and above. 'development' also prints debug + environment: "production" + aligned_layer_deployment_config_file_path: "./contracts/script/output/devnet/alignedlayer_deployment_output.json" + eigen_layer_deployment_config_file_path: "./contracts/script/output/devnet/eigenlayer_deployment_output.json" + eth_rpc_url: "{{.http_rpc_url}}" + eth_rpc_url_fallback: "{{.http_rpc_url}}" + eth_ws_url: "{{.ws_rpc_url}}" + eth_ws_url_fallback: "{{.ws_rpc_url}}" + eigen_metrics_ip_port_address: "localhost:9090" + + ## ECDSA Configurations + ecdsa: + private_key_store_path: "keystores/keys/1.ecdsa.key.json" + private_key_store_password: "{{.keys.operator1_ecdsa.password}}" + + ## BLS Configurations + bls: + private_key_store_path: "keystores/keys/1.bls.key.json" + private_key_store_password: "{{.keys.operator1_bls.password}}" + + ## Operator Configurations + operator: + aggregator_rpc_server_ip_port_address: {{.services.aggregator.ip_address}}:8090 + operator_tracker_ip_port_address: http://localhost:3030 + address: {{.keys.operator1_ecdsa.address}} + earnings_receiver_address: {{.keys.operator1_ecdsa.address}} + delegation_approver_address: "0x0000000000000000000000000000000000000000" + staker_opt_out_window_blocks: 0 + metadata_url: "https://yetanotherco.github.io/operator_metadata/metadata.json" + enable_metrics: true + metrics_ip_port_address: localhost:9092 + max_batch_size: 268435456 # 256 MiB + last_processed_batch_filepath: config-files/operator.last_processed_batch.json + + # Operators variables needed for register it in EigenLayer + el_delegation_manager_address: "{{.addresses.EigenLayer.delegationManager}}" + private_key_store_path: keystores/keys/1.ecdsa.key.json + bls_private_key_store_path: keystores/keys/1.bls.key.json + signer_type: local_keystore + chain_id: 31337 + + avs_addresses_merged: + files: + alignedlayer_deployment_output.json: + template: | + { + "addresses": { + "alignedLayerServiceManager": "{{.addresses.Aligned.alignedLayerServiceManager}}", + "batcherPaymentService": "{{.addresses.BatcherPaymentService.batcherPaymentService}}" + } + } + + batcher_config: + files: + config-batcher-docker.yaml: + template: | + # Common variables for all the services + # 'production' only prints info and above. 'development' also prints debug + environment: "production" + aligned_layer_deployment_config_file_path: "/deployment_output/alignedlayer_deployment_output.json" + eigen_layer_deployment_config_file_path: "/deployment_output/eigenlayer_deployment_output.json" + eth_rpc_url: "{{.http_rpc_url}}" + eth_rpc_url_fallback: "{{.http_rpc_url}}" + eth_ws_url: "{{.ws_rpc_url}}" + eth_ws_url_fallback: "{{.ws_rpc_url}}" + eigen_metrics_ip_port_address: "localhost:9090" + + ## ECDSA Configurations + ecdsa: + private_key_store_path: "config-files/keys/1.ecdsa.key.json" + private_key_store_password: "{{.keys.batcher_ecdsa.password}}" + + ## Batcher configurations + batcher: + block_interval: 3 + batch_size_interval: 10 + transaction_wait_timeout: 96000 # 8 blocks + max_proof_size: 67108864 # 64 MiB + max_batch_byte_size: 268435456 # 256 MiB + max_batch_proof_qty: 3000 # 3000 proofs in a batch + eth_ws_reconnects: 99999999999999 + pre_verification_is_enabled: true + metrics_port: 9093 + telemetry_ip_port_address: localhost:4001 + +ethereum_package: + additional_services: + - blockscout + network_params: + # We use anvil's chain ID because 3151908 is not supported by eigenlayer-cli + network_id: "31337" + seconds_per_slot: 3 From 03229a8bdb542ff81a3cefa446f8888fb416dee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:59:16 -0300 Subject: [PATCH 5/7] docs: add `go install` instructions (#190) Depends on #189 This PR adds another way of installing `avs-devnet`, with `go install`. --- README.md | 20 ++++++++++++++++---- src/cmds/flags/flags.go | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 08660e21..24f120a2 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,29 @@ Also, only contracts inside foundry projects are supported as of now. For development, we require [the `go` toolchain to be installed](https://go.dev/doc/install). +## How to install + +### Using `go` toolchain + +To install the devnet using the `go` toolchain: + +```sh +go install github.com/Layr-Labs/avs-devnet/cmd/avs-devnet +``` + > [!IMPORTANT] -> To be able to [install the CLI via `go`](#installation), you'll need to add `$HOME/go/bin` to your `PATH`. -> You can do so by adding to your `~/.bashrc` (or the equivalent for your shell) the following line: +> To be able to use the CLI when installed via `go`, you'll need to add `GOBIN` (by default `$HOME/go/bin`) to your `PATH`. +> You can do so by adding to your `~/.bashrc`, or the equivalent for your shell, the following line: > > ```bash > export PATH="$PATH:$HOME/go/bin" > ``` +> +> More information [on the Go wiki](https://go.dev/wiki/GOPATH). -## Installation +### With a local copy of the repo -To build and install the CLI locally, run: +To build and install the CLI with a local copy of the repo, first clone the repo, and then run: ```sh make deps # installs dependencies diff --git a/src/cmds/flags/flags.go b/src/cmds/flags/flags.go index 635cf401..12d5928f 100644 --- a/src/cmds/flags/flags.go +++ b/src/cmds/flags/flags.go @@ -6,7 +6,7 @@ import "github.com/urfave/cli/v2" // TODO: move to constants. // //nolint:gochecknoglobals // this is a constant -var DefaultKurtosisPackage string = "" +var DefaultKurtosisPackage string = "github.com/Layr-Labs/avs-devnet/kurtosis_package" //nolint:gochecknoglobals // these are constants var ( From 792ef3094e08d8bc553da0b3c5368d42094a9554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:02:56 -0300 Subject: [PATCH 6/7] ci: remove testing from release workflow (#191) Since we already have a CI, we don't need this setup. We just have to wait for the CI to finish before making a release. --- .github/workflows/release.yml | 25 ------------------------- RELEASE.md | 2 ++ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53dac7b2..52fb6d3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,32 +10,7 @@ env: GO_VERSION: '1.22.4' jobs: - lint: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - working-directory: ./cli - version: latest - - test: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - name: Run go test - working-directory: ./cli - run: go test -v ./... - release: - needs: [lint, test] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/RELEASE.md b/RELEASE.md index 4f2a13a6..ffb57880 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -7,6 +7,8 @@ To release a new version of the CLI, follow the steps below: > [!WARNING] > You need to have write permission to this repo to release a new version +- [ ] Check the last CI run on `main` succeeded. + - [ ] Checkout `main` and pull the latest changes: ```sh From 9f28b30bf36e11115339da9d785fd890f1dd4654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:19:11 -0300 Subject: [PATCH 7/7] chore: parallelize test by starting subshell (#193) This PR makes `TestStartLocalHelloWorld` start a subshell to clone the repo, instead of changing the current directory. That allow us to run it in parallel with the rest. --- Makefile | 5 ++++- src/cmds/start_test.go | 28 +++------------------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index da3846b2..2351542d 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,10 @@ fmt: kurtosis_fmt cli_fmt ## 🧹 Format all code lint: kurtosis_lint cli_lint ## 🧹 Lint all code -test: ## 🧪 Run tests +test: examples/hello-world-avs ## 🧪 Run tests + # NOTE: we do this to avoid race conditions in the engine initialization + kurtosis engine start + go test -v -timeout 30m ./... diff --git a/src/cmds/start_test.go b/src/cmds/start_test.go index 49fdbb1f..f497e9d9 100644 --- a/src/cmds/start_test.go +++ b/src/cmds/start_test.go @@ -3,7 +3,6 @@ package cmds_test import ( "context" "os" - "os/exec" "path/filepath" "testing" @@ -46,23 +45,6 @@ func startDevnet(t *testing.T, devnetConfig config.DevnetConfig, workingDir stri require.NoError(t, err, "Failed to start new devnet") } -func goToDir(t *testing.T, destination string) { - dir, err := os.Getwd() - require.NoError(t, err, "Failed to get cwd") - - err = os.Chdir(destination) - require.NoError(t, err, "Failed to go to repo root") - - t.Cleanup(func() { - // Return to the original directory - err = os.Chdir(dir) - // Panic if failed, to avoid running other tests in the wrong directory - if err != nil { - panic(err) - } - }) -} - func TestStartDefaultDevnet(t *testing.T) { t.Parallel() startDevnet(t, config.DefaultConfig(), examplesDir) @@ -77,13 +59,9 @@ func TestStartIncredibleSquaring(t *testing.T) { } func TestStartLocalHelloWorld(t *testing.T) { - // NOTE: we don't run t.Parallel() here because we need to change the working directory - goToDir(t, "../../") - - // Clone the hello-world-avs repo - err := exec.Command("make", "examples/hello-world-avs").Run() - require.NoError(t, err, "Failed to make hello-world-avs repo") - + // Check that the Hello World AVS repo was downloaded + require.DirExists(t, filepath.Join(examplesDir, "hello-world-avs"), "Hello World AVS repo not found") + t.Parallel() configFile := filepath.Join(examplesDir, "hello_world_local.yaml") devnetConfig, err := config.LoadFromPath(configFile) require.NoError(t, err, "Failed to parse example config")