Skip to main content

tycho_common/
storage.rs

1//! Storage traits used by Tycho
2use std::{
3    collections::{HashMap, HashSet},
4    fmt::Display,
5};
6
7use async_trait::async_trait;
8use chrono::NaiveDateTime;
9use thiserror::Error;
10
11use crate::{
12    dto,
13    models::{
14        blockchain::{
15            Block, EntryPoint, EntryPointWithTracingParams, TracedEntryPoint, TracedEntryPoints,
16            TracingParams, TracingResult, Transaction,
17        },
18        contract::{Account, AccountBalance, AccountDelta},
19        protocol::{
20            ComponentBalance, ProtocolComponent, ProtocolComponentState,
21            ProtocolComponentStateDelta, QualityRange,
22        },
23        token::Token,
24        Address, BlockHash, Chain, ComponentId, ContractId, EntryPointId, ExtractionState,
25        PaginationParams, ProtocolSystem, ProtocolType, StoreKey, TxHash,
26    },
27    Bytes,
28};
29
30/// Identifies a block in storage.
31#[derive(Debug, Clone, PartialEq, Hash, Eq)]
32pub enum BlockIdentifier {
33    /// Identifies the block by its position on a specified chain.
34    ///
35    /// This form of identification has potential risks as it may become
36    /// ambiguous in certain situations. For example, if the block has not been
37    /// finalised, there exists a possibility of forks occurring. As a result,
38    /// the same number could refer to different blocks on different forks.
39    Number((Chain, i64)),
40
41    /// Identifies a block by its hash.
42    ///
43    /// The hash should be unique across multiple chains. Preferred method if
44    /// the block is very recent.
45    Hash(BlockHash),
46
47    /// Latest stored block for the target chain
48    ///
49    /// Returns the block with the highest block number on the target chain.
50    Latest(Chain),
51}
52
53impl Display for BlockIdentifier {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(f, "{self:?}")
56    }
57}
58
59#[derive(Error, Debug, PartialEq, Clone)]
60pub enum StorageError {
61    #[error("Could not find {0} with id `{1}`!")]
62    NotFound(String, String),
63    #[error("The entity {0} with id {1} was already present!")]
64    DuplicateEntry(String, String),
65    #[error("Could not find related {0} for {1} with id `{2}`!")]
66    NoRelatedEntity(String, String, String),
67    #[error("DecodeError: {0}")]
68    DecodeError(String),
69    #[error("Unexpected storage error: {0}")]
70    Unexpected(String),
71    #[error("Currently unsupported operation: {0}")]
72    Unsupported(String),
73    #[error("Write cache unexpectedly dropped notification channel!")]
74    WriteCacheGoneAway(),
75    #[error("Invalid block range encountered")]
76    InvalidBlockRange(),
77}
78
79/// Storage methods for chain specific objects.
80///
81/// This trait abstracts the specific implementation details of a blockchain's
82/// entities, allowing the user to add and retrieve blocks and transactions in a
83/// generic way.
84///
85/// For traceability protocol components and contracts changes are linked to
86/// blocks of their respective chain if applicable. This means while indexing we
87/// need to keep a lightweight and cross chain compatible representation of
88/// blocks and transactions in storage.
89///
90/// It's defined generically over two associated types:
91///
92/// * `Block`: represents a block in the blockchain.
93/// * `Transaction`: represents a transaction within a block.
94#[async_trait]
95pub trait ChainGateway {
96    /// Upserts a new block to the blockchain's storage.
97    ///
98    /// Ignores any existing tx, if the new entry has different attributes
99    /// no error is raised and the old entry is kept.
100    ///
101    /// # Parameters
102    /// - `new`: An instance of `Self::Block`, representing the new block to be stored.
103    ///
104    /// # Returns
105    /// - Empty ok result indicates success. Failure might occur if the block is already present.
106    async fn upsert_block(&self, new: &[Block]) -> Result<(), StorageError>;
107    /// Retrieves a block from storage.
108    ///
109    /// # Parameters
110    /// - `id`: Block's unique identifier of type `BlockIdentifier`.
111    ///
112    /// # Returns
113    /// - An Ok result containing the block. Might fail if the block does not exist yet.
114    async fn get_block(&self, id: &BlockIdentifier) -> Result<Block, StorageError>;
115    /// Upserts a transaction to storage.
116    ///
117    /// Ignores any existing tx, if the new entry has different attributes
118    /// no error is raised and the old entry is kept.
119    ///
120    /// # Parameters
121    /// - `new`: An instance of `Self::Transaction`, representing the new transaction to be stored.
122    ///
123    /// # Returns
124    /// - Empty ok result indicates success. Failure might occur if the
125    /// corresponding block does not exists yet, or if the transaction already
126    /// exists.
127    async fn upsert_tx(&self, new: &[Transaction]) -> Result<(), StorageError>;
128
129    /// Tries to retrieve a transaction from the blockchain's storage using its
130    /// hash.
131    ///
132    /// # Parameters
133    /// - `hash`: The byte slice representing the hash of the transaction to be retrieved.
134    ///
135    /// # Returns
136    /// - An Ok result containing the transaction. Might fail if the transaction does not exist yet.
137    async fn get_tx(&self, hash: &TxHash) -> Result<Transaction, StorageError>;
138
139    /// Reverts the blockchain storage to a previous version.
140    ///
141    /// Reverting state signifies deleting database history. Only the main branch will be kept.
142    ///
143    /// Blocks that are greater than the provided block (`to`) are deleted and any versioned rows
144    /// which were invalidated in the deleted blocks are updated to be valid again.
145    ///
146    /// # Parameters
147    /// - `to` The version to revert to. Given a block uses VersionKind::Last behaviour.
148    /// - `db` The database gateway.
149    ///
150    /// # Returns
151    /// - An Ok if the revert is successful, or a `StorageError` if not.
152    async fn revert_state(&self, to: &BlockIdentifier) -> Result<(), StorageError>;
153}
154
155/// Store and retrieve state of Extractors.
156///
157/// Sometimes extractors may wish to persist their state across restart. E.g.
158/// substreams based extractors need to store the cursor, so they can continue
159/// processing where they left off.
160///
161/// Extractors are uniquely identified by a name and the respective chain which
162/// they are indexing.
163#[async_trait]
164pub trait ExtractionStateGateway {
165    /// Retrieves the state of an extractor instance from a storage.
166    ///
167    /// # Parameters
168    /// - `name` A unique name for the extractor instance.
169    /// - `chain` The chain this extractor is indexing.
170    ///
171    /// # Returns
172    /// Ok if the corrsponding state was retrieved successfully, Err in
173    /// case the state was not found.
174    async fn get_state(&self, name: &str, chain: &Chain) -> Result<ExtractionState, StorageError>;
175
176    /// Saves the state of an extractor instance to a storage.
177    ///
178    /// Creates an entry if not present yet, or updates an already existing
179    /// entry.
180    ///
181    /// # Parameters
182    /// - `state` The state of the extractor that needs to be saved.
183    ///
184    /// # Returns
185    /// Ok, if state was stored successfully, Err if the state is not valid.
186    async fn save_state(&self, state: &ExtractionState) -> Result<(), StorageError>;
187}
188
189/// Point in time as either block or timestamp. If a block is chosen it
190/// timestamp attribute is used.
191#[derive(Debug, Clone, PartialEq, Hash, Eq)]
192pub enum BlockOrTimestamp {
193    Block(BlockIdentifier),
194    Timestamp(NaiveDateTime),
195}
196
197// TODO: remove once deprecated chain field is removed from VersionParam
198#[allow(deprecated)]
199impl TryFrom<&dto::VersionParam> for BlockOrTimestamp {
200    type Error = anyhow::Error;
201
202    fn try_from(version: &dto::VersionParam) -> Result<Self, Self::Error> {
203        match (&version.timestamp, &version.block) {
204            (_, Some(block)) => {
205                // If a full block is provided, we prioritize hash over number and chain
206                let block_identifier = match (&block.hash, &block.chain, &block.number) {
207                    (Some(hash), _, _) => BlockIdentifier::Hash(hash.clone()),
208                    (_, Some(chain), Some(number)) => {
209                        BlockIdentifier::Number((Chain::from(*chain), *number))
210                    }
211                    _ => {
212                        return Err(anyhow::format_err!("Insufficient block information".to_owned()))
213                    }
214                };
215                Ok(BlockOrTimestamp::Block(block_identifier))
216            }
217            (Some(timestamp), None) => Ok(BlockOrTimestamp::Timestamp(*timestamp)),
218            (None, None) => {
219                Err(anyhow::format_err!("Missing timestamp or block identifier".to_owned()))
220            }
221        }
222    }
223}
224
225/// References certain states within a single block.
226///
227/// **Note:** Not all methods that take a version will support all version kinds,
228/// the versions here are included for completeness and to document the
229/// retrieval behaviour that is possible with the storage layout. Please refer
230/// to the individual implementation for information about which version kinds
231/// it supports.
232#[derive(Debug, Clone, Default)]
233pub enum VersionKind {
234    /// Represents the final state within a specific block. Essentially, it
235    /// retrieves the state subsequent to the execution of the last transaction
236    /// executed in that block.
237    #[default]
238    Last,
239    /// Represents the initial state of a specific block. In other words,
240    /// it is the state before any transaction has been executed within that block.
241    First,
242    /// Represents a specific transactions indexed position within a block.
243    /// It includes the state after executing the transaction at that index.
244    Index(i64),
245}
246
247/// A version desribes the state of the DB at a exact point in time.
248/// See the module level docs for more information on how versioning works.
249#[derive(Debug, Clone)]
250pub struct Version(pub BlockOrTimestamp, pub VersionKind);
251
252impl Version {
253    pub fn from_block_number(chain: Chain, number: i64) -> Self {
254        Self(BlockOrTimestamp::Block(BlockIdentifier::Number((chain, number))), VersionKind::Last)
255    }
256    pub fn from_ts(ts: NaiveDateTime) -> Self {
257        Self(BlockOrTimestamp::Timestamp(ts), VersionKind::Last)
258    }
259}
260
261// Helper type to retrieve entities with their total retrievable count.
262#[derive(Debug)]
263pub struct WithTotal<T> {
264    pub entity: T,
265    pub total: Option<i64>,
266}
267
268/// Store and retrieve protocol related structs.
269///
270/// This trait defines how to retrieve protocol components, state as well as
271/// tokens from storage.
272#[async_trait]
273pub trait ProtocolGateway {
274    /// Retrieve ProtocolComponent from the db
275    ///
276    /// # Parameters
277    /// - `chain` The chain of the component
278    /// - `system` Allows to optionally filter by system.
279    /// - `ids` Allows to optionally filter by id.
280    /// - `min_tvl` Allows to optionally filter by min tvl.
281    /// - `pagination_params` Optional pagination parameters to control the number of results.
282    ///
283    /// # Returns
284    /// Ok, if found else Err
285    async fn get_protocol_components(
286        &self,
287        chain: &Chain,
288        system: Option<String>,
289        ids: Option<&[&str]>,
290        min_tvl: Option<f64>,
291        pagination_params: Option<&PaginationParams>,
292    ) -> Result<WithTotal<Vec<ProtocolComponent>>, StorageError>;
293
294    /// Retrieves owners of tokens
295    ///
296    /// Queries for owners (protocol components) of tokens that have a certain minimum
297    /// balance and returns a maximum aggregate of those in case there are multiple
298    /// owners.
299    ///
300    /// # Parameters
301    /// - `chain` The chain of the component
302    /// - `tokens` The tokens to query for, any component with at least one of these tokens is
303    ///   returned.
304    /// - `min_balance` A minimum balance we expect the component to have on any of the tokens
305    ///   mentioned in `tokens`.
306    async fn get_token_owners(
307        &self,
308        chain: &Chain,
309        tokens: &[Address],
310        min_balance: Option<f64>,
311    ) -> Result<HashMap<Address, (ComponentId, Bytes)>, StorageError>;
312
313    async fn add_protocol_components(&self, new: &[ProtocolComponent]) -> Result<(), StorageError>;
314
315    async fn delete_protocol_components(
316        &self,
317        to_delete: &[ProtocolComponent],
318        block_ts: NaiveDateTime,
319    ) -> Result<(), StorageError>;
320
321    /// Stores new found ProtocolTypes.
322    ///
323    /// # Parameters
324    /// - `new_protocol_types`  The new protocol types.
325    ///
326    /// # Returns
327    /// Ok if stored successfully.
328    async fn add_protocol_types(
329        &self,
330        new_protocol_types: &[ProtocolType],
331    ) -> Result<(), StorageError>;
332
333    /// Retrieve protocol component states
334    ///
335    /// This resource is versioned, the version can be specified by either block
336    /// or timestamp, for off-chain components, a block version will error.
337    ///
338    /// As the state is retained on a transaction basis on blockchain systems, a
339    /// single version may relate to more than one state. In these cases a
340    /// versioned result is returned, if requesting `Version:All` with the
341    /// latest entry being the state at the end of the block and the first entry
342    /// represents the first change to the state within the block.
343    ///
344    /// # Parameters
345    /// - `chain` The chain of the component
346    /// - `at` The version at which the state is valid at.
347    /// - `system` The protocol system this component belongs to
348    /// - `ids` The external ids of the components e.g. addresses, or the pairs
349    /// - `retrieve_balances` Whether to retrieve the balances for the components.
350    /// - `pagination_params` Optional pagination parameters to control the number of results.
351    async fn get_protocol_states(
352        &self,
353        chain: &Chain,
354        at: Option<Version>,
355        system: Option<String>,
356        ids: Option<&[&str]>,
357        retrieve_balances: bool,
358        pagination_params: Option<&PaginationParams>,
359    ) -> Result<WithTotal<Vec<ProtocolComponentState>>, StorageError>;
360
361    async fn update_protocol_states(
362        &self,
363        new: &[(TxHash, ProtocolComponentStateDelta)],
364    ) -> Result<(), StorageError>;
365
366    /// Retrieves a tokens from storage
367    ///
368    /// # Parameters
369    /// - `chain` The chain this token is implemented on.
370    /// - `address` The address for the token within the chain.
371    /// - `quality` The quality of the token.
372    /// - `traded_n_days_ago` The number of days ago the token was traded.
373    /// - `pagination_params` Optional pagination parameters to control the number of results.
374    ///
375    /// # Returns
376    /// Ok if the results could be retrieved from the storage, else errors.
377    async fn get_tokens(
378        &self,
379        chain: Chain,
380        address: Option<&[&Address]>,
381        quality: QualityRange,
382        traded_n_days_ago: Option<NaiveDateTime>,
383        pagination_params: Option<&PaginationParams>,
384    ) -> Result<WithTotal<Vec<Token>>, StorageError>;
385
386    /// Saves multiple component balances to storage.
387    ///
388    /// # Parameters
389    /// - `component_balances` The component balances to insert.
390    ///
391    /// # Return
392    /// Ok if all component balances could be inserted, Err if at least one token failed to
393    /// insert.
394    async fn add_component_balances(
395        &self,
396        component_balances: &[ComponentBalance],
397    ) -> Result<(), StorageError>;
398
399    /// Saves multiple tokens to storage.
400    ///
401    /// Inserts token into storage. Tokens and their properties are assumed to
402    /// be immutable.
403    ///
404    /// # Parameters
405    /// - `tokens` The tokens to insert.
406    ///
407    /// # Return
408    /// Ok if all tokens could be inserted, Err if at least one token failed to
409    /// insert.
410    async fn add_tokens(&self, tokens: &[Token]) -> Result<(), StorageError>;
411
412    /// Updates multiple tokens in storage.
413    ///
414    /// Updates token in storage. Will warn if one of the tokens does not exist in the
415    /// database. Currently assumes that token addresses are unique across chains.
416    ///
417    /// # Parameters
418    /// - `tokens` The tokens to update.
419    ///
420    /// # Return
421    /// Ok if all tokens could be inserted, Err if at least one token failed to
422    /// insert.
423    async fn update_tokens(&self, tokens: &[Token]) -> Result<(), StorageError>;
424
425    /// Retrieve protocol state changes
426    ///
427    /// Fetches all state changes that occurred for the given chain
428    ///
429    /// # Parameters
430    /// - `chain` The chain of the component
431    /// - `start_version` The version at which to start looking for changes at.
432    /// - `end_version` The version at which to stop looking for changes.
433    ///
434    /// # Return
435    /// A list of ProtocolStateDeltas containing all state changes, Err if no changes were found.
436    async fn get_protocol_states_delta(
437        &self,
438        chain: &Chain,
439        start_version: Option<&BlockOrTimestamp>,
440        end_version: &BlockOrTimestamp,
441    ) -> Result<Vec<ProtocolComponentStateDelta>, StorageError>;
442
443    /// Retrieve protocol component balance changes
444    ///
445    /// Fetches all balance changes that occurred for the given protocol system
446    ///
447    /// # Parameters
448    /// - `chain` The chain of the component
449    /// - `start_version` The version at which to start looking for changes at.
450    /// - `target_version` The version at which to stop looking for changes.
451    ///
452    /// # Return
453    /// A vec containing ComponentBalance objects for changed components.
454    async fn get_balance_deltas(
455        &self,
456        chain: &Chain,
457        start_version: Option<&BlockOrTimestamp>,
458        target_version: &BlockOrTimestamp,
459    ) -> Result<Vec<ComponentBalance>, StorageError>;
460
461    async fn get_component_balances(
462        &self,
463        chain: &Chain,
464        ids: Option<&[&str]>,
465        version: Option<&Version>,
466    ) -> Result<HashMap<String, HashMap<Bytes, ComponentBalance>>, StorageError>;
467
468    async fn get_token_prices(&self, chain: &Chain) -> Result<HashMap<Bytes, f64>, StorageError>;
469
470    async fn upsert_component_tvl(
471        &self,
472        chain: &Chain,
473        tvl_values: &HashMap<String, f64>,
474    ) -> Result<(), StorageError>;
475
476    /// Retrieve a list of actively supported protocol systems
477    ///
478    /// Fetches the list of protocol systems supported by the Tycho indexing service.
479    ///
480    /// # Parameters
481    /// - `chain` The chain for which to retrieve supported protocol systems.
482    /// - `pagination_params` Optional pagination parameters to control the number of results.
483    ///
484    /// # Return
485    /// A paginated list of supported protocol systems, along with the total count.
486    async fn get_protocol_systems(
487        &self,
488        chain: &Chain,
489        pagination_params: Option<&PaginationParams>,
490    ) -> Result<WithTotal<Vec<String>>, StorageError>;
491
492    /// Retrieve the components total value locked (TVL).
493    ///
494    /// # Parameters
495    /// - `chain` The chain for which to retrieve the total value locked
496    /// - `system` The protocol system for which to retrieve the total value locked
497    /// - `ids` The ids of the components to retrieve the total value locked for
498    /// - `pagination_params` Optional pagination parameters to control the number of results.
499    ///
500    /// # Return
501    /// A result with a map of component ids to their TVL. Err if storage access failed.
502    async fn get_component_tvls(
503        &self,
504        chain: &Chain,
505        system: Option<String>,
506        ids: Option<&[&str]>,
507        pagination_params: Option<&PaginationParams>,
508    ) -> Result<WithTotal<HashMap<String, f64>>, StorageError>;
509}
510
511/// Filters for entry points queries in the database.
512// Shalow but can be used to add more filters without breaking backwards compatibility in the future
513#[derive(Debug, Clone)]
514pub struct EntryPointFilter {
515    pub protocol_system: ProtocolSystem,
516    pub component_ids: Option<Vec<ComponentId>>,
517}
518
519impl EntryPointFilter {
520    pub fn new(protocol: ProtocolSystem) -> Self {
521        Self { protocol_system: protocol, component_ids: None }
522    }
523
524    pub fn with_component_ids(mut self, component_ids: Vec<ComponentId>) -> Self {
525        self.component_ids = Some(component_ids);
526        self
527    }
528}
529
530// Trait for entry point gateway operations.
531#[async_trait]
532pub trait EntryPointGateway {
533    /// Inserts a list of entry points into the database.
534    ///
535    /// # Arguments
536    /// * `entry_points` - The map of component ids to their entry points to insert.
537    ///
538    /// Note: This function ignores conflicts on inserts.
539    async fn insert_entry_points(
540        &self,
541        entry_points: &HashMap<ComponentId, HashSet<EntryPoint>>,
542    ) -> Result<(), StorageError>;
543
544    /// Inserts a list of entry points with their tracing params into the database.
545    ///
546    /// # Arguments
547    /// * `entry_points_params` - The map of entry points to their tracing params to insert and
548    ///   component id for the component linked to the params.
549    ///
550    /// Note: This function ignores conflicts on inserts.
551    async fn insert_entry_point_tracing_params(
552        &self,
553        entry_points_params: &HashMap<EntryPointId, HashSet<(TracingParams, ComponentId)>>,
554    ) -> Result<(), StorageError>;
555
556    /// Retrieves a map of component ids to a set of entry points from the database.
557    ///
558    /// # Arguments
559    /// * `filter` - The EntryPointFilter to apply to the query.
560    /// * `pagination_params` - The pagination parameters to apply to the query, if None, all
561    ///   results are returned.
562    ///
563    /// # Returns
564    /// A map of component ids to a set of entry points.
565    async fn get_entry_points(
566        &self,
567        filter: EntryPointFilter,
568        pagination_params: Option<&PaginationParams>,
569    ) -> Result<WithTotal<HashMap<ComponentId, HashSet<EntryPoint>>>, StorageError>;
570
571    /// Retrieves a map of component ids to a set of entry points with their tracing data from the
572    /// database.
573    ///
574    /// # Arguments
575    /// * `filter` - The EntryPointFilter to apply to the query.
576    /// * `pagination_params` - The pagination parameters to apply to the query, if None, all
577    ///   results are returned.
578    ///
579    /// # Returns
580    /// A map of component ids to a set of entry points with their tracing params.
581    async fn get_entry_points_tracing_params(
582        &self,
583        filter: EntryPointFilter,
584        pagination_params: Option<&PaginationParams>,
585    ) -> Result<WithTotal<HashMap<ComponentId, HashSet<EntryPointWithTracingParams>>>, StorageError>;
586
587    /// Upserts a list of traced entry points into the database. Updates the result if it already
588    /// exists for the same entry point and tracing params.
589    ///
590    /// # Arguments
591    /// * `traced_entry_points` - The list of traced entry points to upsert.
592    async fn upsert_traced_entry_points(
593        &self,
594        traced_entry_points: &[TracedEntryPoint],
595    ) -> Result<(), StorageError>;
596
597    /// Retrieves all tracing results for a set of entry points from the database.
598    ///
599    /// # Arguments
600    /// * `entry_points` - The set of entry points to retrieve tracing results for.
601    ///
602    /// # Returns
603    /// A map of entry point ids to a map of tracing params to tracing results.
604    async fn get_traced_entry_points(
605        &self,
606        entry_points: &HashSet<EntryPointId>,
607    ) -> Result<HashMap<EntryPointId, HashMap<TracingParams, TracingResult>>, StorageError>;
608
609    /// Retrieves the traced entry points of a set of components from the database.
610    ///
611    /// # Arguments
612    /// * `filter` - The EntryPointFilter to apply to the query.
613    /// * `pagination_params` - The pagination parameters to apply to the query, if None, all
614    ///   results are returned.
615    ///
616    /// # Returns
617    /// A map of component ids to the entry points with the tracing params linked to the
618    /// component, and their tracing results. Params without a tracing result are not included.
619    async fn get_traced_entry_points_by_component(
620        &self,
621        filter: EntryPointFilter,
622        pagination_params: Option<&PaginationParams>,
623    ) -> Result<WithTotal<TracedEntryPoints>, StorageError>;
624}
625
626/// Manage contracts and their state in storage.
627///
628/// Specifies how to retrieve, add and update contracts in storage.
629#[async_trait]
630pub trait ContractStateGateway {
631    /// Get a contracts state from storage
632    ///
633    /// This method retrieves a single contract from the database.
634    ///
635    /// # Parameters
636    /// - `id` The identifier for the contract.
637    /// - `version` Version at which to retrieve state for. None retrieves the latest state.
638    /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
639    ///   includes storage slot.
640    async fn get_contract(
641        &self,
642        id: &ContractId,
643        version: Option<&Version>,
644        include_slots: bool,
645    ) -> Result<Account, StorageError>;
646
647    /// Get multiple contracts' states from storage.
648    ///
649    /// This method retrieves balance and code, and optionally storage, of
650    /// multiple contracts in a chain. It can optionally filter by given
651    /// addresses and retrieve state for specific versions.
652    ///
653    /// # Parameters:
654    /// - `chain`: The blockchain where the contracts reside.
655    /// - `addresses`: Filter for specific addresses. If set to `None`, it retrieves all indexed
656    ///   contracts in the chain.
657    /// - `version`: Version at which to retrieve state for. If set to `None`, it retrieves the
658    ///   latest state.
659    /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
660    ///   includes storage slot.
661    /// - `pagination_params`: Optional pagination parameters to control the number of results.
662    ///
663    /// # Returns:
664    /// A `Result` with a list of contract states if the operation is
665    /// successful, or a `StorageError` if the operation fails.
666    async fn get_contracts(
667        &self,
668        chain: &Chain,
669        addresses: Option<&[Address]>,
670        version: Option<&Version>,
671        include_slots: bool,
672        pagination_params: Option<&PaginationParams>,
673    ) -> Result<WithTotal<Vec<Account>>, StorageError>;
674
675    /// Inserts a new contract into the database.
676    ///
677    /// Inserts only the static values of the contract. To insert the contract slots, balance and
678    /// code please use the `update_contracts` method.
679    ///
680    /// # Arguments
681    /// - `new`: A reference to the new contract state to be inserted.
682    ///
683    /// # Returns
684    /// - A Result with Ok if the operation was successful, and an Err containing `StorageError` if
685    ///   there was an issue inserting the contract into the database. E.g. if the contract already
686    ///   existed.
687    async fn insert_contract(&self, new: &Account) -> Result<(), StorageError>;
688
689    /// Update multiple contracts
690    ///
691    /// Given contract deltas, this method will batch all updates to contracts across a single
692    /// chain.
693    ///
694    /// As changes are versioned by transaction, each changeset needs to be associated with a
695    /// transaction hash. All references transaction are assumed to be already persisted.
696    ///
697    /// # Arguments
698    ///
699    /// - `new`: A reference to a slice of tuples where each tuple has a transaction hash (`TxHash`)
700    ///   and a reference to the state delta (`&Self::Delta`) for that transaction.
701    ///
702    /// # Returns
703    ///
704    /// A Result with `Ok` if the operation was successful, and an `Err` containing
705    /// `StorageError` if there was an issue updating the contracts in the database. E.g. if a
706    /// transaction can't be located by it's reference or accounts refer to a different chain then
707    /// the one specified.
708    async fn update_contracts(&self, new: &[(TxHash, AccountDelta)]) -> Result<(), StorageError>;
709
710    /// Mark a contract as deleted
711    ///
712    /// Issues a soft delete of the contract.
713    ///
714    /// # Parameters
715    /// - `id` The identifier for the contract.
716    /// - `at_tx` The transaction hash which deleted the contract. This transaction is assumed to be
717    ///   in storage already. None retrieves the latest state.
718    ///
719    /// # Returns
720    /// Ok if the deletion was successful, might Err if:
721    ///  - Contract is not present in storage.
722    ///  - Deletion transaction is not present in storage.
723    ///  - Contract was already deleted.
724    async fn delete_contract(&self, id: &ContractId, at_tx: &TxHash) -> Result<(), StorageError>;
725
726    /// Retrieve a account delta between two versions.
727    ///
728    /// Given start version V1 and end version V2, this method will return the
729    /// changes necessary to move from V1 to V2. So if V1 < V2, it will contain
730    /// the changes of all accounts that changed between the two versions with the
731    /// values corresponding to V2. If V2 < V1 then it will contain all the
732    /// slots that changed between the two versions with the values corresponding to V1.
733    ///
734    /// This method is mainly meant to handle reverts, but can also be used to create delta changes
735    /// between two historical version thus providing the basis for creating a backtestable stream
736    /// of messages.
737    ///
738    /// # Parameters
739    ///
740    /// - `chain` The chain for which to generate the delta changes.
741    /// - `start_version` The deltas start version, given a block uses VersionKind::Last behaviour.
742    ///   If None the latest version is assumed.
743    /// - `end_version` The deltas end version, given a block uses VersionKind::Last behaviour.
744    ///
745    /// # Note
746    ///
747    /// A choice to utilize `BlockOrTimestamp` has been made intentionally in
748    /// this scenario as passing a `Version` by user isn't quite logical.
749    /// Support for deltas is limited to the states at the start or end of
750    /// blocks because blockchain reorganization at the transaction level is not
751    /// common.
752    ///
753    /// The decision to use either the beginning or end state of a block is
754    /// automatically determined by the underlying logic. For example, if we are
755    /// tracing back, `VersionKind::First` retrieval mode will be used.
756    /// Conversely, if we're progressing forward, we would apply the
757    /// `VersionKind::Last` semantics.
758    ///
759    /// # Returns
760    /// A map containing the necessary changes to update a state from start_version to end_version.
761    /// Errors if:
762    ///     - The versions can't be located in storage.
763    ///     - There was an error with the database
764    async fn get_accounts_delta(
765        &self,
766        chain: &Chain,
767        start_version: Option<&BlockOrTimestamp>,
768        end_version: &BlockOrTimestamp,
769    ) -> Result<Vec<AccountDelta>, StorageError>;
770
771    /// Saves multiple account balances to storage.
772    ///
773    /// # Parameters
774    /// - `account_balances` The account balances to insert.
775    ///
776    /// # Return
777    /// Ok if all account balances could be inserted, Err if at least one token failed to insert.
778    async fn add_account_balances(
779        &self,
780        account_balances: &[AccountBalance],
781    ) -> Result<(), StorageError>;
782
783    /// Retrieve account balances
784    ///
785    /// # Parameters
786    /// - `chain` The chain of the account balances
787    /// - `accounts` The accounts to query for. If set to `None`, it retrieves balances for all
788    ///   indexed
789    ///  accounts in the chain.
790    /// - `version` Version at which to retrieve balances for. If set to `None`, it retrieves the
791    ///   latest balances.
792    async fn get_account_balances(
793        &self,
794        chain: &Chain,
795        accounts: Option<&[Address]>,
796        version: Option<&Version>,
797    ) -> Result<HashMap<Address, HashMap<Address, AccountBalance>>, StorageError>;
798}
799
800pub trait Gateway:
801    ChainGateway
802    + ContractStateGateway
803    + ExtractionStateGateway
804    + ProtocolGateway
805    + ContractStateGateway
806    + EntryPointGateway
807    + Send
808    + Sync
809{
810}
811
812/// When a value was written: the timestamp of the writing block.
813///
814/// This is the unit of the database's `valid_from`, so the cache orders writes the way the
815/// database versions them. Block timestamps are unique per chain: the extractor adds a
816/// microsecond to a block that shares its second with the previous block. A block header supplies
817/// the timestamp; a snapshot row takes it from its `valid_from`.
818#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
819pub struct WriteTimestamp(NaiveDateTime);
820
821impl WriteTimestamp {
822    pub fn new(block_ts: NaiveDateTime) -> Self {
823        Self(block_ts)
824    }
825
826    pub fn block_ts(&self) -> NaiveDateTime {
827        self.0
828    }
829}
830
831impl From<&Block> for WriteTimestamp {
832    fn from(block: &Block) -> Self {
833        Self::new(block.ts)
834    }
835}
836
837/// Write timestamps of one account's values, one per database row.
838#[derive(Debug, Clone, PartialEq)]
839pub struct AccountWriteTimestamps {
840    pub slots: HashMap<StoreKey, WriteTimestamp>,
841    pub native_balance: WriteTimestamp,
842    pub code: WriteTimestamp,
843    pub token_balances: HashMap<Address, WriteTimestamp>,
844}
845
846impl AccountWriteTimestamps {
847    /// One timestamp for every value of `account`.
848    pub fn uniform(account: &Account, at: WriteTimestamp) -> Self {
849        Self {
850            slots: account
851                .slots
852                .keys()
853                .map(|key| (key.clone(), at))
854                .collect(),
855            native_balance: at,
856            code: at,
857            token_balances: account
858                .token_balances
859                .keys()
860                .map(|token| (token.clone(), at))
861                .collect(),
862        }
863    }
864}
865
866/// One account's live state and the write timestamp of every value: the row's `valid_from`.
867#[derive(Debug, Clone, PartialEq)]
868pub struct AccountSnapshot {
869    pub account: Account,
870    pub written_at: AccountWriteTimestamps,
871}
872
873/// One component's live state, timestamped with the newest `valid_from` among its rows or, for a
874/// component without rows, its `created_at`.
875#[derive(Debug, Clone, PartialEq)]
876pub struct ComponentSnapshot {
877    pub system: ProtocolSystem,
878    pub state: ProtocolComponentState,
879    pub updated_at: WriteTimestamp,
880}
881
882/// All live contracts and components of one chain, read from one database snapshot.
883#[derive(Debug, Clone, PartialEq)]
884pub struct StateSnapshot {
885    pub accounts: Vec<AccountSnapshot>,
886    pub components: Vec<ComponentSnapshot>,
887}
888
889/// Reads all live contracts and components of a chain from one database snapshot.
890#[async_trait]
891pub trait StateSnapshotGateway {
892    /// All live accounts and components of `chain`, every value timestamped with the block
893    /// that wrote its row.
894    ///
895    /// Implementations must read everything from one database snapshot: a row committed after
896    /// the read starts is invisible to every part of the result. In SQL terms, a read-only
897    /// `REPEATABLE READ` transaction or stricter.
898    async fn state_snapshot(&self, chain: &Chain) -> Result<StateSnapshot, StorageError>;
899}