espresso_types/v0/v0_1/block.rs
1use std::{borrow::Cow, default::Default, iter::Peekable, ops::Range, sync::Arc};
2
3use derive_more::Display;
4use hotshot_types::vid::advz::{LargeRangeProofType, SmallRangeProofType};
5use serde::{Deserialize, Deserializer, Serialize};
6use thiserror::Error;
7
8use crate::Transaction;
9
10/// Proof of correctness for namespace payload bytes in a block.
11#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
12pub struct ADVZNsProof {
13 pub ns_index: NsIndex,
14 pub ns_payload: NsPayloadOwned,
15 pub ns_proof: Option<LargeRangeProofType>, // `None` if ns_payload is empty
16}
17
18/// Byte lengths for the different items that could appear in a namespace table.
19pub const NUM_NSS_BYTE_LEN: usize = 4;
20pub const NS_OFFSET_BYTE_LEN: usize = 4;
21
22// TODO prefer [`NS_ID_BYTE_LEN`] set to `8` because [`NamespaceId`] is a `u64`
23// but we need to maintain serialization compatibility.
24// https://github.com/EspressoSystems/espresso-network/issues/1574
25pub const NS_ID_BYTE_LEN: usize = 4;
26
27/// Raw binary data for a namespace table.
28///
29/// Any sequence of bytes is a valid [`NsTable`].
30///
31/// # Binary format of a namespace table
32///
33/// Byte lengths for the different items that could appear in a namespace table
34/// are specified in local private constants [`NUM_NSS_BYTE_LEN`],
35/// [`NS_OFFSET_BYTE_LEN`], [`NS_ID_BYTE_LEN`].
36///
37/// ## Number of entries in the namespace table
38///
39/// The first [`NUM_NSS_BYTE_LEN`] bytes of the namespace table indicate the
40/// number `n` of entries in the table as a little-endian unsigned integer. If
41/// the entire table length is smaller than [`NUM_NSS_BYTE_LEN`] then the
42/// missing bytes are zero-padded.
43///
44/// The bytes in the namespace table beyond the first [`NUM_NSS_BYTE_LEN`] bytes
45/// encode table entries. Each entry consumes exactly [`NS_ID_BYTE_LEN`] `+`
46/// [`NS_OFFSET_BYTE_LEN`] bytes.
47///
48/// The number `n` could be anything, including a number much larger than the
49/// number of entries that could fit in the namespace table. As such, the actual
50/// number of entries in the table is defined as the minimum of `n` and the
51/// maximum number of whole entries that could fit in the table.
52///
53/// See [`Self::in_bounds`] for clarification.
54///
55/// ## Namespace table entry
56///
57/// ### Namespace ID
58///
59/// The first [`NS_ID_BYTE_LEN`] bytes of each table entry indicate the
60/// [`NamespaceId`] for this namespace. Any table entry whose [`NamespaceId`] is
61/// a duplicate of a previous entry is ignored. A correct count of the number of
62/// *unique* (non-ignored) entries is given by `NsTable::iter().count()`.
63///
64/// ### Namespace offset
65///
66/// The next [`NS_OFFSET_BYTE_LEN`] bytes of each table entry indicate the
67/// end-index of a namespace in the block payload bytes
68/// [`Payload`](super::payload::Payload). This end-index is a little-endian
69/// unsigned integer.
70///
71/// # How to deduce a namespace's byte range
72///
73/// In order to extract the payload bytes of a single namespace `N` from the
74/// block payload one needs both the start- and end-indices for `N`.
75///
76/// See [`Self::ns_range`] for clarification. What follows is a description of
77/// what's implemented in [`Self::ns_range`].
78///
79/// If `N` occupies the `i`th entry in the namespace table for `i>0` then the
80/// start-index for `N` is defined as the end-index of the `(i-1)`th entry in
81/// the table.
82///
83/// Even if the `(i-1)`the entry would otherwise be ignored (due to a duplicate
84/// [`NamespaceId`] or any other reason), that entry's end-index still defines
85/// the start-index of `N`. This rule guarantees that both start- and
86/// end-indices for any namespace `N` can be read from a constant-size byte
87/// range in the namespace table, and it eliminates the need to traverse an
88/// unbounded number of previous entries of the namespace table looking for a
89/// previous non-ignored entry.
90///
91/// The start-index of the 0th entry in the table is implicitly defined to be
92/// `0`.
93///
94/// The start- and end-indices `(declared_start, declared_end)` declared in the
95/// namespace table could be anything. As such, the actual start- and
96/// end-indices `(start, end)` are defined so as to ensure that the byte range
97/// is well-defined and in-bounds for the block payload:
98/// ```ignore
99/// end = min(declared_end, block_payload_byte_length)
100/// start = min(declared_start, end)
101/// ```
102///
103/// In a "honestly-prepared" namespace table the end-index of the final
104/// namespace equals the byte length of the block payload. (Otherwise the block
105/// payload might have bytes that are not included in any namespace.)
106///
107/// It is possible that a namespace table could indicate two distinct namespaces
108/// whose byte ranges overlap, though no "honestly-prepared" namespace table
109/// would do this.
110///
111/// TODO prefer [`NsTable`] to be a newtype like this
112/// ```ignore
113/// #[repr(transparent)]
114/// #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
115/// #[serde(transparent)]
116/// pub struct NsTable(#[serde(with = "base64_bytes")] Vec<u8>);
117/// ```
118/// but we need to maintain serialization compatibility.
119/// <https://github.com/EspressoSystems/espresso-network/issues/1575>
120#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
121// Boilerplate: `#[serde(remote = "Self")]` needed to check invariants on
122// deserialization. See
123// https://github.com/serde-rs/serde/issues/1220#issuecomment-382589140
124#[serde(remote = "Self")]
125pub struct NsTable {
126 #[serde(with = "base64_bytes")]
127 pub(crate) bytes: Vec<u8>,
128}
129
130/// Return type for [`NsTable::validate`].
131#[derive(Error, Debug, Display, Eq, PartialEq)]
132pub enum NsTableValidationError {
133 InvalidByteLen,
134 NonIncreasingEntries,
135 DuplicateNamespaceId,
136 InvalidHeader, // TODO this variant obsolete after https://github.com/EspressoSystems/espresso-network/issues/1604
137 InvalidFinalOffset, // TODO this variant obsolete after https://github.com/EspressoSystems/espresso-network/issues/1604
138 ExpectNonemptyNsTable,
139}
140
141pub struct NsTableBuilder {
142 pub(crate) bytes: Vec<u8>,
143 pub(crate) num_entries: usize,
144}
145
146/// Index of a transaction.
147pub type Index = hotshot_query_service_types::availability::TransactionIndex<crate::SeqTypes>;
148
149/// Index for an entry in a ns table.
150#[derive(Clone, Debug, Eq, Hash, PartialEq)]
151pub struct NsIndex(pub(crate) usize);
152
153/// Number of entries in a namespace table.
154pub struct NumNss(pub(crate) usize);
155
156/// Return type for [`Payload::ns_iter`].
157pub struct NsIter(pub(crate) Range<usize>);
158
159/// Raw payload data for an entire block.
160///
161/// A block consists of two sequences of arbitrary bytes:
162/// - `ns_table`: namespace table
163/// - `ns_payloads`: namespace payloads
164///
165/// Any sequence of bytes is a valid `ns_table`. Any sequence of bytes is a
166/// valid `ns_payloads`. The contents of `ns_table` determine how to interpret
167/// `ns_payload`.
168///
169/// # Namespace table
170///
171/// See [`NsTable`] for the format of a namespace table.
172///
173/// # Namespace payloads
174///
175/// A concatenation of payload bytes for multiple individual namespaces.
176/// Namespace boundaries are dictated by `ns_table`. See [`NsPayload`] for the
177/// format of a namespace payload.
178#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
179pub struct Payload {
180 // Concatenated payload bytes for each namespace
181 //
182 // TODO want to rename thisfield to `ns_payloads`, but can't due to
183 // serialization compatibility.
184 #[serde(
185 serialize_with = "base64_bytes::serialize",
186 deserialize_with = "deserialize_shared_bytes"
187 )]
188 pub(crate) raw_payload: Arc<[u8]>,
189
190 pub(crate) ns_table: NsTable,
191}
192
193/// Perf: binary formats decode one byte buffer instead of a `u8` sequence.
194fn deserialize_shared_bytes<'de, D: Deserializer<'de>>(d: D) -> Result<Arc<[u8]>, D::Error> {
195 if d.is_human_readable() {
196 base64_bytes::deserialize(d).map(Arc::from)
197 } else {
198 serde_bytes::deserialize::<Cow<[u8]>, _>(d).map(Arc::from)
199 }
200}
201
202/// Byte length of a block payload, which includes all namespaces but *not* the
203/// namespace table.
204#[derive(Clone, Debug, Display, Eq, Hash, PartialEq)]
205pub struct PayloadByteLen(pub(crate) usize);
206
207/// Cartesian product of [`NsIter`], [`TxIter`].
208pub struct Iter<'a> {
209 pub(crate) ns_iter: Peekable<NsIter>,
210 pub(crate) tx_iter: Option<TxIter>,
211 pub(crate) block: &'a Payload,
212}
213
214/// Index range for a namespace payload inside a block payload.
215#[derive(Clone, Debug, Eq, Hash, PartialEq)]
216pub struct NsPayloadRange(pub(crate) Range<usize>);
217
218/// Raw binary data for a single namespace's payload.
219///
220/// Any sequence of bytes is a valid [`NsPayload`].
221///
222/// See module-level documentation [`types`](super::types) for a full
223/// specification of the binary format of a namespace.
224pub struct NsPayload(pub(crate) [u8]);
225
226#[repr(transparent)]
227#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
228#[serde(transparent)]
229pub struct NsPayloadOwned(#[serde(with = "base64_bytes")] pub(crate) Vec<u8>);
230
231/// Proof of correctness for transaction bytes in a block.
232#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
233pub struct ADVZTxProof {
234 // Naming conventions for this struct's fields:
235 // - `payload_x`: bytes from the payload
236 // - `payload_proof_x`: a proof of those bytes from the payload
237 pub(crate) tx_index: TxIndex,
238
239 // Number of txs declared in the tx table
240 pub(crate) payload_num_txs: NumTxsUnchecked,
241 pub(crate) payload_proof_num_txs: SmallRangeProofType,
242
243 // Tx table entries for this tx
244 pub(crate) payload_tx_table_entries: TxTableEntries,
245 pub(crate) payload_proof_tx_table_entries: SmallRangeProofType,
246
247 // This tx's payload bytes.
248 // `None` if this tx has zero length.
249 pub(crate) payload_proof_tx: Option<SmallRangeProofType>,
250}
251
252impl ADVZTxProof {
253 pub fn tx_index(&self) -> &TxIndex {
254 &self.tx_index
255 }
256
257 pub fn payload_num_txs(&self) -> &NumTxsUnchecked {
258 &self.payload_num_txs
259 }
260
261 pub fn payload_proof_num_txs(&self) -> &SmallRangeProofType {
262 &self.payload_proof_num_txs
263 }
264
265 pub fn payload_tx_table_entries(&self) -> &TxTableEntries {
266 &self.payload_tx_table_entries
267 }
268
269 pub fn payload_proof_tx_table_entries(&self) -> &SmallRangeProofType {
270 &self.payload_proof_tx_table_entries
271 }
272
273 pub fn payload_proof_tx(&self) -> Option<&SmallRangeProofType> {
274 self.payload_proof_tx.as_ref()
275 }
276}
277
278/// Byte lengths for the different items that could appear in a tx table.
279pub const NUM_TXS_BYTE_LEN: usize = 4;
280pub const TX_OFFSET_BYTE_LEN: usize = 4;
281
282/// Number of txs in a namespace.
283///
284/// Like [`NumTxsUnchecked`] but checked against a [`NsPayloadByteLen`].
285pub struct NumTxs(pub(crate) usize);
286
287/// Byte length of a namespace payload.
288pub struct NsPayloadByteLen(pub(crate) usize);
289
290/// The part of a tx table that declares the number of txs in the payload.
291///
292/// "Unchecked" because this quantity might exceed the number of tx table
293/// entries that could fit into the namespace that contains it.
294///
295/// Use [`NumTxs`] for the actual number of txs in this namespace.
296#[derive(Clone, Debug, Eq, PartialEq)]
297pub struct NumTxsUnchecked(pub(crate) usize);
298
299/// Byte range for the part of a tx table that declares the number of txs in the
300/// payload.
301pub struct NumTxsRange(pub(crate) Range<usize>);
302
303/// Entries from a tx table in a namespace for use in a transaction proof.
304///
305/// Contains either one or two entries according to whether it was derived from
306/// the first transaction in the namespace.
307#[derive(Clone, Debug, Eq, PartialEq)]
308pub struct TxTableEntries {
309 pub(crate) cur: usize,
310 pub(crate) prev: Option<usize>, // `None` if derived from the first transaction
311}
312
313/// Byte range for entries from a tx table for use in a transaction proof.
314///
315/// This range covers either one or two entries from a tx table according to
316/// whether it was derived from the first transaction in the namespace.
317pub struct TxTableEntriesRange(pub(crate) Range<usize>);
318
319/// A transaction's payload data.
320pub struct TxPayload<'a>(pub(crate) &'a [u8]);
321
322/// Byte range for a transaction's payload data.
323pub struct TxPayloadRange(pub(crate) Range<usize>);
324
325/// Index for an entry in a tx table.
326#[derive(Clone, Debug, Eq, Hash, PartialEq)]
327pub struct TxIndex(pub(crate) usize);
328
329pub struct TxIter(pub(crate) Range<usize>);
330
331/// Build an individual namespace payload one transaction at a time.
332///
333/// Use [`Self::append_tx`] to add each transaction. Use [`Self::write_into`]
334/// when you're done. The output includes a well-formed tx table and all tx
335/// payloads.
336#[derive(Default)]
337pub struct NsPayloadBuilder<'a> {
338 /// Perf: bodies are copied once, straight into the payload.
339 pub(crate) txs: Vec<&'a Transaction>,
340 pub(crate) bodies_len: usize,
341}