Skip to content

Commit 872d1b2

Browse files
committed
feat: batch publish
1 parent d873725 commit 872d1b2

File tree

7 files changed

+495
-15
lines changed

7 files changed

+495
-15
lines changed

Cargo.lock

Lines changed: 1 addition & 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", tag = "oracle-v2.32.1", features = ["library"] }
38+
pyth-oracle = { git = "https://github.com/pyth-network/pyth-client", branch = "batch-publish", 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.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ mod sysvar_cache;
199199
mod transaction_account_state_info;
200200

201201
pub mod pyth_accumulator;
202+
mod pyth_batch_publish;
202203

203204
#[cfg(test)]
204205
mod pyth_accumulator_tests;

runtime/src/bank/pyth_accumulator.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use {
2-
super::Bank,
2+
super::{pyth_batch_publish, Bank},
33
crate::accounts_index::{IndexKey, ScanConfig, ScanError},
44
byteorder::{LittleEndian, ReadBytesExt},
55
log::*,
@@ -16,7 +16,10 @@ use {
1616
hash::hashv,
1717
pubkey::Pubkey,
1818
},
19-
std::env::{self, VarError},
19+
std::{
20+
collections::HashMap,
21+
env::{self, VarError},
22+
},
2023
};
2124

2225
pub const ACCUMULATOR_RING_SIZE: u32 = 10_000;
@@ -44,6 +47,13 @@ lazy_static! {
4447
.parse()
4548
.unwrap(),
4649
);
50+
pub static ref BATCH_PUBLISH_PID: Pubkey = env_pubkey_or(
51+
"BATCH_PUBLISH_PID",
52+
// TODO: replace with real program id
53+
"FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epA"
54+
.parse()
55+
.unwrap(),
56+
);
4757
}
4858

4959
/// Accumulator specific error type. It would be nice to use `transaction::Error` but it does
@@ -121,6 +131,7 @@ pub fn get_accumulator_keys() -> Vec<(
121131
("ACCUMULATOR_SEQUENCE_ADDR", Ok(*ACCUMULATOR_SEQUENCE_ADDR)),
122132
("WORMHOLE_PID", Ok(*WORMHOLE_PID)),
123133
("ORACLE_PID", Ok(*ORACLE_PID)),
134+
("BATCH_PUBLISH_PID", Ok(*BATCH_PUBLISH_PID)),
124135
]
125136
}
126137

@@ -413,19 +424,33 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV
413424
let mut any_v1_aggregations = false;
414425
let mut v2_messages = Vec::new();
415426

427+
let new_prices = pyth_batch_publish::extract_batch_publish_prices(bank).unwrap_or_else(|err| {
428+
warn!("extract_batch_publish_prices failed: {}", err);
429+
HashMap::new()
430+
});
431+
416432
for (pubkey, mut account) in accounts {
417433
let mut price_account_data = account.data().to_owned();
434+
let price_account = if let Ok(data) =
435+
pyth_oracle::validator::validate_price_account(&mut price_account_data)
436+
{
437+
data
438+
} else {
439+
continue; // Not a price account.
440+
};
441+
442+
let mut need_save =
443+
pyth_batch_publish::apply_published_prices(price_account, &new_prices, bank.slot());
418444

419445
// Perform Accumulation
420446
match pyth_oracle::validator::aggregate_price(
421447
bank.slot(),
422448
bank.clock().unix_timestamp,
423449
&pubkey.to_bytes().into(),
424-
&mut price_account_data,
450+
price_account,
425451
) {
426452
Ok(messages) => {
427-
account.set_data(price_account_data);
428-
bank.store_account_and_update_capitalization(&pubkey, &account);
453+
need_save = true;
429454
v2_messages.extend(messages);
430455
}
431456
Err(err) => match err {
@@ -435,6 +460,10 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV
435460
}
436461
},
437462
}
463+
if need_save {
464+
account.set_data(price_account_data);
465+
bank.store_account_and_update_capitalization(&pubkey, &account);
466+
}
438467
}
439468

440469
measure.stop();

runtime/src/bank/pyth_accumulator_tests.rs

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ use {
66
AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude,
77
},
88
bank::{
9-
pyth_accumulator::{get_accumulator_keys, ACCUMULATOR_RING_SIZE, ORACLE_PID},
9+
pyth_accumulator::{
10+
get_accumulator_keys, ACCUMULATOR_RING_SIZE, BATCH_PUBLISH_PID, ORACLE_PID,
11+
},
12+
pyth_batch_publish::publisher_prices_account::{self, PublisherPrice},
1013
Bank,
1114
},
1215
genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo},
1316
},
17+
bytemuck::{cast_slice, checked::from_bytes},
1418
byteorder::{ByteOrder, LittleEndian, ReadBytesExt},
1519
itertools::Itertools,
1620
pyth_oracle::{
@@ -45,7 +49,9 @@ fn create_new_bank_for_tests_with_index(genesis_config: &GenesisConfig) -> Bank
4549
AccountSecondaryIndexes {
4650
keys: Some(AccountSecondaryIndexesIncludeExclude {
4751
exclude: false,
48-
keys: [*ORACLE_PID, *MESSAGE_BUFFER_PID].into_iter().collect(),
52+
keys: [*ORACLE_PID, *MESSAGE_BUFFER_PID, *BATCH_PUBLISH_PID]
53+
.into_iter()
54+
.collect(),
4955
}),
5056
indexes: [AccountIndex::ProgramId].into_iter().collect(),
5157
},
@@ -899,3 +905,128 @@ fn test_get_accumulator_keys() {
899905
];
900906
assert_eq!(accumulator_keys, expected_pyth_keys);
901907
}
908+
909+
#[test]
910+
fn test_batch_publish() {
911+
let leader_pubkey = solana_sdk::pubkey::new_rand();
912+
let GenesisConfigInfo {
913+
mut genesis_config, ..
914+
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
915+
916+
// Set epoch length to 32 so we can advance epochs quickly. We also skip past slot 0 here
917+
// due to slot 0 having special handling.
918+
let slots_in_epoch = 32;
919+
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);
920+
let mut bank = create_new_bank_for_tests_with_index(&genesis_config);
921+
922+
let generate_publisher = |seed, new_prices| {
923+
let publisher1_key = keypair_from_seed(seed).unwrap();
924+
925+
let (publisher1_prices_key, _bump) = Pubkey::find_program_address(
926+
// TODO: real seed
927+
&[
928+
b"PUBLISHER_PRICES_ACCOUNT",
929+
&publisher1_key.pubkey().to_bytes(),
930+
],
931+
&BATCH_PUBLISH_PID,
932+
);
933+
let mut publisher1_prices_account =
934+
AccountSharedData::new(42, publisher_prices_account::size(100), &BATCH_PUBLISH_PID);
935+
{
936+
let (header, prices) = publisher_prices_account::create(
937+
publisher1_prices_account.data_mut(),
938+
publisher1_key.pubkey().to_bytes(),
939+
)
940+
.unwrap();
941+
publisher_prices_account::extend(header, prices, cast_slice(new_prices)).unwrap();
942+
}
943+
bank.store_account(&publisher1_prices_key, &publisher1_prices_account);
944+
945+
publisher1_key
946+
};
947+
948+
let publishers = [
949+
generate_publisher(
950+
&[1u8; 32],
951+
&[
952+
PublisherPrice::new(1, 1, 10, 2).unwrap(),
953+
PublisherPrice::new(2, 1, 20, 3).unwrap(),
954+
],
955+
),
956+
generate_publisher(
957+
&[2u8; 32],
958+
&[
959+
PublisherPrice::new(1, 1, 15, 2).unwrap(),
960+
PublisherPrice::new(2, 1, 25, 3).unwrap(),
961+
],
962+
),
963+
];
964+
965+
let generate_price = |seeds, index| {
966+
let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PID);
967+
let mut price_feed_account =
968+
AccountSharedData::new(42, size_of::<PriceAccount>(), &ORACLE_PID);
969+
970+
let messages = {
971+
let price_feed_info_key = &price_feed_key.to_bytes().into();
972+
let price_feed_info_lamports = &mut 0;
973+
let price_feed_info_owner = &ORACLE_PID.to_bytes().into();
974+
let price_feed_info_data = price_feed_account.data_mut();
975+
let price_feed_info = AccountInfo::new(
976+
price_feed_info_key,
977+
false,
978+
true,
979+
price_feed_info_lamports,
980+
price_feed_info_data,
981+
price_feed_info_owner,
982+
false,
983+
Epoch::default(),
984+
);
985+
986+
let mut price_account = PriceAccount::initialize(&price_feed_info, 0).unwrap();
987+
price_account.flags.insert(
988+
PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED,
989+
);
990+
price_account.unused_3_ = index;
991+
price_account.comp_[0].pub_ = publishers[0].pubkey().to_bytes().into();
992+
price_account.comp_[1].pub_ = publishers[1].pubkey().to_bytes().into();
993+
price_account.num_ = 2;
994+
};
995+
996+
bank.store_account(&price_feed_key, &price_feed_account);
997+
(price_feed_key, messages)
998+
};
999+
1000+
assert!(bank
1001+
.feature_set
1002+
.is_active(&feature_set::enable_accumulator_sysvar::id()));
1003+
assert!(bank
1004+
.feature_set
1005+
.is_active(&feature_set::move_accumulator_to_end_of_block::id()));
1006+
assert!(bank
1007+
.feature_set
1008+
.is_active(&feature_set::undo_move_accumulator_to_end_of_block::id()));
1009+
assert!(bank
1010+
.feature_set
1011+
.is_active(&feature_set::redo_move_accumulator_to_end_of_block::id()));
1012+
1013+
let prices_with_messages = [
1014+
generate_price(b"seeds_1", 1),
1015+
generate_price(b"seeds_2", 2),
1016+
generate_price(b"seeds_3", 3),
1017+
generate_price(b"seeds_4", 4),
1018+
];
1019+
1020+
bank = new_from_parent(&Arc::new(bank)); // Advance slot 1.
1021+
bank = new_from_parent(&Arc::new(bank)); // Advance slot 2.
1022+
1023+
let new_price_feed1_account = bank.get_account(&prices_with_messages[0].0).unwrap();
1024+
let new_price_feed1_data: &PriceAccount = from_bytes(new_price_feed1_account.data());
1025+
assert_eq!(new_price_feed1_data.comp_[0].latest_.price_, 10);
1026+
assert_eq!(new_price_feed1_data.comp_[1].latest_.price_, 15);
1027+
1028+
let new_price_feed2_account = bank.get_account(&prices_with_messages[1].0).unwrap();
1029+
let new_price_feed2_data: &PriceAccount = from_bytes(new_price_feed2_account.data());
1030+
assert_eq!(new_price_feed2_data.comp_[0].latest_.price_, 20);
1031+
assert_eq!(new_price_feed2_data.comp_[1].latest_.price_, 25);
1032+
}

0 commit comments

Comments
 (0)