11package org .tron .core .db ;
22
3- import static java .lang .System .arraycopy ;
4-
53import com .google .protobuf .ByteString ;
64import lombok .extern .slf4j .Slf4j ;
75import org .bouncycastle .util .encoders .Hex ;
8- import org .tron .common .crypto .Hash ;
96import org .tron .common .runtime .vm .DataWord ;
107import org .tron .core .capsule .AccountCapsule ;
118import org .tron .core .capsule .BlockCapsule ;
129import org .tron .core .capsule .CodeCapsule ;
1310import org .tron .core .capsule .ContractCapsule ;
14- import org .tron .core .capsule . StorageRowCapsule ;
11+ import org .tron .core .vm . program . Storage ;
1512import org .tron .protos .Protocol ;
13+ import org .tron .protos .Protocol .Account ;
1614import org .tron .protos .contract .SmartContractOuterClass .SmartContract ;
1715
1816/**
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" )
3327public 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}
0 commit comments