Skip to content

feat(vm): implement TIP-2935 serve historical block hashes from state#6686

Open
yanghang8612 wants to merge 3 commits intotronprotocol:developfrom
yanghang8612:feat/tip-2935-historical-blockhash
Open

feat(vm): implement TIP-2935 serve historical block hashes from state#6686
yanghang8612 wants to merge 3 commits intotronprotocol:developfrom
yanghang8612:feat/tip-2935-historical-blockhash

Conversation

@yanghang8612
Copy link
Copy Markdown
Collaborator

@yanghang8612 yanghang8612 commented Apr 16, 2026

What does this PR do?

Implements TIP-2935 (port of EIP-2935 "Serve Historical Block Hashes from State") for java-tron.

  • Adds proposal ALLOW_TVM_PRAGUE (ID 95), fork-gated by VERSION_4_8_2.
  • On activation, deploys the canonical EIP-2935 runtime bytecode + a minimal SmartContract (version = 0) + CONTRACT-type account at the 20-byte address 0x0000F90827F1C53a10cb7A02335B175320002935 (TRON: 410000F90827F1C53a10cb7A02335B175320002935). The three writes go to CodeStore / ContractStore / AccountStore via normal revoking-store puts — no synthetic transaction, no new actuator.
  • On every block, before the transaction loop, writes the parent block hash to StorageRowStore at slot (block.num - 1) % 8191. The storage-key composition exactly replicates Storage.compose() for contractVersion=0 (sha3(addr)[0:16] || slotKey[16:32]), so the direct-written rows are readable via a normal VM SLOAD when user contracts STATICCALL the deployed bytecode.
  • BLOCKHASH opcode semantics are unchanged (still the 256-block window).

Why are these changes required?

The 256-block BLOCKHASH window is too short for many workloads (rollups, stateless clients, multi-block fraud proofs, long-dated oracle signatures). EIP-2935 is the industry-standard solution on Ethereum (Prague, May 2025). Bringing it to TVM gives TRON DApps the same guarantees and — because we keep the same address and bytecode — lets cross-chain contracts hardcode 0x0000F908…2935 and work unchanged on both chains.

This PR has been tested by:

  • Unit Tests
    • HistoryBlockHashUtilTest (5 cases): storage-key round-trip equivalence with Storage.compose(), deploy populates Code/Contract/Account, deploy idempotency, slot-correctness, ring-buffer modulo wrap.
    • HistoryBlockHashIntegrationTest (4 cases): activation flow, per-block write after activation, gated off before activation, and a full VM round-trip that reads the written hash back through RepositoryImpl.getStorageValue — proving the read path composes the same key as our write path.
    • ProposalUtilTest.validateCheck: new testAllowTvmPragueProposal covering pre-fork rejection, bad-value rejection, already-enabled rejection, and the valid path.
  • Manual Testing
    • ./gradlew :framework:compileJava :framework:compileTestJava — OK.
    • ./gradlew lint — OK.
    • ./gradlew :framework:test --tests "…HistoryBlockHash…" --tests "…ProposalUtilTest.validateCheck" — all pass.

Follow up

  • Verify on mainnet that the address 410000F90827F1C53a10cb7A02335B175320002935 has never held a contract / balance (deployment would collide otherwise).
  • Testnet DApp check: measure energy cost of a STATICCALL to get(blockNum) from a Solidity contract on Nile.

Extra details

Implementation notes on how this diverges from geth's Prague system-call pattern (direct-write instead of system call, proposal activation instead of genesis pre-alloc, one-block activation gap) are summarized in the TIP issue comment.

Port EIP-2935 to TRON: store recent block hashes in a system contract's
storage so smart contracts can access up to 8191 blocks of history beyond
the BLOCKHASH opcode's 256-block window.

- New proposal ALLOW_TVM_PRAGUE(95), fork-gated by VERSION_4_8_2.
- On activation, deploy the EIP-2935 runtime bytecode + ContractCapsule
  (version=0) + AccountCapsule(type=CONTRACT) at the canonical address
  0x0000F90827F1C53a10cb7A02335B175320002935 via direct CodeStore /
  ContractStore / AccountStore writes.
- On every block, before the transaction loop, write the parent block hash
  to StorageRowStore at slot (blockNum - 1) % 8191. The storage key layout
  replicates Storage.compose() for contractVersion=0
  (addrHash[0:16] || slotKey[16:32]) so the written rows are readable via
  VM SLOAD.
- BLOCKHASH opcode semantics unchanged; new storage is accessed by user
  contracts via STATICCALL to the deployed bytecode.

Tests: round-trip equivalence (direct-write key = VM compose key),
deploy idempotency, ring buffer modulo, end-to-end activation flow,
and full VM repository read-back.
Proposal activation deploys the HistoryStorage contract via ProposalService,
but a node started with committee.allowTvmPrague=1 would flip the flag on
through the CommonParameter fallback without the contract ever being written
to CodeStore/ContractStore/AccountStore. External STATICCALLs would then hit
an empty account.

- Add HistoryBlockHashUtil.deployIfMissing(Manager), called from Manager.init
  right after updateDynamicStoreByConfig so the bytecode is guaranteed present
  before any block is applied.
- Guard write() against blockNum <= 0 so (0 - 1) % 8191 = -1 in Java can never
  corrupt a slot if the hook is ever reached for genesis.
- Add integration tests covering the proposal-persisted, config-fallback, and
  already-deployed paths, plus block-1 writes genesis hash to slot 0 and the
  genesis no-op.
…hUtil

Collapse the redundant getChainBaseManager() indirection — Manager already
exposes getCodeStore/getContractStore/getAccountStore/getStorageRowStore/
getDynamicPropertiesStore pass-throughs.
alan-eth

This comment was marked as duplicate.

* Called once from ProposalService when ALLOW_TVM_PRAGUE activates.
*/
public static void deploy(Manager manager) {
byte[] addr = HISTORY_STORAGE_ADDRESS;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idempotent deploy() with per-store has() checks and the genesis guard in write() are really clean.

Minor: would it be helpful to add a log line in deploy() / deployIfMissing() for easier troubleshooting during upgrades?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic:vm VM, smart contract

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants