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}