diff --git a/core/src/validator.rs b/core/src/validator.rs index 923be65d65..cecda8da28 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -74,7 +74,7 @@ use { accounts_db::{AccountShrinkThreshold, AccountsDbConfig}, accounts_index::AccountSecondaryIndexes, accounts_update_notifier_interface::AccountsUpdateNotifier, - bank::{pyth_accumulator, Bank}, + bank::{pyth, Bank}, bank_forks::BankForks, commitment::BlockCommitmentCache, cost_model::CostModel, @@ -1521,7 +1521,7 @@ fn load_blockstore( } } - for (key_name, pk_res) in pyth_accumulator::get_accumulator_keys() { + for (key_name, pk_res) in pyth::accumulator::get_accumulator_keys() { match pk_res { Ok(pk) => info!("Accumulator {}: {}", key_name, pk), Err(err) => { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e7a3bb53d8..a5330d1fe1 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -198,11 +198,7 @@ mod builtin_programs; mod sysvar_cache; mod transaction_account_state_info; -pub mod pyth_accumulator; -mod pyth_batch_publish; - -#[cfg(test)] -mod pyth_accumulator_tests; +pub mod pyth; pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0; @@ -1411,7 +1407,7 @@ impl Bank { // state before the accumulator is used. bank is in a fully // updated state before the accumulator is used. if !accumulator_moved_to_end_of_block { - pyth_accumulator::update_accumulator(&bank); + pyth::accumulator::update_accumulator(&bank); } bank @@ -1797,7 +1793,7 @@ impl Bank { // the accumulator sysvar updates. sysvars are in a fully updated // state before the accumulator sysvar updates. if !accumulator_moved_to_end_of_block { - pyth_accumulator::update_accumulator(&new); + pyth::accumulator::update_accumulator(&new); } }); @@ -3239,7 +3235,7 @@ impl Bank { // 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); + pyth::accumulator::update_accumulator(self); measure.stop(); debug!( diff --git a/runtime/src/bank/pyth_accumulator.rs b/runtime/src/bank/pyth/accumulator.rs similarity index 98% rename from runtime/src/bank/pyth_accumulator.rs rename to runtime/src/bank/pyth/accumulator.rs index ee30aca37c..2c0ae04cc8 100644 --- a/runtime/src/bank/pyth_accumulator.rs +++ b/runtime/src/bank/pyth/accumulator.rs @@ -1,6 +1,9 @@ use { - super::{pyth_batch_publish, Bank}, - crate::accounts_index::{IndexKey, ScanConfig, ScanError}, + super::batch_publish, + crate::{ + accounts_index::{IndexKey, ScanConfig, ScanError}, + bank::Bank, + }, byteorder::{LittleEndian, ReadBytesExt}, log::*, pyth_oracle::validator::AggregationError, @@ -438,7 +441,7 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV v2_messages.push(publisher_stake_caps_message); } - let new_prices = pyth_batch_publish::extract_batch_publish_prices(bank).unwrap_or_else(|err| { + let new_prices = batch_publish::extract_batch_publish_prices(bank).unwrap_or_else(|err| { warn!("extract_batch_publish_prices failed: {}", err); HashMap::new() }); @@ -455,7 +458,7 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV }; let mut need_save = - pyth_batch_publish::apply_published_prices(price_account, &new_prices, bank.slot()); + batch_publish::apply_published_prices(price_account, &new_prices, bank.slot()); // Perform Accumulation match pyth_oracle::validator::aggregate_price( diff --git a/runtime/src/bank/pyth_batch_publish.rs b/runtime/src/bank/pyth/batch_publish.rs similarity index 98% rename from runtime/src/bank/pyth_batch_publish.rs rename to runtime/src/bank/pyth/batch_publish.rs index fe9c6cb371..e8302f8cc5 100644 --- a/runtime/src/bank/pyth_batch_publish.rs +++ b/runtime/src/bank/pyth/batch_publish.rs @@ -1,6 +1,9 @@ use { - super::{pyth_accumulator::BATCH_PUBLISH_PID, Bank}, - crate::accounts_index::{IndexKey, ScanConfig, ScanError}, + super::accumulator::BATCH_PUBLISH_PID, + crate::{ + accounts_index::{IndexKey, ScanConfig, ScanError}, + bank::Bank, + }, log::warn, pyth_oracle::{ find_publisher_index, get_status_for_conf_price_ratio, solana_program::pubkey::Pubkey, diff --git a/runtime/src/bank/pyth/mod.rs b/runtime/src/bank/pyth/mod.rs new file mode 100644 index 0000000000..795e29db71 --- /dev/null +++ b/runtime/src/bank/pyth/mod.rs @@ -0,0 +1,5 @@ +pub mod accumulator; +mod batch_publish; + +#[cfg(test)] +mod tests; diff --git a/runtime/src/bank/pyth_accumulator_tests.rs b/runtime/src/bank/pyth/tests/accumulator_tests.rs similarity index 87% rename from runtime/src/bank/pyth_accumulator_tests.rs rename to runtime/src/bank/pyth/tests/accumulator_tests.rs index 5c3673a131..6ab1c05f11 100644 --- a/runtime/src/bank/pyth_accumulator_tests.rs +++ b/runtime/src/bank/pyth/tests/accumulator_tests.rs @@ -1,21 +1,17 @@ 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, BATCH_PUBLISH_PID, ORACLE_PID, - STAKE_CAPS_PARAMETERS_ADDR, + pyth::{ + accumulator::{ + get_accumulator_keys, ACCUMULATOR_RING_SIZE, BATCH_PUBLISH_PID, ORACLE_PID, + STAKE_CAPS_PARAMETERS_ADDR, + }, + tests::{create_new_bank_for_tests_with_index, new_from_parent}, }, - pyth_batch_publish::publisher_prices_account::{self, PublisherPrice}, Bank, }, genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}, }, - bytemuck::{cast_slice, checked::from_bytes}, byteorder::{ByteOrder, LittleEndian, ReadBytesExt}, itertools::Itertools, pyth_oracle::{ @@ -36,7 +32,6 @@ use { epoch_schedule::EpochSchedule, feature::{self, Feature}, feature_set, - genesis_config::GenesisConfig, hash::hashv, pubkey::Pubkey, signature::keypair_from_seed, @@ -45,23 +40,6 @@ 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, *BATCH_PUBLISH_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 @@ -400,10 +378,6 @@ fn test_update_accumulator_sysvar() { // 3. Check if message offset is > message size to prevent validator crash. } -fn new_from_parent(parent: &Arc) -> Bank { - Bank::new_from_parent(parent, &Pubkey::default(), parent.slot() + 1) -} - #[test] fn test_update_accumulator_end_of_block() { let leader_pubkey = solana_sdk::pubkey::new_rand(); @@ -1146,128 +1120,3 @@ fn test_get_accumulator_keys() { ]; assert_eq!(accumulator_keys, expected_pyth_keys); } - -#[test] -fn test_batch_publish() { - let leader_pubkey = solana_sdk::pubkey::new_rand(); - let GenesisConfigInfo { - mut genesis_config, .. - } = create_genesis_config_with_leader(5, &leader_pubkey, 3); - - // Set epoch length to 32 so we can advance epochs quickly. We also skip past slot 0 here - // due to slot 0 having special handling. - let slots_in_epoch = 32; - genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch); - let mut bank = create_new_bank_for_tests_with_index(&genesis_config); - - let generate_publisher = |seed, new_prices| { - let publisher1_key = keypair_from_seed(seed).unwrap(); - - let (publisher1_prices_key, _bump) = Pubkey::find_program_address( - // TODO: real seed - &[ - b"PUBLISHER_PRICES_ACCOUNT", - &publisher1_key.pubkey().to_bytes(), - ], - &BATCH_PUBLISH_PID, - ); - let mut publisher1_prices_account = - AccountSharedData::new(42, publisher_prices_account::size(100), &BATCH_PUBLISH_PID); - { - let (header, prices) = publisher_prices_account::create( - publisher1_prices_account.data_mut(), - publisher1_key.pubkey().to_bytes(), - ) - .unwrap(); - publisher_prices_account::extend(header, prices, cast_slice(new_prices)).unwrap(); - } - bank.store_account(&publisher1_prices_key, &publisher1_prices_account); - - publisher1_key - }; - - let publishers = [ - generate_publisher( - &[1u8; 32], - &[ - PublisherPrice::new(1, 1, 10, 2).unwrap(), - PublisherPrice::new(2, 1, 20, 3).unwrap(), - ], - ), - generate_publisher( - &[2u8; 32], - &[ - PublisherPrice::new(1, 1, 15, 2).unwrap(), - PublisherPrice::new(2, 1, 25, 3).unwrap(), - ], - ), - ]; - - let generate_price = |seeds, index| { - let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PID); - let mut price_feed_account = - AccountSharedData::new(42, size_of::(), &ORACLE_PID); - - let messages = { - let price_feed_info_key = &price_feed_key.to_bytes().into(); - let price_feed_info_lamports = &mut 0; - let price_feed_info_owner = &ORACLE_PID.to_bytes().into(); - let price_feed_info_data = price_feed_account.data_mut(); - let price_feed_info = AccountInfo::new( - price_feed_info_key, - false, - true, - price_feed_info_lamports, - price_feed_info_data, - price_feed_info_owner, - false, - Epoch::default(), - ); - - let mut price_account = PriceAccount::initialize(&price_feed_info, 0).unwrap(); - price_account.flags.insert( - PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED, - ); - price_account.feed_index = index; - price_account.comp_[0].pub_ = publishers[0].pubkey().to_bytes().into(); - price_account.comp_[1].pub_ = publishers[1].pubkey().to_bytes().into(); - price_account.num_ = 2; - }; - - bank.store_account(&price_feed_key, &price_feed_account); - (price_feed_key, messages) - }; - - assert!(bank - .feature_set - .is_active(&feature_set::enable_accumulator_sysvar::id())); - assert!(bank - .feature_set - .is_active(&feature_set::move_accumulator_to_end_of_block::id())); - assert!(bank - .feature_set - .is_active(&feature_set::undo_move_accumulator_to_end_of_block::id())); - assert!(bank - .feature_set - .is_active(&feature_set::redo_move_accumulator_to_end_of_block::id())); - - let prices_with_messages = [ - generate_price(b"seeds_1", 1), - generate_price(b"seeds_2", 2), - generate_price(b"seeds_3", 3), - generate_price(b"seeds_4", 4), - ]; - - bank = new_from_parent(&Arc::new(bank)); // Advance slot 1. - bank = new_from_parent(&Arc::new(bank)); // Advance slot 2. - - let new_price_feed1_account = bank.get_account(&prices_with_messages[0].0).unwrap(); - let new_price_feed1_data: &PriceAccount = from_bytes(new_price_feed1_account.data()); - assert_eq!(new_price_feed1_data.comp_[0].latest_.price_, 10); - assert_eq!(new_price_feed1_data.comp_[1].latest_.price_, 15); - - let new_price_feed2_account = bank.get_account(&prices_with_messages[1].0).unwrap(); - let new_price_feed2_data: &PriceAccount = from_bytes(new_price_feed2_account.data()); - assert_eq!(new_price_feed2_data.comp_[0].latest_.price_, 20); - assert_eq!(new_price_feed2_data.comp_[1].latest_.price_, 25); -} diff --git a/runtime/src/bank/pyth/tests/batch_publish_tests.rs b/runtime/src/bank/pyth/tests/batch_publish_tests.rs new file mode 100644 index 0000000000..65a1a590dd --- /dev/null +++ b/runtime/src/bank/pyth/tests/batch_publish_tests.rs @@ -0,0 +1,149 @@ +use { + crate::{ + bank::pyth::{ + accumulator::{BATCH_PUBLISH_PID, ORACLE_PID}, + batch_publish::publisher_prices_account::{self, PublisherPrice}, + tests::{create_new_bank_for_tests_with_index, new_from_parent}, + }, + genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}, + }, + bytemuck::{cast_slice, checked::from_bytes}, + pyth_oracle::{ + solana_program::account_info::AccountInfo, PriceAccount, PriceAccountFlags, PythAccount, + }, + solana_sdk::{ + account::{AccountSharedData, ReadableAccount, WritableAccount}, + clock::Epoch, + epoch_schedule::EpochSchedule, + feature_set, + pubkey::Pubkey, + signature::keypair_from_seed, + signer::Signer, + }, + std::{mem::size_of, sync::Arc}, +}; + +#[test] +fn test_batch_publish() { + let leader_pubkey = solana_sdk::pubkey::new_rand(); + let GenesisConfigInfo { + mut genesis_config, .. + } = create_genesis_config_with_leader(5, &leader_pubkey, 3); + + // Set epoch length to 32 so we can advance epochs quickly. We also skip past slot 0 here + // due to slot 0 having special handling. + let slots_in_epoch = 32; + genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch); + let mut bank = create_new_bank_for_tests_with_index(&genesis_config); + + let generate_publisher = |seed, new_prices| { + let publisher1_key = keypair_from_seed(seed).unwrap(); + + let (publisher1_prices_key, _bump) = Pubkey::find_program_address( + // TODO: real seed + &[ + b"PUBLISHER_PRICES_ACCOUNT", + &publisher1_key.pubkey().to_bytes(), + ], + &BATCH_PUBLISH_PID, + ); + let mut publisher1_prices_account = + AccountSharedData::new(42, publisher_prices_account::size(100), &BATCH_PUBLISH_PID); + { + let (header, prices) = publisher_prices_account::create( + publisher1_prices_account.data_mut(), + publisher1_key.pubkey().to_bytes(), + ) + .unwrap(); + publisher_prices_account::extend(header, prices, cast_slice(new_prices)).unwrap(); + } + bank.store_account(&publisher1_prices_key, &publisher1_prices_account); + + publisher1_key + }; + + let publishers = [ + generate_publisher( + &[1u8; 32], + &[ + PublisherPrice::new(1, 1, 10, 2).unwrap(), + PublisherPrice::new(2, 1, 20, 3).unwrap(), + ], + ), + generate_publisher( + &[2u8; 32], + &[ + PublisherPrice::new(1, 1, 15, 2).unwrap(), + PublisherPrice::new(2, 1, 25, 3).unwrap(), + ], + ), + ]; + + let generate_price = |seeds, index| { + let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PID); + let mut price_feed_account = + AccountSharedData::new(42, size_of::(), &ORACLE_PID); + + let messages = { + let price_feed_info_key = &price_feed_key.to_bytes().into(); + let price_feed_info_lamports = &mut 0; + let price_feed_info_owner = &ORACLE_PID.to_bytes().into(); + let price_feed_info_data = price_feed_account.data_mut(); + let price_feed_info = AccountInfo::new( + price_feed_info_key, + false, + true, + price_feed_info_lamports, + price_feed_info_data, + price_feed_info_owner, + false, + Epoch::default(), + ); + + let mut price_account = PriceAccount::initialize(&price_feed_info, 0).unwrap(); + price_account.flags.insert( + PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED, + ); + price_account.feed_index = index; + price_account.comp_[0].pub_ = publishers[0].pubkey().to_bytes().into(); + price_account.comp_[1].pub_ = publishers[1].pubkey().to_bytes().into(); + price_account.num_ = 2; + }; + + bank.store_account(&price_feed_key, &price_feed_account); + (price_feed_key, messages) + }; + + assert!(bank + .feature_set + .is_active(&feature_set::enable_accumulator_sysvar::id())); + assert!(bank + .feature_set + .is_active(&feature_set::move_accumulator_to_end_of_block::id())); + assert!(bank + .feature_set + .is_active(&feature_set::undo_move_accumulator_to_end_of_block::id())); + assert!(bank + .feature_set + .is_active(&feature_set::redo_move_accumulator_to_end_of_block::id())); + + let prices_with_messages = [ + generate_price(b"seeds_1", 1), + generate_price(b"seeds_2", 2), + generate_price(b"seeds_3", 3), + generate_price(b"seeds_4", 4), + ]; + + bank = new_from_parent(&Arc::new(bank)); // Advance slot 1. + bank = new_from_parent(&Arc::new(bank)); // Advance slot 2. + + let new_price_feed1_account = bank.get_account(&prices_with_messages[0].0).unwrap(); + let new_price_feed1_data: &PriceAccount = from_bytes(new_price_feed1_account.data()); + assert_eq!(new_price_feed1_data.comp_[0].latest_.price_, 10); + assert_eq!(new_price_feed1_data.comp_[1].latest_.price_, 15); + + let new_price_feed2_account = bank.get_account(&prices_with_messages[1].0).unwrap(); + let new_price_feed2_data: &PriceAccount = from_bytes(new_price_feed2_account.data()); + assert_eq!(new_price_feed2_data.comp_[0].latest_.price_, 20); + assert_eq!(new_price_feed2_data.comp_[1].latest_.price_, 25); +} diff --git a/runtime/src/bank/pyth/tests/mod.rs b/runtime/src/bank/pyth/tests/mod.rs new file mode 100644 index 0000000000..5fef8052b9 --- /dev/null +++ b/runtime/src/bank/pyth/tests/mod.rs @@ -0,0 +1,36 @@ +use { + super::accumulator::{BATCH_PUBLISH_PID, MESSAGE_BUFFER_PID, ORACLE_PID}, + crate::{ + accounts_db::AccountShrinkThreshold, + accounts_index::{ + AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude, + }, + bank::Bank, + }, + solana_sdk::{genesis_config::GenesisConfig, pubkey::Pubkey}, + std::sync::Arc, +}; + +mod accumulator_tests; +mod batch_publish_tests; + +fn new_from_parent(parent: &Arc) -> Bank { + Bank::new_from_parent(parent, &Pubkey::default(), parent.slot() + 1) +} + +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, *BATCH_PUBLISH_PID] + .into_iter() + .collect(), + }), + indexes: [AccountIndex::ProgramId].into_iter().collect(), + }, + false, + AccountShrinkThreshold::default(), + ) +} diff --git a/validator/src/main.rs b/validator/src/main.rs index efee36b715..a86c88b488 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -52,7 +52,7 @@ use { AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude, AccountsIndexConfig, IndexLimitMb, }, - bank::pyth_accumulator::{BATCH_PUBLISH_PID, MESSAGE_BUFFER_PID, ORACLE_PID}, + bank::pyth::accumulator::{BATCH_PUBLISH_PID, MESSAGE_BUFFER_PID, ORACLE_PID}, hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, runtime_config::RuntimeConfig, snapshot_config::SnapshotConfig,