Skip to main content

rialo_s_builtins_default_costs/
lib.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3// This file is either (a) original to Subzero Labs, Inc. or (b) derived from the Anza codebase and modified by Subzero Labs, Inc.
4
5#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
6#![allow(clippy::arithmetic_side_effects)]
7use std::sync::LazyLock;
8
9use ahash::AHashMap;
10#[cfg(feature = "svm-internal")]
11use qualifier_attr::qualifiers;
12use rialo_s_feature_set::{self as feature_set, FeatureSet};
13use rialo_s_pubkey::Pubkey;
14use rialo_s_sdk_ids::{
15    bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, compute_budget, config,
16    ed25519_program, loader_v4, secp256k1_program, system_program,
17};
18
19#[derive(Clone)]
20#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
21struct MigratingBuiltinCost {
22    native_cost: u64,
23    core_bpf_migration_feature: Pubkey,
24    // encoding positional information explicitly for migration feature item,
25    // its value must be correctly corresponding to this object's position
26    // in MIGRATING_BUILTINS_COSTS, otherwise a const validation
27    // `validate_position(MIGRATING_BUILTINS_COSTS)` will fail at compile time.
28    position: usize,
29}
30
31#[derive(Clone)]
32#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
33struct NotMigratingBuiltinCost {
34    native_cost: u64,
35}
36
37/// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding
38/// migration feature ID to BUILTIN_INSTRUCTION_COSTS, and move it from
39/// NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, so the builtin's
40/// default cost can be determined properly based on feature status.
41/// When migration completed, eg the feature gate is enabled everywhere, please
42/// remove that builtin entry from MIGRATING_BUILTINS_COSTS.
43#[derive(Clone)]
44#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
45enum BuiltinCost {
46    Migrating(MigratingBuiltinCost),
47    NotMigrating(NotMigratingBuiltinCost),
48}
49
50impl BuiltinCost {
51    fn native_cost(&self) -> u64 {
52        match self {
53            BuiltinCost::Migrating(MigratingBuiltinCost { native_cost, .. }) => *native_cost,
54            BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost }) => *native_cost,
55        }
56    }
57
58    #[cfg(feature = "svm-internal")]
59    fn core_bpf_migration_feature(&self) -> Option<&Pubkey> {
60        match self {
61            BuiltinCost::Migrating(MigratingBuiltinCost {
62                core_bpf_migration_feature,
63                ..
64            }) => Some(core_bpf_migration_feature),
65            BuiltinCost::NotMigrating(_) => None,
66        }
67    }
68
69    #[cfg(feature = "svm-internal")]
70    fn position(&self) -> Option<usize> {
71        match self {
72            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => Some(*position),
73            BuiltinCost::NotMigrating(_) => None,
74        }
75    }
76
77    fn has_migrated(&self, feature_set: &FeatureSet) -> bool {
78        match self {
79            BuiltinCost::Migrating(MigratingBuiltinCost {
80                core_bpf_migration_feature,
81                ..
82            }) => feature_set.is_active(core_bpf_migration_feature),
83            BuiltinCost::NotMigrating(_) => false,
84        }
85    }
86}
87
88/// Number of compute units for each built-in programs
89///
90/// DEVELOPER WARNING: This map CANNOT be modified without causing a
91/// consensus failure because this map is used to calculate the compute
92/// limit for transactions that don't specify a compute limit themselves as
93/// of https://github.com/anza-xyz/agave/issues/2212.  It's also used to
94/// calculate the cost of a transaction which is used in replay to enforce
95/// block cost limits as of
96/// https://github.com/solana-labs/solana/issues/29595.
97// DO NOT ADD MORE ENTRIES TO THIS MAP
98static BUILTIN_INSTRUCTION_COSTS: LazyLock<AHashMap<Pubkey, BuiltinCost>> = LazyLock::new(|| {
99    MIGRATING_BUILTINS_COSTS
100        .iter()
101        .chain(NON_MIGRATING_BUILTINS_COSTS.iter())
102        .cloned()
103        .collect()
104});
105
106/// DEVELOPER WARNING: please do not add new entry into MIGRATING_BUILTINS_COSTS or
107/// NON_MIGRATING_BUILTINS_COSTS, do so will modify BUILTIN_INSTRUCTION_COSTS therefore
108/// cause consensus failure. However, when a builtin started being migrated to core bpf,
109/// it MUST be moved from NON_MIGRATING_BUILTINS_COSTS to MIGRATING_BUILTINS_COSTS, then
110/// correctly furnishing `core_bpf_migration_feature`.
111///
112#[allow(dead_code)]
113const TOTAL_COUNT_BUILTINS: usize = 9;
114#[cfg(test)]
115static_assertions::const_assert_eq!(
116    MIGRATING_BUILTINS_COSTS.len() + NON_MIGRATING_BUILTINS_COSTS.len(),
117    TOTAL_COUNT_BUILTINS
118);
119
120#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
121const MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[(
122    config::id(),
123    BuiltinCost::Migrating(MigratingBuiltinCost {
124        native_cost: rialo_s_config_program::config_processor::DEFAULT_COMPUTE_UNITS,
125        core_bpf_migration_feature: feature_set::migrate_config_program_to_core_bpf::id(),
126        position: 0,
127    }),
128)];
129
130const NON_MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
131    (
132        system_program::id(),
133        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
134            native_cost: rialo_s_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
135        }),
136    ),
137    (
138        compute_budget::id(),
139        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
140            native_cost: rialo_s_compute_budget_program::DEFAULT_COMPUTE_UNITS,
141        }),
142    ),
143    (
144        bpf_loader_upgradeable::id(),
145        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
146            native_cost: rialo_s_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS,
147        }),
148    ),
149    (
150        bpf_loader_deprecated::id(),
151        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
152            native_cost: rialo_s_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS,
153        }),
154    ),
155    (
156        bpf_loader::id(),
157        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
158            native_cost: rialo_s_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS,
159        }),
160    ),
161    (
162        loader_v4::id(),
163        BuiltinCost::NotMigrating(NotMigratingBuiltinCost {
164            native_cost: rialo_s_loader_v4_program::DEFAULT_COMPUTE_UNITS,
165        }),
166    ),
167    // Note: These are precompile, run directly in bank during sanitizing;
168    (
169        secp256k1_program::id(),
170        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
171    ),
172    (
173        ed25519_program::id(),
174        BuiltinCost::NotMigrating(NotMigratingBuiltinCost { native_cost: 0 }),
175    ),
176];
177
178/// A table of 256 booleans indicates whether the first `u8` of a Pubkey exists in
179/// BUILTIN_INSTRUCTION_COSTS. If the value is true, the Pubkey might be a builtin key;
180/// if false, it cannot be a builtin key. This table allows for quick filtering of
181/// builtin program IDs without the need for hashing.
182pub static MAYBE_BUILTIN_KEY: LazyLock<[bool; 256]> = LazyLock::new(|| {
183    let mut temp_table: [bool; 256] = [false; 256];
184    BUILTIN_INSTRUCTION_COSTS
185        .keys()
186        .for_each(|key| temp_table[key.as_ref()[0] as usize] = true);
187    temp_table
188});
189
190pub fn get_builtin_instruction_cost<'a>(
191    program_id: &'a Pubkey,
192    feature_set: &'a FeatureSet,
193) -> Option<u64> {
194    BUILTIN_INSTRUCTION_COSTS
195        .get(program_id)
196        .filter(|builtin_cost| !builtin_cost.has_migrated(feature_set))
197        .map(|builtin_cost| builtin_cost.native_cost())
198}
199
200#[cfg(feature = "svm-internal")]
201#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
202enum BuiltinMigrationFeatureIndex {
203    NotBuiltin,
204    BuiltinNoMigrationFeature,
205    BuiltinWithMigrationFeature(usize),
206}
207
208#[cfg(feature = "svm-internal")]
209#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
210fn get_builtin_migration_feature_index(program_id: &Pubkey) -> BuiltinMigrationFeatureIndex {
211    BUILTIN_INSTRUCTION_COSTS.get(program_id).map_or(
212        BuiltinMigrationFeatureIndex::NotBuiltin,
213        |builtin_cost| {
214            builtin_cost.position().map_or(
215                BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
216                BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature,
217            )
218        },
219    )
220}
221
222/// const function validates `position` correctness at compile time.
223#[allow(dead_code)]
224const fn validate_position(migrating_builtins: &[(Pubkey, BuiltinCost)]) {
225    let mut index = 0;
226    while index < migrating_builtins.len() {
227        match migrating_builtins[index].1 {
228            BuiltinCost::Migrating(MigratingBuiltinCost { position, .. }) => assert!(
229                position == index,
230                "migration feture must exist and at correct position"
231            ),
232            BuiltinCost::NotMigrating(_) => {
233                panic!("migration feture must exist and at correct position")
234            }
235        }
236        index += 1;
237    }
238}
239const _: () = validate_position(MIGRATING_BUILTINS_COSTS);
240
241/// Helper function to return ref of migration feature Pubkey at position `index`
242/// from MIGRATING_BUILTINS_COSTS
243#[cfg(feature = "svm-internal")]
244#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
245pub(crate) fn get_migration_feature_id(index: usize) -> &'static Pubkey {
246    MIGRATING_BUILTINS_COSTS
247        .get(index)
248        .expect("valid index of MIGRATING_BUILTINS_COSTS")
249        .1
250        .core_bpf_migration_feature()
251        .expect("migrating builtin")
252}
253
254#[cfg(feature = "dev-context-only-utils")]
255pub fn get_migration_feature_position(feature_id: &Pubkey) -> usize {
256    MIGRATING_BUILTINS_COSTS
257        .iter()
258        .position(|(_, c)| c.core_bpf_migration_feature().expect("migrating builtin") == feature_id)
259        .unwrap()
260}
261
262#[cfg(test)]
263mod test {
264    use super::*;
265
266    #[test]
267    fn test_const_builtin_cost_arrays() {
268        // sanity check to make sure built-ins are declared in the correct array
269        assert!(MIGRATING_BUILTINS_COSTS
270            .iter()
271            .enumerate()
272            .all(|(index, (_, c))| {
273                c.core_bpf_migration_feature().is_some() && c.position() == Some(index)
274            }));
275        assert!(NON_MIGRATING_BUILTINS_COSTS
276            .iter()
277            .all(|(_, c)| c.core_bpf_migration_feature().is_none()));
278    }
279
280    #[test]
281    fn test_get_builtin_instruction_cost() {
282        // use native cost if no migration planned
283        assert_eq!(
284            Some(rialo_s_compute_budget_program::DEFAULT_COMPUTE_UNITS),
285            get_builtin_instruction_cost(&compute_budget::id(), &FeatureSet::all_enabled())
286        );
287
288        // None if not builtin
289        assert!(
290            get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::default()).is_none()
291        );
292        assert!(
293            get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::all_enabled())
294                .is_none()
295        );
296    }
297
298    #[test]
299    fn test_get_builtin_migration_feature_index() {
300        assert!(matches!(
301            get_builtin_migration_feature_index(&Pubkey::new_unique()),
302            BuiltinMigrationFeatureIndex::NotBuiltin
303        ));
304        assert!(matches!(
305            get_builtin_migration_feature_index(&compute_budget::id()),
306            BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
307        ));
308        let feature_index = get_builtin_migration_feature_index(&config::id());
309        assert!(matches!(
310            feature_index,
311            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
312        ));
313        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
314            feature_index
315        else {
316            panic!("expect migrating builtin")
317        };
318        assert_eq!(
319            get_migration_feature_id(feature_index),
320            &feature_set::migrate_config_program_to_core_bpf::id()
321        );
322    }
323
324    #[test]
325    #[should_panic(expected = "valid index of MIGRATING_BUILTINS_COSTS")]
326    fn test_get_migration_feature_id_invalid_index() {
327        let _ = get_migration_feature_id(MIGRATING_BUILTINS_COSTS.len() + 1);
328    }
329}