Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync: fresh and existing wallets skip trial decryption #1707

Merged
merged 10 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/rude-camels-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@penumbra-zone/query': major
'@penumbra-zone/types': major
'@penumbra-zone/wasm': major
---

fresh and existing wallets skip trial decryption
46 changes: 41 additions & 5 deletions packages/query/src/block-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { getAssetIdFromGasPrices } from '@penumbra-zone/getters/compact-block';
import { getSpendableNoteRecordCommitment } from '@penumbra-zone/getters/spendable-note-record';
import { getSwapRecordCommitment } from '@penumbra-zone/getters/swap-record';
import { CompactBlock } from '@penumbra-zone/protobuf/penumbra/core/component/compact_block/v1/compact_block_pb';
import { shouldSkipTrialDecrypt } from './helpers/skip-trial-decrypt.js';

declare global {
// eslint-disable-next-line no-var
Expand All @@ -71,6 +72,13 @@ interface QueryClientProps {
numeraires: AssetId[];
stakingAssetId: AssetId;
genesisBlock: CompactBlock | undefined;
walletCreationBlockHeight: number | undefined;
}

interface ProcessBlockParams {
compactBlock: CompactBlock;
latestKnownBlockHeight: bigint;
skipTrialDecrypt?: boolean;
}

const BLANK_TX_SOURCE = new CommitmentSource({
Expand All @@ -91,7 +99,8 @@ export class BlockProcessor implements BlockProcessorInterface {
private numeraires: AssetId[];
private readonly stakingAssetId: AssetId;
private syncPromise: Promise<void> | undefined;
private genesisBlock: CompactBlock | undefined;
private readonly genesisBlock: CompactBlock | undefined;
private readonly walletCreationBlockHeight: number | undefined;

constructor({
indexedDb,
Expand All @@ -100,13 +109,15 @@ export class BlockProcessor implements BlockProcessorInterface {
numeraires,
stakingAssetId,
genesisBlock,
walletCreationBlockHeight,
}: QueryClientProps) {
this.indexedDb = indexedDb;
this.viewServer = viewServer;
this.querier = querier;
this.numeraires = numeraires;
this.stakingAssetId = stakingAssetId;
this.genesisBlock = genesisBlock;
this.walletCreationBlockHeight = walletCreationBlockHeight;
}

// If sync() is called multiple times concurrently, they'll all wait for
Expand Down Expand Up @@ -171,7 +182,18 @@ export class BlockProcessor implements BlockProcessorInterface {
// begin the chain with local genesis block if provided
if (this.genesisBlock?.height === currentHeight + 1n) {
currentHeight = this.genesisBlock.height;
await this.processBlock(this.genesisBlock, latestKnownBlockHeight);

// Set the trial decryption flag for the genesis compact block
const skipTrialDecrypt = shouldSkipTrialDecrypt(
this.walletCreationBlockHeight,
currentHeight,
);

await this.processBlock({
compactBlock: this.genesisBlock,
latestKnownBlockHeight: latestKnownBlockHeight,
skipTrialDecrypt,
});
}
}

Expand All @@ -189,7 +211,17 @@ export class BlockProcessor implements BlockProcessorInterface {
throw new Error(`Unexpected block height: ${compactBlock.height} at ${currentHeight}`);
}

await this.processBlock(compactBlock, latestKnownBlockHeight);
// Set the trial decryption flag for all other compact blocks
const skipTrialDecrypt = shouldSkipTrialDecrypt(
this.walletCreationBlockHeight,
currentHeight,
);

await this.processBlock({
compactBlock: compactBlock,
latestKnownBlockHeight: latestKnownBlockHeight,
skipTrialDecrypt,
});

// We only query Tendermint for the latest known block height once, when
// the block processor starts running. Once we're caught up, though, the
Expand All @@ -203,7 +235,11 @@ export class BlockProcessor implements BlockProcessorInterface {
}

// logic for processing a compact block
private async processBlock(compactBlock: CompactBlock, latestKnownBlockHeight: bigint) {
private async processBlock({
compactBlock,
latestKnownBlockHeight,
skipTrialDecrypt = false,
}: ProcessBlockParams) {
if (compactBlock.appParametersUpdated) {
await this.indexedDb.saveAppParams(await this.querier.app.appParams());
}
Expand All @@ -229,7 +265,7 @@ export class BlockProcessor implements BlockProcessorInterface {
// - decrypts new notes
// - decrypts new swaps
// - updates idb with advice
const scannerWantsFlush = await this.viewServer.scanBlock(compactBlock);
const scannerWantsFlush = await this.viewServer.scanBlock(compactBlock, skipTrialDecrypt);

// flushing is slow, avoid it until
// - wasm says
Expand Down
53 changes: 53 additions & 0 deletions packages/query/src/helpers/skip-trial-decrypt.test.ts
grod220 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest';
import { shouldSkipTrialDecrypt } from './skip-trial-decrypt.js';

describe('skipTrialDecrypt()', () => {
it('should not skip trial decryption if walletCreationBlockHeight is undefined', () => {
const currentHeight = 0n;
const walletCreationBlockHeight = undefined;

const skipTrialDecrypt = shouldSkipTrialDecrypt(walletCreationBlockHeight, currentHeight);

expect(skipTrialDecrypt).toBe(false);
});
it('should not skip trial decryption for genesis block when wallet creation block height is zero', () => {
const currentHeight = 0n;
const walletCreationBlockHeight = 0;

const skipTrialDecrypt = shouldSkipTrialDecrypt(walletCreationBlockHeight, currentHeight);

expect(skipTrialDecrypt).toBe(false);
});
it('should skip trial decryption for genesis block when wallet creation block height is not zero', () => {
const currentHeight = 0n;
const walletCreationBlockHeight = 100;

const skipTrialDecrypt = shouldSkipTrialDecrypt(walletCreationBlockHeight, currentHeight);

expect(skipTrialDecrypt).toBe(true);
});
it('should skip trial decryption for other blocks when wallet creation block height is not zero', () => {
const currentHeight = 1n;
const walletCreationBlockHeight = 100;

const skipTrialDecrypt = shouldSkipTrialDecrypt(walletCreationBlockHeight, currentHeight);

expect(skipTrialDecrypt).toBe(true);
});
it('should not skip trial decryption when wallet creation block height equals current height', () => {
const currentHeight = 100n;
const walletCreationBlockHeight = 100;

const skipTrialDecrypt = shouldSkipTrialDecrypt(walletCreationBlockHeight, currentHeight);

expect(skipTrialDecrypt).toBe(false);
grod220 marked this conversation as resolved.
Show resolved Hide resolved
});
it('should not skip trial decryption when wallet creation block height is greater than current height', () => {
const currentHeight = 200n;
const walletCreationBlockHeight = 100;

const skipTrialDecrypt = shouldSkipTrialDecrypt(walletCreationBlockHeight, currentHeight);

expect(skipTrialDecrypt).toBe(false);
});
});
11 changes: 11 additions & 0 deletions packages/query/src/helpers/skip-trial-decrypt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Used to determine whether trial decryption should be skipped for this block
export const shouldSkipTrialDecrypt = (
creationHeight: number | undefined,
currentHeight: bigint,
) => {
if (creationHeight === undefined || creationHeight === 0) {
return false;
}

return currentHeight < BigInt(creationHeight);
};
2 changes: 1 addition & 1 deletion packages/types/src/servers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CompactBlock } from '@penumbra-zone/protobuf/penumbra/core/component/co
import { MerkleRoot } from '@penumbra-zone/protobuf/penumbra/crypto/tct/v1/tct_pb';

export interface ViewServerInterface {
scanBlock(compactBlock: CompactBlock): Promise<boolean>;
scanBlock(compactBlock: CompactBlock, skipTrialDecrypt: boolean): Promise<boolean>;
flushUpdates(): ScanBlockResult;
resetTreeToStored(): Promise<void>;
getSctRoot(): MerkleRoot;
Expand Down
16 changes: 13 additions & 3 deletions packages/wasm/crate/src/view_server.rs
grod220 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ impl ViewServer {
/// Use `flush_updates()` to get the scan results
/// Returns: `bool`
#[wasm_bindgen]
pub async fn scan_block(&mut self, compact_block: &[u8]) -> WasmResult<bool> {
pub async fn scan_block(
&mut self,
compact_block: &[u8],
skip_trial_decrypt: bool,
) -> WasmResult<bool> {
grod220 marked this conversation as resolved.
Show resolved Hide resolved
utils::set_panic_hook();

let block = CompactBlock::decode(compact_block)?;
Expand All @@ -125,7 +129,10 @@ impl ViewServer {

match state_payload {
StatePayload::Note { note: payload, .. } => {
match payload.trial_decrypt(&self.fvk) {
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)?;

Expand Down Expand Up @@ -161,7 +168,10 @@ impl ViewServer {
}
}
StatePayload::Swap { swap: payload, .. } => {
match payload.trial_decrypt(&self.fvk) {
let note_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 =
Expand Down
4 changes: 2 additions & 2 deletions packages/wasm/src/view-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ export class ViewServer implements ViewServerInterface {
// Decrypts blocks with viewing key for notes, swaps, and updates revealed for user
// Makes update to internal state-commitment-tree as a side effect.
// Should extract updates via this.flushUpdates().
async scanBlock(compactBlock: CompactBlock): Promise<boolean> {
async scanBlock(compactBlock: CompactBlock, skipTrialDecrypt: boolean): Promise<boolean> {
const res = compactBlock.toBinary();
return this.wasmViewServer.scan_block(res);
return this.wasmViewServer.scan_block(res, skipTrialDecrypt);
}

// Resets the state of the wasmViewServer to the one set in storage
Expand Down
Loading