use crate::primitives::budget::Budget;
use vstd::prelude::*;
verus! {
pub open spec fn delegated(pool: Budget) -> int {
pool.allocated as int + pool.reserved as int
}
pub open spec fn delegated_sum_to(pools: Seq<Budget>, n: int) -> int
decreases n,
{
if n <= 0 || n > pools.len() {
0
} else {
delegated(pools[n - 1]) + delegated_sum_to(pools, n - 1)
}
}
proof fn delegated_sum_zero(pools: Seq<Budget>, n: int)
requires
0 <= n <= pools.len(),
forall|i: int| 0 <= i < pools.len()
==> #[trigger] pools[i].allocated == 0 && pools[i].reserved == 0,
ensures delegated_sum_to(pools, n) == 0,
decreases n,
{
if n > 0 {
delegated_sum_zero(pools, n - 1);
}
}
proof fn delegated_le_sum(pools: Seq<Budget>, index: int, n: int)
requires 0 <= index < n <= pools.len(),
ensures delegated(pools[index]) <= delegated_sum_to(pools, n),
decreases n,
{
if index == n - 1 {
assert(delegated_sum_to(pools, n)
== delegated(pools[index]) + delegated_sum_to(pools, n - 1));
assert(delegated_sum_to(pools, n - 1) >= 0) by {
if n - 1 > 0 {
delegated_le_sum(pools, 0, n - 1);
}
}
} else {
delegated_le_sum(pools, index, n - 1);
}
}
proof fn delegated_sum_update_unaffected(
pools: Seq<Budget>,
index: int,
replacement: Budget,
n: int,
)
requires 0 <= n <= index < pools.len(),
ensures delegated_sum_to(pools.update(index, replacement), n)
== delegated_sum_to(pools, n),
decreases n,
{
if n > 0 {
delegated_sum_update_unaffected(pools, index, replacement, n - 1);
assert(pools.update(index, replacement)[n - 1] == pools[n - 1]);
}
}
proof fn delegated_sum_update(
pools: Seq<Budget>,
index: int,
replacement: Budget,
n: int,
)
requires 0 <= index < n <= pools.len(),
ensures delegated_sum_to(pools.update(index, replacement), n)
== delegated_sum_to(pools, n) - delegated(pools[index]) + delegated(replacement),
decreases n,
{
if n == index + 1 {
delegated_sum_update_unaffected(pools, index, replacement, index);
assert(pools.update(index, replacement)[index] == replacement);
} else {
delegated_sum_update(pools, index, replacement, n - 1);
assert(pools.update(index, replacement)[n - 1] == pools[n - 1]);
}
}
pub struct FederatedBudget {
pub master: Budget,
pub sub_pools: Vec<Budget>,
}
impl FederatedBudget {
pub open spec fn sum_caps(&self) -> int {
delegated_sum_to(self.sub_pools@, self.sub_pools.len() as int)
}
pub open spec fn type_invariant(&self) -> bool {
&&& self.master.safety_invariant()
&&& self.master.reserved == 0
&&& self.master.pending_eviction == 0
&&& forall|i: int| 0 <= i < self.sub_pools.len() ==> {
let pool = #[trigger] self.sub_pools@[i];
&&& pool.capacity == self.master.capacity
&&& pool.safety_invariant()
&&& pool.pending_eviction == 0
}
}
pub open spec fn master_capacity_bound(&self) -> bool {
self.master.allocated <= self.master.capacity
}
pub open spec fn sub_pool_capacity_bound(&self) -> bool {
forall|i: int| 0 <= i < self.sub_pools.len() ==>
#[trigger] self.sub_pools@[i].allocated + self.sub_pools@[i].reserved
<= self.master.capacity
}
pub open spec fn capacity_conservation(&self) -> bool {
self.master.allocated as int == self.sum_caps()
}
pub open spec fn inv(&self) -> bool {
self.type_invariant()
&& self.master_capacity_bound()
&& self.sub_pool_capacity_bound()
&& self.capacity_conservation()
}
pub fn new(master_capacity: u64, num_pools: usize) -> (federated: Self)
ensures
federated.master.capacity == master_capacity,
federated.master.allocated == 0,
federated.master.reserved == 0,
federated.master.pending_eviction == 0,
federated.sub_pools.len() == num_pools,
forall|index: int| 0 <= index < num_pools ==> {
let pool = #[trigger] federated.sub_pools@[index];
&&& pool.capacity == master_capacity
&&& pool.allocated == 0
&&& pool.reserved == 0
&&& pool.pending_eviction == 0
},
federated.inv(),
{
let master = Budget::new(master_capacity);
let mut sub_pools: Vec<Budget> = Vec::new();
let mut index: usize = 0;
while index < num_pools
invariant
index <= num_pools,
master.capacity == master_capacity,
master.allocated == 0,
master.reserved == 0,
master.pending_eviction == 0,
master.safety_invariant(),
sub_pools.len() == index,
forall|i: int| 0 <= i < sub_pools.len() ==> {
let pool = #[trigger] sub_pools@[i];
&&& pool.capacity == master_capacity
&&& pool.allocated == 0
&&& pool.reserved == 0
&&& pool.pending_eviction == 0
&&& pool.safety_invariant()
},
decreases num_pools - index,
{
sub_pools.push(Budget::new(master_capacity));
index += 1;
}
proof { delegated_sum_zero(sub_pools@, sub_pools.len() as int); }
Self { master, sub_pools }
}
pub fn allocate_sub_pool(&mut self, name: usize, amount: u64) -> (accepted: bool)
requires old(self).inv(),
ensures
final(self).inv(),
final(self).master.capacity == old(self).master.capacity,
final(self).sub_pools.len() == old(self).sub_pools.len(),
accepted == (name < old(self).sub_pools.len()
&& amount > 0
&& old(self).master.used() + amount as int <= old(self).master.capacity as int),
!accepted ==> final(self).master.allocated == old(self).master.allocated
&& final(self).sub_pools@ == old(self).sub_pools@,
accepted ==> {
&&& final(self).master.allocated == old(self).master.allocated + amount
&&& final(self).sub_pools@[name as int].allocated
== old(self).sub_pools@[name as int].allocated
&&& final(self).sub_pools@[name as int].reserved
== old(self).sub_pools@[name as int].reserved + amount
&&& final(self).sub_pools@[name as int].capacity
== old(self).sub_pools@[name as int].capacity
&&& final(self).sub_pools@[name as int].pending_eviction
== old(self).sub_pools@[name as int].pending_eviction
&&& forall|index: int| 0 <= index < old(self).sub_pools.len()
&& index != name as int ==>
#[trigger] final(self).sub_pools@[index]
== old(self).sub_pools@[index]
},
{
if name >= self.sub_pools.len() || amount == 0 {
return false;
}
proof {
delegated_le_sum(self.sub_pools@, name as int, self.sub_pools.len() as int);
}
let ghost pool_can_reserve = self.sub_pools@[name as int].used() + amount as int
<= self.sub_pools@[name as int].capacity as int;
let master_accepted = self.master.try_allocate(amount);
if !master_accepted {
return false;
}
let ghost old_pools = self.sub_pools@;
let mut pool = self.sub_pools[name];
assert(pool_can_reserve);
let _pool_accepted = pool.reserve(amount);
assert(_pool_accepted);
self.sub_pools.set(name, pool);
proof {
delegated_sum_update(old_pools, name as int, pool, old_pools.len() as int);
}
true
}
pub fn allocate_from_sub_pool(&mut self, name: usize, amount: u64) -> (accepted: bool)
requires old(self).inv(),
ensures
final(self).inv(),
final(self).master == old(self).master,
final(self).sub_pools.len() == old(self).sub_pools.len(),
accepted == (name < old(self).sub_pools.len()
&& amount > 0
&& amount <= old(self).sub_pools@[name as int].reserved),
!accepted ==> final(self).sub_pools@ == old(self).sub_pools@,
accepted ==> {
&&& final(self).sub_pools@[name as int].allocated
== old(self).sub_pools@[name as int].allocated + amount
&&& final(self).sub_pools@[name as int].reserved
== old(self).sub_pools@[name as int].reserved - amount
&&& final(self).sub_pools@[name as int].capacity
== old(self).sub_pools@[name as int].capacity
&&& final(self).sub_pools@[name as int].pending_eviction
== old(self).sub_pools@[name as int].pending_eviction
&&& forall|index: int| 0 <= index < old(self).sub_pools.len()
&& index != name as int ==>
#[trigger] final(self).sub_pools@[index]
== old(self).sub_pools@[index]
},
{
if name >= self.sub_pools.len() || amount == 0 {
return false;
}
let mut pool = self.sub_pools[name];
if amount > pool.reserved {
return false;
}
let ghost old_pools = self.sub_pools@;
pool.commit_reservation(amount);
self.sub_pools.set(name, pool);
proof {
delegated_sum_update(old_pools, name as int, pool, old_pools.len() as int);
}
true
}
pub fn release_from_sub_pool(&mut self, name: usize, amount: u64) -> (accepted: bool)
requires old(self).inv(),
ensures
final(self).inv(),
final(self).master == old(self).master,
final(self).sub_pools.len() == old(self).sub_pools.len(),
accepted == (name < old(self).sub_pools.len()
&& amount > 0
&& amount <= old(self).sub_pools@[name as int].allocated),
!accepted ==> final(self).sub_pools@ == old(self).sub_pools@,
accepted ==> {
&&& final(self).sub_pools@[name as int].allocated
== old(self).sub_pools@[name as int].allocated - amount
&&& final(self).sub_pools@[name as int].reserved
== old(self).sub_pools@[name as int].reserved + amount
&&& final(self).sub_pools@[name as int].capacity
== old(self).sub_pools@[name as int].capacity
&&& final(self).sub_pools@[name as int].pending_eviction
== old(self).sub_pools@[name as int].pending_eviction
&&& forall|index: int| 0 <= index < old(self).sub_pools.len()
&& index != name as int ==>
#[trigger] final(self).sub_pools@[index]
== old(self).sub_pools@[index]
},
{
if name >= self.sub_pools.len() || amount == 0 {
return false;
}
let mut pool = self.sub_pools[name];
if amount > pool.allocated {
return false;
}
let ghost old_pools = self.sub_pools@;
pool.mark_eviction(amount);
pool.complete_eviction(amount);
let _reserved = pool.reserve(amount);
assert(_reserved);
self.sub_pools.set(name, pool);
proof {
delegated_sum_update(old_pools, name as int, pool, old_pools.len() as int);
}
true
}
}
}