v0.1.0-pre.022
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# file: kb-lib/Cargo.toml
|
||||
# version: 13
|
||||
# version: 14
|
||||
|
||||
[package]
|
||||
name = "kb-lib"
|
||||
@@ -25,6 +25,11 @@ solana-compute-budget-interface.workspace = true
|
||||
solana-feature-gate-interface.workspace = true
|
||||
solana-hash.workspace = true
|
||||
solana-instruction.workspace = true
|
||||
solana-transaction.workspace = true
|
||||
solana-signer.workspace = true
|
||||
solana-nonce.workspace = true
|
||||
solana-message.workspace = true
|
||||
solana-keypair.workspace = true
|
||||
solana-loader-v3-interface.workspace = true
|
||||
solana-pubkey.workspace = true
|
||||
solana-sdk-ids.workspace = true
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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;
|
||||
|
||||
32
kb-lib/src/executor/solana/transaction.rs
Normal file
32
kb-lib/src/executor/solana/transaction.rs
Normal 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;
|
||||
13
kb-lib/src/executor/solana/transaction/constants.rs
Normal file
13
kb-lib/src/executor/solana/transaction/constants.rs
Normal 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";
|
||||
988
kb-lib/src/executor/solana/transaction/core.rs
Normal file
988
kb-lib/src/executor/solana/transaction/core.rs
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
276
kb-lib/src/executor/solana/transaction/nonce.rs
Normal file
276
kb-lib/src/executor/solana/transaction/nonce.rs
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user