v0.1.0-pre.042
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-pipeline/src/lib.rs
|
// file: kb-pipeline/src/lib.rs
|
||||||
// version: 14
|
// version: 15
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -20,6 +20,7 @@ mod solana_memo_execution;
|
|||||||
mod solana_stateful;
|
mod solana_stateful;
|
||||||
mod solana_token2022_correlation;
|
mod solana_token2022_correlation;
|
||||||
mod solana_token2022_crypto_preflight;
|
mod solana_token2022_crypto_preflight;
|
||||||
|
mod solana_token2022_devnet_execution;
|
||||||
mod solana_token2022_devnet_scenarios;
|
mod solana_token2022_devnet_scenarios;
|
||||||
mod solana_token2022_execution_orchestration;
|
mod solana_token2022_execution_orchestration;
|
||||||
mod solana_token2022_preflight;
|
mod solana_token2022_preflight;
|
||||||
@@ -243,6 +244,14 @@ pub use self::solana_token2022_crypto_preflight::Token2022ProofContextRequiremen
|
|||||||
pub use self::solana_token2022_crypto_preflight::ZK_PROOF_CONTEXT_META_BYTES;
|
pub use self::solana_token2022_crypto_preflight::ZK_PROOF_CONTEXT_META_BYTES;
|
||||||
/// Inspects bounded Token-2022 cryptographic proof contexts.
|
/// Inspects bounded Token-2022 cryptographic proof contexts.
|
||||||
pub use self::solana_token2022_crypto_preflight::inspect_token2022_cryptographic_preflight;
|
pub use self::solana_token2022_crypto_preflight::inspect_token2022_cryptographic_preflight;
|
||||||
|
/// Complete request for one Devnet Token-2022 execution.
|
||||||
|
pub use self::solana_token2022_devnet_execution::DevnetSplToken2022ExecutionRequest;
|
||||||
|
/// Complete result of one Devnet Token-2022 execution.
|
||||||
|
pub use self::solana_token2022_devnet_execution::DevnetSplToken2022ExecutionSummary;
|
||||||
|
/// Executes one Devnet Token-2022 simulation or authorized submission.
|
||||||
|
pub use self::solana_token2022_devnet_execution::execute_devnet_spl_token2022;
|
||||||
|
/// Simulates one Devnet Token-2022 operation after stateful preflight.
|
||||||
|
pub use self::solana_token2022_devnet_execution::simulate_devnet_spl_token2022;
|
||||||
/// Stable category of one independent Devnet validation scenario.
|
/// Stable category of one independent Devnet validation scenario.
|
||||||
pub use self::solana_token2022_devnet_scenarios::DevnetSplValidationFamily;
|
pub use self::solana_token2022_devnet_scenarios::DevnetSplValidationFamily;
|
||||||
/// Current implementation status of one Devnet validation scenario.
|
/// Current implementation status of one Devnet validation scenario.
|
||||||
|
|||||||
921
kb-pipeline/src/solana_token2022_devnet_execution.rs
Normal file
921
kb-pipeline/src/solana_token2022_devnet_execution.rs
Normal file
@@ -0,0 +1,921 @@
|
|||||||
|
// file: kb-pipeline/src/solana_token2022_devnet_execution.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Devnet Token-2022 execution with stateful and canonical post-validation.
|
||||||
|
|
||||||
|
/// Complete request for one Devnet Token-2022 execution.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DevnetSplToken2022ExecutionRequest {
|
||||||
|
/// Stable caller-provided execution identifier.
|
||||||
|
pub intent_id: std::string::String,
|
||||||
|
/// Endpoint role used for state, balance, blockhash, fee and hydration calls.
|
||||||
|
pub query_role: std::string::String,
|
||||||
|
/// Endpoint role used for simulation, submission and confirmation polling.
|
||||||
|
pub transaction_role: std::string::String,
|
||||||
|
/// Exact typed Token-2022 operation.
|
||||||
|
pub operation: kb_lib::ExSplToken2022Operation,
|
||||||
|
/// Explicitly authorizes signing and submission after successful simulation.
|
||||||
|
pub submit: bool,
|
||||||
|
/// Explicit operator confirmation required by the active profile.
|
||||||
|
pub operator_confirmed: bool,
|
||||||
|
/// Number of `getTransaction` retries after the first hydration attempt.
|
||||||
|
pub post_validation_max_retries: u32,
|
||||||
|
/// Replaces existing core and decode outputs for the submitted signature.
|
||||||
|
pub force_post_validation_replay: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::DevnetSplToken2022ExecutionRequest {
|
||||||
|
/// Creates a conservative simulation-only request.
|
||||||
|
pub fn new(
|
||||||
|
intent_id: impl std::convert::Into<std::string::String>,
|
||||||
|
operation: kb_lib::ExSplToken2022Operation,
|
||||||
|
) -> Self {
|
||||||
|
return Self {
|
||||||
|
intent_id: intent_id.into(),
|
||||||
|
query_role: "http_queries".to_string(),
|
||||||
|
transaction_role: "http_transactions".to_string(),
|
||||||
|
operation,
|
||||||
|
submit: false,
|
||||||
|
operator_confirmed: false,
|
||||||
|
post_validation_max_retries: 10,
|
||||||
|
force_post_validation_replay: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
"Devnet Token-2022 execution 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(
|
||||||
|
"Devnet Token-2022 execution endpoint roles must not be empty",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if self.post_validation_max_retries > 20 {
|
||||||
|
return std::result::Result::Err(kb_core::Error::config(
|
||||||
|
"post-execution getTransaction retries must not exceed 20",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete result of one Devnet Token-2022 execution.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct DevnetSplToken2022ExecutionSummary {
|
||||||
|
/// 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 readiness report produced before plan simulation.
|
||||||
|
pub stateful_preflight: crate::Token2022PreflightReport,
|
||||||
|
/// Exact prepared Token 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 simulation result bound to the compiled message.
|
||||||
|
pub simulation: kb_lib::ExApiExecutionSimulationResult,
|
||||||
|
/// 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>,
|
||||||
|
/// Canonical hydration result for the exact signature.
|
||||||
|
pub backfill: std::option::Option<crate::BackfillSummary>,
|
||||||
|
/// Core extraction result for the exact signature.
|
||||||
|
pub core_extraction: std::option::Option<crate::CoreExtractionSummary>,
|
||||||
|
/// First Token decode and materialization replay.
|
||||||
|
pub decode_replay: std::option::Option<crate::DecodeReplaySummary>,
|
||||||
|
/// Second replay proving idempotence for the same decoder version and input.
|
||||||
|
pub idempotence_replay: std::option::Option<crate::DecodeReplaySummary>,
|
||||||
|
/// Exact materialized rows produced for the submitted Token transaction.
|
||||||
|
pub materializations: std::vec::Vec<kb_store::MaterializedEventQueryRow>,
|
||||||
|
/// Aggregated post-execution validation diagnostic.
|
||||||
|
pub post_execution: std::option::Option<kb_lib::ExApiPostExecutionDiagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PreparedToken2022Execution {
|
||||||
|
wallet: kb_wallet::TemporaryWallet,
|
||||||
|
unsigned: kb_lib::ExSolanaUnsignedTransaction,
|
||||||
|
evidence: kb_lib::ExSolanaSimulationEvidence,
|
||||||
|
summary: crate::DevnetSplToken2022ExecutionSummary,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simulates one Devnet Token-2022 operation after stateful preflight.
|
||||||
|
pub async fn simulate_devnet_spl_token2022<O>(
|
||||||
|
http_pool: &kb_onchain_transport::HttpEndpointPool,
|
||||||
|
profile: &kb_config::ProfileConfig,
|
||||||
|
workspace_root: &std::path::Path,
|
||||||
|
request: &crate::DevnetSplToken2022ExecutionRequest,
|
||||||
|
observer: &O,
|
||||||
|
) -> kb_core::Result<crate::DevnetSplToken2022ExecutionSummary>
|
||||||
|
where
|
||||||
|
O: crate::SolanaExecutionObserver,
|
||||||
|
{
|
||||||
|
if request.submit {
|
||||||
|
return std::result::Result::Err(kb_core::Error::config(
|
||||||
|
"simulate_devnet_spl_token2022 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 Devnet Token-2022 simulation or authorized submission.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub async fn execute_devnet_spl_token2022<S, O>(
|
||||||
|
http_pool: &kb_onchain_transport::HttpEndpointPool,
|
||||||
|
store: &S,
|
||||||
|
profile: &kb_config::ProfileConfig,
|
||||||
|
workspace_root: &std::path::Path,
|
||||||
|
request: &crate::DevnetSplToken2022ExecutionRequest,
|
||||||
|
decoders: &[std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>],
|
||||||
|
materializers: &[std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>],
|
||||||
|
observer: &O,
|
||||||
|
) -> kb_core::Result<crate::DevnetSplToken2022ExecutionSummary>
|
||||||
|
where
|
||||||
|
S: kb_store::RawTransactionStore
|
||||||
|
+ kb_store::CoreExtractionStore
|
||||||
|
+ kb_store::DecodePipelineStore
|
||||||
|
+ Sync,
|
||||||
|
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 mut diagnostic = kb_lib::ExApiPostExecutionDiagnostic {
|
||||||
|
signature: signature.clone(),
|
||||||
|
canonical_inserted: false,
|
||||||
|
core_extracted: false,
|
||||||
|
decode_replayed: false,
|
||||||
|
materialized: false,
|
||||||
|
diagnostics: std::vec::Vec::new(),
|
||||||
|
};
|
||||||
|
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) => {
|
||||||
|
diagnostic.diagnostics.push(format!("Token-2022 submission failed: {error}"));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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) => {
|
||||||
|
diagnostic.diagnostics.push(format!("Token-2022 confirmation failed: {error}"));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let confirmation_status = confirmation.status;
|
||||||
|
summary.confirmation = std::option::Option::Some(confirmation);
|
||||||
|
if !matches!(
|
||||||
|
confirmation_status,
|
||||||
|
kb_lib::ExApiExecutionConfirmationStatus::Confirmed
|
||||||
|
| kb_lib::ExApiExecutionConfirmationStatus::Finalized
|
||||||
|
) {
|
||||||
|
diagnostic.diagnostics.push(format!(
|
||||||
|
"Token-2022 post-validation stopped at confirmation status {confirmation_status:?}"
|
||||||
|
));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
}
|
||||||
|
let backfill = match crate::hydrate_signature(
|
||||||
|
http_pool,
|
||||||
|
store,
|
||||||
|
profile,
|
||||||
|
request.query_role.as_str(),
|
||||||
|
request.post_validation_max_retries,
|
||||||
|
observer,
|
||||||
|
&signature,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
diagnostic.diagnostics.push(format!("Token-2022 hydration failed: {error}"));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
diagnostic.canonical_inserted = crate::canonical_available(&backfill);
|
||||||
|
summary.backfill = std::option::Option::Some(backfill);
|
||||||
|
if !diagnostic.canonical_inserted {
|
||||||
|
diagnostic.diagnostics.push(
|
||||||
|
"confirmed Token-2022 transaction was unavailable for canonical hydration".to_string(),
|
||||||
|
);
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
}
|
||||||
|
let extraction = match crate::execute_core_extraction(
|
||||||
|
store,
|
||||||
|
&crate::CoreExtractionRequest {
|
||||||
|
source: crate::CoreExtractionSource::Signatures(std::vec![signature.0.clone()]),
|
||||||
|
limit: 1,
|
||||||
|
max_concurrent_extractions: 1,
|
||||||
|
force_replay: request.force_post_validation_replay,
|
||||||
|
},
|
||||||
|
observer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
diagnostic
|
||||||
|
.diagnostics
|
||||||
|
.push(format!("Token-2022 core extraction failed: {error}"));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
diagnostic.core_extracted = extraction.failed == 0
|
||||||
|
&& !extraction.cancelled
|
||||||
|
&& extraction.selected == 1
|
||||||
|
&& extraction.extracted.saturating_add(extraction.skipped) >= 1;
|
||||||
|
summary.core_extraction = std::option::Option::Some(extraction);
|
||||||
|
if !diagnostic.core_extracted {
|
||||||
|
diagnostic
|
||||||
|
.diagnostics
|
||||||
|
.push("Token-2022 core extraction did not complete".to_string());
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
}
|
||||||
|
let first_replay = match crate::replay_program(
|
||||||
|
store,
|
||||||
|
&signature,
|
||||||
|
false,
|
||||||
|
request.force_post_validation_replay,
|
||||||
|
&[kb_program_ids::SPL_TOKEN2022_PROGRAM_ID],
|
||||||
|
decoders,
|
||||||
|
materializers,
|
||||||
|
observer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
diagnostic.diagnostics.push(format!("Token-2022 decode replay failed: {error}"));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
diagnostic.decode_replayed = crate::decode_completed(&first_replay);
|
||||||
|
summary.decode_replay = std::option::Option::Some(first_replay);
|
||||||
|
let filter = match kb_store::MaterializedEventFilter::new(
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::Some(signature.0.clone()),
|
||||||
|
64,
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let rows = match kb_store::DecodePipelineStore::list_materialized_events(store, &filter).await {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
diagnostic
|
||||||
|
.diagnostics
|
||||||
|
.push(format!("Token-2022 materialization query failed: {error}"));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
summary.materializations = rows
|
||||||
|
.into_iter()
|
||||||
|
.filter(|row| return row.source_decoder_name == "spl_token2022")
|
||||||
|
.collect();
|
||||||
|
diagnostic.materialized =
|
||||||
|
!summary.plan.policy.post_execution_validation.materialization_required
|
||||||
|
|| !summary.materializations.is_empty();
|
||||||
|
let second_replay = match crate::replay_program(
|
||||||
|
store,
|
||||||
|
&signature,
|
||||||
|
true,
|
||||||
|
request.force_post_validation_replay,
|
||||||
|
&[kb_program_ids::SPL_TOKEN2022_PROGRAM_ID],
|
||||||
|
decoders,
|
||||||
|
materializers,
|
||||||
|
observer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
diagnostic
|
||||||
|
.diagnostics
|
||||||
|
.push(format!("Token-2022 idempotence replay failed: {error}"));
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
return std::result::Result::Ok(summary);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let idempotent = second_replay.failed_inputs == 0
|
||||||
|
&& second_replay.processors.iter().all(|processor| {
|
||||||
|
return processor.failed == 0
|
||||||
|
&& processor.materialized_outputs == 0
|
||||||
|
&& processor.materialization_refused == 0;
|
||||||
|
});
|
||||||
|
summary.idempotence_replay = std::option::Option::Some(second_replay);
|
||||||
|
if !idempotent {
|
||||||
|
diagnostic
|
||||||
|
.diagnostics
|
||||||
|
.push("second Token-2022 replay did not prove a clean idempotent skip".to_string());
|
||||||
|
} else if diagnostic.canonical_inserted
|
||||||
|
&& diagnostic.core_extracted
|
||||||
|
&& diagnostic.decode_replayed
|
||||||
|
&& diagnostic.materialized
|
||||||
|
{
|
||||||
|
diagnostic.diagnostics.push(
|
||||||
|
"Token-2022 completed canonical hydration, core extraction, decode, materialization and idempotence validation"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
summary.post_execution = std::option::Option::Some(diagnostic);
|
||||||
|
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::DevnetSplToken2022ExecutionRequest,
|
||||||
|
observer: &O,
|
||||||
|
) -> kb_core::Result<PreparedToken2022Execution>
|
||||||
|
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, "token_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 preflight_request = match preflight_request(request) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let readiness = match crate::inspect_token2022_preflight(http_pool, &preflight_request).await {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
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",
|
||||||
|
format!(
|
||||||
|
"Devnet wallet balance {} is below the configured fee ceiling {}",
|
||||||
|
balance.lamports, profile.execution.max_fee_lamports
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let plan = match build_plan(profile, request, fee_payer.clone()) {
|
||||||
|
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_plan_denied",
|
||||||
|
crate::violation_message(plan_evaluation.violations.as_slice()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
if fee.fee_lamports.is_none() {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"execution_fee_unavailable",
|
||||||
|
"getFeeForMessage returned null for the selected recent blockhash",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
"spl_token2022_simulation",
|
||||||
|
format!("simulating exact Token-2022 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 evidence = unsigned.bind_simulation(simulation.clone());
|
||||||
|
let summary = crate::DevnetSplToken2022ExecutionSummary {
|
||||||
|
profile_name: profile.name.clone(),
|
||||||
|
cluster: kb_lib::ExApiExecutionCluster::Devnet,
|
||||||
|
genesis_hash: genesis.genesis_hash,
|
||||||
|
wallet: wallet_summary,
|
||||||
|
balance_lamports: balance.lamports,
|
||||||
|
stateful_preflight: readiness,
|
||||||
|
plan,
|
||||||
|
latest_blockhash,
|
||||||
|
fee,
|
||||||
|
simulation,
|
||||||
|
send_result: std::option::Option::None,
|
||||||
|
confirmation: std::option::Option::None,
|
||||||
|
backfill: std::option::Option::None,
|
||||||
|
core_extraction: std::option::Option::None,
|
||||||
|
decode_replay: std::option::Option::None,
|
||||||
|
idempotence_replay: std::option::Option::None,
|
||||||
|
materializations: std::vec::Vec::new(),
|
||||||
|
post_execution: std::option::Option::None,
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(PreparedToken2022Execution {
|
||||||
|
wallet,
|
||||||
|
unsigned,
|
||||||
|
evidence,
|
||||||
|
summary,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preflight_request(
|
||||||
|
request: &crate::DevnetSplToken2022ExecutionRequest,
|
||||||
|
) -> kb_core::Result<crate::Token2022PreflightRequest> {
|
||||||
|
let operation = match &request.operation {
|
||||||
|
kb_lib::ExSplToken2022Operation::Instruction { value } => value.as_ref(),
|
||||||
|
kb_lib::ExSplToken2022Operation::Batch { instructions: _ } => {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"execution_spl_token2022_batch_not_supported",
|
||||||
|
"Devnet Token-2022 validation scenarios require one non-batch operation",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut requirements = std::vec::Vec::new();
|
||||||
|
match operation {
|
||||||
|
kb_lib::ExSplTokenSingleOperation::MintToChecked {
|
||||||
|
mint,
|
||||||
|
destination,
|
||||||
|
authority,
|
||||||
|
amount: _,
|
||||||
|
decimals,
|
||||||
|
} => {
|
||||||
|
requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals)));
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"destination",
|
||||||
|
destination,
|
||||||
|
mint,
|
||||||
|
std::option::Option::None,
|
||||||
|
));
|
||||||
|
if let std::result::Result::Err(error) = validate_single_authority(authority) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
kb_lib::ExSplTokenSingleOperation::TransferChecked {
|
||||||
|
source,
|
||||||
|
mint,
|
||||||
|
destination,
|
||||||
|
authority,
|
||||||
|
amount: _,
|
||||||
|
decimals,
|
||||||
|
} => {
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"source",
|
||||||
|
source,
|
||||||
|
mint,
|
||||||
|
std::option::Option::Some(authority.authority.clone()),
|
||||||
|
));
|
||||||
|
requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals)));
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"destination",
|
||||||
|
destination,
|
||||||
|
mint,
|
||||||
|
std::option::Option::None,
|
||||||
|
));
|
||||||
|
if let std::result::Result::Err(error) = validate_single_authority(authority) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
kb_lib::ExSplTokenSingleOperation::ApproveChecked {
|
||||||
|
source,
|
||||||
|
mint,
|
||||||
|
delegate: _,
|
||||||
|
authority,
|
||||||
|
amount: _,
|
||||||
|
decimals,
|
||||||
|
} => {
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"source",
|
||||||
|
source,
|
||||||
|
mint,
|
||||||
|
std::option::Option::Some(authority.authority.clone()),
|
||||||
|
));
|
||||||
|
requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals)));
|
||||||
|
if let std::result::Result::Err(error) = validate_single_authority(authority) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
kb_lib::ExSplTokenSingleOperation::Revoke { source, authority } => {
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"source",
|
||||||
|
source,
|
||||||
|
&kb_lib::MdPubkey(std::string::String::new()),
|
||||||
|
std::option::Option::Some(authority.authority.clone()),
|
||||||
|
));
|
||||||
|
requirements[0].expected_mint = std::option::Option::None;
|
||||||
|
if let std::result::Result::Err(error) = validate_single_authority(authority) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
kb_lib::ExSplTokenSingleOperation::BurnChecked {
|
||||||
|
source,
|
||||||
|
mint,
|
||||||
|
authority,
|
||||||
|
amount: _,
|
||||||
|
decimals,
|
||||||
|
} => {
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"source",
|
||||||
|
source,
|
||||||
|
mint,
|
||||||
|
std::option::Option::Some(authority.authority.clone()),
|
||||||
|
));
|
||||||
|
requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals)));
|
||||||
|
if let std::result::Result::Err(error) = validate_single_authority(authority) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
kb_lib::ExSplTokenSingleOperation::FreezeAccount { account, mint, authority }
|
||||||
|
| kb_lib::ExSplTokenSingleOperation::ThawAccount { account, mint, authority } => {
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"account",
|
||||||
|
account,
|
||||||
|
mint,
|
||||||
|
std::option::Option::None,
|
||||||
|
));
|
||||||
|
requirements.push(mint_requirement("mint", mint, std::option::Option::None));
|
||||||
|
if let std::result::Result::Err(error) = validate_single_authority(authority) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
kb_lib::ExSplTokenSingleOperation::CloseAccount { account, destination: _, authority } => {
|
||||||
|
requirements.push(account_requirement(
|
||||||
|
"account",
|
||||||
|
account,
|
||||||
|
&kb_lib::MdPubkey(std::string::String::new()),
|
||||||
|
std::option::Option::Some(authority.authority.clone()),
|
||||||
|
));
|
||||||
|
requirements[0].expected_mint = std::option::Option::None;
|
||||||
|
if let std::result::Result::Err(error) = validate_single_authority(authority) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"execution_spl_token2022_devnet_operation_unsupported",
|
||||||
|
format!(
|
||||||
|
"Token-2022 Devnet validation does not expose {}",
|
||||||
|
operation.operation_code()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(crate::Token2022PreflightRequest {
|
||||||
|
query_role: request.query_role.clone(),
|
||||||
|
min_context_slot: std::option::Option::None,
|
||||||
|
max_accounts: crate::MAX_TOKEN2022_PREFLIGHT_ACCOUNTS,
|
||||||
|
max_total_data_bytes: crate::MAX_TOKEN2022_PREFLIGHT_TOTAL_BYTES,
|
||||||
|
requirements,
|
||||||
|
elgamal_registry: std::option::Option::None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_single_authority(authority: &kb_lib::ExSplTokenAuthority) -> kb_core::Result<()> {
|
||||||
|
if !authority.multisig_signers.is_empty() {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"execution_spl_token2022_devnet_multisig_not_supported",
|
||||||
|
"the Devnet validation UI currently supports one profile-wallet authority",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mint_requirement(
|
||||||
|
role: &str,
|
||||||
|
mint: &kb_lib::MdPubkey,
|
||||||
|
decimals: std::option::Option<u8>,
|
||||||
|
) -> crate::Token2022PreflightRequirement {
|
||||||
|
return crate::Token2022PreflightRequirement {
|
||||||
|
role: role.to_string(),
|
||||||
|
account: mint.clone(),
|
||||||
|
kind: kb_lib::DcToken2022StateKind::Mint,
|
||||||
|
max_data_bytes: 65_536,
|
||||||
|
expected_mint: std::option::Option::None,
|
||||||
|
expected_owner: std::option::Option::None,
|
||||||
|
expected_decimals: decimals,
|
||||||
|
required_extensions: std::vec::Vec::new(),
|
||||||
|
context: crate::Token2022StatefulContext::default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn account_requirement(
|
||||||
|
role: &str,
|
||||||
|
account: &kb_lib::MdPubkey,
|
||||||
|
mint: &kb_lib::MdPubkey,
|
||||||
|
owner: std::option::Option<kb_lib::MdPubkey>,
|
||||||
|
) -> crate::Token2022PreflightRequirement {
|
||||||
|
return crate::Token2022PreflightRequirement {
|
||||||
|
role: role.to_string(),
|
||||||
|
account: account.clone(),
|
||||||
|
kind: kb_lib::DcToken2022StateKind::Account,
|
||||||
|
max_data_bytes: 65_536,
|
||||||
|
expected_mint: std::option::Option::Some(mint.clone()),
|
||||||
|
expected_owner: owner,
|
||||||
|
expected_decimals: std::option::Option::None,
|
||||||
|
required_extensions: std::vec::Vec::new(),
|
||||||
|
context: crate::Token2022StatefulContext::default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_profile(
|
||||||
|
profile: &kb_config::ProfileConfig,
|
||||||
|
request: &crate::DevnetSplToken2022ExecutionRequest,
|
||||||
|
) -> kb_core::Result<()> {
|
||||||
|
if profile.wallet.cluster != "devnet" {
|
||||||
|
return std::result::Result::Err(kb_core::Error::config(
|
||||||
|
"Token-2022 Devnet 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(
|
||||||
|
"Token-2022 Devnet orchestration requires an enabled persistent temporary wallet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !profile.execution.require_simulation {
|
||||||
|
return std::result::Result::Err(kb_core::Error::config(
|
||||||
|
"Token-2022 Devnet 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(
|
||||||
|
"Token-2022 Devnet submission requires explicit operator confirmation",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_plan(
|
||||||
|
profile: &kb_config::ProfileConfig,
|
||||||
|
request: &crate::DevnetSplToken2022ExecutionRequest,
|
||||||
|
fee_payer: kb_lib::MdPubkey,
|
||||||
|
) -> kb_core::Result<kb_lib::ExApiPreparedExecutionPlan> {
|
||||||
|
let materialization_required = operation_requires_materialization(&request.operation);
|
||||||
|
let authorized_signers = std::vec![fee_payer.clone()];
|
||||||
|
let intent = kb_lib::ExSplToken2022ExecutionIntent {
|
||||||
|
intent_id: request.intent_id.clone(),
|
||||||
|
fee_payer,
|
||||||
|
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,
|
||||||
|
dry_run: !request.submit,
|
||||||
|
post_execution_validation: kb_lib::ExApiPostExecutionValidationPolicy {
|
||||||
|
canonical_insert_required: true,
|
||||||
|
core_extraction_required: true,
|
||||||
|
decode_replay_required: true,
|
||||||
|
materialization_required,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
operation: request.operation.clone(),
|
||||||
|
};
|
||||||
|
return kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
|
||||||
|
&kb_lib::ExSplToken2022Executor,
|
||||||
|
&intent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operation_requires_materialization(_operation: &kb_lib::ExSplToken2022Operation) -> bool {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_spl_token2022_external_signer_unavailable",
|
||||||
|
format!(
|
||||||
|
"the Devnet Token orchestrator can sign only with profile wallet {}; required signers are {}",
|
||||||
|
wallet_pubkey,
|
||||||
|
required_signers.join(",")
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user