Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
59d53d2
feat: reactively fetch delegations
pierugo-dfinity Jul 8, 2026
0040823
Automatically updated Cargo*.lock
Jul 8, 2026
e17523d
fix: clippy
pierugo-dfinity Jul 9, 2026
fbb2bde
test: test canister ranges changing
pierugo-dfinity Jul 23, 2026
ecc19a3
feat: in Tree layout, check for subset only
pierugo-dfinity Jul 23, 2026
67dee00
style + docs
pierugo-dfinity Jul 23, 2026
5be6b62
fix: keep original errors
pierugo-dfinity Jul 27, 2026
805f44b
fix: MissedTickBehavior::Delay
pierugo-dfinity Jul 27, 2026
cd8ca34
fix: return Err on empty children
pierugo-dfinity Jul 27, 2026
627dec3
style: rename
pierugo-dfinity Jul 27, 2026
ae4386a
fix: unit test
pierugo-dfinity Jul 27, 2026
b1534a2
test: breadcrumbs
pierugo-dfinity Jul 28, 2026
a0ab96e
feat: add metrics
pierugo-dfinity Jul 29, 2026
4ad8e6d
feat: properly implement lookup_range
pierugo-dfinity Jul 29, 2026
c1b6f56
Revert "feat: properly implement lookup_range"
pierugo-dfinity Jul 30, 2026
23786f1
refactor: move nns_delegation_reader to own crate, separate builder a…
pierugo-dfinity Jul 30, 2026
85cfc37
refactor: move validation to new nns_delegation_reader crate
pierugo-dfinity Jul 30, 2026
3a1cca2
feat: only look at flat layout
pierugo-dfinity Jul 30, 2026
1531f13
refactor: simplify API
pierugo-dfinity Jul 30, 2026
ffa5ccc
Merge branch 'master' into pierugo/delegation-manager/refactor-test
pierugo-dfinity Jul 30, 2026
3bca8c5
fix: revert lookup_range fn
pierugo-dfinity Jul 30, 2026
cf5c307
refactor: move tests
pierugo-dfinity Jul 30, 2026
15dc026
REVERT ME: return 503
pierugo-dfinity Jul 31, 2026
209cf5b
refactor: simplification
pierugo-dfinity Jul 31, 2026
ebcc95d
feat: reduce external API
pierugo-dfinity Jul 31, 2026
da567ad
style: remove polluting log
pierugo-dfinity Jul 31, 2026
f3789fb
Merge branch 'pierugo/delegation-manager/reactive-fetch' into pierugo…
pierugo-dfinity Jul 31, 2026
4a2d7d2
working http handler tests
pierugo-dfinity Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ members = [
"rs/http_endpoints/async_utils",
"rs/http_endpoints/metrics",
"rs/http_endpoints/nns_delegation_manager",
"rs/http_endpoints/nns_delegation_reader",
"rs/http_endpoints/public",
"rs/http_endpoints/test_agent",
"rs/http_endpoints/xnet",
Expand Down
103 changes: 75 additions & 28 deletions rs/execution_environment/src/query_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ use ic_crypto_tree_hash::{Label, LabeledTree, LabeledTree::SubTree, flatmap};
use ic_cycles_account_manager::CyclesAccountManager;
use ic_error_types::{ErrorCode, UserError};
use ic_interfaces::execution_environment::{
QueryExecutionError, QueryExecutionInput, QueryExecutionResponse, QueryExecutionService,
TransformExecutionInput, TransformExecutionService,
CanisterRangesCheck, CanisterRangesFilter, NNSDelegationBuilder, QueryExecutionError,
QueryExecutionInput, QueryExecutionResponse, QueryExecutionService, TransformExecutionInput,
TransformExecutionService,
};
use ic_interfaces_state_manager::{Labeled, StateReader};
use ic_logger::ReplicaLogger;
Expand All @@ -37,7 +38,7 @@ use ic_types::messages::CertificateDelegationMetadata;
use ic_types::{
CanisterId, NumInstructions,
ingress::WasmResult,
messages::{Blob, Certificate, CertificateDelegation, Query},
messages::{Blob, Certificate, Query},
};
use prometheus::{Histogram, histogram_opts, labels};
use serde::Serialize;
Expand Down Expand Up @@ -73,11 +74,29 @@ fn into_cbor<R: Serialize>(r: &R) -> Vec<u8> {
ser.into_inner()
}

/// The result of [`get_latest_certified_state_and_data_certificate`]: the certified
/// state together with the data certificate built from it (with the NNS delegation
/// embedded) and the metadata of the embedded delegation, if any.
struct CertifiedStateWithDataCertificate {
state: Labeled<Arc<ReplicatedState>>,
data_certificate: Vec<u8>,
certificate_delegation_metadata: Option<CertificateDelegationMetadata>,
}

/// Reads the latest certified state and builds the data certificate for the canister
/// from it, with the NNS delegation (built with `canister_ranges_filter`) embedded.
/// Also returns the metadata of the embedded delegation, if any.
///
/// The delegation is only embedded after having been verified (according to
/// `canister_ranges_check`) to be consistent with the exact certified state which the
/// data certificate is built from.
fn get_latest_certified_state_and_data_certificate(
state_reader: Arc<dyn StateReader<State = ReplicatedState>>,
certificate_delegation: Option<CertificateDelegation>,
nns_delegation_builder: Option<Arc<NNSDelegationBuilder>>,
canister_ranges_filter: CanisterRangesFilter,
canister_ranges_check: CanisterRangesCheck,
canister_id: CanisterId,
) -> Option<(Labeled<Arc<ReplicatedState>>, Vec<u8>)> {
) -> Result<CertifiedStateWithDataCertificate, QueryExecutionError> {
// The path to fetch the data certificate for the canister.
let path = SubTree(flatmap! {
label("canister") => SubTree(
Expand All @@ -90,18 +109,44 @@ fn get_latest_certified_state_and_data_certificate(
label("time") => LabeledTree::Leaf(())
});

state_reader
.read_certified_state(&path)
.map(|(state, tree, cert)| {
(
Labeled::new(cert.height, state),
into_cbor(&Certificate {
tree,
signature: Blob(cert.signed.signature.signature.get().0),
delegation: certificate_delegation,
}),
)
})
let Some((state, tree, cert)) = state_reader.read_certified_state(&path) else {
return Err(QueryExecutionError::CertifiedStateUnavailable);
};

// Only embed a delegation which is consistent with the certified state which the
// data certificate is built from.
let (certificate_delegation, certificate_delegation_metadata) = match nns_delegation_builder {
None => (None, None),
Some(builder) => {
let network_topology = &state.metadata.network_topology;
let (delegation, metadata) = builder
.build_verified(canister_ranges_filter, canister_ranges_check, |subnet_id| {
network_topology
.subnets_for_certification()
.get(&subnet_id)
.map(|subnet_topology| {
(
subnet_topology.public_key.clone(),
network_topology
.routing_table_for_certification()
.ranges(subnet_id),
)
})
})
.map_err(|_err| QueryExecutionError::DelegationInconsistentWithState)?;
(Some(delegation), Some(metadata))
}
};

Ok(CertifiedStateWithDataCertificate {
state: Labeled::new(cert.height, state),
data_certificate: into_cbor(&Certificate {
tree,
signature: Blob(cert.signed.signature.signature.get().0),
delegation: certificate_delegation,
}),
certificate_delegation_metadata,
})
}

fn label<T: Into<Label>>(t: T) -> Label {
Expand Down Expand Up @@ -466,7 +511,9 @@ impl Service<QueryExecutionInput> for HttpQueryHandler {
&mut self,
QueryExecutionInput {
query,
certificate_delegation_with_metadata,
nns_delegation_builder,
canister_ranges_filter,
canister_ranges_check,
}: QueryExecutionInput,
) -> Self::Future {
let internal = Arc::clone(&self.internal);
Expand All @@ -487,18 +534,18 @@ impl Service<QueryExecutionInput> for HttpQueryHandler {
// Otherwise, retrieving the state in the Query service in `http_endpoints` can lead to queries being queued up,
// with a reference to older states which can cause out-of-memory crashes.

let (certificate_delegation, certificate_delegation_metadata) =
match certificate_delegation_with_metadata {
Some((delegation, metadata)) => (Some(delegation), Some(metadata)),
None => (None, None),
};

let result = match get_latest_certified_state_and_data_certificate(
state_reader,
certificate_delegation,
nns_delegation_builder,
canister_ranges_filter,
canister_ranges_check,
query.receiver,
) {
Some((state, cert)) => {
Ok(CertifiedStateWithDataCertificate {
state,
data_certificate,
certificate_delegation_metadata,
}) => {
let time = state.get_ref().metadata.batch_time;

let certified_height_used_for_execution = state.height();
Expand All @@ -511,7 +558,7 @@ impl Service<QueryExecutionInput> for HttpQueryHandler {

let data_certificate_with_delegation_metadata =
DataCertificateWithDelegationMetadata {
data_certificate: cert,
data_certificate,
certificate_delegation_metadata,
};

Expand All @@ -526,7 +573,7 @@ impl Service<QueryExecutionInput> for HttpQueryHandler {

Ok((response, time))
}
None => Err(QueryExecutionError::CertifiedStateUnavailable),
Err(err) => Err(err),
};

let _ = tx.send(Ok(result));
Expand Down
28 changes: 6 additions & 22 deletions rs/http_endpoints/nns_delegation_manager/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
load("//bazel:defs.bzl", "rust_ic_bench")

package(default_visibility = [
"//rs/http_endpoints:__subpackages__",
Expand All @@ -19,11 +18,14 @@ rust_library(
"//rs/crypto/tls_interfaces",
"//rs/crypto/tree_hash",
"//rs/crypto/utils/threshold_sig_der",
"//rs/http_endpoints/nns_delegation_reader",
"//rs/interfaces/registry",
"//rs/interfaces/state_manager",
"//rs/monitoring/logger",
"//rs/monitoring/metrics",
"//rs/registry/helpers",
"//rs/registry/subnet_type",
"//rs/replicated_state",
"//rs/types/types",
"@crate_index//:axum",
"@crate_index//:futures",
Expand All @@ -34,7 +36,6 @@ rust_library(
"@crate_index//:prometheus",
"@crate_index//:rand",
"@crate_index//:rustls",
"@crate_index//:serde",
"@crate_index//:serde_cbor",
"@crate_index//:slog",
"@crate_index//:tokio",
Expand Down Expand Up @@ -63,13 +64,14 @@ rust_test(
"//rs/crypto/tls_interfaces/mocks",
"//rs/crypto/tree_hash",
"//rs/crypto/utils/threshold_sig_der",
"//rs/http_endpoints/nns_delegation_manager/test_utils",
"//rs/interfaces/state_manager/mocks",
"//rs/monitoring/logger",
"//rs/monitoring/metrics",
"//rs/protobuf",
"//rs/registry/fake",
"//rs/registry/keys",
"//rs/registry/proto_data_provider",
"//rs/registry/routing_table",
"//rs/test_utilities/registry",
"//rs/test_utilities/types",
"@crate_index//:assert_matches",
Expand All @@ -78,25 +80,7 @@ rust_test(
"@crate_index//:hyper",
"@crate_index//:rand",
"@crate_index//:rcgen",
"@crate_index//:tokio",
],
)

rust_ic_bench(
name = "nns_delegation_reader_bench",
testonly = True,
srcs = ["benches/nns_delegation_reader.rs"],
deps = [
# Keep sorted.
":nns_delegation_manager",
"//rs/crypto/tree_hash",
"//rs/http_endpoints/nns_delegation_manager/test_utils",
"//rs/monitoring/logger",
"//rs/test_utilities/types",
"//rs/types/types",
"@crate_index//:criterion",
"@crate_index//:pprof",
"@crate_index//:serde_cbor",
"@crate_index//:rstest",
"@crate_index//:tokio",
],
)
14 changes: 6 additions & 8 deletions rs/http_endpoints/nns_delegation_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ description.workspace = true
documentation.workspace = true
edition.workspace = true

[[bench]]
name = "nns_delegation_reader"
harness = false

[dependencies]
axum = { workspace = true }
futures = { workspace = true }
Expand All @@ -23,15 +19,17 @@ ic-crypto-tls-interfaces = { path = "../../crypto/tls_interfaces" }
ic-crypto-tree-hash = { path = "../../crypto/tree_hash" }
ic-crypto-utils-threshold-sig-der = { path = "../../crypto/utils/threshold_sig_der" }
ic-interfaces-registry = { path = "../../interfaces/registry" }
ic-interfaces-state-manager = { path = "../../interfaces/state_manager" }
ic-logger = { path = "../../monitoring/logger" }
ic-metrics = { path = "../../monitoring/metrics" }
ic-nns-delegation-reader = { path = "../nns_delegation_reader" }
ic-registry-client-helpers = { path = "../../registry/helpers" }
ic-registry-subnet-type = { path = "../../registry/subnet_type" }
ic-replicated-state = { path = "../../replicated_state" }
ic-types = { path = "../../types/types" }
prometheus = { workspace = true }
rand = { workspace = true }
rustls = { workspace = true }
serde = { workspace = true }
serde_cbor = { workspace = true }
slog = { workspace = true }
tokio = { workspace = true }
Expand All @@ -43,15 +41,15 @@ webpki-roots = { workspace = true }
[dev-dependencies]
assert_matches = { workspace = true }
axum-server = { workspace = true }
criterion = { workspace = true }
ic-certification-test-utils = { path = "../../certification/test-utils" }
ic-crypto-tls-interfaces-mocks = { path = "../../crypto/tls_interfaces/mocks" }
ic-nns-delegation-manager-test-utils = { path = "test_utils" }
ic-interfaces-state-manager-mocks = { path = "../../interfaces/state_manager/mocks" }
ic-protobuf = { path = "../../protobuf" }
ic-registry-client-fake = { path = "../../registry/fake" }
ic-registry-keys = { path = "../../registry/keys" }
ic-registry-proto-data-provider = { path = "../../registry/proto_data_provider" }
ic-registry-routing-table = { path = "../../registry/routing_table" }
ic-test-utilities-registry = { path = "../../test_utilities/registry" }
ic-test-utilities-types = { path = "../../test_utilities/types" }
pprof = { workspace = true }
rcgen = { workspace = true }
rstest = { workspace = true }
Loading
Loading