Skip to main content

espresso_node/request_response/
data_source.rs

1//! This file contains the [`DataSource`] trait. This trait allows the [`RequestResponseProtocol`]
2//! to calculate/derive a response for a specific request. In the confirmation layer the implementer
3//! would be something like a [`FeeMerkleTree`] for fee catchup
4
5use std::{marker::PhantomData, sync::Arc};
6
7use anyhow::{Context, Result, bail};
8use async_trait::async_trait;
9use espresso_types::{
10    NodeState, PubKey, SeqTypes, retain_accounts,
11    traits::SequencerPersistence,
12    v0_3::{RewardAccountV1, RewardMerkleTreeV1},
13    v0_4::{RewardAccountV2, RewardMerkleTreeV2},
14};
15use hotshot::traits::NodeImplementation;
16use hotshot_new_protocol::storage::NewProtocolStorage;
17use hotshot_query_service::{
18    data_source::{
19        VersionedDataSource,
20        storage::{FileSystemStorage, NodeStorage, SqlStorage},
21    },
22    node::BlockId,
23};
24use hotshot_types::{data::ViewNumber, traits::network::ConnectedNetwork, vote::HasViewNumber};
25use itertools::Itertools;
26use jf_merkle_tree_compat::{
27    ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, LookupResult,
28    MerkleTreeScheme, UniversalMerkleTreeScheme,
29};
30use request_response::data_source::DataSource as DataSourceTrait;
31
32use super::request::{Request, Response};
33use crate::{
34    api::{BlocksFrontier, RewardMerkleTreeDataSource, RewardMerkleTreeV2Data},
35    catchup::{
36        CatchupStorage, add_fee_accounts_to_state, add_v1_reward_accounts_to_state,
37        add_v2_reward_accounts_to_state,
38    },
39    consensus_handle::ConsensusHandle,
40};
41
42/// Query Service Storage types that can be used for request-response data source
43#[derive(Clone)]
44pub enum Storage {
45    Sql(Arc<SqlStorage>),
46    Fs(Arc<FileSystemStorage<SeqTypes>>),
47}
48
49#[derive(Clone)]
50pub struct DataSource<
51    I: NodeImplementation<SeqTypes>,
52    N: ConnectedNetwork<PubKey>,
53    P: SequencerPersistence,
54> {
55    /// The consensus adapter handle
56    pub consensus_handle: Arc<ConsensusHandle<SeqTypes, I>>,
57    /// The node's state
58    pub node_state: NodeState,
59    /// The storage
60    pub storage: Option<Storage>,
61    /// sequencer persistence
62    pub persistence: Arc<P>,
63    /// Phantom data
64    pub phantom: PhantomData<N>,
65}
66
67/// Implement the trait that allows the [`RequestResponseProtocol`] to calculate/derive a response for a specific request
68#[async_trait]
69impl<I: NodeImplementation<SeqTypes>, N: ConnectedNetwork<PubKey>, P: SequencerPersistence>
70    DataSourceTrait<Request> for DataSource<I, N, P>
71where
72    I::Storage: NewProtocolStorage<SeqTypes>,
73{
74    async fn derive_response_for(&self, request: &Request) -> Result<Response> {
75        match request {
76            Request::Accounts(height, view, accounts) => {
77                // Try to get accounts from memory first, then fall back to storage
78                if let Some(state) = self.consensus_handle.state(ViewNumber::new(*view)).await
79                    && let Ok(accounts) =
80                        retain_accounts(&state.fee_merkle_tree, accounts.iter().copied())
81                {
82                    return Ok(Response::Accounts(accounts));
83                }
84
85                // Fall back to storage
86                let (merkle_tree, leaf) = match &self.storage {
87                    Some(Storage::Sql(storage)) => storage
88                        .get_accounts(&self.node_state, *height, ViewNumber::new(*view), accounts)
89                        .await
90                        .with_context(|| "failed to get accounts from sql storage")?,
91                    Some(Storage::Fs(_)) => bail!("fs storage not supported for accounts"),
92                    _ => bail!("storage was not initialized"),
93                };
94
95                // If we successfully fetched accounts from storage, try to add them back into the in-memory
96                // state.
97                if let Err(err) = add_fee_accounts_to_state(
98                    &*self.consensus_handle,
99                    &ViewNumber::new(*view),
100                    accounts,
101                    &merkle_tree,
102                    leaf,
103                )
104                .await
105                {
106                    tracing::warn!(?view, "Cannot update fetched account state: {err:#}");
107                }
108
109                Ok(Response::Accounts(merkle_tree))
110            },
111
112            Request::Leaf(height) => {
113                // Legacy heights can be served from in-memory undecided leaves; new-protocol
114                // heights always fall through to storage.
115                if let Ok(leaf_chain) =
116                    legacy_leaf_chain_from_memory(&*self.consensus_handle, *height).await
117                {
118                    return Ok(Response::Leaf(leaf_chain));
119                }
120
121                let leaf_chain = match &self.storage {
122                    Some(Storage::Sql(storage)) => storage
123                        .get_leaf_chain(*height)
124                        .await
125                        .with_context(|| "failed to get leaf from sql storage")?,
126                    Some(Storage::Fs(_)) => bail!("fs storage not supported for leaf"),
127                    _ => bail!("storage was not initialized"),
128                };
129
130                Ok(Response::Leaf(leaf_chain))
131            },
132            Request::ChainConfig(commitment) => {
133                // Try to get the chain config from memory first, then fall back to storage
134                if let Some(state) = self.consensus_handle.decided_state().await {
135                    let chain_config_from_memory = state.chain_config;
136                    if chain_config_from_memory.commit() == *commitment
137                        && let Some(chain_config) = chain_config_from_memory.resolve()
138                    {
139                        return Ok(Response::ChainConfig(chain_config));
140                    }
141                }
142
143                // Fall back to storage
144                Ok(Response::ChainConfig(match &self.storage {
145                    Some(Storage::Sql(storage)) => storage
146                        .get_chain_config(*commitment)
147                        .await
148                        .with_context(|| "failed to get chain config from sql storage")?,
149                    Some(Storage::Fs(_)) => {
150                        bail!("fs storage not supported for chain config")
151                    },
152                    _ => bail!("storage was not initialized"),
153                }))
154            },
155            Request::BlocksFrontier(height, view) => {
156                // First try to respond from memory
157                let blocks_frontier_from_memory: Option<Result<BlocksFrontier>> = self
158                    .consensus_handle
159                    .state(ViewNumber::new(*view))
160                    .await
161                    .map(|state| {
162                        let tree = &state.block_merkle_tree;
163                        let frontier = tree.lookup(tree.num_leaves() - 1).expect_ok()?.1;
164                        Ok(frontier)
165                    });
166
167                if let Some(Ok(blocks_frontier_from_memory)) = blocks_frontier_from_memory {
168                    return Ok(Response::BlocksFrontier(blocks_frontier_from_memory));
169                } else {
170                    // If we can't get the blocks frontier from memory, fall through to storage
171                    let blocks_frontier_from_storage = match &self.storage {
172                        Some(Storage::Sql(storage)) => storage
173                            .get_frontier(&self.node_state, *height, ViewNumber::new(*view))
174                            .await
175                            .with_context(|| "failed to get blocks frontier from sql storage")?,
176                        Some(Storage::Fs(_)) => {
177                            bail!("fs storage not supported for blocks frontier")
178                        },
179                        _ => bail!("storage was not initialized"),
180                    };
181
182                    Ok(Response::BlocksFrontier(blocks_frontier_from_storage))
183                }
184            },
185            Request::RewardAccountsV2(height, view, accounts) => {
186                // Try to get the reward accounts from memory first, then fall back to storage
187                if let Some(state) = self.consensus_handle.state(ViewNumber::new(*view)).await
188                    && let Ok(reward_accounts) = retain_v2_reward_accounts(
189                        &state.reward_merkle_tree_v2,
190                        accounts.iter().copied(),
191                    )
192                {
193                    return Ok(Response::RewardAccountsV2(reward_accounts));
194                }
195
196                // Fall back to storage
197                let (merkle_tree, leaf) = match &self.storage {
198                    Some(Storage::Sql(storage)) => storage
199                        .get_reward_accounts_v2(
200                            &self.node_state,
201                            *height,
202                            ViewNumber::new(*view),
203                            accounts,
204                        )
205                        .await
206                        .with_context(|| "failed to get accounts from sql storage")?,
207                    Some(Storage::Fs(_)) => {
208                        bail!("fs storage not supported for reward accounts")
209                    },
210                    _ => bail!("storage was not initialized"),
211                };
212
213                // If we successfully fetched accounts from storage, try to add them back into the in-memory
214                // state.
215                if let Err(err) = add_v2_reward_accounts_to_state(
216                    &*self.consensus_handle,
217                    &ViewNumber::new(*view),
218                    accounts,
219                    &merkle_tree,
220                    leaf,
221                )
222                .await
223                {
224                    tracing::warn!(?view, "Cannot update fetched account state: {err:#}");
225                }
226
227                Ok(Response::RewardAccountsV2(merkle_tree))
228            },
229
230            Request::RewardAccountsV1(height, view, accounts) => {
231                // Try to get the reward accounts from memory first, then fall back to storage
232                if let Some(state) = self.consensus_handle.state(ViewNumber::new(*view)).await
233                    && let Ok(reward_accounts) = retain_v1_reward_accounts(
234                        &state.reward_merkle_tree_v1,
235                        accounts.iter().copied(),
236                    )
237                {
238                    return Ok(Response::RewardAccountsV1(reward_accounts));
239                }
240
241                // Fall back to storage
242                let (merkle_tree, leaf) = match &self.storage {
243                    Some(Storage::Sql(storage)) => storage
244                        .get_reward_accounts_v1(
245                            &self.node_state,
246                            *height,
247                            ViewNumber::new(*view),
248                            accounts,
249                        )
250                        .await
251                        .with_context(|| "failed to get v1 reward accounts from sql storage")?,
252                    Some(Storage::Fs(_)) => {
253                        bail!("fs storage not supported for v1 reward accounts")
254                    },
255                    _ => bail!("storage was not initialized"),
256                };
257
258                // If we successfully fetched accounts from storage, try to add them back into the in-memory
259                // state.
260                if let Err(err) = add_v1_reward_accounts_to_state(
261                    &*self.consensus_handle,
262                    &ViewNumber::new(*view),
263                    accounts,
264                    &merkle_tree,
265                    leaf,
266                )
267                .await
268                {
269                    tracing::warn!(
270                        ?view,
271                        "Cannot update fetched v1 reward account state: {err:#}"
272                    );
273                }
274
275                Ok(Response::RewardAccountsV1(merkle_tree))
276            },
277            Request::VidShare(block_number, _request_id) => {
278                // Load the VID share from storage
279                let vid_share = match &self.storage {
280                    Some(Storage::Sql(storage)) => storage
281                        .get_vid_share::<SeqTypes>(BlockId::Number(*block_number as usize))
282                        .await
283                        .with_context(|| "failed to get vid share from sql storage")?,
284                    Some(Storage::Fs(storage)) => {
285                        // Open a read transaction
286                        let mut transaction = storage
287                            .read()
288                            .await
289                            .with_context(|| "failed to open fs storage transaction")?;
290
291                        // Get the VID share
292                        transaction
293                            .vid_share(BlockId::Number(*block_number as usize))
294                            .await
295                            .with_context(|| "failed to get vid share from fs storage")?
296                    },
297                    _ => bail!("storage was not initialized"),
298                };
299
300                Ok(Response::VidShare(vid_share))
301            },
302            Request::StateCert(epoch) => {
303                let state_cert = self
304                    .persistence
305                    .get_state_cert_by_epoch(*epoch)
306                    .await
307                    .with_context(|| {
308                        format!("failed to get state cert for epoch {epoch} from persistence")
309                    })?;
310
311                match state_cert {
312                    Some(cert) => Ok(Response::StateCert(cert)),
313                    None => bail!("State certificate for epoch {epoch} not found"),
314                }
315            },
316            Request::Cert2(height) => {
317                let cert2 = match &self.storage {
318                    Some(Storage::Sql(storage)) => storage
319                        .load_cert2(*height)
320                        .await
321                        .with_context(|| "failed to load cert2 from sql storage")?,
322                    Some(Storage::Fs(_)) => bail!("fs storage not supported for cert2"),
323                    _ => bail!("storage was not initialized"),
324                };
325
326                match cert2 {
327                    Some(cert2) => Ok(Response::Cert2(cert2)),
328                    None => bail!("no cert2 available at height {height}"),
329                }
330            },
331            Request::RewardMerkleTreeV2(height, view) => {
332                // Try to get the reward merkle tree from memory first, then fall back to storage
333                if let Some(state) = self.consensus_handle.state(ViewNumber::new(*view)).await {
334                    let tree_data =
335                        TryInto::<RewardMerkleTreeV2Data>::try_into(&state.reward_merkle_tree_v2)
336                            .inspect_err(|err| {
337                            tracing::debug!(
338                                %err, height, view,
339                                "cannot serve reward merkle tree from memory"
340                            )
341                        })?;
342                    let merkle_tree_bytes = bincode::serialize(&tree_data)
343                        .context("Merkle tree serialization failed; this should never happen.")?;
344
345                    return Ok(Response::RewardMerkleTreeV2(merkle_tree_bytes));
346                }
347
348                // Fall back to storage
349                let merkle_tree_bytes = match &self.storage {
350                    Some(Storage::Sql(storage)) => storage
351                        .load_tree(*height)
352                        .await
353                        .with_context(|| "failed to get reward merkle tree from sql storage")?,
354                    Some(Storage::Fs(_)) => {
355                        bail!("fs storage not supported for reward merkle tree catchup")
356                    },
357                    _ => bail!("storage was not initialized"),
358                };
359
360                Ok(Response::RewardMerkleTreeV2(merkle_tree_bytes))
361            },
362        }
363    }
364}
365
366/// Build a legacy-protocol 3-chain leaf chain decided at `height` from in-memory undecided leaves.
367///
368/// Returns an error if the chain cannot be assembled from memory (e.g. the height is below the
369/// latest decided leaf).
370async fn legacy_leaf_chain_from_memory<I: NodeImplementation<SeqTypes>>(
371    consensus_handle: &ConsensusHandle<SeqTypes, I>,
372    height: u64,
373) -> anyhow::Result<Vec<espresso_types::Leaf2>>
374where
375    I::Storage: NewProtocolStorage<SeqTypes>,
376{
377    let mut leaves = consensus_handle.undecided_leaves().await;
378    leaves.sort_by_key(|l| l.view_number());
379
380    let (position, mut last_leaf) = leaves
381        .iter()
382        .find_position(|l| l.height() == height)
383        .ok_or_else(|| anyhow::anyhow!("leaf at height {height} not in memory"))?;
384
385    let mut leaf_chain = vec![last_leaf.clone()];
386    for leaf in leaves.iter().skip(position + 1) {
387        if leaf.justify_qc().view_number() == last_leaf.view_number() {
388            leaf_chain.push(leaf.clone());
389        } else {
390            continue;
391        }
392        if leaf.view_number() == last_leaf.view_number() + 1 {
393            last_leaf = leaf;
394            break;
395        }
396        last_leaf = leaf;
397    }
398
399    for leaf in leaves
400        .iter()
401        .skip_while(|l| l.view_number() <= last_leaf.view_number())
402    {
403        if leaf.justify_qc().view_number() == last_leaf.view_number() {
404            leaf_chain.push(leaf.clone());
405            return Ok(leaf_chain);
406        }
407    }
408
409    anyhow::bail!("incomplete leaf chain in memory for height {height}")
410}
411
412/// Get a partial snapshot of the given reward state, which contains only the specified accounts.
413///
414/// Fails if one of the requested accounts is not represented in the original `state`.
415pub fn retain_v2_reward_accounts(
416    state: &RewardMerkleTreeV2,
417    accounts: impl IntoIterator<Item = RewardAccountV2>,
418) -> anyhow::Result<RewardMerkleTreeV2> {
419    let mut snapshot = RewardMerkleTreeV2::from_commitment(state.commitment());
420    for account in accounts {
421        match state.universal_lookup(account) {
422            LookupResult::Ok(elem, proof) => {
423                // This remember cannot fail, since we just constructed a valid proof, and are
424                // remembering into a tree with the same commitment.
425                snapshot.remember(account, elem, proof).unwrap();
426            },
427            LookupResult::NotFound(proof) => {
428                // Likewise this cannot fail.
429                snapshot.non_membership_remember(account, proof).unwrap()
430            },
431            LookupResult::NotInMemory => {
432                bail!("missing account {account}");
433            },
434        }
435    }
436
437    Ok(snapshot)
438}
439
440/// Get a partial snapshot of the given reward state, which contains only the specified accounts.
441///
442/// Fails if one of the requested accounts is not represented in the original `state`.
443pub fn retain_v1_reward_accounts(
444    state: &RewardMerkleTreeV1,
445    accounts: impl IntoIterator<Item = RewardAccountV1>,
446) -> anyhow::Result<RewardMerkleTreeV1> {
447    let mut snapshot = RewardMerkleTreeV1::from_commitment(state.commitment());
448    for account in accounts {
449        match state.universal_lookup(account) {
450            LookupResult::Ok(elem, proof) => {
451                // This remember cannot fail, since we just constructed a valid proof, and are
452                // remembering into a tree with the same commitment.
453                snapshot.remember(account, elem, proof).unwrap();
454            },
455            LookupResult::NotFound(proof) => {
456                // Likewise this cannot fail.
457                snapshot.non_membership_remember(account, proof).unwrap()
458            },
459            LookupResult::NotInMemory => {
460                bail!("missing account {account}");
461            },
462        }
463    }
464
465    Ok(snapshot)
466}