use crate::prelude::*;
use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell;
use rand::{CryptoRng, Rng};
#[derive(Clone)]
pub struct ResponseBuilder<N: Network> {
request: OnceCell<Request<N>>,
outputs: Vec<Output<N>>,
events: Vec<Event<N>>,
errors: Vec<String>,
}
impl<N: Network> ResponseBuilder<N> {
pub fn new() -> Self {
Self {
request: OnceCell::new(),
outputs: Vec::with_capacity(N::NUM_OUTPUT_RECORDS),
events: Vec::new(),
errors: Vec::new(),
}
}
pub fn add_request(mut self, request: Request<N>) -> Self {
if self.request.get().is_some() {
self.errors.push("Builder already set a request".into());
} else if self.request.set(request).is_err() {
self.errors.push("Builder failed to set a request".into());
}
self
}
pub fn add_output(mut self, output: Output<N>) -> Self {
if self.request.get().is_none() && !output.is_noop() {
self.errors
.push("Builder cannot add new outputs before adding a request".into());
}
match self.outputs.len() < N::NUM_OUTPUT_RECORDS {
true => self.outputs.push(output),
false => self.errors.push("Builder exceeded maximum outputs".into()),
};
self
}
pub fn add_outputs(mut self, outputs: Vec<Output<N>>) -> Self {
for output in outputs {
self = self.add_output(output);
}
self
}
pub fn add_event(mut self, event: Event<N>) -> Self {
match self.events.len() < N::NUM_EVENTS as usize {
true => self.events.push(event),
false => self.errors.push("Builder exceeded maximum number of events".into()),
};
self
}
pub fn build<R: Rng + CryptoRng>(&self, rng: &mut R) -> Result<Response<N>> {
if !self.errors.is_empty() {
for error in &self.errors {
eprintln!("{}", error);
}
return Err(anyhow!("State builder encountered build errors: {:?}", self.errors));
}
let request = match self.request.get() {
Some(request) => request,
None => return Err(anyhow!("Builder is missing request")),
};
let mut events = self.events.clone();
let function_type = request.function_type();
let program_id = request.to_program_id()?;
let input_records = request.records();
let serial_numbers = request.to_serial_numbers()?;
let mut outputs = self.outputs.clone();
while outputs.len() < N::NUM_OUTPUT_RECORDS {
outputs.push(Output::new_noop(rng)?);
}
let (output_records, encryption_randomness): (Vec<_>, Vec<_>) = outputs
.iter()
.enumerate()
.take(N::NUM_OUTPUT_RECORDS)
.map(|(i, output)| {
let (record, encryption_randomness) = output.to_record(rng)?;
if request.is_public() && events.len() < N::NUM_EVENTS as usize {
events.push(Event::RecordViewKey(i as u8, record.record_view_key().clone()))
}
Ok((record, encryption_randomness))
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.unzip();
for (i, input_record) in input_records
.iter()
.enumerate()
.take(function_type.input_count() as usize)
{
if input_record.program_id() != program_id {
return Err(anyhow!("Program ID in input record {} is incorrect", i));
}
}
let commitments: Vec<_> = output_records
.iter()
.take(N::NUM_OUTPUT_RECORDS)
.map(Record::commitment)
.collect();
let mut value_balance = AleoAmount::ZERO;
for record in input_records.iter().take(N::NUM_INPUT_RECORDS) {
value_balance = value_balance.add(record.value());
}
for record in output_records.iter().take(N::NUM_OUTPUT_RECORDS) {
value_balance = value_balance.sub(record.value());
}
if value_balance != request.fee() {
return Err(anyhow!(
"Value balance does not match fee amount from request. Expected {} from request, found {} from response",
request.fee(),
value_balance
));
}
let transition_id = Transition::<N>::compute_transition_id(&serial_numbers, &commitments)?;
Response::new(
transition_id,
output_records,
encryption_randomness,
value_balance,
events,
)
}
}
impl<N: Network> Default for ResponseBuilder<N> {
fn default() -> Self {
Self::new()
}
}