use async_trait::async_trait;
use bsv_rs::transaction::Transaction;
use crate::types::{
AdmittanceInstructions, Outpoint, ServiceMetadata, SubmitMode, TopicAdmittanceContext,
};
#[async_trait(?Send)]
pub trait TopicManager {
async fn identify_admissible_outputs(
&self,
tx: &Transaction,
previous_coins: &[u8],
off_chain_values: Option<&[u8]>,
mode: SubmitMode,
context: &TopicAdmittanceContext,
) -> Result<AdmittanceInstructions, TopicManagerError>;
async fn identify_needed_inputs(
&self,
_beef: &[u8],
_off_chain_values: Option<&[u8]>,
) -> Result<Vec<Outpoint>, TopicManagerError> {
Ok(Vec::new())
}
fn reads_off_chain_values(&self) -> bool {
true
}
async fn get_documentation(&self) -> String;
async fn get_metadata(&self) -> ServiceMetadata;
}
#[derive(Debug, thiserror::Error)]
pub enum TopicManagerError {
#[error("invalid BEEF: {0}")]
InvalidBeef(String),
#[error("no admissible outputs: {0}")]
NoAdmissibleOutputs(String),
#[error("invalid script: {0}")]
InvalidScript(String),
#[error("signature verification failed: {0}")]
SignatureError(String),
#[error("{0}")]
Other(String),
}
#[cfg(test)]
mod tests {
use super::*;
struct AdmitAllManager;
#[async_trait(?Send)]
impl TopicManager for AdmitAllManager {
async fn identify_admissible_outputs(
&self,
_tx: &Transaction,
_previous_coins: &[u8],
_off_chain_values: Option<&[u8]>,
_mode: SubmitMode,
_context: &TopicAdmittanceContext,
) -> Result<AdmittanceInstructions, TopicManagerError> {
Ok(AdmittanceInstructions {
outputs_to_admit: vec![0, 1, 2],
coins_to_retain: vec![],
coins_removed: None,
})
}
async fn get_documentation(&self) -> String {
"Admits everything.".to_string()
}
async fn get_metadata(&self) -> ServiceMetadata {
ServiceMetadata {
name: "admit-all".to_string(),
description: Some("Test manager that admits all outputs".to_string()),
..Default::default()
}
}
}
struct RejectAllManager;
#[async_trait(?Send)]
impl TopicManager for RejectAllManager {
async fn identify_admissible_outputs(
&self,
_tx: &Transaction,
_previous_coins: &[u8],
_off_chain_values: Option<&[u8]>,
_mode: SubmitMode,
_context: &TopicAdmittanceContext,
) -> Result<AdmittanceInstructions, TopicManagerError> {
Ok(AdmittanceInstructions::default())
}
async fn get_documentation(&self) -> String {
"Rejects everything.".to_string()
}
async fn get_metadata(&self) -> ServiceMetadata {
ServiceMetadata {
name: "reject-all".to_string(),
..Default::default()
}
}
}
#[tokio::test]
async fn test_admit_all_manager() {
let mgr = AdmitAllManager;
let result = mgr
.identify_admissible_outputs(
&Transaction::new(),
&[],
None,
SubmitMode::CurrentTx,
&TopicAdmittanceContext::default(),
)
.await
.unwrap();
assert_eq!(result.outputs_to_admit, vec![0, 1, 2]);
assert!(result.has_activity());
}
#[tokio::test]
async fn test_reject_all_manager() {
let mgr = RejectAllManager;
let result = mgr
.identify_admissible_outputs(
&Transaction::new(),
&[],
None,
SubmitMode::CurrentTx,
&TopicAdmittanceContext::default(),
)
.await
.unwrap();
assert!(result.outputs_to_admit.is_empty());
assert!(!result.has_activity());
}
#[tokio::test]
async fn test_default_identify_needed_inputs() {
let mgr = AdmitAllManager;
let inputs = mgr.identify_needed_inputs(&[], None).await.unwrap();
assert!(inputs.is_empty());
}
#[tokio::test]
async fn test_manager_metadata() {
let mgr = AdmitAllManager;
let meta = mgr.get_metadata().await;
assert_eq!(meta.name, "admit-all");
assert!(meta.description.is_some());
let docs = mgr.get_documentation().await;
assert!(!docs.is_empty());
}
#[tokio::test]
async fn test_topic_manager_is_object_safe() {
let mgr: Box<dyn TopicManager> = Box::new(AdmitAllManager);
let result = mgr
.identify_admissible_outputs(
&Transaction::new(),
&[],
None,
SubmitMode::HistoricalTx,
&TopicAdmittanceContext::default(),
)
.await
.unwrap();
assert_eq!(result.outputs_to_admit.len(), 3);
}
}