716 lines
30 KiB
Rust
716 lines
30 KiB
Rust
// file: kb-pipeline-demo-scenarios/src/metadata_solana_program_devnet_execution.rs
|
|
// version: 1
|
|
|
|
//! Real Devnet simulation and controlled submission for Solana Program Metadata.
|
|
|
|
/// Complete request for one Solana Program Metadata Devnet execution.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct DevnetSolanaProgramMetadataExecutionRequest {
|
|
/// Stable caller-provided execution identifier.
|
|
pub intent_id: std::string::String,
|
|
/// Endpoint role used for reads, balance, blockhash and fee queries.
|
|
pub query_role: std::string::String,
|
|
/// Endpoint role used for simulation, submission and confirmation.
|
|
pub transaction_role: std::string::String,
|
|
/// Exact stable Solana Program Metadata operation.
|
|
pub operation: kb_lib::ExMetadataSpmOperation,
|
|
/// Bounded state reads required before execution.
|
|
pub preflight_reads: std::vec::Vec<kb_pipeline::SolanaProgramMetadataStatefulReadRequest>,
|
|
/// Explicit rent observations required by allocation and growth operations.
|
|
pub rent_observations: std::vec::Vec<kb_pipeline::SolanaProgramMetadataRentObservation>,
|
|
/// Bounded state reads repeated after confirmed submission.
|
|
pub postcondition_reads: std::vec::Vec<kb_pipeline::SolanaProgramMetadataStatefulReadRequest>,
|
|
/// Explicit approval for overwrite, destructive or irreversible operations.
|
|
pub allow_destructive_operation: bool,
|
|
/// Explicitly authorizes signing and submission after successful simulation.
|
|
pub submit: bool,
|
|
/// Explicit operator confirmation required for submission.
|
|
pub operator_confirmed: bool,
|
|
/// Materializes confirmed account snapshots after submission.
|
|
pub materialize_after_confirmation: bool,
|
|
}
|
|
|
|
impl crate::DevnetSolanaProgramMetadataExecutionRequest {
|
|
/// Creates a conservative simulation-only request.
|
|
pub fn new(
|
|
intent_id: impl std::convert::Into<std::string::String>,
|
|
operation: kb_lib::ExMetadataSpmOperation,
|
|
) -> Self {
|
|
return Self {
|
|
intent_id: intent_id.into(),
|
|
query_role: "http_queries".to_string(),
|
|
transaction_role: "http_transactions".to_string(),
|
|
operation,
|
|
preflight_reads: std::vec::Vec::new(),
|
|
rent_observations: std::vec::Vec::new(),
|
|
postcondition_reads: std::vec::Vec::new(),
|
|
allow_destructive_operation: false,
|
|
submit: false,
|
|
operator_confirmed: false,
|
|
materialize_after_confirmation: false,
|
|
};
|
|
}
|
|
|
|
/// Creates one conservative request from serialized typed operation JSON.
|
|
pub fn from_operation_json(
|
|
intent_id: impl std::convert::Into<std::string::String>,
|
|
operation_json: &str,
|
|
) -> kb_core::Result<Self> {
|
|
if operation_json.contains('<') || operation_json.trim() == "..." {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata operation JSON still contains a placeholder",
|
|
));
|
|
}
|
|
let operation = match serde_json::from_str::<kb_lib::ExMetadataSpmOperation>(operation_json)
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(kb_core::Error::config(format!(
|
|
"invalid typed Solana Program Metadata operation JSON: {error}"
|
|
)));
|
|
},
|
|
};
|
|
return std::result::Result::Ok(Self::new(intent_id, operation));
|
|
}
|
|
|
|
/// Validates request-local bounds independently from one profile.
|
|
pub fn validate(&self) -> kb_core::Result<()> {
|
|
if self.intent_id.trim().is_empty() {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata Devnet intent id must not be empty",
|
|
));
|
|
}
|
|
if self.query_role.trim().is_empty() || self.transaction_role.trim().is_empty() {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata endpoint roles must not be empty",
|
|
));
|
|
}
|
|
if self.preflight_reads.is_empty()
|
|
|| self.preflight_reads.len()
|
|
> kb_pipeline::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS
|
|
|| self.postcondition_reads.len()
|
|
> kb_pipeline::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata stateful read inventory is empty or above the compiled bound",
|
|
));
|
|
}
|
|
if self.operation.requires_explicit_approval() && !self.allow_destructive_operation {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_metadata_spm_destructive_approval_required",
|
|
"destructive Solana Program Metadata execution requires explicit approval",
|
|
));
|
|
}
|
|
if self.submit && self.postcondition_reads.is_empty() {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"submitted Solana Program Metadata execution requires postcondition reads",
|
|
));
|
|
}
|
|
if self.materialize_after_confirmation && self.postcondition_reads.is_empty() {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata materialization requires postcondition reads",
|
|
));
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
}
|
|
|
|
/// Complete evidence produced by one Solana Program Metadata Devnet execution.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct DevnetSolanaProgramMetadataExecutionSummary {
|
|
/// Profile used by the orchestration.
|
|
pub profile_name: std::string::String,
|
|
/// Exact classified cluster.
|
|
pub cluster: kb_lib::ExApiExecutionCluster,
|
|
/// Genesis hash returned by the selected endpoint.
|
|
pub genesis_hash: std::string::String,
|
|
/// Non-secret persistent wallet description.
|
|
pub wallet: kb_wallet::WalletSummary,
|
|
/// Wallet balance observed before planning.
|
|
pub balance_lamports: u64,
|
|
/// Stateful snapshots observed before simulation.
|
|
pub before: std::vec::Vec<kb_pipeline::SolanaProgramMetadataStatefulReadResult>,
|
|
/// Stateful preflight report bound to the exact plan.
|
|
pub stateful_preflight: kb_pipeline::SolanaProgramMetadataPreflightReport,
|
|
/// Exact prepared plan.
|
|
pub plan: kb_lib::ExApiPreparedExecutionPlan,
|
|
/// Recent blockhash used by the exact transaction.
|
|
pub latest_blockhash: kb_onchain_transport::LatestBlockhashResult,
|
|
/// Fee estimate for the exact compiled message.
|
|
pub fee: kb_onchain_transport::FeeForMessageResult,
|
|
/// Exact real RPC simulation result.
|
|
pub simulation: kb_lib::ExApiExecutionSimulationResult,
|
|
/// Readiness report proving exact-message simulation and signer resolution.
|
|
pub readiness: kb_pipeline::SolanaProgramMetadataExecutionReadinessReport,
|
|
/// Submission result when explicitly authorized.
|
|
pub send_result: std::option::Option<kb_lib::ExApiExecutionSendResult>,
|
|
/// Confirmation result when submitted.
|
|
pub confirmation: std::option::Option<kb_lib::ExApiExecutionConfirmationResult>,
|
|
/// Stateful snapshots observed after confirmed submission.
|
|
pub after: std::vec::Vec<kb_pipeline::SolanaProgramMetadataStatefulReadResult>,
|
|
/// Stateful postcondition report after confirmed submission.
|
|
pub post_execution: std::option::Option<kb_pipeline::SolanaProgramMetadataPostExecutionReport>,
|
|
/// Canonical projections emitted from confirmed account snapshots.
|
|
pub materialized_snapshots: std::vec::Vec<kb_lib::MtApiMaterializedOutput>,
|
|
}
|
|
|
|
struct PreparedSolanaProgramMetadataExecution {
|
|
wallet: kb_wallet::TemporaryWallet,
|
|
unsigned: kb_lib::ExSolanaUnsignedTransaction,
|
|
evidence: kb_lib::ExSolanaSimulationEvidence,
|
|
summary: crate::DevnetSolanaProgramMetadataExecutionSummary,
|
|
}
|
|
|
|
/// Simulates one stable Solana Program Metadata operation against Devnet.
|
|
pub async fn simulate_devnet_solana_program_metadata<O>(
|
|
http_pool: &kb_onchain_transport::HttpEndpointPool,
|
|
profile: &kb_config::ProfileConfig,
|
|
workspace_root: &std::path::Path,
|
|
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
|
|
observer: &O,
|
|
) -> kb_core::Result<crate::DevnetSolanaProgramMetadataExecutionSummary>
|
|
where
|
|
O: crate::SolanaExecutionObserver,
|
|
{
|
|
if request.submit {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"simulate_devnet_solana_program_metadata requires submit=false",
|
|
));
|
|
}
|
|
let prepared =
|
|
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(prepared.summary);
|
|
}
|
|
|
|
/// Executes one Solana Program Metadata simulation or authorized submission.
|
|
pub async fn execute_devnet_solana_program_metadata<O>(
|
|
http_pool: &kb_onchain_transport::HttpEndpointPool,
|
|
profile: &kb_config::ProfileConfig,
|
|
workspace_root: &std::path::Path,
|
|
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
|
|
observer: &O,
|
|
) -> kb_core::Result<crate::DevnetSolanaProgramMetadataExecutionSummary>
|
|
where
|
|
O: crate::SolanaExecutionObserver,
|
|
{
|
|
let prepared =
|
|
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if !request.submit {
|
|
return std::result::Result::Ok(prepared.summary);
|
|
}
|
|
if !prepared.summary.simulation.success {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_simulation_failed",
|
|
crate::simulation_failure_message(&prepared.summary.simulation),
|
|
));
|
|
}
|
|
if let std::result::Result::Err(error) = validate_profile_wallet_signers(
|
|
prepared.unsigned.required_signer_pubkeys(),
|
|
prepared.summary.wallet.public_key.as_str(),
|
|
) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let send_evaluation = match kb_lib::ExSafetyChecker
|
|
.evaluate_send(&prepared.summary.plan, &prepared.summary.simulation)
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if send_evaluation.decision == kb_lib::ExSafetyDecision::Deny {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_send_denied",
|
|
crate::violation_message(send_evaluation.violations.as_slice()),
|
|
));
|
|
}
|
|
let signed = match prepared
|
|
.unsigned
|
|
.sign_after_simulation(&prepared.evidence, &[prepared.wallet.as_signer()])
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if let std::result::Result::Err(error) = signed.verify_signatures() {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let signature = signed.primary_signature().clone();
|
|
let mut summary = prepared.summary;
|
|
let send_config = match kb_onchain_transport::SendTransactionConfig::from_execution_config(
|
|
&profile.execution,
|
|
std::option::Option::Some(summary.latest_blockhash.context.slot),
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let sent = match http_pool
|
|
.send_transaction_for_role(
|
|
request.transaction_role.as_str(),
|
|
signed.transaction_base64().as_str(),
|
|
&signature,
|
|
&send_config,
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
summary.send_result =
|
|
std::option::Option::Some(sent.to_execution_result(kb_lib::ExApiExecutionCluster::Devnet));
|
|
let confirmation_config =
|
|
match kb_onchain_transport::ConfirmTransactionConfig::from_execution_config(
|
|
&profile.execution,
|
|
std::option::Option::Some(summary.latest_blockhash.last_valid_block_height),
|
|
std::option::Option::Some(summary.latest_blockhash.context.slot),
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let confirmation = match http_pool
|
|
.confirm_transaction_for_roles(
|
|
request.transaction_role.as_str(),
|
|
request.query_role.as_str(),
|
|
kb_lib::ExApiExecutionCluster::Devnet,
|
|
&signature,
|
|
&confirmation_config,
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let confirmed = matches!(
|
|
confirmation.status,
|
|
kb_lib::ExApiExecutionConfirmationStatus::Confirmed
|
|
| kb_lib::ExApiExecutionConfirmationStatus::Finalized
|
|
);
|
|
summary.confirmation = std::option::Option::Some(confirmation);
|
|
if confirmed {
|
|
summary.after = match read_snapshots(http_pool, &request.postcondition_reads).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let report = match kb_pipeline::inspect_solana_program_metadata_post_execution(
|
|
&kb_pipeline::SolanaProgramMetadataPostExecutionRequest {
|
|
operation: request.operation.clone(),
|
|
before: summary.before.clone(),
|
|
after: summary.after.clone(),
|
|
},
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if report.status == kb_pipeline::SolanaProgramMetadataPostconditionStatus::Contradicted {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_metadata_spm_postcondition_contradicted",
|
|
"confirmed Solana Program Metadata transaction contradicted its stateful postcondition",
|
|
));
|
|
}
|
|
summary.post_execution = std::option::Option::Some(report);
|
|
if request.materialize_after_confirmation {
|
|
summary.materialized_snapshots =
|
|
materialize_confirmed_snapshots(summary.after.as_slice());
|
|
}
|
|
}
|
|
return std::result::Result::Ok(summary);
|
|
}
|
|
|
|
async fn prepare_execution<O>(
|
|
http_pool: &kb_onchain_transport::HttpEndpointPool,
|
|
profile: &kb_config::ProfileConfig,
|
|
workspace_root: &std::path::Path,
|
|
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
|
|
observer: &O,
|
|
) -> kb_core::Result<PreparedSolanaProgramMetadataExecution>
|
|
where
|
|
O: crate::SolanaExecutionObserver,
|
|
{
|
|
if let std::result::Result::Err(error) = request.validate() {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) = validate_profile(profile, request) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) =
|
|
crate::ensure_not_cancelled(observer, "solana_program_metadata_validate")
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let genesis = match http_pool.get_genesis_hash_for_role(request.query_role.as_str()).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if genesis.classified_cluster
|
|
!= std::option::Option::Some(kb_lib::ExApiExecutionCluster::Devnet)
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_cluster_mismatch",
|
|
format!(
|
|
"expected Devnet genesis hash but endpoint returned {} classified as {:?}",
|
|
genesis.genesis_hash, genesis.classified_cluster
|
|
),
|
|
));
|
|
}
|
|
let wallet = match crate::load_profile_wallet(profile, workspace_root).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let wallet_summary = wallet.summary();
|
|
let fee_payer = kb_lib::MdPubkey(wallet_summary.public_key.clone());
|
|
let balance = match http_pool
|
|
.get_balance_for_role(
|
|
request.query_role.as_str(),
|
|
&fee_payer,
|
|
&kb_onchain_transport::GetBalanceConfig::confirmed(),
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if balance.lamports < profile.execution.max_fee_lamports {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_balance_insufficient",
|
|
"Devnet wallet balance is below the configured fee ceiling",
|
|
));
|
|
}
|
|
let intent = build_intent(profile, request, fee_payer.clone());
|
|
let plan = match kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
|
|
&kb_lib::ExMetadataSolanaProgramMetadataExecutor,
|
|
&intent,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let plan_evaluation = match kb_lib::ExSafetyChecker.evaluate_prepared_plan(&plan) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if plan_evaluation.decision == kb_lib::ExSafetyDecision::Deny {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_metadata_spm_plan_denied",
|
|
crate::violation_message(plan_evaluation.violations.as_slice()),
|
|
));
|
|
}
|
|
let before = match read_snapshots(http_pool, &request.preflight_reads).await {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let stateful_preflight = match kb_pipeline::inspect_solana_program_metadata_preflight(
|
|
&kb_pipeline::SolanaProgramMetadataPreflightRequest {
|
|
intent: intent.clone(),
|
|
plan: plan.clone(),
|
|
before: before.clone(),
|
|
rent_observations: request.rent_observations.clone(),
|
|
},
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let latest_blockhash = match http_pool
|
|
.get_latest_blockhash_for_role(
|
|
request.query_role.as_str(),
|
|
&kb_onchain_transport::GetLatestBlockhashConfig::confirmed(),
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let unsigned = match kb_lib::executor_solana_build_legacy_transaction(
|
|
&plan,
|
|
latest_blockhash.blockhash.as_str(),
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if let std::result::Result::Err(error) = validate_profile_wallet_signers(
|
|
unsigned.required_signer_pubkeys(),
|
|
wallet_summary.public_key.as_str(),
|
|
) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let fee = match http_pool
|
|
.get_fee_for_message_for_role(
|
|
request.query_role.as_str(),
|
|
unsigned.message_base64().as_str(),
|
|
&kb_onchain_transport::GetFeeForMessageConfig::new(
|
|
kb_onchain_transport::RpcCommitmentLevel::Confirmed,
|
|
std::option::Option::Some(latest_blockhash.context.slot),
|
|
),
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let unsigned_base64 = match unsigned.transaction_base64() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let simulation_config = match kb_onchain_transport::SimulateTransactionConfig::new(
|
|
kb_onchain_transport::RpcCommitmentLevel::Confirmed,
|
|
false,
|
|
false,
|
|
std::option::Option::Some(latest_blockhash.context.slot),
|
|
true,
|
|
std::option::Option::None,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
crate::emit(
|
|
observer,
|
|
crate::SolanaExecutionProgressLevel::Info,
|
|
"solana_program_metadata_simulation",
|
|
format!("simulating exact Solana Program Metadata message {}", unsigned.message_hash()),
|
|
std::option::Option::None,
|
|
);
|
|
let simulation_rpc = match http_pool
|
|
.simulate_transaction_for_role(
|
|
request.transaction_role.as_str(),
|
|
unsigned_base64.as_str(),
|
|
&simulation_config,
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let simulation = simulation_rpc.to_execution_result(
|
|
kb_lib::ExApiExecutionCluster::Devnet,
|
|
kb_lib::ExApiExecutionBlockhashKind::Latest,
|
|
std::option::Option::Some(
|
|
simulation_rpc.context.slot.saturating_sub(latest_blockhash.context.slot),
|
|
),
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
std::option::Option::Some(&fee),
|
|
);
|
|
let readiness = match kb_pipeline::validate_solana_program_metadata_execution_readiness(
|
|
&kb_pipeline::SolanaProgramMetadataExecutionReadinessRequest {
|
|
plan: plan.clone(),
|
|
preflight: stateful_preflight.clone(),
|
|
message_hash: unsigned.message_hash().to_string(),
|
|
simulated_message_hash: unsigned.message_hash().to_string(),
|
|
simulated: true,
|
|
simulation_succeeded: simulation.success,
|
|
resolved_signers: vec![fee_payer],
|
|
submit: request.submit,
|
|
operator_confirmed: request.operator_confirmed,
|
|
},
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
let simulation_json = match serde_json::to_string_pretty(&simulation) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => "<simulation serialization unavailable>".to_string(),
|
|
};
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
error.code(),
|
|
format!("{}; simulation={simulation_json}", error.message()),
|
|
));
|
|
},
|
|
};
|
|
let evidence = unsigned.bind_simulation(simulation.clone());
|
|
return std::result::Result::Ok(PreparedSolanaProgramMetadataExecution {
|
|
wallet,
|
|
unsigned,
|
|
evidence,
|
|
summary: crate::DevnetSolanaProgramMetadataExecutionSummary {
|
|
profile_name: profile.name.clone(),
|
|
cluster: kb_lib::ExApiExecutionCluster::Devnet,
|
|
genesis_hash: genesis.genesis_hash,
|
|
wallet: wallet_summary,
|
|
balance_lamports: balance.lamports,
|
|
before,
|
|
stateful_preflight,
|
|
plan,
|
|
latest_blockhash,
|
|
fee,
|
|
simulation,
|
|
readiness,
|
|
send_result: std::option::Option::None,
|
|
confirmation: std::option::Option::None,
|
|
after: std::vec::Vec::new(),
|
|
post_execution: std::option::Option::None,
|
|
materialized_snapshots: std::vec::Vec::new(),
|
|
},
|
|
});
|
|
}
|
|
|
|
fn build_intent(
|
|
profile: &kb_config::ProfileConfig,
|
|
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
|
|
fee_payer: kb_lib::MdPubkey,
|
|
) -> kb_lib::ExMetadataSpmExecutionIntent {
|
|
return kb_lib::ExMetadataSpmExecutionIntent {
|
|
intent_id: request.intent_id.clone(),
|
|
fee_payer: fee_payer.clone(),
|
|
policy: kb_lib::ExApiExecutionPolicy {
|
|
cluster: kb_lib::ExApiExecutionClusterPolicy {
|
|
expected_cluster: kb_lib::ExApiExecutionCluster::Devnet,
|
|
allow_mainnet: false,
|
|
mainnet_confirmation: false,
|
|
},
|
|
simulation: kb_lib::ExApiExecutionSimulationPolicy::Required,
|
|
blockhash: kb_lib::ExApiExecutionBlockhashPolicy {
|
|
kind: kb_lib::ExApiExecutionBlockhashKind::Latest,
|
|
max_age_slots: std::option::Option::Some(
|
|
profile.execution.recent_blockhash_max_age_slots,
|
|
),
|
|
nonce_account: std::option::Option::None,
|
|
nonce_authority: std::option::Option::None,
|
|
},
|
|
cost_limit: kb_lib::ExApiExecutionCostLimit {
|
|
max_spend_lamports: std::option::Option::Some(
|
|
profile.execution.devnet_max_spend_lamports,
|
|
),
|
|
max_fee_lamports: std::option::Option::Some(profile.execution.max_fee_lamports),
|
|
max_compute_unit_price_micro_lamports: std::option::Option::Some(
|
|
profile.execution.max_compute_unit_price_micro_lamports,
|
|
),
|
|
},
|
|
authorized_signers: vec![fee_payer],
|
|
dry_run: !request.submit,
|
|
post_execution_validation: kb_lib::ExApiPostExecutionValidationPolicy {
|
|
canonical_insert_required: true,
|
|
core_extraction_required: true,
|
|
decode_replay_required: true,
|
|
materialization_required: true,
|
|
},
|
|
},
|
|
allow_destructive_operation: request.allow_destructive_operation,
|
|
operation: request.operation.clone(),
|
|
};
|
|
}
|
|
|
|
async fn read_snapshots(
|
|
http_pool: &kb_onchain_transport::HttpEndpointPool,
|
|
requests: &[kb_pipeline::SolanaProgramMetadataStatefulReadRequest],
|
|
) -> kb_core::Result<std::vec::Vec<kb_pipeline::SolanaProgramMetadataStatefulReadResult>> {
|
|
let mut results = std::vec::Vec::with_capacity(requests.len());
|
|
for request in requests {
|
|
let value =
|
|
match kb_pipeline::read_solana_program_metadata_stateful_snapshot(http_pool, request)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
results.push(value);
|
|
}
|
|
return std::result::Result::Ok(results);
|
|
}
|
|
|
|
fn materialize_confirmed_snapshots(
|
|
snapshots: &[kb_pipeline::SolanaProgramMetadataStatefulReadResult],
|
|
) -> std::vec::Vec<kb_lib::MtApiMaterializedOutput> {
|
|
return snapshots
|
|
.iter()
|
|
.filter_map(|result| return result.materialized_output.clone())
|
|
.collect();
|
|
}
|
|
|
|
fn validate_profile(
|
|
profile: &kb_config::ProfileConfig,
|
|
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
|
|
) -> kb_core::Result<()> {
|
|
if profile.wallet.cluster != "devnet" {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata orchestration requires a Devnet wallet profile",
|
|
));
|
|
}
|
|
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata orchestration requires an enabled persistent temporary wallet",
|
|
));
|
|
}
|
|
if !profile.execution.require_simulation {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata orchestration requires simulation",
|
|
));
|
|
}
|
|
if request.submit && !profile.wallet.devnet_send_enabled {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Devnet transaction submission is disabled by the wallet profile",
|
|
));
|
|
}
|
|
if request.submit
|
|
&& profile.execution.require_operator_confirmation
|
|
&& !request.operator_confirmed
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Solana Program Metadata submission requires explicit operator confirmation",
|
|
));
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn validate_profile_wallet_signers(
|
|
required_signers: &[std::string::String],
|
|
wallet_pubkey: &str,
|
|
) -> kb_core::Result<()> {
|
|
if required_signers.iter().any(|value| return value.as_str() != wallet_pubkey) {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"execution_metadata_spm_external_signer_unavailable",
|
|
format!(
|
|
"the Devnet Solana Program Metadata orchestrator can sign only with profile wallet {}; required signers are {}",
|
|
wallet_pubkey,
|
|
required_signers.join(",")
|
|
),
|
|
));
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn wallet() -> kb_lib::MdPubkey {
|
|
return kb_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([7_u8; 32]).to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn request_rejects_destructive_operations_without_approval() {
|
|
let wallet = wallet();
|
|
let mut request = crate::DevnetSolanaProgramMetadataExecutionRequest::new(
|
|
"write",
|
|
kb_lib::ExMetadataSpmOperation::Write {
|
|
buffer: wallet.clone(),
|
|
authority: wallet,
|
|
offset: 0,
|
|
source: kb_lib::ExMetadataSpmWriteSource::Inline { data: vec![1] },
|
|
},
|
|
);
|
|
request
|
|
.preflight_reads
|
|
.push(kb_pipeline::SolanaProgramMetadataStatefulReadRequest {
|
|
query_role: "http_queries".to_string(),
|
|
account: request.operation_target_for_test(),
|
|
expected_state: kb_pipeline::SolanaProgramMetadataExpectedAccountState::Buffer,
|
|
min_context_slot: std::option::Option::None,
|
|
max_data_bytes: kb_pipeline::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES,
|
|
});
|
|
assert!(request.validate().is_err());
|
|
request.allow_destructive_operation = true;
|
|
assert!(request.validate().is_ok());
|
|
}
|
|
|
|
trait OperationTargetForTest {
|
|
fn operation_target_for_test(&self) -> kb_lib::MdPubkey;
|
|
}
|
|
|
|
impl OperationTargetForTest for crate::DevnetSolanaProgramMetadataExecutionRequest {
|
|
fn operation_target_for_test(&self) -> kb_lib::MdPubkey {
|
|
return match &self.operation {
|
|
kb_lib::ExMetadataSpmOperation::Write { buffer, .. } => buffer.clone(),
|
|
_ => kb_lib::MdPubkey(kb_program_ids::SYSTEM_PROGRAM_ID.to_string()),
|
|
};
|
|
}
|
|
}
|
|
}
|