diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index f81ab66a35..760901289b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -3237,7 +3237,15 @@ impl Bank { // If accumulator move to end of block is active update the accumulator before doing // other tasks when freezing to avoid any conflicts. if accumulator_moved_to_end_of_block { + let mut measure = Measure::start("accumulator"); pyth_accumulator::update_accumulator(self); + measure.stop(); + + debug!( + "Accumulator: Updated accumulator before freezing. Slot: {}, time: {}us", + self.slot(), + measure.as_us(), + ); } else { info!( "Accumulator: Skipping accumulating end of block because the feature is disabled. Slot: {}", @@ -3245,6 +3253,8 @@ impl Bank { ); } + let mut measure = Measure::start("freeze"); + // finish up any deferred changes to account state self.collect_rent_eagerly(false); self.collect_fees(); @@ -3252,6 +3262,14 @@ impl Bank { self.update_slot_history(); self.run_incinerator(); + measure.stop(); + debug!( + "Bank frozen (non-accumulator): {} slot {} time: {}us", + self.collector_id, + self.slot(), + measure.as_us() + ); + // freeze is a one-way trip, idempotent self.freeze_started.store(true, Relaxed); *hash = self.hash_internal_state(); diff --git a/runtime/src/bank/pyth_accumulator.rs b/runtime/src/bank/pyth_accumulator.rs index 5ef19dbed1..eb34783073 100644 --- a/runtime/src/bank/pyth_accumulator.rs +++ b/runtime/src/bank/pyth_accumulator.rs @@ -1,6 +1,6 @@ use { super::Bank, - crate::accounts_index::{ScanConfig, ScanError}, + crate::accounts_index::{IndexKey, ScanConfig, ScanError}, byteorder::{LittleEndian, ReadBytesExt}, log::*, pyth_oracle::validator::AggregationError, @@ -9,6 +9,7 @@ use { hashers::keccak256_160::Keccak160, wormhole::{AccumulatorSequenceTracker, MessageData, PostedMessageUnreliableData}, }, + solana_measure::measure::Measure, solana_sdk::{ account::{AccountSharedData, ReadableAccount}, borsh, feature_set, @@ -136,10 +137,29 @@ pub fn update_v1( let message_buffer_accounts; let v1_messages = if use_message_buffers { + let mut measure = Measure::start("update_v1_load_program_accounts"); + + assert!( + bank.account_indexes_include_key(&*MESSAGE_BUFFER_PID), + "MessageBuffer program account index missing" + ); message_buffer_accounts = bank - .get_program_accounts(&MESSAGE_BUFFER_PID, &ScanConfig::new(true)) + .get_filtered_indexed_accounts( + &IndexKey::ProgramId(*MESSAGE_BUFFER_PID), + |account| account.owner() == &*MESSAGE_BUFFER_PID, + &ScanConfig::new(true), + None, + ) .map_err(AccumulatorUpdateErrorV1::GetProgramAccounts)?; + measure.stop(); + debug!( + "Accumulator: Loaded message buffer accounts in {}us", + measure.as_us() + ); + + let mut measure = Measure::start("update_v1_extract_message_data"); + let preimage = b"account:MessageBuffer"; let mut expected_sighash = [0u8; 8]; expected_sighash.copy_from_slice(&hashv(&[preimage]).to_bytes()[..8]); @@ -154,7 +174,7 @@ pub fn update_v1( // This code, using the offsets in each Account, extracts the various data versions from // the account. We deduplicate this result because the accumulator expects a set. - message_buffer_accounts + let res = message_buffer_accounts .map(|(_, account)| { let data = account.data(); let mut cursor = std::io::Cursor::new(&data); @@ -185,11 +205,21 @@ pub fn update_v1( .collect::, std::io::Error>>()? .into_iter() .flatten() - .collect() + .collect(); + + measure.stop(); + debug!( + "Accumulator: Extracted message data in {}us", + measure.as_us() + ); + + res } else { Vec::new() }; + let mut measure = Measure::start("create_message_set"); + let mut messages = v1_messages; messages.extend(v2_messages.iter().map(|x| &**x)); messages.sort_unstable(); @@ -220,11 +250,27 @@ pub fn update_v1( account }; + measure.stop(); + debug!("Accumulator: Created message set in {}us", measure.as_us()); + // Generate a Message owned by Wormhole to be sent cross-chain. This short-circuits the // Wormhole message generation code that would normally be called, but the Guardian // set filters our messages so this does not pose a security risk. - if let Some(accumulator) = MerkleAccumulator::::from_set(messages.into_iter()) { + let mut measure = Measure::start("create_accumulator"); + + let maybe_accumulator = MerkleAccumulator::::from_set(messages.into_iter()); + + measure.stop(); + debug!("Accumulator: Created accumulator in {}us", measure.as_us()); + + if let Some(accumulator) = maybe_accumulator { + let mut measure = Measure::start("post_accumulator_attestation"); post_accumulator_attestation(bank, accumulator, ring_index)?; + measure.stop(); + debug!( + "Accumulator: Posted accumulator attestation in {}us", + measure.as_us() + ); } // Write the Account Set into `accumulator_state` so that the hermes application can @@ -233,7 +279,14 @@ pub fn update_v1( "Accumulator: Writing accumulator state to {:?}", accumulator_account ); + + let mut measure = Measure::start("store_account_and_update_capitalization"); bank.store_account_and_update_capitalization(&accumulator_account, &accumulator_data); + measure.stop(); + debug!( + "Accumulator: Stored accumulator state in {}us", + measure.as_us() + ); Ok(()) } @@ -333,10 +386,30 @@ fn post_accumulator_attestation( } pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV1> { + let mut measure = Measure::start("update_v2_load_program_accounts"); + + assert!( + bank.account_indexes_include_key(&*ORACLE_PID), + "Oracle program account index missing" + ); + let accounts = bank - .get_program_accounts(&ORACLE_PID, &ScanConfig::new(true)) + .get_filtered_indexed_accounts( + &IndexKey::ProgramId(*ORACLE_PID), + |account| account.owner() == &*ORACLE_PID, + &ScanConfig::new(true), + None, + ) .map_err(AccumulatorUpdateErrorV1::GetProgramAccounts)?; + measure.stop(); + debug!( + "Accumulator: Loaded oracle program accounts in {}us", + measure.as_us() + ); + + let mut measure = Measure::start("update_v2_aggregate_price"); + let mut any_v1_aggregations = false; let mut v2_messages = Vec::new(); @@ -364,5 +437,12 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV } } + measure.stop(); + debug!( + "Accumulator: Aggregated oracle prices in {}us and generated {} messages", + measure.as_us(), + v2_messages.len() + ); + update_v1(bank, &v2_messages, any_v1_aggregations) } diff --git a/runtime/src/bank/pyth_accumulator_tests.rs b/runtime/src/bank/pyth_accumulator_tests.rs index 1ded8c42bc..e8736af9ec 100644 --- a/runtime/src/bank/pyth_accumulator_tests.rs +++ b/runtime/src/bank/pyth_accumulator_tests.rs @@ -1,5 +1,10 @@ use { + super::pyth_accumulator::MESSAGE_BUFFER_PID, crate::{ + accounts_db::AccountShrinkThreshold, + accounts_index::{ + AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude, + }, bank::{ pyth_accumulator::{get_accumulator_keys, ACCUMULATOR_RING_SIZE, ORACLE_PID}, Bank, @@ -25,6 +30,7 @@ use { epoch_schedule::EpochSchedule, feature::{self, Feature}, feature_set, + genesis_config::GenesisConfig, hash::hashv, pubkey::Pubkey, signature::keypair_from_seed, @@ -33,6 +39,21 @@ use { std::{io::Read, mem::size_of, sync::Arc}, }; +fn create_new_bank_for_tests_with_index(genesis_config: &GenesisConfig) -> Bank { + Bank::new_with_config_for_tests( + genesis_config, + AccountSecondaryIndexes { + keys: Some(AccountSecondaryIndexesIncludeExclude { + exclude: false, + keys: [*ORACLE_PID, *MESSAGE_BUFFER_PID].into_iter().collect(), + }), + indexes: [AccountIndex::ProgramId].into_iter().collect(), + }, + false, + AccountShrinkThreshold::default(), + ) +} + // Create Message Account Bytes // // NOTE: This was serialized by hand, but should be replaced with the pythnet-sdk @@ -107,7 +128,7 @@ fn test_update_accumulator_sysvar() { // due to slot 0 having special handling. let slots_in_epoch = 32; genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch); - let mut bank = Bank::new_for_tests(&genesis_config); + let mut bank = create_new_bank_for_tests_with_index(&genesis_config); bank = new_from_parent(&Arc::new(bank)); bank = new_from_parent(&Arc::new(bank)); @@ -392,7 +413,7 @@ fn test_update_accumulator_end_of_block() { // due to slot 0 having special handling. let slots_in_epoch = 32; genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch); - let mut bank = Bank::new_for_tests(&genesis_config); + let mut bank = create_new_bank_for_tests_with_index(&genesis_config); bank = new_from_parent(&Arc::new(bank)); bank = new_from_parent(&Arc::new(bank)); @@ -683,7 +704,7 @@ fn test_accumulator_v2(generate_buffers: [bool; 4]) { // due to slot 0 having special handling. let slots_in_epoch = 32; genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch); - let mut bank = Bank::new_for_tests(&genesis_config); + let mut bank = create_new_bank_for_tests_with_index(&genesis_config); let generate_price = |seeds, generate_buffers: bool| { let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PID); diff --git a/validator/src/main.rs b/validator/src/main.rs index 6109a745a2..235cef21b7 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -52,6 +52,7 @@ use { AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude, AccountsIndexConfig, IndexLimitMb, }, + bank::pyth_accumulator::{MESSAGE_BUFFER_PID, ORACLE_PID}, hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, runtime_config::RuntimeConfig, snapshot_config::SnapshotConfig, @@ -3176,6 +3177,10 @@ fn process_account_indexes(matches: &ArgMatches) -> AccountSecondaryIndexes { }) .collect(); + assert(account_indexes.contains(&AccountIndex::ProgramId), + "The indexing should be enabled for program-id accounts. Add the following flag:\n\ + --account-index program-id\n"); + let account_indexes_include_keys: HashSet = values_t!(matches, "account_index_include_key", Pubkey) .unwrap_or_default() @@ -3193,6 +3198,23 @@ fn process_account_indexes(matches: &ArgMatches) -> AccountSecondaryIndexes { let exclude_keys = !account_indexes_exclude_keys.is_empty(); let include_keys = !account_indexes_include_keys.is_empty(); + if include_keys { + if !account_indexes_include_keys.contains(&*ORACLE_PID) || !account_indexes_include_keys.contains(&*MESSAGE_BUFFER_PID) { + panic!( + "The oracle program id and message buffer program id must be included in the account index. Add the following flags\n\ + --account-index-include-key {}\n\ + --account-index-include-key {}\n", + &*ORACLE_PID, &*MESSAGE_BUFFER_PID + ); + } + } + + if exclude_keys { + if account_indexes_exclude_keys.contains(&*ORACLE_PID) || account_indexes_exclude_keys.contains(&*MESSAGE_BUFFER_PID) { + panic!("The oracle program id and message buffer program id must *not* be excluded from the account index."); + } + } + let keys = if !account_indexes.is_empty() && (exclude_keys || include_keys) { let account_indexes_keys = AccountSecondaryIndexesIncludeExclude { exclude: exclude_keys,