diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 09148c9a40c..4e48c183ca7 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -13,14 +13,15 @@ on: jobs: build: runs-on: ubuntu-latest - container: - image: openjdk:8-jdk steps: - uses: actions/checkout@v4 - - name: Setup System Tools - run: | - apt update -y - apt install -y gnupg2 curl + + - name: Setup Java & Gradle + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: 'gradle' - name: Verify files run: | @@ -28,21 +29,17 @@ jobs: gpg2 --verify SHA256SUMS.asc && sha256sum --check SHA256SUMS.asc - uses: actions/cache@v4 - name: Cache Gradle - id: cache-gradle + name: Cache Gradle Wrapper + id: cache-gradle-wrapper with: path: | - .gradle/caches - gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Get gradle wrapper and build dependencies - if: steps.cache-gradle.outputs.cache-hit != 'true' + gradle/wrapper/gradle-wrapper.jar + key: gradle-wrapper-v1 + + - name: Get Gradle wrapper + if: steps.cache-gradle-wrapper.outputs.cache-hit != 'true' run: | ./configure.sh - ./gradlew --no-daemon dependencies - name: Build run: | @@ -56,36 +53,27 @@ jobs: rskj-core/build smell-test: - needs: unit-tests + needs: unit-tests-java17 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup Java JDK - uses: actions/setup-java@v3 + - name: Setup Java & Gradle + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' + cache: 'gradle' - - uses: actions/cache@v4 - name: Restore Gradle cache - id: cache-gradle + - uses: actions/cache/restore@v4 + name: Restore Gradle Wrapper with: path: | - .gradle/caches - gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Get gradle wrapper and build dependencies - run: | - if [ ! -f gradle/wrapper/gradle-wrapper.jar ]; then - ./configure.sh - ./gradlew --no-daemon dependencies - fi + gradle/wrapper/gradle-wrapper.jar + key: gradle-wrapper-v1 + fail-on-cache-miss: true - name: Download build artifacts uses: actions/download-artifact@v4 @@ -192,28 +180,20 @@ jobs: run: | node --unhandled-rejections=strict generateBtcBlocks.js - - name: Setup Java JDK - uses: actions/setup-java@v3 + - name: Setup Java & Gradle + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' + cache: 'gradle' - - uses: actions/cache@v4 - name: Cache Gradle - id: cache-gradle + - uses: actions/cache/restore@v4 + name: Restore Gradle Wrapper with: path: | - .gradle/caches - gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Get gradle wrapper and build dependencies - if: steps.cache-gradle.outputs.cache-hit != 'true' - run: | - ./configure.sh - ./gradlew --no-daemon dependencies + gradle/wrapper/gradle-wrapper.jar + key: gradle-wrapper-v1 + fail-on-cache-miss: true - name: Download build artifacts uses: actions/download-artifact@v4 @@ -247,35 +227,26 @@ jobs: npm test kill $rskpid - unit-tests: + unit-tests-java17: needs: build runs-on: ubuntu-latest - container: - image: openjdk:8-jdk steps: - uses: actions/checkout@v4 - - name: Setup System Tools - run: | - apt update -y - apt install -y curl + - name: Setup Java & Gradle + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: 'gradle' - - uses: actions/cache@v4 - name: Cache Gradle - id: cache-gradle + - uses: actions/cache/restore@v4 + name: Restore Gradle Wrapper with: path: | - .gradle/caches - gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Get gradle wrapper and build dependencies - if: steps.cache-gradle.outputs.cache-hit != 'true' - run: | - ./configure.sh - ./gradlew --no-daemon dependencies + gradle/wrapper/gradle-wrapper.jar + key: gradle-wrapper-v1 + fail-on-cache-miss: true - name: Run tests run: | @@ -295,34 +266,51 @@ jobs: path: | rskj-core/build/reports/ - integration-tests: + unit-tests-java21: + needs: build runs-on: ubuntu-latest - container: - image: openjdk:8-jdk steps: - uses: actions/checkout@v4 - - name: Setup System Tools - run: | - apt update -y - apt install -y curl + - name: Setup Java & Gradle + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'gradle' - - uses: actions/cache@v4 - name: Cache Gradle - id: cache-gradle + - uses: actions/cache/restore@v4 + name: Restore Gradle Wrapper with: path: | - .gradle/caches - gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Get gradle wrapper and build dependencies - if: steps.cache-gradle.outputs.cache-hit != 'true' + gradle/wrapper/gradle-wrapper.jar + key: gradle-wrapper-v1 + fail-on-cache-miss: true + + - name: Run tests run: | - ./configure.sh - ./gradlew --no-daemon dependencies + ./gradlew --no-daemon --stacktrace test + + integration-tests: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Java & Gradle + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: 'gradle' + + - uses: actions/cache/restore@v4 + name: Restore Gradle Wrapper + with: + path: | + gradle/wrapper/gradle-wrapper.jar + key: gradle-wrapper-v1 + fail-on-cache-miss: true - name: Run tests run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fc104a50864..637f0af13e5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -26,20 +26,27 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Setup Java JDK + if: ${{ matrix.language == 'java' }} + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Before Index (java) if: ${{ matrix.language == 'java' }} run: ./configure.sh - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/rit.yml b/.github/workflows/rit.yml new file mode 100644 index 00000000000..889ea657130 --- /dev/null +++ b/.github/workflows/rit.yml @@ -0,0 +1,155 @@ +name: Rootstock Integration Tests + +on: + pull_request: + types: [ opened, synchronize, reopened ] + branches: ["master", "*-rc"] + workflow_dispatch: + inputs: + rit-branch: + description: 'Branch for Rootstock Integration Tests' + required: false + default: 'main' + powpeg-branch: + description: 'Branch for PowPeg Node' + required: false + default: 'master' + +jobs: + rootstock-integration-tests: + name: Rootstock Integration Tests + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout Repository # Step needed to access the PR description using github CLI + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + + - name: Set Branch Variables + id: set-branch-variables + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_event_input_powpeg_branch: ${{ github.event.inputs.powpeg-branch }} + github_event_input_rit_branch: ${{ github.event.inputs.rit-branch }} + github_event_name: ${{ github.event_name }} + github_event_pull_request_number: ${{ github.event.pull_request.number }} + github_head_ref: ${{ github.head_ref }} + github_ref_name: ${{ github.ref_name }} + run: | + PR_DESCRIPTION=pr-description.txt + + ALLOWED_BRANCH_CHARACTERS='[-./0-9A-Z_a-z]' + + default_rskj_branch=master + default_powpeg_branch=master + default_rit_branch=main + + get_branch_from_description() + { + _prefix=$1 + + # On lines matching "`$_prefix:...`", replace the lines with the + # thing in ... and print the result. + _search_re='\@`'$_prefix:$ALLOWED_BRANCH_CHARACTERS'\{1,\}`@' + _replace_re='s@.*`'$_prefix:'\('$ALLOWED_BRANCH_CHARACTERS'\{1,\}\)`.*@\1@p' + _branch=$(sed -n "$_search_re $_replace_re" "$PR_DESCRIPTION") + echo "$_branch" + } + + is_valid_branch_name() + { + echo "$1" | grep -qx "$ALLOWED_BRANCH_CHARACTERS\\{1,\\}" + } + + if [ "$github_event_name" = workflow_dispatch ]; then + RSKJ_BRANCH=$github_ref_name + POWPEG_BRANCH=${github_event_inputs_powpeg_branch:-$default_powpeg_branch} + RIT_BRANCH=${github_event_inputs_rit_branch:-$default_rit_branch} + elif [ "$github_event_name" = pull_request ]; then + gh pr view "$github_event_pull_request_number" --json body -q .body >"$PR_DESCRIPTION" + + RSKJ_BRANCH=$(get_branch_from_description rskj) + : ${RSKJ_BRANCH:=${github_head_ref:-$default_rskj_branch}} + + POWPEG_BRANCH=$(get_branch_from_description fed) + : ${POWPEG_BRANCH:=$default_powpeg_branch} + + RIT_BRANCH=$(get_branch_from_description rit) + : ${RIT_BRANCH:=$default_rit_branch} + else + RSKJ_BRANCH=$default_rskj_branch + POWPEG_BRANCH=$default_powpeg_branch + RIT_BRANCH=$default_rit_branch + fi + + if ! is_valid_branch_name "$RSKJ_BRANCH"; then + echo "rskj: invalid branch name: $RSKJ_BRANCH" >&2 + exit 1 + fi + if ! is_valid_branch_name "$POWPEG_BRANCH"; then + echo "fed: invalid branch name: $POWPEG_BRANCH" >&2 + exit 1 + fi + if ! is_valid_branch_name "$RIT_BRANCH"; then + echo "rit: invalid branch name: $RIT_BRANCH" >&2 + exit 1 + fi + + echo "RSKJ_BRANCH=$RSKJ_BRANCH" >> $GITHUB_ENV + echo "RIT_BRANCH=$RIT_BRANCH" >> $GITHUB_ENV + echo "POWPEG_BRANCH=$POWPEG_BRANCH" >> $GITHUB_ENV + + - name: Set Build URL + id: set-build-url + run: | + BUILD_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + echo "BUILD_URL=$BUILD_URL" >> $GITHUB_ENV + + - name: Sanitize Branch Name + id: sanitize-branch-name + env: + GITHUB_HEAD_REF: ${{ github.head_ref }} + run: | + # Delete non-alphanumeric characters and limit to 255 chars which is the branch limit in GitHub + SAFE_BRANCH_NAME=$(echo "${GITHUB_HEAD_REF}" | tr -cd '[:alnum:]_-' | cut -c1-255) + echo "SAFE_BRANCH_NAME=$SAFE_BRANCH_NAME" >> $GITHUB_ENV + + - name: Run Rootstock Integration Tests + uses: rsksmart/rootstock-integration-tests@497172fd38dcfaf48c77f9bb1eeb6617eef5eed6 #v1 + with: + rskj-branch: ${{ env.RSKJ_BRANCH }} + powpeg-node-branch: ${{ env.POWPEG_BRANCH }} + rit-branch: ${{ env.RIT_BRANCH }} + + - name: Send Slack Notification on Success + if: success() && github.event.pull_request.head.repo.owner.login == 'rsksmart' + uses: slackapi/slack-github-action@37ebaef184d7626c5f204ab8d3baff4262dd30f0 # v1.27.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.GHA_SLACK_NOTIFICATION_TOKEN }} + with: + channel-id: ${{ vars.GHA_SLACK_NOTIFICATION_CHANNEL }} + payload: | + { + "attachments": [ + { + "color": "good", + "text": "OK: :+1: *Pull request*: ${{ env.SAFE_BRANCH_NAME }} - [#${{ github.run_number }}] - (${{ env.BUILD_URL }}) - *Branches used* [rskj:`rsksmart#${{ env.RSKJ_BRANCH }}`] [fed:`${{ env.POWPEG_BRANCH }}`] [rootstock-integration-tests:`${{ env.RIT_BRANCH }}`]" + } + ] + } + + - name: Send Slack Notification on Failure + if: failure() && github.event.pull_request.head.repo.owner.login == 'rsksmart' + uses: slackapi/slack-github-action@37ebaef184d7626c5f204ab8d3baff4262dd30f0 # v1.27.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.GHA_SLACK_NOTIFICATION_TOKEN }} + with: + channel-id: ${{ vars.GHA_SLACK_NOTIFICATION_CHANNEL }} + payload: | + { + "attachments": [ + { + "color": "danger", + "text": "FAILED: :robot_face: *Pull request*: ${{ env.SAFE_BRANCH_NAME }} - [#${{ github.run_number }}] - (${{ env.BUILD_URL }}) - *Branches used* [rskj:`rsksmart#${{ env.RSKJ_BRANCH }}`] [fed:`${{ env.POWPEG_BRANCH }}`] [rootstock-integration-tests:`${{ env.RIT_BRANCH }}`]" + } + ] + } diff --git a/Dockerfile b/Dockerfile index 115ef18970d..8d75e05395c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:11-jdk-slim-buster AS build +FROM eclipse-temurin:17-jdk AS build RUN apt-get update -y && \ apt-get install -y git curl gnupg @@ -19,7 +19,7 @@ RUN gpg --keyserver https://secchannel.rsk.co/SUPPORT.asc --recv-keys 1DC9157991 modifier=$(sed -n 's/^modifier=//p' "$file" | tr -d "\"'") && \ cp "rskj-core/build/libs/rskj-core-$version_number-$modifier-all.jar" rsk.jar -FROM openjdk:11-jre-slim-buster +FROM eclipse-temurin:17-jre LABEL org.opencontainers.image.authors="ops@iovlabs.org" RUN useradd -ms /sbin/nologin -d /var/lib/rsk rsk diff --git a/build.gradle b/build.gradle index 87a3e53a0f3..9a423cc072c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id "org.sonarqube" version "2.7.1" + id "org.sonarqube" version "5.1.0.4882" } subprojects { @@ -7,4 +7,3 @@ subprojects { group = 'co.rsk' version = config.modifier?.trim() ? config.versionNumber + "-" + config.modifier : config.versionNumber } - diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 0cf43352580..08400597a2e 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1,5 +1,5 @@ - + true false @@ -64,19 +64,14 @@ - - - - - - - - + + + @@ -84,9 +79,9 @@ - - - + + + @@ -94,9 +89,9 @@ - - - + + + @@ -104,12 +99,9 @@ - - - - - - + + + @@ -120,12 +112,12 @@ - - - + + + - - + + @@ -136,12 +128,12 @@ - - - + + + - - + + @@ -152,6 +144,14 @@ + + + + + + + + @@ -658,25 +658,25 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -890,41 +890,41 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + @@ -980,6 +980,14 @@ + + + + + + + + @@ -996,14 +1004,6 @@ - - - - - - - - @@ -1012,135 +1012,105 @@ - - - + + + - - + + - - - + + + - - - - - - - + + - - - + + + - - - + + + - - - - + + - - - + + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - + + - - - + + + - - - - - - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -1167,41 +1137,46 @@ - - - + + + - - + + - - + + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -1225,12 +1200,12 @@ - - - + + + - - + + @@ -1238,12 +1213,9 @@ - - - - - - + + + @@ -1254,12 +1226,12 @@ - - - + + + - - + + @@ -1270,12 +1242,9 @@ - - - - - - + + + @@ -1286,12 +1255,12 @@ - - - + + + - - + + @@ -1302,6 +1271,14 @@ + + + + + + + + @@ -1347,35 +1324,35 @@ - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41300f2ff9f..6ccd39c0b61 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,6 +2,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip -distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c name=RskJ diff --git a/rskj-core/build.gradle b/rskj-core/build.gradle index ff9496bf1b3..c807b21457e 100644 --- a/rskj-core/build.gradle +++ b/rskj-core/build.gradle @@ -24,7 +24,7 @@ testing { suites { integrationTest(JvmTestSuite) { dependencies { - implementation project + implementation project() } } } @@ -86,8 +86,8 @@ repositories { } } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceCompatibility = 17 +targetCompatibility = 17 mainClassName = 'co.rsk.Start' applicationDefaultJvmArgs = ["-server", "-Xss32m", "-Xms3G", "-XX:+UseCompressedOops", "-XX:-OmitStackTraceInFastThrow"] @@ -115,7 +115,7 @@ ext { rocksdbJniVer : '7.10.2', slf4jVer : '1.7.36', logbackVer : '1.2.10', - jacksonVer : '2.13.3', + jacksonVer : '2.15.4', commonsLang3Ver : '3.12.0', typesafeConfigVer : '1.4.2', mapdbVer : '2.0-beta13', @@ -150,16 +150,17 @@ ext { ] testLibVersions = [ - junitVer : '5.8.1', - junitSuiteVer : '1.9.0', - mockitoVer : '4.6.1', - awaitilityVer : '4.2.0', - commonsIoVer : '2.11.0', - commonsCodecVer: '1.15', - jacksonVer : '2.13.3', - okhttpWsVer : '2.7.5', - - rskLllVer : '0.0.2', + junitVer : '5.10.3', + junitSuiteVer : '1.10.3', + mockitoInlineVer : '5.2.0', + mockitoJupiterVer : '5.12.0', + awaitilityVer : '4.2.0', + commonsIoVer : '2.11.0', + commonsCodecVer : '1.15', + jacksonVer : '2.15.4', + okhttpWsVer : '2.7.5', + + rskLllVer : '0.0.2', ] jmhLibVersions = [ @@ -171,8 +172,8 @@ ext { junitLib : "org.junit.jupiter:junit-jupiter-engine:${testLibVersions.junitVer}", junitParams : "org.junit.jupiter:junit-jupiter-params:${testLibVersions.junitVer}", junitSuite : "org.junit.platform:junit-platform-suite:${testLibVersions.junitSuiteVer}", - mockitoLib : "org.mockito:mockito-inline:${testLibVersions.mockitoVer}", - mockitoJupiter : "org.mockito:mockito-junit-jupiter:${testLibVersions.mockitoVer}", + mockitoLib : "org.mockito:mockito-inline:${testLibVersions.mockitoInlineVer}", + mockitoJupiter : "org.mockito:mockito-junit-jupiter:${testLibVersions.mockitoJupiterVer}", awaitilityLib : "org.awaitility:awaitility:${testLibVersions.awaitilityVer}", commonsIoLib : "commons-io:commons-io:${testLibVersions.commonsIoVer}", commonsCodecLib : "commons-codec:commons-codec:${testLibVersions.commonsCodecVer}", diff --git a/rskj-core/src/main/java/co/rsk/RskContext.java b/rskj-core/src/main/java/co/rsk/RskContext.java index 28dc0fa7f46..3ca9a4ac69e 100644 --- a/rskj-core/src/main/java/co/rsk/RskContext.java +++ b/rskj-core/src/main/java/co/rsk/RskContext.java @@ -21,6 +21,7 @@ import co.rsk.cli.CliArgs; import co.rsk.cli.RskCli; import co.rsk.config.*; +import co.rsk.config.mining.StableMinGasPriceSystemConfig; import co.rsk.core.*; import co.rsk.core.bc.*; import co.rsk.crypto.Keccak256; @@ -40,6 +41,8 @@ import co.rsk.metrics.HashRateCalculatorMining; import co.rsk.metrics.HashRateCalculatorNonMining; import co.rsk.mine.*; +import co.rsk.mine.gas.provider.MinGasPriceProvider; +import co.rsk.mine.gas.provider.MinGasPriceProviderFactory; import co.rsk.net.*; import co.rsk.net.discovery.KnownPeersHandler; import co.rsk.net.discovery.PeerExplorer; @@ -253,6 +256,7 @@ public class RskContext implements NodeContext, NodeBootstrapper { private TxQuotaChecker txQuotaChecker; private GasPriceTracker gasPriceTracker; private BlockChainFlusher blockChainFlusher; + private MinGasPriceProvider minGasPriceProvider; private final Map dbPathToDbKindMap = new HashMap<>(); private volatile boolean closed; @@ -1843,7 +1847,7 @@ private BlockToMineBuilder getBlockToMineBuilder() { getMinerClock(), getBlockFactory(), getBlockExecutor(), - new MinimumGasPriceCalculator(Coin.valueOf(getMiningConfig().getMinGasPriceTarget())), + new MinimumGasPriceCalculator(getMinGasPriceProvider()), new MinerUtils(), getBlockTxSignatureCache() ); @@ -1852,6 +1856,16 @@ private BlockToMineBuilder getBlockToMineBuilder() { return blockToMineBuilder; } + private MinGasPriceProvider getMinGasPriceProvider() { + if (minGasPriceProvider == null) { + long minGasPrice = getRskSystemProperties().minerMinGasPrice(); + StableMinGasPriceSystemConfig stableGasPriceSystemConfig = getRskSystemProperties().getStableGasPriceSystemConfig(); + minGasPriceProvider = MinGasPriceProviderFactory.create(minGasPrice, stableGasPriceSystemConfig, this::getEthModule); + } + logger.debug("MinGasPriceProvider type: {}", minGasPriceProvider.getType().name()); + return minGasPriceProvider; + } + private BlockNodeInformation getBlockNodeInformation() { if (blockNodeInformation == null) { blockNodeInformation = new BlockNodeInformation(); @@ -2143,7 +2157,6 @@ private MiningConfig getMiningConfig() { rskSystemProperties.coinbaseAddress(), rskSystemProperties.minerMinFeesNotifyInDollars(), rskSystemProperties.minerGasUnitInDollars(), - rskSystemProperties.minerMinGasPrice(), rskSystemProperties.getNetworkConstants().getUncleListLimit(), rskSystemProperties.getNetworkConstants().getUncleGenerationLimit(), new GasLimitConfig( @@ -2152,8 +2165,7 @@ private MiningConfig getMiningConfig() { rskSystemProperties.getForceTargetGasLimit() ), rskSystemProperties.isMinerServerFixedClock(), - rskSystemProperties.workSubmissionRateLimitInMills() - ); + rskSystemProperties.workSubmissionRateLimitInMills()); } return miningConfig; @@ -2208,4 +2220,4 @@ private void checkIfNotClosed() { throw new IllegalStateException("RSK Context is closed and cannot be in use anymore"); } } -} \ No newline at end of file +} diff --git a/rskj-core/src/main/java/co/rsk/config/MiningConfig.java b/rskj-core/src/main/java/co/rsk/config/MiningConfig.java index cc536c880b3..bbdf30b8a96 100644 --- a/rskj-core/src/main/java/co/rsk/config/MiningConfig.java +++ b/rskj-core/src/main/java/co/rsk/config/MiningConfig.java @@ -29,7 +29,6 @@ public class MiningConfig { private final RskAddress coinbaseAddress; private final double minFeesNotifyInDollars; private final double minerGasUnitInDollars; - private final long minGasPriceTarget; private final int uncleListLimit; private final int uncleGenerationLimit; private final GasLimitConfig gasLimit; @@ -37,12 +36,11 @@ public class MiningConfig { private final long workSubmissionRateLimitInMills; public MiningConfig(RskAddress coinbaseAddress, double minFeesNotifyInDollars, double minerGasUnitInDollars, - long minGasPriceTarget, int uncleListLimit, int uncleGenerationLimit, GasLimitConfig gasLimit, + int uncleListLimit, int uncleGenerationLimit, GasLimitConfig gasLimit, boolean isFixedClock, long workSubmissionRateLimitInMills) { this.coinbaseAddress = coinbaseAddress; this.minFeesNotifyInDollars = minFeesNotifyInDollars; this.minerGasUnitInDollars = minerGasUnitInDollars; - this.minGasPriceTarget= minGasPriceTarget; this.uncleListLimit = uncleListLimit; this.uncleGenerationLimit = uncleGenerationLimit; this.gasLimit = gasLimit; @@ -62,10 +60,6 @@ public double getGasUnitInDollars() { return minerGasUnitInDollars; } - public long getMinGasPriceTarget() { - return minGasPriceTarget; - } - public int getUncleListLimit() { return uncleListLimit; } diff --git a/rskj-core/src/main/java/co/rsk/config/NodeCliFlags.java b/rskj-core/src/main/java/co/rsk/config/NodeCliFlags.java index f1f05d598d5..a485d770ebc 100644 --- a/rskj-core/src/main/java/co/rsk/config/NodeCliFlags.java +++ b/rskj-core/src/main/java/co/rsk/config/NodeCliFlags.java @@ -31,7 +31,7 @@ public enum NodeCliFlags implements CliArg { DB_IMPORT("import", SystemProperties.PROPERTY_DB_IMPORT, true), VERIFY_CONFIG("verify-config", SystemProperties.PROPERTY_BC_VERIFY, true), PRINT_SYSTEM_INFO("print-system-info", SystemProperties.PROPERTY_PRINT_SYSTEM_INFO, true), - SKIP_JAVA_CHECK("skip-java-check", SystemProperties.PROPERTY_SKIP_JAVA_VERSION_CHECK, false), + SKIP_JAVA_CHECK("skip-java-check", SystemProperties.PROPERTY_CHECK_JAVA_VERSION, false), NETWORK_TESTNET("testnet", SystemProperties.PROPERTY_BC_CONFIG_NAME, "testnet"), NETWORK_REGTEST("regtest", SystemProperties.PROPERTY_BC_CONFIG_NAME, "regtest"), NETWORK_DEVNET("devnet", SystemProperties.PROPERTY_BC_CONFIG_NAME, "devnet"), diff --git a/rskj-core/src/main/java/co/rsk/config/mining/EthCallMinGasPriceSystemConfig.java b/rskj-core/src/main/java/co/rsk/config/mining/EthCallMinGasPriceSystemConfig.java new file mode 100644 index 00000000000..7c178321dc8 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/config/mining/EthCallMinGasPriceSystemConfig.java @@ -0,0 +1,48 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.config.mining; + +import com.typesafe.config.Config; + +public class EthCallMinGasPriceSystemConfig { + private static final String FROM_PROPERTY = "from"; + private static final String TO_PROPERTY = "to"; + private static final String DATA_PROPERTY = "data"; + private final String address; + private final String from; + private final String data; + + public EthCallMinGasPriceSystemConfig(Config config) { + address = config.getString(TO_PROPERTY); + from = config.getString(FROM_PROPERTY); + data = config.getString(DATA_PROPERTY); + } + + public String getAddress() { + return address; + } + + public String getFrom() { + return from; + } + + public String getData() { + return data; + } +} diff --git a/rskj-core/src/main/java/co/rsk/config/mining/HttpGetStableMinGasSystemConfig.java b/rskj-core/src/main/java/co/rsk/config/mining/HttpGetStableMinGasSystemConfig.java new file mode 100644 index 00000000000..07bdc20d90e --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/config/mining/HttpGetStableMinGasSystemConfig.java @@ -0,0 +1,52 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.config.mining; + +import com.typesafe.config.Config; + +import java.time.Duration; +import java.util.Objects; + +public class HttpGetStableMinGasSystemConfig { + private static final String URL_PROPERTY = "url"; + private static final String JSON_PATH = "jsonPath"; + private static final String TIMEOUT_PROPERTY = "timeout"; + + private final String url; + private final String jsonPath; + private final Duration timeout; + + public HttpGetStableMinGasSystemConfig(Config config) { + this.url = Objects.requireNonNull(config.getString(URL_PROPERTY)); + this.jsonPath = Objects.requireNonNull(config.getString(JSON_PATH)); + this.timeout = Objects.requireNonNull(config.getDuration(TIMEOUT_PROPERTY)); + } + + public String getUrl() { + return url; + } + + public String getJsonPath() { + return jsonPath; + } + + public Duration getTimeout() { + return timeout; + } + +} diff --git a/rskj-core/src/main/java/co/rsk/config/mining/StableMinGasPriceSystemConfig.java b/rskj-core/src/main/java/co/rsk/config/mining/StableMinGasPriceSystemConfig.java new file mode 100644 index 00000000000..36a3fc06318 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/config/mining/StableMinGasPriceSystemConfig.java @@ -0,0 +1,71 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.config.mining; + +import co.rsk.mine.gas.provider.MinGasPriceProviderType; +import com.typesafe.config.Config; + +import java.time.Duration; + +public class StableMinGasPriceSystemConfig { + public static final String STABLE_GAS_PRICE_CONFIG_PATH_PROPERTY = "miner.stableGasPrice"; + + private static final String ENABLED_PROPERTY = "enabled"; + private static final String REFRESH_RATE_PROPERTY = "refreshRate"; + private static final String MIN_STABLE_GAS_PRICE_PROPERTY = "minStableGasPrice"; + private static final String METHOD_PROPERTY = "source.method"; + private static final String PARAMS_PROPERTY = "source.params"; + + private final Duration refreshRate; + private final Long minStableGasPrice; + private final boolean enabled; + private final MinGasPriceProviderType method; + private final Config config; + + public StableMinGasPriceSystemConfig(Config config) { + this.enabled = config.hasPath(ENABLED_PROPERTY) && config.getBoolean(ENABLED_PROPERTY); + this.refreshRate = this.enabled && config.hasPath(REFRESH_RATE_PROPERTY) ? config.getDuration(REFRESH_RATE_PROPERTY) : Duration.ZERO; + this.minStableGasPrice = this.enabled && config.hasPath(MIN_STABLE_GAS_PRICE_PROPERTY) ? config.getLong(MIN_STABLE_GAS_PRICE_PROPERTY) : 0; + this.method = this.enabled ? config.getEnum(MinGasPriceProviderType.class, METHOD_PROPERTY) : MinGasPriceProviderType.FIXED; + this.config = config; + } + + public Duration getRefreshRate() { + return refreshRate; + } + + public Long getMinStableGasPrice() { + return minStableGasPrice; + } + + public boolean isEnabled() { + return enabled; + } + + public HttpGetStableMinGasSystemConfig getHttpGetConfig() { + return new HttpGetStableMinGasSystemConfig(config.getConfig(PARAMS_PROPERTY)); + } + + public EthCallMinGasPriceSystemConfig getEthCallConfig() { + return new EthCallMinGasPriceSystemConfig(config.getConfig(PARAMS_PROPERTY)); + } + + public MinGasPriceProviderType getMethod() { + return method; + } +} diff --git a/rskj-core/src/main/java/co/rsk/mine/MinimumGasPriceCalculator.java b/rskj-core/src/main/java/co/rsk/mine/MinimumGasPriceCalculator.java index d550e14db21..38c16a6c3e8 100644 --- a/rskj-core/src/main/java/co/rsk/mine/MinimumGasPriceCalculator.java +++ b/rskj-core/src/main/java/co/rsk/mine/MinimumGasPriceCalculator.java @@ -19,6 +19,7 @@ package co.rsk.mine; import co.rsk.core.Coin; +import co.rsk.mine.gas.provider.MinGasPriceProvider; /** * This is the implementation of RSKIP-09 @@ -26,14 +27,15 @@ */ public class MinimumGasPriceCalculator { - private final Coin targetMGP; + private MinGasPriceProvider minGasPriceProvider; - public MinimumGasPriceCalculator(Coin targetMGP) { - this.targetMGP = targetMGP; + public MinimumGasPriceCalculator(MinGasPriceProvider minGasPriceProvider) { + this.minGasPriceProvider = minGasPriceProvider; } public Coin calculate(Coin previousMGP) { BlockGasPriceRange priceRange = new BlockGasPriceRange(previousMGP); + Coin targetMGP = minGasPriceProvider.getMinGasPriceAsCoin(); if (priceRange.inRange(targetMGP)) { return targetMGP; } @@ -44,4 +46,5 @@ public Coin calculate(Coin previousMGP) { return priceRange.getLowerLimit(); } + } diff --git a/rskj-core/src/main/java/co/rsk/mine/gas/provider/EthCallMinGasPriceProvider.java b/rskj-core/src/main/java/co/rsk/mine/gas/provider/EthCallMinGasPriceProvider.java new file mode 100644 index 00000000000..e1dce2d3afa --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/mine/gas/provider/EthCallMinGasPriceProvider.java @@ -0,0 +1,95 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.mine.gas.provider; + +import co.rsk.config.mining.EthCallMinGasPriceSystemConfig; +import co.rsk.config.mining.StableMinGasPriceSystemConfig; +import co.rsk.rpc.modules.eth.EthModule; +import co.rsk.util.HexUtils; +import org.ethereum.rpc.parameters.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; + +public class EthCallMinGasPriceProvider extends StableMinGasPriceProvider { + private static final Logger logger = LoggerFactory.getLogger(EthCallMinGasPriceProvider.class); + + private final String toAddress; + private final String fromAddress; + private final String data; + + private final Supplier ethModuleSupplier; + + public EthCallMinGasPriceProvider(MinGasPriceProvider fallBackProvider, StableMinGasPriceSystemConfig config, Supplier ethModuleSupplier) { + super(fallBackProvider, config.getMinStableGasPrice(), config.getRefreshRate()); + this.ethModuleSupplier = ethModuleSupplier; + EthCallMinGasPriceSystemConfig ethCallConfig = config.getEthCallConfig(); + this.toAddress = ethCallConfig.getAddress(); + this.fromAddress = ethCallConfig.getFrom(); + this.data = ethCallConfig.getData(); + } + + @Override + public MinGasPriceProviderType getType() { + return MinGasPriceProviderType.ETH_CALL; + } + + @Override + protected Optional getBtcExchangeRate() { + EthModule ethModule = Objects.requireNonNull(ethModuleSupplier.get()); + + CallArgumentsParam callArguments = new CallArgumentsParam( + new HexAddressParam(fromAddress), + new HexAddressParam(toAddress), + null, + null, + null, + null, + new HexNumberParam(ethModule.chainId()), + null, + new HexDataParam(data), + null + ); + try { + String callOutput = ethModule.call(callArguments, new BlockIdentifierParam("latest")); + + // Parse the output of the call to get the exchange rate. Will not work with bytes32 values! + return Optional.of(HexUtils.jsonHexToLong( + callOutput)); + } catch (Exception e) { + logger.error("Error calling eth module", e); + return Optional.empty(); + } + } + + public String getToAddress() { + return toAddress; + } + + public String getFromAddress() { + return fromAddress; + } + + public String getData() { + return data; + } +} diff --git a/rskj-core/src/main/java/co/rsk/net/handler/TxTimestamp.java b/rskj-core/src/main/java/co/rsk/mine/gas/provider/FixedMinGasPriceProvider.java similarity index 54% rename from rskj-core/src/main/java/co/rsk/net/handler/TxTimestamp.java rename to rskj-core/src/main/java/co/rsk/mine/gas/provider/FixedMinGasPriceProvider.java index 3aeadd8b0f0..2c20ba731c7 100644 --- a/rskj-core/src/main/java/co/rsk/net/handler/TxTimestamp.java +++ b/rskj-core/src/main/java/co/rsk/mine/gas/provider/FixedMinGasPriceProvider.java @@ -1,6 +1,6 @@ /* * This file is part of RskJ - * Copyright (C) 2017 RSK Labs Ltd. + * Copyright (C) 2024 RSK Labs Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -16,20 +16,30 @@ * along with this program. If not, see . */ -package co.rsk.net.handler; +package co.rsk.mine.gas.provider; -import org.ethereum.core.Transaction; +import co.rsk.core.Coin; -/** - * Used to known when a given tx was received by txhandler - */ -class TxTimestamp { +public class FixedMinGasPriceProvider implements MinGasPriceProvider { + + private final long minGasPrice; + + public FixedMinGasPriceProvider(long minGasPrice) { + this.minGasPrice = minGasPrice; + } - long timestamp; - Transaction tx; + @Override + public long getMinGasPrice() { + return minGasPrice; + } + + @Override + public MinGasPriceProviderType getType() { + return MinGasPriceProviderType.FIXED; + } - TxTimestamp(Transaction tx, long timestamp) { - this.timestamp = timestamp; - this.tx = tx; + @Override + public Coin getMinGasPriceAsCoin() { + return Coin.valueOf(minGasPrice); } } diff --git a/rskj-core/src/main/java/co/rsk/mine/gas/provider/HttpGetMinGasPriceProvider.java b/rskj-core/src/main/java/co/rsk/mine/gas/provider/HttpGetMinGasPriceProvider.java new file mode 100644 index 00000000000..31c1999e743 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/mine/gas/provider/HttpGetMinGasPriceProvider.java @@ -0,0 +1,94 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.mine.gas.provider; + +import co.rsk.config.mining.StableMinGasPriceSystemConfig; +import co.rsk.config.mining.HttpGetStableMinGasSystemConfig; +import co.rsk.net.http.SimpleHttpClient; +import com.fasterxml.jackson.core.JsonPointer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public class HttpGetMinGasPriceProvider extends StableMinGasPriceProvider { + private static final Logger logger = LoggerFactory.getLogger("GasPriceProvider"); + private final String url; + private final JsonPointer jsonPath; + private final SimpleHttpClient httpClient; + private final ObjectMapper objectMapper; + + public HttpGetMinGasPriceProvider(StableMinGasPriceSystemConfig config, MinGasPriceProvider fallBackProvider) { + this(config, fallBackProvider, new SimpleHttpClient(config.getHttpGetConfig().getTimeout().toMillis())); + } + + public HttpGetMinGasPriceProvider(StableMinGasPriceSystemConfig config, MinGasPriceProvider fallBackProvider, SimpleHttpClient httpClient) { + super(fallBackProvider, config.getMinStableGasPrice(), config.getRefreshRate()); + HttpGetStableMinGasSystemConfig httpGetConfig = config.getHttpGetConfig(); + this.url = httpGetConfig.getUrl(); + this.jsonPath = JsonPointer.valueOf(httpGetConfig.getJsonPath()); + this.httpClient = httpClient; + this.objectMapper = new ObjectMapper(); + } + + @Override + protected Optional getBtcExchangeRate() { + String response = getResponseFromWeb(); + if (!StringUtils.isBlank(response)) { + Long price = parsePrice(response); + return Optional.ofNullable(price); + } + return Optional.empty(); + } + + private Long parsePrice(String response) { + try { + JsonNode jObject = objectMapper.readTree(response); + if (jObject.at(jsonPath).isMissingNode()) { + return null; + } + return objectMapper.readTree(response).at(jsonPath).asLong(); + } catch (Exception e) { + logger.error("Error parsing min gas price from web provider", e); + return null; + } + } + + private String getResponseFromWeb() { + try { + return httpClient.doGet(url); + } catch (Exception e) { + logger.error("Error getting min gas price from web provider: {}", e.getMessage()); + } + return null; + } + + @Override + public MinGasPriceProviderType getType() { + return MinGasPriceProviderType.HTTP_GET; + } + + public String getUrl() { + return url; + } + +} + diff --git a/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProvider.java b/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProvider.java new file mode 100644 index 00000000000..688495faae1 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProvider.java @@ -0,0 +1,30 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.mine.gas.provider; + +import co.rsk.core.Coin; + +public interface MinGasPriceProvider { + + long getMinGasPrice(); + + MinGasPriceProviderType getType(); + + Coin getMinGasPriceAsCoin(); +} diff --git a/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProviderFactory.java b/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProviderFactory.java new file mode 100644 index 00000000000..844619cf4a5 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProviderFactory.java @@ -0,0 +1,65 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.mine.gas.provider; + +import co.rsk.config.mining.StableMinGasPriceSystemConfig; +import co.rsk.rpc.modules.eth.EthModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.Supplier; + + +public class MinGasPriceProviderFactory { + + private static final Logger logger = LoggerFactory.getLogger("StableMinGasPrice"); + + private MinGasPriceProviderFactory() { + } + + public static MinGasPriceProvider create(long fixedMinGasPrice, StableMinGasPriceSystemConfig stableMinGasPriceSystemConfig, Supplier ethModuleSupplier) { + FixedMinGasPriceProvider fixedMinGasPriceProvider = new FixedMinGasPriceProvider(fixedMinGasPrice); + + if (stableMinGasPriceSystemConfig == null) { + logger.warn("Could not find stable min gas price system config, using {} provider", fixedMinGasPriceProvider.getType().name()); + return fixedMinGasPriceProvider; + } + if (!stableMinGasPriceSystemConfig.isEnabled()) { + return fixedMinGasPriceProvider; + } + + MinGasPriceProviderType method = stableMinGasPriceSystemConfig.getMethod(); + if (method == null) { + logger.error("Could not find valid method in config, using fallback provider: {}", fixedMinGasPriceProvider.getType().name()); + return fixedMinGasPriceProvider; + } + + switch (method) { + case HTTP_GET: + return new HttpGetMinGasPriceProvider(stableMinGasPriceSystemConfig, fixedMinGasPriceProvider); + case ETH_CALL: + return new EthCallMinGasPriceProvider(fixedMinGasPriceProvider, stableMinGasPriceSystemConfig, ethModuleSupplier); + case FIXED: + return fixedMinGasPriceProvider; + default: + logger.debug("Could not find a valid implementation for the method {}. Returning fallback provider {}", method, fixedMinGasPriceProvider.getType().name()); + return fixedMinGasPriceProvider; + } + } +} diff --git a/rskj-core/src/test/java/org/ethereum/vm/ProgramTestLogsOnOnTest.java b/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProviderType.java similarity index 75% rename from rskj-core/src/test/java/org/ethereum/vm/ProgramTestLogsOnOnTest.java rename to rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProviderType.java index c5365297dde..0d6f2e7cf99 100644 --- a/rskj-core/src/test/java/org/ethereum/vm/ProgramTestLogsOnOnTest.java +++ b/rskj-core/src/main/java/co/rsk/mine/gas/provider/MinGasPriceProviderType.java @@ -1,6 +1,6 @@ /* * This file is part of RskJ - * Copyright (C) 2022 RSK Labs Ltd. + * Copyright (C) 2024 RSK Labs Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -16,15 +16,10 @@ * along with this program. If not, see . */ -package org.ethereum.vm; - -import org.junit.jupiter.api.BeforeEach; - -class ProgramTestLogsOnOnTest extends ProgramTest { - - @BeforeEach - void beforeEach() { - setUp(true, true); - } +package co.rsk.mine.gas.provider; +public enum MinGasPriceProviderType { + FIXED, + HTTP_GET, + ETH_CALL } diff --git a/rskj-core/src/main/java/co/rsk/mine/gas/provider/StableMinGasPriceProvider.java b/rskj-core/src/main/java/co/rsk/mine/gas/provider/StableMinGasPriceProvider.java new file mode 100644 index 00000000000..72f83d02a74 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/mine/gas/provider/StableMinGasPriceProvider.java @@ -0,0 +1,141 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.mine.gas.provider; + +import co.rsk.core.Coin; +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public abstract class StableMinGasPriceProvider implements MinGasPriceProvider { + private static final Logger logger = LoggerFactory.getLogger("StableMinGasPrice"); + private static final int ERR_NUM_OF_FAILURES = 20; + + private final MinGasPriceProvider fallBackProvider; + private final long minStableGasPrice; + private final long refreshRateInMillis; + private final AtomicInteger numOfFailures = new AtomicInteger(); + private final AtomicReference> priceFuture = new AtomicReference<>(); + + private volatile long lastMinGasPrice; + private volatile long lastUpdateTimeMillis; + + protected StableMinGasPriceProvider(MinGasPriceProvider fallBackProvider, long minStableGasPrice, Duration refreshRate) { + this.minStableGasPrice = minStableGasPrice; + this.fallBackProvider = fallBackProvider; + this.refreshRateInMillis = refreshRate.toMillis(); + } + + protected abstract Optional getBtcExchangeRate(); + + @Override + public long getMinGasPrice() { + return getMinGasPrice(false); + } + + @VisibleForTesting + public long getMinGasPrice(boolean wait) { + long currentTimeMillis = System.currentTimeMillis(); + if (currentTimeMillis - lastUpdateTimeMillis >= refreshRateInMillis) { + Future priceFuture = fetchPriceAsync(); + if (wait || priceFuture.isDone()) { + try { + return priceFuture.get(); + } catch (InterruptedException e) { + logger.error("Min gas price fetching was interrupted", e); + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + logger.error("Min gas price fetching was failed", e); + } + } + } + + return getLastMinGasPrice(); + } + + @Override + public Coin getMinGasPriceAsCoin() { + return Coin.valueOf(getMinGasPrice()); + } + + @VisibleForTesting + Coin getMinGasPriceAsCoin(boolean wait) { + return Coin.valueOf(getMinGasPrice(wait)); + } + + private long getLastMinGasPrice() { + if (lastMinGasPrice > 0) { + return lastMinGasPrice; + } + + return fallBackProvider.getMinGasPrice(); + } + + private long calculateMinGasPriceBasedOnBtcPrice(long btcValue) { + if (minStableGasPrice == 0 || btcValue == 0) { + return 0; + } + return minStableGasPrice / btcValue; + } + + private synchronized Future fetchPriceAsync() { + Future future = priceFuture.get(); + if (future != null) { + return future; + } + + CompletableFuture newFuture = new CompletableFuture<>(); + priceFuture.set(newFuture); + + new Thread(() -> { + Optional priceResponse = fetchPriceSync(); + newFuture.complete(priceResponse.orElse(getLastMinGasPrice())); + priceFuture.set(null); + }).start(); + + return newFuture; + } + + private Optional fetchPriceSync() { + Optional priceResponse = getBtcExchangeRate(); + if (priceResponse.isPresent() && priceResponse.get() > 0) { + long result = calculateMinGasPriceBasedOnBtcPrice(priceResponse.get()); + lastMinGasPrice = result; + lastUpdateTimeMillis = System.currentTimeMillis(); + numOfFailures.set(0); + return Optional.of(result); + } + + int failedAttempts = numOfFailures.incrementAndGet(); + if (failedAttempts >= ERR_NUM_OF_FAILURES) { + logger.error("Gas price was not updated as it was not possible to obtain valid price from provider. Check your provider setup. Number of failed attempts: {}", failedAttempts); + } else { + logger.warn("Gas price was not updated as it was not possible to obtain valid price from provider. Number of failed attempts: {}", failedAttempts); + } + + return Optional.empty(); + } +} diff --git a/rskj-core/src/main/java/co/rsk/net/NodeMessageHandler.java b/rskj-core/src/main/java/co/rsk/net/NodeMessageHandler.java index 35840584a5c..1ade9f71711 100644 --- a/rskj-core/src/main/java/co/rsk/net/NodeMessageHandler.java +++ b/rskj-core/src/main/java/co/rsk/net/NodeMessageHandler.java @@ -315,7 +315,7 @@ public void run() { long startNanos = System.nanoTime(); logStart(task); this.processMessage(task.getSender(), task.getMessage()); - logEnd(task, startNanos); + logEnd(task, startNanos, loggerMessageProcess); } else { logger.trace("No task"); } @@ -362,7 +362,7 @@ private void logStart(MessageTask task) { } @VisibleForTesting - void logEnd(MessageTask task, long startNanos) { + static void logEnd(MessageTask task, long startNanos, Logger messageProcessingLogger) { Duration processTime = Duration.ofNanos(System.nanoTime() - startNanos); Message message = task.getMessage(); @@ -371,7 +371,7 @@ void logEnd(MessageTask task, long startNanos) { boolean isOtherSlow = !isBlockRelated && processTime.getSeconds() > PROCESSING_TIME_TO_WARN_LIMIT; if (isBlockSlow || isOtherSlow) { - loggerMessageProcess.warn("Message[{}] processing took too much: [{}]s.", message.getMessageType(), processTime.getSeconds()); + messageProcessingLogger.warn("Message[{}] processing took too much: [{}]s.", message.getMessageType(), processTime.getSeconds()); } logger.trace("End {} message task after [{}]s", task.getMessage().getMessageType(), processTime.getSeconds()); diff --git a/rskj-core/src/main/java/co/rsk/net/handler/TxPendingValidator.java b/rskj-core/src/main/java/co/rsk/net/handler/TxPendingValidator.java index 798361abf10..54b81c17b5a 100644 --- a/rskj-core/src/main/java/co/rsk/net/handler/TxPendingValidator.java +++ b/rskj-core/src/main/java/co/rsk/net/handler/TxPendingValidator.java @@ -77,6 +77,7 @@ public TransactionValidationResult isValid(Transaction tx, Block executionBlock, ? BigInteger.valueOf(Math.max(BlockUtils.getSublistGasLimit(executionBlock, true, constants.getMinSequentialSetGasLimit()), BlockUtils.getSublistGasLimit(executionBlock, false, constants.getMinSequentialSetGasLimit()))) : BigIntegers.fromUnsignedByteArray(executionBlock.getGasLimit()); Coin minimumGasPrice = executionBlock.getMinimumGasPrice(); + long bestBlockNumber = executionBlock.getNumber(); long basicTxCost = tx.transactionCost(constants, activations, signatureCache); if (state == null && basicTxCost != 0) { @@ -86,6 +87,10 @@ public TransactionValidationResult isValid(Transaction tx, Block executionBlock, return TransactionValidationResult.withError("the sender account doesn't exist"); } + if (tx.isInitCodeSizeInvalidForTx(activationConfig.forBlock(bestBlockNumber))) { + return TransactionValidationResult.withError("transaction's init code size is invalid"); + } + if (tx.getSize() > TX_MAX_SIZE) { return TransactionValidationResult.withError(String.format("transaction's size is higher than defined maximum: %s > %s", tx.getSize(), TX_MAX_SIZE)); } diff --git a/rskj-core/src/main/java/co/rsk/net/http/HttpException.java b/rskj-core/src/main/java/co/rsk/net/http/HttpException.java new file mode 100644 index 00000000000..c7c77b54414 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/net/http/HttpException.java @@ -0,0 +1,28 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.net.http; + +public class HttpException extends Exception { + + private static final long serialVersionUID = 9209168000899420627L; + + public HttpException(String message) { + super(message); + } + +} diff --git a/rskj-core/src/main/java/co/rsk/net/http/SimpleHttpClient.java b/rskj-core/src/main/java/co/rsk/net/http/SimpleHttpClient.java new file mode 100644 index 00000000000..b287247349c --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/net/http/SimpleHttpClient.java @@ -0,0 +1,75 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.net.http; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.UnknownHostException; + +public class SimpleHttpClient { + private static final Logger logger = LoggerFactory.getLogger("simpleHttp"); + private static final String GET_METHOD = "GET"; + private final int timeoutMillis; + + public SimpleHttpClient(long timeoutMillis) { + if (timeoutMillis > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Timeout value is too large."); + } + this.timeoutMillis = (int) timeoutMillis; + } + + public String doGet(String targetUrl) throws HttpException { + StringBuilder response = new StringBuilder(); + try { + HttpURLConnection conn = (HttpURLConnection) new URL(targetUrl).openConnection(); + conn.setRequestMethod(GET_METHOD); + conn.setConnectTimeout(timeoutMillis); + conn.setReadTimeout(timeoutMillis); + int responseCode = conn.getResponseCode(); + + if (responseCode >= 300 || responseCode < 200) { + String responseMessage = conn.getResponseMessage(); + throw new HttpException("Http request failed with code : " + responseCode + " - " + responseMessage); + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + } + } catch (HttpException httpException) { + throw httpException; + } catch (UnknownHostException unknownHostException) { + logger.error("Unknown host from url: {}. {}", targetUrl, unknownHostException.getMessage()); + throw new HttpException("Unknown host from url: " + targetUrl); + } catch (Exception e) { + logger.error("Http request failed.", e); + throw new HttpException("Http request failed with error : " + e.getMessage()); + } + + return response.toString(); + } + + +} diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index f23537a9e05..1bf670b7af3 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -25,13 +25,10 @@ public static Optional getFirstInputSigHash(BtcTransaction btcTx){ } TransactionInput txInput = btcTx.getInput(FIRST_INPUT_INDEX); Optional