Skip to content

Commit 6de651d

Browse files
committed
feat(state): add state check
1 parent 3a6d3c1 commit 6de651d

File tree

22 files changed

+599
-41
lines changed

22 files changed

+599
-41
lines changed

common/src/main/java/org/tron/common/math/Maths.java renamed to chainbase/src/main/java/org/tron/common/math/Maths.java

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,89 @@
11
package org.tron.common.math;
22

3+
import com.google.common.primitives.Bytes;
4+
import java.nio.ByteBuffer;
5+
import java.util.Optional;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.stereotype.Component;
9+
import org.tron.common.context.GlobalContext;
10+
import org.tron.core.store.MathStore;
11+
import org.tron.core.store.StrictMathStore;
12+
313
/**
414
* This class is deprecated and should not be used in new code,
515
* for cross-platform consistency, please use {@link StrictMathWrapper} instead,
616
* especially for floating-point calculations.
717
*/
818
@Deprecated
19+
@Component
20+
@Slf4j(topic = "math")
921
public class Maths {
1022

23+
private static Optional<MathStore> mathStore = Optional.empty();
24+
private static Optional<StrictMathStore> strictMathStore = Optional.empty();
25+
26+
@Autowired
27+
public Maths(@Autowired MathStore mathStore, @Autowired StrictMathStore strictMathStore) {
28+
Maths.mathStore = Optional.ofNullable(mathStore);
29+
Maths.strictMathStore = Optional.ofNullable(strictMathStore);
30+
}
31+
32+
private enum Op {
33+
34+
POW((byte) 0x01);
35+
36+
private final byte code;
37+
38+
Op(byte code) {
39+
this.code = code;
40+
}
41+
}
42+
1143
/**
1244
* Returns the value of the first argument raised to the power of the second argument.
1345
* @param a the base.
1446
* @param b the exponent.
1547
* @return the value {@code a}<sup>{@code b}</sup>.
1648
*/
1749
public static double pow(double a, double b, boolean useStrictMath) {
18-
return useStrictMath ? StrictMathWrapper.pow(a, b) : MathWrapper.pow(a, b);
50+
double result = MathWrapper.pow(a, b);
51+
double strictResult = StrictMathWrapper.pow(a, b);
52+
if (useStrictMath) {
53+
return strictResult;
54+
}
55+
final boolean isNoStrict = Double.compare(result, strictResult) != 0;
56+
Optional<Long> header = GlobalContext.getHeader();
57+
header.ifPresent(h -> {
58+
byte[] key = Bytes.concat(longToBytes(h), new byte[]{Op.POW.code},
59+
doubleToBytes(a), doubleToBytes(b));
60+
if (isNoStrict) {
61+
logger.info("{}\t{}\t{}\t{}\t{}\t{}", h, Op.POW.code, doubleToHex(a), doubleToHex(b),
62+
doubleToHex(result), doubleToHex(strictResult));
63+
}
64+
mathStore.ifPresent(s -> s.put(key, doubleToBytes(result)));
65+
strictMathStore.ifPresent(s -> s.put(key, doubleToBytes(strictResult)));
66+
});
67+
return result;
68+
}
69+
70+
static String doubleToHex(double input) {
71+
// Convert the starting value to the equivalent value in a long
72+
long doubleAsLong = Double.doubleToRawLongBits(input);
73+
// and then convert the long to a hex string
74+
return Long.toHexString(doubleAsLong);
75+
}
76+
77+
private static byte[] doubleToBytes(double value) {
78+
ByteBuffer buffer = ByteBuffer.allocate(Double.BYTES);
79+
buffer.putDouble(value);
80+
return buffer.array();
81+
}
82+
83+
private static byte[] longToBytes(long value) {
84+
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
85+
buffer.putLong(value);
86+
return buffer.array();
1987
}
2088

2189
/**

chainbase/src/main/java/org/tron/core/ChainBaseManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import org.tron.core.store.NullifierStore;
6060
import org.tron.core.store.ProposalStore;
6161
import org.tron.core.store.SectionBloomStore;
62+
import org.tron.core.store.StateRootStore;
6263
import org.tron.core.store.StorageRowStore;
6364
import org.tron.core.store.TransactionHistoryStore;
6465
import org.tron.core.store.TransactionRetStore;
@@ -233,6 +234,10 @@ public class ChainBaseManager {
233234
@Getter
234235
private SectionBloomStore sectionBloomStore;
235236

237+
@Autowired
238+
@Getter
239+
private StateRootStore stateRootStore;
240+
236241
@Autowired
237242
private DbStatService dbStatService;
238243

chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,16 @@ public String toString() {
335335
return toStringBuff.toString();
336336
}
337337

338+
public Sha256Hash getStateRoot() {
339+
ByteString stateRoot = this.block.getBlockHeader().getStateRoot();
340+
return stateRoot.isEmpty() ? Sha256Hash.ZERO_HASH : Sha256Hash.wrap(stateRoot);
341+
}
342+
343+
public void clearStateRoot() {
344+
BlockHeader blockHeader = this.block.getBlockHeader().toBuilder().clearStateRoot().build();
345+
this.block = this.block.toBuilder().setBlockHeader(blockHeader).build();
346+
}
347+
338348
public static class BlockId extends Sha256Hash {
339349

340350
private long num;

chainbase/src/main/java/org/tron/core/db/TronDatabase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public DbSourceInter<byte[]> getDbSource() {
7070
}
7171

7272
public void updateByBatch(Map<byte[], byte[]> rows) {
73+
this.updateByBatch(rows, writeOptions);
74+
}
75+
76+
public void updateByBatch(Map<byte[], byte[]> rows, WriteOptionsWrapper writeOptions) {
7377
this.dbSource.updateByBatch(rows, writeOptions);
7478
}
7579

chainbase/src/main/java/org/tron/core/db2/core/SnapshotManager.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import lombok.Getter;
2626
import lombok.Setter;
2727
import lombok.extern.slf4j.Slf4j;
28+
import org.springframework.beans.factory.ObjectFactory;
2829
import org.springframework.beans.factory.annotation.Autowired;
2930
import org.tron.common.error.TronDBException;
3031
import org.tron.common.es.ExecutorServiceManager;
@@ -80,6 +81,9 @@ public class SnapshotManager implements RevokingDatabase {
8081
@Getter
8182
private CheckTmpStore checkTmpStore;
8283

84+
@Autowired
85+
private ObjectFactory<CheckPointV2Store> checkPointV2Store;
86+
8387
@Setter
8488
private volatile int maxFlushCount = DEFAULT_MIN_FLUSH_COUNT;
8589

@@ -387,15 +391,14 @@ public void createCheckpoint() {
387391
}
388392
}
389393
if (isV2Open()) {
390-
String dbName = String.valueOf(System.currentTimeMillis());
391-
checkPointStore = getCheckpointDB(dbName);
394+
checkPointStore = checkPointV2Store.getObject();
392395
syncFlag = CommonParameter.getInstance().getStorage().isCheckpointSync();
393396
} else {
394397
checkPointStore = checkTmpStore;
395398
syncFlag = CommonParameter.getInstance().getStorage().isDbSync();
396399
}
397400

398-
checkPointStore.getDbSource().updateByBatch(batch.entrySet().stream()
401+
checkPointStore.updateByBatch(batch.entrySet().stream()
399402
.map(e -> Maps.immutableEntry(e.getKey().getBytes(), e.getValue().getBytes()))
400403
.collect(HashMap::new, (m, k) -> m.put(k.getKey(), k.getValue()), HashMap::putAll),
401404
WriteOptionsWrapper.getInstance().sync(syncFlag));
@@ -410,7 +413,7 @@ public void createCheckpoint() {
410413
}
411414

412415
private TronDatabase<byte[]> getCheckpointDB(String dbName) {
413-
return new CheckPointV2Store(CHECKPOINT_V2_DIR+"/"+dbName);
416+
return new CheckPointV2Store(CHECKPOINT_V2_DIR, dbName);
414417
}
415418

416419
public List<String> getCheckpointList() {
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package org.tron.core.service;
2+
3+
import com.google.common.collect.Sets;
4+
import com.google.common.collect.Streams;
5+
import com.google.common.primitives.Bytes;
6+
import com.google.common.primitives.Ints;
7+
import com.google.protobuf.ByteString;
8+
import java.io.IOException;
9+
import java.util.Arrays;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.Objects;
14+
import java.util.Optional;
15+
import java.util.Set;
16+
import java.util.TreeMap;
17+
import java.util.concurrent.atomic.AtomicReference;
18+
import java.util.stream.Collectors;
19+
import lombok.extern.slf4j.Slf4j;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.stereotype.Component;
22+
import org.tron.common.context.GlobalContext;
23+
import org.tron.common.error.TronDBException;
24+
import org.tron.common.utils.ByteArray;
25+
import org.tron.common.utils.MerkleRoot;
26+
import org.tron.common.utils.Pair;
27+
import org.tron.common.utils.Sha256Hash;
28+
import org.tron.core.db.TronDatabase;
29+
import org.tron.core.db2.common.Value;
30+
import org.tron.core.store.AccountAssetStore;
31+
import org.tron.core.store.CorruptedCheckpointStore;
32+
import org.tron.protos.Protocol;
33+
34+
@Slf4j(topic = "DB")
35+
@Component
36+
public class RootHashService {
37+
38+
private static final byte[] HEADER_KEY = "latest_block_header_number".getBytes();
39+
40+
private static Optional<CorruptedCheckpointStore> corruptedCheckpointStore = Optional.empty();
41+
private static AccountAssetStore assetStore;
42+
private static final List<String> stateDbs = Arrays.asList(
43+
"account", "account-asset", "asset-issue-v2",
44+
"code", "contract", "contract-state", "storage-row",
45+
"delegation", "DelegatedResource",
46+
"exchange-v2",
47+
"market_account", "market_order", "market_pair_price_to_order", "market_pair_to_price",
48+
"properties", "proposal",
49+
"votes", "witness", "witness_schedule"
50+
);
51+
private static final byte[] CURRENT_SHUFFLED_WITNESSES = "current_shuffled_witnesses".getBytes();
52+
private static final String FORK_PREFIX = "FORK_VERSION_";
53+
private static final String DONE_SUFFIX = "_DONE";
54+
private static final String ACCOUNT_VOTE_SUFFIX = "-account-vote";
55+
private static final Set<String> ignoredProperties = Sets.newHashSet(
56+
"VOTE_REWARD_RATE", "SINGLE_REPEAT", "NON_EXISTENT_ACCOUNT_TRANSFER_MIN",
57+
"ALLOW_TVM_ASSET_ISSUE", "ALLOW_TVM_STAKE",
58+
"MAX_VOTE_NUMBER", "MAX_FROZEN_NUMBER", "MAINTENANCE_TIME_INTERVAL",
59+
"LATEST_SOLIDIFIED_BLOCK_NUM", "BLOCK_NET_USAGE",
60+
"BLOCK_FILLED_SLOTS_INDEX", "BLOCK_FILLED_SLOTS_NUMBER", "BLOCK_FILLED_SLOTS");
61+
62+
@Autowired
63+
public RootHashService(@Autowired CorruptedCheckpointStore corruptedCheckpointStore,
64+
@Autowired AccountAssetStore assetStore) {
65+
RootHashService.corruptedCheckpointStore = Optional.ofNullable(corruptedCheckpointStore);
66+
RootHashService.assetStore = assetStore;
67+
}
68+
69+
public static Pair<Optional<Long>, Sha256Hash> getRootHash(Map<byte[], byte[]> rows) {
70+
try {
71+
Map<byte[], byte[]> preparedStateData = preparedStateData(rows);
72+
AtomicReference<Optional<Long>> height = new AtomicReference<>(Optional.empty());
73+
List<Sha256Hash> ids = Streams.stream(preparedStateData.entrySet()).parallel().map(entry -> {
74+
if (Arrays.equals(HEADER_KEY, entry.getKey())) {
75+
height.set(Optional.of(ByteArray.toLong(entry.getValue())));
76+
}
77+
return getHash(entry);
78+
}).sorted().collect(Collectors.toList());
79+
Sha256Hash actual = MerkleRoot.root(ids);
80+
long num = height.get().orElseThrow(() -> new TronDBException("blockNum is null"));
81+
Optional<Sha256Hash> expected = GlobalContext.popBlockHash(num);
82+
if (expected.isPresent() && !Objects.equals(expected.get(), actual)) {
83+
corruptedCheckpointStore.ifPresent(TronDatabase::reset);
84+
corruptedCheckpointStore.ifPresent(store -> store.updateByBatch(rows));
85+
throw new TronDBException(String.format(
86+
"Root hash mismatch for blockNum: %s, expected: %s, actual: %s",
87+
num, expected, actual));
88+
}
89+
return new Pair<>(height.get(), actual);
90+
} catch (IOException e) {
91+
throw new TronDBException(e);
92+
}
93+
}
94+
95+
private static Map<byte[], byte[]> preparedStateData(Map<byte[], byte[]> rows)
96+
throws IOException {
97+
Map<byte[], byte[]> preparedStateData = new HashMap<>(rows.size());
98+
for (Map.Entry<byte[], byte[]> e : rows.entrySet()) {
99+
byte[] key = e.getKey();
100+
String dbName = simpleDecode(key);
101+
if (!stateDbs.contains(dbName)) {
102+
continue;
103+
}
104+
byte[] realKey = Arrays.copyOfRange(key, dbName.getBytes().length + Integer.BYTES,
105+
key.length);
106+
if ("witness_schedule".equals(dbName) && Arrays.equals(realKey, CURRENT_SHUFFLED_WITNESSES)) {
107+
continue;
108+
}
109+
if ("properties".equals(dbName)) {
110+
String keyStr = new String(realKey);
111+
if (ignoredProperties.contains(keyStr)
112+
|| keyStr.startsWith(FORK_PREFIX) || keyStr.endsWith(DONE_SUFFIX)) {
113+
continue;
114+
}
115+
}
116+
byte[] value = e.getValue();
117+
byte[] realValue = value.length == 1 ? null : Arrays.copyOfRange(value, 1, value.length);
118+
if (realValue != null) {
119+
if ("witness".equals(dbName)) {
120+
realValue = Protocol.Witness.parseFrom(realValue)
121+
.toBuilder().clearTotalMissed()
122+
.build().toByteArray(); // ignore totalMissed
123+
}
124+
if ("account".equals(dbName)) {
125+
Protocol.Account account = Protocol.Account.parseFrom(realValue);
126+
Map<String, Long> assets = new TreeMap<>(assetStore.getAllAssets(account));
127+
assets.entrySet().removeIf(entry -> entry.getValue() == 0);
128+
realValue = account.toBuilder().clearAsset().clearAssetV2().clearAssetOptimized()
129+
.putAllAssetV2(assets)
130+
.build().toByteArray();
131+
}
132+
if ("delegation".equals(dbName) && new String(key).endsWith(ACCOUNT_VOTE_SUFFIX)) {
133+
Protocol.Account account = Protocol.Account.parseFrom(realValue);
134+
realValue = Protocol.Account.newBuilder().addAllVotes(account.getVotesList())
135+
.build().toByteArray();
136+
}
137+
}
138+
if (realValue != null) {
139+
preparedStateData.put(realKey, realValue);
140+
} else {
141+
if (Value.Operator.DELETE.getValue() != value[0]) {
142+
preparedStateData.put(realKey, ByteString.EMPTY.toByteArray());
143+
}
144+
}
145+
}
146+
return preparedStateData;
147+
}
148+
149+
private static String simpleDecode(byte[] bytes) {
150+
byte[] lengthBytes = Arrays.copyOf(bytes, Integer.BYTES);
151+
int length = Ints.fromByteArray(lengthBytes);
152+
byte[] value = Arrays.copyOfRange(bytes, Integer.BYTES, Integer.BYTES + length);
153+
return new String(value);
154+
}
155+
156+
private static Sha256Hash getHash(Map.Entry<byte[], byte[]> entry) {
157+
return Sha256Hash.of(true, Bytes.concat(entry.getKey(), entry.getValue()));
158+
}
159+
}

0 commit comments

Comments
 (0)