v0.1.0-pre.022

This commit is contained in:
2026-07-24 23:24:14 +02:00
parent 34f96582bc
commit 929c89cf9f
15 changed files with 1450 additions and 69 deletions

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/executor.rs
// version: 7
// version: 8
//! Consolidated executor modules.
@@ -493,6 +493,22 @@ pub use self::solana::ExSolanaCoreVoteLockout;
pub use self::solana::ExSolanaCoreZkElGamalContextState;
/// Exposes the official ZK ElGamal proof kinds.
pub use self::solana::ExSolanaCoreZkElGamalProofType;
/// Validated current durable nonce account state used for transaction assembly.
pub use self::solana::ExSolanaDurableNonceAccountState;
/// Signed Solana transaction ready for RPC submission.
pub use self::solana::ExSolanaSignedTransaction;
/// Simulation result bound to the exact assembled message that was simulated.
pub use self::solana::ExSolanaSimulationEvidence;
/// Unsigned assembled Solana transaction used for fee estimation and simulation.
pub use self::solana::ExSolanaUnsignedTransaction;
/// Builds an unsigned durable nonce transaction from a prepared execution plan and validated nonce state.
pub use self::solana::executor_solana_build_durable_nonce_transaction;
/// Builds an unsigned legacy Solana transaction from a prepared execution plan.
pub use self::solana::executor_solana_build_legacy_transaction;
/// Returns the exact serialized byte length of a Solana nonce account state.
pub use self::solana::executor_solana_durable_nonce_account_data_length;
/// Parses and validates a complete System Program-owned durable nonce account snapshot.
pub use self::solana::executor_solana_parse_durable_nonce_account;
/// Exposes the idempotent ATA creation operation code.
pub use self::spl::EX_SPL_ATA_CREATE_IDEMPOTENT_OPERATION;
/// Exposes the strict ATA creation operation code.
@@ -884,8 +900,14 @@ pub use self::wallet::ExWalletJupiterApeproSmartWalletExecutor;
/// Exposes the reserved `weighted/swap_stabble` executor.
pub use self::weighted::ExWeightedSwapStabbleExecutor;
/// Stable operation code for the nonce advance injected by durable transaction assembly.
pub(crate) use self::solana::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION;
/// Maximum over-the-wire size of a Solana transaction packet.
pub(crate) use self::solana::EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES;
/// Canonical tracing target for this crate.
pub(crate) use self::solana::TRACING_TARGET_EXECUTOR_SOLANA_CORE;
/// Canonical tracing target for Solana transaction assembly.
pub(crate) use self::solana::TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION;
/// Crate-root access to `build_prepared_plan` from `address_lookup_table`.
pub(crate) use self::solana::executor_solana_core_address_lookup_table_build_prepared_plan;
/// Crate-root access to `build_prepared_plan` from `builder`.

View File

@@ -1,9 +1,10 @@
// file: kb-lib/src/executor/solana.rs
// version: 4
// version: 5
//! `solana` executor family.
mod core;
mod transaction;
/// Exposes the Address Lookup Table close operation code.
pub use self::core::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION;
@@ -253,6 +254,22 @@ pub use self::core::ExSolanaCoreVoteLockout;
pub use self::core::ExSolanaCoreZkElGamalContextState;
/// Exposes the official ZK ElGamal proof kinds.
pub use self::core::ExSolanaCoreZkElGamalProofType;
/// Validated current durable nonce account state used for transaction assembly.
pub use self::transaction::ExSolanaDurableNonceAccountState;
/// Signed Solana transaction ready for RPC submission.
pub use self::transaction::ExSolanaSignedTransaction;
/// Simulation result bound to the exact assembled message that was simulated.
pub use self::transaction::ExSolanaSimulationEvidence;
/// Unsigned assembled Solana transaction used for fee estimation and simulation.
pub use self::transaction::ExSolanaUnsignedTransaction;
/// Builds an unsigned durable nonce transaction from a prepared execution plan and validated nonce state.
pub use self::transaction::executor_solana_build_durable_nonce_transaction;
/// Builds an unsigned legacy Solana transaction from a prepared execution plan.
pub use self::transaction::executor_solana_build_legacy_transaction;
/// Returns the exact serialized byte length of a Solana nonce account state.
pub use self::transaction::executor_solana_durable_nonce_account_data_length;
/// Parses and validates a complete System Program-owned durable nonce account snapshot.
pub use self::transaction::executor_solana_parse_durable_nonce_account;
/// Canonical tracing target for this crate.
pub(crate) use self::core::TRACING_TARGET_EXECUTOR_SOLANA_CORE;
@@ -292,3 +309,9 @@ pub(crate) use self::core::executor_solana_core_validate_seeded_address;
pub(crate) use self::core::executor_solana_core_vote_build_prepared_plan;
/// Crate-root access to `build_prepared_plan` from `zk_elgamal`.
pub(crate) use self::core::executor_solana_core_zk_elgamal_build_prepared_plan;
/// Stable operation code for the nonce advance injected by durable transaction assembly.
pub(crate) use self::transaction::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION;
/// Maximum over-the-wire size of a Solana transaction packet.
pub(crate) use self::transaction::EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES;
/// Canonical tracing target for Solana transaction assembly.
pub(crate) use self::transaction::TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION;

View File

@@ -0,0 +1,32 @@
// file: kb-lib/src/executor/solana/transaction.rs
// version: 2
//! Solana transaction assembly and signing for prepared execution plans.
mod constants;
mod core;
mod nonce;
/// Signed Solana transaction ready for RPC submission.
pub use self::core::ExSolanaSignedTransaction;
/// Simulation result bound to the exact assembled message that was simulated.
pub use self::core::ExSolanaSimulationEvidence;
/// Unsigned assembled Solana transaction used for fee estimation and simulation.
pub use self::core::ExSolanaUnsignedTransaction;
/// Builds an unsigned durable nonce transaction from a prepared execution plan and validated nonce state.
pub use self::core::executor_solana_build_durable_nonce_transaction;
/// Builds an unsigned legacy Solana transaction from a prepared execution plan.
pub use self::core::executor_solana_build_legacy_transaction;
/// Validated current durable nonce account state used for transaction assembly.
pub use self::nonce::ExSolanaDurableNonceAccountState;
/// Returns the exact serialized byte length of a Solana nonce account state.
pub use self::nonce::executor_solana_durable_nonce_account_data_length;
/// Parses and validates a complete System Program-owned durable nonce account snapshot.
pub use self::nonce::executor_solana_parse_durable_nonce_account;
/// Stable operation code for the nonce advance injected by durable transaction assembly.
pub(crate) use self::constants::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION;
/// Maximum over-the-wire size of a Solana transaction packet.
pub(crate) use self::constants::EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES;
/// Canonical tracing target for Solana transaction assembly.
pub(crate) use self::constants::TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION;

View File

@@ -0,0 +1,13 @@
// file: kb-lib/src/executor/solana/transaction/constants.rs
// version: 2
//! Local constants for Solana transaction assembly.
/// Stable operation code for the nonce advance injected by durable transaction assembly.
pub(crate) const EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION: &str =
"solana_core.system.nonce.advance";
/// Maximum over-the-wire size of a Solana transaction packet.
pub(crate) const EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES: usize = 1_232;
/// Canonical tracing target for Solana transaction assembly.
pub(crate) const TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION: &str =
"kb-lib.executor.solana.transaction";

View File

@@ -0,0 +1,988 @@
// file: kb-lib/src/executor/solana/transaction/core.rs
// version: 9
//! Legacy and durable nonce Solana transaction assembly, simulation binding and signing.
use base64::Engine; // rust-rules: trait-import
use std::str::FromStr; // rust-rules: trait-import
/// Unsigned assembled Solana transaction used for fee estimation and simulation.
#[derive(Clone, Debug)]
pub struct ExSolanaUnsignedTransaction {
plan: crate::ExApiPreparedExecutionPlan,
transaction: solana_transaction::Transaction,
message_hash: std::string::String,
blockhash: std::string::String,
required_signer_pubkeys: std::vec::Vec<std::string::String>,
}
impl crate::ExSolanaUnsignedTransaction {
/// Returns the effective immutable plan represented by this transaction.
///
/// Durable nonce transactions include the injected nonce-advance instruction.
pub fn plan(&self) -> &crate::ExApiPreparedExecutionPlan {
return &self.plan;
}
/// Returns the base58 hash of the exact compiled transaction message.
pub fn message_hash(&self) -> &str {
return self.message_hash.as_str();
}
/// Returns the recent blockhash or durable nonce value compiled into the message.
pub fn blockhash(&self) -> &str {
return self.blockhash.as_str();
}
/// Returns required signer public keys in compiled message order.
pub fn required_signer_pubkeys(&self) -> &[std::string::String] {
return self.required_signer_pubkeys.as_slice();
}
/// Returns the serialized message in base64 for `getFeeForMessage`.
pub fn message_base64(&self) -> std::string::String {
return base64::engine::general_purpose::STANDARD.encode(self.transaction.message_data());
}
/// Returns the unsigned serialized transaction in base64 for `simulateTransaction` with `sigVerify = false`.
pub fn transaction_base64(&self) -> kb_core::Result<std::string::String> {
let wire_bytes = match serialize_transaction(&self.transaction) {
std::result::Result::Ok(wire_bytes) => wire_bytes,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(
base64::engine::general_purpose::STANDARD.encode(wire_bytes),
);
}
/// Binds a typed RPC simulation result to this exact compiled message.
pub fn bind_simulation(
&self,
simulation: crate::ExApiExecutionSimulationResult,
) -> crate::ExSolanaSimulationEvidence {
return crate::ExSolanaSimulationEvidence {
message_hash: self.message_hash.clone(),
blockhash: self.blockhash.clone(),
simulation,
};
}
/// Signs the transaction after safety validation of a matching simulation result.
pub fn sign_after_simulation(
mut self,
evidence: &crate::ExSolanaSimulationEvidence,
signers: &[&dyn solana_signer::Signer],
) -> kb_core::Result<crate::ExSolanaSignedTransaction> {
if evidence.message_hash != self.message_hash {
return std::result::Result::Err(kb_core::Error::new(
"execution_simulation_message_mismatch",
"the simulation result is not bound to the transaction message being signed",
));
}
if evidence.blockhash != self.blockhash {
return std::result::Result::Err(kb_core::Error::new(
"execution_simulation_blockhash_mismatch",
"the simulation result is not bound to the blockhash or durable nonce value being signed",
));
}
let safety_evaluation =
match crate::ExSafetyChecker.evaluate_send(&self.plan, &evidence.simulation) {
std::result::Result::Ok(evaluation) => evaluation,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if safety_evaluation.decision != crate::ExSafetyDecision::Allow {
return std::result::Result::Err(kb_core::Error::new(
"execution_signing_not_authorized",
format!(
"execution safety decision {:?} forbids signing: {}",
safety_evaluation.decision,
violation_codes(&safety_evaluation.violations)
),
));
}
let signers = match resolve_signers(self.required_signer_pubkeys.as_slice(), signers) {
std::result::Result::Ok(signers) => signers,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let recent_blockhash = self.transaction.message.recent_blockhash;
if let std::result::Result::Err(error) =
self.transaction.try_sign(signers.as_slice(), recent_blockhash)
{
return std::result::Result::Err(kb_core::Error::new(
"execution_transaction_sign_failed",
error.to_string(),
));
}
if !self.transaction.is_signed() {
return std::result::Result::Err(kb_core::Error::new(
"execution_transaction_not_fully_signed",
"the transaction is not fully signed after signer resolution",
));
}
let wire_bytes = match serialize_transaction(&self.transaction) {
std::result::Result::Ok(wire_bytes) => wire_bytes,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let primary_signature = match self.transaction.signatures.first() {
std::option::Option::Some(signature) => signature.to_string(),
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::new(
"execution_primary_signature_missing",
"the signed transaction contains no primary signature",
));
},
};
tracing::info!(
target: crate::TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION,
action = "sign_transaction",
intent_id = %self.plan.intent_id,
operation_code = %self.plan.operation_code,
message_hash = %self.message_hash,
signer_count = self.required_signer_pubkeys.len(),
wire_length = wire_bytes.len(),
"signed prepared Solana transaction"
);
return std::result::Result::Ok(crate::ExSolanaSignedTransaction {
transaction: self.transaction,
message_hash: self.message_hash,
primary_signature: crate::MdSignature(primary_signature),
signer_pubkeys: self.required_signer_pubkeys,
wire_bytes,
});
}
}
/// Simulation result bound to the exact assembled message that was simulated.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExSolanaSimulationEvidence {
message_hash: std::string::String,
blockhash: std::string::String,
simulation: crate::ExApiExecutionSimulationResult,
}
impl crate::ExSolanaSimulationEvidence {
/// Returns the base58 hash of the simulated compiled message.
pub fn message_hash(&self) -> &str {
return self.message_hash.as_str();
}
/// Returns the recent blockhash or durable nonce value used by the simulated message.
pub fn blockhash(&self) -> &str {
return self.blockhash.as_str();
}
/// Returns the typed simulation result.
pub fn simulation(&self) -> &crate::ExApiExecutionSimulationResult {
return &self.simulation;
}
}
/// Signed Solana transaction ready for RPC submission.
#[derive(Clone, Debug)]
pub struct ExSolanaSignedTransaction {
transaction: solana_transaction::Transaction,
message_hash: std::string::String,
primary_signature: crate::MdSignature,
signer_pubkeys: std::vec::Vec<std::string::String>,
wire_bytes: std::vec::Vec<u8>,
}
impl crate::ExSolanaSignedTransaction {
/// Returns the base58 hash of the signed compiled message.
pub fn message_hash(&self) -> &str {
return self.message_hash.as_str();
}
/// Returns the transaction's primary signature.
pub fn primary_signature(&self) -> &crate::MdSignature {
return &self.primary_signature;
}
/// Returns signer public keys in signature order.
pub fn signer_pubkeys(&self) -> &[std::string::String] {
return self.signer_pubkeys.as_slice();
}
/// Returns the signed transaction wire length.
pub fn wire_length(&self) -> usize {
return self.wire_bytes.len();
}
/// Returns the signed transaction in base64 for future RPC submission.
pub fn transaction_base64(&self) -> std::string::String {
return base64::engine::general_purpose::STANDARD.encode(self.wire_bytes.as_slice());
}
/// Verifies every signature against the compiled message.
pub fn verify_signatures(&self) -> kb_core::Result<()> {
return match self.transaction.verify() {
std::result::Result::Ok(()) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new(
"execution_transaction_signature_verification_failed",
error.to_string(),
)),
};
}
}
/// Builds an unsigned legacy Solana transaction from a prepared execution plan.
pub fn executor_solana_build_legacy_transaction(
plan: &crate::ExApiPreparedExecutionPlan,
recent_blockhash: &str,
) -> kb_core::Result<crate::ExSolanaUnsignedTransaction> {
if plan.policy.blockhash.kind != crate::ExApiExecutionBlockhashKind::Latest {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_requires_dedicated_assembly",
"durable nonce plans require executor_solana_build_durable_nonce_transaction",
));
}
if plan.instructions.is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"execution_plan_has_no_instructions",
"cannot assemble a Solana transaction without instructions",
));
}
let fee_payer = match parse_pubkey(plan.fee_payer.0.as_str(), "execution fee payer") {
std::result::Result::Ok(fee_payer) => fee_payer,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let blockhash = match solana_hash::Hash::from_str(recent_blockhash) {
std::result::Result::Ok(blockhash) => blockhash,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_recent_blockhash_invalid",
format!("invalid recent blockhash: {error}"),
));
},
};
let mut instructions = std::vec::Vec::with_capacity(plan.instructions.len());
for planned_instruction in &plan.instructions {
let instruction = match convert_instruction(planned_instruction) {
std::result::Result::Ok(instruction) => instruction,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
instructions.push(instruction);
}
let message = solana_message::Message::new_with_blockhash(
instructions.as_slice(),
std::option::Option::Some(&fee_payer),
&blockhash,
);
let compiled_signers = message
.signer_keys()
.iter()
.map(|pubkey| return pubkey.to_string())
.collect::<std::vec::Vec<_>>();
match validate_compiled_signers(plan, compiled_signers.as_slice()) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let transaction = solana_transaction::Transaction::new_unsigned(message);
let wire_bytes = match serialize_transaction(&transaction) {
std::result::Result::Ok(wire_bytes) => wire_bytes,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let message_hash = transaction.message.hash().to_string();
tracing::debug!(
target: crate::TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION,
action = "assemble_transaction",
intent_id = %plan.intent_id,
operation_code = %plan.operation_code,
instruction_count = plan.instructions.len(),
signer_count = compiled_signers.len(),
message_hash = %message_hash,
wire_length = wire_bytes.len(),
"assembled unsigned legacy Solana transaction"
);
return std::result::Result::Ok(crate::ExSolanaUnsignedTransaction {
plan: plan.clone(),
transaction,
message_hash,
blockhash: blockhash.to_string(),
required_signer_pubkeys: compiled_signers,
});
}
/// Builds an unsigned transaction that consumes a validated durable nonce state.
pub fn executor_solana_build_durable_nonce_transaction(
plan: &crate::ExApiPreparedExecutionPlan,
nonce_state: &crate::ExSolanaDurableNonceAccountState,
) -> kb_core::Result<crate::ExSolanaUnsignedTransaction> {
if plan.policy.blockhash.kind != crate::ExApiExecutionBlockhashKind::DurableNonce {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_policy_required",
"the dedicated durable nonce assembly path requires a DurableNonce blockhash policy",
));
}
if plan.policy.blockhash.max_age_slots.is_some() {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_has_blockhash_age",
"durable nonce assembly cannot use a recent blockhash age limit",
));
}
if plan.instructions.is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"execution_plan_has_no_instructions",
"cannot assemble a durable nonce transaction without operation instructions",
));
}
let policy_nonce_account = match &plan.policy.blockhash.nonce_account {
std::option::Option::Some(account) => account,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_account_missing",
"durable nonce assembly requires the nonce account in the execution policy",
));
},
};
let policy_nonce_authority = match &plan.policy.blockhash.nonce_authority {
std::option::Option::Some(authority) => authority,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_authority_missing",
"durable nonce assembly requires the nonce authority in the execution policy",
));
},
};
if policy_nonce_account != nonce_state.account() {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_account_mismatch",
format!(
"execution policy nonce account {} differs from validated account {}",
policy_nonce_account.0,
nonce_state.account().0
),
));
}
if policy_nonce_authority != nonce_state.authority() {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_authority_mismatch",
format!(
"execution policy nonce authority {} differs from validated authority {}",
policy_nonce_authority.0,
nonce_state.authority().0
),
));
}
if !plan
.required_signers
.iter()
.any(|required| return &required.pubkey == policy_nonce_authority)
{
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_authority_not_declared",
format!(
"durable nonce authority {} is missing from required signers",
policy_nonce_authority.0
),
));
}
let fee_payer = match parse_pubkey(plan.fee_payer.0.as_str(), "execution fee payer") {
std::result::Result::Ok(fee_payer) => fee_payer,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let nonce_account = match parse_pubkey(policy_nonce_account.0.as_str(), "durable nonce account")
{
std::result::Result::Ok(nonce_account) => nonce_account,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let nonce_authority =
match parse_pubkey(policy_nonce_authority.0.as_str(), "durable nonce authority") {
std::result::Result::Ok(nonce_authority) => nonce_authority,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let nonce_blockhash = match solana_hash::Hash::from_str(nonce_state.blockhash()) {
std::result::Result::Ok(blockhash) => blockhash,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_blockhash_invalid",
format!("invalid durable nonce value: {error}"),
));
},
};
let advance_instruction = solana_system_interface::instruction::advance_nonce_account(
&nonce_account,
&nonce_authority,
);
let mut effective_plan = plan.clone();
effective_plan.instructions.insert(
0,
planned_instruction(
crate::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION,
&advance_instruction,
),
);
let mut operation_instructions = std::vec::Vec::with_capacity(plan.instructions.len());
for planned_instruction in &plan.instructions {
let instruction = match convert_instruction(planned_instruction) {
std::result::Result::Ok(instruction) => instruction,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
operation_instructions.push(instruction);
}
let mut message = solana_message::Message::new_with_nonce(
operation_instructions,
std::option::Option::Some(&fee_payer),
&nonce_account,
&nonce_authority,
);
message.recent_blockhash = nonce_blockhash;
let compiled_signers = message
.signer_keys()
.iter()
.map(|pubkey| return pubkey.to_string())
.collect::<std::vec::Vec<_>>();
match validate_compiled_signers(&effective_plan, compiled_signers.as_slice()) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let transaction = solana_transaction::Transaction::new_unsigned(message);
if solana_transaction::uses_durable_nonce(&transaction).is_none() {
return std::result::Result::Err(kb_core::Error::new(
"execution_durable_nonce_marker_missing",
"the assembled transaction is not recognized as a durable nonce transaction",
));
}
let wire_bytes = match serialize_transaction(&transaction) {
std::result::Result::Ok(wire_bytes) => wire_bytes,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let message_hash = transaction.message.hash().to_string();
tracing::debug!(
target: crate::TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION,
action = "assemble_durable_nonce_transaction",
intent_id = %effective_plan.intent_id,
operation_code = %effective_plan.operation_code,
nonce_account = %policy_nonce_account.0,
nonce_authority = %policy_nonce_authority.0,
nonce_blockhash = %nonce_state.blockhash(),
instruction_count = effective_plan.instructions.len(),
signer_count = compiled_signers.len(),
message_hash = %message_hash,
wire_length = wire_bytes.len(),
"assembled unsigned durable nonce Solana transaction"
);
return std::result::Result::Ok(crate::ExSolanaUnsignedTransaction {
plan: effective_plan,
transaction,
message_hash,
blockhash: nonce_state.blockhash().to_string(),
required_signer_pubkeys: compiled_signers,
});
}
fn convert_instruction(
planned: &crate::ExApiPlannedInstruction,
) -> kb_core::Result<solana_instruction::Instruction> {
let program_id =
match parse_pubkey(planned.program_id.0.as_str(), "planned instruction program id") {
std::result::Result::Ok(program_id) => program_id,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut accounts = std::vec::Vec::with_capacity(planned.accounts.len());
for planned_account in &planned.accounts {
let pubkey =
match parse_pubkey(planned_account.pubkey.0.as_str(), "planned instruction account") {
std::result::Result::Ok(pubkey) => pubkey,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
accounts.push(solana_instruction::AccountMeta {
pubkey,
is_signer: planned_account.is_signer,
is_writable: planned_account.is_writable,
});
}
return std::result::Result::Ok(solana_instruction::Instruction {
program_id,
accounts,
data: planned.data.clone(),
});
}
fn planned_instruction(
operation_code: &str,
instruction: &solana_instruction::Instruction,
) -> crate::ExApiPlannedInstruction {
return crate::ExApiPlannedInstruction {
program_id: crate::MdProgramId(instruction.program_id.to_string()),
operation_code: operation_code.to_string(),
accounts: instruction
.accounts
.iter()
.map(|account| {
return crate::ExApiPlannedAccount {
pubkey: crate::MdPubkey(account.pubkey.to_string()),
is_signer: account.is_signer,
is_writable: account.is_writable,
};
})
.collect(),
data: instruction.data.clone(),
};
}
fn parse_pubkey(value: &str, field_name: &str) -> kb_core::Result<solana_pubkey::Pubkey> {
return match solana_pubkey::Pubkey::from_str(value) {
std::result::Result::Ok(pubkey) => std::result::Result::Ok(pubkey),
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new(
"execution_pubkey_invalid",
format!("invalid {field_name} {value}: {error}"),
)),
};
}
fn validate_compiled_signers(
plan: &crate::ExApiPreparedExecutionPlan,
compiled_signers: &[std::string::String],
) -> kb_core::Result<()> {
let declared_signers = plan
.required_signers
.iter()
.map(|required| return required.pubkey.0.as_str())
.collect::<std::collections::BTreeSet<_>>();
let compiled_signer_set = compiled_signers
.iter()
.map(std::string::String::as_str)
.collect::<std::collections::BTreeSet<_>>();
if declared_signers != compiled_signer_set {
return std::result::Result::Err(kb_core::Error::new(
"execution_compiled_signer_contract_mismatch",
format!(
"declared signers [{}] differ from compiled signers [{}]",
declared_signers.into_iter().collect::<std::vec::Vec<_>>().join(","),
compiled_signer_set.into_iter().collect::<std::vec::Vec<_>>().join(",")
),
));
}
return std::result::Result::Ok(());
}
fn resolve_signers<'a>(
required_signers: &[std::string::String],
signers: &'a [&'a dyn solana_signer::Signer],
) -> kb_core::Result<std::vec::Vec<&'a dyn solana_signer::Signer>> {
let mut signer_by_pubkey = std::collections::BTreeMap::new();
for signer in signers {
let public_key = signer.pubkey().to_string();
if signer_by_pubkey.insert(public_key.clone(), *signer).is_some() {
return std::result::Result::Err(kb_core::Error::new(
"execution_signer_duplicate",
format!("signer {public_key} was supplied more than once"),
));
}
}
if signer_by_pubkey.len() != required_signers.len() {
return std::result::Result::Err(kb_core::Error::new(
"execution_signer_count_mismatch",
format!(
"{} signers were supplied for {} required signers",
signer_by_pubkey.len(),
required_signers.len()
),
));
}
let mut resolved = std::vec::Vec::with_capacity(required_signers.len());
for required_signer in required_signers {
let signer = match signer_by_pubkey.get(required_signer) {
std::option::Option::Some(signer) => *signer,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::new(
"execution_required_signer_missing",
format!("required signer {required_signer} was not supplied"),
));
},
};
resolved.push(signer);
}
return std::result::Result::Ok(resolved);
}
fn serialize_transaction(
transaction: &solana_transaction::Transaction,
) -> kb_core::Result<std::vec::Vec<u8>> {
let wire_bytes = match wincode::serialize(transaction) {
std::result::Result::Ok(wire_bytes) => wire_bytes,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_transaction_serialize_failed",
error.to_string(),
));
},
};
if wire_bytes.len() > crate::EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES {
return std::result::Result::Err(kb_core::Error::new(
"execution_transaction_wire_size_exceeded",
format!(
"serialized transaction length {} exceeds the Solana packet limit {}",
wire_bytes.len(),
crate::EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES
),
));
}
return std::result::Result::Ok(wire_bytes);
}
fn violation_codes(violations: &[crate::ExSafetyViolation]) -> std::string::String {
return violations
.iter()
.map(|violation| return violation.code.as_str())
.collect::<std::vec::Vec<_>>()
.join(",");
}
#[cfg(test)]
mod tests {
use solana_signer::Signer; // rust-rules: trait-import
use std::str::FromStr; // rust-rules: trait-import
fn wallet(_alias: &str) -> solana_keypair::Keypair {
return solana_keypair::Keypair::new();
}
fn transfer_plan(
wallet: &solana_keypair::Keypair,
dry_run: bool,
lamports: u64,
) -> crate::ExApiPreparedExecutionPlan {
let payer = crate::MdPubkey(wallet.pubkey().to_string());
let destination = crate::MdPubkey(solana_keypair::Keypair::new().pubkey().to_string());
let intent = crate::ExSolanaCoreExecutionIntent {
intent_id: std::string::String::from("transfer-test"),
fee_payer: payer.clone(),
operation: crate::ExSolanaCoreOperation::SystemTransfer {
from: payer.clone(),
to: destination,
lamports,
},
policy: crate::ExApiExecutionPolicy {
dry_run,
authorized_signers: vec![payer],
cost_limit: crate::ExApiExecutionCostLimit {
max_spend_lamports: std::option::Option::Some(lamports),
max_fee_lamports: std::option::Option::Some(10_000),
max_compute_unit_price_micro_lamports: std::option::Option::Some(100),
},
..crate::ExApiExecutionPolicy::default()
},
};
return crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSolanaCoreExecutor,
&intent,
)
.unwrap_or_else(|error| panic!("unexpected plan error: {error}"));
}
fn durable_transfer_plan(
payer: &solana_keypair::Keypair,
nonce_authority: &solana_keypair::Keypair,
nonce_account: &crate::MdPubkey,
) -> crate::ExApiPreparedExecutionPlan {
let payer_pubkey = crate::MdPubkey(payer.pubkey().to_string());
let authority_pubkey = crate::MdPubkey(nonce_authority.pubkey().to_string());
let destination = crate::MdPubkey(solana_keypair::Keypair::new().pubkey().to_string());
let intent = crate::ExSolanaCoreExecutionIntent {
intent_id: std::string::String::from("durable-transfer-test"),
fee_payer: payer_pubkey.clone(),
operation: crate::ExSolanaCoreOperation::SystemTransfer {
from: payer_pubkey.clone(),
to: destination,
lamports: 100,
},
policy: crate::ExApiExecutionPolicy {
dry_run: false,
authorized_signers: vec![payer_pubkey, authority_pubkey.clone()],
blockhash: crate::ExApiExecutionBlockhashPolicy {
kind: crate::ExApiExecutionBlockhashKind::DurableNonce,
max_age_slots: std::option::Option::None,
nonce_account: std::option::Option::Some(nonce_account.clone()),
nonce_authority: std::option::Option::Some(authority_pubkey),
},
cost_limit: crate::ExApiExecutionCostLimit {
max_spend_lamports: std::option::Option::Some(100),
max_fee_lamports: std::option::Option::Some(10_000),
max_compute_unit_price_micro_lamports: std::option::Option::Some(100),
},
..crate::ExApiExecutionPolicy::default()
},
};
return crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSolanaCoreExecutor,
&intent,
)
.unwrap_or_else(|error| panic!("unexpected durable plan error: {error}"));
}
fn nonce_state(
nonce_account: &crate::MdPubkey,
nonce_authority: &solana_keypair::Keypair,
) -> crate::ExSolanaDurableNonceAccountState {
let source_blockhash = solana_hash::Hash::new_unique();
let durable_nonce = solana_nonce::state::DurableNonce::from_blockhash(&source_blockhash);
let authority =
solana_pubkey::Pubkey::from_str(nonce_authority.pubkey().to_string().as_str())
.unwrap_or_else(|error| panic!("invalid authority fixture: {error}"));
let versions = solana_nonce::versions::Versions::new(
solana_nonce::state::State::new_initialized(&authority, durable_nonce, 5_000),
);
let bytes = wincode::serialize(&versions)
.unwrap_or_else(|error| panic!("unexpected nonce serialization error: {error}"));
return crate::executor_solana_parse_durable_nonce_account(
nonce_account,
&crate::MdProgramId(solana_sdk_ids::system_program::id().to_string()),
false,
bytes.len() as u64,
bytes.as_slice(),
)
.unwrap_or_else(|error| panic!("unexpected nonce parse error: {error}"));
}
fn successful_simulation() -> crate::ExApiExecutionSimulationResult {
return crate::ExApiExecutionSimulationResult {
simulated: true,
success: true,
cluster: crate::ExApiExecutionCluster::Devnet,
blockhash_kind: crate::ExApiExecutionBlockhashKind::Latest,
blockhash_age_slots: std::option::Option::Some(1),
replacement_blockhash: std::option::Option::None,
replacement_last_valid_block_height: std::option::Option::None,
nonce_account: std::option::Option::None,
nonce_authority: std::option::Option::None,
units_consumed: std::option::Option::Some(500),
estimated_fee_lamports: std::option::Option::Some(5_000),
logs: std::vec::Vec::new(),
error: std::option::Option::None,
};
}
fn successful_nonce_simulation(
nonce_account: &crate::MdPubkey,
nonce_authority: &solana_keypair::Keypair,
) -> crate::ExApiExecutionSimulationResult {
return crate::ExApiExecutionSimulationResult {
simulated: true,
success: true,
cluster: crate::ExApiExecutionCluster::Devnet,
blockhash_kind: crate::ExApiExecutionBlockhashKind::DurableNonce,
blockhash_age_slots: std::option::Option::None,
replacement_blockhash: std::option::Option::None,
replacement_last_valid_block_height: std::option::Option::None,
nonce_account: std::option::Option::Some(nonce_account.clone()),
nonce_authority: std::option::Option::Some(crate::MdPubkey(
nonce_authority.pubkey().to_string(),
)),
units_consumed: std::option::Option::Some(700),
estimated_fee_lamports: std::option::Option::Some(10_000),
logs: std::vec::Vec::new(),
error: std::option::Option::None,
};
}
#[test]
fn legacy_transaction_preserves_plan_and_serializes_for_rpc() {
let wallet = wallet("payer");
let plan = transfer_plan(&wallet, false, 100);
let blockhash = solana_hash::Hash::new_unique().to_string();
let unsigned = crate::executor_solana_build_legacy_transaction(&plan, blockhash.as_str())
.unwrap_or_else(|error| panic!("unexpected assembly error: {error}"));
assert_eq!(unsigned.plan().intent_id, plan.intent_id);
assert_eq!(unsigned.required_signer_pubkeys(), &[wallet.pubkey().to_string()]);
assert!(!unsigned.message_hash().is_empty());
assert!(!unsigned.message_base64().is_empty());
assert!(
!unsigned
.transaction_base64()
.unwrap_or_else(|error| panic!("unexpected serialization error: {error}"))
.is_empty()
);
}
#[test]
fn matching_simulation_and_signer_produce_verified_signature() {
let wallet = wallet("payer");
let plan = transfer_plan(&wallet, false, 100);
let blockhash = solana_hash::Hash::new_unique().to_string();
let unsigned = crate::executor_solana_build_legacy_transaction(&plan, blockhash.as_str())
.unwrap_or_else(|error| panic!("unexpected assembly error: {error}"));
let evidence = unsigned.bind_simulation(successful_simulation());
let signed = unsigned
.sign_after_simulation(&evidence, &[&wallet])
.unwrap_or_else(|error| panic!("unexpected signing error: {error}"));
signed
.verify_signatures()
.unwrap_or_else(|error| panic!("unexpected verification error: {error}"));
assert!(!signed.primary_signature().0.is_empty());
assert_eq!(signed.signer_pubkeys(), &[wallet.pubkey().to_string()]);
assert!(signed.wire_length() <= crate::EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES);
assert!(!signed.transaction_base64().is_empty());
}
#[test]
fn dry_run_policy_blocks_signing_after_successful_simulation() {
let wallet = wallet("payer");
let plan = transfer_plan(&wallet, true, 100);
let blockhash = solana_hash::Hash::new_unique().to_string();
let unsigned = crate::executor_solana_build_legacy_transaction(&plan, blockhash.as_str())
.unwrap_or_else(|error| panic!("unexpected assembly error: {error}"));
let evidence = unsigned.bind_simulation(successful_simulation());
let result = unsigned.sign_after_simulation(&evidence, &[&wallet]);
assert!(result.is_err());
}
#[test]
fn simulation_evidence_from_another_message_is_rejected() {
let wallet = wallet("payer");
let plan = transfer_plan(&wallet, false, 100);
let first_hash = solana_hash::Hash::new_unique().to_string();
let second_hash = solana_hash::Hash::new_unique().to_string();
let first = crate::executor_solana_build_legacy_transaction(&plan, first_hash.as_str())
.unwrap_or_else(|error| panic!("unexpected first assembly error: {error}"));
let second = crate::executor_solana_build_legacy_transaction(&plan, second_hash.as_str())
.unwrap_or_else(|error| panic!("unexpected second assembly error: {error}"));
let evidence = first.bind_simulation(successful_simulation());
let result = second.sign_after_simulation(&evidence, &[&wallet]);
assert!(result.is_err());
}
#[test]
fn missing_extra_and_duplicate_signers_are_rejected() {
let tmpwallet = wallet("payer");
let extra = wallet("extra");
let plan = transfer_plan(&tmpwallet, false, 100);
let blockhash = solana_hash::Hash::new_unique().to_string();
let unsigned = crate::executor_solana_build_legacy_transaction(&plan, blockhash.as_str())
.unwrap_or_else(|error| panic!("unexpected assembly error: {error}"));
let evidence = unsigned.bind_simulation(successful_simulation());
assert!(unsigned.clone().sign_after_simulation(&evidence, &[]).is_err());
assert!(
unsigned
.clone()
.sign_after_simulation(&evidence, &[&tmpwallet, &extra],)
.is_err()
);
assert!(unsigned.sign_after_simulation(&evidence, &[&tmpwallet, &tmpwallet],).is_err());
}
#[test]
fn durable_nonce_transaction_injects_exact_advance_and_signs() {
let payer = wallet("durable-payer");
let authority = wallet("durable-authority");
let nonce_account = crate::MdPubkey(solana_pubkey::Pubkey::new_unique().to_string());
let plan = durable_transfer_plan(&payer, &authority, &nonce_account);
assert_eq!(plan.instructions.len(), 1);
assert_eq!(plan.required_signers.len(), 2);
let nonce_state = nonce_state(&nonce_account, &authority);
let unsigned = crate::executor_solana_build_durable_nonce_transaction(&plan, &nonce_state)
.unwrap_or_else(|error| panic!("unexpected durable assembly error: {error}"));
assert_eq!(plan.instructions.len(), 1);
assert_eq!(unsigned.plan().instructions.len(), 2);
assert_eq!(unsigned.blockhash(), nonce_state.blockhash());
let authority_address =
solana_pubkey::Pubkey::from_str(authority.pubkey().to_string().as_str())
.unwrap_or_else(|error| panic!("invalid authority fixture: {error}"));
let nonce_address = solana_pubkey::Pubkey::from_str(nonce_account.0.as_str())
.unwrap_or_else(|error| panic!("invalid nonce fixture: {error}"));
let expected_advance = solana_system_interface::instruction::advance_nonce_account(
&nonce_address,
&authority_address,
);
let actual_advance = &unsigned.plan().instructions[0];
assert_eq!(
actual_advance.operation_code,
crate::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION
);
assert_eq!(actual_advance.program_id.0, expected_advance.program_id.to_string());
assert_eq!(actual_advance.data, expected_advance.data);
assert_eq!(actual_advance.accounts.len(), expected_advance.accounts.len());
for (actual, expected) in
actual_advance.accounts.iter().zip(expected_advance.accounts.iter())
{
assert_eq!(actual.pubkey.0, expected.pubkey.to_string());
assert_eq!(actual.is_signer, expected.is_signer);
assert_eq!(actual.is_writable, expected.is_writable);
}
assert!(solana_transaction::uses_durable_nonce(&unsigned.transaction).is_some());
let compiled_first = &unsigned.transaction.message.instructions[0];
assert_eq!(
unsigned.transaction.message.account_keys[compiled_first.program_id_index as usize],
expected_advance.program_id
);
assert_eq!(compiled_first.data, expected_advance.data);
let signer_set = unsigned
.required_signer_pubkeys()
.iter()
.cloned()
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(
signer_set,
std::collections::BTreeSet::from([
payer.pubkey().to_string(),
authority.pubkey().to_string()
])
);
let evidence =
unsigned.bind_simulation(successful_nonce_simulation(&nonce_account, &authority));
assert_eq!(evidence.blockhash(), nonce_state.blockhash());
let signed = unsigned
.sign_after_simulation(&evidence, &[&payer, &authority])
.unwrap_or_else(|error| panic!("unexpected durable signing error: {error}"));
signed
.verify_signatures()
.unwrap_or_else(|error| panic!("unexpected signature verification error: {error}"));
}
#[test]
fn durable_nonce_assembly_rejects_mismatched_state_and_simulation() {
let payer = wallet("mismatch-payer");
let authority = wallet("mismatch-authority");
let wrong_authority = wallet("wrong-authority");
let nonce_account = crate::MdPubkey(solana_pubkey::Pubkey::new_unique().to_string());
let plan = durable_transfer_plan(&payer, &authority, &nonce_account);
let wrong_state = nonce_state(&nonce_account, &wrong_authority);
assert!(
crate::executor_solana_build_durable_nonce_transaction(&plan, &wrong_state).is_err()
);
let nonce_state = nonce_state(&nonce_account, &authority);
let unsigned = crate::executor_solana_build_durable_nonce_transaction(&plan, &nonce_state)
.unwrap_or_else(|error| panic!("unexpected durable assembly error: {error}"));
let latest_evidence = unsigned.bind_simulation(successful_simulation());
assert!(
unsigned
.sign_after_simulation(&latest_evidence, &[&payer, &authority],)
.is_err()
);
let mut undeclared = plan.clone();
undeclared
.required_signers
.retain(|required| return required.pubkey.0 != authority.pubkey().to_string());
assert!(
crate::executor_solana_build_durable_nonce_transaction(&undeclared, &nonce_state)
.is_err()
);
}
#[test]
fn durable_nonce_plan_requires_dedicated_assembly() {
let wallet = wallet("payer");
let mut plan = transfer_plan(&wallet, false, 100);
plan.policy.blockhash.kind = crate::ExApiExecutionBlockhashKind::DurableNonce;
plan.policy.blockhash.max_age_slots = std::option::Option::None;
plan.policy.blockhash.nonce_account =
std::option::Option::Some(crate::MdPubkey(wallet.pubkey().to_string()));
plan.policy.blockhash.nonce_authority =
std::option::Option::Some(crate::MdPubkey(wallet.pubkey().to_string()));
let nonce = solana_hash::Hash::new_unique().to_string();
assert!(crate::executor_solana_build_legacy_transaction(&plan, nonce.as_str()).is_err());
}
#[test]
fn invalid_blockhash_and_signer_contract_are_rejected() {
let wallet = wallet("payer");
let mut plan = transfer_plan(&wallet, false, 100);
assert!(crate::executor_solana_build_legacy_transaction(&plan, "invalid").is_err());
plan.required_signers.clear();
let blockhash = solana_hash::Hash::new_unique().to_string();
assert!(
crate::executor_solana_build_legacy_transaction(&plan, blockhash.as_str()).is_err()
);
}
}

View File

@@ -0,0 +1,276 @@
// file: kb-lib/src/executor/solana/transaction/nonce.rs
// version: 6
//! Durable nonce account state parsing and validation.
use std::str::FromStr; // rust-rules: trait-import
/// Validated current durable nonce account state used for transaction assembly.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExSolanaDurableNonceAccountState {
account: crate::MdPubkey,
authority: crate::MdPubkey,
blockhash: std::string::String,
lamports_per_signature: u64,
}
impl crate::ExSolanaDurableNonceAccountState {
/// Returns the nonce account address.
pub fn account(&self) -> &crate::MdPubkey {
return &self.account;
}
/// Returns the authority that must sign the nonce advance instruction.
pub fn authority(&self) -> &crate::MdPubkey {
return &self.authority;
}
/// Returns the durable nonce value used as the transaction message blockhash.
pub fn blockhash(&self) -> &str {
return self.blockhash.as_str();
}
/// Returns the fee rate stored when the nonce was last advanced.
pub fn lamports_per_signature(&self) -> u64 {
return self.lamports_per_signature;
}
}
/// Returns the exact serialized byte length of a Solana nonce account state.
pub const fn executor_solana_durable_nonce_account_data_length() -> usize {
return solana_nonce::state::State::size();
}
/// Parses and validates a complete System Program-owned durable nonce account snapshot.
pub fn executor_solana_parse_durable_nonce_account(
account: &crate::MdPubkey,
owner: &crate::MdProgramId,
executable: bool,
space: u64,
data: &[u8],
) -> kb_core::Result<crate::ExSolanaDurableNonceAccountState> {
let account_address = match solana_pubkey::Pubkey::from_str(account.0.as_str()) {
std::result::Result::Ok(account_address) => account_address,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_address_invalid",
format!("invalid durable nonce account address {}: {error}", account.0),
));
},
};
let owner_address = match solana_pubkey::Pubkey::from_str(owner.0.as_str()) {
std::result::Result::Ok(owner_address) => owner_address,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_owner_invalid",
format!("invalid durable nonce account owner {}: {error}", owner.0),
));
},
};
if owner_address != solana_sdk_ids::system_program::id() {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_owner_mismatch",
format!(
"durable nonce account {account_address} is owned by {owner_address} instead of the System Program"
),
));
}
if executable {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_executable",
format!("durable nonce account {account_address} must not be executable"),
));
}
let expected_length = crate::executor_solana_durable_nonce_account_data_length();
if space != expected_length as u64 {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_space_invalid",
format!(
"durable nonce account {account_address} reports {space} bytes instead of {expected_length}"
),
));
}
if data.len() != expected_length {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_data_length_invalid",
format!(
"durable nonce account {account_address} contains {} decoded bytes instead of {expected_length}",
data.len()
),
));
}
let versions = match wincode::deserialize_exact::<solana_nonce::versions::Versions>(data) {
std::result::Result::Ok(versions) => versions,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_state_invalid",
format!("cannot decode durable nonce account {account_address}: {error}"),
));
},
};
let state = match versions {
solana_nonce::versions::Versions::Legacy(_) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_legacy_version",
format!(
"durable nonce account {account_address} uses the legacy nonce domain and must be upgraded before use"
),
));
},
solana_nonce::versions::Versions::Current(state) => state,
};
let initialized = match *state {
solana_nonce::state::State::Uninitialized => {
return std::result::Result::Err(kb_core::Error::new(
"execution_nonce_account_uninitialized",
format!("durable nonce account {account_address} is not initialized"),
));
},
solana_nonce::state::State::Initialized(initialized) => initialized,
};
let authority = crate::MdPubkey(initialized.authority.to_string());
let blockhash = initialized.blockhash().to_string();
let lamports_per_signature = initialized.get_lamports_per_signature();
return std::result::Result::Ok(crate::ExSolanaDurableNonceAccountState {
account: account.clone(),
authority,
blockhash,
lamports_per_signature,
});
}
#[cfg(test)]
mod tests {
fn initialized_bytes(
authority: solana_pubkey::Pubkey,
) -> (std::vec::Vec<u8>, std::string::String) {
let source_blockhash = solana_hash::Hash::new_unique();
let durable_nonce = solana_nonce::state::DurableNonce::from_blockhash(&source_blockhash);
let expected_blockhash = durable_nonce.as_hash().to_string();
let state = solana_nonce::versions::Versions::new(
solana_nonce::state::State::new_initialized(&authority, durable_nonce, 5_000),
);
let bytes = wincode::serialize(&state)
.unwrap_or_else(|error| panic!("unexpected nonce serialization error: {error}"));
return (bytes, expected_blockhash);
}
fn account(value: solana_pubkey::Pubkey) -> crate::MdPubkey {
return crate::MdPubkey(value.to_string());
}
#[test]
fn current_initialized_nonce_state_is_decoded_exactly() {
let account = account(solana_pubkey::Pubkey::new_unique());
let authority = solana_pubkey::Pubkey::new_unique();
let (bytes, expected_blockhash) = initialized_bytes(authority);
assert_eq!(bytes.len(), crate::executor_solana_durable_nonce_account_data_length());
let parsed = crate::executor_solana_parse_durable_nonce_account(
&account,
&crate::MdProgramId(solana_sdk_ids::system_program::id().to_string()),
false,
bytes.len() as u64,
bytes.as_slice(),
)
.unwrap_or_else(|error| panic!("unexpected nonce parse error: {error}"));
assert_eq!(parsed.account(), &account);
assert_eq!(parsed.authority().0, authority.to_string());
assert_eq!(parsed.blockhash(), expected_blockhash);
assert_eq!(parsed.lamports_per_signature(), 5_000);
}
#[test]
fn malformed_owner_shape_and_state_are_rejected() {
let saccount = account(solana_pubkey::Pubkey::new_unique());
let authority = solana_pubkey::Pubkey::new_unique();
let (bytes, _blockhash) = initialized_bytes(authority);
let owner = crate::MdProgramId(solana_sdk_ids::system_program::id().to_string());
assert!(
crate::executor_solana_parse_durable_nonce_account(
&saccount,
&crate::MdProgramId(solana_pubkey::Pubkey::new_unique().to_string()),
false,
bytes.len() as u64,
bytes.as_slice(),
)
.is_err()
);
assert!(
crate::executor_solana_parse_durable_nonce_account(
&saccount,
&owner,
true,
bytes.len() as u64,
bytes.as_slice(),
)
.is_err()
);
assert!(
crate::executor_solana_parse_durable_nonce_account(
&saccount,
&owner,
false,
(bytes.len() + 1) as u64,
bytes.as_slice(),
)
.is_err()
);
assert!(
crate::executor_solana_parse_durable_nonce_account(
&saccount,
&owner,
false,
bytes.len() as u64,
&bytes[..bytes.len() - 1],
)
.is_err()
);
let mut malformed = bytes.clone();
malformed[0] = 255;
assert!(
crate::executor_solana_parse_durable_nonce_account(
&saccount,
&owner,
false,
malformed.len() as u64,
malformed.as_slice(),
)
.is_err()
);
}
#[test]
fn legacy_and_uninitialized_nonce_states_are_rejected() {
let saccount = account(solana_pubkey::Pubkey::new_unique());
let owner = crate::MdProgramId(solana_sdk_ids::system_program::id().to_string());
let legacy = solana_nonce::versions::Versions::Legacy(std::boxed::Box::new(
solana_nonce::state::State::Initialized(solana_nonce::state::Data::default()),
));
let legacy_bytes = wincode::serialize(&legacy)
.unwrap_or_else(|error| panic!("unexpected legacy serialization error: {error}"));
assert!(
crate::executor_solana_parse_durable_nonce_account(
&saccount,
&owner,
false,
legacy_bytes.len() as u64,
legacy_bytes.as_slice(),
)
.is_err()
);
let uninitialized =
solana_nonce::versions::Versions::new(solana_nonce::state::State::Uninitialized);
let uninitialized_bytes = wincode::serialize(&uninitialized)
.unwrap_or_else(|error| panic!("unexpected state serialization error: {error}"));
assert!(
crate::executor_solana_parse_durable_nonce_account(
&saccount,
&owner,
false,
uninitialized_bytes.len() as u64,
uninitialized_bytes.as_slice(),
)
.is_err()
);
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/lib.rs
// version: 22
// version: 23
//! Consolidated decoder, executor, materializer and shared model library.
#![warn(missing_docs)]
@@ -1067,6 +1067,14 @@ pub use self::executor::ExSolanaCoreVoteLockout;
pub use self::executor::ExSolanaCoreZkElGamalContextState;
/// Exposes the official ZK ElGamal proof kinds.
pub use self::executor::ExSolanaCoreZkElGamalProofType;
/// Validated current durable nonce account state used for transaction assembly.
pub use self::executor::ExSolanaDurableNonceAccountState;
/// Signed Solana transaction ready for RPC submission.
pub use self::executor::ExSolanaSignedTransaction;
/// Simulation result bound to the exact assembled message that was simulated.
pub use self::executor::ExSolanaSimulationEvidence;
/// Unsigned assembled Solana transaction used for fee estimation and simulation.
pub use self::executor::ExSolanaUnsignedTransaction;
/// Exposes the reserved `ExSplAccountCompressionExecutor` implementation.
pub use self::executor::ExSplAccountCompressionExecutor;
/// Exposes the typed SPL ATA execution intent.
@@ -1191,6 +1199,14 @@ pub use self::executor::ExWeightedSwapStabbleExecutor;
pub use self::executor::executor_api_serialize_payload_json;
/// Exposes the pretty JSON payload serializer.
pub use self::executor::executor_api_serialize_payload_json_pretty;
/// Builds an unsigned durable nonce transaction from a prepared execution plan and validated nonce state.
pub use self::executor::executor_solana_build_durable_nonce_transaction;
/// Builds an unsigned legacy Solana transaction from a prepared execution plan.
pub use self::executor::executor_solana_build_legacy_transaction;
/// Returns the exact serialized byte length of a Solana nonce account state.
pub use self::executor::executor_solana_durable_nonce_account_data_length;
/// Parses and validates a complete System Program-owned durable nonce account snapshot.
pub use self::executor::executor_solana_parse_durable_nonce_account;
/// Exposes `executor_spl_token2022_validate_confidential_proof_references`.
pub use self::executor::executor_spl_token2022_validate_confidential_proof_references;
/// Stable native and SPL administration materializer.
@@ -1756,6 +1772,10 @@ pub(crate) use self::decoder::decoder_spl_token2022_decode;
pub(crate) use self::decoder::decoder_spl_token2022_entry_for_tag;
/// Returns the first byte of one retained Token-2022 payload.
pub(crate) use self::decoder::decoder_spl_token2022_payload_tag;
/// Stable operation code for the nonce advance injected by durable transaction assembly.
pub(crate) use self::executor::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION;
/// Maximum over-the-wire size of a Solana transaction packet.
pub(crate) use self::executor::EX_SOLANA_TRANSACTION_MAX_WIRE_BYTES;
/// Crate-root access to `EX_SPL_TOKEN_MAX_BATCH_ACCOUNTS`.
pub(crate) use self::executor::EX_SPL_TOKEN_MAX_BATCH_ACCOUNTS;
/// Crate-root access to `EX_SPL_TOKEN_MAX_BATCH_INSTRUCTIONS`.
@@ -1772,6 +1792,8 @@ pub(crate) use self::executor::EX_SPL_TOKEN2022_MAX_TRANSFER_FEE_SOURCE_ACCOUNTS
pub(crate) use self::executor::EX_SPL_TOKEN2022_MAX_UI_AMOUNT_BYTES;
/// Canonical tracing target for this crate.
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SOLANA_CORE;
/// Canonical tracing target for Solana transaction assembly.
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SOLANA_TRANSACTION;
/// Canonical SPL Memo tracing target.
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_ATA;
/// Canonical tracing target for the SPL ElGamal registry executor.