@@ -38,7 +38,7 @@ import { type IncomingNoteDao } from '../database/incoming_note_dao.js';
38
38
import { type PxeDatabase } from '../database/index.js' ;
39
39
import { produceNoteDaos } from '../note_decryption_utils/produce_note_daos.js' ;
40
40
import { getAcirSimulator } from '../simulator/index.js' ;
41
- import { getInitialIndexes , getLeftMostIndexedTaggingSecrets , getRightMostIndexes } from './tagging_utils.js' ;
41
+ import { getIndexedTaggingSecretsForTheWindow , getInitialIndexesMap } from './tagging_utils.js' ;
42
42
43
43
/**
44
44
* A data oracle that provides information needed for simulating a transaction.
@@ -424,116 +424,137 @@ export class SimulatorOracle implements DBOracle {
424
424
// Half the size of the window we slide over the tagging secret indexes.
425
425
const WINDOW_HALF_SIZE = 10 ;
426
426
427
+ // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles.
428
+ // However it is impossible at the moment due to the language not supporting nested slices.
429
+ // This nesting is necessary because for a given set of tags we don't
430
+ // know how many logs we will get back. Furthermore, these logs are of undetermined
431
+ // length, since we don't really know the note they correspond to until we decrypt them.
432
+
427
433
const recipients = scopes ? scopes : await this . keyStore . getAccounts ( ) ;
428
- // A map of never-before-seen logs going from recipient address to logs
429
- const newLogsMap = new Map < string , TxScopedL2Log [ ] > ( ) ;
434
+ // A map of logs going from recipient address to logs. Note that the logs might have been processed before
435
+ // due to us having a sliding window that "looks back" for logs as well. (We look back as there is no guarantee
436
+ // that a logs will be received ordered by a given tax index and that the tags won't be reused).
437
+ const logsMap = new Map < string , TxScopedL2Log [ ] > ( ) ;
430
438
const contractName = await this . contractDataOracle . getDebugContractName ( contractAddress ) ;
431
439
for ( const recipient of recipients ) {
432
- const logs : TxScopedL2Log [ ] = [ ] ;
433
- // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles.
434
- // However it is impossible at the moment due to the language not supporting nested slices.
435
- // This nesting is necessary because for a given set of tags we don't
436
- // know how many logs we will get back. Furthermore, these logs are of undetermined
437
- // length, since we don't really know the note they correspond to until we decrypt them.
438
-
439
- // 1. Get all the secrets for the recipient and sender pairs (#9365)
440
- const indexedTaggingSecrets = await this . #getIndexedTaggingSecretsForContacts ( contractAddress , recipient ) ;
441
-
442
- // 1.1 Set up a sliding window with an offset. Chances are the sender might have messed up
443
- // and inadvertently incremented their index without us getting any logs (for example, in case
444
- // of a revert). If we stopped looking for logs the first time we don't receive any logs for a tag,
445
- // we might never receive anything from that sender again.
446
- // Also there's a possibility that we have advanced our index, but the sender has reused it,
447
- // so we might have missed some logs. For these reasons, we have to look both back and ahead of
448
- // the stored index.
449
-
450
- // App tagging secrets along with an index in a window to check in the current iteration. Called current because
451
- // this value will be updated as we iterate through the window.
452
- let currentSecrets = getLeftMostIndexedTaggingSecrets ( indexedTaggingSecrets , WINDOW_HALF_SIZE ) ;
453
- // Right-most indexes in a window to check stored in a key-value map where key is the app tagging secret
454
- // and value is the index to check (the right-most index in the window).
455
- const rightMostIndexesMap = getRightMostIndexes ( indexedTaggingSecrets , WINDOW_HALF_SIZE ) ;
440
+ const logsForRecipient : TxScopedL2Log [ ] = [ ] ;
441
+
442
+ // Get all the secrets for the recipient and sender pairs (#9365)
443
+ const secrets = await this . #getIndexedTaggingSecretsForContacts ( contractAddress , recipient ) ;
444
+
445
+ // We fetch logs for a window of indexes in a range:
446
+ // <latest_log_index - WINDOW_HALF_SIZE, latest_log_index + WINDOW_HALF_SIZE>.
447
+ //
448
+ // We use this window approach because it could happen that a sender might have messed up and inadvertently
449
+ // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking
450
+ // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again.
451
+ // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed
452
+ // some logs. For these reasons, we have to look both back and ahead of the stored index.
453
+ let secretsAndWindows = secrets . map ( secret => {
454
+ return {
455
+ appTaggingSecret : secret . appTaggingSecret ,
456
+ leftMostIndex : Math . max ( 0 , secret . index - WINDOW_HALF_SIZE ) ,
457
+ rightMostIndex : secret . index + WINDOW_HALF_SIZE ,
458
+ } ;
459
+ } ) ;
460
+
461
+ // As we iterate we store the largest index we have seen for a given secret to later on store it in the db.
462
+ const newLargestIndexMapToStore : { [ k : string ] : number } = { } ;
463
+
456
464
// The initial/unmodified indexes of the secrets stored in a key-value map where key is the app tagging secret.
457
- const initialIndexesMap = getInitialIndexes ( indexedTaggingSecrets ) ;
458
- // A map of indexes to increment for secrets for which we have found logs with an index higher than the one
459
- // stored.
460
- const indexesToIncrementMap : { [ k : string ] : number } = { } ;
461
-
462
- while ( currentSecrets . length > 0 ) {
463
- // 2. Compute tags using the secrets, recipient and index. Obtain logs for each tag (#9380)
464
- const currentTags = currentSecrets . map ( secret =>
465
- // We compute the siloed tags since we need the tags as they appear in the log.
465
+ const initialIndexesMap = getInitialIndexesMap ( secrets ) ;
466
+
467
+ while ( secretsAndWindows . length > 0 ) {
468
+ const secretsForTheWholeWindow = getIndexedTaggingSecretsForTheWindow ( secretsAndWindows ) ;
469
+ const tagsForTheWholeWindow = secretsForTheWholeWindow . map ( secret =>
466
470
secret . computeSiloedTag ( recipient , contractAddress ) ,
467
471
) ;
468
472
473
+ // We store the new largest indexes we find in the iteration in the following map to later on construct
474
+ // a new set of secrets and windows to fetch logs for.
475
+ const newLargestIndexMapForIteration : { [ k : string ] : number } = { } ;
476
+
469
477
// Fetch the logs for the tags and iterate over them
470
- const logsByTags = await this . aztecNode . getLogsByTags ( currentTags ) ;
471
- const secretsWithNewIndex : IndexedTaggingSecret [ ] = [ ] ;
478
+ const logsByTags = await this . aztecNode . getLogsByTags ( tagsForTheWholeWindow ) ;
479
+
472
480
logsByTags . forEach ( ( logsByTag , logIndex ) => {
473
- const { appTaggingSecret : currentSecret , index : currentIndex } = currentSecrets [ logIndex ] ;
474
- const currentSecretAsStr = currentSecret . toString ( ) ;
475
- this . log . debug ( `Syncing logs for recipient ${ recipient } at contract ${ contractName } (${ contractAddress } )` , {
476
- recipient,
477
- secret : currentSecret ,
478
- index : currentIndex ,
479
- contractName,
480
- contractAddress,
481
- } ) ;
482
- // 3.1. Append logs to the list and increment the index for the tags that have logs (#9380)
483
481
if ( logsByTag . length > 0 ) {
484
- const newIndex = currentIndex + 1 ;
485
- this . log . debug (
486
- `Found ${ logsByTag . length } logs as recipient ${ recipient } . Incrementing index to ${ newIndex } at contract ${ contractName } (${ contractAddress } )` ,
487
- {
488
- recipient,
489
- secret : currentSecret ,
490
- newIndex,
491
- contractName,
492
- contractAddress,
493
- } ,
494
- ) ;
495
- logs . push ( ...logsByTag ) ;
496
-
497
- if ( currentIndex >= initialIndexesMap [ currentSecretAsStr ] ) {
498
- // 3.2. We found an index higher than the stored/initial one so we update it in the db later on (#9380)
499
- indexesToIncrementMap [ currentSecretAsStr ] = newIndex ;
500
- // 3.3. We found an index higher than the initial one so we slide the window.
501
- rightMostIndexesMap [ currentSecretAsStr ] = currentIndex + WINDOW_HALF_SIZE ;
482
+ // The logs for the given tag exist so we store them for later processing
483
+ logsForRecipient . push ( ...logsByTag ) ;
484
+
485
+ // We retrieve the indexed tagging secret corresponding to the log as I need that to evaluate whether
486
+ // a new largest index have been found.
487
+ const secretCorrespondingToLog = secretsForTheWholeWindow [ logIndex ] ;
488
+ const initialIndex = initialIndexesMap [ secretCorrespondingToLog . appTaggingSecret . toString ( ) ] ;
489
+
490
+ this . log . debug ( `Found ${ logsByTag . length } logs as recipient ${ recipient } ` , {
491
+ recipient,
492
+ secret : secretCorrespondingToLog . appTaggingSecret ,
493
+ contractName,
494
+ contractAddress,
495
+ } ) ;
496
+
497
+ if (
498
+ secretCorrespondingToLog . index >= initialIndex &&
499
+ ( newLargestIndexMapForIteration [ secretCorrespondingToLog . appTaggingSecret . toString ( ) ] === undefined ||
500
+ secretCorrespondingToLog . index >=
501
+ newLargestIndexMapForIteration [ secretCorrespondingToLog . appTaggingSecret . toString ( ) ] )
502
+ ) {
503
+ // We have found a new largest index so we store it for later processing (storing it in the db + fetching
504
+ // the difference of the window sets of current and the next iteration)
505
+ newLargestIndexMapForIteration [ secretCorrespondingToLog . appTaggingSecret . toString ( ) ] =
506
+ secretCorrespondingToLog . index + 1 ;
507
+
508
+ this . log . debug (
509
+ `Incrementing index to ${
510
+ secretCorrespondingToLog . index + 1
511
+ } at contract ${ contractName } (${ contractAddress } )`,
512
+ ) ;
502
513
}
503
514
}
504
- // 3.4 Keep increasing the index (inside the window) temporarily for the tags that have no logs
505
- // There's a chance the sender missed some and we want to catch up
506
- if ( currentIndex < rightMostIndexesMap [ currentSecretAsStr ] ) {
507
- const newTaggingSecret = new IndexedTaggingSecret ( currentSecret , currentIndex + 1 ) ;
508
- secretsWithNewIndex . push ( newTaggingSecret ) ;
509
- }
510
515
} ) ;
511
516
512
- // We store the new indexes for the secrets that have logs with an index higher than the one stored.
513
- await this . db . setTaggingSecretsIndexesAsRecipient (
514
- Object . keys ( indexesToIncrementMap ) . map (
515
- secret => new IndexedTaggingSecret ( Fr . fromHexString ( secret ) , indexesToIncrementMap [ secret ] ) ,
516
- ) ,
517
- ) ;
517
+ // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs
518
+ // for. Note that it's very unlikely that a new log from the current window would appear between the iterations
519
+ // so we fetch the logs only for the difference of the window sets.
520
+ const newSecretsAndWindows = [ ] ;
521
+ for ( const [ appTaggingSecret , newIndex ] of Object . entries ( newLargestIndexMapForIteration ) ) {
522
+ const secret = secrets . find ( secret => secret . appTaggingSecret . toString ( ) === appTaggingSecret ) ;
523
+ if ( secret ) {
524
+ newSecretsAndWindows . push ( {
525
+ appTaggingSecret : secret . appTaggingSecret ,
526
+ // We set the left most index to the new index to avoid fetching the same logs again
527
+ leftMostIndex : newIndex ,
528
+ rightMostIndex : newIndex + WINDOW_HALF_SIZE ,
529
+ } ) ;
530
+
531
+ // We store the new largest index in the map to later store it in the db.
532
+ newLargestIndexMapToStore [ appTaggingSecret ] = newIndex ;
533
+ } else {
534
+ throw new Error (
535
+ `Secret not found for appTaggingSecret ${ appTaggingSecret } . This is a bug as it should never happen!` ,
536
+ ) ;
537
+ }
538
+ }
518
539
519
- // We've processed all the current secret-index pairs so we proceed to the next iteration.
520
- currentSecrets = secretsWithNewIndex ;
540
+ // Now we set the new secrets and windows and proceed to the next iteration.
541
+ secretsAndWindows = newSecretsAndWindows ;
521
542
}
522
543
523
- newLogsMap . set (
544
+ // We filter the logs by block number and store them in the map.
545
+ logsMap . set (
524
546
recipient . toString ( ) ,
525
- // Remove logs with a block number higher than the max block number
526
- // Duplicates are likely to happen due to the sliding window, so we also filter them out
527
- logs . filter (
528
- ( log , index , self ) =>
529
- // The following condition is true if the log has small enough block number and is unique
530
- // --> the right side of the && is true if the index of the current log is the first occurrence
531
- // of the log in the array --> that way we ensure uniqueness.
532
- log . blockNumber <= maxBlockNumber && index === self . findIndex ( otherLog => otherLog . equals ( log ) ) ,
547
+ logsForRecipient . filter ( log => log . blockNumber <= maxBlockNumber ) ,
548
+ ) ;
549
+
550
+ // At this point we have processed all the logs for the recipient so we store the new largest indexes in the db.
551
+ await this . db . setTaggingSecretsIndexesAsRecipient (
552
+ Object . entries ( newLargestIndexMapToStore ) . map (
553
+ ( [ appTaggingSecret , index ] ) => new IndexedTaggingSecret ( Fr . fromHexString ( appTaggingSecret ) , index ) ,
533
554
) ,
534
555
) ;
535
556
}
536
- return newLogsMap ;
557
+ return logsMap ;
537
558
}
538
559
539
560
/**
0 commit comments