|
| 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