|
8 | 8 | import static org.junit.Assert.assertTrue; |
9 | 9 |
|
10 | 10 | import com.google.protobuf.ByteString; |
| 11 | +import java.lang.reflect.Field; |
11 | 12 | import java.util.Arrays; |
| 13 | +import java.util.concurrent.atomic.AtomicReference; |
12 | 14 | import org.junit.Before; |
13 | 15 | import org.junit.Test; |
| 16 | +import org.mockito.Mockito; |
14 | 17 | import org.tron.common.BaseTest; |
15 | 18 | import org.tron.common.TestConstants; |
| 19 | +import org.tron.common.crypto.ECKey; |
16 | 20 | import org.tron.common.runtime.vm.DataWord; |
17 | 21 | import org.tron.common.utils.Sha256Hash; |
| 22 | +import org.tron.consensus.base.Param; |
18 | 23 | import org.tron.core.capsule.AccountCapsule; |
19 | 24 | import org.tron.core.capsule.BlockCapsule; |
20 | 25 | import org.tron.core.capsule.CodeCapsule; |
21 | 26 | import org.tron.core.capsule.ContractCapsule; |
22 | 27 | import org.tron.core.config.args.Args; |
| 28 | +import org.tron.core.db.accountstate.callback.AccountStateCallBack; |
23 | 29 | import org.tron.core.store.DynamicPropertiesStore; |
24 | 30 | import org.tron.core.store.StoreFactory; |
25 | 31 | import org.tron.core.vm.program.Storage; |
@@ -245,6 +251,64 @@ public void deploySkipsWhenForeignContractPresent() { |
245 | 251 | * upgrade the type to {@code Contract} in place — preserving balance — |
246 | 252 | * rather than failing or zeroing the account. |
247 | 253 | */ |
| 254 | + /** |
| 255 | + * SR / validator parity: the producer's {@code generateBlock} simulation |
| 256 | + * loop and the validator's {@code processBlock} apply loop must see the |
| 257 | + * same storage state when transactions hit {@code HISTORY_STORAGE_ADDRESS}. |
| 258 | + * That requires {@link HistoryBlockHashUtil#write} to run before the tx |
| 259 | + * loop on both paths. {@code processBlock} writes at line 1858; this test |
| 260 | + * pins the matching write inside {@code generateBlock}. |
| 261 | + * |
| 262 | + * <p>Spy {@code accountStateCallBack.preExecute} — called between the |
| 263 | + * write and the tx loop on both paths — and snapshot the slot from inside |
| 264 | + * the revoking session. Pre-fix the slot is empty (write never ran); |
| 265 | + * post-fix it holds the parent block hash. |
| 266 | + */ |
| 267 | + @Test |
| 268 | + public void generateBlockWritesParentHashBeforeTxLoop() throws Exception { |
| 269 | + chainBaseManager.getDynamicPropertiesStore().saveAllowTvmPrague(1L); |
| 270 | + HistoryBlockHashUtil.deploy(dbManager); |
| 271 | + |
| 272 | + byte[] expectedParentHash = chainBaseManager.getHeadBlockId().getBytes(); |
| 273 | + long nextBlockNum = chainBaseManager.getHeadBlockNum() + 1; |
| 274 | + long expectedSlot = |
| 275 | + (nextBlockNum - 1) % HistoryBlockHashUtil.HISTORY_SERVE_WINDOW; |
| 276 | + |
| 277 | + Field cbField = Manager.class.getDeclaredField("accountStateCallBack"); |
| 278 | + cbField.setAccessible(true); |
| 279 | + AccountStateCallBack realCb = (AccountStateCallBack) cbField.get(dbManager); |
| 280 | + AccountStateCallBack spy = Mockito.spy(realCb); |
| 281 | + AtomicReference<DataWord> captured = new AtomicReference<>(); |
| 282 | + Mockito.doAnswer(inv -> { |
| 283 | + Storage st = new Storage( |
| 284 | + HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS, |
| 285 | + chainBaseManager.getStorageRowStore()); |
| 286 | + captured.set(st.getValue(new DataWord(expectedSlot))); |
| 287 | + return inv.callRealMethod(); |
| 288 | + }).when(spy).preExecute(Mockito.any(BlockCapsule.class)); |
| 289 | + cbField.set(dbManager, spy); |
| 290 | + |
| 291 | + try { |
| 292 | + ECKey ecKey = new ECKey(); |
| 293 | + ByteString witness = ByteString.copyFrom(ecKey.getAddress()); |
| 294 | + Param.Miner miner = |
| 295 | + Param.getInstance().new Miner(ecKey.getPrivKeyBytes(), witness, witness); |
| 296 | + long blockTime = System.currentTimeMillis() / 3000 * 3000; |
| 297 | + BlockCapsule generated = dbManager.generateBlock( |
| 298 | + miner, blockTime, System.currentTimeMillis() + 1000); |
| 299 | + assertNotNull("generateBlock returned null", generated); |
| 300 | + } finally { |
| 301 | + cbField.set(dbManager, realCb); |
| 302 | + } |
| 303 | + |
| 304 | + assertNotNull( |
| 305 | + "preExecute fired with an empty slot — write() must run before preExecute", |
| 306 | + captured.get()); |
| 307 | + assertArrayEquals( |
| 308 | + "slot must hold the parent block hash before the tx loop runs", |
| 309 | + expectedParentHash, captured.get().getData()); |
| 310 | + } |
| 311 | + |
248 | 312 | @Test |
249 | 313 | public void deployUpgradesPreExistingNormalAccountPreservingBalance() { |
250 | 314 | byte[] addr = HistoryBlockHashUtil.HISTORY_STORAGE_ADDRESS; |
|
0 commit comments