Skip to content

Commit

Permalink
Merge pull request #142 from HerodotusDev/chore/remove_feeder_dep
Browse files Browse the repository at this point in the history
Remove dependency on feeder gateway for dry run
  • Loading branch information
Okm165 authored Feb 10, 2025
2 parents e07c595 + 637ed28 commit 129414a
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 106 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/rust-vm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ jobs:
- name: Run Nextest Tests
env:
RPC_URL_ETHEREUM: ${{ secrets.RPC_URL_ETHEREUM_SEPOLIA }}
RPC_URL_FEEDER_GATEWAY: ${{ secrets.RPC_URL_FEEDER_GATEWAY }}
RPC_URL_HERODOTUS_INDEXER: ${{ secrets.RPC_URL_HERODOTUS_INDEXER }}
RPC_URL_HERODOTUS_INDEXER_GROWER: ${{ secrets.RPC_URL_HERODOTUS_INDEXER_GROWER }}
RPC_URL_HERODOTUS_INDEXER_STAGING: ${{ secrets.RPC_URL_HERODOTUS_INDEXER_STAGING }}
RPC_URL_STARKNET: ${{ secrets.RPC_URL_STARKNET_SEPOLIA }}
HERODOTUS_STAGING_INDEXER: ${{ secrets.HERODOTUS_STAGING_INDEXER }}
run: cargo nextest run

3 changes: 2 additions & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/dry_hint_processor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ cairo-lang-casm.workspace = true
cairo-lang-starknet-classes.workspace = true
cairo-vm.workspace = true
hints.workspace = true
reqwest.workspace = true
indexer.workspace = true
serde.workspace = true
starknet-types-core.workspace = true
starknet.workspace = true
Expand Down
53 changes: 13 additions & 40 deletions crates/dry_hint_processor/src/syscall_handler/starknet/header.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
use std::env;

use cairo_vm::{types::relocatable::Relocatable, vm::vm_core::VirtualMachine, Felt252};
use reqwest::Url;
use indexer::{models::blocks, Indexer};
use syscall_handler::{traits::CallHandler, SyscallExecutionError, SyscallResult};
use types::{
cairo::{
starknet::header::{Block, FunctionId, StarknetBlock},
starknet::header::{FunctionId, StarknetBlock},
structs::CairoFelt,
traits::CairoType,
},
keys::starknet::header::{CairoKey, Key},
RPC_URL_FEEDER_GATEWAY, STARKNET_MAINNET_CHAIN_ID, STARKNET_TESTNET_CHAIN_ID,
};

#[derive(Debug, Default)]
Expand Down Expand Up @@ -41,43 +38,19 @@ impl CallHandler for HeaderCallHandler {
}

async fn handle(&mut self, key: Self::Key, function_id: Self::Id, _vm: &VirtualMachine) -> SyscallResult<Self::CallHandlerResult> {
let base_url = Url::parse(&env::var(RPC_URL_FEEDER_GATEWAY).unwrap()).unwrap();

// Feeder Gateway rejects the requests if this header is not set
let host_header = match key.chain_id {
STARKNET_MAINNET_CHAIN_ID => "alpha-mainnet.starknet.io",
STARKNET_TESTNET_CHAIN_ID => "alpha-sepolia.starknet.io",
_ => {
return Err(SyscallExecutionError::InternalError(
format!("Unknown chain id: {}", key.chain_id).into(),
))
}
};

let request = reqwest::Client::new()
.get(format!("{}get_block", base_url))
.header("Host", host_header)
.query(&[("blockNumber", key.block_number.to_string())]);

let response = request
.send()
let provider = Indexer::default();

// Fetch proof response
let response = provider
.get_blocks(blocks::IndexerQuery::new(
key.chain_id,
key.block_number.into(),
key.block_number.into(),
))
.await
.map_err(|e| SyscallExecutionError::InternalError(format!("Network request failed: {}", e).into()))?;

let block_data: Block = match response.status().is_success() {
true => response.json().await,
false => {
let status = response.status();
let error_body = response.text().await.unwrap_or_default();
return Err(SyscallExecutionError::InternalError(
format!("Request failed ({}): {}", status, error_body).into(),
));
}
}
.map_err(|e| SyscallExecutionError::InternalError(format!("Failed to parse block data: {}", e).into()))?;

let sn_block: StarknetBlock = block_data.into();

Ok(sn_block.handle(function_id))
// Create block and handle function
Ok(StarknetBlock::from_hash_fields(response.fields).handle(function_id))
}
}
9 changes: 3 additions & 6 deletions crates/fetcher/src/proof_keys/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use alloy::{hex::FromHexError, primitives::Bytes};
use indexer::{
models::{IndexerQuery, MMRProof},
Indexer,
};
use indexer::{models::accumulators, Indexer};
use types::proofs::mmr::MmrMeta;

use crate::FetcherError;
Expand All @@ -28,12 +25,12 @@ impl ProofKeys {
format!("{:0>width$}", hex_str, width = (hex_str.len() + 1) / 2 * 2)
}

pub async fn fetch_mmr_proof(chain_id: u128, block_number: u64) -> Result<(MMRProof, MmrMeta), FetcherError> {
pub async fn fetch_mmr_proof(chain_id: u128, block_number: u64) -> Result<(accumulators::MMRProof, MmrMeta), FetcherError> {
let provider = Indexer::default();

// Fetch proof response
let response = provider
.get_headers_proof(IndexerQuery::new(chain_id, block_number, block_number))
.get_headers_proof(accumulators::IndexerQuery::new(chain_id, block_number, block_number))
.await?;

// Extract MMR metadata
Expand Down
1 change: 1 addition & 0 deletions crates/indexer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
alloy.workspace = true
cairo-vm.workspace = true
reqwest.workspace = true
serde_json.workspace = true
serde_with.workspace = true
Expand Down
57 changes: 45 additions & 12 deletions crates/indexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ pub mod models;

use std::env;

use models::{IndexerError, IndexerHeadersProofResponse, IndexerQuery, MMRResponse};
use reqwest::Client;
use types::RPC_URL_HERODOTUS_INDEXER;
use cairo_vm::Felt252;
use models::{accumulators, blocks, IndexerError};
use reqwest::{Client, Url};
use types::{RPC_URL_HERODOTUS_INDEXER_GROWER, RPC_URL_HERODOTUS_INDEXER_STAGING};

#[derive(Clone)]
pub struct Indexer {
Expand All @@ -28,20 +29,20 @@ impl Indexer {
}

/// Fetch MMR and headers proof from Herodotus Indexer
pub async fn get_headers_proof(&self, query: IndexerQuery) -> Result<IndexerHeadersProofResponse, IndexerError> {
pub async fn get_headers_proof(&self, query: accumulators::IndexerQuery) -> Result<accumulators::IndexerProofResponse, IndexerError> {
// Parse base URL from environment variable
let base_url = Url::parse(&env::var(RPC_URL_HERODOTUS_INDEXER_GROWER).unwrap()).unwrap();

let response = self
.client
.get(
env::var(RPC_URL_HERODOTUS_INDEXER)
.map_err(|e| IndexerError::GetHeadersProofError(format!("Missing RPC_URL_HERODOTUS_INDEXER env var: {}", e)))?,
)
.get(base_url.join("/accumulators/proofs").unwrap())
.query(&query)
.send()
.await
.map_err(IndexerError::ReqwestError)?;

if response.status().is_success() {
let parsed_mmr: MMRResponse =
let parsed_mmr: accumulators::MMRResponse =
serde_json::from_value(response.json().await.map_err(IndexerError::ReqwestError)?).map_err(IndexerError::SerdeJsonError)?;

if parsed_mmr.data.is_empty() {
Expand All @@ -55,7 +56,7 @@ impl Indexer {
"Indexer didn't return the correct number of headers that were requested".to_string(),
))
} else {
Ok(IndexerHeadersProofResponse::new(mmr_data))
Ok(accumulators::IndexerProofResponse::new(mmr_data))
}
}
} else {
Expand All @@ -64,6 +65,38 @@ impl Indexer {
))
}
}

/// Fetch MMR and headers proof from Herodotus Indexer
pub async fn get_blocks(&self, query: blocks::IndexerQuery) -> Result<blocks::IndexerBlockResponse, IndexerError> {
// Parse base URL from environment variable
let base_url = Url::parse(&env::var(RPC_URL_HERODOTUS_INDEXER_STAGING).unwrap()).unwrap();

let response = self
.client
.get(base_url.join("/blocks").unwrap())
.query(&query)
.send()
.await
.map_err(IndexerError::ReqwestError)?;

if response.status().is_success() {
let parsed_mmr: blocks::BlocksResponse =
serde_json::from_value(response.json().await.map_err(IndexerError::ReqwestError)?).map_err(IndexerError::SerdeJsonError)?;

let block = parsed_mmr.data.first().unwrap();

match &block.block_header {
models::BlockHeader::Fields(fields) => Ok(blocks::IndexerBlockResponse {
fields: fields.iter().map(|hex| Felt252::from_hex(hex).unwrap()).collect::<Vec<_>>(),
}),
_ => Err(IndexerError::ValidationError("Invalid block header return type".to_string())),
}
} else {
Err(IndexerError::GetBlocksProofError(
response.text().await.map_err(IndexerError::ReqwestError)?,
))
}
}
}

#[cfg(test)]
Expand All @@ -73,7 +106,7 @@ mod tests {
#[tokio::test]
async fn test_get_headers_proof() {
let response = Indexer::default()
.get_headers_proof(IndexerQuery::new(11155111, 5000000, 5000000))
.get_headers_proof(accumulators::IndexerQuery::new(11155111, 5000000, 5000000))
.await
.unwrap();
assert_eq!(response.headers.len(), 1);
Expand All @@ -82,7 +115,7 @@ mod tests {
#[tokio::test]
async fn test_get_headers_proof_multiple_blocks() {
let response = Indexer::default()
.get_headers_proof(IndexerQuery::new(11155111, 5800000, 5800010))
.get_headers_proof(accumulators::IndexerQuery::new(11155111, 5800000, 5800010))
.await
.unwrap();
assert_eq!(response.headers.len(), 11);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,8 @@ use std::collections::HashMap;
use alloy::primitives::BlockNumber;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use thiserror::Error;

/// Enum for available hashing functions
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum HashingFunction {
Keccak,
Poseidon,
Pedersen,
}
use super::{BlockHeader, HashingFunction};

/// Enum for available contract types
#[derive(Debug, Serialize)]
Expand All @@ -23,30 +15,6 @@ pub enum ContractType {
Remapper,
}

/// Error from [`Indexer`]
#[derive(Error, Debug)]
pub enum IndexerError {
/// The block range provided is invalid.
#[error("Invalid block range")]
InvalidBlockRange,

/// Failed to send a request using [`reqwest`].
#[error("Failed to send request")]
ReqwestError(#[from] reqwest::Error),

/// Failed to parse the response using [`serde_json`].
#[error("Failed to parse response")]
SerdeJsonError(#[from] serde_json::Error),

/// Validation error with a detailed message.
#[error("Validation error: {0}")]
ValidationError(String),

/// Failed to get headers proof with a detailed message.
#[error("Failed to get headers proof: {0}")]
GetHeadersProofError(String),
}

/// Query parameters for the indexer
#[derive(Debug, Serialize)]
pub struct IndexerQuery {
Expand Down Expand Up @@ -112,20 +80,13 @@ pub struct MMRProof {
pub siblings_hashes: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum BlockHeader {
RlpString(String),
RlpLittleEndian8ByteChunks(Vec<String>),
Fields(Vec<String>),
}

#[derive(Debug)]
pub struct IndexerHeadersProofResponse {
pub struct IndexerProofResponse {
pub mmr_meta: MMRMetadata,
pub headers: HashMap<BlockNumber, MMRProof>,
}

impl IndexerHeadersProofResponse {
impl IndexerProofResponse {
pub fn new(mmr_data: MMRData) -> Self {
let mmr_meta = mmr_data.meta;
let headers = mmr_data.proofs.into_iter().map(|block| (block.block_number, block)).collect();
Expand Down
55 changes: 55 additions & 0 deletions crates/indexer/src/models/blocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use cairo_vm::Felt252;
use serde::{Deserialize, Serialize};

use super::{BlockHeader, HashingFunction};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Sorting {
Ascending,
Descending,
}

/// Query parameters for the indexer
#[derive(Debug, Serialize)]
pub struct IndexerQuery {
pub chain_id: u128,
pub hashing_function: HashingFunction,
pub from_block_number_inclusive: u128,
pub to_block_number_inclusive: u128,
pub sort: Sorting,
}

impl IndexerQuery {
pub fn new(chain_id: u128, from_block: u128, to_block: u128) -> Self {
Self {
chain_id,
hashing_function: HashingFunction::Poseidon,
from_block_number_inclusive: from_block,
to_block_number_inclusive: to_block,
sort: Sorting::Ascending,
}
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Block {
pub block_number: u64,
pub block_header: BlockHeader,
pub block_hash: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BlocksResponse {
pub data: Vec<Block>,
}

#[derive(Debug)]
pub struct IndexerBlockResponse {
pub fields: Vec<Felt252>,
}

impl IndexerBlockResponse {
pub fn new(fields: Vec<Felt252>) -> Self {
Self { fields }
}
}
Loading

0 comments on commit 129414a

Please sign in to comment.