Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/revm-context-interface/src/tron_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ pub struct TronStorageLoadReport {
/// java returned without caching (`oldKeyTaken`); the journal must not
/// keep the value either.
pub uncached: bool,
/// Proposal 102 detected a claimed legacy key with an existing row.
pub out_of_time: bool,
}

/// TRON's address prefix byte (java `Constant.ADD_PRE_FIX_BYTE_MAINNET`).
Expand Down
4 changes: 4 additions & 0 deletions crates/revm-context/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,10 @@ impl<
};
if let Some(report) = self.journaled_state.db_mut().tron_storage_take_load_report() {
if report.address == address && report.key == key {
if report.out_of_time {
self.journaled_state.tron_set_pending_out_of_time();
return;
}
if let Some(kind) = report.kind {
self.journaled_state
.tron_set_storage_read_kind(address, key, kind);
Expand Down
16 changes: 15 additions & 1 deletion crates/tron-actuator/src/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,19 @@ fn validate_proposal_value(
return Err(bad());
}
}
// java 4.8.2.3: proposable only once `forkController.pass(VERSION_4_8_2_3)`.
// java 4.8.2.4: storage-key protection is independently proposable.
AllowOptimizeTvmStorageKey => {
if !dyn_props.fork_passed(tron_chainbase::fork::VERSION_4_8_2_4) {
return Err(bad());
}
if flag(b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY") == 1 {
return Err(bad());
}
if value != 1 {
return Err(only_one("ALLOW_OPTIMIZE_TVM_STORAGE_KEY"));
}
}
// java 4.8.2.3: storage optimization without legacy-read protection.
AllowOptimizeTvmStorage => {
if !dyn_props.fork_passed(tron_chainbase::fork::VERSION_4_8_2_3) {
return Err(bad());
Expand Down Expand Up @@ -666,6 +678,7 @@ enum ProposalType {
AllowHardenResourceCalculation,
AllowHardenExchangeCalculation,
AllowOptimizeTvmStorage,
AllowOptimizeTvmStorageKey,
}

impl ProposalType {
Expand Down Expand Up @@ -755,6 +768,7 @@ impl ProposalType {
97 => AllowHardenResourceCalculation,
98 => AllowHardenExchangeCalculation,
99 => AllowOptimizeTvmStorage,
102 => AllowOptimizeTvmStorageKey,
_ => return None,
})
}
Expand Down
51 changes: 51 additions & 0 deletions crates/tron-actuator/tests/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,3 +709,54 @@ fn create_rejects_optimize_tvm_storage_when_already_active() {
.unwrap_err();
assert!(matches!(err, ActuatorError::ProposalParameterOutOfRange), "got: {err:?}");
}

// Parameter 102 must be understood at proposal creation, before activation.
fn storage_key_param(value: i64) -> BTreeMap<i64, i64> {
BTreeMap::from([(102, value)])
}

fn pass_storage_key_fork(ctx: &Ctx) {
ctx.dp.save_latest_block_header_timestamp(1_790_000_000_000);
ctx.dp.save_fork_stats(tron_chainbase::fork::VERSION_4_8_2_4, &[1u8; 27]);
}

#[test]
fn storage_key_proposal_requires_4_8_2_4_not_only_4_8_2_3() {
let ctx = ctx();
put_witness(&ctx, ALICE);
pass_version_4_8_2_3(&ctx);
let c = ProposalCreateContract {
owner_address: ALICE.to_vec(),
parameters: storage_key_param(1),
};
assert!(proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).is_err());
pass_storage_key_fork(&ctx);
proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).unwrap();
}

#[test]
fn storage_key_proposal_only_accepts_one_and_cannot_be_reenabled() {
let ctx = ctx();
put_witness(&ctx, ALICE);
pass_storage_key_fork(&ctx);
for value in [-1, 0, 2] {
let c = ProposalCreateContract {
owner_address: ALICE.to_vec(),
parameters: storage_key_param(value),
};
assert!(proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).is_err());
}
// Proposal 99 being active does not prevent proposing 102.
ctx.dp.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE", 1);
let c = ProposalCreateContract {
owner_address: ALICE.to_vec(),
parameters: storage_key_param(1),
};
proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).unwrap();
proposal::execute_proposal_create(&ctx.proposals, &ctx.dp, &c).unwrap();
assert!(!ctx.dp.allow_optimize_tvm_storage_key());
let created = ctx.proposals.get(1).unwrap().unwrap();
assert_eq!(created.parameters.get(&102), Some(&1));
ctx.dp.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", 1);
assert!(proposal::validate_proposal_create(&ctx.accounts, &ctx.witnesses, &ctx.dp, &c).is_err());
}
6 changes: 4 additions & 2 deletions crates/tron-chainbase/src/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ pub const VERSION_4_8_1_1: i32 = 35;
pub const VERSION_4_8_2: i32 = 36;
pub const VERSION_4_8_2_2: i32 = 37;
pub const VERSION_4_8_2_3: i32 = 38;
pub const VERSION_4_8_2_4: i32 = 39;

/// `Parameter.ChainConstant.BLOCK_VERSION` — the version a producing node
/// stamps on its blocks.
pub const BLOCK_VERSION: i32 = 38;
pub const BLOCK_VERSION: i32 = 39;

/// `ForkBlockVersionEnum.values()`, in declaration order.
pub const FORK_VERSIONS: [ForkVersion; 28] = [
pub const FORK_VERSIONS: [ForkVersion; 29] = [
ForkVersion { version: ENERGY_LIMIT, hard_fork_time_ms: 0, rate: 0 },
ForkVersion { version: VERSION_3_2_2, hard_fork_time_ms: 0, rate: 0 },
ForkVersion { version: VERSION_3_5, hard_fork_time_ms: 0, rate: 0 },
Expand Down Expand Up @@ -97,6 +98,7 @@ pub const FORK_VERSIONS: [ForkVersion; 28] = [
ForkVersion { version: VERSION_4_8_2, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 80 },
ForkVersion { version: VERSION_4_8_2_2, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 70 },
ForkVersion { version: VERSION_4_8_2_3, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 70 },
ForkVersion { version: VERSION_4_8_2_4, hard_fork_time_ms: HARD_FORK_TIME_2020_08_07, rate: 70 },
];

const STATS_KEY_PREFIX: &[u8] = b"FORK_VERSION_";
Expand Down
11 changes: 11 additions & 0 deletions crates/tron-chainbase/src/stores/dynamic_properties_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ pub mod keys {
pub const ALLOW_TVM_OSAKA: &[u8] = b"ALLOW_TVM_OSAKA";
/// Proposal 99: the 48-byte storage-row key scheme (`Storage.getNewRowKey`).
pub const ALLOW_OPTIMIZE_TVM_STORAGE: &[u8] = b"ALLOW_OPTIMIZE_TVM_STORAGE";
/// Proposal 102: optimized keys with protected legacy reads.
pub const ALLOW_OPTIMIZE_TVM_STORAGE_KEY: &[u8] = b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY";
/// TIP-2935 install marker, set once `BlockHashHistory` is deployed.
pub const BLOCK_HASH_HISTORY_INSTALLED: &[u8] = b"BLOCK_HASH_HISTORY_INSTALLED";
pub const ALLOW_TVM_FREEZE: &[u8] = b"ALLOW_TVM_FREEZE";
Expand Down Expand Up @@ -771,6 +773,15 @@ impl DynamicPropertiesStore {
self.get_long(keys::ALLOW_OPTIMIZE_TVM_STORAGE).unwrap_or(0) == 1
}

pub fn allow_optimize_tvm_storage_key(&self) -> bool {
self.get_long(keys::ALLOW_OPTIMIZE_TVM_STORAGE_KEY).unwrap_or(0) == 1
}

/// java DynamicPropertiesStore.allowTvmStorageOptimization().
pub fn allow_tvm_storage_optimization(&self) -> bool {
self.allow_optimize_tvm_storage() || self.allow_optimize_tvm_storage_key()
}

pub fn is_block_hash_history_installed(&self) -> bool {
self.get_long(keys::BLOCK_HASH_HISTORY_INSTALLED).unwrap_or(0) == 1
}
Expand Down
36 changes: 36 additions & 0 deletions crates/tron-consensus/src/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ pub fn parameter_id_to_key(id: i64) -> Option<&'static [u8]> {
97 => b"ALLOW_HARDEN_RESOURCE_CALCULATION",
98 => b"ALLOW_HARDEN_EXCHANGE_CALCULATION",
99 => b"ALLOW_OPTIMIZE_TVM_STORAGE",
102 => b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY",
_ => return None,
})
}
Expand Down Expand Up @@ -525,3 +526,38 @@ mod bitmap_evolution_tests {
assert!(bit_set(&act, 48) && bit_set(&act, 49));
}
}

#[cfg(test)]
mod storage_key_activation_tests {
use super::*;
use std::{collections::BTreeMap, sync::Arc};
use tron_chainbase::MemBackend;

#[test]
fn parameter_102_activates_only_when_an_approved_proposal_expires() {
let dp = DynamicPropertiesStore::new(Arc::new(MemBackend::new()));
let proposals = ProposalStore::new(Arc::new(MemBackend::new()));
let active: Vec<Address> = (0..3).map(|n| {
let mut raw = [0u8; 21];
raw[0] = 0x41;
raw[20] = n;
Address::from_raw(raw)
}).collect();
proposals.put(108, &tron_proto::Proposal {
proposal_id: 108,
expiration_time: 20_000,
parameters: BTreeMap::from([(102, 1)]),
approvals: active[..2].iter().map(|a| a.as_bytes().to_vec()).collect(),
..Default::default()
}).unwrap();
activate_expired_proposals(&proposals, &dp, 19_999, &active).unwrap();
assert!(!dp.allow_tvm_storage_optimization());
let report = activate_expired_proposals(&proposals, &dp, 20_001, &active).unwrap();
assert_eq!(report.approved, vec![108]);
assert!(dp.allow_optimize_tvm_storage_key());
assert!(dp.allow_tvm_storage_optimization());
assert!(!dp.allow_optimize_tvm_storage());
assert_eq!(parameter_id_to_key(102), Some(&b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY"[..]));
assert_eq!(proposals.get(108).unwrap().unwrap().state, ProposalState::Approved as i32);
}
}
1 change: 1 addition & 0 deletions crates/tron-grpc/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,7 @@ impl Wallet for WalletService {
"ALLOW_HARDEN_RESOURCE_CALCULATION",
"ALLOW_HARDEN_EXCHANGE_CALCULATION",
"ALLOW_OPTIMIZE_TVM_STORAGE",
"ALLOW_OPTIMIZE_TVM_STORAGE_KEY",
];
let mut chain_parameter = Vec::with_capacity(KEYS.len());
for k in KEYS {
Expand Down
3 changes: 2 additions & 1 deletion crates/tron-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ pub fn eth_get_storage_at(p: &Value, s: &RpcState) -> Result<Value, RpcError> {
// a read-only RPC the v2 default matches what new contracts emit.
let addr_hash = tron_chainbase::StorageRowStore::addr_hash(&addr, &[]);
let value = storage
.read_slot(&addr_hash, &slot, false, s.dyn_props.allow_optimize_tvm_storage())?
.read_slot(&addr_hash, &slot, false, s.dyn_props.allow_tvm_storage_optimization())?
.unwrap_or_else(|| vec![0u8; 32]);
let mut padded = [0u8; 32];
let n = value.len().min(32);
Expand Down Expand Up @@ -1290,6 +1290,7 @@ pub fn get_chain_parameters(_p: &Value, s: &RpcState) -> Result<Value, RpcError>
("getAllowHardenResourceCalculation", b"ALLOW_HARDEN_RESOURCE_CALCULATION", 0),
("getAllowHardenExchangeCalculation", b"ALLOW_HARDEN_EXCHANGE_CALCULATION", 0),
("getAllowOptimizeTvmStorage", b"ALLOW_OPTIMIZE_TVM_STORAGE", 0),
("getAllowOptimizeTvmStorageKey", b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", 0),
];
let entries: Vec<Value> = PARAMS
.iter()
Expand Down
7 changes: 5 additions & 2 deletions crates/tron-rpc/tests/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ async fn get_chain_parameters_returns_seeded_entries() {
let (addr, _accts, _blocks, _idx, _txs, dp) = spawn_server().await;
dp.put_long(b"TRANSACTION_FEE", 1234);
dp.put_long(b"ENERGY_FEE", 5678);
dp.put_long(b"ALLOW_OPTIMIZE_TVM_STORAGE_KEY", 1);
let resp = call(
addr,
json!({"jsonrpc":"2.0","method":"getChainParameters","id":1}),
Expand All @@ -477,10 +478,11 @@ async fn get_chain_parameters_returns_seeded_entries() {
assert_eq!(by_key.get("getTransactionFee"), Some(&1234));
assert_eq!(by_key.get("getEnergyFee"), Some(&5678));
// Every java entry is present even when unset (value omitted → 0).
assert_eq!(params.len(), 80);
assert_eq!(params.len(), 81);
assert_eq!(by_key.get("getAllowOptimizeTvmStorageKey"), Some(&1));
assert_eq!(by_key.get("getAllowNewResourceModel"), Some(&0));
assert_eq!(by_key.get("getAllowOptimizeTvmStorage"), Some(&0));
// java 4.8.2.3 order: the five newest keys follow getProposalExpireTime.
// java 4.8.2.4 order: the six newest keys follow getProposalExpireTime.
let tail: Vec<&str> = params[75..]
.iter()
.map(|p| p["key"].as_str().unwrap())
Expand All @@ -493,6 +495,7 @@ async fn get_chain_parameters_returns_seeded_entries() {
"getAllowHardenResourceCalculation",
"getAllowHardenExchangeCalculation",
"getAllowOptimizeTvmStorage",
"getAllowOptimizeTvmStorageKey",
]
);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/tron-sim/src/override_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ fn write_slot(
is_v1: bool,
) -> Result<(), tron_chainbase::StoreError> {
let legacy = StorageRowStore::compose_key_with_addr_hash(addr_hash, slot, is_v1);
if vm.dynamic_properties.allow_optimize_tvm_storage() {
if vm.dynamic_properties.allow_tvm_storage_optimization() {
vm.storage
.put_raw(&StorageRowStore::new_row_key(addr_hash, slot), value)?;
vm.storage.delete(&legacy)
Expand Down
18 changes: 17 additions & 1 deletion crates/tron-tvm/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ pub struct TronDatabase {
/// `ALLOW_OPTIMIZE_TVM_STORAGE`: storage rows live under 48-byte keys,
/// loads consult the new key first, commits migrate legacy rows.
pub(crate) optimize_tvm_storage: bool,
/// Proposal 102: reject aliased legacy reads and leave read-only rows unmigrated.
pub(crate) legacy_read_protection: bool,
/// `(address, slot, old_key_taken)` the journal set before a cache-miss
/// load under the optimized scheme (java `Storage.oldKeyTaken`).
pub(crate) storage_load_hint: RefCell<Option<(EvmAddress, StorageKey, bool)>>,
Expand Down Expand Up @@ -212,6 +214,7 @@ impl TronDatabase {
top_level_deploy_version: None,
fork_4_8_2_2: false,
optimize_tvm_storage: false,
legacy_read_protection: false,
storage_load_hint: RefCell::new(None),
storage_load_report: RefCell::new(None),
commit_read_kinds: HashMap::new(),
Expand All @@ -227,6 +230,12 @@ impl TronDatabase {
self
}

/// java Storage.legacyReadProtection (proposal 102), separate from proposal 99.
pub fn with_legacy_read_protection(mut self, enabled: bool) -> Self {
self.legacy_read_protection = enabled;
self
}

/// Mark the top-level `CreateSmartContract` deploy address so its init
/// frame executes as `version` for the EIP-150 1/64 / GASPRICE gates
/// (java forces a new CREATE to version 1 under `ALLOW_TVM_COMPATIBLE_EVM`).
Expand Down Expand Up @@ -409,6 +418,7 @@ impl TronDatabase {
kind: None,
own_old_key: false,
uncached: false,
out_of_time: false,
};
let new_key = StorageRowStore::new_row_key(&ah, &slot);
let value = if let Some(raw) = self.storage.get_raw(&new_key)? {
Expand All @@ -420,6 +430,10 @@ impl TronDatabase {
storage_word(&raw)
}
} else if old_key_taken {
if self.legacy_read_protection {
let old = StorageRowStore::compose_key_with_addr_hash(&ah, &slot, is_v1);
report.out_of_time = self.storage.get(&old)?.is_some();
}
report.uncached = true;
StorageValue::ZERO
} else if let Some(raw) = self
Expand Down Expand Up @@ -940,7 +954,9 @@ impl TronDatabase {
let dirty = slot.present_value != slot.original_value
|| self.commit_written.contains(&(*address, *slot_key));
let kind = self.commit_read_kinds.get(&(*address, *slot_key)).copied();
let migrate = !dirty && kind == Some(TronStorageReadKind::Old);
let migrate = !self.legacy_read_protection
&& !dirty
&& kind == Some(TronStorageReadKind::Old);
if !dirty && !migrate {
continue;
}
Expand Down
13 changes: 8 additions & 5 deletions crates/tron-tvm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,9 @@ fn execute_trigger_inner(
)
.with_fork_flags(
stores.dynamic_properties.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2),
stores.dynamic_properties.allow_optimize_tvm_storage(),
);
stores.dynamic_properties.allow_tvm_storage_optimization(),
)
.with_legacy_read_protection(stores.dynamic_properties.allow_optimize_tvm_storage_key());
if let Some(tx_id) = root_tx_id {
tron_db = tron_db.with_root_tx_id(tx_id);
}
Expand Down Expand Up @@ -999,8 +1000,9 @@ fn execute_trigger_inner_with_tracer(
)
.with_fork_flags(
stores.dynamic_properties.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2),
stores.dynamic_properties.allow_optimize_tvm_storage(),
);
stores.dynamic_properties.allow_tvm_storage_optimization(),
)
.with_legacy_read_protection(stores.dynamic_properties.allow_optimize_tvm_storage_key());
if let Some(tx_id) = root_tx_id {
tron_db = tron_db.with_root_tx_id(tx_id);
}
Expand Down Expand Up @@ -1615,8 +1617,9 @@ fn execute_create_inner(
)
.with_fork_flags(
stores.dynamic_properties.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2),
stores.dynamic_properties.allow_optimize_tvm_storage(),
stores.dynamic_properties.allow_tvm_storage_optimization(),
)
.with_legacy_read_protection(stores.dynamic_properties.allow_optimize_tvm_storage_key())
.with_root_tx_id(*tx_id);
if let Some(idx) = &stores.block_index {
tron_db = tron_db.with_block_index(Arc::clone(idx));
Expand Down
4 changes: 2 additions & 2 deletions crates/tron-tvm/src/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub struct ProposalSet {
/// `ForkController.pass(VERSION_4_8_2_3)` — block version 38 on 70% of
/// SRs: java `Storage.checkAlias`.
pub fork_4_8_2_3: bool,
/// `ALLOW_OPTIMIZE_TVM_STORAGE` (proposal 99): 48-byte storage-row keys.
/// Effective storage optimization (proposal 99 or 102): 48-byte row keys.
pub allow_optimize_tvm_storage: bool,
}

Expand Down Expand Up @@ -161,7 +161,7 @@ impl ProposalSet {
allow_energy_adjustment: flag(b"ALLOW_ENERGY_ADJUSTMENT"),
fork_4_8_2_2: dps.fork_passed(tron_chainbase::fork::VERSION_4_8_2_2),
fork_4_8_2_3: dps.fork_passed(tron_chainbase::fork::VERSION_4_8_2_3),
allow_optimize_tvm_storage: flag(b"ALLOW_OPTIMIZE_TVM_STORAGE"),
allow_optimize_tvm_storage: dps.allow_tvm_storage_optimization(),
}
}

Expand Down
Loading