Skip to content

Commit 36510cb

Browse files
committed
refactor(vm): align TIP-2935 deploy with create2 and use Storage for writes
1 parent 213a6fa commit 36510cb

3 files changed

Lines changed: 117 additions & 122 deletions

File tree

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
package org.tron.core.db;
22

3-
import static java.lang.System.arraycopy;
4-
53
import com.google.protobuf.ByteString;
64
import lombok.extern.slf4j.Slf4j;
75
import org.bouncycastle.util.encoders.Hex;
8-
import org.tron.common.crypto.Hash;
96
import org.tron.common.runtime.vm.DataWord;
107
import org.tron.core.capsule.AccountCapsule;
118
import org.tron.core.capsule.BlockCapsule;
129
import org.tron.core.capsule.CodeCapsule;
1310
import org.tron.core.capsule.ContractCapsule;
14-
import org.tron.core.capsule.StorageRowCapsule;
11+
import org.tron.core.vm.program.Storage;
1512
import org.tron.protos.Protocol;
13+
import org.tron.protos.Protocol.Account;
1614
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;
1715

1816
/**
@@ -21,13 +19,9 @@
2119
* <p>Approach A1 — at proposal activation, deploy the BlockHashHistory bytecode
2220
* and minimal contract/account metadata via direct store writes; on every block
2321
* (before the tx loop) write the parent block hash to slot
24-
* {@code (blockNum - 1) % HISTORY_SERVE_WINDOW} via direct StorageRowStore write.
22+
* {@code (blockNum - 1) % HISTORY_SERVE_WINDOW} via {@link Storage}.
2523
* No VM execution is needed for {@code set()}; user contracts read via normal
2624
* STATICCALL which executes the deployed bytecode.
27-
*
28-
* <p>Storage key layout replicates {@code Storage.compose()} for
29-
* {@code contractVersion=0}: first 16 bytes of {@code sha3(address)} followed by
30-
* the last 16 bytes of the 32-byte slot key.
3125
*/
3226
@Slf4j(topic = "DB")
3327
public class HistoryBlockHashUtil {
@@ -38,6 +32,14 @@ public class HistoryBlockHashUtil {
3832
public static final byte[] HISTORY_STORAGE_ADDRESS =
3933
Hex.decode("410000f90827f1c53a10cb7a02335b175320002935");
4034

35+
// Recovered sender of the EIP-2935 presigned (no-private-key) deploy
36+
// transaction on Ethereum, in TRON 21-byte form. Used as {@code originAddress}
37+
// on the deployed SmartContract so the deployer-of-record matches Ethereum
38+
// byte-for-byte; cross-chain tooling that inspects this field sees the same
39+
// address on both sides.
40+
public static final byte[] HISTORY_DEPLOYER_ADDRESS =
41+
Hex.decode("413462413Af4609098e1E27A490f554f260213D685");
42+
4143
// TIP-2935 runtime bytecode (83 bytes, no constructor prefix). Identical to
4244
// EIP-2935's so the same address resolves to the same code on both chains.
4345
public static final byte[] HISTORY_STORAGE_CODE = Hex.decode(
@@ -46,27 +48,36 @@ public class HistoryBlockHashUtil {
4648
+ "611fff81430311604257611fff9006545f5260205ff3"
4749
+ "5b5f5ffd5b5f35611fff60014303065500");
4850

49-
public static final String BLOCK_HASH_HISTORY_NAME = "BlockHashHistory";
50-
51-
private static final int PREFIX_BYTES = 16;
51+
public static final String HISTORY_STORAGE_NAME = "BlockHashHistory";
52+
53+
// Account template for the new-account branch of {@code deploy()} (no prior
54+
// state at the canonical address). Equivalent to create2's
55+
// {@code createAccount(addr, name, Contract)}: only type, accountName, and
56+
// address are set. The pre-existing-account branch never uses this template
57+
// — it mutates the existing capsule in place to preserve balance / asset
58+
// state, mirroring the CREATE2 collision path. Safe to share: the proto is
59+
// immutable, and AccountCapsule mutations rebuild via {@code toBuilder}.
60+
private static final Account HISTORY_STORAGE_ACCOUNT = Account.newBuilder()
61+
.setType(Protocol.AccountType.Contract)
62+
.setAccountName(ByteString.copyFromUtf8(HISTORY_STORAGE_NAME))
63+
.setAddress(ByteString.copyFrom(HISTORY_STORAGE_ADDRESS))
64+
.build();
65+
66+
// SmartContract template: every field is fixed at activation time, so the
67+
// proto is immutable and shared across calls. Mirrors the create2 path's
68+
// shape (version=0, contractAddress, consumeUserResourcePercent=100,
69+
// originAddress) plus a descriptive name. No trxHash since activation is
70+
// not a transaction.
71+
private static final SmartContract HISTORY_STORAGE_CONTRACT = SmartContract.newBuilder()
72+
.setName(HISTORY_STORAGE_NAME)
73+
.setContractAddress(ByteString.copyFrom(HISTORY_STORAGE_ADDRESS))
74+
.setOriginAddress(ByteString.copyFrom(HISTORY_DEPLOYER_ADDRESS))
75+
.setConsumeUserResourcePercent(100L)
76+
.build();
5277

5378
private HistoryBlockHashUtil() {
5479
}
5580

56-
/**
57-
* Compose the raw StorageRowStore key for {@code (address, slot)} at
58-
* {@code contractVersion=0}. Must match {@code Storage.compose()} byte-for-byte
59-
* so that a subsequent VM SLOAD(slot) at this address reads back the written value.
60-
*/
61-
public static byte[] composeStorageKey(long slot, byte[] address) {
62-
byte[] addrHash = Hash.sha3(address);
63-
byte[] slotKey = new DataWord(slot).getData();
64-
byte[] result = new byte[32];
65-
arraycopy(addrHash, 0, result, 0, PREFIX_BYTES);
66-
arraycopy(slotKey, PREFIX_BYTES, result, PREFIX_BYTES, PREFIX_BYTES);
67-
return result;
68-
}
69-
7081
/**
7182
* Deploy the TIP-2935 BlockHashHistory contract at {@code HISTORY_STORAGE_ADDRESS}.
7283
* If foreign code or contract metadata already sits at the canonical address,
@@ -77,7 +88,9 @@ public static byte[] composeStorageKey(long slot, byte[] address) {
7788
* the only realistic way that branch fires, so it's belt-and-braces. A
7889
* pre-existing non-contract account at the address is the common case (anyone
7990
* can transfer TRX there to activate it as an EOA), so we upgrade its type to
80-
* {@code Contract} in place, preserving balance/asset state.
91+
* {@code Contract} in place — matching the CREATE2 collision branch
92+
* ({@code updateAccountType} + {@code clearDelegatedResource}) and preserving
93+
* balance/asset state.
8194
*
8295
* <p>Called only from {@code ProposalService} inside maintenance-time block
8396
* processing. Proposal validation rejects re-activation, so this runs at most
@@ -86,28 +99,27 @@ public static byte[] composeStorageKey(long slot, byte[] address) {
8699
* the {@code saveAllowTvmPrague(1)} write back atomically.
87100
*/
88101
public static void deploy(Manager manager) {
89-
byte[] addr = HISTORY_STORAGE_ADDRESS;
90-
if (manager.getCodeStore().has(addr) || manager.getContractStore().has(addr)) {
102+
if (manager.getCodeStore().has(HISTORY_STORAGE_ADDRESS)
103+
|| manager.getContractStore().has(HISTORY_STORAGE_ADDRESS)) {
91104
logger.warn("TIP-2935: foreign state at {}, skipping deploy",
92-
Hex.toHexString(addr));
105+
Hex.toHexString(HISTORY_STORAGE_ADDRESS));
93106
return;
94107
}
95108

96-
manager.getCodeStore().put(addr, new CodeCapsule(HISTORY_STORAGE_CODE));
97-
manager.getContractStore().put(addr, new ContractCapsule(SmartContract.newBuilder()
98-
.setName(BLOCK_HASH_HISTORY_NAME)
99-
.setContractAddress(ByteString.copyFrom(addr))
100-
.setOriginAddress(ByteString.copyFrom(addr))
101-
.setConsumeUserResourcePercent(100L)
102-
.setOriginEnergyLimit(0L)
103-
.build()));
104-
105-
boolean preExistingAccount = manager.getAccountStore().has(addr);
106-
AccountCapsule account = preExistingAccount
107-
? manager.getAccountStore().get(addr)
108-
: new AccountCapsule(ByteString.copyFrom(addr), Protocol.AccountType.Contract);
109-
account.updateAccountType(Protocol.AccountType.Contract);
110-
manager.getAccountStore().put(addr, account);
109+
manager.getCodeStore().put(HISTORY_STORAGE_ADDRESS,
110+
new CodeCapsule(HISTORY_STORAGE_CODE));
111+
manager.getContractStore().put(HISTORY_STORAGE_ADDRESS,
112+
new ContractCapsule(HISTORY_STORAGE_CONTRACT));
113+
114+
AccountCapsule account = manager.getAccountStore().get(HISTORY_STORAGE_ADDRESS);
115+
boolean accountExisting = account != null;
116+
if (!accountExisting) {
117+
account = new AccountCapsule(HISTORY_STORAGE_ACCOUNT);
118+
} else {
119+
account.updateAccountType(Protocol.AccountType.Contract);
120+
account.clearDelegatedResource();
121+
}
122+
manager.getAccountStore().put(HISTORY_STORAGE_ADDRESS, account);
111123

112124
// Flip the install marker only after all three store writes succeed; this
113125
// gates the per-block write() path so a skipped deploy never mutates
@@ -116,7 +128,7 @@ public static void deploy(Manager manager) {
116128
manager.getDynamicPropertiesStore().saveBlockHashHistoryInstalled(1L);
117129

118130
logger.info("TIP-2935: deployed BlockHashHistory at {} (preExistingAccount={})",
119-
Hex.toHexString(addr), preExistingAccount);
131+
Hex.toHexString(HISTORY_STORAGE_ADDRESS), accountExisting);
120132
}
121133

122134
/**
@@ -139,8 +151,8 @@ public static void write(Manager manager, BlockCapsule block) {
139151
return;
140152
}
141153
long slot = (block.getNum() - 1) % HISTORY_SERVE_WINDOW;
142-
byte[] storageKey = composeStorageKey(slot, HISTORY_STORAGE_ADDRESS);
143-
byte[] parentHash = block.getParentHash().getBytes();
144-
manager.getStorageRowStore().put(storageKey, new StorageRowCapsule(storageKey, parentHash));
154+
Storage storage = new Storage(HISTORY_STORAGE_ADDRESS, manager.getStorageRowStore());
155+
storage.put(new DataWord(slot), new DataWord(block.getParentHash().getBytes()));
156+
storage.commit();
145157
}
146158
}

framework/src/test/java/org/tron/core/db/HistoryBlockHashIntegrationTest.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.Assert.assertEquals;
55
import static org.junit.Assert.assertFalse;
66
import static org.junit.Assert.assertNotNull;
7+
import static org.junit.Assert.assertNull;
78
import static org.junit.Assert.assertTrue;
89

910
import com.google.protobuf.ByteString;
@@ -18,10 +19,10 @@
1819
import org.tron.core.capsule.BlockCapsule;
1920
import org.tron.core.capsule.CodeCapsule;
2021
import org.tron.core.capsule.ContractCapsule;
21-
import org.tron.core.capsule.StorageRowCapsule;
2222
import org.tron.core.config.args.Args;
2323
import org.tron.core.store.DynamicPropertiesStore;
2424
import org.tron.core.store.StoreFactory;
25+
import org.tron.core.vm.program.Storage;
2526
import org.tron.core.vm.repository.RepositoryImpl;
2627
import org.tron.protos.Protocol;
2728
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;
@@ -46,10 +47,18 @@ public void resetState() {
4647
chainBaseManager.getCodeStore().delete(addr);
4748
chainBaseManager.getContractStore().delete(addr);
4849
chainBaseManager.getAccountStore().delete(addr);
50+
Storage storage = new Storage(addr, chainBaseManager.getStorageRowStore());
4951
for (long slot : new long[]{0L, 99L, 499L, 776L}) {
50-
chainBaseManager.getStorageRowStore()
51-
.delete(HistoryBlockHashUtil.composeStorageKey(slot, addr));
52+
storage.put(new DataWord(slot), DataWord.ZERO());
5253
}
54+
storage.commit();
55+
}
56+
57+
private DataWord readSlot(long slot) {
58+
Storage storage = new Storage(
59+
HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS,
60+
chainBaseManager.getStorageRowStore());
61+
return storage.getValue(new DataWord(slot));
5362
}
5463

5564
@Test
@@ -86,18 +95,15 @@ public void writeAfterActivationFillsStorageSlot() {
8695

8796
HistoryBlockHashUtil.write(dbManager, block);
8897

89-
byte[] storageKey = HistoryBlockHashUtil.composeStorageKey(
90-
499L, HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS);
91-
StorageRowCapsule row = chainBaseManager.getStorageRowStore().get(storageKey);
92-
assertNotNull(row);
93-
assertArrayEquals(parentHash, row.getValue());
98+
DataWord readBack = readSlot(499L);
99+
assertNotNull(readBack);
100+
assertArrayEquals(parentHash, readBack.getData());
94101
}
95102

96103
@Test
97104
public void vmRepositoryReadsBackWrittenHash() {
98-
// Full round-trip: direct-write -> VM Repository -> getStorageValue.
99-
// Proves Storage.compose() on the read side agrees with
100-
// HistoryBlockHashUtil.composeStorageKey() on the write side.
105+
// Full round-trip: direct-write through Storage -> VM Repository -> getStorageValue.
106+
// Proves write and read go through the same Storage.compose() layer.
101107
chainBaseManager.getDynamicPropertiesStore().saveAllowTvmPrague(1L);
102108
HistoryBlockHashUtil.deploy(dbManager);
103109

@@ -143,9 +149,7 @@ public void noWriteBeforeActivation() {
143149
// before activation, so write() must early-return.
144150
HistoryBlockHashUtil.write(dbManager, block);
145151

146-
byte[] storageKey = HistoryBlockHashUtil.composeStorageKey(
147-
99L, HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS);
148-
assertFalse(chainBaseManager.getStorageRowStore().has(storageKey));
152+
assertNull(readSlot(99L));
149153
}
150154

151155
/**
@@ -167,11 +171,9 @@ public void writeForBlock1StoresGenesisHashAtSlot0() {
167171

168172
HistoryBlockHashUtil.write(dbManager, block1);
169173

170-
byte[] key = HistoryBlockHashUtil.composeStorageKey(
171-
0L, HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS);
172-
StorageRowCapsule row = chainBaseManager.getStorageRowStore().get(key);
173-
assertNotNull(row);
174-
assertArrayEquals(genesisHash, row.getValue());
174+
DataWord readBack = readSlot(0L);
175+
assertNotNull(readBack);
176+
assertArrayEquals(genesisHash, readBack.getData());
175177
}
176178

177179
/**
@@ -192,9 +194,7 @@ public void writeIsNoOpForGenesisBlock() {
192194

193195
HistoryBlockHashUtil.write(dbManager, genesis);
194196

195-
byte[] slot0Key = HistoryBlockHashUtil.composeStorageKey(
196-
0L, HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS);
197-
assertFalse(chainBaseManager.getStorageRowStore().has(slot0Key));
197+
assertNull(readSlot(0L));
198198
}
199199

200200
/**

0 commit comments

Comments
 (0)