sov_rollup_interface/state_machine/da.rs
1//! Defines traits and types used by the rollup to verify claims about the
2//! DA layer.
3use core::fmt::Debug;
4use std::cmp::min;
5
6use borsh::{BorshDeserialize, BorshSerialize};
7use bytes::Buf;
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::zk::ValidityCondition;
13use crate::BasicAddress;
14
15/// A specification for the types used by a DA layer.
16pub trait DaSpec: 'static + Debug + PartialEq + Eq {
17 /// The hash of a DA layer block
18 type SlotHash: BlockHashTrait;
19
20 /// The block header type used by the DA layer
21 type BlockHeader: BlockHeaderTrait<Hash = Self::SlotHash>;
22
23 /// The transaction type used by the DA layer.
24 type BlobTransaction: BlobReaderTrait<Address = Self::Address>;
25
26 /// The type used to represent addresses on the DA layer.
27 type Address: BasicAddress;
28
29 /// Any conditions imposed by the DA layer which need to be checked outside of the SNARK
30 type ValidityCondition: ValidityCondition;
31
32 /// A proof that each tx in a set of blob transactions is included in a given block.
33 type InclusionMultiProof: Serialize + DeserializeOwned;
34
35 /// A proof that a claimed set of transactions is complete.
36 /// For example, this could be a range proof demonstrating that
37 /// the provided BlobTransactions represent the entire contents
38 /// of Celestia namespace in a given block
39 type CompletenessProof: Serialize + DeserializeOwned;
40
41 /// The parameters of the rollup which are baked into the state-transition function.
42 /// For example, this could include the namespace of the rollup on Celestia.
43 type ChainParams;
44}
45
46/// A `DaVerifier` implements the logic required to create a zk proof that some data
47/// has been processed.
48///
49/// This trait implements the required functionality to *verify* claims of the form
50/// "If X is the most recent block in the DA layer, then Y is the ordered set of transactions that must
51/// be processed by the rollup."
52pub trait DaVerifier {
53 /// The set of types required by the DA layer.
54 type Spec: DaSpec;
55
56 /// The error type returned by the DA layer's verification function
57 /// TODO: Should we add `std::Error` bound so it can be `()?` ?
58 type Error: Debug;
59
60 /// Create a new da verifier with the given chain parameters
61 fn new(params: <Self::Spec as DaSpec>::ChainParams) -> Self;
62
63 /// Verify a claimed set of transactions against a block header.
64 fn verify_relevant_tx_list(
65 &self,
66 block_header: &<Self::Spec as DaSpec>::BlockHeader,
67 txs: &[<Self::Spec as DaSpec>::BlobTransaction],
68 inclusion_proof: <Self::Spec as DaSpec>::InclusionMultiProof,
69 completeness_proof: <Self::Spec as DaSpec>::CompletenessProof,
70 ) -> Result<<Self::Spec as DaSpec>::ValidityCondition, Self::Error>;
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize, PartialEq)]
74/// Simple structure that implements the Read trait for a buffer and counts the number of bytes read from the beginning.
75/// Useful for the partial blob reading optimization: we know for each blob how many bytes have been read from the beginning.
76///
77/// Because of soundness issues we cannot implement the Buf trait because the prover could get unproved blob data using the chunk method.
78pub struct CountedBufReader<B: Buf> {
79 /// The original blob data.
80 inner: B,
81
82 /// An accumulator that stores the data read from the blob buffer into a vector.
83 /// Allows easy access to the data that has already been read
84 accumulator: Vec<u8>,
85}
86
87impl<B: Buf> CountedBufReader<B> {
88 /// Creates a new buffer reader with counter from an objet that implements the buffer trait
89 pub fn new(inner: B) -> Self {
90 let buf_size = inner.remaining();
91 CountedBufReader {
92 inner,
93 accumulator: Vec::with_capacity(buf_size),
94 }
95 }
96
97 /// Advance the accumulator by `num_bytes` bytes. If `num_bytes` is greater than the length
98 /// of remaining unverified data, then all remaining unverified data is added to the accumulator.
99 pub fn advance(&mut self, num_bytes: usize) {
100 let requested = num_bytes;
101 let remaining = self.inner.remaining();
102 if remaining == 0 {
103 return;
104 }
105 // `Buf::advance` would panic if `num_bytes` was greater than the length of the remaining unverified data,
106 // but we just advance to the end of the buffer.
107 let num_to_read = min(remaining, requested);
108 // Extend the inner vector with zeros (copy_to_slice requires the vector to have
109 // the correct *length* not just capacity)
110 self.accumulator
111 .resize(self.accumulator.len() + num_to_read, 0);
112
113 // Use copy_to_slice to overwrite the zeros we just added
114 let accumulator_len = self.accumulator.len();
115 self.inner
116 .copy_to_slice(self.accumulator[accumulator_len - num_to_read..].as_mut());
117 }
118
119 /// Getter: returns a reference to an accumulator of the blob data read by the rollup
120 pub fn accumulator(&self) -> &[u8] {
121 &self.accumulator
122 }
123
124 /// Contains the total length of the data (length already read + length remaining)
125 pub fn total_len(&self) -> usize {
126 self.inner.remaining() + self.accumulator.len()
127 }
128}
129
130/// This trait wraps "blob transaction" from a data availability layer allowing partial consumption of the
131/// blob data by the rollup.
132///
133/// The motivation for this trait is limit the amount of validation work that a rollup has to perform when
134/// verifying a state transition. In general, it will often be the case that a rollup only cares about some
135/// portion of the data from a blob. For example, if a blob contains a malformed transaction then the rollup
136/// will slash the sequencer and exit early - so it only cares about the content of the blob up to that point.
137///
138/// This trait allows the DaVerifier to track which data was read by the rollup, and only verify the relevant data.
139pub trait BlobReaderTrait: Serialize + DeserializeOwned + Send + Sync + 'static {
140 /// The type used to represent addresses on the DA layer.
141 type Address: BasicAddress;
142
143 /// Returns the address (on the DA layer) of the entity which submitted the blob transaction
144 fn sender(&self) -> Self::Address;
145
146 /// Returns the hash of the blob as it appears on the DA layer
147 fn hash(&self) -> [u8; 32];
148
149 /// Returns a slice containing all the data accessible to the rollup at this point in time.
150 /// When running in native mode, the rollup can extend this slice by calling `advance`. In zk-mode,
151 /// the rollup is limited to only the verified data.
152 ///
153 /// Rollups should use this method in conjunction with `advance` to read only the minimum amount
154 /// of data required for execution
155 fn verified_data(&self) -> &[u8];
156
157 /// Returns the total number of bytes in the blob. Note that this may be unequal to `verified_data.len()`.
158 fn total_len(&self) -> usize;
159
160 /// Extends the `partial_data` accumulator with the next `num_bytes` of data from the blob
161 /// and returns a reference to the entire contents of the blob up to this point.
162 ///
163 /// If `num_bytes` is greater than the length of the remaining unverified data,
164 /// then all remaining unverified data is added to the accumulator.
165 ///
166 /// ### Note:
167 /// This method is only available when the `native` feature is enabled because it is unsafe to access
168 /// unverified data during execution
169 #[cfg(feature = "native")]
170 fn advance(&mut self, num_bytes: usize) -> &[u8];
171
172 /// Verifies all remaining unverified data in the blob and returns a reference to the entire contents of the blob.
173 /// For efficiency, rollups should prefer use of `verified_data` and `advance` unless they know that all of the
174 /// blob data will be required for execution.
175 #[cfg(feature = "native")]
176 fn full_data(&mut self) -> &[u8] {
177 self.advance(self.total_len())
178 }
179}
180
181/// Trait with collection of trait bounds for a block hash.
182pub trait BlockHashTrait:
183 Serialize + DeserializeOwned + PartialEq + Debug + Send + Sync + Clone + Eq + Into<[u8; 32]>
184{
185}
186
187/// A block header, typically used in the context of an underlying DA blockchain.
188pub trait BlockHeaderTrait: PartialEq + Debug + Clone + Serialize + DeserializeOwned {
189 /// Each block header must have a unique canonical hash.
190 type Hash: Clone;
191
192 /// Each block header must contain the hash of the previous block.
193 fn prev_hash(&self) -> Self::Hash;
194
195 /// Hash the type to get the digest.
196 fn hash(&self) -> Self::Hash;
197
198 /// The current header height
199 fn height(&self) -> u64;
200
201 /// The timestamp of the block
202 fn time(&self) -> Time;
203}
204
205#[derive(
206 Serialize, Deserialize, Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Default,
207)]
208/// A timestamp, represented as seconds since the unix epoch.
209pub struct Time {
210 /// The number of seconds since the unix epoch
211 secs: i64,
212 nanos: u32,
213}
214
215#[derive(Debug, Error)]
216#[error("Only intervals less than one second may be represented as nanoseconds")]
217/// An error that occurs when trying to create a `NanoSeconds` representing more than one second
218pub struct ErrTooManyNanos;
219
220/// A number of nanoseconds
221pub struct NanoSeconds(u32);
222
223impl NanoSeconds {
224 /// Try to turn a u32 into a `NanoSeconds`. Only values less than one second are valid.
225 pub fn new(nanos: u32) -> Result<Self, ErrTooManyNanos> {
226 if nanos < NANOS_PER_SECOND {
227 Ok(NanoSeconds(nanos))
228 } else {
229 Err(ErrTooManyNanos)
230 }
231 }
232}
233
234const NANOS_PER_SECOND: u32 = 1_000_000_000;
235
236impl Time {
237 /// The time since the unix epoch
238 pub const fn new(secs: i64, nanos: NanoSeconds) -> Self {
239 Time {
240 secs,
241 nanos: nanos.0,
242 }
243 }
244
245 /// Create a time from the specified number of whole seconds.
246 pub const fn from_secs(secs: i64) -> Self {
247 Time { secs, nanos: 0 }
248 }
249
250 /// Returns the number of whole seconds since the epoch
251 ///
252 /// The returned value does not include the fractional (nanosecond) part of the duration,
253 /// which can be obtained using `subsec_nanos`.
254 pub fn secs(&self) -> i64 {
255 self.secs
256 }
257
258 /// Returns the fractional part of this [`Time`], in nanoseconds.
259 ///
260 /// This method does not return the length of the time when represented by nanoseconds.
261 /// The returned number always represents a fractional portion of a second (i.e., it is less than one billion).
262 pub fn subsec_nanos(&self) -> u32 {
263 self.nanos
264 }
265}