1373 lines
64 KiB
Rust
1373 lines
64 KiB
Rust
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
|
// version: 8
|
|
|
|
fn grpc_endpoint(cluster: &str) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
|
|
return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider");
|
|
}
|
|
|
|
fn grpc_endpoint_with_identity(
|
|
cluster: &str,
|
|
endpoint_name: &str,
|
|
provider: &str,
|
|
) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
|
|
let url = match ksp_onchain_transport_lib::YellowstoneGrpcEndpointUrl::parse("http://127.0.0.1:10000/GRPC-SECRET-CANARY") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
return std::option::Option::Some(ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings::new(
|
|
endpoint_name,
|
|
true,
|
|
ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new(provider),
|
|
ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new(cluster),
|
|
url,
|
|
ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings::default(),
|
|
));
|
|
}
|
|
|
|
fn http_pool(cluster: &str, role_name: &str, request_kind: &str) -> std::option::Option<ksp_onchain_transport_lib::HttpTransportPool> {
|
|
let url = match ksp_onchain_transport_lib::HttpEndpointUrl::parse("https://fixture.invalid/rpc?token=HTTP-SECRET-CANARY") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let role = ksp_onchain_transport_lib::HttpEndpointRoleSettings::new(
|
|
ksp_onchain_transport_lib::HttpRoleName::new(role_name),
|
|
true,
|
|
std::vec![ksp_onchain_transport_lib::HttpRequestKind::new(request_kind)],
|
|
10,
|
|
ksp_onchain_transport_lib::HttpRoleLimits::new(
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
),
|
|
);
|
|
let endpoint = ksp_onchain_transport_lib::HttpEndpointSettings::new(
|
|
"http-fixture",
|
|
true,
|
|
ksp_onchain_transport_lib::HttpProviderName::new("fixture-provider"),
|
|
ksp_onchain_transport_lib::HttpClusterName::new(cluster),
|
|
url,
|
|
std::time::Duration::from_secs(1),
|
|
std::time::Duration::from_secs(2),
|
|
std::option::Option::Some(4),
|
|
std::vec![role],
|
|
);
|
|
let settings = ksp_onchain_transport_lib::HttpTransportSettings::new(
|
|
std::vec![endpoint],
|
|
ksp_onchain_transport_lib::HttpRetrySettings::new(1, std::time::Duration::from_millis(10), std::time::Duration::from_millis(20)),
|
|
);
|
|
return match ksp_onchain_transport_lib::HttpTransportPool::new(settings) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn transaction_request(
|
|
commitment: std::option::Option<ksp_onchain_transport_lib::SolanaCommitment>,
|
|
) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneSubscribeRequest> {
|
|
let name = match ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("tx-fixture") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let mut request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
|
|
if request.insert_transaction_filter(name, ksp_onchain_transport_lib::YellowstoneSubscribeTransactionFilter::new()).is_err() {
|
|
return std::option::Option::None;
|
|
}
|
|
request.set_commitment(commitment);
|
|
return std::option::Option::Some(request);
|
|
}
|
|
|
|
struct SignalViewFixture {
|
|
created_at: std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp>,
|
|
family: super::RawTransactionIngestSourceFamily,
|
|
filters: std::vec::Vec<ksp_onchain_transport_lib::YellowstoneSubscribeFilterName>,
|
|
index: u64,
|
|
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature,
|
|
slot: u64,
|
|
}
|
|
|
|
impl super::RawTransactionIngestYellowstoneSignalView for SignalViewFixture {
|
|
fn created_at(&self) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp> {
|
|
return self.created_at;
|
|
}
|
|
|
|
fn family(&self) -> super::RawTransactionIngestSourceFamily {
|
|
return self.family;
|
|
}
|
|
|
|
fn filters(&self) -> &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName] {
|
|
return self.filters.as_slice();
|
|
}
|
|
|
|
fn index(&self) -> u64 {
|
|
return self.index;
|
|
}
|
|
|
|
fn signature(&self) -> ksp_onchain_transport_lib::YellowstoneTransactionSignature {
|
|
return self.signature;
|
|
}
|
|
|
|
fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
}
|
|
|
|
struct BlockViewFixture {
|
|
created_at: std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp>,
|
|
filters: std::vec::Vec<ksp_onchain_transport_lib::YellowstoneSubscribeFilterName>,
|
|
slot: u64,
|
|
transactions: std::vec::Vec<(ksp_onchain_transport_lib::YellowstoneTransactionSignature, u64)>,
|
|
}
|
|
|
|
impl super::RawTransactionIngestYellowstoneBlockView for BlockViewFixture {
|
|
fn created_at(&self) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp> {
|
|
return self.created_at;
|
|
}
|
|
|
|
fn filters(&self) -> &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName] {
|
|
return self.filters.as_slice();
|
|
}
|
|
|
|
fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
|
|
fn transaction_count(&self) -> usize {
|
|
return self.transactions.len();
|
|
}
|
|
|
|
fn transaction_identity(&self, position: usize) -> ksp_core_lib::Result<(ksp_onchain_transport_lib::YellowstoneTransactionSignature, u64)> {
|
|
return match self.transactions.get(position) {
|
|
std::option::Option::Some(value) => std::result::Result::Ok(*value),
|
|
std::option::Option::None => std::result::Result::Err(crate::runtime_error("fixture.block_transaction_missing")),
|
|
};
|
|
}
|
|
}
|
|
|
|
struct ContinuityViewFixture {
|
|
created_at: std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp>,
|
|
family: super::RawTransactionIngestContinuityFamily,
|
|
filters: std::vec::Vec<ksp_onchain_transport_lib::YellowstoneSubscribeFilterName>,
|
|
parent_slot: std::option::Option<u64>,
|
|
slot: u64,
|
|
status: std::option::Option<super::RawTransactionIngestContinuityStatus>,
|
|
}
|
|
|
|
impl super::RawTransactionIngestYellowstoneContinuityView for ContinuityViewFixture {
|
|
fn created_at(&self) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp> {
|
|
return self.created_at;
|
|
}
|
|
|
|
fn family(&self) -> super::RawTransactionIngestContinuityFamily {
|
|
return self.family;
|
|
}
|
|
|
|
fn filters(&self) -> &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName] {
|
|
return self.filters.as_slice();
|
|
}
|
|
|
|
fn parent_slot(&self) -> std::option::Option<u64> {
|
|
return self.parent_slot;
|
|
}
|
|
|
|
fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
|
|
fn status(&self) -> std::option::Option<super::RawTransactionIngestContinuityStatus> {
|
|
return self.status;
|
|
}
|
|
}
|
|
|
|
fn filter_names(values: &[&str]) -> std::option::Option<std::vec::Vec<ksp_onchain_transport_lib::YellowstoneSubscribeFilterName>> {
|
|
let mut filters = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let filter = match ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new(*value) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
filters.push(filter);
|
|
}
|
|
return std::option::Option::Some(filters);
|
|
}
|
|
|
|
fn signal_source() -> std::option::Option<crate::RawTransactionIngestYellowstoneSource> {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let request = match transaction_request(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let pool = match http_pool("devnet", "hydration", "get_transaction") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
return match crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration")) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
const PRE_004_OTHER_TRANSACTION_BASE64: &str = "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEA";
|
|
const PRE_004_ZERO_SIGNATURE_TEXT: &str = "1111111111111111111111111111111111111111111111111111111111111111";
|
|
const PRE_004_ZERO_TRANSACTION_BASE64: &str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
|
|
|
fn http_pool_for_url(url: &str, cluster: &str, endpoint_name: &str, provider: &str) -> std::option::Option<ksp_onchain_transport_lib::HttpTransportPool> {
|
|
let url = match ksp_onchain_transport_lib::HttpEndpointUrl::parse(url) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let role = ksp_onchain_transport_lib::HttpEndpointRoleSettings::new(
|
|
ksp_onchain_transport_lib::HttpRoleName::new("hydration"),
|
|
true,
|
|
std::vec![ksp_onchain_transport_lib::HttpRequestKind::new("get_transaction")],
|
|
10,
|
|
ksp_onchain_transport_lib::HttpRoleLimits::new(
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
),
|
|
);
|
|
let endpoint = ksp_onchain_transport_lib::HttpEndpointSettings::new(
|
|
endpoint_name,
|
|
true,
|
|
ksp_onchain_transport_lib::HttpProviderName::new(provider),
|
|
ksp_onchain_transport_lib::HttpClusterName::new(cluster),
|
|
url,
|
|
std::time::Duration::from_secs(1),
|
|
std::time::Duration::from_secs(1),
|
|
std::option::Option::Some(1),
|
|
std::vec![role],
|
|
);
|
|
let settings = ksp_onchain_transport_lib::HttpTransportSettings::new(
|
|
std::vec![endpoint],
|
|
ksp_onchain_transport_lib::HttpRetrySettings::new(0, std::time::Duration::from_millis(1), std::time::Duration::from_millis(1)),
|
|
);
|
|
return match ksp_onchain_transport_lib::HttpTransportPool::new(settings) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn signal_source_for_http_url(url: &str) -> std::option::Option<crate::RawTransactionIngestYellowstoneSource> {
|
|
return signal_source_for_http_url_with_commitment(url, ksp_onchain_transport_lib::SolanaCommitment::Confirmed);
|
|
}
|
|
|
|
fn signal_source_for_http_url_with_commitment(
|
|
url: &str,
|
|
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
|
) -> std::option::Option<crate::RawTransactionIngestYellowstoneSource> {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let request = match transaction_request(std::option::Option::Some(commitment)) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let pool = match http_pool_for_url(url, "devnet", "http-hydration-fixture", "fixture-http-provider") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
return match crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration")) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn pre_004_settings() -> std::option::Option<crate::RawTransactionIngestSettings> {
|
|
let network = match ksp_store_lib::RawNetworkId::new("devnet") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let worker_id = match ksp_worker_api::WorkerId::new("worker-fixture") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
return std::option::Option::Some(crate::RawTransactionIngestSettings::with_defaults(network, worker_id));
|
|
}
|
|
|
|
fn pre_004_signal(
|
|
source: &crate::RawTransactionIngestYellowstoneSource,
|
|
family: super::RawTransactionIngestSourceFamily,
|
|
slot: u64,
|
|
index: u64,
|
|
filters: &[&str],
|
|
signature_byte: u8,
|
|
) -> std::option::Option<super::RawTransactionIngestSourceSignal> {
|
|
let filters = match filter_names(filters) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let created_at = match ksp_onchain_transport_lib::YellowstoneUpdateTimestamp::new(1_760_000_123, 456_000_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let fixture = SignalViewFixture {
|
|
created_at: std::option::Option::Some(created_at),
|
|
family,
|
|
filters,
|
|
index,
|
|
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([signature_byte; 64]),
|
|
slot,
|
|
};
|
|
return std::option::Option::Some(super::project_yellowstone_signal(source, &fixture));
|
|
}
|
|
|
|
fn serve_http_once(body: std::string::String) -> std::io::Result<(std::string::String, std::thread::JoinHandle<std::io::Result<std::string::String>>)> {
|
|
let listener = match std::net::TcpListener::bind("127.0.0.1:0") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let address = match listener.local_addr() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let handle = std::thread::spawn(move || {
|
|
let (mut stream, _) = match listener.accept() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let request = match read_http_request(&mut stream) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let response =
|
|
std::format!("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", body.len(), body,);
|
|
if let std::result::Result::Err(error) = std::io::Write::write_all(&mut stream, response.as_bytes()) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
return std::result::Result::Ok(request);
|
|
});
|
|
return std::result::Result::Ok((std::format!("http://{address}"), handle));
|
|
}
|
|
|
|
fn read_http_request(stream: &mut std::net::TcpStream) -> std::io::Result<std::string::String> {
|
|
let mut bytes = std::vec::Vec::new();
|
|
let mut buffer = [0_u8; 1024];
|
|
loop {
|
|
let count = match std::io::Read::read(stream, &mut buffer) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if count == 0 {
|
|
break;
|
|
}
|
|
bytes.extend_from_slice(&buffer[..count]);
|
|
if http_request_complete(bytes.as_slice()) {
|
|
break;
|
|
}
|
|
}
|
|
return match std::string::String::from_utf8(bytes) {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(_) => std::result::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "fixture HTTP request is not UTF-8")),
|
|
};
|
|
}
|
|
|
|
fn http_request_complete(bytes: &[u8]) -> bool {
|
|
let text = match std::str::from_utf8(bytes) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return false,
|
|
};
|
|
let header_end = match text.find("\r\n\r\n") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return false,
|
|
};
|
|
let mut content_length = 0_usize;
|
|
for line in text[..header_end].lines() {
|
|
let (name, value) = match line.split_once(':') {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => continue,
|
|
};
|
|
if name.eq_ignore_ascii_case("content-length") {
|
|
content_length = match value.trim().parse::<usize>() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return false,
|
|
};
|
|
}
|
|
}
|
|
return bytes.len() >= header_end.saturating_add(4).saturating_add(content_length);
|
|
}
|
|
|
|
fn http_request_body(request: &str) -> &str {
|
|
return match request.split_once("\r\n\r\n") {
|
|
std::option::Option::Some((_, body)) => body,
|
|
std::option::Option::None => "",
|
|
};
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_002_source_accepts_matching_confirmed_transaction_and_get_transaction_route_without_io() {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match transaction_request(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let pool = match http_pool("devnet", "hydration", "get_transaction") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let source = crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration"));
|
|
assert!(source.is_ok());
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_002_source_rejects_processed_and_implicit_commitment() {
|
|
for commitment in [std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed), std::option::Option::None] {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match transaction_request(commitment) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let pool = match http_pool("devnet", "hydration", "get_transaction") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let source = crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration"));
|
|
let error = match source {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == "runtime_resources.hydration_commitment_invalid"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_002_source_requires_ingestion_bearing_filter_family() {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let mut request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
|
|
request.set_commitment(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized));
|
|
let pool = match http_pool("devnet", "hydration", "get_transaction") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let source = crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration"));
|
|
let error = match source {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == "runtime_resources.ingestion_filter_missing"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_002_source_rejects_missing_hydration_capability_and_cross_transport_network() {
|
|
for (http_cluster, request_kind, expected_condition) in [
|
|
("devnet", "get_balance", "runtime_resources.hydration_role_unsupported"),
|
|
("mainnet", "get_transaction", "runtime_resources.transport_network_mismatch"),
|
|
] {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match transaction_request(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let pool = match http_pool(http_cluster, "hydration", request_kind) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let source = crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration"));
|
|
let error = match source {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == expected_condition));
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_002_runtime_resource_debug_is_safe_and_exposes_no_client_inner() {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match transaction_request(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let pool = match http_pool("devnet", "hydration", "get_transaction") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let source = match crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration")) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let resources = crate::RawTransactionIngestRuntimeResources::new(source);
|
|
let debug = std::format!("{resources:?}");
|
|
assert!(debug.contains("RawTransactionIngestRuntimeResources"));
|
|
assert!(debug.contains("devnet"));
|
|
assert!(debug.contains("blocks_meta_filter_count"));
|
|
assert!(debug.contains("slot_filter_count"));
|
|
assert!(!debug.contains("GRPC-SECRET-CANARY"));
|
|
assert!(!debug.contains("HTTP-SECRET-CANARY"));
|
|
assert!(!debug.contains("127.0.0.1"));
|
|
assert!(!debug.contains("fixture.invalid"));
|
|
assert!(!debug.contains("tx-fixture"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_002_runtime_resources_reject_worker_network_mismatch_without_starting_source() {
|
|
let endpoint = match grpc_endpoint("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match transaction_request(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let pool = match http_pool("devnet", "hydration", "get_transaction") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let source = match crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration")) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let resources = crate::RawTransactionIngestRuntimeResources::new(source);
|
|
let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let error = match resources.validate_network(&network) {
|
|
std::result::Result::Ok(()) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == "runtime_resources.worker_network_mismatch"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_003_transaction_signal_fixture_preserves_exact_source_neutral_identity() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let filters = match filter_names(&["filter-b", "filter-a", "filter-a"]) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let created_at = match ksp_onchain_transport_lib::YellowstoneUpdateTimestamp::new(1_760_000_123, 456_000_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let fixture = SignalViewFixture {
|
|
created_at: std::option::Option::Some(created_at),
|
|
family: super::RawTransactionIngestSourceFamily::Transaction,
|
|
filters,
|
|
index: 7,
|
|
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([0xAB; 64]),
|
|
slot: 42,
|
|
};
|
|
let signal = super::project_yellowstone_signal(&source, &fixture);
|
|
assert_eq!(signal.family, super::RawTransactionIngestSourceFamily::Transaction);
|
|
assert_eq!(signal.network.as_str(), "devnet");
|
|
assert_eq!(signal.signature.as_bytes(), &[0xAB; 64]);
|
|
assert_eq!(signal.slot, 42);
|
|
assert_eq!(signal.transaction_index, std::option::Option::Some(7));
|
|
assert_eq!(signal.route.provider.as_str(), "fixture-provider");
|
|
assert_eq!(signal.route.endpoint_id.as_str(), "yellowstone-fixture");
|
|
assert_eq!(signal.matched_filter_count, 3);
|
|
assert_eq!(
|
|
signal.matched_filter_fingerprint,
|
|
[
|
|
0x20, 0x83, 0x1d, 0xc8, 0x37, 0x8b, 0x60, 0x70, 0xf9, 0x1d, 0x9d, 0xe6, 0xd3, 0xa1, 0xd2, 0xb3, 0xd7, 0x72, 0x98, 0xe3, 0xad, 0xa7, 0x6c, 0x01,
|
|
0xfa, 0xa3, 0x08, 0x26, 0xff, 0xab, 0xcd, 0x84,
|
|
]
|
|
);
|
|
assert_eq!(signal.created_at, std::option::Option::Some(super::RawTransactionIngestSourceTimestamp { nanos: 456_000_000, seconds: 1_760_000_123 }));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_003_transaction_status_signal_has_no_payload_or_remote_error_material() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let filters = match filter_names(&["status-secret-canary"]) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let fixture = SignalViewFixture {
|
|
created_at: std::option::Option::None,
|
|
family: super::RawTransactionIngestSourceFamily::TransactionStatus,
|
|
filters,
|
|
index: 9,
|
|
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([0xCD; 64]),
|
|
slot: 84,
|
|
};
|
|
let signal = super::project_yellowstone_signal(&source, &fixture);
|
|
assert_eq!(signal.family, super::RawTransactionIngestSourceFamily::TransactionStatus);
|
|
assert_eq!(signal.signature.as_bytes(), &[0xCD; 64]);
|
|
assert_eq!(signal.slot, 84);
|
|
assert_eq!(signal.transaction_index, std::option::Option::Some(9));
|
|
assert_eq!(signal.created_at, std::option::Option::None);
|
|
let debug = std::format!("{signal:?}");
|
|
assert!(!debug.contains("status-secret-canary"));
|
|
assert!(debug.contains("signature_bytes"));
|
|
assert!(!debug.contains("YellowstoneTransactionError"));
|
|
assert!(!debug.contains("transaction_body"));
|
|
assert!(!debug.contains("transaction_meta"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_003_filter_fingerprint_is_order_and_duplicate_insensitive() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let first_filters = match filter_names(&["filter-b", "filter-a", "filter-a"]) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let second_filters = match filter_names(&["filter-a", "filter-b"]) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let first = SignalViewFixture {
|
|
created_at: std::option::Option::None,
|
|
family: super::RawTransactionIngestSourceFamily::Transaction,
|
|
filters: first_filters,
|
|
index: 1,
|
|
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([1; 64]),
|
|
slot: 1,
|
|
};
|
|
let second = SignalViewFixture {
|
|
created_at: std::option::Option::None,
|
|
family: super::RawTransactionIngestSourceFamily::Transaction,
|
|
filters: second_filters,
|
|
index: 1,
|
|
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([1; 64]),
|
|
slot: 1,
|
|
};
|
|
let first_signal = super::project_yellowstone_signal(&source, &first);
|
|
let second_signal = super::project_yellowstone_signal(&source, &second);
|
|
assert_eq!(first_signal.matched_filter_fingerprint, second_signal.matched_filter_fingerprint);
|
|
assert_eq!(first_signal.matched_filter_count, 3);
|
|
assert_eq!(second_signal.matched_filter_count, 2);
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_003_source_rejects_route_identity_that_cannot_become_safe_provenance() {
|
|
for (endpoint_name, provider, expected_condition) in [
|
|
("yellowstone-fixture", "fixture/provider", "runtime_resources.yellowstone_provider_unrepresentable"),
|
|
("yellowstone/fixture", "fixture-provider", "runtime_resources.yellowstone_endpoint_unrepresentable"),
|
|
] {
|
|
let endpoint = match grpc_endpoint_with_identity("devnet", endpoint_name, provider) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match transaction_request(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let pool = match http_pool("devnet", "hydration", "get_transaction") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration"));
|
|
let error = match result {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == expected_condition));
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_005_block_projects_transaction_signals_in_exact_source_order() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let filters = match filter_names(&["block-filter-b", "block-filter-a"]) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let created_at = match ksp_onchain_transport_lib::YellowstoneUpdateTimestamp::new(1_760_000_321, 123_000_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let fixture = BlockViewFixture {
|
|
created_at: std::option::Option::Some(created_at),
|
|
filters,
|
|
slot: 777,
|
|
transactions: std::vec![
|
|
(ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([0x11; 64]), 9),
|
|
(ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([0x22; 64]), 3),
|
|
(ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([0x33; 64]), 17),
|
|
],
|
|
};
|
|
let signals = match super::project_yellowstone_block_signals(&source, &fixture) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(signals.len(), 3);
|
|
assert_eq!(signals[0].signature.as_bytes(), &[0x11; 64]);
|
|
assert_eq!(signals[1].signature.as_bytes(), &[0x22; 64]);
|
|
assert_eq!(signals[2].signature.as_bytes(), &[0x33; 64]);
|
|
assert_eq!(signals[0].transaction_index, std::option::Option::Some(9));
|
|
assert_eq!(signals[1].transaction_index, std::option::Option::Some(3));
|
|
assert_eq!(signals[2].transaction_index, std::option::Option::Some(17));
|
|
for signal in &signals {
|
|
assert_eq!(signal.family, super::RawTransactionIngestSourceFamily::Block);
|
|
assert_eq!(signal.slot, 777);
|
|
assert_eq!(signal.network.as_str(), "devnet");
|
|
assert_eq!(signal.matched_filter_count, 2);
|
|
assert_eq!(signal.created_at, std::option::Option::Some(super::RawTransactionIngestSourceTimestamp { nanos: 123_000_000, seconds: 1_760_000_321 }));
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_005_block_hydration_uses_distinct_composite_method() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Block, 42, 7, &["block-filter"], 0) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let provenance = match super::build_hydration_provenance(
|
|
&settings,
|
|
&signal,
|
|
"fixture-http-provider",
|
|
"http-hydration-fixture",
|
|
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
|
|
received_at,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(provenance.acquisition_method().as_str(), "block_get_transaction");
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_005_block_meta_and_slot_project_continuity_only_without_remote_dead_error() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let block_filters = match filter_names(&["block-meta-filter"]) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let slot_filters = match filter_names(&["slot-filter-secret", "slot-filter-secret"]) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let block_meta = ContinuityViewFixture {
|
|
created_at: std::option::Option::None,
|
|
family: super::RawTransactionIngestContinuityFamily::BlockMeta,
|
|
filters: block_filters,
|
|
parent_slot: std::option::Option::Some(899),
|
|
slot: 900,
|
|
status: std::option::Option::None,
|
|
};
|
|
let slot = ContinuityViewFixture {
|
|
created_at: std::option::Option::None,
|
|
family: super::RawTransactionIngestContinuityFamily::Slot,
|
|
filters: slot_filters,
|
|
parent_slot: std::option::Option::Some(900),
|
|
slot: 901,
|
|
status: std::option::Option::Some(super::RawTransactionIngestContinuityStatus::Dead),
|
|
};
|
|
let block_meta_signal = super::project_yellowstone_continuity_signal(&source, &block_meta);
|
|
let slot_signal = super::project_yellowstone_continuity_signal(&source, &slot);
|
|
assert_eq!(block_meta_signal.family, super::RawTransactionIngestContinuityFamily::BlockMeta);
|
|
assert_eq!(block_meta_signal.slot, 900);
|
|
assert_eq!(block_meta_signal.parent_slot, std::option::Option::Some(899));
|
|
assert_eq!(block_meta_signal.status, std::option::Option::None);
|
|
assert_eq!(slot_signal.family, super::RawTransactionIngestContinuityFamily::Slot);
|
|
assert_eq!(slot_signal.slot, 901);
|
|
assert_eq!(slot_signal.parent_slot, std::option::Option::Some(900));
|
|
assert_eq!(slot_signal.status, std::option::Option::Some(super::RawTransactionIngestContinuityStatus::Dead));
|
|
assert_eq!(slot_signal.matched_filter_count, 2);
|
|
let debug = std::format!("{slot_signal:?}");
|
|
assert!(!debug.contains("slot-filter-secret"));
|
|
assert!(!debug.contains("dead_error"));
|
|
assert!(!debug.contains("transaction"));
|
|
assert!(!debug.contains("meta"));
|
|
assert!(!debug.contains("payload"));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_005_slot_status_projection_is_exact_and_source_neutral() {
|
|
for (source, expected) in [
|
|
(ksp_onchain_transport_lib::YellowstoneSlotStatus::Processed, super::RawTransactionIngestContinuityStatus::Processed),
|
|
(ksp_onchain_transport_lib::YellowstoneSlotStatus::Confirmed, super::RawTransactionIngestContinuityStatus::Confirmed),
|
|
(ksp_onchain_transport_lib::YellowstoneSlotStatus::Finalized, super::RawTransactionIngestContinuityStatus::Finalized),
|
|
(ksp_onchain_transport_lib::YellowstoneSlotStatus::FirstShredReceived, super::RawTransactionIngestContinuityStatus::FirstShredReceived),
|
|
(ksp_onchain_transport_lib::YellowstoneSlotStatus::Completed, super::RawTransactionIngestContinuityStatus::Completed),
|
|
(ksp_onchain_transport_lib::YellowstoneSlotStatus::CreatedBank, super::RawTransactionIngestContinuityStatus::CreatedBank),
|
|
(ksp_onchain_transport_lib::YellowstoneSlotStatus::Dead, super::RawTransactionIngestContinuityStatus::Dead),
|
|
] {
|
|
assert_eq!(super::map_yellowstone_slot_status(source), expected);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_004_composite_provenance_rejects_overflow_without_truncation() {
|
|
let provider = match ksp_store_lib::RawProvenanceCode::new("yellowstone-provider") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let endpoint_id = match ksp_store_lib::RawProvenanceCode::new("yellowstone-endpoint") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let route = super::RawTransactionIngestSourceRoute { endpoint_id, provider };
|
|
let result = super::composite_provenance_codes(&route, "h".repeat(120).as_str(), "http-endpoint");
|
|
let error = match result {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == "runtime_resources.composite_provider_unrepresentable"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_004_observed_get_transaction_closes_signal_to_common_raw_ingress_with_composite_provenance() {
|
|
let body = std::format!(
|
|
"{{\"jsonrpc\":\"2.0\",\"result\":{{\"slot\":42,\"blockTime\":1760000120,\"transaction\":[\"{}\",\"base64\"],\"meta\":{{\"err\":null,\"fee\":5000}},\"version\":\"legacy\",\"transactionIndex\":7}},\"id\":1}}",
|
|
PRE_004_ZERO_TRANSACTION_BASE64,
|
|
);
|
|
let (url, server) = match serve_http_once(body) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = match signal_source_for_http_url(url.as_str()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 42, 7, &["tx-fixture"], 0) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let outcome = super::hydrate_yellowstone_signal(&source, &settings, signal, received_at).await;
|
|
assert!(outcome.is_ok());
|
|
let outcome = match outcome {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match server.join() {
|
|
std::result::Result::Ok(std::result::Result::Ok(value)) => value,
|
|
_ => return,
|
|
};
|
|
let request_body = http_request_body(request.as_str());
|
|
assert!(request_body.contains("\"method\":\"getTransaction\""));
|
|
assert!(request_body.contains(PRE_004_ZERO_SIGNATURE_TEXT));
|
|
assert!(request_body.contains("\"commitment\":\"confirmed\""));
|
|
assert!(request_body.contains("\"encoding\":\"base64\""));
|
|
assert!(request_body.contains("\"maxSupportedTransactionVersion\":0"));
|
|
let ingress = match outcome {
|
|
super::RawTransactionIngestHydrationOutcome::Available(value) => value,
|
|
super::RawTransactionIngestHydrationOutcome::Missing(_) => return,
|
|
};
|
|
assert_eq!(ingress.provenance.provider().as_str(), "ys.fixture-provider:http.fixture-http-provider");
|
|
assert_eq!(ingress.provenance.protocol().as_str(), "yellowstone_http");
|
|
assert_eq!(ingress.provenance.acquisition_method().as_str(), "transaction_get_transaction");
|
|
assert_eq!(ingress.provenance.origin(), ksp_store_lib::RawAcquisitionOrigin::Live);
|
|
assert_eq!(ingress.provenance.received_at(), received_at);
|
|
assert_eq!(
|
|
ingress.provenance.endpoint_id().map(ksp_store_lib::RawProvenanceCode::as_str),
|
|
std::option::Option::Some("ys.yellowstone-fixture:http.http-hydration-fixture"),
|
|
);
|
|
assert_eq!(ingress.provenance.commitment().map(ksp_store_lib::RawProvenanceCode::as_str), std::option::Option::Some("confirmed"));
|
|
assert_eq!(ingress.provenance.capture_session_id().map(ksp_store_lib::RawProvenanceCode::as_str), std::option::Option::Some("worker-fixture"));
|
|
assert_eq!(ingress.provenance.filter_id().map(ksp_store_lib::RawProvenanceCode::as_str), std::option::Option::Some("tx-fixture"));
|
|
assert_eq!(ingress.provenance.observed_at().map(|value| return value.unix_millis()), std::option::Option::Some(1_760_000_123_456));
|
|
let (mut admission, sender) = crate::RawTransactionAdmission::new(1);
|
|
if sender.send(*ingress).await.is_err() {
|
|
return;
|
|
}
|
|
std::mem::drop(sender);
|
|
let acquisition = match admission.receive(settings.network()).await {
|
|
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
|
_ => return,
|
|
};
|
|
assert_eq!(acquisition.transaction().reference().network().as_str(), "devnet");
|
|
assert_eq!(acquisition.transaction().reference().signature().as_bytes(), &[0_u8; 64]);
|
|
assert_eq!(acquisition.transaction().slot(), 42);
|
|
assert_eq!(acquisition.transaction().block_time().map(|value| return value.unix_millis()), std::option::Option::Some(1_760_000_120_000),);
|
|
assert_eq!(acquisition.observation().provenance().provider().as_str(), "ys.fixture-provider:http.fixture-http-provider");
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_004_finalized_hydration_preserves_request_and_provenance_commitment() {
|
|
let body = std::format!(
|
|
"{{\"jsonrpc\":\"2.0\",\"result\":{{\"slot\":42,\"blockTime\":1760000120,\"transaction\":[\"{}\",\"base64\"],\"meta\":null,\"version\":\"legacy\",\"transactionIndex\":7}},\"id\":1}}",
|
|
PRE_004_ZERO_TRANSACTION_BASE64,
|
|
);
|
|
let (url, server) = match serve_http_once(body) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = match signal_source_for_http_url_with_commitment(url.as_str(), ksp_onchain_transport_lib::SolanaCommitment::Finalized) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 42, 7, &["tx-fixture"], 0) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let outcome = match super::hydrate_yellowstone_signal(&source, &settings, signal, received_at).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match server.join() {
|
|
std::result::Result::Ok(std::result::Result::Ok(value)) => value,
|
|
_ => return,
|
|
};
|
|
let request_body = http_request_body(request.as_str());
|
|
assert!(request_body.contains("\"commitment\":\"finalized\""));
|
|
assert!(request_body.contains("\"encoding\":\"base64\""));
|
|
assert!(request_body.contains("\"maxSupportedTransactionVersion\":0"));
|
|
let ingress = match outcome {
|
|
super::RawTransactionIngestHydrationOutcome::Available(value) => value,
|
|
super::RawTransactionIngestHydrationOutcome::Missing(_) => return,
|
|
};
|
|
assert_eq!(ingress.provenance.commitment().map(ksp_store_lib::RawProvenanceCode::as_str), std::option::Option::Some("finalized"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_004_get_transaction_null_is_missing_without_ingress_or_worker_retry() {
|
|
let (url, server) = match serve_http_once("{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":1}".to_owned()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = match signal_source_for_http_url(url.as_str()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::TransactionStatus, 42, 7, &["status-fixture"], 0) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let outcome = match super::hydrate_yellowstone_signal(&source, &settings, signal, received_at).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let request = match server.join() {
|
|
std::result::Result::Ok(std::result::Result::Ok(value)) => value,
|
|
_ => return,
|
|
};
|
|
assert!(http_request_body(request.as_str()).contains(PRE_004_ZERO_SIGNATURE_TEXT));
|
|
let reference = match outcome {
|
|
super::RawTransactionIngestHydrationOutcome::Available(_) => return,
|
|
super::RawTransactionIngestHydrationOutcome::Missing(value) => value,
|
|
};
|
|
assert_eq!(reference.network().as_str(), "devnet");
|
|
assert_eq!(reference.signature().as_bytes(), &[0_u8; 64]);
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_004_v0_null_and_omitted_wire_fields_preserve_common_raw_semantics() {
|
|
let body = std::format!(
|
|
"{{\"jsonrpc\":\"2.0\",\"result\":{{\"slot\":42,\"blockTime\":null,\"transaction\":[\"{}\",\"base64\"],\"meta\":null,\"version\":0}},\"id\":1}}",
|
|
PRE_004_ZERO_TRANSACTION_BASE64,
|
|
);
|
|
let (url, server) = match serve_http_once(body) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = match signal_source_for_http_url(url.as_str()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 42, 7, &["filter-a", "filter-b"], 0) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let outcome = match super::hydrate_yellowstone_signal(&source, &settings, signal, received_at).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let _request = match server.join() {
|
|
std::result::Result::Ok(std::result::Result::Ok(value)) => value,
|
|
_ => return,
|
|
};
|
|
let ingress = match outcome {
|
|
super::RawTransactionIngestHydrationOutcome::Available(value) => value,
|
|
super::RawTransactionIngestHydrationOutcome::Missing(_) => return,
|
|
};
|
|
let filter_id = match ingress.provenance.filter_id() {
|
|
std::option::Option::Some(value) => value.as_str(),
|
|
std::option::Option::None => return,
|
|
};
|
|
assert!(filter_id.starts_with("sha256."));
|
|
assert_eq!(filter_id.len(), 71);
|
|
let (mut admission, sender) = crate::RawTransactionAdmission::new(1);
|
|
if sender.send(*ingress).await.is_err() {
|
|
return;
|
|
}
|
|
std::mem::drop(sender);
|
|
let acquisition = match admission.receive(settings.network()).await {
|
|
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
|
_ => return,
|
|
};
|
|
assert_eq!(acquisition.transaction().block_time(), std::option::Option::None);
|
|
let payload = match std::str::from_utf8(acquisition.transaction().payload().bytes()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert!(payload.contains("\"meta\":null"));
|
|
assert!(payload.contains("\"version\":0"));
|
|
assert!(!payload.contains("transactionIndex"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_004_source_hydration_rejects_slot_index_signature_and_network_mismatch_before_admission() {
|
|
for (slot, index, transaction_data, expected_condition) in [
|
|
(43_u64, 7_u32, PRE_004_ZERO_TRANSACTION_BASE64, "hydration.slot_mismatch"),
|
|
(42_u64, 8_u32, PRE_004_ZERO_TRANSACTION_BASE64, "hydration.transaction_index_mismatch"),
|
|
(42_u64, 7_u32, PRE_004_OTHER_TRANSACTION_BASE64, "hydration.signature_mismatch"),
|
|
] {
|
|
let body = std::format!(
|
|
"{{\"jsonrpc\":\"2.0\",\"result\":{{\"slot\":{},\"blockTime\":1760000120,\"transaction\":[\"{}\",\"base64\"],\"meta\":null,\"version\":\"legacy\",\"transactionIndex\":{}}},\"id\":1}}",
|
|
slot,
|
|
transaction_data,
|
|
index,
|
|
);
|
|
let (url, server) = match serve_http_once(body) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = match signal_source_for_http_url(url.as_str()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 42, 7, &["tx-fixture"], 0) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let error = match super::hydrate_yellowstone_signal(&source, &settings, signal, received_at).await {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
let _request = match server.join() {
|
|
std::result::Result::Ok(std::result::Result::Ok(value)) => value,
|
|
_ => return,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == expected_condition));
|
|
}
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let mut signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 42, 7, &["tx-fixture"], 0) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
signal.network = match ksp_store_lib::RawNetworkId::new("mainnet") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let error = match super::hydrate_yellowstone_signal(&source, &settings, signal, received_at).await {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.value() == "hydration.network_mismatch"));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_004_status_provenance_and_future_source_timestamp_are_bounded_and_redacted() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let mut signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::TransactionStatus, 42, 7, &["filter-b", "filter-a", "filter-a"], 0)
|
|
{
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
signal.created_at = std::option::Option::Some(super::RawTransactionIngestSourceTimestamp { seconds: 1_760_000_200, nanos: 0 });
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_124_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let provenance = match super::build_hydration_provenance(
|
|
&settings,
|
|
&signal,
|
|
"fixture-http-provider",
|
|
"http-hydration-fixture",
|
|
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
|
|
received_at,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(provenance.acquisition_method().as_str(), "status_get_transaction");
|
|
assert_eq!(provenance.observed_at(), std::option::Option::None);
|
|
let filter_id = match provenance.filter_id() {
|
|
std::option::Option::Some(value) => value.as_str(),
|
|
std::option::Option::None => return,
|
|
};
|
|
assert!(filter_id.starts_with("sha256."));
|
|
let debug = std::format!("{provenance:?}");
|
|
assert!(!debug.contains("filter-a"));
|
|
assert!(!debug.contains("filter-b"));
|
|
assert!(!debug.contains(PRE_004_ZERO_SIGNATURE_TEXT));
|
|
return;
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_006_coalescence_key_merges_transaction_and_status_before_http_fanout() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let settings = match pre_004_settings() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let transaction = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 91, 3, &["transaction-filter"], 0x44) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let status = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::TransactionStatus, 91, 3, &["status-filter"], 0x44) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let transaction_key = match super::hydration_key(&source, &transaction) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let status_key = match super::hydration_key(&source, &status) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert!(transaction_key == status_key);
|
|
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_200_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let mut coordinator = super::RawTransactionIngestHydrationCoordinator::new(&settings);
|
|
let (frontier_sender, _frontier_receiver) = tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty());
|
|
let mut processing_frontier = super::RawTransactionIngestProcessingFrontierReporter::new(frontier_sender);
|
|
if coordinator.queue_signal(&source, transaction, received_at, &mut processing_frontier).is_err() {
|
|
return;
|
|
}
|
|
if coordinator.queue_signal(&source, status, received_at, &mut processing_frontier).is_err() {
|
|
return;
|
|
}
|
|
assert_eq!(coordinator.pending.len(), 1);
|
|
assert_eq!(coordinator.pending_signal_count, 2);
|
|
assert_eq!(coordinator.tasks.len(), 1);
|
|
coordinator.abort_all().await;
|
|
assert_eq!(coordinator.pending_signal_count, 0);
|
|
assert!(coordinator.pending.is_empty());
|
|
assert!(coordinator.tasks.is_empty());
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_coalescence_key_separates_network_signature_and_commitment() {
|
|
let source = match signal_source() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let first = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 12, 1, &["same"], 0x10) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let second = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Block, 12, 1, &["same"], 0x11) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let first_key = match super::hydration_key(&source, &first) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let second_key = match super::hydration_key(&source, &second) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert!(first_key != second_key);
|
|
assert_eq!(first_key.commitment, "confirmed");
|
|
assert_eq!(first_key.network.as_str(), "devnet");
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_007_processing_frontier_never_advances_through_pending_work() {
|
|
let mut frontier = super::RawTransactionIngestProcessingFrontier::new();
|
|
assert!(frontier.observe_settled(100).is_ok());
|
|
let projection = frontier.projection();
|
|
assert_eq!(projection.hydration_pending(), 0);
|
|
assert_eq!(projection.processing_frontier_slot(), std::option::Option::Some(100));
|
|
assert_eq!(projection.oldest_pending_slot(), std::option::Option::None);
|
|
assert!(frontier.observe_pending(102).is_ok());
|
|
assert!(frontier.observe_settled(105).is_ok());
|
|
assert!(frontier.observe_pending(104).is_ok());
|
|
let projection = frontier.projection();
|
|
assert_eq!(projection.hydration_pending(), 2);
|
|
assert_eq!(projection.processing_frontier_slot(), std::option::Option::Some(100));
|
|
assert_eq!(projection.oldest_pending_slot(), std::option::Option::Some(102));
|
|
assert!(frontier.settle_pending(102).is_ok());
|
|
let projection = frontier.projection();
|
|
assert_eq!(projection.hydration_pending(), 1);
|
|
assert_eq!(projection.processing_frontier_slot(), std::option::Option::Some(102));
|
|
assert_eq!(projection.oldest_pending_slot(), std::option::Option::Some(104));
|
|
assert!(frontier.settle_pending(104).is_ok());
|
|
let projection = frontier.projection();
|
|
assert_eq!(projection.hydration_pending(), 0);
|
|
assert_eq!(projection.processing_frontier_slot(), std::option::Option::Some(105));
|
|
assert_eq!(projection.oldest_pending_slot(), std::option::Option::None);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_007_processing_frontier_compacts_settled_slots_to_pending_intervals() {
|
|
let mut frontier = super::RawTransactionIngestProcessingFrontier::new();
|
|
assert!(frontier.observe_pending(10).is_ok());
|
|
assert!(frontier.observe_pending(20).is_ok());
|
|
for slot in 0_u64..=1000_u64 {
|
|
if slot == 10 || slot == 20 {
|
|
continue;
|
|
}
|
|
assert!(frontier.observe_settled(slot).is_ok());
|
|
}
|
|
assert!(frontier.slot_state_count() <= 5);
|
|
let projection = frontier.projection();
|
|
assert_eq!(projection.hydration_pending(), 2);
|
|
assert_eq!(projection.oldest_pending_slot(), std::option::Option::Some(10));
|
|
assert_eq!(projection.processing_frontier_slot(), std::option::Option::Some(9));
|
|
return;
|
|
}
|