diff --git a/governor/src/lib.rs b/governor/src/lib.rs index 6198c43..027bed2 100644 --- a/governor/src/lib.rs +++ b/governor/src/lib.rs @@ -30,6 +30,12 @@ use vortex_common::require_admin; /// the checkpoint history stays cheap and storage stays predictable. pub const MAX_CHECKPOINTS: u32 = 64; +/// Persistent entries are bumped once their remaining TTL drops below this +/// many ledgers, extending them back out to `PERSISTENT_TTL_EXTEND_TO`. +pub const PERSISTENT_TTL_THRESHOLD: u32 = 100_000; +/// Target TTL (in ledgers) for persistent entries after a bump. +pub const PERSISTENT_TTL_EXTEND_TO: u32 = 1_000_000; + /// Maximum number of proposals returned by a single `list_proposals` call. pub const MAX_LIST_LIMIT: u32 = 50; @@ -266,7 +272,9 @@ impl Governor { function, calldata, }; - env.storage().persistent().set(&Self::proposal_key(&env, id), &proposal); + let proposal_key = Self::proposal_key(&env, id); + env.storage().persistent().set(&proposal_key, &proposal); + Self::bump_persistent_ttl(&env, &proposal_key); env.storage().instance().set(&Symbol::new(&env, "count"), &(id + 1)); env.events().publish( (symbol_short!("propose"),), @@ -364,7 +372,10 @@ impl Governor { } } env.storage().persistent().set(&vote_key, &power); - env.storage().persistent().set(&Self::proposal_key(&env, proposal_id), &proposal); + Self::bump_persistent_ttl(&env, &vote_key); + let proposal_key = Self::proposal_key(&env, proposal_id); + env.storage().persistent().set(&proposal_key, &proposal); + Self::bump_persistent_ttl(&env, &proposal_key); env.events().publish( (symbol_short!("vote"),), (proposal_id, voter.clone(), support, power), @@ -393,7 +404,9 @@ impl Governor { (proposal.target.clone(), proposal.function.clone(), args, eta).into_val(&env), ); proposal.eta = eta; - env.storage().persistent().set(&Self::proposal_key(&env, proposal_id), &proposal); + let proposal_key = Self::proposal_key(&env, proposal_id); + env.storage().persistent().set(&proposal_key, &proposal); + Self::bump_persistent_ttl(&env, &proposal_key); env.events() .publish((symbol_short!("queue"),), (proposal_id, proposal.eta)); Ok(()) @@ -426,7 +439,9 @@ impl Governor { .into_val(&env), ); proposal.executed = true; - env.storage().persistent().set(&Self::proposal_key(&env, proposal_id), &proposal); + let proposal_key = Self::proposal_key(&env, proposal_id); + env.storage().persistent().set(&proposal_key, &proposal); + Self::bump_persistent_ttl(&env, &proposal_key); env.events().publish( (symbol_short!("execute"),), (proposal_id, proposal.target.clone(), proposal.function.clone()), @@ -648,6 +663,13 @@ impl Governor { } } + /// Extend the TTL of a persistent entry if it is close to expiring. + fn bump_persistent_ttl(env: &Env, key: &impl soroban_sdk::IntoVal) { + env.storage() + .persistent() + .extend_ttl(key, PERSISTENT_TTL_THRESHOLD, PERSISTENT_TTL_EXTEND_TO); + } + /// Compute a proposal's deadline, guarding against ledger overflow. fn deadline(env: &Env, snapshot: u32, config: &GovernorConfig) -> Result { let _ = env; diff --git a/timelock/src/lib.rs b/timelock/src/lib.rs index 73d9913..840366a 100644 --- a/timelock/src/lib.rs +++ b/timelock/src/lib.rs @@ -96,6 +96,11 @@ const ROLE_KEY: Symbol = symbol_short!("Role"); /// Default grace period after `ready_at` during which an operation may execute. const DEFAULT_GRACE_PERIOD: u64 = 14 * 24 * 60 * 60; +/// Number of ledgers below which a persistent entry's TTL is bumped on write. +const PERSISTENT_TTL_THRESHOLD: u32 = 100_000; +/// Number of ledgers a persistent entry's TTL is extended to on write. +const PERSISTENT_TTL_EXTEND_TO: u32 = 500_000; + #[contract] pub struct TimelockController; @@ -131,6 +136,19 @@ impl TimelockController { } } + // --------------------------------------------------------------------- + // TTL helpers + // --------------------------------------------------------------------- + + /// Bump the TTL of a persistent storage key so it does not get archived. + fn bump_persistent_ttl>(env: &Env, key: &K) { + env.storage().persistent().extend_ttl( + key, + PERSISTENT_TTL_THRESHOLD, + PERSISTENT_TTL_EXTEND_TO, + ); + } + // --------------------------------------------------------------------- // Role management (must be authorised by the timelock itself) // --------------------------------------------------------------------- @@ -142,9 +160,9 @@ impl TimelockController { pub fn revoke_role(env: Env, role: Symbol, account: Address) { Self::require_admin(&env); - env.storage() - .persistent() - .set(&(ROLE_KEY, role, account), &false); + let key = (ROLE_KEY, role, account); + env.storage().persistent().set(&key, &false); + Self::bump_persistent_ttl(&env, &key); } pub fn has_role(env: Env, role: Symbol, account: Address) -> bool { @@ -239,9 +257,9 @@ impl TimelockController { executed: false, }; - env.storage() - .persistent() - .set(&(OP_KEY, id.clone()), &operation); + let op_key = (OP_KEY, id.clone()); + env.storage().persistent().set(&op_key, &operation); + Self::bump_persistent_ttl(&env, &op_key); env.events() .publish((symbol_short!("scheduled"), id.clone()), id.clone());