use crate::prelude::StateMutationResult;
use holo_hash::AgentPubKey;
use holochain_sqlite::rusqlite::OptionalExtension;
use holochain_sqlite::rusqlite::{named_params, Transaction};
use holochain_zome_types::prelude::*;
#[derive(Debug, Clone)]
pub struct ChainLock {
subject: Vec<u8>,
expires_at: Timestamp,
}
impl ChainLock {
pub fn subject(&self) -> &[u8] {
&self.subject
}
pub fn is_expired_at(&self, timestamp: Timestamp) -> bool {
timestamp > self.expires_at
}
}
pub fn get_chain_lock(
txn: &Transaction,
author: &AgentPubKey,
) -> StateMutationResult<Option<ChainLock>> {
Ok(txn
.query_row(
"
SELECT subject, expires_at_timestamp
FROM ChainLock
WHERE author = :author
",
named_params! {
":author": author,
},
|row| {
Ok(ChainLock {
subject: row.get(0)?,
expires_at: row.get(1)?,
})
},
)
.optional()?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::{lock_chain, unlock_chain, StateMutationError};
use holochain_sqlite::db::{DbKindAuthored, DbWrite};
use std::ops::Add;
use std::sync::Arc;
use std::time::Duration;
#[tokio::test]
async fn source_chain_lock() {
let agent_pub_key = AgentPubKey::from_raw_36(vec![0; 36]);
let db = DbWrite::test_in_mem(DbKindAuthored(Arc::new(CellId::new(
DnaHash::from_raw_36(vec![1; 36]),
agent_pub_key.clone(),
))))
.unwrap();
let lock = db
.read_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| get_chain_lock(txn, &agent_pub_key)
})
.await
.unwrap();
assert!(lock.is_none());
db.write_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| {
let timestamp = Timestamp::now().add(Duration::from_secs(10)).unwrap();
lock_chain(txn, &agent_pub_key, &[1, 2, 3], ×tamp)
}
})
.await
.unwrap();
let lock = db
.read_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| get_chain_lock(txn, &agent_pub_key)
})
.await
.unwrap();
assert!(lock.is_some());
assert!(!lock.as_ref().unwrap().is_expired_at(Timestamp::now()));
assert_eq!(&[1, 2, 3], lock.as_ref().unwrap().subject());
assert!(lock
.unwrap()
.is_expired_at(Timestamp::now().add(Duration::from_secs(12)).unwrap()));
db.write_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| unlock_chain(txn, &agent_pub_key)
})
.await
.unwrap();
let lock = db
.read_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| get_chain_lock(txn, &agent_pub_key)
})
.await
.unwrap();
assert!(lock.is_none());
}
#[tokio::test]
async fn cannot_hold_multiple_locks() {
let agent_pub_key = AgentPubKey::from_raw_36(vec![0; 36]);
let db = DbWrite::test_in_mem(DbKindAuthored(Arc::new(CellId::new(
DnaHash::from_raw_36(vec![1; 36]),
agent_pub_key.clone(),
))))
.unwrap();
db.write_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| {
let timestamp = Timestamp::now().add(Duration::from_secs(10)).unwrap();
lock_chain(txn, &agent_pub_key, &[1, 2, 3], ×tamp)
}
})
.await
.unwrap();
let check_is_constraint_err = |err: StateMutationError| match err {
StateMutationError::Sql(e) => {
assert_eq!(
holochain_sqlite::rusqlite::ErrorCode::ConstraintViolation,
e.sqlite_error_code().unwrap()
);
}
_ => panic!("Expected a SQL error"),
};
let err = db
.write_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| {
let timestamp = Timestamp::now().add(Duration::from_secs(10)).unwrap();
lock_chain(txn, &agent_pub_key, &[1, 2, 3], ×tamp)
}
})
.await
.unwrap_err();
check_is_constraint_err(err);
let err = db
.write_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| {
let timestamp = Timestamp::now().add(Duration::from_secs(10)).unwrap();
lock_chain(txn, &agent_pub_key, &[1, 2, 4], ×tamp)
}
})
.await
.unwrap_err();
check_is_constraint_err(err);
let lock = db
.read_async({
let agent_pub_key = agent_pub_key.clone();
move |txn| get_chain_lock(txn, &agent_pub_key)
})
.await
.unwrap();
assert!(lock.is_some());
}
}