407 lines
17 KiB
Rust
407 lines
17 KiB
Rust
// file: kb_pipeline/src/solana_elgamal_registry_stateful.rs
|
|
// version: 3
|
|
|
|
//! Contextual SPL ElGamal registry account-state validation and materialization routing.
|
|
|
|
use std::str::FromStr; // rust-rules: trait-import
|
|
|
|
/// Exact byte length of one SPL ElGamal registry account.
|
|
pub const ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES: usize = 64;
|
|
|
|
/// One bounded SPL ElGamal registry RPC read request.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct ElGamalRegistryStatefulReadRequest {
|
|
/// Endpoint role used for the HTTP RPC request.
|
|
pub query_role: std::string::String,
|
|
/// Canonical registry PDA.
|
|
pub registry_account: kb_model::Pubkey,
|
|
/// Optional minimum RPC context slot.
|
|
pub min_context_slot: std::option::Option<u64>,
|
|
}
|
|
|
|
/// One bounded registry RPC read and validated administrative projection.
|
|
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
pub struct ElGamalRegistryStatefulReadResult {
|
|
/// Commitment used for the RPC read.
|
|
pub commitment: std::string::String,
|
|
/// Context slot returned by the endpoint.
|
|
pub context_slot: u64,
|
|
/// Complete validated registry snapshot.
|
|
pub snapshot: crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulSnapshot,
|
|
}
|
|
|
|
/// Reads, validates, and routes one exact SPL ElGamal registry account.
|
|
pub async fn read_elgamal_registry_stateful_snapshot(
|
|
pool: &kb_rpc::HttpEndpointPool,
|
|
request: &crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadRequest,
|
|
) -> kb_core::Result<crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadResult> {
|
|
if request.query_role.trim().is_empty() {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"SPL ElGamal registry stateful read query_role must not be empty",
|
|
));
|
|
}
|
|
let config = match kb_rpc::GetAccountInfoConfig::new_with_data(
|
|
kb_rpc::RpcCommitmentLevel::Confirmed,
|
|
request.min_context_slot,
|
|
crate::solana_elgamal_registry_stateful::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let result = match pool
|
|
.get_account_info_for_role(request.query_role.as_str(), &request.registry_account, &config)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result(
|
|
request,
|
|
&result,
|
|
);
|
|
}
|
|
|
|
/// Validates one complete RPC registry response before parsing and routing.
|
|
pub fn materialize_elgamal_registry_account_info_result(
|
|
request: &crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadRequest,
|
|
result: &kb_rpc::AccountInfoResult,
|
|
) -> kb_core::Result<crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadResult> {
|
|
if let std::option::Option::Some(min_context_slot) = request.min_context_slot {
|
|
if result.context.slot < min_context_slot {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"elgamal_registry_stateful_context_slot_too_old",
|
|
format!(
|
|
"SPL ElGamal registry context slot {} is below requested minimum {min_context_slot}",
|
|
result.context.slot
|
|
),
|
|
));
|
|
}
|
|
}
|
|
let account = match result.account.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"elgamal_registry_stateful_account_missing",
|
|
format!(
|
|
"SPL ElGamal registry account {} does not exist",
|
|
request.registry_account.0
|
|
),
|
|
));
|
|
},
|
|
};
|
|
if account.executable {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"elgamal_registry_stateful_account_executable",
|
|
format!(
|
|
"SPL ElGamal registry state account {} must not be executable",
|
|
request.registry_account.0
|
|
),
|
|
));
|
|
}
|
|
if account.owner.0 != kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"elgamal_registry_stateful_owner_mismatch",
|
|
format!(
|
|
"SPL ElGamal registry owner must be {}, got {}",
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
|
account.owner.0
|
|
),
|
|
));
|
|
}
|
|
if account.space
|
|
!= crate::solana_elgamal_registry_stateful::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES as u64
|
|
|| account.data.len()
|
|
!= crate::solana_elgamal_registry_stateful::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"elgamal_registry_stateful_account_length_invalid",
|
|
format!(
|
|
"SPL ElGamal registry account must report and return exactly {} bytes, got space {} and data {}",
|
|
crate::solana_elgamal_registry_stateful::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES,
|
|
account.space,
|
|
account.data.len()
|
|
),
|
|
));
|
|
}
|
|
let snapshot = match crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot(
|
|
request.registry_account.0.as_str(),
|
|
account.owner.0.as_str(),
|
|
result.context.slot,
|
|
account.data.as_slice(),
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"elgamal_registry_stateful_projection_failed",
|
|
error,
|
|
));
|
|
},
|
|
};
|
|
return std::result::Result::Ok(
|
|
crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadResult {
|
|
commitment: "confirmed".to_string(),
|
|
context_slot: result.context.slot,
|
|
snapshot,
|
|
},
|
|
);
|
|
}
|
|
|
|
/// One contextually validated SPL ElGamal registry state snapshot.
|
|
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
pub struct ElGamalRegistryStatefulSnapshot {
|
|
/// Canonical registry PDA identity.
|
|
pub registry_account_key: std::string::String,
|
|
/// Wallet address stored as the registry owner.
|
|
pub owner: std::string::String,
|
|
/// Context slot associated with the account read.
|
|
pub slot: u64,
|
|
/// Processor-owned administrative projection.
|
|
pub output: kb_materializer_api::MaterializedOutput,
|
|
}
|
|
|
|
/// Parse, validate the official owner-derived PDA, and materialize one registry snapshot.
|
|
pub fn materialize_elgamal_registry_stateful_snapshot(
|
|
registry_account_key: &str,
|
|
owner_program_id: &str,
|
|
slot: u64,
|
|
data: &[u8],
|
|
) -> std::result::Result<
|
|
crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulSnapshot,
|
|
String,
|
|
> {
|
|
if owner_program_id != kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID {
|
|
return std::result::Result::Err(format!(
|
|
"SPL ElGamal registry snapshot owner must be {}, got {owner_program_id}",
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID
|
|
));
|
|
}
|
|
let state = match kb_decoder_spl_elgamal_registry::state::parse_elgamal_registry_state(data) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let owner = match solana_pubkey::Pubkey::from_str(state.owner.as_str()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(format!(
|
|
"SPL ElGamal registry owner is not a valid address: {error}"
|
|
));
|
|
},
|
|
};
|
|
let program_id = match solana_pubkey::Pubkey::from_str(
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(format!(
|
|
"Configured SPL ElGamal registry program ID is invalid: {error}"
|
|
));
|
|
},
|
|
};
|
|
let expected =
|
|
spl_elgamal_registry_interface::get_elgamal_registry_address(&owner, &program_id);
|
|
if expected.to_string() != registry_account_key {
|
|
return std::result::Result::Err(format!(
|
|
"SPL ElGamal registry account {registry_account_key} does not match owner-derived PDA {expected}"
|
|
));
|
|
}
|
|
let output = match kb_materializer_admin::materialize_elgamal_registry_state_snapshot(
|
|
registry_account_key,
|
|
slot,
|
|
&state,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(
|
|
crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulSnapshot {
|
|
registry_account_key: registry_account_key.to_string(),
|
|
owner: state.owner,
|
|
slot,
|
|
output,
|
|
},
|
|
);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::str::FromStr; // rust-rules: trait-import
|
|
|
|
fn registry_fixture(owner: &solana_pubkey::Pubkey) -> (std::string::String, [u8; 64]) {
|
|
let program_id = match solana_pubkey::Pubkey::from_str(
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => solana_pubkey::Pubkey::default(),
|
|
};
|
|
let registry =
|
|
spl_elgamal_registry_interface::get_elgamal_registry_address(owner, &program_id);
|
|
let mut data = [0u8; 64];
|
|
data[..32].copy_from_slice(owner.as_ref());
|
|
data[32..].copy_from_slice(&[9u8; 32]);
|
|
return (registry.to_string(), data);
|
|
}
|
|
|
|
#[test]
|
|
fn exact_owner_derived_registry_pda_routes_one_admin_snapshot() {
|
|
let owner = solana_pubkey::Pubkey::new_from_array([7u8; 32]);
|
|
let (registry, data) =
|
|
crate::solana_elgamal_registry_stateful::tests::registry_fixture(&owner);
|
|
let snapshot =
|
|
crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot(
|
|
registry.as_str(),
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
|
55,
|
|
&data,
|
|
);
|
|
assert_eq!(
|
|
snapshot.as_ref().map(|value| return value.owner.clone()),
|
|
std::result::Result::Ok(owner.to_string())
|
|
);
|
|
assert_eq!(
|
|
snapshot
|
|
.as_ref()
|
|
.map(|value| return value.output.payload_json["finalAccountStateCaptured"].clone()),
|
|
std::result::Result::Ok(serde_json::json!(true))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn foreign_owner_program_wrong_pda_and_invalid_length_fail_closed() {
|
|
let owner = solana_pubkey::Pubkey::new_from_array([8u8; 32]);
|
|
let (registry, data) =
|
|
crate::solana_elgamal_registry_stateful::tests::registry_fixture(&owner);
|
|
let foreign =
|
|
crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot(
|
|
registry.as_str(),
|
|
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
|
55,
|
|
&data,
|
|
);
|
|
assert!(foreign.is_err());
|
|
let wrong =
|
|
crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot(
|
|
solana_pubkey::Pubkey::new_from_array([10u8; 32]).to_string().as_str(),
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
|
55,
|
|
&data,
|
|
);
|
|
assert!(wrong.is_err());
|
|
let invalid =
|
|
crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot(
|
|
registry.as_str(),
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
|
55,
|
|
&data[..63],
|
|
);
|
|
assert!(invalid.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn exact_rpc_registry_routes_after_context_owner_and_length_validation() {
|
|
let owner = solana_pubkey::Pubkey::new_from_array([13u8; 32]);
|
|
let (registry, data) =
|
|
crate::solana_elgamal_registry_stateful::tests::registry_fixture(&owner);
|
|
let request = crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadRequest {
|
|
query_role: "execution".to_string(),
|
|
registry_account: kb_model::Pubkey(registry),
|
|
min_context_slot: std::option::Option::Some(70),
|
|
};
|
|
let result = kb_rpc::AccountInfoResult {
|
|
context: kb_rpc::RpcResponseContext {
|
|
slot: 71,
|
|
api_version: std::option::Option::None,
|
|
},
|
|
account: std::option::Option::Some(kb_rpc::AccountInfoValue {
|
|
lamports: 1,
|
|
owner: kb_model::ProgramId(
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
|
|
),
|
|
executable: false,
|
|
rent_epoch: 0,
|
|
space: 64,
|
|
data: data.to_vec(),
|
|
}),
|
|
};
|
|
let materialized = crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result(
|
|
&request,
|
|
&result,
|
|
);
|
|
assert_eq!(
|
|
materialized.as_ref().map(|value| return value.context_slot),
|
|
std::result::Result::Ok(71)
|
|
);
|
|
assert_eq!(
|
|
materialized.as_ref().map(|value| return value.snapshot.owner.clone()),
|
|
std::result::Result::Ok(owner.to_string())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn stale_missing_foreign_executable_and_wrong_length_registry_reads_fail_closed() {
|
|
let owner = solana_pubkey::Pubkey::new_from_array([14u8; 32]);
|
|
let (registry, data) =
|
|
crate::solana_elgamal_registry_stateful::tests::registry_fixture(&owner);
|
|
let request = crate::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadRequest {
|
|
query_role: "execution".to_string(),
|
|
registry_account: kb_model::Pubkey(registry),
|
|
min_context_slot: std::option::Option::Some(80),
|
|
};
|
|
let base = kb_rpc::AccountInfoValue {
|
|
lamports: 1,
|
|
owner: kb_model::ProgramId(
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
|
|
),
|
|
executable: false,
|
|
rent_epoch: 0,
|
|
space: 64,
|
|
data: data.to_vec(),
|
|
};
|
|
let stale = kb_rpc::AccountInfoResult {
|
|
context: kb_rpc::RpcResponseContext {
|
|
slot: 79,
|
|
api_version: std::option::Option::None,
|
|
},
|
|
account: std::option::Option::Some(base.clone()),
|
|
};
|
|
assert!(crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result(&request, &stale).is_err());
|
|
let missing = kb_rpc::AccountInfoResult {
|
|
context: kb_rpc::RpcResponseContext {
|
|
slot: 80,
|
|
api_version: std::option::Option::None,
|
|
},
|
|
account: std::option::Option::None,
|
|
};
|
|
assert!(crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result(&request, &missing).is_err());
|
|
let mut foreign = base.clone();
|
|
foreign.owner = kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
|
|
let foreign = kb_rpc::AccountInfoResult {
|
|
context: kb_rpc::RpcResponseContext {
|
|
slot: 80,
|
|
api_version: std::option::Option::None,
|
|
},
|
|
account: std::option::Option::Some(foreign),
|
|
};
|
|
assert!(crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result(&request, &foreign).is_err());
|
|
let mut executable = base.clone();
|
|
executable.executable = true;
|
|
let executable = kb_rpc::AccountInfoResult {
|
|
context: kb_rpc::RpcResponseContext {
|
|
slot: 80,
|
|
api_version: std::option::Option::None,
|
|
},
|
|
account: std::option::Option::Some(executable),
|
|
};
|
|
assert!(crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result(&request, &executable).is_err());
|
|
let mut wrong_length = base;
|
|
wrong_length.data.pop();
|
|
let wrong_length = kb_rpc::AccountInfoResult {
|
|
context: kb_rpc::RpcResponseContext {
|
|
slot: 80,
|
|
api_version: std::option::Option::None,
|
|
},
|
|
account: std::option::Option::Some(wrong_length),
|
|
};
|
|
assert!(crate::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result(&request, &wrong_length).is_err());
|
|
}
|
|
}
|