ab_client_api/
lib.rs

1//! Client API
2
3#![expect(incomplete_features, reason = "generic_const_exprs")]
4// TODO: This feature is not actually used in this crate, but is added as a workaround for
5//  https://github.com/rust-lang/rust/issues/141492
6#![feature(generic_const_exprs)]
7
8use ab_aligned_buffer::SharedAlignedBuffer;
9use ab_core_primitives::address::Address;
10use ab_core_primitives::block::owned::GenericOwnedBlock;
11use ab_core_primitives::block::{BlockNumber, BlockRoot};
12use ab_core_primitives::segments::{SegmentHeader, SegmentIndex};
13use ab_merkle_tree::mmr::MerkleMountainRange;
14use rclite::Arc;
15use std::io;
16use std::sync::Arc as StdArc;
17
18// TODO: This is a workaround for https://github.com/rust-lang/rust/issues/139866 that allows the
19//  code to compile. Constant 4294967295 is hardcoded here and below for compilation to succeed.
20#[expect(clippy::assertions_on_constants, reason = "Intentional documentation")]
21#[expect(clippy::eq_op, reason = "Intentional documentation")]
22const _: () = {
23    assert!(u32::MAX == 4294967295);
24};
25
26// TODO: Make this a `#[transparent]` struct to improve usability (avoiding the need for
27//  `generic_const_exprs` feature in downstream crates)?
28/// Type alias for Merkle Mountain Range with block roots.
29///
30/// NOTE: `u32` is smaller than `BlockNumber`'s internal `u64` but will be sufficient for a long
31/// time and substantially decrease the size of the data structure.
32pub type BlockMerkleMountainRange = MerkleMountainRange<4294967295>;
33
34/// State of a contract slot
35#[derive(Debug, Clone)]
36pub struct ContractSlotState {
37    /// Owner of the slot
38    pub owner: Address,
39    /// Contract that manages the slot
40    pub contract: Address,
41    /// Slot contents
42    pub contents: SharedAlignedBuffer,
43}
44
45/// Additional details about a block
46#[derive(Debug, Clone)]
47pub struct BlockDetails {
48    /// Merkle Mountain Range with block
49    pub mmr_with_block: Arc<BlockMerkleMountainRange>,
50    /// System contracts state after block
51    pub system_contract_states: StdArc<[ContractSlotState]>,
52}
53
54// TODO: Probably move it elsewhere
55/// Origin
56#[derive(Debug, Clone)]
57pub enum BlockOrigin {
58    // TODO: Take advantage of this in block import
59    /// Created locally by block builder
60    LocalBlockBuilder {
61        /// Additional details about a block
62        block_details: BlockDetails,
63    },
64    /// Received during the sync process
65    Sync,
66    /// Broadcast on the network during normal operation (not sync)
67    Broadcast,
68}
69
70/// Error for [`ChainInfo::block()`]
71#[derive(Debug, thiserror::Error)]
72pub enum ReadBlockError {
73    /// Unknown block root
74    #[error("Unknown block root")]
75    UnknownBlockRoot,
76    /// Failed to decode the block
77    #[error("Failed to decode the block")]
78    FailedToDecode,
79    /// Storage item read error
80    #[error("Storage item read error")]
81    StorageItemReadError {
82        /// Low-level error
83        #[from]
84        error: io::Error,
85    },
86}
87
88/// Error for [`ChainInfoWrite::persist_block()`]
89#[derive(Debug, thiserror::Error)]
90pub enum PersistBlockError {
91    /// Missing parent
92    #[error("Missing parent")]
93    MissingParent,
94    /// Block is outside the acceptable range
95    #[error("Block is outside the acceptable range")]
96    OutsideAcceptableRange,
97    /// Storage item write error
98    #[error("Storage item write error")]
99    StorageItemWriteError {
100        /// Low-level error
101        #[from]
102        error: io::Error,
103    },
104}
105
106/// Error for [`ChainInfoWrite::persist_segment_headers()`]
107#[derive(Debug, thiserror::Error)]
108pub enum PersistSegmentHeadersError {
109    /// Segment index must strictly follow the last segment index, can't store segment header
110    #[error(
111        "Segment index {segment_index} must strictly follow last segment index \
112        {last_segment_index}, can't store segment header"
113    )]
114    MustFollowLastSegmentIndex {
115        /// Segment index that was attempted to be inserted
116        segment_index: SegmentIndex,
117        /// Last segment index
118        last_segment_index: SegmentIndex,
119    },
120    /// The first segment index must be zero
121    #[error("First segment index must be zero, found {segment_index}")]
122    FirstSegmentIndexZero {
123        /// Segment index that was attempted to be inserted
124        segment_index: SegmentIndex,
125    },
126    /// Storage item write error
127    #[error("Storage item write error")]
128    StorageItemWriteError {
129        /// Low-level error
130        #[from]
131        error: io::Error,
132    },
133}
134
135// TODO: Split this into different more narrow traits
136/// Chain info.
137///
138/// NOTE:
139/// <div class="warning">
140/// Blocks or their parts returned from these APIs are reference-counted and cheap to clone.
141/// However, it is not expected that they will be retained in memory for a long time. Blocks and
142/// headers will not be pruned until their reference count goes down to one. This is imported when
143/// there is an ongoing block import happening and its parent must exist until the import
144/// finishes.
145/// </div>
146pub trait ChainInfo<Block>: Clone + Send + Sync + 'static
147where
148    Block: GenericOwnedBlock,
149{
150    /// Best block root
151    fn best_root(&self) -> BlockRoot;
152
153    // TODO: Uncomment if/when necessary
154    // /// Find root of ancestor block number for descendant block root
155    // fn ancestor_root(
156    //     &self,
157    //     ancestor_block_number: BlockNumber,
158    //     descendant_block_root: &BlockRoot,
159    // ) -> Option<BlockRoot>;
160
161    /// Best block header
162    fn best_header(&self) -> Block::Header;
163
164    /// Returns the best block header like [`Self::best_header()`] with additional block details
165    fn best_header_with_details(&self) -> (Block::Header, BlockDetails);
166
167    /// Get header of ancestor block number for descendant block root
168    fn ancestor_header(
169        &self,
170        ancestor_block_number: BlockNumber,
171        descendant_block_root: &BlockRoot,
172    ) -> Option<Block::Header>;
173
174    /// Block header
175    fn header(&self, block_root: &BlockRoot) -> Option<Block::Header>;
176
177    /// Returns a block header like [`Self::header()`] with additional block details
178    fn header_with_details(&self, block_root: &BlockRoot) -> Option<(Block::Header, BlockDetails)>;
179
180    fn block(
181        &self,
182        block_root: &BlockRoot,
183    ) -> impl Future<Output = Result<Block, ReadBlockError>> + Send;
184
185    /// Returns last observed segment header
186    fn last_segment_header(&self) -> Option<SegmentHeader>;
187
188    /// Returns last observed segment index
189    fn max_segment_index(&self) -> Option<SegmentIndex>;
190
191    /// Get a single segment header
192    fn get_segment_header(&self, segment_index: SegmentIndex) -> Option<SegmentHeader>;
193
194    /// Get segment headers that are expected to be included at specified block number.
195    fn segment_headers_for_block(&self, block_number: BlockNumber) -> Vec<SegmentHeader>;
196}
197
198/// [`ChainInfo`] extension for writing information
199pub trait ChainInfoWrite<Block>: ChainInfo<Block>
200where
201    Block: GenericOwnedBlock,
202{
203    /// Persist newly imported block
204    fn persist_block(
205        &self,
206        block: Block,
207        block_details: BlockDetails,
208    ) -> impl Future<Output = Result<(), PersistBlockError>> + Send;
209
210    /// Persist segment headers.
211    ///
212    /// Multiple can be inserted for efficiency purposes.
213    fn persist_segment_headers(
214        &self,
215        segment_headers: Vec<SegmentHeader>,
216    ) -> impl Future<Output = Result<(), PersistSegmentHeadersError>> + Send;
217}
218
219/// Chain sync status
220pub trait ChainSyncStatus: Clone + Send + Sync + 'static {
221    /// The block number that the sync process is targeting right now.
222    ///
223    /// Can be zero if not syncing actively.
224    fn target_block_number(&self) -> BlockNumber;
225
226    /// Returns `true` if the chain is currently syncing
227    fn is_syncing(&self) -> bool;
228
229    /// Returns `true` if the node is currently offline
230    fn is_offline(&self) -> bool;
231}