From b70cac38827e499d34c3a521eac17c68fb1b5b1f Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Fri, 10 Jan 2025 17:38:20 +0100 Subject: [PATCH] remove solana-sdk from compute-budget-instruction (#4319) --- Cargo.lock | 14 +- compute-budget-instruction/Cargo.toml | 16 ++- .../process_compute_budget_instructions.rs | 38 +++-- .../src/builtin_programs_filter.rs | 17 +-- .../src/compute_budget_instruction_details.rs | 130 ++++++++---------- .../src/compute_budget_program_id_filter.rs | 4 +- .../src/instructions_processor.rs | 29 ++-- programs/sbf/Cargo.lock | 8 +- svm/examples/Cargo.lock | 8 +- 9 files changed, 141 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88da797360e920..49d878834998bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6769,12 +6769,24 @@ dependencies = [ "criterion", "log", "rand 0.8.5", + "solana-borsh", "solana-builtins-default-costs", "solana-compute-budget", + "solana-compute-budget-interface", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-packet", "solana-program", "solana-pubkey", - "solana-sdk", + "solana-sdk-ids", + "solana-signer", "solana-svm-transaction", + "solana-system-interface", + "solana-transaction", + "solana-transaction-error", "thiserror 2.0.9", ] diff --git a/compute-budget-instruction/Cargo.toml b/compute-budget-instruction/Cargo.toml index 8e5bdd0b1c9bf7..dcce912056ab69 100644 --- a/compute-budget-instruction/Cargo.toml +++ b/compute-budget-instruction/Cargo.toml @@ -11,11 +11,17 @@ edition = { workspace = true } [dependencies] log = { workspace = true } +solana-borsh = { workspace = true } solana-builtins-default-costs = { workspace = true } solana-compute-budget = { workspace = true } +solana-compute-budget-interface = { workspace = true } +solana-feature-set = { workspace = true } +solana-instruction = { workspace = true } +solana-packet = { workspace = true } solana-pubkey = { workspace = true } -solana-sdk = { workspace = true } +solana-sdk-ids = { workspace = true } solana-svm-transaction = { workspace = true } +solana-transaction-error = { workspace = true } thiserror = { workspace = true } [lib] @@ -26,8 +32,14 @@ name = "solana_compute_budget_instruction" bincode = { workspace = true } criterion = { workspace = true } rand = { workspace = true } -solana-builtins-default-costs = { workspace = true, features = ["dev-context-only-utils"] } +solana-builtins-default-costs = { workspace = true, features = ["dev-context-only-utils"] } +solana-hash = { workspace = true } +solana-keypair = { workspace = true } +solana-message = { workspace = true } solana-program = { workspace = true } +solana-signer = { workspace = true } +solana-system-interface = { workspace = true } +solana-transaction = { workspace = true } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/compute-budget-instruction/benches/process_compute_budget_instructions.rs b/compute-budget-instruction/benches/process_compute_budget_instructions.rs index 21a9dd13c58092..92c46600ba15a0 100644 --- a/compute-budget-instruction/benches/process_compute_budget_instructions.rs +++ b/compute-budget-instruction/benches/process_compute_budget_instructions.rs @@ -1,18 +1,16 @@ use { criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}, solana_compute_budget_instruction::instructions_processor::process_compute_budget_instructions, - solana_sdk::{ - compute_budget::ComputeBudgetInstruction, - feature_set::FeatureSet, - instruction::Instruction, - message::Message, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_instruction::{self}, - transaction::{SanitizedTransaction, Transaction}, - }, + solana_compute_budget_interface::ComputeBudgetInstruction, + solana_feature_set::FeatureSet, + solana_instruction::Instruction, + solana_keypair::Keypair, + solana_message::Message, + solana_pubkey::Pubkey, + solana_signer::Signer, solana_svm_transaction::svm_message::SVMMessage, + solana_system_interface::instruction::transfer, + solana_transaction::{sanitized::SanitizedTransaction, Transaction}, }; const NUM_TRANSACTIONS_PER_ITER: usize = 1024; @@ -110,14 +108,18 @@ fn bench_process_compute_budget_instructions_builtins(c: &mut Criterion) { .throughput(Throughput::Elements(NUM_TRANSACTIONS_PER_ITER as u64)) .bench_function("4 dummy builtins", |bencher| { let ixs = vec![ - Instruction::new_with_bincode(solana_sdk::bpf_loader::id(), &(), vec![]), - Instruction::new_with_bincode(solana_sdk::secp256k1_program::id(), &(), vec![]), + Instruction::new_with_bincode(solana_sdk_ids::bpf_loader::id(), &(), vec![]), Instruction::new_with_bincode( - solana_sdk::address_lookup_table::program::id(), + solana_sdk_ids::secp256k1_program::id(), &(), vec![], ), - Instruction::new_with_bincode(solana_sdk::loader_v4::id(), &(), vec![]), + Instruction::new_with_bincode( + solana_sdk_ids::address_lookup_table::id(), + &(), + vec![], + ), + Instruction::new_with_bincode(solana_sdk_ids::loader_v4::id(), &(), vec![]), ]; let tx = build_sanitized_transaction(&Keypair::new(), &ixs); bencher.iter(|| { @@ -156,11 +158,7 @@ fn bench_process_compute_budget_instructions_mixed(c: &mut Criterion) { ComputeBudgetInstruction::set_compute_unit_limit(u32::MAX), ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(u32::MAX), - system_instruction::transfer( - &payer_keypair.pubkey(), - &Pubkey::new_unique(), - 1, - ), + transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 1), ]); let tx = build_sanitized_transaction(&payer_keypair, &ixs); diff --git a/compute-budget-instruction/src/builtin_programs_filter.rs b/compute-budget-instruction/src/builtin_programs_filter.rs index 1525dd1f2cfc61..a8b1058e0102c4 100644 --- a/compute-budget-instruction/src/builtin_programs_filter.rs +++ b/compute-budget-instruction/src/builtin_programs_filter.rs @@ -2,7 +2,8 @@ use { solana_builtins_default_costs::{ get_builtin_migration_feature_index, BuiltinMigrationFeatureIndex, MAYBE_BUILTIN_KEY, }, - solana_sdk::{packet::PACKET_DATA_SIZE, pubkey::Pubkey}, + solana_packet::PACKET_DATA_SIZE, + solana_pubkey::Pubkey, }; // The maximum number of pubkeys that a packet can contain. @@ -64,7 +65,7 @@ impl BuiltinProgramsFilter { mod test { use { super::*, solana_builtins_default_costs::get_migration_feature_position, - solana_sdk::feature_set, + solana_feature_set as feature_set, }; const DUMMY_PROGRAM_ID: &str = "dummmy1111111111111111111111111111111111111"; @@ -90,36 +91,36 @@ mod test { // lookup same `index` will return cached data, will not lookup `program_id` // again assert_eq!( - test_store.get_program_kind(index, &solana_sdk::loader_v4::id()), + test_store.get_program_kind(index, &solana_sdk_ids::loader_v4::id()), ProgramKind::NotBuiltin ); // not-migrating builtin index += 1; assert_eq!( - test_store.get_program_kind(index, &solana_sdk::loader_v4::id()), + test_store.get_program_kind(index, &solana_sdk_ids::loader_v4::id()), ProgramKind::Builtin, ); // compute-budget index += 1; assert_eq!( - test_store.get_program_kind(index, &solana_sdk::compute_budget::id()), + test_store.get_program_kind(index, &solana_sdk_ids::compute_budget::id()), ProgramKind::Builtin, ); // migrating builtins for (migrating_builtin_pubkey, migration_feature_id) in [ ( - solana_sdk::stake::program::id(), + solana_sdk_ids::stake::id(), feature_set::migrate_stake_program_to_core_bpf::id(), ), ( - solana_sdk::config::program::id(), + solana_sdk_ids::config::id(), feature_set::migrate_config_program_to_core_bpf::id(), ), ( - solana_sdk::address_lookup_table::program::id(), + solana_sdk_ids::address_lookup_table::id(), feature_set::migrate_address_lookup_table_program_to_core_bpf::id(), ), ] { diff --git a/compute-budget-instruction/src/compute_budget_instruction_details.rs b/compute-budget-instruction/src/compute_budget_instruction_details.rs index e0ef6341694167..145172033d0dfb 100644 --- a/compute-budget-instruction/src/compute_budget_instruction_details.rs +++ b/compute-budget-instruction/src/compute_budget_instruction_details.rs @@ -3,19 +3,16 @@ use { builtin_programs_filter::{BuiltinProgramsFilter, ProgramKind}, compute_budget_program_id_filter::ComputeBudgetProgramIdFilter, }, + solana_borsh::v1::try_from_slice_unchecked, solana_builtins_default_costs::{get_migration_feature_id, MIGRATING_BUILTINS_COSTS}, solana_compute_budget::compute_budget_limits::*, - solana_sdk::{ - borsh1::try_from_slice_unchecked, - compute_budget::ComputeBudgetInstruction, - feature_set::{self, FeatureSet}, - instruction::InstructionError, - pubkey::Pubkey, - saturating_add_assign, - transaction::{Result, TransactionError}, - }, + solana_compute_budget_interface::ComputeBudgetInstruction, + solana_feature_set::{self as feature_set, FeatureSet}, + solana_instruction::error::InstructionError, + solana_pubkey::Pubkey, solana_svm_transaction::instruction::SVMInstruction, - std::num::NonZeroU32, + solana_transaction_error::{TransactionError, TransactionResult as Result}, + std::num::{NonZeroU32, Saturating}, }; #[cfg_attr(test, derive(Eq, PartialEq))] @@ -25,13 +22,13 @@ 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()], + migrating_builtin: [Saturating; MIGRATING_BUILTINS_COSTS.len()], } impl Default for MigrationBuiltinFeatureCounter { fn default() -> Self { Self { - migrating_builtin: [0; MIGRATING_BUILTINS_COSTS.len()], + migrating_builtin: [Saturating(0); MIGRATING_BUILTINS_COSTS.len()], } } } @@ -46,10 +43,10 @@ pub struct ComputeBudgetInstructionDetails { requested_compute_unit_price: Option<(u8, u64)>, requested_heap_size: Option<(u8, u32)>, requested_loaded_accounts_data_size_limit: Option<(u8, u32)>, - num_non_compute_budget_instructions: u16, + num_non_compute_budget_instructions: Saturating, // Additional builtin program counters - num_non_migratable_builtin_instructions: u16, - num_non_builtin_instructions: u16, + num_non_migratable_builtin_instructions: Saturating, + num_non_builtin_instructions: Saturating, migrating_builtin_feature_counters: MigrationBuiltinFeatureCounter, } @@ -64,10 +61,7 @@ impl ComputeBudgetInstructionDetails { if filter.is_compute_budget_program(instruction.program_id_index as usize, program_id) { compute_budget_instruction_details.process_instruction(i as u8, &instruction)?; } else { - saturating_add_assign!( - compute_budget_instruction_details.num_non_compute_budget_instructions, - 1 - ); + compute_budget_instruction_details.num_non_compute_budget_instructions += 1; } } @@ -80,31 +74,22 @@ impl ComputeBudgetInstructionDetails { for (program_id, instruction) in instructions { match filter.get_program_kind(instruction.program_id_index as usize, program_id) { ProgramKind::Builtin => { - saturating_add_assign!( - compute_budget_instruction_details - .num_non_migratable_builtin_instructions, - 1 - ); + compute_budget_instruction_details + .num_non_migratable_builtin_instructions += 1; } ProgramKind::NotBuiltin => { - saturating_add_assign!( - compute_budget_instruction_details.num_non_builtin_instructions, - 1 - ); + compute_budget_instruction_details.num_non_builtin_instructions += 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 - ); + *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; } } } @@ -217,23 +202,23 @@ impl ComputeBudgetInstructionDetails { .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) + if count.0 > 0 && feature_set.is_active(get_migration_feature_id(index)) { + (migrated + count.0, not_migrated) } else { - (migrated, not_migrated + count) + (migrated, not_migrated + count.0) } }); - u32::from(self.num_non_migratable_builtin_instructions) + u32::from(self.num_non_migratable_builtin_instructions.0) .saturating_add(u32::from(num_not_migrated)) .saturating_mul(MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT) .saturating_add( - u32::from(self.num_non_builtin_instructions) + u32::from(self.num_non_builtin_instructions.0) .saturating_add(u32::from(num_migrated)) .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT), ) } else { - u32::from(self.num_non_compute_budget_instructions) + u32::from(self.num_non_compute_budget_instructions.0) .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT) } } @@ -244,15 +229,13 @@ mod test { use { super::*, solana_builtins_default_costs::get_migration_feature_position, - solana_sdk::{ - instruction::Instruction, - message::Message, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - transaction::{SanitizedTransaction, Transaction}, - }, + solana_instruction::Instruction, + solana_keypair::Keypair, + solana_message::Message, + solana_pubkey::Pubkey, + solana_signer::Signer, solana_svm_transaction::svm_message::SVMMessage, + solana_transaction::{sanitized::SanitizedTransaction, Transaction}, }; fn build_sanitized_transaction(instructions: &[Instruction]) -> SanitizedTransaction { @@ -272,9 +255,9 @@ mod test { ]); let expected_details = Ok(ComputeBudgetInstructionDetails { requested_heap_size: Some((1, 40 * 1024)), - num_non_compute_budget_instructions: 2, - num_non_migratable_builtin_instructions: 1, - num_non_builtin_instructions: 2, + num_non_compute_budget_instructions: Saturating(2), + num_non_migratable_builtin_instructions: Saturating(1), + num_non_builtin_instructions: Saturating(2), ..ComputeBudgetInstructionDetails::default() }); assert_eq!( @@ -302,7 +285,7 @@ mod test { ]); let expected_details = Ok(ComputeBudgetInstructionDetails { requested_compute_unit_limit: Some((1, u32::MAX)), - num_non_compute_budget_instructions: 2, + num_non_compute_budget_instructions: Saturating(2), ..ComputeBudgetInstructionDetails::default() }); assert_eq!( @@ -330,9 +313,9 @@ mod test { ]); let expected_details = Ok(ComputeBudgetInstructionDetails { requested_compute_unit_price: Some((1, u64::MAX)), - num_non_compute_budget_instructions: 2, - num_non_migratable_builtin_instructions: 1, - num_non_builtin_instructions: 2, + num_non_compute_budget_instructions: Saturating(2), + num_non_migratable_builtin_instructions: Saturating(1), + num_non_builtin_instructions: Saturating(2), ..ComputeBudgetInstructionDetails::default() }); assert_eq!( @@ -360,9 +343,9 @@ mod test { ]); let expected_details = Ok(ComputeBudgetInstructionDetails { requested_loaded_accounts_data_size_limit: Some((1, u32::MAX)), - num_non_compute_budget_instructions: 2, - num_non_migratable_builtin_instructions: 1, - num_non_builtin_instructions: 2, + num_non_compute_budget_instructions: Saturating(2), + num_non_migratable_builtin_instructions: Saturating(1), + num_non_builtin_instructions: Saturating(2), ..ComputeBudgetInstructionDetails::default() }); assert_eq!( @@ -397,11 +380,12 @@ mod test { &feature_set::reserve_minimal_cus_for_builtin_instructions::id(), 0, ); - u32::from(num_non_builtin_instructions) * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - + u32::from(num_non_migratable_builtin_instructions) + u32::from(num_non_builtin_instructions.0) * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT + + u32::from(num_non_migratable_builtin_instructions.0) * MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT } else { - u32::from(num_non_compute_budget_instructions) * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT + u32::from(num_non_compute_budget_instructions.0) + * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT }; (feature_set, expected_cu_limit) @@ -422,9 +406,9 @@ mod test { // no compute-budget instructions, all default ComputeBudgetLimits except cu-limit let instruction_details = ComputeBudgetInstructionDetails { - num_non_compute_budget_instructions: 4, - num_non_migratable_builtin_instructions: 1, - num_non_builtin_instructions: 3, + num_non_compute_budget_instructions: Saturating(4), + num_non_migratable_builtin_instructions: Saturating(1), + num_non_builtin_instructions: Saturating(3), ..ComputeBudgetInstructionDetails::default() }; for is_active in [true, false] { @@ -534,7 +518,7 @@ mod test { requested_compute_unit_price: Some((2, u64::MAX)), requested_heap_size: Some((3, MAX_HEAP_FRAME_BYTES)), requested_loaded_accounts_data_size_limit: Some((4, u32::MAX)), - num_non_compute_budget_instructions: 4, + num_non_compute_budget_instructions: Saturating(4), ..ComputeBudgetInstructionDetails::default() }; for is_active in [true, false] { @@ -579,7 +563,7 @@ mod 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( + solana_program::stake::instruction::delegate_stake( &Pubkey::new_unique(), &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -588,13 +572,13 @@ mod test { 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, + num_non_compute_budget_instructions: Saturating(2), + num_non_builtin_instructions: Saturating(1), ..ComputeBudgetInstructionDetails::default() }; expected_details .migrating_builtin_feature_counters - .migrating_builtin[feature_id_index] = 1; + .migrating_builtin[feature_id_index] = Saturating(1); let expected_details = Ok(expected_details); let details = ComputeBudgetInstructionDetails::try_from(SVMMessage::program_instructions_iter(&tx)); diff --git a/compute-budget-instruction/src/compute_budget_program_id_filter.rs b/compute-budget-instruction/src/compute_budget_program_id_filter.rs index 62ec47b7209ef0..14bf24232a77d2 100644 --- a/compute-budget-instruction/src/compute_budget_program_id_filter.rs +++ b/compute-budget-instruction/src/compute_budget_program_id_filter.rs @@ -1,7 +1,7 @@ // static account keys has max use { crate::builtin_programs_filter::FILTER_SIZE, solana_builtins_default_costs::MAYBE_BUILTIN_KEY, - solana_sdk::pubkey::Pubkey, + solana_pubkey::Pubkey, }; pub(crate) struct ComputeBudgetProgramIdFilter { @@ -31,6 +31,6 @@ impl ComputeBudgetProgramIdFilter { if !MAYBE_BUILTIN_KEY[program_id.as_ref()[0] as usize] { return false; } - solana_sdk::compute_budget::check_id(program_id) + solana_sdk_ids::compute_budget::check_id(program_id) } } diff --git a/compute-budget-instruction/src/instructions_processor.rs b/compute-budget-instruction/src/instructions_processor.rs index 6259044feb9023..47e99d0b3359d0 100644 --- a/compute-budget-instruction/src/instructions_processor.rs +++ b/compute-budget-instruction/src/instructions_processor.rs @@ -1,8 +1,8 @@ use { - crate::compute_budget_instruction_details::*, - solana_compute_budget::compute_budget_limits::*, - solana_sdk::{feature_set::FeatureSet, pubkey::Pubkey, transaction::TransactionError}, + crate::compute_budget_instruction_details::*, solana_compute_budget::compute_budget_limits::*, + solana_feature_set::FeatureSet, solana_pubkey::Pubkey, solana_svm_transaction::instruction::SVMInstruction, + solana_transaction_error::TransactionError, }; /// Processing compute_budget could be part of tx sanitizing, failed to process @@ -22,18 +22,17 @@ pub fn process_compute_budget_instructions<'a>( mod tests { use { super::*, - solana_sdk::{ - compute_budget::ComputeBudgetInstruction, - hash::Hash, - instruction::{Instruction, InstructionError}, - message::Message, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_instruction::{self}, - transaction::{SanitizedTransaction, Transaction, TransactionError}, - }, + solana_compute_budget_interface::ComputeBudgetInstruction, + solana_hash::Hash, + solana_instruction::{error::InstructionError, Instruction}, + solana_keypair::Keypair, + solana_message::Message, + solana_pubkey::Pubkey, + solana_signer::Signer, solana_svm_transaction::svm_message::SVMMessage, + solana_system_interface::instruction::transfer, + solana_transaction::{sanitized::SanitizedTransaction, Transaction}, + solana_transaction_error::TransactionError, std::num::NonZeroU32, }; @@ -412,7 +411,7 @@ mod tests { SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer( &[ Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - system_instruction::transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2), + transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2), ], Some(&payer_keypair.pubkey()), &[&payer_keypair], diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index fd4cd2c2049ed5..535391b9b6fec3 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5434,11 +5434,17 @@ name = "solana-compute-budget-instruction" version = "2.2.0" dependencies = [ "log", + "solana-borsh", "solana-builtins-default-costs", "solana-compute-budget", + "solana-compute-budget-interface", + "solana-feature-set", + "solana-instruction", + "solana-packet", "solana-pubkey", - "solana-sdk", + "solana-sdk-ids", "solana-svm-transaction", + "solana-transaction-error", "thiserror 2.0.9", ] diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index f38ef7446fa02c..d0c224f422118d 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -5285,11 +5285,17 @@ name = "solana-compute-budget-instruction" version = "2.2.0" dependencies = [ "log", + "solana-borsh", "solana-builtins-default-costs", "solana-compute-budget", + "solana-compute-budget-interface", + "solana-feature-set", + "solana-instruction", + "solana-packet", "solana-pubkey", - "solana-sdk", + "solana-sdk-ids", "solana-svm-transaction", + "solana-transaction-error", "thiserror 2.0.9", ]