142 lines
8.0 KiB
Rust
142 lines
8.0 KiB
Rust
// file: crates/ksp-job-backfill-lib/tests/http_block_material.rs
|
|
// version: 1
|
|
|
|
//! Cross-layer canary for the future Worker-owned HTTP block adapter without changing production ownership.
|
|
|
|
fn pool_for_url(url: &str) -> ksp_onchain_transport_lib::HttpTransportPool {
|
|
let role = ksp_onchain_transport_lib::HttpEndpointRoleSettings::new(
|
|
ksp_onchain_transport_lib::HttpRoleName::new("default"),
|
|
true,
|
|
std::vec![ksp_onchain_transport_lib::HttpRequestKind::wildcard()],
|
|
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-block-fixture",
|
|
true,
|
|
ksp_onchain_transport_lib::HttpProviderName::new("fixture-provider"),
|
|
ksp_onchain_transport_lib::HttpClusterName::new("devnet"),
|
|
ksp_onchain_transport_lib::HttpEndpointUrl::parse(url).expect("fixture URL must parse"),
|
|
std::time::Duration::from_secs(1),
|
|
std::time::Duration::from_secs(1),
|
|
std::option::Option::Some(1),
|
|
std::vec![role],
|
|
);
|
|
return ksp_onchain_transport_lib::HttpTransportPool::new(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)),
|
|
))
|
|
.expect("fixture pool must build");
|
|
}
|
|
|
|
fn serve_once(body: &'static str) -> (std::string::String, std::thread::JoinHandle<()>) {
|
|
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("fixture listener must bind");
|
|
let address = listener.local_addr().expect("fixture listener address must resolve");
|
|
let handle = std::thread::spawn(move || {
|
|
let (mut stream, _) = listener.accept().expect("fixture server must accept one request");
|
|
let mut bytes = std::vec::Vec::new();
|
|
let mut buffer = [0_u8; 1024];
|
|
loop {
|
|
let count = std::io::Read::read(&mut stream, &mut buffer).expect("fixture request must read");
|
|
if count == 0 {
|
|
break;
|
|
}
|
|
bytes.extend_from_slice(&buffer[..count]);
|
|
if bytes.windows(4).any(|window| return window == b"\r\n\r\n") {
|
|
break;
|
|
}
|
|
}
|
|
let response = format!("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", body.len(), body);
|
|
std::io::Write::write_all(&mut stream, response.as_bytes()).expect("fixture response must write");
|
|
return;
|
|
});
|
|
return (format!("http://{address}"), handle);
|
|
}
|
|
|
|
fn map_meta(field: &ksp_onchain_transport_lib::SolanaWireField<serde_json::Value>) -> ksp_raw_transaction_lib::RawTransactionWireField<serde_json::Value> {
|
|
return match field {
|
|
ksp_onchain_transport_lib::SolanaWireField::Omitted => ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
|
ksp_onchain_transport_lib::SolanaWireField::Null => ksp_raw_transaction_lib::RawTransactionWireField::Null,
|
|
ksp_onchain_transport_lib::SolanaWireField::Value(value) => ksp_raw_transaction_lib::RawTransactionWireField::Value(value.clone()),
|
|
};
|
|
}
|
|
|
|
fn map_version(
|
|
field: &ksp_onchain_transport_lib::SolanaWireField<ksp_onchain_transport_lib::SolanaTransactionVersion>,
|
|
) -> ksp_raw_transaction_lib::RawTransactionWireField<ksp_raw_transaction_lib::RawTransactionVersion> {
|
|
return match field {
|
|
ksp_onchain_transport_lib::SolanaWireField::Omitted => ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
|
ksp_onchain_transport_lib::SolanaWireField::Null => ksp_raw_transaction_lib::RawTransactionWireField::Null,
|
|
ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Legacy) => {
|
|
ksp_raw_transaction_lib::RawTransactionWireField::Value(ksp_raw_transaction_lib::RawTransactionVersion::Legacy)
|
|
},
|
|
ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Number(number)) => {
|
|
ksp_raw_transaction_lib::RawTransactionWireField::Value(ksp_raw_transaction_lib::RawTransactionVersion::Number(*number))
|
|
},
|
|
};
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn pre_004_http_observed_block_projects_each_base64_transaction_to_common_material_without_new_production_edge() {
|
|
const BODY: &str = concat!(
|
|
"{\"jsonrpc\":\"2.0\",\"result\":{\"previousBlockhash\":\"previous\",\"blockhash\":\"block\",\"parentSlot\":430000122,",
|
|
"\"rewards\":[],\"numRewardPartitions\":0,\"blockTime\":1787072400,\"blockHeight\":410000000,",
|
|
"\"transactions\":[{\"transaction\":[\"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
"AAAAAA\",\"base64\"],\"meta\":{\"err\":null,\"fee\":5000},\"version\":\"legacy\"},",
|
|
"{\"transaction\":[\"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEA\",",
|
|
"\"base64\"],\"meta\":{\"err\":null,\"fee\":6000},\"version\":0}]},\"id\":1}",
|
|
);
|
|
let (url, handle) = serve_once(BODY);
|
|
let pool = pool_for_url(url.as_str());
|
|
let config = ksp_onchain_transport_lib::SolanaGetBlockConfig::new(
|
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
|
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::Base64),
|
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Full),
|
|
std::option::Option::Some(0),
|
|
std::option::Option::Some(false),
|
|
);
|
|
let observed = pool
|
|
.get_block_observed(&ksp_onchain_transport_lib::HttpRoleName::new("default"), 430_000_123, std::option::Option::Some(&config))
|
|
.await
|
|
.expect("observed block fixture must succeed");
|
|
assert_eq!(observed.endpoint_name(), "http-block-fixture");
|
|
assert_eq!(observed.provider().as_str(), "fixture-provider");
|
|
let block = observed.value().as_ref().expect("fixture block must be present");
|
|
let transactions = block.transactions().value().expect("fixture transactions must be present");
|
|
assert_eq!(transactions.len(), 2);
|
|
let network = ksp_store_lib::RawNetworkId::new("devnet").expect("fixture network must be valid");
|
|
let expected_signatures = [[0_u8; 64], [1_u8; 64]];
|
|
for (index, transaction) in transactions.iter().enumerate() {
|
|
let (data, meta, version) = match transaction.transaction() {
|
|
ksp_onchain_transport_lib::SolanaEncodedTransaction::Binary {
|
|
data,
|
|
encoding: ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64,
|
|
} => (data.as_str(), map_meta(transaction.meta()), map_version(transaction.version())),
|
|
_ => panic!("fixture must remain full Base64 transaction material"),
|
|
};
|
|
let transaction_index = u32::try_from(index).expect("fixture transaction index must fit u32");
|
|
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64_with_embedded_signature(
|
|
network.clone(),
|
|
430_000_123,
|
|
block.block_time(),
|
|
data,
|
|
meta,
|
|
version,
|
|
ksp_raw_transaction_lib::RawTransactionWireField::Value(transaction_index),
|
|
)
|
|
.expect("HTTP block material must map to common RAW material");
|
|
let raw = ksp_raw_transaction_lib::canonicalize_raw_transaction(material).expect("mapped HTTP block material must canonicalize");
|
|
assert_eq!(raw.reference().signature().as_bytes(), &expected_signatures[index]);
|
|
assert_eq!(raw.slot(), 430_000_123);
|
|
assert_eq!(raw.block_time().map(|value| return value.unix_millis()), std::option::Option::Some(1_787_072_400_000));
|
|
assert!(raw.payload().bytes().ends_with(format!("\"transactionIndex\":{transaction_index}}}").as_bytes()));
|
|
}
|
|
handle.join().expect("fixture server must join");
|
|
return;
|
|
}
|