Skip to content

Commit d791c9a

Browse files
authored
Fix builtin default cost dependents on migration (solana-labs#3768)
* Add public function to as placeholder to implement fetching default cost depends on feature_set; Make AhashMap private * Add BuiltinCost to help determine default cost based on migration status * test * fix - after migration feature activation, it should no longer be considered as builtin * use lazy_static, avoid naked unwrap * rename * add comments to BUILINS
1 parent a20c138 commit d791c9a

File tree

7 files changed

+229
-67
lines changed

7 files changed

+229
-67
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

builtins-default-costs/benches/builtin_instruction_costs.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@
22
extern crate test;
33
use {
44
rand::Rng,
5-
solana_builtins_default_costs::BUILTIN_INSTRUCTION_COSTS,
5+
solana_builtins_default_costs::get_builtin_instruction_cost,
66
solana_sdk::{
77
address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
8-
compute_budget, ed25519_program, loader_v4, pubkey::Pubkey, secp256k1_program,
8+
compute_budget, ed25519_program, feature_set::FeatureSet, loader_v4, pubkey::Pubkey,
9+
secp256k1_program,
910
},
1011
test::Bencher,
1112
};
1213

1314
struct BenchSetup {
1415
pubkeys: [Pubkey; 12],
16+
feature_set: FeatureSet,
1517
}
1618

1719
const NUM_TRANSACTIONS_PER_ITER: usize = 1024;
18-
const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000;
1920

20-
fn setup() -> BenchSetup {
21+
fn setup(all_features_enabled: bool) -> BenchSetup {
2122
let pubkeys: [Pubkey; 12] = [
2223
solana_stake_program::id(),
2324
solana_config_program::id(),
@@ -33,23 +34,39 @@ fn setup() -> BenchSetup {
3334
ed25519_program::id(),
3435
];
3536

36-
BenchSetup { pubkeys }
37+
let feature_set = if all_features_enabled {
38+
FeatureSet::all_enabled()
39+
} else {
40+
FeatureSet::default()
41+
};
42+
43+
BenchSetup {
44+
pubkeys,
45+
feature_set,
46+
}
47+
}
48+
49+
fn do_hash_find(setup: &BenchSetup) {
50+
for _t in 0..NUM_TRANSACTIONS_PER_ITER {
51+
let idx = rand::thread_rng().gen_range(0..setup.pubkeys.len());
52+
get_builtin_instruction_cost(&setup.pubkeys[idx], &setup.feature_set);
53+
}
54+
}
55+
56+
#[bench]
57+
fn bench_hash_find_builtins_not_migrated(bencher: &mut Bencher) {
58+
let bench_setup = setup(false);
59+
60+
bencher.iter(|| {
61+
do_hash_find(&bench_setup);
62+
});
3763
}
3864

3965
#[bench]
40-
fn bench_hash_find(bencher: &mut Bencher) {
41-
let BenchSetup { pubkeys } = setup();
66+
fn bench_hash_find_builtins_migrated(bencher: &mut Bencher) {
67+
let bench_setup = setup(true);
4268

4369
bencher.iter(|| {
44-
for _t in 0..NUM_TRANSACTIONS_PER_ITER {
45-
let idx = rand::thread_rng().gen_range(0..pubkeys.len());
46-
let ix_execution_cost =
47-
if let Some(builtin_cost) = BUILTIN_INSTRUCTION_COSTS.get(&pubkeys[idx]) {
48-
*builtin_cost
49-
} else {
50-
u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT)
51-
};
52-
assert!(ix_execution_cost != DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
53-
}
70+
do_hash_find(&bench_setup);
5471
});
5572
}

builtins-default-costs/src/lib.rs

Lines changed: 160 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,25 @@ use {
55
lazy_static::lazy_static,
66
solana_sdk::{
77
address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
8-
compute_budget, ed25519_program, loader_v4, pubkey::Pubkey, secp256k1_program,
8+
compute_budget, ed25519_program,
9+
feature_set::{self, FeatureSet},
10+
loader_v4,
11+
pubkey::Pubkey,
12+
secp256k1_program,
913
},
1014
};
1115

12-
// Number of compute units for each built-in programs
16+
/// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding
17+
/// migration feature ID to BUILTIN_INSTRUCTION_COSTS, so the builtin's default
18+
/// cost can be determined properly based on feature status.
19+
/// When migration completed, eg the feature gate is enabled everywhere, please
20+
/// remove that builtin entry from BUILTIN_INSTRUCTION_COSTS.
21+
#[derive(Clone)]
22+
struct BuiltinCost {
23+
native_cost: u64,
24+
core_bpf_migration_feature: Option<Pubkey>,
25+
}
26+
1327
lazy_static! {
1428
/// Number of compute units for each built-in programs
1529
///
@@ -20,21 +34,95 @@ lazy_static! {
2034
/// calculate the cost of a transaction which is used in replay to enforce
2135
/// block cost limits as of
2236
/// https://github.com/solana-labs/solana/issues/29595.
23-
pub static ref BUILTIN_INSTRUCTION_COSTS: AHashMap<Pubkey, u64> = [
24-
(solana_stake_program::id(), solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS),
25-
(solana_config_program::id(), solana_config_program::config_processor::DEFAULT_COMPUTE_UNITS),
26-
(solana_vote_program::id(), solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS),
27-
(solana_system_program::id(), solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS),
28-
(compute_budget::id(), solana_compute_budget_program::DEFAULT_COMPUTE_UNITS),
29-
(address_lookup_table::program::id(), solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS),
30-
(bpf_loader_upgradeable::id(), solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS),
31-
(bpf_loader_deprecated::id(), solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS),
32-
(bpf_loader::id(), solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS),
33-
(loader_v4::id(), solana_loader_v4_program::DEFAULT_COMPUTE_UNITS),
34-
// Note: These are precompile, run directly in bank during sanitizing;
35-
(secp256k1_program::id(), 0),
36-
(ed25519_program::id(), 0),
37-
// DO NOT ADD MORE ENTRIES TO THIS MAP
37+
static ref BUILTIN_INSTRUCTION_COSTS: AHashMap<Pubkey, BuiltinCost> = [
38+
(
39+
solana_stake_program::id(),
40+
BuiltinCost {
41+
native_cost: solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS,
42+
core_bpf_migration_feature: Some(feature_set::migrate_stake_program_to_core_bpf::id()),
43+
},
44+
),
45+
(
46+
solana_config_program::id(),
47+
BuiltinCost {
48+
native_cost: solana_config_program::config_processor::DEFAULT_COMPUTE_UNITS,
49+
core_bpf_migration_feature: Some(feature_set::migrate_config_program_to_core_bpf::id()),
50+
},
51+
),
52+
(
53+
solana_vote_program::id(),
54+
BuiltinCost {
55+
native_cost: solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
56+
core_bpf_migration_feature: None,
57+
},
58+
),
59+
(
60+
solana_system_program::id(),
61+
BuiltinCost {
62+
native_cost: solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS,
63+
core_bpf_migration_feature: None,
64+
},
65+
),
66+
(
67+
compute_budget::id(),
68+
BuiltinCost {
69+
native_cost: solana_compute_budget_program::DEFAULT_COMPUTE_UNITS,
70+
core_bpf_migration_feature: None,
71+
},
72+
),
73+
(
74+
address_lookup_table::program::id(),
75+
BuiltinCost {
76+
native_cost: solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS,
77+
core_bpf_migration_feature: Some(
78+
feature_set::migrate_address_lookup_table_program_to_core_bpf::id(),
79+
),
80+
},
81+
),
82+
(
83+
bpf_loader_upgradeable::id(),
84+
BuiltinCost {
85+
native_cost: solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS,
86+
core_bpf_migration_feature: None,
87+
},
88+
),
89+
(
90+
bpf_loader_deprecated::id(),
91+
BuiltinCost {
92+
native_cost: solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS,
93+
core_bpf_migration_feature: None,
94+
},
95+
),
96+
(
97+
bpf_loader::id(),
98+
BuiltinCost {
99+
native_cost: solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS,
100+
core_bpf_migration_feature: None,
101+
},
102+
),
103+
(
104+
loader_v4::id(),
105+
BuiltinCost {
106+
native_cost: solana_loader_v4_program::DEFAULT_COMPUTE_UNITS,
107+
core_bpf_migration_feature: None,
108+
},
109+
),
110+
// Note: These are precompile, run directly in bank during sanitizing;
111+
(
112+
secp256k1_program::id(),
113+
BuiltinCost {
114+
native_cost: 0,
115+
core_bpf_migration_feature: None,
116+
},
117+
),
118+
(
119+
ed25519_program::id(),
120+
BuiltinCost {
121+
native_cost: 0,
122+
core_bpf_migration_feature: None,
123+
},
124+
),
125+
// DO NOT ADD MORE ENTRIES TO THIS MAP
38126
]
39127
.iter()
40128
.cloned()
@@ -54,3 +142,58 @@ lazy_static! {
54142
temp_table
55143
};
56144
}
145+
146+
pub fn get_builtin_instruction_cost<'a>(
147+
program_id: &'a Pubkey,
148+
feature_set: &'a FeatureSet,
149+
) -> Option<u64> {
150+
BUILTIN_INSTRUCTION_COSTS
151+
.get(program_id)
152+
.filter(
153+
// Returns true if builtin program id has no core_bpf_migration_feature or feature is not activated;
154+
// otherwise returns false because it's not considered as builtin
155+
|builtin_cost| -> bool {
156+
builtin_cost
157+
.core_bpf_migration_feature
158+
.map(|feature_id| !feature_set.is_active(&feature_id))
159+
.unwrap_or(true)
160+
},
161+
)
162+
.map(|builtin_cost| builtin_cost.native_cost)
163+
}
164+
165+
#[cfg(test)]
166+
mod test {
167+
use super::*;
168+
169+
#[test]
170+
fn test_get_builtin_instruction_cost() {
171+
// use native cost if no migration planned
172+
assert_eq!(
173+
Some(solana_compute_budget_program::DEFAULT_COMPUTE_UNITS),
174+
get_builtin_instruction_cost(&compute_budget::id(), &FeatureSet::all_enabled())
175+
);
176+
177+
// use native cost if migration is planned but not activated
178+
assert_eq!(
179+
Some(solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS),
180+
get_builtin_instruction_cost(&solana_stake_program::id(), &FeatureSet::default())
181+
);
182+
183+
// None if migration is planned and activated, in which case, it's no longer builtin
184+
assert!(get_builtin_instruction_cost(
185+
&solana_stake_program::id(),
186+
&FeatureSet::all_enabled()
187+
)
188+
.is_none());
189+
190+
// None if not builtin
191+
assert!(
192+
get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::default()).is_none()
193+
);
194+
assert!(
195+
get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::all_enabled())
196+
.is_none()
197+
);
198+
}
199+
}

core/src/banking_stage/packet_filter.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
use {
22
super::immutable_deserialized_packet::ImmutableDeserializedPacket,
3-
solana_builtins_default_costs::BUILTIN_INSTRUCTION_COSTS,
4-
solana_sdk::{ed25519_program, saturating_add_assign, secp256k1_program},
3+
lazy_static::lazy_static,
4+
solana_builtins_default_costs::get_builtin_instruction_cost,
5+
solana_sdk::{
6+
ed25519_program, feature_set::FeatureSet, saturating_add_assign, secp256k1_program,
7+
},
58
thiserror::Error,
69
};
710

11+
lazy_static! {
12+
// To calculate the static_builtin_cost_sum conservatively, an all-enabled dummy feature_set
13+
// is used. It lowers required minimal compute_unit_limit, aligns with future versions.
14+
static ref FEATURE_SET: FeatureSet = FeatureSet::all_enabled();
15+
}
16+
817
#[derive(Debug, Error, PartialEq)]
918
pub enum PacketFilterFailure {
1019
#[error("Insufficient compute unit limit")]
@@ -22,8 +31,8 @@ impl ImmutableDeserializedPacket {
2231
pub fn check_insufficent_compute_unit_limit(&self) -> Result<(), PacketFilterFailure> {
2332
let mut static_builtin_cost_sum: u64 = 0;
2433
for (program_id, _) in self.transaction().get_message().program_instructions_iter() {
25-
if let Some(ix_cost) = BUILTIN_INSTRUCTION_COSTS.get(program_id) {
26-
saturating_add_assign!(static_builtin_cost_sum, *ix_cost);
34+
if let Some(ix_cost) = get_builtin_instruction_cost(program_id, &FEATURE_SET) {
35+
saturating_add_assign!(static_builtin_cost_sum, ix_cost);
2736
}
2837
}
2938

cost-model/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ name = "solana_cost_model"
3636
itertools = { workspace = true }
3737
rand = "0.8.5"
3838
# See order-crates-for-publishing.py for using this unusual `path = "."`
39+
solana-compute-budget-program = { workspace = true }
3940
solana-cost-model = { path = ".", features = ["dev-context-only-utils"] }
4041
solana-logger = { workspace = true }
4142
solana-runtime-transaction = { workspace = true, features = [

0 commit comments

Comments
 (0)