diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index f35538c0961..8d951ae4abe 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -52,7 +52,11 @@ jobs: restore-keys: macos26-${{ matrix.arch }}-gradle- - name: Build - run: ./gradlew clean build --no-daemon + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew clean build --no-daemon --console=plain 2>&1 | tee ci-logs/build.log - name: Toolkit jar smoke test run: | @@ -63,6 +67,17 @@ jobs: java -jar "$JAR" db archive -h java -jar "$JAR" keystore --help + - name: Upload test diagnostics + if: failure() + uses: actions/upload-artifact@v6 + with: + name: tron-test-logs-${{ github.job }}-jdk${{ matrix.java }}-${{ matrix.arch }} + path: | + **/logs/tron-test.log + ci-logs/*.log + if-no-files-found: warn + retention-days: 7 + build-ubuntu: name: Build ubuntu24 (JDK 17 / aarch64) if: ${{ github.event_name == 'pull_request' || inputs.job == 'all' || inputs.job == 'ubuntu' }} @@ -91,7 +106,11 @@ jobs: restore-keys: ubuntu24-aarch64-gradle- - name: Build - run: ./gradlew clean build --no-daemon + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew clean build --no-daemon --console=plain 2>&1 | tee ci-logs/build.log - name: Toolkit jar smoke test run: | @@ -102,6 +121,17 @@ jobs: java -jar "$JAR" db archive -h java -jar "$JAR" keystore --help + - name: Upload test diagnostics + if: failure() + uses: actions/upload-artifact@v6 + with: + name: tron-test-logs-${{ github.job }} + path: | + **/logs/tron-test.log + ci-logs/*.log + if-no-files-found: warn + retention-days: 7 + docker-build-rockylinux: name: Build rockylinux (JDK 8 / x86_64) if: ${{ github.event_name == 'pull_request' || inputs.job == 'all' || inputs.job == 'rockylinux' }} @@ -113,15 +143,31 @@ jobs: env: GRADLE_USER_HOME: /github/home/.gradle - LANG: en_US.UTF-8 - LC_ALL: en_US.UTF-8 + LANG: C.utf8 + LC_ALL: C.utf8 steps: - name: Install dependencies (Rocky 8 + JDK8) run: | set -euxo pipefail - dnf -y install java-1.8.0-openjdk-devel git wget unzip which jq bc curl glibc-langpack-en - dnf -y groupinstall "Development Tools" + # Rocky 8 already provides CA certificates, JNI runtime libraries, tar and gzip. + # Its built-in C.utf8 locale provides UTF-8 without an extra language pack. + # git-core provides checkout commands; zstd supports actions/cache compression. + # Abandon connections below 256 KiB/s for 30 seconds so DNF can try another mirror. + dnf -y \ + --setopt=install_weak_deps=False \ + --setopt=max_parallel_downloads=10 \ + --setopt=minrate=256k \ + --setopt=timeout=30 \ + install java-1.8.0-openjdk-devel git-core zstd + # Set JAVA_HOME so the Gradle wrapper does not need which. + javac_path=$(command -v javac) + javac_real=$(readlink -f "$javac_path") + jdk_bin=$(dirname "$javac_real") + jdk_home=$(dirname "$jdk_bin") + test -x "$jdk_home/bin/java" + test -x "$jdk_home/bin/javac" + printf 'JAVA_HOME=%s\n' "$jdk_home" >> "$GITHUB_ENV" - name: Checkout code uses: actions/checkout@v5 @@ -143,7 +189,11 @@ jobs: run: ./gradlew --stop || true - name: Build - run: ./gradlew clean build --no-daemon + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew clean build --no-daemon --console=plain 2>&1 | tee ci-logs/build.log - name: Toolkit jar smoke test run: | @@ -155,7 +205,22 @@ jobs: java -jar "$JAR" keystore --help - name: Test with RocksDB engine - run: ./gradlew :framework:testWithRocksDb --no-daemon + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew :framework:testWithRocksDb --no-daemon --console=plain 2>&1 | tee ci-logs/rocksdb-test.log + + - name: Upload test diagnostics + if: failure() + uses: actions/upload-artifact@v6 + with: + name: tron-test-logs-${{ github.job }} + path: | + **/logs/tron-test.log + ci-logs/*.log + if-no-files-found: warn + retention-days: 7 docker-build-debian11: name: Build debian11 (JDK 8 / x86_64) @@ -197,7 +262,11 @@ jobs: debian11-x86_64-gradle- - name: Build - run: ./gradlew clean build --no-daemon --no-build-cache + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew clean build --no-daemon --console=plain 2>&1 | tee ci-logs/build.log - name: Toolkit jar smoke test run: | @@ -209,10 +278,18 @@ jobs: java -jar "$JAR" keystore --help - name: Test with RocksDB engine - run: ./gradlew :framework:testWithRocksDb --no-daemon --no-build-cache + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew :framework:testWithRocksDb --no-daemon --console=plain 2>&1 | tee ci-logs/rocksdb-test.log - name: Generate module coverage reports - run: ./gradlew jacocoTestReport --no-daemon + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew jacocoTestReport --no-daemon --console=plain 2>&1 | tee ci-logs/coverage.log - name: Upload PR coverage reports uses: actions/upload-artifact@v6 @@ -222,6 +299,17 @@ jobs: **/build/reports/jacoco/test/jacocoTestReport.xml if-no-files-found: error + - name: Upload test diagnostics + if: failure() + uses: actions/upload-artifact@v6 + with: + name: tron-test-logs-${{ github.job }} + path: | + **/logs/tron-test.log + ci-logs/*.log + if-no-files-found: warn + retention-days: 7 + coverage-base: name: Coverage Base (JDK 8 / x86_64) if: ${{ github.event_name == 'pull_request' }} @@ -260,19 +348,33 @@ jobs: coverage-base-x86_64-gradle- - name: Build (base) + id: base_build # Test failures on the base branch are tolerated: merge-order races can # leave the base with a pre-existing failing test that is unrelated to # this PR. The only output we need from this job is the jacoco XML for # coverage diffing, so we must not let a stale test failure block it. continue-on-error: true - run: ./gradlew clean build --no-daemon --no-build-cache + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew clean build --no-daemon --console=plain 2>&1 | tee ci-logs/build.log - name: Test with RocksDB engine (base) + id: base_rocksdb_test continue-on-error: true - run: ./gradlew :framework:testWithRocksDb --no-daemon --no-build-cache + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew :framework:testWithRocksDb --no-daemon --console=plain 2>&1 | tee ci-logs/rocksdb-test.log - name: Generate module coverage reports (base) - run: ./gradlew jacocoTestReport --no-daemon + shell: bash + run: | + set -euo pipefail + mkdir -p ci-logs + ./gradlew jacocoTestReport --no-daemon --console=plain 2>&1 | tee ci-logs/coverage.log - name: Upload base coverage reports uses: actions/upload-artifact@v6 @@ -282,6 +384,18 @@ jobs: **/build/reports/jacoco/test/jacocoTestReport.xml if-no-files-found: warn + - name: Upload test diagnostics + # Preserve logs for test failures tolerated by continue-on-error above. + if: ${{ failure() || steps.base_build.outcome == 'failure' || steps.base_rocksdb_test.outcome == 'failure' }} + uses: actions/upload-artifact@v6 + with: + name: tron-test-logs-${{ github.job }} + path: | + **/logs/tron-test.log + ci-logs/*.log + if-no-files-found: warn + retention-days: 7 + coverage-gate: name: Coverage Gate needs: [docker-build-debian11, coverage-base] diff --git a/.github/workflows/pr-cancel.yml b/.github/workflows/pr-cancel.yml index 3213026d3f9..a4d46153d47 100644 --- a/.github/workflows/pr-cancel.yml +++ b/.github/workflows/pr-cancel.yml @@ -21,7 +21,6 @@ jobs: 'pr-build.yml', 'codeql.yml', 'integration-test-single-node.yml', - 'integration-test-multinode.yml', ]; const headSha = context.payload.pull_request.head.sha; const prNumber = context.payload.pull_request.number; diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 506a823a4f7..6a7337ce310 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -43,9 +43,16 @@ jobs: errors.push(`PR title is too long (${title.length}/72 characters).`); } - // 2. Conventional format check - const conventionalRegex = /^(feat|fix|refactor|docs|style|test|chore|ci|perf|build|revert)(\([^)]+\))?:\s\S.*/; - if (title && !conventionalRegex.test(title)) { + // 2. Conventional format check (require a space after the colon) + const titlePrefix = '(?:feat|fix|refactor|docs|style|test|chore|ci|perf|build|revert)(?:[(][^)]+[)])?'; + const missingSpaceAfterColonRegex = new RegExp(`^${titlePrefix}:[^ ]`); + const conventionalRegex = new RegExp(`^${titlePrefix}: [^ ].*`); + if (title && missingSpaceAfterColonRegex.test(title)) { + errors.push( + 'PR title must include a space after the colon.\n' + + ' Example: `feat(tvm): add blob opcodes`' + ); + } else if (title && !conventionalRegex.test(title)) { errors.push( 'PR title must follow conventional format: `type(scope): description`\n' + ' Allowed types: ' + allowedTypes.map(t => `\`${t}\``).join(', ') + '\n' + @@ -60,7 +67,7 @@ jobs: // 4. Description part should not start with a capital letter if (title) { - const descMatch = title.match(/^\w+(?:\([^)]+\))?:\s*(.+)/); + const descMatch = title.match(/^\w+(?:\([^)]+\))?: (.+)/); if (descMatch) { const desc = descMatch[1]; if (/^[A-Z]/.test(desc)) { diff --git a/.github/workflows/pr-reviewer.yml b/.github/workflows/pr-reviewer.yml index bf124acf576..e10b98aaf1c 100644 --- a/.github/workflows/pr-reviewer.yml +++ b/.github/workflows/pr-reviewer.yml @@ -59,13 +59,23 @@ jobs: const normalize = s => s.toLowerCase().replace(/[\s\-_]/g, ''); // ── Extract scope from conventional commit title ────────── - // Format: type(scope): description + // Formats documented by CONTRIBUTING.md: + // type(scope): description + // type: description // Also supports: type(scope1,scope2): description + // Only bare "ci" currently has an equivalent reviewer scope. const scopeMatch = title.match(/^\w+\(([^)]+)\):/); - const rawScope = scopeMatch ? scopeMatch[1] : null; + const bareTypeMatch = title.match(/^(\w+):/); + const inferredScope = !scopeMatch && bareTypeMatch?.[1].toLowerCase() === 'ci' + ? 'ci' + : null; + const rawScope = scopeMatch ? scopeMatch[1] : inferredScope; core.info(`PR title : ${title}`); core.info(`Raw scope: ${rawScope || '(none)'}`); + if (inferredScope) { + core.info('Inferred scope "ci" from bare "ci" PR title type.'); + } // ── Skip if reviewers already assigned ────────────────── const pr = await github.rest.pulls.get({ diff --git a/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java b/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java index 7801a18798a..07189603f37 100644 --- a/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java +++ b/actuator/src/main/java/org/tron/core/vm/repository/RepositoryImpl.java @@ -949,7 +949,6 @@ private long increase(long lastUsage, long usage, long lastTime, long now, long } if (lastTime != now) { - assert now > lastTime; if (lastTime + windowSize > now) { long delta = now - lastTime; double decay = (windowSize - delta) / (double) windowSize; @@ -998,8 +997,6 @@ public long calculateGlobalEnergyLimit(AccountCapsule accountCapsule) { long totalEnergyLimit = getDynamicPropertiesStore().getTotalEnergyCurrentLimit(); long totalEnergyWeight = getDynamicPropertiesStore().getTotalEnergyWeight(); - assert totalEnergyWeight > 0; - if (hardenResourceCalculation()) { return BigInteger.valueOf(energyWeight) .multiply(BigInteger.valueOf(totalEnergyLimit)) diff --git a/build.gradle b/build.gradle index 65e72c0fb73..04dee79fbae 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } ext { - grpcVersion = "1.83.0" + grpcVersion = "1.83.1" } allprojects { @@ -91,16 +91,14 @@ subprojects { } dependencies { - implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.36' - implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.36' - implementation group: 'org.slf4j', name: 'jul-to-slf4j', version: '1.7.36' - implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.13' + implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.17' + implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '2.0.17' + implementation group: 'org.slf4j', name: 'jul-to-slf4j', version: '2.0.17' + implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.3.16' implementation "com.google.code.findbugs:jsr305:3.0.0" implementation group: 'org.springframework', name: 'spring-context', version: "${springVersion}" - implementation "org.apache.commons:commons-lang3:3.4" - implementation group: 'org.apache.commons', name: 'commons-math', version: '2.2' - implementation "org.apache.commons:commons-collections4:4.1" - implementation group: 'joda-time', name: 'joda-time', version: '2.3' + implementation "org.apache.commons:commons-lang3:3.20.0" + implementation "org.apache.commons:commons-collections4:4.6.0" implementation group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: '1.84' compileOnly 'org.projectlombok:lombok:1.18.34' diff --git a/chainbase/src/main/java/org/tron/common/utils/ForkUtils.java b/chainbase/src/main/java/org/tron/common/utils/ForkUtils.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/chainbase/src/main/java/org/tron/common/zksnark/MerklePath.java b/chainbase/src/main/java/org/tron/common/zksnark/MerklePath.java index 96d6ceac893..7beba9f9eff 100644 --- a/chainbase/src/main/java/org/tron/common/zksnark/MerklePath.java +++ b/chainbase/src/main/java/org/tron/common/zksnark/MerklePath.java @@ -75,7 +75,6 @@ private static long convertVectorToLong(List v) throws ZksnarkException } public byte[] encode() throws ZksnarkException { - assert (authenticationPath.size() == index.size()); List> pathByteList = Lists.newArrayList(); long indexLong; // 64 for (int i = 0; i < authenticationPath.size(); i++) { diff --git a/chainbase/src/main/java/org/tron/core/config/args/Parameter.java b/chainbase/src/main/java/org/tron/core/config/args/Parameter.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java b/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java index 0c429178636..267abbf06c4 100644 --- a/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java +++ b/chainbase/src/main/java/org/tron/core/db/EnergyProcessor.java @@ -155,8 +155,6 @@ public long calculateGlobalEnergyLimit(AccountCapsule accountCapsule) { long totalEnergyWeight = dynamicPropertiesStore.getTotalEnergyWeight(); if (dynamicPropertiesStore.allowNewReward() && totalEnergyWeight <= 0) { return 0; - } else { - assert totalEnergyWeight > 0; } if (hardenCalculation()) { return calculateGlobalLimitV1(frozeBalance, totalEnergyLimit, totalEnergyWeight); @@ -205,4 +203,3 @@ private long scaleByRate(long value, long numerator, long denominator) { } } - diff --git a/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java b/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java index 6706c430084..8b6f96504ec 100644 --- a/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java +++ b/chainbase/src/main/java/org/tron/core/db/ResourceProcessor.java @@ -63,7 +63,6 @@ protected long increase(long lastUsage, long usage, long lastTime, long now, lon } if (lastTime != now) { - assert now > lastTime; if (lastTime + windowSize > now) { long delta = now - lastTime; double decay = (windowSize - delta) / (double) windowSize; diff --git a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java index 0f74f20d379..33bbaa4a362 100644 --- a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java +++ b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java @@ -12,13 +12,13 @@ import java.util.stream.IntStream; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Sha256Hash; +import org.tron.common.utils.Time; import org.tron.core.capsule.BytesCapsule; import org.tron.core.config.Parameter.ChainConstant; import org.tron.core.db.TronStoreWithRevoking; @@ -2261,8 +2261,8 @@ public void updateNextMaintenanceTime(long blockTime) { logger.info( "Do update nextMaintenanceTime, currentMaintenanceTime: {}, blockTime: {}, " + "nextMaintenanceTime: {}.", - new DateTime(currentMaintenanceTime), new DateTime(blockTime), - new DateTime(nextMaintenanceTime) + Time.getIsoTimeString(currentMaintenanceTime), Time.getIsoTimeString(blockTime), + Time.getIsoTimeString(nextMaintenanceTime) ); } diff --git a/common/build.gradle b/common/build.gradle index 14d3eb4e637..4b36d067b70 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -8,7 +8,9 @@ sourceCompatibility = 1.8 dependencies { - api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.6' // https://github.com/FasterXML/jackson-databind/issues/3627 + // avoid x.y.z.w micro-patches, they may ship broken Gradle module metadata: + // https://github.com/FasterXML/jackson-databind/issues/3627 + api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.10' api "com.cedarsoftware:java-util:3.2.0" api group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.1' api group: 'commons-codec', name: 'commons-codec', version: '1.11' diff --git a/common/src/main/java/org/tron/common/entity/NodeInfo.java b/common/src/main/java/org/tron/common/entity/NodeInfo.java index 4b23bd185e3..6f53b3935b8 100644 --- a/common/src/main/java/org/tron/common/entity/NodeInfo.java +++ b/common/src/main/java/org/tron/common/entity/NodeInfo.java @@ -146,7 +146,7 @@ public Protocol.NodeInfo transferToProtoEntity() { peerInfoBuilder.setLastBlockUpdateTime(peerInfo.getLastBlockUpdateTime()); peerInfoBuilder.setSyncFlag(peerInfo.isSyncFlag()); peerInfoBuilder.setHeadBlockTimeWeBothHave(peerInfo.getHeadBlockTimeWeBothHave()); - peerInfoBuilder.setNeedSyncFromPeer(peerInfo.isSyncFlag()); + peerInfoBuilder.setNeedSyncFromPeer(peerInfo.isNeedSyncFromPeer()); peerInfoBuilder.setNeedSyncFromUs(peerInfo.isNeedSyncFromUs()); peerInfoBuilder.setHost(peerInfo.getHost()); peerInfoBuilder.setPort(peerInfo.getPort()); diff --git a/common/src/main/java/org/tron/common/setting/RocksDbSettings.java b/common/src/main/java/org/tron/common/setting/RocksDbSettings.java index d5df5e261b5..8696092a0a0 100644 --- a/common/src/main/java/org/tron/common/setting/RocksDbSettings.java +++ b/common/src/main/java/org/tron/common/setting/RocksDbSettings.java @@ -6,7 +6,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.rocksdb.BlockBasedTableConfig; -import org.rocksdb.BloomFilter; import org.rocksdb.ComparatorOptions; import org.rocksdb.InfoLogLevel; import org.rocksdb.LRUCache; @@ -211,13 +210,7 @@ protected void log(InfoLogLevel infoLogLevel, String logMsg) { options.setTargetFileSizeBase(settings.getTargetFileSizeBase()); // table options - final BlockBasedTableConfig tableCfg; - options.setTableFormatConfig(tableCfg = new BlockBasedTableConfig()); - tableCfg.setBlockSize(settings.getBlockSize()); - tableCfg.setBlockCache(RocksDbSettings.getCache()); - tableCfg.setCacheIndexAndFilterBlocks(true); - tableCfg.setPinL0FilterAndIndexBlocksInCache(true); - tableCfg.setFilter(new BloomFilter(10, false)); + options.setTableFormatConfig(new BlockBasedTableConfig()); if (Constant.MARKET_PAIR_PRICE_TO_ORDER.equals(dbName)) { ComparatorOptions comparatorOptions = new ComparatorOptions(); options.setComparator(new MarketOrderPriceComparatorForRocksDB(comparatorOptions)); diff --git a/common/src/main/java/org/tron/common/utils/Time.java b/common/src/main/java/org/tron/common/utils/Time.java index fdbfcb5f283..15e9d3d4b55 100644 --- a/common/src/main/java/org/tron/common/utils/Time.java +++ b/common/src/main/java/org/tron/common/utils/Time.java @@ -1,9 +1,17 @@ package org.tron.common.utils; import java.sql.Timestamp; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; public class Time { + // Matches joda-time's DateTime.toString() output, byte for byte: fixed + // 3-digit millis, offset as +08:00, and Z when the system zone is UTC. + private static final DateTimeFormatter ISO_MILLIS_FORMAT = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + public static long getCurrentMillis() { return System.currentTimeMillis(); } @@ -11,4 +19,8 @@ public static long getCurrentMillis() { public static String getTimeString(long time) { return new Timestamp(time).toString(); } + + public static String getIsoTimeString(long time) { + return Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).format(ISO_MILLIS_FORMAT); + } } diff --git a/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java b/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java index 660fa289e3b..2696c220231 100644 --- a/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java +++ b/common/src/main/java/org/tron/core/config/args/CommitteeConfig.java @@ -1,11 +1,14 @@ package org.tron.core.config.args; +import static org.tron.core.exception.TronError.ErrCode.PARAMETER_INIT; + import com.typesafe.config.Config; import com.typesafe.config.ConfigBeanFactory; import com.typesafe.config.ConfigValue; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.tron.core.exception.TronError; /** * Committee (governance) configuration bean. @@ -160,11 +163,11 @@ private void postProcess() { // cross-field: allowOldRewardOpt requires at least one reward/vote flag if (allowOldRewardOpt == 1 && allowNewRewardAlgorithm != 1 && allowNewReward != 1 && allowTvmVote != 1) { - throw new IllegalArgumentException( + throw new TronError( "At least one of the following proposals is required to be opened first: " + "committee.allowNewRewardAlgorithm = 1" + " or committee.allowNewReward = 1" - + " or committee.allowTvmVote = 1."); + + " or committee.allowTvmVote = 1.", PARAMETER_INIT); } } } diff --git a/common/src/main/resources/reference.conf b/common/src/main/resources/reference.conf index d8c483d932a..7c5ee1da8a6 100644 --- a/common/src/main/resources/reference.conf +++ b/common/src/main/resources/reference.conf @@ -108,7 +108,7 @@ storage { dbSettings = { levelNumber = 7 // Number of RocksDB levels. compactThreads = 0 // 0 = auto: max(availableProcessors, 1) - blocksize = 16 // n * KB + blocksize = 16 // n * KB. Currently retained for compatibility but not applied to native RocksDB table options. maxBytesForLevelBase = 256 // n * MB maxBytesForLevelMultiplier = 10 // Level size multiplier. level0FileNumCompactionTrigger = 2 // L0 files that trigger compaction. diff --git a/common/src/test/java/org/tron/common/entity/NodeInfoTest.java b/common/src/test/java/org/tron/common/entity/NodeInfoTest.java new file mode 100644 index 00000000000..302fdb7b080 --- /dev/null +++ b/common/src/test/java/org/tron/common/entity/NodeInfoTest.java @@ -0,0 +1,58 @@ +package org.tron.common.entity; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; +import org.tron.protos.Protocol; + +public class NodeInfoTest { + + private PeerInfo newPeerInfo(boolean syncFlag, boolean needSyncFromPeer, + boolean needSyncFromUs) { + PeerInfo peerInfo = new PeerInfo(); + peerInfo.setSyncFlag(syncFlag); + peerInfo.setNeedSyncFromPeer(needSyncFromPeer); + peerInfo.setNeedSyncFromUs(needSyncFromUs); + // string fields must be non-null, otherwise the protobuf setters throw NPE + peerInfo.setLastSyncBlock(""); + peerInfo.setHost("127.0.0.1"); + peerInfo.setNodeId(""); + peerInfo.setHeadBlockWeBothHave(""); + peerInfo.setLocalDisconnectReason(""); + peerInfo.setRemoteDisconnectReason(""); + return peerInfo; + } + + /** + * The protobuf conversion must map each peer flag from its own source field. A previous + * copy-and-paste defect populated needSyncFromPeer from isSyncFlag(); distinct values for + * syncFlag and needSyncFromPeer are required so that such a mismatch is detected. + */ + @Test + public void testPeerFlagMappingIsIndependent() { + NodeInfo nodeInfo = new NodeInfo(); + nodeInfo.setBlock(""); + nodeInfo.setSolidityBlock(""); + List peerList = new ArrayList<>(); + // syncFlag != needSyncFromPeer so the two fields cannot be confused + peerList.add(newPeerInfo(false, true, false)); + peerList.add(newPeerInfo(true, false, true)); + nodeInfo.setPeerList(peerList); + nodeInfo.setCheatWitnessInfoMap(new java.util.HashMap<>()); + + Protocol.NodeInfo proto = nodeInfo.transferToProtoEntity(); + + Assert.assertEquals(2, proto.getPeerInfoListCount()); + + Protocol.NodeInfo.PeerInfo peer0 = proto.getPeerInfoList(0); + Assert.assertFalse(peer0.getSyncFlag()); + Assert.assertTrue(peer0.getNeedSyncFromPeer()); + Assert.assertFalse(peer0.getNeedSyncFromUs()); + + Protocol.NodeInfo.PeerInfo peer1 = proto.getPeerInfoList(1); + Assert.assertTrue(peer1.getSyncFlag()); + Assert.assertFalse(peer1.getNeedSyncFromPeer()); + Assert.assertTrue(peer1.getNeedSyncFromUs()); + } +} diff --git a/common/src/test/java/org/tron/common/setting/RocksDbSettingsTest.java b/common/src/test/java/org/tron/common/setting/RocksDbSettingsTest.java new file mode 100644 index 00000000000..44258df8255 --- /dev/null +++ b/common/src/test/java/org/tron/common/setting/RocksDbSettingsTest.java @@ -0,0 +1,59 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.tron.common.setting; + +import static org.junit.Assert.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.stream.Stream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.rocksdb.Options; +import org.rocksdb.RocksDB; + +public class RocksDbSettingsTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void shouldKeepNativeBlockTableDefaults() throws Exception { + Path database = temporaryFolder.newFolder("rocksdb").toPath(); + + try (Options options = RocksDbSettings.getOptionsByDbName("test")) { + try (RocksDB ignored = RocksDB.open(options, database.toString())) { + // Opening the DB materializes the table factory and persists its native settings. + } + } + + Path optionsFile; + try (Stream files = Files.list(database)) { + optionsFile = files + .filter(path -> path.getFileName().toString().startsWith("OPTIONS-")) + .max(Comparator.comparing(path -> path.getFileName().toString())) + .orElseThrow(() -> new AssertionError("RocksDB OPTIONS file not found")); + } + String nativeOptions = new String(Files.readAllBytes(optionsFile), StandardCharsets.UTF_8); + + assertTrue(nativeOptions.contains("block_size=4096")); + assertTrue(nativeOptions.contains("pin_l0_filter_and_index_blocks_in_cache=false")); + assertTrue(nativeOptions.contains("filter_policy=nullptr")); + } +} diff --git a/common/src/test/java/org/tron/core/config/args/CommitteeConfigTest.java b/common/src/test/java/org/tron/core/config/args/CommitteeConfigTest.java index 559198100fb..f2fe81851ba 100644 --- a/common/src/test/java/org/tron/core/config/args/CommitteeConfigTest.java +++ b/common/src/test/java/org/tron/core/config/args/CommitteeConfigTest.java @@ -1,10 +1,12 @@ package org.tron.core.config.args; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.Test; +import org.tron.core.exception.TronError; public class CommitteeConfigTest { @@ -57,9 +59,16 @@ public void testDynamicEnergyThresholdClamped() { .getDynamicEnergyThreshold()); } - @Test(expected = IllegalArgumentException.class) + @Test public void testAllowOldRewardOptWithoutPrerequisites() { - CommitteeConfig.fromConfig(withRef("committee { allowOldRewardOpt = 1 }")); + TronError error = assertThrows(TronError.class, + () -> CommitteeConfig.fromConfig(withRef("committee { allowOldRewardOpt = 1 }"))); + + assertEquals(TronError.ErrCode.PARAMETER_INIT, error.getErrCode()); + assertEquals("At least one of the following proposals is required to be opened first: " + + "committee.allowNewRewardAlgorithm = 1" + + " or committee.allowNewReward = 1" + + " or committee.allowTvmVote = 1.", error.getMessage()); } @Test diff --git a/consensus/src/main/java/org/tron/consensus/dpos/DposService.java b/consensus/src/main/java/org/tron/consensus/dpos/DposService.java index 397c9d0835c..0a40ec8e076 100644 --- a/consensus/src/main/java/org/tron/consensus/dpos/DposService.java +++ b/consensus/src/main/java/org/tron/consensus/dpos/DposService.java @@ -14,12 +14,12 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.tron.common.args.GenesisBlock; import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.ByteArray; +import org.tron.common.utils.Time; import org.tron.consensus.ConsensusDelegate; import org.tron.consensus.base.BlockHandle; import org.tron.consensus.base.ConsensusInterface; @@ -134,14 +134,14 @@ public boolean validBlock(BlockCapsule blockCapsule) { if (slot == 0 && consensusDelegate.getDynamicPropertiesStore().allowConsensusLogicOptimization()) { logger.warn("ValidBlock failed: slot error, witness: {}, timeStamp: {}", - ByteArray.toHexString(witnessAddress.toByteArray()), new DateTime(timeStamp)); + ByteArray.toHexString(witnessAddress.toByteArray()), Time.getIsoTimeString(timeStamp)); return false; } final ByteString scheduledWitness = dposSlot.getScheduledWitness(slot); if (!scheduledWitness.equals(witnessAddress)) { logger.warn("ValidBlock failed: sWitness: {}, bWitness: {}, bTimeStamp: {}, slot: {}", ByteArray.toHexString(scheduledWitness.toByteArray()), - ByteArray.toHexString(witnessAddress.toByteArray()), new DateTime(timeStamp), slot); + ByteArray.toHexString(witnessAddress.toByteArray()), Time.getIsoTimeString(timeStamp), slot); return false; } diff --git a/consensus/src/main/java/org/tron/consensus/dpos/DposTask.java b/consensus/src/main/java/org/tron/consensus/dpos/DposTask.java index 9e42552c80f..38f5614e571 100644 --- a/consensus/src/main/java/org/tron/consensus/dpos/DposTask.java +++ b/consensus/src/main/java/org/tron/consensus/dpos/DposTask.java @@ -6,7 +6,6 @@ import java.util.concurrent.ExecutorService; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; @@ -15,6 +14,7 @@ import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Sha256Hash; +import org.tron.common.utils.Time; import org.tron.consensus.ConsensusDelegate; import org.tron.consensus.base.Param.Miner; import org.tron.consensus.base.State; @@ -123,7 +123,7 @@ private State produceBlock() { BlockHeader.raw raw = blockCapsule.getInstance().getBlockHeader().getRawData(); logger.info("Produce block successfully, num: {}, time: {}, witness: {}, ID:{}, parentID:{}", raw.getNumber(), - new DateTime(raw.getTimestamp()), + Time.getIsoTimeString(raw.getTimestamp()), ByteArray.toHexString(raw.getWitnessAddress().toByteArray()), new Sha256Hash(raw.getNumber(), Sha256Hash.of(CommonParameter .getInstance().isECKeyCryptoEngine(), raw.toByteArray())), diff --git a/crypto/src/main/java/org/tron/common/crypto/Blake2bfMessageDigest.java b/crypto/src/main/java/org/tron/common/crypto/Blake2bfMessageDigest.java index 64073210493..d15ea5b2f56 100644 --- a/crypto/src/main/java/org/tron/common/crypto/Blake2bfMessageDigest.java +++ b/crypto/src/main/java/org/tron/common/crypto/Blake2bfMessageDigest.java @@ -76,22 +76,6 @@ public static class Blake2bfDigest implements Digest { v = new long[16]; } - // for tests - Blake2bfDigest( - final long[] h, final long[] m, final long[] t, final boolean f, final long rounds) { - assert rounds <= 4294967295L; // uint max value - buffer = new byte[MESSAGE_LENGTH_BYTES]; - bufferPos = 0; - - this.h = h; - this.m = m; - this.t = t; - this.f = f; - this.rounds = rounds; - - v = new long[16]; - } - @Override public String getAlgorithmName() { return "BLAKE2f"; diff --git a/framework/build.gradle b/framework/build.gradle index 8255fc30d18..5fbbbbc8916 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -1,5 +1,5 @@ plugins { - id "org.gradle.test-retry" version "1.5.9" + // id "org.gradle.test-retry" version "1.5.9" id "org.sonarqube" version "2.6" id "com.gorylenko.gradle-git-properties" version "2.4.1" } @@ -110,10 +110,10 @@ run { } def configureTestTask = { Task t -> - t.retry { - maxRetries = 5 - maxFailures = 20 - } + // t.retry { + // maxRetries = 5 + // maxFailures = 20 + // } t.testLogging { exceptionFormat = 'full' } diff --git a/framework/src/main/java/org/tron/common/application/GrpcNettyMaxConcurrentStreamsLimiter.java b/framework/src/main/java/org/tron/common/application/GrpcNettyMaxConcurrentStreamsLimiter.java deleted file mode 100644 index cdd71ffee3c..00000000000 --- a/framework/src/main/java/org/tron/common/application/GrpcNettyMaxConcurrentStreamsLimiter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * java-tron is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * java-tron 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with java-tron. If not, see . - */ - -package org.tron.common.application; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import io.grpc.netty.GrpcHttp2ConnectionHandler; -import io.grpc.netty.InternalProtocolNegotiator; -import io.grpc.netty.InternalProtocolNegotiators; -import io.grpc.netty.NettyServerBuilder; -import io.netty.channel.ChannelHandler; -import io.netty.util.AsciiString; - -/** Enforces the advertised HTTP/2 concurrent stream limit for grpc-netty servers. */ -final class GrpcNettyMaxConcurrentStreamsLimiter { - - private GrpcNettyMaxConcurrentStreamsLimiter() { - } - - static NettyServerBuilder configurePlaintext( - NettyServerBuilder builder, int maxConcurrentStreams) { - checkNotNull(builder, "builder"); - checkArgument(maxConcurrentStreams > 0, "maxConcurrentStreams must be positive"); - builder.maxConcurrentCallsPerConnection(maxConcurrentStreams); - // TODO: Remove this shim after https://github.com/grpc/grpc-java/issues/12930 is fixed. - return builder.protocolNegotiator(newPlaintextNegotiator(maxConcurrentStreams)); - } - - static InternalProtocolNegotiator.ProtocolNegotiator newPlaintextNegotiator( - int maxConcurrentStreams) { - checkArgument(maxConcurrentStreams > 0, "maxConcurrentStreams must be positive"); - return new EnforcingProtocolNegotiator( - InternalProtocolNegotiators.serverPlaintext(), maxConcurrentStreams); - } - - private static final class EnforcingProtocolNegotiator - implements InternalProtocolNegotiator.ProtocolNegotiator { - - private final InternalProtocolNegotiator.ProtocolNegotiator delegate; - private final int maxConcurrentStreams; - - private EnforcingProtocolNegotiator( - InternalProtocolNegotiator.ProtocolNegotiator delegate, int maxConcurrentStreams) { - this.delegate = checkNotNull(delegate, "delegate"); - this.maxConcurrentStreams = maxConcurrentStreams; - } - - @Override - public AsciiString scheme() { - return delegate.scheme(); - } - - @Override - public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { - // grpc-java builds the connection directly, bypassing Netty's builder-side enforcement. - grpcHandler.connection().remote().maxActiveStreams(maxConcurrentStreams); - return delegate.newHandler(grpcHandler); - } - - @Override - public void close() { - delegate.close(); - } - } -} diff --git a/framework/src/main/java/org/tron/common/application/RpcService.java b/framework/src/main/java/org/tron/common/application/RpcService.java index 27fcc479f4e..c398b71ae41 100644 --- a/framework/src/main/java/org/tron/common/application/RpcService.java +++ b/framework/src/main/java/org/tron/common/application/RpcService.java @@ -100,9 +100,8 @@ protected NettyServerBuilder initServerBuilder() { serverBuilder = serverBuilder.executor(this.executorService); } // Set configs from config.conf or default value - serverBuilder = GrpcNettyMaxConcurrentStreamsLimiter.configurePlaintext( - serverBuilder, parameter.getMaxConcurrentCallsPerConnection()); serverBuilder + .maxConcurrentCallsPerConnection(parameter.getMaxConcurrentCallsPerConnection()) .flowControlWindow(parameter.getFlowControlWindow()) .maxConnectionIdle(parameter.getMaxConnectionIdleInMillis(), TimeUnit.MILLISECONDS) .maxConnectionAge(parameter.getMaxConnectionAgeInMillis(), TimeUnit.MILLISECONDS) diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index 0bca242606e..8d56a2193f0 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -1045,8 +1045,9 @@ private static void loadDnsPublishParameters(NodeConfig.DnsConfig dns, String serverType = dns.getServerType(); if (StringUtils.isNotEmpty(serverType)) { if (!"aws".equalsIgnoreCase(serverType) && !"aliyun".equalsIgnoreCase(serverType)) { - throw new IllegalArgumentException( - "Check node.dns.serverType, must be aws or aliyun"); + throw new TronError( + "Check node.dns.serverType, must be aws or aliyun", + TronError.ErrCode.PARAMETER_INIT); } if ("aws".equalsIgnoreCase(serverType)) { publishConfig.setDnsType(DnsType.AwsRoute53); @@ -1088,7 +1089,8 @@ private static void loadDnsPublishParameters(NodeConfig.DnsConfig dns, } private static void logEmptyError(String arg) { - throw new IllegalArgumentException(String.format("Check %s, must not be null or empty", arg)); + throw new TronError(String.format("Check %s, must not be null or empty", arg), + TronError.ErrCode.PARAMETER_INIT); } // createTriggerConfig removed — logic moved to applyEventConfig() @@ -1315,4 +1317,3 @@ private static Map getOptionGroup() { return optionGroupMap; } } - diff --git a/framework/src/main/java/org/tron/core/metrics/net/NetMetricManager.java b/framework/src/main/java/org/tron/core/metrics/net/NetMetricManager.java index 38dfccff05a..037580037e3 100644 --- a/framework/src/main/java/org/tron/core/metrics/net/NetMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/net/NetMetricManager.java @@ -181,7 +181,7 @@ public Protocol.MetricsInfo.NetInfo getNetProtoInfo() { // udp RateInfo udpInTraffic = net.getUdpInTraffic(); Protocol.MetricsInfo.RateInfo udpInTrafficInfo = udpInTraffic.toProtoEntity(); - netInfo.setTcpOutTraffic(udpInTrafficInfo); + netInfo.setUdpInTraffic(udpInTrafficInfo); RateInfo udpOutTraffic = net.getUdpOutTraffic(); Protocol.MetricsInfo.RateInfo udpOutTrafficInfo = udpOutTraffic.toProtoEntity(); netInfo.setUdpOutTraffic(udpOutTrafficInfo); diff --git a/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java index 2320fc87c7d..a953ae11802 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBlockServlet.java @@ -77,9 +77,7 @@ private void fillResponse(boolean visible, BlockReq request, HttpServletResponse response.getWriter().println("{}"); } } catch (IllegalArgumentException e) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("Error", e.getMessage()); - response.getWriter().println(jsonObject.toJSONString()); + Util.writeAuditedError(e.getMessage(), response); } } diff --git a/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java index 1fbd94fe690..b735878d1e1 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBrokerageServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -27,12 +26,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { } response.getWriter().println("{\"brokerage\": " + value + "}"); } catch (DecoderException | IllegalArgumentException e) { - try { - response.getWriter() - .println("{\"Error\": " + "\"INVALID address, " + e.getMessage() + "\"}"); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.writeAuditedError(Util.INVALID_ADDRESS_MSG, response); } catch (Exception e) { Util.processError(e, response); } diff --git a/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java b/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java index ea066a6e98c..3a19825ba75 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetBurnTrxServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -24,12 +23,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { : "{\"burnTrxAmount\": " + value + "}"; response.getWriter().println(out); } catch (Exception e) { - logger.error("", e); - try { - response.getWriter().println(Util.printErrorMsg(e)); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processServerError(e, response); } } diff --git a/framework/src/main/java/org/tron/core/services/http/GetNodeInfoServlet.java b/framework/src/main/java/org/tron/core/services/http/GetNodeInfoServlet.java index 0b8f7b9ce2b..c8b4aa39785 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetNodeInfoServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetNodeInfoServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -24,12 +23,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { response.getWriter().println(JSON.toJSONString(nodeInfo)); } catch (Exception e) { - logger.error("", e); - try { - response.getWriter().println(Util.printErrorMsg(e)); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processServerError(e, response); } } diff --git a/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java b/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java index 9788c926586..41a47c49001 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetPendingSizeServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -24,12 +23,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { : "{\"pendingSize\": " + value + "}"; response.getWriter().println(out); } catch (Exception e) { - logger.error("", e); - try { - response.getWriter().println(Util.printErrorMsg(e)); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processServerError(e, response); } } diff --git a/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java b/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java index 61b88d1160f..780bab6ac94 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetRewardServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -29,19 +28,9 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { : "{\"reward\": " + value + "}"; response.getWriter().println(out); } catch (DecoderException | IllegalArgumentException e) { - try { - response.getWriter() - .println("{\"Error\": " + "\"INVALID address, " + e.getMessage() + "\"}"); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.writeAuditedError(Util.INVALID_ADDRESS_MSG, response); } catch (Exception e) { - logger.error("", e); - try { - response.getWriter().println(Util.printErrorMsg(e)); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processServerError(e, response); } } diff --git a/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java b/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java index 5d0a09b1a68..25998c909b6 100644 --- a/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/GetTransactionInfoByBlockNumServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.io.IOException; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -52,12 +51,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { response.getWriter().println("{}"); } } catch (Exception e) { - logger.debug("Exception: {}", e.getMessage()); - try { - response.getWriter().println(Util.printErrorMsg(e)); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processError(e, response); } } @@ -75,12 +69,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println("{}"); } } catch (Exception e) { - logger.debug("Exception: {}", e.getMessage()); - try { - response.getWriter().println(Util.printErrorMsg(e)); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processError(e, response); } } } diff --git a/framework/src/main/java/org/tron/core/services/http/RateLimiterServlet.java b/framework/src/main/java/org/tron/core/services/http/RateLimiterServlet.java index b5ae7d58623..6f67aba3020 100644 --- a/framework/src/main/java/org/tron/core/services/http/RateLimiterServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/RateLimiterServlet.java @@ -131,8 +131,7 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) super.service(req, resp); Metrics.histogramObserve(requestTimer); } else { - resp.getWriter() - .println(Util.printErrorMsg(new IllegalAccessException("lack of computing resources"))); + Util.writeAuditedError(Util.RATE_LIMITER_ERROR_MSG, resp); } } catch (ServletException | IOException | BadMessageException e) { throw e; diff --git a/framework/src/main/java/org/tron/core/services/http/Util.java b/framework/src/main/java/org/tron/core/services/http/Util.java index 5be2495e1f7..ca20902c4d8 100644 --- a/framework/src/main/java/org/tron/core/services/http/Util.java +++ b/framework/src/main/java/org/tron/core/services/http/Util.java @@ -48,6 +48,8 @@ import org.tron.core.capsule.TransactionCapsule; import org.tron.core.config.args.Args; import org.tron.core.db.TransactionTrace; +import org.tron.core.exception.ContractValidateException; +import org.tron.core.exception.MaintenanceUnavailableException; import org.tron.core.services.http.JsonFormat.ParseException; import org.tron.json.JSON; import org.tron.json.JSONArray; @@ -65,6 +67,10 @@ @Slf4j(topic = "API") public class Util { + private static final String INTERNAL_SERVER_ERROR = "internal server error"; + public static final String RATE_LIMITER_ERROR_MSG = "lack of computing resources"; + static final String INVALID_ADDRESS_MSG = "INVALID address"; + public static final String EVENTS_DEPRECATED_MSG = "'events' field is deprecated and no longer supported"; @@ -114,12 +120,31 @@ public static String printTransactionFee(String transactionFee) { return jsonObject.toJSONString(); } - public static String printErrorMsg(Exception e) { + private static String printErrorMsg(String msg) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("Error", e.getClass() + " : " + e.getMessage()); + jsonObject.put("Error", msg); return jsonObject.toJSONString(); } + private static String clientMessage(Exception e) { + if (e == null) { + return INTERNAL_SERVER_ERROR; + } + + Class type = e.getClass(); + if (type == IllegalArgumentException.class) { + return EVENTS_DEPRECATED_MSG.equals(e.getMessage()) + ? EVENTS_DEPRECATED_MSG : INTERNAL_SERVER_ERROR; + } + if (type == ParseException.class + || type == ContractValidateException.class + || type == MaintenanceUnavailableException.class) { + String message = e.getMessage(); + return StringUtils.isBlank(message) ? INTERNAL_SERVER_ERROR : message; + } + return INTERNAL_SERVER_ERROR; + } + public static String printBlockList(BlockList list, boolean selfType) { List blocks = list.getBlockList(); JSONObject jsonObject = new JSONObject(); @@ -526,11 +551,24 @@ public static String getMemo(byte[] memo) { } public static void processError(Exception e, HttpServletResponse response) { - logger.debug(e.getMessage(), e); + logger.debug("HTTP request failed", e); + writeAuditedError(clientMessage(e), response); + } + + // For catch blocks that cover server-side work only, so the failure stays visible at the + // default log level. The Exception entry point above keeps debug because its callers also + // cover request parsing, which an unauthenticated client can fail cheaply and repeatedly. + static void processServerError(Exception e, HttpServletResponse response) { + logger.error("HTTP request failed", e); + writeAuditedError(clientMessage(e), response); + } + + // Bypasses clientMessage: callers must pass audited fixed or pre-existing client texts only. + static void writeAuditedError(String msg, HttpServletResponse response) { try { - response.getWriter().println(Util.printErrorMsg(e)); + response.getWriter().println(Util.printErrorMsg(msg)); } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); + logger.debug("Failed to write HTTP error response", ioe); } } diff --git a/framework/src/main/java/org/tron/core/services/http/ValidateAddressServlet.java b/framework/src/main/java/org/tron/core/services/http/ValidateAddressServlet.java index 07eecfc5466..3ef45b42a7e 100644 --- a/framework/src/main/java/org/tron/core/services/http/ValidateAddressServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/ValidateAddressServlet.java @@ -47,7 +47,7 @@ private String validAddress(String input) { } } catch (Exception e) { result = false; - msg = e.getMessage(); + msg = "Invalid address"; } JSONObject jsonAddress = new JSONObject(); diff --git a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java index f98c7450afc..5998bc0850f 100644 --- a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServlet.java @@ -30,12 +30,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { String input = request.getParameter("value"); fillResponse(ByteString.copyFrom(ByteArray.fromHexString(input)), visible, response); } catch (Exception e) { - logger.debug("Exception: {}", e.getMessage()); - try { - response.getWriter().println(e.getMessage()); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processError(e, response); } } @@ -46,12 +41,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) JsonFormat.merge(params.getParams(), build, params.isVisible()); fillResponse(build.build().getValue(), params.isVisible(), response); } catch (Exception e) { - logger.debug("Exception: {}", e.getMessage()); - try { - response.getWriter().println(e.getMessage()); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processError(e, response); } } diff --git a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java index 0408215f09d..197f5aaec0d 100644 --- a/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServlet.java @@ -1,7 +1,6 @@ package org.tron.core.services.http.solidity; import com.google.protobuf.ByteString; -import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -37,12 +36,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { response.getWriter().println(JsonFormat.printToString(transInfo, visible)); } } catch (Exception e) { - logger.debug("Exception: {}", e.getMessage()); - try { - response.getWriter().println(e.getMessage()); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processError(e, response); } } @@ -60,12 +54,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println(JsonFormat.printToString(transInfo, params.isVisible())); } } catch (Exception e) { - logger.debug("Exception: {}", e.getMessage()); - try { - response.getWriter().println(e.getMessage()); - } catch (IOException ioe) { - logger.debug("IOException: {}", ioe.getMessage()); - } + Util.processError(e, response); } } diff --git a/framework/src/test/java/org/tron/common/BaseMethodTest.java b/framework/src/test/java/org/tron/common/BaseMethodTest.java index 9ee1dfa3b36..c91310681a1 100644 --- a/framework/src/test/java/org/tron/common/BaseMethodTest.java +++ b/framework/src/test/java/org/tron/common/BaseMethodTest.java @@ -10,6 +10,7 @@ import org.tron.common.application.Application; import org.tron.common.application.ApplicationFactory; import org.tron.common.application.TronApplicationContext; +import org.tron.common.utils.PeerManagerStateResetter; import org.tron.core.ChainBaseManager; import org.tron.core.config.DefaultConfig; import org.tron.core.config.args.Args; @@ -42,6 +43,9 @@ public abstract class BaseMethodTest { @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Rule + public final VMConfigRule vmConfigRule = new VMConfigRule(); + protected TronApplicationContext context; protected Application appT; protected Manager dbManager; @@ -57,6 +61,7 @@ protected String configFile() { @Before public final void initContext() throws IOException { + PeerManagerStateResetter.reset(); String[] baseArgs = new String[]{ "--output-directory", temporaryFolder.newFolder().toString()}; String[] allArgs = mergeArgs(baseArgs, extraArgs()); diff --git a/framework/src/test/java/org/tron/common/BaseTest.java b/framework/src/test/java/org/tron/common/BaseTest.java index 6d075a2d6aa..471aaa3d383 100644 --- a/framework/src/test/java/org/tron/common/BaseTest.java +++ b/framework/src/test/java/org/tron/common/BaseTest.java @@ -7,7 +7,9 @@ import lombok.extern.slf4j.Slf4j; import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Before; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.springframework.test.annotation.DirtiesContext; @@ -17,6 +19,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.Commons; +import org.tron.common.utils.PeerManagerStateResetter; import org.tron.common.utils.Sha256Hash; import org.tron.consensus.base.Param; import org.tron.core.ChainBaseManager; @@ -64,6 +67,9 @@ public abstract class BaseTest { @ClassRule public static final TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Rule + public final VMConfigRule vmConfigRule = new VMConfigRule(); + @Resource protected Manager dbManager; @Resource @@ -75,6 +81,11 @@ public abstract class BaseTest { private static Application appT1; + @Before + public void resetPeerManagerState() { + PeerManagerStateResetter.reset(); + } + @PostConstruct private void prepare() { appT1 = appT; diff --git a/framework/src/test/java/org/tron/common/VMConfigRule.java b/framework/src/test/java/org/tron/common/VMConfigRule.java new file mode 100644 index 00000000000..2ee7374d816 --- /dev/null +++ b/framework/src/test/java/org/tron/common/VMConfigRule.java @@ -0,0 +1,52 @@ +package org.tron.common; + +import java.lang.reflect.Field; +import org.junit.rules.ExternalResource; +import org.tron.common.parameter.CommonParameter; +import org.tron.core.vm.config.ConfigLoader; +import org.tron.core.vm.config.VMConfig; + +/** + * Restores VM flags after each test, including failed setup and assertion paths. + * + *

Snapshotting enumerates {@link VMConfig.Snapshot} fields reflectively, so any static flag + * not mirrored there is outside this rule's protection: when adding a static flag to + * {@link VMConfig} or {@link ConfigLoader}, it must also be mirrored into + * {@code VMConfig.Snapshot} (before/after save and restore) or it will leak across tests. + * + *

This is a method-level rule: the baseline is captured before every test method, so global + * flags written from class-level {@code @BeforeClass} code are not covered — such classes must + * add their own {@code @AfterClass} to reset them manually (a leaked London hard-fork flag from + * class-level setup is an instance of exactly this gap). + */ +public class VMConfigRule extends ExternalResource { + + private VMConfig.Snapshot savedSnapshot; + private boolean savedLoaderDisabled; + private boolean savedHardFork; + private boolean savedTrace; + + @Override + protected void before() throws Exception { + Field global = VMConfig.class.getDeclaredField("globalSnapshot"); + global.setAccessible(true); + VMConfig.Snapshot current = (VMConfig.Snapshot) global.get(null); + savedSnapshot = new VMConfig.Snapshot(); + // init* methods mutate the snapshot in place, so saving only its reference is insufficient. + for (Field flag : VMConfig.Snapshot.class.getFields()) { + flag.set(savedSnapshot, flag.get(current)); + } + savedLoaderDisabled = ConfigLoader.disable; + savedHardFork = CommonParameter.ENERGY_LIMIT_HARD_FORK; + savedTrace = VMConfig.vmTrace(); + VMConfig.clearLocalSnapshot(); + } + + @Override + protected void after() { + VMConfig.setGlobalSnapshot(savedSnapshot); + ConfigLoader.disable = savedLoaderDisabled; + VMConfig.initVmHardFork(savedHardFork); + VMConfig.setVmTrace(savedTrace); + } +} diff --git a/framework/src/test/java/org/tron/common/application/GrpcNettyMaxConcurrentStreamsLimiterTest.java b/framework/src/test/java/org/tron/common/application/GrpcNettyMaxConcurrentStreamsLimiterTest.java deleted file mode 100644 index fc578ca7947..00000000000 --- a/framework/src/test/java/org/tron/common/application/GrpcNettyMaxConcurrentStreamsLimiterTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * java-tron is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * java-tron 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with java-tron. If not, see . - */ - -package org.tron.common.application; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; - -import io.grpc.ChannelLogger; -import io.grpc.ChannelLogger.ChannelLogLevel; -import io.grpc.netty.GrpcHttp2ConnectionHandler; -import io.grpc.netty.InternalProtocolNegotiator; -import io.netty.channel.ChannelHandler; -import io.netty.handler.codec.http2.DefaultHttp2Connection; -import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder; -import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder; -import io.netty.handler.codec.http2.DefaultHttp2FrameReader; -import io.netty.handler.codec.http2.DefaultHttp2FrameWriter; -import io.netty.handler.codec.http2.Http2Connection; -import io.netty.handler.codec.http2.Http2ConnectionDecoder; -import io.netty.handler.codec.http2.Http2ConnectionEncoder; -import io.netty.handler.codec.http2.Http2Error; -import io.netty.handler.codec.http2.Http2Exception; -import io.netty.handler.codec.http2.Http2FrameWriter; -import io.netty.handler.codec.http2.Http2Settings; -import org.junit.Test; - -public class GrpcNettyMaxConcurrentStreamsLimiterTest { - - private static final ChannelLogger NOOP_LOGGER = new ChannelLogger() { - @Override - public void log(ChannelLogLevel level, String message) { - } - - @Override - public void log(ChannelLogLevel level, String messageFormat, Object... args) { - } - }; - - @Test - public void shouldEnforceMaxStreamsBeforeSettingsAck() throws Exception { - Http2Connection connection = new DefaultHttp2Connection(true); - GrpcHttp2ConnectionHandler grpcHandler = newGrpcHandler(connection); - InternalProtocolNegotiator.ProtocolNegotiator negotiator = - GrpcNettyMaxConcurrentStreamsLimiter.newPlaintextNegotiator(2); - - ChannelHandler negotiationHandler = negotiator.newHandler(grpcHandler); - - assertNotNull(negotiationHandler); - assertEquals(2, connection.remote().maxActiveStreams()); - connection.remote().createStream(1, true); - connection.remote().createStream(3, true); - Http2Exception exception = assertThrows( - Http2Exception.class, () -> connection.remote().createStream(5, true)); - assertEquals(Http2Error.REFUSED_STREAM, exception.error()); - negotiator.close(); - } - - @Test - public void shouldIgnoreClientMaxHeaderListSizeOnServer() throws Exception { - Http2Connection connection = new DefaultHttp2Connection(true); - Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter(); - Http2ConnectionEncoder encoder = - new DefaultHttp2ConnectionEncoder(connection, frameWriter); - long originalMaxHeaderListSize = - encoder.configuration().headersConfiguration().maxHeaderListSize(); - - encoder.remoteSettings(new Http2Settings().maxHeaderListSize(1)); - - assertEquals(originalMaxHeaderListSize, - encoder.configuration().headersConfiguration().maxHeaderListSize()); - encoder.close(); - } - - @Test - public void shouldRejectNonPositiveStreamLimit() { - IllegalArgumentException zeroLimitException = assertThrows(IllegalArgumentException.class, - () -> GrpcNettyMaxConcurrentStreamsLimiter.newPlaintextNegotiator(0)); - assertEquals("maxConcurrentStreams must be positive", zeroLimitException.getMessage()); - IllegalArgumentException negativeLimitException = assertThrows(IllegalArgumentException.class, - () -> GrpcNettyMaxConcurrentStreamsLimiter.newPlaintextNegotiator(-1)); - assertEquals("maxConcurrentStreams must be positive", negativeLimitException.getMessage()); - } - - private static GrpcHttp2ConnectionHandler newGrpcHandler(Http2Connection connection) { - Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter(); - Http2ConnectionEncoder encoder = - new DefaultHttp2ConnectionEncoder(connection, frameWriter); - Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder( - connection, encoder, new DefaultHttp2FrameReader()); - return new GrpcHttp2ConnectionHandler( - null, decoder, encoder, new Http2Settings(), NOOP_LOGGER) { - }; - } -} diff --git a/framework/src/test/java/org/tron/common/application/NettyHttp2HeaderSecurityTest.java b/framework/src/test/java/org/tron/common/application/NettyHttp2HeaderSecurityTest.java new file mode 100644 index 00000000000..6a4f4330f04 --- /dev/null +++ b/framework/src/test/java/org/tron/common/application/NettyHttp2HeaderSecurityTest.java @@ -0,0 +1,53 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with java-tron. If not, see . + */ + +package org.tron.common.application; + +import static org.junit.Assert.assertEquals; + +import io.netty.handler.codec.http2.DefaultHttp2Connection; +import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder; +import io.netty.handler.codec.http2.DefaultHttp2FrameWriter; +import io.netty.handler.codec.http2.Http2Connection; +import io.netty.handler.codec.http2.Http2ConnectionEncoder; +import io.netty.handler.codec.http2.Http2FrameWriter; +import io.netty.handler.codec.http2.Http2Settings; +import org.junit.Test; + +/** Guards the netty HTTP/2 header-size behaviour the gRPC server relies on. */ +public class NettyHttp2HeaderSecurityTest { + + /** + * CVE-2026-50560: SETTINGS_MAX_HEADER_LIST_SIZE tells the server what the client is willing to + * receive, so it must not shrink the server encoder's own limit. Otherwise a hostile client can + * advertise a tiny value and make every response-header write throw, which is a Rapid-Reset-like + * denial of service. Netty enforced the client value before 4.1.135.Final / 4.2.15.Final. + */ + @Test + public void shouldIgnoreClientMaxHeaderListSizeOnServer() throws Exception { + Http2Connection connection = new DefaultHttp2Connection(true); + Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter(); + Http2ConnectionEncoder encoder = + new DefaultHttp2ConnectionEncoder(connection, frameWriter); + long originalMaxHeaderListSize = + encoder.configuration().headersConfiguration().maxHeaderListSize(); + + encoder.remoteSettings(new Http2Settings().maxHeaderListSize(1)); + + assertEquals(originalMaxHeaderListSize, + encoder.configuration().headersConfiguration().maxHeaderListSize()); + encoder.close(); + } +} diff --git a/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java b/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java index 5ff02fc8cb5..0efbb13a481 100644 --- a/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java +++ b/framework/src/test/java/org/tron/common/backup/BackupManagerTest.java @@ -1,5 +1,6 @@ package org.tron.common.backup; +import io.netty.channel.Channel; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetAddress; @@ -9,8 +10,8 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import org.junit.After; import org.junit.Assert; @@ -25,6 +26,7 @@ import org.tron.common.backup.socket.UdpEvent; import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.PublicMethod; +import org.tron.common.utils.ReflectUtils; import org.tron.core.config.args.Args; import org.tron.core.config.args.InetUtil; @@ -34,7 +36,8 @@ public class BackupManagerTest { public TemporaryFolder temporaryFolder = new TemporaryFolder(); private BackupManager manager; private BackupServer backupServer; - private BiFunction savedLookup; + private BiFunction previousDnsLookup; + private boolean backupServerClosed; @Before public void setUp() throws Exception { @@ -43,13 +46,36 @@ public void setUp() throws Exception { CommonParameter.getInstance().setBackupPort(PublicMethod.chooseRandomPort()); manager = new BackupManager(); backupServer = new BackupServer(manager); - savedLookup = InetUtil.dnsLookup; + previousDnsLookup = InetUtil.dnsLookup; } @After - public void tearDown() { - InetUtil.dnsLookup = savedLookup; + public void tearDown() throws Exception { + List errors = new ArrayList<>(); + Channel channel = null; + if (backupServer != null) { + try { + channel = BackupTestUtils.getChannel(backupServer); + } catch (Throwable t) { + errors.add(t); + } + } + if (!backupServerClosed && backupServer != null) { + BackupTestUtils.runQuietly(errors, backupServer::close); + } + if (manager != null) { + BackupTestUtils.runQuietly(errors, manager::stop); + } + Channel captured = channel; + BackupTestUtils.runQuietly(errors, () -> { + if (captured != null) { + Assert.assertFalse("backup channel must close", captured.isOpen()); + } + BackupTestUtils.assertExecutorsTerminated(manager, backupServer); + }); + InetUtil.dnsLookup = previousDnsLookup; Args.clearParam(); + BackupTestUtils.throwIfAnyError(errors); } @Test @@ -121,7 +147,7 @@ public void test() throws Exception { } @Test - public void testSendKeepAliveMessage() throws Exception { + public void testBackupServerLifecycleDuringKeepAliveInterval() throws Exception { CommonParameter parameter = CommonParameter.getInstance(); parameter.setBackupPriority(8); List members = new ArrayList<>(); @@ -134,21 +160,19 @@ public void testSendKeepAliveMessage() throws Exception { Assert.assertEquals(manager.getStatus(), BackupManager.BackupStatusEnum.MASTER); backupServer.initServer(); + awaitBackupServerReady(); manager.init(); - - Thread.sleep(parameter.getKeepAliveInterval() + 1000);//test send KeepAliveMessage - - field = manager.getClass().getDeclaredField("executorService"); - field.setAccessible(true); - ScheduledExecutorService executorService = (ScheduledExecutorService) field.get(manager); - executorService.shutdown(); - - Field field2 = backupServer.getClass().getDeclaredField("executor"); - field2.setAccessible(true); - ExecutorService executorService2 = (ExecutorService) field2.get(backupServer); - executorService2.shutdown(); + long keepAliveDeadline = System.nanoTime() + + TimeUnit.MILLISECONDS.toNanos(parameter.getKeepAliveInterval() + 1000L); + BackupTestUtils.awaitCondition("keep-alive interval", + () -> System.nanoTime() >= keepAliveDeadline); Assert.assertEquals(BackupManager.BackupStatusEnum.INIT, manager.getStatus()); + Channel channel = BackupTestUtils.getChannel(backupServer); + backupServer.close(); + backupServerClosed = true; + Assert.assertFalse("backup channel must close", channel.isOpen()); + BackupTestUtils.assertExecutorsTerminated(manager, backupServer); } // ===== domain-handling tests for init() ===== @@ -161,8 +185,8 @@ public void testInitResolvesDomainsToMembers() throws Exception { InetUtil.dnsLookup = (host, ipv4) -> ("node.example.com".equals(host) && ipv4) ? resolved : null; manager.init(); - Set members = getField(manager, "members"); - Map cache = getField(manager, "domainIpCache"); + Set members = ReflectUtils.getFieldValue(manager, "members"); + Map cache = ReflectUtils.getFieldValue(manager, "domainIpCache"); Assert.assertTrue(members.contains("1.2.3.4")); Assert.assertEquals("1.2.3.4", cache.get("node.example.com")); manager.stop(); @@ -174,8 +198,8 @@ public void testInitSkipsUnresolvableDomain() throws Exception { Collections.singletonList("bad.invalid.domain")); InetUtil.dnsLookup = (host, ipv4) -> null; manager.init(); - Set members = getField(manager, "members"); - Map cache = getField(manager, "domainIpCache"); + Set members = ReflectUtils.getFieldValue(manager, "members"); + Map cache = ReflectUtils.getFieldValue(manager, "domainIpCache"); Assert.assertTrue("unresolvable domain should be silently dropped", members.isEmpty()); Assert.assertTrue(cache.isEmpty()); manager.stop(); @@ -190,7 +214,7 @@ public void testInitSkipsDomainResolvingToLocalIp() throws Exception { InetUtil.dnsLookup = (host, ipv4) -> ("self.local.host".equals(host) && ipv4) ? selfAddr : null; manager.init(); - Set members = getField(manager, "members"); + Set members = ReflectUtils.getFieldValue(manager, "members"); Assert.assertFalse("domain resolving to local IP should not be in members", members.contains(localIp)); manager.stop(); @@ -200,8 +224,8 @@ public void testInitSkipsDomainResolvingToLocalIp() throws Exception { @Test(timeout = 5000) public void testRefreshMemberIpsIpChanged() throws Exception { - Set members = getField(manager, "members"); - Map cache = getField(manager, "domainIpCache"); + Set members = ReflectUtils.getFieldValue(manager, "members"); + Map cache = ReflectUtils.getFieldValue(manager, "domainIpCache"); members.add("1.1.1.1"); cache.put("peer.tron.network", "1.1.1.1"); @@ -216,8 +240,8 @@ public void testRefreshMemberIpsIpChanged() throws Exception { @Test(timeout = 5000) public void testRefreshMemberIpsIpUnchanged() throws Exception { - Set members = getField(manager, "members"); - Map cache = getField(manager, "domainIpCache"); + Set members = ReflectUtils.getFieldValue(manager, "members"); + Map cache = ReflectUtils.getFieldValue(manager, "domainIpCache"); members.add("1.1.1.1"); cache.put("peer.tron.network", "1.1.1.1"); @@ -231,8 +255,8 @@ public void testRefreshMemberIpsIpUnchanged() throws Exception { @Test(timeout = 5000) public void testRefreshMemberIpsDnsFailure() throws Exception { - Set members = getField(manager, "members"); - Map cache = getField(manager, "domainIpCache"); + Set members = ReflectUtils.getFieldValue(manager, "members"); + Map cache = ReflectUtils.getFieldValue(manager, "domainIpCache"); members.add("1.1.1.1"); cache.put("peer.tron.network", "1.1.1.1"); @@ -242,16 +266,18 @@ public void testRefreshMemberIpsDnsFailure() throws Exception { Assert.assertEquals("1.1.1.1", cache.get("peer.tron.network")); } - @SuppressWarnings("unchecked") - private T getField(Object obj, String name) throws Exception { - Field f = obj.getClass().getDeclaredField(name); - f.setAccessible(true); - return (T) f.get(obj); - } - private void invokeRefreshMemberIps(BackupManager mgr) throws Exception { Method m = mgr.getClass().getDeclaredMethod("refreshMemberIps"); m.setAccessible(true); m.invoke(mgr); } + + private void awaitBackupServerReady() throws Exception { + BackupTestUtils.awaitCondition("backup channel to become active", + () -> BackupTestUtils.getChannel(backupServer) != null + && BackupTestUtils.getChannel(backupServer).isActive()); + BackupTestUtils.awaitCondition("backup message handler assignment", + () -> ReflectUtils.getFieldObject(manager, "messageHandler") != null); + } + } diff --git a/framework/src/test/java/org/tron/common/backup/BackupServerTest.java b/framework/src/test/java/org/tron/common/backup/BackupServerTest.java index 50778970d87..b1d60d5d38b 100644 --- a/framework/src/test/java/org/tron/common/backup/BackupServerTest.java +++ b/framework/src/test/java/org/tron/common/backup/BackupServerTest.java @@ -1,8 +1,10 @@ package org.tron.common.backup; +import io.netty.channel.Channel; import java.util.ArrayList; import java.util.List; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -23,6 +25,8 @@ public class BackupServerTest { @Rule public Timeout globalTimeout = Timeout.seconds(60); private BackupServer backupServer; + private BackupManager backupManager; + private boolean backupServerClosed; @Before public void setUp() throws Exception { @@ -32,21 +36,39 @@ public void setUp() throws Exception { List members = new ArrayList<>(); members.add("127.0.0.2"); CommonParameter.getInstance().setBackupMembers(members); - BackupManager backupManager = new BackupManager(); + backupManager = new BackupManager(); backupManager.init(); backupServer = new BackupServer(backupManager); } @After - public void tearDown() { - backupServer.close(); + public void tearDown() throws Exception { + List errors = new ArrayList<>(); + if (!backupServerClosed && backupServer != null) { + BackupTestUtils.runQuietly(errors, backupServer::close); + } + if (backupManager != null) { + BackupTestUtils.runQuietly(errors, backupManager::stop); + } + BackupTestUtils.runQuietly(errors, + () -> BackupTestUtils.assertExecutorsTerminated(backupManager, backupServer)); Args.clearParam(); + BackupTestUtils.throwIfAnyError(errors); } @Test(timeout = 60_000) - public void test() throws InterruptedException { + public void test() throws Exception { backupServer.initServer(); - // wait for the server to start so channel is assigned before close() is called - Thread.sleep(1000); + BackupTestUtils.awaitCondition("backup channel to become active", + () -> BackupTestUtils.getChannel(backupServer) != null + && BackupTestUtils.getChannel(backupServer).isActive()); + Channel channel = BackupTestUtils.getChannel(backupServer); + Assert.assertTrue("backup channel must be active after startup", channel.isActive()); + + backupServer.close(); + backupServerClosed = true; + + Assert.assertFalse("backup channel must close", channel.isOpen()); + BackupTestUtils.assertExecutorsTerminated(backupManager, backupServer); } } diff --git a/framework/src/test/java/org/tron/common/backup/BackupTestUtils.java b/framework/src/test/java/org/tron/common/backup/BackupTestUtils.java new file mode 100644 index 00000000000..45f2cae596f --- /dev/null +++ b/framework/src/test/java/org/tron/common/backup/BackupTestUtils.java @@ -0,0 +1,72 @@ +package org.tron.common.backup; + +import io.netty.channel.Channel; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; +import org.junit.Assert; +import org.junit.function.ThrowingRunnable; +import org.tron.common.backup.socket.BackupServer; +import org.tron.common.utils.ReflectUtils; + +/** + * Shared reflection/await/cleanup helpers for backup tests. Assertion messages and timeout + * parameters mirror the helpers they replace, so failure output is unchanged. + */ +public final class BackupTestUtils { + + private BackupTestUtils() { + } + + public static Channel getChannel(BackupServer server) { + try { + return (Channel) ReflectUtils.getFieldObject(server, "channel"); + } catch (Exception e) { + throw new AssertionError("cannot inspect backup channel", e); + } + } + + public static void awaitCondition(String description, BooleanSupplier condition) + throws Exception { + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); + while (System.nanoTime() < deadline) { + if (condition.getAsBoolean()) { + return; + } + Thread.sleep(20); + } + Assert.fail("timed out waiting for " + description); + } + + public static void assertExecutorsTerminated(BackupManager manager, BackupServer server) + throws Exception { + if (manager == null || server == null) { + return; + } + ExecutorService managerExecutor = + (ExecutorService) ReflectUtils.getFieldObject(manager, "executorService"); + Assert.assertTrue("backup manager executor must terminate", managerExecutor.isTerminated()); + ExecutorService serverExecutor = + (ExecutorService) ReflectUtils.getFieldObject(server, "executor"); + if (serverExecutor != null) { + Assert.assertTrue("backup server executor must terminate", serverExecutor.isTerminated()); + } + } + + public static void runQuietly(List errors, ThrowingRunnable step) { + try { + step.run(); + } catch (Throwable t) { + errors.add(t); + } + } + + public static void throwIfAnyError(List errors) { + if (!errors.isEmpty()) { + AssertionError failure = new AssertionError("backup test cleanup failed"); + errors.forEach(failure::addSuppressed); + throw failure; + } + } +} diff --git a/framework/src/test/java/org/tron/common/logsfilter/EventLoaderTest.java b/framework/src/test/java/org/tron/common/logsfilter/EventLoaderTest.java index 958af4f7b7b..0857b1b9391 100644 --- a/framework/src/test/java/org/tron/common/logsfilter/EventLoaderTest.java +++ b/framework/src/test/java/org/tron/common/logsfilter/EventLoaderTest.java @@ -15,6 +15,7 @@ import org.pf4j.PluginWrapper; import org.tron.common.logsfilter.trigger.BlockLogTrigger; import org.tron.common.logsfilter.trigger.TransactionLogTrigger; +import org.tron.common.utils.PublicMethod; public class EventLoaderTest { @@ -22,7 +23,7 @@ public class EventLoaderTest { public void launchNativeQueue() { EventPluginConfig config = new EventPluginConfig(); config.setSendQueueLength(1000); - config.setBindPort(5555); + config.setBindPort(PublicMethod.chooseRandomPort()); config.setUseNativeQueue(true); config.setPluginPath("pluginPath"); config.setServerAddress("serverAddress"); @@ -48,9 +49,11 @@ public void launchNativeQueue() { config.setTriggerConfigList(triggerConfigList); - assertTrue(EventPluginLoader.getInstance().start(config)); - - EventPluginLoader.getInstance().stopPlugin(); + try { + assertTrue(EventPluginLoader.getInstance().start(config)); + } finally { + EventPluginLoader.getInstance().stopPlugin(); + } } @Test diff --git a/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java b/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java index 5219654977b..b32f1c22d39 100644 --- a/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java +++ b/framework/src/test/java/org/tron/common/logsfilter/NativeMessageQueueTest.java @@ -6,13 +6,16 @@ import org.junit.Test; import org.tron.common.es.ExecutorServiceManager; import org.tron.common.logsfilter.nativequeue.NativeMessageQueue; +import org.tron.common.utils.PublicMethod; import org.zeromq.SocketType; import org.zeromq.ZContext; import org.zeromq.ZMQ; public class NativeMessageQueueTest { - public int bindPort = 5555; + // Random port avoids fixed 5555 conflicts; note invalidBindPort/invalidSendLength still + // remap to DEFAULT_BIND_PORT (5555) in production start() — known low-risk residual. + public int bindPort = PublicMethod.chooseRandomPort(); public String dataToSend = "################"; public String topic = "testTopic"; diff --git a/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java b/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java index 4c2e9292d29..4c1404bb232 100644 --- a/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java +++ b/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java @@ -13,6 +13,7 @@ import org.junit.Test; import org.tron.common.BaseTest; import org.tron.common.TestConstants; +import org.tron.common.utils.PublicMethod; import org.tron.common.utils.StringUtil; import org.tron.consensus.dpos.MaintenanceManager; import org.tron.core.capsule.AccountCapsule; @@ -38,6 +39,7 @@ public class SRMetricsTest extends BaseTest { Args.setParam(new String[]{"-d", dbPath()}, TestConstants.TEST_CONF); Args.getInstance().setNodeListenPort(20000 + PORT.incrementAndGet()); Args.getInstance().setMetricsPrometheusEnable(true); + Args.getInstance().setMetricsPrometheusPort(PublicMethod.chooseRandomPort()); Metrics.init(); } diff --git a/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmCompatibleEvmTest.java b/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmCompatibleEvmTest.java index 74d44dfca7d..a3711ba8de7 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmCompatibleEvmTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmCompatibleEvmTest.java @@ -306,6 +306,7 @@ public static void afterClass() { VMConfig.initAllowTvmConstantinople(0); VMConfig.initAllowTvmSolidity059(0); VMConfig.initAllowTvmIstanbul(0); + VMConfig.initAllowTvmLondon(0); VMConfig.initAllowTvmCompatibleEvm(0); } diff --git a/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmLondonTest.java b/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmLondonTest.java index e93eca39092..11a02e615db 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmLondonTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmLondonTest.java @@ -74,8 +74,8 @@ public void testBaseFee() throws ContractExeException, ReceiptCheckErrException, factoryAddress, Hex.decode(hexInput), 0, feeLimit, manager, null); byte[] returnValue = result.getRuntime().getResult().getHReturn(); Assert.assertNull(result.getRuntime().getRuntimeError()); - Assert.assertArrayEquals(returnValue, - longTo32Bytes(manager.getDynamicPropertiesStore().getEnergyFee())); + Assert.assertArrayEquals(longTo32Bytes(manager.getDynamicPropertiesStore().getEnergyFee()), + returnValue); } @Test diff --git a/framework/src/test/java/org/tron/common/utils/PeerManagerStateResetter.java b/framework/src/test/java/org/tron/common/utils/PeerManagerStateResetter.java new file mode 100644 index 00000000000..b4766115f2d --- /dev/null +++ b/framework/src/test/java/org/tron/common/utils/PeerManagerStateResetter.java @@ -0,0 +1,99 @@ +package org.tron.common.utils; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.util.ReflectionUtils; +import org.tron.common.es.ExecutorServiceManager; +import org.tron.core.net.peer.PeerConnection; +import org.tron.core.net.peer.PeerManager; +import org.tron.protos.Protocol.ReasonCode; + +/** + * Test source-set utility: restores {@link PeerManager} to a cold-start state. + * + *

{@link PeerManager#close()} neither clears the raw static peers/counters nor rebuilds the + * static executor, and tests share one JVM across Spring contexts. + * + *

The old executor is drained to termination before touching any state: {@code + * check()} is not synchronized (it snapshots peers, then removes entries and decrements + * counters), so a task left running would decrement the counters zeroed in step 4 and leave + * them negative; shutdown alone does not guarantee that, so reset fails if termination cannot + * be confirmed. + * Residual peers are disconnected before the raw list is cleared because {@code close()} may + * fail midway and leave live channels that a bare {@code clear()} would orphan; each peer is + * handled defensively (null channel tolerated, per-peer catch). A fresh executor is then + * installed (lazy thread, no tasks until the next {@code init()}). + * + *

Wired broadly from BaseTest/BaseMethodTest against unknown prior pollution; the reset is + * idempotent and cheap for tests that never use PeerManager. Remove this utility once + * production {@code close()}/{@code init()} is restart-safe. + */ +public final class PeerManagerStateResetter { + + private static final String EXECUTOR_NAME = "peer-manager"; + + private PeerManagerStateResetter() { + } + + public static synchronized void reset() { + // 1) Drain the old executor first: let running/queued check() tasks die out so they + // cannot interleave with the list/counter reset below. Gate on isTerminated(), not + // isShutdown(): shutdown() still lets a running check() finish asynchronously, and + // check() decrements the counters even when its peers.remove() is a no-op. If + // termination cannot be confirmed, fail the setup instead of resetting anyway. + ScheduledExecutorService executor = getFieldValue("executor"); + if (executor != null && !executor.isTerminated()) { + ExecutorServiceManager.shutdownAndAwaitTermination(executor, EXECUTOR_NAME); + if (!executor.isTerminated()) { + throw new IllegalStateException( + "peer-manager executor did not terminate; refusing to reset shared state"); + } + } + // 2) Unconditionally install a fresh executor (the old one may be shut down or null); + // its thread is created lazily. + setFieldValue("executor", + ExecutorServiceManager.newSingleThreadScheduledExecutor(EXECUTOR_NAME)); + + // 3) Release residual live connections before clearing the raw list. + List peers = getFieldValue("peers"); + if (peers == null) { + setFieldValue("peers", Collections.synchronizedList(new ArrayList())); + } else { + for (PeerConnection peer : new ArrayList<>(peers)) { + try { + if (!peer.isDisconnect()) { + peer.disconnect(ReasonCode.PEER_QUITING); + if (peer.getChannel() != null) { + peer.getChannel().close(); + } + } + } catch (Exception e) { + // best effort: a single corrupted leftover peer must not fail the reset + } + } + peers.clear(); + } + + // 4) Zero the counters; old tasks can no longer decrement them at this point. + AtomicInteger active = PeerManager.getActivePeersCount(); + AtomicInteger passive = PeerManager.getPassivePeersCount(); + active.set(0); + passive.set(0); + } + + private static T getFieldValue(String fieldName) { + Field field = ReflectionUtils.findField(PeerManager.class, fieldName); + ReflectionUtils.makeAccessible(field); + return (T) ReflectionUtils.getField(field, null); + } + + private static void setFieldValue(String fieldName, Object value) { + Field field = ReflectionUtils.findField(PeerManager.class, fieldName); + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, null, value); + } +} diff --git a/framework/src/test/java/org/tron/common/utils/PeerManagerStateResetterTest.java b/framework/src/test/java/org/tron/common/utils/PeerManagerStateResetterTest.java new file mode 100644 index 00000000000..d7316c06333 --- /dev/null +++ b/framework/src/test/java/org/tron/common/utils/PeerManagerStateResetterTest.java @@ -0,0 +1,79 @@ +package org.tron.common.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; +import org.mockito.Mockito; +import org.tron.core.net.peer.PeerConnection; +import org.tron.core.net.peer.PeerManager; + +/** + * Pins the cold-start guarantees of {@link PeerManagerStateResetter#reset()}: after a reset the + * shared peers list is empty, both counters are zero, and the installed executor is fresh — + * including on the path where the old executor was already shut down but might still be + * draining a {@code check()} task. + */ +public class PeerManagerStateResetterTest { + + @Test + @SuppressWarnings("unchecked") + public void testResetRestoresColdStartState() throws Exception { + Field peersField = PeerManager.class.getDeclaredField("peers"); + peersField.setAccessible(true); + List peers = (List) peersField.get(null); + peers.clear(); + PeerConnection stalePeer = Mockito.mock(PeerConnection.class); + Mockito.when(stalePeer.isDisconnect()).thenReturn(true); + peers.add(stalePeer); + AtomicInteger active = PeerManager.getActivePeersCount(); + AtomicInteger passive = PeerManager.getPassivePeersCount(); + active.set(7); + passive.set(3); + + PeerManagerStateResetter.reset(); + + assertEquals(0, peers.size()); + assertEquals(0, active.get()); + assertEquals(0, passive.get()); + Field executorField = PeerManager.class.getDeclaredField("executor"); + executorField.setAccessible(true); + ScheduledExecutorService executor = (ScheduledExecutorService) executorField.get(null); + assertFalse(executor.isShutdown()); + } + + // pin: reset() clears PeerManager statics by hardcoded field names — a new static field + // silently leaks across tests unless it gets resetter coverage or an allowlist entry here. + @Test + public void resetterCoversAllPeerManagerStaticFields() { + // fields reset() actually drains/rebuilds/clears/zeroes + Set handled = new HashSet<>(Arrays.asList( + "peers", "executor", "activePeersCount", "passivePeersCount")); + // fields intentionally untouched: constants / config that never mutates across tests + Set allowed = new HashSet<>(Arrays.asList( + "esName", "DISCONNECTION_TIME_OUT", "logger")); + + List unclassified = new java.util.ArrayList<>(); + for (Field field : PeerManager.class.getDeclaredFields()) { + // skip compiler/JaCoCo-generated synthetic fields (e.g. $jacocoData) — not business state + if (!Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) { + continue; + } + String name = field.getName(); + if (!handled.contains(name) && !allowed.contains(name)) { + unclassified.add(name + " (" + field.getType().getSimpleName() + ")"); + } + } + assertEquals("new static field needs resetter coverage or explicit allowlist entry: " + + unclassified, Collections.emptyList(), unclassified); + } +} diff --git a/framework/src/test/java/org/tron/common/utils/RandomGeneratorTest.java b/framework/src/test/java/org/tron/common/utils/RandomGeneratorTest.java index 4de441d940d..34c7536ebd0 100644 --- a/framework/src/test/java/org/tron/common/utils/RandomGeneratorTest.java +++ b/framework/src/test/java/org/tron/common/utils/RandomGeneratorTest.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import org.joda.time.DateTime; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -42,7 +41,7 @@ public void shuffle() { final List witnessCapsuleListBefore = this.getWitnessList(); logger.info("updateWitnessSchedule,before: " + getWitnessStringList(witnessCapsuleListBefore)); final List witnessCapsuleListAfter = new RandomGenerator() - .shuffle(witnessCapsuleListBefore, DateTime.now().getMillis()); + .shuffle(witnessCapsuleListBefore, System.currentTimeMillis()); logger.info("updateWitnessSchedule,after: " + getWitnessStringList(witnessCapsuleListAfter)); } diff --git a/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java b/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java index cf652af3650..622d20ae7d2 100755 --- a/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java +++ b/framework/src/test/java/org/tron/core/BandwidthProcessorTest.java @@ -5,8 +5,8 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import java.nio.charset.StandardCharsets; +import java.time.ZonedDateTime; import lombok.extern.slf4j.Slf4j; -import org.joda.time.DateTime; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -64,8 +64,8 @@ public class BandwidthProcessorTest extends BaseTest { TO_ADDRESS = Wallet.getAddressPreFixString() + "abd4b9367799eaa3197fecb144eb71de1e049abc"; ASSET_ADDRESS = Wallet.getAddressPreFixString() + "548794500882809695a8a687866e76d4271a3456"; ASSET_ADDRESS_V2 = Wallet.getAddressPreFixString() + "548794500882809695a8a687866e76d4271a7890"; - START_TIME = DateTime.now().minusDays(1).getMillis(); - END_TIME = DateTime.now().getMillis(); + START_TIME = ZonedDateTime.now().minusDays(1).toInstant().toEpochMilli(); + END_TIME = System.currentTimeMillis(); } /** @@ -616,7 +616,7 @@ public void sameTokenNameCloseConsumeSuccess() { AccountType.Normal, chainBaseManager.getDynamicPropertiesStore().getAssetIssueFee()); ownerCapsule.setBalance(10_000_000L); - long expireTime = DateTime.now().getMillis() + 6 * 86_400_000; + long expireTime = System.currentTimeMillis() + 6 * 86_400_000; ownerCapsule.setFrozenForBandwidth(2_000_000L, expireTime); chainBaseManager.getAccountStore().put(ownerCapsule.getAddress().toByteArray(), ownerCapsule); @@ -627,7 +627,7 @@ public void sameTokenNameCloseConsumeSuccess() { AccountType.Normal, chainBaseManager.getDynamicPropertiesStore().getAssetIssueFee()); toAddressCapsule.setBalance(10_000_000L); - long expireTime2 = DateTime.now().getMillis() + 6 * 86_400_000; + long expireTime2 = System.currentTimeMillis() + 6 * 86_400_000; toAddressCapsule.setFrozenForBandwidth(2_000_000L, expireTime2); chainBaseManager.getAccountStore().put(toAddressCapsule.getAddress().toByteArray(), toAddressCapsule); @@ -731,7 +731,7 @@ public void sameTokenNameOpenConsumeSuccess() { AccountType.Normal, chainBaseManager.getDynamicPropertiesStore().getAssetIssueFee()); ownerCapsule.setBalance(10_000_000L); - long expireTime = DateTime.now().getMillis() + 6 * 86_400_000; + long expireTime = System.currentTimeMillis() + 6 * 86_400_000; ownerCapsule.setFrozenForBandwidth(2_000_000L, expireTime); chainBaseManager.getAccountStore().put(ownerCapsule.getAddress().toByteArray(), ownerCapsule); @@ -742,7 +742,7 @@ public void sameTokenNameOpenConsumeSuccess() { AccountType.Normal, chainBaseManager.getDynamicPropertiesStore().getAssetIssueFee()); toAddressCapsule.setBalance(10_000_000L); - long expireTime2 = DateTime.now().getMillis() + 6 * 86_400_000; + long expireTime2 = System.currentTimeMillis() + 6 * 86_400_000; toAddressCapsule.setFrozenForBandwidth(2_000_000L, expireTime2); chainBaseManager.getAccountStore().put(toAddressCapsule.getAddress().toByteArray(), toAddressCapsule); @@ -816,7 +816,7 @@ public void sameTokenNameCloseTransferToAccountNotExist() { AccountType.Normal, chainBaseManager.getDynamicPropertiesStore().getAssetIssueFee()); ownerCapsule.setBalance(10_000_000L); - long expireTime = DateTime.now().getMillis() + 6 * 86_400_000; + long expireTime = System.currentTimeMillis() + 6 * 86_400_000; ownerCapsule.setFrozenForBandwidth(2_000_000L, expireTime); chainBaseManager.getAccountStore().put(ownerCapsule.getAddress().toByteArray(), ownerCapsule); @@ -827,7 +827,7 @@ public void sameTokenNameCloseTransferToAccountNotExist() { AccountType.Normal, chainBaseManager.getDynamicPropertiesStore().getAssetIssueFee()); toAddressCapsule.setBalance(10_000_000L); - long expireTime2 = DateTime.now().getMillis() + 6 * 86_400_000; + long expireTime2 = System.currentTimeMillis() + 6 * 86_400_000; toAddressCapsule.setFrozenForBandwidth(2_000_000L, expireTime2); chainBaseManager.getAccountStore().delete(toAddressCapsule.getAddress().toByteArray()); diff --git a/framework/src/test/java/org/tron/core/WalletTest.java b/framework/src/test/java/org/tron/core/WalletTest.java index 9dbab338b67..7215a287912 100644 --- a/framework/src/test/java/org/tron/core/WalletTest.java +++ b/framework/src/test/java/org/tron/core/WalletTest.java @@ -30,12 +30,12 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import javax.annotation.Resource; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.joda.time.DateTime; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; @@ -113,21 +113,29 @@ public class WalletTest extends BaseTest { public static final long BLOCK_NUM_THREE = 3; public static final long BLOCK_NUM_FOUR = 4; public static final long BLOCK_NUM_FIVE = 5; - public static final long BLOCK_TIMESTAMP_ONE = DateTime.now().minusDays(4).getMillis(); - public static final long BLOCK_TIMESTAMP_TWO = DateTime.now().minusDays(3).getMillis(); - public static final long BLOCK_TIMESTAMP_THREE = DateTime.now().minusDays(2).getMillis(); - public static final long BLOCK_TIMESTAMP_FOUR = DateTime.now().minusDays(1).getMillis(); - public static final long BLOCK_TIMESTAMP_FIVE = DateTime.now().getMillis(); + public static final long BLOCK_TIMESTAMP_ONE = + ZonedDateTime.now().minusDays(4).toInstant().toEpochMilli(); + public static final long BLOCK_TIMESTAMP_TWO = + ZonedDateTime.now().minusDays(3).toInstant().toEpochMilli(); + public static final long BLOCK_TIMESTAMP_THREE = + ZonedDateTime.now().minusDays(2).toInstant().toEpochMilli(); + public static final long BLOCK_TIMESTAMP_FOUR = + ZonedDateTime.now().minusDays(1).toInstant().toEpochMilli(); + public static final long BLOCK_TIMESTAMP_FIVE = System.currentTimeMillis(); public static final long BLOCK_WITNESS_ONE = 12; public static final long BLOCK_WITNESS_TWO = 13; public static final long BLOCK_WITNESS_THREE = 14; public static final long BLOCK_WITNESS_FOUR = 15; public static final long BLOCK_WITNESS_FIVE = 16; - public static final long TRANSACTION_TIMESTAMP_ONE = DateTime.now().minusDays(4).getMillis(); - public static final long TRANSACTION_TIMESTAMP_TWO = DateTime.now().minusDays(3).getMillis(); - public static final long TRANSACTION_TIMESTAMP_THREE = DateTime.now().minusDays(2).getMillis(); - public static final long TRANSACTION_TIMESTAMP_FOUR = DateTime.now().minusDays(1).getMillis(); - public static final long TRANSACTION_TIMESTAMP_FIVE = DateTime.now().getMillis(); + public static final long TRANSACTION_TIMESTAMP_ONE = + ZonedDateTime.now().minusDays(4).toInstant().toEpochMilli(); + public static final long TRANSACTION_TIMESTAMP_TWO = + ZonedDateTime.now().minusDays(3).toInstant().toEpochMilli(); + public static final long TRANSACTION_TIMESTAMP_THREE = + ZonedDateTime.now().minusDays(2).toInstant().toEpochMilli(); + public static final long TRANSACTION_TIMESTAMP_FOUR = + ZonedDateTime.now().minusDays(1).toInstant().toEpochMilli(); + public static final long TRANSACTION_TIMESTAMP_FIVE = System.currentTimeMillis(); @Resource private Wallet wallet; private static Block block1; diff --git a/framework/src/test/java/org/tron/core/actuator/ParticipateAssetIssueActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/ParticipateAssetIssueActuatorTest.java index 5c168f51bee..4af63285b1e 100755 --- a/framework/src/test/java/org/tron/core/actuator/ParticipateAssetIssueActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/ParticipateAssetIssueActuatorTest.java @@ -2,7 +2,7 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; -import org.joda.time.DateTime; +import java.time.ZonedDateTime; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -403,8 +403,8 @@ public void sameTokenNameOpenRightAssetIssue() { */ @Test public void sameTokenNameCloseAssetIssueTimeRight() { - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), now.toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(1000L)); @@ -436,8 +436,8 @@ public void sameTokenNameCloseAssetIssueTimeRight() { @Test public void sameTokenNameOpenAssetIssueTimeRight() { chainBaseManager.getDynamicPropertiesStore().saveAllowSameTokenName(1); - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), now.toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(1000L)); @@ -470,8 +470,8 @@ public void sameTokenNameOpenAssetIssueTimeRight() { */ @Test public void sameTokenNameCloseAssetIssueTimeLeft() { - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), now.toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(1000L)); @@ -504,8 +504,8 @@ public void sameTokenNameCloseAssetIssueTimeLeft() { @Test public void sameTokenNameOpenAssetIssueTimeLeft() { chainBaseManager.getDynamicPropertiesStore().saveAllowSameTokenName(1); - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), now.toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(1000L)); @@ -605,8 +605,9 @@ public void sameTokenNameOpenExchangeDevisibleTest() { */ @Test public void sameTokenNameCloseNegativeAmountTest() { - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(-999L)); @@ -639,8 +640,9 @@ public void sameTokenNameCloseNegativeAmountTest() { @Test public void sameTokenNameOpenNegativeAmountTest() { chainBaseManager.getDynamicPropertiesStore().saveAllowSameTokenName(1); - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(-999L)); @@ -675,8 +677,9 @@ public void sameTokenNameOpenNegativeAmountTest() { */ @Test public void sameTokenNameCloseZeroAmountTest() { - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(0)); @@ -709,8 +712,9 @@ public void sameTokenNameCloseZeroAmountTest() { @Test public void sameTokenNameOpenZeroAmountTest() { chainBaseManager.getDynamicPropertiesStore().saveAllowSameTokenName(1); - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager).setAny(getContract(0)); @@ -746,8 +750,9 @@ public void sameTokenNameOpenZeroAmountTest() { */ @Test public void sameTokenNameCloseNoExitOwnerTest() { - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager) .setAny(getContractWithOwner(101, NOT_EXIT_ADDRESS)); @@ -782,8 +787,9 @@ public void sameTokenNameCloseNoExitOwnerTest() { @Test public void sameTokenNameOpenNoExitOwnerTest() { chainBaseManager.getDynamicPropertiesStore().saveAllowSameTokenName(1); - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager) .setAny(getContractWithOwner(101, NOT_EXIT_ADDRESS)); @@ -1310,8 +1316,9 @@ public void sameTokenNameOpenNotEnoughAssetTest() { */ @Test public void sameTokenNameCloseNoneExistAssetTest() { - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager) .setAny(getContract(1, "TTTTTTTTTTTT")); @@ -1346,8 +1353,9 @@ public void sameTokenNameCloseNoneExistAssetTest() { @Test public void sameTokenNameOpenNoneExistAssetTest() { chainBaseManager.getDynamicPropertiesStore().saveAllowSameTokenName(1); - DateTime now = DateTime.now(); - initAssetIssue(now.minusDays(1).getMillis(), now.plusDays(1).getMillis()); + ZonedDateTime now = ZonedDateTime.now(); + initAssetIssue(now.minusDays(1).toInstant().toEpochMilli(), + now.plusDays(1).toInstant().toEpochMilli()); ParticipateAssetIssueActuator actuator = new ParticipateAssetIssueActuator(); actuator.setChainBaseManager(chainBaseManager) .setAny(getContract(1, "TTTTTTTTTTTT")); diff --git a/framework/src/test/java/org/tron/core/config/ConfigurationTest.java b/framework/src/test/java/org/tron/core/config/ConfigurationTest.java index b066bc1e6be..b23f350716c 100644 --- a/framework/src/test/java/org/tron/core/config/ConfigurationTest.java +++ b/framework/src/test/java/org/tron/core/config/ConfigurationTest.java @@ -35,6 +35,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.utils.ByteArray; import org.tron.core.Wallet; +import org.tron.core.config.args.StorageConfig; @Slf4j public class ConfigurationTest { @@ -91,4 +92,20 @@ public void getConfigurationWhenOnlyConfFileName() { assertTrue(config.hasPath("seed.node")); assertTrue(config.hasPath("genesis.block")); } + + @Test + public void defaultConfigShouldPreserveEffectiveRocksDbSettings() { + Config config = Configuration.getByFileName("config.conf"); + StorageConfig.DbSettingsConfig settings = StorageConfig.fromConfig(config).getDbSettings(); + + assertTrue(config.hasPath("storage.dbSettings.blocksize")); + assertEquals(64, settings.getBlocksize()); + assertEquals(7, settings.getLevelNumber()); + assertEquals(256, settings.getMaxBytesForLevelBase()); + assertEquals(10, settings.getMaxBytesForLevelMultiplier(), 0.01); + assertEquals(4, settings.getLevel0FileNumCompactionTrigger()); + assertEquals(256, settings.getTargetFileSizeBase()); + assertEquals(1, settings.getTargetFileSizeMultiplier()); + assertEquals(5000, settings.getMaxOpenFiles()); + } } diff --git a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java index 36b8a3269c1..2118140b45b 100644 --- a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java +++ b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java @@ -519,6 +519,64 @@ public void testMaxMessageSizeNegativeValueRejected() { } } + @Test + public void testDnsPublishRejectsInvalidServerTypeWithParameterInitError() { + Config config = dnsPublishConfig( + "node.dns.serverType", "unsupported"); + + TronError error = Assert.assertThrows(TronError.class, + () -> Args.loadDnsPublishConfig(NodeConfig.fromConfig(config))); + + Assert.assertEquals(TronError.ErrCode.PARAMETER_INIT, error.getErrCode()); + Assert.assertEquals("Check node.dns.serverType, must be aws or aliyun", + error.getMessage()); + } + + @Test + public void testDnsPublishRejectsEmptyRequiredParameterWithParameterInitError() { + Config config = dnsPublishConfig("node.dns.dnsDomain", ""); + + TronError error = Assert.assertThrows(TronError.class, + () -> Args.loadDnsPublishConfig(NodeConfig.fromConfig(config))); + + Assert.assertEquals(TronError.ErrCode.PARAMETER_INIT, error.getErrCode()); + Assert.assertEquals("Check node.dns.dnsDomain, must not be null or empty", + error.getMessage()); + } + + @Test + public void testCommitteeConfigRejectsOldRewardOptimizationWithoutPrerequisite() { + Map configMap = new HashMap<>(); + configMap.put("storage.db.directory", "database"); + configMap.put("committee.allowOldRewardOpt", 1); + Config config = ConfigFactory.parseMap(configMap) + .withFallback(ConfigFactory.defaultReference()); + + try { + TronError error = Assert.assertThrows(TronError.class, + () -> Args.applyConfigParams(config)); + + Assert.assertEquals(TronError.ErrCode.PARAMETER_INIT, error.getErrCode()); + } finally { + Args.clearParam(); + } + } + + private Config dnsPublishConfig(String key, String value) { + Map configMap = new HashMap<>(); + configMap.put("node.dns.publish", true); + configMap.put("node.dns.dnsDomain", "nodes.example.org"); + configMap.put("node.dns.dnsPrivate", + "1234567890123456789012345678901234567890123456789012345678901234"); + configMap.put("node.dns.serverType", "aliyun"); + configMap.put("node.dns.accessKeyId", "access-key-id"); + configMap.put("node.dns.accessKeySecret", "access-key-secret"); + configMap.put("node.dns.aliyunDnsEndpoint", "dns.aliyuncs.com"); + configMap.put(key, value); + return ConfigFactory.parseMap(configMap) + .withFallback(ConfigFactory.defaultReference()); + } + @Test public void testRpcMaxMessageSizeExceedsIntMax() { // HOCON's Config.getInt() throws when a numeric value exceeds int range. diff --git a/framework/src/test/java/org/tron/core/event/BlockEventGetTest.java b/framework/src/test/java/org/tron/core/event/BlockEventGetTest.java index e2815e46063..6df63c6c04e 100644 --- a/framework/src/test/java/org/tron/core/event/BlockEventGetTest.java +++ b/framework/src/test/java/org/tron/core/event/BlockEventGetTest.java @@ -125,6 +125,10 @@ public void before() throws IOException { @AfterClass public static void after() throws IOException { + // stopPlugin() is safe when never started: it null-checks pluginManager, and + // NativeMessageQueue.stop() null-checks publisher/context. Ensures the native + // queue socket bound in test() is released even when assertions fail earlier. + EventPluginLoader.getInstance().stopPlugin(); context.destroy(); Args.clearParam(); } @@ -174,7 +178,7 @@ public void test() throws Exception { EventPluginConfig config = new EventPluginConfig(); config.setSendQueueLength(1000); - config.setBindPort(5555); + config.setBindPort(PublicMethod.chooseRandomPort()); config.setUseNativeQueue(true); config.setTriggerConfigList(new ArrayList<>()); diff --git a/framework/src/test/java/org/tron/core/metrics/MetricsApiServiceTest.java b/framework/src/test/java/org/tron/core/metrics/MetricsApiServiceTest.java index f96a03d92e3..07f7bcfc6e8 100644 --- a/framework/src/test/java/org/tron/core/metrics/MetricsApiServiceTest.java +++ b/framework/src/test/java/org/tron/core/metrics/MetricsApiServiceTest.java @@ -38,6 +38,10 @@ protected void afterInit() { @Test public void testProcessMessage() { + MetricsUtil.getMeter(MetricsKey.NET_TCP_IN_TRAFFIC).mark(1000); + MetricsUtil.getMeter(MetricsKey.NET_TCP_OUT_TRAFFIC).mark(2000); + MetricsUtil.getMeter(MetricsKey.NET_UDP_IN_TRAFFIC).mark(4000); + MetricsUtil.getMeter(MetricsKey.NET_UDP_OUT_TRAFFIC).mark(8000); MetricsInfo m1 = metricsApiService.getMetricsInfo(); @@ -79,6 +83,20 @@ public void testProcessMessage() { Assert.assertEquals(m1.getNet().getErrorProtoCount(), m2.getNet().getErrorProtoCount()); Assert .assertEquals(m1.getNet().getValidConnectionCount(), m2.getNet().getValidConnectionCount()); + + long tcpIn = m1.getNet().getTcpInTraffic().getCount(); + long tcpOut = m1.getNet().getTcpOutTraffic().getCount(); + long udpIn = m1.getNet().getUdpInTraffic().getCount(); + long udpOut = m1.getNet().getUdpOutTraffic().getCount(); + + Assert.assertNotEquals(tcpOut, udpIn); + Assert.assertNotEquals(tcpIn, tcpOut); + Assert.assertNotEquals(udpIn, udpOut); + + Assert.assertEquals(tcpIn, m2.getNet().getTcpInTraffic().getCount()); + Assert.assertEquals(tcpOut, m2.getNet().getTcpOutTraffic().getCount()); + Assert.assertEquals(udpIn, m2.getNet().getUdpInTraffic().getCount()); + Assert.assertEquals(udpOut, m2.getNet().getUdpOutTraffic().getCount()); } } diff --git a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java index dd260a1b869..4722caec08d 100644 --- a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java +++ b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java @@ -62,6 +62,7 @@ public class PrometheusApiServiceTest extends BaseTest { Args.setParam(new String[] {"-d", dbPath()}, TestConstants.TEST_CONF); Args.getInstance().setNodeListenPort(10000 + port.incrementAndGet()); initParameter(Args.getInstance()); + Args.getInstance().setMetricsPrometheusPort(PublicMethod.chooseRandomPort()); Metrics.init(); } diff --git a/framework/src/test/java/org/tron/core/net/messagehandler/MessageHandlerTest.java b/framework/src/test/java/org/tron/core/net/messagehandler/MessageHandlerTest.java index be843674632..c8205b6b721 100644 --- a/framework/src/test/java/org/tron/core/net/messagehandler/MessageHandlerTest.java +++ b/framework/src/test/java/org/tron/core/net/messagehandler/MessageHandlerTest.java @@ -16,6 +16,7 @@ import org.tron.common.ClassLevelAppContextFixture; import org.tron.common.TestConstants; import org.tron.common.application.TronApplicationContext; +import org.tron.common.utils.PeerManagerStateResetter; import org.tron.common.utils.ReflectUtils; import org.tron.common.utils.Sha256Hash; import org.tron.consensus.pbft.message.PbftMessage; @@ -45,6 +46,7 @@ public class MessageHandlerTest { @BeforeClass public static void init() throws Exception { + PeerManagerStateResetter.reset(); Args.setParam(new String[] {"--output-directory", temporaryFolder.newFolder().toString(), "--debug"}, TestConstants.TEST_CONF); context = APP_FIXTURE.createContext(); diff --git a/framework/src/test/java/org/tron/core/net/messagehandler/PbftMsgHandlerTest.java b/framework/src/test/java/org/tron/core/net/messagehandler/PbftMsgHandlerTest.java index 65a8f615bfe..15d7107b58f 100644 --- a/framework/src/test/java/org/tron/core/net/messagehandler/PbftMsgHandlerTest.java +++ b/framework/src/test/java/org/tron/core/net/messagehandler/PbftMsgHandlerTest.java @@ -17,6 +17,7 @@ import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.SignUtils; import org.tron.common.utils.FileUtil; +import org.tron.common.utils.PeerManagerStateResetter; import org.tron.common.utils.PublicMethod; import org.tron.common.utils.ReflectUtils; import org.tron.common.utils.Sha256Hash; @@ -46,6 +47,7 @@ public class PbftMsgHandlerTest { @BeforeClass public static void init() { + PeerManagerStateResetter.reset(); Args.setParam(new String[] {"--output-directory", dbPath, "--debug"}, TestConstants.TEST_CONF); context = new TronApplicationContext(DefaultConfig.class); diff --git a/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java b/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java index ed2121d360f..282c80f9f6d 100644 --- a/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java +++ b/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java @@ -4,17 +4,19 @@ import com.google.protobuf.ByteString; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; -import lombok.Getter; -import org.joda.time.DateTime; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -23,7 +25,6 @@ import org.tron.common.TestConstants; import org.tron.common.runtime.TvmTestUtils; import org.tron.common.utils.ByteArray; -import org.tron.common.utils.ReflectUtils; import org.tron.core.ChainBaseManager; import org.tron.core.config.args.Args; import org.tron.core.exception.P2pException; @@ -48,6 +49,7 @@ public static void init() { @Test public void testProcessMessage() { TransactionsMsgHandler transactionsMsgHandler = new TransactionsMsgHandler(); + ExecutorService originalPool = null; try { transactionsMsgHandler.init(); @@ -67,7 +69,7 @@ public void testProcessMessage() { .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString("121212a9cf"))) .setToAddress(ByteString.copyFrom(ByteArray.fromHexString("232323a9cf"))).build(); - long transactionTimestamp = DateTime.now().minusDays(4).getMillis(); + long transactionTimestamp = ZonedDateTime.now().minusDays(4).toInstant().toEpochMilli(); Protocol.Transaction trx = Protocol.Transaction.newBuilder().setRawData( Protocol.Transaction.raw.newBuilder().setTimestamp(transactionTimestamp) .setRefBlockNum(1) @@ -80,17 +82,29 @@ public void testProcessMessage() { Item item = new Item(new TransactionMessage(trx).getMessageId(), Protocol.Inventory.InventoryType.TRX); advInvRequest.put(item, 0L); + // The non-executing pool must be installed before the first submission so no + // real-pool worker can touch the peer mock while the test re-stubs it (Mockito + // stubbing is not thread-safe). The latch counts down only for off-thread callers, + // which after the replacement is exactly the smart-contract scheduler. + CountDownLatch smartContractSubmitted = new CountDownLatch(1); + Thread testThread = Thread.currentThread(); + ExecutorService mockPool = Mockito.mock(ExecutorService.class); + Future submittedTask = Mockito.mock(Future.class); + Mockito.when(mockPool.submit(Mockito.any(Runnable.class))).thenAnswer(invocation -> { + if (Thread.currentThread() != testThread) { + smartContractSubmitted.countDown(); + } + return submittedTask; + }); + originalPool = replaceTrxHandlePool(transactionsMsgHandler, mockPool); + Mockito.when(peer.getAdvInvRequest()).thenReturn(advInvRequest); List transactionList = new ArrayList<>(); transactionList.add(trx); transactionsMsgHandler.processMessage(peer, new TransactionsMessage(transactionList)); Assert.assertNull(advInvRequest.get(item)); - //Thread.sleep(10); - BlockingQueue smartContractQueue = - new LinkedBlockingQueue(2); - smartContractQueue.offer(new TrxEvent(null, null)); - smartContractQueue.offer(new TrxEvent(null, null)); + BlockingQueue smartContractQueue = new LinkedBlockingQueue<>(1); Field field1 = TransactionsMsgHandler.class.getDeclaredField("smartContractQueue"); field1.setAccessible(true); field1.set(transactionsMsgHandler, smartContractQueue); @@ -99,15 +113,27 @@ public void testProcessMessage() { ByteArray.fromHexString("121212a9cf"), ByteArray.fromHexString("123456"), 100, 100000000, 0, 0); + Protocol.Transaction trx3 = TvmTestUtils.generateTriggerSmartContractAndGetTransaction( + ByteArray.fromHexString("121212a9cf"), + ByteArray.fromHexString("121212a9cf"), + ByteArray.fromHexString("123457"), + 100, 100000000, 0, 0); Map advInvRequest1 = new ConcurrentHashMap<>(); Item item1 = new Item(new TransactionMessage(trx1).getMessageId(), Protocol.Inventory.InventoryType.TRX); advInvRequest1.put(item1, 0L); + Item item3 = new Item(new TransactionMessage(trx3).getMessageId(), + Protocol.Inventory.InventoryType.TRX); + advInvRequest1.put(item3, 0L); Mockito.when(peer.getAdvInvRequest()).thenReturn(advInvRequest1); List transactionList1 = new ArrayList<>(); transactionList1.add(trx1); + transactionList1.add(trx3); transactionsMsgHandler.processMessage(peer, new TransactionsMessage(transactionList1)); - Assert.assertNull(advInvRequest.get(item1)); + Assert.assertNull(advInvRequest1.get(item1)); + Assert.assertNull(advInvRequest1.get(item3)); + Assert.assertTrue("smart-contract scheduler did not submit work", + smartContractSubmitted.await(3, TimeUnit.SECONDS)); // test 0 contract Protocol.Transaction trx2 = Protocol.Transaction.newBuilder().setRawData( @@ -132,37 +158,40 @@ public void testProcessMessage() { Assert.assertTrue(true); } } catch (Exception e) { - Assert.fail(); + Assert.fail(e.getMessage()); } finally { - transactionsMsgHandler.close(); + closeHandlerAndOriginalPool(transactionsMsgHandler, originalPool); } } @Test public void testProcessMessageAfterClose() throws Exception { TransactionsMsgHandler handler = new TransactionsMsgHandler(); - handler.init(); - handler.close(); + try { + handler.init(); + handler.close(); - PeerConnection peer = Mockito.mock(PeerConnection.class); - TransactionsMessage msg = Mockito.mock(TransactionsMessage.class); + PeerConnection peer = Mockito.mock(PeerConnection.class); + TransactionsMessage msg = Mockito.mock(TransactionsMessage.class); - handler.processMessage(peer, msg); + handler.processMessage(peer, msg); - Mockito.verify(msg, Mockito.never()).getTransactions(); - Mockito.verifyNoInteractions(peer); + Mockito.verify(msg, Mockito.never()).getTransactions(); + Mockito.verifyNoInteractions(peer); + } finally { + handler.close(); + } } @Test public void testRejectedExecution() throws Exception { TransactionsMsgHandler handler = new TransactionsMsgHandler(); + ExecutorService originalPool = null; try { ExecutorService mockPool = Mockito.mock(ExecutorService.class); Mockito.when(mockPool.submit(Mockito.any(Runnable.class))) .thenThrow(new RejectedExecutionException("pool closed")); - Field poolField = TransactionsMsgHandler.class.getDeclaredField("trxHandlePool"); - poolField.setAccessible(true); - poolField.set(handler, mockPool); + originalPool = replaceTrxHandlePool(handler, mockPool); PeerConnection peer = Mockito.mock(PeerConnection.class); TransactionsMessage msg = buildTransferMessage(2); @@ -172,26 +201,26 @@ public void testRejectedExecution() throws Exception { Mockito.verify(mockPool, Mockito.times(1)).submit(Mockito.any(Runnable.class)); } finally { - handler.close(); + closeHandlerAndOriginalPool(handler, originalPool); } } @Test public void testCloseDuringProcessing() throws Exception { TransactionsMsgHandler handler = new TransactionsMsgHandler(); + ExecutorService originalPool = null; try { Field closedField = TransactionsMsgHandler.class.getDeclaredField("isClosed"); closedField.setAccessible(true); ExecutorService mockPool = Mockito.mock(ExecutorService.class); + Future submittedTask = Mockito.mock(Future.class); // on the first submit, flip isClosed to true so the second iteration breaks Mockito.when(mockPool.submit(Mockito.any(Runnable.class))).thenAnswer(inv -> { closedField.set(handler, true); - return null; + return submittedTask; }); - Field poolField = TransactionsMsgHandler.class.getDeclaredField("trxHandlePool"); - poolField.setAccessible(true); - poolField.set(handler, mockPool); + originalPool = replaceTrxHandlePool(handler, mockPool); PeerConnection peer = Mockito.mock(PeerConnection.class); TransactionsMessage msg = buildTransferMessage(2); @@ -200,7 +229,7 @@ public void testCloseDuringProcessing() throws Exception { Mockito.verify(mockPool, Mockito.times(1)).submit(Mockito.any(Runnable.class)); } finally { - handler.close(); + closeHandlerAndOriginalPool(handler, originalPool); } } @@ -234,6 +263,34 @@ private void stubAdvInvRequest(PeerConnection peer, TransactionsMessage msg) { Mockito.when(peer.getAdvInvRequest()).thenReturn(advInvRequest); } + private ExecutorService replaceTrxHandlePool(TransactionsMsgHandler handler, ExecutorService pool) + throws Exception { + Field poolField = TransactionsMsgHandler.class.getDeclaredField("trxHandlePool"); + poolField.setAccessible(true); + ExecutorService originalPool = (ExecutorService) poolField.get(handler); + poolField.set(handler, pool); + return originalPool; + } + + private void closeHandlerAndOriginalPool(TransactionsMsgHandler handler, + ExecutorService originalPool) { + try { + handler.close(); + } finally { + if (originalPool != null) { + originalPool.shutdown(); + try { + if (!originalPool.awaitTermination(5, TimeUnit.SECONDS)) { + originalPool.shutdownNow(); + } + } catch (InterruptedException e) { + originalPool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + } + @Test public void testHandleTransaction() throws Exception { TransactionsMsgHandler handler = new TransactionsMsgHandler(); @@ -341,7 +398,20 @@ public void testDuplicateTransactionRejected() throws Exception { public void testInvalidSigLength() throws Exception { TransactionsMsgHandler handler = new TransactionsMsgHandler(); handler.init(); + ExecutorService originalPool = null; try { + // Mock pool never executes submitted tasks: the async worker would invoke isBadPeer() + // on the stubbed peer concurrently with main-thread re-stubbing of getAdvInvRequest(), + // and Mockito's per-mock invocationForStubbing state is not thread-safe + // (intermittent WrongTypeOfReturnValue: ConcurrentHashMap cannot be returned by + // isBadPeer()). This test only asserts the synchronous check() length validation, + // so not running the worker is intentional. + ExecutorService mockPool = Mockito.mock(ExecutorService.class); + Future submittedTask = Mockito.mock(Future.class); + Mockito.when(mockPool.submit(Mockito.any(Runnable.class))) + .thenAnswer(invocation -> submittedTask); + originalPool = replaceTrxHandlePool(handler, mockPool); + PeerConnection peer = Mockito.mock(PeerConnection.class); BalanceContract.TransferContract transferContract = BalanceContract.TransferContract @@ -418,45 +488,32 @@ public void testInvalidSigLength() throws Exception { stubAdvInvRequest(peer, new TransactionsMessage(paddedList)); handler.processMessage(peer, new TransactionsMessage(paddedList)); } finally { - handler.close(); + closeHandlerAndOriginalPool(handler, originalPool); } } @Test public void testIsBusyWithCachedTransactions() throws Exception { TransactionsMsgHandler handler = new TransactionsMsgHandler(); + try { + int threshold = Args.getInstance().getMaxTrxCacheSize(); + TronNetDelegate tronNetDelegateMock = Mockito.mock(TronNetDelegate.class); + Field field = TransactionsMsgHandler.class.getDeclaredField("tronNetDelegate"); + field.setAccessible(true); + field.set(handler, tronNetDelegateMock); - int threshold = Args.getInstance().getMaxTrxCacheSize(); - TronNetDelegate tronNetDelegateMock = Mockito.mock(TronNetDelegate.class); - Field field = TransactionsMsgHandler.class.getDeclaredField("tronNetDelegate"); - field.setAccessible(true); - field.set(handler, tronNetDelegateMock); - - // queue and smartContractQueue are empty, but cached size > threshold - Mockito.when(tronNetDelegateMock.getCachedTransactionSize()).thenReturn(threshold + 1); - Assert.assertTrue(handler.isBusy()); - - // boundary: cached size == threshold, isBusy() uses strict >, so not busy - Mockito.when(tronNetDelegateMock.getCachedTransactionSize()).thenReturn(threshold); - Assert.assertFalse(handler.isBusy()); - - Mockito.when(tronNetDelegateMock.getCachedTransactionSize()).thenReturn(0); - Assert.assertFalse(handler.isBusy()); - } - - class TrxEvent { + // queue and smartContractQueue are empty, but cached size > threshold + Mockito.when(tronNetDelegateMock.getCachedTransactionSize()).thenReturn(threshold + 1); + Assert.assertTrue(handler.isBusy()); - @Getter - private PeerConnection peer; - @Getter - private TransactionMessage msg; - @Getter - private long time; + // boundary: cached size == threshold, isBusy() uses strict >, so not busy + Mockito.when(tronNetDelegateMock.getCachedTransactionSize()).thenReturn(threshold); + Assert.assertFalse(handler.isBusy()); - public TrxEvent(PeerConnection peer, TransactionMessage msg) { - this.peer = peer; - this.msg = msg; - this.time = System.currentTimeMillis(); + Mockito.when(tronNetDelegateMock.getCachedTransactionSize()).thenReturn(0); + Assert.assertFalse(handler.isBusy()); + } finally { + handler.close(); } } } diff --git a/framework/src/test/java/org/tron/core/net/peer/PeerManagerTest.java b/framework/src/test/java/org/tron/core/net/peer/PeerManagerTest.java index ffba127a6fd..16e88b38584 100644 --- a/framework/src/test/java/org/tron/core/net/peer/PeerManagerTest.java +++ b/framework/src/test/java/org/tron/core/net/peer/PeerManagerTest.java @@ -17,6 +17,7 @@ import org.springframework.context.ApplicationContext; import org.tron.common.TestConstants; import org.tron.common.parameter.CommonParameter; +import org.tron.common.utils.PeerManagerStateResetter; import org.tron.common.utils.ReflectUtils; import org.tron.core.config.args.Args; import org.tron.p2p.connection.Channel; @@ -25,6 +26,7 @@ public class PeerManagerTest { @BeforeClass public static void initArgs() { + PeerManagerStateResetter.reset(); Args.setParam(new String[]{}, TestConstants.TEST_CONF); CommonParameter.getInstance().setRateLimiterSyncBlockChain(10); CommonParameter.getInstance().setRateLimiterFetchInvData(10); diff --git a/framework/src/test/java/org/tron/core/net/services/HandShakeServiceTest.java b/framework/src/test/java/org/tron/core/net/services/HandShakeServiceTest.java index b8b0d5f6deb..dce5ccb851f 100644 --- a/framework/src/test/java/org/tron/core/net/services/HandShakeServiceTest.java +++ b/framework/src/test/java/org/tron/core/net/services/HandShakeServiceTest.java @@ -19,6 +19,7 @@ import org.springframework.context.ApplicationContext; import org.tron.common.TestConstants; import org.tron.common.application.TronApplicationContext; +import org.tron.common.utils.PeerManagerStateResetter; import org.tron.common.utils.ReflectUtils; import org.tron.common.utils.Sha256Hash; import org.tron.core.ChainBaseManager; @@ -52,6 +53,7 @@ public class HandShakeServiceTest { @BeforeClass public static void init() throws Exception { + PeerManagerStateResetter.reset(); Args.setParam(new String[] {"--output-directory", temporaryFolder.newFolder().toString(), "--debug"}, TestConstants.TEST_CONF); context = new TronApplicationContext(DefaultConfig.class); diff --git a/framework/src/test/java/org/tron/core/services/WalletApiTest.java b/framework/src/test/java/org/tron/core/services/WalletApiTest.java index 4a55556afb1..25b21f30872 100644 --- a/framework/src/test/java/org/tron/core/services/WalletApiTest.java +++ b/framework/src/test/java/org/tron/core/services/WalletApiTest.java @@ -17,6 +17,7 @@ import org.tron.common.ClassLevelAppContextFixture; import org.tron.common.TestConstants; import org.tron.common.application.TronApplicationContext; +import org.tron.common.utils.PeerManagerStateResetter; import org.tron.common.utils.PublicMethod; import org.tron.common.utils.TimeoutInterceptor; import org.tron.core.config.args.Args; @@ -38,6 +39,7 @@ public class WalletApiTest { @BeforeClass public static void init() throws IOException { + PeerManagerStateResetter.reset(); Args.setParam(new String[] {"-d", temporaryFolder.newFolder().toString(), "--p2p-disable", "true"}, TestConstants.TEST_CONF); Args.getInstance().setRpcPort(PublicMethod.chooseRandomPort()); diff --git a/framework/src/test/java/org/tron/core/services/http/BroadcastServletTest.java b/framework/src/test/java/org/tron/core/services/http/BroadcastServletTest.java index d6bf3850f30..532ddcd5521 100644 --- a/framework/src/test/java/org/tron/core/services/http/BroadcastServletTest.java +++ b/framework/src/test/java/org/tron/core/services/http/BroadcastServletTest.java @@ -156,7 +156,7 @@ public void doPostTest() throws IOException { while ((text = bufferedReader.readLine()) != null) { sb.append(text); } - Assert.assertTrue(sb.toString().contains("null")); + Assert.assertTrue(sb.toString().contains("{\"Error\":\"internal server error\"}")); httpUrlConnection.disconnect(); } } \ No newline at end of file diff --git a/framework/src/test/java/org/tron/core/services/http/JsonRpcRateLimiterServletTest.java b/framework/src/test/java/org/tron/core/services/http/JsonRpcRateLimiterServletTest.java new file mode 100644 index 00000000000..52ff23a7d2d --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/http/JsonRpcRateLimiterServletTest.java @@ -0,0 +1,129 @@ +package org.tron.core.services.http; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import com.googlecode.jsonrpc4j.JsonRpcServer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.MockedStatic; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; +import org.tron.common.TestConstants; +import org.tron.core.config.args.Args; +import org.tron.core.services.interfaceJsonRpcOnPBFT.JsonRpcOnPBFTServlet; +import org.tron.core.services.interfaceJsonRpcOnSolidity.JsonRpcOnSolidityServlet; +import org.tron.core.services.interfaceOnPBFT.WalletOnPBFT; +import org.tron.core.services.interfaceOnSolidity.WalletOnSolidity; +import org.tron.core.services.jsonrpc.JsonRpcServlet; +import org.tron.core.services.ratelimiter.GlobalRateLimiter; +import org.tron.core.services.ratelimiter.RateLimiterContainer; +import org.tron.core.services.ratelimiter.RuntimeData; +import org.tron.core.services.ratelimiter.adapter.IRateLimiter; + +@RunWith(Parameterized.class) +public class JsonRpcRateLimiterServletTest { + + private final Class servletClass; + private RateLimiterServlet servlet; + private IRateLimiter perEndpoint; + private Object dispatcher; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + + public JsonRpcRateLimiterServletTest(Class servletClass) { + this.servletClass = servletClass; + } + + @Parameterized.Parameters(name = "{0}") + public static Collection servlets() { + return Arrays.asList(new Object[][] { + {JsonRpcServlet.class}, + {JsonRpcOnSolidityServlet.class}, + {JsonRpcOnPBFTServlet.class} + }); + } + + @Before + public void setUp() throws Exception { + // Initialize Args before GlobalRateLimiter's static QPS limiters are loaded. + Args.setParam(new String[0], TestConstants.TEST_CONF); + servlet = servletClass.getDeclaredConstructor().newInstance(); + RateLimiterContainer container = new RateLimiterContainer(); + perEndpoint = mock(IRateLimiter.class); + container.add("http_", servletClass.getSimpleName(), perEndpoint); + ReflectionTestUtils.setField(servlet, "container", container); + + if (servlet instanceof JsonRpcOnSolidityServlet) { + dispatcher = mock(WalletOnSolidity.class); + ReflectionTestUtils.setField(servlet, "walletOnSolidity", dispatcher); + } else if (servlet instanceof JsonRpcOnPBFTServlet) { + dispatcher = mock(WalletOnPBFT.class); + ReflectionTestUtils.setField(servlet, "walletOnPBFT", dispatcher); + } else { + dispatcher = mock(JsonRpcServer.class); + ReflectionTestUtils.setField(servlet, "rpcServer", dispatcher); + } + + request = new MockHttpServletRequest("POST", "/jsonrpc"); + request.setServletPath("/jsonrpc"); + request.setRemoteAddr("10.0.0.1"); + request.setContentType("application/json"); + request.setContent("{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"id\":1}" + .getBytes(StandardCharsets.UTF_8)); + response = new MockHttpServletResponse(); + } + + @After + public void tearDown() { + Args.clearParam(); + } + + @Test + public void testPerEndpointRejectionReturnsSanitizedHttpError() throws Exception { + when(perEndpoint.acquirePermit(any(RuntimeData.class))).thenReturn(false); + + try (MockedStatic global = mockStatic(GlobalRateLimiter.class)) { + servlet.service(request, response); + + global.verify(() -> GlobalRateLimiter.acquirePermit(any()), never()); + assertRateLimitResponse(); + } + } + + @Test + public void testGlobalRejectionReturnsSanitizedHttpError() throws Exception { + when(perEndpoint.acquirePermit(any(RuntimeData.class))).thenReturn(true); + + try (MockedStatic global = mockStatic(GlobalRateLimiter.class)) { + global.when(() -> GlobalRateLimiter.acquirePermit(any())).thenReturn(false); + + servlet.service(request, response); + + global.verify(() -> GlobalRateLimiter.acquirePermit(any())); + assertRateLimitResponse(); + } + } + + private void assertRateLimitResponse() throws Exception { + assertEquals(200, response.getStatus()); + assertEquals("application/json; charset=utf-8", response.getContentType()); + assertEquals("{\"Error\":\"lack of computing resources\"}", + response.getContentAsString().trim()); + verify(perEndpoint).acquirePermit(any(RuntimeData.class)); + verifyNoInteractions(dispatcher); + } +} diff --git a/framework/src/test/java/org/tron/core/services/http/UtilProcessErrorTest.java b/framework/src/test/java/org/tron/core/services/http/UtilProcessErrorTest.java new file mode 100644 index 00000000000..5d4baa34c6f --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/http/UtilProcessErrorTest.java @@ -0,0 +1,107 @@ +package org.tron.core.services.http; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.bouncycastle.util.encoders.DecoderException; +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletResponse; +import org.tron.core.exception.ContractValidateException; +import org.tron.core.exception.HeaderNotFound; +import org.tron.core.exception.MaintenanceUnavailableException; +import org.tron.core.exception.ZkProofValidateException; +import org.tron.json.JSONException; +import org.tron.json.JSONObject; + +public class UtilProcessErrorTest { + + private static final String INTERNAL_SERVER_ERROR = "internal server error"; + private static final String RATE_LIMITER_ERROR_MSG = "lack of computing resources"; + + @Test + public void exactCompatibilityTypesPreserveNonBlankMessage() throws Exception { + assertError(new JsonFormat.ParseException("1:2: invalid \"field\"\nvalue"), + "1:2: invalid \"field\"\nvalue"); + assertError(new ContractValidateException("balance is not sufficient"), + "balance is not sufficient"); + assertError(new MaintenanceUnavailableException("maintenance in progress"), + "maintenance in progress"); + } + + @Test + public void unclassifiedTypesFailClosed() throws Exception { + DecoderException decoder = assertThrows(DecoderException.class, () -> Hex.decode("zz")); + Exception[] errors = { + new NullPointerException("internal field name"), + new JSONException("server serialization detail"), + new InvalidProtocolBufferException("stored protobuf detail"), + decoder, + new HeaderNotFound("latest block not found"), + new IllegalArgumentException("No enum constant internal.Type.VALUE"), + new IllegalAccessException(RATE_LIMITER_ERROR_MSG), + new IllegalAccessException("other access failure"), + new ZkProofValidateException("wrapped validation detail", true) + }; + + for (Exception error : errors) { + assertError(error, INTERNAL_SERVER_ERROR); + } + } + + @Test + public void onlyExactFixedControlSignalsArePreserved() throws Exception { + assertError(new IllegalArgumentException(Util.EVENTS_DEPRECATED_MSG), + Util.EVENTS_DEPRECATED_MSG); + assertError(new IllegalArgumentException("other argument failure"), INTERNAL_SERVER_ERROR); + assertError(new NumberFormatException(Util.EVENTS_DEPRECATED_MSG), INTERNAL_SERVER_ERROR); + } + + @Test + public void nullBlankAndSubclassMessagesFailClosed() throws Exception { + assertError(null, INTERNAL_SERVER_ERROR); + assertError(new JsonFormat.ParseException(null), INTERNAL_SERVER_ERROR); + assertError(new JsonFormat.ParseException(""), INTERNAL_SERVER_ERROR); + assertError(new JsonFormat.ParseException(" "), INTERNAL_SERVER_ERROR); + assertError(new ContractValidateException("subclass message") { }, INTERNAL_SERVER_ERROR); + } + + @Test + public void auditedErrorWriterPreservesTextVerbatim() throws Exception { + for (String audited : new String[] {Util.INVALID_ADDRESS_MSG, Util.RATE_LIMITER_ERROR_MSG}) { + MockHttpServletResponse response = new MockHttpServletResponse(); + Util.writeAuditedError(audited, response); + JSONObject body = JSONObject.parseObject(response.getContentAsString()); + assertEquals(audited, body.getString("Error")); + } + } + + @Test + public void auditedErrorWriterWithNullMessageWritesEmptyObject() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + Util.writeAuditedError(null, response); + assertEquals("{}", response.getContentAsString().trim()); + } + + @Test + public void serverErrorChannelSanitizesLikeTheSharedPath() throws Exception { + assertServerError(new NullPointerException("internal field name"), INTERNAL_SERVER_ERROR); + assertServerError(new ContractValidateException("balance is not sufficient"), + "balance is not sufficient"); + } + + private static void assertServerError(Exception error, String expected) throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + Util.processServerError(error, response); + JSONObject body = JSONObject.parseObject(response.getContentAsString()); + assertEquals(expected, body.getString("Error")); + } + + private static void assertError(Exception error, String expected) throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + Util.processError(error, response); + JSONObject body = JSONObject.parseObject(response.getContentAsString()); + assertEquals(expected, body.getString("Error")); + } +} diff --git a/framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServletTest.java b/framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServletTest.java index e1abb41d1e1..cacb904d9b9 100644 --- a/framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServletTest.java +++ b/framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServletTest.java @@ -1,202 +1,146 @@ package org.tron.core.services.http.solidity; -import static org.mockito.BDDMockito.given; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLStreamHandlerFactory; -import java.nio.charset.StandardCharsets; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; +import com.google.protobuf.ByteString; +import java.util.Arrays; +import java.util.Collection; import org.junit.After; -import org.junit.Assert; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; -import org.tron.common.utils.FileUtil; -import org.tron.common.utils.PublicMethod; -import org.tron.core.services.http.solidity.mockito.HttpUrlStreamHandler; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.Sha256Hash; +import org.tron.core.Wallet; +import org.tron.core.config.args.Args; +import org.tron.json.JSONObject; +import org.tron.protos.Protocol.Transaction; + +@RunWith(Parameterized.class) +public class GetTransactionByIdSolidityServletTest { + private static final String TRANSACTION_ID = + "309b6fa3d01353e46f57dd8a8f27611f98e392b50d035cef213f2c55225a8bd2"; + private static final ByteString TRANSACTION_ID_BYTES = + ByteString.copyFrom(ByteArray.fromHexString(TRANSACTION_ID)); -@Slf4j -public class GetTransactionByIdSolidityServletTest { + @Parameter + public String method; - private static HttpUrlStreamHandler httpUrlStreamHandler; - private GetTransactionByIdSolidityServlet getTransactionByIdSolidityServlet; - private HttpServletRequest request; - private HttpServletResponse response; - private HttpURLConnection httpUrlConnection; - private OutputStreamWriter outputStreamWriter; - private URL url; - - /** - * . - */ - @BeforeClass - public static void init() { - // Allows for mocking URL connections - URLStreamHandlerFactory urlStreamHandlerFactory = mock(URLStreamHandlerFactory.class); - try { - URL.setURLStreamHandlerFactory(urlStreamHandlerFactory); - } catch (Error e) { - logger.info("Ignore error: {}", e.getMessage()); - } + private GetTransactionByIdSolidityServlet servlet; + private Wallet wallet; + private long savedMaxMessageSize; - httpUrlStreamHandler = new HttpUrlStreamHandler(); - given(urlStreamHandlerFactory.createURLStreamHandler("http")).willReturn(httpUrlStreamHandler); + @Parameters(name = "{0}") + public static Collection methods() { + return Arrays.asList(new Object[][] {{"GET"}, {"POST"}}); } - /** - * Init. - */ - @Before public void setUp() { - getTransactionByIdSolidityServlet = new GetTransactionByIdSolidityServlet(); - this.request = mock(HttpServletRequest.class); - this.response = mock(HttpServletResponse.class); - this.httpUrlConnection = mock(HttpURLConnection.class); - this.outputStreamWriter = mock(OutputStreamWriter.class); - httpUrlStreamHandler.resetConnections(); + savedMaxMessageSize = Args.getInstance().getHttpMaxMessageSize(); + Args.getInstance().setHttpMaxMessageSize(1024); + servlet = new GetTransactionByIdSolidityServlet(); + wallet = mock(Wallet.class); + ReflectionTestUtils.setField(servlet, "wallet", wallet); } - /** - * Release Resource. - */ @After public void tearDown() { - if (FileUtil.deleteDir(new File("temp.txt"))) { - logger.info("Release resources successful."); + Args.getInstance().setHttpMaxMessageSize(savedMaxMessageSize); + } + + @Test + public void walletFailureReturnsSanitizedJson() throws Exception { + when(wallet.getTransactionById(TRANSACTION_ID_BYTES)) + .thenThrow(new NullPointerException("internal transaction store detail")); + + MockHttpServletResponse response = request(TRANSACTION_ID); + + assertEquals("internal server error", errorMessage(response)); + verify(wallet).getTransactionById(TRANSACTION_ID_BYTES); + } + + @Test + public void invalidHexReturnsJsonWithoutCallingWallet() throws Exception { + MockHttpServletResponse response = request("zz"); + + String message = errorMessage(response); + if ("GET".equals(method)) { + assertEquals("internal server error", message); } else { - logger.info("Release resources failure."); + assertTrue(message.matches("\\d+:\\d+: INVALID hex String")); } + verifyNoInteractions(wallet); } @Test - public void doPostTest() throws IOException { - - //send Post request - - final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - System.setOut(new PrintStream(outContent)); - String href = "http://127.0.0.1:" - + PublicMethod.chooseRandomPort() + "/walletsolidity/gettransactioninfobyid"; - httpUrlStreamHandler.addConnection(new URL(href), httpUrlConnection); - httpUrlConnection.setRequestMethod("POST"); - httpUrlConnection.setRequestProperty("Content-Type", "application/json"); - httpUrlConnection.setRequestProperty("Connection", "Keep-Alive"); - httpUrlConnection.setUseCaches(false); - httpUrlConnection.setDoOutput(true); - String postData = "{\"value\": \"309b6fa3d01353e46f57dd8a8f27611f98e392b50d035cef21" - + "3f2c55225a8bd2\"}"; - httpUrlConnection.setRequestProperty("Content-Length", String.valueOf(postData.length())); - - when(httpUrlConnection.getOutputStream()).thenReturn(outContent); - OutputStreamWriter out = new OutputStreamWriter(httpUrlConnection.getOutputStream(), - StandardCharsets.UTF_8); - out.write(postData); - out.flush(); - out.close(); - PrintWriter writer = new PrintWriter("temp.txt"); - when(response.getWriter()).thenReturn(writer); - - getTransactionByIdSolidityServlet.doPost(request, response); - // Get Response Body - String line; - StringBuilder result = new StringBuilder(); - - byte[] buffer = new byte[1024]; - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(buffer); - when(httpUrlConnection.getInputStream()).thenReturn(byteArrayInputStream); - BufferedReader in = new BufferedReader(new InputStreamReader(httpUrlConnection.getInputStream(), - StandardCharsets.UTF_8)); - - while ((line = in.readLine()) != null) { - result.append(line).append("\n"); - } - Assert.assertNotNull(result); - in.close(); - writer.flush(); - FileInputStream fileInputStream = new FileInputStream("temp.txt"); - InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); - BufferedReader bufferedReader = new BufferedReader(inputStreamReader); - - StringBuilder sb = new StringBuilder(); - String text; - while ((text = bufferedReader.readLine()) != null) { - sb.append(text); - } - Assert.assertTrue(sb.toString().contains("null")); - httpUrlConnection.disconnect(); + public void missingTransactionKeepsEmptyObject() throws Exception { + MockHttpServletResponse response = request(TRANSACTION_ID); + + assertEquals(200, response.getStatus()); + assertEquals("{}", response.getContentAsString().trim()); + verify(wallet).getTransactionById(TRANSACTION_ID_BYTES); } @Test - public void doGetTest() throws IOException { - - final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - System.setOut(new PrintStream(outContent)); - String href = "http://127.0.0.1:" - + PublicMethod.chooseRandomPort() + "/walletsolidity/gettransactioninfobyid"; - httpUrlStreamHandler.addConnection(new URL(href), httpUrlConnection); - httpUrlConnection.setRequestMethod("GET"); - httpUrlConnection.setRequestProperty("Content-Type", "application/json"); - httpUrlConnection.setRequestProperty("Connection", "Keep-Alive"); - httpUrlConnection.setUseCaches(false); - httpUrlConnection.setDoOutput(true); - String postData = "{\"value\": \"309b6fa3d01353e46f57dd8a8f27611f98e392b50d035cef21" - + "3f2c55225a8bd2\"}"; - httpUrlConnection.setRequestProperty("Content-Length", String.valueOf(postData.length())); - - when(httpUrlConnection.getOutputStream()).thenReturn(outContent); - OutputStreamWriter out = new OutputStreamWriter(httpUrlConnection.getOutputStream(), - StandardCharsets.UTF_8); - out.write(postData); - out.flush(); - out.close(); - PrintWriter writer = new PrintWriter("temp.txt"); - when(response.getWriter()).thenReturn(writer); - - getTransactionByIdSolidityServlet.doPost(request, response); - // Get Response Body - String line; - StringBuilder result = new StringBuilder(); - - byte[] buffer = new byte[1024]; - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(buffer); - when(httpUrlConnection.getInputStream()).thenReturn(byteArrayInputStream); - BufferedReader in = new BufferedReader(new InputStreamReader(httpUrlConnection.getInputStream(), - StandardCharsets.UTF_8)); - - while ((line = in.readLine()) != null) { - result.append(line).append("\n"); - } - Assert.assertNotNull(result); - in.close(); - writer.flush(); - FileInputStream fileInputStream = new FileInputStream("temp.txt"); - InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); - BufferedReader bufferedReader = new BufferedReader(inputStreamReader); - - StringBuilder sb = new StringBuilder(); - String text; - while ((text = bufferedReader.readLine()) != null) { - sb.append(text); + public void successfulLookupKeepsTransaction() throws Exception { + ByteString signature = ByteString.copyFromUtf8("transaction signature"); + Transaction transaction = Transaction.newBuilder() + .setRawData(Transaction.raw.newBuilder().setTimestamp(123).setExpiration(456)) + .addSignature(signature).build(); + when(wallet.getTransactionById(TRANSACTION_ID_BYTES)).thenReturn(transaction); + + MockHttpServletResponse response = request(TRANSACTION_ID); + + assertEquals(200, response.getStatus()); + JSONObject body = JSONObject.parseObject(response.getContentAsString()); + assertEquals(4, body.size()); + JSONObject rawData = body.getJSONObject("raw_data"); + assertEquals(123L, rawData.getLongValue("timestamp")); + assertEquals(456L, rawData.getLongValue("expiration")); + assertEquals(0, rawData.getJSONArray("contract").size()); + assertEquals(ByteArray.toHexString(transaction.getRawData().toByteArray()), + body.getString("raw_data_hex")); + assertEquals(Sha256Hash.of(Args.getInstance().isECKeyCryptoEngine(), + transaction.getRawData().toByteArray()).toString(), body.getString("txID")); + assertEquals(1, body.getJSONArray("signature").size()); + assertEquals(ByteArray.toHexString(signature.toByteArray()), + body.getJSONArray("signature").getString(0)); + verify(wallet).getTransactionById(TRANSACTION_ID_BYTES); + } + + private MockHttpServletResponse request(String value) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(method, + "/walletsolidity/gettransactionbyid"); + MockHttpServletResponse response = new MockHttpServletResponse(); + if ("GET".equals(method)) { + request.setParameter("value", value); + servlet.doGet(request, response); + } else { + request.setContentType("application/json"); + request.setContent(("{\"value\":\"" + value + "\"}").getBytes(UTF_8)); + servlet.doPost(request, response); } - Assert.assertTrue(sb.toString().contains("null")); - httpUrlConnection.disconnect(); + return response; } -} + private static String errorMessage(MockHttpServletResponse response) throws Exception { + assertEquals(200, response.getStatus()); + JSONObject body = JSONObject.parseObject(response.getContentAsString()); + assertEquals(1, body.size()); + return body.getString("Error"); + } +} diff --git a/framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServletTest.java b/framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServletTest.java new file mode 100644 index 00000000000..a8810114f82 --- /dev/null +++ b/framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionInfoByIdSolidityServletTest.java @@ -0,0 +1,135 @@ +package org.tron.core.services.http.solidity; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import com.google.protobuf.ByteString; +import java.util.Arrays; +import java.util.Collection; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; +import org.tron.common.utils.ByteArray; +import org.tron.core.Wallet; +import org.tron.core.config.args.Args; +import org.tron.json.JSONObject; +import org.tron.protos.Protocol.TransactionInfo; + +@RunWith(Parameterized.class) +public class GetTransactionInfoByIdSolidityServletTest { + + private static final String TRANSACTION_ID = + "309b6fa3d01353e46f57dd8a8f27611f98e392b50d035cef213f2c55225a8bd2"; + private static final ByteString TRANSACTION_ID_BYTES = + ByteString.copyFrom(ByteArray.fromHexString(TRANSACTION_ID)); + + @Parameter + public String method; + + private GetTransactionInfoByIdSolidityServlet servlet; + private Wallet wallet; + private long savedMaxMessageSize; + + @Parameters(name = "{0}") + public static Collection methods() { + return Arrays.asList(new Object[][] {{"GET"}, {"POST"}}); + } + + @Before + public void setUp() { + savedMaxMessageSize = Args.getInstance().getHttpMaxMessageSize(); + Args.getInstance().setHttpMaxMessageSize(1024); + servlet = new GetTransactionInfoByIdSolidityServlet(); + wallet = mock(Wallet.class); + ReflectionTestUtils.setField(servlet, "wallet", wallet); + } + + @After + public void tearDown() { + Args.getInstance().setHttpMaxMessageSize(savedMaxMessageSize); + } + + @Test + public void walletFailureReturnsSanitizedJson() throws Exception { + when(wallet.getTransactionInfoById(TRANSACTION_ID_BYTES)) + .thenThrow(new NullPointerException("internal transaction store detail")); + + MockHttpServletResponse response = request(TRANSACTION_ID); + + assertEquals("internal server error", errorMessage(response)); + verify(wallet).getTransactionInfoById(TRANSACTION_ID_BYTES); + } + + @Test + public void invalidHexReturnsJsonWithoutCallingWallet() throws Exception { + MockHttpServletResponse response = request("zz"); + + String message = errorMessage(response); + if ("GET".equals(method)) { + assertEquals("internal server error", message); + } else { + assertTrue(message.matches("\\d+:\\d+: INVALID hex String")); + } + verifyNoInteractions(wallet); + } + + @Test + public void missingTransactionKeepsEmptyObject() throws Exception { + MockHttpServletResponse response = request(TRANSACTION_ID); + + assertEquals(200, response.getStatus()); + assertEquals("{}", response.getContentAsString().trim()); + verify(wallet).getTransactionInfoById(TRANSACTION_ID_BYTES); + } + + @Test + public void successfulLookupKeepsTransactionInfo() throws Exception { + TransactionInfo info = TransactionInfo.newBuilder() + .setId(TRANSACTION_ID_BYTES).setFee(7).setBlockNumber(123).build(); + when(wallet.getTransactionInfoById(TRANSACTION_ID_BYTES)).thenReturn(info); + + MockHttpServletResponse response = request(TRANSACTION_ID); + + assertEquals(200, response.getStatus()); + JSONObject body = JSONObject.parseObject(response.getContentAsString()); + assertEquals(3, body.size()); + assertEquals(TRANSACTION_ID, body.getString("id")); + assertEquals(7L, body.getLongValue("fee")); + assertEquals(123L, body.getLongValue("blockNumber")); + verify(wallet).getTransactionInfoById(TRANSACTION_ID_BYTES); + } + + private MockHttpServletResponse request(String value) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(method, + "/walletsolidity/gettransactioninfobyid"); + MockHttpServletResponse response = new MockHttpServletResponse(); + if ("GET".equals(method)) { + request.setParameter("value", value); + servlet.doGet(request, response); + } else { + request.setContentType("application/json"); + request.setContent(("{\"value\":\"" + value + "\"}").getBytes(UTF_8)); + servlet.doPost(request, response); + } + return response; + } + + private static String errorMessage(MockHttpServletResponse response) throws Exception { + assertEquals(200, response.getStatus()); + JSONObject body = JSONObject.parseObject(response.getContentAsString()); + assertEquals(1, body.size()); + return body.getString("Error"); + } +} diff --git a/framework/src/test/java/org/tron/core/zksnark/SendCoinShieldTest.java b/framework/src/test/java/org/tron/core/zksnark/SendCoinShieldTest.java index 08de83ca8bf..efa60139b12 100644 --- a/framework/src/test/java/org/tron/core/zksnark/SendCoinShieldTest.java +++ b/framework/src/test/java/org/tron/core/zksnark/SendCoinShieldTest.java @@ -14,6 +14,7 @@ import java.util.Optional; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -106,6 +107,7 @@ public class SendCoinShieldTest extends BaseTest { private static final int VOTE_SCORE = 2; private static final String DESCRIPTION = "TRX"; private static final String URL = "https://tron.network"; + private long previousAllowShieldedTransaction; @Resource private Wallet wallet; @@ -130,6 +132,8 @@ public static void initZksnarkParams() { */ @Before public void init() { + previousAllowShieldedTransaction = dbManager.getDynamicPropertiesStore() + .getAllowShieldedTransaction(); if (init) { return; } @@ -155,6 +159,12 @@ public void init() { init = true; } + @After + public void restoreAllowShieldedTransaction() { + dbManager.getDynamicPropertiesStore() + .saveAllowShieldedTransaction(previousAllowShieldedTransaction); + } + private void addZeroValueOutputNote(ZenTransactionBuilder builder) throws ZksnarkException { SpendingKey spendingKey = SpendingKey.random(); FullViewingKey fullViewingKey = spendingKey.fullViewingKey(); diff --git a/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java b/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java index 5854b731e97..e62396bc046 100755 --- a/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java +++ b/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java @@ -8,7 +8,6 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import java.lang.reflect.Field; import java.security.SignatureException; import java.util.Arrays; import java.util.HashSet; @@ -21,6 +20,7 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -47,7 +47,6 @@ import org.tron.common.zksnark.LibrustzcashParam.OutputProofParams; import org.tron.common.zksnark.LibrustzcashParam.SpendSigParams; import org.tron.consensus.dpos.DposSlot; -import org.tron.consensus.dpos.DposTask; import org.tron.core.Wallet; import org.tron.core.actuator.Actuator; import org.tron.core.actuator.ActuatorCreator; @@ -126,6 +125,7 @@ public class ShieldedReceiveTest extends BaseTest { "librustzcashSaplingCheckSpend error", "Rt is invalid." )); + private long previousAllowShieldedTransaction; private static final String FROM_ADDRESS; private static final String ADDRESS_ONE_PRIVATE_KEY; @@ -143,13 +143,11 @@ public class ShieldedReceiveTest extends BaseTest { @Resource private ConsensusService consensusService; @Resource - private DposTask dposTask; - @Resource private Wallet wallet; @Resource private DposSlot dposSlot; - private static boolean init; + private static boolean consensusScheduleInitialized; static { Args.setParam(new String[] {"--output-directory", dbPath(), "-w"}, SHIELD_CONF); @@ -167,14 +165,21 @@ public static void initZksnarkParams() { */ @Before public void init() { + previousAllowShieldedTransaction = chainBaseManager.getDynamicPropertiesStore() + .getAllowShieldedTransaction(); if (init) { return; } - consensusService.start(); chainBaseManager.getDynamicPropertiesStore().saveTotalShieldedPoolValue(10_000_000_000L); init = true; } + @After + public void restoreAllowShieldedTransaction() { + chainBaseManager.getDynamicPropertiesStore() + .saveAllowShieldedTransaction(previousAllowShieldedTransaction); + } + private static byte[] randomUint256() { return org.tron.keystore.Wallet.generateRandomBytes(32); } @@ -254,9 +259,28 @@ private void updateTotalShieldedPoolValue(long valueBalance) { @Test public void testIsMining() { + initializeActiveWitnessSchedule(); Assert.assertTrue(wallet.isMining()); } + private void initializeActiveWitnessSchedule() { + synchronized (ShieldedReceiveTest.class) { + if (consensusScheduleInitialized) { + return; + } + boolean started = false; + try { + consensusService.start(); + started = true; + } finally { + if (started) { + consensusService.stop(); + } + } + consensusScheduleInitialized = true; + } + } + /* * test of change ShieldedTransactionFee proposal */ @@ -2407,144 +2431,134 @@ public void pushSameSkAndScanAndSpend() throws Exception { assert ecKey != null; byte[] witnessAddress = ecKey.getAddress(); WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(witnessAddress)); - // Stop the consensus task before modifying the witness schedule: DposTask uses the same - // localwitness key and would otherwise race to produce blocks at the same slot, - // triggering fork resolution and making the test slow. - consensusService.stop(); - try { - chainBaseManager.addWitness(ByteString.copyFrom(witnessAddress)); - - long time = nextScheduledTime(witnessCapsule.getAddress()); - Block block = getSignedBlock(witnessCapsule.getAddress(), time, privateKey); - dbManager.pushBlock(new BlockCapsule(block)); - - //create transactions - chainBaseManager.getDynamicPropertiesStore().saveAllowShieldedTransaction(1); - chainBaseManager.getDynamicPropertiesStore().saveTotalShieldedPoolValue(1000 * 1000000L); - ZenTransactionBuilder builder = new ZenTransactionBuilder(wallet); - - // generate spend proof - SpendingKey sk = SpendingKey - .decode("ff2c06269315333a9207f817d2eca0ac555ca8f90196976324c7756504e7c9ee"); - ExpandedSpendingKey expsk = sk.expandedSpendingKey(); - byte[] senderOvk = expsk.getOvk(); - PaymentAddress address = sk.defaultAddress(); - Note note = new Note(address, 1000 * 1000000L); - IncrementalMerkleVoucherContainer voucher = createSimpleMerkleVoucherContainer(note.cm()); - byte[] anchor = voucher.root().getContent().toByteArray(); - chainBaseManager.getMerkleContainer() - .putMerkleTreeIntoStore(anchor, voucher.getVoucherCapsule().getTree()); - builder.addSpend(expsk, note, anchor, voucher); - - // generate output proof - SpendingKey sk2 = SpendingKey.random(); - FullViewingKey fullViewingKey = sk2.fullViewingKey(); - IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); - - byte[] memo = org.tron.keystore.Wallet.generateRandomBytes(512); - - //send coin to 2 different address generated by same sk - DiversifierT d1 = DiversifierT.random(); - PaymentAddress paymentAddress1 = incomingViewingKey.address(d1).get(); - builder.addOutput(senderOvk, paymentAddress1, - (1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo); - - DiversifierT d2 = DiversifierT.random(); - PaymentAddress paymentAddress2 = incomingViewingKey.address(d2).get(); - builder.addOutput(senderOvk, paymentAddress2, - (1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo); + // Initialize the same schedule as DPoS startup without starting its producer thread. + // Manual block production below therefore cannot race the background producer. + initializeActiveWitnessSchedule(); + chainBaseManager.addWitness(ByteString.copyFrom(witnessAddress)); - TransactionCapsule transactionCap = builder.build(); + long time = nextScheduledTime(witnessCapsule.getAddress()); + Block block = getSignedBlock(witnessCapsule.getAddress(), time, privateKey); + dbManager.pushBlock(new BlockCapsule(block)); - byte[] trxId = transactionCap.getTransactionId().getBytes(); - boolean ok = dbManager.pushTransaction(transactionCap); - Assert.assertTrue(ok); - - Thread.sleep(500); - //package transaction to block - long expectedBlockNum = chainBaseManager.getDynamicPropertiesStore() - .getLatestBlockHeaderNumber() + 1; - block = getSignedBlock(witnessCapsule.getAddress(), - nextScheduledTime(witnessCapsule.getAddress()), privateKey); - dbManager.pushBlock(new BlockCapsule(block)); - - BlockCapsule blockCapsule3 = new BlockCapsule(wallet.getNowBlock()); - Assert.assertEquals("unexpected block number", expectedBlockNum, blockCapsule3.getNum()); - - block = getSignedBlock(witnessCapsule.getAddress(), - nextScheduledTime(witnessCapsule.getAddress()), privateKey); - dbManager.pushBlock(new BlockCapsule(block)); - - // scan note by ivk - byte[] receiverIvk = incomingViewingKey.getValue(); - DecryptNotes notes1 = wallet.scanNoteByIvk(0, 100, receiverIvk); - Assert.assertEquals(2, notes1.getNoteTxsCount()); - - // scan note by ivk and mark - DecryptNotesMarked notes3 = wallet.scanAndMarkNoteByIvk(0, 100, receiverIvk, - fullViewingKey.getAk(), fullViewingKey.getNk()); - Assert.assertEquals(2, notes3.getNoteTxsCount()); - - // scan note by ovk - DecryptNotes notes2 = wallet.scanNoteByOvk(0, 100, senderOvk); - Assert.assertEquals(2, notes2.getNoteTxsCount()); - - // to spend received note above. - ZenTransactionBuilder builder2 = new ZenTransactionBuilder(wallet); - - //query merkleinfo - OutputPointInfo.Builder request = OutputPointInfo.newBuilder(); - for (int i = 0; i < notes1.getNoteTxsCount(); i++) { - OutputPoint.Builder outPointBuild = OutputPoint.newBuilder(); - outPointBuild.setHash(ByteString.copyFrom(trxId)); - outPointBuild.setIndex(i); - request.addOutPoints(outPointBuild.build()); - } - request.setBlockNum(1); - IncrementalMerkleVoucherInfo merkleVoucherInfo = wallet - .getMerkleTreeVoucherInfo(request.build()); - - //build spend proof. allow only one note in spend - ExpandedSpendingKey expsk2 = sk2.expandedSpendingKey(); - for (int i = 0; i < 1; i++) { - org.tron.api.GrpcAPI.Note grpcNote = notes1.getNoteTxs(i).getNote(); - PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(grpcNote.getPaymentAddress()); - Note note2 = new Note(paymentAddress.getD(), - paymentAddress.getPkD(), - grpcNote.getValue(), - grpcNote.getRcm().toByteArray() - ); - - IncrementalMerkleVoucherContainer voucher2 = - new IncrementalMerkleVoucherContainer( - new IncrementalMerkleVoucherCapsule(merkleVoucherInfo.getVouchers(i))); - byte[] anchor2 = voucher2.root().getContent().toByteArray(); - builder2.addSpend(expsk2, note2, anchor2, voucher2); - } + //create transactions + chainBaseManager.getDynamicPropertiesStore().saveAllowShieldedTransaction(1); + chainBaseManager.getDynamicPropertiesStore().saveTotalShieldedPoolValue(1000 * 1000000L); + ZenTransactionBuilder builder = new ZenTransactionBuilder(wallet); + + // generate spend proof + SpendingKey sk = SpendingKey + .decode("ff2c06269315333a9207f817d2eca0ac555ca8f90196976324c7756504e7c9ee"); + ExpandedSpendingKey expsk = sk.expandedSpendingKey(); + byte[] senderOvk = expsk.getOvk(); + PaymentAddress address = sk.defaultAddress(); + Note note = new Note(address, 1000 * 1000000L); + IncrementalMerkleVoucherContainer voucher = createSimpleMerkleVoucherContainer(note.cm()); + byte[] anchor = voucher.root().getContent().toByteArray(); + chainBaseManager.getMerkleContainer() + .putMerkleTreeIntoStore(anchor, voucher.getVoucherCapsule().getTree()); + builder.addSpend(expsk, note, anchor, voucher); + + // generate output proof + SpendingKey sk2 = SpendingKey.random(); + FullViewingKey fullViewingKey = sk2.fullViewingKey(); + IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); + + byte[] memo = org.tron.keystore.Wallet.generateRandomBytes(512); + + //send coin to 2 different address generated by same sk + DiversifierT d1 = DiversifierT.random(); + PaymentAddress paymentAddress1 = incomingViewingKey.address(d1).get(); + builder.addOutput(senderOvk, paymentAddress1, + (1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo); + + DiversifierT d2 = DiversifierT.random(); + PaymentAddress paymentAddress2 = incomingViewingKey.address(d2).get(); + builder.addOutput(senderOvk, paymentAddress2, + (1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo); - //build output proof - SpendingKey sk3 = SpendingKey.random(); - FullViewingKey fvk3 = sk3.fullViewingKey(); - IncomingViewingKey ivk3 = fvk3.inViewingKey(); - - DiversifierT d3 = DiversifierT.random(); - PaymentAddress paymentAddress3 = incomingViewingKey.address(d3).get(); - byte[] memo3 = org.tron.keystore.Wallet.generateRandomBytes(512); - builder2.addOutput(expsk2.getOvk(), paymentAddress3, - (1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2 - wallet - .getShieldedTransactionFee(), memo3); - - TransactionCapsule transactionCap2 = builder2.build(); - boolean ok2 = dbManager.pushTransaction(transactionCap2); - Assert.assertTrue(ok2); - } finally { - // DposTask.init() does not reset isRunning (it stays false after stop()), so force it back - // to true via reflection before restarting. - Field isRunning = DposTask.class.getDeclaredField("isRunning"); - isRunning.setAccessible(true); - isRunning.set(dposTask, true); - consensusService.start(); + TransactionCapsule transactionCap = builder.build(); + + byte[] trxId = transactionCap.getTransactionId().getBytes(); + boolean ok = dbManager.pushTransaction(transactionCap); + Assert.assertTrue(ok); + + Thread.sleep(500); + //package transaction to block + long expectedBlockNum = chainBaseManager.getDynamicPropertiesStore() + .getLatestBlockHeaderNumber() + 1; + block = getSignedBlock(witnessCapsule.getAddress(), + nextScheduledTime(witnessCapsule.getAddress()), privateKey); + dbManager.pushBlock(new BlockCapsule(block)); + + BlockCapsule blockCapsule3 = new BlockCapsule(wallet.getNowBlock()); + Assert.assertEquals("unexpected block number", expectedBlockNum, blockCapsule3.getNum()); + + block = getSignedBlock(witnessCapsule.getAddress(), + nextScheduledTime(witnessCapsule.getAddress()), privateKey); + dbManager.pushBlock(new BlockCapsule(block)); + + // scan note by ivk + byte[] receiverIvk = incomingViewingKey.getValue(); + DecryptNotes notes1 = wallet.scanNoteByIvk(0, 100, receiverIvk); + Assert.assertEquals(2, notes1.getNoteTxsCount()); + + // scan note by ivk and mark + DecryptNotesMarked notes3 = wallet.scanAndMarkNoteByIvk(0, 100, receiverIvk, + fullViewingKey.getAk(), fullViewingKey.getNk()); + Assert.assertEquals(2, notes3.getNoteTxsCount()); + + // scan note by ovk + DecryptNotes notes2 = wallet.scanNoteByOvk(0, 100, senderOvk); + Assert.assertEquals(2, notes2.getNoteTxsCount()); + + // to spend received note above. + ZenTransactionBuilder builder2 = new ZenTransactionBuilder(wallet); + + //query merkleinfo + OutputPointInfo.Builder request = OutputPointInfo.newBuilder(); + for (int i = 0; i < notes1.getNoteTxsCount(); i++) { + OutputPoint.Builder outPointBuild = OutputPoint.newBuilder(); + outPointBuild.setHash(ByteString.copyFrom(trxId)); + outPointBuild.setIndex(i); + request.addOutPoints(outPointBuild.build()); + } + request.setBlockNum(1); + IncrementalMerkleVoucherInfo merkleVoucherInfo = wallet + .getMerkleTreeVoucherInfo(request.build()); + + //build spend proof. allow only one note in spend + ExpandedSpendingKey expsk2 = sk2.expandedSpendingKey(); + for (int i = 0; i < 1; i++) { + org.tron.api.GrpcAPI.Note grpcNote = notes1.getNoteTxs(i).getNote(); + PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(grpcNote.getPaymentAddress()); + Note note2 = new Note(paymentAddress.getD(), + paymentAddress.getPkD(), + grpcNote.getValue(), + grpcNote.getRcm().toByteArray() + ); + + IncrementalMerkleVoucherContainer voucher2 = + new IncrementalMerkleVoucherContainer( + new IncrementalMerkleVoucherCapsule(merkleVoucherInfo.getVouchers(i))); + byte[] anchor2 = voucher2.root().getContent().toByteArray(); + builder2.addSpend(expsk2, note2, anchor2, voucher2); } + + //build output proof + SpendingKey sk3 = SpendingKey.random(); + FullViewingKey fvk3 = sk3.fullViewingKey(); + IncomingViewingKey ivk3 = fvk3.inViewingKey(); + + DiversifierT d3 = DiversifierT.random(); + PaymentAddress paymentAddress3 = incomingViewingKey.address(d3).get(); + byte[] memo3 = org.tron.keystore.Wallet.generateRandomBytes(512); + builder2.addOutput(expsk2.getOvk(), paymentAddress3, + (1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2 - wallet + .getShieldedTransactionFee(), memo3); + + TransactionCapsule transactionCap2 = builder2.build(); + boolean ok2 = dbManager.pushTransaction(transactionCap2); + Assert.assertTrue(ok2); } // Returns the earliest timestamp at which witnessAddr is the DPoS-scheduled producer, diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 6a3e641d5d6..2e30496116f 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -49,25 +49,25 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -189,9 +189,9 @@ - - - + + + @@ -199,9 +199,9 @@ - - - + + + @@ -219,15 +219,15 @@ - - - + + + - - + + - - + + @@ -235,15 +235,15 @@ - - - + + + - - + + - - + + @@ -251,15 +251,15 @@ - - - + + + - - + + - - + + @@ -1171,76 +1171,76 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -1251,18 +1251,18 @@ - - - + + + - - + + - - + + - - + + @@ -1528,14 +1528,6 @@ - - - - - - - - @@ -1684,11 +1676,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1710,36 +1728,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + @@ -1792,6 +1783,11 @@ + + + + + @@ -2408,6 +2404,14 @@ + + + + + + + + @@ -2424,6 +2428,14 @@ + + + + + + + + @@ -2448,6 +2460,11 @@ + + + + + @@ -2612,20 +2629,20 @@ - - - + + + - - + + - - - + + + - - + + @@ -2647,16 +2664,26 @@ - - - - - - + + + + + + + + + + + + + + + + diff --git a/plugins/src/main/resources/logback.xml b/plugins/src/main/resources/logback.xml index fa557f1a412..3f5eff3a1e0 100644 --- a/plugins/src/main/resources/logback.xml +++ b/plugins/src/main/resources/logback.xml @@ -3,16 +3,7 @@ - - - - - %d{HH:mm:ss.SSS} %-5level [%t] [%c{1}]\(%F:%L\) %m%n - - - INFO - - +