diff --git a/Cargo.lock b/Cargo.lock index 888c6b7c5f..34a08fbd3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,7 +508,17 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" dependencies = [ - "borsh-derive", + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", "hashbrown 0.11.2", ] @@ -518,8 +528,21 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2 1.0.86", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal 0.10.3", + "borsh-schema-derive-internal 0.10.3", "proc-macro-crate 0.1.5", "proc-macro2 1.0.86", "syn 1.0.109", @@ -536,6 +559,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 1.0.109", +] + [[package]] name = "borsh-schema-derive-internal" version = "0.9.3" @@ -547,6 +581,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 1.0.109", +] + [[package]] name = "brotli" version = "3.3.4" @@ -3555,8 +3600,8 @@ dependencies = [ [[package]] name = "pyth-oracle" -version = "2.32.1" -source = "git+https://github.com/pyth-network/pyth-client?tag=oracle-v2.32.1#94babc4096eb7ff804f5087fb9600ee110b9591d" +version = "2.33.0" +source = "git+https://github.com/pyth-network/pyth-client?tag=oracle-v2.33.0#5ee8b5f63f8b1d72f84c1177adaf564d7d61d092" dependencies = [ "bindgen 0.60.1", "bitflags 2.6.0", @@ -3564,7 +3609,7 @@ dependencies = [ "byteorder", "num-derive", "num-traits", - "pythnet-sdk 1.13.6 (git+https://github.com/pyth-network/pyth-crosschain?rev=60144002053a93f424be70decd8a8ccb8d618d81)", + "pythnet-sdk 2.2.0", "solana-program 1.14.17", "solana-sdk 1.14.17", "thiserror", @@ -3573,37 +3618,38 @@ dependencies = [ [[package]] name = "pythnet-sdk" version = "1.13.6" -source = "git+https://github.com/pyth-network/pyth-crosschain?rev=60144002053a93f424be70decd8a8ccb8d618d81#60144002053a93f424be70decd8a8ccb8d618d81" +source = "git+https://github.com/pyth-network/pyth-crosschain?rev=33f901aa45f4f0005aa5a84a1479b78ca9033074#33f901aa45f4f0005aa5a84a1479b78ca9033074" dependencies = [ "bincode", - "borsh", + "borsh 0.9.3", "bytemuck", - "byteorder", "fast-math", "hex", "rustc_version 0.4.0", "serde", + "serde_wormhole", "sha3 0.10.8", "slow_primes", - "thiserror", + "wormhole-sdk", ] [[package]] name = "pythnet-sdk" -version = "1.13.6" -source = "git+https://github.com/pyth-network/pyth-crosschain?rev=e670f57f89b05398ca352e4accb1e32724a8e1b4#e670f57f89b05398ca352e4accb1e32724a8e1b4" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f81d23a693463366640d1ba14c612841b5b07adc56ee20bbf9acaca2ad86b5" dependencies = [ "bincode", - "borsh", + "borsh 0.10.3", "bytemuck", + "byteorder", "fast-math", "hex", "rustc_version 0.4.0", "serde", - "serde_wormhole", "sha3 0.10.8", "slow_primes", - "wormhole-sdk", + "thiserror", ] [[package]] @@ -4733,7 +4779,7 @@ dependencies = [ name = "solana-banks-client" version = "1.14.177" dependencies = [ - "borsh", + "borsh 0.9.3", "futures 0.3.21", "solana-banks-interface", "solana-banks-server", @@ -5877,8 +5923,8 @@ dependencies = [ "bincode", "bitflags 1.3.2", "blake3", - "borsh", - "borsh-derive", + "borsh 0.9.3", + "borsh-derive 0.9.3", "bs58", "bv", "bytemuck", @@ -5926,8 +5972,8 @@ dependencies = [ "bincode", "bitflags 1.3.2", "blake3", - "borsh", - "borsh-derive", + "borsh 0.9.3", + "borsh-derive 0.9.3", "bs58", "bv", "bytemuck", @@ -6154,7 +6200,7 @@ dependencies = [ "once_cell", "ouroboros", "pyth-oracle", - "pythnet-sdk 1.13.6 (git+https://github.com/pyth-network/pyth-crosschain?rev=e670f57f89b05398ca352e4accb1e32724a8e1b4)", + "pythnet-sdk 1.13.6", "rand 0.7.3", "rand_chacha 0.2.2", "rayon", @@ -6199,7 +6245,7 @@ dependencies = [ "base64 0.13.0", "bincode", "bitflags 1.3.2", - "borsh", + "borsh 0.9.3", "bs58", "bytemuck", "byteorder", @@ -6249,7 +6295,7 @@ dependencies = [ "base64 0.13.0", "bincode", "bitflags 1.3.2", - "borsh", + "borsh 0.9.3", "bs58", "bytemuck", "byteorder", @@ -6561,7 +6607,7 @@ dependencies = [ "Inflector", "base64 0.13.0", "bincode", - "borsh", + "borsh 0.9.3", "bs58", "lazy_static", "log", @@ -6815,7 +6861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc000f0fdf1f12f99d77d398137c1751345b18c88258ce0f99b7872cf6c9bd6" dependencies = [ "assert_matches", - "borsh", + "borsh 0.9.3", "num-derive", "num-traits", "solana-program 1.14.17", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 23e02ae54f..72c9fd2090 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -35,8 +35,8 @@ num-traits = { version = "0.2" } num_cpus = "1.13.1" once_cell = "1.12.0" ouroboros = "0.15.0" -pyth-oracle = { git = "https://github.com/pyth-network/pyth-client", tag = "oracle-v2.32.1", features = ["library"] } -pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", version = "1.13.6", rev = "e670f57f89b05398ca352e4accb1e32724a8e1b4" } +pyth-oracle = { git = "https://github.com/pyth-network/pyth-client", tag = "oracle-v2.33.0", features = ["library"] } +pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", version = "1.13.6", rev = "33f901aa45f4f0005aa5a84a1479b78ca9033074" } rand = "0.7.0" rayon = "1.5.3" regex = "1.5.6" diff --git a/runtime/src/bank/pyth_accumulator.rs b/runtime/src/bank/pyth_accumulator.rs index eb34783073..f915cc3f98 100644 --- a/runtime/src/bank/pyth_accumulator.rs +++ b/runtime/src/bank/pyth_accumulator.rs @@ -7,6 +7,7 @@ use { pythnet_sdk::{ accumulators::{merkle::MerkleAccumulator, Accumulator}, hashers::keccak256_160::Keccak160, + publisher_stake_caps::StakeCapParameters, wormhole::{AccumulatorSequenceTracker, MessageData, PostedMessageUnreliableData}, }, solana_measure::measure::Measure, @@ -44,6 +45,12 @@ lazy_static! { .parse() .unwrap(), ); + pub static ref STAKE_CAPS_PARAMETERS_ADDR: Pubkey = env_pubkey_or( + "STAKE_CAPS_PARAMETERS_ADDR", + "879ZVNagiWaAKsWDjGVf8pLq1wUBeBz7sREjUh3hrU36" + .parse() + .unwrap(), + ); } /// Accumulator specific error type. It would be nice to use `transaction::Error` but it does @@ -121,6 +128,10 @@ pub fn get_accumulator_keys() -> Vec<( ("ACCUMULATOR_SEQUENCE_ADDR", Ok(*ACCUMULATOR_SEQUENCE_ADDR)), ("WORMHOLE_PID", Ok(*WORMHOLE_PID)), ("ORACLE_PID", Ok(*ORACLE_PID)), + ( + "STAKE_CAPS_PARAMETERS_ADDR", + Ok(*STAKE_CAPS_PARAMETERS_ADDR), + ), ] } @@ -408,11 +419,16 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV measure.as_us() ); - let mut measure = Measure::start("update_v2_aggregate_price"); - let mut any_v1_aggregations = false; let mut v2_messages = Vec::new(); + if let Some(publisher_stake_caps_message) = compute_publisher_stake_caps(bank, &accounts) { + info!("PublisherStakeCaps: Adding publisher stake caps to the accumulator"); + v2_messages.push(publisher_stake_caps_message); + } + + let mut measure = Measure::start("update_v2_aggregate_price"); + for (pubkey, mut account) in accounts { let mut price_account_data = account.data().to_owned(); @@ -446,3 +462,42 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV update_v1(bank, &v2_messages, any_v1_aggregations) } + +pub fn compute_publisher_stake_caps( + bank: &Bank, + accounts: &[(Pubkey, AccountSharedData)], +) -> Option> { + let mut measure = Measure::start("compute_publisher_stake_caps"); + + let parameters: StakeCapParameters = { + let data = bank + .get_account_with_fixed_root(&STAKE_CAPS_PARAMETERS_ADDR) + .unwrap_or_default(); + let data = data.data(); + solana_sdk::borsh::try_from_slice_unchecked(data).unwrap_or_default() + }; + + let message = pyth_oracle::validator::compute_publisher_stake_caps( + accounts.iter().map(|(_, account)| account.data()), + bank.clock().unix_timestamp, + parameters.m, + parameters.z, + ); + + measure.stop(); + debug!( + "PublisherStakeCaps: Computed publisher stake caps with m : {} and z : {} in {} us", + parameters.m, + parameters.z, + measure.as_us() + ); + + if bank + .feature_set + .is_active(&feature_set::add_publisher_stake_caps_to_the_accumulator::id()) + { + Some(message) + } else { + None + } +} diff --git a/runtime/src/bank/pyth_accumulator_tests.rs b/runtime/src/bank/pyth_accumulator_tests.rs index e8736af9ec..68a1bfe0b9 100644 --- a/runtime/src/bank/pyth_accumulator_tests.rs +++ b/runtime/src/bank/pyth_accumulator_tests.rs @@ -6,7 +6,9 @@ use { AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude, }, bank::{ - pyth_accumulator::{get_accumulator_keys, ACCUMULATOR_RING_SIZE, ORACLE_PID}, + pyth_accumulator::{ + get_accumulator_keys, ACCUMULATOR_RING_SIZE, ORACLE_PID, STAKE_CAPS_PARAMETERS_ADDR, + }, Bank, }, genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}, @@ -20,6 +22,7 @@ use { pythnet_sdk::{ accumulators::{merkle::MerkleAccumulator, Accumulator}, hashers::{keccak256_160::Keccak160, Hasher}, + publisher_stake_caps::StakeCapParameters, wormhole::{AccumulatorSequenceTracker, MessageData, PostedMessageUnreliableData}, ACCUMULATOR_EMITTER_ADDRESS, }, @@ -105,6 +108,14 @@ fn get_accumulator_state(bank: &Bank, ring_index: u32) -> Vec { account.data().to_vec() } +fn activate_feature(bank: &mut Bank, feature_id: &Pubkey) { + let feature = Feature { + activated_at: Some(30), + }; + bank.store_account(feature_id, &feature::create_account(&feature, 42)); + bank.compute_active_feature_set(true); +} + #[test] fn test_update_accumulator_sysvar() { let leader_pubkey = solana_sdk::pubkey::new_rand(); @@ -123,6 +134,10 @@ fn test_update_accumulator_sysvar() { .accounts .remove(&feature_set::move_accumulator_to_end_of_block::id()) .unwrap(); + genesis_config + .accounts + .remove(&feature_set::add_publisher_stake_caps_to_the_accumulator::id()) + .unwrap(); // 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. @@ -196,16 +211,10 @@ fn test_update_accumulator_sysvar() { assert_eq!(wormhole_message_account.data().len(), 0); // Enable Accumulator Feature (42 = random lamport balance, and the meaning of the universe). - let feature_id = feature_set::enable_accumulator_sysvar::id(); - let feature = Feature { - activated_at: Some(30), - }; - bank.store_account(&feature_id, &feature::create_account(&feature, 42)); - bank.compute_active_feature_set(true); + activate_feature(&mut bank, &feature_set::enable_accumulator_sysvar::id()); for _ in 0..slots_in_epoch { bank = new_from_parent(&Arc::new(bank)); } - // Feature should now be enabled on the new bank as the epoch has changed. assert!(bank .feature_set @@ -408,6 +417,10 @@ fn test_update_accumulator_end_of_block() { .accounts .remove(&feature_set::move_accumulator_to_end_of_block::id()) .unwrap(); + genesis_config + .accounts + .remove(&feature_set::add_publisher_stake_caps_to_the_accumulator::id()) + .unwrap(); // 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. @@ -485,19 +498,11 @@ fn test_update_accumulator_end_of_block() { assert_eq!(wormhole_message_account.data().len(), 0); // Enable Accumulator Features (42 = random lamport balance, and the meaning of the universe). - let feature_id = feature_set::enable_accumulator_sysvar::id(); - let feature = Feature { - activated_at: Some(30), - }; - bank.store_account(&feature_id, &feature::create_account(&feature, 42)); - - let feature_id = feature_set::move_accumulator_to_end_of_block::id(); - let feature = Feature { - activated_at: Some(30), - }; - bank.store_account(&feature_id, &feature::create_account(&feature, 42)); - - bank.compute_active_feature_set(true); + activate_feature(&mut bank, &feature_set::enable_accumulator_sysvar::id()); + activate_feature( + &mut bank, + &feature_set::move_accumulator_to_end_of_block::id(), + ); for _ in 0..slots_in_epoch { bank = new_from_parent(&Arc::new(bank)); } @@ -679,136 +684,84 @@ fn test_update_accumulator_end_of_block() { ); } -#[test] -fn test_accumulator_v2_all_v2() { - test_accumulator_v2([false, false, false, false]); -} - -#[test] -fn test_accumulator_v2_all_v1() { - test_accumulator_v2([true, true, true, true]); -} - -#[test] -fn test_accumulator_v2_mixed() { - test_accumulator_v2([true, true, false, false]); -} - -fn test_accumulator_v2(generate_buffers: [bool; 4]) { - let leader_pubkey = solana_sdk::pubkey::new_rand(); - let GenesisConfigInfo { - mut genesis_config, .. - } = create_genesis_config_with_leader(5, &leader_pubkey, 3); +fn generate_price( + bank: &Bank, + seeds: &[u8], + generate_buffers: bool, + publishers: &[Pubkey], +) -> (Pubkey, Vec>) { + let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PID); + let mut price_feed_account = AccountSharedData::new(42, size_of::(), &ORACLE_PID); - // 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 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 generate_price = |seeds, generate_buffers: bool| { - 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(); + if !generate_buffers { + price_account.flags.insert( + PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED, ); - - let mut price_account = PriceAccount::initialize(&price_feed_info, 0).unwrap(); - if !generate_buffers { - price_account.flags.insert( - PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED, - ); - } - - vec![ - price_account - .as_price_feed_message(&price_feed_key.to_bytes().into()) - .to_bytes(), - price_account - .as_twap_message(&price_feed_key.to_bytes().into()) - .to_bytes(), - ] - }; - - bank.store_account(&price_feed_key, &price_feed_account); - - if generate_buffers { - let message_buffer_bytes = create_message_buffer_bytes(messages.clone()); - - let mut seed = vec![1; 32]; - seed[..seeds.len()].copy_from_slice(seeds); - // Create a Message account. - let price_message_key = keypair_from_seed(&seed).unwrap(); - let mut price_message_account = bank - .get_account(&price_message_key.pubkey()) - .unwrap_or_default(); - - price_message_account.set_lamports(1_000_000_000); - price_message_account - .set_owner(Pubkey::new_from_array(pythnet_sdk::MESSAGE_BUFFER_PID)); - price_message_account.set_data(message_buffer_bytes); - - // Store Message account so the accumulator sysvar updater can find it. - bank.store_account(&price_message_key.pubkey(), &price_message_account); + } + price_account.num_ = publishers.len() as u32; + for (i, publisher) in publishers.iter().enumerate() { + price_account.comp_[i].pub_ = publisher.to_bytes().into(); } - (price_feed_key, messages) + vec![ + price_account + .as_price_feed_message(&price_feed_key.to_bytes().into()) + .to_bytes(), + price_account + .as_twap_message(&price_feed_key.to_bytes().into()) + .to_bytes(), + ] }; - 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())); + bank.store_account(&price_feed_key, &price_feed_account); - let prices_with_messages = [ - generate_price(b"seeds_1", generate_buffers[0]), - generate_price(b"seeds_2", generate_buffers[1]), - generate_price(b"seeds_3", generate_buffers[2]), - generate_price(b"seeds_4", generate_buffers[3]), - ]; + if generate_buffers { + let message_buffer_bytes = create_message_buffer_bytes(messages.clone()); - bank = new_from_parent(&Arc::new(bank)); // Advance slot 1. - bank = new_from_parent(&Arc::new(bank)); // Advance slot 2. + let mut seed = vec![1; 32]; + seed[..seeds.len()].copy_from_slice(seeds); + // Create a Message account. + let price_message_key = keypair_from_seed(&seed).unwrap(); + let mut price_message_account = bank + .get_account(&price_message_key.pubkey()) + .unwrap_or_default(); - let messages = prices_with_messages - .iter() - .flat_map(|(_, messages)| messages) - .map(|message| &message[..]) - .sorted_unstable() - .dedup() - .collect::>(); - assert_eq!(messages.len(), 8); + price_message_account.set_lamports(1_000_000_000); + price_message_account.set_owner(Pubkey::new_from_array(pythnet_sdk::MESSAGE_BUFFER_PID)); + price_message_account.set_data(message_buffer_bytes); - // Trigger Aggregation. We freeze instead of new_from_parent so - // we can keep access to the bank. - let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank); - bank.freeze(); + // Store Message account so the accumulator sysvar updater can find it. + bank.store_account(&price_message_key.pubkey(), &price_message_account); + } + (price_feed_key, messages) +} + +fn check_accumulator_state_matches_messages( + bank: &Bank, + sequence_tracker_before_bank_freeze: &AccumulatorSequenceTracker, + messages: &[&[u8]], +) { // Get the wormhole message generated by freezed. We don't need // to offset the ring index as our test is always below 10K slots. - let wormhole_message_account = get_wormhole_message_account(&bank, bank.slot() as u32); + let wormhole_message_account = get_wormhole_message_account(bank, bank.slot() as u32); assert_ne!(wormhole_message_account.data().len(), 0); let deserialized_wormhole_message = PostedMessageUnreliableData::deserialize(&mut wormhole_message_account.data()).unwrap(); @@ -852,7 +805,7 @@ fn test_accumulator_v2(generate_buffers: [bool; 4]) { } // Verify accumulator state account. - let accumulator_state = get_accumulator_state(&bank, bank.slot() as u32); + let accumulator_state = get_accumulator_state(bank, bank.slot() as u32); let acc_state_magic = &accumulator_state[..4]; let acc_state_slot = LittleEndian::read_u64(&accumulator_state[4..12]); let acc_state_ring_size = LittleEndian::read_u32(&accumulator_state[12..16]); @@ -879,10 +832,301 @@ fn test_accumulator_v2(generate_buffers: [bool; 4]) { // Verify sequence_tracker increments for wormhole to accept it. assert_eq!( - get_acc_sequence_tracker(&bank).sequence, + get_acc_sequence_tracker(bank).sequence, sequence_tracker_before_bank_freeze.sequence + 1 ); } + +#[test] +fn test_accumulator_v2_all_v2() { + test_accumulator_v2([false, false, false, false]); +} + +#[test] +fn test_accumulator_v2_all_v1() { + test_accumulator_v2([true, true, true, true]); +} + +#[test] +fn test_accumulator_v2_mixed() { + test_accumulator_v2([true, true, false, false]); +} + +fn test_accumulator_v2(generate_buffers: [bool; 4]) { + let leader_pubkey = solana_sdk::pubkey::new_rand(); + let GenesisConfigInfo { + mut genesis_config, .. + } = create_genesis_config_with_leader(5, &leader_pubkey, 3); + + genesis_config + .accounts + .remove(&feature_set::add_publisher_stake_caps_to_the_accumulator::id()) + .unwrap(); + + // 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); + + 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(&bank, b"seeds_1", generate_buffers[0], &[]), + generate_price(&bank, b"seeds_2", generate_buffers[1], &[]), + generate_price(&bank, b"seeds_3", generate_buffers[2], &[]), + generate_price(&bank, b"seeds_4", generate_buffers[3], &[]), + ]; + + bank = new_from_parent(&Arc::new(bank)); // Advance slot 1. + bank = new_from_parent(&Arc::new(bank)); // Advance slot 2. + + let messages = prices_with_messages + .iter() + .flat_map(|(_, messages)| messages) + .map(|message| &message[..]) + .sorted_unstable() + .dedup() + .collect::>(); + assert_eq!(messages.len(), 8); + + // Trigger Aggregation. We freeze instead of new_from_parent so + // we can keep access to the bank. + let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank); + bank.freeze(); + + check_accumulator_state_matches_messages( + &bank, + &sequence_tracker_before_bank_freeze, + &messages, + ); +} + +#[test] +fn test_publisher_stake_caps() { + let leader_pubkey = solana_sdk::pubkey::new_rand(); + let GenesisConfigInfo { + mut genesis_config, .. + } = create_genesis_config_with_leader(5, &leader_pubkey, 3); + + genesis_config + .accounts + .remove(&feature_set::add_publisher_stake_caps_to_the_accumulator::id()) + .unwrap(); + + // 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); + + 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())); + + // We will set the stake cap parameters to this later + let new_m = 1_000_000_000_000; + let new_z = 3; + // This is an array of tuples where each tuple contains the pubkey of the publisher, the expected cap with default values and the expected cap with the new values. + let mut publishers_with_expected_caps: [(Pubkey, u64, u64); 4] = [ + ( + solana_sdk::pubkey::new_rand(), + StakeCapParameters::default().m * 5 / 4, + new_m / 3 + new_m / 4, + ), + ( + solana_sdk::pubkey::new_rand(), + StakeCapParameters::default().m * 3 / 4, + new_m / 3 + new_m / 4, + ), + ( + solana_sdk::pubkey::new_rand(), + StakeCapParameters::default().m * 3 / 4, + new_m / 3 + new_m / 4, + ), + ( + solana_sdk::pubkey::new_rand(), + StakeCapParameters::default().m * 5 / 4, + new_m / 3 + new_m / 4, + ), + ]; + + let prices_with_messages = [ + generate_price( + &bank, + b"seeds_1", + false, + &[publishers_with_expected_caps[0].0], + ), + generate_price( + &bank, + b"seeds_2", + false, + &[ + publishers_with_expected_caps[1].0, + publishers_with_expected_caps[2].0, + ], + ), + generate_price( + &bank, + b"seeds_3", + true, + &[publishers_with_expected_caps[3].0], + ), + generate_price( + &bank, + b"seeds_4", + true, + &[ + publishers_with_expected_caps[3].0, + publishers_with_expected_caps[1].0, + publishers_with_expected_caps[0].0, + publishers_with_expected_caps[2].0, + ], + ), + generate_price(&bank, b"seeds_5", false, &[]), + + + ]; + + // Publishers are sorted in the publisher stake caps message so we sort them here too + publishers_with_expected_caps.sort_by_key(|(pk, _, _)| *pk); + + bank = new_from_parent(&Arc::new(bank)); // Advance slot 1. + bank = new_from_parent(&Arc::new(bank)); // Advance slot 2. + + let mut messages = prices_with_messages + .iter() + .flat_map(|(_, messages)| messages) + .map(|message| &message[..]) + .sorted_unstable() + .dedup() + .collect::>(); + assert_eq!(messages.len(), 10); + + // We turn on the feature to add publisher stake caps to the accumulator but it won't be active until next epoch + activate_feature( + &mut bank, + &feature_set::add_publisher_stake_caps_to_the_accumulator::id(), + ); + + // Trigger accumulation. We freeze instead of new_from_parent so + // we can keep access to the bank. + let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank); + bank.freeze(); + check_accumulator_state_matches_messages( + &bank, + &sequence_tracker_before_bank_freeze, + &messages, + ); + + for _ in 0..slots_in_epoch { + bank = new_from_parent(&Arc::new(bank)); + } + + let publisher_caps_message = { + let mut result = vec![2]; + result.extend_from_slice(&bank.clock().unix_timestamp.to_be_bytes()); + result.extend_from_slice(&4u16.to_be_bytes()); + for (pk, m, _) in publishers_with_expected_caps { + result.extend_from_slice(&pk.to_bytes()); + result.extend_from_slice(&m.to_be_bytes()); + } + result + }; + + // Now the messages contain the publisher caps message + messages.push(&publisher_caps_message); + assert_eq!(messages.len(), 11); + + let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank); + bank.freeze(); + check_accumulator_state_matches_messages( + &bank, + &sequence_tracker_before_bank_freeze, + &messages, + ); + + bank = new_from_parent(&Arc::new(bank)); + + // We add some badly formatted stake cap parameters + let mut stake_cap_parameters_account = + AccountSharedData::new(42, size_of::(), &ORACLE_PID); + stake_cap_parameters_account.set_data( + vec![1,2,3,4], + ); + bank.store_account(&STAKE_CAPS_PARAMETERS_ADDR, &stake_cap_parameters_account); + + // Nothing should change as the stake cap parameters are invalid + let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank); + bank.freeze(); + check_accumulator_state_matches_messages( + &bank, + &sequence_tracker_before_bank_freeze, + &messages, + ); + bank = new_from_parent(&Arc::new(bank)); + + // Now we update the stake cap parameters + let mut stake_cap_parameters_account = + AccountSharedData::new(42, size_of::(), &ORACLE_PID); + stake_cap_parameters_account.set_data( + StakeCapParameters { + _discriminator: 0, + _current_authority: [0u8; 32], + m: new_m, + z: new_z, + } + .try_to_vec() + .unwrap(), + ); + bank.store_account(&STAKE_CAPS_PARAMETERS_ADDR, &stake_cap_parameters_account); + + let publisher_caps_message_with_new_parameters = { + let mut result = vec![2]; + result.extend_from_slice(&bank.clock().unix_timestamp.to_be_bytes()); + result.extend_from_slice(&4u16.to_be_bytes()); + for (pk, _, m) in publishers_with_expected_caps { + result.extend_from_slice(&pk.to_bytes()); + result.extend_from_slice(&m.to_be_bytes()); + } + result + }; + + // Update the publisher caps message in the message arrays to match the new parameters + let last_element_index = messages.len() - 1; + messages[last_element_index] = &publisher_caps_message_with_new_parameters; + assert_eq!(messages.len(), 11); + + let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank); + bank.freeze(); + check_accumulator_state_matches_messages( + &bank, + &sequence_tracker_before_bank_freeze, + &messages, + ); +} + #[test] fn test_get_accumulator_keys() { use pythnet_sdk::{pythnet, ACCUMULATOR_EMITTER_ADDRESS, MESSAGE_BUFFER_PID}; @@ -896,6 +1140,7 @@ fn test_get_accumulator_keys() { Pubkey::new_from_array(pythnet::ACCUMULATOR_SEQUENCE_ADDR), Pubkey::new_from_array(pythnet::WORMHOLE_PID), *ORACLE_PID, + *STAKE_CAPS_PARAMETERS_ADDR, ]; assert_eq!(accumulator_keys, expected_pyth_keys); } diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 0677821d18..8fb6e8eef0 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -564,6 +564,10 @@ pub mod redo_move_accumulator_to_end_of_block { solana_sdk::declare_id!("skyhwRBbP1LoHzWy1QrwLWy3vo2uHkzVV1zpN9UsGuw"); } +pub mod add_publisher_stake_caps_to_the_accumulator { + solana_sdk::declare_id!("J5u6Vrgj7de8zLcjQVhuRAPzEzfDamrxAQMz3q6HSmi1"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -699,6 +703,7 @@ lazy_static! { (zero_wormhole_message_timestamps::id(), "use zeroed timestamps in wormhole messages"), (undo_move_accumulator_to_end_of_block::id(), "undo accumulator end of block change"), (redo_move_accumulator_to_end_of_block::id(), "redo accumulator end of block change"), + (add_publisher_stake_caps_to_the_accumulator::id(), "add publisher stake caps to the accumulator") /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/validator/src/main.rs b/validator/src/main.rs index 13d510e8d2..711c3b7806 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -3177,9 +3177,11 @@ fn process_account_indexes(matches: &ArgMatches) -> AccountSecondaryIndexes { }) .collect(); - assert!(account_indexes.contains(&AccountIndex::ProgramId), + 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"); + --account-index program-id\n" + ); let account_indexes_include_keys: HashSet = values_t!(matches, "account_index_include_key", Pubkey) @@ -3199,7 +3201,9 @@ fn process_account_indexes(matches: &ArgMatches) -> AccountSecondaryIndexes { 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) { + 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\ @@ -3210,7 +3214,9 @@ fn process_account_indexes(matches: &ArgMatches) -> AccountSecondaryIndexes { } if exclude_keys { - if account_indexes_exclude_keys.contains(&*ORACLE_PID) || account_indexes_exclude_keys.contains(&*MESSAGE_BUFFER_PID) { + 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."); } }