From c4c0b78c999627316857fcedf54aec34dbb10abe Mon Sep 17 00:00:00 2001 From: Pacu Date: Tue, 28 Apr 2026 09:33:32 -0300 Subject: [PATCH 1/3] Add proto-validation CI: buf lint/breaking + per-language codegen Introduces the first GitHub Actions workflow for this repo. Every PR touching walletrpc/** now runs: - buf lint (BASIC, with PACKAGE_DIRECTORY_MATCH excepted to preserve the walletrpc/ layout that downstream subtree consumers depend on). - buf breaking (WIRE_JSON) against the latest v* git tag, so PRs are checked against what consumers actually pulled in via git subtree. - Codegen-only validation in parallel for Go, Rust (tonic-build), Python, Swift (swift-protobuf + grpc-swift via Mint on macOS), and Kotlin. Codegen is enough to catch reserved-word collisions and plugin-level errors without paying for full downstream compilation. All toolchain versions are pinned in a single env: block at the top of the workflow for easy bumping. Path filters and a concurrency group keep wall-clock time down. Refs #27. --- .github/workflows/proto-validation.yml | 239 +++++++++++++++++++++++++ buf.yaml | 14 ++ 2 files changed, 253 insertions(+) create mode 100644 .github/workflows/proto-validation.yml create mode 100644 buf.yaml diff --git a/.github/workflows/proto-validation.yml b/.github/workflows/proto-validation.yml new file mode 100644 index 0000000..500ea4a --- /dev/null +++ b/.github/workflows/proto-validation.yml @@ -0,0 +1,239 @@ +name: Proto validation + +on: + pull_request: + paths: + - 'walletrpc/**' + - 'buf.yaml' + - 'buf.lock' + - '.github/workflows/proto-validation.yml' + push: + branches: [main] + tags: ['v*'] + +concurrency: + group: proto-validation-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# All toolchain versions are pinned. Bump them here when updating. +env: + BUF_VERSION: 1.47.2 + PROTOC_VERSION: '28.3' + GO_VERSION: '1.23.4' + PROTOC_GEN_GO_VERSION: v1.36.4 + PROTOC_GEN_GO_GRPC_VERSION: v1.5.1 + RUST_TOOLCHAIN: '1.83.0' + TONIC_BUILD_VERSION: '0.12.3' + PYTHON_VERSION: '3.12' + GRPCIO_TOOLS_VERSION: '1.68.1' + JAVA_VERSION: '21' + PROTOC_GEN_GRPC_KOTLIN_VERSION: '1.4.1' + SWIFT_PROTOBUF_VERSION: '1.28.2' + GRPC_SWIFT_VERSION: '1.24.1' + +jobs: + buf: + name: buf lint + breaking + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Need full history + tags to compare against the latest release tag. + fetch-depth: 0 + - uses: bufbuild/buf-setup-action@v1 + with: + version: ${{ env.BUF_VERSION }} + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Lint + run: buf lint + - name: Discover latest release tag + id: baseline + run: | + LATEST_TAG=$(git tag --list 'v*' --sort=-v:refname | grep -v -- '-rc' | head -n1 || true) + if [ -z "$LATEST_TAG" ]; then + echo "No release tag found; skipping breaking-change check." + else + echo "Baseline tag: $LATEST_TAG" + fi + echo "tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" + - name: Breaking-change check vs latest release tag + if: steps.baseline.outputs.tag != '' + run: buf breaking --against ".git#tag=${{ steps.baseline.outputs.tag }}" + + codegen-go: + name: codegen (Go) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + - uses: arduino/setup-protoc@v3 + with: + version: ${{ env.PROTOC_VERSION }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install protoc plugins + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@${{ env.PROTOC_GEN_GO_VERSION }} + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${{ env.PROTOC_GEN_GO_GRPC_VERSION }} + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + - name: Generate + run: | + mkdir -p out + protoc -I walletrpc \ + --go_out=out --go_opt=paths=source_relative \ + --go-grpc_out=out --go-grpc_opt=paths=source_relative \ + walletrpc/service.proto walletrpc/compact_formats.proto + - name: Verify output + run: | + test -s out/service.pb.go + test -s out/service_grpc.pb.go + test -s out/compact_formats.pb.go + + codegen-rust: + name: codegen (Rust / tonic) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + - uses: arduino/setup-protoc@v3 + with: + version: ${{ env.PROTOC_VERSION }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: Swatinem/rust-cache@v2 + with: + workspaces: rust-check + - name: Scaffold tonic-build harness + run: | + mkdir -p rust-check + cat > rust-check/Cargo.toml < rust-check/lib.rs + cat > rust-check/build.rs <<'EOF' + fn main() { + tonic_build::configure() + .build_server(true) + .build_client(true) + .compile_protos( + &["../walletrpc/service.proto", "../walletrpc/compact_formats.proto"], + &["../walletrpc"], + ) + .expect("tonic-build failed"); + } + EOF + - name: Generate + working-directory: rust-check + # `cargo build` runs build.rs, which invokes tonic-build (codegen). + # lib.rs is empty, so this is a codegen-only check. + run: cargo build + + codegen-python: + name: codegen (Python) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + - name: Install grpcio-tools + run: pip install "grpcio-tools==${{ env.GRPCIO_TOOLS_VERSION }}" + - name: Generate + run: | + mkdir -p out + python -m grpc_tools.protoc \ + -I walletrpc \ + --python_out=out \ + --pyi_out=out \ + --grpc_python_out=out \ + walletrpc/service.proto walletrpc/compact_formats.proto + - name: Verify output imports + run: | + test -s out/service_pb2.py + test -s out/service_pb2_grpc.py + test -s out/compact_formats_pb2.py + PYTHONPATH=out python -c "import service_pb2, service_pb2_grpc, compact_formats_pb2" + + codegen-kotlin: + name: codegen (Kotlin) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + - uses: arduino/setup-protoc@v3 + with: + version: ${{ env.PROTOC_VERSION }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Download protoc-gen-grpc-kotlin + run: | + V="${{ env.PROTOC_GEN_GRPC_KOTLIN_VERSION }}" + URL="https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-kotlin/${V}/protoc-gen-grpc-kotlin-${V}-jdk8.sh" + curl -fsSL "$URL" -o /tmp/protoc-gen-grpc-kotlin + chmod +x /tmp/protoc-gen-grpc-kotlin + - name: Generate + run: | + mkdir -p out-kotlin + protoc -I walletrpc \ + --kotlin_out=out-kotlin \ + --grpc-kotlin_out=out-kotlin \ + --plugin=protoc-gen-grpc-kotlin=/tmp/protoc-gen-grpc-kotlin \ + walletrpc/service.proto walletrpc/compact_formats.proto + - name: Verify output + run: | + KT_COUNT=$(find out-kotlin -name '*.kt' | wc -l | tr -d ' ') + if [ "$KT_COUNT" -eq 0 ]; then + echo "No Kotlin files generated" + ls -R out-kotlin || true + exit 1 + fi + echo "Generated $KT_COUNT Kotlin file(s)." + + codegen-swift: + name: codegen (Swift) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: arduino/setup-protoc@v3 + with: + version: ${{ env.PROTOC_VERSION }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install Mint + run: brew install mint + - name: Cache Mint packages + uses: actions/cache@v4 + with: + path: ~/.mint + key: mint-${{ runner.os }}-swift-protobuf-${{ env.SWIFT_PROTOBUF_VERSION }}-grpc-swift-${{ env.GRPC_SWIFT_VERSION }} + - name: Install Swift protoc plugins + run: | + mint install apple/swift-protobuf@${{ env.SWIFT_PROTOBUF_VERSION }} protoc-gen-swift + mint install grpc/grpc-swift@${{ env.GRPC_SWIFT_VERSION }} protoc-gen-grpc-swift + echo "$HOME/.mint/bin" >> "$GITHUB_PATH" + - name: Generate + run: | + mkdir -p out-swift + protoc -I walletrpc \ + --swift_out=out-swift \ + --grpc-swift_out=Client=true,Server=true:out-swift \ + walletrpc/service.proto walletrpc/compact_formats.proto + - name: Verify output + run: | + test -s out-swift/service.pb.swift + test -s out-swift/service.grpc.swift + test -s out-swift/compact_formats.pb.swift diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..518c170 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,14 @@ +version: v2 +modules: + - path: walletrpc +lint: + use: + - BASIC + except: + # walletrpc/ does not match the proto package path cash/z/wallet/sdk/rpc/. + # Renaming the directory would break downstream consumers that vendor this + # repo via `git subtree --prefix=lightwallet-protocol/`. + - PACKAGE_DIRECTORY_MATCH +breaking: + use: + - WIRE_JSON From baae8e3df1bb0d7f7f6b999c3546f1412b7dd1f4 Mon Sep 17 00:00:00 2001 From: Pacu Date: Tue, 28 Apr 2026 10:04:17 -0300 Subject: [PATCH 2/3] Fix proto-validation CI: lint exceptions, plugin URL, toolchain bumps - buf: except FIELD_LOWER_SNAKE_CASE and ENUM_VALUE_UPPER_SNAKE_CASE so the existing camelCase fields and lowercase enum values pass lint without churn (renaming would be JSON-name-breaking). - Python: drop `cache: pip`. setup-python requires a requirements.txt or pyproject.toml to hash; we have neither, and one dep installs in ~2s anyway. - Kotlin: Maven Central only ships the plugin as a JAR (no .sh wrapper exists, contrary to the original assumption); download the JAR and wrap it in a `java -jar` shim. Bump to 1.4.3 (latest in the 1.4.x series). - Rust: bump RUST_TOOLCHAIN to 1.85.0. tonic-build 0.12.3 transitively pulls indexmap 2.14, which requires the edition2024 Cargo feature stabilized in Rust 1.85. Refs #27. --- .github/workflows/proto-validation.yml | 14 +++++++++----- buf.yaml | 5 +++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/proto-validation.yml b/.github/workflows/proto-validation.yml index 500ea4a..1061cd2 100644 --- a/.github/workflows/proto-validation.yml +++ b/.github/workflows/proto-validation.yml @@ -22,12 +22,12 @@ env: GO_VERSION: '1.23.4' PROTOC_GEN_GO_VERSION: v1.36.4 PROTOC_GEN_GO_GRPC_VERSION: v1.5.1 - RUST_TOOLCHAIN: '1.83.0' + RUST_TOOLCHAIN: '1.85.0' TONIC_BUILD_VERSION: '0.12.3' PYTHON_VERSION: '3.12' GRPCIO_TOOLS_VERSION: '1.68.1' JAVA_VERSION: '21' - PROTOC_GEN_GRPC_KOTLIN_VERSION: '1.4.1' + PROTOC_GEN_GRPC_KOTLIN_VERSION: '1.4.3' SWIFT_PROTOBUF_VERSION: '1.28.2' GRPC_SWIFT_VERSION: '1.24.1' @@ -148,7 +148,6 @@ jobs: - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - cache: pip - name: Install grpcio-tools run: pip install "grpcio-tools==${{ env.GRPCIO_TOOLS_VERSION }}" - name: Generate @@ -183,8 +182,13 @@ jobs: - name: Download protoc-gen-grpc-kotlin run: | V="${{ env.PROTOC_GEN_GRPC_KOTLIN_VERSION }}" - URL="https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-kotlin/${V}/protoc-gen-grpc-kotlin-${V}-jdk8.sh" - curl -fsSL "$URL" -o /tmp/protoc-gen-grpc-kotlin + URL="https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-kotlin/${V}/protoc-gen-grpc-kotlin-${V}-jdk8.jar" + curl -fsSL "$URL" -o /tmp/protoc-gen-grpc-kotlin.jar + # Maven only ships the JAR; protoc needs an executable, so wrap it. + cat > /tmp/protoc-gen-grpc-kotlin <<'EOF' + #!/usr/bin/env bash + exec java -jar /tmp/protoc-gen-grpc-kotlin.jar "$@" + EOF chmod +x /tmp/protoc-gen-grpc-kotlin - name: Generate run: | diff --git a/buf.yaml b/buf.yaml index 518c170..fbee849 100644 --- a/buf.yaml +++ b/buf.yaml @@ -9,6 +9,11 @@ lint: # Renaming the directory would break downstream consumers that vendor this # repo via `git subtree --prefix=lightwallet-protocol/`. - PACKAGE_DIRECTORY_MATCH + # Existing field and enum names use camelCase / lowercase. Renaming them + # would be JSON-name-breaking for downstream consumers; grandfather the + # current names rather than churn the wire contract. + - FIELD_LOWER_SNAKE_CASE + - ENUM_VALUE_UPPER_SNAKE_CASE breaking: use: - WIRE_JSON From 73a8ce899619ca81e9d58fa833bf30529077ddc8 Mon Sep 17 00:00:00 2001 From: Pacu Date: Tue, 28 Apr 2026 10:06:02 -0300 Subject: [PATCH 3/3] buf breaking: pin baseline subdir to walletrpc Tags older than this PR (e.g. v0.4.1) have no buf.yaml at the repo root, so buf cannot infer that walletrpc/ is the module path. That caused the breaking-change check to fail with "import \"compact_formats.proto\": file does not exist". Setting `subdir=walletrpc` on the --against input makes buf treat walletrpc/ as the module root in the baseline, which is what the in-PR buf.yaml already does for the current tree. Refs #27. --- .github/workflows/proto-validation.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/proto-validation.yml b/.github/workflows/proto-validation.yml index 1061cd2..d241f69 100644 --- a/.github/workflows/proto-validation.yml +++ b/.github/workflows/proto-validation.yml @@ -58,7 +58,10 @@ jobs: echo "tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" - name: Breaking-change check vs latest release tag if: steps.baseline.outputs.tag != '' - run: buf breaking --against ".git#tag=${{ steps.baseline.outputs.tag }}" + # subdir=walletrpc treats walletrpc/ as the module root in the baseline, + # which is necessary for tags older than the introduction of buf.yaml + # (it lets buf resolve `import "compact_formats.proto"` correctly). + run: buf breaking --against ".git#tag=${{ steps.baseline.outputs.tag }},subdir=walletrpc" codegen-go: name: codegen (Go)