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