v0.3.13-pre.002
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
const MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES: usize = 128;
|
||||
const MAX_GRPC_BLOCK_VECTOR_COUNT: usize = 65_536;
|
||||
@@ -2529,13 +2529,34 @@ impl YellowstoneSubscribeEntryFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque deterministic identity material for one complete Yellowstone Subscribe request.
|
||||
///
|
||||
/// The encoded bytes remain private. `Hash` feeds those canonical bytes to a caller-provided hasher while `Debug` exposes only their length.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneSubscribeRequestIdentity {
|
||||
bytes: std::vec::Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for crate::YellowstoneSubscribeRequestIdentity {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write(&(self.bytes.len() as u64).to_be_bytes());
|
||||
state.write(self.bytes.as_slice());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for crate::YellowstoneSubscribeRequestIdentity {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.debug_struct("YellowstoneSubscribeRequestIdentity").field("byte_len", &self.bytes.len()).finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider-neutral standard Yellowstone subscribe request owned by KSP.
|
||||
///
|
||||
/// The seven upstream maps are represented independently and retain named empty entries. An entirely empty map is the logical KSP representation of no active
|
||||
/// filter in that family; protobuf map encoding does not distinguish an omitted map from an empty map. Filter-group names are globally unique across all seven
|
||||
/// maps so the names echoed by `SubscribeUpdate.filters` remain unambiguous. `Debug` exposes only counts and common scalar options, never filter names or
|
||||
/// future
|
||||
/// filter payloads.
|
||||
/// future filter payloads.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneSubscribeRequest {
|
||||
accounts: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeAccountFilter>,
|
||||
@@ -2551,7 +2572,7 @@ pub struct YellowstoneSubscribeRequest {
|
||||
from_slot: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
impl YellowstoneSubscribeRequest {
|
||||
impl crate::YellowstoneSubscribeRequest {
|
||||
/// Creates an empty subscribe request. Empty requests are valid because later bidi lifecycle code uses request mutations to clear filters or carry ping
|
||||
/// state.
|
||||
#[must_use]
|
||||
@@ -2762,6 +2783,40 @@ impl YellowstoneSubscribeRequest {
|
||||
return self.from_slot;
|
||||
}
|
||||
|
||||
/// Builds one opaque deterministic identity for the complete logical Subscribe request.
|
||||
///
|
||||
/// The identity preserves filter-family separation, globally sorted filter names, exact filter wire payloads and common request options. Its Debug surface
|
||||
/// exposes only the encoded byte length. Callers may hash the opaque value but cannot recover the underlying identity bytes through this API.
|
||||
pub fn identity(&self) -> ksp_core_lib::Result<crate::YellowstoneSubscribeRequestIdentity> {
|
||||
let validation = self.validate();
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let mut bytes = std::vec::Vec::new();
|
||||
append_subscribe_identity_map(&mut bytes, b"accounts", &self.accounts, |filter| return filter.to_wire());
|
||||
append_subscribe_identity_map(&mut bytes, b"blocks", &self.blocks, |filter| return filter.to_wire());
|
||||
append_subscribe_identity_map(&mut bytes, b"blocks_meta", &self.blocks_meta, |filter| return filter.to_wire());
|
||||
append_subscribe_identity_map(&mut bytes, b"entry", &self.entry, |filter| return filter.to_wire());
|
||||
append_subscribe_identity_map(&mut bytes, b"slots", &self.slots, |filter| return filter.to_wire());
|
||||
append_subscribe_identity_map(&mut bytes, b"transactions", &self.transactions, |filter| return filter.to_wire());
|
||||
append_subscribe_identity_map(&mut bytes, b"transactions_status", &self.transactions_status, |filter| return filter.to_wire());
|
||||
let mut common = match self.to_wire() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
common.accounts.clear();
|
||||
common.blocks.clear();
|
||||
common.blocks_meta.clear();
|
||||
common.entry.clear();
|
||||
common.slots.clear();
|
||||
common.transactions.clear();
|
||||
common.transactions_status.clear();
|
||||
let common = yellowstone_grpc_proto::prost::Message::encode_to_vec(&common);
|
||||
append_subscribe_identity_component(&mut bytes, b"common");
|
||||
append_subscribe_identity_component(&mut bytes, common.as_slice());
|
||||
return std::result::Result::Ok(crate::YellowstoneSubscribeRequestIdentity { bytes });
|
||||
}
|
||||
|
||||
/// Validates all deterministic common subscribe-request bounds before any network I/O.
|
||||
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
||||
if self.total_filter_count() > MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT {
|
||||
@@ -3710,13 +3765,38 @@ fn commitment_to_wire(commitment: std::option::Option<crate::SolanaCommitment>)
|
||||
});
|
||||
}
|
||||
|
||||
impl std::default::Default for YellowstoneSubscribeRequest {
|
||||
fn append_subscribe_identity_component(output: &mut std::vec::Vec<u8>, value: &[u8]) {
|
||||
output.extend_from_slice(&(value.len() as u64).to_be_bytes());
|
||||
output.extend_from_slice(value);
|
||||
return;
|
||||
}
|
||||
|
||||
fn append_subscribe_identity_map<V, W, F>(
|
||||
output: &mut std::vec::Vec<u8>,
|
||||
family: &[u8],
|
||||
values: &std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, V>,
|
||||
mut to_wire: F,
|
||||
) where
|
||||
W: yellowstone_grpc_proto::prost::Message,
|
||||
F: FnMut(&V) -> W,
|
||||
{
|
||||
append_subscribe_identity_component(output, family);
|
||||
output.extend_from_slice(&(values.len() as u64).to_be_bytes());
|
||||
for (name, filter) in values {
|
||||
append_subscribe_identity_component(output, name.as_str().as_bytes());
|
||||
let wire = yellowstone_grpc_proto::prost::Message::encode_to_vec(&to_wire(filter));
|
||||
append_subscribe_identity_component(output, wire.as_slice());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
impl std::default::Default for crate::YellowstoneSubscribeRequest {
|
||||
fn default() -> Self {
|
||||
return Self::new();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneSubscribeRequest {
|
||||
impl std::fmt::Debug for crate::YellowstoneSubscribeRequest {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneSubscribeRequest")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 46
|
||||
// version: 47
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -215,6 +215,8 @@ pub use self::grpc_subscribe::YellowstoneSubscribePingUpdate;
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribePongUpdate;
|
||||
/// Provider-neutral standard Yellowstone Subscribe request.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeRequest;
|
||||
/// Opaque deterministic identity for one complete Yellowstone Subscribe request.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeRequestIdentity;
|
||||
/// Complete slot-family filter group for standard Yellowstone Subscribe.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeSlotFilter;
|
||||
/// Complete transaction-family filter shared by transactions and transaction-status maps.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
// version: 51
|
||||
// version: 52
|
||||
|
||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||
|
||||
@@ -1032,3 +1032,15 @@ fn public_v0_3_10_pre_004_observed_get_block_surface_is_available_from_crate_roo
|
||||
let _ = method;
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_v0_3_13_pre_002_yellowstone_subscribe_identity_is_opaque_and_available_from_crate_root() {
|
||||
let request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
|
||||
let identity = request.identity().expect("empty validated request identity must build");
|
||||
let _identity_type = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeRequestIdentity>();
|
||||
let debug = std::format!("{identity:?}");
|
||||
assert!(debug.contains("YellowstoneSubscribeRequestIdentity"));
|
||||
assert!(debug.contains("byte_len"));
|
||||
assert!(!debug.contains("bytes:"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 43
|
||||
// version: 44
|
||||
|
||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||
|
||||
@@ -1339,3 +1339,18 @@ fn release_v0_2_9_pre_010_adds_bounded_reconnect_replay_and_conservative_continu
|
||||
let _snapshot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot>();
|
||||
let _state = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_v0_3_13_pre_002_yellowstone_subscribe_identity_remains_opaque_and_dependency_neutral() {
|
||||
let source = include_str!("../src/grpc_subscribe.rs");
|
||||
let root = include_str!("../src/lib.rs");
|
||||
let identity_start = source.find("pub struct YellowstoneSubscribeRequestIdentity").expect("identity struct must remain present");
|
||||
let request_start = source.find("/// Provider-neutral standard Yellowstone subscribe request").expect("request contract marker must remain present");
|
||||
let identity_surface = &source[identity_start..request_start];
|
||||
assert!(source.contains("pub fn identity(&self)"));
|
||||
assert!(root.contains("pub use self::grpc_subscribe::YellowstoneSubscribeRequestIdentity;"));
|
||||
assert!(!identity_surface.contains("pub fn bytes("));
|
||||
assert!(!identity_surface.contains("pub fn as_bytes("));
|
||||
assert!(!root.contains("pub use yellowstone_grpc_proto"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
|
||||
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
|
||||
@@ -138,6 +138,37 @@ fn yellowstone_subscribe_common_bounds_reject_before_wire_conversion() {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user