diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 742b0b6f6f..b8854bfbf2 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2551,28 +2551,19 @@ impl Bank { &self, hashed_tx: &HashedTransaction, status_cache: &StatusCache>, + check_duplicates_by_hash_enabled: bool, ) -> bool { let tx = hashed_tx.transaction(); - if status_cache - .get_status( - &hashed_tx.message_hash, - &tx.message().recent_blockhash, - &self.ancestors, - ) - .is_some() - { - return true; - } - - // Fallback to signature check in case this validator has only recently started - // adding the message hash to the status cache. - return tx - .signatures - .get(0) - .and_then(|sig0| { - status_cache.get_status(sig0, &tx.message().recent_blockhash, &self.ancestors) + let status_cache_key: Option<&[u8]> = if check_duplicates_by_hash_enabled { + Some(hashed_tx.message_hash.as_ref()) + } else { + tx.signatures.get(0).map(|sig0| sig0.as_ref()) + }; + status_cache_key + .and_then(|key| { + status_cache.get_status(key, &tx.message().recent_blockhash, &self.ancestors) }) - .is_some(); + .is_some() } fn check_status_cache( @@ -2582,11 +2573,18 @@ impl Bank { error_counters: &mut ErrorCounters, ) -> Vec { let rcache = self.src.status_cache.read().unwrap(); + let check_duplicates_by_hash_enabled = self.check_duplicates_by_hash_enabled(); hashed_txs .iter() .zip(lock_results) .map(|(hashed_tx, (lock_res, nonce_rollback))| { - if lock_res.is_ok() && self.is_tx_already_processed(hashed_tx, &rcache) { + if lock_res.is_ok() + && self.is_tx_already_processed( + hashed_tx, + &rcache, + check_duplicates_by_hash_enabled, + ) + { error_counters.already_processed += 1; return (Err(TransactionError::AlreadyProcessed), None); } @@ -4758,6 +4756,11 @@ impl Bank { .is_active(&feature_set::check_init_vote_data::id()) } + pub fn check_duplicates_by_hash_enabled(&self) -> bool { + self.feature_set + .is_active(&feature_set::check_duplicates_by_hash::id()) + } + // Check if the wallclock time from bank creation to now has exceeded the allotted // time for transaction processing pub fn should_bank_still_be_processing_txs( @@ -7958,7 +7961,8 @@ pub(crate) mod tests { #[test] fn test_tx_already_processed() { let (genesis_config, mint_keypair) = create_genesis_config(2); - let bank = Bank::new(&genesis_config); + let mut bank = Bank::new(&genesis_config); + assert!(!bank.check_duplicates_by_hash_enabled()); let key1 = Keypair::new(); let mut tx = @@ -7976,6 +7980,9 @@ pub(crate) mod tests { // Clear transaction signature tx.signatures[0] = Signature::default(); + // Enable duplicate check by message hash + bank.feature_set = Arc::new(FeatureSet::all_enabled()); + // Ensure that message hash check works assert_eq!( bank.process_transaction(&tx), diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 481b5afb71..e831de9468 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -131,6 +131,10 @@ pub mod sysvar_via_syscall { solana_sdk::declare_id!("7411E6gFQLDhQkdRjmpXwM1hzHMMoYQUjHicmvGPC1Nf"); } +pub mod check_duplicates_by_hash { + solana_sdk::declare_id!("8ZqTSYHgzyaYCcXJPMViRy6afCFSgNvYooPDeVdyj5GC"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -164,6 +168,7 @@ lazy_static! { (upgradeable_close_instruction::id(), "close upgradeable buffer accounts"), (demote_sysvar_write_locks::id(), "demote builtins and sysvar write locks to readonly #15497"), (sysvar_via_syscall::id(), "Provide sysvars via syscalls"), + (check_duplicates_by_hash::id(), "use transaction message hash for duplicate check"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()