Files
khadhroony-bot3/kb-lib/src/executor/solana/transaction/nonce.rs
2026-07-24 23:24:14 +02:00

277 lines
10 KiB
Rust

// 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()
);
}
}