use std::{collections::HashMap, mem};
use indexmap::{IndexMap, IndexSet};
use tari_engine_types::{
Utxo,
component::Component,
lock::{LockFlag, LockId},
substate::{Substate, SubstateId, SubstateValue},
vault::Vault,
};
use tari_ootle_common_types::optional::Optional;
use tari_template_lib::types::{ComponentAddress, UtxoAddress, VaultId};
use crate::{
runtime::{
RuntimeError,
locking::{LockError, LockedSubstates},
},
state_store::StateReader,
};
#[derive(Debug, Clone)]
pub struct WorkingStateStore<TStore> {
new_substates: IndexMap<SubstateId, SubstateValue>,
loaded_substates: HashMap<SubstateId, Substate>,
locked_substates: LockedSubstates,
downed_utxos: IndexSet<UtxoAddress>,
state_store: TStore,
}
impl<TStore: StateReader> WorkingStateStore<TStore> {
pub fn new(state_store: TStore) -> Self {
Self {
new_substates: IndexMap::new(),
loaded_substates: HashMap::new(),
locked_substates: LockedSubstates::default(),
downed_utxos: IndexSet::default(),
state_store,
}
}
pub fn try_lock(&mut self, id: SubstateId, lock_flag: LockFlag) -> Result<LockId, RuntimeError> {
if !self.exists(&id)? {
return Err(RuntimeError::SubstateNotFound { id: id.clone() });
}
let lock_id = self.locked_substates.try_lock(id.clone(), lock_flag)?;
self.load_and_cache(id)?;
Ok(lock_id)
}
pub fn try_unlock(&mut self, lock_id: LockId) -> Result<(), LockError> {
self.locked_substates.try_unlock(lock_id)?;
Ok(())
}
pub fn get_locked_substate_mut(
&mut self,
lock_id: LockId,
) -> Result<(SubstateId, &mut SubstateValue), RuntimeError> {
let lock = self.locked_substates.get(lock_id, LockFlag::Write)?;
let substate = self.get_for_mut(lock.substate_id())?;
Ok((lock.substate_id().clone(), substate))
}
pub fn mutate_locked_substate_with<
R,
F: FnOnce(&SubstateId, &mut SubstateValue) -> Result<Option<R>, RuntimeError>,
>(
&mut self,
lock_id: LockId,
callback: F,
) -> Result<Option<R>, RuntimeError> {
let lock = self.locked_substates.get(lock_id, LockFlag::Write)?;
if let Some(mut substate) = self.loaded_substates.remove(lock.substate_id()) {
return match callback(lock.substate_id(), substate.substate_value_mut())? {
Some(ret) => {
self.new_substates
.insert(lock.substate_id().clone(), substate.into_substate_value());
Ok(Some(ret))
},
None => {
self.loaded_substates.insert(lock.substate_id().clone(), substate);
Ok(None)
},
};
}
let substate_mut =
self.new_substates
.get_mut(lock.substate_id())
.ok_or_else(|| LockError::SubstateNotLocked {
address: lock.substate_id().clone(),
})?;
callback(lock.substate_id(), substate_mut)
}
pub fn get_locked_substate(&self, lock_id: LockId) -> Result<(SubstateId, &SubstateValue), RuntimeError> {
let lock = self.locked_substates.get(lock_id, LockFlag::Read)?;
let substate = self.get_ref(lock.substate_id())?;
Ok((lock.substate_id().clone(), substate))
}
fn get_ref(&self, address: &SubstateId) -> Result<&SubstateValue, LockError> {
self.new_substates
.get(address)
.or_else(|| self.loaded_substates.get(address).map(|s| s.substate_value()))
.ok_or_else(|| LockError::SubstateNotLocked {
address: address.clone(),
})
}
fn get_for_mut(&mut self, address: &SubstateId) -> Result<&mut SubstateValue, LockError> {
if let Some(substate) = self.loaded_substates.remove(address) {
self.new_substates
.insert(address.clone(), substate.into_substate_value());
}
if let Some(substate_mut) = self.new_substates.get_mut(address) {
return Ok(substate_mut);
}
Err(LockError::SubstateNotLocked {
address: address.clone(),
})
}
pub fn exists(&self, id: &SubstateId) -> Result<bool, RuntimeError> {
let exists = self.new_substates.contains_key(id) ||
self.loaded_substates.contains_key(id) ||
self.state_store.exists(id)?;
Ok(exists)
}
pub fn insert(&mut self, id: SubstateId, value: SubstateValue) -> Result<(), RuntimeError> {
if self.exists(&id)? {
return Err(RuntimeError::DuplicateSubstate { address: id });
}
self.new_substates.insert(id, value);
Ok(())
}
fn load_and_cache(&mut self, id: SubstateId) -> Result<&SubstateValue, RuntimeError> {
if let Some(s) = self.new_substates.get(&id) {
return Ok(s);
}
if !self.loaded_substates.contains_key(&id) {
let substate = self
.state_store
.get_state(&id)
.optional()?
.ok_or_else(|| RuntimeError::SubstateNotFound { id: id.clone() })?;
self.loaded_substates.insert(id.clone(), substate.clone());
}
Ok(self.loaded_substates.get(&id).unwrap().substate_value())
}
pub fn take_mutated_substates(&mut self) -> IndexMap<SubstateId, SubstateValue> {
mem::take(&mut self.new_substates)
}
pub fn take_downed_utxos(&mut self) -> IndexSet<UtxoAddress> {
mem::take(&mut self.downed_utxos)
}
pub fn mutated_substates(&self) -> &IndexMap<SubstateId, SubstateValue> {
&self.new_substates
}
pub fn down_utxo(&mut self, lock_id: LockId) -> Result<Utxo, RuntimeError> {
let lock = self.locked_substates.get(lock_id, LockFlag::Write)?;
let substate_id = lock.substate_id();
let address = substate_id
.as_utxo_address()
.ok_or_else(|| RuntimeError::InvariantError {
function: "down_utxo",
details: format!("Substate at address {} is not a UTXO", substate_id),
})?;
const EXPECT: &str = "invariant: substate found at utxo address is not a UTXO";
if let Some(value) = self.new_substates.shift_remove(substate_id) {
return Ok(value.into_utxo().expect(EXPECT));
}
if let Some(substate) = self.loaded_substates.remove(substate_id) {
self.downed_utxos.insert(address);
return Ok(substate.into_substate_value().into_utxo().expect(EXPECT));
}
Err(RuntimeError::SubstateNotFound {
id: substate_id.clone(),
})
}
pub fn new_vaults(&self) -> impl Iterator<Item = (VaultId, &Vault)> + '_ {
self.new_substates
.iter()
.filter(|(address, _)| address.is_vault())
.map(|(addr, vault)| (addr.as_vault_id().unwrap(), vault.as_vault().unwrap()))
}
pub fn load_and_cache_component(&mut self, address: ComponentAddress) -> Result<&Component, RuntimeError> {
let addr = SubstateId::Component(address);
let component = self.load_and_cache(addr)?;
component.component().ok_or_else(|| RuntimeError::InvariantError {
function: "load_component",
details: format!("Substate at address {} is not a component", address),
})
}
pub(super) fn get_unmodified_substate(&self, id: &SubstateId) -> Result<&Substate, RuntimeError> {
match self.loaded_substates.get(id) {
Some(substate) => Ok(substate),
None => self
.state_store
.get_state(id)
.optional()?
.ok_or_else(|| RuntimeError::SubstateNotFound { id: id.clone() }),
}
}
}