diff --git a/.dockerignore b/.dockerignore index d171944d877..57c53d50a15 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1 @@ ./* -!docker-entrypoint.sh - diff --git a/.github/workflows/docker-check.yml b/.github/workflows/docker-check.yml new file mode 100644 index 00000000000..75291afa4bb --- /dev/null +++ b/.github/workflows/docker-check.yml @@ -0,0 +1,56 @@ +name: Docker Check + +on: + push: + branches: [ 'master', 'release_**' ] + paths: + - 'docker/docker.sh' + - 'docker/Dockerfile' + - 'docker/arm64/Dockerfile' + - '.github/workflows/docker-check.yml' + pull_request: + branches: [ 'master', 'develop', 'release_**' ] + paths: + - 'docker/docker.sh' + - 'docker/Dockerfile' + - 'docker/arm64/Dockerfile' + - '.github/workflows/docker-check.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + docker-check: + name: Docker Static Check + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v5 + + - name: Check shell syntax + run: bash -n docker/docker.sh + + - name: Run ShellCheck + run: shellcheck docker/docker.sh + + - name: Check amd64 Dockerfile + run: > + docker buildx build + --check + --platform linux/amd64 + --file docker/Dockerfile + docker + + - name: Check ARM64 Dockerfile + run: > + docker buildx build + --check + --platform linux/arm64 + --file docker/arm64/Dockerfile + docker diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index f35538c0961..c7a5a6f4160 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -197,7 +197,7 @@ jobs: debian11-x86_64-gradle- - name: Build - run: ./gradlew clean build --no-daemon --no-build-cache + run: ./gradlew clean build --no-daemon - name: Toolkit jar smoke test run: | @@ -209,7 +209,7 @@ jobs: java -jar "$JAR" keystore --help - name: Test with RocksDB engine - run: ./gradlew :framework:testWithRocksDb --no-daemon --no-build-cache + run: ./gradlew :framework:testWithRocksDb --no-daemon - name: Generate module coverage reports run: ./gradlew jacocoTestReport --no-daemon @@ -265,11 +265,11 @@ jobs: # 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 + run: ./gradlew clean build --no-daemon - name: Test with RocksDB engine (base) continue-on-error: true - run: ./gradlew :framework:testWithRocksDb --no-daemon --no-build-cache + run: ./gradlew :framework:testWithRocksDb --no-daemon - name: Generate module coverage reports (base) run: ./gradlew jacocoTestReport --no-daemon 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/docker/Dockerfile b/docker/Dockerfile index 2732f5a55ed..2f7915e7032 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,44 +1,69 @@ -FROM tronprotocol/centos7:0.2 +FROM ubuntu:24.04 +ARG VERSION="dev" +ENV NO_PROXY_CACHE="-o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0" ENV TMP_DIR="/tron-build" -ENV JDK_TAR="jdk-8u202-linux-x64.tar.gz" -ENV JDK_DIR="jdk1.8.0_202" -ENV JDK_MD5="0029351f7a946f6c05b582100c7d45b7" +ENV OPENJDK8_URL="https://api.adoptium.net/v3/binary/latest/8/ga/linux/x64/jdk/hotspot/normal/eclipse" +ENV ADOPTIUM_SIGNING_FINGERPRINT="3B04D753C9050D9A5D343F39843C48A565F8F04B" +ENV JDK_DIR="/usr/local/openjdk-8" ENV BASE_DIR="/java-tron" - -RUN set -o errexit -o nounset \ - && yum -y install git wget \ - && wget -P /usr/local https://github.com/frekele/oracle-java/releases/download/8u202-b08/$JDK_TAR \ - && echo "$JDK_MD5 /usr/local/$JDK_TAR" | md5sum -c \ - && tar -zxf /usr/local/$JDK_TAR -C /usr/local\ - && rm /usr/local/$JDK_TAR \ - && export JAVA_HOME=/usr/local/$JDK_DIR \ - && export CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar \ - && export PATH=$PATH:$JAVA_HOME/bin \ - && echo "git clone" \ - && mkdir -p $TMP_DIR \ - && cd $TMP_DIR \ - && git clone https://github.com/tronprotocol/java-tron.git \ - && cd java-tron \ - && git checkout master \ - && ./gradlew build -x test \ - && cd build/distributions \ - && 7za x -y java-tron-1.0.0.zip \ - && mv java-tron-1.0.0 $BASE_DIR \ - && rm -rf $TMP_DIR \ - && rm -rf ~/.gradle \ - && mv $JAVA_HOME/jre /usr/local \ - && rm -rf $JAVA_HOME \ - && yum clean all - -RUN wget -P $BASE_DIR/config https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/main_net_config.conf +# Update and install dependencies without using any cache +RUN apt-get update $NO_PROXY_CACHE && \ + apt-get --quiet --yes install git 7zip curl jq libtcmalloc-minimal4 gnupg dirmngr ca-certificates && \ + cd /usr/local \ + && FETCH_URL="$(curl -fsS -w "%{redirect_url}" -o /dev/null "$OPENJDK8_URL")" \ + && JDK_TAR="$(curl -fsSL -w "%{filename_effective}" -O "$FETCH_URL")" \ + && curl -fsSLo "$JDK_TAR.sig" "$FETCH_URL.sig" \ + && GNUPGHOME="$(mktemp -d)" \ + && export GNUPGHOME \ + && gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys "$ADOPTIUM_SIGNING_FINGERPRINT" \ + && gpg --batch --verify "$JDK_TAR.sig" "$JDK_TAR" \ + && rm -rf "$GNUPGHOME" "$JDK_TAR.sig" \ + && mkdir -p "$JDK_DIR" \ + && tar -zxf "$JDK_TAR" -C "$JDK_DIR" --strip-components=1 \ + && rm "$JDK_TAR" \ + && export JAVA_HOME=$JDK_DIR \ + && export CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar \ + && export PATH=$PATH:$JAVA_HOME/bin \ + && echo "git clone" \ + && mkdir -p $TMP_DIR \ + && cd $TMP_DIR \ + && git clone https://github.com/tronprotocol/java-tron.git \ + && cd java-tron \ + && git checkout master \ + && ./gradlew clean build -x test -x check --no-daemon \ + && cd build/distributions \ + && 7z x -y java-tron-1.0.0.zip \ + && cp $TMP_DIR/java-tron/framework/src/main/resources/config.conf java-tron-1.0.0/config.conf \ + && mv java-tron-1.0.0 $BASE_DIR \ + && rm -rf $TMP_DIR \ + && rm -rf ~/.gradle \ + && mv $JDK_DIR/jre /usr/local \ + && rm -rf $JDK_DIR \ + # Clean apt cache + && apt-get clean \ + && rm -rf /var/cache/apt/archives/* /var/cache/apt/archives/partial/* \ + && rm -rf /var/lib/apt/lists/* ENV JAVA_HOME="/usr/local/jre" ENV PATH=$PATH:$JAVA_HOME/bin - -COPY docker-entrypoint.sh $BASE_DIR/bin +ENV LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4" +ENV TCMALLOC_RELEASE_RATE=10 WORKDIR $BASE_DIR -ENTRYPOINT ["./bin/docker-entrypoint.sh"] +ENTRYPOINT ["./bin/FullNode"] + +# Build-time metadata as defined at http://label-schema.org +ARG BUILD_DATE +ARG VCS_REF +LABEL org.label-schema.build-date=$BUILD_DATE \ + org.label-schema.name="Java-TRON" \ + org.label-schema.description="TRON protocol" \ + org.label-schema.url="https://tron.network/" \ + org.label-schema.vcs-ref=$VCS_REF \ + org.label-schema.vcs-url="https://github.com/tronprotocol/java-tron.git" \ + org.label-schema.vendor="TRON protocol" \ + org.label-schema.version=$VERSION \ + org.label-schema.schema-version="1.0" diff --git a/docker/arm64/Dockerfile b/docker/arm64/Dockerfile index 6435faf7ead..8b4601223b6 100644 --- a/docker/arm64/Dockerfile +++ b/docker/arm64/Dockerfile @@ -1,33 +1,49 @@ -FROM arm64v8/eclipse-temurin:17 +FROM ubuntu:24.04 +ARG VERSION="dev" +ENV NO_PROXY_CACHE="-o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0" ENV TMP_DIR="/tron-build" ENV BASE_DIR="/java-tron" -RUN set -o errexit -o nounset \ - && apt-get update \ - && apt-get -y install git p7zip-full wget libtcmalloc-minimal4 \ - && echo "git clone" \ - && mkdir -p $TMP_DIR \ - && cd $TMP_DIR \ - && git clone https://github.com/tronprotocol/java-tron.git \ - && cd java-tron \ - && git checkout master \ - && ./gradlew clean build -x test -x check --no-daemon \ - && cd build/distributions \ - && 7za x -y java-tron-1.0.0.zip \ - && mv java-tron-1.0.0 $BASE_DIR \ - && rm -rf $TMP_DIR \ - && rm -rf ~/.gradle \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +# Update and install dependencies without using any cache +RUN apt-get update $NO_PROXY_CACHE \ + && apt-get --quiet --yes install git 7zip curl jq libtcmalloc-minimal4 openjdk-17-jre-headless=17* \ + && echo "git clone" \ + && mkdir -p $TMP_DIR \ + && cd $TMP_DIR \ + && git clone https://github.com/tronprotocol/java-tron.git \ + && cd java-tron \ + && git checkout master \ + && ./gradlew clean build -x test -x check --no-daemon \ + && cd build/distributions \ + && 7z x -y java-tron-1.0.0.zip \ + && cp $TMP_DIR/java-tron/framework/src/main/resources/config.conf java-tron-1.0.0/config.conf \ + && mv java-tron-1.0.0 $BASE_DIR \ + && rm -rf $TMP_DIR \ + && rm -rf ~/.gradle \ + # Clean apt cache + && apt-get clean \ + && rm -rf /var/cache/apt/archives/* /var/cache/apt/archives/partial/* \ + && rm -rf /var/lib/apt/lists/* +ENV JAVA_HOME="/usr/lib/jvm/java-17-openjdk-arm64" +ENV PATH=$PATH:$JAVA_HOME/bin ENV LD_PRELOAD="/usr/lib/aarch64-linux-gnu/libtcmalloc_minimal.so.4" ENV TCMALLOC_RELEASE_RATE=10 -RUN wget -P $BASE_DIR/config https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/main_net_config.conf - -COPY docker-entrypoint.sh $BASE_DIR/bin - WORKDIR $BASE_DIR -ENTRYPOINT ["./bin/docker-entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["./bin/FullNode"] + +# Build-time metadata as defined at http://label-schema.org +ARG BUILD_DATE +ARG VCS_REF +LABEL org.label-schema.build-date=$BUILD_DATE \ + org.label-schema.name="Java-TRON" \ + org.label-schema.description="TRON protocol" \ + org.label-schema.url="https://tron.network/" \ + org.label-schema.vcs-ref=$VCS_REF \ + org.label-schema.vcs-url="https://github.com/tronprotocol/java-tron.git" \ + org.label-schema.vendor="TRON protocol" \ + org.label-schema.version=$VERSION \ + org.label-schema.schema-version="1.0" diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh deleted file mode 100755 index d3c5d4c65c8..00000000000 --- a/docker/docker-entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -eo pipefail -shopt -s nullglob - -echo "./bin/FullNode $@" > command.txt -exec "./bin/FullNode" "$@" \ No newline at end of file diff --git a/docker/docker.md b/docker/docker.md index 79aa6b08e2d..4941139ae64 100644 --- a/docker/docker.md +++ b/docker/docker.md @@ -1,110 +1,202 @@ # Docker Shell Guide -java-tron support containerized processes, we maintain a Docker image with latest version build from our master branch on DockerHub. To simplify the use of Docker and common docker commands, we also provide a shell script to help you better manage container services,this guide describes how to use the script tool. - +java-tron supports containerized processes. Official versioned release images are published on Docker Hub. The mutable `latest` tag points to the latest published release; it does not represent the current head of `master`. The `docker.sh` helper simplifies common image and container lifecycle operations. ## Prerequisites -Requires a docker to be installed on the system. Docker version >=20.10.12. +Install Docker 20.10.12 or later before using the helper. +`docker.sh` requires Bash. On Windows, use Docker Desktop with Linux containers and run the helper from [WSL 2](https://docs.docker.com/desktop/features/wsl/) with Docker integration enabled. It cannot be executed directly from PowerShell or Command Prompt. ## Quick Start -Shell can be obtained from the java-tron project or independently, you can get the script from [here](https://github.com/tronprotocol/java-tron/blob/develop/docker/docker.sh) or download via the wget: +Obtain the helper from the java-tron repository, or download it independently: + ```shell $ wget https://raw.githubusercontent.com/tronprotocol/java-tron/develop/docker/docker.sh ``` -### Pull the mirror image -Get the `tronprotocol/java-tron` image from the DockerHub, this image contains the full JDK environment and the host network configuration file, using the script for simple docker operations. +### Pull the official image + +Get the `tronprotocol/java-tron` image from Docker Hub. The image contains a Java runtime environment and the mainnet configuration file. The helper pulls `tronprotocol/java-tron:latest`. For long-running or reproducible deployments, use Docker directly to select a versioned tag or an `image@sha256:...` reference. See the available [Docker Hub tags](https://hub.docker.com/r/tronprotocol/java-tron/tags). + ```shell -$ sh docker.sh --pull +$ bash docker.sh --pull ``` ### Run the service -Before running the java-tron service, make sure some ports on your local machine are open,the image has the following ports automatically exposed: -- `8090`: used by the HTTP based JSON API -- `50051`: used by the GRPC based API -- `18888`: TCP and UDP, used by the P2P protocol running the network + +Before running java-tron, make sure the required ports are available on the host. By default, HTTP and gRPC APIs are bound to the host loopback interface. Mainnet P2P remains available on all host interfaces: + +- `127.0.0.1:8090`: used by the HTTP-based JSON API +- `127.0.0.1:50051`: used by the gRPC-based API +- `18888`: TCP and UDP on all host interfaces, used by the P2P protocol + +The helper manages one container named `tronprotocol-java-tron` and creates it with Docker's `always` restart policy. If this container already exists, `--run` exits without changing it. Use `--start` to start a stopped container, or use `--rm` before `--run` to recreate it with new settings. The helper cannot run mainnet and private-network instances simultaneously; remove the existing container before switching networks. A manually stopped container remains stopped until it is manually restarted or the Docker daemon restarts. Use Docker directly when multiple instances, a custom container name, or a different restart policy is required. #### Full node on the main network ```shell -$ sh docker.sh --run --net main +$ bash docker.sh --run ``` -or you can use `-p` to customize the port mapping, more custom parameters, please refer to [Options](#Options) + +The helper does not provide an option for setting JVM heap parameters. Nodes started this way use the JVM options bundled in the image and the JVM's automatically selected heap size. For production mainnet deployments that require explicit heap sizing or other JVM tuning, use the direct `docker run` example in the [quick-start guide](../quickstart.md#run-a-mainnet-fullnode). + +The mainnet configuration is bundled in the image at `/java-tron/config.conf` and comes from the same java-tron revision used to build the image. `--net main` remains available as an explicit form. + +Use `-p` to customize the port mapping. Supplying any custom `-p` replaces the complete default port set, so include both TCP and UDP mappings for P2P. For more parameters, see [Options](#options). ```shell -$ sh docker.sh --run --net main -p 8080:8090 -p 40051:50051 +$ bash docker.sh --run --net main \ + -p 127.0.0.1:8080:8090 \ + -p 127.0.0.1:40051:50051 \ + -p 18888:18888 \ + -p 18888:18888/udp ``` -#### Full node on the nile test network +#### Single-node private network + +You can also run a single-node private network with the configuration maintained by `tron-deployment`. If `config/private_net_config.conf` does not exist in the current directory, the script downloads it automatically. An existing local configuration is reused so that local changes are preserved. + ```shell -$ sh docker.sh --run --net test +$ bash docker.sh --run --net private ``` -#### Full node on the private network -you can also build your own private-net and will download a configuration file from the network for your private network, which will be stored in your local `config` directory. +Private mode starts FullNode with `--witness` so that the genesis witness produces blocks. By default, the helper publishes the following ports used by `private_net_config.conf`: + +- `127.0.0.1:16667`: used by the HTTP-based JSON API +- `127.0.0.1:50051`: used by the gRPC-based API + +The private configuration also enables JSON-RPC on container port `8545` and listens for P2P on container port `16666`, but the helper publishes neither port by default. To make JSON-RPC available on the host loopback interface, provide the complete custom port set because specifying any `-p` replaces all default mappings: + +```shell +$ bash docker.sh --run --net private \ + -p 127.0.0.1:16667:16667 \ + -p 127.0.0.1:50051:50051 \ + -p 127.0.0.1:8545:8545 +``` + +The downloaded configuration contains a publicly known development witness key and genesis accounts. Use it only for isolated local development. For a multi-node, shared, or security-sensitive private network, use the maintained [`tron-docker/private_net`](https://github.com/tronprotocol/tron-docker/tree/main/private_net) setup and replace its keys and configuration as appropriate. + +To connect an intentionally configured helper-based node from another machine, provide the complete custom port set and include explicit P2P mappings such as `-p :16666:16666` and `-p :16666:16666/udp`. Before exposing P2P, replace the public development credentials and configure the peers and witness roles; publishing the ports alone does not create a multi-node private network. + +Existing containers keep their original port mappings when restarted. After upgrading from a helper version that published private P2P by default, run `bash docker.sh --rm` and then create the private node again with `bash docker.sh --run --net private`. + +To replace an existing local copy with the latest maintained configuration, explicitly request an update. This overwrites `config/private_net_config.conf`. + ```shell -$ sh docker.sh --run --net private +$ bash docker.sh --run --net private --update-config true ``` + #### Configuration -The script will automatically download and use the corresponding configuration file from the github repository according to the `--net` parameter. if you don't want to update the configuration file every time you start the service, please add a startup parameter. + +Mainnet uses the configuration bundled in the image and never downloads another configuration. The `private` network option uses `config/private_net_config.conf` from the current directory, downloading it from `tron-deployment` only when it is missing or an update is explicitly requested. It also enables witness mode so that the single-node network can produce blocks. + +Nile is intentionally not supported by this script because it may require features that are not yet available on the mainnet source revision. Follow the Nile-specific build instructions in the project README instead. + +Alternatively, mount a configuration into the container and select it with `-c`: + +```shell +$ bash docker.sh --run \ + -v /absolute/path/custom.conf:/java-tron/custom.conf:ro \ + -c /java-tron/custom.conf +``` + +### Data and log persistence + +By default, the helper bind-mounts `output-directory` from the directory where `docker.sh` is executed to `/java-tron/output-directory` in the container. The blockchain database therefore remains on the host after the container is removed. Make sure that the current filesystem has sufficient space, or mount a dedicated data directory: ```shell -$ sh docker.sh --run --update-config false +$ mkdir -p "$PWD/mainnet-data" +$ bash docker.sh --run --net main \ + -v "$PWD/mainnet-data:/java-tron/output-directory" ``` -Or use the `-c` parameter to specify your own configuration file, which will not automatically download a new configuration file from github repository. +Do not reuse one database directory across different networks. Use separate directories for mainnet and private-network data. +Application logs are not persisted by default; they remain in the container writable layer and are deleted with the container. To retain logs after `--rm`, mount a host directory explicitly: + +```shell +$ mkdir -p "$PWD/logs" +$ bash docker.sh --run --net main \ + -v "$PWD/logs:/java-tron/logs" +``` + +Adding a log or configuration volume does not disable the default database mount. The default is replaced only when a custom volume targets `/java-tron/output-directory`. ### View logs -If you want to see the logs of the java-tron service, please use the `--log` parameter + +Use `--log` to follow the java-tron service log: ```shell -$ sh docker.sh --log | grep 'PushBlock' +$ bash docker.sh --log | grep 'PushBlock' ``` + ### Stop the service -If you want to stop the container of java-tron, you can execute +Use `--stop` to stop the java-tron container: ```shell -$ sh docker.sh --stop +$ bash docker.sh --stop ``` ## Build Image -If you do not want to use the default official image, you can also compile your own local image, first you need to change some parameters in the shell script to specify your own mirror info. -`DOCKER_REPOSITORY` is your repository name -`DOCKER_IMAGES` is the image name -`DOCKER_TARGET` is the version number, here is an example: +The Dockerfiles clone the remote java-tron repository and check out `master` at build time. They do not build the Java sources in the current checkout. The resulting image can differ from the published Docker Hub `latest` image. + +The helper uses `tronprotocol/java-tron:latest` for `--pull`, `--build`, and `--run` and does not support selecting another image reference through command-line options or environment variables. After `--build`, the local `tronprotocol/java-tron:latest` tag points to the newly built `master` image, so subsequent `--run` commands use that build. Use Docker directly when a separate tag, digest, or image name is required. + +Use a complete java-tron checkout containing the matching Dockerfiles. From its `docker` directory, build the image: + +```shell +$ bash docker.sh --build +``` + +The script detects the Docker daemon architecture by default. You can also select the target architecture explicitly: ```shell -DOCKER_REPOSITORY="your_repository" -DOCKER_IMAGES="java-tron" -DOCKER_TARGET="1.0" +$ bash docker.sh --build amd64 +$ bash docker.sh --build arm64 ``` -then execute the build: +Building for an architecture different from the Docker daemon requires a builder with the corresponding emulation support. Docker Desktop provides this by default; standalone Docker Engine installations may require QEMU/binfmt configuration. + +Docker may reuse cached layers, so rebuilding does not necessarily fetch the latest remote `master`. To fetch the source again, run one of the following commands from the java-tron repository root. The helper does not accept `--no-cache`; use Docker directly: ```shell -$ sh docker.sh --build +# amd64 +docker build --no-cache --platform linux/amd64 \ + -f docker/Dockerfile -t tronprotocol/java-tron:latest docker + +# arm64 +docker build --no-cache --platform linux/arm64 \ + -f docker/arm64/Dockerfile -t tronprotocol/java-tron:latest docker ``` +These commands rerun the build steps without deleting existing build caches. + +When the script is used from a java-tron checkout, only the Dockerfile and build context are resolved relative to `docker.sh`, regardless of the current working directory. The current checkout's Java sources are not added to that context; the Dockerfiles build the remote `master` branch. + +Standalone `--build` using only a downloaded `docker.sh` is currently unavailable. The helper downloads only the architecture-specific Dockerfile from `develop`, but the current `develop` Dockerfiles also require `docker-entrypoint.sh`, which is missing from the temporary build context. Use a complete checkout containing the matching Dockerfiles until those files are synchronized to `develop` and standalone builds are verified for both architectures. + ## Options -Parameters for all functions: +### Commands + +- **`--build [amd64|arm64]`**: build `tronprotocol/java-tron:latest` from the remote `master` branch, optionally for the specified architecture +- **`--pull`**: download `tronprotocol/java-tron:latest` from Docker Hub +- **`--run`**: run `tronprotocol/java-tron:latest` +- **`--start`**: start the existing java-tron container +- **`--log`**: follow the java-tron log in the container +- **`--stop`**: stop the running container +- **`--rm`**: remove the container without removing the image -* **`--build`** building a local mirror image -* **`--pull`** download a docker mirror from **DockerHub** -* **`--run`** run the docker mirror -* **`--log`** exporting the java-tron run log on the container -* **`--stop`** stopping a running container -* **`--rm`** remove container,only deletes the container, not the image -* **`-p`** publish a container's port to the host, format:`-p hostPort:containerPort` -* **`-c`** specify other java-tron configuration file in the container -* **`-v`** bind mount a volume for the container,format: `-v host-src:container-dest`, the `host-src` is an absolute path -* **`--net`** select the network, you can join the main-net, test-net -* **`--update-config`** update configuration file, default true +### Run options +The following options apply only to `--run`: +- **`-p`**: publish a container port using `-p hostPort:containerPort[/protocol]`; custom mappings replace all defaults +- **`-c`**: specify another java-tron configuration file in the container +- **`-v`**: bind mount a volume using `-v host-src:container-dest`; `host-src` must be an absolute path +- **`--net`**: select `main` or `private`; a missing private configuration is downloaded automatically +- **`--update-config`**: set to `true` with `--net private` to replace the local private configuration diff --git a/docker/docker.sh b/docker/docker.sh index bf4961f0620..0a9f511d0b9 100644 --- a/docker/docker.sh +++ b/docker/docker.sh @@ -17,12 +17,16 @@ # ############################################################################## +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" BASE_DIR="/java-tron" DOCKER_REPOSITORY="tronprotocol" DOCKER_IMAGES="java-tron" # latest or version DOCKER_TARGET="latest" +CONTAINER_NAME="$DOCKER_REPOSITORY-$DOCKER_IMAGES" +IMAGE_REFERENCE="$DOCKER_REPOSITORY/$DOCKER_IMAGES:$DOCKER_TARGET" +HOST_API_BIND_ADDRESS="127.0.0.1" HOST_HTTP_PORT=8090 HOST_RPC_PORT=50051 HOST_LISTEN_PORT=18888 @@ -31,261 +35,422 @@ DOCKER_HTTP_PORT=8090 DOCKER_RPC_PORT=50051 DOCKER_LISTEN_PORT=18888 -VOLUME=`pwd` -CONFIG="$VOLUME/config" +PRIVATE_HTTP_PORT=16667 + +VOLUME=$(pwd) +CONFIG_DIR="$VOLUME/config" OUTPUT_DIRECTORY="$VOLUME/output-directory" -CONFIG_PATH="/java-tron/config/" -CONFIG_FILE="main_net_config.conf" -MAIN_NET_CONFIG_FILE="main_net_config.conf" -TEST_NET_CONFIG_FILE="test_net_config.conf" +BUNDLED_CONFIG_FILE="$BASE_DIR/config.conf" PRIVATE_NET_CONFIG_FILE="private_net_config.conf" +PRIVATE_NET_CONFIG_URL="https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/$PRIVATE_NET_CONFIG_FILE" -# update the configuration file, if true, the configuration file will be fetched from the network every time you start -UPDATE_CONFIG=true +LOG_FILE="$BASE_DIR/logs/tron.log" -LOG_FILE="/logs/tron.log" +JAVA_TRON_DOCKER_URL="https://raw.githubusercontent.com/tronprotocol/java-tron/develop/docker" -JAVA_TRON_REPOSITORY="https://raw.githubusercontent.com/tronprotocol/java-tron/develop/" -DOCKER_FILE="Dockerfile" -ENDPOINT_SHELL="docker-entrypoint.sh" +if ! command -v docker >/dev/null 2>&1; then + echo "docker is required but was not found" >&2 + exit 1 +fi -if test docker; then - docker -v -else - echo "warning: docker must be installed, please install docker first." - exit +if ! docker info >/dev/null 2>&1; then + echo "unable to connect to the Docker daemon" >&2 + exit 1 fi -docker_ps() { - containerID=`docker ps -a | grep "$DOCKER_REPOSITORY-$DOCKER_IMAGES" | awk '{print $1}'` - cid=$containerID +docker_container_exists() { + docker container inspect "$CONTAINER_NAME" >/dev/null 2>&1 } -docker_image() { - image_name=`docker images |grep "$DOCKER_REPOSITORY/$DOCKER_IMAGES" |awk {'print $1'}| awk 'NR==1'` - image=$image_name +docker_image_exists() { + docker image inspect "$IMAGE_REFERENCE" >/dev/null 2>&1 } -download_config() { - mkdir -p config - if test curl; then - curl -o config/$CONFIG_FILE -LO https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/$CONFIG_FILE -s - elif test wget; then - wget -P -q config/ https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/$CONFIG_FILE +download_file() { + local source_url=$1 + local destination=$2 + local failure_message=$3 + local missing_tool_message=$4 + local empty_file_message=$5 + local error_fd=$6 + local -a download_command + + if command -v curl >/dev/null 2>&1; then + download_command=(curl --fail --silent --show-error --location + --output "$destination" "$source_url") + elif command -v wget >/dev/null 2>&1; then + download_command=(wget --quiet --output-document="$destination" "$source_url") + else + echo "$missing_tool_message" >&"$error_fd" + return 1 + fi + + if ! "${download_command[@]}"; then + echo "$failure_message" >&"$error_fd" + return 1 + fi + + if [[ ! -s "$destination" ]]; then + echo "$empty_file_message" >&"$error_fd" + return 1 fi } +download_private_config() { + local config_file=$1 + local temp_file -check_download_config() { - if [[ ! -d 'config' || ! -f "config/$CONFIG_FILE" ]]; then - mkdir -p config - if test curl; then - curl -o config/$CONFIG_FILE -LO https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/$CONFIG_FILE -s - elif test wget; then - wget -P -q config/ https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/$CONFIG_FILE - fi + if ! mkdir -p "$CONFIG_DIR"; then + echo "run: failed to create configuration directory: $CONFIG_DIR" >&2 + return 1 + fi + + if ! temp_file=$(mktemp "$CONFIG_DIR/.private_net_config.conf.XXXXXX"); then + echo "run: failed to create a temporary configuration file" >&2 + return 1 + fi + + if ! download_file "$PRIVATE_NET_CONFIG_URL" "$temp_file" \ + "run: failed to download private network configuration" \ + "run: curl or wget is required to download the private network configuration" \ + "run: downloaded private network configuration is empty" 2; then + rm -f "$temp_file" + return 1 + fi + + if ! chmod 644 "$temp_file" || ! mv -f "$temp_file" "$config_file"; then + rm -f "$temp_file" + echo "run: failed to save private network configuration: $config_file" >&2 + return 1 + fi + + echo "private network configuration saved to $config_file" +} + +require_run_option_value() { + local option=$1 + + if [[ $# -lt 2 || -z "$2" ]]; then + echo "run: $option requires a value" >&2 + return 1 + fi +} + +require_no_args() { + local command=$1 + shift + + if [[ $# -gt 0 ]]; then + echo "$command: does not accept arguments: $*" >&2 + return 1 fi } +is_output_volume() { + local destination=$1 + local segment + local normalized="" + + # Docker accepts destination, source:destination, or source:destination:options. + if [[ "$destination" == *:* ]]; then + destination=${destination#*:} + destination=${destination%%:*} + fi + [[ "$destination" == /* ]] || return 1 + + # Normalize the container path without resolving it on the host filesystem. + destination="$destination/" + while [[ -n "$destination" ]]; do + segment=${destination%%/*} + destination=${destination#*/} + case "$segment" in + "" | .) ;; + ..) normalized=${normalized%/*} ;; + *) normalized="$normalized/$segment" ;; + esac + done + + [[ "$normalized" == "/java-tron/output-directory" ]] +} + run() { - docker_image + local -a volume_args=() + local -a port_args=() + local -a tron_args=() + local network="main" + local network_config="" + local update_config=false + local has_output_volume=false + + while [[ $# -gt 0 ]]; do + case "$1" in + -v) + require_run_option_value "$@" || return 1 + volume_args+=(-v "$2") + if is_output_volume "$2"; then + has_output_volume=true + fi + shift 2 + ;; + -p) + require_run_option_value "$@" || return 1 + port_args+=(-p "$2") + shift 2 + ;; + -c) + require_run_option_value "$@" || return 1 + tron_args+=(-c "$2") + shift 2 + ;; + --net) + require_run_option_value "$@" || return 1 + network=$2 + shift 2 + ;; + --update-config) + require_run_option_value "$@" || return 1 + if [[ "$2" != "true" && "$2" != "false" ]]; then + echo "run: --update-config expects true or false" >&2 + return 1 + fi + update_config=$2 + shift 2 + ;; + *) + echo "run: arg $1 is not a valid parameter" >&2 + return 1 + ;; + esac + done + + if [[ "$network" = "private" ]]; then + network_config="$CONFIG_DIR/$PRIVATE_NET_CONFIG_FILE" + elif [[ "$network" != "main" ]]; then + echo "run: unsupported network '$network'; expected main or private" >&2 + return 1 + fi - if [ ! $image ] ; then + if [[ ${#tron_args[@]} -gt 0 && -n "$network_config" ]]; then + echo "run: -c cannot be combined with --net private" >&2 + return 1 + fi + + if [[ "$update_config" = true && "$network" != "private" ]]; then + echo "run: --update-config true is only supported with --net private" >&2 + return 1 + fi + + if docker_container_exists; then + echo "container already exists: $CONTAINER_NAME" >&2 + echo "use --start if it is stopped, or --rm before rerunning --run" >&2 + return 1 + fi + + if ! docker_image_exists; then echo 'warning: no java-tron mirror image, do you need to get the mirror image?[y/n]' - read need + IFS= read -r need if [[ $need == 'y' || $need == 'yes' ]]; then - pull + pull || return $? else echo "warning: no mirror image found, go ahead and download a mirror." - exit + return 1 fi fi - volume="" - parameter="" - tron_parameter="" - if [ $# -gt 0 ]; then - while [ -n "$1" ]; do - case "$1" in - -v) - volume="$volume -v $2" - shift 2 - ;; - -p) - parameter="$parameter -p $2" - shift 2 - ;; - -c) - tron_parameter="$tron_parameter -c $2" - UPDATE_CONFIG=false - shift 2 - ;; - --net) - if [[ "$2" = "main" ]]; then - CONFIG_FILE=$MAIN_NET_CONFIG_FILE - elif [[ "$2" = "test" ]]; then - CONFIG_FILE=$TEST_NET_CONFIG_FILE - elif [[ "$2" = "private" ]]; then - CONFIG_FILE=$PRIVATE_NET_CONFIG_FILE - fi - shift 2 - ;; - --update-config) - UPDATE_CONFIG=$2 - shift 2 - ;; - *) - echo "run: arg $1 is not a valid parameter" - exit - ;; - esac - done - if [ $UPDATE_CONFIG = true ]; then - download_config + if [[ -n "$network_config" ]]; then + if [[ "$update_config" = true ]]; then + echo "updating private network configuration from tron-deployment" + download_private_config "$network_config" || return 1 + elif [[ ! -f "$network_config" ]]; then + echo "private network configuration not found; downloading it from tron-deployment" + download_private_config "$network_config" || return 1 fi + volume_args+=(-v "$network_config:$BUNDLED_CONFIG_FILE:ro") + fi - if [ -z "$volume" ]; then - volume=" -v $CONFIG:/java-tron/config -v $OUTPUT_DIRECTORY:/java-tron/output-directory" - fi + if [[ "$has_output_volume" = false ]]; then + volume_args=(-v "$OUTPUT_DIRECTORY:/java-tron/output-directory" "${volume_args[@]}") + fi - if [ -z "$parameter" ]; then - parameter=" -p $HOST_HTTP_PORT:$DOCKER_HTTP_PORT -p $HOST_RPC_PORT:$DOCKER_RPC_PORT -p $HOST_LISTEN_PORT:$DOCKER_LISTEN_PORT" + if [[ ${#port_args[@]} -eq 0 ]]; then + if [[ "$network" = "private" ]]; then + port_args=( + -p "$HOST_API_BIND_ADDRESS:$PRIVATE_HTTP_PORT:$PRIVATE_HTTP_PORT" + -p "$HOST_API_BIND_ADDRESS:$HOST_RPC_PORT:$DOCKER_RPC_PORT" + ) + else + port_args=( + -p "$HOST_API_BIND_ADDRESS:$HOST_HTTP_PORT:$DOCKER_HTTP_PORT" + -p "$HOST_API_BIND_ADDRESS:$HOST_RPC_PORT:$DOCKER_RPC_PORT" + -p "$HOST_LISTEN_PORT:$DOCKER_LISTEN_PORT" + -p "$HOST_LISTEN_PORT:$DOCKER_LISTEN_PORT/udp" + ) fi + fi - if [ -z "$tron_parameter" ]; then - tron_parameter=" -c $CONFIG_PATH$CONFIG_FILE" - fi + if [[ ${#tron_args[@]} -eq 0 ]]; then + tron_args=(-c "$BUNDLED_CONFIG_FILE") + fi - # Using custom parameters - docker run -d -it --name "$DOCKER_REPOSITORY-$DOCKER_IMAGES" \ - $volume \ - $parameter \ - --restart always \ - "$DOCKER_REPOSITORY/$DOCKER_IMAGES:$DOCKER_TARGET" \ - $tron_parameter - else - if [ $UPDATE_CONFIG = true ]; then - download_config - fi - # Default parameters - docker run -d -it --name "$DOCKER_REPOSITORY-$DOCKER_IMAGES" \ - -v $CONFIG:/java-tron/config \ - -v $OUTPUT_DIRECTORY:/java-tron/output-directory \ - -p $HOST_HTTP_PORT:$DOCKER_HTTP_PORT \ - -p $HOST_RPC_PORT:$DOCKER_RPC_PORT \ - -p $HOST_LISTEN_PORT:$DOCKER_LISTEN_PORT \ - --restart always \ - "$DOCKER_REPOSITORY/$DOCKER_IMAGES:$DOCKER_TARGET" \ - -c "$CONFIG_PATH$CONFIG_FILE" + if [[ "$network" = "private" ]]; then + tron_args+=(--witness) fi + + docker run -d --name "$CONTAINER_NAME" \ + "${volume_args[@]}" \ + "${port_args[@]}" \ + --restart always \ + "$IMAGE_REFERENCE" \ + "${tron_args[@]}" } build() { - echo 'docker build' - if [ ! -f "Dockerfile" ]; then - echo 'warning: Dockerfile not exists.' - if test curl; then - DOWNLOAD_CMD="curl -LJO " - elif test wget; then - DOWNLOAD_CMD="wget " - else - echo "Dockerfile cannot be downloaded, you need to install 'curl' or 'wget'!" - exit + local arch="${1:-}" + local platform + local dockerfile_path + local dockerfile_source + local build_context="$SCRIPT_DIR" + local temporary_context="" + local build_status + + if [[ $# -gt 1 ]]; then + echo "build: expected at most one architecture argument" >&2 + return 1 + fi + + if [[ -z "$arch" ]]; then + if ! arch=$(docker info --format '{{.Architecture}}'); then + echo "build: failed to determine the Docker daemon architecture" >&2 + return 1 + fi + fi + + case "$arch" in + amd64 | x86_64) + platform="linux/amd64" + dockerfile_path="$SCRIPT_DIR/Dockerfile" + dockerfile_source="$JAVA_TRON_DOCKER_URL/Dockerfile" + ;; + arm64 | aarch64) + platform="linux/arm64" + dockerfile_path="$SCRIPT_DIR/arm64/Dockerfile" + dockerfile_source="$JAVA_TRON_DOCKER_URL/arm64/Dockerfile" + ;; + *) + echo "build: unsupported architecture: $arch" >&2 + return 1 + ;; + esac + + if [[ ! -f "$dockerfile_path" ]]; then + if ! temporary_context=$(mktemp -d "${TMPDIR:-/tmp}/java-tron-docker.XXXXXX"); then + echo "build: failed to create a temporary build context" >&2 + return 1 + fi + + build_context="$temporary_context" + dockerfile_path="$temporary_context/Dockerfile" + + echo "build files not found next to docker.sh; downloading a temporary build context" + if ! download_file "$dockerfile_source" "$dockerfile_path" \ + "build: failed to download: $dockerfile_source" \ + "build: curl or wget is required to download build files" \ + "build: downloaded file is empty: $dockerfile_source" 2; then + rm -rf "$temporary_context" + return 1 fi - # download Dockerfile - `$DOWNLOAD_CMD "$JAVA_TRON_REPOSITORY$DOCKER_FILE"` - `$DOWNLOAD_CMD "$JAVA_TRON_REPOSITORY$ENDPOINT_SHELL"` - chmod u+rwx $ENDPOINT_SHELL fi - docker build -t "$DOCKER_REPOSITORY/$DOCKER_IMAGES:$DOCKER_TARGET" . + + echo "docker build --platform $platform --file $dockerfile_path" + docker build \ + --platform "$platform" \ + --file "$dockerfile_path" \ + --tag "$IMAGE_REFERENCE" \ + "$build_context" + build_status=$? + + if [[ -n "$temporary_context" ]]; then + rm -rf "$temporary_context" + fi + + return "$build_status" } pull() { - echo "docker pull $DOCKER_REPOSITORY/$DOCKER_IMAGES:$DOCKER_TARGET" - docker pull "$DOCKER_REPOSITORY/$DOCKER_IMAGES:$DOCKER_TARGET" + require_no_args pull "$@" || return 1 + + echo "docker pull $IMAGE_REFERENCE" + docker pull "$IMAGE_REFERENCE" } -start() { - docker_ps - if [ $cid ]; then - echo "containerID: $cid" - echo "docker stop $cid" - docker start $cid - docker ps - else - echo "container not running!" +change_container_state() { + local command_name=$1 + shift + + require_no_args "$command_name" "$@" || return 1 + if ! docker_container_exists; then + echo "container not found: $CONTAINER_NAME" >&2 + return 1 fi + + echo "container: $CONTAINER_NAME" + echo "docker $command_name $CONTAINER_NAME" + docker "$command_name" "$CONTAINER_NAME" || return $? + docker ps +} + +start() { + change_container_state start "$@" } stop() { - docker_ps - if [ $cid ]; then - echo "containerID: $cid" - echo "docker stop $cid" - docker stop $cid - docker ps - else - echo "container not running!" - fi + change_container_state stop "$@" } rm_container() { - stop - if [ $cid ]; then - echo "containerID: $cid" - echo "docker rm $cid" - docker rm $cid - docker_ps - else - echo "image not exists!" + require_no_args rm "$@" || return 1 + + if ! docker_container_exists; then + echo "container not found: $CONTAINER_NAME" >&2 + return 1 fi + + echo "container: $CONTAINER_NAME" + echo "docker stop $CONTAINER_NAME" + docker stop "$CONTAINER_NAME" || return $? + echo "docker rm $CONTAINER_NAME" + docker rm "$CONTAINER_NAME" } log() { - docker_ps + require_no_args log "$@" || return 1 - if [ $cid ]; then - echo "containerID: $cid" - docker exec -it $cid tail -100f $BASE_DIR/$LOG_FILE + if docker_container_exists; then + echo "container: $CONTAINER_NAME" + docker exec "$CONTAINER_NAME" tail -100f "$LOG_FILE" else - echo "container not exists!" + echo "container not found: $CONTAINER_NAME" >&2 + return 1 fi - } -case "$1" in - --pull) - pull ${@: 2} - exit - ;; - --start) - start ${@: 2} - exit - ;; - --stop) - stop ${@: 2} - exit - ;; - --build) - build ${@: 2} - exit - ;; - --run) - run ${@: 2} - exit - ;; - --rm) - rm_container ${@: 2} - exit - ;; - --log) - log ${@: 2} - exit - ;; +command_name=${1:-} +[[ $# -eq 0 ]] || shift + +case "$command_name" in + --pull) pull "$@" ;; + --start) start "$@" ;; + --stop) stop "$@" ;; + --build) build "$@" ;; + --run) run "$@" ;; + --rm) rm_container "$@" ;; + --log) log "$@" ;; *) - echo "arg: $1 is not a valid parameter" - exit + echo "arg: $command_name is not a valid parameter" >&2 + exit 1 ;; esac + +exit $? 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 - - + diff --git a/quickstart.md b/quickstart.md index b3eeb7b7713..cf30722ee22 100644 --- a/quickstart.md +++ b/quickstart.md @@ -2,222 +2,155 @@ ## Introduction -This guide provides two ways for TRON quickstart: -- Set up a FullNode using the official tools: providing a wealth of configurable parameters to startup a FullNode -- Set up a complete private network for Tron development using a third-party tool: [docker-tron-quickstart](https://github.com/TRON-US/docker-tron-quickstart) +This guide covers three common ways to get started with TRON: + +- Run a mainnet FullNode with the official java-tron Docker image. +- Start an isolated local development chain with [TRON Runtime Environment (TRE)](https://hub.docker.com/r/tronbox/tre). +- Deploy a multi-node private network with the official [tron-docker](https://github.com/tronprotocol/tron-docker/tree/main/private_net) configuration. ## Dependencies -### Docker +Install the latest Docker release for your platform: -Please download and install the latest Docker from Docker official website: -* Docker Installation for [Mac](https://docs.docker.com/docker-for-mac/install/) -* Docker Installation for [Windows](https://docs.docker.com/docker-for-windows/install/) +- [macOS](https://docs.docker.com/desktop/setup/install/mac-install/) +- [Windows](https://docs.docker.com/desktop/setup/install/windows-install/) +- [Linux](https://docs.docker.com/engine/install/) -## Quickstart based on official tools +All commands in this guide use POSIX shell syntax. On Windows, use Docker Desktop with Linux containers and run the commands from [WSL 2](https://docs.docker.com/desktop/features/wsl/) with Docker integration enabled. The examples are not intended for native PowerShell or Command Prompt. -### Build the docker image from source +## Run a mainnet FullNode -#### Clone the java-tron repo +Pull the official image from Docker Hub: -Clone the java-tron repo from github and enter the directory `java-tron`: +```shell +docker pull tronprotocol/java-tron:latest ``` -git clone https://github.com/tronprotocol/java-tron.git -cd java-tron + +Create host directories for the blockchain database and application logs: + +```shell +mkdir -p output-directory logs ``` -#### Build the docker image +Set JVM memory options for the architecture used by the Docker image. Run one of the following commands: -Use the command below to navigate to the docker directory and start the build: +```shell +# amd64 / JDK 8 +JAVA_TRON_JVM_OPTIONS="-Xms9G -Xmx12G -XX:MaxDirectMemorySize=1G" ``` -cd docker -docker build -t tronprotocol/java-tron . + +```shell +# ARM64 / JDK 17 +JAVA_TRON_JVM_OPTIONS="-Xmx9G -XX:MaxDirectMemorySize=1G" ``` -#### Using the official Docker images +These baseline values follow the official guidance for a host with 16 GB of memory. For hosts with 32 GB or more, size the heap using the official [JVM tuning guide][jvm-guide] and leave sufficient memory for direct buffers, native allocations, the operating system, and the database page cache. -Download the official docker image from the Dockerhub with below command if you'd like to use the official images: -``` -docker pull tronprotocol/java-tron +Start the FullNode with the mainnet configuration bundled in the image: + +```shell +docker run -d \ + --name java-tron \ + --restart unless-stopped \ + -v "$(pwd)/output-directory:/java-tron/output-directory" \ + -v "$(pwd)/logs:/java-tron/logs" \ + -p 127.0.0.1:8090:8090 \ + -p 127.0.0.1:50051:50051 \ + -p 18888:18888 \ + -p 18888:18888/udp \ + tronprotocol/java-tron:latest \ + -jvm "{$JAVA_TRON_JVM_OPTIONS}" \ + -c /java-tron/config.conf ``` -### Run the container +The HTTP and gRPC APIs are bound to localhost by default, while the TCP and UDP P2P ports are available to the network. Change the API bindings only when remote access is required, and protect them with appropriate network controls. Pin a versioned image tag or digest for long-running or reproducible deployments. The image also loads architecture-specific GC options from `bin/java-tron.vmoptions`; do not copy JDK 8 GC options to an ARM64/JDK 17 deployment. -You can run the command below to start the java-tron: +View the FullNode log: + +```shell +docker exec java-tron tail -100f /java-tron/logs/tron.log ``` -docker run -it -d -p 8090:8090 -p 18888:18888 -p 50051:50051 --restart always tronprotocol/java-tron + +Stop the container: + +```shell +docker stop java-tron ``` -The `-p` flag defines the ports that the container needs to be mapped on the host machine. By default the container will start and join in the mainnet -using the built-in configuration file, you can specify other configuration file by mounting a directory and using the flag `-c`. -This image also supports customizing some startup parameters,here is an example for running a FullNode as an SR in production env: +Restart the stopped container: + +```shell +docker start java-tron ``` -docker run -it -d -p 8080:8080 -p 8090:8090 -p 18888:18888 -p 50051:50051 \ - -v /Users/quan/tron/docker/conf:/java-tron/conf \ - -v /Users/quan/tron/docker/datadir:/java-tron/data \ - tronprotocol/java-tron \ - -jvm "{-Xmx10g -Xms10g}" \ - -c /java-tron/conf/config-localtest.conf \ - -d /java-tron/data \ - -w + +To recreate the container with a different image or configuration, remove the stopped container first: + +```shell +docker rm java-tron ``` -Note: The directory `/Users/tron/docker/conf` must contain the file `config-localtest.conf`. The jvm parameters must be enclosed in double quotes and braces. -## Quickstart for using docker-tron-quickstart +The bind-mounted database and logs remain on the host after the container is removed. -The image exposes a Full Node and Event Server. Through TRON Quickstart, users can deploy DApps, smart contracts, and interact with the TronWeb library. +The optional `docker.sh` helper provides shorter commands for image builds, private network configuration, common port mappings, and lifecycle operations. See the [Docker Shell Guide](docker/docker.md) for details. -> Note: `docker-tron-quickstart` is a community-maintained tool. Check its repository for the latest status: [Quickstart](https://github.com/TRON-US/docker-tron-quickstart) +### Mainnet and SR requirements -### Node.JS Console - Node.JS is used to interact with the Full and Solidity Nodes via Tron-Web. - [Node.JS](https://nodejs.org/en/) Console Download - -### Clone TRON Quickstart -```shell -git clone https://github.com/TRON-US/docker-tron-quickstart.git -``` +A Mainnet FullNode requires production-grade CPU, memory, SSD capacity, and network bandwidth. The current official deployment requirements are: -### Pull the image using docker: -```shell -docker pull trontools/quickstart -``` +| Deployment | CPU | Memory | High-performance SSD | Network bandwidth | +| --- | ---: | ---: | ---: | ---: | +| Minimum FullNode | 8 cores | 16 GB | 3 TB | 100 Mbps | +| Recommended FullNode | 16 cores | 32 GB | 3.5 TB or more | 100 Mbps | +| Block-producing SR | 32 cores | 64 GB | 3.5 TB or more | 100 Mbps | + +The example above stores the database under `$(pwd)/output-directory`. Before starting it, ensure that the current filesystem has sufficient high-performance SSD capacity, or replace the host side of the volume mapping with a dedicated data disk. + +A new node otherwise synchronizes the full chain; use a compatible [data snapshot][snapshot-guide] to reduce the initial synchronization time. [Lite FullNode][lite-guide] deployments have different storage requirements and require the corresponding Lite data and configuration. + +Review the official [java-tron deployment guide][deployment-guide] and [JVM tuning guide][jvm-guide] before choosing JVM values, storage layout, snapshots, monitoring, and upgrade procedures. + +Do not convert the quick-start container into a production Super Representative merely by adding `--witness`. An SR requires stronger hardware, protected block-signing keys, an SR-specific configuration, monitoring, backup, and operational failover. Follow the official [block-production deployment guide][block-production-guide] and review the [Super Representative requirements][sr-guide] before enabling block production. + +[deployment-guide]: https://tronprotocol.github.io/documentation-en/using_javatron/installing_javatron/ +[jvm-guide]: https://tronprotocol.github.io/documentation-en/using_javatron/installing_javatron/#jvm-parameter-optimization-for-mainnet-fullnode-deployment +[snapshot-guide]: https://tronprotocol.github.io/documentation-en/using_javatron/installing_javatron/#speeding-up-node-data-synchronization +[lite-guide]: https://tronprotocol.github.io/documentation-en/using_javatron/litefullnode/ +[block-production-guide]: https://tronprotocol.github.io/documentation-en/using_javatron/installing_javatron/#starting-a-block-production-node +[sr-guide]: https://tronprotocol.github.io/documentation-en/mechanism-algorithm/sr/ + +## Start a local development chain with TRE + +[TRE](https://hub.docker.com/r/tronbox/tre) is the maintained successor for local smart-contract and DApp development. It provides a single-container development chain with funded test accounts, automatic block production, and commonly used HTTP and event APIs on port `9090`. + +Pull and run the current stable image: -## Setup TRON Quickstart -### TRON Quickstart Run -Run the "docker run" command to launch TRON Quickstart. TRON Quickstart exposes port 9090 for Full Node and Event Server. ```shell -docker run -it \ - -p 9090:9090 \ - --rm \ +docker pull tronbox/tre +docker run --rm \ --name tron \ - trontools/quickstart -``` -Notice: the option --rm automatically removes the container after it exits. This is very important because the container cannot be restarted, it MUST be run from scratch to correctly configure the environment. - -### Testing - -If everything goes well, your terminal console output will look like following : -

- -Run Console Output - - - [PM2] Spawning PM2 daemon with pm2_home=/root/.pm2 - [PM2] PM2 Successfully daemonized - [PM2][WARN] Applications eventron not running, starting... - [PM2] App [eventron] launched (1 instances) - ┌──────────┬────┬─────────┬──────┬─────┬────────┬─────────┬────────┬─────┬───────────┬──────┬──────────┐ - │ App name │ id │ version │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ - ├──────────┼────┼─────────┼──────┼─────┼────────┼─────────┼────────┼─────┼───────────┼──────┼──────────┤ - │ eventron │ 0 │ N/A │ fork │ 60 │ online │ 0 │ 0s │ 0% │ 25.4 MB │ root │ disabled │ - └──────────┴────┴─────────┴──────┴─────┴────────┴─────────┴────────┴─────┴───────────┴──────┴──────────┘ - Use `pm2 show ` to get more details about an app - Start the http proxy for dApps... - [HPM] Proxy created: / -> http://127.0.0.1:18191 - [HPM] Proxy created: / -> http://127.0.0.1:18190 - [HPM] Proxy created: / -> http://127.0.0.1:8060 - - Tron Quickstart listening on http://127.0.0.1:9090 - - - - ADMIN /admin/accounts-generation - Sleeping for 1 second...Waiting when nodes are ready to generate 10 accounts... - (1) Waiting for sync... - Slept. - ... - Loading the accounts and waiting for the node to mine the transactions... - (1) Waiting for receipts... - Sending 10000 TRX to TSjfWSWcKCrJ1DbgMZSCbSqNK8DsEfqM9p - Sending 10000 TRX to THpWnj3dBQ5FrqW1KMVXXYSbHPtcBKeUJY - Sending 10000 TRX to TWFTHaKdeHWi3oPoaBokyZFfA7q1iiiAAb - Sending 10000 TRX to TFDGQo6f6dm9ikoV4Rc9NyTxMD5NNiSFJD - Sending 10000 TRX to TDZZNigWitFp5aE6j2j8YcycF7DVjtogBu - Sending 10000 TRX to TT8NRMcwdS9P3X9pvPC8JWi3x2zjwxZuhs - Sending 10000 TRX to TBBJw6Bk7w2NSZeqmzfUPnsn6CwDJAXTv8 - Sending 10000 TRX to TVcgSLpT97mvoiyv5ChyhQ6hWbjYLWdCVB - Sending 10000 TRX to TYjQd4xrLZQGYMdLJqsTCuXVGapPqUp9ZX - Sending 10000 TRX to THCw6hPZpFcLCWDcsZg3W77rXZ9rJQPncD - Sleeping for 3 seconds... Slept. - (2) Waiting for receipts... - Sleeping for 3 seconds... Slept. - (3) Waiting for receipts... - Sleeping for 3 seconds... Slept. - (4) Waiting for receipts... - Sleeping for 3 seconds... Slept. - (5) Waiting for receipts... - Sleeping for 3 seconds... Slept. - (6) Waiting for receipts... - Sleeping for 3 seconds... Slept. - (7) Waiting for receipts... - Done. - - Available Accounts - ================== - - (0) TSjfWSWcKCrJ1DbgMZSCbSqNK8DsEfqM9p (10000 TRX) - (1) THpWnj3dBQ5FrqW1KMVXXYSbHPtcBKeUJY (10000 TRX) - (2) TWFTHaKdeHWi3oPoaBokyZFfA7q1iiiAAb (10000 TRX) - (3) TFDGQo6f6dm9ikoV4Rc9NyTxMD5NNiSFJD (10000 TRX) - (4) TDZZNigWitFp5aE6j2j8YcycF7DVjtogBu (10000 TRX) - (5) TT8NRMcwdS9P3X9pvPC8JWi3x2zjwxZuhs (10000 TRX) - (6) TBBJw6Bk7w2NSZeqmzfUPnsn6CwDJAXTv8 (10000 TRX) - (7) TVcgSLpT97mvoiyv5ChyhQ6hWbjYLWdCVB (10000 TRX) - (8) TYjQd4xrLZQGYMdLJqsTCuXVGapPqUp9ZX (10000 TRX) - (9) THCw6hPZpFcLCWDcsZg3W77rXZ9rJQPncD (10000 TRX) - -
- - -### web browser ### -1. open your web browser -2. enter : http://127.0.0.1:9090/ -3. there will be a response JSON data: - -``` - {"Welcome to":"TronGrid v2.2.8"} + -p 127.0.0.1:9090:9090 \ + -e useDefaultPrivateKey=true \ + tronbox/tre ``` -## Docker Commands -Here are some useful docker commands, which will help you manage the TRON Quickstart Docker container on your machine. +The container runs in the foreground. In another terminal, check that the HTTP service is running: -**To list all active containers on your machine, run:** -```shell -docker container ps -``` -**Output:** ```shell -docker container ps +curl -fsS http://127.0.0.1:9090/healthcheck +``` -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -513078dc7816 tron "./quickstart v2.0.0" About an hour ago Up About an hour 0.0.0.0:9090->9090/tcp, 0.0.0.0:18190->18190/tcp tron -``` -**To kill an active container, run:** -```shell -docker container kill 513078dc7816 // use your container ID -``` +Account generation and funding continue asynchronously after the HTTP service starts. Before running tests that depend on funded accounts, wait for `/admin/accounts` to report them as available: -### How to check the logs of the FullNode ### -``` - docker exec -it tron tail -f /tron/FullNode/logs/tron.log +```shell +until curl -fsS http://127.0.0.1:9090/admin/accounts | grep -q 'Available Accounts'; do + sleep 1 +done ``` -
- -Output: something like following - - ``` - number=204 - parentId=00000000000000cb0985978b3c780e4219dc51e4329beecabe7b71f99d269985 - witness address=41928c9af0651632157ef27a2cf17ca72c575a4d21 - generated by myself=true - generate time=2019-12-09 18:33:33.0 - txs are empty - ] - 18:33:33.008 INFO [Thread-5] [DB](Manager.java:1095) pushBlock block number:204, cost/txs:1/0 - 18:33:33.008 INFO [Thread-5] [witness](WitnessService.java:283) Produce block successfully, blockNumber:204, abSlot[525305471], blockId:00000000000000ccc37f1f5c2ceb574d14c490e3d0b86909855646f9384ba666, transactionSize:0, blockTime:2019-12-09T18:33:33.000Z, parentBlockId:00000000000000cb0985978b3c780e4219dc51e4329beecabe7b71f99d269985 - 18:33:33.008 INFO [Thread-5] [net](AdvService.java:156) Ready to broadcast block Num:204,ID:00000000000000ccc37f1f5c2ceb574d14c490e3d0b86909855646f9384ba666 - ........ etc - ``` -
+The default image tag follows the current stable release. Pin a versioned tag or image digest in CI when reproducible builds are required. See the [TronBox documentation](https://tronbox.io/docs/quickstart) for contract development and deployment workflows. + +> **Warning:** TRE is intended only for isolated development and testing. The default private key, funded accounts, and administrative APIs are not secure. Keep port `9090` bound to localhost and never expose this environment to production or an untrusted network. + +## Deploy a multi-node private network + +For multi-node private network deployment, follow the official [tron-docker private network guide](https://github.com/tronprotocol/tron-docker/tree/main/private_net).