use {
crate::{
move_boundary::NexusPtbBuilder,
nexus::{
client::NexusClient,
error::NexusError,
scheduler::resolve,
signer::ExecutedTransaction,
},
scheduler::{DispatchOffer, OccurrenceRef, Schedule, SchedulerError, TaskSpec},
sui,
transactions::scheduler::{
compile_append_dispatch_occurrence,
compile_append_expire_occurrence,
TaskDraftCompiler,
},
types::{NexusContext, PackageRole},
},
std::sync::Arc,
};
#[derive(Clone, Copy, Debug)]
enum TransactionSource {
Object(sui::types::Address),
Creator {
package: sui::types::Address,
role: PackageRole,
},
}
#[must_use = "dropping a Nexus transaction discards its commands"]
pub struct NexusTransaction {
client: NexusClient,
source: TransactionSource,
transaction: NexusPtbBuilder,
}
impl NexusTransaction {
pub(super) fn for_object(
client: NexusClient,
context: Arc<NexusContext>,
object_id: sui::types::Address,
) -> Self {
Self {
client,
source: TransactionSource::Object(object_id),
transaction: NexusPtbBuilder::new(context),
}
}
pub(super) fn for_creator(
client: NexusClient,
context: Arc<NexusContext>,
package: sui::types::Address,
role: PackageRole,
) -> Self {
Self {
client,
source: TransactionSource::Creator { package, role },
transaction: NexusPtbBuilder::new(context),
}
}
pub fn scheduler(&mut self) -> SchedulerTransaction<'_> {
SchedulerTransaction {
client: &self.client,
source: self.source,
transaction: &mut self.transaction,
}
}
#[must_use]
pub fn finish(self) -> sui::types::ProgrammableTransaction {
self.transaction.finish()
}
pub async fn submit(self) -> Result<ExecutedTransaction, NexusError> {
let sender = self.client.owner()?;
let transaction = self.transaction.finish();
self.client.submit_transaction(transaction, sender).await
}
}
pub struct SchedulerTransaction<'transaction> {
client: &'transaction NexusClient,
source: TransactionSource,
transaction: &'transaction mut NexusPtbBuilder,
}
impl SchedulerTransaction<'_> {
fn require_task_source(
&self,
task: &sui::types::ObjectReference,
) -> Result<(), SchedulerError> {
match self.source {
TransactionSource::Object(object_id) if object_id == *task.object_id() => Ok(()),
TransactionSource::Object(object_id) => Err(SchedulerError::InvalidRequest {
message: format!(
"transaction source object '{object_id}' differs from Task '{}'",
task.object_id()
),
}),
TransactionSource::Creator { package, role } => Err(SchedulerError::InvalidRequest {
message: format!(
"Task commands require an object source, but package '{package}' selected \
the '{}' creator role",
role.as_str()
),
}),
}
}
pub async fn create_task<'draft>(
&'draft mut self,
task: &TaskSpec,
) -> Result<TaskDraft<'draft>, SchedulerError> {
if !matches!(
self.source,
TransactionSource::Creator {
role: PackageRole::Scheduler,
..
}
) {
return Err(SchedulerError::InvalidRequest {
message: "Task creation requires an explicit Scheduler creator package".to_owned(),
});
}
let prepared = resolve::prepare_task(self.client, self.transaction.context(), task).await?;
let compiler = TaskDraftCompiler::create(self.transaction, &prepared)?;
Ok(TaskDraft {
client: self.client,
compiler,
})
}
pub fn dispatch_occurrence(
&mut self,
offer: &DispatchOffer,
task: &sui::types::ObjectReference,
dag: &sui::types::ObjectReference,
leader_cap: &sui::types::ObjectReference,
gas_charge: u64,
) -> Result<(), SchedulerError> {
self.require_task_source(task)?;
if offer.occurrence().task_id() != *task.object_id() {
return Err(SchedulerError::InvalidRequest {
message: format!(
"dispatch offer Task '{}' differs from object '{}'",
offer.occurrence().task_id(),
task.object_id()
),
});
}
compile_append_dispatch_occurrence(
self.transaction,
task,
dag,
leader_cap,
offer.occurrence().occurrence_id(),
gas_charge,
)
}
pub fn expire_occurrence(
&mut self,
occurrence: OccurrenceRef,
task: &sui::types::ObjectReference,
) -> Result<(), SchedulerError> {
self.require_task_source(task)?;
if occurrence.task_id() != *task.object_id() {
return Err(SchedulerError::InvalidRequest {
message: format!(
"occurrence Task '{}' differs from object '{}'",
occurrence.task_id(),
task.object_id()
),
});
}
compile_append_expire_occurrence(self.transaction, task, occurrence.occurrence_id())
}
}
#[must_use = "a Task draft must be shared before its transaction is finished"]
pub struct TaskDraft<'builder> {
client: &'builder NexusClient,
compiler: TaskDraftCompiler<'builder>,
}
impl TaskDraft<'_> {
pub async fn schedule(self, schedule: &Schedule) -> Result<Self, SchedulerError> {
let prepared = resolve::prepare_schedule(self.client, schedule).await?;
let compiler = self.compiler.schedule(&prepared)?;
Ok(Self {
client: self.client,
compiler,
})
}
pub fn share(self) -> Result<(), SchedulerError> {
let owner = self.client.owner().map_err(SchedulerError::transport)?;
self.compiler.share(owner)
}
}