use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::batch::{BatchId, ProvenBatch};
use crate::block::{BlockHeader, BlockNumber};
use crate::crypto::merkle::MerkleError;
use crate::errors::ProposedBatchError;
use crate::note::{NoteHeader, NoteId, NoteInclusionProof, Nullifier};
use crate::transaction::{
InputNoteCommitment,
OutputNote,
PartialBlockchain,
ProvenTransaction,
TransactionId,
};
use crate::{ProposedBlockError, Word};
type BatchInputNotes = Vec<InputNoteCommitment>;
type BlockInputNotes = Vec<InputNoteCommitment>;
type ErasedNotes = Vec<Nullifier>;
type BlockOutputNotes = BTreeMap<NoteId, (BatchId, OutputNote)>;
type BatchOutputNotes = Vec<OutputNote>;
#[derive(Debug)]
pub(crate) struct InputOutputNoteTracker<ContainerId> {
input_notes: BTreeMap<Nullifier, (ContainerId, InputNoteCommitment)>,
output_notes: BTreeMap<NoteId, (ContainerId, OutputNote)>,
}
impl InputOutputNoteTracker<TransactionId> {
pub fn from_transactions<'a>(
txs: impl Iterator<Item = &'a ProvenTransaction> + Clone,
unauthenticated_note_proofs: &BTreeMap<NoteId, NoteInclusionProof>,
partial_blockchain: &PartialBlockchain,
batch_reference_block: &BlockHeader,
) -> Result<(BatchInputNotes, BatchOutputNotes), ProposedBatchError> {
let input_notes_iter = txs.clone().flat_map(|tx| {
tx.input_notes()
.iter()
.map(|input_note_commitment| (input_note_commitment.clone(), tx.id()))
});
let output_notes_iter = txs.flat_map(|tx| {
tx.output_notes().iter().map(|output_note| (output_note.clone(), tx.id()))
});
let tracker = Self::from_iter(
input_notes_iter,
output_notes_iter,
unauthenticated_note_proofs,
partial_blockchain,
batch_reference_block,
)
.map_err(ProposedBatchError::from)?;
let (batch_input_notes, _erased_notes, batch_output_notes) =
tracker.erase_notes().map_err(ProposedBatchError::from)?;
let final_output_notes = batch_output_notes
.into_iter()
.map(|(_, (_, output_note))| output_note)
.collect();
Ok((batch_input_notes, final_output_notes))
}
}
impl InputOutputNoteTracker<BatchId> {
pub fn from_batches<'a>(
batches: impl Iterator<Item = &'a ProvenBatch> + Clone,
unauthenticated_note_proofs: &BTreeMap<NoteId, NoteInclusionProof>,
partial_blockchain: &PartialBlockchain,
prev_block: &BlockHeader,
) -> Result<(BlockInputNotes, ErasedNotes, BlockOutputNotes), ProposedBlockError> {
let input_notes_iter = batches.clone().flat_map(|batch| {
batch
.input_notes()
.iter()
.map(|input_note_commitment| (input_note_commitment.clone(), batch.id()))
});
let output_notes_iter = batches.flat_map(|batch| {
batch.output_notes().iter().map(|output_note| (output_note.clone(), batch.id()))
});
let tracker = Self::from_iter(
input_notes_iter,
output_notes_iter,
unauthenticated_note_proofs,
partial_blockchain,
prev_block,
)
.map_err(ProposedBlockError::from)?;
let (block_input_notes, erased_notes, block_output_notes) =
tracker.erase_notes().map_err(ProposedBlockError::from)?;
Ok((block_input_notes, erased_notes, block_output_notes))
}
}
impl<ContainerId: Copy> InputOutputNoteTracker<ContainerId> {
fn from_iter(
input_notes_iter: impl Iterator<Item = (InputNoteCommitment, ContainerId)>,
output_notes_iter: impl Iterator<Item = (OutputNote, ContainerId)>,
unauthenticated_note_proofs: &BTreeMap<NoteId, NoteInclusionProof>,
partial_blockchain: &PartialBlockchain,
reference_block: &BlockHeader,
) -> Result<Self, InputOutputNoteTrackerError<ContainerId>> {
let mut input_notes = BTreeMap::new();
let mut output_notes = BTreeMap::new();
for (mut input_note_commitment, container_id) in input_notes_iter {
if let Some(note_header) = input_note_commitment.header()
&& let Some(proof) = unauthenticated_note_proofs.get(¬e_header.id())
{
input_note_commitment = Self::authenticate_unauthenticated_note(
input_note_commitment.nullifier(),
note_header,
proof,
partial_blockchain,
reference_block,
)?;
}
let nullifier = input_note_commitment.nullifier();
if let Some((first_container_id, _)) =
input_notes.insert(nullifier, (container_id, input_note_commitment))
{
return Err(InputOutputNoteTrackerError::DuplicateInputNote {
note_nullifier: nullifier,
first_container_id,
second_container_id: container_id,
});
}
}
for (note, container_id) in output_notes_iter {
if let Some((first_container_id, _)) =
output_notes.insert(note.id(), (container_id, note.clone()))
{
return Err(InputOutputNoteTrackerError::DuplicateOutputNote {
note_id: note.id(),
first_container_id,
second_container_id: container_id,
});
}
}
Ok(Self { input_notes, output_notes })
}
#[allow(clippy::type_complexity)]
fn erase_notes(
mut self,
) -> Result<
(
Vec<InputNoteCommitment>,
ErasedNotes,
BTreeMap<NoteId, (ContainerId, OutputNote)>,
),
InputOutputNoteTrackerError<ContainerId>,
> {
let mut erased_notes = Vec::new();
let mut final_input_notes = Vec::new();
for (_, input_note_commitment) in self.input_notes.values() {
match input_note_commitment.header() {
Some(input_note_header) => {
let is_output_note =
Self::remove_output_note(input_note_header, &mut self.output_notes)?;
if is_output_note {
erased_notes.push(input_note_commitment.nullifier());
} else {
final_input_notes.push(input_note_commitment.clone());
}
},
None => {
final_input_notes.push(input_note_commitment.clone());
},
}
}
Ok((final_input_notes, erased_notes, self.output_notes))
}
fn remove_output_note(
input_note_header: &NoteHeader,
output_notes: &mut BTreeMap<NoteId, (ContainerId, OutputNote)>,
) -> Result<bool, InputOutputNoteTrackerError<ContainerId>> {
let id = input_note_header.id();
if let Some((_, output_note)) = output_notes.remove(&id) {
let input_commitment = input_note_header.commitment();
let output_commitment = output_note.commitment();
if output_commitment != input_commitment {
return Err(InputOutputNoteTrackerError::NoteCommitmentMismatch {
id,
input_commitment,
output_commitment,
});
}
return Ok(true);
}
Ok(false)
}
fn authenticate_unauthenticated_note(
nullifier: Nullifier,
note_header: &NoteHeader,
proof: &NoteInclusionProof,
partial_blockchain: &PartialBlockchain,
reference_block: &BlockHeader,
) -> Result<InputNoteCommitment, InputOutputNoteTrackerError<ContainerId>> {
let proof_reference_block = proof.location().block_num();
let note_block_header = if reference_block.block_num() == proof_reference_block {
reference_block
} else {
partial_blockchain.get_block(proof.location().block_num()).ok_or_else(|| {
InputOutputNoteTrackerError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number: proof.location().block_num(),
note_id: note_header.id(),
}
})?
};
let note_index = proof.location().node_index_in_block().into();
let note_commitment = note_header.commitment();
proof
.note_path()
.verify(note_index, note_commitment, ¬e_block_header.note_root())
.map_err(|source| {
InputOutputNoteTrackerError::UnauthenticatedNoteAuthenticationFailed {
note_id: note_header.id(),
block_num: proof.location().block_num(),
source,
}
})?;
Ok(InputNoteCommitment::from(nullifier))
}
}
enum InputOutputNoteTrackerError<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,
},
NoteCommitmentMismatch {
id: NoteId,
input_commitment: Word,
output_commitment: Word,
},
UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number: BlockNumber,
note_id: NoteId,
},
UnauthenticatedNoteAuthenticationFailed {
note_id: NoteId,
block_num: BlockNumber,
source: MerkleError,
},
}
impl From<InputOutputNoteTrackerError<BatchId>> for ProposedBlockError {
fn from(error: InputOutputNoteTrackerError<BatchId>) -> Self {
match error {
InputOutputNoteTrackerError::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,
},
InputOutputNoteTrackerError::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,
},
InputOutputNoteTrackerError::NoteCommitmentMismatch {
id,
input_commitment,
output_commitment,
} => ProposedBlockError::NoteCommitmentMismatch {
id,
input_commitment,
output_commitment,
},
InputOutputNoteTrackerError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
} => ProposedBlockError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
},
InputOutputNoteTrackerError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
} => ProposedBlockError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
},
}
}
}
impl From<InputOutputNoteTrackerError<TransactionId>> for ProposedBatchError {
fn from(error: InputOutputNoteTrackerError<TransactionId>) -> Self {
match error {
InputOutputNoteTrackerError::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,
},
InputOutputNoteTrackerError::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,
},
InputOutputNoteTrackerError::NoteCommitmentMismatch {
id,
input_commitment,
output_commitment,
} => ProposedBatchError::NoteCommitmentMismatch {
id,
input_commitment,
output_commitment,
},
InputOutputNoteTrackerError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
} => ProposedBatchError::UnauthenticatedInputNoteBlockNotInPartialBlockchain {
block_number,
note_id,
},
InputOutputNoteTrackerError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
} => ProposedBatchError::UnauthenticatedNoteAuthenticationFailed {
note_id,
block_num,
source,
},
}
}
}