refactor: Simplify nonce management by replacing global cache with direct fetch

Remove NonceCache singleton pattern and replace with fetch_nonce_info function
that directly fetches nonce information from RPC. This simplifies the API by
eliminating cache initialization and state management, making it easier for
users to manage durable nonces.
This commit is contained in:
ysq
2025-10-07 21:20:00 +08:00
parent 2d9976368b
commit e13c891cc9
7 changed files with 78 additions and 244 deletions
+1 -54
View File
@@ -1,10 +1,7 @@
use crate::common::SolanaRpcClient;
use anyhow::Result;
use solana_address_lookup_table_interface::state::AddressLookupTable;
use solana_sdk::{
message::{v0, AddressLookupTableAccount},
pubkey::Pubkey,
};
use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey};
pub async fn fetch_address_lookup_table_account(
rpc: &SolanaRpcClient,
@@ -18,53 +15,3 @@ pub async fn fetch_address_lookup_table_account(
};
Ok(address_lookup_table_account)
}
#[inline]
pub fn extract_lookup_table_indexes(
instructions: &[solana_sdk::instruction::Instruction],
lookup_table_account: &AddressLookupTableAccount,
) -> Option<v0::MessageAddressTableLookup> {
use std::collections::{HashMap, HashSet};
// 构建地址到索引的映射(O(1) 查找)
let addr_to_index: HashMap<&Pubkey, u8> = lookup_table_account
.addresses
.iter()
.enumerate()
.filter_map(|(idx, addr)| u8::try_from(idx).ok().map(|i| (addr, i)))
.collect();
// 收集所有需要的账户及其权限
let mut writable_indexes = Vec::new();
let mut readonly_indexes = Vec::new();
let mut seen = HashSet::new();
for instruction in instructions {
for account_meta in &instruction.accounts {
// 跳过已处理的账户
if !seen.insert(&account_meta.pubkey) {
continue;
}
// 在查找表中查找账户
if let Some(&index) = addr_to_index.get(&account_meta.pubkey) {
if account_meta.is_writable {
writable_indexes.push(index);
} else {
readonly_indexes.push(index);
}
}
}
}
// 如果没有找到任何账户,返回 None
if writable_indexes.is_empty() && readonly_indexes.is_empty() {
return None;
}
Some(v0::MessageAddressTableLookup {
account_key: lookup_table_account.key,
writable_indexes,
readonly_indexes,
})
}
+20 -115
View File
@@ -1,23 +1,10 @@
use parking_lot::Mutex;
use crate::common::SolanaRpcClient;
use solana_hash::Hash;
use solana_nonce::state::State;
use solana_nonce::versions::Versions;
use solana_sdk::account_utils::StateMut;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
use std::sync::{Arc, OnceLock};
use tracing::error;
use crate::common::SolanaRpcClient;
/// NonceInfo structure to store nonce-related information
pub struct NonceInfo {
/// Nonce account address
pub nonce_account: Option<Pubkey>,
/// Current nonce value
pub current_nonce: Hash,
/// Whether it has been used
pub used: bool,
}
/// DurableNonceInfo structure to store durable nonce-related information
#[derive(Clone)]
@@ -28,109 +15,27 @@ pub struct DurableNonceInfo {
pub current_nonce: Option<Hash>,
}
/// NonceInfoStore singleton for storing and managing NonceInfo
pub struct NonceCache {
/// Internally stored NonceInfo data
nonce_info: Mutex<NonceInfo>,
}
// Use static OnceLock to ensure thread safety of singleton pattern
static NONCE_CACHE: OnceLock<Arc<NonceCache>> = OnceLock::new();
impl NonceCache {
/// Get NonceInfoStore singleton instance
pub fn get_instance() -> Arc<NonceCache> {
NONCE_CACHE
.get_or_init(|| {
Arc::new(NonceCache {
nonce_info: Mutex::new(NonceInfo {
nonce_account: None,
current_nonce: Hash::default(),
used: false,
}),
})
})
.clone()
}
/// Initialize nonce information
pub fn init(&self, nonce_account_str: Option<String>) {
let nonce_account = nonce_account_str.and_then(|s| Pubkey::from_str(&s).ok());
self.update_nonce_info_partial(nonce_account, None, Some(false));
}
/// Get a copy of NonceInfo
pub fn get_nonce_info(&self) -> NonceInfo {
let nonce_info = self.nonce_info.lock();
NonceInfo {
nonce_account: nonce_info.nonce_account,
current_nonce: nonce_info.current_nonce,
used: nonce_info.used,
}
}
pub fn get_durable_nonce_info() -> DurableNonceInfo {
let nonce_info = Self::get_instance().get_nonce_info();
let nonce_account = nonce_info.nonce_account;
let current_nonce =
if nonce_account.is_some() && nonce_info.current_nonce != Hash::default() {
Some(nonce_info.current_nonce)
} else {
None
};
DurableNonceInfo { nonce_account, current_nonce }
}
/// Partially update NonceInfo, only update the passed fields
pub fn update_nonce_info_partial(
&self,
nonce_account: Option<Pubkey>,
current_nonce: Option<Hash>,
used: Option<bool>,
) {
let mut current = self.nonce_info.lock();
// Only update the passed fields
if let Some(account) = nonce_account {
current.nonce_account = Some(account);
}
if let Some(nonce) = current_nonce {
current.current_nonce = nonce;
}
if let Some(u) = used {
current.used = u;
}
}
/// Mark nonce as used
pub fn mark_used(&self) {
self.update_nonce_info_partial(None, None, Some(true));
}
/// Fetch nonce information using RPC
pub async fn fetch_nonce_info_use_rpc(
&self,
rpc: &SolanaRpcClient,
) -> Result<(), anyhow::Error> {
match rpc.get_account(&self.get_nonce_info().nonce_account.unwrap()).await {
Ok(account) => match account.state() {
Ok(Versions::Current(state)) => {
if let State::Initialized(data) = *state {
let blockhash = data.durable_nonce.as_hash();
let old_nonce_info = self.get_nonce_info();
if old_nonce_info.current_nonce != *blockhash {
self.update_nonce_info_partial(None, Some(*blockhash), Some(false));
}
}
/// Fetch nonce information using RPC
pub async fn fetch_nonce_info(
rpc: &SolanaRpcClient,
nonce_account: Pubkey,
) -> Option<DurableNonceInfo> {
match rpc.get_account(&nonce_account).await {
Ok(account) => match account.state() {
Ok(Versions::Current(state)) => {
if let State::Initialized(data) = *state {
let blockhash = data.durable_nonce.as_hash();
return Some(DurableNonceInfo {
nonce_account: Some(nonce_account),
current_nonce: Some(*blockhash),
});
}
_ => (),
},
Err(e) => {
error!("Failed to get nonce account information: {:?}", e);
}
_ => (),
},
Err(e) => {
error!("Failed to get nonce account information: {:?}", e);
}
Ok(())
}
None
}