diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 08de03a78a..7a767e1b9e 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -735,7 +735,7 @@ impl<'a> InvokeContext<'a> { _ => None, } .ok_or(InstructionError::UnsupportedProgramId)?; - entry.usage_counter.fetch_add(1, Ordering::Relaxed); + entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed); let program_id = *instruction_context.get_last_program_key(self.transaction_context)?; self.transaction_context diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index e213eeba51..fe00e00b36 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -105,8 +105,10 @@ pub struct LoadedProgram { pub effective_slot: Slot, /// Optional expiration slot for this entry, after which it is treated as non-existent pub maybe_expiration_slot: Option, - /// How often this entry was used - pub usage_counter: AtomicU64, + /// How often this entry was used by a transaction + pub tx_usage_counter: AtomicU64, + /// How often this entry was used by a transaction + pub ix_usage_counter: AtomicU64, } #[derive(Debug, Default)] @@ -264,8 +266,9 @@ impl LoadedProgram { account_size, effective_slot, maybe_expiration_slot, - usage_counter: AtomicU64::new(0), + tx_usage_counter: AtomicU64::new(0), program, + ix_usage_counter: AtomicU64::new(0), }) } @@ -276,7 +279,8 @@ impl LoadedProgram { deployment_slot: self.deployment_slot, effective_slot: self.effective_slot, maybe_expiration_slot: self.maybe_expiration_slot, - usage_counter: AtomicU64::new(self.usage_counter.load(Ordering::Relaxed)), + tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)), + ix_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)), } } @@ -295,8 +299,9 @@ impl LoadedProgram { account_size, effective_slot: deployment_slot, maybe_expiration_slot: None, - usage_counter: AtomicU64::new(0), + tx_usage_counter: AtomicU64::new(0), program: LoadedProgramType::Builtin(program), + ix_usage_counter: AtomicU64::new(0), } } @@ -309,7 +314,8 @@ impl LoadedProgram { deployment_slot: slot, effective_slot: slot, maybe_expiration_slot, - usage_counter: AtomicU64::default(), + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), }; debug_assert!(tombstone.is_tombstone()); tombstone @@ -444,8 +450,14 @@ impl LoadedPrograms { if matches!(existing.program, LoadedProgramType::Unloaded) { // The unloaded program is getting reloaded // Copy over the usage counter to the new entry - entry.usage_counter.store( - existing.usage_counter.load(Ordering::Relaxed), + let mut usage_count = existing.tx_usage_counter.load(Ordering::Relaxed); + saturating_add_assign!( + usage_count, + entry.tx_usage_counter.load(Ordering::Relaxed) + ); + entry.tx_usage_counter.store(usage_count, Ordering::Relaxed); + entry.ix_usage_counter.store( + existing.ix_usage_counter.load(Ordering::Relaxed), Ordering::Relaxed, ); second_level.swap_remove(entry_index); @@ -548,11 +560,11 @@ impl LoadedPrograms { pub fn extract( &self, working_slot: &S, - keys: impl Iterator, - ) -> (LoadedProgramsForTxBatch, Vec) { + keys: impl Iterator, + ) -> (LoadedProgramsForTxBatch, Vec<(Pubkey, u64)>) { let mut missing = Vec::new(); let found = keys - .filter_map(|(key, match_criteria)| { + .filter_map(|(key, (match_criteria, count))| { if let Some(second_level) = self.entries.get(&key) { for entry in second_level.iter().rev() { let current_slot = working_slot.current_slot(); @@ -561,11 +573,15 @@ impl LoadedPrograms { || working_slot.is_ancestor(entry.deployment_slot) { if !Self::is_entry_usable(entry, current_slot, &match_criteria) { - missing.push(key); + missing.push((key, count)); return None; } if current_slot >= entry.effective_slot { + let mut usage_count = + entry.tx_usage_counter.load(Ordering::Relaxed); + saturating_add_assign!(usage_count, count); + entry.tx_usage_counter.store(usage_count, Ordering::Relaxed); return Some((key, entry.clone())); } else if entry.is_implicit_delay_visibility_tombstone(current_slot) { // Found a program entry on the current fork, but it's not effective @@ -582,7 +598,7 @@ impl LoadedPrograms { } } } - missing.push(key); + missing.push((key, count)); None }) .collect::>>(); @@ -628,7 +644,7 @@ impl LoadedPrograms { | LoadedProgramType::Builtin(_) => None, }) }) - .sorted_by_cached_key(|(_id, program)| program.usage_counter.load(Ordering::Relaxed)) + .sorted_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed)) .collect(); let num_to_unload = sorted_candidates @@ -690,7 +706,7 @@ impl LoadedPrograms { for (id, program) in remove { if let Some(entries) = self.entries.get_mut(id) { if let Some(candidate) = entries.iter_mut().find(|entry| entry == &program) { - if candidate.usage_counter.load(Ordering::Relaxed) == 1 { + if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 { self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed); } self.stats @@ -758,7 +774,8 @@ mod tests { deployment_slot, effective_slot, maybe_expiration_slot: None, - usage_counter: AtomicU64::default(), + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), }) } @@ -935,7 +952,7 @@ mod tests { .flat_map(|(id, cached_programs)| { cached_programs.iter().filter_map(|program| { matches!(program.program, LoadedProgramType::Unloaded) - .then_some((*id, program.usage_counter.load(Ordering::Relaxed))) + .then_some((*id, program.tx_usage_counter.load(Ordering::Relaxed))) }) }) .collect::>(); @@ -990,7 +1007,7 @@ mod tests { programs.iter().for_each(|program| { if matches!(program.program, LoadedProgramType::Unloaded) { // Test that the usage counter is retained for the unloaded program - assert_eq!(program.usage_counter.load(Ordering::Relaxed), 10); + assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10); assert_eq!(program.deployment_slot, 0); assert_eq!(program.effective_slot, 2); } @@ -1011,7 +1028,7 @@ mod tests { && program.effective_slot == 2 { // Test that the usage counter was correctly updated. - assert_eq!(program.usage_counter.load(Ordering::Relaxed), 10); + assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10); } }) }); @@ -1253,7 +1270,8 @@ mod tests { deployment_slot, effective_slot, maybe_expiration_slot: None, - usage_counter, + tx_usage_counter: usage_counter, + ix_usage_counter: AtomicU64::default(), }) } @@ -1349,10 +1367,10 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 2)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 3)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 4)), ] .into_iter(), ); @@ -1360,18 +1378,18 @@ mod tests { assert!(match_slot(&found, &program1, 20, 22)); assert!(match_slot(&found, &program4, 0, 22)); - assert!(missing.contains(&program2)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program2, 2))); + assert!(missing.contains(&(program3, 3))); // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16 let mut working_slot = TestWorkingSlot::new(15, &[0, 5, 11, 15, 16, 18, 19, 23]); let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1388,17 +1406,17 @@ mod tests { )); assert_eq!(tombstone.deployment_slot, 15); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4). working_slot.update_slot(18); let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1409,17 +1427,17 @@ mod tests { // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18. assert!(match_slot(&found, &program4, 15, 18)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4). working_slot.update_slot(23); let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1430,17 +1448,17 @@ mod tests { // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23. assert!(match_slot(&found, &program4, 15, 23)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11 let working_slot = TestWorkingSlot::new(11, &[0, 5, 11, 15, 16]); let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1455,7 +1473,7 @@ mod tests { assert_eq!(tombstone.deployment_slot, 11); assert!(match_slot(&found, &program4, 5, 11)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // The following is a special case, where there's an expiration slot let test_program = Arc::new(LoadedProgram { @@ -1464,7 +1482,8 @@ mod tests { deployment_slot: 19, effective_slot: 19, maybe_expiration_slot: Some(21), - usage_counter: AtomicU64::default(), + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), }); assert!(!cache.replenish(program4, test_program).0); @@ -1473,10 +1492,10 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1486,7 +1505,7 @@ mod tests { // Program4 deployed at slot 19 should not be expired yet assert!(match_slot(&found, &program4, 19, 19)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 21 // This would cause program4 deployed at slot 19 to be expired. @@ -1494,10 +1513,10 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1505,8 +1524,8 @@ mod tests { assert!(match_slot(&found, &program1, 0, 21)); assert!(match_slot(&found, &program2, 11, 21)); - assert!(missing.contains(&program3)); - assert!(missing.contains(&program4)); + assert!(missing.contains(&(program3, 1))); + assert!(missing.contains(&(program4, 1))); // Remove the expired entry to let the rest of the test continue if let Some(programs) = cache.entries.get_mut(&program4) { @@ -1535,10 +1554,10 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1548,17 +1567,17 @@ mod tests { assert!(match_slot(&found, &program2, 11, 22)); assert!(match_slot(&found, &program4, 15, 22)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 let working_slot = TestWorkingSlot::new(27, &[11, 25, 27]); let (found, _missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1590,10 +1609,10 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), - (program4, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1603,7 +1622,7 @@ mod tests { assert!(match_slot(&found, &program4, 15, 23)); // program3 was deployed on slot 25, which has been pruned - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); } #[test] @@ -1646,9 +1665,9 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1656,7 +1675,7 @@ mod tests { assert!(match_slot(&found, &program1, 0, 12)); assert!(match_slot(&found, &program2, 11, 12)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // Test the same fork, but request the program modified at a later slot than what's in the cache. let (found, missing) = cache.extract( @@ -1664,21 +1683,21 @@ mod tests { vec![ ( program1, - LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), + (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1), ), ( program2, - LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), + (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1), ), - (program3, LoadedProgramMatchCriteria::NoCriteria), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); assert!(match_slot(&found, &program2, 11, 12)); - assert!(missing.contains(&program1)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program1, 1))); + assert!(missing.contains(&(program3, 1))); } #[test] @@ -1723,7 +1742,8 @@ mod tests { deployment_slot: 11, effective_slot: 11, maybe_expiration_slot: Some(15), - usage_counter: AtomicU64::default(), + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), }); assert!(!cache.replenish(program1, test_program).0); @@ -1732,9 +1752,9 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); @@ -1743,7 +1763,7 @@ mod tests { assert!(match_slot(&found, &program1, 11, 12)); assert!(match_slot(&found, &program2, 11, 12)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program3, 1))); // Testing fork 0 - 5 - 11 - 12 - 15 - 16 - 19 - 21 - 23 with current slot at 15 // This would cause program4 deployed at slot 15 to be expired. @@ -1751,17 +1771,17 @@ mod tests { let (found, missing) = cache.extract( &working_slot, vec![ - (program1, LoadedProgramMatchCriteria::NoCriteria), - (program2, LoadedProgramMatchCriteria::NoCriteria), - (program3, LoadedProgramMatchCriteria::NoCriteria), + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ] .into_iter(), ); assert!(match_slot(&found, &program2, 11, 15)); - assert!(missing.contains(&program1)); - assert!(missing.contains(&program3)); + assert!(missing.contains(&(program1, 1))); + assert!(missing.contains(&(program3, 1))); // Test that the program still exists in the cache, even though it is expired. assert_eq!( @@ -1816,7 +1836,7 @@ mod tests { let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); let (found, _missing) = cache.extract( &working_slot, - vec![(program1, LoadedProgramMatchCriteria::NoCriteria)].into_iter(), + vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(), ); // The cache should have the program deployed at slot 0 @@ -1838,7 +1858,8 @@ mod tests { deployment_slot: 0, effective_slot: 0, maybe_expiration_slot: None, - usage_counter: AtomicU64::default(), + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), }); assert!(!LoadedPrograms::is_entry_usable( &unloaded_entry, @@ -1934,7 +1955,8 @@ mod tests { deployment_slot: 0, effective_slot: 1, maybe_expiration_slot: Some(2), - usage_counter: AtomicU64::default(), + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), }); assert!(LoadedPrograms::is_entry_usable( diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 06c6ce442b..1cea36f5c4 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -205,8 +205,14 @@ macro_rules! deploy_program { Arc::new(program_runtime_environment), )?; if let Some(old_entry) = find_program_in_cache($invoke_context, &$program_id) { - let usage_counter = old_entry.usage_counter.load(Ordering::Relaxed); - executor.usage_counter.store(usage_counter, Ordering::Relaxed); + executor.tx_usage_counter.store( + old_entry.tx_usage_counter.load(Ordering::Relaxed), + Ordering::Relaxed + ); + executor.ix_usage_counter.store( + old_entry.ix_usage_counter.load(Ordering::Relaxed), + Ordering::Relaxed + ); } $drop load_program_metrics.program_id = $program_id.to_string(); @@ -562,7 +568,7 @@ fn process_instruction_inner( get_or_create_executor_time.as_us() ); - executor.usage_counter.fetch_add(1, Ordering::Relaxed); + executor.ix_usage_counter.fetch_add(1, Ordering::Relaxed); match &executor.program { LoadedProgramType::FailedVerification | LoadedProgramType::Closed @@ -4128,7 +4134,8 @@ mod tests { deployment_slot: 0, effective_slot: 0, maybe_expiration_slot: None, - usage_counter: AtomicU64::new(100), + tx_usage_counter: AtomicU64::new(100), + ix_usage_counter: AtomicU64::new(100), }; invoke_context .programs_modified_by_tx @@ -4145,7 +4152,14 @@ mod tests { .expect("Didn't find upgraded program in the cache"); assert_eq!(updated_program.deployment_slot, 2); - assert_eq!(updated_program.usage_counter.load(Ordering::Relaxed), 100); + assert_eq!( + updated_program.tx_usage_counter.load(Ordering::Relaxed), + 100 + ); + assert_eq!( + updated_program.ix_usage_counter.load(Ordering::Relaxed), + 100 + ); } #[test] @@ -4159,7 +4173,8 @@ mod tests { deployment_slot: 0, effective_slot: 0, maybe_expiration_slot: None, - usage_counter: AtomicU64::new(100), + tx_usage_counter: AtomicU64::new(100), + ix_usage_counter: AtomicU64::new(100), }; invoke_context .programs_modified_by_tx @@ -4177,6 +4192,7 @@ mod tests { .expect("Didn't find upgraded program in the cache"); assert_eq!(program2.deployment_slot, 2); - assert_eq!(program2.usage_counter.load(Ordering::Relaxed), 0); + assert_eq!(program2.tx_usage_counter.load(Ordering::Relaxed), 0); + assert_eq!(program2.ix_usage_counter.load(Ordering::Relaxed), 0); } } diff --git a/programs/loader-v4/src/lib.rs b/programs/loader-v4/src/lib.rs index fbf54b8b05..5befe98496 100644 --- a/programs/loader-v4/src/lib.rs +++ b/programs/loader-v4/src/lib.rs @@ -608,7 +608,9 @@ pub fn process_instruction_inner( get_or_create_executor_time.as_us() ); drop(program); - loaded_program.usage_counter.fetch_add(1, Ordering::Relaxed); + loaded_program + .ix_usage_counter + .fetch_add(1, Ordering::Relaxed); match &loaded_program.program { LoadedProgramType::FailedVerification | LoadedProgramType::Closed diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 8eba9f1d17..f96aa5ad1e 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -62,7 +62,10 @@ use { solana_system_program::{get_system_account_kind, SystemAccountKind}, std::{ cmp::Reverse, - collections::{hash_map, BinaryHeap, HashMap, HashSet}, + collections::{ + hash_map::{self, Entry}, + BinaryHeap, HashMap, HashSet, + }, num::NonZeroUsize, ops::RangeBounds, path::PathBuf, @@ -291,7 +294,7 @@ impl Accounts { key: &Pubkey, feature_set: &FeatureSet, program: &LoadedProgram, - program_accounts: &HashMap, + program_accounts: &HashMap, ) -> Result { // Check for tombstone let result = match &program.program { @@ -314,7 +317,7 @@ impl Accounts { // So the account data is not needed. Return a dummy AccountSharedData with meta // information. let mut program_account = AccountSharedData::default(); - let program_owner = program_accounts + let (program_owner, _count) = program_accounts .get(key) .ok_or(TransactionError::AccountNotFound)?; program_account.set_owner(**program_owner); @@ -333,7 +336,7 @@ impl Accounts { feature_set: &FeatureSet, account_overrides: Option<&AccountOverrides>, _reward_interval: RewardInterval, - program_accounts: &HashMap, + program_accounts: &HashMap, loaded_programs: &LoadedProgramsForTxBatch, ) -> Result { // NOTE: this check will never fail because `tx` is sanitized @@ -637,8 +640,8 @@ impl Accounts { lock_results: &mut [TransactionCheckResult], program_owners: &[&'a Pubkey], hash_queue: &BlockhashQueue, - ) -> HashMap { - let mut result = HashMap::new(); + ) -> HashMap { + let mut result: HashMap = HashMap::new(); lock_results.iter_mut().zip(txs).for_each(|etx| { if let ((Ok(()), nonce), tx) = etx { if nonce @@ -649,19 +652,26 @@ impl Accounts { }) .is_some() { - tx.message().account_keys().iter().for_each(|key| { - if !result.contains_key(key) { - if let Ok(index) = self.accounts_db.account_matches_owners( - ancestors, - key, - program_owners, - ) { - program_owners - .get(index) - .and_then(|owner| result.insert(*key, *owner)); + tx.message() + .account_keys() + .iter() + .for_each(|key| match result.entry(*key) { + Entry::Occupied(mut entry) => { + let (_, count) = entry.get_mut(); + saturating_add_assign!(*count, 1); } - } - }); + Entry::Vacant(entry) => { + if let Ok(index) = self.accounts_db.account_matches_owners( + ancestors, + key, + program_owners, + ) { + program_owners + .get(index) + .map(|owner| entry.insert((*owner, 1))); + } + } + }); } else { // If the transaction's nonce account was not valid, and blockhash is not found, // the transaction will fail to process. Let's not load any programs from the @@ -686,7 +696,7 @@ impl Accounts { fee_structure: &FeeStructure, account_overrides: Option<&AccountOverrides>, in_reward_interval: RewardInterval, - program_accounts: &HashMap, + program_accounts: &HashMap, loaded_programs: &LoadedProgramsForTxBatch, ) -> Vec { txs.iter() @@ -2088,13 +2098,13 @@ mod tests { programs .get(&account3_pubkey) .expect("failed to find the program account"), - &&program1_pubkey + &(&program1_pubkey, 2) ); assert_eq!( programs .get(&account4_pubkey) .expect("failed to find the program account"), - &&program2_pubkey + &(&program2_pubkey, 1) ); } @@ -2197,7 +2207,7 @@ mod tests { programs .get(&account3_pubkey) .expect("failed to find the program account"), - &&program1_pubkey + &(&program1_pubkey, 1) ); assert_eq!(lock_results[1].0, Err(TransactionError::BlockhashNotFound)); } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index faac20c8a0..df88a4255f 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -181,7 +181,7 @@ use { sync::{ atomic::{ AtomicBool, AtomicI64, AtomicU64, AtomicUsize, - Ordering::{AcqRel, Acquire, Relaxed}, + Ordering::{self, AcqRel, Acquire, Relaxed}, }, Arc, LockResult, RwLock, RwLockReadGuard, RwLockWriteGuard, }, @@ -4407,26 +4407,31 @@ impl Bank { fn replenish_program_cache( &self, - program_accounts_map: &HashMap, + program_accounts_map: &HashMap, ) -> LoadedProgramsForTxBatch { - let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> = + let programs_and_slots: Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))> = if self.check_program_modification_slot { program_accounts_map - .keys() - .map(|pubkey| { + .iter() + .map(|(pubkey, (_, count))| { ( *pubkey, - self.program_modification_slot(pubkey) - .map_or(LoadedProgramMatchCriteria::Tombstone, |slot| { - LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot) - }), + ( + self.program_modification_slot(pubkey) + .map_or(LoadedProgramMatchCriteria::Tombstone, |slot| { + LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot) + }), + *count, + ), ) }) .collect() } else { program_accounts_map - .keys() - .map(|pubkey| (*pubkey, LoadedProgramMatchCriteria::NoCriteria)) + .iter() + .map(|(pubkey, (_, count))| { + (*pubkey, (LoadedProgramMatchCriteria::NoCriteria, *count)) + }) .collect() }; @@ -4439,7 +4444,7 @@ impl Bank { // Load missing programs while global cache is unlocked let missing_programs: Vec<(Pubkey, Arc)> = missing_programs .iter() - .map(|key| { + .map(|(key, count)| { let program = self.load_program(key).unwrap_or_else(|err| { // Create a tombstone for the program in the cache debug!("Failed to load program {}, error {:?}", key, err); @@ -4448,6 +4453,7 @@ impl Bank { LoadedProgramType::FailedVerification, )) }); + program.tx_usage_counter.store(*count, Ordering::Relaxed); (*key, program) }) .collect(); @@ -4539,7 +4545,7 @@ impl Bank { ); let native_loader = native_loader::id(); for builtin_program in self.builtin_programs.iter() { - program_accounts_map.insert(*builtin_program, &native_loader); + program_accounts_map.insert(*builtin_program, (&native_loader, 0)); } let programs_loaded_for_tx_batch = Rc::new(RefCell::new(