809 lines
46 KiB
Rust
809 lines
46 KiB
Rust
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
|
// version: 6
|
|
|
|
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
|
|
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
|
|
}
|
|
|
|
fn minimal_transaction_info(signature_byte: u8, index: u64) -> yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo {
|
|
let meta = yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionStatusMeta { fee: 5_000, ..std::default::Default::default() };
|
|
return yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo {
|
|
signature: vec![signature_byte; 64],
|
|
is_vote: false,
|
|
transaction: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Transaction {
|
|
signatures: vec![vec![signature_byte; 64]],
|
|
message: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Message {
|
|
header: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::MessageHeader {
|
|
num_required_signatures: 1,
|
|
num_readonly_signed_accounts: 0,
|
|
num_readonly_unsigned_accounts: 0,
|
|
}),
|
|
account_keys: vec![vec![1_u8; 32]],
|
|
recent_blockhash: vec![2_u8; 32],
|
|
instructions: vec![],
|
|
versioned: false,
|
|
address_table_lookups: vec![],
|
|
config: std::option::Option::None,
|
|
}),
|
|
}),
|
|
meta: std::option::Option::Some(meta),
|
|
index,
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_subscribe_filter_name_is_bounded_globally_unique_and_debug_redacted() {
|
|
assert!(crate::YellowstoneSubscribeFilterName::new("").is_err());
|
|
assert!(crate::YellowstoneSubscribeFilterName::new(" leading").is_err());
|
|
assert!(crate::YellowstoneSubscribeFilterName::new("trailing ").is_err());
|
|
assert!(crate::YellowstoneSubscribeFilterName::new("line\nbreak").is_err());
|
|
assert!(crate::YellowstoneSubscribeFilterName::new("x".repeat(129)).is_err());
|
|
let name = filter_name("account-primary");
|
|
assert_eq!(name.as_str(), "account-primary");
|
|
assert!(!format!("{name:?}").contains("account-primary"));
|
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
|
assert!(request.insert_account_filter(name.clone(), crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
|
let duplicate = request.insert_slot_filter(name, crate::YellowstoneSubscribeSlotFilter::new());
|
|
assert!(duplicate.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_subscribe_empty_and_named_empty_maps_encode_exactly() {
|
|
let empty = crate::YellowstoneSubscribeRequest::new().to_wire().expect("empty request must encode");
|
|
assert!(empty.accounts.is_empty());
|
|
assert!(empty.slots.is_empty());
|
|
assert!(empty.transactions.is_empty());
|
|
assert!(empty.transactions_status.is_empty());
|
|
assert!(empty.blocks.is_empty());
|
|
assert!(empty.blocks_meta.is_empty());
|
|
assert!(empty.entry.is_empty());
|
|
assert_eq!(empty.commitment, std::option::Option::None);
|
|
assert!(empty.accounts_data_slice.is_empty());
|
|
assert_eq!(empty.ping, std::option::Option::None);
|
|
assert_eq!(empty.from_slot, std::option::Option::None);
|
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
|
assert!(request.insert_account_filter(filter_name("accounts"), crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
|
assert!(request.insert_slot_filter(filter_name("slots"), crate::YellowstoneSubscribeSlotFilter::new()).is_ok());
|
|
assert!(request.insert_transaction_filter(filter_name("transactions"), crate::YellowstoneSubscribeTransactionFilter::new()).is_ok());
|
|
assert!(request.insert_transaction_status_filter(filter_name("transaction-status"), crate::YellowstoneSubscribeTransactionFilter::new()).is_ok());
|
|
assert!(request.insert_block_filter(filter_name("blocks"), crate::YellowstoneSubscribeBlockFilter::new()).is_ok());
|
|
assert!(request.insert_blocks_meta_filter(filter_name("blocks-meta"), crate::YellowstoneSubscribeBlocksMetaFilter::new()).is_ok());
|
|
assert!(request.insert_entry_filter(filter_name("entry"), crate::YellowstoneSubscribeEntryFilter::new()).is_ok());
|
|
let wire = request.to_wire().expect("named empty request must encode");
|
|
assert_eq!(wire.accounts.len(), 1);
|
|
assert_eq!(wire.slots.len(), 1);
|
|
assert_eq!(wire.transactions.len(), 1);
|
|
assert_eq!(wire.transactions_status.len(), 1);
|
|
assert_eq!(wire.blocks.len(), 1);
|
|
assert_eq!(wire.blocks_meta.len(), 1);
|
|
assert_eq!(wire.entry.len(), 1);
|
|
assert_eq!(wire.accounts.get("accounts"), std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts::default()));
|
|
assert_eq!(wire.slots.get("slots"), std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots::default()));
|
|
assert_eq!(
|
|
wire.transactions.get("transactions"),
|
|
std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions::default()),
|
|
);
|
|
assert_eq!(
|
|
wire.transactions_status.get("transaction-status"),
|
|
std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions::default())
|
|
);
|
|
assert_eq!(wire.blocks.get("blocks"), std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks::default()));
|
|
assert!(wire.blocks_meta.contains_key("blocks-meta"));
|
|
assert!(wire.entry.contains_key("entry"));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_subscribe_common_fields_preserve_optional_and_ordered_wire_semantics() {
|
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
|
request.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Finalized));
|
|
let first = crate::YellowstoneAccountsDataSlice::new(8, 16).expect("first slice must validate");
|
|
let second = crate::YellowstoneAccountsDataSlice::new(64, 0).expect("zero-length slice remains representable");
|
|
assert!(request.push_accounts_data_slice(first).is_ok());
|
|
assert!(request.push_accounts_data_slice(second).is_ok());
|
|
request.set_ping(std::option::Option::Some(crate::YellowstoneSubscribePing::new(-7)));
|
|
request.set_from_slot(std::option::Option::Some(42));
|
|
assert_eq!(request.commitment(), std::option::Option::Some(crate::SolanaCommitment::Finalized));
|
|
assert_eq!(request.accounts_data_slices(), &[first, second]);
|
|
assert_eq!(request.ping(), std::option::Option::Some(crate::YellowstoneSubscribePing::new(-7)));
|
|
assert_eq!(request.from_slot(), std::option::Option::Some(42));
|
|
let wire = request.to_wire().expect("common fields must encode");
|
|
assert_eq!(wire.commitment, std::option::Option::Some(yellowstone_grpc_proto::geyser::CommitmentLevel::Finalized as i32));
|
|
assert_eq!(wire.accounts_data_slice.len(), 2);
|
|
assert_eq!(wire.accounts_data_slice[0].offset, 8);
|
|
assert_eq!(wire.accounts_data_slice[0].length, 16);
|
|
assert_eq!(wire.accounts_data_slice[1].offset, 64);
|
|
assert_eq!(wire.accounts_data_slice[1].length, 0);
|
|
assert_eq!(wire.ping.map(|ping| return ping.id), std::option::Option::Some(-7));
|
|
assert_eq!(wire.from_slot, std::option::Option::Some(42));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_subscribe_common_bounds_reject_before_wire_conversion() {
|
|
assert!(crate::YellowstoneAccountsDataSlice::new(0, 64 * 1024 * 1024).is_ok());
|
|
assert!(crate::YellowstoneAccountsDataSlice::new(0, 64 * 1024 * 1024 + 1).is_err());
|
|
assert!(crate::YellowstoneAccountsDataSlice::new(u64::MAX, 1).is_err());
|
|
let mut slices = crate::YellowstoneSubscribeRequest::new();
|
|
for index in 0..128_u64 {
|
|
let slice = crate::YellowstoneAccountsDataSlice::new(index, 1).expect("bounded fixture slice must validate");
|
|
assert!(slices.push_accounts_data_slice(slice).is_ok());
|
|
}
|
|
let excess = crate::YellowstoneAccountsDataSlice::new(129, 1).expect("excess fixture slice itself must validate");
|
|
assert!(slices.push_accounts_data_slice(excess).is_err());
|
|
let mut filters = crate::YellowstoneSubscribeRequest::new();
|
|
for index in 0..1_024_u32 {
|
|
let name = crate::YellowstoneSubscribeFilterName::new(format!("f{index}")).expect("bounded filter name must validate");
|
|
assert!(filters.insert_account_filter(name, crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
|
}
|
|
let excess_name = crate::YellowstoneSubscribeFilterName::new("excess").expect("excess filter name must validate independently");
|
|
assert!(filters.insert_account_filter(excess_name, crate::YellowstoneSubscribeAccountFilter::new()).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_subscribe_request_identity_is_order_stable_exact_and_debug_redacted() {
|
|
let mut first = crate::YellowstoneSubscribeRequest::new();
|
|
let mut second = crate::YellowstoneSubscribeRequest::new();
|
|
let mut changed = crate::YellowstoneSubscribeRequest::new();
|
|
let mut filter_a = crate::YellowstoneSubscribeTransactionFilter::new();
|
|
filter_a.set_failed(std::option::Option::Some(false));
|
|
let mut filter_b = crate::YellowstoneSubscribeTransactionFilter::new();
|
|
filter_b.set_vote(std::option::Option::Some(false));
|
|
assert!(first.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b.clone()).is_ok());
|
|
assert!(first.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a.clone()).is_ok());
|
|
assert!(second.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a.clone()).is_ok());
|
|
assert!(second.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b.clone()).is_ok());
|
|
filter_a.set_failed(std::option::Option::Some(true));
|
|
assert!(changed.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a).is_ok());
|
|
assert!(changed.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b).is_ok());
|
|
first.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
|
second.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
|
changed.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
|
let first_identity = first.identity().expect("first request identity must build");
|
|
let second_identity = second.identity().expect("second request identity must build");
|
|
let changed_identity = changed.identity().expect("changed request identity must build");
|
|
assert_eq!(first_identity, second_identity);
|
|
assert_ne!(first_identity, changed_identity);
|
|
let debug = std::format!("{first_identity:?}");
|
|
assert!(debug.contains("byte_len"));
|
|
assert!(!debug.contains("identity-filter-alpha-canary"));
|
|
assert!(!debug.contains("identity-filter-beta-canary"));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_subscribe_debug_omits_filter_names_and_future_payloads() {
|
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
|
assert!(request.insert_account_filter(filter_name("sensitive-label"), crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
|
request.set_ping(std::option::Option::Some(crate::YellowstoneSubscribePing::new(9)));
|
|
request.set_from_slot(std::option::Option::Some(77));
|
|
let debug = format!("{request:?}");
|
|
assert!(!debug.contains("sensitive-label"));
|
|
assert!(debug.contains("account_filter_count"));
|
|
assert!(debug.contains("from_slot"));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_account_and_slot_filters_encode_complete_current_wire() {
|
|
let account = ksp_core_lib::Pubkey::new_from_array([1_u8; 32]);
|
|
let owner = ksp_core_lib::Pubkey::new_from_array([2_u8; 32]);
|
|
let mut filter = crate::YellowstoneSubscribeAccountFilter::new();
|
|
assert!(filter.push_account(account).is_ok());
|
|
assert!(filter.push_owner(owner).is_ok());
|
|
let raw = crate::YellowstoneAccountMemcmp::bytes(4, vec![1_u8, 2, 3]).expect("raw memcmp must validate");
|
|
let base58 = crate::YellowstoneAccountMemcmp::base58(8, "1234").expect("base58 memcmp must validate");
|
|
let base64 = crate::YellowstoneAccountMemcmp::base64(12, "AQID==").expect("base64 memcmp must validate");
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(raw)).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(base58)).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(base64)).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::DataSize(165)).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::TokenAccountState(true)).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Eq(1))).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Ne(2))).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Lt(3))).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Lamports(crate::YellowstoneAccountLamportsFilter::Gt(4))).is_ok());
|
|
filter.set_nonempty_txn_signature(std::option::Option::Some(true));
|
|
let cuckoo =
|
|
crate::YellowstoneCuckooFilter::new(vec![9_u8; 16], 4, 4, 8, 77, crate::YellowstoneCuckooHashAlgorithm::SipHash).expect("cuckoo fixture must validate");
|
|
filter.set_cuckoo_accounts_filter(std::option::Option::Some(cuckoo));
|
|
let wire = filter.to_wire();
|
|
assert_eq!(wire.account, vec![account.to_string()]);
|
|
assert_eq!(wire.owner, vec![owner.to_string()]);
|
|
assert_eq!(wire.filters.len(), 9);
|
|
match wire.filters[0].filter.as_ref().expect("raw memcmp oneof must be present") {
|
|
yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::Memcmp(value) => assert_eq!(
|
|
value.data,
|
|
std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_memcmp::Data::Bytes(vec![1_u8, 2, 3]))
|
|
),
|
|
_ => panic!("first predicate must stay memcmp"),
|
|
}
|
|
match wire.filters[5].filter.as_ref().expect("lamports oneof must be present") {
|
|
yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter::Filter::Lamports(value) => {
|
|
assert_eq!(value.cmp, std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_request_filter_accounts_filter_lamports::Cmp::Eq(1)))
|
|
},
|
|
_ => panic!("sixth predicate must stay lamports"),
|
|
}
|
|
assert_eq!(wire.nonempty_txn_signature, std::option::Option::Some(true));
|
|
let cuckoo = wire.cuckoo_accounts_filter.expect("cuckoo filter must be present");
|
|
assert_eq!(cuckoo.data, vec![9_u8; 16]);
|
|
assert_eq!(cuckoo.bucket_count, 4);
|
|
assert_eq!(cuckoo.entries_per_bucket, 4);
|
|
assert_eq!(cuckoo.fingerprint_bits, 8);
|
|
assert_eq!(cuckoo.hash_seed, 77);
|
|
assert_eq!(cuckoo.hash_algorithm, yellowstone_grpc_proto::geyser::CuckooHashAlgorithm::SipHash as i32);
|
|
let mut slots = crate::YellowstoneSubscribeSlotFilter::new();
|
|
slots.set_filter_by_commitment(std::option::Option::Some(false));
|
|
slots.set_interslot_updates(std::option::Option::Some(true));
|
|
assert_eq!(slots.filter_by_commitment(), std::option::Option::Some(false));
|
|
assert_eq!(slots.interslot_updates(), std::option::Option::Some(true));
|
|
let slots_wire = slots.to_wire();
|
|
assert_eq!(slots_wire.filter_by_commitment, std::option::Option::Some(false));
|
|
assert_eq!(slots_wire.interslot_updates, std::option::Option::Some(true));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_account_filter_bounds_and_debug_are_provider_neutral() {
|
|
assert!(crate::YellowstoneCuckooFilter::new(vec![], 0, 4, 8, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_err());
|
|
assert!(crate::YellowstoneCuckooFilter::new(vec![], 1, 0, 8, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_err());
|
|
assert!(crate::YellowstoneCuckooFilter::new(vec![], 1, 4, 7, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_err());
|
|
assert!(crate::YellowstoneCuckooFilter::new(vec![], 1, 4, 12, 0, crate::YellowstoneCuckooHashAlgorithm::SipHash).is_ok());
|
|
assert!(crate::YellowstoneAccountMemcmp::base58(0, "contains whitespace").is_err());
|
|
assert!(crate::YellowstoneAccountMemcmp::base64(0, "line\nbreak").is_err());
|
|
let memcmp = crate::YellowstoneAccountMemcmp::bytes(5, vec![7_u8, 8, 9]).expect("memcmp must validate");
|
|
let debug = format!("{memcmp:?}");
|
|
assert!(debug.contains("payload_length"));
|
|
assert!(!debug.contains("7, 8, 9"));
|
|
let mut filter = crate::YellowstoneSubscribeAccountFilter::new();
|
|
assert!(filter.push_account(ksp_core_lib::Pubkey::new_from_array([3_u8; 32])).is_ok());
|
|
assert!(filter.push_filter(crate::YellowstoneAccountFilterPredicate::Memcmp(memcmp)).is_ok());
|
|
let filter_debug = format!("{filter:?}");
|
|
assert!(filter_debug.contains("account_count"));
|
|
assert!(!filter_debug.contains(&ksp_core_lib::Pubkey::new_from_array([3_u8; 32]).to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_account_update_decodes_complete_wire_and_redacts_payload_debug() {
|
|
let signature = vec![6_u8; 64];
|
|
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["accounts-main".to_owned(), "accounts-owner".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount {
|
|
account: std::option::Option::Some(yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
|
pubkey: vec![1_u8; 32],
|
|
lamports: 42,
|
|
owner: vec![2_u8; 32],
|
|
executable: true,
|
|
rent_epoch: 9,
|
|
data: vec![0xAA_u8, 0xBB, 0xCC],
|
|
write_version: 7,
|
|
txn_signature: std::option::Option::Some(signature),
|
|
}),
|
|
slot: 123,
|
|
is_startup: true,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 1_700_000_000, nanos: 123_456_789 }),
|
|
};
|
|
let update = super::decode_account_update(wire).expect("account update fixture must decode");
|
|
assert_eq!(update.filters()[0].as_str(), "accounts-main");
|
|
assert_eq!(update.filters()[1].as_str(), "accounts-owner");
|
|
assert_eq!(update.created_at().expect("timestamp must be present").seconds(), 1_700_000_000);
|
|
assert_eq!(update.created_at().expect("timestamp must be present").nanos(), 123_456_789);
|
|
assert_eq!(update.slot(), 123);
|
|
assert!(update.is_startup());
|
|
assert_eq!(update.account().pubkey(), &ksp_core_lib::Pubkey::new_from_array([1_u8; 32]));
|
|
assert_eq!(update.account().owner(), &ksp_core_lib::Pubkey::new_from_array([2_u8; 32]));
|
|
assert_eq!(update.account().lamports(), 42);
|
|
assert!(update.account().executable());
|
|
assert_eq!(update.account().rent_epoch(), 9);
|
|
assert_eq!(update.account().data(), &[0xAA_u8, 0xBB, 0xCC]);
|
|
assert_eq!(update.account().write_version(), 7);
|
|
assert_eq!(update.account().transaction_signature().expect("signature must be present").as_bytes(), &[6_u8; 64]);
|
|
let debug = format!("{update:?}");
|
|
assert!(!debug.contains("accounts-main"));
|
|
assert!(!debug.contains("170, 187, 204"));
|
|
assert!(!debug.contains(&ksp_core_lib::Pubkey::new_from_array([1_u8; 32]).to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_account_update_rejects_malformed_fixed_width_and_envelope_fields() {
|
|
let malformed_pubkey = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["accounts".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount {
|
|
account: std::option::Option::Some(yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
|
pubkey: vec![1_u8; 31],
|
|
lamports: 0,
|
|
owner: vec![2_u8; 32],
|
|
executable: false,
|
|
rent_epoch: 0,
|
|
data: vec![],
|
|
write_version: 0,
|
|
txn_signature: std::option::Option::None,
|
|
}),
|
|
slot: 0,
|
|
is_startup: false,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_account_update(malformed_pubkey).is_err());
|
|
let malformed_signature = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["accounts".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount {
|
|
account: std::option::Option::Some(yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
|
pubkey: vec![1_u8; 32],
|
|
lamports: 0,
|
|
owner: vec![2_u8; 32],
|
|
executable: false,
|
|
rent_epoch: 0,
|
|
data: vec![],
|
|
write_version: 0,
|
|
txn_signature: std::option::Option::Some(vec![9_u8; 63]),
|
|
}),
|
|
slot: 0,
|
|
is_startup: false,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_account_update(malformed_signature).is_err());
|
|
let missing_info = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["accounts".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Account(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateAccount { account: std::option::Option::None, slot: 0, is_startup: false },
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_account_update(missing_info).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_slot_update_preserves_all_current_statuses_and_bounds_dead_error() {
|
|
let statuses = [
|
|
(yellowstone_grpc_proto::geyser::SlotStatus::SlotProcessed, crate::YellowstoneSlotStatus::Processed),
|
|
(yellowstone_grpc_proto::geyser::SlotStatus::SlotConfirmed, crate::YellowstoneSlotStatus::Confirmed),
|
|
(yellowstone_grpc_proto::geyser::SlotStatus::SlotFinalized, crate::YellowstoneSlotStatus::Finalized),
|
|
(yellowstone_grpc_proto::geyser::SlotStatus::SlotFirstShredReceived, crate::YellowstoneSlotStatus::FirstShredReceived),
|
|
(yellowstone_grpc_proto::geyser::SlotStatus::SlotCompleted, crate::YellowstoneSlotStatus::Completed),
|
|
(yellowstone_grpc_proto::geyser::SlotStatus::SlotCreatedBank, crate::YellowstoneSlotStatus::CreatedBank),
|
|
(yellowstone_grpc_proto::geyser::SlotStatus::SlotDead, crate::YellowstoneSlotStatus::Dead),
|
|
];
|
|
for (wire_status, expected) in statuses {
|
|
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["slots".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateSlot {
|
|
slot: 88,
|
|
parent: std::option::Option::Some(87),
|
|
status: wire_status as i32,
|
|
dead_error: if expected == crate::YellowstoneSlotStatus::Dead {
|
|
std::option::Option::Some("fork rejected".to_owned())
|
|
} else {
|
|
std::option::Option::None
|
|
},
|
|
},
|
|
)),
|
|
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 5, nanos: 6 }),
|
|
};
|
|
let update = super::decode_slot_update(wire).expect("slot update fixture must decode");
|
|
assert_eq!(update.status(), expected);
|
|
assert_eq!(update.slot(), 88);
|
|
assert_eq!(update.parent(), std::option::Option::Some(87));
|
|
assert_eq!(update.filters()[0].as_str(), "slots");
|
|
if expected == crate::YellowstoneSlotStatus::Dead {
|
|
assert_eq!(update.dead_error(), std::option::Option::Some("fork rejected"));
|
|
assert!(!format!("{update:?}").contains("fork rejected"));
|
|
}
|
|
}
|
|
let unknown = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["slots".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateSlot {
|
|
slot: 1,
|
|
parent: std::option::Option::None,
|
|
status: 99,
|
|
dead_error: std::option::Option::None,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_slot_update(unknown).is_err());
|
|
let oversized = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["slots".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Slot(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateSlot {
|
|
slot: 1,
|
|
parent: std::option::Option::None,
|
|
status: yellowstone_grpc_proto::geyser::SlotStatus::SlotDead as i32,
|
|
dead_error: std::option::Option::Some("x".repeat(16 * 1024 + 1)),
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_slot_update(oversized).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_transaction_filters_encode_complete_current_wire_and_redact_selectors() {
|
|
let mut filter = crate::YellowstoneSubscribeTransactionFilter::new();
|
|
filter.set_vote(std::option::Option::Some(false));
|
|
filter.set_failed(std::option::Option::Some(true));
|
|
let signature = crate::YellowstoneTransactionSignatureSelector::new("1".repeat(64)).expect("signature selector must validate");
|
|
filter.set_signature(std::option::Option::Some(signature));
|
|
assert!(filter.push_account_include(ksp_core_lib::Pubkey::new_from_array([1_u8; 32])).is_ok());
|
|
assert!(filter.push_account_exclude(ksp_core_lib::Pubkey::new_from_array([2_u8; 32])).is_ok());
|
|
assert!(filter.push_account_required(ksp_core_lib::Pubkey::new_from_array([3_u8; 32])).is_ok());
|
|
let cuckoo =
|
|
crate::YellowstoneCuckooFilter::new(vec![0_u8; 16], 4, 4, 8, 7, crate::YellowstoneCuckooHashAlgorithm::SipHash).expect("cuckoo filter must validate");
|
|
filter.set_cuckoo_account_include(std::option::Option::Some(cuckoo));
|
|
filter.set_token_accounts(std::option::Option::Some(crate::YellowstoneTokenAccountExpansion::BalanceChanged));
|
|
let wire = filter.to_wire();
|
|
assert_eq!(wire.vote, std::option::Option::Some(false));
|
|
assert_eq!(wire.failed, std::option::Option::Some(true));
|
|
assert!(wire.signature.is_some());
|
|
assert_eq!(wire.account_include.len(), 1);
|
|
assert_eq!(wire.account_exclude.len(), 1);
|
|
assert_eq!(wire.account_required.len(), 1);
|
|
assert!(wire.cuckoo_account_include.is_some());
|
|
assert_eq!(wire.token_accounts, std::option::Option::Some(yellowstone_grpc_proto::geyser::TokenAccountExpansionControlFlag::BalanceChanged as i32));
|
|
let debug = format!("{filter:?}");
|
|
assert!(debug.contains("account_include_count"));
|
|
assert!(!debug.contains(&"1".repeat(32)));
|
|
assert!(!debug.contains(&ksp_core_lib::Pubkey::new_from_array([1_u8; 32]).to_string()));
|
|
assert!(crate::YellowstoneTransactionSignatureSelector::new("contains-0-O-I-l").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_transaction_update_decodes_current_storage_wire_including_v1_config_and_meta() {
|
|
let confirmed = yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo {
|
|
signature: vec![9_u8; 64],
|
|
is_vote: false,
|
|
transaction: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Transaction {
|
|
signatures: vec![vec![9_u8; 64], vec![8_u8; 64]],
|
|
message: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Message {
|
|
header: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::MessageHeader {
|
|
num_required_signatures: 2,
|
|
num_readonly_signed_accounts: 1,
|
|
num_readonly_unsigned_accounts: 1,
|
|
}),
|
|
account_keys: vec![vec![1_u8; 32], vec![2_u8; 32]],
|
|
recent_blockhash: vec![3_u8; 32],
|
|
instructions: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::CompiledInstruction {
|
|
program_id_index: 1,
|
|
accounts: vec![0_u8, 1],
|
|
data: vec![4_u8, 5, 6],
|
|
}],
|
|
versioned: true,
|
|
address_table_lookups: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::MessageAddressTableLookup {
|
|
account_key: vec![4_u8; 32],
|
|
writable_indexes: vec![1_u8, 2],
|
|
readonly_indexes: vec![3_u8],
|
|
}],
|
|
config: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionConfig {
|
|
priority_fee: std::option::Option::Some(7),
|
|
compute_unit_limit: std::option::Option::Some(8),
|
|
loaded_accounts_data_size_limit: std::option::Option::Some(9),
|
|
heap_size: std::option::Option::Some(10),
|
|
}),
|
|
}),
|
|
}),
|
|
meta: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionStatusMeta {
|
|
err: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionError { err: vec![11_u8, 12] }),
|
|
fee: 5_000,
|
|
pre_balances: vec![100, 200],
|
|
post_balances: vec![90, 210],
|
|
inner_instructions: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::InnerInstructions {
|
|
index: 0,
|
|
instructions: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::InnerInstruction {
|
|
program_id_index: 1,
|
|
accounts: vec![0_u8],
|
|
data: vec![13_u8, 14],
|
|
stack_height: std::option::Option::Some(2),
|
|
}],
|
|
}],
|
|
inner_instructions_none: false,
|
|
log_messages: vec!["Program log: fixture".to_owned()],
|
|
log_messages_none: false,
|
|
pre_token_balances: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::TokenBalance {
|
|
account_index: 0,
|
|
mint: "mint-fixture".to_owned(),
|
|
ui_token_amount: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::UiTokenAmount {
|
|
ui_amount: 1.5,
|
|
decimals: 6,
|
|
amount: "1500000".to_owned(),
|
|
ui_amount_string: "1.5".to_owned(),
|
|
}),
|
|
owner: "owner-fixture".to_owned(),
|
|
program_id: "program-fixture".to_owned(),
|
|
}],
|
|
post_token_balances: vec![],
|
|
rewards: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::Reward {
|
|
pubkey: ksp_core_lib::Pubkey::new_from_array([5_u8; 32]).to_string(),
|
|
lamports: 17,
|
|
post_balance: 18,
|
|
reward_type: yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Staking as i32,
|
|
commission: "5".to_owned(),
|
|
commission_bps: "500".to_owned(),
|
|
}],
|
|
loaded_writable_addresses: vec![vec![6_u8; 32]],
|
|
loaded_readonly_addresses: vec![vec![7_u8; 32]],
|
|
return_data: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::ReturnData {
|
|
program_id: vec![8_u8; 32],
|
|
data: vec![15_u8, 16],
|
|
}),
|
|
return_data_none: false,
|
|
compute_units_consumed: std::option::Option::Some(123),
|
|
cost_units: std::option::Option::Some(456),
|
|
}),
|
|
index: 3,
|
|
};
|
|
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["transactions-main".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Transaction(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateTransaction { transaction: std::option::Option::Some(confirmed), slot: 42 },
|
|
)),
|
|
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 100, nanos: 200 }),
|
|
};
|
|
let update = super::decode_transaction_update(wire).expect("transaction update fixture must decode");
|
|
assert_eq!(update.slot(), 42);
|
|
assert_eq!(update.filters()[0].as_str(), "transactions-main");
|
|
assert_eq!(update.transaction().signature().as_bytes(), &[9_u8; 64]);
|
|
assert_eq!(format!("{:?}", update.transaction().signature()), "YellowstoneTransactionSignature(<redacted>)");
|
|
assert_eq!(update.transaction().index(), 3);
|
|
assert_eq!(update.transaction().transaction().signatures().len(), 2);
|
|
assert!(update.transaction().transaction().message().versioned());
|
|
let config = update.transaction().transaction().message().config().expect("v1 config must be preserved");
|
|
assert_eq!(config.priority_fee(), std::option::Option::Some(7));
|
|
assert_eq!(config.heap_size(), std::option::Option::Some(10));
|
|
assert_eq!(update.transaction().meta().fee(), 5_000);
|
|
assert_eq!(update.transaction().meta().error().expect("error must be present").as_bytes(), &[11_u8, 12]);
|
|
assert_eq!(update.transaction().meta().inner_instructions()[0].instructions()[0].stack_height(), std::option::Option::Some(2));
|
|
assert_eq!(update.transaction().meta().pre_token_balances()[0].ui_token_amount().expect("token amount must be present").amount(), "1500000");
|
|
let token_debug = format!("{:?}", update.transaction().meta().pre_token_balances()[0]);
|
|
assert!(!token_debug.contains("mint-fixture"));
|
|
assert!(!token_debug.contains("owner-fixture"));
|
|
assert!(!token_debug.contains("program-fixture"));
|
|
assert_eq!(update.transaction().meta().rewards()[0].reward_type(), crate::YellowstoneRewardType::Staking);
|
|
assert_eq!(update.transaction().meta().loaded_writable_addresses()[0], ksp_core_lib::Pubkey::new_from_array([6_u8; 32]));
|
|
assert_eq!(update.transaction().meta().return_data().expect("return data must be present").data(), &[15_u8, 16]);
|
|
assert_eq!(update.transaction().meta().compute_units_consumed(), std::option::Option::Some(123));
|
|
assert_eq!(update.transaction().meta().cost_units(), std::option::Option::Some(456));
|
|
let debug = format!("{update:?}");
|
|
assert!(!debug.contains("Program log: fixture"));
|
|
assert!(!debug.contains("11, 12"));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_transaction_status_update_preserves_error_and_rejects_malformed_signature() {
|
|
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["status".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::TransactionStatus(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionStatus {
|
|
slot: 55,
|
|
signature: vec![2_u8; 64],
|
|
is_vote: true,
|
|
index: 4,
|
|
err: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionError { err: vec![99_u8] }),
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
let update = super::decode_transaction_status_update(wire).expect("transaction-status update must decode");
|
|
assert_eq!(update.slot(), 55);
|
|
assert!(update.is_vote());
|
|
assert_eq!(update.index(), 4);
|
|
assert_eq!(update.signature().as_bytes(), &[2_u8; 64]);
|
|
assert_eq!(update.error().expect("status error must be present").as_bytes(), &[99_u8]);
|
|
assert!(!format!("{update:?}").contains("99"));
|
|
let malformed = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["status".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::TransactionStatus(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionStatus {
|
|
slot: 1,
|
|
signature: vec![0_u8; 63],
|
|
is_vote: false,
|
|
index: 0,
|
|
err: std::option::Option::None,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_transaction_status_update(malformed).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_block_filter_encodes_complete_current_wire_and_redacts_selectors() {
|
|
let mut filter = crate::YellowstoneSubscribeBlockFilter::new();
|
|
let account = ksp_core_lib::Pubkey::new_from_array([31_u8; 32]);
|
|
assert!(filter.push_account_include(account).is_ok());
|
|
filter.set_include_transactions(std::option::Option::Some(true));
|
|
filter.set_include_accounts(std::option::Option::Some(false));
|
|
filter.set_include_entries(std::option::Option::Some(true));
|
|
let cuckoo = crate::YellowstoneCuckooFilter::new(vec![0_u8; 16], 4, 4, 8, 9, crate::YellowstoneCuckooHashAlgorithm::SipHash)
|
|
.expect("block Cuckoo filter must validate");
|
|
filter.set_cuckoo_account_include(std::option::Option::Some(cuckoo));
|
|
let wire = filter.to_wire();
|
|
assert_eq!(wire.account_include, vec![account.to_string()]);
|
|
assert_eq!(wire.include_transactions, std::option::Option::Some(true));
|
|
assert_eq!(wire.include_accounts, std::option::Option::Some(false));
|
|
assert_eq!(wire.include_entries, std::option::Option::Some(true));
|
|
assert!(wire.cuckoo_account_include.is_some());
|
|
let debug = format!("{filter:?}");
|
|
assert!(debug.contains("account_include_count"));
|
|
assert!(!debug.contains(&account.to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_block_update_reuses_transaction_account_entry_dtos_and_preserves_server_counts() {
|
|
let blockhash = ksp_core_lib::Pubkey::new_from_array([21_u8; 32]).to_string();
|
|
let parent_blockhash = ksp_core_lib::Pubkey::new_from_array([22_u8; 32]).to_string();
|
|
let reward_pubkey = ksp_core_lib::Pubkey::new_from_array([23_u8; 32]);
|
|
let account_pubkey = ksp_core_lib::Pubkey::new_from_array([24_u8; 32]);
|
|
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["blocks-main".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Block(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateBlock {
|
|
slot: 500,
|
|
blockhash: blockhash.clone(),
|
|
rewards: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Rewards {
|
|
rewards: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::Reward {
|
|
pubkey: reward_pubkey.to_string(),
|
|
lamports: 10,
|
|
post_balance: 11,
|
|
reward_type: yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Fee as i32,
|
|
commission: "".to_owned(),
|
|
commission_bps: "".to_owned(),
|
|
}],
|
|
num_partitions: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::NumPartitions { num_partitions: 3 }),
|
|
}),
|
|
block_time: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::UnixTimestamp { timestamp: 1_700_000_000 }),
|
|
block_height: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::BlockHeight { block_height: 499 }),
|
|
transactions: vec![minimal_transaction_info(7, 2)],
|
|
parent_slot: 499,
|
|
parent_blockhash: parent_blockhash.clone(),
|
|
executed_transaction_count: 12,
|
|
updated_account_count: 34,
|
|
accounts: vec![yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
|
pubkey: vec![24_u8; 32],
|
|
lamports: 77,
|
|
owner: vec![25_u8; 32],
|
|
executable: false,
|
|
rent_epoch: 4,
|
|
data: vec![1_u8, 2, 3],
|
|
write_version: 8,
|
|
txn_signature: std::option::Option::Some(vec![7_u8; 64]),
|
|
}],
|
|
entries_count: 56,
|
|
entries: vec![yellowstone_grpc_proto::geyser::SubscribeUpdateEntry {
|
|
slot: 500,
|
|
index: 4,
|
|
num_hashes: 5,
|
|
hash: vec![26_u8; 32],
|
|
executed_transaction_count: 6,
|
|
starting_transaction_index: 7,
|
|
}],
|
|
},
|
|
)),
|
|
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 12, nanos: 34 }),
|
|
};
|
|
let update = super::decode_block_update(wire).expect("block update fixture must decode");
|
|
assert_eq!(update.filters()[0].as_str(), "blocks-main");
|
|
assert_eq!(update.slot(), 500);
|
|
assert_eq!(update.blockhash(), blockhash);
|
|
assert_eq!(update.parent_slot(), 499);
|
|
assert_eq!(update.parent_blockhash(), parent_blockhash);
|
|
assert_eq!(update.block_time(), std::option::Option::Some(1_700_000_000));
|
|
assert_eq!(update.block_height(), std::option::Option::Some(499));
|
|
assert_eq!(update.executed_transaction_count(), 12);
|
|
assert_eq!(update.transactions().len(), 1);
|
|
assert_eq!(update.transactions()[0].index(), 2);
|
|
assert_eq!(update.updated_account_count(), 34);
|
|
assert_eq!(update.accounts().len(), 1);
|
|
assert_eq!(update.accounts()[0].pubkey(), &account_pubkey);
|
|
assert_eq!(update.entries_count(), 56);
|
|
assert_eq!(update.entries().len(), 1);
|
|
assert_eq!(update.entries()[0].starting_transaction_index(), 7);
|
|
let rewards = update.rewards().expect("block rewards must be present");
|
|
assert_eq!(rewards.rewards().len(), 1);
|
|
assert_eq!(rewards.num_partitions(), std::option::Option::Some(3));
|
|
assert_eq!(rewards.rewards()[0].pubkey(), &reward_pubkey);
|
|
let debug = format!("{update:?}");
|
|
assert!(!debug.contains(&blockhash));
|
|
assert!(!debug.contains(&parent_blockhash));
|
|
assert!(!debug.contains(&reward_pubkey.to_string()));
|
|
assert!(!debug.contains(&account_pubkey.to_string()));
|
|
let malformed = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["blocks-main".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Block(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateBlock {
|
|
slot: 1,
|
|
blockhash: "not-a-solana-hash".to_owned(),
|
|
rewards: std::option::Option::None,
|
|
block_time: std::option::Option::None,
|
|
block_height: std::option::Option::None,
|
|
transactions: vec![],
|
|
parent_slot: 0,
|
|
parent_blockhash: ksp_core_lib::Pubkey::new_from_array([1_u8; 32]).to_string(),
|
|
executed_transaction_count: 0,
|
|
updated_account_count: 0,
|
|
accounts: vec![],
|
|
entries_count: 0,
|
|
entries: vec![],
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_block_update(malformed).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn yellowstone_block_meta_and_entry_updates_preserve_optional_and_legacy_entry_fields() {
|
|
let blockhash = ksp_core_lib::Pubkey::new_from_array([41_u8; 32]).to_string();
|
|
let parent_blockhash = ksp_core_lib::Pubkey::new_from_array([42_u8; 32]).to_string();
|
|
let meta_wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["meta".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::BlockMeta(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateBlockMeta {
|
|
slot: 900,
|
|
blockhash: blockhash.clone(),
|
|
rewards: std::option::Option::None,
|
|
block_time: std::option::Option::None,
|
|
block_height: std::option::Option::None,
|
|
parent_slot: 899,
|
|
parent_blockhash: parent_blockhash.clone(),
|
|
executed_transaction_count: 17,
|
|
entries_count: 18,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
let meta = super::decode_block_meta_update(meta_wire).expect("block-meta update must decode");
|
|
assert_eq!(meta.slot(), 900);
|
|
assert_eq!(meta.blockhash(), blockhash);
|
|
assert_eq!(meta.parent_blockhash(), parent_blockhash);
|
|
assert_eq!(meta.rewards(), std::option::Option::None);
|
|
assert_eq!(meta.block_time(), std::option::Option::None);
|
|
assert_eq!(meta.block_height(), std::option::Option::None);
|
|
assert_eq!(meta.executed_transaction_count(), 17);
|
|
assert_eq!(meta.entries_count(), 18);
|
|
assert!(!format!("{meta:?}").contains(&blockhash));
|
|
let entry_wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["entries".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Entry(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateEntry {
|
|
slot: 900,
|
|
index: 3,
|
|
num_hashes: 4,
|
|
hash: vec![43_u8; 32],
|
|
executed_transaction_count: 5,
|
|
starting_transaction_index: 0,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 6, nanos: 7 }),
|
|
};
|
|
let entry = super::decode_entry_update(entry_wire).expect("entry update must decode");
|
|
assert_eq!(entry.filters()[0].as_str(), "entries");
|
|
assert_eq!(entry.entry().slot(), 900);
|
|
assert_eq!(entry.entry().index(), 3);
|
|
assert_eq!(entry.entry().num_hashes(), 4);
|
|
assert_eq!(entry.entry().hash().as_bytes(), &[43_u8; 32]);
|
|
assert_eq!(entry.entry().executed_transaction_count(), 5);
|
|
assert_eq!(entry.entry().starting_transaction_index(), 0);
|
|
assert!(!format!("{entry:?}").contains("43, 43"));
|
|
let malformed_entry = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
|
filters: vec!["entries".to_owned()],
|
|
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Entry(
|
|
yellowstone_grpc_proto::geyser::SubscribeUpdateEntry {
|
|
slot: 1,
|
|
index: 0,
|
|
num_hashes: 0,
|
|
hash: vec![0_u8; 31],
|
|
executed_transaction_count: 0,
|
|
starting_transaction_index: 0,
|
|
},
|
|
)),
|
|
created_at: std::option::Option::None,
|
|
};
|
|
assert!(super::decode_entry_update(malformed_entry).is_err());
|
|
}
|