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