Skip to content

Commit 4dc85c5

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

File tree

1 file changed

+289
-0
lines changed

1 file changed

+289
-0
lines changed

runtime/src/bank/pyth_accumulator_tests.rs

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,295 @@ fn test_update_accumulator_end_of_block() {
669669
);
670670
}
671671

672+
// This test will
673+
#[test]
674+
fn test_accumulator_v2() {
675+
let leader_pubkey = solana_sdk::pubkey::new_rand();
676+
let GenesisConfigInfo {
677+
mut genesis_config, ..
678+
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
679+
680+
// Set epoch length to 32 so we can advance epochs quickly. We also skip past slot 0 here
681+
// due to slot 0 having special handling.
682+
let slots_in_epoch = 32;
683+
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);
684+
let mut bank = Bank::new_for_tests(&genesis_config);
685+
bank = new_from_parent(&Arc::new(bank));
686+
bank = new_from_parent(&Arc::new(bank));
687+
688+
let generate_price = |seeds, generate_buffers| {
689+
let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PUBKEY);
690+
let mut price_feed_account =
691+
AccountSharedData::new(42, size_of::<PriceAccount>(), &ORACLE_PUBKEY);
692+
693+
let price_account = PriceAccount::initialize(
694+
&AccountInfo::new(
695+
&price_feed_key.to_bytes().into(),
696+
false,
697+
true,
698+
&mut 0,
699+
&mut price_feed_account.data_mut(),
700+
&ORACLE_PUBKEY.to_bytes().into(),
701+
false,
702+
Epoch::default(),
703+
),
704+
0,
705+
)
706+
.unwrap();
707+
708+
bank.store_account(&price_feed_key, &price_feed_account);
709+
710+
if generate_buffers {
711+
// Insert into message buffer in reverse order to test that accumulator
712+
// sorts first.
713+
let message_0 = vec![1u8; 127]; // Price Account
714+
let message_1 = vec![2u8; 127]; // Twap
715+
let messages = vec![message_1, message_0];
716+
let message_buffer_bytes = create_message_buffer_bytes(messages.clone());
717+
718+
// Create a Message account.
719+
let price_message_key = keypair_from_seed(&[1u8; 32]).unwrap();
720+
let mut price_message_account = bank
721+
.get_account(&price_message_key.pubkey())
722+
.unwrap_or_default();
723+
price_message_account.set_lamports(1_000_000_000);
724+
price_message_account
725+
.set_owner(Pubkey::new_from_array(pythnet_sdk::MESSAGE_BUFFER_PID));
726+
price_message_account.set_data(message_buffer_bytes);
727+
728+
// Store Message account so the accumulator sysvar updater can find it.
729+
bank.store_account(&price_message_key.pubkey(), &price_message_account);
730+
}
731+
};
732+
733+
// TODO: New test functionality here.
734+
// 1. Create Price Feed Accounts owned by ORACLE_PUBKEY
735+
// 2. Populate Price Feed Accounts
736+
// 3. Call update_v2()
737+
// - Cases:
738+
// - No V1 Messages, Only Price Accounts with no V2
739+
// - No V1 Messages, Some Price Accounts with no V2
740+
// - Some V1 Messages, No Price Accounts with no V2
741+
// - Some V1 Messages, Some Price Accounts with no V2
742+
// - Simulate PriceUpdate that WOULD trigger a real V1 aggregate before End of Slot
743+
// - Simulate PriceUpdate that doesn't trigger a real V1 aggregate, only V2.
744+
745+
// Case 1:
746+
{
747+
let price_pubkeys = [
748+
generate_price(&["seeds_1"], false),
749+
generate_price(&["seeds_2"], false),
750+
generate_price(&["seeds_3"], false),
751+
generate_price(&["seeds_4"], false),
752+
];
753+
}
754+
755+
// Derive the Wormhole Message Account that will be generated by the sysvar updater.
756+
let (wormhole_message_pubkey, _bump) = Pubkey::find_program_address(
757+
&[b"AccumulatorMessage", &(bank.slot() as u32).to_be_bytes()],
758+
&Pubkey::new_from_array(pythnet_sdk::pythnet::WORMHOLE_PID),
759+
);
760+
761+
// Account Data should be empty at this point. Check account data is [].
762+
let wormhole_message_account = bank
763+
.get_account(&wormhole_message_pubkey)
764+
.unwrap_or_default();
765+
assert_eq!(wormhole_message_account.data().len(), 0);
766+
767+
// Run accumulator by creating a new bank from parent, the feature is
768+
// disabled so account data should still be empty. Check account data is
769+
// still [].
770+
bank = new_from_parent(&Arc::new(bank));
771+
772+
assert_eq!(
773+
bank.feature_set
774+
.is_active(&feature_set::enable_accumulator_sysvar::id()),
775+
false
776+
);
777+
assert_eq!(
778+
bank.feature_set
779+
.is_active(&feature_set::move_accumulator_to_end_of_block::id()),
780+
false
781+
);
782+
783+
let wormhole_message_account = bank
784+
.get_account(&wormhole_message_pubkey)
785+
.unwrap_or_default();
786+
assert_eq!(wormhole_message_account.data().len(), 0);
787+
788+
bank.compute_active_feature_set(true);
789+
for _ in 0..slots_in_epoch {
790+
bank = new_from_parent(&Arc::new(bank));
791+
}
792+
793+
// The current sequence value will be used in the message when the bank advances, so we snapshot
794+
// it here before freezing the bank so we can assert the correct sequence is present in the message.
795+
let sequence_tracker_before_bank_freeze = get_acc_sequence_tracker(&bank);
796+
// Freeze the bank to make sure accumulator is updated
797+
bank.freeze();
798+
799+
// get the timestamp & slot for the message
800+
let ring_index = (bank.slot() % ACCUMULATOR_RING_SIZE as u64) as u32;
801+
let wormhole_message_account = get_wormhole_message_account(&bank, ring_index);
802+
803+
assert_ne!(wormhole_message_account.data().len(), 0);
804+
805+
let wormhole_message =
806+
PostedMessageUnreliableData::deserialize(&mut wormhole_message_account.data()).unwrap();
807+
808+
let messages = messages.iter().map(|m| m.as_slice()).collect::<Vec<_>>();
809+
let accumulator_elements = messages.clone().into_iter().sorted_unstable().dedup();
810+
let expected_accumulator =
811+
MerkleAccumulator::<Keccak160>::from_set(accumulator_elements).unwrap();
812+
let expected_wormhole_message_payload =
813+
expected_accumulator.serialize(bank.slot(), ACCUMULATOR_RING_SIZE);
814+
assert_eq!(
815+
wormhole_message.message.payload,
816+
expected_wormhole_message_payload
817+
);
818+
819+
let expected_wormhole_message = PostedMessageUnreliableData {
820+
message: MessageData {
821+
vaa_version: 1,
822+
consistency_level: 1,
823+
submission_time: bank.clock().unix_timestamp as u32,
824+
sequence: sequence_tracker_before_bank_freeze.sequence, // sequence is incremented after the message is processed
825+
emitter_chain: 26,
826+
emitter_address: ACCUMULATOR_EMITTER_ADDRESS,
827+
payload: expected_wormhole_message_payload,
828+
..Default::default()
829+
},
830+
};
831+
832+
assert_eq!(
833+
wormhole_message_account.data().to_vec(),
834+
expected_wormhole_message.try_to_vec().unwrap()
835+
);
836+
837+
// verify hashes verify in accumulator
838+
for msg in messages {
839+
let msg_hash = Keccak160::hashv(&[[0u8].as_ref(), msg]);
840+
let msg_proof = expected_accumulator.prove(msg).unwrap();
841+
842+
assert!(expected_accumulator.nodes.contains(&msg_hash));
843+
assert!(expected_accumulator.check(msg_proof, msg));
844+
}
845+
846+
// verify accumulator state account
847+
let accumulator_state = get_accumulator_state(&bank, ring_index);
848+
let acc_state_magic = &accumulator_state[..4];
849+
let acc_state_slot = LittleEndian::read_u64(&accumulator_state[4..12]);
850+
let acc_state_ring_size = LittleEndian::read_u32(&accumulator_state[12..16]);
851+
852+
assert_eq!(acc_state_magic, b"PAS1");
853+
assert_eq!(acc_state_slot, bank.slot());
854+
assert_eq!(acc_state_ring_size, ACCUMULATOR_RING_SIZE);
855+
856+
let mut cursor = std::io::Cursor::new(&accumulator_state[16..]);
857+
let num_elems = cursor.read_u32::<LittleEndian>().unwrap();
858+
for _ in 0..(num_elems as usize) {
859+
let element_len = cursor.read_u32::<LittleEndian>().unwrap();
860+
let mut element_data = vec![0u8; element_len as usize];
861+
cursor.read_exact(&mut element_data).unwrap();
862+
863+
let elem_hash = Keccak160::hashv(&[[0u8].as_ref(), element_data.as_slice()]);
864+
let elem_proof = expected_accumulator.prove(element_data.as_slice()).unwrap();
865+
866+
assert!(expected_accumulator.nodes.contains(&elem_hash));
867+
assert!(expected_accumulator.check(elem_proof, element_data.as_slice()));
868+
}
869+
870+
// verify sequence_tracker increments
871+
assert_eq!(
872+
get_acc_sequence_tracker(&bank).sequence,
873+
sequence_tracker_before_bank_freeze.sequence + 1
874+
);
875+
876+
// verify ring buffer cycles
877+
let ring_index_before_buffer_cycle = (bank.slot() % ACCUMULATOR_RING_SIZE as u64) as u32;
878+
let target_slot = bank.slot() + ACCUMULATOR_RING_SIZE as u64;
879+
// advance ACCUMULATOR_RING_SIZE slots using warp_from_parent since doing large loops
880+
// with new_from_parent takes a long time. warp_from_parent results in a bank that is frozen.
881+
bank = Bank::warp_from_parent(&Arc::new(bank), &Pubkey::default(), target_slot);
882+
883+
// accumulator messages should still be the same before looping around
884+
let ring_index_after_buffer_cycle = (bank.slot() % ACCUMULATOR_RING_SIZE as u64) as u32;
885+
assert_eq!(
886+
ring_index_before_buffer_cycle,
887+
ring_index_after_buffer_cycle
888+
);
889+
890+
let accumulator_state_after_skip = get_accumulator_state(&bank, ring_index_after_buffer_cycle);
891+
assert_eq!(
892+
&accumulator_state[16..],
893+
&accumulator_state_after_skip[16..]
894+
);
895+
896+
// insert new message to make sure the update is written in the right position
897+
// in the ring buffer and overwrites the existing message
898+
899+
// advance the bank to unfreeze it (to be able to store accounts). see the comment on warp_from_parent above.
900+
bank = new_from_parent(&Arc::new(bank));
901+
902+
let wh_sequence_before_acc_update = get_acc_sequence_tracker(&bank).sequence;
903+
904+
let message_0 = vec![1u8; 127];
905+
let message_1 = vec![2u8; 127];
906+
let message_2 = vec![3u8; 254];
907+
908+
let updated_messages = vec![message_1.clone(), message_2.clone(), message_0.clone()];
909+
910+
let updated_message_buffer_bytes = create_message_buffer_bytes(updated_messages.clone());
911+
price_message_account.set_data(updated_message_buffer_bytes);
912+
913+
// Store Message account so the accumulator sysvar updater can find it.
914+
bank.store_account(&price_message_key.pubkey(), &price_message_account);
915+
916+
// Freeze the bank to run accumulator
917+
bank.freeze();
918+
919+
let ring_index = (bank.slot() % ACCUMULATOR_RING_SIZE as u64) as u32;
920+
let updated_wormhole_message_account = get_wormhole_message_account(&bank, ring_index);
921+
922+
let updated_wormhole_message =
923+
PostedMessageUnreliableData::deserialize(&mut updated_wormhole_message_account.data())
924+
.unwrap();
925+
926+
let updated_messages = updated_messages
927+
.iter()
928+
.map(|m| m.as_slice())
929+
.collect::<Vec<_>>();
930+
let updated_accumulator_elements = updated_messages
931+
.clone()
932+
.into_iter()
933+
.sorted_unstable()
934+
.dedup();
935+
936+
let expected_accumulator =
937+
MerkleAccumulator::<Keccak160>::from_set(updated_accumulator_elements).unwrap();
938+
assert_eq!(
939+
updated_wormhole_message.message.payload,
940+
expected_accumulator.serialize(bank.slot(), ACCUMULATOR_RING_SIZE)
941+
);
942+
943+
let expected_wormhole_message = PostedMessageUnreliableData {
944+
message: MessageData {
945+
vaa_version: 1,
946+
consistency_level: 1,
947+
submission_time: bank.clock().unix_timestamp as u32,
948+
sequence: wh_sequence_before_acc_update,
949+
emitter_chain: 26,
950+
emitter_address: ACCUMULATOR_EMITTER_ADDRESS,
951+
payload: expected_accumulator.serialize(bank.slot(), ACCUMULATOR_RING_SIZE),
952+
..Default::default()
953+
},
954+
};
955+
956+
assert_eq!(
957+
updated_wormhole_message_account.data(),
958+
expected_wormhole_message.try_to_vec().unwrap()
959+
);
960+
}
672961
#[test]
673962
fn test_get_accumulator_keys() {
674963
use pythnet_sdk::{pythnet, ACCUMULATOR_EMITTER_ADDRESS, MESSAGE_BUFFER_PID};

0 commit comments

Comments
 (0)