Validate gentx PR #28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Validate gentx PR | |
# Updated to trigger validation for existing PRs | |
on: | |
pull_request: | |
types: [opened, synchronize] # only run on PR open and updates | |
paths: | |
- "mainnet/gentx/**.json" # run only when gentx files change | |
workflow_dispatch: # allow manual triggering | |
inputs: | |
pr_number: | |
description: 'PR number to validate (optional)' | |
required: false | |
type: string | |
jobs: | |
validate-gentx: | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
pull-requests: write | |
issues: write | |
steps: | |
- name: Checkout repo | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 # we need full history for diffs | |
- name: Checkout PR if specified | |
if: github.event.inputs.pr_number | |
run: | | |
gh pr checkout ${{ github.event.inputs.pr_number }} | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Check for gentx files | |
run: | | |
if [ ! -d "mainnet/gentx" ] || [ -z "$(ls -A mainnet/gentx/*.json 2>/dev/null)" ]; then | |
echo "No gentx files found in mainnet/gentx/ directory" | |
echo "This workflow validates gentx files in PRs that add files to mainnet/gentx/" | |
exit 0 | |
fi | |
echo "Found gentx files to validate:" | |
ls -la mainnet/gentx/*.json | |
- name: Validate PR file requirements | |
id: pr_validation | |
run: | | |
echo "Checking PR file requirements..." | |
# Get list of changed files in this PR | |
if [ "${{ github.event_name }}" = "pull_request" ]; then | |
# For automatic PR triggers | |
git diff --name-only origin/${{ github.base_ref }}...HEAD > changed_files.txt | |
elif [ -n "${{ github.event.inputs.pr_number }}" ]; then | |
# For manual triggers, get the PR's changed files | |
gh pr diff ${{ github.event.inputs.pr_number }} --name-only > changed_files.txt | |
else | |
echo "Unable to determine changed files" | |
exit 1 | |
fi | |
echo "Changed files in this PR:" | |
cat changed_files.txt | |
# Initialize validation status | |
validation_error="" | |
# Check if mainnet/genesis.json is modified | |
if grep -q "^mainnet/genesis.json$" changed_files.txt; then | |
validation_error="❌ **ERROR: PRs must not modify mainnet/genesis.json**\n\nThe genesis file should only be updated by maintainers after collecting all gentx files.\n\n**How the process works:**\n- Your genesis account will be automatically created from the public key in your gentx file\n- Maintainers will collect all gentx files and generate the final genesis.json\n- No manual genesis.json modifications are needed from contributors\n\n**Required changes:**\n- Remove mainnet/genesis.json from this PR\n- Only include your gentx file in mainnet/gentx/" | |
fi | |
# Count gentx files in the PR | |
gentx_count=$(grep -c "^mainnet/gentx/.*\.json$" changed_files.txt || echo "0") | |
if [ "$gentx_count" -eq 0 ] && [ -z "$validation_error" ]; then | |
validation_error="❌ **ERROR: No gentx files found in this PR**\n\nPRs should add exactly one gentx file to mainnet/gentx/\n\n**Required changes:**\n- Add your gentx file to mainnet/gentx/ directory\n- Ensure the file follows the naming convention: gentx-[validator-address].json" | |
elif [ "$gentx_count" -gt 1 ] && [ -z "$validation_error" ]; then | |
validation_error="❌ **ERROR: Multiple gentx files found in this PR ($gentx_count files)**\n\nPRs should add exactly one gentx file to mainnet/gentx/\n\n**Found gentx files:**\n$(grep "^mainnet/gentx/.*\.json$" changed_files.txt | sed 's/^/- /')\n\n**Required changes:**\n- Keep only one gentx file in this PR\n- Create separate PRs for additional validators" | |
fi | |
if [ -n "$validation_error" ]; then | |
echo "pr_validation_status=failed" >> $GITHUB_OUTPUT | |
echo -e "$validation_error" > pr_validation_error.txt | |
echo -e "$validation_error" | |
exit 1 | |
fi | |
echo "pr_validation_status=passed" >> $GITHUB_OUTPUT | |
echo "✅ PR validation passed: exactly 1 gentx file, no genesis.json modifications" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Comment on PR validation failure | |
if: failure() && steps.pr_validation.outputs.pr_validation_status == 'failed' | |
uses: actions/github-script@v7 | |
continue-on-error: true | |
with: | |
script: | | |
try { | |
const fs = require('fs'); | |
let commentBody = "## 🚫 PR Validation Failed\n\n"; | |
if (fs.existsSync('pr_validation_error.txt')) { | |
const errorContent = fs.readFileSync('pr_validation_error.txt', 'utf8'); | |
commentBody += errorContent; | |
} | |
commentBody += "\n\n---\n"; | |
commentBody += "**This PR is blocked from merging until these issues are resolved.**\n"; | |
commentBody += `_Validation failed at ${new Date().toISOString()}_`; | |
// Determine PR number | |
const prNumber = context.payload.pull_request?.number || '${{ github.event.inputs.pr_number }}'; | |
if (prNumber && prNumber !== 'undefined') { | |
await github.rest.issues.createComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: parseInt(prNumber), | |
body: commentBody | |
}); | |
console.log('Validation failure comment posted successfully'); | |
} | |
} catch (error) { | |
console.log('Unable to post validation failure comment (likely due to permissions):', error.message); | |
console.log('Validation failure details:'); | |
const fs = require('fs'); | |
if (fs.existsSync('pr_validation_error.txt')) { | |
console.log(fs.readFileSync('pr_validation_error.txt', 'utf8')); | |
} | |
} | |
- name: Install tooling (jq + lumerad) | |
run: | | |
sudo apt-get update -qq | |
sudo apt-get install -y jq curl | |
curl -L \ | |
https://github.com/LumeraProtocol/lumera/releases/download/v1.1.0/lumera_v1.1.0_linux_amd64.tar.gz \ | |
-o lumera_v1.1.0_linux_amd64.tar.gz | |
tar -xzf lumera_v1.1.0_linux_amd64.tar.gz | |
chmod +x lumerad | |
sudo mv lumerad /usr/local/bin/ | |
sudo mv libwasmvm.x86_64.so /usr/local/lib/ | |
sudo ldconfig | |
- name: Run CI validation script | |
id: validation | |
run: | | |
echo "Running gentx validation..." | |
# Inline validation script with correct genesis path | |
set -euo pipefail | |
# Chain-specific constants | |
CHAIN_ID="lumera-mainnet-1" | |
DENOM="ulume" | |
AMOUNT="1000000${DENOM}" | |
BINARY="lumerad" | |
HOME_DIR="$(pwd)/_ci_home" | |
GENESIS_TEMPLATE="mainnet/genesis.json" | |
echo "🔧 CI gentx validation started" | |
# 1. Fresh init | |
echo "• Initializing home directory at ${HOME_DIR}" | |
rm -rf "${HOME_DIR}" | |
"${BINARY}" init ci --chain-id "${CHAIN_ID}" --home "${HOME_DIR}" >/dev/null | |
# 2. Replace genesis with template committed in repo | |
if ! cp "${GENESIS_TEMPLATE}" "${HOME_DIR}/config/genesis.json" 2>/dev/null; then | |
echo "• ${GENESIS_TEMPLATE} not found, trying mainnet/base_genesis.json as fallback" | |
cp "mainnet/base_genesis.json" "${HOME_DIR}/config/genesis.json" | |
fi | |
mkdir -p "${HOME_DIR}/config/gentx" | |
# 3. Iterate over every gentx file in the repo | |
echo "• Processing gentx files in mainnet/gentx/" | |
for gentx in mainnet/gentx/*.json; do | |
echo "• processing ${gentx}" | |
# 3a. Extract signer pubkey (secp256k1, base64) | |
signer_b64=$(jq -r '.auth_info.signer_infos[0].public_key.key' "${gentx}") | |
if [[ -z "${signer_b64}" ]]; then | |
echo "❌ Could not extract signer pubkey from ${gentx}" | |
exit 1 | |
fi | |
echo " - signer pubkey: ${signer_b64} ✅" | |
# 3b. Convert pubkey → account address | |
pubkey="{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"${signer_b64}\"}" | |
hex_addr=$("${BINARY}" debug pubkey $pubkey | awk '/Address:/ {print $2}') | |
if [[ -z "${hex_addr}" ]]; then | |
echo "❌ Could not derive account address from pubkey in ${gentx}" | |
exit 1 | |
fi | |
echo " - hex address: ${hex_addr} ✅" | |
# 3c. Convert HEX → Bech32 account address | |
account_addr=$("${BINARY}" debug addr "${hex_addr}" | awk '/Bech32 Acc:/ {print $3}') | |
if [[ -z "${account_addr}" ]]; then | |
echo "❌ Could not derive Bech32 account address from pubkey in ${gentx}" | |
exit 1 | |
fi | |
echo " - Bech32 account address: ${account_addr} ✅" | |
# 3d. Add account to genesis (skip if already present) | |
if ! grep -q "${account_addr}" "${HOME_DIR}/config/genesis.json"; then | |
"${BINARY}" genesis add-genesis-account "${account_addr}" "${AMOUNT}" \ | |
--home "${HOME_DIR}" >/dev/null | |
fi | |
# 3e. Copy gentx into config/gentx | |
cp "${gentx}" "${HOME_DIR}/config/gentx/" | |
echo " - ${gentx} copied to ${HOME_DIR}/config/gentx/ ✅" | |
done | |
# 4. Collect & validate | |
echo "• Collecting gentx files" | |
"${BINARY}" genesis collect-gentxs --home "${HOME_DIR}" >/dev/null | |
echo " - gentx files collected ✅" | |
echo "• Validating genesis file" | |
"${BINARY}" genesis validate-genesis --home "${HOME_DIR}" | |
echo " - genesis file valid ✅" | |
echo "✅✅✅ All gentx files valid ✨" | |
# If we get here, validation succeeded | |
echo "validation_status=success" >> $GITHUB_OUTPUT | |
echo "Validation completed successfully!" | |
- name: Handle validation failure | |
if: failure() && steps.validation.outcome == 'failure' | |
run: | | |
echo "validation_status=failure" >> $GITHUB_OUTPUT | |
echo "Validation failed!" | |
- name: Prepare comment body | |
if: always() | |
run: | | |
# Prepare comment body | |
echo "## 🔍 Gentx Validation Results" > comment_body.txt | |
echo "" >> comment_body.txt | |
if [ "${{ steps.validation.outcome }}" = "success" ]; then | |
echo "✅ **Status: PASSED**" >> comment_body.txt | |
echo "" >> comment_body.txt | |
echo "All gentx files have been successfully validated! 🎉" >> comment_body.txt | |
else | |
echo "❌ **Status: FAILED**" >> comment_body.txt | |
echo "" >> comment_body.txt | |
echo "### Validation Output:" >> comment_body.txt | |
echo '```' >> comment_body.txt | |
echo "Validation failed during execution" >> comment_body.txt | |
echo '```' >> comment_body.txt | |
fi | |
echo "" >> comment_body.txt | |
echo "_Validation run at $(date)_" >> comment_body.txt | |
- name: Comment on PR (manual trigger) | |
if: github.event.inputs.pr_number | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const fs = require('fs'); | |
const commentBody = fs.readFileSync('comment_body.txt', 'utf8'); | |
await github.rest.issues.createComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: ${{ github.event.inputs.pr_number }}, | |
body: commentBody | |
}); | |
- name: Comment on PR (automatic trigger) | |
if: github.event_name == 'pull_request' | |
uses: actions/github-script@v7 | |
continue-on-error: true | |
with: | |
script: | | |
try { | |
const fs = require('fs'); | |
const commentBody = fs.readFileSync('comment_body.txt', 'utf8'); | |
await github.rest.issues.createComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: context.issue.number, | |
body: commentBody | |
}); | |
console.log('Comment posted successfully'); | |
} catch (error) { | |
console.log('Unable to post comment (likely due to permissions):', error.message); | |
console.log('Validation results:'); | |
const fs = require('fs'); | |
if (fs.existsSync('comment_body.txt')) { | |
console.log(fs.readFileSync('comment_body.txt', 'utf8')); | |
} | |
} | |
- name: Set job status based on validation | |
run: | | |
if [ "${{ steps.validation.outputs.validation_status }}" = "failure" ]; then | |
exit 1 | |
fi |