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

feat: chunk genesis sync #257

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
112 changes: 104 additions & 8 deletions packages/query/src/block-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
skipTrialDecrypt?: boolean;
}

interface ProcessGenesisBlockParams {
start: number;
compactBlock: CompactBlock;
skipTrialDecrypt?: boolean;
}

const POSITION_STATES: PositionState[] = [
new PositionState({ state: PositionState_PositionStateEnum.OPENED }),
new PositionState({ state: PositionState_PositionStateEnum.CLOSED }),
Expand Down Expand Up @@ -145,6 +151,7 @@
*/
private async syncAndStore() {
const PRE_GENESIS_SYNC_HEIGHT = -1n;
const GENESIS_CHUNK_SIZE = 500;

// start at next block, or genesis if height is undefined
let currentHeight = (await this.indexedDb.getFullSyncHeight()) ?? PRE_GENESIS_SYNC_HEIGHT;
Expand Down Expand Up @@ -177,17 +184,46 @@
if (this.genesisBlock?.height === currentHeight + 1n) {
currentHeight = this.genesisBlock.height;

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

await this.processBlock({
compactBlock: this.genesisBlock,
latestKnownBlockHeight: latestKnownBlockHeight,
skipTrialDecrypt,
});
// to prevent blocking the single-threaded service worker environment, iterate through
// the genesis block's state payloads in manageable chunks to prevent blocking the
// single threaded service worker runtime. This approach segments the computationally
// intensive tasks of trial decryption and merkle poseidon hashing.
for (
let start = 0;
start < this.genesisBlock.statePayloads.length;
start += GENESIS_CHUNK_SIZE
) {
// slice out a subset of state payloads
const chunkedPayloads = this.genesisBlock.statePayloads.slice(
start,
start + GENESIS_CHUNK_SIZE,
);

const chunkedBlock = new CompactBlock({
...toPlainMessage(this.genesisBlock),
statePayloads: chunkedPayloads,
});

await this.trialDecryptGenesisChunk({
start,
compactBlock: chunkedBlock,
skipTrialDecrypt,
});

// check if this is the last chunk
if (start + GENESIS_CHUNK_SIZE >= this.genesisBlock.statePayloads.length) {
await this.genesisAdvice(this.genesisBlock);
}

// critically, we yield the event loop after each chunk
await new Promise(r => setTimeout(r, 0));

Check failure on line 225 in packages/query/src/block-processor.ts

View workflow job for this annotation

GitHub Actions / Lint

Return values from promise executor functions cannot be read
}
}
}

Expand All @@ -205,7 +241,7 @@
throw new Error(`Unexpected block height: ${compactBlock.height} at ${currentHeight}`);
}

// Set the trial decryption flag for all other compact blocks
// set the trial decryption flag for all other compact blocks
const skipTrialDecrypt = shouldSkipTrialDecrypt(
this.walletCreationBlockHeight,
currentHeight,
Expand All @@ -228,7 +264,67 @@
}
}

// logic for processing a compact block
/**
* Trial decrypts a chunk of the genesis block for notes owned by the wallet.
*/
private async trialDecryptGenesisChunk({
start,
compactBlock,
skipTrialDecrypt = false,
}: ProcessGenesisBlockParams) {
if (compactBlock.appParametersUpdated) {
await this.indexedDb.saveAppParams(await this.querier.app.appParams());
}
if (compactBlock.fmdParameters) {
await this.indexedDb.saveFmdParams(compactBlock.fmdParameters);
}
if (compactBlock.gasPrices) {
await this.indexedDb.saveGasPrices({
...toPlainMessage(compactBlock.gasPrices),
assetId: toPlainMessage(this.stakingAssetId),
});
}
if (compactBlock.altGasPrices.length) {
for (const altGas of compactBlock.altGasPrices) {
await this.indexedDb.saveGasPrices({
...toPlainMessage(altGas),
assetId: getAssetIdFromGasPrices(altGas),
});
}
}

await this.viewServer.trialDecryptGenesisChunk(BigInt(start), compactBlock, skipTrialDecrypt);

Check failure on line 296 in packages/query/src/block-processor.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe call of a(n) `error` type typed value
}

/**
* Processes accumulated genesis notes by constructing the state commitment tree (SCT)
* and saving relevant transaction data.
*/
private async genesisAdvice(compactBlock: CompactBlock) {
const scannerWantsFlush = await this.viewServer.genesisAdvice(compactBlock);

Check failure on line 304 in packages/query/src/block-processor.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an error typed value

Check failure on line 304 in packages/query/src/block-processor.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe call of a(n) `error` type typed value

const recordsByCommitment = new Map<StateCommitment, SpendableNoteRecord | SwapRecord>();
let flush: ScanBlockResult | undefined;
if (Object.values(scannerWantsFlush).some(Boolean)) {

Check failure on line 308 in packages/query/src/block-processor.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe argument of type error typed assigned to a parameter of type `{ [s: string]: unknown; } | ArrayLike<unknown>`
flush = this.viewServer.flushUpdates();

// in an atomic query, this
// - saves 'sctUpdates'
// - saves new decrypted notes
// - saves new decrypted swaps
// - updates last block synced
await this.indexedDb.saveScanResult(flush);

for (const spendableNoteRecord of flush.newNotes) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- TODO: justify non-null assertion
recordsByCommitment.set(spendableNoteRecord.noteCommitment!, spendableNoteRecord);
}
}
}

/**
* Logic for processing a compact block.
*/
private async processBlock({
compactBlock,
latestKnownBlockHeight,
Expand Down
Loading