Distribute storage rewards #27
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: Distribute storage rewards | |
| # --------------------------------------------------------------------------- | |
| # Hourly cron + manual dispatch. Triggers ONLY on schedule and workflow_dispatch | |
| # — never on PR events. Secrets are exposed only to runs targeting `main`. | |
| # --------------------------------------------------------------------------- | |
| # | |
| # STATE LIVES ON A SEPARATE BRANCH (`state-data`). | |
| # | |
| # Why: `main` is protected with strict rules (require PR, CodeQL, signed | |
| # commits). The bot can't satisfy any of those — so we write state to an | |
| # UNPROTECTED branch instead. Two checkouts: | |
| # • src-repo/ → main branch (source code, read-only here) | |
| # • state-branch/ → state-data branch (state JSON, bot reads + writes) | |
| # | |
| # This keeps `main` maximally strict for humans while letting the cron operate | |
| # unattended on the state branch. See docs/OPERATIONS.md "State branch setup". | |
| # --------------------------------------------------------------------------- | |
| on: | |
| schedule: | |
| - cron: "0 * * * *" | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: distribute | |
| cancel-in-progress: false | |
| jobs: | |
| tick: | |
| runs-on: ubuntu-latest | |
| environment: production | |
| permissions: | |
| contents: write # for pushing to state-data branch | |
| issues: write # for the failure-issue creation step | |
| timeout-minutes: 20 | |
| steps: | |
| # 1. Source code (read-only from this branch's perspective) | |
| - name: Checkout source (main) | |
| # actions/checkout v6.0.2 — Node 24 runtime | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| ref: main | |
| path: src-repo | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| # 2. State branch — fetch separately so we can commit + push to it | |
| # independently of main. | |
| - name: Checkout state-data branch | |
| id: state-checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| ref: state-data | |
| path: state-branch | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| continue-on-error: true | |
| # 3. If state-data branch doesn't exist yet, fail loudly with a hint. | |
| # The branch must be created once during setup (see OPERATIONS.md). | |
| - name: Verify state-data branch exists | |
| if: steps.state-checkout.outcome != 'success' | |
| run: | | |
| echo "::error::state-data branch is missing. Create it once with the procedure in docs/OPERATIONS.md (search for 'State branch setup')." | |
| exit 1 | |
| - name: Setup Node | |
| # actions/setup-node v6.4.0 — Node 24 runtime | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| cache-dependency-path: src-repo/package-lock.json | |
| - name: Install | |
| working-directory: src-repo | |
| run: npm ci | |
| - name: Run tick | |
| working-directory: src-repo | |
| env: | |
| OPERATOR_PRIVATE_KEY: ${{ secrets.OPERATOR_PRIVATE_KEY }} | |
| BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} | |
| SKALE_RPC_URL: ${{ secrets.SKALE_RPC_URL }} | |
| ALLOWED_SUBMITTERS: ${{ vars.ALLOWED_SUBMITTERS }} | |
| DRY_RUN: "false" | |
| LOG_LEVEL: ${{ vars.LOG_LEVEL || 'info' }} | |
| # Point the tick command's STATE_DIR at the state-data checkout. | |
| # The command's loadState/saveState/loadInbox/clearInbox honor this. | |
| STATE_DIR: ${{ github.workspace }}/state-branch/state | |
| run: npm run tick | |
| - name: Commit state changes to state-data branch | |
| if: success() | |
| working-directory: state-branch | |
| run: | | |
| git config user.name "storage-reward-distributor[bot]" | |
| git config user.email "${{ github.repository_owner }}+bot@users.noreply.github.com" | |
| git add state/ | |
| if git diff --cached --quiet; then | |
| echo "no state changes" | |
| else | |
| git commit -m "state: tick $(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]" | |
| for i in 1 2 3; do | |
| if git push origin state-data; then | |
| exit 0 | |
| fi | |
| echo "push failed (attempt $i); pulling and retrying" | |
| git pull --rebase --autostash origin state-data || true | |
| sleep 5 | |
| done | |
| echo "::error::Failed to push state changes after 3 attempts" | |
| exit 1 | |
| fi | |
| - name: Open failure issue | |
| if: failure() | |
| # actions/github-script v9.0.0 — Node 24 runtime | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const title = `Distributor tick failed at ${new Date().toISOString()}`; | |
| const body = [ | |
| `Workflow run: ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`, | |
| ``, | |
| `Investigate the log; if needed re-run the workflow manually via Actions -> Distribute storage rewards -> Run workflow.`, | |
| ].join('\n'); | |
| await github.rest.issues.create({ owner, repo, title, body, labels: ['distributor-failure'] }); |