Skip to main content

solana_builtins_default_costs/
lib.rs

1#![cfg_attr(
2    not(feature = "agave-unstable-api"),
3    deprecated(
4        since = "3.1.0",
5        note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6                v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7                acknowledge use of an interface that may break without warning."
8    )
9)]
10#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
11#![allow(clippy::arithmetic_side_effects)]
12
13#[cfg(feature = "dev-context-only-utils")]
14use qualifier_attr::field_qualifiers;
15use {
16    ahash::AHashMap,
17    solana_pubkey::Pubkey,
18    solana_sdk_ids::{
19        bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, compute_budget, ed25519_program,
20        loader_v4, secp256k1_program, system_program, vote,
21    },
22};
23
24#[derive(Clone)]
25#[cfg_attr(
26    feature = "dev-context-only-utils",
27    field_qualifiers(core_bpf_migration_feature(pub), position(pub))
28)]
29pub struct MigratingBuiltinCost {
30    core_bpf_migration_feature: Pubkey,
31    // encoding positional information explicitly for migration feature item,
32    // its value must be correctly corresponding to this object's position
33    // in MIGRATING_BUILTINS_COSTS, otherwise a const validation
34    // `validate_position(MIGRATING_BUILTINS_COSTS)` will fail at compile time.
35    position: usize,
36}
37
38/// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding
39/// migration feature ID to BUILTIN_INSTRUCTION_COSTS, and move it from
40/// NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, so the builtin's
41/// default cost can be determined properly based on feature status.
42/// When migration completed, eg the feature gate is enabled everywhere, please
43/// remove that builtin entry from MIGRATING_BUILTINS_COSTS.
44#[derive(Clone)]
45pub enum BuiltinCost {
46    Migrating(MigratingBuiltinCost),
47    NotMigrating,
48}
49
50impl BuiltinCost {
51    fn core_bpf_migration_feature(&self) -> Option<&Pubkey> {
52        match self {
53            BuiltinCost::Migrating(MigratingBuiltinCost {
54                core_bpf_migration_feature,
55                ..
56            }) => Some(core_bpf_migration_feature),
57            BuiltinCost::NotMigrating => None,
58        }
59    }
60
61    fn position(&self) -> Option<usize> {
62        match self {
63            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => Some(*position),
64            BuiltinCost::NotMigrating => None,
65        }
66    }
67}
68
69/// Number of compute units for each built-in programs
70///
71/// DEVELOPER WARNING: This map CANNOT be modified without causing a
72/// consensus failure because this map is used to calculate the compute
73/// limit for transactions that don't specify a compute limit themselves as
74/// of https://github.com/anza-xyz/agave/issues/2212.  It's also used to
75/// calculate the cost of a transaction which is used in replay to enforce
76/// block cost limits as of
77/// https://github.com/solana-labs/solana/issues/29595.
78static BUILTIN_INSTRUCTION_COSTS: std::sync::LazyLock<AHashMap<Pubkey, BuiltinCost>> =
79    std::sync::LazyLock::new(|| {
80        MIGRATING_BUILTINS_COSTS
81            .iter()
82            .chain(NON_MIGRATING_BUILTINS_COSTS.iter())
83            .cloned()
84            .collect()
85    });
86// DO NOT ADD MORE ENTRIES TO THIS MAP
87
88/// DEVELOPER WARNING: please do not add new entry into MIGRATING_BUILTINS_COSTS or
89/// NON_MIGRATING_BUILTINS_COSTS, do so will modify BUILTIN_INSTRUCTION_COSTS therefore
90/// cause consensus failure. However, when a builtin started being migrated to core bpf,
91/// it MUST be moved from NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, then
92/// correctly furnishing `core_bpf_migration_feature`.
93///
94#[allow(dead_code)]
95const TOTAL_COUNT_BUILTINS: usize = 9;
96#[cfg(test)]
97static_assertions::const_assert_eq!(
98    MIGRATING_BUILTINS_COSTS.len() + NON_MIGRATING_BUILTINS_COSTS.len(),
99    TOTAL_COUNT_BUILTINS
100);
101
102/// MIGRATING_BUILTINS_COSTS is empty as no builtins are presently being migrated.
103/// We leave it and the related scaffolding in place for future planned migrations.
104pub const MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[];
105
106const NON_MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
107    (vote::id(), BuiltinCost::NotMigrating),
108    (system_program::id(), BuiltinCost::NotMigrating),
109    (compute_budget::id(), BuiltinCost::NotMigrating),
110    (bpf_loader_upgradeable::id(), BuiltinCost::NotMigrating),
111    (bpf_loader_deprecated::id(), BuiltinCost::NotMigrating),
112    (bpf_loader::id(), BuiltinCost::NotMigrating),
113    (loader_v4::id(), BuiltinCost::NotMigrating),
114    (secp256k1_program::id(), BuiltinCost::NotMigrating),
115    (ed25519_program::id(), BuiltinCost::NotMigrating),
116];
117
118/// A table of 256 booleans indicates whether the first `u8` of a Pubkey exists in
119/// BUILTIN_INSTRUCTION_COSTS. If the value is true, the Pubkey might be a builtin key;
120/// if false, it cannot be a builtin key. This table allows for quick filtering of
121/// builtin program IDs without the need for hashing.
122pub static MAYBE_BUILTIN_KEY: std::sync::LazyLock<[bool; 256]> = std::sync::LazyLock::new(|| {
123    let mut temp_table: [bool; 256] = [false; 256];
124    BUILTIN_INSTRUCTION_COSTS
125        .keys()
126        .for_each(|key| temp_table[key.as_ref()[0] as usize] = true);
127    temp_table
128});
129
130pub enum BuiltinMigrationFeatureIndex {
131    NotBuiltin,
132    BuiltinNoMigrationFeature,
133    BuiltinWithMigrationFeature(usize),
134}
135
136pub fn get_builtin_migration_feature_index(program_id: &Pubkey) -> BuiltinMigrationFeatureIndex {
137    BUILTIN_INSTRUCTION_COSTS.get(program_id).map_or(
138        BuiltinMigrationFeatureIndex::NotBuiltin,
139        |builtin_cost| {
140            builtin_cost.position().map_or(
141                BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
142                BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature,
143            )
144        },
145    )
146}
147
148/// const function validates `position` correctness at compile time.
149#[allow(dead_code)]
150const fn validate_position(migrating_builtins: &[(Pubkey, BuiltinCost)]) {
151    let mut index = 0;
152    while index < migrating_builtins.len() {
153        match migrating_builtins[index].1 {
154            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => assert!(
155                position == index,
156                "migration feture must exist and at correct position"
157            ),
158            BuiltinCost::NotMigrating => {
159                panic!("migration feture must exist and at correct position")
160            }
161        }
162        index += 1;
163    }
164}
165const _: () = validate_position(MIGRATING_BUILTINS_COSTS);
166
167/// Helper function to return ref of migration feature Pubkey at position `index`
168/// from MIGRATING_BUILTINS_COSTS
169pub fn get_migration_feature_id(index: usize) -> &'static Pubkey {
170    MIGRATING_BUILTINS_COSTS
171        .get(index)
172        .expect("valid index of MIGRATING_BUILTINS_COSTS")
173        .1
174        .core_bpf_migration_feature()
175        .expect("migrating builtin")
176}
177
178#[cfg(feature = "dev-context-only-utils")]
179pub fn get_migration_feature_position(feature_id: &Pubkey) -> usize {
180    MIGRATING_BUILTINS_COSTS
181        .iter()
182        .position(|(_, c)| c.core_bpf_migration_feature().expect("migrating builtin") == feature_id)
183        .unwrap()
184}
185
186#[cfg(test)]
187mod test {
188    use super::*;
189
190    #[test]
191    fn test_const_builtin_cost_arrays() {
192        // sanity check to make sure built-ins are declared in the correct array
193        assert!(MIGRATING_BUILTINS_COSTS
194            .iter()
195            .enumerate()
196            .all(|(index, (_, c))| {
197                c.core_bpf_migration_feature().is_some() && c.position() == Some(index)
198            }));
199        assert!(NON_MIGRATING_BUILTINS_COSTS
200            .iter()
201            .all(|(_, c)| c.core_bpf_migration_feature().is_none()));
202    }
203
204    #[test]
205    fn test_get_builtin_migration_feature_index() {
206        assert!(matches!(
207            get_builtin_migration_feature_index(&Pubkey::new_unique()),
208            BuiltinMigrationFeatureIndex::NotBuiltin
209        ));
210        assert!(matches!(
211            get_builtin_migration_feature_index(&compute_budget::id()),
212            BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
213        ));
214        for (program_id, migrating_builtin) in MIGRATING_BUILTINS_COSTS {
215            let feature_index = get_builtin_migration_feature_index(program_id);
216            assert!(matches!(
217                feature_index,
218                BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
219            ));
220            let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
221                feature_index
222            else {
223                panic!("expect migrating builtin")
224            };
225            assert_eq!(
226                get_migration_feature_id(feature_index),
227                migrating_builtin.core_bpf_migration_feature().unwrap(),
228            );
229        }
230    }
231
232    #[test]
233    #[should_panic(expected = "valid index of MIGRATING_BUILTINS_COSTS")]
234    fn test_get_migration_feature_id_invalid_index() {
235        let _ = get_migration_feature_id(MIGRATING_BUILTINS_COSTS.len() + 1);
236    }
237}