|
1 | 1 | package org.tron.core.store; |
2 | 2 |
|
3 | 3 | import com.google.protobuf.ByteString; |
4 | | -import java.nio.charset.StandardCharsets; |
5 | | -import java.util.Arrays; |
6 | 4 | import java.util.Locale; |
7 | 5 | import java.util.Objects; |
8 | | -import lombok.extern.slf4j.Slf4j; |
9 | 6 | import org.apache.commons.lang3.ArrayUtils; |
10 | 7 | import org.springframework.beans.factory.annotation.Autowired; |
11 | 8 | import org.springframework.beans.factory.annotation.Value; |
|
14 | 11 | import org.tron.core.capsule.BytesCapsule; |
15 | 12 | import org.tron.core.db.TronStoreWithRevoking; |
16 | 13 |
|
17 | | -@Slf4j(topic = "DB") |
| 14 | +//todo : need Compatibility test |
18 | 15 | @Component |
19 | 16 | public class AccountIdIndexStore extends TronStoreWithRevoking<BytesCapsule> { |
20 | 17 |
|
21 | | - /** |
22 | | - * Turkish dotless-ı (U+0131). On Turkish/Azerbaijani locales, |
23 | | - * {@code 'I'.toLowerCase()} produces this instead of ASCII {@code 'i'}. |
24 | | - * This is the ONLY ASCII letter that differs between ROOT and Turkish |
25 | | - * {@code toLowerCase()} — verified by testTurkishLowerCaseDiffForAllAsciiLetters. |
26 | | - */ |
27 | | - private static final char DOTLESS_I = '\u0131'; // ı Turkish dotless-i |
28 | | - private static final Locale TURKISH = Locale.forLanguageTag("tr"); |
29 | | - |
30 | 18 | @Autowired |
31 | 19 | public AccountIdIndexStore(@Value("accountid-index") String dbName) { |
32 | 20 | super(dbName); |
33 | 21 | } |
34 | 22 |
|
35 | | - public static byte[] getLowerCaseAccountId(byte[] accountId) { |
| 23 | + public static byte[] getLowerCaseAccountId(byte[] bsAccountId) { |
36 | 24 | return ByteString |
37 | | - .copyFromUtf8(ByteString.copyFrom(accountId).toStringUtf8().toLowerCase(Locale.ROOT)) |
| 25 | + .copyFromUtf8(ByteString.copyFrom(bsAccountId).toStringUtf8().toLowerCase(Locale.ROOT)) |
38 | 26 | .toByteArray(); |
39 | 27 | } |
40 | 28 |
|
41 | | - /** |
42 | | - * Turkish direct key: {@code toLowerCase(TURKISH)} on the original input. |
43 | | - * Reproduces the exact key a Turkish node stored for the same-case input. |
44 | | - * Handles lookups where query case matches the original accountId case. |
45 | | - * |
46 | | - * <p>Example: input "AiBI" → "aibı" (lowercase 'i' stays, uppercase 'I' → 'ı'). |
47 | | - */ |
48 | | - @SuppressWarnings("StringCaseLocaleUsage") |
49 | | - private static byte[] getTurkishDirectKey(byte[] accountId) { |
50 | | - String str = ByteString.copyFrom(accountId).toStringUtf8(); |
51 | | - return ByteString.copyFromUtf8(str.toLowerCase(TURKISH)).toByteArray(); |
52 | | - } |
53 | | - |
54 | | - /** |
55 | | - * Turkish normalized key: ROOT key with all {@code 'i'} replaced by {@code 'ı'}. |
56 | | - * Handles cross-case lookups (e.g., lowercase query for an accountId that |
57 | | - * was originally uppercase on a Turkish node). |
58 | | - * |
59 | | - * <p>Example: rootKey "aibi" → "aıbı". |
60 | | - * |
61 | | - * @param rootKey the already-computed ROOT-lowered key |
62 | | - * @return the normalized key, or {@code rootKey} itself if no 'i' is present |
63 | | - */ |
64 | | - private static byte[] getTurkishNormalizedKey(byte[] rootKey) { |
65 | | - String str = new String(rootKey, StandardCharsets.UTF_8); |
66 | | - if (str.indexOf('i') < 0) { |
67 | | - return rootKey; |
68 | | - } |
69 | | - return str.replace('i', DOTLESS_I).getBytes(StandardCharsets.UTF_8); |
70 | | - } |
71 | | - |
72 | 29 | public void put(AccountCapsule accountCapsule) { |
73 | 30 | byte[] lowerCaseAccountId = getLowerCaseAccountId(accountCapsule.getAccountId().toByteArray()); |
74 | 31 | super.put(lowerCaseAccountId, new BytesCapsule(accountCapsule.getAddress().toByteArray())); |
75 | 32 | } |
76 | 33 |
|
77 | | - public byte[] get(ByteString accountId) { |
78 | | - BytesCapsule bytesCapsule = get(accountId.toByteArray()); |
| 34 | + public byte[] get(ByteString name) { |
| 35 | + BytesCapsule bytesCapsule = get(name.toByteArray()); |
79 | 36 | if (Objects.nonNull(bytesCapsule)) { |
80 | 37 | return bytesCapsule.getData(); |
81 | 38 | } |
82 | 39 | return null; |
83 | 40 | } |
84 | 41 |
|
85 | | - /** |
86 | | - * Look up by the standard (Locale.ROOT) accountId first; on miss, fall back to |
87 | | - * Turkish legacy keys. The fallback covers nodes that previously ran under |
88 | | - * tr/az locale and wrote keys containing dotless-ı (U+0131). |
89 | | - * |
90 | | - * <p>Two fallback probes are used: |
91 | | - * <ol> |
92 | | - * <li><b>Direct</b>: {@code toLowerCase(TURKISH)} — matches when query |
93 | | - * case equals original accountId case (handles mixed 'i'/'I').</li> |
94 | | - * <li><b>Normalized</b>: ROOT accountId with all 'i' → 'ı' — matches when |
95 | | - * query case differs from original (e.g., all-lowercase query for |
96 | | - * an all-uppercase stored accountId).</li> |
97 | | - * </ol> |
98 | | - * |
99 | | - * <p>Each probe is skipped when it produces the same accountId as the ROOT accountId |
100 | | - * (i.e., input contains no 'I' or 'i'). AccountIdIndexStore is a small |
101 | | - * dataset, so the overhead of up to two extra lookups is negligible. |
102 | | - */ |
103 | 42 | @Override |
104 | | - public BytesCapsule get(byte[] accountId) { |
105 | | - byte[] value = lookupWithFallback(accountId); |
106 | | - return ArrayUtils.isEmpty(value) ? null : new BytesCapsule(value); |
| 43 | + public BytesCapsule get(byte[] key) { |
| 44 | + byte[] lowerCaseKey = getLowerCaseAccountId(key); |
| 45 | + byte[] value = revokingDB.getUnchecked(lowerCaseKey); |
| 46 | + if (ArrayUtils.isEmpty(value)) { |
| 47 | + return null; |
| 48 | + } |
| 49 | + return new BytesCapsule(value); |
107 | 50 | } |
108 | 51 |
|
109 | | - /** See {@link #get(byte[])} for fallback strategy. */ |
110 | 52 | @Override |
111 | | - public boolean has(byte[] accountId) { |
112 | | - return !ArrayUtils.isEmpty(lookupWithFallback(accountId)); |
113 | | - } |
114 | | - |
115 | | - private byte[] lookupWithFallback(byte[] accountId) { |
116 | | - byte[] rootLocaleKey = getLowerCaseAccountId(accountId); |
117 | | - byte[] value = revokingDB.getUnchecked(rootLocaleKey); |
118 | | - // Fallback 1: direct Turkish accountId (same-case match). |
119 | | - // Needed for accountIds containing BOTH 'i' and 'I' (e.g., "AiBI"). |
120 | | - // A Turkish node stored toLowerCase(TURKISH) = "aibı" — only the |
121 | | - // direct probe reproduces this mixed 'i'/'ı' key correctly. |
122 | | - // The normalized probe (Fallback 2) would produce "aıbı" instead. |
123 | | - if (ArrayUtils.isEmpty(value)) { |
124 | | - byte[] directKey = getTurkishDirectKey(accountId); |
125 | | - if (!Arrays.equals(rootLocaleKey, directKey)) { |
126 | | - value = revokingDB.getUnchecked(directKey); |
127 | | - } |
128 | | - } |
129 | | - // Fallback 2: normalized Turkish accountId (cross-case match). |
130 | | - // Handles queries where case differs from the original accountId, |
131 | | - // e.g., lowercase "aibi" looking up an entry stored as "AIBI" |
132 | | - // on a Turkish node (stored key = "aıbı"). |
133 | | - if (ArrayUtils.isEmpty(value)) { |
134 | | - byte[] normalizedKey = getTurkishNormalizedKey(rootLocaleKey); |
135 | | - if (!Arrays.equals(rootLocaleKey, normalizedKey)) { |
136 | | - value = revokingDB.getUnchecked(normalizedKey); |
137 | | - } |
138 | | - } |
139 | | - return value; |
| 53 | + public boolean has(byte[] key) { |
| 54 | + byte[] lowerCaseKey = getLowerCaseAccountId(key); |
| 55 | + byte[] value = revokingDB.getUnchecked(lowerCaseKey); |
| 56 | + return !ArrayUtils.isEmpty(value); |
140 | 57 | } |
141 | 58 |
|
142 | 59 | } |
0 commit comments