Skip to content

Commit

Permalink
feat: validate contract config (#176)
Browse files Browse the repository at this point in the history
* validate config

* refactor

* refactor

* use right sol

* add

* feat: code

* add
  • Loading branch information
ratankaliani authored Oct 21, 2024
1 parent 8eb227b commit bc3bdb1
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 91 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions proposer/op/proposer/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ func (l *L2OutputSubmitter) StartL2OutputSubmitting() error {
}
}

// Validate the contract's configuration of the aggregation and range verification keys as well
// as the rollup config hash.
err = l.ValidateConfig(l.Cfg.L2OutputOracleAddr.Hex())
if err != nil {
return fmt.Errorf("failed to validate config: %w", err)
}

l.wg.Add(1)
go l.loop()

Expand Down
94 changes: 72 additions & 22 deletions proposer/op/proposer/prove.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
Expand Down Expand Up @@ -149,7 +150,7 @@ func (l *L2OutputSubmitter) RequestQueuedProofs(ctx context.Context) error {
}

// The total number of concurrent proofs is capped at MAX_CONCURRENT_PROOF_REQUESTS.
if (witnessGenProofs+provingProofs) >= int(l.Cfg.MaxConcurrentProofRequests) {
if (witnessGenProofs + provingProofs) >= int(l.Cfg.MaxConcurrentProofRequests) {
l.Log.Info("max concurrent proof requests reached, waiting for next cycle")
return nil
}
Expand Down Expand Up @@ -244,19 +245,6 @@ func (l *L2OutputSubmitter) RequestOPSuccinctProof(p ent.ProofRequest) error {
return nil
}

type SpanProofRequest struct {
Start uint64 `json:"start"`
End uint64 `json:"end"`
}

type AggProofRequest struct {
Subproofs [][]byte `json:"subproofs"`
L1Head string `json:"head"`
}
type ProofResponse struct {
ProofID string `json:"proof_id"`
}

// Request a span proof for the range [l2Start, l2End].
func (l *L2OutputSubmitter) RequestSpanProof(l2Start, l2End uint64) (string, error) {
if l2Start >= l2End {
Expand All @@ -273,7 +261,7 @@ func (l *L2OutputSubmitter) RequestSpanProof(l2Start, l2End uint64) (string, err
return "", fmt.Errorf("failed to marshal request body: %w", err)
}

return l.RequestProofFromServer("request_span_proof", jsonBody)
return l.RequestProofFromServer(proofrequest.TypeSPAN, jsonBody)
}

// Request an aggregate proof for the range [start, end]. If there is not a consecutive set of span proofs,
Expand All @@ -296,12 +284,18 @@ func (l *L2OutputSubmitter) RequestAggProof(start, end uint64, l1BlockHash strin
}

// Request the agg proof from the server.
return l.RequestProofFromServer("request_agg_proof", jsonBody)
return l.RequestProofFromServer(proofrequest.TypeAGG, jsonBody)
}

// Request a proof from the OP Succinct server, given the path and the body of the request. Returns
// the proof ID on a successful request.
func (l *L2OutputSubmitter) RequestProofFromServer(urlPath string, jsonBody []byte) (string, error) {
func (l *L2OutputSubmitter) RequestProofFromServer(proofType proofrequest.Type, jsonBody []byte) (string, error) {
var urlPath string
if proofType == proofrequest.TypeAGG {
urlPath = "request_agg_proof"
} else if proofType == proofrequest.TypeSPAN {
urlPath = "request_span_proof"
}
req, err := http.NewRequest("POST", l.Cfg.OPSuccinctServerUrl+"/"+urlPath, bytes.NewBuffer(jsonBody))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
Expand Down Expand Up @@ -341,11 +335,6 @@ func (l *L2OutputSubmitter) RequestProofFromServer(urlPath string, jsonBody []by
return response.ProofID, nil
}

type ProofStatus struct {
Status string `json:"status"`
Proof []byte `json:"proof"`
}

// Get the status of a proof given its ID.
func (l *L2OutputSubmitter) GetProofStatus(proofId string) (string, []byte, error) {
req, err := http.NewRequest("GET", l.Cfg.OPSuccinctServerUrl+"/status/"+proofId, nil)
Expand Down Expand Up @@ -382,3 +371,64 @@ func (l *L2OutputSubmitter) GetProofStatus(proofId string) (string, []byte, erro

return response.Status, response.Proof, nil
}

// Validate the contract's configuration of the aggregation and range verification keys as well
// as the rollup config hash.
func (l *L2OutputSubmitter) ValidateConfig(address string) error {
l.Log.Info("requesting config validation", "address", address)
requestBody := ValidateConfigRequest{
Address: address,
}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("failed to marshal request body: %w", err)
}

req, err := http.NewRequest("POST", l.Cfg.OPSuccinctServerUrl+"/validate_config", bytes.NewBuffer(jsonBody))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}

client := &http.Client{
Timeout: PROOF_STATUS_TIMEOUT,
}
resp, err := client.Do(req)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
return fmt.Errorf("request timed out after %s: %w", PROOF_STATUS_TIMEOUT, err)
}
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()

// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error reading the response body: %v", err)
}

// Create a variable of the ValidateConfigResponse type
var response ValidateConfigResponse

// Unmarshal the JSON into the response variable
err = json.Unmarshal(body, &response)
if err != nil {
return fmt.Errorf("error decoding JSON response: %v", err)
}

var invalidConfigs []string
if !response.RollupConfigHashValid {
invalidConfigs = append(invalidConfigs, "rollup config hash")
}
if !response.AggVkeyValid {
invalidConfigs = append(invalidConfigs, "aggregation verification key")
}
if !response.RangeVkeyValid {
invalidConfigs = append(invalidConfigs, "range verification key")
}
if len(invalidConfigs) > 0 {
return fmt.Errorf("config is invalid: %s", strings.Join(invalidConfigs, ", "))
}

return nil
}
30 changes: 30 additions & 0 deletions proposer/op/proposer/rpc_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package proposer

type SpanProofRequest struct {
Start uint64 `json:"start"`
End uint64 `json:"end"`
}

type AggProofRequest struct {
Subproofs [][]byte `json:"subproofs"`
L1Head string `json:"head"`
}

type ValidateConfigRequest struct {
Address string `json:"address"`
}

type ValidateConfigResponse struct {
RollupConfigHashValid bool `json:"rollup_config_hash_valid"`
AggVkeyValid bool `json:"agg_vkey_valid"`
RangeVkeyValid bool `json:"range_vkey_valid"`
}

type ProofResponse struct {
ProofID string `json:"proof_id"`
}

type ProofStatus struct {
Status string `json:"status"`
Proof []byte `json:"proof"`
}
1 change: 1 addition & 0 deletions proposer/succinct/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ path = "bin/server.rs"
# workspace
tokio = { workspace = true }
alloy-primitives = { workspace = true }
alloy = { workspace = true }

# local
op-succinct-host-utils.workspace = true
Expand Down
119 changes: 68 additions & 51 deletions proposer/succinct/bin/server.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,39 @@
use alloy_primitives::hex;
use alloy_primitives::{hex, Address, B256};
use axum::{
extract::{DefaultBodyLimit, Path},
extract::{DefaultBodyLimit, Path, State},
http::StatusCode,
response::{IntoResponse, Response},
routing::{get, post},
Json, Router,
};
use base64::{engine::general_purpose, Engine as _};
use log::info;
use op_succinct_client_utils::boot::BootInfoStruct;
use op_succinct_client_utils::{
boot::{hash_rollup_config, BootInfoStruct},
types::u32_to_u8,
};
use op_succinct_host_utils::{
fetcher::{CacheMode, OPSuccinctDataFetcher},
get_agg_proof_stdin, get_proof_stdin,
witnessgen::WitnessGenExecutor,
ProgramType,
L2OutputOracle, ProgramType,
};
use op_succinct_proposer::{
AggProofRequest, ContractConfig, ProofResponse, ProofStatus, SpanProofRequest,
ValidateConfigRequest, ValidateConfigResponse,
};
use serde::{Deserialize, Deserializer, Serialize};
use sp1_sdk::{
network::{
client::NetworkClient,
proto::network::{ProofMode, ProofStatus as SP1ProofStatus},
},
utils, NetworkProverV1, Prover, SP1Proof, SP1ProofWithPublicValues,
utils, HashableKey, NetworkProverV1, ProverClient, SP1Proof, SP1ProofWithPublicValues,
};
use std::{env, time::Duration};
use std::{env, str::FromStr, time::Duration};
use tower_http::limit::RequestBodyLimitLayer;

pub const MULTI_BLOCK_ELF: &[u8] = include_bytes!("../../../elf/range-elf");
pub const AGG_ELF: &[u8] = include_bytes!("../../../elf/aggregation-elf");

#[derive(Deserialize, Serialize, Debug)]
struct SpanProofRequest {
start: u64,
end: u64,
}

#[derive(Deserialize, Serialize, Debug)]
struct AggProofRequest {
#[serde(deserialize_with = "deserialize_base64_vec")]
subproofs: Vec<Vec<u8>>,
head: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct ProofResponse {
proof_id: String,
}

#[derive(Serialize)]
struct ProofStatus {
status: String,
proof: Vec<u8>,
}

#[tokio::main]
async fn main() {
utils::setup_logger();
Expand All @@ -61,12 +42,35 @@ async fn main() {

env::set_var("SKIP_SIMULATION", "true");

let prover = ProverClient::new();
let (_, agg_vk) = prover.setup(AGG_ELF);
let (_, range_vk) = prover.setup(MULTI_BLOCK_ELF);
let multi_block_vkey_u8 = u32_to_u8(range_vk.vk.hash_u32());
let range_vkey_commitment = B256::from(multi_block_vkey_u8);
let agg_vkey_hash = B256::from_str(&agg_vk.bytes32()).unwrap();

let fetcher = OPSuccinctDataFetcher::default();
// Note: The rollup config hash never changes for a given chain, so we can just hash it once at
// server start-up. The only time a rollup config changes is typically when a new version of the
// [`RollupConfig`] is released from `op-alloy`.
let rollup_config_hash = hash_rollup_config(&fetcher.rollup_config);

// Initialize global hashes.
let global_hashes = ContractConfig {
agg_vkey_hash,
range_vkey_commitment,
rollup_config_hash,
range_vk,
};

let app = Router::new()
.route("/request_span_proof", post(request_span_proof))
.route("/request_agg_proof", post(request_agg_proof))
.route("/status/:proof_id", get(get_proof_status))
.route("/validate_config", post(validate_config))
.layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(102400 * 1024 * 1024));
.layer(RequestBodyLimitLayer::new(102400 * 1024 * 1024))
.with_state(global_hashes);

let port = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
Expand All @@ -77,6 +81,34 @@ async fn main() {
axum::serve(listener, app).await.unwrap();
}

/// Validate the configuration of the L2 Output Oracle.
async fn validate_config(
State(state): State<ContractConfig>,
Json(payload): Json<ValidateConfigRequest>,
) -> Result<(StatusCode, Json<ValidateConfigResponse>), AppError> {
let fetcher = OPSuccinctDataFetcher::default();

let address = Address::from_str(&payload.address).unwrap();
let l2_output_oracle = L2OutputOracle::new(address, fetcher.l1_provider);

let agg_vkey = l2_output_oracle.aggregationVkey().call().await?;
let range_vkey = l2_output_oracle.rangeVkeyCommitment().call().await?;
let rollup_config_hash = l2_output_oracle.rollupConfigHash().call().await?;

let agg_vkey_valid = agg_vkey.aggregationVkey == state.agg_vkey_hash;
let range_vkey_valid = range_vkey.rangeVkeyCommitment == state.range_vkey_commitment;
let rollup_config_hash_valid = rollup_config_hash.rollupConfigHash == state.rollup_config_hash;

Ok((
StatusCode::OK,
Json(ValidateConfigResponse {
rollup_config_hash_valid,
agg_vkey_valid,
range_vkey_valid,
}),
))
}

/// Request a proof for a span of blocks.
async fn request_span_proof(
Json(payload): Json<SpanProofRequest>,
Expand Down Expand Up @@ -129,6 +161,7 @@ async fn request_span_proof(

/// Request an aggregation proof for a set of subproofs.
async fn request_agg_proof(
State(state): State<ContractConfig>,
Json(payload): Json<AggProofRequest>,
) -> Result<(StatusCode, Json<ProofResponse>), AppError> {
info!("Received agg proof request");
Expand Down Expand Up @@ -162,9 +195,9 @@ async fn request_agg_proof(
.await?;

let prover = NetworkProverV1::new();
let (_, vkey) = prover.setup(MULTI_BLOCK_ELF);

let stdin = get_agg_proof_stdin(proofs, boot_infos, headers, &vkey, l1_head.into()).unwrap();
let stdin =
get_agg_proof_stdin(proofs, boot_infos, headers, &state.range_vk, l1_head.into()).unwrap();

// Set simulation to true on aggregation proofs as they're relatively small.
env::set_var("SKIP_SIMULATION", "false");
Expand Down Expand Up @@ -260,19 +293,3 @@ where
Self(err.into())
}
}

/// Deserialize a vector of base64 strings into a vector of vectors of bytes. Go serializes
/// the subproofs as base64 strings.
fn deserialize_base64_vec<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let s: Vec<String> = Deserialize::deserialize(deserializer)?;
s.into_iter()
.map(|base64_str| {
general_purpose::STANDARD
.decode(base64_str)
.map_err(serde::de::Error::custom)
})
.collect()
}
Loading

0 comments on commit bc3bdb1

Please sign in to comment.