diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2644182e..3b134818 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,7 +17,7 @@ /workflows/ @smartcontractkit/foundations @smartcontractkit/core @smartcontractkit/op-tooling /billing/ @smartcontractkit/cre-business @smartcontractkit/core @smartcontractkit/op-tooling /storage-service/ @smartcontractkit/dev-services @smartcontractkit/op-tooling -/.github/workflows/cre-validations.yml @smartcontractkit/keystone @smartcontractkit/op-tooling +/.github/workflows/cre-*.yml @smartcontractkit/keystone @smartcontractkit/op-tooling /linking-service/ @smartcontractkit/cre-business @smartcontractkit/core @smartcontractkit/op-tooling # Data diff --git a/.github/workflows/cre-add-evm-chain.yml b/.github/workflows/cre-add-evm-chain.yml new file mode 100644 index 00000000..ff5967c7 --- /dev/null +++ b/.github/workflows/cre-add-evm-chain.yml @@ -0,0 +1,139 @@ +# Adds a new EVM chain to client.proto and creates a PR. +# +# Usage: +# 1. Manual: Actions tab → "Add EVM Chain" → Enter selector → Run +# 2. From another workflow: +# jobs: +# add-chain: +# uses: ./.github/workflows/add-evm-chain.yml +# with: +# selector: "5009297550715157269" +# 3. Via API: +# curl -X POST -H "Authorization: token $TOKEN" \ +# -H "Accept: application/vnd.github.v3+json" \ +# https://api.github.com/repos/OWNER/REPO/dispatches \ +# -d '{"event_type":"add-evm-chain","client_payload":{"selector":"5009297550715157269"}}' +# +# Outputs: +# - Creates a PR adding the chain to cre/capabilities/blockchain/evm/v1alpha/client.proto +# - Annotation with chain name and PR link +# - Warning annotation if chain already exists +name: Add EVM Chain + +on: + # Manual trigger from GitHub Actions UI + workflow_dispatch: + inputs: + selector: + description: 'Chain selector (uint64), e.g. 5009297550715157269 for ethereum-mainnet' + required: true + type: string + + # Allows this workflow to be called from other workflows + workflow_call: + inputs: + selector: + description: 'Chain selector (uint64), e.g. 5009297550715157269 for ethereum-mainnet' + required: true + type: string + + # Allows triggering via GitHub API with event_type "add-evm-chain" + repository_dispatch: + types: [add-evm-chain] + +permissions: + contents: write + pull-requests: write + +jobs: + add-chain: + runs-on: ubuntu-latest + # Resolve selector from inputs (workflow_dispatch/workflow_call) or client_payload (repository_dispatch) + env: + CHAIN_SELECTOR: ${{ inputs.selector || github.event.client_payload.selector }} + steps: + # Clone the repository so we can modify files and create a PR + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + # Parse .tool-versions file to extract version numbers for Go, protoc, etc. + - name: Set tool versions + id: tool-versions + uses: smartcontractkit/tool-versions-to-env-action@aabd5efbaf28005284e846c5cf3a02f2cba2f4c2 # v1.0.8 + + # Install Go runtime using the version specified in .tool-versions + - name: Set up Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.0.0 + with: + go-version: ${{ steps.tool-versions.outputs.golang_version }} + + # Install asdf version manager and all plugins defined in .tool-versions + - name: Install asdf + uses: asdf-vm/actions/plugins-add@1902764435ca0dd2f3388eea723a4f92a4eb8302 # v3.0.2 + with: + asdf_branch: v0.16.7 + + # Add asdf binaries and shims to PATH so installed tools are available + - name: Setup asdf environment + run: | + echo "$HOME/.asdf/bin" >> $GITHUB_PATH + echo "$HOME/.asdf/shims" >> $GITHUB_PATH + + # Install the protobuf compiler using the version from .tool-versions + - name: Install protoc + run: asdf install protoc + + # Install the Go protobuf code generator plugin for protoc + - name: Install protoc-gen-go + run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v${{ steps.tool-versions.outputs.protoc-gen-go_version }} + + # Fetch the latest chain-selectors library which contains chain name mappings + - name: Update chain-selectors and download dependencies + working-directory: cre/go + run: | + go get github.com/smartcontractkit/chain-selectors@latest + go mod tidy + + # Run the add-evm-chain tool to append the new chain entry to client.proto + - name: Add chain to proto + id: add-chain + working-directory: cre/go + run: | + OUTPUT=$(go run ./tools/add-evm-chain -selector $CHAIN_SELECTOR 2>&1) + echo "$OUTPUT" + # Extract chain name from "added (selector: ...)" or "chain already exists" + CHAIN_NAME=$(echo "$OUTPUT" | sed -n 's/^added \([^ ]*\) .*/\1/p; s/^chain \([^ ]*\) already exists/\1/p') + echo "chain_name=$CHAIN_NAME" >> $GITHUB_OUTPUT + # Warn if chain already exists + if echo "$OUTPUT" | grep -q "already exists"; then + echo "::warning title=Chain Already Exists::$CHAIN_NAME (selector: $CHAIN_SELECTOR) already exists" + fi + + # Run go generate to update embedded_gen.go with the modified proto content + - name: Regenerate embedded files + working-directory: cre/go + run: go generate ./... + + # Commit all changes and open a pull request for review + - name: Create PR + id: create-pr + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "feat(cre): add EVM chain" + title: "feat(cre): add EVM chain (selector: ${{ env.CHAIN_SELECTOR }})" + body: "Adds EVM chain with selector `${{ env.CHAIN_SELECTOR }}` to client.proto" + branch: add-evm-chain/${{ env.CHAIN_SELECTOR }} + + # Output chain and PR details as GitHub Actions annotations and job summary + - name: Output summary + run: | + echo "::notice title=Chain Added::Chain: ${{ steps.add-chain.outputs.chain_name }} (selector: $CHAIN_SELECTOR)" + echo "::notice title=Pull Request::${{ steps.create-pr.outputs.pull-request-url }}" + echo "## 🔗 EVM Chain Added" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| **Chain Name** | \`${{ steps.add-chain.outputs.chain_name }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Chain Selector** | \`$CHAIN_SELECTOR\` |" >> $GITHUB_STEP_SUMMARY + echo "| **Pull Request** | ${{ steps.create-pr.outputs.pull-request-url }} |" >> $GITHUB_STEP_SUMMARY + echo "| **PR Number** | #${{ steps.create-pr.outputs.pull-request-number }} |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/cre-notify-cre-sdk-go.yml b/.github/workflows/cre-notify-cre-sdk-go.yml new file mode 100644 index 00000000..c6a239ab --- /dev/null +++ b/.github/workflows/cre-notify-cre-sdk-go.yml @@ -0,0 +1,103 @@ +# Dispatches to cre-sdk-go to bump chainlink-protos version when a PR is merged. +# Uses the CRE Github Automation App (app_id: 2666290) for authentication. +# +# Required Organization Secrets: +# - CRE_GH_AUTOMATION_APP_ID: GitHub App ID +# - CRE_GH_AUTOMATION_APP_PRIVATE_KEY: GitHub App private key +name: Notify cre-sdk-go + +permissions: + contents: read + +on: + # Manual trigger + workflow_dispatch: + inputs: + version: + description: 'Override version (leave empty to calculate from current commit)' + required: false + type: string + skip_tagging: + description: 'Skip version tagging in cre-sdk-go' + required: false + type: boolean + default: false + + pull_request: + types: [closed] + branches: + - main + +jobs: + dispatch: + # Run if manually triggered OR if the PR was merged (not just closed) + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: main + fetch-depth: 1 + + - name: Calculate pseudo-version + id: version + run: | + # Use input version if provided, otherwise calculate from commit + if [ -n "${{ inputs.version }}" ]; then + VERSION="${{ inputs.version }}" + echo "Using provided version: ${VERSION}" + else + # Get the merge commit info + TIMESTAMP=$(TZ=UTC git log -1 --format='%cd' --date=format:%Y%m%d%H%M%S) + SHORT_SHA=$(git rev-parse --short=12 HEAD) + VERSION="v0.0.0-${TIMESTAMP}-${SHORT_SHA}" + echo "Calculated version: ${VERSION}" + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + - name: Generate GitHub App Token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.CRE_GH_AUTOMATION_APP_ID }} + private-key: ${{ secrets.CRE_GH_AUTOMATION_APP_PRIVATE_KEY }} + owner: smartcontractkit + repositories: cre-sdk-go + + - name: Dispatch to cre-sdk-go + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: smartcontractkit/cre-sdk-go + event-type: bump-chainlink-protos + client-payload: | + { + "version": "${{ steps.version.outputs.version }}", + "skip_tagging": ${{ inputs.skip_tagging == true }} + } + + - name: Summary + run: | + DISPATCH_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + + echo "## 🚀 Dispatch Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Target" >> $GITHUB_STEP_SUMMARY + echo "- **Repository:** [\`smartcontractkit/cre-sdk-go\`](https://github.com/smartcontractkit/cre-sdk-go)" >> $GITHUB_STEP_SUMMARY + echo "- **Workflow:** \`bump-chainlink-protos.yml\`" >> $GITHUB_STEP_SUMMARY + echo "- **Event Type:** \`bump-chainlink-protos\`" >> $GITHUB_STEP_SUMMARY + echo "- **Dispatched at:** ${DISPATCH_TIME}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Payload" >> $GITHUB_STEP_SUMMARY + echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| version | \`${{ steps.version.outputs.version }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| skip_tagging | \`${{ inputs.skip_tagging == true }}\` |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### View Triggered Workflow" >> $GITHUB_STEP_SUMMARY + echo "👉 **[View bump-chainlink-protos runs](https://github.com/smartcontractkit/cre-sdk-go/actions/workflows/bump-chainlink-protos.yml?query=event%3Arepository_dispatch)**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "_Look for the run started at approximately ${DISPATCH_TIME}_" >> $GITHUB_STEP_SUMMARY + + diff --git a/cre/go/go.mod b/cre/go/go.mod index c417f2f3..1cc49f3a 100644 --- a/cre/go/go.mod +++ b/cre/go/go.mod @@ -5,12 +5,14 @@ go 1.24.5 require ( github.com/go-viper/mapstructure/v2 v2.4.0 github.com/shopspring/decimal v1.4.0 - github.com/stretchr/testify v1.10.0 + github.com/smartcontractkit/chain-selectors v1.0.89 + github.com/stretchr/testify v1.11.1 google.golang.org/protobuf v1.36.7 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/cre/go/go.sum b/cre/go/go.sum index e1ac1829..a0580bf6 100644 --- a/cre/go/go.sum +++ b/cre/go/go.sum @@ -4,12 +4,16 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/smartcontractkit/chain-selectors v1.0.89 h1:L9oWZGqQXWyTPnC6ODXgu3b0DFyLmJ9eHv+uJrE9IZY= +github.com/smartcontractkit/chain-selectors v1.0.89/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= diff --git a/cre/go/tools/add-evm-chain/main.go b/cre/go/tools/add-evm-chain/main.go new file mode 100644 index 00000000..8e8f729c --- /dev/null +++ b/cre/go/tools/add-evm-chain/main.go @@ -0,0 +1,106 @@ +// Package main adds a new EVM chain selector to client.proto. +package main + +import ( + "flag" + "fmt" + "os" + "regexp" + "sort" + "strconv" + "strings" + + chain_selectors "github.com/smartcontractkit/chain-selectors" +) + +const protoPath = "../capabilities/blockchain/evm/v1alpha/client.proto" + +func main() { + selector := flag.Uint64("selector", 0, "chain selector value (required)") + flag.Parse() + + if *selector == 0 { + fatal("selector is required") + } + + // Look up chain ID from selector + chainId, err := chain_selectors.ChainIdFromSelector(*selector) + if err != nil { + fatal("selector %d not found: %v", *selector, err) + } + + // Get chain name from chain ID + chainName, err := chain_selectors.NameFromChainId(chainId) + if err != nil { + fatal("failed to get chain name for chain ID %d: %v", chainId, err) + } + + // Read proto file + content, err := os.ReadFile(protoPath) + if err != nil { + fatal("failed to read %s: %v", protoPath, err) + } + + // Check if already exists + if strings.Contains(string(content), fmt.Sprintf(`key: "%s"`, chainName)) { + fmt.Printf("chain %s already exists\n", chainName) + return + } + + // Parse, add, sort, rebuild + newContent, err := addChain(string(content), chainName, *selector) + if err != nil { + fatal("failed to add chain: %v", err) + } + + if err := os.WriteFile(protoPath, []byte(newContent), 0644); err != nil { + fatal("failed to write file: %v", err) + } + + fmt.Printf("added %s (selector: %d)\n", chainName, *selector) +} + +func addChain(content, name string, selector uint64) (string, error) { + // Find defaults array + re := regexp.MustCompile(`defaults:\s*\[([\s\S]*?)\n\s*\]`) + match := re.FindStringSubmatch(content) + if len(match) < 2 { + return "", fmt.Errorf("defaults array not found") + } + + // Parse entries + entryRe := regexp.MustCompile(`\{\s*key:\s*"([^"]+)"\s*value:\s*(\d+)\s*\}`) + entries := entryRe.FindAllStringSubmatch(match[1], -1) + + type entry struct { + key string + val uint64 + } + var list []entry + for _, e := range entries { + v, _ := strconv.ParseUint(e[2], 10, 64) + list = append(list, entry{e[1], v}) + } + list = append(list, entry{name, selector}) + + sort.Slice(list, func(i, j int) bool { return list[i].key < list[j].key }) + + var b strings.Builder + b.WriteString("defaults: [\n") + for i, e := range list { + b.WriteString(fmt.Sprintf(" {\n key: \"%s\"\n value: %d\n }", e.key, e.val)) + if i < len(list)-1 { + b.WriteString(",\n") + } else { + b.WriteString("\n") + } + } + b.WriteString(" ]") + + return re.ReplaceAllString(content, b.String()), nil +} + +func fatal(format string, args ...any) { + fmt.Fprintf(os.Stderr, "error: "+format+"\n", args...) + os.Exit(1) +}