mirror of https://github.com/hyperledger/besu
commit
1ca33624f4
@ -0,0 +1,114 @@ |
||||
name: acceptance-tests |
||||
on: |
||||
pull_request: |
||||
pull_request_review: |
||||
types: [submitted] |
||||
|
||||
env: |
||||
GRADLE_OPTS: "-Xmx6g -Dorg.gradle.daemon=false" |
||||
total-runners: 16 |
||||
|
||||
jobs: |
||||
shouldRun: |
||||
name: checks to ensure we should run |
||||
# necessary because there is no single PR approved event, need to check all comments/approvals/denials |
||||
runs-on: ubuntu-22.04 |
||||
outputs: |
||||
shouldRun: ${{steps.shouldRun.outputs.result}} |
||||
steps: |
||||
- name: required check |
||||
id: shouldRun |
||||
uses: actions/github-script@v7.0.1 |
||||
env: |
||||
# fun fact, this changes based on incoming event, it will be different when we run this on pushes to main |
||||
RELEVANT_SHA: ${{ github.event.pull_request.head.sha || github.sha }} |
||||
with: |
||||
script: | |
||||
const { RELEVANT_SHA } = process.env; |
||||
const { data: { statuses } } = await github.rest.repos.getCombinedStatusForRef({ |
||||
owner: context.repo.owner, |
||||
repo: context.repo.repo, |
||||
ref: RELEVANT_SHA, |
||||
}); |
||||
const acceptanceTested = statuses && statuses.filter(({ context }) => context === 'acceptance-tests'); |
||||
const alreadyRun = acceptanceTested && acceptanceTested.find(({ state }) => state === 'success') > 0; |
||||
const { data: reviews } = await github.rest.pulls.listReviews({ |
||||
owner: context.repo.owner, |
||||
repo: context.repo.repo, |
||||
pull_number: context.issue.number, |
||||
}); |
||||
const approvingReviews = reviews && reviews.filter(review => review.state === 'APPROVED'); |
||||
const shouldRun = !alreadyRun && github.actor != 'dependabot[bot]' && (approvingReviews.length > 0); |
||||
|
||||
console.log("tests should be run = %j", shouldRun); |
||||
console.log("alreadyRun = %j", alreadyRun); |
||||
console.log("approvingReviews = %j", approvingReviews.length); |
||||
|
||||
return shouldRun; |
||||
acceptanceTestEthereum: |
||||
runs-on: ubuntu-22.04 |
||||
name: "Acceptance Runner" |
||||
needs: shouldRun |
||||
permissions: |
||||
statuses: write |
||||
checks: write |
||||
if: ${{ needs.shouldRun.outputs.shouldRun == 'true'}} |
||||
strategy: |
||||
fail-fast: true |
||||
matrix: |
||||
runner_index: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: get acceptance test report |
||||
uses: dawidd6/action-download-artifact@v2 |
||||
with: |
||||
branch: main |
||||
name_is_regexp: true |
||||
name: 'acceptance-node-\d*\d-test-results' |
||||
path: tmp/junit-xml-reports-downloaded |
||||
if_no_artifact_found: true |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: Split tests |
||||
id: split-tests |
||||
uses: r7kamura/split-tests-by-timings@v0 |
||||
with: |
||||
reports: tmp/junit-xml-reports-downloaded |
||||
glob: 'acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/**/*Test.java' |
||||
total: ${{env.total-runners}} |
||||
index: ${{ matrix.runner_index }} |
||||
- name: write out test list |
||||
run: echo "${{ steps.split-tests.outputs.paths }}" >> testList.txt |
||||
- name: format gradle args |
||||
#regex means: first truncate file paths to align with package name, then swap path delimiter with package delimiter, |
||||
#then drop file extension, then insert --tests option between each. |
||||
run: cat testList.txt | sed -e 's@acceptance-tests/tests/src/test/java/@--tests\ @g;s@/@.@g;s/\.java//g' > gradleArgs.txt |
||||
- name: run acceptance tests |
||||
run: ./gradlew acceptanceTest `cat gradleArgs.txt` -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
- name: cleanup tempfiles |
||||
run: rm testList.txt gradleArgs.txt |
||||
- name: Upload Acceptance Test Results |
||||
uses: actions/upload-artifact@v3.1.0 |
||||
with: |
||||
name: acceptance-node-${{matrix.runner_index}}-test-results |
||||
path: 'acceptance-tests/tests/build/test-results/acceptanceTest/TEST-*.xml' |
||||
- name: Publish Test Report |
||||
uses: mikepenz/action-junit-report@v4 |
||||
if: (success() || failure()) # always run even if the build step fails |
||||
with: |
||||
report_paths: 'acceptance-tests/tests/build/test-results/acceptanceTest/TEST-*.xml' |
||||
acceptance-tests: |
||||
runs-on: ubuntu-22.04 |
||||
needs: [ acceptanceTestEthereum ] |
||||
permissions: |
||||
checks: write |
||||
statuses: write |
||||
steps: |
||||
- name: consolidation |
||||
run: echo "consolidating statuses" |
@ -0,0 +1,76 @@ |
||||
|
||||
name: artifacts |
||||
|
||||
on: |
||||
release: |
||||
types: |
||||
- prereleased |
||||
|
||||
jobs: |
||||
artifacts: |
||||
runs-on: ubuntu-22.04 |
||||
permissions: |
||||
contents: write |
||||
steps: |
||||
- name: checkout |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up JDK 17 |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: 'temurin' |
||||
java-version: '17' |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: assemble distributions |
||||
run: |
||||
./gradlew -Prelease.releaseVersion=${{github.ref_name}} assemble -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
- name: hashes |
||||
id: hashes |
||||
run: | |
||||
cd build/distributions |
||||
echo "zipSha=$(shasum -a 256 besu*.zip)" >> $GITHUB_OUTPUT |
||||
echo "tarSha=$(shasum -a 256 besu*.tar.gz)" >> $GITHUB_OUTPUT |
||||
- name: upload tarball |
||||
uses: actions/upload-artifact@v3 |
||||
with: |
||||
path: 'build/distributions/besu*.tar.gz' |
||||
name: besu-${{ github.ref_name }}.tar.gz |
||||
- name: upload zipfile |
||||
uses: actions/upload-artifact@v3 |
||||
with: |
||||
path: 'build/distributions/besu*.zip' |
||||
name: besu-${{ github.ref_name }}.zip |
||||
- name: Upload Release assets |
||||
uses: softprops/action-gh-release@v1 |
||||
with: |
||||
append_body: true |
||||
files: | |
||||
build/distributions/besu*.tar.gz |
||||
build/distributions/besu*.zip |
||||
body: | |
||||
${{steps.hashes.outputs.tarSha}} |
||||
${{steps.hashes.outputs.zipSha}} |
||||
testWindows: |
||||
runs-on: windows-2022 |
||||
needs: artifacts |
||||
timeout-minutes: 10 |
||||
if: ${{ github.actor != 'dependabot[bot]' }} |
||||
steps: |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: adopt |
||||
java-version: 17 |
||||
- name: Download zip |
||||
uses: actions/download-artifact@v3 |
||||
with: |
||||
name: besu-${{ github.ref_name }}.zip |
||||
- name: test Besu |
||||
run: | |
||||
unzip besu-*.zip -d besu-tmp |
||||
cd besu-tmp |
||||
mv besu-* ../besu |
||||
cd .. |
||||
besu\bin\besu.bat --help |
||||
besu\bin\besu.bat --version |
||||
|
@ -1,36 +0,0 @@ |
||||
name: checks |
||||
on: |
||||
push: |
||||
branches: [ main ] |
||||
pull_request: |
||||
workflow_dispatch: |
||||
|
||||
jobs: |
||||
spotless: |
||||
runs-on: [besu-research-ubuntu-16] |
||||
if: ${{ github.actor != 'dependabot[bot]' }} |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4 |
||||
with: |
||||
distribution: 'temurin' |
||||
java-version: 17 |
||||
cache: gradle |
||||
- name: spotless |
||||
run: ./gradlew --no-daemon --parallel clean spotlessCheck |
||||
javadoc_17: |
||||
runs-on: [besu-research-ubuntu-8] |
||||
if: ${{ github.actor != 'dependabot[bot]' }} |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4 |
||||
- name: Set up Java 17 |
||||
uses: actions/setup-java@v4 |
||||
with: |
||||
distribution: 'temurin' |
||||
java-version: 17 |
||||
cache: gradle |
||||
- name: javadoc (JDK 17) |
||||
run: ./gradlew --no-daemon clean javadoc |
@ -1,10 +0,0 @@ |
||||
name: dco |
||||
on: |
||||
merge_group: |
||||
|
||||
jobs: |
||||
dco: |
||||
runs-on: [besu-research-ubuntu-8] |
||||
if: ${{ github.actor != 'dependabot[bot]' }} |
||||
steps: |
||||
- run: echo "This DCO job runs on merge_queue event and doesn't check PR contents" |
@ -1,20 +0,0 @@ |
||||
name: dco |
||||
on: |
||||
pull_request: |
||||
workflow_dispatch: |
||||
|
||||
jobs: |
||||
dco: |
||||
runs-on: [besu-research-ubuntu-8] |
||||
if: ${{ github.actor != 'dependabot[bot]' }} |
||||
steps: |
||||
- run: echo "This DCO job runs on pull_request event and workflow_dispatch" |
||||
- name: Get PR Commits |
||||
id: 'get-pr-commits' |
||||
uses: tim-actions/get-pr-commits@v1.2.0 |
||||
with: |
||||
token: ${{ secrets.GITHUB_TOKEN }} |
||||
- name: DCO Check |
||||
uses: tim-actions/dco@v1.1.0 |
||||
with: |
||||
commits: ${{ steps.get-pr-commits.outputs.commits }} |
@ -0,0 +1,113 @@ |
||||
name: docker |
||||
on: |
||||
release: |
||||
types: |
||||
- prereleased |
||||
env: |
||||
registry: ghcr.io |
||||
|
||||
jobs: |
||||
hadolint: |
||||
runs-on: ubuntu-22.04 |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: hadoLint_openj9-jdk_17 |
||||
run: docker run --rm -i hadolint/hadolint < docker/openj9-jdk-17/Dockerfile |
||||
- name: hadoLint_openjdk_17 |
||||
run: docker run --rm -i hadolint/hadolint < docker/openjdk-17/Dockerfile |
||||
- name: hadoLint_openjdk_17_debug |
||||
run: docker run --rm -i hadolint/hadolint < docker/openjdk-17-debug/Dockerfile |
||||
- name: hadoLint_openjdk_latest |
||||
run: docker run --rm -i hadolint/hadolint < docker/openjdk-latest/Dockerfile |
||||
- name: hadoLint_graalvm |
||||
run: docker run --rm -i hadolint/hadolint < docker/graalvm/Dockerfile |
||||
buildDocker: |
||||
needs: hadolint |
||||
permissions: |
||||
contents: read |
||||
packages: write |
||||
|
||||
strategy: |
||||
fail-fast: false |
||||
matrix: |
||||
platform: |
||||
- ubuntu-22.04 |
||||
- [self-hosted, ARM64] |
||||
runs-on: ${{ matrix.platform }} |
||||
steps: |
||||
- name: Prepare |
||||
id: prep |
||||
run: | |
||||
platform=${{ matrix.platform }} |
||||
if [ "$platform" = 'ubuntu-22.04' ]; then |
||||
echo "PLATFORM_PAIR=linux-amd64" >> $GITHUB_OUTPUT |
||||
echo "ARCH=amd64" >> $GITHUB_OUTPUT |
||||
else |
||||
echo "PLATFORM_PAIR=linux-arm64" >> $GITHUB_OUTPUT |
||||
echo "ARCH=arm64" >> $GITHUB_OUTPUT |
||||
fi |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: short sha |
||||
id: shortSha |
||||
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: install goss |
||||
run: | |
||||
mkdir -p docker/reports |
||||
curl -L https://github.com/aelsabbahy/goss/releases/download/v0.4.4/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} -o ./docker/tests/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} |
||||
- name: build and test docker |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
env: |
||||
architecture: ${{ steps.prep.outputs.ARCH }} |
||||
with: |
||||
arguments: testDocker -PdockerOrgName=${{ env.registry }}/${{ github.repository_owner }} -Prelease.releaseVersion=${{ github.ref_name }} |
||||
- name: login to ghcr |
||||
uses: docker/login-action@v3.0.0 |
||||
with: |
||||
registry: ${{ env.registry }} |
||||
username: ${{ github.actor }} |
||||
password: ${{ secrets.GITHUB_TOKEN }} |
||||
- name: publish |
||||
env: |
||||
architecture: ${{ steps.prep.outputs.ARCH }} |
||||
run: ./gradlew --no-daemon dockerUpload -PdockerOrgName=${{ env.registry }}/${{ github.repository_owner }} -Prelease.releaseVersion=${{ github.ref_name }} |
||||
multiArch: |
||||
needs: buildDocker |
||||
runs-on: ubuntu-22.04 |
||||
permissions: |
||||
contents: read |
||||
packages: write |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: login to ghcr |
||||
uses: docker/login-action@v3.0.0 |
||||
with: |
||||
registry: ${{ env.registry }} |
||||
username: ${{ github.actor }} |
||||
password: ${{ secrets.GITHUB_TOKEN }} |
||||
- name: multi-arch docker |
||||
run: ./gradlew manifestDocker -PdockerOrgName=${{ env.registry }}/${{ github.repository_owner }} -Prelease.releaseVersion=${{ github.ref_name }} |
||||
|
@ -1,11 +0,0 @@ |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
name: "Validate Gradle Wrapper" |
||||
on: [push, pull_request] |
||||
|
||||
jobs: |
||||
validation: |
||||
name: "Gradle Wrapper Validation" |
||||
runs-on: [besu-research-ubuntu-8] |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- uses: gradle/wrapper-validation-action@v1 |
@ -0,0 +1,73 @@ |
||||
name: integration-tests |
||||
on: |
||||
pull_request: |
||||
pull_request_review: |
||||
types: |
||||
- submitted |
||||
|
||||
env: |
||||
GRADLE_OPTS: "-Xmx6g -Dorg.gradle.daemon=false" |
||||
|
||||
jobs: |
||||
shouldRun: |
||||
name: checks to ensure we should run |
||||
runs-on: ubuntu-22.04 |
||||
outputs: |
||||
shouldRun: ${{steps.shouldRun.outputs.result}} |
||||
steps: |
||||
- name: required check |
||||
id: shouldRun |
||||
uses: actions/github-script@v7.0.1 |
||||
env: |
||||
# fun fact, this changes based on incoming event, it will be different when we run this on pushes to main |
||||
RELEVANT_SHA: ${{ github.event.pull_request.head.sha || github.sha }} |
||||
with: |
||||
script: | |
||||
const { RELEVANT_SHA } = process.env; |
||||
const { data: { statuses } } = await github.rest.repos.getCombinedStatusForRef({ |
||||
owner: context.repo.owner, |
||||
repo: context.repo.repo, |
||||
ref: RELEVANT_SHA, |
||||
}); |
||||
|
||||
const intTested = statuses && statuses.filter(({ context }) => context === 'integration-tests'); |
||||
const alreadyRun = intTested && intTested.find(({ state }) => state === 'success') > 0; |
||||
const { data: reviews } = await github.rest.pulls.listReviews({ |
||||
owner: context.repo.owner, |
||||
repo: context.repo.repo, |
||||
pull_number: context.issue.number, |
||||
}); |
||||
const approvingReviews = reviews && reviews.filter(review => review.state === 'APPROVED'); |
||||
const shouldRun = !alreadyRun && github.actor != 'dependabot[bot]' && (approvingReviews.length > 0); |
||||
|
||||
console.log("tests should be run = %j", shouldRun); |
||||
console.log("alreadyRun = %j", alreadyRun); |
||||
console.log("approvingReviews = %j", approvingReviews.length); |
||||
|
||||
return shouldRun; |
||||
integration-tests: |
||||
runs-on: ubuntu-22.04 |
||||
needs: shouldRun |
||||
if: ${{ needs.shouldRun.outputs.shouldRun == 'true' }} |
||||
permissions: |
||||
statuses: write |
||||
checks: write |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: run integration tests |
||||
run: ./gradlew integrationTest compileJmh -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
- name: Publish Test Report |
||||
uses: mikepenz/action-junit-report@v4 |
||||
if: (success() || failure()) |
||||
with: |
||||
report_paths: '**/build/test-results/integrationTest/TEST-*.xml' |
||||
|
||||
|
@ -0,0 +1,121 @@ |
||||
name: nightly |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
schedule: |
||||
# * is a special character in YAML so you have to quote this string |
||||
# expression evaluates to midnight every night |
||||
- cron: '0 0 * * *' |
||||
|
||||
env: |
||||
nightly-tag: develop |
||||
registry: ghcr.io |
||||
|
||||
jobs: |
||||
hadolint: |
||||
runs-on: ubuntu-22.04 |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: hadoLint_openj9-jdk_17 |
||||
run: docker run --rm -i hadolint/hadolint < docker/openj9-jdk-17/Dockerfile |
||||
- name: hadoLint_openjdk_17 |
||||
run: docker run --rm -i hadolint/hadolint < docker/openjdk-17/Dockerfile |
||||
- name: hadoLint_openjdk_17_debug |
||||
run: docker run --rm -i hadolint/hadolint < docker/openjdk-17-debug/Dockerfile |
||||
- name: hadoLint_openjdk_latest |
||||
run: docker run --rm -i hadolint/hadolint < docker/openjdk-latest/Dockerfile |
||||
- name: hadoLint_graalvm |
||||
run: docker run --rm -i hadolint/hadolint < docker/graalvm/Dockerfile |
||||
buildDocker: |
||||
needs: hadolint |
||||
permissions: |
||||
contents: read |
||||
packages: write |
||||
strategy: |
||||
fail-fast: false |
||||
matrix: |
||||
platform: |
||||
- ubuntu-22.04 |
||||
- [self-hosted, ARM64] |
||||
runs-on: ${{ matrix.platform }} |
||||
steps: |
||||
- name: Prepare |
||||
id: prep |
||||
run: | |
||||
platform=${{ matrix.platform }} |
||||
if [ "$platform" = 'ubuntu-22.04' ]; then |
||||
echo "PLATFORM_PAIR=linux-amd64" >> $GITHUB_OUTPUT |
||||
echo "ARCH=amd64" >> $GITHUB_OUTPUT |
||||
else |
||||
echo "PLATFORM_PAIR=linux-arm64" >> $GITHUB_OUTPUT |
||||
echo "ARCH=arm64" >> $GITHUB_OUTPUT |
||||
fi |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: short sha |
||||
id: shortSha |
||||
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: build image |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
with: |
||||
arguments: distDocker -PdockerOrgName=${{ env.registry }}/${{ github.repository_owner }} -Pbranch=main |
||||
- name: install goss |
||||
run: | |
||||
mkdir -p docker/reports |
||||
curl -L https://github.com/aelsabbahy/goss/releases/download/v0.4.4/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} -o ./docker/tests/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} |
||||
- name: test docker |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
env: |
||||
architecture: ${{ steps.prep.outputs.ARCH }} |
||||
with: |
||||
arguments: testDocker -PdockerOrgName=${{ env.registry }}/${{ github.repository_owner }} -Pbranch=main |
||||
- name: login to ghcr |
||||
uses: docker/login-action@v3.0.0 |
||||
with: |
||||
registry: ${{ env.registry }} |
||||
username: ${{ github.actor }} |
||||
password: ${{ secrets.GITHUB_TOKEN }} |
||||
- name: publish |
||||
env: |
||||
architecture: ${{ steps.prep.outputs.ARCH }} |
||||
run: ./gradlew --no-daemon dockerUpload -PdockerOrgName=${{ env.registry }}/${{ github.repository_owner }} -Pbranch=main |
||||
multiArch: |
||||
permissions: |
||||
contents: read |
||||
packages: write |
||||
needs: buildDocker |
||||
runs-on: ubuntu-22.04 |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: Login to DockerHub |
||||
uses: docker/login-action@v3.0.0 |
||||
with: |
||||
registry: ${{ env.registry }} |
||||
username: ${{ github.actor }} |
||||
password: ${{ secrets.GITHUB_TOKEN }} |
||||
- name: multi-arch docker |
||||
run: ./gradlew manifestDocker -PdockerOrgName=${{ env.registry }}/${{ github.repository_owner }} -Pbranch=main |
||||
|
@ -0,0 +1,49 @@ |
||||
name: parallel-unit-tests |
||||
#experimental work in progress - trying to figure out how to split tests across multi-modules by runtime |
||||
on: |
||||
workflow_dispatch: |
||||
|
||||
env: |
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false" |
||||
total-runners: 4 |
||||
jobs: |
||||
junit: |
||||
runs-on: ubuntu-latest |
||||
strategy: |
||||
fail-fast: false |
||||
matrix: |
||||
runner_index: |
||||
- 0 |
||||
- 1 |
||||
- 2 |
||||
- 3 |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Split tests |
||||
id: split-tests |
||||
uses: chaosaffe/split-tests@v1-alpha.1 |
||||
with: |
||||
glob: '**/src/test/java/**/*.java' |
||||
split-total: ${{ env.total-runners }} |
||||
split-index: ${{ matrix.runner_index }} |
||||
line-count: true |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: adopt |
||||
java-version: 17 |
||||
cache: gradle |
||||
- name: write out test list |
||||
run: echo "${{ steps.split-tests.outputs.test-suite }}" >> testList.txt |
||||
- name: debug testfile paths |
||||
run: cat testList.txt |
||||
- name: format gradle args |
||||
# regex means: truncate file paths to align with package name, replacing with tests switch, then drop file extension, |
||||
# then swap path delimiter with package delimiter |
||||
run: cat testList.txt | sed -e 's/[^ ]*src\/test\/java\//--tests\ /g' -e 's/\.java//g' -e 's/\//\./g' >> gradleArgs.txt |
||||
- name: debug test class list |
||||
run: cat gradleArgs.txt |
||||
- name: run unit tests |
||||
run: ./gradlew test `cat gradleArgs.txt` |
||||
|
@ -0,0 +1,103 @@ |
||||
name: pre-review |
||||
|
||||
on: |
||||
pull_request: |
||||
workflow_dispatch: |
||||
|
||||
permissions: |
||||
statuses: write |
||||
checks: write |
||||
|
||||
jobs: |
||||
repolint: |
||||
name: "Repository Linting" |
||||
runs-on: ubuntu-22.04 |
||||
container: ghcr.io/todogroup/repolinter:v0.11.2 |
||||
steps: |
||||
- name: Checkout Code |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Lint Repo |
||||
run: bundle exec /app/bin/repolinter.js --rulesetUrl https://raw.githubusercontent.com/hyperledger-labs/hyperledger-community-management-tools/main/repo_structure/repolint.json --format markdown |
||||
gradle-wrapper: |
||||
name: "Gradle Wrapper Validation" |
||||
runs-on: ubuntu-22.04 |
||||
steps: |
||||
- uses: actions/checkout@v4.1.1 |
||||
- uses: gradle/wrapper-validation-action@v1.1.0 |
||||
spotless: |
||||
runs-on: ubuntu-22.04 |
||||
if: ${{ github.actor != 'dependabot[bot]' }} |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: Setup Gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: run spotless |
||||
run: ./gradlew spotlessCheck -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
compile: |
||||
runs-on: ubuntu-22.04 |
||||
timeout-minutes: 30 |
||||
needs: [spotless, gradle-wrapper, repolint] |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: Setup Gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: Gradle Compile |
||||
run: ./gradlew build -x test -x spotlessCheck -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
unitTests: |
||||
env: |
||||
GRADLEW_UNIT_TEST_ARGS: ${{matrix.gradle_args}} |
||||
runs-on: ubuntu-22.04 |
||||
needs: [ compile ] |
||||
permissions: |
||||
checks: write |
||||
statuses: write |
||||
strategy: |
||||
fail-fast: true |
||||
matrix: |
||||
gradle_args: |
||||
- "test -x besu:test -x consensus:test -x crypto:test -x ethereum:eth:test -x ethereum:api:test -x ethereum:core:test" |
||||
- "besu:test consensus:test crypto:test" |
||||
- "ethereum:api:testBonsai" |
||||
- "ethereum:api:testForest" |
||||
- "ethereum:api:testRemainder" |
||||
- "ethereum:core:test" |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: Setup Gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: run unit tests |
||||
id: unitTest |
||||
run: ./gradlew $GRADLEW_UNIT_TEST_ARGS -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
- name: Publish Test Report |
||||
uses: mikepenz/action-junit-report@v4 |
||||
if: success() || failure() # always run even if the build step fails |
||||
with: |
||||
report_paths: '**/test-results/**/TEST-*.xml' |
||||
annotate_only: true |
||||
pre-review: |
||||
runs-on: ubuntu-22.04 |
||||
needs: [unitTests] |
||||
permissions: |
||||
checks: write |
||||
statuses: write |
||||
steps: |
||||
- name: consolidation |
||||
run: echo "consolidating statuses" |
@ -0,0 +1,147 @@ |
||||
name: reference-tests |
||||
on: |
||||
pull_request: |
||||
pull_request_review: |
||||
types: |
||||
- submitted |
||||
|
||||
env: |
||||
GRADLE_OPTS: "-Xmx6g -Dorg.gradle.daemon=false" |
||||
total-runners: 6 |
||||
|
||||
jobs: |
||||
shouldRun: |
||||
name: checks to ensure we should run |
||||
# necessary because there is no single PR approved event, need to check all comments/approvals/denials |
||||
# might also be a job running, and additional approvals |
||||
runs-on: ubuntu-22.04 |
||||
outputs: |
||||
shouldRun: ${{steps.shouldRun.outputs.result}} |
||||
steps: |
||||
- name: required check |
||||
id: shouldRun |
||||
uses: actions/github-script@v7.0.1 |
||||
env: |
||||
# fun fact, this changes based on incoming event, it will be different when we run this on pushes to main |
||||
RELEVANT_SHA: ${{ github.event.pull_request.head.sha || github.sha }} |
||||
with: |
||||
script: | |
||||
const { RELEVANT_SHA } = process.env; |
||||
const { data: { statuses } } = await github.rest.repos.getCombinedStatusForRef({ |
||||
owner: context.repo.owner, |
||||
repo: context.repo.repo, |
||||
ref: RELEVANT_SHA, |
||||
}); |
||||
|
||||
|
||||
const refTested = statuses && statuses.filter(({ context }) => context === 'reference-tests'); |
||||
const alreadyRun = refTested && refTested.find(({ state }) => state === 'success') > 0; |
||||
const { data: reviews } = await github.rest.pulls.listReviews({ |
||||
owner: context.repo.owner, |
||||
repo: context.repo.repo, |
||||
pull_number: context.issue.number, |
||||
}); |
||||
const approvingReviews = reviews && reviews.filter(review => review.state === 'APPROVED'); |
||||
const shouldRun = !alreadyRun && github.actor != 'dependabot[bot]' && (approvingReviews.length > 0); |
||||
|
||||
console.log("tests should be run = %j", shouldRun); |
||||
console.log("alreadyRun = %j", alreadyRun); |
||||
console.log("approvingReviews = %j", approvingReviews.length); |
||||
|
||||
return shouldRun; |
||||
|
||||
prepareReferenceTestEthereum: |
||||
runs-on: ubuntu-22.04 |
||||
needs: shouldRun |
||||
if: ${{ needs.shouldRun.outputs.shouldRun == 'true' }} |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
with: |
||||
submodules: recursive |
||||
set-safe-directory: true |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: temurin |
||||
java-version: 17 |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: execute generate reference tests |
||||
run: ./gradlew ethereum:referencetests:blockchainReferenceTests ethereum:referencetests:generalstateReferenceTests ethereum:referencetests:generalstateRegressionReferenceTests -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
- name: store generated tests |
||||
uses: actions/upload-artifact@v3 |
||||
with: |
||||
name: 'reference-tests' |
||||
path: 'ethereum/referencetests/build/generated/sources/reference-test/**/*.java' |
||||
|
||||
referenceTestEthereum: |
||||
runs-on: ubuntu-22.04 |
||||
permissions: |
||||
statuses: write |
||||
checks: write |
||||
needs: |
||||
- prepareReferenceTestEthereum |
||||
if: ${{ needs.shouldRun.outputs.shouldRun == 'true' }} |
||||
strategy: |
||||
fail-fast: true |
||||
matrix: |
||||
runner_index: [0,1,2,3,4,5] |
||||
steps: |
||||
- name: Checkout Repo |
||||
uses: actions/checkout@v4.1.1 |
||||
with: |
||||
submodules: recursive |
||||
- name: Set up Java |
||||
uses: actions/setup-java@v4.0.0 |
||||
with: |
||||
distribution: adopt-openj9 |
||||
java-version: 17 |
||||
- name: retrieve generated tests |
||||
uses: actions/download-artifact@v3.0.2 |
||||
with: |
||||
name: 'reference-tests' |
||||
path: 'ethereum/referencetests/build/generated/sources/reference-test/' |
||||
- name: get reference test report |
||||
uses: dawidd6/action-download-artifact@v2 |
||||
with: |
||||
branch: main |
||||
name_is_regexp: true |
||||
name: 'reference-test-node-\d*\d-results' |
||||
path: tmp/ref-xml-reports-downloaded |
||||
if_no_artifact_found: true |
||||
- name: setup gradle |
||||
uses: gradle/gradle-build-action@v2.12.0 |
||||
- name: Split tests |
||||
id: split-tests |
||||
uses: r7kamura/split-tests-by-timings@v0 |
||||
with: |
||||
reports: tmp/ref-xml-reports-downloaded |
||||
glob: 'ethereum/referencetests/build/generated/sources/reference-test/**/*.java' |
||||
total: ${{env.total-runners}} |
||||
index: ${{ matrix.runner_index }} |
||||
- name: compose gradle args |
||||
run: echo ${{ steps.split-tests.outputs.paths }} | sed -e 's/^.*java\///' -e 's@/@.@g' -e 's/\.java//' -e 's/^/--tests /' > refTestArgs.txt |
||||
- name: run reference tests |
||||
run: ./gradlew ethereum:referenceTests:referenceTests `cat refTestArgs.txt` -Dorg.gradle.parallel=true -Dorg.gradle.caching=true |
||||
- name: Upload Test Report |
||||
uses: actions/upload-artifact@v3 |
||||
if: always() # always run even if the previous step fails |
||||
with: |
||||
name: reference-test-node-${{matrix.runner_index}}-results |
||||
path: '**/build/test-results/referenceTests/TEST-*.xml' |
||||
- name: Publish Test Report |
||||
uses: mikepenz/action-junit-report@v4 |
||||
if: success() || failure() # always run even if the build step fails |
||||
with: |
||||
report_paths: '**/build/test-results/referenceTest/TEST-*.xml' |
||||
reference-tests: |
||||
runs-on: ubuntu-22.04 |
||||
needs: [ referenceTestEthereum ] |
||||
permissions: |
||||
checks: write |
||||
statuses: write |
||||
steps: |
||||
- name: consolidation |
||||
run: echo "consolidating statuses" |
||||
|
@ -1,24 +0,0 @@ |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
# Hyperledger Repolinter Action |
||||
name: Repolinter |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
push: |
||||
branches: |
||||
- master |
||||
- main |
||||
pull_request: |
||||
branches: |
||||
- master |
||||
- main |
||||
|
||||
jobs: |
||||
build: |
||||
runs-on: [besu-research-ubuntu-16] |
||||
container: ghcr.io/todogroup/repolinter:v0.10.1 |
||||
steps: |
||||
- name: Checkout Code |
||||
uses: actions/checkout@v4 |
||||
- name: Lint Repo |
||||
run: bundle exec /app/bin/repolinter.js --rulesetUrl https://raw.githubusercontent.com/hyperledger-labs/hyperledger-community-management-tools/main/repo_structure/repolint.json --format markdown |
@ -0,0 +1,40 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.PrivateCondition; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivSyncingTransactions; |
||||
|
||||
public class PrivateSyncingStatusCondition implements PrivateCondition { |
||||
|
||||
private final PrivSyncingTransactions transaction; |
||||
private final boolean syncingMiningStatus; |
||||
|
||||
public PrivateSyncingStatusCondition( |
||||
final PrivSyncingTransactions transaction, final boolean syncingStatus) { |
||||
this.transaction = transaction; |
||||
this.syncingMiningStatus = syncingStatus; |
||||
} |
||||
|
||||
@Override |
||||
public void verify(final PrivacyNode node) { |
||||
WaitUtils.waitFor( |
||||
10, () -> assertThat(node.execute(transaction)).isEqualTo(syncingMiningStatus)); |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.web3j.protocol.core.methods.response.EthSyncing; |
||||
|
||||
public class PrivSyncingTransactions implements Transaction<Boolean> { |
||||
|
||||
PrivSyncingTransactions() {} |
||||
|
||||
@Override |
||||
public Boolean execute(final NodeRequests node) { |
||||
try { |
||||
EthSyncing response = node.eth().ethSyncing().send(); |
||||
assertThat(response).isNotNull(); |
||||
return response.isSyncing(); |
||||
} catch (final IOException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.cli.options.stable; |
||||
|
||||
import static java.util.Arrays.asList; |
||||
import static org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration.DEFAULT_GRAPHQL_HTTP_PORT; |
||||
|
||||
import org.hyperledger.besu.cli.DefaultCommandValues; |
||||
import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty; |
||||
import org.hyperledger.besu.cli.util.CommandLineUtils; |
||||
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; |
||||
|
||||
import java.util.List; |
||||
|
||||
import com.google.common.base.Strings; |
||||
import org.slf4j.Logger; |
||||
import picocli.CommandLine; |
||||
|
||||
/** Handles configuration options for the GraphQL HTTP service in Besu. */ |
||||
public class GraphQlOptions { |
||||
@CommandLine.Option( |
||||
names = {"--graphql-http-enabled"}, |
||||
description = "Set to start the GraphQL HTTP service (default: ${DEFAULT-VALUE})") |
||||
private final Boolean isGraphQLHttpEnabled = false; |
||||
|
||||
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
|
||||
@CommandLine.Option( |
||||
names = {"--graphql-http-host"}, |
||||
paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, |
||||
description = "Host for GraphQL HTTP to listen on (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private String graphQLHttpHost; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--graphql-http-port"}, |
||||
paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, |
||||
description = "Port for GraphQL HTTP to listen on (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private final Integer graphQLHttpPort = DEFAULT_GRAPHQL_HTTP_PORT; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--graphql-http-cors-origins"}, |
||||
description = "Comma separated origin domain URLs for CORS validation (default: none)") |
||||
private final CorsAllowedOriginsProperty graphQLHttpCorsAllowedOrigins = |
||||
new CorsAllowedOriginsProperty(); |
||||
|
||||
/** |
||||
* Validates the GraphQL HTTP options. |
||||
* |
||||
* @param logger Logger instance |
||||
* @param commandLine CommandLine instance |
||||
*/ |
||||
public void validate(final Logger logger, final CommandLine commandLine) { |
||||
CommandLineUtils.checkOptionDependencies( |
||||
logger, |
||||
commandLine, |
||||
"--graphql-http-enabled", |
||||
!isGraphQLHttpEnabled, |
||||
asList("--graphql-http-cors-origins", "--graphql-http-host", "--graphql-http-port")); |
||||
} |
||||
|
||||
/** |
||||
* Creates a GraphQLConfiguration based on the provided options. |
||||
* |
||||
* @param hostsAllowlist List of hosts allowed |
||||
* @param defaultHostAddress Default host address |
||||
* @param timoutSec Timeout in seconds |
||||
* @return A GraphQLConfiguration instance |
||||
*/ |
||||
public GraphQLConfiguration graphQLConfiguration( |
||||
final List<String> hostsAllowlist, final String defaultHostAddress, final Long timoutSec) { |
||||
final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); |
||||
graphQLConfiguration.setEnabled(isGraphQLHttpEnabled); |
||||
graphQLConfiguration.setHost( |
||||
Strings.isNullOrEmpty(graphQLHttpHost) ? defaultHostAddress : graphQLHttpHost); |
||||
graphQLConfiguration.setPort(graphQLHttpPort); |
||||
graphQLConfiguration.setHostsAllowlist(hostsAllowlist); |
||||
graphQLConfiguration.setCorsAllowedDomains(graphQLHttpCorsAllowedOrigins); |
||||
graphQLConfiguration.setHttpTimeoutSec(timoutSec); |
||||
return graphQLConfiguration; |
||||
} |
||||
|
||||
/** |
||||
* Checks if GraphQL over HTTP is enabled. |
||||
* |
||||
* @return true if enabled, false otherwise |
||||
*/ |
||||
public Boolean isGraphQLHttpEnabled() { |
||||
return isGraphQLHttpEnabled; |
||||
} |
||||
|
||||
/** |
||||
* Returns the port for GraphQL over HTTP. |
||||
* |
||||
* @return The port number |
||||
*/ |
||||
public Integer getGraphQLHttpPort() { |
||||
return graphQLHttpPort; |
||||
} |
||||
} |
@ -0,0 +1,206 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.cli.options.stable; |
||||
|
||||
import org.hyperledger.besu.cli.DefaultCommandValues; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration; |
||||
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; |
||||
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; |
||||
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfigurationBuilder; |
||||
import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningConfiguration; |
||||
|
||||
import java.nio.file.Path; |
||||
import java.util.Optional; |
||||
|
||||
import org.slf4j.Logger; |
||||
import picocli.CommandLine; |
||||
|
||||
/** Handles configuration options for permissions in Besu. */ |
||||
public class PermissionsOptions { |
||||
@CommandLine.Option( |
||||
names = {"--permissions-nodes-config-file-enabled"}, |
||||
description = "Enable node level permissions (default: ${DEFAULT-VALUE})") |
||||
private final Boolean permissionsNodesEnabled = false; |
||||
|
||||
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-nodes-config-file"}, |
||||
description = |
||||
"Node permissioning config TOML file (default: a file named \"permissions_config.toml\" in the Besu data folder)") |
||||
private String nodePermissionsConfigFile = null; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-accounts-config-file-enabled"}, |
||||
description = "Enable account level permissions (default: ${DEFAULT-VALUE})") |
||||
private final Boolean permissionsAccountsEnabled = false; |
||||
|
||||
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-accounts-config-file"}, |
||||
description = |
||||
"Account permissioning config TOML file (default: a file named \"permissions_config.toml\" in the Besu data folder)") |
||||
private String accountPermissionsConfigFile = null; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-nodes-contract-address"}, |
||||
description = "Address of the node permissioning smart contract", |
||||
arity = "1") |
||||
private final Address permissionsNodesContractAddress = null; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-nodes-contract-version"}, |
||||
description = "Version of the EEA Node Permissioning interface (default: ${DEFAULT-VALUE})") |
||||
private final Integer permissionsNodesContractVersion = 1; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-nodes-contract-enabled"}, |
||||
description = "Enable node level permissions via smart contract (default: ${DEFAULT-VALUE})") |
||||
private final Boolean permissionsNodesContractEnabled = false; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-accounts-contract-address"}, |
||||
description = "Address of the account permissioning smart contract", |
||||
arity = "1") |
||||
private final Address permissionsAccountsContractAddress = null; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--permissions-accounts-contract-enabled"}, |
||||
description = |
||||
"Enable account level permissions via smart contract (default: ${DEFAULT-VALUE})") |
||||
private final Boolean permissionsAccountsContractEnabled = false; |
||||
|
||||
/** |
||||
* Creates a PermissioningConfiguration based on the provided options. |
||||
* |
||||
* @param jsonRpcHttpOptions The JSON-RPC HTTP options |
||||
* @param rpcWebsocketOptions The RPC websocket options |
||||
* @param enodeDnsConfiguration The enode DNS configuration |
||||
* @param dataPath The data path |
||||
* @param logger The logger |
||||
* @param commandLine The command line |
||||
* @return An Optional PermissioningConfiguration instance |
||||
* @throws Exception If an error occurs while creating the configuration |
||||
*/ |
||||
public Optional<PermissioningConfiguration> permissioningConfiguration( |
||||
final JsonRpcHttpOptions jsonRpcHttpOptions, |
||||
final RpcWebsocketOptions rpcWebsocketOptions, |
||||
final EnodeDnsConfiguration enodeDnsConfiguration, |
||||
final Path dataPath, |
||||
final Logger logger, |
||||
final CommandLine commandLine) |
||||
throws Exception { |
||||
if (!(localPermissionsEnabled() || contractPermissionsEnabled())) { |
||||
if (jsonRpcHttpOptions.getRpcHttpApis().contains(RpcApis.PERM.name()) |
||||
|| rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.PERM.name())) { |
||||
logger.warn( |
||||
"Permissions are disabled. Cannot enable PERM APIs when not using Permissions."); |
||||
} |
||||
return Optional.empty(); |
||||
} |
||||
|
||||
final Optional<LocalPermissioningConfiguration> localPermissioningConfigurationOptional; |
||||
if (localPermissionsEnabled()) { |
||||
final Optional<String> nodePermissioningConfigFile = |
||||
Optional.ofNullable(nodePermissionsConfigFile); |
||||
final Optional<String> accountPermissioningConfigFile = |
||||
Optional.ofNullable(accountPermissionsConfigFile); |
||||
|
||||
final LocalPermissioningConfiguration localPermissioningConfiguration = |
||||
PermissioningConfigurationBuilder.permissioningConfiguration( |
||||
permissionsNodesEnabled, |
||||
enodeDnsConfiguration, |
||||
nodePermissioningConfigFile.orElse(getDefaultPermissioningFilePath(dataPath)), |
||||
permissionsAccountsEnabled, |
||||
accountPermissioningConfigFile.orElse(getDefaultPermissioningFilePath(dataPath))); |
||||
|
||||
localPermissioningConfigurationOptional = Optional.of(localPermissioningConfiguration); |
||||
} else { |
||||
if (nodePermissionsConfigFile != null && !permissionsNodesEnabled) { |
||||
logger.warn( |
||||
"Node permissioning config file set {} but no permissions enabled", |
||||
nodePermissionsConfigFile); |
||||
} |
||||
|
||||
if (accountPermissionsConfigFile != null && !permissionsAccountsEnabled) { |
||||
logger.warn( |
||||
"Account permissioning config file set {} but no permissions enabled", |
||||
accountPermissionsConfigFile); |
||||
} |
||||
localPermissioningConfigurationOptional = Optional.empty(); |
||||
} |
||||
|
||||
final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = |
||||
SmartContractPermissioningConfiguration.createDefault(); |
||||
|
||||
if (Boolean.TRUE.equals(permissionsNodesContractEnabled)) { |
||||
if (permissionsNodesContractAddress == null) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
"No node permissioning contract address specified. Cannot enable smart contract based node permissioning."); |
||||
} else { |
||||
smartContractPermissioningConfiguration.setSmartContractNodeAllowlistEnabled( |
||||
permissionsNodesContractEnabled); |
||||
smartContractPermissioningConfiguration.setNodeSmartContractAddress( |
||||
permissionsNodesContractAddress); |
||||
smartContractPermissioningConfiguration.setNodeSmartContractInterfaceVersion( |
||||
permissionsNodesContractVersion); |
||||
} |
||||
} else if (permissionsNodesContractAddress != null) { |
||||
logger.warn( |
||||
"Node permissioning smart contract address set {} but smart contract node permissioning is disabled.", |
||||
permissionsNodesContractAddress); |
||||
} |
||||
|
||||
if (Boolean.TRUE.equals(permissionsAccountsContractEnabled)) { |
||||
if (permissionsAccountsContractAddress == null) { |
||||
throw new CommandLine.ParameterException( |
||||
commandLine, |
||||
"No account permissioning contract address specified. Cannot enable smart contract based account permissioning."); |
||||
} else { |
||||
smartContractPermissioningConfiguration.setSmartContractAccountAllowlistEnabled( |
||||
permissionsAccountsContractEnabled); |
||||
smartContractPermissioningConfiguration.setAccountSmartContractAddress( |
||||
permissionsAccountsContractAddress); |
||||
} |
||||
} else if (permissionsAccountsContractAddress != null) { |
||||
logger.warn( |
||||
"Account permissioning smart contract address set {} but smart contract account permissioning is disabled.", |
||||
permissionsAccountsContractAddress); |
||||
} |
||||
|
||||
final PermissioningConfiguration permissioningConfiguration = |
||||
new PermissioningConfiguration( |
||||
localPermissioningConfigurationOptional, |
||||
Optional.of(smartContractPermissioningConfiguration)); |
||||
|
||||
return Optional.of(permissioningConfiguration); |
||||
} |
||||
|
||||
private boolean localPermissionsEnabled() { |
||||
return permissionsAccountsEnabled || permissionsNodesEnabled; |
||||
} |
||||
|
||||
private boolean contractPermissionsEnabled() { |
||||
return permissionsNodesContractEnabled || permissionsAccountsContractEnabled; |
||||
} |
||||
|
||||
private String getDefaultPermissioningFilePath(final Path dataPath) { |
||||
return dataPath |
||||
+ System.getProperty("file.separator") |
||||
+ DefaultCommandValues.PERMISSIONING_CONFIG_LOCATION; |
||||
} |
||||
} |
@ -0,0 +1,93 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.cli.options; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
import org.hyperledger.besu.cli.CommandTestAbstract; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
@ExtendWith(MockitoExtension.class) |
||||
public class GraphQlOptionsTest extends CommandTestAbstract { |
||||
@Test |
||||
public void graphQLHttpEnabledPropertyMustBeUsed() { |
||||
parseCommand("--graphql-http-enabled"); |
||||
|
||||
verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(graphQLConfigArgumentCaptor.getValue().isEnabled()).isTrue(); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void graphQLHttpHostAndPortOptionsMustBeUsed() { |
||||
|
||||
final String host = "1.2.3.4"; |
||||
final int port = 1234; |
||||
parseCommand( |
||||
"--graphql-http-enabled", |
||||
"--graphql-http-host", |
||||
host, |
||||
"--graphql-http-port", |
||||
String.valueOf(port)); |
||||
|
||||
verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||
assertThat(graphQLConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void graphQLHttpHostMayBeLocalhost() { |
||||
|
||||
final String host = "localhost"; |
||||
parseCommand("--graphql-http-enabled", "--graphql-http-host", host); |
||||
|
||||
verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void graphQLHttpHostMayBeIPv6() { |
||||
|
||||
final String host = "2600:DB8::8545"; |
||||
parseCommand("--graphql-http-enabled", "--graphql-http-host", host); |
||||
|
||||
verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); |
||||
|
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
} |
@ -0,0 +1,453 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.cli.options; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
import org.hyperledger.besu.cli.CommandTestAbstract; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; |
||||
import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; |
||||
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; |
||||
import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningConfiguration; |
||||
import org.hyperledger.besu.plugin.data.EnodeURL; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URL; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import com.google.common.collect.Lists; |
||||
import com.google.common.io.Resources; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.junit.jupiter.api.io.TempDir; |
||||
import org.mockito.Mockito; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
@ExtendWith(MockitoExtension.class) |
||||
public class PermissionsOptionsTest extends CommandTestAbstract { |
||||
private static final String PERMISSIONING_CONFIG_TOML = "/permissioning_config.toml"; |
||||
|
||||
@Test |
||||
public void errorIsRaisedIfStaticNodesAreNotAllowed(final @TempDir Path testFolder) |
||||
throws IOException { |
||||
final Path staticNodesFile = testFolder.resolve("static-nodes.json"); |
||||
final Path permissioningConfig = testFolder.resolve("permissioning.json"); |
||||
|
||||
final EnodeURL staticNodeURI = |
||||
EnodeURLImpl.builder() |
||||
.nodeId( |
||||
"50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa") |
||||
.ipAddress("127.0.0.1") |
||||
.useDefaultPorts() |
||||
.build(); |
||||
|
||||
final EnodeURL allowedNode = |
||||
EnodeURLImpl.builder() |
||||
.nodeId( |
||||
"50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa") |
||||
.useDefaultPorts() |
||||
.ipAddress("127.0.0.1") |
||||
.listeningPort(30304) |
||||
.build(); |
||||
|
||||
Files.write(staticNodesFile, ("[\"" + staticNodeURI.toString() + "\"]").getBytes(UTF_8)); |
||||
Files.write( |
||||
permissioningConfig, |
||||
("nodes-allowlist=[\"" + allowedNode.toString() + "\"]").getBytes(UTF_8)); |
||||
|
||||
parseCommand( |
||||
"--data-path=" + testFolder, |
||||
"--bootnodes", |
||||
"--permissions-nodes-config-file-enabled=true", |
||||
"--permissions-nodes-config-file=" + permissioningConfig); |
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.contains(staticNodeURI.toString(), "not in nodes-allowlist"); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissionsSmartContractWithoutOptionMustError() { |
||||
parseCommand("--permissions-nodes-contract-address"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.startsWith("Missing required parameter for option '--permissions-nodes-contract-address'"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissionsEnabledWithoutContractAddressMustError() { |
||||
parseCommand("--permissions-nodes-contract-enabled"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.contains("No node permissioning contract address specified"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissionsEnabledWithInvalidContractAddressMustError() { |
||||
parseCommand( |
||||
"--permissions-nodes-contract-enabled", |
||||
"--permissions-nodes-contract-address", |
||||
"invalid-smart-contract-address"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissionsEnabledWithTooShortContractAddressMustError() { |
||||
parseCommand( |
||||
"--permissions-nodes-contract-enabled", "--permissions-nodes-contract-address", "0x1234"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissionsSmartContractMustUseOption() { |
||||
|
||||
final String smartContractAddress = "0x0000000000000000000000000000000000001234"; |
||||
|
||||
parseCommand( |
||||
"--permissions-nodes-contract-enabled", |
||||
"--permissions-nodes-contract-address", |
||||
smartContractAddress); |
||||
final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = |
||||
new SmartContractPermissioningConfiguration(); |
||||
smartContractPermissioningConfiguration.setNodeSmartContractAddress( |
||||
Address.fromHexString(smartContractAddress)); |
||||
smartContractPermissioningConfiguration.setSmartContractNodeAllowlistEnabled(true); |
||||
|
||||
verify(mockRunnerBuilder) |
||||
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
final PermissioningConfiguration config = |
||||
permissioningConfigurationArgumentCaptor.getValue().get(); |
||||
assertThat(config.getSmartContractConfig().get()) |
||||
.usingRecursiveComparison() |
||||
.isEqualTo(smartContractPermissioningConfiguration); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissionsContractVersionDefaultValue() { |
||||
final SmartContractPermissioningConfiguration expectedConfig = |
||||
new SmartContractPermissioningConfiguration(); |
||||
expectedConfig.setNodeSmartContractAddress( |
||||
Address.fromHexString("0x0000000000000000000000000000000000001234")); |
||||
expectedConfig.setSmartContractNodeAllowlistEnabled(true); |
||||
expectedConfig.setNodeSmartContractInterfaceVersion(1); |
||||
|
||||
parseCommand( |
||||
"--permissions-nodes-contract-enabled", |
||||
"--permissions-nodes-contract-address", |
||||
"0x0000000000000000000000000000000000001234"); |
||||
|
||||
verify(mockRunnerBuilder) |
||||
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
final PermissioningConfiguration config = |
||||
permissioningConfigurationArgumentCaptor.getValue().get(); |
||||
assertThat(config.getSmartContractConfig().get()) |
||||
.usingRecursiveComparison() |
||||
.isEqualTo(expectedConfig); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissionsContractVersionSetsValue() { |
||||
final SmartContractPermissioningConfiguration expectedConfig = |
||||
new SmartContractPermissioningConfiguration(); |
||||
expectedConfig.setNodeSmartContractAddress( |
||||
Address.fromHexString("0x0000000000000000000000000000000000001234")); |
||||
expectedConfig.setSmartContractNodeAllowlistEnabled(true); |
||||
expectedConfig.setNodeSmartContractInterfaceVersion(2); |
||||
|
||||
parseCommand( |
||||
"--permissions-nodes-contract-enabled", |
||||
"--permissions-nodes-contract-address", |
||||
"0x0000000000000000000000000000000000001234", |
||||
"--permissions-nodes-contract-version", |
||||
"2"); |
||||
|
||||
verify(mockRunnerBuilder) |
||||
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
final PermissioningConfiguration config = |
||||
permissioningConfigurationArgumentCaptor.getValue().get(); |
||||
assertThat(config.getSmartContractConfig().get()) |
||||
.usingRecursiveComparison() |
||||
.isEqualTo(expectedConfig); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissionsSmartContractWithoutOptionMustError() { |
||||
parseCommand("--permissions-accounts-contract-address"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.startsWith( |
||||
"Missing required parameter for option '--permissions-accounts-contract-address'"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissionsEnabledWithoutContractAddressMustError() { |
||||
parseCommand("--permissions-accounts-contract-enabled"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.contains("No account permissioning contract address specified"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissionsEnabledWithInvalidContractAddressMustError() { |
||||
parseCommand( |
||||
"--permissions-accounts-contract-enabled", |
||||
"--permissions-accounts-contract-address", |
||||
"invalid-smart-contract-address"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissionsEnabledWithTooShortContractAddressMustError() { |
||||
parseCommand( |
||||
"--permissions-accounts-contract-enabled", |
||||
"--permissions-accounts-contract-address", |
||||
"0x1234"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissionsSmartContractMustUseOption() { |
||||
final String smartContractAddress = "0x0000000000000000000000000000000000001234"; |
||||
|
||||
parseCommand( |
||||
"--permissions-accounts-contract-enabled", |
||||
"--permissions-accounts-contract-address", |
||||
smartContractAddress); |
||||
final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = |
||||
new SmartContractPermissioningConfiguration(); |
||||
smartContractPermissioningConfiguration.setAccountSmartContractAddress( |
||||
Address.fromHexString(smartContractAddress)); |
||||
smartContractPermissioningConfiguration.setSmartContractAccountAllowlistEnabled(true); |
||||
|
||||
verify(mockRunnerBuilder) |
||||
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); |
||||
final PermissioningConfiguration permissioningConfiguration = |
||||
permissioningConfigurationArgumentCaptor.getValue().get(); |
||||
assertThat(permissioningConfiguration.getSmartContractConfig()).isPresent(); |
||||
|
||||
final SmartContractPermissioningConfiguration effectiveSmartContractConfig = |
||||
permissioningConfiguration.getSmartContractConfig().get(); |
||||
assertThat(effectiveSmartContractConfig.isSmartContractAccountAllowlistEnabled()).isTrue(); |
||||
assertThat(effectiveSmartContractConfig.getAccountSmartContractAddress()) |
||||
.isEqualTo(Address.fromHexString(smartContractAddress)); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissioningTomlPathWithoutOptionMustDisplayUsage() { |
||||
parseCommand("--permissions-nodes-config-file"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.startsWith("Missing required parameter for option '--permissions-nodes-config-file'"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissioningTomlPathWithoutOptionMustDisplayUsage() { |
||||
parseCommand("--permissions-accounts-config-file"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)) |
||||
.startsWith("Missing required parameter for option '--permissions-accounts-config-file'"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissioningEnabledWithNonexistentConfigFileMustError() { |
||||
parseCommand( |
||||
"--permissions-nodes-config-file-enabled", |
||||
"--permissions-nodes-config-file", |
||||
"file-does-not-exist"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).contains("Configuration file does not exist"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissioningEnabledWithNonexistentConfigFileMustError() { |
||||
parseCommand( |
||||
"--permissions-accounts-config-file-enabled", |
||||
"--permissions-accounts-config-file", |
||||
"file-does-not-exist"); |
||||
|
||||
Mockito.verifyNoInteractions(mockRunnerBuilder); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).contains("Configuration file does not exist"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissioningTomlFileWithNoPermissionsEnabledMustNotError() throws IOException { |
||||
|
||||
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); |
||||
final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); |
||||
parseCommand("--permissions-nodes-config-file", permToml.toString()); |
||||
|
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissioningTomlFileWithNoPermissionsEnabledMustNotError() |
||||
throws IOException { |
||||
|
||||
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); |
||||
final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); |
||||
parseCommand("--permissions-accounts-config-file", permToml.toString()); |
||||
|
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void defaultPermissionsTomlFileWithNoPermissionsEnabledMustNotError() { |
||||
parseCommand("--p2p-enabled", "false"); |
||||
|
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).doesNotContain("no permissions enabled"); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void nodePermissioningTomlPathMustUseOption() throws IOException { |
||||
final List<EnodeURL> allowedNodes = |
||||
Lists.newArrayList( |
||||
EnodeURLImpl.fromString( |
||||
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567"), |
||||
EnodeURLImpl.fromString( |
||||
"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568")); |
||||
|
||||
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); |
||||
final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); |
||||
|
||||
final String allowedNodesString = |
||||
allowedNodes.stream().map(Object::toString).collect(Collectors.joining(",")); |
||||
parseCommand( |
||||
"--permissions-nodes-config-file-enabled", |
||||
"--permissions-nodes-config-file", |
||||
permToml.toString(), |
||||
"--bootnodes", |
||||
allowedNodesString); |
||||
final LocalPermissioningConfiguration localPermissioningConfiguration = |
||||
LocalPermissioningConfiguration.createDefault(); |
||||
localPermissioningConfiguration.setNodePermissioningConfigFilePath(permToml.toString()); |
||||
localPermissioningConfiguration.setNodeAllowlist(allowedNodes); |
||||
|
||||
verify(mockRunnerBuilder) |
||||
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); |
||||
verify(mockRunnerBuilder).build(); |
||||
|
||||
final PermissioningConfiguration config = |
||||
permissioningConfigurationArgumentCaptor.getValue().get(); |
||||
assertThat(config.getLocalConfig().get()) |
||||
.usingRecursiveComparison() |
||||
.isEqualTo(localPermissioningConfiguration); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void accountPermissioningTomlPathMustUseOption() throws IOException { |
||||
|
||||
final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); |
||||
final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); |
||||
|
||||
parseCommand( |
||||
"--permissions-accounts-config-file-enabled", |
||||
"--permissions-accounts-config-file", |
||||
permToml.toString()); |
||||
final LocalPermissioningConfiguration localPermissioningConfiguration = |
||||
LocalPermissioningConfiguration.createDefault(); |
||||
localPermissioningConfiguration.setAccountPermissioningConfigFilePath(permToml.toString()); |
||||
localPermissioningConfiguration.setAccountAllowlist( |
||||
Collections.singletonList("0x0000000000000000000000000000000000000009")); |
||||
|
||||
verify(mockRunnerBuilder) |
||||
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); |
||||
final PermissioningConfiguration permissioningConfiguration = |
||||
permissioningConfigurationArgumentCaptor.getValue().get(); |
||||
assertThat(permissioningConfiguration.getLocalConfig()).isPresent(); |
||||
|
||||
final LocalPermissioningConfiguration effectiveLocalPermissioningConfig = |
||||
permissioningConfiguration.getLocalConfig().get(); |
||||
assertThat(effectiveLocalPermissioningConfig.isAccountAllowlistEnabled()).isTrue(); |
||||
assertThat(effectiveLocalPermissioningConfig.getAccountPermissioningConfigFilePath()) |
||||
.isEqualTo(permToml.toString()); |
||||
|
||||
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); |
||||
assertThat(commandOutput.toString(UTF_8)).isEmpty(); |
||||
} |
||||
} |
@ -1,39 +0,0 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.util; |
||||
|
||||
import java.util.concurrent.Executors; |
||||
import java.util.concurrent.ScheduledExecutorService; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import java.util.function.Consumer; |
||||
|
||||
public class LogUtil { |
||||
static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); |
||||
|
||||
public static void throttledLog( |
||||
final Consumer<String> logger, |
||||
final String logMessage, |
||||
final AtomicBoolean shouldLog, |
||||
final int logRepeatDelay) { |
||||
|
||||
if (shouldLog.compareAndSet(true, false)) { |
||||
logger.accept(logMessage); |
||||
|
||||
final Runnable runnable = () -> shouldLog.set(true); |
||||
executor.schedule(runnable, logRepeatDelay, TimeUnit.SECONDS); |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue