diff --git a/actuator/src/main/java/org/tron/core/actuator/AssetIssueActuator.java b/actuator/src/main/java/org/tron/core/actuator/AssetIssueActuator.java index 618a9fb191e..59fa6e0aaa9 100644 --- a/actuator/src/main/java/org/tron/core/actuator/AssetIssueActuator.java +++ b/actuator/src/main/java/org/tron/core/actuator/AssetIssueActuator.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.tron.common.math.StrictMathWrapper; @@ -166,7 +167,7 @@ public boolean validate() throws ContractValidateException { } if (dynamicStore.getAllowSameTokenName() != 0) { - String name = assetIssueContract.getName().toStringUtf8().toLowerCase(); + String name = assetIssueContract.getName().toStringUtf8().toLowerCase(Locale.ROOT); if (("trx").equals(name)) { throw new ContractValidateException("assetName can't be trx"); } diff --git a/build.gradle b/build.gradle index 12a0622db99..e71dcc056cc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,16 @@ import org.gradle.nativeplatform.platform.internal.Architectures import org.gradle.internal.os.OperatingSystem + +plugins { + id 'net.ltgt.errorprone' version '5.0.0' apply false +} + allprojects { version = "1.0.0" apply plugin: "java-library" ext { springVersion = "5.3.39" + errorproneVersion = "2.42.0" } } def arch = System.getProperty("os.arch").toLowerCase() @@ -107,6 +113,26 @@ subprojects { testImplementation "org.mockito:mockito-core:4.11.0" testImplementation "org.mockito:mockito-inline:4.11.0" } + if (project.name != 'protocol' && project.name != 'errorprone' + && javaVersion.isJava11Compatible()) { + apply plugin: 'net.ltgt.errorprone' + dependencies { + errorprone "com.google.errorprone:error_prone_core:${errorproneVersion}" + errorprone rootProject.project(':errorprone') + } + tasks.withType(JavaCompile).configureEach { + options.errorprone { + enabled = true + disableWarningsInGeneratedCode = true + disableAllChecks = true + excludedPaths = '.*/generated/.*' + errorproneArgs.addAll([ + '-Xep:StringCaseLocaleUsage:ERROR', + '-Xep:StringCaseLocaleUsageMethodRef:ERROR', + ]) + } + } + } task sourcesJar(type: Jar, dependsOn: classes) { classifier = "sources" diff --git a/chainbase/src/main/java/org/tron/core/db/TronDatabase.java b/chainbase/src/main/java/org/tron/core/db/TronDatabase.java index 40762568c82..17b75f66a61 100644 --- a/chainbase/src/main/java/org/tron/core/db/TronDatabase.java +++ b/chainbase/src/main/java/org/tron/core/db/TronDatabase.java @@ -3,6 +3,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import java.nio.file.Paths; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import javax.annotation.PostConstruct; @@ -37,10 +38,10 @@ protected TronDatabase(String dbName) { this.dbName = dbName; if ("LEVELDB".equals(CommonParameter.getInstance().getStorage() - .getDbEngine().toUpperCase())) { + .getDbEngine().toUpperCase(Locale.ROOT))) { dbSource = new LevelDbDataSourceImpl(StorageUtils.getOutputDirectoryByDbName(dbName), dbName); } else if ("ROCKSDB".equals(CommonParameter.getInstance() - .getStorage().getDbEngine().toUpperCase())) { + .getStorage().getDbEngine().toUpperCase(Locale.ROOT))) { String parentName = Paths.get(StorageUtils.getOutputDirectoryByDbName(dbName), CommonParameter.getInstance().getStorage().getDbDirectory()).toString(); dbSource = new RocksDbDataSourceImpl(parentName, dbName); diff --git a/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java b/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java index 73b1b103d76..72e7a1cd82f 100644 --- a/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java +++ b/chainbase/src/main/java/org/tron/core/db/TronStoreWithRevoking.java @@ -9,6 +9,7 @@ import java.lang.reflect.InvocationTargetException; import java.nio.file.Paths; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -38,7 +39,7 @@ @Slf4j(topic = "DB") public abstract class TronStoreWithRevoking implements ITronChainBase { - @Getter // only for unit test + @Getter protected IRevokingDB revokingDB; private TypeToken token = new TypeToken(getClass()) { }; @@ -54,10 +55,10 @@ public abstract class TronStoreWithRevoking implements I protected TronStoreWithRevoking(String dbName) { String dbEngine = CommonParameter.getInstance().getStorage().getDbEngine(); - if ("LEVELDB".equals(dbEngine.toUpperCase())) { + if ("LEVELDB".equals(dbEngine.toUpperCase(Locale.ROOT))) { this.db = new LevelDB( new LevelDbDataSourceImpl(StorageUtils.getOutputDirectoryByDbName(dbName), dbName)); - } else if ("ROCKSDB".equals(dbEngine.toUpperCase())) { + } else if ("ROCKSDB".equals(dbEngine.toUpperCase(Locale.ROOT))) { String parentPath = Paths .get(StorageUtils.getOutputDirectoryByDbName(dbName), CommonParameter .getInstance().getStorage().getDbDirectory()).toString(); diff --git a/chainbase/src/main/java/org/tron/core/db2/common/TxCacheDB.java b/chainbase/src/main/java/org/tron/core/db2/common/TxCacheDB.java index 31131de0866..e545f560830 100644 --- a/chainbase/src/main/java/org/tron/core/db2/common/TxCacheDB.java +++ b/chainbase/src/main/java/org/tron/core/db2/common/TxCacheDB.java @@ -20,6 +20,7 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -102,10 +103,10 @@ public TxCacheDB(String name, RecentTransactionStore recentTransactionStore, this.recentTransactionStore = recentTransactionStore; this.dynamicPropertiesStore = dynamicPropertiesStore; String dbEngine = CommonParameter.getInstance().getStorage().getDbEngine(); - if ("LEVELDB".equals(dbEngine.toUpperCase())) { + if ("LEVELDB".equals(dbEngine.toUpperCase(Locale.ROOT))) { this.persistentStore = new LevelDB( new LevelDbDataSourceImpl(StorageUtils.getOutputDirectoryByDbName(name), name)); - } else if ("ROCKSDB".equals(dbEngine.toUpperCase())) { + } else if ("ROCKSDB".equals(dbEngine.toUpperCase(Locale.ROOT))) { String parentPath = Paths .get(StorageUtils.getOutputDirectoryByDbName(name), CommonParameter .getInstance().getStorage().getDbDirectory()).toString(); diff --git a/chainbase/src/main/java/org/tron/core/store/AccountIdIndexStore.java b/chainbase/src/main/java/org/tron/core/store/AccountIdIndexStore.java index 1a695c5f627..4f5a53e3551 100644 --- a/chainbase/src/main/java/org/tron/core/store/AccountIdIndexStore.java +++ b/chainbase/src/main/java/org/tron/core/store/AccountIdIndexStore.java @@ -1,6 +1,7 @@ package org.tron.core.store; import com.google.protobuf.ByteString; +import java.util.Locale; import java.util.Objects; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -21,7 +22,8 @@ public AccountIdIndexStore(@Value("accountid-index") String dbName) { private static byte[] getLowerCaseAccountId(byte[] bsAccountId) { return ByteString - .copyFromUtf8(ByteString.copyFrom(bsAccountId).toStringUtf8().toLowerCase()).toByteArray(); + .copyFromUtf8(ByteString.copyFrom(bsAccountId).toStringUtf8().toLowerCase(Locale.ROOT)) + .toByteArray(); } public void put(AccountCapsule accountCapsule) { @@ -54,4 +56,4 @@ public boolean has(byte[] key) { return !ArrayUtils.isEmpty(value); } -} \ No newline at end of file +} 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 e0adb0d444a..e74e84f736d 100644 --- a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java +++ b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java @@ -240,6 +240,9 @@ public class DynamicPropertiesStore extends TronStoreWithRevoking private static final byte[] ALLOW_TVM_OSAKA = "ALLOW_TVM_OSAKA".getBytes(); + private static final byte[] TURKISH_KEY_MIGRATION_DONE = + "TURKISH_KEY_MIGRATION_DONE".getBytes(); + @Autowired private DynamicPropertiesStore(@Value("properties") String dbName) { super(dbName); @@ -2993,6 +2996,18 @@ public void saveAllowTvmOsaka(long value) { this.put(ALLOW_TVM_OSAKA, new BytesCapsule(ByteArray.fromLong(value))); } + public void saveTurkishKeyMigrationDone(long num) { + this.put(TURKISH_KEY_MIGRATION_DONE, + new BytesCapsule(ByteArray.fromLong(num))); + } + + public long getTurkishKeyMigrationDone() { + return Optional.ofNullable(getUnchecked(TURKISH_KEY_MIGRATION_DONE)) + .map(BytesCapsule::getData) + .map(ByteArray::toLong) + .orElse(0L); + } + private static class DynamicResourceProperties { private static final byte[] ONE_DAY_NET_LIMIT = "ONE_DAY_NET_LIMIT".getBytes(); diff --git a/common/src/main/java/org/tron/common/args/Account.java b/common/src/main/java/org/tron/common/args/Account.java index 872d202f86e..bbaaf9d1249 100644 --- a/common/src/main/java/org/tron/common/args/Account.java +++ b/common/src/main/java/org/tron/common/args/Account.java @@ -17,6 +17,7 @@ import com.google.protobuf.ByteString; import java.io.Serializable; +import java.util.Locale; import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.tron.common.utils.ByteArray; @@ -120,7 +121,7 @@ public boolean isAccountType(final String accountType) { return false; } - switch (accountType.toUpperCase()) { + switch (accountType.toUpperCase(Locale.ROOT)) { case ACCOUNT_TYPE_NORMAL: case ACCOUNT_TYPE_ASSETISSUE: case ACCOUNT_TYPE_CONTRACT: @@ -138,7 +139,7 @@ public AccountType getAccountTypeByString(final String accountType) { throw new IllegalArgumentException("Account type error: Not a Normal/AssetIssue/Contract"); } - switch (accountType.toUpperCase()) { + switch (accountType.toUpperCase(Locale.ROOT)) { case ACCOUNT_TYPE_NORMAL: return AccountType.Normal; case ACCOUNT_TYPE_ASSETISSUE: diff --git a/common/src/main/java/org/tron/common/runtime/vm/DataWord.java b/common/src/main/java/org/tron/common/runtime/vm/DataWord.java index faeae45782e..3f6c7571b9b 100644 --- a/common/src/main/java/org/tron/common/runtime/vm/DataWord.java +++ b/common/src/main/java/org/tron/common/runtime/vm/DataWord.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.util.Locale; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.tron.common.utils.ByteArray; @@ -121,7 +122,7 @@ public static boolean isZero(byte[] data) { public static String shortHex(byte[] data) { byte[] bytes = ByteUtil.stripLeadingZeroes(data); - String hexValue = Hex.toHexString(bytes).toUpperCase(); + String hexValue = Hex.toHexString(bytes).toUpperCase(Locale.ROOT); return "0x" + hexValue.replaceFirst("^0+(?!$)", ""); } @@ -451,7 +452,7 @@ public String toPrefixString() { } public String shortHex() { - String hexValue = Hex.toHexString(getNoLeadZeroesData()).toUpperCase(); + String hexValue = Hex.toHexString(getNoLeadZeroesData()).toUpperCase(Locale.ROOT); return "0x" + hexValue.replaceFirst("^0+(?!$)", ""); } diff --git a/errorprone/build.gradle b/errorprone/build.gradle new file mode 100644 index 00000000000..f8a634b7edc --- /dev/null +++ b/errorprone/build.gradle @@ -0,0 +1,13 @@ +if (!JavaVersion.current().isJava11Compatible()) { + // ErrorProne core requires JDK 11+; skip this module on JDK 8 + tasks.withType(JavaCompile).configureEach { enabled = false } + tasks.withType(Jar).configureEach { enabled = false } +} else { + dependencies { + compileOnly "com.google.errorprone:error_prone_annotations:${errorproneVersion}" + compileOnly "com.google.errorprone:error_prone_check_api:${errorproneVersion}" + compileOnly "com.google.errorprone:error_prone_core:${errorproneVersion}" + compileOnly "com.google.auto.service:auto-service:1.1.1" + annotationProcessor "com.google.auto.service:auto-service:1.1.1" + } +} diff --git a/errorprone/src/main/java/errorprone/StringCaseLocaleUsageMethodRef.java b/errorprone/src/main/java/errorprone/StringCaseLocaleUsageMethodRef.java new file mode 100644 index 00000000000..1486d945b58 --- /dev/null +++ b/errorprone/src/main/java/errorprone/StringCaseLocaleUsageMethodRef.java @@ -0,0 +1,56 @@ +package errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; + +/** + * Flags method references {@code String::toLowerCase} and {@code String::toUpperCase} + * that resolve to the no-arg overload (which uses {@code Locale.getDefault()}). + * + *

The built-in ErrorProne {@code StringCaseLocaleUsage} checker only catches + * direct method invocations ({@code s.toLowerCase()}), not method references + * ({@code String::toLowerCase}). This checker closes that gap. + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "StringCaseLocaleUsageMethodRef", + summary = "String::toLowerCase and String::toUpperCase method references use " + + "Locale.getDefault(). Replace with a lambda that specifies Locale.ROOT, " + + "e.g. s -> s.toLowerCase(Locale.ROOT).", + severity = BugPattern.SeverityLevel.ERROR +) +public class StringCaseLocaleUsageMethodRef extends BugChecker + implements BugChecker.MemberReferenceTreeMatcher { + + @Override + public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) { + String name = tree.getName().toString(); + if (!"toLowerCase".equals(name) && !"toUpperCase".equals(name)) { + return Description.NO_MATCH; + } + // Verify the qualifier type is java.lang.String + Type qualifierType = ((com.sun.tools.javac.tree.JCTree) tree.getQualifierExpression()) + .type; + if (qualifierType == null) { + return Description.NO_MATCH; + } + if (!state.getTypes().isSameType( + qualifierType, state.getSymtab().stringType)) { + return Description.NO_MATCH; + } + // Only flag the no-arg overload; the Locale-taking overload is safe + Symbol sym = ASTHelpers.getSymbol(tree); + if (sym instanceof Symbol.MethodSymbol + && ((Symbol.MethodSymbol) sym).getParameters().isEmpty()) { + return describeMatch(tree); + } + return Description.NO_MATCH; + } +} diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 39e8f06c281..51c9041e08b 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -57,6 +57,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -3880,14 +3881,14 @@ private int getShieldedTRC20LogType(TransactionInfo.Log log, byte[] contractAddr for (String topic : topicsList) { byte[] topicHash = Hash.sha3(ByteArray.fromString(topic)); if (Arrays.equals(topicsBytes, topicHash)) { - if (topic.toLowerCase().contains("mint")) { + if (topic.toLowerCase(Locale.ROOT).contains("mint")) { return 1; - } else if (topic.toLowerCase().contains("transfer")) { + } else if (topic.toLowerCase(Locale.ROOT).contains("transfer")) { return 2; - } else if (topic.toLowerCase().contains("burn")) { - if (topic.toLowerCase().contains("leaf")) { + } else if (topic.toLowerCase(Locale.ROOT).contains("burn")) { + if (topic.toLowerCase(Locale.ROOT).contains("leaf")) { return 3; - } else if (topic.toLowerCase().contains("token")) { + } else if (topic.toLowerCase(Locale.ROOT).contains("token")) { return 4; } } 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 83d7fd2c63d..0dd0b2811e5 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 @@ -35,6 +35,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -892,7 +893,7 @@ public static void applyConfigParams( PARAMETER.disabledApiList = config.hasPath(ConfigKey.NODE_DISABLED_API_LIST) ? config.getStringList(ConfigKey.NODE_DISABLED_API_LIST) - .stream().map(String::toLowerCase).collect(Collectors.toList()) + .stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()) : Collections.emptyList(); if (config.hasPath(ConfigKey.NODE_SHUTDOWN_BLOCK_TIME)) { @@ -1770,7 +1771,7 @@ public static void printHelp(JCommander jCommander) { Map groupOptionListMap = Args.getOptionGroup(); for (Map.Entry entry : groupOptionListMap.entrySet()) { String group = entry.getKey(); - helpStr.append(String.format("%n%s OPTIONS:%n", group.toUpperCase())); + helpStr.append(String.format("%n%s OPTIONS:%n", group.toUpperCase(Locale.ROOT))); int optionMaxLength = Arrays.stream(entry.getValue()).mapToInt(p -> { ParameterDescription tmpParameterDescription = stringParameterDescriptionMap.get(p); if (tmpParameterDescription == null) { @@ -1810,7 +1811,7 @@ public static String upperFirst(String name) { if (name.length() <= 1) { return name; } - name = name.substring(0, 1).toUpperCase() + name.substring(1); + name = name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1); return name; } diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index cd1a61c01fe..851db8e6d19 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -109,6 +109,7 @@ import org.tron.core.db.api.AssetUpdateHelper; import org.tron.core.db.api.BandwidthPriceHistoryLoader; import org.tron.core.db.api.EnergyPriceHistoryLoader; +import org.tron.core.db.api.MigrateTurkishKeyHelper; import org.tron.core.db.api.MoveAbiHelper; import org.tron.core.db2.ISession; import org.tron.core.db2.core.Chainbase; @@ -372,6 +373,10 @@ public boolean needToSetBlackholePermission() { return getDynamicPropertiesStore().getSetBlackholeAccountPermission() == 0L; } + private boolean needToMigrateTurkishKeys() { + return getDynamicPropertiesStore().getTurkishKeyMigrationDone() == 0L; + } + private void resetBlackholeAccountPermission() { AccountCapsule blackholeAccount = getAccountStore().getBlackhole(); @@ -542,6 +547,10 @@ public void init() { resetBlackholeAccountPermission(); } + if (needToMigrateTurkishKeys()) { + new MigrateTurkishKeyHelper(chainBaseManager).doWork(); + } + //for test only chainBaseManager.getDynamicPropertiesStore().updateDynamicStoreByConfig(); diff --git a/framework/src/main/java/org/tron/core/db/api/MigrateTurkishKeyHelper.java b/framework/src/main/java/org/tron/core/db/api/MigrateTurkishKeyHelper.java new file mode 100644 index 00000000000..46c3d367ece --- /dev/null +++ b/framework/src/main/java/org/tron/core/db/api/MigrateTurkishKeyHelper.java @@ -0,0 +1,92 @@ +package org.tron.core.db.api; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.tron.core.ChainBaseManager; +import org.tron.core.db2.common.IRevokingDB; +import org.tron.core.store.AccountIdIndexStore; + +/** + * One-time migration: normalize any Turkish legacy keys (containing + * dotless-ı U+0131) to ROOT keys (with ASCII 'i') in AccountIdIndexStore. + * + *

On Turkish/Azerbaijani locales, {@code String.toLowerCase()} maps + * uppercase 'I' to dotless-ı instead of 'i'. Nodes that ran under such + * locales wrote different index keys, causing lookup failures. + * This migration ensures all nodes have identical DB state regardless + * of their locale history. + * + *

Called from {@code Manager.init()} via the standard + * {@code DynamicPropertiesStore} flag pattern. + * + * @see AccountIdIndexStore + */ +@Slf4j(topic = "DB") +public class MigrateTurkishKeyHelper { + + private static final char DOTLESS_I = '\u0131'; // ı Turkish dotless-i + + private final ChainBaseManager chainBaseManager; + + public MigrateTurkishKeyHelper(ChainBaseManager chainBaseManager) { + this.chainBaseManager = chainBaseManager; + } + + /** + * Scan AccountIdIndexStore for keys containing Turkish dotless-ı (U+0131), + * replace them with ROOT-equivalent keys (ı → i), and delete the old keys. + * + *

Uses raw {@link IRevokingDB} access to bypass the fallback lookup + * logic in {@link AccountIdIndexStore#has(byte[])} — we need exact-match + * checks to determine whether the ROOT key already exists in the DB. + */ + public void doWork() { + long start = System.currentTimeMillis(); + logger.info("Start to migrate Turkish legacy keys in AccountIdIndexStore"); + + final IRevokingDB revokingDB = chainBaseManager.getAccountIdIndexStore() + .getRevokingDB(); + long totalKeys = 0; + List keysToDelete = new ArrayList<>(); + List> entriesToMigrate = new ArrayList<>(); + + // Phase 1: scan for keys containing 'ı' (U+0131) + for (Map.Entry entry : revokingDB) { + totalKeys++; + String keyStr = new String(entry.getKey(), StandardCharsets.UTF_8); + if (keyStr.indexOf(DOTLESS_I) >= 0) { + entriesToMigrate.add(entry); + } + } + + // Phase 2: migrate each Turkish key to its ROOT equivalent + for (Map.Entry entry : entriesToMigrate) { + String keyStr = new String(entry.getKey(), StandardCharsets.UTF_8); + byte[] rootKey = keyStr.replace(DOTLESS_I, 'i') + .getBytes(StandardCharsets.UTF_8); + // Only write if ROOT key doesn't already exist + byte[] existing = revokingDB.getUnchecked(rootKey); + if (ArrayUtils.isEmpty(existing)) { + revokingDB.put(rootKey, entry.getValue()); + } + keysToDelete.add(entry.getKey()); + } + + // Phase 3: delete old Turkish keys + for (byte[] key : keysToDelete) { + revokingDB.delete(key); + } + + // Phase 4: mark migration as done + chainBaseManager.getDynamicPropertiesStore().saveTurkishKeyMigrationDone(1); + + logger.info( + "Complete the Turkish key migration, total time: {} milliseconds," + + " total keys: {}, migrated count: {}", + System.currentTimeMillis() - start, totalKeys, entriesToMigrate.size()); + } +} diff --git a/framework/src/main/java/org/tron/core/services/filter/HttpApiAccessFilter.java b/framework/src/main/java/org/tron/core/services/filter/HttpApiAccessFilter.java index 59b9b15582b..f4994d9af08 100644 --- a/framework/src/main/java/org/tron/core/services/filter/HttpApiAccessFilter.java +++ b/framework/src/main/java/org/tron/core/services/filter/HttpApiAccessFilter.java @@ -3,6 +3,7 @@ import com.alibaba.fastjson.JSONObject; import java.net.URI; import java.util.List; +import java.util.Locale; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -63,7 +64,7 @@ private boolean isDisabled(String endpoint) { endpoint = URI.create(endpoint).normalize().toString(); List disabledApiList = CommonParameter.getInstance().getDisabledApiList(); if (!disabledApiList.isEmpty()) { - disabled = disabledApiList.contains(endpoint.split("/")[2].toLowerCase()); + disabled = disabledApiList.contains(endpoint.split("/")[2].toLowerCase(Locale.ROOT)); } } catch (Exception e) { logger.warn("check isDisabled except, endpoint={}, {}", endpoint, e.getMessage()); 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 2b6b929d8a0..f7fda3ac4a0 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 @@ -22,6 +22,7 @@ import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import javax.servlet.http.HttpServletRequest; @@ -525,10 +526,10 @@ public static byte[] getAddress(HttpServletRequest request) throws Exception { private static String checkGetParam(HttpServletRequest request, String key) throws Exception { String method = request.getMethod(); - if (HttpMethod.GET.toString().toUpperCase().equalsIgnoreCase(method)) { + if (HttpMethod.GET.toString().toUpperCase(Locale.ROOT).equalsIgnoreCase(method)) { return request.getParameter(key); } - if (HttpMethod.POST.toString().toUpperCase().equals(method)) { + if (HttpMethod.POST.toString().toUpperCase(Locale.ROOT).equals(method)) { String contentType = request.getContentType(); if (StringUtils.isBlank(contentType)) { return null; diff --git a/framework/src/main/java/org/tron/core/services/ratelimiter/RpcApiAccessInterceptor.java b/framework/src/main/java/org/tron/core/services/ratelimiter/RpcApiAccessInterceptor.java index c3471c2829c..d3a6bf74efd 100644 --- a/framework/src/main/java/org/tron/core/services/ratelimiter/RpcApiAccessInterceptor.java +++ b/framework/src/main/java/org/tron/core/services/ratelimiter/RpcApiAccessInterceptor.java @@ -7,6 +7,7 @@ import io.grpc.ServerInterceptor; import io.grpc.Status; import java.util.List; +import java.util.Locale; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.tron.common.parameter.CommonParameter; @@ -43,7 +44,7 @@ private boolean isDisabled(String endpoint) { try { List disabledApiList = CommonParameter.getInstance().getDisabledApiList(); if (!disabledApiList.isEmpty()) { - disabled = disabledApiList.contains(endpoint.split("/")[1].toLowerCase()); + disabled = disabledApiList.contains(endpoint.split("/")[1].toLowerCase(Locale.ROOT)); } } catch (Exception e) { logger.error("check isDisabled except, endpoint={}, error is {}", endpoint, e.getMessage()); diff --git a/framework/src/main/java/org/tron/keystore/WalletUtils.java b/framework/src/main/java/org/tron/keystore/WalletUtils.java index 8bcc68cbab0..42b33d626e6 100644 --- a/framework/src/main/java/org/tron/keystore/WalletUtils.java +++ b/framework/src/main/java/org/tron/keystore/WalletUtils.java @@ -12,6 +12,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.Locale; import java.util.Scanner; import org.apache.commons.lang3.StringUtils; import org.tron.common.crypto.SignInterface; @@ -94,7 +95,7 @@ public static String getDefaultKeyDirectory() { } static String getDefaultKeyDirectory(String osName1) { - String osName = osName1.toLowerCase(); + String osName = osName1.toLowerCase(Locale.ROOT); if (osName.startsWith("mac")) { return String.format( diff --git a/framework/src/main/java/org/tron/program/KeystoreFactory.java b/framework/src/main/java/org/tron/program/KeystoreFactory.java index 8199d7e9076..ea941e54007 100755 --- a/framework/src/main/java/org/tron/program/KeystoreFactory.java +++ b/framework/src/main/java/org/tron/program/KeystoreFactory.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.util.Locale; import java.util.Scanner; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -114,7 +115,7 @@ private void run() { if ("".equals(cmd)) { continue; } - String cmdLowerCase = cmd.toLowerCase(); + String cmdLowerCase = cmd.toLowerCase(Locale.ROOT); switch (cmdLowerCase) { case "help": { diff --git a/framework/src/test/java/org/tron/common/cron/CronExpressionTest.java b/framework/src/test/java/org/tron/common/cron/CronExpressionTest.java index 5e41670763c..1c8bdf86134 100644 --- a/framework/src/test/java/org/tron/common/cron/CronExpressionTest.java +++ b/framework/src/test/java/org/tron/common/cron/CronExpressionTest.java @@ -24,6 +24,7 @@ import java.text.ParseException; import java.util.Calendar; import java.util.Date; +import java.util.Locale; import org.junit.Test; public class CronExpressionTest { @@ -164,8 +165,8 @@ public void testLastDayOffset() throws Exception { @Test public void testQuartz() throws Exception { CronExpression cronExpression = new CronExpression("19 15 10 4 Apr ? "); - assertEquals("19 15 10 4 Apr ? ".toUpperCase(), cronExpression.getCronExpression()); - assertEquals("19 15 10 4 Apr ? ".toUpperCase(), cronExpression.toString()); + assertEquals("19 15 10 4 Apr ? ".toUpperCase(Locale.ROOT), cronExpression.getCronExpression()); + assertEquals("19 15 10 4 Apr ? ".toUpperCase(Locale.ROOT), cronExpression.toString()); // if broken, this will throw an exception cronExpression.getNextValidTimeAfter(new Date()); diff --git a/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java b/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java index 87e4e14698c..b8507256ba3 100644 --- a/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java +++ b/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java @@ -13,6 +13,7 @@ import java.security.KeyPairGenerator; import java.security.SignatureException; import java.util.Arrays; +import java.util.Locale; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.util.encoders.Hex; @@ -123,7 +124,7 @@ public void testSM3Hash() { String message = "message digest"; byte[] hash = signer.generateSM3Hash(message.getBytes()); assertEquals("2A723761EAE35429DF643648FD69FB7787E7FC32F321BFAF7E294390F529BAF4", - Hex.toHexString(hash).toUpperCase()); + Hex.toHexString(hash).toUpperCase(Locale.ROOT)); } diff --git a/framework/src/test/java/org/tron/common/runtime/vm/BytecodeCompiler.java b/framework/src/test/java/org/tron/common/runtime/vm/BytecodeCompiler.java index 48355f137f4..38e813719fc 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/BytecodeCompiler.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/BytecodeCompiler.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.bouncycastle.util.encoders.Hex; import org.tron.core.vm.Op; @@ -17,7 +18,7 @@ private byte[] compile(String[] tokens) { int ntokens = tokens.length; for (String s : tokens) { - String token = s.trim().toUpperCase(); + String token = s.trim().toUpperCase(Locale.ROOT); if (token.isEmpty()) { continue; diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java index 583b0131942..db5cdd3f21a 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java @@ -5,6 +5,7 @@ import static org.tron.core.config.Parameter.ChainConstant.FROZEN_PERIOD; import java.util.List; +import java.util.Locale; import java.util.Random; import lombok.SneakyThrows; @@ -538,7 +539,7 @@ public void testMemoryStorageAndFlowOperations() throws ContractValidateExceptio testSingleOperation(program); Assert.assertEquals(20, program.getResult().getEnergyUsed()); Assert.assertEquals("00000000000000000000000000000000000000000000000000000000000000CC", - Hex.toHexString(program.getStack().peek().getData()).toUpperCase()); + Hex.toHexString(program.getStack().peek().getData()).toUpperCase(Locale.ROOT)); // PC = 0x58 op = new byte[]{0x60, 0x01, 0x60, 0x00, 0x58}; @@ -861,7 +862,7 @@ public void testComplexOperations() throws ContractValidateException { testSingleOperation(program); Assert.assertEquals(10065, program.getResult().getEnergyUsed()); Assert.assertEquals("0000000000000000000000000000000000000000000000000000000000000033", - Hex.toHexString(program.getStack().peek().getData()).toUpperCase()); + Hex.toHexString(program.getStack().peek().getData()).toUpperCase(Locale.ROOT)); // EXTCODESIZE = 0x3b op = new byte[]{0x3b}; @@ -881,7 +882,7 @@ public void testComplexOperations() throws ContractValidateException { testSingleOperation(program); Assert.assertEquals(38, program.getResult().getEnergyUsed()); Assert.assertEquals("6000600000000000000000000000000000000000000000000000000000000000", - Hex.toHexString(program.getMemory()).toUpperCase()); + Hex.toHexString(program.getMemory()).toUpperCase(Locale.ROOT)); } diff --git a/framework/src/test/java/org/tron/common/utils/client/utils/DataWord.java b/framework/src/test/java/org/tron/common/utils/client/utils/DataWord.java index f676ef6c8e4..a719b7bb9af 100644 --- a/framework/src/test/java/org/tron/common/utils/client/utils/DataWord.java +++ b/framework/src/test/java/org/tron/common/utils/client/utils/DataWord.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.util.Locale; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; @@ -431,7 +432,7 @@ public String toPrefixString() { } public String shortHex() { - String hexValue = Hex.toHexString(getNoLeadZeroesData()).toUpperCase(); + String hexValue = Hex.toHexString(getNoLeadZeroesData()).toUpperCase(Locale.ROOT); return "0x" + hexValue.replaceFirst("^0+(?!$)", ""); } diff --git a/framework/src/test/java/org/tron/core/db/AccountIdIndexStoreTest.java b/framework/src/test/java/org/tron/core/db/AccountIdIndexStoreTest.java index 236c3464697..4c70bbcdb8a 100644 --- a/framework/src/test/java/org/tron/core/db/AccountIdIndexStoreTest.java +++ b/framework/src/test/java/org/tron/core/db/AccountIdIndexStoreTest.java @@ -1,6 +1,8 @@ package org.tron.core.db; import com.google.protobuf.ByteString; +import java.nio.charset.StandardCharsets; +import java.util.Locale; import java.util.Random; import javax.annotation.Resource; import org.junit.Assert; @@ -11,7 +13,9 @@ import org.tron.common.TestConstants; import org.tron.core.Wallet; import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.BytesCapsule; import org.tron.core.config.args.Args; +import org.tron.core.db.api.MigrateTurkishKeyHelper; import org.tron.core.store.AccountIdIndexStore; import org.tron.protos.Protocol.AccountType; @@ -26,6 +30,7 @@ public class AccountIdIndexStoreTest extends BaseTest { private static final byte[] ACCOUNT_NAME_THREE = randomBytes(6); private static final byte[] ACCOUNT_NAME_FOUR = randomBytes(6); private static final byte[] ACCOUNT_NAME_FIVE = randomBytes(6); + private static final Locale TURKISH = Locale.forLanguageTag("tr"); @Resource private AccountIdIndexStore accountIdIndexStore; private static AccountCapsule accountCapsule1; @@ -101,6 +106,7 @@ public void putAndHas() { } @Test + @SuppressWarnings("StringCaseLocaleUsage") public void testCaseInsensitive() { byte[] ACCOUNT_NAME = "aABbCcDd_ssd1234".getBytes(); byte[] ACCOUNT_ADDRESS = randomBytes(16); @@ -128,4 +134,59 @@ public void testCaseInsensitive() { Assert.assertNotNull("getLowerCase fail", accountIdIndexStore.get(upperCase)); } -} \ No newline at end of file + + @Test + @SuppressWarnings("StringCaseLocaleUsage") + public void testKeysMigration() { + String[]accountIds = {"", "12345678", "543838383", "BitTorrent", + "Converse", "HelloWorld", "InfStonesSSRWallet", "ISSRWallet", "JustDoIt", + "JustinSun", "JustinSunTron", "RtytIturtet", "TronBetFestival", "vena_family" + }; + + byte[][] addresses = new byte[accountIds.length][]; + byte[][] turkishKeys = new byte[accountIds.length][]; + + for (int i = 0; i < accountIds.length; i++) { + addresses[i] = randomBytes(21); + String turkishLower = accountIds[i].toLowerCase(TURKISH); + turkishKeys[i] = turkishLower.getBytes(StandardCharsets.UTF_8); + accountIdIndexStore.put(turkishKeys[i], new BytesCapsule(addresses[i])); + } + + for (int i = 0; i < accountIds.length; i++) { + String rootLower = accountIds[i].toLowerCase(Locale.ROOT); + String turkishLower = accountIds[i].toLowerCase(TURKISH); + boolean shouldMiss = !rootLower.equals(turkishLower); + if (shouldMiss) { + Assert.assertNull( + "pre-migrate: ROOT query should miss for " + accountIds[i], + accountIdIndexStore.get(ByteString.copyFrom( + accountIds[i].getBytes(StandardCharsets.UTF_8)))); + } else { + Assert.assertArrayEquals( + "pre-migrate: ROOT query should hit for " + accountIds[i], + addresses[i], + accountIdIndexStore.get(ByteString.copyFrom( + accountIds[i].getBytes(StandardCharsets.UTF_8)))); + } + } + + new MigrateTurkishKeyHelper(chainBaseManager).doWork(); + + for (int i = 0; i < accountIds.length; i++) { + Assert.assertArrayEquals( + "post-migrate: get(" + accountIds[i] + ")", + addresses[i], + accountIdIndexStore.get(ByteString.copyFrom( + accountIds[i].getBytes(StandardCharsets.UTF_8)))); + String lower = accountIds[i].toLowerCase(Locale.ROOT); + Assert.assertTrue( + "post-migrate: has(" + lower + ")", + accountIdIndexStore.has(lower.getBytes(StandardCharsets.UTF_8))); + String upper = accountIds[i].toUpperCase(Locale.ROOT); + Assert.assertTrue( + "post-migrate: has(" + upper + ")", + accountIdIndexStore.has(upper.getBytes(StandardCharsets.UTF_8))); + } + } +} 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 7150f1a0541..7143cef43e2 100755 --- a/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java +++ b/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.Set; import javax.annotation.Resource; @@ -2531,7 +2532,7 @@ public void pushSameSkAndScanAndSpend() throws Exception { public void decodePaymentAddressIgnoreCase() { String addressLower = "ztron1975m0wyg8f30cgf2l5fgndhzqzkzgkgnxge8cwx2wr7m3q7chsuwewh2e6u24yykum0hq8ue99u"; - String addressUpper = addressLower.toUpperCase(); + String addressUpper = addressLower.toUpperCase(Locale.ROOT); PaymentAddress paymentAddress1 = KeyIo.decodePaymentAddress(addressLower); PaymentAddress paymentAddress2 = KeyIo.decodePaymentAddress(addressUpper); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 86880157f35..6eff27a39f1 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -270,6 +270,20 @@ + + + + + + + + + + + + + + @@ -289,6 +303,14 @@ + + + + + + + + @@ -323,6 +345,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -373,6 +458,14 @@ + + + + + + + + @@ -382,6 +475,9 @@ + + + @@ -394,6 +490,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -409,6 +529,24 @@ + + + + + + + + + + + + + + + + + + @@ -417,6 +555,14 @@ + + + + + + + + @@ -478,6 +624,17 @@ + + + + + + + + + + + @@ -518,6 +675,11 @@ + + + + + @@ -535,6 +697,9 @@ + + + @@ -547,6 +712,11 @@ + + + + + @@ -560,6 +730,14 @@ + + + + + + + + @@ -576,6 +754,11 @@ + + + + + @@ -610,6 +793,9 @@ + + + @@ -840,6 +1026,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -1311,6 +1518,22 @@ + + + + + + + + + + + + + + + + @@ -1675,6 +1898,25 @@ + + + + + + + + + + + + + + + + + + + @@ -1886,6 +2128,9 @@ + + + @@ -2012,6 +2257,17 @@ + + + + + + + + + + + @@ -2168,6 +2424,17 @@ + + + + + + + + + + + diff --git a/platform/src/main/java/common/org/tron/common/arch/Arch.java b/platform/src/main/java/common/org/tron/common/arch/Arch.java index f115d1f07c2..999bb631bea 100644 --- a/platform/src/main/java/common/org/tron/common/arch/Arch.java +++ b/platform/src/main/java/common/org/tron/common/arch/Arch.java @@ -1,5 +1,6 @@ package org.tron.common.arch; +import java.util.Locale; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "arch") @@ -21,11 +22,11 @@ public static String withAll() { } public static String getOsName() { - return System.getProperty("os.name").toLowerCase().trim(); + return System.getProperty("os.name").toLowerCase(Locale.ROOT).trim(); } public static String getOsArch() { - return System.getProperty("os.arch").toLowerCase().trim(); + return System.getProperty("os.arch").toLowerCase(Locale.ROOT).trim(); } public static int getBitModel() { @@ -45,15 +46,15 @@ public static int getBitModel() { } public static String javaVersion() { - return System.getProperty("java.version").toLowerCase().trim(); + return System.getProperty("java.version").toLowerCase(Locale.ROOT).trim(); } public static String javaSpecificationVersion() { - return System.getProperty("java.specification.version").toLowerCase().trim(); + return System.getProperty("java.specification.version").toLowerCase(Locale.ROOT).trim(); } public static String javaVendor() { - return System.getProperty("java.vendor").toLowerCase().trim(); + return System.getProperty("java.vendor").toLowerCase(Locale.ROOT).trim(); } public static boolean isArm64() { diff --git a/settings.gradle b/settings.gradle index af32bfca702..0a1fd84bdf9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,9 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} rootProject.name = 'java-tron' include 'framework' include 'chainbase' @@ -9,4 +15,5 @@ include 'example:actuator-example' include 'crypto' include 'plugins' include 'platform' +include 'errorprone'