@@ -772,35 +772,51 @@ impl SchedulingStateMachine {
772
772
index : usize ,
773
773
usage_queue_loader : & mut impl FnMut ( Pubkey ) -> UsageQueue ,
774
774
) -> Task {
775
- // Calling the _unchecked() version here is safe for faster operation, because
776
- // `get_account_locks()` (the safe variant) is ensured to be called in
777
- // DefaultTransactionHandler::handle() via Bank::prepare_unlocked_batch_from_single_tx().
775
+ // It's crucial for tasks to be validated with
776
+ // `account_locks::validate_account_locks()` prior to the creation.
777
+ // That's because it's part of protocol consensus regarding the
778
+ // rejection of blocks containing malformed transactions
779
+ // (`AccountLoadedTwice` and `TooManyAccountLocks`). Even more,
780
+ // `SchedulingStateMachine` can't properly handle transactions with
781
+ // duplicate addresses (those falling under `AccountLoadedTwice`).
778
782
//
779
- // The safe variant has additional account-locking related verifications, which is crucial.
783
+ // However, it's okay for now not to call `::validate_account_locks()`
784
+ // here.
780
785
//
781
- // Currently the replaying stage is redundantly calling `get_account_locks()` when unified
782
- // scheduler is enabled on the given transaction at the blockstore. This will be relaxed
783
- // for optimization in the future. As for banking stage with unified scheduler, it will
784
- // need to run .get_account_locks() at least once somewhere in the code path. In the
785
- // distant future, this function `create_task()` should be adjusted so that both stages do
786
- // the checks before calling this (say, with some ad-hoc type like
787
- // `SanitizedTransactionWithCheckedAccountLocks`) or do the checks here, resulting in
788
- // eliminating the redundant one in the replaying stage and in the handler.
789
- let locks = transaction. get_account_locks_unchecked ( ) ;
790
-
791
- let writable_locks = locks
792
- . writable
793
- . iter ( )
794
- . map ( |address| ( address, RequestedUsage :: Writable ) ) ;
795
- let readonly_locks = locks
796
- . readonly
786
+ // Currently `replay_stage` is always calling
787
+ //`::validate_account_locks()` regardless of whether unified-scheduler
788
+ // is enabled or not at the blockstore
789
+ // (`Bank::prepare_sanitized_batch()` is called in
790
+ // `process_entries()`). This verification will be hoisted for
791
+ // optimization when removing
792
+ // `--block-verification-method=blockstore-processor`.
793
+ //
794
+ // As for `banking_stage` with unified scheduler, it will need to run
795
+ // `validate_account_locks()` at least once somewhere in the code path.
796
+ // In the distant future, this function (`create_task()`) should be
797
+ // adjusted so that both stages do the checks before calling this or do
798
+ // the checks here, to simplify the two code paths regarding the
799
+ // essential `validate_account_locks` validation.
800
+ //
801
+ // Lastly, `validate_account_locks()` is currently called in
802
+ // `DefaultTransactionHandler::handle()` via
803
+ // `Bank::prepare_unlocked_batch_from_single_tx()` as well.
804
+ // This redundancy is known. It was just left as-is out of abundance
805
+ // of caution.
806
+ let lock_contexts = transaction
807
+ . message ( )
808
+ . account_keys ( )
797
809
. iter ( )
798
- . map ( |address| ( address, RequestedUsage :: Readonly ) ) ;
799
-
800
- let lock_contexts = writable_locks
801
- . chain ( readonly_locks)
802
- . map ( |( address, requested_usage) | {
803
- LockContext :: new ( usage_queue_loader ( * * address) , requested_usage)
810
+ . enumerate ( )
811
+ . map ( |( index, address) | {
812
+ LockContext :: new (
813
+ usage_queue_loader ( * address) ,
814
+ if transaction. message ( ) . is_writable ( index) {
815
+ RequestedUsage :: Writable
816
+ } else {
817
+ RequestedUsage :: Readonly
818
+ } ,
819
+ )
804
820
} )
805
821
. collect ( ) ;
806
822
0 commit comments