Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
9 changes: 9 additions & 0 deletions rs/https_outcalls/consensus/src/payload_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ impl CanisterHttpPayloadBuilderImpl {
.find(|share| share.signature.signer == *node_id)
.map(|correct_share| (metadata, vec![*correct_share]))
}
Some(Replication::Flexible { .. }) => {
// TODO(flexible-http-outcalls): implement Flexible payload construction
None
}
None | Some(Replication::FullyReplicated) => {
let signers: BTreeSet<_> =
shares.iter().map(|share| share.signature.signer).collect();
Expand Down Expand Up @@ -529,6 +533,11 @@ impl CanisterHttpPayloadBuilderImpl {
..
}) => (vec![*node_id], 1),
None
// TODO(flexible-http-outcalls): implement Flexible payload validation
| Some(&CanisterHttpRequestContext {
replication: Replication::Flexible { .. },
..
})
| Some(&CanisterHttpRequestContext {
replication: Replication::FullyReplicated,
..
Expand Down
2 changes: 2 additions & 0 deletions rs/https_outcalls/consensus/src/pool_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@ impl CanisterHttpPoolManagerImpl {
));
}
}
// TODO(flexible-http-outcalls): implement proper Flexible validation
Replication::Flexible { .. } => {}
}

let node_is_in_committee = self
Expand Down
7 changes: 7 additions & 0 deletions rs/protobuf/def/state/metadata/v1/metadata.proto
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,16 @@ message Replication {
oneof replication_type {
google.protobuf.Empty fully_replicated = 1;
types.v1.NodeId non_replicated = 2;
FlexibleReplication flexible = 3;
}
}

message FlexibleReplication {
repeated types.v1.NodeId committee = 1;
uint32 min_responses = 2;
uint32 max_responses = 3;
}

message CanisterHttpRequestContextTree {
uint64 callback_id = 1;
CanisterHttpRequestContext context = 2;
Expand Down
13 changes: 12 additions & 1 deletion rs/protobuf/src/gen/state/state.metadata.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ pub mod pricing_version {
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Replication {
#[prost(oneof = "replication::ReplicationType", tags = "1, 2")]
#[prost(oneof = "replication::ReplicationType", tags = "1, 2, 3")]
pub replication_type: ::core::option::Option<replication::ReplicationType>,
}
/// Nested message and enum types in `Replication`.
Expand All @@ -258,9 +258,20 @@ pub mod replication {
FullyReplicated(()),
#[prost(message, tag = "2")]
NonReplicated(super::super::super::super::types::v1::NodeId),
#[prost(message, tag = "3")]
Flexible(super::FlexibleReplication),
}
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FlexibleReplication {
#[prost(message, repeated, tag = "1")]
pub committee: ::prost::alloc::vec::Vec<super::super::super::types::v1::NodeId>,
#[prost(uint32, tag = "2")]
pub min_responses: u32,
#[prost(uint32, tag = "3")]
pub max_responses: u32,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CanisterHttpRequestContextTree {
#[prost(uint64, tag = "1")]
pub callback_id: u64,
Expand Down
119 changes: 82 additions & 37 deletions rs/types/types/src/canister_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ pub enum Replication {
FullyReplicated,
/// The request is not replicated, i.e. only the node with the given `NodeId` will attempt the http request.
NonReplicated(NodeId),
/// The request is sent to a committee of nodes that all attempt the http request.
/// The canister receives between `min_responses` and `max_responses` (potentially differing) responses.
Flexible {
committee: BTreeSet<NodeId>,
min_responses: u32,
max_responses: u32,
},
}

#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize, Serialize, FromRepr)]
Expand All @@ -179,15 +186,30 @@ pub enum PricingVersion {

impl From<&CanisterHttpRequestContext> for pb_metadata::CanisterHttpRequestContext {
fn from(context: &CanisterHttpRequestContext) -> Self {
let replication_type = match context.replication {
let replication_type = match &context.replication {
Replication::FullyReplicated => {
pb_metadata::replication::ReplicationType::FullyReplicated(())
}
Replication::NonReplicated(node_id) => {
pb_metadata::replication::ReplicationType::NonReplicated(node_id_into_protobuf(
node_id,
*node_id,
))
}
Replication::Flexible {
committee,
min_responses,
max_responses,
} => pb_metadata::replication::ReplicationType::Flexible(
pb_metadata::FlexibleReplication {
committee: committee
.iter()
.copied()
.map(node_id_into_protobuf)
.collect(),
min_responses: *min_responses,
max_responses: *max_responses,
},
),
};

let replication_message = pb_metadata::Replication {
Expand Down Expand Up @@ -269,6 +291,17 @@ impl TryFrom<pb_metadata::CanisterHttpRequestContext> for CanisterHttpRequestCon
Some(pb_metadata::replication::ReplicationType::NonReplicated(node_id)) => {
Replication::NonReplicated(node_id_try_from_protobuf(node_id)?)
}
Some(pb_metadata::replication::ReplicationType::Flexible(flexible)) => {
Replication::Flexible {
committee: flexible
.committee
.into_iter()
.map(node_id_try_from_protobuf)
.collect::<Result<BTreeSet<NodeId>, ProxyDecodeError>>()?,
min_responses: flexible.min_responses,
max_responses: flexible.max_responses,
}
}
None => Replication::FullyReplicated,
},
None => Replication::FullyReplicated,
Expand Down Expand Up @@ -1016,43 +1049,55 @@ mod tests {

#[test]
fn canister_http_request_context_proto_round_trip() {
let initial = CanisterHttpRequestContext {
url: "https://example.com".to_string(),
headers: vec![CanisterHttpHeader {
name: "Content-Type".to_string(),
value: "application/json".to_string(),
}],
body: Some(b"{\"hello\":\"world\"}".to_vec()),
max_response_bytes: Some(NumBytes::from(1234)),
http_method: CanisterHttpMethod::POST,
transform: Some(Transform {
method_name: "transform_response".to_string(),
context: vec![1, 2, 3],
}),
request: Request {
receiver: CanisterId::ic_00(),
sender: CanisterId::ic_00(),
sender_reply_callback: CallbackId::from(3),
payment: Cycles::new(10),
method_name: "transform".to_string(),
method_payload: Vec::new(),
metadata: Default::default(),
deadline: NO_DEADLINE,
},
time: UNIX_EPOCH,
replication: Replication::NonReplicated(node_test_id(42)),
pricing_version: PricingVersion::PayAsYouGo,
refund_status: RefundStatus {
refundable_cycles: Cycles::new(13_000_000),
per_replica_allowance: Cycles::new(1_000_000),
refunded_cycles: Cycles::new(123),
refunding_nodes: BTreeSet::from([node_test_id(1), node_test_id(2)]),
let replications = [
Replication::FullyReplicated,
Replication::NonReplicated(node_test_id(42)),
Replication::Flexible {
committee: BTreeSet::from([node_test_id(1), node_test_id(2), node_test_id(3)]),
min_responses: 2,
max_responses: 3,
},
};
];

for replication in replications {
let initial = CanisterHttpRequestContext {
url: "https://example.com".to_string(),
headers: vec![CanisterHttpHeader {
name: "Content-Type".to_string(),
value: "application/json".to_string(),
}],
body: Some(b"{\"hello\":\"world\"}".to_vec()),
max_response_bytes: Some(NumBytes::from(1234)),
http_method: CanisterHttpMethod::POST,
transform: Some(Transform {
method_name: "transform_response".to_string(),
context: vec![1, 2, 3],
}),
request: Request {
receiver: CanisterId::ic_00(),
sender: CanisterId::ic_00(),
sender_reply_callback: CallbackId::from(3),
payment: Cycles::new(10),
method_name: "transform".to_string(),
method_payload: Vec::new(),
metadata: Default::default(),
deadline: NO_DEADLINE,
},
time: UNIX_EPOCH,
replication,
pricing_version: PricingVersion::PayAsYouGo,
refund_status: RefundStatus {
refundable_cycles: Cycles::new(13_000_000),
per_replica_allowance: Cycles::new(1_000_000),
refunded_cycles: Cycles::new(123),
refunding_nodes: BTreeSet::from([node_test_id(1), node_test_id(2)]),
},
};

let pb: pb_metadata::CanisterHttpRequestContext = (&initial).into();
let round_trip: CanisterHttpRequestContext = pb.try_into().unwrap();
assert_eq!(initial, round_trip);
let pb: pb_metadata::CanisterHttpRequestContext = (&initial).into();
let round_trip: CanisterHttpRequestContext = pb.try_into().unwrap();
assert_eq!(initial, round_trip);
}
}

#[test]
Expand Down
Loading