Skip to content

Commit

Permalink
Merge branch 'nightly' into dub/offchain_documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
dubbelosix committed Oct 4, 2023
2 parents 9fd821d + 5664adc commit f14b721
Show file tree
Hide file tree
Showing 25 changed files with 205 additions and 114 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ must implement. In the Sovereign SDK, we define a rollup as the combination of t
1. A [State Transition Function](./rollup-interface/specs/interfaces/stf.md) ("STF") which defines the "business logic" of the rollup
1. A [Data Availability Layer](./rollup-interface/specs/interfaces/da.md) ("DA layer") which determines the set of transactions that are fed
to the state transition function
1. A Zero Knowledge proving system (aka "Zero Knowledge Virtual Machine" or "ZKVM"), which takes the compiled rollup code and
1. A Zero Knowledge proving system (aka "Zero Knowledge Virtual Machine" or "zkVM"), which takes the compiled rollup code and
produces succinct proofs that the logic has been executed correctly.

One of the primary goals of the Sovereign SDK is to enable a clean separation of concerns between these three components.
Expand Down Expand Up @@ -104,7 +104,7 @@ If you want to add support for a new data availability layer, the easiest way to

Adapters contain the logic integrating 3rd party codebases into the Sovereign SDK. Over time, we expect Sovereign SDK
to have adapters for almost all Data Availability Layers and LLVM-compatible proof systems. Currently, we
maintain adapters for [`Risc0`](https://www.risczero.com) (a ZKVM) and [`Celestia`](https://www.celestia.org) a (DA layer).
maintain adapters for [`Risc0`](https://www.risczero.com) (a zkVM) and [`Celestia`](https://www.celestia.org) a (DA layer).
The Avail project also maintains an adapter for their DA layer, which can be found [here](https://github.com/availproject/avail-sovereign-da-adapter).
[Chainway](https://chainway.xyz/) team also maintains an open-source Bitcoin DA adapter for their Sovereign Rollup on Bitcoin, which can be found [here](https://github.com/chainwayxyz/bitcoin-da).

Expand Down
2 changes: 1 addition & 1 deletion examples/const-rollup-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

In Sovereign, many state transition functions require consensus critical configuration. For example, rollups on Celestia
need to configure a namespace which they check for data. This consensus critical configuration needs to be available
to packages at compile time, so that it is baked into the binary which is fed to the ZKVM. Otherwise, a malicious
to packages at compile time, so that it is baked into the binary which is fed to the zkVM. Otherwise, a malicious
prover might be able to overwrite this configuration at runtime and create valid-looking proofs that were run
over the wrong namespace.

Expand Down
4 changes: 2 additions & 2 deletions examples/demo-rollup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ This is a demo full node running a simple Sovereign SDK rollup on [Celestia](htt

## What is This?

This demo shows how to integrate a State Transition Function (STF) with a Data Availability (DA) layer and a ZKVM to create a full
This demo shows how to integrate a State Transition Function (STF) with a Data Availability (DA) layer and a zkVM to create a full
zk-rollup. The code in this repository corresponds to running a full-node of the rollup, which executes
every transaction. If you want to see the logic for _proof generation_, check out the [demo-prover](../demo-prover/)
package instead.
Expand Down Expand Up @@ -333,7 +333,7 @@ The above setup runs Celestia node locally to avoid any external network depende

## How to Customize This Example

Any time you change out the state transition function, ZKVM, or DA layer of your rollup, you'll
Any time you change out the state transition function, zkVM, or DA layer of your rollup, you'll
need to tweak this full-node code. At the very least, you'll need to modify the dependencies. In most cases,
your full node will also need to be aware of the STF's initialization logic, and how it exposes RPC.

Expand Down
2 changes: 1 addition & 1 deletion examples/demo-rollup/src/rollup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn configure_prover<Vm: ZkvmHost, Da: DaService>(
pub enum DemoProverConfig {
/// Run the rollup verification logic inside the current process
Simulate,
/// Run the rollup verifier in a zkvm executor
/// Run the rollup verifier in a zkVM executor
Execute,
/// Run the rollup verifier and create a SNARK of execution
Prove,
Expand Down
2 changes: 1 addition & 1 deletion examples/demo-simple-stf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ In this tutorial, we’ll build an STF which checks if the input data (called a

The [State Transition Function
interface](../../rollup-interface/specs/interfaces/stf.md) serves as the core component of our rollup, where the business logic will reside.
Implementations of this trait can be integrated with any ZKVM and DA Layer resulting in a fully functional rollup. To begin, we will create a structure called `CheckHashPreimageStf`, and implement the `StateTransitionFunction` trait for it. You can find the complete code in the `lib.rs` file, but we will go over the most important parts here:
Implementations of this trait can be integrated with any zkVM and DA Layer resulting in a fully functional rollup. To begin, we will create a structure called `CheckHashPreimageStf`, and implement the `StateTransitionFunction` trait for it. You can find the complete code in the `lib.rs` file, but we will go over the most important parts here:

```rust, ignore
pub struct CheckHashPreimageStf {}
Expand Down
6 changes: 3 additions & 3 deletions examples/demo-stf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface](../../rollup-interface/specs/interfaces/stf.md) ("STF") , which speci
a related trait called `State Transition Runner` ("STR") which tells a full node how to instantiate your abstract STF on a concrete machine.

Strictly speaking, it's sufficient for a rollup to only implement the first interface. If you've done that, it's possible to integrate
with ZKVMs and DA Layers - but you'll have to customize your full node implementation a bit to deal with your particular rollup's
with zkVMs and DA Layers - but you'll have to customize your full node implementation a bit to deal with your particular rollup's
configuration. By implementing the STR trait, we make it much easier for the full-node implementation to understand how to interact
with the rollup generically - so we can keep our modifications to the node as minimal as possible. In this demo, we'll implement both traits.

Expand Down Expand Up @@ -168,7 +168,7 @@ to import your custom STF! But, when you're building an STF it's useful to stick
That way, you can minimize the changeset for your custom node implementation, which reduces the risk of bugs.

To help you integrate with full node implementations, we provide standard tools for initializing an app (`StateTransitionRunner`). In this section, we'll briefly show how to use them. Again it is not strictly
required - just by implementing STF, you get the capability to integrate with DA layers and ZKVMs. But, using these structures
required - just by implementing STF, you get the capability to integrate with DA layers and zkVMs. But, using these structures
makes you more compatible with full node implementations out of the box.

### Using State Transition Runner
Expand All @@ -184,4 +184,4 @@ The State Transition Runner struct contains logic related to initialization and

Whew, that was a lot of information. To recap, implementing your own state transition function is as simple as plugging
a Runtime, a Transaction Verifier, and some Transaction Hooks into the pre-built app template. Once you've done that,
you can integrate with any DA layer and ZKVM to create a Sovereign Rollup.
you can integrate with any DA layer and zkVM to create a Sovereign Rollup.
51 changes: 24 additions & 27 deletions examples/demo-stf/src/genesis_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ use std::convert::AsRef;
use std::path::Path;

use anyhow::Context as AnyhowContext;
#[cfg(feature = "experimental")]
use reth_primitives::Bytes;
use serde::de::DeserializeOwned;
use sov_accounts::AccountConfig;
use sov_bank::BankConfig;
use sov_chain_state::ChainStateConfig;
use sov_cli::wallet_state::PrivateKeyAndAddress;
#[cfg(feature = "experimental")]
use sov_evm::{AccountData, EvmConfig, SpecId};
use sov_evm::EvmConfig;
pub use sov_modules_api::default_context::DefaultContext;
use sov_modules_api::Context;
use sov_nft_module::NonFungibleTokenConfig;
Expand All @@ -36,23 +34,23 @@ pub const DEMO_TOKEN_NAME: &str = "sov-demo-token";
/// ```
pub fn get_genesis_config<C: Context, Da: DaSpec>(
sequencer_da_address: Da::Address,
#[cfg(feature = "experimental")] evm_genesis_addresses: Vec<reth_primitives::Address>,
#[cfg(feature = "experimental")] eth_signers: Vec<reth_primitives::Address>,
) -> GenesisConfig<C, Da> {
let token_deployer: PrivateKeyAndAddress<C> = read_private_key();

create_genesis_config(
token_deployer.address.clone(),
sequencer_da_address,
#[cfg(feature = "experimental")]
evm_genesis_addresses,
eth_signers,
)
.expect("Unable to read genesis configuration")
}

fn create_genesis_config<C: Context, Da: DaSpec>(
sequencer_address: C::Address,
sequencer_da_address: Da::Address,
#[cfg(feature = "experimental")] evm_genesis_addresses: Vec<reth_primitives::Address>,
#[cfg(feature = "experimental")] eth_signers: Vec<reth_primitives::Address>,
) -> anyhow::Result<GenesisConfig<C, Da>> {
// This path will be injected as a parameter: #872
let bank_genesis_path = "../test-data/genesis/bank.json";
Expand Down Expand Up @@ -86,6 +84,12 @@ fn create_genesis_config<C: Context, Da: DaSpec>(
let chain_state_path = "../test-data/genesis/chain_state.json";
let chain_state_config: ChainStateConfig = read_json_file(chain_state_path)?;

#[cfg(feature = "experimental")]
let evm_path = "../test-data/genesis/evm.json";

#[cfg(feature = "experimental")]
let evm_config = get_evm_config(evm_path, eth_signers)?;

Ok(GenesisConfig::new(
bank_config,
sequencer_registry_config,
Expand All @@ -94,7 +98,7 @@ fn create_genesis_config<C: Context, Da: DaSpec>(
value_setter_config,
accounts_config,
#[cfg(feature = "experimental")]
get_evm_config(evm_genesis_addresses),
evm_config,
nft_config,
))
}
Expand All @@ -110,28 +114,21 @@ fn read_json_file<T: DeserializeOwned, P: AsRef<Path>>(path: P) -> anyhow::Resul
Ok(config)
}

// TODO: #840
#[cfg(feature = "experimental")]
fn get_evm_config(genesis_addresses: Vec<reth_primitives::Address>) -> EvmConfig {
let data = genesis_addresses
.into_iter()
.map(|address| AccountData {
address,
balance: AccountData::balance(u64::MAX),
code_hash: AccountData::empty_code(),
code: Bytes::default(),
nonce: 0,
})
.collect();

EvmConfig {
data,
chain_id: 1,
limit_contract_code_size: None,
spec: vec![(0, SpecId::SHANGHAI)].into_iter().collect(),
block_timestamp_delta: 1u64,
..Default::default()
fn get_evm_config<P: AsRef<Path>>(
evm_path: P,
signers: Vec<reth_primitives::Address>,
) -> anyhow::Result<EvmConfig> {
let config: EvmConfig = read_json_file(evm_path)?;
let addresses: std::collections::HashSet<reth_primitives::Address> =
config.data.iter().map(|acc| acc.address).collect();

// check if all the eth signer are in genesis.
for signer in signers {
assert!(addresses.contains(&signer));
}

Ok(config)
}

pub fn read_private_key<C: Context>() -> PrivateKeyAndAddress<C> {
Expand Down
12 changes: 12 additions & 0 deletions examples/demo-stf/src/hooks_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ impl<C: Context, Da: DaSpec> SlotHooks<Da> for Runtime<C, Da> {
#[cfg(feature = "experimental")]
self.evm
.begin_slot_hook(slot_header.hash().into(), working_set);

self.chain_state.begin_slot_hook(
slot_header,
validity_condition,
pre_state_root,
working_set,
);
}

fn end_slot_hook(
Expand All @@ -98,6 +105,8 @@ impl<C: Context, Da: DaSpec> SlotHooks<Da> for Runtime<C, Da> {
) {
#[cfg(feature = "experimental")]
self.evm.end_slot_hook(working_set);

self.chain_state.end_slot_hook(working_set);
}
}

Expand All @@ -111,5 +120,8 @@ impl<C: Context, Da: sov_modules_api::DaSpec> FinalizeHook<Da> for Runtime<C, Da
) {
#[cfg(feature = "experimental")]
self.evm.finalize_hook(root_hash, accessory_working_set);

self.chain_state
.finalize_hook(root_hash, accessory_working_set);
}
}
24 changes: 24 additions & 0 deletions examples/test-data/genesis/evm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"data":[
{
"address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"balance":"0xffffffffffffffff",
"code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"code":"0x",
"nonce":0
}],
"chain_id":1,
"limit_contract_code_size":null,
"spec":{
"0":"SHANGHAI"
},
"coinbase":"0x0000000000000000000000000000000000000000",
"starting_base_fee":7,
"block_gas_limit":30000000,
"genesis_timestamp":0,
"block_timestamp_delta":1,
"base_fee_params":{
"max_change_denominator":8,
"elasticity_multiplier":2
}
}
26 changes: 13 additions & 13 deletions full-node/sov-stf-runner/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ where
prover: Option<Prover<V, Da, Vm>>,
}

/// Represents the possible modes of execution for a zkvm program
/// Represents the possible modes of execution for a zkVM program
pub enum ProofGenConfig<ST, Da: DaService, Vm: ZkvmHost>
where
ST: StateTransitionFunction<Vm::Guest, Da::Spec>,
{
/// The simulator runs the rollup verifier logic without even emulating the zkvm
/// The simulator runs the rollup verifier logic without even emulating the zkVM
Simulate(StateTransitionVerifier<ST, Da::Verifier, Vm::Guest>),
/// The executor runs the rollup verification logic in the zkvm, but does not actually
/// The executor runs the rollup verification logic in the zkVM, but does not actually
/// produce a zk proof
Execute,
/// The prover runs the rollup verification logic in the zkvm and produces a zk proof
/// The prover runs the rollup verification logic in the zkVM and produces a zk proof
Prover,
}

Expand Down Expand Up @@ -196,16 +196,16 @@ where
state_transition_witness: slot_result.witness,
};
vm.add_hint(transition_data);

match config {
ProofGenConfig::Simulate(verifier) => {
verifier.run_block(vm.simulate_with_hints()).map_err(|e| {
tracing::info_span!("guest_execution").in_scope(|| match config {
ProofGenConfig::Simulate(verifier) => verifier
.run_block(vm.simulate_with_hints())
.map_err(|e| {
anyhow::anyhow!("Guest execution must succeed but failed with {:?}", e)
})?;
}
ProofGenConfig::Execute => vm.run(false)?,
ProofGenConfig::Prover => vm.run(true)?,
}
})
.map(|_| ()),
ProofGenConfig::Execute => vm.run(false),
ProofGenConfig::Prover => vm.run(true),
})?;
}
let next_state_root = slot_result.state_root;

Expand Down
48 changes: 27 additions & 21 deletions module-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,25 +241,28 @@ which re-executes the transactions in a (more expensive) zk environment to creat
workflow looks roughly like this:

```rust
// First, execute transactions natively to generate a witness for the zkvm
let native_rollup_instance = my_state_transition::<DefaultContext>::new(config);
let witness = Default::default()
native_rollup_instance.begin_slot(witness);
for batch in batches.cloned() {
native_rollup_instance.apply_batch(batch);
use sov_modules_api::DefaultContext;
fn main() {
// First, execute transactions natively to generate a witness for the zkVM
let native_rollup_instance = my_state_transition::<DefaultContext>::new(config);
let witness = Default::default();
native_rollup_instance.begin_slot(witness);
for batch in batches.cloned() {
native_rollup_instance.apply_batch(batch);
}
let (_new_state_root, populated_witness) = native_rollup_instance.end_batch();

// Then, re-execute the state transitions in the zkVM using the witness
let proof = MyZkvm::prove(|| {
let zk_rollup_instance = my_state_transition::<ZkDefaultContext>::new(config);
zk_rollup_instance.begin_slot(populated_witness);
for batch in batches {
zk_rollup_instance.apply(batch);
}
let (new_state_root, _) = zk_rollup_instance.end_batch();
MyZkvm::commit(new_state_root)
});
}
let (_new_state_root, populated_witness) = native_rollup_instance.end_batch();

// Then, re-execute the state transitions in the zkvm using the witness
let proof = MyZkvm::prove(|| {
let zk_rollup_instance = my_state_transition::<ZkDefaultContext>::new(config);
zk_rollup_instance.begin_slot(populated_witness);
for batch in batches {
zk_rollup_instance.apply(batch);
}
let (new_state_root, _) = zk_rollup_instance.end_batch();
MyZkvm::commit(new_state_root)
})
```

This distinction between native _execution_ and zero-knowledge _re-execution_ is deeply baked into the Module System. We take the
Expand All @@ -272,16 +275,19 @@ The most important trait we use to enable this abstraction is the `Spec` trait.

```rust
pub trait Spec {
type Address;
type Storage;
type PrivateKey;
type PublicKey;
type Hasher;
type Signature;
type Witness;
}
```

As you can see, a `Spec` for a rollup specifies the concrete types that will be used for many kinds of cryptographic operations.
That way, you can define your business logic in terms of _abstract_ cryptography, and then instantiate it with cryptography which
is efficient in your particular choice of ZKVM.
That way, you can define your business logic in terms of _abstract_ cryptography, and then instantiate it with cryptography, which
is efficient in your particular choice of zkVM.

In addition to the `Spec` trait, the Module System provides a simple `Context` trait which is defined like this:

Expand Down Expand Up @@ -318,7 +324,7 @@ Similarly, since each of the banks helper functions is automatically generic ove
can abstract away the distinctions between `zk` and `native` execution. For example, when a rollup is running in native mode
its `Storage` type will almost certainly be [`ProverStorage`](./sov-state/src/prover_storage.rs), which holds its data in a
Merkle tree backed by RocksDB. But if you're running in zk mode the `Storage` type will instead be [`ZkStorage`](./sov-state/src/zk_storage.rs), which reads
its data from a set of "hints" provided by the prover. Because all of the rollups modules are generic, none of them need to worry
its data from a set of "hints" provided by the prover. Because all the rollups modules are generic, none of them need to worry
about this distinction.

For more information on `Context` and `Spec`, and to see some example implementations, check out the [`sov_modules_api`](./sov-modules-api/) docs.
4 changes: 2 additions & 2 deletions module-system/module-implementations/sov-evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ mod experimental {
pub(crate) const MIN_CREATE_GAS: u64 = 53_000u64;

/// Evm account.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub struct AccountData {
/// Account address.
pub address: Address,
Expand All @@ -79,7 +79,7 @@ mod experimental {
}

/// Genesis configuration.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub struct EvmConfig {
/// Genesis accounts.
pub data: Vec<AccountData>,
Expand Down
Loading

0 comments on commit f14b721

Please sign in to comment.