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}