Skip to content

Commit f3deb0f

Browse files
committed
test: add initial v2 tests to validator
1 parent da4eb87 commit f3deb0f

File tree

3 files changed

+200
-4
lines changed

3 files changed

+200
-4
lines changed

Cargo.lock

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

runtime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ num-traits = { version = "0.2" }
3535
num_cpus = "1.13.1"
3636
once_cell = "1.12.0"
3737
ouroboros = "0.15.0"
38-
pyth-oracle = { git = "https://github.com/pyth-network/pyth-client", branch = "accumulator-v2", features = ["library"] }
38+
pyth-oracle = { path = "/code/pyth-network/pyth-client/program/rust", features = ["library"] }
3939
pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", version = "1.13.6", rev = "e670f57f89b05398ca352e4accb1e32724a8e1b4" }
4040
rand = "0.7.0"
4141
rayon = "1.5.3"

runtime/src/bank/pyth_accumulator_tests.rs

Lines changed: 199 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use crate::{
88
use byteorder::ByteOrder;
99
use byteorder::{LittleEndian, ReadBytesExt};
1010
use itertools::Itertools;
11-
use pyth_oracle::solana_program::account_info::AccountInfo;
11+
use pyth_oracle::PythOracleSerialize;
12+
use pyth_oracle::{solana_program::account_info::AccountInfo, PriceAccountFlags};
1213
use pyth_oracle::{PriceAccount, PythAccount};
1314
use pythnet_sdk::{
1415
accumulators::{merkle::MerkleAccumulator, Accumulator},
@@ -131,7 +132,7 @@ fn test_update_accumulator_sysvar() {
131132
let (price_feed_key, _bump) = Pubkey::find_program_address(&[b"123"], &ORACLE_PUBKEY);
132133
let mut price_feed_account =
133134
AccountSharedData::new(42, size_of::<PriceAccount>(), &ORACLE_PUBKEY);
134-
PriceAccount::initialize(
135+
let _ = PriceAccount::initialize(
135136
&AccountInfo::new(
136137
&price_feed_key.to_bytes().into(),
137138
false,
@@ -669,6 +670,202 @@ fn test_update_accumulator_end_of_block() {
669670
);
670671
}
671672

673+
// This test will
674+
#[test]
675+
fn test_accumulator_v2() {
676+
let leader_pubkey = solana_sdk::pubkey::new_rand();
677+
let GenesisConfigInfo {
678+
mut genesis_config, ..
679+
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
680+
681+
// Set epoch length to 32 so we can advance epochs quickly. We also skip past slot 0 here
682+
// due to slot 0 having special handling.
683+
let slots_in_epoch = 32;
684+
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);
685+
let mut bank = Bank::new_for_tests(&genesis_config);
686+
687+
bank = new_from_parent(&Arc::new(bank)); // Advance slot 1.
688+
bank = new_from_parent(&Arc::new(bank)); // Advance slot 2.
689+
690+
let generate_price = |seeds, generate_buffers: bool| {
691+
let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PUBKEY);
692+
let mut price_feed_account =
693+
AccountSharedData::new(42, size_of::<PriceAccount>(), &ORACLE_PUBKEY);
694+
695+
let messages = {
696+
let price_feed_info_key = &price_feed_key.to_bytes().into();
697+
let price_feed_info_lamports = &mut 0;
698+
let price_feed_info_owner = &ORACLE_PUBKEY.to_bytes().into();
699+
let price_feed_info_data = price_feed_account.data_mut();
700+
let price_feed_info = AccountInfo::new(
701+
price_feed_info_key,
702+
false,
703+
true,
704+
price_feed_info_lamports,
705+
price_feed_info_data,
706+
price_feed_info_owner,
707+
false,
708+
Epoch::default(),
709+
);
710+
711+
let mut price_account = PriceAccount::initialize(&price_feed_info, 0).unwrap();
712+
if !generate_buffers {
713+
price_account.flags.insert(
714+
PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED,
715+
);
716+
}
717+
718+
vec![
719+
price_account
720+
.as_price_feed_message(&price_feed_key.to_bytes().into())
721+
.to_bytes(),
722+
price_account
723+
.as_twap_message(&price_feed_key.to_bytes().into())
724+
.to_bytes(),
725+
]
726+
};
727+
728+
bank.store_account(&price_feed_key, &price_feed_account);
729+
730+
if generate_buffers {
731+
// Insert into message buffer in reverse order to test that accumulator
732+
// sorts first.
733+
let message_buffer_bytes = create_message_buffer_bytes(messages.clone());
734+
735+
// Create a Message account.
736+
let price_message_key = keypair_from_seed(&[1u8; 32]).unwrap();
737+
let mut price_message_account = bank
738+
.get_account(&price_message_key.pubkey())
739+
.unwrap_or_default();
740+
741+
price_message_account.set_lamports(1_000_000_000);
742+
price_message_account
743+
.set_owner(Pubkey::new_from_array(pythnet_sdk::MESSAGE_BUFFER_PID));
744+
price_message_account.set_data(message_buffer_bytes);
745+
746+
// Store Message account so the accumulator sysvar updater can find it.
747+
bank.store_account(&price_message_key.pubkey(), &price_message_account);
748+
}
749+
750+
(price_feed_key, messages)
751+
};
752+
753+
// TODO: New test functionality here.
754+
// 1. Create Price Feed Accounts owned by ORACLE_PUBKEY
755+
// 2. Populate Price Feed Accounts
756+
// 3. Call update_v2()
757+
// - Cases:
758+
// - No V1 Messages, Only Price Accounts with no V2
759+
// - No V1 Messages, Some Price Accounts with no V2
760+
// - Some V1 Messages, No Price Accounts with no V2
761+
// - Some V1 Messages, Some Price Accounts with no V2
762+
// - Simulate PriceUpdate that WOULD trigger a real V1 aggregate before End of Slot
763+
// - Simulate PriceUpdate that doesn't trigger a real V1 aggregate, only V2.
764+
765+
assert!(bank
766+
.feature_set
767+
.is_active(&feature_set::enable_accumulator_sysvar::id()));
768+
assert!(bank
769+
.feature_set
770+
.is_active(&feature_set::move_accumulator_to_end_of_block::id()));
771+
assert!(bank
772+
.feature_set
773+
.is_active(&feature_set::undo_move_accumulator_to_end_of_block::id()));
774+
assert!(bank
775+
.feature_set
776+
.is_active(&feature_set::redo_move_accumulator_to_end_of_block::id()));
777+
778+
let prices_with_messages = [
779+
generate_price(b"seeds_1", false),
780+
generate_price(b"seeds_2", false),
781+
generate_price(b"seeds_3", false),
782+
generate_price(b"seeds_4", false),
783+
];
784+
785+
let messages = prices_with_messages
786+
.iter()
787+
.map(|(_, messages)| messages)
788+
.flatten()
789+
.map(|message| &message[..]);
790+
791+
// Trigger Aggregation. We freeze instead of new_from_parent so
792+
// we can keep access to the bank.
793+
let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank);
794+
bank.freeze();
795+
796+
// Get the wormhole message generated by freezed. We don't need
797+
// to offset the ring index as our test is always below 10K slots.
798+
let wormhole_message_account = get_wormhole_message_account(&bank, bank.slot() as u32);
799+
assert_ne!(wormhole_message_account.data().len(), 0);
800+
PostedMessageUnreliableData::deserialize(&mut wormhole_message_account.data()).unwrap();
801+
802+
// Create MerkleAccumulator by hand to verify that the Wormhole message
803+
// contents are correctg.
804+
let expected_accumulator =
805+
MerkleAccumulator::<Keccak160>::from_set(messages.clone().sorted_unstable().dedup())
806+
.unwrap();
807+
808+
let expected_wormhole_message_payload =
809+
expected_accumulator.serialize(bank.slot(), ACCUMULATOR_RING_SIZE);
810+
811+
let expected_wormhole_message = PostedMessageUnreliableData {
812+
message: MessageData {
813+
vaa_version: 1,
814+
consistency_level: 1,
815+
submission_time: bank.clock().unix_timestamp as u32,
816+
sequence: sequence_tracker_before_bank_freeze.sequence, // sequence is incremented after the message is processed
817+
emitter_chain: 26,
818+
emitter_address: ACCUMULATOR_EMITTER_ADDRESS,
819+
payload: expected_wormhole_message_payload,
820+
..Default::default()
821+
},
822+
};
823+
824+
assert_eq!(
825+
wormhole_message_account.data().to_vec(),
826+
expected_wormhole_message.try_to_vec().unwrap()
827+
);
828+
829+
// Verify hashes in accumulator.
830+
for msg in messages {
831+
let msg_hash = Keccak160::hashv(&[[0u8].as_ref(), msg]);
832+
let msg_proof = expected_accumulator.prove(msg).unwrap();
833+
assert!(expected_accumulator.nodes.contains(&msg_hash));
834+
assert!(expected_accumulator.check(msg_proof, msg));
835+
}
836+
837+
// Verify accumulator state account.
838+
let accumulator_state = get_accumulator_state(&bank, bank.slot() as u32);
839+
let acc_state_magic = &accumulator_state[..4];
840+
let acc_state_slot = LittleEndian::read_u64(&accumulator_state[4..12]);
841+
let acc_state_ring_size = LittleEndian::read_u32(&accumulator_state[12..16]);
842+
843+
assert_eq!(acc_state_magic, b"PAS1");
844+
assert_eq!(acc_state_slot, bank.slot());
845+
assert_eq!(acc_state_ring_size, ACCUMULATOR_RING_SIZE);
846+
847+
// Verify the messages within the accumulator state account
848+
// were in the accumulator as well.
849+
let mut cursor = std::io::Cursor::new(&accumulator_state[16..]);
850+
let num_elems = cursor.read_u32::<LittleEndian>().unwrap();
851+
for _ in 0..(num_elems as usize) {
852+
let element_len = cursor.read_u32::<LittleEndian>().unwrap();
853+
let mut element_data = vec![0u8; element_len as usize];
854+
cursor.read_exact(&mut element_data).unwrap();
855+
856+
let elem_hash = Keccak160::hashv(&[[0u8].as_ref(), element_data.as_slice()]);
857+
let elem_proof = expected_accumulator.prove(element_data.as_slice()).unwrap();
858+
859+
assert!(expected_accumulator.nodes.contains(&elem_hash));
860+
assert!(expected_accumulator.check(elem_proof, element_data.as_slice()));
861+
}
862+
863+
// Verify sequence_tracker increments for wormhole to accept it.
864+
assert_eq!(
865+
get_acc_sequence_tracker(&bank).sequence,
866+
sequence_tracker_before_bank_freeze.sequence + 1
867+
);
868+
}
672869
#[test]
673870
fn test_get_accumulator_keys() {
674871
use pythnet_sdk::{pythnet, ACCUMULATOR_EMITTER_ADDRESS, MESSAGE_BUFFER_PID};

0 commit comments

Comments
 (0)