diff --git a/.github/workflows/release-aggregator.yml b/.github/workflows/release-aggregator.yml index 5980369..f344176 100644 --- a/.github/workflows/release-aggregator.yml +++ b/.github/workflows/release-aggregator.yml @@ -102,7 +102,7 @@ jobs: secret-files: | git_config=${{ env.HOME }}/.gitconfig git_credentials=${{ env.XDG_CONFIG_HOME }}/git/credentials - file: aggregator.Dockerfile + file: ./ops/aggregator.Dockerfile labels: ${{ steps.aggregator-meta.outputs.labels }} tags: ${{ steps.aggregator-meta.outputs.tags }} diff --git a/.github/workflows/release-operator-tool.yml b/.github/workflows/release-operator-tool.yml new file mode 100644 index 0000000..4025f7d --- /dev/null +++ b/.github/workflows/release-operator-tool.yml @@ -0,0 +1,108 @@ +name: Release Operator Tool +on: + push: + tags: ["*"] + workflow_dispatch: + # pull_request: # for testing only +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always + # https://github.com/mozilla/sccache/releases + SCCACHE_TAR_URL: https://github.com/mozilla/sccache/releases/download/v0.7.4/sccache-v0.7.4-x86_64-unknown-linux-musl.tar.gz + SCCACHE_CACHE_SIZE: "3G" + RUSTC_WRAPPER: sccache + SUBWASM_VERSION: v0.20.0 + CARGO_INCREMENTAL: "0" + CARGO_NET_GIT_FETCH_WITH_CLI: true + ECR_REPO: 305587085711.dkr.ecr.us-west-2.amazonaws.com/mach-operator-tool + PUB_REPO: public.ecr.aws + +jobs: + build-docker-image: + name: Build Docker Image and extract files + runs-on: ["self-hosted", "linux", "x64", "ubuntu-latest"] + steps: + - name: Login to ECR + uses: docker/login-action@v3 + with: + registry: ${{ env.ECR_REPO }} + username: ${{ secrets.ECR_ACCESS_KEY_ID }} + password: ${{ secrets.ECR_ACCESS_KEY }} + + - name: Login to public ECR + uses: docker/login-action@v3 + with: + registry: ${{ env.PUB_REPO }} + username: ${{ secrets.ECR_ACCESS_KEY_ID }} + password: ${{ secrets.ECR_ACCESS_KEY }} + + - name: Checkout Sources + uses: actions/checkout@v4 + with: + submodules: "true" + token: ${{ secrets.GIT_CREDENTIALS }} + + - uses: de-vri-es/setup-git-credentials@v2 + with: + credentials: https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_CREDENTIALS }}@github.com + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + id: docker-builder + uses: docker/setup-buildx-action@v3 + + - name: Prepare Environment Variables + run: | + echo "HOME=$HOME" | tee -a $GITHUB_ENV + echo "XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}" | tee -a $GITHUB_ENV + echo "SHORT_SHA=${GITHUB_SHA::7}" | tee -a $GITHUB_ENV + GIT_TAG=$(git tag --points-at HEAD) + echo "GIT_TAG=$GIT_TAG" | tee -a $GITHUB_ENV + GIT_BRANCH=$(git branch --show-current) + echo "GIT_BRANCH=$GIT_BRANCH" | tee -a $GITHUB_ENV + echo "REF_NAME=$(echo ${GIT_TAG:-$GITHUB_REF_NAME} | sed 's/[^a-zA-Z0-9._]/-/g')" | tee -a $GITHUB_ENV + - run: cat $HOME/.gitconfig && cat $XDG_CONFIG_HOME/git/credentials + + - name: Extract operator metadata (tags, labels) for Docker + id: operator-meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.ECR_REPO }} + ${{ env.PUB_REPO }}/altlayer/mach-operator-tool + flavor: | + prefix= + suffix= + tags: | + type=sha,format=short,prefix= + type=ref,event=branch + type=ref,event=branch,suffix=-${{ github.run_number }} + type=ref,event=tag + type=raw,value=${{ env.REF_NAME }},enable=${{ github.event_name == 'pull_request' }} + type=raw,value=${{ env.GIT_BRANCH }},enable=${{ env.GIT_BRANCH != '' }} + type=raw,value=latest,enable=${{ env.GIT_BRANCH == 'master' }} + + - name: Build operator docker with cache and push images + uses: docker/build-push-action@v5 + id: docker_operator_tool_build + with: + context: . + builder: ${{ steps.docker-builder.outputs.name }} + target: app + push: ${{ github.event_name != 'pull_request' }} + provenance: false + cache-from: | + type=registry,ref=${{ env.ECR_REPO }}:latest + type=registry,ref=${{ env.ECR_REPO }}:${{ env.REF_NAME }} + secret-files: | + git_config=${{ env.HOME }}/.gitconfig + git_credentials=${{ env.XDG_CONFIG_HOME }}/git/credentials + file: ops/operator-tool.Dockerfile + labels: ${{ steps.operator-meta.outputs.labels }} + tags: ${{ steps.operator-meta.outputs.tags }} + + diff --git a/.github/workflows/release-operator.yml b/.github/workflows/release-operator.yml index 63d0c19..88191ec 100644 --- a/.github/workflows/release-operator.yml +++ b/.github/workflows/release-operator.yml @@ -101,7 +101,7 @@ jobs: secret-files: | git_config=${{ env.HOME }}/.gitconfig git_credentials=${{ env.XDG_CONFIG_HOME }}/git/credentials - file: operator.Dockerfile + file: ./ops/operator.Dockerfile labels: ${{ steps.operator-meta.outputs.labels }} tags: ${{ steps.operator-meta.outputs.tags }} diff --git a/cli/actions/deposit_into_strategy.go b/cli/actions/deposit_into_strategy.go index 936cfc3..47a448a 100644 --- a/cli/actions/deposit_into_strategy.go +++ b/cli/actions/deposit_into_strategy.go @@ -17,15 +17,18 @@ func DepositIntoStrategy(ctx *cli.Context) error { configPath := ctx.GlobalString(config.ConfigFileFlag.Name) nodeConfig := config.NodeConfig{} - err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) - if err != nil { - return err - } - configJson, err := json.MarshalIndent(nodeConfig, "", " ") - if err != nil { - log.Fatalf(err.Error()) + + if configPath != "" { + err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) + if err != nil { + return err + } + configJson, err := json.MarshalIndent(nodeConfig, "", " ") + if err != nil { + log.Fatalf(err.Error()) + } + log.Println("Config:", string(configJson)) } - log.Println("Config:", string(configJson)) operator, err := operator.NewOperatorFromConfig(nodeConfig) if err != nil { diff --git a/cli/actions/deregister_operator_with_avs.go b/cli/actions/deregister_operator_with_avs.go new file mode 100644 index 0000000..36e7e40 --- /dev/null +++ b/cli/actions/deregister_operator_with_avs.go @@ -0,0 +1,40 @@ +package actions + +import ( + "encoding/json" + "log" + + sdkutils "github.com/Layr-Labs/eigensdk-go/utils" + "github.com/alt-research/avs/core/config" + "github.com/alt-research/avs/operator" + "github.com/urfave/cli" +) + +func DeregisterOperatorWithAvs(ctx *cli.Context) error { + configPath := ctx.GlobalString(config.ConfigFileFlag.Name) + nodeConfig := config.NodeConfig{} + + if configPath != "" { + err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) + if err != nil { + return err + } + configJson, err := json.MarshalIndent(nodeConfig, "", " ") + if err != nil { + log.Fatalf(err.Error()) + } + log.Println("Config:", string(configJson)) + } + + operator, err := operator.NewOperatorFromConfig(nodeConfig) + if err != nil { + return err + } + + err = operator.DeregisterOperatorWithAvs() + if err != nil { + return err + } + + return nil +} diff --git a/cli/actions/print_operator_status.go b/cli/actions/print_operator_status.go index 221b620..13c131c 100644 --- a/cli/actions/print_operator_status.go +++ b/cli/actions/print_operator_status.go @@ -14,15 +14,18 @@ func PrintOperatorStatus(ctx *cli.Context) error { configPath := ctx.GlobalString(config.ConfigFileFlag.Name) nodeConfig := config.NodeConfig{} - err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) - if err != nil { - return err - } - configJson, err := json.MarshalIndent(nodeConfig, "", " ") - if err != nil { - log.Fatalf(err.Error()) + + if configPath != "" { + err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) + if err != nil { + return err + } + configJson, err := json.MarshalIndent(nodeConfig, "", " ") + if err != nil { + log.Fatalf(err.Error()) + } + log.Println("Config:", string(configJson)) } - log.Println("Config:", string(configJson)) operator, err := operator.NewOperatorFromConfig(nodeConfig) if err != nil { diff --git a/cli/actions/register_operator_with_avs.go b/cli/actions/register_operator_with_avs.go index a6de22b..0b57d74 100644 --- a/cli/actions/register_operator_with_avs.go +++ b/cli/actions/register_operator_with_avs.go @@ -16,15 +16,18 @@ func RegisterOperatorWithAvs(ctx *cli.Context) error { configPath := ctx.GlobalString(config.ConfigFileFlag.Name) nodeConfig := config.NodeConfig{} - err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) - if err != nil { - return err - } - configJson, err := json.MarshalIndent(nodeConfig, "", " ") - if err != nil { - log.Fatalf(err.Error()) + + if configPath != "" { + err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) + if err != nil { + return err + } + configJson, err := json.MarshalIndent(nodeConfig, "", " ") + if err != nil { + log.Fatalf(err.Error()) + } + log.Println("Config:", string(configJson)) } - log.Println("Config:", string(configJson)) operator, err := operator.NewOperatorFromConfig(nodeConfig) if err != nil { @@ -36,7 +39,7 @@ func RegisterOperatorWithAvs(ctx *cli.Context) error { log.Printf("OPERATOR_ECDSA_KEY_PASSWORD env var not set. using empty string") } operatorEcdsaPrivKey, err := sdkecdsa.ReadKey( - nodeConfig.EcdsaPrivateKeyStorePath, + operator.Config().EcdsaPrivateKeyStorePath, ecdsaKeyPassword, ) if err != nil { diff --git a/cli/actions/register_operator_with_eigenlayer.go b/cli/actions/register_operator_with_eigenlayer.go index 4b6ef39..d9f6e88 100644 --- a/cli/actions/register_operator_with_eigenlayer.go +++ b/cli/actions/register_operator_with_eigenlayer.go @@ -15,15 +15,18 @@ func RegisterOperatorWithEigenlayer(ctx *cli.Context) error { configPath := ctx.GlobalString(config.ConfigFileFlag.Name) nodeConfig := config.NodeConfig{} - err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) - if err != nil { - return err - } - configJson, err := json.MarshalIndent(nodeConfig, "", " ") - if err != nil { - log.Fatalf(err.Error()) + + if configPath != "" { + err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) + if err != nil { + return err + } + configJson, err := json.MarshalIndent(nodeConfig, "", " ") + if err != nil { + log.Fatalf(err.Error()) + } + log.Println("Config:", string(configJson)) } - log.Println("Config:", string(configJson)) operator, err := operator.NewOperatorFromConfig(nodeConfig) if err != nil { diff --git a/cli/main.go b/cli/main.go index f09ab78..684603e 100644 --- a/cli/main.go +++ b/cli/main.go @@ -47,10 +47,7 @@ func main() { { Name: "deregister-operator-with-avs", Aliases: []string{"d"}, - Action: func(ctx *cli.Context) error { - log.Fatal("Command not implemented.") - return nil - }, + Action: actions.DeregisterOperatorWithAvs, }, { Name: "print-operator-status", diff --git a/core/config/avs_config.go b/core/config/avs_config.go index 4b60b75..13ba692 100644 --- a/core/config/avs_config.go +++ b/core/config/avs_config.go @@ -16,4 +16,5 @@ type NodeConfig struct { EnableNodeApi bool `yaml:"enable_node_api"` OperatorServerIpPortAddr string `yaml:"operator_server_ip_port_addr"` MetadataURI string `yaml:"metadata_uri"` + OperatorSocket string `yaml:"operator_socket"` } diff --git a/core/config/config.go b/core/config/config.go index a5b9a4c..5c17be1 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -187,7 +187,7 @@ var ( /* Required Flags */ ConfigFileFlag = cli.StringFlag{ Name: "config", - Required: true, + Required: false, Usage: "Load configuration from `FILE`", } DeploymentFileFlag = cli.StringFlag{ diff --git a/docker-compose.yml b/docker-compose.yml index 18a50b3..ba52b27 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: contracts-deploy: build: context: . - dockerfile: contract-deployer.Dockerfile + dockerfile: ./ops/contract-deployer.Dockerfile container_name: contracts-deploy working_dir: /app/ volumes: diff --git a/operator/cmd/main.go b/operator/cmd/main.go index cb9e647..714e6bd 100644 --- a/operator/cmd/main.go +++ b/operator/cmd/main.go @@ -33,15 +33,18 @@ func operatorMain(ctx *cli.Context) error { log.Println("Initializing Operator") configPath := ctx.GlobalString(config.ConfigFileFlag.Name) nodeConfig := config.NodeConfig{} - err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) - if err != nil { - return err - } - configJson, err := json.MarshalIndent(nodeConfig, "", " ") - if err != nil { - log.Fatalf(err.Error()) + + if configPath != "" { + err := sdkutils.ReadYamlConfig(configPath, &nodeConfig) + if err != nil { + return err + } + configJson, err := json.MarshalIndent(nodeConfig, "", " ") + if err != nil { + log.Fatalf(err.Error()) + } + log.Println("Config from file:", string(configJson)) } - log.Println("Config from file:", string(configJson)) log.Println("initializing operator") operator, err := operator.NewOperatorFromConfig(nodeConfig) diff --git a/operator/operator.go b/operator/operator.go index 7b84601..a766080 100644 --- a/operator/operator.go +++ b/operator/operator.go @@ -82,6 +82,11 @@ func withEnvConfig(c config.NodeConfig) config.NodeConfig { // - `OPERATOR_SERVER_URL` : operator_server_ip_port_addr // - `METADATA_URI` : metadata_uri + Production, ok := os.LookupEnv("OPERATOR_PRODUCTION") + if ok && Production != "" { + c.Production = Production == "true" + } + ethRpcUrl, ok := os.LookupEnv("ETH_RPC_URL") if ok && ethRpcUrl != "" { c.EthRpcUrl = ethRpcUrl @@ -147,6 +152,11 @@ func withEnvConfig(c config.NodeConfig) config.NodeConfig { c.MetadataURI = metadataURI } + operatorSocket, ok := os.LookupEnv("OPERATOR_SOCKET") + if ok && operatorSocket != "" { + c.OperatorSocket = operatorSocket + } + configJson, err := json.MarshalIndent(c, "", " ") if err != nil { panic(err) @@ -443,3 +453,7 @@ func (o *Operator) SignTaskResponse(taskResponse *message.AlertTaskInfo) (*messa o.logger.Debug("Signed task response", "signedTaskResponse", signedTaskResponse) return signedTaskResponse, nil } + +func (o Operator) Config() config.NodeConfig { + return o.config +} diff --git a/operator/registration.go b/operator/registration.go index 59d8c35..b260594 100644 --- a/operator/registration.go +++ b/operator/registration.go @@ -12,9 +12,11 @@ import ( "encoding/json" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/Layr-Labs/eigensdk-go/crypto/bls" eigenSdkTypes "github.com/Layr-Labs/eigensdk-go/types" @@ -52,8 +54,15 @@ func (o *Operator) RegisterOperatorWithAvs( ) error { // hardcode these things for now quorumNumbers := []byte{0} - socket := "Not Needed" - operatorToAvsRegistrationSigSalt := [32]byte{123} + socket := o.config.OperatorSocket + + // Generate salt and expiry + privateKeyBytes := []byte(o.blsKeypair.PrivKey.String()) + salt := [32]byte{} + copy(salt[:], crypto.Keccak256([]byte("churn"), []byte(time.Now().String()), quorumNumbers[:], privateKeyBytes)) + + operatorToAvsRegistrationSigSalt := salt + curBlockNum, err := o.ethClient.BlockNumber(context.Background()) if err != nil { o.logger.Errorf("Unable to get current block number") @@ -93,6 +102,36 @@ func (o *Operator) RegisterOperatorWithAvs( return nil } +// Deregistration specific functions +func (o *Operator) DeregisterOperatorWithAvs() error { + // hardcode these things for now + quorumNumbers := []byte{0} + operatorAddr := o.operatorAddr + o.logger.Info( + "DeregisterOperatorFromAvs", + "quorumNumbers", quorumNumbers[0], + "operatorAddr", operatorAddr, + ) + + quorumNumbersToSDK := make([]sdktypes.QuorumNum, len(quorumNumbers)) + for i, _ := range quorumNumbers { + quorumNumbersToSDK[i] = sdktypes.QuorumNum(uint8(quorumNumbers[i])) + } + + _, err := o.avsWriter.DeregisterOperator( + context.Background(), + quorumNumbersToSDK, + regcoord.BN254G1Point{}, + ) + if err != nil { + o.logger.Error("Unable to deregister operator with avs registry coordinator", err) + return err + } + o.logger.Infof("Deregister operator with avs registry coordinator.") + + return nil +} + // PRINTING STATUS OF OPERATOR: 1 // operator address: 0xa0ee7a142d267c1f36714e4a8f75612f20a79720 // dummy token balance: 0 diff --git a/aggregator.Dockerfile b/ops/aggregator.Dockerfile similarity index 54% rename from aggregator.Dockerfile rename to ops/aggregator.Dockerfile index 793d172..54331a7 100644 --- a/aggregator.Dockerfile +++ b/ops/aggregator.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 as build +FROM golang:1.21-bullseye as build WORKDIR /usr/src/app @@ -11,6 +11,12 @@ COPY . . WORKDIR /usr/src/app/aggregator/cmd RUN go build -v -o /usr/local/bin/aggregator ./... -FROM debian:latest as app +FROM debian:bullseye as app COPY --from=build /usr/local/bin/aggregator /usr/local/bin/aggregator + +RUN apt-get update && \ + apt-get install --no-install-recommends -y curl sudo daemontools jq ca-certificates && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + ENTRYPOINT [ "aggregator"] diff --git a/contract-deployer.Dockerfile b/ops/contract-deployer.Dockerfile similarity index 100% rename from contract-deployer.Dockerfile rename to ops/contract-deployer.Dockerfile diff --git a/ops/operator-tool.Dockerfile b/ops/operator-tool.Dockerfile new file mode 100644 index 0000000..c538de5 --- /dev/null +++ b/ops/operator-tool.Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.21-bullseye as build + +WORKDIR /usr/src/app + +COPY go.mod go.sum ./ + +RUN go mod download && go mod tidy && go mod verify + +COPY . . + +RUN make build-cli + +RUN cp ./bin/mach-operator-cli /usr/local/bin/mach-operator-cli + +FROM debian:bullseye as app +COPY --from=build /usr/local/bin/mach-operator-cli /usr/local/bin/mach-operator-cli + +RUN apt-get update && \ + apt-get install --no-install-recommends -y curl sudo daemontools jq ca-certificates && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ENTRYPOINT [ "mach-operator-cli"] diff --git a/operator.Dockerfile b/ops/operator.Dockerfile similarity index 53% rename from operator.Dockerfile rename to ops/operator.Dockerfile index 2fd1466..748f510 100644 --- a/operator.Dockerfile +++ b/ops/operator.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 as build +FROM golang:1.21-bullseye as build WORKDIR /usr/src/app @@ -11,6 +11,12 @@ COPY . . WORKDIR /usr/src/app/operator/cmd RUN go build -v -o /usr/local/bin/operator ./... -FROM debian:latest as app +FROM debian:bullseye as app COPY --from=build /usr/local/bin/operator /usr/local/bin/operator + +RUN apt-get update && \ + apt-get install --no-install-recommends -y curl sudo daemontools jq ca-certificates && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + ENTRYPOINT [ "operator"] diff --git a/ops/scripts/init-devnet.sh b/ops/scripts/init-devnet.sh index 3ebbc21..ac66084 100755 --- a/ops/scripts/init-devnet.sh +++ b/ops/scripts/init-devnet.sh @@ -21,6 +21,7 @@ AVS_ADDRESS_PATH='./contracts/script/output/machavs_deploy_output.json' UNDERLAYING_TOKEN=$(cat $EIGENLAYER_ADDRESS_PATH | jq -r '.underlayingToken' ) REGISTRY_COORDINATOR_ADDR=$(cat $AVS_ADDRESS_PATH | jq -r '.registryCoordinator' ) OPERATOR_STATE_RETRIEVER_ADDR=$(cat $AVS_ADDRESS_PATH | jq -r '.operatorStateRetriever' ) +MACH_AVS_ADDR=$(cat $AVS_ADDRESS_PATH | jq -r '.machServiceManager' ) STRATEGY_BASE_TVL_LIMITS_ADDR=$(cat $EIGENLAYER_ADDRESS_PATH | jq -r '.strategyBaseTVLLimits' ) cast send -f $OWNER_ADDR --private-key $OWNER_PRIVATE --rpc-url $RPC_URL --value 2ether $OPERATOR_ADDR @@ -33,6 +34,9 @@ sed -i 's/ecdsa_private_key_store_path: .\+/ecdsa_private_key_store_path: .\/con sed -i 's/bls_private_key_store_path: .\+/bls_private_key_store_path: .\/config-files\/key\/'${OPERATOR_KEY_NAME}'.bls.key.json/g' ./ops/configs/operator-docker-compose.yaml sed -i 's/metadata_uri: .\+/metadata_uri: '${METADATA_URI}'/g' ./ops/configs/operator-docker-compose.yaml +# disable the allow list, so we can test easy. +cast send -f $OWNER_ADDR --private-key $OWNER_PRIVATE $MACH_AVS_ADDR --rpc-url $RPC_URL 'disableAllowlist()' + ./bin/mach-operator-cli --config ./ops/configs/operator-docker-compose.yaml rel ./bin/mach-operator-cli --config ./ops/configs/operator-docker-compose.yaml d --strategy-addr $STRATEGY_BASE_TVL_LIMITS_ADDR --amount 10000000 ./bin/mach-operator-cli --config ./ops/configs/operator-docker-compose.yaml r