use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::fmt::Debug;
use crate::batch::{BatchId, ProvenBatch};
use crate::block::{BlockHeader, BlockNumber};
use crate::crypto::merkle::MerkleError;
use crate::errors::{ProposedBatchError, ProposedBlockError};
use crate::note::{NoteHeader, NoteId, NoteInclusionProof, Nullifier};
use crate::transaction::{
InputNoteCommitment,
OutputNote,
PartialBlockchain,
ProvenTransaction,
TransactionId,
};
pub(crate) trait NoteContainer {
type Id: Copy + Eq + Debug;
fn id(&self) -> Self::Id;
fn input_notes(&self) -> impl Iterator<Item = InputNoteCommitment>;
fn output_notes(&self) -> impl Iterator<Item = OutputNote>;
}
impl NoteContainer for &ProvenTransaction {
type Id = TransactionId;
fn id(&self) -> Self::Id {
ProvenTransaction::id(self)
}
fn input_notes(&self) -> impl Iterator<Item = InputNoteCommitment> {
ProvenTransaction::input_notes(self).iter().cloned()
}
fn output_notes(&self) -> impl Iterator<Item = OutputNote> {
ProvenTransaction::output_notes(self).iter().cloned()
}
}
impl NoteContainer for &ProvenBatch {
type Id = BatchId;
fn id(&self) -> Self::Id {
ProvenBatch::id(self)
}
fn input_notes(&self) -> impl Iterator<Item = InputNoteCommitment> {
ProvenBatch::input_notes(self).iter().cloned()
}
fn output_notes(&self) -> impl Iterator<Item = OutputNote> {
ProvenBatch::output_notes(self).iter().cloned()
}
}
pub(crate) struct NoteTracker<'container, ContainerId: Copy + Eq + Debug> {
partial_blockchain: &'container PartialBlockchain,
reference_block: &'container BlockHeader,
unauthenticated_note_proofs: &'container BTreeMap<NoteId, NoteInclusionProof>,
input_notes: BTreeMap<Nullifier, (ContainerId, InputNoteCommitment)>,
output_notes: BTreeMap<NoteId, (ContainerId, OutputNote)>,
erased_notes: Vec<Nullifier>,
}
pub(crate) struct TrackerOutput<ContainerId> {
pub input_notes: Vec<InputNoteCommitment>,
pub erased_notes: Vec<Nullifier>,
pub output_notes: BTreeMap<NoteId, (ContainerId, OutputNote)>,
}
impl<'container, ContainerId: Copy + Eq + Debug> NoteTracker<'container, ContainerId> {
pub(crate) fn new(
partial_blockchain: &'container PartialBlockchain,
reference_block: &'container BlockHeader,
unauthenticated_note_proofs: &'container BTreeMap<NoteId, NoteInclusionProof>,
) -> Self {
Self {
partial_blockchain,
reference_block,
unauthenticated_note_proofs,
input_notes: BTreeMap::new(),
output_notes: BTreeMap::new(),
erased_notes: Vec::new(),
}
}
pub(crate) fn push<Container: NoteContainer<Id = ContainerId>>(
&mut self,
container: Container,
) -> Result<(), NoteTrackerError<ContainerId>> {
let container_id = container.id();
self.process_output_notes(container_id, container.output_notes())?;
self.process_input_notes(container_id, container.input_notes())?;
Ok(())
}
fn process_output_notes(
&mut self,
container_id: ContainerId,
output_notes: impl Iterator<Item = OutputNote>,
) -> Result<(), NoteTrackerError<ContainerId>> {
for output_note in output_notes {
let output_note_id = output_note.id();
if let Some((first_container_id, _)) =
self.output_notes.insert(output_note_id, (container_id, output_note))
{
return Err(NoteTrackerError::DuplicateOutputNote {
note_id: output_note_id,
first_container_id,
second_container_id: container_id,
});
}
}
Ok(())
}
fn process_input_notes(
&mut self,
container_id: ContainerId,
input_notes: impl Iterator<Item = InputNoteCommitment>,
) -> Result<(), NoteTrackerError<ContainerId>> {
'input_note_iter: for mut input_note_commitment in input_notes {
if let Some(input_note_header) = input_note_commitment.header() {
if let Some((created_by, _output_note)) =
self.output_notes.remove(&input_note_header.id())
{
assert_ne!(
created_by, container_id,
"transactions and batches should never create and consume the same note"
);
self.erased_notes.push(input_note_commitment.nullifier());
continue 'input_note_iter;
} else {
if let Some(proof) =
self.unauthenticated_note_proofs.get(&input_note_header.id())
{
input_note_commitment = self.authenticate_unauthenticated_note(
input_note_commitment.nullifier(),
input_note_header,
proof,
)?;
}
}
}
let nullifier = input_note_commitment.nullifier();
if let Some((first_container_id, _)) =
self.input_notes.insert(nullifier, (container_id, input_note_commitment))
{
return Err(NoteTrackerError::DuplicateInputNote {
note_nullifier: nullifier,
first_container_id,
second_container_id: container_id,
});
}
}
Ok(())
}
pub(crate) fn finalize(
self,
) -> Result<TrackerOutput<ContainerId>, NoteTrackerError<ContainerId>> {
for (consumed_by, unauthenticated_input_note) in
self.input_notes.values().filter_map(|(consumed_by, input_note_commitment)| {
input_note_commitment.header().map(|header| (consumed_by, header))
})
{
if let Some((created_by, _)) = self.output_notes.get(&unauthenticated_input_note.id()) {
return Err(NoteTrackerError::NoteConsumedBeforeCreated {
note_id: unauthenticated_input_note.id(),
consumed_by: *consumed_by,
created_by: *created_by,
});
}
}
Ok(TrackerOutput {
input_notes: self
.input_notes
.into_values()
.map(|(_, input_note_commitment)| input_note_commitment)
.collect(),
erased_notes: self.erased_notes,
output_notes: self.output_notes,
})
}
fn authenticate_unauthenticated_note(
&self,
nullifier: Nullifier,
note_header: &NoteHeader,
proof: &NoteInclusionProof,
) -> Result<InputNoteCommitment, NoteTrackerError<ContainerId>> {
let proof_reference_block = proof.location().block_num();
let note_block_header = if self.reference_block.block_num() == proof_reference_block {
self.reference_block
} else {
self.partial_blockchain.get_block(proof.location().block_num()).ok_or_else(|| {
NoteTrackerError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number: proof.location().block_num(),
note_id: note_header.id(),
}
})?
};
let note_index = proof.location().block_note_tree_index().into();
let note_id = note_header.id().as_word();
proof
.note_path()
.verify(note_index, note_id, ¬e_block_header.note_root())
.map_err(|source| NoteTrackerError::UnauthenticatedNoteAuthenticationFailed {
note_id: note_header.id(),
block_num: proof.location().block_num(),
source,
})?;
Ok(InputNoteCommitment::from(nullifier))
}
}
pub(crate) enum NoteTrackerError<ContainerId: Copy> {
DuplicateInputNote {
note_nullifier: Nullifier,
first_container_id: ContainerId,
second_container_id: ContainerId,
},
DuplicateOutputNote {
note_id: NoteId,
first_container_id: ContainerId,
second_container_id: ContainerId,
},
UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number: BlockNumber,
note_id: NoteId,
},
UnauthenticatedNoteAuthenticationFailed {
note_id: NoteId,
block_num: BlockNumber,
source: MerkleError,
},
NoteConsumedBeforeCreated {
note_id: NoteId,
consumed_by: ContainerId,
created_by: ContainerId,
},
}
impl From<NoteTrackerError<BatchId>> for ProposedBlockError {
fn from(error: NoteTrackerError<BatchId>) -> Self {
match error {
NoteTrackerError::DuplicateInputNote {
note_nullifier,
first_container_id,
second_container_id,
} => ProposedBlockError::DuplicateInputNote {
note_nullifier,
first_batch_id: first_container_id,
second_batch_id: second_container_id,
},
NoteTrackerError::DuplicateOutputNote {
note_id,
first_container_id,
second_container_id,
} => ProposedBlockError::DuplicateOutputNote {
note_id,
first_batch_id: first_container_id,
second_batch_id: second_container_id,
},
NoteTrackerError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
} => ProposedBlockError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
},
NoteTrackerError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
} => ProposedBlockError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
},
NoteTrackerError::NoteConsumedBeforeCreated { note_id, consumed_by, created_by } => {
ProposedBlockError::NoteConsumedBeforeCreated { note_id, consumed_by, created_by }
},
}
}
}
impl From<NoteTrackerError<TransactionId>> for ProposedBatchError {
fn from(error: NoteTrackerError<TransactionId>) -> Self {
match error {
NoteTrackerError::DuplicateInputNote {
note_nullifier,
first_container_id,
second_container_id,
} => ProposedBatchError::DuplicateInputNote {
note_nullifier,
first_transaction_id: first_container_id,
second_transaction_id: second_container_id,
},
NoteTrackerError::DuplicateOutputNote {
note_id,
first_container_id,
second_container_id,
} => ProposedBatchError::DuplicateOutputNote {
note_id,
first_transaction_id: first_container_id,
second_transaction_id: second_container_id,
},
NoteTrackerError::NoteConsumedBeforeCreated { note_id, consumed_by, created_by } => {
ProposedBatchError::NoteConsumedBeforeCreated { note_id, consumed_by, created_by }
},
NoteTrackerError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
} => ProposedBatchError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
},
NoteTrackerError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
} => ProposedBatchError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
},
}
}
}