use crate::{
alloc::{Allocator, Global, vec, vec::Vec},
codec::v1::{
Attestation, FinalizationError, MayHaveInput, PendingAttestation,
attestation::RawAttestation, opcode::OpCode,
},
utils::Hexed,
};
use allocator_api2::SliceExt;
use core::fmt::Debug;
use std::sync::OnceLock;
pub(crate) mod builder;
mod decode;
mod encode;
mod fmt;
#[derive(Clone, Debug)]
pub enum Timestamp<A: Allocator = Global> {
Step(Step<A>),
Attestation(RawAttestation<A>),
}
#[derive(Clone)]
pub struct Step<A: Allocator = Global> {
op: OpCode,
data: Vec<u8, A>,
input: OnceLock<Vec<u8, A>>,
next: Vec<Timestamp<A>, A>,
}
impl<A: Allocator> PartialEq for Timestamp<A> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Timestamp::Step(s1), Timestamp::Step(s2)) => s1 == s2,
(Timestamp::Attestation(a1), Timestamp::Attestation(a2)) => a1 == a2,
_ => false,
}
}
}
impl<A: Allocator> Eq for Timestamp<A> {}
impl<A: Allocator> PartialEq for Step<A> {
fn eq(&self, other: &Self) -> bool {
self.op == other.op && self.data == other.data && self.next == other.next
}
}
impl<A: Allocator> Eq for Step<A> {}
impl<A: Allocator + Debug> Debug for Step<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut f = f.debug_struct("Step");
f.field("op", &self.op);
if self.op.has_immediate() {
f.field("data", &Hexed(&self.data));
}
f.field("next", &self.next).finish()
}
}
impl Timestamp {
pub fn builder() -> builder::TimestampBuilder<Global> {
builder::TimestampBuilder::new_in(Global)
}
pub fn merge(timestamps: Vec<Timestamp, Global>) -> Timestamp {
Self::merge_in(timestamps, Global)
}
pub fn try_merge(timestamps: Vec<Timestamp, Global>) -> Result<Timestamp, FinalizationError> {
Self::try_merge_in(timestamps, Global)
}
}
impl<A: Allocator> Timestamp<A> {
pub fn op(&self) -> OpCode {
match self {
Timestamp::Step(step) => {
debug_assert_ne!(
step.op,
OpCode::ATTESTATION,
"sanity check failed: Step with ATTESTATION opcode"
);
step.op
}
Timestamp::Attestation(_) => OpCode::ATTESTATION,
}
}
#[inline]
pub fn as_step(&self) -> Option<&Step<A>> {
match self {
Timestamp::Step(step) => Some(step),
Timestamp::Attestation(_) => None,
}
}
#[inline]
pub fn as_attestation(&self) -> Option<&RawAttestation<A>> {
match self {
Timestamp::Attestation(attestation) => Some(attestation),
Timestamp::Step(_) => None,
}
}
#[inline]
pub fn allocator(&self) -> &A {
match self {
Self::Attestation(attestation) => attestation.allocator(),
Self::Step(step) => step.allocator(),
}
}
#[inline]
pub fn is_finalized(&self) -> bool {
self.input().is_some()
}
#[inline]
pub fn attestations(&self) -> AttestationIter<'_, A> {
AttestationIter { stack: vec![self] }
}
#[inline]
pub fn pending_attestations_mut(&mut self) -> PendingAttestationIterMut<'_, A> {
PendingAttestationIterMut { stack: vec![self] }
}
}
impl<A: Allocator + Clone> Timestamp<A> {
pub fn builder_in(alloc: A) -> builder::TimestampBuilder<A> {
builder::TimestampBuilder::new_in(alloc)
}
#[inline]
pub fn finalize(&self, input: &[u8]) {
self.try_finalize(input)
.expect("conflicting inputs when finalizing timestamp")
}
pub fn try_finalize(&self, input: &[u8]) -> Result<(), FinalizationError> {
let init_fn = || SliceExt::to_vec_in(input, self.allocator().clone());
match self {
Self::Attestation(attestation) => {
if let Some(already) = attestation.value.get() {
return if input != already {
Err(FinalizationError)
} else {
Ok(())
};
}
let _ = attestation.value.get_or_init(init_fn);
}
Self::Step(step) => {
if let Some(already) = step.input.get() {
return if input != already {
Err(FinalizationError)
} else {
Ok(())
};
}
let input = step.input.get_or_init(init_fn);
match step.op {
OpCode::FORK => {
debug_assert!(step.next.len() >= 2, "FORK must have at least two children");
for child in &step.next {
child.try_finalize(input)?;
}
}
OpCode::ATTESTATION => unreachable!("should not happen"),
op => {
let output = op.execute_in(input, &step.data, step.allocator().clone());
debug_assert!(step.next.len() == 1, "non-FORK must have exactly one child");
step.next[0].try_finalize(&output)?;
}
}
}
}
Ok(())
}
pub fn merge_in(timestamps: Vec<Timestamp<A>, A>, alloc: A) -> Timestamp<A> {
Self::try_merge_in(timestamps, alloc).expect("conflicting inputs when merging timestamps")
}
pub fn try_merge_in(
timestamps: Vec<Timestamp<A>, A>,
alloc: A,
) -> Result<Timestamp<A>, FinalizationError> {
let finalized_input = timestamps.iter().find_map(|ts| ts.input());
if let Some(input) = finalized_input {
for ts in timestamps.iter().filter(|ts| !ts.is_finalized()) {
ts.try_finalize(input)?;
}
}
Ok(Timestamp::Step(Step {
op: OpCode::FORK,
data: Vec::new_in(alloc.clone()),
input: OnceLock::new(),
next: timestamps,
}))
}
}
impl<A: Allocator> MayHaveInput for Timestamp<A> {
#[inline]
fn input(&self) -> Option<&[u8]> {
match self {
Timestamp::Step(step) => step.input(),
Timestamp::Attestation(attestation) => attestation.input(),
}
}
}
impl<A: Allocator> Step<A> {
pub fn op(&self) -> OpCode {
self.op
}
pub fn data(&self) -> &[u8] {
self.data.as_slice()
}
pub fn next(&self) -> &[Timestamp<A>] {
self.next.as_slice()
}
pub fn next_mut(&mut self) -> &mut [Timestamp<A>] {
self.next.as_mut_slice()
}
pub fn allocator(&self) -> &A {
self.data.allocator()
}
}
impl<A: Allocator> MayHaveInput for Step<A> {
#[inline]
fn input(&self) -> Option<&[u8]> {
self.input.get().map(|v| v.as_slice())
}
}
#[must_use = "AttestationIter is an iterator, it does nothing unless consumed"]
pub struct AttestationIter<'a, A: Allocator> {
stack: Vec<&'a Timestamp<A>>,
}
impl<'a, A: Allocator> Iterator for AttestationIter<'a, A> {
type Item = &'a RawAttestation<A>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(ts) = self.stack.pop() {
match ts {
Timestamp::Step(step) => {
for next in step.next().iter().rev() {
self.stack.push(next);
}
}
Timestamp::Attestation(attestation) => return Some(attestation),
}
}
None
}
}
pub struct PendingAttestationIterMut<'a, A: Allocator> {
stack: Vec<&'a mut Timestamp<A>>,
}
impl<'a, A: Allocator> Iterator for PendingAttestationIterMut<'a, A> {
type Item = &'a mut Timestamp<A>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(ts) = self.stack.pop() {
match ts {
Timestamp::Step(step) => {
for next in step.next_mut().iter_mut().rev() {
self.stack.push(next);
}
}
Timestamp::Attestation(attestation) => {
if attestation.tag == PendingAttestation::TAG {
return Some(ts);
}
}
}
}
None
}
}