@@ -8,7 +8,8 @@ use crate::{
88use byteorder:: ByteOrder ;
99use byteorder:: { LittleEndian , ReadBytesExt } ;
1010use 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 } ;
1213use pyth_oracle:: { PriceAccount , PythAccount } ;
1314use 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]
673870fn test_get_accumulator_keys ( ) {
674871 use pythnet_sdk:: { pythnet, ACCUMULATOR_EMITTER_ADDRESS , MESSAGE_BUFFER_PID } ;
0 commit comments