<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Sovereign</title>
        <link>https://paragraph.com/@sovlabs</link>
        <description>Build real-time rollups with the industry's most flexible toolkit, Sovereign SDK.</description>
        <lastBuildDate>Thu, 12 Nov 2026 03:21:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Sovereign</title>
            <url>https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/4f3bd86892b3156590cc0fbc6e0285a317e233fdb86549ecd009785f94d1fa07.png</url>
            <link>https://paragraph.com/@sovlabs</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Nearly Optimal State Merklization]]></title>
            <link>https://paragraph.com/@sovlabs/nearly-optimal-state-merklization</link>
            <guid>enE7j1n4qFXgCbZIUWvr</guid>
            <pubDate>Tue, 09 Jan 2024 17:05:11 GMT</pubDate>
            <description><![CDATA[There&apos;s a meme going around Twitter that Solana is fast because it doesn&apos;t merklize its state. Larry Engineer 🍡 @larry0x I think I'm having a Eureka moment in performant blockchain design. Three main points: 1. Do not Merklize chain state. This removes a huuuuge overhead in both execution and disk usage! 2. Use an SQL DB instead of a raw KV store. Instead of the chain implementing a query server, 174 9:38 PM • Dec 26, 2023 ... And to some extent, this is true. In the Sovereign SDK,...]]></description>
            <content:encoded><![CDATA[<p>There&apos;s a meme going around Twitter that Solana is fast because it doesn&apos;t merklize its state.</p><div data-type="twitter" tweetId="1739853256455582170" tweetData="{&quot;__typename&quot;:&quot;Tweet&quot;,&quot;lang&quot;:&quot;en&quot;,&quot;favorite_count&quot;:174,&quot;created_at&quot;:&quot;2023-12-27T03:38:35.000Z&quot;,&quot;display_text_range&quot;:[0,280],&quot;entities&quot;:{&quot;hashtags&quot;:[],&quot;urls&quot;:[],&quot;user_mentions&quot;:[],&quot;symbols&quot;:[]},&quot;id_str&quot;:&quot;1739853256455582170&quot;,&quot;text&quot;:&quot;I think I&apos;m having a Eureka moment in performant blockchain design. Three main points:\n\n1. Do not Merklize chain state. This removes a huuuuge overhead in both execution and disk usage!\n\n2. Use an SQL DB instead of a raw KV store. Instead of the chain implementing a query server,&quot;,&quot;user&quot;:{&quot;id_str&quot;:&quot;1134151355776585729&quot;,&quot;name&quot;:&quot;Larry Engineer 🍡&quot;,&quot;screen_name&quot;:&quot;larry0x&quot;,&quot;is_blue_verified&quot;:true,&quot;profile_image_shape&quot;:&quot;Circle&quot;,&quot;verified&quot;:false,&quot;profile_image_url_https&quot;:&quot;https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/afb7c34fbcdef7c44814e9f3303b10f014423e4ed0b336ee30f70cfc7400601c.jpg&quot;,&quot;highlighted_label&quot;:{&quot;description&quot;:&quot;dango🍡&quot;,&quot;badge&quot;:{&quot;url&quot;:&quot;https://pbs.twimg.com/profile_images/1940431337912971264/h2G3EAC1_bigger.jpg&quot;},&quot;url&quot;:{&quot;url&quot;:&quot;https://twitter.com/dango&quot;,&quot;url_type&quot;:&quot;DeepLink&quot;},&quot;user_label_type&quot;:&quot;BusinessLabel&quot;,&quot;user_label_display_type&quot;:&quot;Badge&quot;}},&quot;edit_control&quot;:{&quot;edit_tweet_ids&quot;:[&quot;1739853256455582170&quot;],&quot;editable_until_msecs&quot;:&quot;1703651915000&quot;,&quot;is_edit_eligible&quot;:false,&quot;edits_remaining&quot;:&quot;5&quot;},&quot;conversation_count&quot;:55,&quot;news_action_type&quot;:&quot;conversation&quot;,&quot;isEdited&quot;:false,&quot;isStaleEdit&quot;:false,&quot;note_tweet&quot;:{&quot;id&quot;:&quot;Tm90ZVR3ZWV0UmVzdWx0czoxNzM5ODUzMjU2Mzg4NDg1MTIw&quot;}}"> 
  <div class="twitter-embed embed">
    <div class="twitter-header">
        <div style="display:flex">
          <a target="_blank" href="https://twitter.com/larry0x">
              <img alt="User Avatar" class="twitter-avatar" src="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/afb7c34fbcdef7c44814e9f3303b10f014423e4ed0b336ee30f70cfc7400601c.jpg" />
            </a>
            <div style="margin-left:4px;margin-right:auto;line-height:1.2;">
              <a target="_blank" href="https://twitter.com/larry0x" class="twitter-displayname">Larry Engineer 🍡</a>
              <p><a target="_blank" href="https://twitter.com/larry0x" class="twitter-username">@larry0x</a></p>
    
            </div>
            <a href="https://twitter.com/larry0x/status/1739853256455582170" target="_blank">
              <img alt="Twitter Logo" class="twitter-logo" src="https://paragraph.com/editor/twitter/logo.png" />
            </a>
          </div>
        </div>
      
    <div class="twitter-body">
      I think I'm having a Eureka moment in performant blockchain design. Three main points:<br /><br />1. Do not Merklize chain state. This removes a huuuuge overhead in both execution and disk usage!<br /><br />2. Use an SQL DB instead of a raw KV store. Instead of the chain implementing a query server,
      
      
       
    </div>
    
     <div class="twitter-footer">
          <a target="_blank" href="https://twitter.com/larry0x/status/1739853256455582170" style="margin-right:16px; display:flex;">
            <img alt="Like Icon" class="twitter-heart" src="https://paragraph.com/editor/twitter/heart.png">
            174
          </a>
          <a target="_blank" href="https://twitter.com/larry0x/status/1739853256455582170"><p>9:38 PM • Dec 26, 2023</p></a>
        </div>
    
  </div> 
  </div><p>... And to some extent, this is true. In the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign-sdk?ref=127.0.0.1">Sovereign SDK</a>, for example, we find that our (mostly unoptimized) merkle tree update currently takes at least 50% of our total runtime. Although exact performance numbers vary, updating the state trie is also a significant bottleneck for Ethereum and Cosmos node implementations.</p><p>The commonly cited reason for this inefficiency is that maintaining a merkle tree incurs lots of random disk accesses. And indeed, if you look at existing merkle tree implementations, you&apos;ll find this to be exactly the case. But I think this state of affairs is more an artifact of history than a fundamental limitation of merklization. In this post, I&apos;ll outline a more efficient strategy for merklizing state data, which I hope will be useful to the broader community.</p><blockquote><p>N.B. Although this post makes some original contributions, it also combines many existing optimizations into a single scheme. When ideas were not developed originally, I credit the original author in block like this one.</p></blockquote><h2 id="h-background-why-is-merklization-expensive" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Background: Why is Merklization Expensive?</strong></h2><h3 id="h-a-very-brief-introduction-to-addressable-merkle-trees" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>A Very Brief Introduction To Addressable Merkle Trees</strong></h3><blockquote><p>N.B. If you&apos;re totally unfamiliar with merkle trees and want a better introduction, I recommend the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://developers.diem.com/papers/jellyfish-merkle-tree/2021-01-14.pdf?ref=127.0.0.1">Jellyfish Merkle Tree paper</a></p></blockquote><p>Addressable merkle trees usually work by grouping intermediate hashes into sets of 16 (called a <code>node</code>) and storing them in a key-value store like LMDB or RocksDB, where the key is the node hash and the value is the node itself.</p><p>To traverse the trie, implementations do something like the following:</p><pre data-type="codeBlock" text="struct ChildInfo { 
  is_leaf: bool,
  hash: H256,
  // ... some metadata omitted here
}

struct Node { 
  children: [Option&lt;ChildInfo&gt;; 16]
} 

fn get(db: TrieDb, path: &amp;[u4]) -&gt; Option&lt;Leaf&gt; { 
  let mut current_node: Node = db.get_root_node();
  for nibble in path { 
    let child: ChildInfo = current_node[nibble]?;
    if child.is_leaf { 
      // Some checks omitted here
      return db.get_leaf(child.hash)
    }
    current_node = db.get_node(child.hash)
  } 
}
"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">ChildInfo</span> { 
  is_leaf: <span class="hljs-keyword">bool</span>,
  hash: H256,
  <span class="hljs-comment">// ... some metadata omitted here</span>
}

<span class="hljs-keyword">struct</span> <span class="hljs-title">Node</span> { 
  children: [Option<span class="hljs-operator">&#x3C;</span>ChildInfo<span class="hljs-operator">></span>; <span class="hljs-number">16</span>]
} 

fn get(db: TrieDb, path: <span class="hljs-operator">&#x26;</span>[u4]) <span class="hljs-operator">-</span><span class="hljs-operator">></span> Option<span class="hljs-operator">&#x3C;</span>Leaf<span class="hljs-operator">></span> { 
  let mut current_node: Node <span class="hljs-operator">=</span> db.get_root_node();
  <span class="hljs-keyword">for</span> nibble in path { 
    let child: ChildInfo <span class="hljs-operator">=</span> current_node[nibble]?;
    <span class="hljs-keyword">if</span> child.is_leaf { 
      <span class="hljs-comment">// Some checks omitted here</span>
      <span class="hljs-keyword">return</span> db.get_leaf(child.hash)
    }
    current_node <span class="hljs-operator">=</span> db.get_node(child.hash)
  } 
}
</code></pre><p>As you can clearly see, this algorithm requires one random disk access for each layer of the tree that needs to be traversed. A basic property of addressable merkle tries is that their <em>expected</em> <em>depth</em> is logarithmic in the number of items stored, and the base of the logarithm is the width of the intermediate nodes. So if your blockchain has five billion objects in state and uses intermediate nodes with a width of 16, each state update will need to traverse to an expected depth of  <code>log_16(5,000,000,000) ~= 8</code>. Putting all of this together, we can see that traversing a naive merkle trie will require roughly 8 <em>sequential</em> database queries per entry.</p><p>Compare that to the single query required to look up a value a &quot;flat&quot; (non-merklized) store like the one used in Solana, and you&apos;ll begin to see why merklization has such a big performance impact.</p><h3 id="h-but-wait-theres-more-overhead" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>But Wait, there&apos;s More (Overhead)!</strong></h3><p>Unfortunately, this isn&apos;t the end of the story. In the previous discussion, we treated the underlying key-value store as if it could store and fetch values with no overhead. In the real world, though, lookups can be expensive. Almost all key-value stores use a tree structure of their own internally, which causes &quot;read amplification&quot; - multiplying the number of disk accesses for a random read by another factor of <code>log(n)</code>.</p><p>So, for those of you keeping track at home, each read against a merkle tree incurs an overhead of <code>log(n) ^ 2</code>, where <code>n</code> is the number of items in global state. On Ethereum mainnet today, this <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.karalabe.com/talks/2022-ethprague.html?ref=127.0.0.1#6">log-squared factor is roughly 50</a> - meaning that each state update takes roughly 50 sequential disk accesses. When you observe that a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/tx/0x1e84e38a7bbb3eede7f65769279d71759e74c3b9120d693652825c651a65270f?ref=127.0.0.1#statechange">typical Swap</a> on Uniswap might write to 7 storage slots across 4 different contracts addresses and update the balances of 3 additional accounts, you can see how this becomes an issue.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.karalabe.com/talks/2022-ethprague.html?ref=127.0.0.1#8">This is the reason</a> Ethereum doesn&apos;t just &quot;raise the gas limit&quot;. Even though all the major full node implementations could easily a handle an order of magnitude increase in throughput <em>today</em>, the resulting state growth would eventually degrade node performance to an unacceptable degree.</p><h2 id="h-part-2-lets-make-it-better" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Part 2: Let&apos;s make it better</strong></h2><p>But hang on... aren&apos;t merkle trees a theoretically optimal data structure? They <em>should</em> be fast! Can we make them live up to the hype?</p><p>I think so, yes. Here&apos;s how.</p><h3 id="h-step-1-make-the-trie-binary" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 1: Make the Trie <em>Binary</em></strong></h3><p>The first thing to do is to decouple your data <em>storage</em> format from the logical structure of your merkle trie. While it&apos;s a good idea to store nodes into groups of 16 (or more!) on disk and in-memory, using a high-arity merkle tree wastes computation and bandwidth. The solution is simple. Store your nodes in power-of-two-minus-two sized groups where each cluster contains a rootless binary subtree of some constant depth. In plain english, store your nodes as arrays of size (say), 6, where the array represents a binary tree with depth (say) 2 - with the additional wrinkle that the root of each tree is stored at the layer above. (See diagram).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/8a36cd1009ed632316cc1299a0458762c1e9bee8a1353360741b217c46143a1c.png" alt="A diagram showing the physical and logical layout of an example merkle tree during an insertion at q. Modified items are shown in green, while siblings which are needed to compute the new root are shown in blue." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">A diagram showing the physical and logical layout of an example merkle tree during an insertion at q. Modified items are shown in green, while siblings which are needed to compute the new root are shown in blue.</figcaption></figure><p>The reason we store data this way is to ensure that all of the <em>siblings</em> needed in order to make a proof for a particular leaf are stored in the same nodes as the hashes that will be modified. If we also included the root of the binary trie in the node, then we would have to do an extra disk access each time we wanted to do an update that touched the root hash of a node (since the <em>sibling</em> of the root would be stored is stored in a different node).</p><p>Using this binary layout, we can eliminate a significant amount of overhead. For example, when we want to prove an insertion at  <code>q</code>, we only need to perform four hash compressions: <code>H(a || b)</code>, <code>H(c || d)</code>, <code>H(g || h)</code>, and <code>H(q || r)</code>. This is significantly better than the number of compressions we would need to ingest a branch node in a 16-ary trie (roughly 9). Similarly, we only need to send four siblings in the merkle proof instead of the (minimum) 15 required by a 16-ary trie.</p><blockquote><p>N.B. The idea of separating disk-format from tree arity is not new to Sovereign. It is already implemented in the Jellyfish Merkle Tree pioneered by Diem, and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751?ref=127.0.0.1">was suggested by Vitalik</a> as least as early as 2018</p></blockquote><h3 id="h-step-2-improve-the-disk-layout" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 2: Improve the Disk Layout</strong></h3><p>Now that we&apos;ve decoupled our on-disk layout from the underlying logical structure of the merkle tree, lets design the best possible layout.</p><blockquote><p>N.B. Both Monad and Avalanche are doing related work at the same time this blog post is being written. Monad&apos;s work is still closed source, so it&apos;s not possible to assess how much overlap there is between our construction and theirs. Avalanche has open-sourced their FirewoodDB, but has not released much documentation, so I haven&apos;t yet had time to assess it in detail.</p></blockquote><p>First, we&apos;re going to increase the size of our on-disk nodes to be the size of a page on a standard SSD. SSDs can’t read or write data in units smaller than a page - so storing smaller chunks than that just wastes IOPS. While there&apos;s some variance in the page size across popular SSDs, 4096 bytes seems to be a popular one. Let&apos;s start there.</p><p>Using nodes of size 4096, we can fit a rootless binary trie of depth 6 into a single node and still have 64 bytes left over for metadata. Great.</p><p>Now, how can we use our disk most effectively? Well, SSDs accesses have pretty high latency, but modern drives have lots of internal parallelism. So, we want to make sure that our lookups can be pipelined. In other words, the on-disk storage location where we keep a node should <em>not</em> depend on the specific contents of the merkle tree! That way, we can fetch all of the data at once, instead of reading one node at a time.</p><p>How can we pull that off? Simple - we give each node in our tree a unique key. Since we&apos;re using nodes of size <code>2^6</code> to store a binary tree of (logical) size <code>2^256</code>, we have <code>2^250</code> distinct logical nodes that might be stored on disk. We&apos;ll give the root node the ID <code>0</code>, its leftmost child the ID <code>1</code>, and so on. To convert between <em>the path to a node</em> and that node&apos;s ID, we can use a relatively simple formula. The <code>ith</code> child of a the node with ID <code>k</code> has ID <code>k * 2^6 + i</code>.</p><p>Cool, now each node in our tree has a unique key. Using those keys, we can build a simple hashmap backed by our SSD. First, we pick some fixed number of blocks on the SSD to use for storing merkle tree nodes. Then, we assign each of those blocks a logical index. To compute the expected location of a particular node on disk, we simply use the formula <code>index = hash(node_id) % num_blocks</code>. In other words, it works exactly like a standard hashmap, just on disk instead of in memory.</p><p>The beauty of this construction is that it lets us look up all of the data on the path to a given node concurrently. Say, I want to update the merkle leaf at path <code>0x0f73f0ff947861b4af10ff94e2bdecc060a185915e6839ba3ebb86b6b2644d2f</code>. I can compute exactly which pages of the SSD are likely to contain relevant merkle nodes <em>up front,</em> and then have the SSD fetch all of those pages in parallel. In particular, I know that my tree is unlikely to store more than a few trillion items, so in general I shouldn&apos;t find any non-empty nodes beyond the first ~40 layers of the tree. So, I can just fetch the first <code>40/6 = 7</code> nodes along the path - well within the capability of the SSD to handle in parallel.</p><p>But wait... hash maps can have collisions. How can we handle those? In the current setup, we can&apos;t. We can&apos;t even <em>detect</em> them. Thankfully, there&apos;s a simple fix that&apos;s been used in hash maps since time immemorial. If you&apos;re worried about collisions, just store the <em>key</em> inside the hash bucket alongside the value. By a lucky coincidence, our keys are just node ids - which themselves are just 250-bit integers. And as we mentioned before, each of our on-disk nodes happens to have 64 bytes of extra space. So, we can just follow the standard practice of storing the key along with its value and call it a day. With that setup in place, we can now detect collisions - so all that&apos;s left to do is figure out how to handle them. Since we still have about 32 bytes of extra space for metadata (which we can use to record the fact that a collision has occurred and link to the colliding node), this should be straightforward. There are a plethora of strategies in the literature (linear probing, quadratic probing, double-hashing, etc.), and a full discussion is out of scope for this article.</p><p>Whew, that was a lot. Let&apos;s take a moment and survey what we&apos;ve built so far.</p><ul><li><p>We have a binary merkle-patricia tree, which gives us optimal hashing load and bandwidth consumption. This gives us performance which should be roughly equivalent to the best known constructions in terms of CPU, bandwidth, and (if we&apos;re using zk) proving cost.</p></li><li><p>We have an on-disk structure which is mostly data independent. This means that <em>in most cases</em> we can fetch all nodes on the path to a particular leaf node with a single roundtrip to our SSD - taking full advantage of the drive&apos;s internal parallelism. Compare this to the naive merkle-tree construction I outlined in the first section, which needs 7 or 8 sequential <em>database lookups,</em> and about 50 sequential disk accesses due to nested tree structure.</p></li></ul><h2 id="h-step-3-compress-your-metadata" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 3: Compress your Metadata</strong></h2><p>At this stage, astute readers will have noticed a problem. Up to this point, we&apos;ve made two contradictory assumptions:</p><p><strong>Assumption 1:</strong> Every item we want to store is only 32 bytes long</p><p><strong>Assumption 2:</strong> Operations are logarithmic in the number of items actually <em>stored</em>in the tree, rather than in the logical size of the tree.</p><p>These two assumptions can&apos;t co-exist naturally. For Assumption 2 to hold, we need to use an optimized leaf-node construction which allows us to elide empty branches of the tree (see the JMT paper for details). To make that work, we need to give leaf nodes a completely different structure from internal nodes. So we need a <em>domain separator</em> to allow us to distinguish between internal and leaf nodes. (N.B. Domain separators are a good idea for security reasons as well. Type confusion is bad!) But, we&apos;ve already stipulated that each of the items in the tree is a 32-byte hash. If we increase the size to 33 bytes, our on-disk nodes will get too large to fit on a single SSD page, killing our performance.</p><p>Thankfully, there&apos;s a simple solution here too. Since all we need is a single bit of discriminant, we just borrow the one bit from our hash. In other words, we mandate that all internal nodes must have the leading bit set to <code>1</code>, and all leaf nodes must have their leading bit set to <code>0</code>. (That leaves us with 255 bits of hash output, which still leaves plenty of security margin.)</p><h2 id="h-step-4-halve-your-compressions" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 4: Halve your Compressions</strong></h2><p>Thanks to our trick from the previous section, we can now guarantee the invariant that every internal node is the hash of exactly two children, and that each child has a width of exactly 32 bytes. That means that at each layer of the tree, we need our hasher to ingest <em>exactly</em> 64 bytes of data. By a shocking coincidence, 64 bytes happens to be exactly the amount of data that can be ingested by many hash &quot;compression functions&quot; in a single invocation. (Most notably, SHA-256).</p><p>Unfortunately, such fixed-length compression functions are insecure on their own due to length extension attacks - so they aren&apos;t exposed by common cryptographic libraries. If you invoke the <code>sha256()</code> function in your favorite programming language, the library will be secretly padding your input with 64 bytes of metadata to prevent exactly this issue. Such padding is mandated by the NIST standard, and is absolutely necessary to ensure collision resistance in the presence of arbitrary-length messages.</p><p>But in our case, the messages don&apos;t have arbitrary length. <em>Internal</em> nodes are always constructed from 64 byte inputs. And thanks to our domain separators, we can easily distinguish internal nodes (which have fixed-size inputs) from leaf-nodes (which do not). By exposing the internals of our favorite hashing library, we can reduce the number of hashes required for tree traversal by a factor of 2 by using a padding-free hasher for internal nodes.</p><blockquote><p>(Note: Messing with hash functions should only be done with good reason, and always under the supervision of a qualified cryptographer! This optimization is most useful in contexts where you&apos;re proving statements about the tree in zero-knowledge. If you&apos;re only using the tree in &quot;native&quot; contexts, you may want to skip this one!)</p></blockquote><h2 id="h-step-5-blake-is-cool-be-like-blake" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 5: Blake is cool. Be like Blake.</strong></h2><p>So far, we’ve focused exclusively on internal nodes of the tree. Now, lets turn our attention to <em>leaf nodes.</em></p><p>The first thing to do is to recognize that, despite all of our hard work, state trees are <em>expensive</em>. While we&apos;ve drastically reduced overheads, the cost for each access is still logarithmic in the number of values stored. &quot;Normal&quot; hashing, on the other hand, is relatively cheap. You can do a standard (linear) hash of about a kilobyte of data for the same compute cost as a single merkle tree lookup (assuming a tree depth of 32). So, if two values have even a relatively small chance off being accessed together, you want to store them in a single leaf.</p><p>But increasing leaf sizes hits an efficiency ceiling disappointingly quickly. Using a linear hasher like <code>sha256</code>, you pay roughly one compression for every 32 bytes of data - so the &quot;breakeven&quot; leaf size is about one kilobyte. (In other words, if you&apos;d need to add more than a kilobyte of data to the leaf before you&apos;d expect that two distinct values from that leaf would be acessed together, then the clustering would not be worthwhile.)</p><p>Thankfully, there&apos;s yet another trick we can use to reduce the overhead. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/fd_ripatel?ref=127.0.0.1">@fd_ripatel</a> recently pointed out to me that Blake3 uses an internal merkle tree instead of a linear hasher. This means that it&apos;s possible to do partial reads and incremental updates against a Blake3 hash with only logarithmic overhead. With this advancement, the breakeven size of a leaf increases from about a kilobyte to over a <em>gigabyte</em>. In other words, if you&apos;re using Blake3 as your leaf hasher then your leaves should be <em>really, really big</em>.</p><blockquote><p>Note: It seems like it <em>should</em> be possible to combine the padding scheme from Blake3 with the compression function from just about any secure hasher to yield an optimized leaf hasher. But IANAC! If you are a cryptographer and have thoughts on this, please reach out!</p></blockquote><h2 id="h-step-6-cache-aggressively" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 6: Cache Aggressively</strong></h2><p>Last but not least, if we&apos;re going to make a performant merkle tree we need to cache aggressively. We&apos;ll start simple - the (physical) root node of the tree should <em>always</em> live in memory. Why? Because any read or write against the tree is going to read and/or write that node. By keeping this node in memory, we can already reduce our expected SSD load from 7 pages per query to 6 pages per query.</p><p>But we can go further. If we keep the next level of the <em>physical</em> tree in memory, we can eliminate one more page from each query. This only costs us an extra 256k of memory (since there are 2^6 nodes of 4k each at this height), and we&apos;ve already reduced our disk IOPS by 30%.</p><h2 id="h-step-7-be-lazy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Step 7: Be Lazy</strong></h2><p>Last but not least, we can improve our efficiency even using pretty standard tricks.</p><p>First, we should avoid reading from the merkle tree wherever possible. We can do this by storing our leaf-data in a &quot;flat&quot; key-value store so that we don&apos;t need to traverse the tree just to read data. This reduces disk IOPS and reduces tail latencies (since each page we avoid querying is one less opportunity for a hash collision). If the data is modified, we&apos;ll still end up needing to read the merkle path eventually - but this optimization is still well worth the effort since reads are so common.</p><blockquote><p>N.B. Almost all merkle tree implementations already supporting reading data from a flat store without traversing the trie.</p></blockquote><p>Second, we should always defer updates to our trie for as long as possible. The obvious reason for this is that two sequential updates might both update the same key (meaning that we can skip one trie update altogether). The less obvious reason is that a properly implemented merkle tree is asymptotically more efficient when updates are done in large batches.</p><p>The reason for this is relatively easy to understand. Suppose we have a sequence of four writes against the pictured merkle tree. Since the tree has depth 4 (excluding the root), we incur a cost of 16 hashes if we perform the updates sequentially (4 hashes per update time 4 updates). But if we perform the updates in a batch, we only need 9 hashes to compute the new root. Intuitively, this is because the hashing work for the top layers of the tree is amortized across 4 inserts. Even though we made four modifications, we only recomputed the root hash once.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/d24a944c2eef600c46daa9a2a2cfefd80b0fbfc26fdf168c1f3c0e2a78e35e9a.png" alt="A diagram showing four insertions into the merkle tree, at locations q, r, t, and z." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">A diagram showing four insertions into the merkle tree, at locations q, r, t, and z.</figcaption></figure><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Conclusion</strong></h2><p>Using the techniques described in this post, it should be possible to reduce the disk-induced <em>latency</em> of merkle tree operations a factor of ten or more, the computational overhead by a factor of two or more, and the total disk IOPS by a factor of 25 or more. With those improvements, it’s my hope that the cost of generating state commitments should no longer be prohibitive even for high throughput chains.</p><p>Of course, this approach is not magic. In engineering, tradeoffs abound. In this case, we achieve much of our IO performance gains by sacrificing the ability to store a complete archive of the merkle tree at all points in time. Also, we require lower-level interfaces to the SSD than standard constructions - which translates to some engineering complexity. Still, we’re excited to see where this approach leads!</p><hr><h3 id="h-acknowledgements" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Acknowledgements</strong></h3><p>Thanks to…</p><ul><li><p>Cem Ozer and Patrick O&apos;Grady for reviewing drafts of this post.</p></li><li><p>Zhenhuan Gao, Yuxuan Hu, and Qinfan Wu for the Jellyfish Merkle Tree paper</p></li><li><p>Peter Szilagyi for his excellent <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.youtube.com/watch?v=Cmuz_Xn_YJw&amp;ref=127.0.0.1">talk on the physical limits of Ethereum</a> (and for his tireless work on Geth)</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/fd_ripatel?ref=127.0.0.1">Richard Patel</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/dubbel06?ref=127.0.0.1">@dubbel06</a> for introducing me to Blake3’s secret merkle hasher</p></li><li><p>Emmanuel Goossaert for <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/?ref=127.0.0.1">his excellent series on SSDs</a>.</p></li></ul><h3 id="h-final-note" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Final Note</strong></h3><p>Since this post is now approaching Jon Charb levels of verbosity, I’ve made the editorial decision to omit a complete description of insertion, deletion, and proving algorithms compatible with the general construction described here. You can find suitable algorithms in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/penumbra-zone/jmt?ref=127.0.0.1">Jellyfish Merkle Tree</a> repo that Sovereign co-maintains. To understand the batched algorithms, see the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/jellyfish-merkle-generic/blob/master/src/lib.rs?ref=127.0.0.1#L444">Aptos fork of the JMT</a>. I’ve also omitted some details related to crash recovery, but the problem is not too difficult (hint: use a WAL). Finally, I’ve left aside the question of storage for the leaf nodes of the merkle tree. The best store for that data will depend on the application, but either a key-value store or a custom database might be appropriate.</p>]]></content:encoded>
            <author>sovlabs@newsletter.paragraph.com (Sovereign)</author>
            <enclosure url="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/912870ee956d1cb1e6784e77e5a422e143267929415459d5685dff4d67adabec.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Introducing the Sovereign SDK Alpha Release]]></title>
            <link>https://paragraph.com/@sovlabs/introducing-the-sovereign-sdk-alpha-release</link>
            <guid>GYyqhZxE3znnrlGh1ekY</guid>
            <pubDate>Wed, 31 May 2023 15:58:12 GMT</pubDate>
            <description><![CDATA[ZK-rollups are revolutionizing blockchains, but until now, it has been a real challenge for developers to deploy them. That&apos;s why we are thrilled to introduce the alpha release of the Sovereign SDK, a development toolkit that allows developers to launch their applications as zk-rollups.What is the Sovereign SDK Alpha Release?This is an MVP release focused on a few core features that will give you a feel for the developer experience of the Sovereign SDK. Concretely, we&apos;ve built:A dem...]]></description>
            <content:encoded><![CDATA[<p>ZK-rollups are revolutionizing blockchains, but until now, it has been a real challenge for developers to deploy them. That&apos;s why we are thrilled to introduce the alpha release of the Sovereign SDK, a development toolkit that allows developers to launch their applications as zk-rollups.</p><h2 id="h-what-is-the-sovereign-sdk-alpha-release" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is the Sovereign SDK Alpha Release?</h2><p>This is an MVP release focused on a few core features that will give you a feel for the developer experience of the Sovereign SDK. Concretely, we&apos;ve built:</p><ul><li><p>A demo rollup and prover</p></li><li><p>Data availability layer adapters for Celestia and Avail (Thanks to the Avail team for implementing their adapter!)*</p></li><li><p>A ZKVM adapter for Risc0**</p></li><li><p>A module system that enables you to compose off-the-shelf modules and custom logic to build your application as a ZK-rollup</p></li><li><p>Ready-to-use banking, storage, account, and centralized sequencing modules</p></li><li><p>A simple and extensible RPC interface for reading application state</p></li><li><p>A database layer to persist application state to disk, and an authenticated state store using a Jellyfish Merkle Tree</p></li><li><p>And lots of documentation to make it as easy as possible to build your own rollup</p></li></ul><p>It is important to note that the Sovereign SDK has not yet been audited and should not be used in production under any circumstances. During alpha, we will maintain API stability and compliance with semantic versioning only on a best effort basis.</p><h2 id="h-whats-next" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What&apos;s next?</h2><p>Over the next few months, we will be heads down productionizing our client: fixing known bugs, optimizing, improving developer experience, adding missing features like bridging and decentralized sequencing, and thoroughly testing the codebase.</p><p>Our goal is to reach beta (with APIs stabilized) in a year.</p><h2 id="h-why-sovereign-sdk" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Why Sovereign SDK?</h2><p>Once ready, the SDK will enable applications with:</p><ul><li><p><strong>Guaranteed and Scalable Throughput:</strong> The Sovereign SDK will ensure a scalable, verifiable, and censorship-resistant backend that keeps pace with your application&apos;s growth, sparing your users from exorbitant fees.</p></li><li><p><strong>No Need to Bootstrap Validators:</strong> Developing your application is challenging enough. The Sovereign SDK will eliminate the need to persuade validators or worry about liveness issues if they fail to show up.</p></li><li><p><strong>Fast, Cheap, and Trustless Bridging:</strong> Users shouldn&apos;t worry about funds getting stuck in an application or paying unnecessary transfer fees to liquidity providers. With the Sovereign SDK, you will bid farewell to week-long withdrawal periods while securely scaling your application.</p></li><li><p><strong>Full Customizability:</strong> Use your token as the gas token or add custom transaction processing logic. The Sovereign SDK will empower you to fully customize your stack, freeing you to build a limitless application.</p></li><li><p><strong>Reduced Platform Risk:</strong> It&apos;s difficult to pick winners in the DA and zk-proof systems. Thankfully, our agnostic design will let you deploy rollups with any compatible DA layer (including Ethereum!) and zk-proof system, mitigating risks.</p></li></ul><h2 id="h-ready-to-explore" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ready to explore?</h2><p>Check out the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign-sdk">Sovereign SDK</a> and start building your zk-rollup demo app! And fill out our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://airtable.com/shrPlG9rok3sVd8Mh">early partner interest form</a> if you have an idea you&apos;d like to brainstorm with us.</p><h2 id="h-the-internet-of-rollups" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Internet of Rollups</h2><p>We imagine a future where verifiable and censorship-resistant blockchain applications run side-by-side, scaling and communicating seamlessly.</p><p>Our mission is to enable anyone to build zk-rollups, not just cryptography experts or protocol engineers.</p><p>*: Tutorials to help you get started with the Avail adapter are coming soon.</p><p>**: The Risc0 adapter is currently limited as Risc0 does not support recursion yet.</p>]]></content:encoded>
            <author>sovlabs@newsletter.paragraph.com (Sovereign)</author>
            <enclosure url="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/c4697e9cc3bd4c68fc3c3eafdc8ec8d7146e99912c122da7c4e8d426b516a573.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Achieving fast finality with sovereign zk-rollups]]></title>
            <link>https://paragraph.com/@sovlabs/achieving-fast-finality-with-sovereign-zk-rollups</link>
            <guid>P48BvJw8L3twVzNIsDDi</guid>
            <pubDate>Tue, 07 Mar 2023 18:50:44 GMT</pubDate>
            <description><![CDATA[There’s a broad misconception in the crypto space that zk-rollups have a very long time-to-finality. In this post, we’ll explain why first-generation zk-rollups take so long to finalize, and how we can achieve lower latency without sacrificing security. By solving this problem, we’ll motivate a design for sovereign zk-rollups. Then, we’ll briefly explore the other advantages offered by sovereign designs.Finality Bottlenecks… and How to Solve ThemFor most zk-rollups, one of the largest expense...]]></description>
            <content:encoded><![CDATA[<p>There’s a broad misconception in the crypto space that zk-rollups have a very long time-to-finality. In this post, we’ll explain why first-generation zk-rollups take so long to finalize, and how we can achieve lower latency without sacrificing security.</p><p>By solving this problem, we’ll motivate a design for <strong>sovereign</strong> zk-rollups. Then, we’ll briefly explore the other advantages offered by sovereign designs.</p><h3 id="h-finality-bottlenecks-and-how-to-solve-them" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Finality Bottlenecks… and How to Solve Them</h3><p>For most zk-rollups, one of the largest expenses is proof verification. Depending on the underlying proof system, a rollup on Ethereum might spend anywhere from 300k to 5m gas to validate a single proof. But since proof sizes grow very slowly (or not at all) with the number of transactions, rollups can reduce their per-transaction cost by waiting to accumulate a large batch of transactions before submitting a proof. And indeed, this is exactly what most zk-rollups do today; popular rollups like StarkEx typically post proofs to L1 only once every few hours.</p><p>But waiting several hours for your transaction to finalize is an unacceptably bad user-experience. Can we do better? If a rollup posts raw transaction data onto its DA layer (rather than state diffs), then power-users who want fast finality can run full nodes. But if the rollup uses state-diffs, then even running a full-node doesn’t help. This is why most zk-rollups today have their sequencers give out “soft confirmations”. But soft confirmations are only possible in systems with centralized sequencers. So the question remains - how do we make <em>decentralized</em> zk-rollup competitive on latency?</p><p>If we only care about lowering the latency for full-nodes, then posting raw transaction data on chain solves the problem. As long as the full transaction data is available, full nodes can finalize as quickly as transactions can be applied (assuming the DA layer has single-slot finality). <strong>This lets our zk-rollup be just as responsive as a traditional L1!</strong></p><p>This is a very powerful result. <strong>It makes zk-rollups viable for highly latency-sensitive applications like on-chain order books.</strong> Nevertheless, it doesn’t completely solve the original problem. What we really want is to reduce latency for <em>light clients</em>. Can we do that without drastically increasing proof verification costs?</p><p>Surprisingly, the answer is still yes! Here’s how: instead of waiting to create a single batch proof, we can create proofs in real time and use recursion to aggregate them into a batch proof later. As long as we distribute incremental proofs right away via the p2p network, light clients will experience fast finality. And since we’re still just posting the slower batch proofs on chain, we don’t have to pay extra for on-chain verification. <strong>In other words, we still get a trust-minimized bridge with the L1 - but the bridge is no longer the source of truth for light clients.</strong> Congratulations, we’ve just built a sovereign rollup!</p><h3 id="h-proving-the-fork-choice" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Proving the Fork Choice</h3><p>Eagle-eyed readers will have noticed a problem with the system described in the previous paragraph. In today’s rollups, the L1 smart contract is responsible for enforcing the rollups fork-choice rule. In other words, the smart contract doesn’t just verify the zk-proof, it also checks that the proof builds on the previous proof (and not some other fork), and that it’s processed all of the relevant “forced transactions” which were sent on L1. So, if we don’t use a smart contract, how do we pick a canonical fork?</p><p>Once again, the answer is surprisingly simple - if we require proofs to be posted as calldata on the L1, then we can add a rule that a new proof is only valid if it builds on the previous valid proof. Although we could enforce this rule client-side, it would require users to scan through the chain’s history all the way back to genesis (or at least some widely known checkpoint) - which isn’t ideal.</p><p>Thankfully, it’s not too hard to add it directly into the zk-proof instead. By tying calldata back to the L1 block headers, we can add a statement saying “I have scanned the DA layer for proofs (starting at block X and ending at block Y), and this proof builds on the most recent valid proof”. This lets us prove the fork-choice rule directly, rather than enforcing it client side! And if we’re already scanning the DA layer for proofs, we can easily scan for forced transactions as well.</p><h3 id="h-portability-and-interoperability" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Portability and Interoperability</h3><p>In the Smart Contract rollup paradigm, the L1 bridge is unique. Since the bridge is responsible for checking statements that aren’t included in the proof (statements like “this proof includes all of the forced transactions”), adding proof-based bridges to other destinations doesn’t usually make sense. But once we’ve moved all of our logic inside the proof, this is no longer an issue. We can add bridges anywhere we want!</p><p>And we can even go a step further. If our L1 smart contract is just a regular bridge, we can do away with it altogether. For the first time, it makes sense to think about building rollups on systems that don’t support smart contracts. Just take your existing rollup logic and remove the L1 bridge. Suddenly, you can deploy on any DA layer! In other words, your rollup logic becomes instantly portable across different L1s.</p><h3 id="h-conclusion" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h3><p>The future of rollups is sovereign. By proving their fork-choice rules directly, sovereign zk-rollups enable seamless interoperability, instant portability, and fast finality. Just like smart-contract rollups, they provide low gas costs and trust-minimized bridges - but they aren’t forced to trade-off between cost and latency. Sometimes, you can have your cake and eat it too 🙂</p><p>As always at Sovereign, we’re building in public. All of our code is free and open source, and contributions are welcome. Oh, and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.sovereign.xyz/join">we’re hiring</a>. If this sounds interesting, come <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign/blob/research/sdk/specs/overview.md">build with us</a>!</p>]]></content:encoded>
            <author>sovlabs@newsletter.paragraph.com (Sovereign)</author>
        </item>
        <item>
            <title><![CDATA[How the Sovereign SDK works]]></title>
            <link>https://paragraph.com/@sovlabs/how-the-sovereign-sdk-works</link>
            <guid>l2nq0G3sjekLq34aT7r8</guid>
            <pubDate>Tue, 14 Feb 2023 15:22:27 GMT</pubDate>
            <description><![CDATA[In our announcement blog post, we sketched out a vision of a future in which creating a zk-rollup is as easy as launching a dapp. This blog post seeks to explain how we’ll realize that vision. Before we dive in, a quick warning - this post will be somewhat technical but not completely rigorous. If you want a non-technical introduction to the Sovereign SDK, see here instead. If you want full technical rigor, you can find draft specs and implementation of the Sovereign SDK on GitHub or reach ou...]]></description>
            <content:encoded><![CDATA[<p>In our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/sovlabs.eth/V-nUycehpu-cwCRaStz7984mYGOKpRGMyC-tTDaQIsk">announcement blog post</a>, we sketched out a vision of a future in which creating a zk-rollup is as easy as launching a dapp. This blog post seeks to explain how we’ll realize that vision. Before we dive in, a quick warning - this post will be somewhat technical but not completely rigorous. If you want a non-technical introduction to the Sovereign SDK, see <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.sovereign.xyz/learn">here</a> instead. If you want full technical rigor, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign">you can find draft specs and implementation of the Sovereign SDK on GitHub</a> or <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://discord.gg/kbykCcPrcA">reach out on Discord</a>. With that out of the way, let’s get started!</p><h2 id="h-why-zk-rollups" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Why ZK-Rollups?</h2><p>In this section, we’ll try to explain why we think zk-rollups are the “endgame” for blockchains, from first principles.</p><h3 id="h-the-price-of-atomic-composability" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">The Price of Atomic Composability</h3><p>We start from the thesis that Ethereum’s killer feature isn’t the EVM itself, it’s <em>atomic composability</em>. Any smart-contract can interact with any other one, and the <em>system</em> guarantees that the caller won’t get stuck in some intermediate state - even if something goes wrong further up the call stack. This drastically simplifies the programming model, making Ethereum one of the friendliest environments for developers. But atomic composability comes at a price. In order to execute a call atomically, full nodes need to access the relevant state of all contracts in the call stack <em>in real time</em>. That means that they have to have all of the relevant state stored locally, and they need to be able to load it very rapidly. In other words, full nodes need to buy SSDs, and they need to buy enough of them to store the entire chain state.</p><p>To maximize scalability, systems like Near attempt to relax these requirements. Instead of atomic composability, Near only allows <em>asynchronous</em> cross-contract calls. This setup allows much more scalability, since each contract’s state can be stored only on a dedicated subset of full nodes. But it comes at great cost to the programmer - who has to reason through a combinatorial explosion of code paths.</p><h3 id="h-best-of-both-worlds" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Best of Both Worlds</h3><p>Our vision is the synthesis of these two extremes. We believe that no fully atomic system can scale to the needs of the entire world. But at the same time, we recognize the central role that atomicity plays in developing simple and composable smart contracts. And so, we seek to achieve the best of both worlds: an ecosystem of optimized chains that each provide atomic composability, connected by fast (but asynchronous) cross-chain bridges across the ecosystem.</p><p>Within a single chain, developers retain atomic composability. Contract designs can be relatively simple, and users can know the outcomes of their transactions in near-real time. But at the same time, no set of full nodes is responsible to store and update the state of the entire world.</p><h3 id="h-build-rollups-not-blockchains" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Build rollups, not Blockchains</h3><p>We certainly aren’t the first people to think along these lines. As far as we know, the first attempt to create an integrated multi-chain system was Cosmos. Much like our vision, Cosmos is a bottom-up ecosystem of heterogeneous chains with fast and reliable bridging. Where Cosmos falls short is in the ability to create new chains. Before a chain becomes useable, it needs a “validator set” to throw meaningful economic weight behind its operation. Costs of capital being what they are, it’s extremely difficult to attract a validator set to a chain until the chain has enough usage to create significant revenues. But of course, no user wants to risk their money on an insecure chain. This leads to a chicken-and-egg scenario, and makes launching new chains a herculean undertaking.</p><p>In addition, Cosmos chains face fundamental difficulties in implementing bridges. Their light client bridges (IBC) are expensive to maintain because headers have to be processed on-chain. This results in a sparse bridge graph, which translates into a bad user experience. Even worse, funds can be lost if a chain suffers a long-distance reorg or its validator set executes an invalid state transition.</p><p>Our solution to these problems is simple: share a validator set with many other chains. In other words, <em>don’t build blockchains; build rollups.</em></p><p>This, at last, leads us to our offering: the Sovereign SDK.</p><h2 id="h-what-is-the-sovereign-sdk" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is the Sovereign SDK?</h2><p>The Sovereign SDK is a toolkit for building <em>asynchronously-proven, sovereign zk-rollups.</em> That’s quite a mouthful, so let’s go through it piece by piece.</p><p>First, what does it mean that transactions are <em>proven asynchronously</em>? In plain English, it simply means that <em>raw transaction data</em> is posted to the L1 by a sequencer in real-time, and proofs are generated later. This is in contrast to rollups like StarkNet, where proofs are generated off-chain <em>before</em> any data is posted on chain. The advantage of asynchronous proving is that it allows transactions to be finalized in real-time, giving the rollup responsiveness like that of an Optimistic Rollup.</p><p>Second, what does it mean for a rollup to be <em>sovereign</em>? This is easiest to understand by contrast with a “smart-contract” rollup. In a smart-contract rollup, the L2 state is only final (in the view of light clients) when it is accepted by a smart-contract on the L1. Because today’s L1 chains have such limited throughput, smart-contract rollups are forced to update their canonical on-chain state relatively infrequently. On the other hand, light clients of <em>sovereign</em> rollups are responsible to decide for themselves if a particular block should be accepted. This allows sovereign rollups to finalize much more quickly, since they don’t have to throttle their update frequency to accommodate congested L1s.</p><h2 id="h-how-does-it-work" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How Does it Work?</h2><p>The Sovereign SDK works by abstracting the functionality of a zk-rollup into a hierarchy of interfaces. At each level of the hierarchy, developers are free to use one of the pre-packaged implementations provided by the SDK or build their own functionality from scratch. By encapsulating logic behind well-defined interfaces, we’re able to provide pluggable components that create a simple developer experience without sacrificing flexibility.</p><h3 id="h-the-core-apis" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">The Core APIs</h3><p>At the highest level of abstraction, each Sovereign SDK chain combines three distinct elements:</p><ol><li><p>An L1 blockchain, which provides DA and consensus</p></li><li><p>A state transition function (written in Rust), which implements the &quot;business logic&quot; of the chain</p></li><li><p>A zero-knowledge proof system capable of (1) recursion and (2) running a subset of Rust</p></li></ol><pre data-type="codeBlock" text="interface DaLayer {
  // Gets all transactions from a particular da layer block that are relevant to the rollup
  // Used only by full-nodes of the rollup
  function get_relevant_txs(header: DaHeader) -&gt; Array&lt;DaTxWithSender&gt;
  // Gets all transactions from a particular da layer block that are relevant to the rollup,
  // along with a merkle proof (or similar) showing that each transaction really was included in the da layer block.
  // Depending on the DA layer, may need to include auxiliary information to show that no 
  // relevant transactions were omitted.
  // Used by provers
  function get_relevant_txs_with_proof(header: DaHeader) -&gt; (Array&lt;DaTxWithSender&gt;, DaMultiProof, CompletenessProof&gt;
  // Verifies that a list of Da layer transactions provided by an untrusted prover is both 
  // complete and correct.
  // Used by the &quot;verifier&quot; circuit in the zkVM
  function verify_relevant_tx_list(txs: Array&lt;DaTxWithSender&gt;, header: DaHeader, witness: DaMultiProof, completenessproof: CompletenessProof)
}

// The interface to a state transition function, inspired by Tendermint&apos;s ABCI
interface StateTransitionFunction {
    // Called once at rollup Genesis to set up the chain
  function init_chain(config: Config) 
  // A slot is a DA layer block, and may contain 0 or more rollup blocks
  function begin_slot(slotnumber: u64) 
  // Applies a batch of transactions to the current rollup state, returning a list of the
  // events from each transaction, or slashing the sequencer if the batch was malformed
  function apply_batch(batch: Array&lt;Transaction&gt;, sequencer: Bytes): Array&lt;Array&lt;Event&gt;&gt; | ConsensusSetUpdate
  // Process a zero-knowledge proof, rewarding (or punishing) the prover
  function apply_proof(proof: RollupProof): Array&lt;ConsensusSetUpdate&gt;
  // Commit changes after processing all rollup messages
  function end_slot(): StateRoot
}

interface ZkVM { 
  // Runs some code, creating a proof of correct execution
  function run(f: Function): Proof
  // Verifies a proof, returning its public outputs on success
  function verify(p: Proof): (Result&lt;Array&lt;byte&gt;&gt;)
}
"><code>interface <span class="hljs-title class_">DaLayer</span> {
  <span class="hljs-comment">// Gets all transactions from a particular da layer block that are relevant to the rollup</span>
  <span class="hljs-comment">// Used only by full-nodes of the rollup</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">get_relevant_txs</span>(<span class="hljs-params">header: DaHeader</span>) -> <span class="hljs-title class_">Array</span>&#x3C;<span class="hljs-title class_">DaTxWithSender</span>>
  <span class="hljs-comment">// Gets all transactions from a particular da layer block that are relevant to the rollup,</span>
  <span class="hljs-comment">// along with a merkle proof (or similar) showing that each transaction really was included in the da layer block.</span>
  <span class="hljs-comment">// Depending on the DA layer, may need to include auxiliary information to show that no </span>
  <span class="hljs-comment">// relevant transactions were omitted.</span>
  <span class="hljs-comment">// Used by provers</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">get_relevant_txs_with_proof</span>(<span class="hljs-params">header: DaHeader</span>) -> (<span class="hljs-title class_">Array</span>&#x3C;<span class="hljs-title class_">DaTxWithSender</span>>, <span class="hljs-title class_">DaMultiProof</span>, <span class="hljs-title class_">CompletenessProof</span>>
  <span class="hljs-comment">// Verifies that a list of Da layer transactions provided by an untrusted prover is both </span>
  <span class="hljs-comment">// complete and correct.</span>
  <span class="hljs-comment">// Used by the "verifier" circuit in the zkVM</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">verify_relevant_tx_list</span>(<span class="hljs-params">txs: <span class="hljs-built_in">Array</span>&#x3C;DaTxWithSender>, header: DaHeader, witness: DaMultiProof, completenessproof: CompletenessProof</span>)
}

<span class="hljs-comment">// The interface to a state transition function, inspired by Tendermint's ABCI</span>
interface <span class="hljs-title class_">StateTransitionFunction</span> {
    <span class="hljs-comment">// Called once at rollup Genesis to set up the chain</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">init_chain</span>(<span class="hljs-params">config: Config</span>) 
  <span class="hljs-comment">// A slot is a DA layer block, and may contain 0 or more rollup blocks</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">begin_slot</span>(<span class="hljs-params">slotnumber: u64</span>) 
  <span class="hljs-comment">// Applies a batch of transactions to the current rollup state, returning a list of the</span>
  <span class="hljs-comment">// events from each transaction, or slashing the sequencer if the batch was malformed</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">apply_batch</span>(<span class="hljs-params">batch: <span class="hljs-built_in">Array</span>&#x3C;Transaction>, sequencer: Bytes</span>): <span class="hljs-title class_">Array</span>&#x3C;<span class="hljs-title class_">Array</span>&#x3C;<span class="hljs-title class_">Event</span>>> | <span class="hljs-title class_">ConsensusSetUpdate</span>
  <span class="hljs-comment">// Process a zero-knowledge proof, rewarding (or punishing) the prover</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">apply_proof</span>(<span class="hljs-params">proof: RollupProof</span>): <span class="hljs-title class_">Array</span>&#x3C;<span class="hljs-title class_">ConsensusSetUpdate</span>>
  <span class="hljs-comment">// Commit changes after processing all rollup messages</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">end_slot</span>(<span class="hljs-params"></span>): <span class="hljs-title class_">StateRoot</span>
}

interface <span class="hljs-title class_">ZkVM</span> { 
  <span class="hljs-comment">// Runs some code, creating a proof of correct execution</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">f: <span class="hljs-built_in">Function</span></span>): <span class="hljs-title class_">Proof</span>
  <span class="hljs-comment">// Verifies a proof, returning its public outputs on success</span>
  <span class="hljs-keyword">function</span> <span class="hljs-title function_">verify</span>(<span class="hljs-params">p: Proof</span>): (<span class="hljs-title class_">Result</span>&#x3C;<span class="hljs-title class_">Array</span>&#x3C;byte>>)
}
</code></pre><h3 id="h-generalized-full-node" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Generalized Full Node</h3><p>Using the core interfaces we just described, the Sovereign SDK will provide a generalized full-node implementation capable of running almost any state-transition function over any data availability layer. In general terms, the full node works by encapsulating rollup <em>functionality</em> behind the core interfaces we just described, and treating rollup <em>data</em> as opaque byte strings that can be stored and transmitted. To give you a sense of how the full node will operate, here’s a rough illustration of what the core event loop will look like.</p><pre data-type="codeBlock" text="// A pseudocode illustration of block execution. Plays fast and loose
// with types for the sake of brevity.
function run_next_da_block(self, prev_proof: Proof, db: Database) {
  // Each proof commits to the previous state as a public output. Use that state 
  // to initialize the state transition processor
  let prev_state = deserialize(prev_proof.verify());
  let current_da_header = db.get_da_header(prev_state.slot_number + 1);
  let processor = stf::new(db, current_da_header, prev_state);
  
  // Fetch the relevant transactions from the DA layer
  let (da_txs, inclusion_proof, completeness_proof) = da::get_relevant_txs_with_proof(current_header);
  da::verify_relevant_tx_list(da_txs, inclusion_proof, completeness_proof)
  
  // Process the relevant DA transactions 
  processor.begin_slot(prev_state.slot_number + 1)
  for da_tx in da_txs { 
    let msg = parse_msg(da_tx);
    if msg.is_batch() { 
      processor.apply_batch(msg)
    } else {
      processor.apply_proof(msg)
    }
  }
  processor.end_slot();
}
"><code><span class="hljs-comment">// A pseudocode illustration of block execution. Plays fast and loose</span>
<span class="hljs-comment">// with types for the sake of brevity.</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run_next_da_block</span>(<span class="hljs-params"><span class="hljs-built_in">self</span>, prev_proof: Proof, db: Database</span>) </span>{
  <span class="hljs-comment">// Each proof commits to the previous state as a public output. Use that state </span>
  <span class="hljs-comment">// to initialize the state transition processor</span>
  let prev_state <span class="hljs-operator">=</span> deserialize(prev_proof.verify());
  let current_da_header <span class="hljs-operator">=</span> db.get_da_header(prev_state.slot_number <span class="hljs-operator">+</span> <span class="hljs-number">1</span>);
  let processor <span class="hljs-operator">=</span> stf::<span class="hljs-keyword">new</span>(db, current_da_header, prev_state);
  
  <span class="hljs-comment">// Fetch the relevant transactions from the DA layer</span>
  let (da_txs, inclusion_proof, completeness_proof) <span class="hljs-operator">=</span> da::get_relevant_txs_with_proof(current_header);
  da::verify_relevant_tx_list(da_txs, inclusion_proof, completeness_proof)
  
  <span class="hljs-comment">// Process the relevant DA transactions </span>
  processor.begin_slot(prev_state.slot_number <span class="hljs-operator">+</span> <span class="hljs-number">1</span>)
  <span class="hljs-keyword">for</span> da_tx in da_txs { 
    let <span class="hljs-built_in">msg</span> <span class="hljs-operator">=</span> parse_msg(da_tx);
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">msg</span>.is_batch() { 
      processor.apply_batch(<span class="hljs-built_in">msg</span>)
    } <span class="hljs-keyword">else</span> {
      processor.apply_proof(<span class="hljs-built_in">msg</span>)
    }
  }
  processor.end_slot();
}
</code></pre><h3 id="h-pluggable-components" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Pluggable Components</h3><p>Because the full node implementation is completely general, developers can easily swap out modules to change the characteristics of their chain. For example, a developer who was very concerned about the cost of calldata would probably use <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/Jupiter">Jupiter</a>, our integration with the Celestia blockchain. On the other hand, a developer who cared primarily about access to liquidity might build on top of Ethereum or EigenDA (we’ll have a pluggable module for this too, but it won’t be developed until after the prototype stage).</p><p>Similarly, developers are free to choose their own zkVM - as long as it supports no_std Rust and a few shim APIs. Developers who prefer to use a standard compiler toolchain and VM interface will likely pick <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/risc0/risc0">Risc0</a>, while those who value small proof sizes might pick another VM like that of the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nil.foundation/">=nil; foundation</a>.</p><h3 id="h-default-modules" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Default Modules</h3><p>Jumping down a layer of abstraction, the Sovereign SDK also aims to simplify the process of creating state transition functions. Toward this end, we’ll provide a composable module system loosely inspired by the Cosmos SDK. Although we’ll continue building out functionality over time, we plan to launch with at least the following modules: storage, bridging, (fungible) tokens, and proving/sequencing.</p><p>As of this writing, the design of the module system is still in its early stages - so everything you read in the section is subject to rapid change. But, we still think it’s useful to sketch out some of the designs we’ve been working on just to give you a taste of what’s to come.</p><p><strong>The Bridging Module</strong></p><p>First up, bridging. Unlike bridging between separate chains (which is <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://spiral.imperial.ac.uk/bitstream/10044/1/75810/6/2019-1128.pdf">known to be impossible</a> without strong trust assumptions), bridging between rollups on a shared DA layer is not <em>fundamentally</em> difficult. In the Sovereign SDK, we plan to take full advantage of this fact to provide low-latency trust minimized bridges by default.</p><p>It works like this: assume you had 50 rollups running on a shared DA layer. To run trust-minimized bridges between each of them in the standard paradigm, you would need every rollup to run an on-chain light client of every other rollup. Doing the math, that’s 50 * 49 = 2450 on-chain light clients. Since this would be prohibitively expensive to actually execute, you’d fall back to using multi-hop bridges instead. You’d have each chain connects to (say) 3 other chains, and - assuming your networks had a reasonable topology - you’d be able to route messages between any pair of chains using 3 or 4 hops.</p><p>With the power of zk, we can do much better. Recall that any two zero-knowledge proofs (in the VM model) can be aggregated into a third proof, and that this new proof is no more expensive to verify than the originals were. Leveraging this insight, we can recursively aggregate all 50 rollup proofs off-chain, and then verify that aggregate proof on each of the rollups. So instead of maintaining 2450 light client connections, we can do one off-chain proof aggregation and 50 on-chain proof verifications. In other words, we’ve reduced the communication complexity of all-to-all bridging from O(n^2) to O(n).</p><p><strong>The Storage Module</strong></p><p>A key component of efficient zk-rollups is state management. When designing an authenticated data store, developers have to juggle several competing concerns. On the one hand, they want something that’s efficient to represent in-circuit, since every read and write to state has to be authenticated inside the zero-knowledge proof. On the other hand, they want something that’s fast and lightweight during native execution so that it doesn’t bottleneck full-nodes and sequencers.</p><p>In the Sovereign SDK, we plan to provide a default storage module which balances these two design goals. For efficiency <em>outside</em> of the zkVM, we’ll use the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://developers.diem.com/papers/jellyfish-merkle-tree/2021-01-14.pdf">Jellyfish Merkle Tree</a> (JMT) originally developed by Diem, to store authenticated state data. For rapid access, we’ll also maintain a flat on-disk data structure to hold raw state. For efficiency in-circuit, we’ll make our JMT generic over hash function so that developers can easily plug in a hash tailored to their choice of zkVM.</p><h3 id="h-putting-it-all-together-the-block-lifecycle" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Putting it All Together: the Block Lifecycle</h3><p>Adding a block to a Sovereign chain happens in three steps. First, a sequencer posts a new blob of transaction data onto the L1 chain. As soon as the blob is finalized on the L1, a new <em>logical</em> rollup state is finalized as well.</p><p>As each L1 block is finalized, <em>full nodes</em> of the rollup scan through it and identify the blobs that are relevant to the rollup’s state transition function. Then, they apply the rollup’s STF to each of these blobs in order, computing the new rollup state root. At this point, the block is subjectively finalized from the perspective of the full nodes.</p><p>Next, prover nodes (full nodes running inside of a zkVM) perform the same process as full nodes - scanning through the DA block and processing all of the data blobs in order. Unlike full nodes, however, provers race against one another to generate proofs and post them on chain. The first eligible node to create a valid proof is rewarded with tokens corresponding to (some portion of) the rollup’s gas fees. Once a proof for a given batch has been posted on-chain, the batch is subjectively final to light clients.</p><p>Finally, a relayer <em>may</em> choose to submit a recent rollup proof to a bridge smart-contract on another chain - either the underlying DA layer or another rollup. For efficiency, relayers will probably use proof aggregation to combine validity proofs of many rollups into a single proof before submitting updates.</p><h3 id="h-conclusion" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h3><p>If you’ve made it this far, you should now have a good high-level understanding of the Sovereign SDK. If you’re interested in learning even more, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign">dive into our github</a> (contributions welcome!), <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/sovereign_labs">reach out on Twitter</a>, or <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://discord.gg/kbykCcPrcA">come hang on Discord</a>! Also, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.sovereign.xyz/join">we’re hiring!</a></p>]]></content:encoded>
            <author>sovlabs@newsletter.paragraph.com (Sovereign)</author>
            <enclosure url="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/12e076297bd03bc03555ad15129fb380e16e8dbf3f78671ad95ae81599a26a5b.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing Sovereign]]></title>
            <link>https://paragraph.com/@sovlabs/introducing-sovereign</link>
            <guid>0drk6nNOO0IVHxKAjhUz</guid>
            <pubDate>Mon, 30 Jan 2023 01:37:53 GMT</pubDate>
            <description><![CDATA[Sovereign is an open, interconnected rollup ecosystem. Our goal is to enable all developers to deploy seamlessly interoperable and scalable rollups that can run on any blockchain. That’s why we’re building the Sovereign SDK, the simplest framework for creating secure and interoperable sovereign zk-rollups.The Problem: Blockchain Apps Don’t ScaleMonolithic L1s Don’t ScaleIn the quest to scale blockchain apps, three dominant paradigms have emerged: application-specific layer 1s, optimistic roll...]]></description>
            <content:encoded><![CDATA[<p>Sovereign is an open, interconnected <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.sovereign.xyz/learn">rollup</a> ecosystem. Our goal is to enable all developers to deploy <strong>seamlessly interoperable and scalable</strong> rollups that can <strong>run on any blockchain</strong>. That’s why we’re building the Sovereign SDK, the simplest framework for creating secure and interoperable sovereign zk-rollups.</p><h2 id="h-the-problem-blockchain-apps-dont-scale" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Problem: Blockchain Apps Don’t Scale</h2><h3 id="h-monolithic-l1s-dont-scale" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Monolithic L1s Don’t Scale</h3><p>In the quest to scale blockchain apps, three dominant paradigms have emerged: application-specific layer 1s, optimistic rollups, and zk-rollups. App-specific layer 1s are the simplest to design and implement - but they come with a significant drawback. Every layer 1 has to recruit its own validators and convince them to stake enough capital to defend the chain against attacks. Costs of capital being what they are, this approach is only viable for a few well-funded apps.</p><h3 id="h-optimistic-rollups-break-composability" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Optimistic Rollups Break Composability</h3><p>Rollups try to solve this problem by allowing developers to extend <em>existing</em> blockchains with new logic, rather than starting new ones. This drastically decreases the barrier to entry for app-chain development.</p><p>But optimistic rollups (the rollups that <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://l2beat.com/scaling/tvl">dominate the market</a> today) are not a panacea. In order to minimize the burden on the shared validator set, optimistic rollups rely on fraud proofs to prevent misbehavior. During an attack, these fraud proofs might be censored - so optimistic rollups have long “finality delays” during which transactions could be rolled back by the appearance of a fraud proof. This makes bridging out of optimistic rollups slow and costly.</p><p>So, optimistic rollup developers have to make a tough choice: do they build narrow, special-purpose rollups and expect users to bridge frequently between chains? Or do they build general-purpose rollups that try to satisfy all of a user’s needs in one place? Neither choice is good. Bridging between optimistic chains is too slow, too expensive, and too risky to be an everyday operation. But general-purpose optimistic rollups suffer from many of the drawbacks of monolithic L1s: not only do they suffer from scalability issues of their own, but they also fail to support the full diversity of functionality that app-chains can create.</p><h3 id="h-zk-rollups-are-the-future" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Zk-Rollups are the Future</h3><p>For all of these reasons, we expect zk-rollups to emerge as the dominant scaling paradigm. They share the advantages of optimistic rollups, but without the long finality delay. Instead of waiting days to see if a fraud proof appears, users can be confident that their transaction is final as soon as a validity proof is created - a process which can take mere seconds.</p><p>But if zk-rollups are the superior scaling solution, why isn’t everyone using them? Because until now, actually building zk-rollups has been a huge undertaking - requiring years of effort from expert cryptography engineers.</p><h2 id="h-enter-sovereign-the-internet-of-rollups" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Enter Sovereign: The Internet Of Rollups</h2><h3 id="h-what-is-the-sovereign-sdk" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">What is the Sovereign SDK?</h3><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign-sdk">Sovereign SDK</a> aims to do for zk-rollups what the Cosmos SDK did for layer 1 chains.</p><p>It will provide all of the boilerplate that would usually go into a blockchain - a p2p network, a database, and an RPC node - letting builders focus on the business logic of their chain. In addition, it will provide a set of default modules that operate efficiently in a zero-knowledge environment: a token implementation, an authenticated data store, and a bridging module. Developers will be able to combine these primitives into app-chains, or build their own state-transition functions completely from scratch by implementing a standard set of APIs.</p><p>Even better, the Sovereign SDK will be the first rollup framework to abstract away the details of zero-knowledge. To take advantage of its power, developers won’t need to become experts in cryptography. Instead, developers will be able to write their apps in idiomatic Rust (or eventually C++), and the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign-sdk/blob/research/specs/interfaces/zkvm.md">SDK</a> will automatically compile it to an <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/nilfoundation/zkllvm">efficient</a> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/risc0/risc0">zk virtual machine</a>.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/24ab222e1c814d4a614c98e95497f603d3fece2c83ba61c31529f0666ce36ee7.jpg" alt="Proof aggregation based bridging" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Proof aggregation based bridging</figcaption></figure><h3 id="h-what-properties-will-the-sovereign-sdk-provide" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">What properties will the Sovereign SDK provide?</h3><ol><li><p>Sovereign SDK rollups will be <strong>seamlessly interoperable.</strong> We use a novel bridging technique based on proof aggregation to allow Sovereign SDK rollups on a shared L1 to bridge back and forth without a trusted third party. Since zk-proofs can be aggregated, rollups will be able to maintain as many bridges as they want at minimal cost. Off-chain relayers can aggregate the proofs of all the peer rollups into a single proof, and just that single proof can be verified on chain. And since state transitions are proven to be valid, there will be no need to pay fees to a liquidity provider or wait a week for transactions to finalize. <strong>Bridge right away, with no drawbacks</strong>.</p></li><li><p>Sovereign SDK rollups will be <strong>securely scalable.</strong> By purpose-building every component especially for use in zk-rollups, the Sovereign SDK will provide excellent performance by default. Thanks to advances in parallelism, our proving will have latencies measured in seconds. And with the magic of zero-knowledge proofs, chains will be able to scale without sacrificing end-user verifiability.</p></li><li><p>Sovereign SDK Rollups can <strong>run on any blockchain</strong>. Sovereign SDK chains delegate the responsibility for checking proofs to the end user, not the underlying L1. This is what makes them “sovereign” rollups as opposed to smart-contract rollups. Since the data availability layer doesn’t need the capability to verify proofs, Sovereign SDK rollups are automatically portable across any L1. <strong>Write once, run anywhere.</strong></p></li></ol><h3 id="h-where-are-we-now" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Where are we now?</h3><p>We’re currently hard at work developing the Sovereign SDK. Our current roadmap is split into three phases.</p><p><strong>Phase 1: Research (In Progress)</strong></p><p>The Sovereign SDK is currently undergoing R&amp;D. We’re in the process of designing the default storage module, cryptoeconomics, and core APIs. (If you’re interested in working on these problems, check out our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://sovereign.xyz/join">jobs board</a>!). We’re also working on a research prototype. Although the SDK will support a wide range of data availability layers and proving systems, our current prototype integrates Celestia for data availability and Risc0 for proving. We expect this phase to be complete around Q2 2023.</p><p><strong>Phase 2: Initial Implementation</strong></p><p>The initial implementation of the Sovereign SDK will begin in parallel with the remaining research. During this time, we’ll implement a peer-to-peer network, an RPC node, the core APIs, and default storage and sequencing modules.</p><p><strong>Phase 3: Hardening</strong></p><p>Once the SDK is feature complete, we’ll need plenty of time for code cleanup, testing, fuzzing, and audits. We’ll also begin developing our first use cases of the SDK during this time (more details on these to be announced!). We expect to see mainnet chains using the SDK after about six months of hardening the initial implementation.</p><h2 id="h-make-scaling-simple" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Make scaling simple</h2><p>At Sovereign Labs, we want to see blockchains support <em>billions</em> of users. That will only happen if zero-knowledge technology is accessible to developers without a PhD in cryptography - and if building rollups is accessible to developers who aren’t expert protocol engineers.</p><p>But limitless scalability is worthless if it comes at the expense of the end-user verifiability. If building, running, or viewing the state of your blockchain requires the cooperation of some centralized entity, then we have failed. That’s why the Sovereign SDK will always be completely free and open source - and why we’re committed to engineering every component of the stack for maximum resilience. But don’t just take our word for it. We’re building <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign-sdk/">in public</a> - come see for yourself.</p><p>Scaling should be simple. With the Sovereign SDK, it finally will be.</p><h3 id="h-resources" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Resources</h3><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.sovereign.xyz/join">Join our team</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/sovereign_labs">Follow us on Twitter</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://discord.gg/kbykCcPrcA">Join our Discord</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Sovereign-Labs/sovereign-sdk">Check out our Github</a></p></li></ul><h3 id="h-acknowledgments" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Acknowledgments</h3><p>We really appreciate our supporters, especially Tim Beiko, James Prestwich, Mustafa Al-Bassam, Chris Ahn, and Ekram Ahmed, for reviewing drafts of this post, and their guidance along our journey.</p>]]></content:encoded>
            <author>sovlabs@newsletter.paragraph.com (Sovereign)</author>
            <enclosure url="https://img.paragraph.com/cdn-cgi/image/format=auto/https://storage.googleapis.com/papyrus_images/4020bfa74636323f2245a1f1902b24a5c56153cf9fd0bf25767edea39580e6ff.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>