1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
use super::super::crossbar::CrossbarClient;
use super::super::Gateway;
use super::super::lut_owner::LutOwner;
use super::OracleAccountData;
use anyhow::{anyhow, Context};
use anyhow::Error as AnyhowError;
use bytemuck::{Pod, Zeroable};
use futures::future::join_all;
use solana_client::nonblocking::rpc_client::RpcClient;
use anchor_lang::prelude::Pubkey;
use std::str::FromStr;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct QueueAccountData {
/// The address of the authority which is permitted to add/remove allowed enclave measurements.
pub authority: Pubkey,
/// Allowed enclave measurements.
pub mr_enclaves: [[u8; 32]; 32],
/// The addresses of the quote oracles who have a valid
/// verification status and have heartbeated on-chain recently.
pub oracle_keys: [Pubkey; 78],
reserved1: [u8; 40],
pub secp_oracle_signing_keys: [[u8; 20]; 30],
pub ed25519_oracle_signing_keys: [Pubkey; 30],
/// The maximum allowable time until a EnclaveAccount needs to be re-verified on-chain.
pub max_quote_verification_age: i64,
/// The unix timestamp when the last quote oracle heartbeated on-chain.
pub last_heartbeat: i64,
pub node_timeout: i64,
/// The minimum number of lamports a quote oracle needs to lock-up in order to heartbeat and verify other quotes.
pub oracle_min_stake: u64,
pub allow_authority_override_after: i64,
/// The number of allowed enclave measurements.
pub mr_enclaves_len: u32,
/// The length of valid quote oracles for the given attestation queue.
pub oracle_keys_len: u32,
/// The reward paid to quote oracles for attesting on-chain.
pub reward: u32,
/// Incrementer used to track the current quote oracle permitted to run any available functions.
pub curr_idx: u32,
/// Incrementer used to garbage collect and remove stale quote oracles.
pub gc_idx: u32,
pub require_authority_heartbeat_permission: u8,
pub require_authority_verify_permission: u8,
pub require_usage_permissions: u8,
pub signer_bump: u8,
pub mint: Pubkey,
pub lut_slot: u64,
pub allow_subsidies: u8,
_ebuf6: [u8; 15],
pub ncn: Pubkey,
_resrved: u64, // only necessary for multiple vaults at once, otherwise we can use the ncn
// tickets
pub vaults: [VaultInfo; 4],
pub last_reward_epoch: u64,
_ebuf4: [u8; 32],
_ebuf2: [u8; 256],
_ebuf1: [u8; 504], // was 512 change to 504 to make room for new u64
}
unsafe impl Pod for QueueAccountData {}
unsafe impl Zeroable for QueueAccountData {}
#[repr(C)]
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct VaultInfo {
pub vault_key: Pubkey,
pub last_reward_epoch: u64,
}
unsafe impl Pod for VaultInfo {}
unsafe impl Zeroable for VaultInfo {}
impl QueueAccountData {
pub fn size() -> usize {
8 + std::mem::size_of::<QueueAccountData>()
}
/// Loads the oracles currently in the queue.
pub fn oracle_keys(&self) -> Vec<Pubkey> {
self.oracle_keys[..self.oracle_keys_len as usize].to_vec()
}
/// Loads the QueueAccountData from the given key.
pub async fn load(client: &RpcClient, key: &Pubkey) -> Result<QueueAccountData, AnyhowError> {
let account = client.get_account_data(key).await?;
let buf = account[8..].to_vec();
let parsed: &QueueAccountData = bytemuck::try_from_bytes(&buf)
.map_err(|e| anyhow!("Failed to parse QueueAccountData: {:?}", e))?;
Ok(*parsed)
}
/// Fetches all oracle accounts from the oracle keys and returns them as a list of (Pubkey, OracleAccountData).
pub async fn fetch_oracle_accounts(
&self,
client: &RpcClient,
) -> Result<Vec<(Pubkey, OracleAccountData)>, AnyhowError> {
let keys = self.oracle_keys();
let accounts_data = client
.get_multiple_accounts(&keys)
.await?
.into_iter()
.map(|account| {
let buf = account.unwrap_or_default().data[8..].to_vec();
let oracle_account: &OracleAccountData = bytemuck::try_from_bytes(&buf).unwrap();
*oracle_account
})
.collect::<Vec<_>>();
let result = keys
.into_iter()
.zip(accounts_data.into_iter())
.collect::<Vec<_>>();
Ok(result)
}
/// Fetches all gateways from the oracle accounts and tests them to see if they are reachable.
/// Returns a list of reachable gateways.
/// # Arguments
/// * `client` - The RPC client to use for fetching the oracle accounts.
/// # Returns
/// A list of reachable gateways.
pub async fn fetch_gateways(&self, client: &RpcClient) -> Result<Vec<Gateway>, AnyhowError> {
let gateways = self
.fetch_oracle_accounts(client)
.await?
.into_iter()
.map(|x| x.1)
.filter_map(|x| x.gateway_uri())
.map(Gateway::new)
.collect::<Vec<_>>();
let mut test_futures = Vec::new();
for gateway in gateways.iter() {
test_futures.push(gateway.test_gateway());
}
let results = join_all(test_futures).await;
let mut good_gws = Vec::new();
for (i, is_good) in results.into_iter().enumerate() {
if is_good {
good_gws.push(gateways[i].clone());
}
}
Ok(good_gws)
}
/// Fetches a gateway from the crossbar service
///
/// # Arguments
/// * `crossbar` - The crossbar client to use for fetching gateways
///
/// # Returns
/// * `Result<Gateway, AnyhowError>` - A Gateway instance ready for use
pub async fn fetch_gateway_from_crossbar(&self, crossbar: &CrossbarClient) -> Result<Gateway, AnyhowError> {
// Default to mainnet, but this could be made configurable
let network = "mainnet";
// Fetch gateways for the network
let gateways = crossbar.fetch_gateways(network).await
.context("Failed to fetch gateways from crossbar")?;
let gateway_url = gateways
.first()
.ok_or_else(|| anyhow!("No gateways available for network: {}", network))?;
Ok(Gateway::new(gateway_url.clone()))
}
}
impl LutOwner for QueueAccountData {
fn lut_slot(&self) -> u64 {
self.lut_slot
}
}
/// Higher-level Queue struct that matches the JavaScript pattern
///
/// This struct represents a queue instance with a specific pubkey,
/// similar to the JavaScript Queue class which has a program and pubkey.
pub struct Queue {
pub pubkey: Pubkey,
pub client: RpcClient,
}
impl Queue {
/// Default devnet queue key
pub const DEFAULT_DEVNET_KEY: &'static str = "EYiAmGSdsQTuCw413V5BzaruWuCCSDgTPtBGvLkXHbe7";
/// Default mainnet queue key
pub const DEFAULT_MAINNET_KEY: &'static str = "A43DyUGA7s8eXPxqEjJY6EBu1KKbNgfxF8h17VAHn13w";
/// Creates a new Queue instance
///
/// # Arguments
/// * `client` - RPC client for Solana connections
/// * `pubkey` - The public key of the queue account
pub fn new(client: RpcClient, pubkey: Pubkey) -> Self {
Self { pubkey, client }
}
/// Creates a Queue instance with the default mainnet key
///
/// # Arguments
/// * `client` - RPC client for Solana connections
pub fn default_mainnet(client: RpcClient) -> Result<Self, AnyhowError> {
let pubkey = Pubkey::from_str(Self::DEFAULT_MAINNET_KEY)
.map_err(|e| anyhow!("Failed to parse mainnet queue key: {}", e))?;
Ok(Self::new(client, pubkey))
}
/// Creates a Queue instance with the default devnet key
///
/// # Arguments
/// * `client` - RPC client for Solana connections
pub fn default_devnet(client: RpcClient) -> Result<Self, AnyhowError> {
let pubkey = Pubkey::from_str(Self::DEFAULT_DEVNET_KEY)
.map_err(|e| anyhow!("Failed to parse devnet queue key: {}", e))?;
Ok(Self::new(client, pubkey))
}
/// Loads the queue data from on-chain
///
/// # Returns
/// * `Result<QueueAccountData, AnyhowError>` - The queue account data
pub async fn load_data(&self) -> Result<QueueAccountData, AnyhowError> {
QueueAccountData::load(&self.client, &self.pubkey).await
}
/// Fetches a gateway from the crossbar service, automatically detecting network
///
/// This method matches the JavaScript implementation exactly:
/// 1. Tries to load data from the default mainnet queue
/// 2. If that fails, assumes devnet
/// 3. Fetches available gateways for the detected network
/// 4. Returns the first gateway
///
/// # Arguments
/// * `crossbar` - The crossbar client to use for fetching gateways
///
/// # Returns
/// * `Result<Gateway, AnyhowError>` - A Gateway instance ready for use
///
/// # Example
/// ```rust,no_run
/// use switchboard_on_demand_client::{CrossbarClient, accounts::queue::Queue};
/// use solana_client::nonblocking::rpc_client::RpcClient;
/// use std::str::FromStr;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let client = RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
/// let queue = Queue::default_mainnet(client)?;
/// let crossbar = CrossbarClient::default();
/// let gateway = queue.fetch_gateway_from_crossbar(&crossbar).await?;
/// # Ok(())
/// # }
/// ```
pub async fn fetch_gateway_from_crossbar(&self, crossbar: &CrossbarClient) -> Result<Gateway, AnyhowError> {
let mut network = "mainnet";
// Try to load data from the default mainnet queue to detect network
let mainnet_client = RpcClient::new(self.client.url());
let mainnet_queue = Queue::default_mainnet(mainnet_client)?;
if mainnet_queue.load_data().await.is_err() {
network = "devnet";
}
// Fetch gateways for the detected network
let gateways = crossbar.fetch_gateways(network).await
.context("Failed to fetch gateways from crossbar")?;
let gateway_url = gateways
.first()
.ok_or_else(|| anyhow!("No gateways available for network: {}", network))?;
Ok(Gateway::new(gateway_url.clone()))
}
}