Skip to content

Commit

Permalink
sync: use TCT block acceleration (#1716)
Browse files Browse the repository at this point in the history
* wip: use TCT insert_block

* code comments

* linting

* changeset

---------

Co-authored-by: Tal Derei <talderei99@gmail.com>
  • Loading branch information
hdevalence and TalDerei authored Sep 4, 2024
1 parent ad64968 commit 990291f
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 114 deletions.
5 changes: 5 additions & 0 deletions .changeset/twelve-schools-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/wasm': minor
---

TCT block acceleration
232 changes: 118 additions & 114 deletions packages/wasm/crate/src/view_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,140 +124,144 @@ impl ViewServer {

let mut found_new_data: bool = false;

for state_payload in block.state_payloads {
let clone_payload = state_payload.clone();
let mut note_advice = BTreeMap::new();
let mut swap_advice = BTreeMap::new();

// This structure allows all of the trial decryption to be done in one pass,
// before processing any of the decrypted data. This makes it easier to skip
// over an entire block with no known data.
for state_payload in &block.state_payloads {
match state_payload {
StatePayload::Note { note: payload, .. } => {
let note_opt = (!skip_trial_decrypt)
.then(|| payload.trial_decrypt(&self.fvk))
.flatten();
match note_opt {
Some(note) => {
let note_position = self.sct.insert(Keep, payload.note_commitment)?;

let source = clone_payload.source().clone();
let nullifier = Nullifier::derive(
self.fvk.nullifier_key(),
note_position,
clone_payload.commitment(),
);
let address_index = self
.fvk
.incoming()
.index_for_diversifier(note.diversifier());

let note_record = SpendableNoteRecord {
note_commitment: *clone_payload.commitment(),
height_spent: None,
height_created: block.height,
note: note.clone(),
address_index,
nullifier,
position: note_position,
source,
return_address: None,
};
self.notes
.insert(payload.note_commitment, note_record.clone());
found_new_data = true;
}
None => {
self.sct.insert(Forget, payload.note_commitment)?;
}
if let Some(note) = note_opt {
// It's safe to avoid recomputing the note commitment here because
// trial_decrypt checks that the decrypted data is consistent
note_advice.insert(payload.note_commitment, note);
}
}
StatePayload::Swap { swap: payload, .. } => {
let note_opt = (!skip_trial_decrypt)
let swap_opt = (!skip_trial_decrypt)
.then(|| payload.trial_decrypt(&self.fvk))
.flatten();
match note_opt {
Some(swap) => {
let swap_position = self.sct.insert(Keep, payload.commitment)?;
let batch_data =
block.swap_outputs.get(&swap.trading_pair).ok_or_else(|| {
anyhow::anyhow!("server gave invalid compact block")
})?;

let source = clone_payload.source().clone();
let nullifier = Nullifier::derive(
self.fvk.nullifier_key(),
swap_position,
clone_payload.commitment(),
);

let swap_record = SwapRecord {
swap_commitment: *clone_payload.commitment(),
swap: swap.clone(),
position: swap_position,
nullifier,
source,
output_data: *batch_data,
height_claimed: None,
};
self.swaps.insert(payload.commitment, swap_record);

let batch_data =
block.swap_outputs.get(&swap.trading_pair).ok_or_else(|| {
anyhow::anyhow!("server gave invalid compact block")
})?;

let (output_1, output_2) = swap.output_notes(batch_data);

self.storage.store_advice(output_1).await?;
self.storage.store_advice(output_2).await?;
found_new_data = true;
}
None => {
self.sct.insert(Forget, payload.commitment)?;
}
if let Some(swap) = swap_opt {
// It's safe to avoid recomputing the note commitment here because
// trial_decrypt checks that the decrypted data is consistent
swap_advice.insert(payload.commitment, swap);
}
}
StatePayload::RolledUp { commitment, .. } => {
// This is a note we anticipated, so retain its auth path.

let advice_result = self.storage.read_advice(commitment).await?;

match advice_result {
None => {
self.sct.insert(Forget, commitment)?;
}
Some(note) => {
let position = self.sct.insert(Keep, commitment)?;

let address_index_1 = self
.fvk
.incoming()
.index_for_diversifier(note.diversifier());

let nullifier =
Nullifier::derive(self.fvk.nullifier_key(), position, &commitment);

let source = clone_payload.source().clone();

let spendable_note = SpendableNoteRecord {
note_commitment: note.commit(),
height_spent: None,
height_created: block.height,
note: note.clone(),
address_index: address_index_1,
nullifier,
position,
source,
return_address: None,
};
self.notes
.insert(spendable_note.note_commitment, spendable_note.clone());
found_new_data = true;
}
// Query the storage to find out if we have stored advice for this note commitment.
if let Some(note) = self.storage.read_advice(*commitment).await? {
note_advice.insert(*commitment, note);
}
}
}
}

self.sct.end_block()?;
if note_advice.is_empty() && swap_advice.is_empty() {
// If there are no notes we care about in this block, just insert the block root into the
// tree instead of processing each commitment individually
self.sct
.insert_block(block.block_root)
.expect("inserting a block root must succeed");
} else {
// If we found at least one note for us in this block, we have to explicitly construct the
// whole block in the SCT by inserting each commitment one at a time
for payload in block.state_payloads.into_iter() {
// We proceed commitment by commitment, querying our in-memory advice
// to see if we have any data for the commitment and act accordingly.
// We need to insert each commitment, so use a match statement to ensure we
// exhaustively cover all possible cases.
match (
note_advice.get(payload.commitment()),
swap_advice.get(payload.commitment()),
) {
(Some(note), None) => {
let position = self.sct.insert(Keep, *payload.commitment())?;

let source = payload.source().clone();
let nullifier = Nullifier::derive(
self.fvk.nullifier_key(),
position,
payload.commitment(),
);
let address_index = self
.fvk
.incoming()
.index_for_diversifier(note.diversifier());

let note_record = SpendableNoteRecord {
note_commitment: *payload.commitment(),
height_spent: None,
height_created: block.height,
note: note.clone(),
address_index,
nullifier,
position,
source,
return_address: None,
};
self.notes
.insert(*payload.commitment(), note_record.clone());

found_new_data = true;
}
(None, Some(swap)) => {
let position = self.sct.insert(Keep, *payload.commitment())?;
let output_data = *block
.swap_outputs
.get(&swap.trading_pair)
.ok_or_else(|| anyhow::anyhow!("server gave invalid compact block"))?;

let source = payload.source().clone();
let nullifier = Nullifier::derive(
self.fvk.nullifier_key(),
position,
payload.commitment(),
);

// Save the output notes that will be created by this swap as advice
// so that we can correctly detect the rolled-up output notes when they
// are claimed in the future.
let (output_1, output_2) = swap.output_notes(&output_data);
self.storage.store_advice(output_1).await?;
self.storage.store_advice(output_2).await?;

let swap_record = SwapRecord {
swap_commitment: *payload.commitment(),
swap: swap.clone(),
position,
nullifier,
source,
output_data,
height_claimed: None,
};
self.swaps.insert(*payload.commitment(), swap_record);

found_new_data = true;
}
(None, None) => {
// Don't remember this commitment; it wasn't ours, and
// it doesn't matter what kind of payload it was either.
// Just insert and forget
self.sct
.insert(tct::Witness::Forget, *payload.commitment())
.expect("inserting a commitment must succeed");
}
(Some(_), Some(_)) => unreachable!("swap and note commitments are distinct"),
}
}

// End the block in the commitment tree
self.sct.end_block().expect("ending the block must succed");
}

// If we've also reached the end of the epoch, end the epoch in the commitment tree
if block.epoch_root.is_some() {
self.sct.end_epoch()?;
self.sct.end_epoch().expect("ending the epoch must succeed");
}

self.latest_height = block.height;
Expand Down

0 comments on commit 990291f

Please sign in to comment.