Skip to content

Commit 5806e22

Browse files
committed
test(vm): cover TIP-2935 activation, deploy, and per-block writes
ProposalUtilTest: validateCheck for ALLOW_TVM_PRAGUE pre/post-fork and re-activation. HistoryBlockHashUtilTest + IntegrationTest: deploy/write paths, install marker, foreign-state collision.
1 parent 38d1cd8 commit 5806e22

3 files changed

Lines changed: 528 additions & 0 deletions

File tree

framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ public void validateCheck() {
448448

449449
testAllowTvmSelfdestructRestrictionProposal();
450450

451+
testAllowTvmPragueProposal();
452+
451453
forkUtils.getManager().getDynamicPropertiesStore()
452454
.statsByVersion(ForkBlockVersionEnum.ENERGY_LIMIT.getValue(), stats);
453455
forkUtils.reset();
@@ -719,6 +721,56 @@ private void testAllowTvmSelfdestructRestrictionProposal() {
719721
}
720722
}
721723

724+
private void testAllowTvmPragueProposal() {
725+
byte[] stats = new byte[27];
726+
forkUtils.getManager().getDynamicPropertiesStore()
727+
.statsByVersion(ForkBlockVersionEnum.VERSION_4_8_2.getValue(), stats);
728+
try {
729+
ProposalUtil.validator(dynamicPropertiesStore, forkUtils,
730+
ProposalType.ALLOW_TVM_PRAGUE.getCode(), 1);
731+
Assert.fail();
732+
} catch (ContractValidateException e) {
733+
Assert.assertEquals(
734+
"Bad chain parameter id [ALLOW_TVM_PRAGUE]",
735+
e.getMessage());
736+
}
737+
738+
long maintenanceTimeInterval = forkUtils.getManager().getDynamicPropertiesStore()
739+
.getMaintenanceTimeInterval();
740+
741+
long hardForkTime =
742+
((ForkBlockVersionEnum.VERSION_4_8_2.getHardForkTime() - 1) / maintenanceTimeInterval + 1)
743+
* maintenanceTimeInterval;
744+
forkUtils.getManager().getDynamicPropertiesStore()
745+
.saveLatestBlockHeaderTimestamp(hardForkTime + 1);
746+
747+
stats = new byte[27];
748+
Arrays.fill(stats, (byte) 1);
749+
forkUtils.getManager().getDynamicPropertiesStore()
750+
.statsByVersion(ForkBlockVersionEnum.VERSION_4_8_2.getValue(), stats);
751+
752+
try {
753+
ProposalUtil.validator(dynamicPropertiesStore, forkUtils,
754+
ProposalType.ALLOW_TVM_PRAGUE.getCode(), 2);
755+
Assert.fail();
756+
} catch (ContractValidateException e) {
757+
Assert.assertEquals(
758+
"This value[ALLOW_TVM_PRAGUE] is only allowed to be 1",
759+
e.getMessage());
760+
}
761+
762+
dynamicPropertiesStore.saveAllowTvmPrague(1);
763+
try {
764+
ProposalUtil.validator(dynamicPropertiesStore, forkUtils,
765+
ProposalType.ALLOW_TVM_PRAGUE.getCode(), 1);
766+
Assert.fail();
767+
} catch (ContractValidateException e) {
768+
Assert.assertEquals(
769+
"[ALLOW_TVM_PRAGUE] has been valid, no need to propose again",
770+
e.getMessage());
771+
}
772+
}
773+
722774
private void testAllowMarketTransaction() {
723775
ThrowingRunnable off = () -> ProposalUtil.validator(dynamicPropertiesStore, forkUtils,
724776
ProposalType.ALLOW_MARKET_TRANSACTION.getCode(), 0);
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package org.tron.core.db;
2+
3+
import static org.junit.Assert.assertArrayEquals;
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertFalse;
6+
import static org.junit.Assert.assertNotNull;
7+
import static org.junit.Assert.assertTrue;
8+
9+
import com.google.protobuf.ByteString;
10+
import java.util.Arrays;
11+
import org.junit.Before;
12+
import org.junit.Test;
13+
import org.tron.common.BaseTest;
14+
import org.tron.common.TestConstants;
15+
import org.tron.common.runtime.vm.DataWord;
16+
import org.tron.common.utils.Sha256Hash;
17+
import org.tron.core.capsule.AccountCapsule;
18+
import org.tron.core.capsule.BlockCapsule;
19+
import org.tron.core.capsule.CodeCapsule;
20+
import org.tron.core.capsule.ContractCapsule;
21+
import org.tron.core.capsule.StorageRowCapsule;
22+
import org.tron.core.config.args.Args;
23+
import org.tron.core.store.DynamicPropertiesStore;
24+
import org.tron.core.store.StoreFactory;
25+
import org.tron.core.vm.repository.RepositoryImpl;
26+
import org.tron.protos.Protocol;
27+
import org.tron.protos.contract.SmartContractOuterClass.SmartContract;
28+
29+
/**
30+
* TIP-2935 end-to-end: activation deploys the contract, subsequent blocks
31+
* populate the ring buffer via the pre-tx hook, and the VM repository reads
32+
* back written hashes through the same {@code Storage.compose()} layer that
33+
* production {@code SLOAD} uses.
34+
*/
35+
public class HistoryBlockHashIntegrationTest extends BaseTest {
36+
37+
static {
38+
Args.setParam(new String[]{"--output-directory", dbPath()}, TestConstants.TEST_CONF);
39+
}
40+
41+
@Before
42+
public void resetState() {
43+
byte[] addr = HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS;
44+
chainBaseManager.getDynamicPropertiesStore().saveAllowTvmPrague(0L);
45+
chainBaseManager.getDynamicPropertiesStore().saveBlockHashHistoryInstalled(0L);
46+
chainBaseManager.getCodeStore().delete(addr);
47+
chainBaseManager.getContractStore().delete(addr);
48+
chainBaseManager.getAccountStore().delete(addr);
49+
for (long slot : new long[]{0L, 99L, 499L, 776L}) {
50+
chainBaseManager.getStorageRowStore()
51+
.delete(HistoryBlockHashUtil.composeStorageKey(slot, addr));
52+
}
53+
}
54+
55+
@Test
56+
public void activationDeploysContractAndFlagIsSet() {
57+
DynamicPropertiesStore dps = chainBaseManager.getDynamicPropertiesStore();
58+
byte[] addr = HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS;
59+
60+
assertEquals(0L, dps.getAllowTvmPrague());
61+
assertFalse(chainBaseManager.getCodeStore().has(addr));
62+
63+
dps.saveAllowTvmPrague(1L);
64+
HistoryBlockHashUtil.deploy(dbManager);
65+
66+
assertEquals(1L, dps.getAllowTvmPrague());
67+
assertTrue(chainBaseManager.getCodeStore().has(addr));
68+
CodeCapsule code = chainBaseManager.getCodeStore().get(addr);
69+
assertNotNull(code);
70+
assertArrayEquals(HistoryBlockHashUtil.HISTORY_STORAGE_CODE, code.getData());
71+
}
72+
73+
@Test
74+
public void writeAfterActivationFillsStorageSlot() {
75+
chainBaseManager.getDynamicPropertiesStore().saveAllowTvmPrague(1L);
76+
HistoryBlockHashUtil.deploy(dbManager);
77+
78+
long blockNum = 500L;
79+
byte[] parentHash = new byte[32];
80+
Arrays.fill(parentHash, (byte) 0x5a);
81+
BlockCapsule block = new BlockCapsule(
82+
blockNum,
83+
Sha256Hash.wrap(parentHash),
84+
System.currentTimeMillis(),
85+
ByteString.copyFrom(new byte[21]));
86+
87+
HistoryBlockHashUtil.write(dbManager, block);
88+
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());
94+
}
95+
96+
@Test
97+
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.
101+
chainBaseManager.getDynamicPropertiesStore().saveAllowTvmPrague(1L);
102+
HistoryBlockHashUtil.deploy(dbManager);
103+
104+
long blockNum = 777L;
105+
byte[] parentHash = new byte[32];
106+
Arrays.fill(parentHash, (byte) 0x77);
107+
BlockCapsule block = new BlockCapsule(
108+
blockNum,
109+
Sha256Hash.wrap(parentHash),
110+
System.currentTimeMillis(),
111+
ByteString.copyFrom(new byte[21]));
112+
HistoryBlockHashUtil.write(dbManager, block);
113+
114+
RepositoryImpl repo = RepositoryImpl.createRoot(StoreFactory.getInstance());
115+
116+
// (777 - 1) % 8191 = 776
117+
DataWord slotKey = new DataWord(776L);
118+
DataWord readBack = repo.getStorageValue(
119+
HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS, slotKey);
120+
121+
assertNotNull("VM repository failed to read stored hash", readBack);
122+
assertArrayEquals("VM read-back != direct-written hash",
123+
parentHash, readBack.getData());
124+
}
125+
126+
@Test
127+
public void noWriteBeforeActivation() {
128+
assertEquals(0L,
129+
chainBaseManager.getDynamicPropertiesStore().getAllowTvmPrague());
130+
assertFalse(chainBaseManager.getDynamicPropertiesStore()
131+
.isBlockHashHistoryInstalled());
132+
133+
long blockNum = 100L;
134+
byte[] parentHash = new byte[32];
135+
Arrays.fill(parentHash, (byte) 0xff);
136+
BlockCapsule block = new BlockCapsule(
137+
blockNum,
138+
Sha256Hash.wrap(parentHash),
139+
System.currentTimeMillis(),
140+
ByteString.copyFrom(new byte[21]));
141+
142+
// Manager calls write() unconditionally; the install marker stays 0
143+
// before activation, so write() must early-return.
144+
HistoryBlockHashUtil.write(dbManager, block);
145+
146+
byte[] storageKey = HistoryBlockHashUtil.composeStorageKey(
147+
99L, HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS);
148+
assertFalse(chainBaseManager.getStorageRowStore().has(storageKey));
149+
}
150+
151+
/**
152+
* Block 1 is the first block to go through {@code applyBlock -> processBlock}.
153+
* Its parent is the genesis block, so slot 0 must hold the genesis block hash.
154+
*/
155+
@Test
156+
public void writeForBlock1StoresGenesisHashAtSlot0() {
157+
chainBaseManager.getDynamicPropertiesStore().saveAllowTvmPrague(1L);
158+
HistoryBlockHashUtil.deploy(dbManager);
159+
160+
byte[] genesisHash = new byte[32];
161+
Arrays.fill(genesisHash, (byte) 0x01);
162+
BlockCapsule block1 = new BlockCapsule(
163+
1L,
164+
Sha256Hash.wrap(genesisHash),
165+
System.currentTimeMillis(),
166+
ByteString.copyFrom(new byte[21]));
167+
168+
HistoryBlockHashUtil.write(dbManager, block1);
169+
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());
175+
}
176+
177+
/**
178+
* Genesis never goes through {@code applyBlock}, but the guard keeps
179+
* {@code (0 - 1) % 8191 = -1} from ever corrupting a slot if it ever did.
180+
*/
181+
@Test
182+
public void writeIsNoOpForGenesisBlock() {
183+
chainBaseManager.getDynamicPropertiesStore().saveAllowTvmPrague(1L);
184+
HistoryBlockHashUtil.deploy(dbManager);
185+
186+
byte[] zeroHash = new byte[32];
187+
BlockCapsule genesis = new BlockCapsule(
188+
0L,
189+
Sha256Hash.wrap(zeroHash),
190+
0L,
191+
ByteString.copyFrom(new byte[21]));
192+
193+
HistoryBlockHashUtil.write(dbManager, genesis);
194+
195+
byte[] slot0Key = HistoryBlockHashUtil.composeStorageKey(
196+
0L, HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS);
197+
assertFalse(chainBaseManager.getStorageRowStore().has(slot0Key));
198+
}
199+
200+
/**
201+
* Collision guard: if foreign bytecode already sits at the canonical address
202+
* (theoretically impossible short of a hash pre-image), activation must skip
203+
* the deploy entirely — leaving the foreign code intact and writing nothing
204+
* to ContractStore / AccountStore — rather than silently merging into a
205+
* broken contract. Same expectation applies to foreign contract metadata.
206+
*/
207+
@Test
208+
public void deploySkipsWhenForeignBytecodePresent() {
209+
byte[] addr = HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS;
210+
byte[] foreignCode = new byte[]{0x60, 0x00};
211+
chainBaseManager.getCodeStore().put(addr, new CodeCapsule(foreignCode));
212+
213+
HistoryBlockHashUtil.deploy(dbManager);
214+
215+
assertArrayEquals(foreignCode,
216+
chainBaseManager.getCodeStore().get(addr).getData());
217+
assertFalse(chainBaseManager.getContractStore().has(addr));
218+
assertFalse(chainBaseManager.getAccountStore().has(addr));
219+
}
220+
221+
@Test
222+
public void deploySkipsWhenForeignContractPresent() {
223+
byte[] addr = HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS;
224+
SmartContract foreign = SmartContract.newBuilder()
225+
.setName("NotBlockHashHistory")
226+
.setContractAddress(ByteString.copyFrom(addr))
227+
.setOriginAddress(ByteString.copyFrom(addr))
228+
.build();
229+
chainBaseManager.getContractStore().put(addr, new ContractCapsule(foreign));
230+
231+
HistoryBlockHashUtil.deploy(dbManager);
232+
233+
assertEquals("NotBlockHashHistory",
234+
chainBaseManager.getContractStore().get(addr).getInstance().getName());
235+
assertFalse(chainBaseManager.getCodeStore().has(addr));
236+
assertFalse(chainBaseManager.getAccountStore().has(addr));
237+
}
238+
239+
/**
240+
* Anyone can transfer TRX to {@code HISTORY_STORAGE_ADDRESS} before the
241+
* proposal fires, leaving an EOA at the canonical address. Activation must
242+
* upgrade the type to {@code Contract} in place — preserving balance —
243+
* rather than failing or zeroing the account.
244+
*/
245+
@Test
246+
public void deployUpgradesPreExistingNormalAccountPreservingBalance() {
247+
byte[] addr = HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS;
248+
long balance = 12345L;
249+
AccountCapsule eoa = new AccountCapsule(
250+
ByteString.copyFrom(addr), Protocol.AccountType.Normal);
251+
eoa.setBalance(balance);
252+
chainBaseManager.getAccountStore().put(addr, eoa);
253+
254+
HistoryBlockHashUtil.deploy(dbManager);
255+
256+
AccountCapsule after = chainBaseManager.getAccountStore().get(addr);
257+
assertEquals(Protocol.AccountType.Contract, after.getType());
258+
assertEquals(balance, after.getBalance());
259+
assertTrue(chainBaseManager.getCodeStore().has(addr));
260+
assertTrue(chainBaseManager.getContractStore().has(addr));
261+
}
262+
263+
}

0 commit comments

Comments
 (0)