diff --git a/.dockerignore b/.dockerignore index 659e2f5..4a6e0c1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ **target/ **direnv/ +**vendor/ diff --git a/Cargo.lock b/Cargo.lock index d6d600d..f58f15e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2727,7 +2727,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" dependencies = [ "ahash 0.8.11", - "serde", ] [[package]] @@ -3858,7 +3857,7 @@ dependencies = [ "near-light-client-rpc", "near-primitives", "pretty_env_logger", - "rand 0.7.3", + "rand 0.8.5", "serde", "sled", "tokio", @@ -3890,7 +3889,7 @@ dependencies = [ "near-primitives", "near-primitives-core", "pretty_env_logger", - "rand 0.7.3", + "rand 0.8.5", "serde", "serde_json", "test-utils", @@ -3913,7 +3912,7 @@ dependencies = [ "near-primitives", "near-primitives-core", "pretty_env_logger", - "rand 0.7.3", + "rand 0.8.5", "serde", "serde_json", "tokio", @@ -4123,7 +4122,7 @@ dependencies = [ "anyhow", "config", "futures", - "hashbrown 0.13.1", + "hashbrown 0.14.3", "hex", "http-cache-reqwest", "jsonrpsee", diff --git a/Dockerfile b/Dockerfile.offchain similarity index 100% rename from Dockerfile rename to Dockerfile.offchain diff --git a/Dockerfile.operator b/Dockerfile.operator new file mode 100644 index 0000000..9de76f6 --- /dev/null +++ b/Dockerfile.operator @@ -0,0 +1,39 @@ +FROM --platform=$BUILDPLATFORM rust:1.72.0-bookworm as build +ARG TARGETARCH + +WORKDIR /near + +COPY rust-toolchain.toml ./rust-toolchain.toml +COPY Cargo.toml Cargo.lock ./ +COPY nearx/contract near/contract + +RUN rustup show +RUN apt-get update && apt-get install -y \ + git \ + jq \ + make \ + bash \ + openssl \ + libssl-dev \ + protobuf-compiler \ + pkg-config \ + cbindgen + +RUN cargo new --bin bin/dummy +RUN cargo new --lib crates/dummylib +RUN cargo new --lib nearx +RUN cargo build --release --bin dummy + +COPY ./ ./ +RUN cargo build --release --bin nearx-operator +RUN ldd target/release/nearx-operator +RUN cp target/release/nearx-operator /near/near-light-client + +FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y openssl libssl-dev pkg-config ca-certificates && rm -rf /var/lib/apt/lists/* +COPY --from=build /near/target/release/nearx-operator /usr/local/bin +COPY --from=build /near/default.toml /var/near-light-client/default.toml + +ENV NEAR_LIGHT_CLIENT_DIR=/var/near-light-client/ + +ENTRYPOINT ["/usr/local/bin/nearx-operator"] diff --git a/bin/client/src/main.rs b/bin/client/src/main.rs index 8ee4024..9242093 100644 --- a/bin/client/src/main.rs +++ b/bin/client/src/main.rs @@ -1,4 +1,5 @@ use coerce::actor::{system::ActorSystem, IntoActor}; +use config::Config; use near_light_client_primitives::config::BaseConfig; use crate::client::{actor::Shutdown, LightClient}; @@ -13,7 +14,7 @@ pub struct ShutdownMsg; async fn main() -> anyhow::Result<()> { pretty_env_logger::init(); - let config = config::Config::default(); + let config = Config::new(std::env::var("NEAR_LIGHT_CLIENT_DIR").ok().as_deref())?; let system = ActorSystem::builder() .system_name("near-light-client") .build(); diff --git a/bin/operator/src/config.rs b/bin/operator/src/config.rs index 4b1c4ca..5148b9a 100644 --- a/bin/operator/src/config.rs +++ b/bin/operator/src/config.rs @@ -8,6 +8,7 @@ pub struct Config { pub rpc: near_light_client_rpc::Config, pub protocol: near_light_client_protocol::config::Config, pub succinct: crate::succinct::Config, + pub engine: crate::engine::Config, } impl Configurable for Config {} diff --git a/bin/operator/src/engine/mod.rs b/bin/operator/src/engine/mod.rs index 09b5833..07d6d69 100644 --- a/bin/operator/src/engine/mod.rs +++ b/bin/operator/src/engine/mod.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use futures::FutureExt; use hashbrown::{hash_map::DefaultHashBuilder, HashMap}; use near_light_client_rpc::{prelude::Itertools, TransactionOrReceiptId}; -use near_light_clientx::VERIFY_AMT; +use near_light_clientx::config::bps_from_network; use priority_queue::PriorityQueue; use serde::{Deserialize, Serialize}; pub use types::RegistryInfo; @@ -19,22 +19,43 @@ use crate::succinct::{ mod types; -// TODO: decide if we can try to identity hash based on ids, they're already -// hashed -// Collision <> receipt & tx? +// TODO[Optimisation]: decide if we can try to identity hash based on ids, +// they're already hashed, perhaps a collision would be if receipt_id ++ tx_id +// are the same, unlikely type Queue = PriorityQueue; +#[derive(Debug, Deserialize, Clone)] +#[serde(default)] +pub struct Config { + drain_interval: u64, + sync_interval: u64, + cleanup_interval: u64, + persist_interval: u64, +} + +impl Default for Config { + fn default() -> Self { + Config { + drain_interval: 1, + sync_interval: 60 * 30, + cleanup_interval: 60, + persist_interval: 30, + } + } +} + pub struct Engine { registry: HashMap, succinct_client: Arc, - // TODO: persist me proving_queue: Queue, batches: HashMap>, request_info: HashMap>, + config: Config, + verify_amt: usize, } impl Engine { - pub fn new(succinct_client: Arc) -> Self { + pub fn new(config: &super::Config, succinct_client: Arc) -> Self { log::info!("starting queue manager"); let state = PersistedState::try_from("state.json"); @@ -54,6 +75,8 @@ impl Engine { .map(|s| s.batches.clone()) .unwrap_or_default(), request_info: state.map(|s| s.request_info).unwrap_or_default(), + config: config.engine.clone(), + verify_amt: bps_from_network(&config.rpc.network), } } @@ -70,24 +93,23 @@ impl Engine { } else { 1 }; - log::debug!("adding to {:?} with weight: {weight}", tx); + log::debug!("enqueuing {:?} with weight: {weight}", tx); self.proving_queue.push(tx.into(), weight); Ok(()) } fn make_batch(&mut self) -> Option<(u32, Vec)> { - if self.proving_queue.len() >= VERIFY_AMT { - let id = self.batches.len() as u32; - let mut txs = vec![]; - for _ in 0..VERIFY_AMT { - let (req, _) = self.proving_queue.pop().unwrap(); - txs.push(req.0); - } - self.batches.insert(id, None); - Some((id, txs)) - } else { - None + if self.proving_queue.len() < self.verify_amt { + return None; + } + let id = self.batches.len() as u32; + let mut txs = vec![]; + for _ in 0..self.verify_amt { + let (req, _) = self.proving_queue.pop()?; + txs.push(req.0); } + self.batches.insert(id, None); + Some((id, txs)) } } @@ -95,18 +117,20 @@ impl Actor for Engine { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { - ctx.run_interval(Duration::from_secs(1), |_, ctx| { + ctx.run_interval(Duration::from_secs(self.config.drain_interval), |_, ctx| { ctx.address().do_send(Drain) }); - ctx.run_interval(Duration::from_secs(60 * 30), |_, ctx| { + ctx.run_interval(Duration::from_secs(self.config.sync_interval), |_, ctx| { ctx.address().do_send(Sync) }); - ctx.run_interval(Duration::from_secs(60), |_, ctx| { - ctx.address().do_send(Cleanup) - }); - ctx.run_interval(Duration::from_secs(60), |_, ctx| { - ctx.address().do_send(Persist) - }); + ctx.run_interval( + Duration::from_secs(self.config.cleanup_interval), + |_, ctx| ctx.address().do_send(Cleanup), + ); + ctx.run_interval( + Duration::from_secs(self.config.persist_interval), + |_, ctx| ctx.address().do_send(Persist), + ); } } @@ -311,6 +335,8 @@ mod tests { use super::*; use crate::succinct::tests::mocks; + const VERIFY_AMT: usize = 64; + async fn manager() -> Engine { let client = mocks().await; Engine::new(Arc::new(client)) diff --git a/bin/operator/src/engine/types.rs b/bin/operator/src/engine/types.rs index d5ea281..31b9d9a 100644 --- a/bin/operator/src/engine/types.rs +++ b/bin/operator/src/engine/types.rs @@ -65,3 +65,51 @@ pub struct RegistryInfo { // Their weight in the shared queue pub weight: PriorityWeight, } + +#[cfg(test)] +mod tests { + + use std::str::FromStr; + + use near_light_client_protocol::near_account_id::AccountId; + use test_utils::CryptoHash; + + use super::*; + + #[test] + fn test_transaction_or_receipt_id_eq() { + let transaction1 = TransactionOrReceiptIdNewtype(TransactionOrReceiptId::Transaction { + transaction_hash: CryptoHash::default(), + sender_id: AccountId::from_str("sender1").unwrap(), + }); + let transaction2 = TransactionOrReceiptIdNewtype(TransactionOrReceiptId::Transaction { + transaction_hash: CryptoHash::default(), + sender_id: AccountId::from_str("sender1").unwrap(), + }); + assert!(transaction1 == transaction2); + + let receipt1 = TransactionOrReceiptIdNewtype(TransactionOrReceiptId::Receipt { + receipt_id: CryptoHash::default(), + receiver_id: AccountId::from_str("receiver1").unwrap(), + }); + let receipt2 = TransactionOrReceiptIdNewtype(TransactionOrReceiptId::Receipt { + receipt_id: CryptoHash::default(), + receiver_id: AccountId::from_str("receiver1").unwrap(), + }); + assert!(receipt1 == receipt2); + + let transaction3 = TransactionOrReceiptIdNewtype(TransactionOrReceiptId::Transaction { + transaction_hash: CryptoHash::default(), + sender_id: AccountId::from_str("sender2").unwrap(), + }); + assert!(transaction1 != transaction3); + + let receipt3 = TransactionOrReceiptIdNewtype(TransactionOrReceiptId::Receipt { + receipt_id: CryptoHash::default(), + receiver_id: AccountId::from_str("receiver2").unwrap(), + }); + assert!(receipt1 != receipt3); + + assert!(transaction1 != receipt1); + } +} diff --git a/bin/operator/src/main.rs b/bin/operator/src/main.rs index 18534b5..ed04f0e 100644 --- a/bin/operator/src/main.rs +++ b/bin/operator/src/main.rs @@ -11,11 +11,11 @@ pub async fn main() -> anyhow::Result<()> { .filter_module("reqwest", LevelFilter::Info) .init(); - let config = Config::default(); + let config = Config::new(std::env::var("NEAR_LIGHT_CLIENT_DIR").ok().as_deref())?; let client = Arc::new(SuccinctClient::new(&config).await?); - let engine = Engine::new(client.clone()).start(); + let engine = Engine::new(&config, client.clone()).start(); let server_handle = RpcServer::new(client, engine.clone()).run(&config).await?; diff --git a/bin/operator/src/succinct/mod.rs b/bin/operator/src/succinct/mod.rs index 1dc6cf7..a814c74 100644 --- a/bin/operator/src/succinct/mod.rs +++ b/bin/operator/src/succinct/mod.rs @@ -4,13 +4,14 @@ use alloy::{ primitives::*, providers::{network::Ethereum, Provider as ProviderExt, RootProvider}, sol_types::SolValue, - transports::{http::Http, TransportResult}, + transports::http::Http, }; use anyhow::{ensure, Context}; use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions}; use near_light_client_rpc::prelude::{CryptoHash, Itertools}; pub use near_light_clientx::plonky2x::backend::prover::ProofId; use near_light_clientx::{ + config::bps_from_network, plonky2x::{ backend::{ circuit::DefaultParameters, @@ -18,7 +19,6 @@ use near_light_clientx::{ }, utils::hex, }, - VERIFY_AMT, }; use reqwest::{ header::{self, HeaderMap, HeaderValue}, @@ -39,18 +39,27 @@ type Provider = RootProvider>; #[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash)] pub struct Config { + /// The succinct platform api key pub api_key: String, + /// The succinct platform rpc #[serde(default = "default_rpc")] pub rpc_url: String, + /// ETH rpc where the contract is deployed pub eth_rpc_url: String, + /// Address of the eth contract pub contract_address: Address, + /// The version we are targeting pub version: String, + /// Github organisation_id id for the proof platform #[serde(default = "default_organisation")] pub organisation_id: String, + /// Github project id #[serde(default = "default_project")] pub project_id: String, + /// Max retries when waiting for a request #[serde(default = "default_max_retries")] pub client_max_retries: u32, + /// Request timeout from the platform, in seconds #[serde(default = "default_timeout")] pub client_timeout: u64, } @@ -82,6 +91,7 @@ pub struct Client { ext: SuccinctClientExt, genesis: CryptoHash, releases: Vec, + verify_amt: usize, } impl Client { @@ -92,7 +102,8 @@ impl Client { let inner = Self::init_inner_client(&config.succinct).await?; - // TODO: introduce override when succinct wont relay the proof + // TODO[Feature]: introduce override if succinct wont relay the proof, call to a + // hosted prover who will prove, relay and unbrick let succinct_client = SuccinctClientExt::new( config.succinct.rpc_url.clone(), config.succinct.api_key.clone(), @@ -107,6 +118,7 @@ impl Client { ext: succinct_client, genesis: config.protocol.genesis, releases: Default::default(), + verify_amt: bps_from_network(&config.rpc.network), }; s.releases = s.fetch_releases(&chain_id).await?; ensure!( @@ -169,7 +181,7 @@ impl Client { .inspect(|r| log::trace!("releases: {:?}", r)) } - pub fn extract_release_details( + fn extract_release_details( deployments: Vec, chain_id: &u32, version: &str, @@ -193,7 +205,7 @@ impl Client { .collect_vec() } - pub fn build_proof_request_bytes( + fn build_proof_request_bytes( &self, release_id: &str, data: BytesRequestData, @@ -211,14 +223,14 @@ impl Client { }) } - pub fn build_sync_request(&self, trusted_header_hash: CryptoHash) -> BytesRequestData { + fn build_sync_request(&self, trusted_header_hash: CryptoHash) -> BytesRequestData { log::debug!("building sync request for {:?}", trusted_header_hash); BytesRequestData { input: trusted_header_hash.0.to_vec(), } } - pub fn build_verify_request( + fn build_verify_request( &self, trusted_header_hash: CryptoHash, ids: Vec, @@ -268,7 +280,7 @@ impl Client { .submit_request( circuit.deployment(&self.releases).chain_id, self.config.contract_address.0 .0.into(), - circuit.as_function_input(&req.input).into(), + circuit.with_selector(&req.input).into(), circuit .function_id(&self.contract) .await @@ -281,7 +293,7 @@ impl Client { self.wait_for_proof(&request_id).await } - pub async fn fetch_proofs(&self) -> anyhow::Result> { + async fn fetch_proofs(&self) -> anyhow::Result> { let res: anyhow::Result<_> = Ok(self .inner .get(format!("{}/proofs", self.config.rpc_url)) @@ -300,9 +312,8 @@ impl Client { res } - // We wait for the proof to be submitted to the explorer so we can track them by - // their proof id's - // TODO: change this to support request & proof id, for checking later + /// Wait for the proof to be submitted to the explorer so we can track them + /// by their proof id pub async fn wait_for_proof(&self, request_id: &str) -> anyhow::Result { let mut interval = tokio::time::interval(Duration::from_secs(60)); let mut attempts = 0; @@ -318,6 +329,7 @@ impl Client { interval.tick().await; } } + fn search_for_request<'p>( proofs: &'p [ProofResponse], request_id: &str, @@ -337,6 +349,8 @@ impl Client { .inspect(|p| log::debug!("found proof {:?} matching request: {:?}", p.id, request_id)) } + /// Request a proof to be proven, this doesn't relay the proof to the + /// contract, useful for users who don't want to relay pub async fn request_proof( &self, circuit: &Circuit, @@ -371,6 +385,7 @@ impl Client { .inspect(|d| log::debug!("fetched proof: {:?}/{:?}", d.id, d.status))?) } + /// Sync the light client pub async fn sync(&self, relay: bool) -> anyhow::Result { let circuit = Circuit::Sync; let req = self.build_sync_request(self.fetch_trusted_header_hash().await?); @@ -383,6 +398,7 @@ impl Client { Ok(id) } + /// Verify a set of transactions pub async fn verify( &self, ids: Vec, @@ -390,7 +406,7 @@ impl Client { ) -> anyhow::Result { log::trace!("verifying {} ids", ids.len()); ensure!( - ids.len() == VERIFY_AMT, + ids.len() == self.verify_amt, "wrong number of transactions for verify" ); let circuit = Circuit::Verify; @@ -404,6 +420,7 @@ impl Client { Ok(id) } + /// Fetch the last synced header from the contract async fn fetch_trusted_header_hash(&self) -> anyhow::Result { let mut h = self .contract @@ -447,6 +464,7 @@ pub mod tests { }; hex!(selector) } + pub fn proof_id(&self) -> Uuid { Uuid::from_str(match self.0 { Circuit::Sync => "cde59ba0-a60b-4721-b96c-61401ff28852", @@ -454,6 +472,7 @@ pub mod tests { }) .unwrap() } + pub fn request_id(&self) -> String { match self.0 { Circuit::Sync => "64bb0a1e-2695-42c8-aee3-9d8c1b17b379", @@ -461,6 +480,7 @@ pub mod tests { } .to_string() } + pub async fn mock(&self, server: &MockServer, releases: &[Deployment]) { let d = self.0.deployment(releases); // Stub the get function id @@ -635,9 +655,9 @@ pub mod tests { .collect_vec(); let req = client.build_verify_request(hash, txs); - assert_eq!(req.input[..32], hash.0.to_vec()); + pretty_assertions::assert_eq!(req.input[..32], hash.0.to_vec()); // Not using the same as above because of the padding - assert_eq!(hex!(&req.input[32..]), "0x009dbbc777884bc0ccc05fc9177bd442e19a9b82608f7ae6c8b81cbadee2320e1c77616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01026d02a778ce47a4a670e343cebf90a67309157b2a3a54079c13b8962908b080686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0024e2d4f8d3394fabea1a8ac255ec3ef9c6e14cc90e8e45c1d185f9a858d484107a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c000a594de1c36eca52f15d9f7d4177515570f3a6966e5ac51da1ce4abb7e496c6a706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00311fa837e07749c47b2825d06dd14d3aa6f438e2e1cc69857b737d0104ac080576325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007f454878ba125cc5f380439ee3c3e6510e8d66e7adcb70e59951bcf51c2916d5686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c012c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d797a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0156b47c3c713180318844195b0e0e29810c5f099fe19411eaf116d55b3f6d1f96706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0027d1be4cbf7826333bb3a66339314d6b23088907bc03abe8e1f0402f6b8e99fb6f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006d6c0e7346e597949cf4ef07e57b029426cac6d2a0e80761b07aaa83e5622fe16f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007f4bf0c2de11327648a0b56a170029f349308fc88d64badffaf4b1575a0444056f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00ae58764b2108d1e23de28591a61e52e6fdeb49f0985ab6bf5f332e338db742f877616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c000e7d92ca3c2fbd087783533f3e3c493881189b9e95829763ee2222d5ef50524361737365742d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0010478412784e05112c2ab987ce3d691a1f8d284f5e80d71d573229b6d643563b61737365742d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c009eac5b84d08bbf7716b595fc0d10686ead30355e5b7a8c9305ac02240823513961737365742d6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0124e495308164a0a97925a27e59fadec0c6fc59de23c5ffaef3ff30a2c513d51a686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0133fb9d5f75d87a3cf8eca81ad16c669a686aac61fc514a6cf1159e739618c2e86f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01d6c2a78d92595947756cc38ad2fb077984f691ebbba0d1db03c2cbed071d16ef6f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0133f6198c994c4ca12b360abc226c232f1dd46bef6c5be02c39278b8de8ea04696f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c003d268ee173d6cbe5f0c0efa3833fe6590950938cb7b24b15957587fd0380729375736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c008be60e1ba421b4c0cf104749bd2f322f6d985763053b347bf68c6000908aa693796b616a753261386a6366672e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c002aa060c38662a90fa07a34b800cd3c84360d894dc4bec1c81a7b41d3eb282092706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006674894d631ca3e373a87b27ea614f16468fa9ddaf401f079d93359f14f29f6e72656c61792e6175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00bc656c82f3aed97695b555e49b55e584f960197f092a53ac9bcc3f872125436476325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01a988b23956e4e5ab00dd3d16decdd0714554562ae9fbfae9053acca1a91f37cc75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00883c8876a557200a3b14ad46b0646af75750403ab3cb5ff04ef6a72f4f71b7786175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01d916068721ec0d451382e00bebd8f4f713321e3bde850c36463517d6c50115c5706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01bf4b51df07bf9819b996b720eadafc5323ec7a2ad7fc0555190771faaa582d3272656c61792e6175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c1e6131d4648085ea2f1e23ba516e6d03a05c6448c30639b1b082c8650544506686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00956fff9f472d68ff61b7c3e88b678738f5082e44ca40277cb394501a86d8b42177616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c005ef99fadc671ee47c6015c92351d2c172995832ac01ecd1e8b8ceae3722ccc296f7261636c652d322e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c002c0d2c9385d28114166aee120e40fdf5f713f07477e0abd4eb63c7a39da10ac770726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01796847ad9c2cbe2a5006a89dafe3e7838846085f4cd240b97c29d1253a9476c1686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01dcfc4f30c70a7da653f1166a1e4abd70865b0042773485674804591a2d1f001b6f7261636c652d322e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00094532de95f1fa7fa1ae18fdbe8e09bb98c4e3fbb5033a6b5ae990594569b27775736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c21d9dab00ab7481de442b9d8273dfe151799c66cdf52346a6d9c44c418824306c776a64766c767a666f37392e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c003f185492791bff0827767b40925d34beb5530e55ea6a18cc559a513c96598431706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00af6aecb8c18aeed12c89e10024d7848732c1edc39ac0b47d421c92807d14835c76325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01a25c13eeab4dbb89d8308e148b34c7d52fbb32044c0552da9be982c9b480f22175736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01e99199a0ee36379f8ec381be29fb5429950d7ea2a4e661f9a352d7c2e3f087a6706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0049fcac1e1358c141f432296c655a7f37ad6f799e676b864741e017fa48e1619d6f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c004c55453e68e24a730befcedf4dbf17dcb4522774ebdcdc959bbb8881216f095d6f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00caeed0384fc2e3d27b729993601365870abdeac789239a155b2d2c7c86921ee06f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007366f9850730663c25ad58c7d0b2c92887d5b3521c36627a3f5b0f1cafc23e3d61737365742d6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c008dc95d832842725b8e2dd1752ed1ecbebe0354d7a8b384f6036267433bf8f4f861737365742d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00a2dcd933e13734be2cf545f6f4150a2911041bc4840cd8df25b2e4cfaf84ac4b61737365742d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0045989ff55b321cf17042336002143923eaeb1f7aca1890d06b8661beb5469f4a686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01178da8f0216f3935a7b18792f2f524aaa3e0ce04878be5e88e7374a22588187b6f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c016f7c2658d5db3ad5c21b8cd74b67e3cd88583efbb0990c866eada559459f15296f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01c743450fa747831ab768ff6b81769b99fdf4dd7a96d77b11b4b134746994b2e06f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00e1d64d332912d157d409dd7b2cd15922d1458c9f89db1a6aaec788722c8c9feb77616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01376545409c923b585549e2775ea44f17cf666c8a96b7f46000470ce8215cc29f686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00bb801a7742b42c0a63572de86a669f4278cbfd2ad83890a78aca8927c5c559a8706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0025effa5cf9769e5e4ee9ea894e96e5fa7f8d2d3e02779afb093c71bfd191c0b175736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c67d9883f54fdbe34cb4eca5ebea3bd11eb1c643759bfd68312fe30a5538860876325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c1f41e50f9b71459d5e937ab570c7ff6eff9c1ca59c58512fb4442de7435160637337576793879366b7872382e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01fbbdfc15d6cc230ad33560005bd260d319c55b740e974f2670556660bcb1b569706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01450b4cd2c1041e86dd7131ff0d0565112eb9a6eddf472ab0e40f117fae2e9e1b75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00a51296de1fa4bdb501f5e22e70bd7357c311eb96c480c7b5a83b0c73cac3a3a56f7261636c652d322e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00ada0187c6e035b3726b80677a4dfe0b580ec9bf6fbea5ad9ddc5334377e4bd0f686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006970f24b1c8d919325a5bf853410ed55ce3ebd1c7d209b7f44a4125ac9192b4a77616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0051d453f91d122400d4ffea377689f251447437e4e870a159be8d3e9604b21e9c70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01b2bd890c05c3af200de1cd245cff4156daf7109813703e9642423b3e5c721967686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01d9ce54d510d4c9c42931dcd9e46ff5c8253ad86e3e0a8aa2803d7698f2f123c06f7261636c652d322e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00bcb5dba2c2640610b8d0189cd7a545c1082261aac92bfef5ed36771f16cb925075736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c003c2ef2b1d44fc0f6a8952dd90894737519c9ae1687b9641c43cc3c7e69eae62c6474617a7a317336777465322e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00f6f1bc126b85da0b9413c839d94cf0753e933ee94e59112b3755769ab15091866f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c005e6cdecba3c18cd8df4504fcce70585d95224e8cb9536584bce3de584f6af6116f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00ba78aeb2fefc974b28b920d55d2272067e520ef9c7bda33d190be43f8e5331d96f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c018debe92ce04dae79f833258017ee1ecac7d32b7772c3489db3f382a9fdf0dc5275736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0092ac8da13b7eb470beb940aefe3113cb1691ea11699fffc0a90e34dfb5ba02ae61737365742d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00a5fb923fa82b73f48c911ebff4595c0a86f4cb7f7ca3d54387da724f0d6e316d61737365742d6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006420a00089b6c26f96f900f1422e95a1b5dca6b4664e8c8057a8ce49e7c2dc6f61737365742d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01b875da1c575c2e1254103f3633918084fe49012d2e528e2cc0061e1e30bd3d286f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01f6eb39a6fff73ba199ce13195d9b61461239d32217941ed26e45c74a651b93876f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c017bddbe92024918efcc2187f0f1cf02d5f08c1410131742965b1618dca217dd376f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00a59b1ab3a8038c1185fffdab48360c18011c6b334767abaf573aa8976edac890686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007d0eec60dded5c4f117c27cf63139ab6c19e918736acc0c52be3edfb6a65b0ce77616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0090ef5aefc01261767eb94d1a3cb59dd73b20c01e5ff9f55e4b463f07247e8d3c706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01e40d099c2e6883cd319aaf52b3b29d43305d069484017f8dbe6d1e6dfb0945a6686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00dba102a52675cc53f8176ea91727afcbcfdbe9d0ea1a595a34f869357086e2db76325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007235081eba3164d3a3079bf54dca800270b8f8f7f2ac4b8b7b23b3bc199de22c75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00e2d28a4d3a035cb1ac7860c98c9727361995622667df5199fec7be4288932b4e7a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0153fc02f4e7ccb921d3416493d27b55624b320a2d38c5b9f61a6048451e0206a9706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00d166b618dce48f4a119a14abca9f85181a67016596916a22063530537f01ce5e313266626831676c636761742e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0074d2fd7d043d81e094fb01cccc56b1e65490a890eb32c78c0bb7dc60dff76abc70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c018359b2a6712f088e072ad798f7c09bedfe82f79963a31a1b4258553107d67bd575736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01b5e9920a135036cfd2af5b8959b91eec0010e60fe6161551517a7babd941436b7a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c000bab00c39618436abebd736b23681eee010d8698d76e674a4b74774a9340ad5f686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c009c917c130c06c09904bb4fa785134c6620540b3e514c0eb6dc46d0d2eb4d0c1c706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0030ae7beef7ef03cb7a8e7272f31caa776a811ea14baf2f88166e153a091eea9177616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00d2977d006dc76464f3fff3c0d79ee8e272cff2ede29ce5435abf20e434b981c776325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01b1397b648ffbe568f0948c663de51478ee59ea7f9650ddc66385ec82d999332f686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01eac41dbd9e4ac61e6563807449cf6c24373a0a9d936d4c0460d434e9e5686e56706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00108e67bad908a8d93ad2ad11237b14a7213ba72dd94fd12cfd7634cccec030376f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00398683f803de41db53ba1fb3ce05f1b16ce00acd3b63a13ed8848a4b472375c16f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c000d71a4926cfa09f4209c5733b9930b2487d6153cd442b53867c0b914069cf8d075736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00e3227be1fff28d7f6a8e2f60550060b522edd9fa2525e6d38fb772cc62181a7f6f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0078a53eab5a1761e01aefba7655d437a386a96c603efb2bc55d15c67effc5f8b761737365742d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0044cb39b104c68014193ab25802fe3bd6193ca6fa27b978975244a092e9ddd00261737365742d6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00e7ff3a76008fba50478f67a8632fcd210a925ca93916ead39ef2f24df9f04c9336386672647269613439766d2e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00e9af93fb98c6eb6eeb6c3488626c7c879454d84ec8b3ca773431d606fc705ae961737365742d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c008ca8cfea66a0d9c8620fda8254dcab2939f2e35f1516032e6a86d1fa5937fd60732d6c616d312e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0026cfd72efde1617a80c0e1150a4dd9e1d1a4649b5135413c2ae0dcfb699a224870726963652d6f7261636c652d76312e6e6561726c656e642d6f6666696369616c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c016d6b45b053655e9e9cc076cda1c36a678276de517fbdddc0b38664d4c8abc7f96f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0183d42a5fe196260fc9a9ee3902030bdd443b6417fdf3772312ea73fe64cc56986f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c017a804a4b16015ea05f501a0eecb47777428beb383ad2fbe090e62d499c2e6a3675736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01b4082a419e23b30b701aa1345b9de41ca0763a3a46e6839029cf51bc8ee92be76f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c018f31d24f72c4bb135ffdb56f022723cf284add497c5ec0e016c94830422d2086732d6c616d312e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00f776278d1a804f9d9ed5b803f77720907b3b07af6eb47869aa26a884dcb95360706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00032072129958d50ab29289651f84af960adc57cbd09afa6231466f50323d75d2686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0076d6768b9a80c969e557e465ce259b7b3c40c8c6c0dfb33e26b07c1c0bbf541072656c61792e6175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00314203fbdc85f52832e2b026322a23393adccf428d262c8d83cf495e2ad8d17077616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006d41ac0ddbbb3f4fef405ad78337ea1ed1745df288cfd8a0bde449a04beea7ba76325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00882758a173b262a6c607e80a59ac1553dcc57c4cfdc63e9fa98d2344acbffa2f746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00ccf5eac51a1b6e57fd2605a80773c6a432769c8e142a2a3ef72462e131b76f48746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007a2c6c982ab354c4448948a2bef0f86d353dda983ae3dd810c81e8fd0dc53a2d6175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01f2dac06800a69fa823fb11fb44cf520cd1c8475ec23cab10204dfb3e2e720bf0686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01a96d55bf23fc006afdfd3ce4334fa0eb6969192b0b34e628fb64b12b2ae23f5f706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006ebce1b1a64ddb3849beabf354e62829bfcbdcaed484e17ca26b298bcab71e226465762d313730353330373935343733322d32333231323236313631323538342c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00d1553da94dcee551b0834725f2ab762e936b332f24ac962fb3f4b73e99163a636465762d313730353330373935343731362d32363030353632313832303036312c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01afdca85f436fafadc675a42f9c6d9c1f1d6454eca062c83fc1e0690e5381ba8a72656c61792e6175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c019fc084d6c408f3369330354027e008ac5a83cc83606d99e200d0f4b14a74f8dc746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c013192067f557baa41444430c749bb7dc241d238dae5be85bcd4615f86ae13ae26746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0070ff84e558f127c26d63ca4e7d26fe62d490b71c9187bae3b3fee13c6ca7f55075736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00203ca6c4a7c5e60fe8fa95adf12682dbf891c7cf164ea52e4e177516889c21b57676746a716868666e726c322e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c013b6dcc41f7c0df9e45c1aa2602488a5075f2b7f73013d9e3096622c3cfdd1dff75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"); + pretty_assertions::assert_eq!(hex!(&req.input[32..]), "0x009dbbc777884bc0ccc05fc9177bd442e19a9b82608f7ae6c8b81cbadee2320e1c77616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01026d02a778ce47a4a670e343cebf90a67309157b2a3a54079c13b8962908b080686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0024e2d4f8d3394fabea1a8ac255ec3ef9c6e14cc90e8e45c1d185f9a858d484107a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c000a594de1c36eca52f15d9f7d4177515570f3a6966e5ac51da1ce4abb7e496c6a706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00311fa837e07749c47b2825d06dd14d3aa6f438e2e1cc69857b737d0104ac080576325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007f454878ba125cc5f380439ee3c3e6510e8d66e7adcb70e59951bcf51c2916d5686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c012c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d797a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0156b47c3c713180318844195b0e0e29810c5f099fe19411eaf116d55b3f6d1f96706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0027d1be4cbf7826333bb3a66339314d6b23088907bc03abe8e1f0402f6b8e99fb6f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006d6c0e7346e597949cf4ef07e57b029426cac6d2a0e80761b07aaa83e5622fe16f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007f4bf0c2de11327648a0b56a170029f349308fc88d64badffaf4b1575a0444056f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00ae58764b2108d1e23de28591a61e52e6fdeb49f0985ab6bf5f332e338db742f877616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c000e7d92ca3c2fbd087783533f3e3c493881189b9e95829763ee2222d5ef50524361737365742d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0010478412784e05112c2ab987ce3d691a1f8d284f5e80d71d573229b6d643563b61737365742d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c009eac5b84d08bbf7716b595fc0d10686ead30355e5b7a8c9305ac02240823513961737365742d6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0124e495308164a0a97925a27e59fadec0c6fc59de23c5ffaef3ff30a2c513d51a686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0133fb9d5f75d87a3cf8eca81ad16c669a686aac61fc514a6cf1159e739618c2e86f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01d6c2a78d92595947756cc38ad2fb077984f691ebbba0d1db03c2cbed071d16ef6f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0133f6198c994c4ca12b360abc226c232f1dd46bef6c5be02c39278b8de8ea04696f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c003d268ee173d6cbe5f0c0efa3833fe6590950938cb7b24b15957587fd0380729375736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c008be60e1ba421b4c0cf104749bd2f322f6d985763053b347bf68c6000908aa693796b616a753261386a6366672e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c002aa060c38662a90fa07a34b800cd3c84360d894dc4bec1c81a7b41d3eb282092706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006674894d631ca3e373a87b27ea614f16468fa9ddaf401f079d93359f14f29f6e72656c61792e6175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00bc656c82f3aed97695b555e49b55e584f960197f092a53ac9bcc3f872125436476325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01a988b23956e4e5ab00dd3d16decdd0714554562ae9fbfae9053acca1a91f37cc75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00883c8876a557200a3b14ad46b0646af75750403ab3cb5ff04ef6a72f4f71b7786175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01d916068721ec0d451382e00bebd8f4f713321e3bde850c36463517d6c50115c5706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01bf4b51df07bf9819b996b720eadafc5323ec7a2ad7fc0555190771faaa582d3272656c61792e6175726f72612c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c1e6131d4648085ea2f1e23ba516e6d03a05c6448c30639b1b082c8650544506686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00956fff9f472d68ff61b7c3e88b678738f5082e44ca40277cb394501a86d8b42177616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c005ef99fadc671ee47c6015c92351d2c172995832ac01ecd1e8b8ceae3722ccc296f7261636c652d322e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c002c0d2c9385d28114166aee120e40fdf5f713f07477e0abd4eb63c7a39da10ac770726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01796847ad9c2cbe2a5006a89dafe3e7838846085f4cd240b97c29d1253a9476c1686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01dcfc4f30c70a7da653f1166a1e4abd70865b0042773485674804591a2d1f001b6f7261636c652d322e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00094532de95f1fa7fa1ae18fdbe8e09bb98c4e3fbb5033a6b5ae990594569b27775736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c21d9dab00ab7481de442b9d8273dfe151799c66cdf52346a6d9c44c418824306c776a64766c767a666f37392e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c003f185492791bff0827767b40925d34beb5530e55ea6a18cc559a513c96598431706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00af6aecb8c18aeed12c89e10024d7848732c1edc39ac0b47d421c92807d14835c76325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01a25c13eeab4dbb89d8308e148b34c7d52fbb32044c0552da9be982c9b480f22175736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01e99199a0ee36379f8ec381be29fb5429950d7ea2a4e661f9a352d7c2e3f087a6706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0049fcac1e1358c141f432296c655a7f37ad6f799e676b864741e017fa48e1619d6f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c004c55453e68e24a730befcedf4dbf17dcb4522774ebdcdc959bbb8881216f095d6f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00caeed0384fc2e3d27b729993601365870abdeac789239a155b2d2c7c86921ee06f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007366f9850730663c25ad58c7d0b2c92887d5b3521c36627a3f5b0f1cafc23e3d61737365742d6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c008dc95d832842725b8e2dd1752ed1ecbebe0354d7a8b384f6036267433bf8f4f861737365742d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00a2dcd933e13734be2cf545f6f4150a2911041bc4840cd8df25b2e4cfaf84ac4b61737365742d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0045989ff55b321cf17042336002143923eaeb1f7aca1890d06b8661beb5469f4a686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01178da8f0216f3935a7b18792f2f524aaa3e0ce04878be5e88e7374a22588187b6f70657261746f725f6d616e616765722e6f726465726c792e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c016f7c2658d5db3ad5c21b8cd74b67e3cd88583efbb0990c866eada559459f15296f70657261746f722d6d616e616765722e6f726465726c792d71612e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01c743450fa747831ab768ff6b81769b99fdf4dd7a96d77b11b4b134746994b2e06f70657261746f722d6d616e616765722e6f726465726c792d6465762e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00e1d64d332912d157d409dd7b2cd15922d1458c9f89db1a6aaec788722c8c9feb77616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01376545409c923b585549e2775ea44f17cf666c8a96b7f46000470ce8215cc29f686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00bb801a7742b42c0a63572de86a669f4278cbfd2ad83890a78aca8927c5c559a8706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0025effa5cf9769e5e4ee9ea894e96e5fa7f8d2d3e02779afb093c71bfd191c0b175736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c67d9883f54fdbe34cb4eca5ebea3bd11eb1c643759bfd68312fe30a5538860876325f312e706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00c1f41e50f9b71459d5e937ab570c7ff6eff9c1ca59c58512fb4442de7435160637337576793879366b7872382e75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01fbbdfc15d6cc230ad33560005bd260d319c55b740e974f2670556660bcb1b569706572702e7370696e2d66692e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01450b4cd2c1041e86dd7131ff0d0565112eb9a6eddf472ab0e40f117fae2e9e1b75736572732e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00a51296de1fa4bdb501f5e22e70bd7357c311eb96c480c7b5a83b0c73cac3a3a56f7261636c652d322e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c00ada0187c6e035b3726b80677a4dfe0b580ec9bf6fbea5ad9ddc5334377e4bd0f686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c006970f24b1c8d919325a5bf853410ed55ce3ebd1c7d209b7f44a4125ac9192b4a77616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c0051d453f91d122400d4ffea377689f251447437e4e870a159be8d3e9604b21e9c70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c01b2bd890c05c3af200de1cd245cff4156daf7109813703e9642423b3e5c721967686f7477616c6c65742e6465762d6b61696368696e672e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"); if let ProofRequest::Bytes(full) = client.build_proof_request_bytes(&verify_release_id, req.clone()) @@ -649,7 +669,8 @@ pub mod tests { } else { panic!("wrong request type"); } - let bytes = Circuit::Verify.as_function_input(&req.input); + + let bytes = Circuit::Verify.with_selector(&req.input); pretty_assertions::assert_eq!(hex!(&bytes[4..]), hex!(req.input)); } diff --git a/bin/operator/src/succinct/types.rs b/bin/operator/src/succinct/types.rs index 8895616..d654dd1 100644 --- a/bin/operator/src/succinct/types.rs +++ b/bin/operator/src/succinct/types.rs @@ -1,11 +1,6 @@ use alloy::{ - primitives::*, - providers::network::Ethereum, - sol, - sol_types::{Selectors, SolCall, SolInterface}, - transports::http::Http, + primitives::*, providers::network::Ethereum, sol, sol_types::SolCall, transports::http::Http, }; -use futures::FutureExt; use near_light_client_primitives::pad_account_id; pub use near_light_client_rpc::TransactionOrReceiptId as TransactionOrReceiptIdPrimitive; use near_light_clientx::plonky2x::backend::{ @@ -13,7 +8,6 @@ use near_light_clientx::plonky2x::backend::{ function::{ProofRequest, ProofResult}, prover::ProofId, }; -use reqwest_middleware::ClientWithMiddleware; use serde::{Deserialize, Serialize}; use uuid::Uuid; use NearX::TransactionOrReceiptId; @@ -23,30 +17,35 @@ use crate::types::NearX::{syncCall, verifyCall}; pub type NearXClient = NearXInstance, super::Provider>; +// TODO: update ABI when updating contract, can we just pass the path of the +// contract now we use alloy? sol!( #[sol(abi, rpc)] NearX, "../../nearx/contract/abi.json" ); -// // TODO: update ABI when updating contract -//abigen!(NearXClient, "../../nearx/contract/abi.json",); - /// The circuits we support in this nearxclient pub enum Circuit { Sync, Verify, } impl Circuit { - pub fn selector(&self) -> [u8; 4] { + fn selector(&self) -> [u8; 4] { match self { Circuit::Sync => syncCall::SELECTOR, Circuit::Verify => verifyCall::SELECTOR, } } - pub fn as_function_input(&self, input: &[u8]) -> Vec { + + /// Writes the input prepended with the selector + pub fn with_selector(&self, input: &[u8]) -> Vec { vec![&self.selector()[..], input].concat() } + + /// Get the function id from the contract + /// This can be updated in realtime, so we query this every time without + /// caching pub async fn function_id(&self, client: &NearXClient) -> anyhow::Result<[u8; 32]> { let id = match self { Circuit::Sync => client.syncFunctionId().call().await.map(|x| x._0), @@ -54,6 +53,9 @@ impl Circuit { }?; Ok(*id) } + + /// Filter a deployment from the release list + /// Safety: panics when a deployment cannot be found pub fn deployment(&self, releases: &[Deployment]) -> Deployment { log::debug!("finding deployment in {:?}", releases); let find = |entrypoint: &str| -> Deployment { @@ -71,6 +73,7 @@ impl Circuit { } } +// Eventually we can get these types from succinct crate #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProofRequestResponse { pub proof_id: ProofId, @@ -159,6 +162,7 @@ mod tests { use super::*; + // TODO: integration tests #[test] fn test_deserialise_deployments() { let _ = fixture::>("deployments.json"); diff --git a/bin/operator/tests/succinct.rs b/bin/operator/tests/succinct.rs index ca44688..7d015a6 100644 --- a/bin/operator/tests/succinct.rs +++ b/bin/operator/tests/succinct.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] // Justification: Until we decide on test feature flags use std::str::FromStr; use near_light_client_rpc::prelude::Itertools; @@ -13,43 +14,44 @@ async fn client() -> Client { Client::new(&Config::test_config()).await.unwrap() } -#[tokio::test] -async fn test_sync() { - let s = client().await.sync(false).await.unwrap(); - println!("synced with {:?}", s); -} - -#[tokio::test] -async fn test_sync_relay() { - let s = client().await.sync(true).await.unwrap(); - println!("synced with {:?}", s); -} - -#[tokio::test] -async fn test_verify() { - let client = client().await; - - let txs = fixture::>("ids.json") - .into_iter() - .take(VERIFY_AMT) - .collect_vec(); - - let s = client.verify(txs, false).await.unwrap(); - println!("verify with {:?}", s); -} - -#[tokio::test] -async fn test_verify_relay() { - let client: Client = client().await; - - let txs = fixture::>("ids.json") - .into_iter() - .take(VERIFY_AMT) - .collect_vec(); - - let s = client.verify(txs, true).await.unwrap(); - println!("verify with {:?}", s); -} +// TODO: these test shouldn't be run in CI, probably sporadically +// #[tokio::test] +// async fn test_sync() { +// let s = client().await.sync(false).await.unwrap(); +// println!("synced with {:?}", s); +// } +// +// #[tokio::test] +// async fn test_sync_relay() { +// let s = client().await.sync(true).await.unwrap(); +// println!("synced with {:?}", s); +// } +// +// #[tokio::test] +// async fn test_verify() { +// let client = client().await; +// +// let txs = fixture::>("ids.json") +// .into_iter() +// .take(VERIFY_AMT) +// .collect_vec(); +// +// let s = client.verify(txs, false).await.unwrap(); +// println!("verify with {:?}", s); +// } +// +// #[tokio::test] +// async fn test_verify_relay() { +// let client: Client = client().await; +// +// let txs = fixture::>("ids.json") +// .into_iter() +// .take(VERIFY_AMT) +// .collect_vec(); +// +// let s = client.verify(txs, true).await.unwrap(); +// println!("verify with {:?}", s); +// } #[tokio::test] async fn test_check_proof() { diff --git a/nearx/src/builder.rs b/nearx/src/builder.rs index 07475b2..e4fc70b 100644 --- a/nearx/src/builder.rs +++ b/nearx/src/builder.rs @@ -1,4 +1,3 @@ -use near_light_client_primitives::NUM_BLOCK_PRODUCER_SEATS; use near_light_client_protocol::prelude::Itertools; use plonky2x::{ frontend::{ @@ -303,7 +302,7 @@ impl, const D: usize> Sync for CircuitBuilder &next_block.next_bps_hash, ); self.assertx(bps_valid); - assert!(next_block.next_bps.inner.len() == NUM_BLOCK_PRODUCER_SEATS); + assert_eq!(next_block.next_bps.inner.len(), LEN); next_block.header.to_owned() } @@ -388,6 +387,7 @@ fn to_le_bytes, V: CircuitVariable, const D: usize, const #[cfg(test)] mod tests { + use near_light_client_primitives::NUM_BLOCK_PRODUCER_SEATS; use near_light_client_protocol::{Protocol, StakeInfo}; use self::assert_eq; diff --git a/nearx/src/config.rs b/nearx/src/config.rs new file mode 100644 index 0000000..5219413 --- /dev/null +++ b/nearx/src/config.rs @@ -0,0 +1,47 @@ +use near_light_client_rpc::Network; + +#[const_trait] +pub trait Config: std::fmt::Debug + Clone + PartialEq + Sync + Send + 'static { + const NETWORK: Network; + const BPS: usize; + + const VERIFY_AMT: usize; + const VERIFY_BATCH: usize; +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Testnet; +impl const Config for Testnet { + const NETWORK: Network = Network::Testnet; + const BPS: usize = 35; // In practice we only see 30-35 + + const VERIFY_AMT: usize = 64; + const VERIFY_BATCH: usize = 4; +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Mainnet; +impl const Config for Mainnet { + const NETWORK: Network = Network::Mainnet; + const BPS: usize = 50; + + const VERIFY_AMT: usize = 128; + const VERIFY_BATCH: usize = 4; +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CustomBatchNumConfig(); +impl const Config for CustomBatchNumConfig<{ A }, { B }> { + const NETWORK: near_light_client_rpc::Network = Testnet::NETWORK; + const BPS: usize = Testnet::BPS; + const VERIFY_AMT: usize = A; + const VERIFY_BATCH: usize = B; +} + +pub fn bps_from_network(n: &near_light_client_rpc::Network) -> usize { + match n { + near_light_client_rpc::Network::Mainnet => Mainnet::BPS, + near_light_client_rpc::Network::Testnet => Testnet::BPS, + _ => todo!("Unsupported"), + } +} diff --git a/nearx/src/hint.rs b/nearx/src/hint.rs index 1df5c1e..f8ad1a7 100644 --- a/nearx/src/hint.rs +++ b/nearx/src/hint.rs @@ -2,166 +2,90 @@ use std::{collections::HashMap, marker::PhantomData}; use async_trait::async_trait; use log::debug; -use near_light_client_primitives::NUM_BLOCK_PRODUCER_SEATS; -use near_light_client_protocol::{prelude::CryptoHash, Proof}; -use near_light_client_rpc::{prelude::GetProof, LightClientRpc, NearRpcClient, Network}; +use near_light_client_protocol::{prelude::CryptoHash, Proof, ValidatorStake}; +use near_light_client_rpc::{ + prelude::{GetProof, Itertools}, + LightClientRpc, NearRpcClient, Network, +}; use plonky2x::{ frontend::hint::asynchronous::hint::AsyncHint, prelude::{plonky2::field::types::PrimeField64, *}, }; use serde::{Deserialize, Serialize}; -use crate::variables::{ - normalise_account_id, BlockVariable, BlockVariableValue, CryptoHashVariable, HeaderVariable, - ProofVariable, TransactionOrReceiptIdVariable, Validators, ValidatorsVariable, - ValidatorsVariableValue, +use crate::{ + config::Config, + variables::{ + normalise_account_id, BlockVariable, BlockVariableValue, CryptoHashVariable, HashBpsInputs, + HeaderVariable, ProofVariable, TransactionOrReceiptIdVariable, Validators, + ValidatorsVariableValue, + }, }; #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct FetchNextHeaderInputs(pub Network); - -#[async_trait] -impl, const D: usize> AsyncHint for FetchNextHeaderInputs { - async fn hint( - &self, - input_stream: &mut ValueStream, - output_stream: &mut ValueStream, - ) { - let client = NearRpcClient::new(&self.0.into()); - - let h = input_stream.read_value::().0; +pub struct InputFetcher(pub PhantomData); - let next = client - .fetch_latest_header(&CryptoHash(h)) - .await - .expect("Failed to fetch header") - .expect("Expected a header"); - - output_stream.write_value::>(next.into()); +impl Default for InputFetcher { + fn default() -> Self { + Self(Default::default()) } } -impl FetchNextHeaderInputs { - pub fn fetch, const D: usize, const LEN: usize>( - &self, - b: &mut CircuitBuilder, - hash: &CryptoHashVariable, - ) -> Option> { - let mut input_stream = VariableStream::new(); - input_stream.write::(hash); - - let output_stream = b.async_hint(input_stream, self.clone()); - Some(output_stream.read::>(b)) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct FetchHeaderInputs(pub Network); - #[async_trait] -impl, const D: usize> AsyncHint for FetchHeaderInputs { - async fn hint( - &self, - input_stream: &mut ValueStream, - output_stream: &mut ValueStream, - ) { - let client = NearRpcClient::new(&self.0.into()); - - let h = input_stream.read_value::().0; - - let header = client - .fetch_header(&CryptoHash(h)) - .await - .expect("Failed to fetch header"); - - output_stream.write_value::(header.into()); - } -} - -impl FetchHeaderInputs { - /// Fetches a header based on its known hash and witnesses the result. - pub fn fetch, const D: usize>( - &self, - b: &mut CircuitBuilder, - trusted_hash: &CryptoHashVariable, - ) -> HeaderVariable { - let mut input_stream = VariableStream::new(); - input_stream.write::(trusted_hash); - - let output_stream = b.async_hint(input_stream, self.clone()); - let untrusted = output_stream.read::(b); - let untrusted_hash = untrusted.hash(b); - b.assert_is_equal(*trusted_hash, untrusted_hash); - untrusted - } -} - -pub enum Fetch { - Sync { - untrusted_header_hash: CryptoHashVariable, - }, -} - -pub enum Inputs { - Sync { - header: HeaderVariable, - bps: Validators, - next_block: BlockVariable, - }, -} - -impl Inputs { - fn validate, const D: usize>(&self, b: &mut CircuitBuilder) { - // TODO: validate based on inputs, here is where we might entrust - // untrusted headers and such - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct InputFetcher, const D: usize, const N: usize>( - (Network, PhantomData<(L)>), -); - -#[async_trait] -impl, const D: usize, const N: usize> AsyncHint - for InputFetcher +impl, const D: usize, C: Config> AsyncHint for InputFetcher +where + [(); C::BPS]:, { async fn hint( &self, input_stream: &mut ValueStream, output_stream: &mut ValueStream, ) { - let client = NearRpcClient::new(&self.0 .0.clone().into()); + let client = NearRpcClient::new(&C::NETWORK.into()); let marker: L::Field = input_stream.read_value::(); let marker = marker.to_canonical_u64(); match marker { 0 => { - let current_hash = input_stream.read_value::().0; + let trusted_header_hash = input_stream.read_value::().0; - let header = client - .fetch_header(&CryptoHash(current_hash)) + let trusted_header = client + .fetch_header(&CryptoHash(trusted_header_hash)) .await .expect("Failed to fetch header"); - log::debug!("Fetched header: {:#?}", header); + log::debug!("Fetched header: {:#?}", trusted_header); + // This is a very interesting trick to be able to get the BPS for the next epoch + // without the need to store the BPS, we verify the hash of the BPS in the + // circuit let bps = client - .fetch_latest_header(&header.inner_lite.next_epoch_id) + .fetch_latest_header(&trusted_header.inner_lite.next_epoch_id) .await .expect("Failed to fetch bps") .expect("Expected a header") - .next_bps; + .next_bps + .expect("Expected bps for the trusted header") + .into_iter() + .map(Into::::into) + .collect_vec(); log::debug!("Fetched next epoch bps: {:?}", bps); let next_block = client - .fetch_latest_header(&CryptoHash(current_hash)) + .fetch_latest_header(&CryptoHash(trusted_header_hash)) .await .expect("Failed to fetch header") .expect("Expected a header"); - let header_var = header.into(); - let bps_var = ValidatorsVariableValue::from_iter(bps.unwrap()); - let next_block_var: BlockVariableValue = next_block.into(); + // Test the protocol with the offchain protocol. + near_light_client_protocol::Protocol::sync( + &trusted_header, + &bps, + next_block.clone(), + ) + .expect("Offchain protocol verification failed"); + + let bps_var = ValidatorsVariableValue::from_iter(bps); + let next_block_var: BlockVariableValue<{ C::BPS }, L::Field> = next_block.into(); use ethers::utils::hex; next_block_var @@ -179,71 +103,87 @@ impl, const D: usize, const N: usize> AsyncHint ); }); - output_stream.write_value::(header_var); - // TODO: fix the bounding issue here - output_stream.write_value::>(bps_var); - output_stream.write_value::>(next_block_var); + output_stream.write_value::(trusted_header.into()); + output_stream.write_value::>(bps_var); + output_stream.write_value::>(next_block_var); + } + 1 => { + let trusted_header_hash = input_stream.read_value::().0; + + let trusted_header = client + .fetch_header(&CryptoHash(trusted_header_hash)) + .await + .expect("Failed to fetch header"); + log::debug!("Fetched header: {:#?}", trusted_header); + output_stream.write_value::(trusted_header.into()); } _ => panic!("Invalid marker"), } } } -impl, const D: usize, const N: usize> InputFetcher { - pub fn fetch(&self, b: &mut CircuitBuilder, args: &Fetch) -> Inputs { +impl InputFetcher +where + [(); C::BPS]:, +{ + pub fn fetch_sync, const D: usize>( + &self, + b: &mut CircuitBuilder, + trusted_header_hash: &CryptoHashVariable, + ) -> ( + HeaderVariable, + Validators<{ C::BPS }>, + BlockVariable<{ C::BPS }>, + ) { let mut input_stream = VariableStream::new(); - match args { - Fetch::Sync { - untrusted_header_hash, - } => { - input_stream.write::(&b.constant(L::Field::from_canonical_u64(0))); - input_stream.write::(untrusted_header_hash); - } - } + input_stream.write::(&b.constant(L::Field::from_canonical_u64(0))); + input_stream.write::(trusted_header_hash); + let output_stream = b.async_hint(input_stream, self.clone()); - match args { - Fetch::Sync { .. } => { - let header = output_stream.read::(b); - let bps = output_stream.read::>(b); - let next_block = output_stream.read::>(b); - Inputs::Sync { - header, - bps, - next_block, - } - } - } + let untrusted_header = output_stream.read::(b); + let untrusted_header_hash = untrusted_header.hash(b); + b.watch(&untrusted_header_hash, "untrusted_header_hash"); + // Build trust in the header by hashing, ensuring equality + b.assert_is_equal(untrusted_header_hash, untrusted_header_hash); + let header = untrusted_header; + + let bps = output_stream.read::>(b); + let bps_hash = HashBpsInputs.hash(b, &bps); + b.assert_is_equal(header.inner_lite.next_bp_hash, bps_hash); + b.watch(&bps_hash, "calculate_bps_hash"); + + let next_block = output_stream.read::>(b); + (header, bps, next_block) } - pub fn fetch_sync_inputs( + + pub fn fetch_verify, const D: usize>( &self, b: &mut CircuitBuilder, - untrusted_header_hash: &CryptoHashVariable, - ) -> (HeaderVariable, Validators, BlockVariable) { - let fetch_header = FetchHeaderInputs(self.0 .0.into()); - let fetch_next_header = FetchNextHeaderInputs(self.0 .0.into()); - - let header = fetch_header.fetch(b, &untrusted_header_hash); - let bps = fetch_next_header - .fetch(b, &header.inner_lite.next_epoch_id) - .unwrap() - .next_bps; - - let next_block = fetch_next_header - .fetch(b, &untrusted_header_hash) - .expect("Failed to fetch next block"); + trusted_header_hash: &CryptoHashVariable, + ) -> HeaderVariable { + let mut input_stream = VariableStream::new(); + input_stream.write::(&b.constant(L::Field::from_canonical_u64(1))); + input_stream.write::(trusted_header_hash); + let output_stream = b.async_hint(input_stream, self.clone()); - (header, bps, next_block) + let untrusted_header = output_stream.read::(b); + let untrusted_header_hash = untrusted_header.hash(b); + b.watch(&untrusted_header_hash, "untrusted_header_hash"); + // Build trust in the header by hashing, ensuring equality + b.assert_is_equal(untrusted_header_hash, untrusted_header_hash); + let header = untrusted_header; + header } } // TODO: refactor into some client-like carrier for all hints that is serdeable #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct FetchProofInputs(pub Network); +pub struct FetchProofInputs(pub Network); #[async_trait] -impl, const D: usize, const B: usize> AsyncHint - for FetchProofInputs +impl, const D: usize, const N: usize> AsyncHint + for FetchProofInputs { async fn hint( &self, @@ -255,7 +195,7 @@ impl, const D: usize, const B: usize> AsyncHint let last_verified = input_stream.read_value::().0; let mut reqs = vec![]; - for _ in 0..B { + for _ in 0..N { let tx = input_stream.read_value::(); reqs.push(if tx.is_transaction { GetProof::Transaction { @@ -287,7 +227,7 @@ impl, const D: usize, const B: usize> AsyncHint .collect::>(); debug!("Fetched {} proofs", proofs.len()); - assert_eq!(proofs.len(), B, "Invalid number of proofs"); + assert_eq!(proofs.len(), N, "Invalid number of proofs"); for (k, p) in proofs.into_iter() { output_stream.write_value::(k.0.into()); @@ -334,16 +274,14 @@ pub struct ProofInputVariable { #[cfg(test)] mod tests { - use near_light_client_protocol::{LightClientBlockLiteView, ValidatorStake}; - use near_light_client_rpc::{ - prelude::Itertools, LightClientBlockView, Network, ValidatorStakeView, - }; + use near_light_client_primitives::NUM_BLOCK_PRODUCER_SEATS; use super::*; use crate::{ builder::{Ensure, Sync}, + config::{Mainnet, Testnet}, test_utils::{builder_suite, test_state, testnet_state, B, PI, PO}, - variables::{BlockVariableValue, BpsApprovals, HeaderVariable}, + variables::{BlockVariableValue, HeaderVariable, ValidatorsVariable}, }; #[test] @@ -353,15 +291,19 @@ mod tests { let define = |b: &mut B| { let header = b.read::(); - let hash = header.hash(b); - let next_block = FetchNextHeaderInputs(Network::Mainnet).fetch(b, &hash); - b.write::>(next_block.unwrap()); + let trusted_header_hash = header.hash(b); + + let (header, bps, next_block) = + InputFetcher::(Default::default()).fetch_sync(b, &trusted_header_hash); + b.write::>(next_block); let header = b.read::(); - let hash = header.hash(b); - let next_block = FetchNextHeaderInputs(Network::Testnet).fetch(b, &hash); - b.write::>(next_block.unwrap()); + let trusted_header_hash = header.hash(b); + let (header, bps, next_block) = + InputFetcher::(Default::default()).fetch_sync(b, &trusted_header_hash); + b.write::>(next_block); }; + let writer = |input: &mut PI| { input.write::(main_h.into()); input.write::(test_h.into()); @@ -378,32 +320,25 @@ mod tests { builder_suite(define, writer, assertions); } + // This test was a proof where there were appended BPS, the bug was the + // signature was active, but dummy BPS from the LC protocol #[test] fn test_problem_header() { let problem_hash = - bytes32!("0xeff7dccf304315aa520ad7e704062a8b8deadc5c0906e7e16d7305067a72a57e"); + bytes32!("0x6fd201bb6c09c3708793945be6d5e2c3dc8c9fcf65e9e3ccf81d4720735e5fe6"); //let problem_hash = testnet_state().0.hash().0; const AMT: usize = 50; - let fetcher = InputFetcher::(Default::default()); + let fetcher = InputFetcher::(Default::default()); let define = |b: &mut B| { let trusted_header_hash = b.read::(); - let Inputs::Sync { - header, - bps, - next_block, - } = fetcher.fetch( - b, - &Fetch::Sync { - untrusted_header_hash: trusted_header_hash, - }, - ); + let (header, bps, next_block) = fetcher.fetch_sync(b, &trusted_header_hash); // TODO: validate b.write::(header.clone()); - b.write::>(bps.clone()); - b.write::>(next_block.clone()); + b.write::>(bps.clone()); + b.write::>(next_block.clone()); let approval = b.reconstruct_approval_message(&next_block); b.validate_signatures(&next_block.approvals_after_next, &bps, approval); @@ -412,31 +347,10 @@ mod tests { input.write::(problem_hash.into()); }; let assertions = |mut output: PO| { - // let header = output.read::(); - // let bps = output.read::>(); - // let nb = output.read::(); + let header = output.read::(); + let bps = output.read::>(); + let nb = output.read::>(); }; builder_suite(define, writer, assertions); - - // TODO: everything doing anything sync wise should also verify with the - // data in the base protocol, if possible - // let head = serde_json::from_str::( - // std::fs::read_to_string("header.json").unwrap().as_str(), - // ) - // .unwrap(); - // let epoch_bps: Vec = - // serde_json::from_str::>( - // std::fs::read_to_string("bps.json").unwrap().as_str(), - // ) - // .unwrap() - // .into_iter() - // .map(ValidatorStakeView::into) - // .collect(); - // let next_block = serde_json::from_str::( - // std::fs::read_to_string("next_block.json").unwrap().as_str(), - // ) - // .unwrap(); - // near_light_client_protocol::Protocol::sync(&head, &epoch_bps, - // next_block).unwrap(); } } diff --git a/nearx/src/lib.rs b/nearx/src/lib.rs index e588525..33d432b 100644 --- a/nearx/src/lib.rs +++ b/nearx/src/lib.rs @@ -1,3 +1,8 @@ +#![feature(generic_const_exprs)] +#![allow(incomplete_features)] +#![feature(generic_arg_infer)] +#![feature(const_trait_impl)] + pub use plonky2x::{self, backend::circuit::Circuit, prelude::*}; pub use sync::SyncCircuit; pub use verify::VerifyCircuit; @@ -16,8 +21,7 @@ pub mod verify; #[cfg(test)] mod test_utils; -pub const VERIFY_AMT: usize = 64; -pub const VERIFY_BATCH: usize = 4; +pub mod config; #[cfg(test)] mod beefy_tests { @@ -25,7 +29,7 @@ mod beefy_tests { use serial_test::serial; use super::*; - use crate::test_utils::NETWORK; + use crate::config::Testnet; #[test] #[serial] @@ -36,14 +40,14 @@ mod beefy_tests { let mut builder = DefaultBuilder::new(); log::debug!("Defining circuit"); - SyncCircuit::::define(&mut builder); + SyncCircuit::::define(&mut builder); let circuit = builder.build(); log::debug!("Done building circuit"); let mut hint_registry = HintRegistry::new(); let mut gate_registry = GateRegistry::new(); - SyncCircuit::::register_generators(&mut hint_registry); - SyncCircuit::::register_gates(&mut gate_registry); + SyncCircuit::::register_generators(&mut hint_registry); + SyncCircuit::::register_gates(&mut gate_registry); circuit.test_serializers(&gate_registry, &hint_registry); } @@ -57,14 +61,14 @@ mod beefy_tests { let mut builder = DefaultBuilder::new(); log::debug!("Defining circuit"); - VerifyCircuit::::define(&mut builder); + VerifyCircuit::::define(&mut builder); let circuit = builder.build(); log::debug!("Done building circuit"); let mut hint_registry = HintRegistry::new(); let mut gate_registry = GateRegistry::new(); - VerifyCircuit::::register_generators(&mut hint_registry); - VerifyCircuit::::register_gates(&mut gate_registry); + VerifyCircuit::::register_generators(&mut hint_registry); + VerifyCircuit::::register_gates(&mut gate_registry); circuit.test_serializers(&gate_registry, &hint_registry); } diff --git a/nearx/src/main.rs b/nearx/src/main.rs index e1a1448..d12a709 100644 --- a/nearx/src/main.rs +++ b/nearx/src/main.rs @@ -1,24 +1,31 @@ -#[allow(unused_imports)] -use near_light_clientx::{plonky2x::backend::function::Plonky2xFunction, VERIFY_AMT, VERIFY_BATCH}; +#![allow(unused_imports)] +use near_light_clientx::{ + config::{Config, Mainnet, Testnet}, + plonky2x::backend::function::Plonky2xFunction, +}; -// Testnet, FIXME: this is error prone, use something else -#[allow(dead_code)] -const NETWORK: usize = 1; +cfg_if::cfg_if! { + if #[cfg(feature = "testnet")] { + type CFG = Testnet; + } else if #[cfg(feature = "mainnet")] { + type CFG = Mainnet; + } else { + panic!("No network feature enabled") + } +} -// TODO: make this use a nicer API for use by the prover. -// TODO: perpetually sync, use queue etc fn main() { cfg_if::cfg_if! { if #[cfg(feature = "sync")] { use near_light_clientx::SyncCircuit; - SyncCircuit::::entrypoint(); + SyncCircuit::::entrypoint(); } else if #[cfg(feature = "verify")] { - assert!(VERIFY_AMT % VERIFY_BATCH == 0); - assert!((VERIFY_AMT / VERIFY_BATCH).is_power_of_two()); + assert!(CFG::VERIFY_AMT % CFG::VERIFY_BATCH == 0); + assert!((CFG::VERIFY_AMT / CFG::VERIFY_BATCH).is_power_of_two()); use near_light_clientx::VerifyCircuit; - VerifyCircuit::::entrypoint(); + VerifyCircuit::::entrypoint(); } else { panic!("No circuit feature enabled"); } diff --git a/nearx/src/sync.rs b/nearx/src/sync.rs index 4d4399e..f80bd6b 100644 --- a/nearx/src/sync.rs +++ b/nearx/src/sync.rs @@ -1,9 +1,12 @@ +use std::marker::PhantomData; + use plonky2x::register_watch_generator; pub use plonky2x::{self, backend::circuit::Circuit, prelude::*}; use crate::{ builder::Sync, - hint::{FetchHeaderInputs, FetchNextHeaderInputs}, + config::Config, + hint::InputFetcher, variables::{ ApprovalMessage, BuildEndorsement, CryptoHashVariable, EncodeInner, HashBpsInputs, StakeInfoVariable, @@ -16,38 +19,24 @@ use crate::{ // differences between protocol crate // TODO: determine fees, allows integrators to charge #[derive(Debug, Clone)] -pub struct SyncCircuit; +pub struct SyncCircuit(PhantomData); -impl Circuit for SyncCircuit { +impl Circuit for SyncCircuit +where + [(); T::BPS]:, +{ fn define, const D: usize>(b: &mut CircuitBuilder) where <>::Config as plonky2::plonk::config::GenericConfig>::Hasher: plonky2::plonk::config::AlgebraicHasher<>::Field>, { - let network = NETWORK.into(); - let fetch_header = FetchHeaderInputs(network); - let fetch_next_header = FetchNextHeaderInputs(network); + let fetcher = InputFetcher::::default(); // TODO: we do need to be defensive to ensure that this is actually the trusted // header hash, do not allow anybody to provide this input. let trusted_header_hash = b.evm_read::(); - // This is a very interesting trick to be able to get the BPS for the next epoch - // without the need to store the BPS, we verify the hash of the BPS in the - // circuit - let header = fetch_header.fetch(b, &trusted_header_hash); - let bps = fetch_next_header - .fetch(b, &header.inner_lite.next_epoch_id) - .unwrap() - .next_bps; - - let bps_hash = HashBpsInputs.hash(b, &bps); - b.assert_is_equal(header.inner_lite.next_bp_hash, bps_hash); - b.watch(&bps_hash, "calculate_bps_hash"); - - let next_block = fetch_next_header - .fetch(b, &trusted_header_hash) - .expect("Failed to fetch next block"); + let (header, bps, next_block) = fetcher.fetch_sync(b, &trusted_header_hash); let new_head = b.sync(&header, &bps, &next_block); let new_hash = new_head.hash(b); @@ -59,11 +48,10 @@ impl Circuit for SyncCircuit { <>::Config as plonky2::plonk::config::GenericConfig>::Hasher: plonky2::plonk::config::AlgebraicHasher, { - registry.register_async_hint::(); - registry.register_async_hint::(); + registry.register_async_hint::>(); registry.register_hint::(); registry.register_hint::(); - registry.register_hint::(); + registry.register_hint::>(); register_watch_generator!(registry, L, D, ApprovalMessage, StakeInfoVariable); } @@ -74,7 +62,12 @@ mod beefy_tests { use serial_test::serial; use super::*; - use crate::test_utils::{builder_suite, testnet_state, B, NETWORK, PI, PO}; + use crate::{ + config::Testnet, + test_utils::{builder_suite, testnet_state, B, PI, PO}, + }; + + type SyncCircuit = super::SyncCircuit; #[test] #[serial] @@ -84,7 +77,26 @@ mod beefy_tests { let header = header.hash().0; let define = |b: &mut B| { - SyncCircuit::::define(b); + SyncCircuit::define(b); + }; + let writer = |input: &mut PI| { + input.evm_write::(header.into()); + }; + let assertions = |mut output: PO| { + let hash = output.evm_read::(); + println!("hash: {:?}", hash); + }; + builder_suite(define, writer, assertions); + } + + #[test] + #[serial] + #[ignore] + fn sync_e2e_blocked() { + let header = bytes32!("0x6fd201bb6c09c3708793945be6d5e2c3dc8c9fcf65e9e3ccf81d4720735e5fe6"); + + let define = |b: &mut B| { + SyncCircuit::define(b); }; let writer = |input: &mut PI| { input.evm_write::(header.into()); diff --git a/nearx/src/test_utils.rs b/nearx/src/test_utils.rs index 1893ca2..0626d45 100644 --- a/nearx/src/test_utils.rs +++ b/nearx/src/test_utils.rs @@ -9,9 +9,6 @@ pub use plonky2x::{ }; pub use test_utils::*; -// Testnet Repr -pub const NETWORK: usize = 1; - pub type B = CircuitBuilder; pub type PI = PublicInput; pub type PO = PublicOutput; diff --git a/nearx/src/variables.rs b/nearx/src/variables.rs index 9bf0984..a6427a0 100644 --- a/nearx/src/variables.rs +++ b/nearx/src/variables.rs @@ -16,7 +16,6 @@ use plonky2x::{ EDDSASignatureVariable, EDDSASignatureVariableValue, DUMMY_PUBLIC_KEY, DUMMY_SIGNATURE, }, hint::simple::hint::Hint, - uint::Uint, vars::EvmVariable, }, prelude::*, @@ -415,14 +414,13 @@ pub struct ValidatorsVariable { pub(crate) inner: BpsArr, } -impl, const N: usize> FromIterator +impl, const N: usize> FromIterator for ValidatorsVariableValue { fn from_iter>(iter: T) -> Self { let mut bps = iter .into_iter() .take(N) - .map(Into::::into) .map(Into::::into) .map(Into::>::into) .collect_vec(); @@ -570,11 +568,11 @@ impl, const D: usize> Hint for BuildEndorsement { } #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct HashBpsInputs; +pub struct HashBpsInputs; -impl, const D: usize> Hint for HashBpsInputs { +impl, const D: usize, const A: usize> Hint for HashBpsInputs { fn hint(&self, input_stream: &mut ValueStream, output_stream: &mut ValueStream) { - let bps = input_stream.read_value::(); + let bps = input_stream.read_value::>(); // TODO: if we use a bitmask we wont need default checks let default_validator = ValidatorStakeVariableValue::<>::Field>::default(); @@ -595,14 +593,14 @@ impl, const D: usize> Hint for HashBpsInputs { } } -impl HashBpsInputs { +impl HashBpsInputs { pub fn hash, const D: usize>( self, b: &mut CircuitBuilder, - bps: &Validators, + bps: &Validators, ) -> CryptoHashVariable { let mut input_stream = VariableStream::new(); - input_stream.write::(bps); + input_stream.write::>(bps); let output_stream = b.hint(input_stream, self); output_stream.read::(b) diff --git a/nearx/src/verify.rs b/nearx/src/verify.rs index 3faeae6..bdb412b 100644 --- a/nearx/src/verify.rs +++ b/nearx/src/verify.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use near_light_client_protocol::prelude::Itertools; pub use plonky2x::{self, backend::circuit::Circuit, prelude::*}; use plonky2x::{ @@ -9,7 +11,8 @@ use serde::{Deserialize, Serialize}; use crate::{ builder::Verify, - hint::{FetchHeaderInputs, FetchProofInputs, ProofInputVariable}, + config::Config, + hint::{FetchProofInputs, InputFetcher, ProofInputVariable}, variables::{byte_from_bool, CryptoHashVariable, EncodeInner, TransactionOrReceiptIdVariable}, }; @@ -22,31 +25,36 @@ pub struct ProofVerificationResultVariable { } #[derive(Debug, Clone)] -pub struct VerifyCircuit; +pub struct VerifyCircuit(PhantomData); -impl Circuit - for VerifyCircuit +impl Circuit for VerifyCircuit +where + [(); C::BPS]:, + [(); C::VERIFY_AMT]:, + [(); C::VERIFY_BATCH]:, { fn define, const D: usize>(b: &mut CircuitBuilder) where <>::Config as GenericConfig>::Hasher: AlgebraicHasher<>::Field>, { + let fetcher = InputFetcher::::default(); + // TODO: this is trusted, we should join the result of this to assert that the // light client did know about this header OR ensure in the circuit we knew // about this information, the contract should emit an event for the height so // it's easily queryable let trusted_header_hash = b.evm_read::(); - let head = FetchHeaderInputs(NETWORK.into()).fetch(b, &trusted_header_hash); + let header = fetcher.fetch_verify(b, &trusted_header_hash); // TODO: check that the head.block_height was once known to the verifier if not // the checkpoint header let mut ids = vec![]; - for _ in 0..N { + for _ in 0..C::VERIFY_AMT { ids.push(b.evm_read::()); } - let proofs = FetchProofInputs::(NETWORK.into()).fetch(b, &head, &ids); + let proofs = FetchProofInputs::<{ C::VERIFY_AMT }>(C::NETWORK).fetch(b, &header, &ids); // Init a default result for N let zero = b.constant::([0u8; 32].into()); @@ -58,7 +66,7 @@ impl Circuit }; // TODO: write some outputs here for each ID - let output = b.mapreduce_dynamic::, Self, B, _, _>( + let output = b.mapreduce_dynamic::, Self, {C::VERIFY_BATCH}, _, _>( default, proofs.data, |default, proofs, b| { @@ -72,13 +80,13 @@ impl Circuit } results.resize( - N, + C::VERIFY_AMT, default, ); results.into() }, - |_, l, r, b| MergeProofHint::.merge(b, &l, &r), + |_, l, r, b| MergeProofHint::<{C::VERIFY_AMT}>.merge(b, &l, &r), ); b.watch_slice(&output.data, "output"); @@ -94,9 +102,9 @@ impl Circuit where <>::Config as GenericConfig>::Hasher: AlgebraicHasher, { - registry.register_async_hint::>(); - registry.register_hint::>(); - registry.register_async_hint::(); + registry.register_async_hint::>(); + registry.register_hint::>(); + registry.register_async_hint::>(); // We hash in verify registry.register_hint::(); @@ -107,9 +115,9 @@ impl Circuit L, ProofVerificationResultVariable, ProofInputVariable, - ArrayVariable, + ArrayVariable, Self, - B, + { C::VERIFY_BATCH }, D, >>(dynamic_id); @@ -194,7 +202,6 @@ impl MergeProofHint { mod beefy_tests { use std::str::FromStr; - use log::logger; use near_light_client_protocol::prelude::Itertools; use near_primitives::types::TransactionOrReceiptId; use serial_test::serial; @@ -202,7 +209,8 @@ mod beefy_tests { use super::*; use crate::{ - test_utils::{builder_suite, testnet_state, B, NETWORK, PI, PO}, + config::CustomBatchNumConfig, + test_utils::{builder_suite, testnet_state, B, PI, PO}, variables::TransactionOrReceiptIdVariableValue, }; @@ -210,12 +218,9 @@ mod beefy_tests { #[serial] #[ignore] fn verify_e2e_2x1() { + type C = CustomBatchNumConfig<2, 1>; let (header, _, _) = testnet_state(); - // TODO: test many configs of these - const AMT: usize = 2; - const BATCH: usize = 1; - fn tx(hash: &str, sender: &str) -> TransactionOrReceiptId { TransactionOrReceiptId::Transaction { transaction_hash: CryptoHash::from_str(hash).unwrap(), @@ -244,10 +249,10 @@ mod beefy_tests { .map(Into::into) .collect_vec(); - assert_eq!(txs.len(), AMT); + assert_eq!(txs.len(), C::VERIFY_AMT); let define = |b: &mut B| { - VerifyCircuit::::define(b); + VerifyCircuit::::define(b); }; let writer = |input: &mut PI| { input.evm_write::(header.hash().0.into()); @@ -257,7 +262,7 @@ mod beefy_tests { }; let assertions = |mut output: PO| { let mut results = vec![]; - for _ in 0..AMT { + for _ in 0..C::VERIFY_AMT { let id = output.evm_read::(); let result = output.evm_read::(); results.push(ProofVerificationResultVariableValue:: { @@ -276,21 +281,18 @@ mod beefy_tests { // #[ignore] #[allow(dead_code)] // Justification: huge test, takes 36 minutes. keep for local testing fn verify_e2e_128x4() { - let (header, _, _) = testnet_state(); + type C = CustomBatchNumConfig<128, 4>; - const AMT: usize = 128; - const BATCH: usize = 4; + let (header, _, _) = testnet_state(); let txs = fixture::>("ids.json") .into_iter() - .take(AMT) + .take(C::VERIFY_AMT) .map(Into::>::into) .collect_vec(); - assert_eq!(txs.len(), AMT); - let define = |b: &mut B| { - VerifyCircuit::::define(b); + VerifyCircuit::::define(b); }; let writer = |input: &mut PI| { input.evm_write::(header.hash().0.into()); @@ -300,7 +302,7 @@ mod beefy_tests { }; let assertions = |mut output: PO| { let mut results = vec![]; - for _ in 0..AMT { + for _ in 0..C::VERIFY_AMT { let id = output.evm_read::(); let result = output.evm_read::(); results.push(ProofVerificationResultVariableValue:: { diff --git a/state.json b/state.json deleted file mode 100644 index 5fe8460..0000000 --- a/state.json +++ /dev/null @@ -1 +0,0 @@ -{"registry":{},"batches":{},"request_info":{"5686c544-b213-45d1-af79-1c6f00fd3510":null,"a26aca88-dcca-48ed-aa66-2a185bcab05e":null,"f757bd21-4112-4504-a972-3cf41542fc9a":null,"705554c2-0c57-4413-b98e-b6fa795c40d7":null,"0a426706-31b8-4b4f-8d5a-8583b8ac85f6":null,"cde59ba0-a60b-4721-b96c-61401ff28852":null,"d0e2b421-b3f6-4fbc-ae57-4725609225ab":null,"13e1bc4f-d6b4-44b3-9a7d-a1011fb5e989":null,"56197c16-1522-4858-b8e2-45af2d31a85e":null},"queue":{}} \ No newline at end of file diff --git a/testnet.toml b/testnet.toml index e184ed8..f5f90df 100644 --- a/testnet.toml +++ b/testnet.toml @@ -1,17 +1,14 @@ -catchup = true -host = "0.0.0.0:3030" -state_path = "state.db" +catchup = true +host = "0.0.0.0:3030" +state_path = "state.db" [protocol] starting_head = "EfFrYtjeTtpqmFkD3xdB2UJHtfr4dhxQkp6fFHtg6Yav" [rpc] -network = "Testnet" +network = "Testnet" [succinct] contract_address = "0x73876e41ca149853160Ed5BFeC22e3C7bABEA67a" eth_rpc_url = "https://gateway.tenderly.co/public/sepolia" version = "v0.0.4-rc.1" - -#contract_address = "0xa5AA56257a89E972cdf3a2D47032646750B5d9Ba" -# eth_rpc_url = "https://ethereum-holesky-rpc.publicnode.com"