Sfoglia il codice sorgente

Accounting migrating builtin programs default Compute Unit Limit with feature status (#3975)

* Accounting migrating builtin programs default Compute Unit Limit with its feature gate status

* Declare Non/migrating buiiltins in const array, eleminates heap allocation (Vec<>) per transaction

* updates for review commients

add explicit positional information to migrating builtin feature obj

update developer notes, added static_assertion to validate no new items are added, add enum type

* use enum to separately define migrating and not-migrating builtins

* rename for clarity
Tao Zhu 11 mesi fa
parent
commit
9379fbcba4

+ 1 - 0
Cargo.lock

@@ -6312,6 +6312,7 @@ dependencies = [
  "solana-stake-program",
  "solana-stake-program",
  "solana-system-program",
  "solana-system-program",
  "solana-vote-program",
  "solana-vote-program",
+ "static_assertions",
 ]
 ]
 
 
 [[package]]
 [[package]]

+ 2 - 0
builtins-default-costs/Cargo.toml

@@ -35,6 +35,7 @@ name = "solana_builtins_default_costs"
 
 
 [dev-dependencies]
 [dev-dependencies]
 rand = "0.8.5"
 rand = "0.8.5"
+static_assertions = { workspace = true }
 
 
 [package.metadata.docs.rs]
 [package.metadata.docs.rs]
 targets = ["x86_64-unknown-linux-gnu"]
 targets = ["x86_64-unknown-linux-gnu"]
@@ -44,6 +45,7 @@ frozen-abi = [
     "dep:solana-frozen-abi",
     "dep:solana-frozen-abi",
     "solana-vote-program/frozen-abi",
     "solana-vote-program/frozen-abi",
 ]
 ]
+dev-context-only-utils = []
 
 
 [lints]
 [lints]
 workspace = true
 workspace = true

+ 252 - 77
builtins-default-costs/src/lib.rs

@@ -12,15 +12,68 @@ use {
     },
     },
 };
 };
 
 
+#[derive(Clone)]
+pub struct MigratingBuiltinCost {
+    native_cost: u64,
+    core_bpf_migration_feature: Pubkey,
+    // encoding positional information explicitly for migration feature item,
+    // its value must be correctly corresponding to this object's position
+    // in MIGRATING_BUILTINS_COSTS, otherwise a const validation
+    // `validate_position(MIGRATING_BUILTINS_COSTS)` will fail at compile time.
+    position: usize,
+}
+
+#[derive(Clone)]
+pub struct NotMigratingBuiltinCost {
+    native_cost: u64,
+}
+
 /// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding
 /// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding
-/// migration feature ID to BUILTIN_INSTRUCTION_COSTS, so the builtin's default
-/// cost can be determined properly based on feature status.
+/// migration feature ID to BUILTIN_INSTRUCTION_COSTS, and move it from
+/// NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, so the builtin's
+/// default cost can be determined properly based on feature status.
 /// When migration completed, eg the feature gate is enabled everywhere, please
 /// When migration completed, eg the feature gate is enabled everywhere, please
-/// remove that builtin entry from BUILTIN_INSTRUCTION_COSTS.
+/// remove that builtin entry from MIGRATING_BUILTINS_COSTS.
 #[derive(Clone)]
 #[derive(Clone)]
-struct BuiltinCost {
-    native_cost: u64,
-    core_bpf_migration_feature: Option<Pubkey>,
+pub enum BuiltinCost {
+    Migrating(MigratingBuiltinCost),
+    NotMigrating(NotMigratingBuiltinCost),
+}
+
+impl BuiltinCost {
+    pub fn native_cost(&self) -> u64 {
+        match self {
+            BuiltinCost::Migrating(MigratingBuiltinCost { native_cost, .. }) => *native_cost,
+            BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost }) => *native_cost,
+        }
+    }
+
+    pub fn core_bpf_migration_feature(&self) -> Option<&Pubkey> {
+        match self {
+            BuiltinCost::Migrating(MigratingBuiltinCost {
+                core_bpf_migration_feature,
+                ..
+            }) => Some(core_bpf_migration_feature),
+            BuiltinCost::NotMigrating(_) => None,
+        }
+    }
+
+    pub fn position(&self) -> Option<usize> {
+        match self {
+            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => Some(*position),
+            BuiltinCost::NotMigrating(_) => None,
+        }
+    }
+
+    fn has_migrated(&self, feature_set: &FeatureSet) -> bool {
+        match self {
+            BuiltinCost::Migrating(MigratingBuiltinCost {
+                core_bpf_migration_feature,
+                ..
+            }) => feature_set.is_active(core_bpf_migration_feature),
+            BuiltinCost::NotMigrating(_) => false,
+        }
+    }
 }
 }
 
 
 lazy_static! {
 lazy_static! {
@@ -33,100 +86,110 @@ lazy_static! {
     /// calculate the cost of a transaction which is used in replay to enforce
     /// calculate the cost of a transaction which is used in replay to enforce
     /// block cost limits as of
     /// block cost limits as of
     /// https://github.com/solana-labs/solana/issues/29595.
     /// https://github.com/solana-labs/solana/issues/29595.
-    static ref BUILTIN_INSTRUCTION_COSTS: AHashMap<Pubkey, BuiltinCost> = [
+    static ref BUILTIN_INSTRUCTION_COSTS: AHashMap<Pubkey, BuiltinCost> =
+        MIGRATING_BUILTINS_COSTS
+          .iter()
+          .chain(NON_MIGRATING_BUILTINS_COSTS.iter())
+          .cloned()
+          .collect();
+    // DO NOT ADD MORE ENTRIES TO THIS MAP
+}
+
+/// DEVELOPER WARNING: please do not add new entry into MIGRATING_BUILTINS_COSTS or
+/// NON_MIGRATING_BUILTINS_COSTS, do so will modify BUILTIN_INSTRUCTION_COSTS therefore
+/// cause consensus failure. However, when a builtin started being migrated to core bpf,
+/// it MUST be moved from NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, then
+/// correctly furnishing `core_bpf_migration_feature`.
+///
+#[allow(dead_code)]
+const TOTAL_COUNT_BUILTS: usize = 12;
+#[cfg(test)]
+static_assertions::const_assert_eq!(
+    MIGRATING_BUILTINS_COSTS.len() + NON_MIGRATING_BUILTINS_COSTS.len(),
+    TOTAL_COUNT_BUILTS
+);
+
+pub const MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
     (
     (
         stake::id(),
         stake::id(),
-        BuiltinCost {
+        BuiltinCost::Migrating(MigratingBuiltinCost {
             native_cost: solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS,
             native_cost: solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS,
-            core_bpf_migration_feature: Some(feature_set::migrate_stake_program_to_core_bpf::id()),
-        },
+            core_bpf_migration_feature: feature_set::migrate_stake_program_to_core_bpf::id(),
+            position: 0,
+        }),
     ),
     ),
     (
     (
         config::id(),
         config::id(),
-        BuiltinCost {
+        BuiltinCost::Migrating(MigratingBuiltinCost {
             native_cost: solana_config_program::config_processor::DEFAULT_COMPUTE_UNITS,
             native_cost: solana_config_program::config_processor::DEFAULT_COMPUTE_UNITS,
-            core_bpf_migration_feature: Some(feature_set::migrate_config_program_to_core_bpf::id()),
-        },
+            core_bpf_migration_feature: feature_set::migrate_config_program_to_core_bpf::id(),
+            position: 1,
+        }),
     ),
     ),
+    (
+        address_lookup_table::id(),
+        BuiltinCost::Migrating(MigratingBuiltinCost {
+            native_cost: solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS,
+            core_bpf_migration_feature:
+                feature_set::migrate_address_lookup_table_program_to_core_bpf::id(),
+            position: 2,
+        }),
+    ),
+];
+
+pub const NON_MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
     (
     (
         vote::id(),
         vote::id(),
-        BuiltinCost {
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
             native_cost: solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
             native_cost: solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
-            core_bpf_migration_feature: None,
-        },
+        }),
     ),
     ),
     (
     (
         system_program::id(),
         system_program::id(),
-        BuiltinCost {
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
             native_cost: solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
             native_cost: solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
-            core_bpf_migration_feature: None,
-        },
+        }),
     ),
     ),
     (
     (
         compute_budget::id(),
         compute_budget::id(),
-        BuiltinCost {
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
             native_cost: solana_compute_budget_program::DEFAULT_COMPUTE_UNITS,
             native_cost: solana_compute_budget_program::DEFAULT_COMPUTE_UNITS,
-            core_bpf_migration_feature: None,
-        },
-    ),
-    (
-        address_lookup_table::id(),
-        BuiltinCost {
-            native_cost: solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS,
-            core_bpf_migration_feature: Some(
-                feature_set::migrate_address_lookup_table_program_to_core_bpf::id(),
-            ),
-        },
+        }),
     ),
     ),
     (
     (
         bpf_loader_upgradeable::id(),
         bpf_loader_upgradeable::id(),
-        BuiltinCost {
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
             native_cost: solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS,
             native_cost: solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS,
-            core_bpf_migration_feature: None,
-        },
+        }),
     ),
     ),
     (
     (
         bpf_loader_deprecated::id(),
         bpf_loader_deprecated::id(),
-        BuiltinCost {
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
             native_cost: solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS,
             native_cost: solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS,
-            core_bpf_migration_feature: None,
-        },
+        }),
     ),
     ),
     (
     (
         bpf_loader::id(),
         bpf_loader::id(),
-        BuiltinCost {
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
             native_cost: solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS,
             native_cost: solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS,
-            core_bpf_migration_feature: None,
-        },
+        }),
     ),
     ),
     (
     (
         loader_v4::id(),
         loader_v4::id(),
-        BuiltinCost {
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
             native_cost: solana_loader_v4_program::DEFAULT_COMPUTE_UNITS,
             native_cost: solana_loader_v4_program::DEFAULT_COMPUTE_UNITS,
-            core_bpf_migration_feature: None,
-        },
+        }),
     ),
     ),
     // Note: These are precompile, run directly in bank during sanitizing;
     // Note: These are precompile, run directly in bank during sanitizing;
     (
     (
         secp256k1_program::id(),
         secp256k1_program::id(),
-        BuiltinCost {
-            native_cost: 0,
-            core_bpf_migration_feature: None,
-        },
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
     ),
     ),
     (
     (
         ed25519_program::id(),
         ed25519_program::id(),
-        BuiltinCost {
-            native_cost: 0,
-            core_bpf_migration_feature: None,
-        },
+        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
     ),
     ),
-    // DO NOT ADD MORE ENTRIES TO THIS MAP
-    ]
-    .iter()
-    .cloned()
-    .collect();
-}
+];
 
 
 lazy_static! {
 lazy_static! {
     /// A table of 256 booleans indicates whether the first `u8` of a Pubkey exists in
     /// A table of 256 booleans indicates whether the first `u8` of a Pubkey exists in
@@ -148,28 +211,84 @@ pub fn get_builtin_instruction_cost<'a>(
 ) -> Option<u64> {
 ) -> Option<u64> {
     BUILTIN_INSTRUCTION_COSTS
     BUILTIN_INSTRUCTION_COSTS
         .get(program_id)
         .get(program_id)
-        .filter(
-            // Returns true if builtin program id has no core_bpf_migration_feature or feature is not activated;
-            // otherwise returns false because it's not considered as builtin
-            |builtin_cost| -> bool {
-                builtin_cost
-                    .core_bpf_migration_feature
-                    .map(|feature_id| !feature_set.is_active(&feature_id))
-                    .unwrap_or(true)
-            },
-        )
-        .map(|builtin_cost| builtin_cost.native_cost)
+        .filter(|builtin_cost| !builtin_cost.has_migrated(feature_set))
+        .map(|builtin_cost| builtin_cost.native_cost())
 }
 }
 
 
-#[inline]
-pub fn is_builtin_program(program_id: &Pubkey) -> bool {
-    BUILTIN_INSTRUCTION_COSTS.contains_key(program_id)
+pub enum BuiltinMigrationFeatureIndex {
+    NotBuiltin,
+    BuiltinNoMigrationFeature,
+    BuiltinWithMigrationFeature(usize),
+}
+
+pub fn get_builtin_migration_feature_index(program_id: &Pubkey) -> BuiltinMigrationFeatureIndex {
+    BUILTIN_INSTRUCTION_COSTS.get(program_id).map_or(
+        BuiltinMigrationFeatureIndex::NotBuiltin,
+        |builtin_cost| {
+            builtin_cost.position().map_or(
+                BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
+                BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature,
+            )
+        },
+    )
+}
+
+/// const function validates `position` correctness at compile time.
+#[allow(dead_code)]
+const fn validate_position(migrating_builtins: &[(Pubkey, BuiltinCost)]) {
+    let mut index = 0;
+    while index < migrating_builtins.len() {
+        match migrating_builtins[index].1 {
+            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => assert!(
+                position == index,
+                "migration feture must exist and at correct position"
+            ),
+            BuiltinCost::NotMigrating(_) => {
+                panic!("migration feture must exist and at correct position")
+            }
+        }
+        index += 1;
+    }
+}
+const _: () = validate_position(MIGRATING_BUILTINS_COSTS);
+
+/// Helper function to return ref of migration feature Pubkey at position `index`
+/// from MIGRATING_BUILTINS_COSTS
+pub fn get_migration_feature_id(index: usize) -> &'static Pubkey {
+    MIGRATING_BUILTINS_COSTS
+        .get(index)
+        .expect("valid index of MIGRATING_BUILTINS_COSTS")
+        .1
+        .core_bpf_migration_feature()
+        .expect("migrating builtin")
+}
+
+#[cfg(feature = "dev-context-only-utils")]
+pub fn get_migration_feature_position(feature_id: &Pubkey) -> usize {
+    MIGRATING_BUILTINS_COSTS
+        .iter()
+        .position(|(_, c)| c.core_bpf_migration_feature().expect("migrating builtin") == feature_id)
+        .unwrap()
 }
 }
 
 
 #[cfg(test)]
 #[cfg(test)]
 mod test {
 mod test {
     use super::*;
     use super::*;
 
 
+    #[test]
+    fn test_const_builtin_cost_arrays() {
+        // sanity check to make sure built-ins are declared in the correct array
+        assert!(MIGRATING_BUILTINS_COSTS
+            .iter()
+            .enumerate()
+            .all(|(index, (_, c))| {
+                c.core_bpf_migration_feature().is_some() && c.position() == Some(index)
+            }));
+        assert!(NON_MIGRATING_BUILTINS_COSTS
+            .iter()
+            .all(|(_, c)| c.core_bpf_migration_feature().is_none()));
+    }
+
     #[test]
     #[test]
     fn test_get_builtin_instruction_cost() {
     fn test_get_builtin_instruction_cost() {
         // use native cost if no migration planned
         // use native cost if no migration planned
@@ -181,15 +300,11 @@ mod test {
         // use native cost if migration is planned but not activated
         // use native cost if migration is planned but not activated
         assert_eq!(
         assert_eq!(
             Some(solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS),
             Some(solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS),
-            get_builtin_instruction_cost(&solana_stake_program::id(), &FeatureSet::default())
+            get_builtin_instruction_cost(&stake::id(), &FeatureSet::default())
         );
         );
 
 
         // None if migration is planned and activated, in which case, it's no longer builtin
         // None if migration is planned and activated, in which case, it's no longer builtin
-        assert!(get_builtin_instruction_cost(
-            &solana_stake_program::id(),
-            &FeatureSet::all_enabled()
-        )
-        .is_none());
+        assert!(get_builtin_instruction_cost(&stake::id(), &FeatureSet::all_enabled()).is_none());
 
 
         // None if not builtin
         // None if not builtin
         assert!(
         assert!(
@@ -200,4 +315,64 @@ mod test {
                 .is_none()
                 .is_none()
         );
         );
     }
     }
+
+    #[test]
+    fn test_get_builtin_migration_feature_index() {
+        assert!(matches!(
+            get_builtin_migration_feature_index(&Pubkey::new_unique()),
+            BuiltinMigrationFeatureIndex::NotBuiltin
+        ));
+        assert!(matches!(
+            get_builtin_migration_feature_index(&compute_budget::id()),
+            BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
+        ));
+        let feature_index = get_builtin_migration_feature_index(&stake::id());
+        assert!(matches!(
+            feature_index,
+            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
+        ));
+        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
+            feature_index
+        else {
+            panic!("expect migrating builtin")
+        };
+        assert_eq!(
+            get_migration_feature_id(feature_index),
+            &feature_set::migrate_stake_program_to_core_bpf::id()
+        );
+        let feature_index = get_builtin_migration_feature_index(&config::id());
+        assert!(matches!(
+            feature_index,
+            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
+        ));
+        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
+            feature_index
+        else {
+            panic!("expect migrating builtin")
+        };
+        assert_eq!(
+            get_migration_feature_id(feature_index),
+            &feature_set::migrate_config_program_to_core_bpf::id()
+        );
+        let feature_index = get_builtin_migration_feature_index(&address_lookup_table::id());
+        assert!(matches!(
+            feature_index,
+            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
+        ));
+        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
+            feature_index
+        else {
+            panic!("expect migrating builtin")
+        };
+        assert_eq!(
+            get_migration_feature_id(feature_index),
+            &feature_set::migrate_address_lookup_table_program_to_core_bpf::id()
+        );
+    }
+
+    #[test]
+    #[should_panic(expected = "valid index of MIGRATING_BUILTINS_COSTS")]
+    fn test_get_migration_feature_id_invalid_index() {
+        let _ = get_migration_feature_id(MIGRATING_BUILTINS_COSTS.len() + 1);
+    }
 }
 }

+ 1 - 0
compute-budget-instruction/Cargo.toml

@@ -26,6 +26,7 @@ name = "solana_compute_budget_instruction"
 bincode = { workspace = true }
 bincode = { workspace = true }
 criterion = { workspace = true }
 criterion = { workspace = true }
 rand = { workspace = true }
 rand = { workspace = true }
+solana-builtins-default-costs =  { workspace = true, features = ["dev-context-only-utils"] }
 solana-program = { workspace = true }
 solana-program = { workspace = true }
 
 
 [package.metadata.docs.rs]
 [package.metadata.docs.rs]

+ 47 - 6
compute-budget-instruction/src/builtin_programs_filter.rs

@@ -1,5 +1,7 @@
 use {
 use {
-    solana_builtins_default_costs::{is_builtin_program, MAYBE_BUILTIN_KEY},
+    solana_builtins_default_costs::{
+        get_builtin_migration_feature_index, BuiltinMigrationFeatureIndex, MAYBE_BUILTIN_KEY,
+    },
     solana_sdk::{packet::PACKET_DATA_SIZE, pubkey::Pubkey},
     solana_sdk::{packet::PACKET_DATA_SIZE, pubkey::Pubkey},
 };
 };
 
 
@@ -10,6 +12,12 @@ pub const FILTER_SIZE: u8 = (PACKET_DATA_SIZE / core::mem::size_of::<Pubkey>())
 pub(crate) enum ProgramKind {
 pub(crate) enum ProgramKind {
     NotBuiltin,
     NotBuiltin,
     Builtin,
     Builtin,
+    // Builtin program maybe in process of being migrated to core bpf,
+    // if core_bpf_migration_feature is activated, then the migration has
+    // completed and it should no longer be considered as builtin
+    MigratingBuiltin {
+        core_bpf_migration_feature_index: usize,
+    },
 }
 }
 
 
 pub(crate) struct BuiltinProgramsFilter {
 pub(crate) struct BuiltinProgramsFilter {
@@ -40,17 +48,24 @@ impl BuiltinProgramsFilter {
             return ProgramKind::NotBuiltin;
             return ProgramKind::NotBuiltin;
         }
         }
 
 
-        if is_builtin_program(program_id) {
-            ProgramKind::Builtin
-        } else {
-            ProgramKind::NotBuiltin
+        match get_builtin_migration_feature_index(program_id) {
+            BuiltinMigrationFeatureIndex::NotBuiltin => ProgramKind::NotBuiltin,
+            BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature => ProgramKind::Builtin,
+            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(
+                core_bpf_migration_feature_index,
+            ) => ProgramKind::MigratingBuiltin {
+                core_bpf_migration_feature_index,
+            },
         }
         }
     }
     }
 }
 }
 
 
 #[cfg(test)]
 #[cfg(test)]
 mod test {
 mod test {
-    use super::*;
+    use {
+        super::*, solana_builtins_default_costs::get_migration_feature_position,
+        solana_sdk::feature_set,
+    };
 
 
     const DUMMY_PROGRAM_ID: &str = "dummmy1111111111111111111111111111111111111";
     const DUMMY_PROGRAM_ID: &str = "dummmy1111111111111111111111111111111111111";
 
 
@@ -92,6 +107,32 @@ mod test {
             test_store.get_program_kind(index, &solana_sdk::compute_budget::id()),
             test_store.get_program_kind(index, &solana_sdk::compute_budget::id()),
             ProgramKind::Builtin,
             ProgramKind::Builtin,
         );
         );
+
+        // migrating builtins
+        for (migrating_builtin_pubkey, migration_feature_id) in [
+            (
+                solana_sdk::stake::program::id(),
+                feature_set::migrate_stake_program_to_core_bpf::id(),
+            ),
+            (
+                solana_sdk::config::program::id(),
+                feature_set::migrate_config_program_to_core_bpf::id(),
+            ),
+            (
+                solana_sdk::address_lookup_table::program::id(),
+                feature_set::migrate_address_lookup_table_program_to_core_bpf::id(),
+            ),
+        ] {
+            index += 1;
+            assert_eq!(
+                test_store.get_program_kind(index, &migrating_builtin_pubkey),
+                ProgramKind::MigratingBuiltin {
+                    core_bpf_migration_feature_index: get_migration_feature_position(
+                        &migration_feature_id
+                    ),
+                }
+            );
+        }
     }
     }
 
 
     #[test]
     #[test]

+ 145 - 9
compute-budget-instruction/src/compute_budget_instruction_details.rs

@@ -3,6 +3,7 @@ use {
         builtin_programs_filter::{BuiltinProgramsFilter, ProgramKind},
         builtin_programs_filter::{BuiltinProgramsFilter, ProgramKind},
         compute_budget_program_id_filter::ComputeBudgetProgramIdFilter,
         compute_budget_program_id_filter::ComputeBudgetProgramIdFilter,
     },
     },
+    solana_builtins_default_costs::{get_migration_feature_id, MIGRATING_BUILTINS_COSTS},
     solana_compute_budget::compute_budget_limits::*,
     solana_compute_budget::compute_budget_limits::*,
     solana_sdk::{
     solana_sdk::{
         borsh1::try_from_slice_unchecked,
         borsh1::try_from_slice_unchecked,
@@ -17,6 +18,24 @@ use {
     std::num::NonZeroU32,
     std::num::NonZeroU32,
 };
 };
 
 
+#[cfg_attr(test, derive(Eq, PartialEq))]
+#[cfg_attr(feature = "dev-context-only-utils", derive(Clone))]
+#[derive(Debug)]
+struct MigrationBuiltinFeatureCounter {
+    // The vector of counters, matching the size of the static vector MIGRATION_FEATURE_IDS,
+    // each counter representing the number of times its corresponding feature ID is
+    // referenced in this transaction.
+    migrating_builtin: [u16; MIGRATING_BUILTINS_COSTS.len()],
+}
+
+impl Default for MigrationBuiltinFeatureCounter {
+    fn default() -> Self {
+        Self {
+            migrating_builtin: [0; MIGRATING_BUILTINS_COSTS.len()],
+        }
+    }
+}
+
 #[cfg_attr(test, derive(Eq, PartialEq))]
 #[cfg_attr(test, derive(Eq, PartialEq))]
 #[cfg_attr(feature = "dev-context-only-utils", derive(Clone))]
 #[cfg_attr(feature = "dev-context-only-utils", derive(Clone))]
 #[derive(Default, Debug)]
 #[derive(Default, Debug)]
@@ -29,8 +48,9 @@ pub struct ComputeBudgetInstructionDetails {
     requested_loaded_accounts_data_size_limit: Option<(u8, u32)>,
     requested_loaded_accounts_data_size_limit: Option<(u8, u32)>,
     num_non_compute_budget_instructions: u16,
     num_non_compute_budget_instructions: u16,
     // Additional builtin program counters
     // Additional builtin program counters
-    num_builtin_instructions: u16,
+    num_non_migratable_builtin_instructions: u16,
     num_non_builtin_instructions: u16,
     num_non_builtin_instructions: u16,
+    migrating_builtin_feature_counters: MigrationBuiltinFeatureCounter,
 }
 }
 
 
 impl ComputeBudgetInstructionDetails {
 impl ComputeBudgetInstructionDetails {
@@ -61,7 +81,8 @@ impl ComputeBudgetInstructionDetails {
                 match filter.get_program_kind(instruction.program_id_index as usize, program_id) {
                 match filter.get_program_kind(instruction.program_id_index as usize, program_id) {
                     ProgramKind::Builtin => {
                     ProgramKind::Builtin => {
                         saturating_add_assign!(
                         saturating_add_assign!(
-                            compute_budget_instruction_details.num_builtin_instructions,
+                            compute_budget_instruction_details
+                                .num_non_migratable_builtin_instructions,
                             1
                             1
                         );
                         );
                     }
                     }
@@ -71,6 +92,20 @@ impl ComputeBudgetInstructionDetails {
                             1
                             1
                         );
                         );
                     }
                     }
+                    ProgramKind::MigratingBuiltin {
+                        core_bpf_migration_feature_index,
+                    } => {
+                        saturating_add_assign!(
+                            *compute_budget_instruction_details
+                                .migrating_builtin_feature_counters
+                                .migrating_builtin
+                                .get_mut(core_bpf_migration_feature_index)
+                                .expect(
+                                    "migrating feature index within range of MIGRATION_FEATURE_IDS"
+                                ),
+                            1
+                        );
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -175,10 +210,26 @@ impl ComputeBudgetInstructionDetails {
 
 
     fn calculate_default_compute_unit_limit(&self, feature_set: &FeatureSet) -> u32 {
     fn calculate_default_compute_unit_limit(&self, feature_set: &FeatureSet) -> u32 {
         if feature_set.is_active(&feature_set::reserve_minimal_cus_for_builtin_instructions::id()) {
         if feature_set.is_active(&feature_set::reserve_minimal_cus_for_builtin_instructions::id()) {
-            u32::from(self.num_builtin_instructions)
+            // evaluate if any builtin has migrated with feature_set
+            let (num_migrated, num_not_migrated) = self
+                .migrating_builtin_feature_counters
+                .migrating_builtin
+                .iter()
+                .enumerate()
+                .fold((0, 0), |(migrated, not_migrated), (index, count)| {
+                    if *count > 0 && feature_set.is_active(get_migration_feature_id(index)) {
+                        (migrated + count, not_migrated)
+                    } else {
+                        (migrated, not_migrated + count)
+                    }
+                });
+
+            u32::from(self.num_non_migratable_builtin_instructions)
+                .saturating_add(u32::from(num_not_migrated))
                 .saturating_mul(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT)
                 .saturating_mul(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT)
                 .saturating_add(
                 .saturating_add(
                     u32::from(self.num_non_builtin_instructions)
                     u32::from(self.num_non_builtin_instructions)
+                        .saturating_add(u32::from(num_migrated))
                         .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
                         .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
                 )
                 )
         } else {
         } else {
@@ -192,6 +243,7 @@ impl ComputeBudgetInstructionDetails {
 mod test {
 mod test {
     use {
     use {
         super::*,
         super::*,
+        solana_builtins_default_costs::get_migration_feature_position,
         solana_sdk::{
         solana_sdk::{
             instruction::Instruction,
             instruction::Instruction,
             message::Message,
             message::Message,
@@ -221,7 +273,7 @@ mod test {
         let expected_details = Ok(ComputeBudgetInstructionDetails {
         let expected_details = Ok(ComputeBudgetInstructionDetails {
             requested_heap_size: Some((1, 40 * 1024)),
             requested_heap_size: Some((1, 40 * 1024)),
             num_non_compute_budget_instructions: 2,
             num_non_compute_budget_instructions: 2,
-            num_builtin_instructions: 1,
+            num_non_migratable_builtin_instructions: 1,
             num_non_builtin_instructions: 2,
             num_non_builtin_instructions: 2,
             ..ComputeBudgetInstructionDetails::default()
             ..ComputeBudgetInstructionDetails::default()
         });
         });
@@ -279,7 +331,7 @@ mod test {
         let expected_details = Ok(ComputeBudgetInstructionDetails {
         let expected_details = Ok(ComputeBudgetInstructionDetails {
             requested_compute_unit_price: Some((1, u64::MAX)),
             requested_compute_unit_price: Some((1, u64::MAX)),
             num_non_compute_budget_instructions: 2,
             num_non_compute_budget_instructions: 2,
-            num_builtin_instructions: 1,
+            num_non_migratable_builtin_instructions: 1,
             num_non_builtin_instructions: 2,
             num_non_builtin_instructions: 2,
             ..ComputeBudgetInstructionDetails::default()
             ..ComputeBudgetInstructionDetails::default()
         });
         });
@@ -309,7 +361,7 @@ mod test {
         let expected_details = Ok(ComputeBudgetInstructionDetails {
         let expected_details = Ok(ComputeBudgetInstructionDetails {
             requested_loaded_accounts_data_size_limit: Some((1, u32::MAX)),
             requested_loaded_accounts_data_size_limit: Some((1, u32::MAX)),
             num_non_compute_budget_instructions: 2,
             num_non_compute_budget_instructions: 2,
-            num_builtin_instructions: 1,
+            num_non_migratable_builtin_instructions: 1,
             num_non_builtin_instructions: 2,
             num_non_builtin_instructions: 2,
             ..ComputeBudgetInstructionDetails::default()
             ..ComputeBudgetInstructionDetails::default()
         });
         });
@@ -336,7 +388,7 @@ mod test {
         let mut feature_set = FeatureSet::default();
         let mut feature_set = FeatureSet::default();
         let ComputeBudgetInstructionDetails {
         let ComputeBudgetInstructionDetails {
             num_non_compute_budget_instructions,
             num_non_compute_budget_instructions,
-            num_builtin_instructions,
+            num_non_migratable_builtin_instructions,
             num_non_builtin_instructions,
             num_non_builtin_instructions,
             ..
             ..
         } = *instruction_details;
         } = *instruction_details;
@@ -346,7 +398,8 @@ mod test {
                 0,
                 0,
             );
             );
             u32::from(num_non_builtin_instructions) * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
             u32::from(num_non_builtin_instructions) * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
-                + u32::from(num_builtin_instructions) * MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT
+                + u32::from(num_non_migratable_builtin_instructions)
+                    * MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT
         } else {
         } else {
             u32::from(num_non_compute_budget_instructions) * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
             u32::from(num_non_compute_budget_instructions) * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
         };
         };
@@ -370,7 +423,7 @@ mod test {
         // no compute-budget instructions, all default ComputeBudgetLimits except cu-limit
         // no compute-budget instructions, all default ComputeBudgetLimits except cu-limit
         let instruction_details = ComputeBudgetInstructionDetails {
         let instruction_details = ComputeBudgetInstructionDetails {
             num_non_compute_budget_instructions: 4,
             num_non_compute_budget_instructions: 4,
-            num_builtin_instructions: 1,
+            num_non_migratable_builtin_instructions: 1,
             num_non_builtin_instructions: 3,
             num_non_builtin_instructions: 3,
             ..ComputeBudgetInstructionDetails::default()
             ..ComputeBudgetInstructionDetails::default()
         };
         };
@@ -521,4 +574,87 @@ mod test {
             );
             );
         }
         }
     }
     }
+
+    #[test]
+    fn test_builtin_program_migration() {
+        let tx = build_sanitized_transaction(&[
+            Instruction::new_with_bincode(Pubkey::new_unique(), &(), vec![]),
+            solana_sdk::stake::instruction::delegate_stake(
+                &Pubkey::new_unique(),
+                &Pubkey::new_unique(),
+                &Pubkey::new_unique(),
+            ),
+        ]);
+        let feature_id_index =
+            get_migration_feature_position(&feature_set::migrate_stake_program_to_core_bpf::id());
+        let mut expected_details = ComputeBudgetInstructionDetails {
+            num_non_compute_budget_instructions: 2,
+            num_non_builtin_instructions: 1,
+            ..ComputeBudgetInstructionDetails::default()
+        };
+        expected_details
+            .migrating_builtin_feature_counters
+            .migrating_builtin[feature_id_index] = 1;
+        let expected_details = Ok(expected_details);
+        let details =
+            ComputeBudgetInstructionDetails::try_from(SVMMessage::program_instructions_iter(&tx));
+        assert_eq!(details, expected_details);
+        let details = details.unwrap();
+
+        // reserve_minimal_cus_for_builtin_instructions: false;
+        // migrate_stake_program_to_core_bpf: false;
+        // expect: 1 bpf ix, 1 non-compute-budget builtin, cu-limit = 2 * 200K
+        let mut feature_set = FeatureSet::default();
+        let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
+        assert_eq!(
+            cu_limits,
+            Ok(ComputeBudgetLimits {
+                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 2,
+                ..ComputeBudgetLimits::default()
+            })
+        );
+
+        // reserve_minimal_cus_for_builtin_instructions: true;
+        // migrate_stake_program_to_core_bpf: false;
+        // expect: 1 bpf ix, 1 non-compute-budget builtin, cu-limit = 200K + 3K
+        feature_set.activate(
+            &feature_set::reserve_minimal_cus_for_builtin_instructions::id(),
+            0,
+        );
+        let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
+        assert_eq!(
+            cu_limits,
+            Ok(ComputeBudgetLimits {
+                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
+                    + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
+                ..ComputeBudgetLimits::default()
+            })
+        );
+
+        // reserve_minimal_cus_for_builtin_instructions: true;
+        // migrate_stake_program_to_core_bpf: true;
+        // expect: 2 bpf ix, cu-limit = 2 * 200K
+        feature_set.activate(&feature_set::migrate_stake_program_to_core_bpf::id(), 0);
+        let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
+        assert_eq!(
+            cu_limits,
+            Ok(ComputeBudgetLimits {
+                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 2,
+                ..ComputeBudgetLimits::default()
+            })
+        );
+
+        // reserve_minimal_cus_for_builtin_instructions: false;
+        // migrate_stake_program_to_core_bpf: false;
+        // expect: 1 bpf ix, 1 non-compute-budget builtin, cu-limit = 2 * 200K
+        feature_set.deactivate(&feature_set::reserve_minimal_cus_for_builtin_instructions::id());
+        let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
+        assert_eq!(
+            cu_limits,
+            Ok(ComputeBudgetLimits {
+                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 2,
+                ..ComputeBudgetLimits::default()
+            })
+        );
+    }
 }
 }