diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2fc16f3155b..9c3af93f787 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -6,6 +6,9 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [ 'develop' ] + paths-ignore: [ '**/*.md', '.gitignore', '**/.gitignore', '.editorconfig', + '.gitattributes', 'docs/**', 'CHANGELOG', '.github/ISSUE_TEMPLATE/**', + '.github/PULL_REQUEST_TEMPLATE/**', '.github/CODEOWNERS' ] schedule: - cron: '6 10 * * 0' @@ -29,16 +32,23 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} + build-mode: manual - - name: Autobuild - uses: github/codeql-action/autobuild@v4 + - name: Set up JDK 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'temurin' + + - name: Build + run: ./gradlew build -x test --no-daemon - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/coverage-build.yml b/.github/workflows/coverage-build.yml new file mode 100644 index 00000000000..cc0350d4927 --- /dev/null +++ b/.github/workflows/coverage-build.yml @@ -0,0 +1,58 @@ +name: Coverage Build + +on: + pull_request: + branches: [ 'develop', 'release_**' ] + types: [ opened, synchronize, reopened ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build-coverage: + name: Build ubuntu24 (JDK 8 / x86_64) + runs-on: ubuntu-24.04 + timeout-minutes: 60 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up JDK 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'temurin' + cache: 'gradle' + + - name: Check Java version + run: java -version + + - name: Stop Gradle daemon + run: ./gradlew --stop || true + + - name: Build + run: ./gradlew clean build --no-daemon --no-build-cache --parallel + + - name: Generate JaCoCo report + run: ./gradlew jacocoTestReport --no-daemon --no-build-cache + + - name: Upload JaCoCo artifact + uses: actions/upload-artifact@v6 + with: + name: jacoco-coverage + path: | + actuator/build/reports/jacoco/test/jacocoTestReport.xml + chainbase/build/reports/jacoco/test/jacocoTestReport.xml + common/build/reports/jacoco/test/jacocoTestReport.xml + consensus/build/reports/jacoco/test/jacocoTestReport.xml + crypto/build/reports/jacoco/test/jacocoTestReport.xml + framework/build/reports/jacoco/test/jacocoTestReport.xml + plugins/build/reports/jacoco/test/jacocoTestReport.xml + if-no-files-found: error diff --git a/.github/workflows/coverage-update-baseline.yml b/.github/workflows/coverage-update-baseline.yml new file mode 100644 index 00000000000..34a13aa3d91 --- /dev/null +++ b/.github/workflows/coverage-update-baseline.yml @@ -0,0 +1,92 @@ +name: Base Coverage Upload + +on: + push: + branches: [ 'develop', 'release_**' ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-base-coverage: + name: Build Base Coverage + runs-on: ubuntu-24.04 + timeout-minutes: 60 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up JDK 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'temurin' + cache: 'gradle' + + - name: Check Java version + run: java -version + + - name: Grant execute permission + run: chmod +x gradlew + + - name: Stop Gradle daemon + run: ./gradlew --stop || true + + - name: Build + run: ./gradlew clean build --no-daemon --no-build-cache --parallel + + - name: Generate JaCoCo report + run: ./gradlew jacocoTestReport --no-daemon --no-build-cache + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v6 + with: + name: base-jacoco-xml + path: | + actuator/build/reports/jacoco/test/jacocoTestReport.xml + chainbase/build/reports/jacoco/test/jacocoTestReport.xml + common/build/reports/jacoco/test/jacocoTestReport.xml + consensus/build/reports/jacoco/test/jacocoTestReport.xml + crypto/build/reports/jacoco/test/jacocoTestReport.xml + framework/build/reports/jacoco/test/jacocoTestReport.xml + plugins/build/reports/jacoco/test/jacocoTestReport.xml + if-no-files-found: error + + upload-base-coverage: + name: Upload Base Coverage to Codecov + needs: build-base-coverage + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout repo + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Download coverage artifacts + uses: actions/download-artifact@v7 + with: + name: base-jacoco-xml + path: coverage-artifacts + + - name: Show coverage files + run: find coverage-artifacts -type f | sort + + - name: Upload base coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: ./coverage-artifacts + root_dir: ./ + gcov_executable: '' + override_branch: ${{ github.ref_name }} + fail_ci_if_error: true + verbose: true diff --git a/.github/workflows/coverage-upload.yml b/.github/workflows/coverage-upload.yml new file mode 100644 index 00000000000..e97eaa63de9 --- /dev/null +++ b/.github/workflows/coverage-upload.yml @@ -0,0 +1,73 @@ +name: Codecov Upload & Compare + +on: + workflow_run: + workflows: + - Coverage Build + types: + - completed + +permissions: + contents: read + actions: read + pull-requests: read + +jobs: + upload-coverage: + name: Upload Coverage + if: > + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.pr.outputs.pr_number }} + + steps: + - name: Checkout repo + uses: actions/checkout@v5 # must download source code + with: + fetch-depth: 0 + persist-credentials: false + + - name: Download coverage artifact + uses: actions/download-artifact@v7 + with: + name: jacoco-coverage + path: coverage + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get PR details + id: pr + uses: actions/github-script@v8 + with: + script: | + const headSha = context.payload.workflow_run.head_sha; + const headOwner = context.payload.workflow_run.head_repository.owner.login; + const headBranch = context.payload.workflow_run.head_branch; + const { data: pulls } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'all', + head: `${headOwner}:${headBranch}`, + }); + const pr = pulls.find((p) => p.head.sha === headSha); + if (pr) { + core.setOutput('pr_number', pr.number); + core.setOutput('pr_sha', headSha); + core.setOutput('pr_branch', headBranch); + core.setOutput('base_sha', pr.base.sha); + } else { + core.setFailed(`No pull request found for commit ${headSha}`); + } + + - name: Upload to Codecov + if: ${{ steps.pr.outputs.pr_number != '' }} + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + override_commit: ${{ steps.pr.outputs.pr_sha }} + override_branch: ${{ steps.pr.outputs.pr_branch }} + override_pr: ${{ steps.pr.outputs.pr_number }} + fail_ci_if_error: true + verbose: true diff --git a/.github/workflows/coverage-waiting.yml b/.github/workflows/coverage-waiting.yml new file mode 100644 index 00000000000..465ae54c702 --- /dev/null +++ b/.github/workflows/coverage-waiting.yml @@ -0,0 +1,119 @@ +name: Waiting Coverage project + +on: + pull_request: + branches: [ 'develop', 'release_**' ] + types: [ opened, synchronize, reopened ] + +permissions: + contents: read + checks: read + statuses: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + waiting-coverage-project: + name: waiting-coverage-report + runs-on: ubuntu-latest + timeout-minutes: 70 + + steps: + - name: Wait for codecov/project status + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const ref = context.payload.pull_request.head.sha; + + const targetContext = 'codecov/project'; + const maxAttempts = 120; // 120 * 30s = 60 minutes + const intervalMs = 30 * 1000; + + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + core.info(`Polling attempt ${attempt}/${maxAttempts} for ${targetContext} on ${ref}`); + + try { + // Check legacy commit statuses + const combined = await github.rest.repos.getCombinedStatusForRef({ + owner, + repo, + ref, + per_page: 100 + }); + + const statuses = combined.data.statuses || []; + const matchedStatus = statuses.find(s => s.context === targetContext); + + if (matchedStatus) { + core.info(`Found commit status: ${matchedStatus.context} = ${matchedStatus.state}`); + + if (matchedStatus.state === 'success') { + core.info(`${targetContext} succeeded.`); + return; + } + + if (matchedStatus.state === 'failure' || matchedStatus.state === 'error') { + core.setFailed(`${targetContext} is ${matchedStatus.state}.`); + return; + } + + // pending + await sleep(intervalMs); + continue; + } + + // Check check-runs as a fallback + const checks = await github.rest.checks.listForRef({ + owner, + repo, + ref, + per_page: 100 + }); + + const checkRuns = checks.data.check_runs || []; + const matchedCheck = checkRuns.find(c => c.name === targetContext); + + if (matchedCheck) { + core.info( + `Found check run: ${matchedCheck.name}, status=${matchedCheck.status}, conclusion=${matchedCheck.conclusion}` + ); + + if (matchedCheck.status === 'completed') { + if (matchedCheck.conclusion === 'success') { + core.info(`${targetContext} succeeded.`); + return; + } + + core.setFailed( + `${targetContext} completed with conclusion=${matchedCheck.conclusion}.` + ); + return; + } + + // queued / in_progress + await sleep(intervalMs); + continue; + } + + core.info(`${targetContext} not reported yet. Waiting...`); + } catch (error) { + core.warning( + `Attempt ${attempt}/${maxAttempts} failed with transient error: ${error.message}. Retrying in ${intervalMs / 1000}s...` + ); + } + + await sleep(intervalMs); + } + + core.setFailed( + `Timed out waiting for ${targetContext} to report success on commit ${ref}.` + ); diff --git a/.github/workflows/math-check.yml b/.github/workflows/math-check.yml index 0f0255815d5..a5db3351a94 100644 --- a/.github/workflows/math-check.yml +++ b/.github/workflows/math-check.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Check for java.lang.Math usage id: check-math @@ -55,14 +55,14 @@ jobs: - name: Upload findings if: steps.check-math.outputs.math_found == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: math-usage-report path: math_usage.txt - name: Create comment if: github.event_name == 'pull_request' && steps.check-math.outputs.math_found == 'true' - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: | const fs = require('fs'); diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml new file mode 100644 index 00000000000..cd76487fefe --- /dev/null +++ b/.github/workflows/pr-build.yml @@ -0,0 +1,183 @@ +name: PR Build + +on: + pull_request: + branches: [ 'master','develop', 'release_**' ] + types: [ opened, synchronize, reopened ] + paths-ignore: [ '**/*.md', '.gitignore', '**/.gitignore', '.editorconfig', + '.gitattributes', 'docs/**', 'CHANGELOG', '.github/ISSUE_TEMPLATE/**', + '.github/PULL_REQUEST_TEMPLATE/**', '.github/CODEOWNERS' ] + workflow_dispatch: + inputs: + job: + description: 'Job to run: all / macos / ubuntu / rockylinux / debian11' + required: false + default: 'all' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + + build-macos: + name: Build macos26 (JDK ${{ matrix.java }} / ${{ matrix.arch }}) + if: ${{ github.event_name == 'pull_request' || inputs.job == 'all' || inputs.job == 'macos' }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - java: '8' + runner: macos-26-intel + arch: x86_64 + - java: '17' + runner: macos-26 + arch: aarch64 + + steps: + - uses: actions/checkout@v5 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v5 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: macos26-${{ matrix.arch }}-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} + restore-keys: macos26-${{ matrix.arch }}-gradle- + + - name: Build + run: ./gradlew clean build --no-daemon + + - name: Test with RocksDB engine + if: matrix.arch == 'x86_64' + run: ./gradlew :framework:testWithRocksDb --no-daemon + + build-ubuntu: + name: Build ubuntu24 (JDK 17 / aarch64) + if: ${{ github.event_name == 'pull_request' || inputs.job == 'all' || inputs.job == 'ubuntu' }} + runs-on: ubuntu-24.04-arm + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + + - name: Check Java version + run: java -version + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ubuntu24-aarch64-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} + restore-keys: ubuntu24-aarch64-gradle- + + - name: Build + run: ./gradlew clean build --no-daemon + + docker-build-rockylinux: + name: Build rockylinux (JDK 8 / x86_64) + if: ${{ github.event_name == 'pull_request' || inputs.job == 'all' || inputs.job == 'rockylinux' }} + runs-on: ubuntu-latest + timeout-minutes: 60 + + container: + image: rockylinux:8 + + env: + GRADLE_USER_HOME: /github/home/.gradle + LANG: en_US.UTF-8 + LC_ALL: en_US.UTF-8 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install dependencies (Rocky 8 + JDK8) + run: | + set -euxo pipefail + dnf -y install java-1.8.0-openjdk-devel git wget unzip which jq bc curl glibc-langpack-en + dnf -y groupinstall "Development Tools" + + - name: Check Java version + run: java -version + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + /github/home/.gradle/caches + /github/home/.gradle/wrapper + key: rockylinux-x86_64-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} + restore-keys: | + rockylinux-x86_64-gradle- + + - name: Stop Gradle daemon + run: ./gradlew --stop || true + + - name: Build + run: ./gradlew clean build --no-daemon + + - name: Test with RocksDB engine + run: ./gradlew :framework:testWithRocksDb --no-daemon + + docker-build-debian11: + name: Build debian11 (JDK 8 / x86_64) + if: ${{ github.event_name == 'pull_request' || inputs.job == 'all' || inputs.job == 'debian11' }} + runs-on: ubuntu-latest + timeout-minutes: 60 + + container: + image: eclipse-temurin:8-jdk # base image is Debian 11 (Bullseye) + + defaults: + run: + shell: bash + + env: + GRADLE_USER_HOME: /github/home/.gradle + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install dependencies (Debian + build tools) + run: | + set -euxo pipefail + apt-get update + apt-get install -y git wget unzip build-essential curl jq + + - name: Check Java version + run: java -version + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + /github/home/.gradle/caches + /github/home/.gradle/wrapper + key: debian11-x86_64-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} + restore-keys: | + debian11-x86_64-gradle- + + - name: Build + run: ./gradlew clean build --no-daemon + + - name: Test with RocksDB engine + run: ./gradlew :framework:testWithRocksDb --no-daemon diff --git a/.github/workflows/pr-cancel.yml b/.github/workflows/pr-cancel.yml new file mode 100644 index 00000000000..9dc0dee4a4c --- /dev/null +++ b/.github/workflows/pr-cancel.yml @@ -0,0 +1,51 @@ +name: Cancel PR Workflows on Close + +on: + pull_request: + types: [ closed ] + +permissions: + actions: write + +jobs: + cancel: + name: Cancel In-Progress Workflows + if: github.event.pull_request.merged == false + runs-on: ubuntu-latest + steps: + - name: Cancel PR Build and System Test + uses: actions/github-script@v8 + with: + script: | + const workflows = ['pr-build.yml', 'system-test.yml', 'codeql.yml', 'coverage-waiting.yml']; + const headSha = context.payload.pull_request.head.sha; + const prNumber = context.payload.pull_request.number; + + for (const workflowId of workflows) { + for (const status of ['in_progress', 'queued']) { + const runs = await github.paginate( + github.rest.actions.listWorkflowRuns, + { + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflowId, + status, + event: 'pull_request', + per_page: 100, + }, + (response) => response.data.workflow_runs + ); + + for (const run of runs) { + const isTargetPr = !run.pull_requests?.length || run.pull_requests.some((pr) => pr.number === prNumber); + if (run.head_sha === headSha && isTargetPr) { + await github.rest.actions.cancelWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: run.id, + }); + console.log(`Cancelled ${workflowId} run #${run.id} (${status})`); + } + } + } + } diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 264794f8757..19425209bbc 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Validate PR title and description - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const title = context.payload.pull_request.title; @@ -101,10 +101,10 @@ jobs: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' @@ -123,167 +123,9 @@ jobs: - name: Upload Checkstyle reports if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: checkstyle-reports path: | framework/build/reports/checkstyle/ plugins/build/reports/checkstyle/ - - build: - name: Build ${{ matrix.os-name }}(JDK ${{ matrix.java }} / ${{ matrix.arch }}) - if: github.event.action != 'edited' && !failure() - needs: [pr-lint, checkstyle] - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - include: - - java: '8' - runner: ubuntu-latest - os-name: ubuntu - arch: x86_64 - - java: '17' - runner: ubuntu-24.04-arm - os-name: ubuntu - arch: aarch64 - - java: '8' - runner: macos-26-intel - os-name: macos - arch: x86_64 - - java: '17' - runner: macos-26 - os-name: macos - arch: aarch64 - - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v4 - with: - java-version: ${{ matrix.java }} - distribution: 'temurin' - - - name: Cache Gradle packages - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ matrix.arch }}-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} - restore-keys: ${{ runner.os }}-${{ matrix.arch }}-gradle- - - - name: Build - run: ./gradlew clean build --no-daemon - - - name: Test with RocksDB engine - if: matrix.arch == 'x86_64' - run: ./gradlew :framework:testWithRocksDb --no-daemon - - docker-build-rockylinux: - name: Build rockylinux (JDK 8 / x86_64) - if: github.event.action != 'edited' && !failure() - needs: [pr-lint, checkstyle] - runs-on: ubuntu-latest - timeout-minutes: 60 - - container: - image: rockylinux:8 - - env: - GRADLE_USER_HOME: /github/home/.gradle - LANG: en_US.UTF-8 - LC_ALL: en_US.UTF-8 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install dependencies (Rocky 8 + JDK8) - run: | - set -euxo pipefail - dnf -y install java-1.8.0-openjdk-devel git wget unzip which jq bc curl glibc-langpack-en - dnf -y groupinstall "Development Tools" - - - name: Check Java version - run: java -version - - - name: Cache Gradle - uses: actions/cache@v4 - with: - path: | - /github/home/.gradle/caches - /github/home/.gradle/wrapper - key: ${{ runner.os }}-rockylinux-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-rockylinux-gradle- - - - name: Grant execute permission - run: chmod +x gradlew - - - name: Stop Gradle daemon - run: ./gradlew --stop || true - - - name: Build - run: ./gradlew clean build --no-daemon --no-build-cache - - - name: Generate JaCoCo report - run: ./gradlew jacocoTestReport --no-daemon --no-build-cache - - - name: Upload JaCoCo artifacts - uses: actions/upload-artifact@v4 - with: - name: jacoco-rockylinux - path: | - **/build/reports/jacoco/test/jacocoTestReport.xml - **/build/reports/** - **/build/test-results/** - if-no-files-found: error - - docker-build-debian11: - name: Build debian11 (JDK 8 / x86_64) - if: github.event.action != 'edited' && !failure() - needs: [pr-lint, checkstyle] - runs-on: ubuntu-latest - timeout-minutes: 60 - - container: - image: eclipse-temurin:8-jdk # base image is Debian 11 (Bullseye) - - defaults: - run: - shell: bash - - env: - GRADLE_USER_HOME: /github/home/.gradle - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install dependencies (Debian + build tools) - run: | - set -euxo pipefail - apt-get update - apt-get install -y git wget unzip build-essential curl jq - - - name: Check Java version - run: java -version - - - name: Cache Gradle - uses: actions/cache@v4 - with: - path: | - /github/home/.gradle/caches - /github/home/.gradle/wrapper - key: ${{ runner.os }}-debian11-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-debian11-gradle- - - - name: Grant execute permission - run: chmod +x gradlew - - - name: Build - run: ./gradlew clean build --no-daemon --no-build-cache diff --git a/.github/workflows/system-test.yml b/.github/workflows/system-test.yml index 4c234d4bf33..f6184fb0efc 100644 --- a/.github/workflows/system-test.yml +++ b/.github/workflows/system-test.yml @@ -6,9 +6,12 @@ on: pull_request: branches: [ 'develop', 'release_**' ] types: [ opened, synchronize, reopened ] + paths-ignore: [ '**/*.md', '.gitignore', '**/.gitignore', '.editorconfig', + '.gitattributes', 'docs/**', 'CHANGELOG', '.github/ISSUE_TEMPLATE/**', + '.github/PULL_REQUEST_TEMPLATE/**', '.github/CODEOWNERS' ] concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: @@ -19,20 +22,20 @@ jobs: steps: - name: Set up JDK 8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '8' distribution: 'temurin' - name: Clone system-test - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: tronprotocol/system-test ref: release_workflow path: system-test - name: Checkout java-tron - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: java-tron @@ -85,7 +88,7 @@ jobs: - name: Upload FullNode log if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: fullnode-log path: java-tron/fullnode.log diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000000..fd5929fb024 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,34 @@ +# Post a Codecov comment on pull requests. If don't need comment, use comment: false, else use following +comment: false +#comment: +# # Show coverage diff, flags table, and changed files in the PR comment +# layout: "diff, flags, files" +# # Update existing comment if present, otherwise create a new one +# behavior: default +# # Post a comment even when coverage numbers do not change +# require_changes: false +# # Do not require a base report before posting the comment +# require_base: false +# # Require the PR head commit to have a coverage report +# require_head: true +# # Show both project coverage and patch coverage in the PR comment +# hide_project_coverage: false + +codecov: + # Do not wait for all CI checks to pass before sending notifications + require_ci_to_pass: false + notify: + wait_for_ci: false + +coverage: + status: + project: # PR coverage/project UI + default: + # Compare against the base branch automatically + target: auto + # Allow a small coverage drop tolerance + threshold: 0.02% +# patch: off + patch: # PR coverage/patch UI + default: + target: 60% diff --git a/framework/build.gradle b/framework/build.gradle index a32357253db..9b287fdeb27 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -155,6 +155,14 @@ task testWithRocksDb(type: Test) { group = 'verification' configureTestTask(it) systemProperty 'storage.db.engine', 'ROCKSDB' + // Keep x86 RocksDB coverage focused on engine-sensitive tests instead of + // rerunning the entire framework suite with a different storage backend. + include 'org/tron/common/storage/**' + include 'org/tron/core/config/args/ArgsTest.class' + include 'org/tron/core/db/DBIteratorTest.class' + include 'org/tron/core/db/TronDatabaseTest.class' + include 'org/tron/core/db/backup/BackupDbUtilTest.class' + include 'org/tron/core/db2/ChainbaseTest.class' exclude '**/LevelDbDataSourceImplTest.class' } diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index 7eec4f10830..dd8fd3422de 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -7,6 +7,7 @@ import static org.tron.core.Constant.DEFAULT_PROPOSAL_EXPIRE_TIME; import static org.tron.core.Constant.DYNAMIC_ENERGY_INCREASE_FACTOR_RANGE; import static org.tron.core.Constant.DYNAMIC_ENERGY_MAX_FACTOR_RANGE; +import static org.tron.core.Constant.ENERGY_LIMIT_IN_CONSTANT_TX; import static org.tron.core.Constant.MAX_PROPOSAL_EXPIRE_TIME; import static org.tron.core.Constant.MIN_PROPOSAL_EXPIRE_TIME; import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCE_TIMEOUT_PERCENT; @@ -83,6 +84,40 @@ @Component public class Args extends CommonParameter { + /** + * Maps deprecated CLI option names to their config-file equivalents. + * Options not in this map have no config equivalent and are being removed entirely. + */ + private static final Map DEPRECATED_CLI_TO_CONFIG; + + static { + Map m = new HashMap<>(); + m.put("--storage-db-directory", "storage.db.directory"); + m.put("--storage-db-engine", "storage.db.engine"); + m.put("--storage-db-synchronous", "storage.db.sync"); + m.put("--storage-index-directory", "storage.index.directory"); + m.put("--storage-index-switch", "storage.index.switch"); + m.put("--storage-transactionHistory-switch", "storage.transHistory.switch"); + m.put("--contract-parse-enable", "event.subscribe.contractParse"); + m.put("--support-constant", "vm.supportConstant"); + m.put("--max-energy-limit-for-constant", "vm.maxEnergyLimitForConstant"); + m.put("--lru-cache-size", "vm.lruCacheSize"); + m.put("--min-time-ratio", "vm.minTimeRatio"); + m.put("--max-time-ratio", "vm.maxTimeRatio"); + m.put("--save-internaltx", "vm.saveInternalTx"); + m.put("--save-featured-internaltx", "vm.saveFeaturedInternalTx"); + m.put("--save-cancel-all-unfreeze-v2-details", "vm.saveCancelAllUnfreezeV2Details"); + m.put("--long-running-time", "vm.longRunningTime"); + m.put("--max-connect-number", "node.maxHttpConnectNumber"); + m.put("--rpc-thread", "node.rpc.thread"); + m.put("--solidity-thread", "node.solidity.threads"); + m.put("--validate-sign-thread", "node.validateSignThreadNum"); + m.put("--trust-node", "node.trustNode"); + m.put("--history-balance-lookup", "storage.balance.history.lookup"); + m.put("--es", "event.subscribe.enable"); + DEPRECATED_CLI_TO_CONFIG = Collections.unmodifiableMap(m); + } + @Getter private static String configFilePath = ""; @@ -169,7 +204,7 @@ public static void applyConfigParams( if (config.hasPath(ConfigKey.VM_MAX_ENERGY_LIMIT_FOR_CONSTANT)) { long configLimit = config.getLong(ConfigKey.VM_MAX_ENERGY_LIMIT_FOR_CONSTANT); - PARAMETER.maxEnergyLimitForConstant = max(3_000_000L, configLimit, true); + PARAMETER.maxEnergyLimitForConstant = max(ENERGY_LIMIT_IN_CONSTANT_TX, configLimit, true); } if (config.hasPath(ConfigKey.VM_LRU_CACHE_SIZE)) { @@ -678,6 +713,9 @@ public static void applyConfigParams( PARAMETER.eventFilter = config.hasPath(ConfigKey.EVENT_SUBSCRIBE_FILTER) ? getEventFilter(config) : null; + PARAMETER.eventSubscribe = config.hasPath(ConfigKey.EVENT_SUBSCRIBE_ENABLE) + && config.getBoolean(ConfigKey.EVENT_SUBSCRIBE_ENABLE); + if (config.hasPath(ConfigKey.ALLOW_SHIELDED_TRANSACTION_API)) { PARAMETER.allowShieldedTransactionApi = config.getBoolean(ConfigKey.ALLOW_SHIELDED_TRANSACTION_API); @@ -1012,6 +1050,28 @@ private static void applyCLIParams(CLIParameter cmd, JCommander jc) { .map(ParameterDescription::getLongestName) .collect(Collectors.toSet()); + jc.getParameters().stream() + .filter(ParameterDescription::isAssigned) + .filter(pd -> { + try { + return CLIParameter.class.getDeclaredField(pd.getParameterized().getName()) + .isAnnotationPresent(Deprecated.class); + } catch (NoSuchFieldException e) { + return false; + } + }) + .forEach(pd -> { + String cliOption = pd.getLongestName(); + String configKey = DEPRECATED_CLI_TO_CONFIG.get(cliOption); + if (configKey != null) { + logger.warn("CLI option '{}' is deprecated and will be removed in a future release." + + " Please use config key '{}' instead.", cliOption, configKey); + } else { + logger.warn("CLI option '{}' is deprecated and will be removed in a future release.", + cliOption); + } + }); + if (assigned.contains("--output-directory")) { PARAMETER.outputDirectory = cmd.outputDirectory; } @@ -1022,7 +1082,8 @@ private static void applyCLIParams(CLIParameter cmd, JCommander jc) { PARAMETER.supportConstant = cmd.supportConstant; } if (assigned.contains("--max-energy-limit-for-constant")) { - PARAMETER.maxEnergyLimitForConstant = cmd.maxEnergyLimitForConstant; + PARAMETER.maxEnergyLimitForConstant = max(ENERGY_LIMIT_IN_CONSTANT_TX, + cmd.maxEnergyLimitForConstant, true); } if (assigned.contains("--lru-cache-size")) { PARAMETER.lruCacheSize = cmd.lruCacheSize; @@ -1105,7 +1166,12 @@ private static void applyCLIParams(CLIParameter cmd, JCommander jc) { if (assigned.contains("--log-config")) { PARAMETER.logbackPath = cmd.logbackPath; } + // seedNodes is a JCommander positional (main) parameter, which does not support + // isAssigned(). An empty-check is used instead — this is safe because the default + // is an empty list, so non-empty means the user explicitly passed values on CLI. if (!cmd.seedNodes.isEmpty()) { + logger.warn("Positional seed-node arguments are deprecated. " + + "Please use seed.node.ip.list in the config file instead."); List seeds = new ArrayList<>(); for (String s : cmd.seedNodes) { seeds.add(NetUtil.parseInetSocketAddress(s)); @@ -1691,6 +1757,9 @@ public static void printHelp(JCommander jCommander) { jCommander.getProgramName(); helpStr.append(String.format("%nUsage: java -jar %s [options] [seedNode ...]%n", programName)); + helpStr.append(String.format( + "%nNote: Positional seedNode arguments are deprecated." + + " Use seed.node.ip.list in the config file instead.%n")); helpStr.append(String.format("%nVERSION: %n%s-%s%n", Version.getVersion(), getCommitIdAbbrev())); @@ -1712,9 +1781,21 @@ public static void printHelp(JCommander jCommander) { logger.warn("Miss option:{}", option); continue; } + boolean isDeprecated; + try { + isDeprecated = CLIParameter.class.getDeclaredField( + parameterDescription.getParameterized().getName()) + .isAnnotationPresent(Deprecated.class); + } catch (NoSuchFieldException e) { + isDeprecated = false; + } + String desc = upperFirst(parameterDescription.getDescription()); + if (isDeprecated) { + desc += " (deprecated)"; + } String tmpOptionDesc = String.format("%s\t\t\t%s%n", Strings.padEnd(parameterDescription.getNames(), optionMaxLength, ' '), - upperFirst(parameterDescription.getDescription())); + desc); helpStr.append(tmpOptionDesc); } } diff --git a/framework/src/main/java/org/tron/core/config/args/CLIParameter.java b/framework/src/main/java/org/tron/core/config/args/CLIParameter.java index b5bd0e2e85a..4f056a32e3a 100644 --- a/framework/src/main/java/org/tron/core/config/args/CLIParameter.java +++ b/framework/src/main/java/org/tron/core/config/args/CLIParameter.java @@ -10,6 +10,9 @@ * Fields here have NO default values — defaults live in CommonParameter. * JCommander only populates fields that are explicitly passed on the * command line. + * + *

Parameters marked {@code @Deprecated} are scheduled for removal. + * Use the corresponding config-file options instead.

*/ @NoArgsConstructor public class CLIParameter { @@ -50,9 +53,11 @@ public class CLIParameter { @Parameter(names = {"--keystore-factory"}, description = "running KeystoreFactory") public boolean keystoreFactory; + @Deprecated @Parameter(names = {"--fast-forward"}) public boolean fastForward; + @Deprecated @Parameter(names = {"--es"}, description = "Start event subscribe server") public boolean eventSubscribe; @@ -60,47 +65,58 @@ public class CLIParameter { + "(default: false)", arity = 1) public boolean p2pDisable; + @Deprecated @Parameter(description = "--seed-nodes") public List seedNodes = new ArrayList<>(); - // -- Storage parameters -- + // -- Storage parameters (deprecated, use config file instead) -- + @Deprecated @Parameter(names = {"--storage-db-directory"}, description = "Storage db directory") public String storageDbDirectory; + @Deprecated @Parameter(names = {"--storage-db-engine"}, description = "Storage db engine.(leveldb or rocksdb)") public String storageDbEngine; + @Deprecated @Parameter(names = {"--storage-db-synchronous"}, description = "Storage db is synchronous or not.(true or false)") public String storageDbSynchronous; + @Deprecated @Parameter(names = {"--storage-index-directory"}, description = "Storage index directory") public String storageIndexDirectory; + @Deprecated @Parameter(names = {"--storage-index-switch"}, description = "Storage index switch.(on or off)") public String storageIndexSwitch; + @Deprecated @Parameter(names = {"--storage-transactionHistory-switch"}, description = "Storage transaction history switch.(on or off)") public String storageTransactionHistorySwitch; + @Deprecated @Parameter(names = {"--contract-parse-enable"}, description = "Switch for contract parses in " + "java-tron. (default: true)") public String contractParseEnable; - // -- Runtime parameters -- + // -- Runtime parameters (deprecated except --debug, use config file instead) -- + @Deprecated @Parameter(names = {"--support-constant"}, description = "Support constant calling for TVM. " + "(default: false)") public boolean supportConstant; + @Deprecated @Parameter(names = {"--max-energy-limit-for-constant"}, description = "Max energy limit for constant calling. (default: 100,000,000)") public long maxEnergyLimitForConstant; + @Deprecated @Parameter(names = {"--lru-cache-size"}, description = "Max LRU size for caching bytecode and " + "result of JUMPDEST analysis. (default: 500)") public int lruCacheSize; @@ -109,48 +125,60 @@ public class CLIParameter { + "will not check for timeout. (default: false)") public boolean debug; + @Deprecated @Parameter(names = {"--min-time-ratio"}, description = "Minimum CPU tolerance when executing " + "timeout transactions while synchronizing blocks. (default: 0.0)") public double minTimeRatio; + @Deprecated @Parameter(names = {"--max-time-ratio"}, description = "Maximum CPU tolerance when executing " + "non-timeout transactions while synchronizing blocks. (default: 5.0)") public double maxTimeRatio; + @Deprecated @Parameter(names = {"--save-internaltx"}, description = "Save internal transactions generated " + "during TVM execution, such as create, call and suicide. (default: false)") public boolean saveInternalTx; + @Deprecated @Parameter(names = {"--save-featured-internaltx"}, description = "Save featured internal " + "transactions generated during TVM execution, such as freeze, vote and so on. " + "(default: false)") public boolean saveFeaturedInternalTx; + @Deprecated @Parameter(names = {"--save-cancel-all-unfreeze-v2-details"}, description = "Record the details of the internal transactions generated by the " + "CANCELALLUNFREEZEV2 opcode, such as bandwidth/energy/tronpower cancel amount. " + "(default: false)") public boolean saveCancelAllUnfreezeV2Details; + @Deprecated @Parameter(names = {"--long-running-time"}) public int longRunningTime; + @Deprecated @Parameter(names = {"--max-connect-number"}, description = "Http server max connect number " + "(default:50)") public int maxHttpConnectNumber; + @Deprecated @Parameter(names = {"--rpc-thread"}, description = "Num of gRPC thread") public int rpcThreadNum; + @Deprecated @Parameter(names = {"--solidity-thread"}, description = "Num of solidity thread") public int solidityThreads; + @Deprecated @Parameter(names = {"--validate-sign-thread"}, description = "Num of validate thread") public int validateSignThreadNum; + @Deprecated @Parameter(names = {"--trust-node"}, description = "Trust node addr") public String trustNodeAddr; + @Deprecated @Parameter(names = {"--history-balance-lookup"}) public boolean historyBalanceLookup; } diff --git a/framework/src/main/java/org/tron/core/config/args/ConfigKey.java b/framework/src/main/java/org/tron/core/config/args/ConfigKey.java index dbb872febce..06dd7d9d57a 100644 --- a/framework/src/main/java/org/tron/core/config/args/ConfigKey.java +++ b/framework/src/main/java/org/tron/core/config/args/ConfigKey.java @@ -272,6 +272,7 @@ private ConfigKey() { // event public static final String EVENT_SUBSCRIBE = "event.subscribe"; + public static final String EVENT_SUBSCRIBE_ENABLE = "event.subscribe.enable"; public static final String EVENT_SUBSCRIBE_FILTER = "event.subscribe.filter"; public static final String EVENT_SUBSCRIBE_VERSION = "event.subscribe.version"; public static final String EVENT_SUBSCRIBE_START_SYNC_BLOCK_NUM = diff --git a/framework/src/main/resources/config.conf b/framework/src/main/resources/config.conf index 661a592e431..069b51286d9 100644 --- a/framework/src/main/resources/config.conf +++ b/framework/src/main/resources/config.conf @@ -762,6 +762,7 @@ committee = { } event.subscribe = { + enable = false // enable event subscribe, replaces deprecated CLI flag --es native = { useNativeQueue = true // if true, use native message queue, else use event plugin. bindport = 5555 // bind port diff --git a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java index 52c2eeb26ff..88d893bcf97 100644 --- a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java +++ b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java @@ -318,6 +318,28 @@ public void testCliOverridesStorageConfig() { Args.clearParam(); } + /** + * Verify that event.subscribe.enable = false from config is read correctly. + */ + @Test + public void testEventSubscribeFromConfig() { + Args.setParam(new String[] {}, TestConstants.TEST_CONF); + Assert.assertFalse(Args.getInstance().isEventSubscribe()); + Args.clearParam(); + } + + /** + * Verify that CLI --es overrides event.subscribe.enable from config. + * config-test.conf defines: event.subscribe.enable = false, + * passing --es explicitly sets eventSubscribe = true, overriding config. + */ + @Test + public void testCliEsOverridesConfig() { + Args.setParam(new String[] {"--es"}, TestConstants.TEST_CONF); + Assert.assertTrue(Args.getInstance().isEventSubscribe()); + Args.clearParam(); + } + /** * Verify that config file storage values are applied when no CLI override is present. * diff --git a/framework/src/test/resources/config-test.conf b/framework/src/test/resources/config-test.conf index 41c1e9a55d6..71e93f84db5 100644 --- a/framework/src/test/resources/config-test.conf +++ b/framework/src/test/resources/config-test.conf @@ -382,4 +382,8 @@ rate.limiter.http = [ ] node.dynamicConfig.enable = true + +event.subscribe = { + enable = false +} node.dynamicConfig.checkInterval = 0 \ No newline at end of file