diff --git a/Cargo.lock b/Cargo.lock index 7311e9ce0..0eb9339af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4054,6 +4054,19 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" +[[package]] +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.58.0", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -4118,6 +4131,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "getrandom_or_panic" version = "0.0.3" @@ -5464,7 +5489,10 @@ dependencies = [ "frame-system", "jsonrpsee", "kate", + "kate-recovery", "log", + "moka", + "rayon", "sc-client-api", "sp-api", "sp-blockchain", @@ -6139,6 +6167,19 @@ dependencies = [ "serde", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber 0.3.19", +] + [[package]] name = "lru" version = "0.10.1" @@ -6473,6 +6514,28 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "moka" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener 5.4.0", + "futures-util", + "loom", + "parking_lot 0.12.3", + "portable-atomic", + "rustc_version 0.4.1", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "uuid", +] + [[package]] name = "multiaddr" version = "0.17.1" @@ -8812,6 +8875,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -10678,6 +10747,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -12757,6 +12832,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -13675,6 +13756,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "valuable" version = "0.1.0" @@ -13776,6 +13866,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -14243,6 +14342,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -14262,6 +14371,41 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "windows-registry" version = "0.2.0" @@ -14534,6 +14678,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/node/src/cli.rs b/node/src/cli.rs index baacb0518..4993e7aa8 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -59,6 +59,14 @@ pub struct Cli { #[arg(long, default_value_t = 64, value_parser=kate_max_cells_size_upper_bound)] pub kate_max_cells_size: usize, + /// The maximum size of the evaluation grid cache in MiB. + #[arg(long, default_value_t = 64)] + pub kate_eval_grid_size: u64, + + /// The maximum size of the polynomial grid cache in MiB. + #[arg(long, default_value_t = 64)] + pub kate_poly_grid_size: u64, + /// The name of the network. /// /// This parameter can be used to update the network name and id of the `dev` and `dev_tri` chains. @@ -71,7 +79,7 @@ pub struct Cli { pub tx_state_rpc_enabled: bool, /// The maximum number of results the transaction state RPC will return for a transaction hash. - /// If a transaction hash appears in multiple blocks, the RPC will return only the top `X` transaction states. + /// If a transaction hash appears in multiple blocks, the RPC will return only the top `X` transaction states. /// In most cases, the transaction hash is unique, so this parameter is usually irrelevant. #[clap(long, default_value_t = 10)] pub tx_state_rpc_max_search_results: usize, diff --git a/node/src/rpc.rs b/node/src/rpc.rs index f6f84cd1a..97ab9a25f 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -233,6 +233,8 @@ where io.merge(KateApiMetricsServer::into_rpc(Kate::::new( client.clone(), kate_rpc_deps.max_cells_size, + kate_rpc_deps.eval_grid_size, + kate_rpc_deps.poly_grid_size, )))?; } @@ -240,6 +242,8 @@ where io.merge(KateApiServer::into_rpc(Kate::::new( client, kate_rpc_deps.max_cells_size, + kate_rpc_deps.eval_grid_size, + kate_rpc_deps.poly_grid_size, )))?; } diff --git a/node/src/service.rs b/node/src/service.rs index bb82fa438..c181718e3 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -726,6 +726,8 @@ pub fn new_full(config: Configuration, cli: Cli) -> Result = ::Hash; @@ -27,6 +42,9 @@ pub type MaxRows = ConstU32<64>; pub type Rows = BoundedVec; pub type MaxCells = ConstU32<10_000>; pub type Cells = BoundedVec; +type RTExtractor = ::HeaderExtensionDataFilter; + +static SRS: std::sync::OnceLock = std::sync::OnceLock::new(); pub mod metrics; @@ -40,6 +58,10 @@ pub struct Deps { /// /// Should not be used unless unless you know what you're doing. pub rpc_metrics_enabled: bool, + /// Max size of evaluation grid cache in MiB. + pub eval_grid_size: u64, + /// Max size of polynomial grid cache in MiB. + pub poly_grid_size: u64, } /// # TODO @@ -73,14 +95,42 @@ where #[allow(clippy::type_complexity)] pub struct Kate { client: Arc, + eval_grid_cache: Cache>, + poly_grid_cache: Cache>, max_cells_size: usize, _block: PhantomData, } impl Kate { - pub fn new(client: Arc, max_cells_size: usize) -> Self { + pub fn new( + client: Arc, + max_cells_size: usize, + eval_grid_cache_size: u64, + poly_grid_cach_size: u64, + ) -> Self { + // cache sizes are in MiB + let eval_grid_cache_size = eval_grid_cache_size * 1024 * 1024; + let poly_grid_cach_size = poly_grid_cach_size * 1024 * 1024; + Self { client, + eval_grid_cache: Cache::<_, Arc>::builder() + .weigher(|_, v| { + let n_cells: u32 = v.dims().size(); + n_cells * 32 + 8 + }) + .max_capacity(eval_grid_cache_size) + .build(), + poly_grid_cache: Cache::<_, Arc<(Dimensions, PolynomialGrid)>>::builder() + .weigher(|_, v| { + let n_cells: u32 = v.0.size(); + // currently we support only 2^10 points, and can never have more than 2^32 points + let n_points: u32 = + v.0.width().try_into().expect("Never more than 2^32 points"); + n_cells * 32 + n_points * 32 + }) + .max_capacity(poly_grid_cach_size) + .build(), max_cells_size, _block: PhantomData, } @@ -111,19 +161,16 @@ macro_rules! internal_err { }} } -// ApiRef<'_, dyn ApiExt>, - type Opaques = Vec<::Extrinsic>; type Api<'a, C, B> = ApiRef<'a, >::Api>; impl Kate where - Block: BlockT, + Block: BlockT, ::Header: ExtendedHeader, Client: Send + Sync + 'static, Client: HeaderBackend + ProvideRuntimeApi + BlockBackend, Client::Api: DataAvailApi, - // Extrinsic: TryFrom<::Extrinsic>, { #[allow(clippy::type_complexity)] fn scope( @@ -182,6 +229,65 @@ where self.ensure_block_finalized(&signed_block)?; Ok(signed_block) } + + /// Get the evaluation grid for the given block from cache if available, otherwise construct it. + async fn get_eval_grid(&self, at: Option) -> RpcResult> { + let (_api, at, block_number, block_len, extrinsics, header) = self.scope(at)?; + self.eval_grid_cache + .try_get_with(at, async move { + match header.extension() { + HeaderExtension::V3(ext) => { + if ext.commitment.commitment.is_empty() { + return Err(internal_err!( + "Requested block {at} has empty commitments" + )); + } + }, + }; + + let app_extrinsics = HeaderExtensionBuilderData::from_opaque_extrinsics::< + RTExtractor, + >(block_number, &extrinsics) + .to_app_extrinsics(); + + let grid = EvaluationGrid::from_extrinsics( + app_extrinsics, + MIN_WIDTH, + block_len.cols.0 as usize, + block_len.rows.0 as usize, + Seed::default(), + ) + .map_err(|e| internal_err!("Building evals grid failed: {:?}", e))? + .extend_columns(NonZeroU16::new(2).expect("2>0")) + .map_err(|_| { + internal_err!("Failed to extend the columns of the evaluation grid") + })?; + Ok(Arc::new(grid)) + }) + .await + .map_err(|e| internal_err!("Failed to get evaluation grid: {e:?}")) + } + + /// Get the polynomial grid for the given block from cache if available, otherwise construct it. + async fn get_poly_grid( + &self, + at: Option, + ) -> RpcResult> { + let block_hash = self.at_or_best(at); + self.poly_grid_cache + .try_get_with(block_hash, async move { + let evals = self.get_eval_grid(Some(block_hash)).await?; + let polys = evals + .make_polynomial_grid() + .map_err(|e| internal_err!("Error getting polynomial grid {:?}", e))?; + Ok::, ErrorObject<'static>>(Arc::new(( + evals.dims(), + polys, + ))) + }) + .await + .map_err(|e| internal_err!("Failed to construct polynomial grid: {e:?}")) + } } #[async_trait] @@ -196,22 +302,31 @@ where async fn query_rows(&self, rows: Rows, at: Option>) -> RpcResult> { let _metric_observer = MetricObserver::new(ObserveKind::KateQueryRows); - let (api, at, number, block_len, extrinsics, header) = self.scope(at)?; - - match header.extension() { - HeaderExtension::V3(ext) => { - if ext.commitment.commitment.is_empty() { - return Err(internal_err!("Requested block {at} has empty commitments")); - } - }, - }; - - let grid_rows = api - .rows(at, number, extrinsics, block_len, rows.into()) - .map_err(|kate_err| internal_err!("Failed Kate rows: {kate_err:?}"))? - .map_err(|api_err| internal_err!("Failed API: {api_err:?}"))?; - - Ok(grid_rows) + let grid = self.get_eval_grid(at).await?; + + let selected_rows = rows + .iter() + .map(|&row| usize::try_from(row)) + .collect::, _>>() + .map_err(|_| internal_err!("Failed to convert row indexes"))?; + + let rows_data = selected_rows + .into_par_iter() + .map(|row_idx| { + grid.row(row_idx) + .ok_or_else(|| internal_err!("Row does not exist: {row_idx}")) + .and_then(|row| { + row.iter() + .map(|scalar| scalar.to_bytes().map(GRawScalar::from)) + .collect::, _>>() + .map_err(|_| { + internal_err!("Failed to convert scalar for row {row_idx}") + }) + }) + }) + .collect::, _>>()?; + + Ok(rows_data) } async fn query_proof( @@ -220,36 +335,52 @@ where at: Option>, ) -> RpcResult> { if cells.len() > self.max_cells_size { - return Err( - internal_err!( - "Cannot query ({}) more than {} amount of cells per request. Either increase the max cells size (--kate-max-cells-size) or query less amount of cells per request.", - cells.len(), - self.max_cells_size - ) - ); + return Err(internal_err!( + "Cannot query ({}) more than {} cells per request. Either increase the max cells size (--kate-max-cells-size) or query fewer cells.", + cells.len(), + self.max_cells_size + )); } let _metric_observer = MetricObserver::new(ObserveKind::KateQueryProof); - - let (api, at, number, block_len, extrinsics, header) = self.scope(at)?; - match header.extension() { - HeaderExtension::V3(ext) => { - if ext.commitment.commitment.is_empty() { - return Err(internal_err!("Requested block {at} has empty commitments")); - } - }, - }; - - let cells = cells - .into_iter() - .map(|cell| (cell.row.0, cell.col.0)) - .collect::>(); - let proof = api - .proof(at, number, extrinsics, block_len, cells) - .map_err(|kate_err| internal_err!("KateApi::proof failed: {kate_err:?}"))? - .map_err(|api_err| internal_err!("Failed API: {api_err:?}"))?; - - Ok(proof) + let grid = self.get_eval_grid(at).await?; + let poly = self.get_poly_grid(at).await?; + let srs = SRS.get_or_init(multiproof_params); + + let proofs: Result, ErrorObject> = cells + .par_iter() + .map(|cell| { + let (row, col) = (cell.row.0 as usize, cell.col.0 as usize); + + let data = grid.get(row, col).ok_or_else(|| { + internal_err!( + "Invalid cell {:?} for grid dimensions {:?}", + cell, + grid.dims() + ) + })?; + + let proof = poly + .1 + .proof(srs, cell) + .map_err(|e| internal_err!("Unable to generate proof: {:?}", e))?; + + Ok(( + GRawScalar::from( + data.to_bytes() + .map_err(|_| internal_err!("Failed to serialize data"))?, + ), + GProof::try_from( + proof + .to_bytes() + .map_err(|_| internal_err!("Failed to serialize proof"))? + .to_vec(), + ) + .map_err(|_| internal_err!("Failed to convert proof"))?, + )) + }) + .collect(); + proofs.map_err(|e| internal_err!("Failed to generate proof: {:?}", e)) } async fn query_block_length(&self, at: Option>) -> RpcResult { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1912ecc09..2c1922cb0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -327,8 +327,8 @@ mod tests { const DA_CALL_SIZE: usize = size_of::>(); const SYSTEM_CALL_SIZE: usize = size_of::>(); - #[test_case(RUNTIME_CALL_SIZE => 192)] - #[test_case(DA_CALL_SIZE => 56)] + #[test_case(RUNTIME_CALL_SIZE => 208)] + #[test_case(DA_CALL_SIZE => 64)] #[test_case(SYSTEM_CALL_SIZE => 40)] fn call_size(size: usize) -> usize { const MAX_CALL_SIZE: usize = 208;