Skip to content

Commit a77e854

Browse files
committed
Merge branch 'leon/payload_builder_overhaul' into 'master'
Payload Builder Refactor (Part 1) This MR refactors the way, the `BatchPayload` is being constructed. The idea is quite simple: I am introducing a new trait called `BatchPayloadSectionBuilder`. It is supposed to replace the traits `XNetPayloadBuilder`, `SelfValidatingPayloadBuilder` and `IngressSelector` (and also the not yet implemented `CanisterHttpPayloadBuilder`), as they all do the same things. Then inside consensus, there is wrapper called `BatchPayloadSectionAdapter`, that is responsible for mapping the sections into the `BatchPayload`. This boilerplate is unfortunately necessary, to eliminate the Generic in `BatchPayloadSectionBuilder`. My original design would use another trait, however, this approach was prevented because of a [long standing compiler bug](rust-lang/rust#20400). On the validation side, every validation call also returns its `byte_size`, so we can check, that the overall size is not exceeded either. This MR also contains the implementations of `BatchPayloadSectionBuilder` on top the existing traits. This way, We don't need to touch test code for now. We might want to remove the old traits at a later date. The payload builder itself just has a Vec<BatchPayloadSectionAdapter> that it calls in a rotating order on a default `BatchPayload`. **NOTE**: This MR does NOT contain the functional changes to `payload_builder.rs`. Those will be made in a separate MR, to keep the scope of this MR small. See merge request dfinity-lab/public/ic!3543
2 parents fbfce20 + 1b53f7a commit a77e854

File tree

11 files changed

+256
-10
lines changed

11 files changed

+256
-10
lines changed

rs/bitcoin/consensus/src/payload_builder.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::metrics::BitcoinPayloadBuilderMetrics;
22
use ic_btc_types_internal::{BitcoinAdapterResponse, BitcoinAdapterResponseWrapper};
3-
use ic_interfaces::self_validating_payload::{
4-
SelfValidatingPayloadBuilder, SelfValidatingPayloadValidationError,
3+
use ic_interfaces::{
4+
payload::BatchPayloadSectionBuilder,
5+
self_validating_payload::{SelfValidatingPayloadBuilder, SelfValidatingPayloadValidationError},
56
};
67
use ic_interfaces_bitcoin_adapter_client::{BitcoinAdapterClient, Options};
78
use ic_interfaces_state_manager::{StateManager, StateManagerError};
@@ -11,7 +12,8 @@ use ic_registry_subnet_features::BitcoinFeature;
1112
use ic_replicated_state::ReplicatedState;
1213
use ic_types::{
1314
batch::{SelfValidatingPayload, ValidationContext},
14-
Height, NumBytes,
15+
consensus::Payload,
16+
CountBytes, Height, NumBytes, Time,
1517
};
1618
use std::sync::Arc;
1719
use thiserror::Error;
@@ -231,5 +233,31 @@ impl SelfValidatingPayloadBuilder for BitcoinPayloadBuilder {
231233
}
232234
}
233235

236+
impl BatchPayloadSectionBuilder<SelfValidatingPayload> for BitcoinPayloadBuilder {
237+
fn build_payload(
238+
&self,
239+
validation_context: &ValidationContext,
240+
max_size: NumBytes,
241+
_priority: usize,
242+
past_payloads: &[(Height, Time, Payload)],
243+
) -> (SelfValidatingPayload, NumBytes) {
244+
let past_payloads = self.filter_past_payloads(past_payloads);
245+
let payload =
246+
self.get_self_validating_payload(validation_context, &past_payloads, max_size);
247+
let size = NumBytes::new(payload.count_bytes() as u64);
248+
(payload, size)
249+
}
250+
251+
fn validate_payload(
252+
&self,
253+
payload: &SelfValidatingPayload,
254+
validation_context: &ValidationContext,
255+
past_payloads: &[(Height, Time, Payload)],
256+
) -> Result<NumBytes, SelfValidatingPayloadValidationError> {
257+
let past_payloads = self.filter_past_payloads(past_payloads);
258+
self.validate_self_validating_payload(payload, validation_context, &past_payloads)
259+
}
260+
}
261+
234262
#[cfg(test)]
235263
mod tests;

rs/consensus/src/consensus.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod malicious_consensus;
1111
pub(crate) mod membership;
1212
pub(crate) mod metrics;
1313
mod notary;
14+
mod payload;
1415
pub mod payload_builder;
1516
pub mod pool_reader;
1617
mod prelude;

rs/consensus/src/consensus/payload.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// TODO: Remove after implementing functional change
2+
#![allow(dead_code)]
3+
4+
//! Consensus internal traits that are used in the `payload_builder`.
5+
use ic_interfaces::{consensus::PayloadValidationError, payload::BatchPayloadSectionBuilder};
6+
use ic_types::{
7+
batch::{BatchPayload, IngressPayload, SelfValidatingPayload, ValidationContext, XNetPayload},
8+
consensus::Payload,
9+
Height, NumBytes, Time,
10+
};
11+
12+
// NOTE: Unfortunately, the BatchPayloadSectionAdapter has to be implemented as an enum, because of a compiler bug,
13+
// that effectively prevents mutually exclusive traits to be implemted.
14+
// See: https://github.com/rust-lang/rust/issues/20400
15+
16+
/// Maps [`BatchPayloadSectionBuilder`] implementations onto the
17+
/// [`BatchPayload`]. By requiring this adapter, we can eliminate the generic inside [`BatchPayloadSectionBuilder`] and
18+
/// make sure that only the consensus crate has access to the whole [`BatchPayload`].
19+
enum BatchPayloadSectionAdapter {
20+
Ingress(Box<dyn BatchPayloadSectionBuilder<IngressPayload>>),
21+
XNet(Box<dyn BatchPayloadSectionBuilder<XNetPayload>>),
22+
SelfValidating(Box<dyn BatchPayloadSectionBuilder<SelfValidatingPayload>>),
23+
}
24+
25+
impl BatchPayloadSectionAdapter {
26+
fn build_payload(
27+
&self,
28+
payload: &mut BatchPayload,
29+
validation_context: &ValidationContext,
30+
max_size: NumBytes,
31+
priority: usize,
32+
past_payloads: &[(Height, Time, Payload)],
33+
) -> NumBytes {
34+
match self {
35+
Self::Ingress(builder) => {
36+
let (payload_section, size) =
37+
builder.build_payload(validation_context, max_size, priority, past_payloads);
38+
payload.ingress = payload_section;
39+
size
40+
}
41+
Self::XNet(builder) => {
42+
let (payload_section, size) =
43+
builder.build_payload(validation_context, max_size, priority, past_payloads);
44+
payload.xnet = payload_section;
45+
size
46+
}
47+
Self::SelfValidating(builder) => {
48+
let (payload_section, size) =
49+
builder.build_payload(validation_context, max_size, priority, past_payloads);
50+
payload.self_validating = payload_section;
51+
size
52+
}
53+
}
54+
}
55+
56+
fn validate_payload(
57+
&self,
58+
payload: &BatchPayload,
59+
validation_context: &ValidationContext,
60+
past_payloads: &[(Height, Time, Payload)],
61+
) -> Result<NumBytes, PayloadValidationError> {
62+
match self {
63+
Self::Ingress(builder) => builder
64+
.validate_payload(&payload.ingress, validation_context, past_payloads)
65+
.map_err(PayloadValidationError::from),
66+
Self::XNet(builder) => builder
67+
.validate_payload(&payload.xnet, validation_context, past_payloads)
68+
.map_err(PayloadValidationError::from),
69+
Self::SelfValidating(builder) => builder
70+
.validate_payload(&payload.self_validating, validation_context, past_payloads)
71+
.map_err(PayloadValidationError::from),
72+
}
73+
}
74+
}

rs/consensus/src/consensus/payload_builder.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Payload creation/validation subcomponent
22
3-
use crate::consensus::metrics::PayloadBuilderMetrics;
3+
use crate::consensus::{block_maker::SubnetRecords, metrics::PayloadBuilderMetrics};
44
use ic_interfaces::{
55
consensus::{PayloadPermanentError, PayloadTransientError, PayloadValidationError},
66
ingress_manager::IngressSelector,
@@ -21,9 +21,7 @@ use ic_types::{
2121
};
2222
use std::sync::Arc;
2323

24-
use super::block_maker::SubnetRecords;
25-
26-
/// The PayloadBuilder is responsible for creating and validating payload that
24+
/// The [`PayloadBuilder`] is responsible for creating and validating payload that
2725
/// is included in consensus blocks.
2826
pub trait PayloadBuilder: Send + Sync {
2927
/// Produces a payload that is valid given `past_payloads` and `context`.

rs/ingress_manager/src/ingress_selector.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use ic_interfaces::{
1212
IngressTransientError,
1313
},
1414
ingress_pool::{IngressPoolSelect, SelectResult},
15+
payload::BatchPayloadSectionBuilder,
1516
validation::{ValidationError, ValidationResult},
1617
};
1718
use ic_interfaces_state_manager::StateManagerError;
@@ -272,6 +273,32 @@ impl<'a> IngressSelector for IngressManager {
272273
}
273274
}
274275

276+
impl BatchPayloadSectionBuilder<IngressPayload> for IngressManager {
277+
fn build_payload(
278+
&self,
279+
validation_context: &ValidationContext,
280+
max_size: NumBytes,
281+
_priority: usize,
282+
past_payloads: &[(Height, Time, Payload)],
283+
) -> (IngressPayload, NumBytes) {
284+
let past_payloads = self.filter_past_payloads(past_payloads, validation_context);
285+
let payload = self.get_ingress_payload(&past_payloads, validation_context, max_size);
286+
let size = NumBytes::new(payload.count_bytes() as u64);
287+
(payload, size)
288+
}
289+
290+
fn validate_payload(
291+
&self,
292+
payload: &IngressPayload,
293+
validation_context: &ValidationContext,
294+
past_payloads: &[(Height, Time, Payload)],
295+
) -> Result<NumBytes, IngressPayloadValidationError> {
296+
let past_payloads = self.filter_past_payloads(past_payloads, validation_context);
297+
self.validate_ingress_payload(payload, &past_payloads, validation_context)?;
298+
Ok(NumBytes::new(payload.count_bytes() as u64))
299+
}
300+
}
301+
275302
impl IngressManager {
276303
#[allow(clippy::too_many_arguments)]
277304
fn validate_ingress(

rs/interfaces/src/ingress_manager.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
use crate::{
33
execution_environment::{CanisterOutOfCyclesError, IngressHistoryError},
44
ingress_pool::{ChangeSet, IngressPool},
5+
payload::BatchPayloadSectionType,
56
validation::{ValidationError, ValidationResult},
67
};
78
use ic_types::{
@@ -80,6 +81,11 @@ pub enum IngressTransientError {
8081
pub type IngressPayloadValidationError =
8182
ValidationError<IngressPermanentError, IngressTransientError>;
8283

84+
impl BatchPayloadSectionType for IngressPayload {
85+
type PermanentValidationError = IngressPermanentError;
86+
type TransientValidationError = IngressTransientError;
87+
}
88+
8389
impl From<CryptoError> for IngressTransientError {
8490
fn from(err: CryptoError) -> IngressTransientError {
8591
IngressTransientError::CryptoError(err)

rs/interfaces/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod ingress_manager;
1818
pub mod ingress_pool;
1919
pub mod messages;
2020
pub mod messaging;
21+
pub mod payload;
2122
pub mod registry;
2223
pub mod replica_config;
2324
pub mod self_validating_payload;

rs/interfaces/src/messaging.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Message Routing public interfaces.
2-
use crate::validation::ValidationError;
2+
use crate::{payload::BatchPayloadSectionType, validation::ValidationError};
33
use ic_types::{
44
batch::{Batch, ValidationContext, XNetPayload},
55
consensus::Payload,
@@ -34,6 +34,10 @@ pub enum XNetTransientValidationError {
3434
pub type XNetPayloadValidationError =
3535
ValidationError<InvalidXNetPayload, XNetTransientValidationError>;
3636

37+
impl BatchPayloadSectionType for XNetPayload {
38+
type PermanentValidationError = InvalidXNetPayload;
39+
type TransientValidationError = XNetTransientValidationError;
40+
}
3741
/// The public interface for the MessageRouting layer.
3842
pub trait MessageRouting: Send + Sync {
3943
/// Delivers a finalized `Batch` for deterministic processing.

rs/interfaces/src/payload.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! This module defines traits related to payloads and payload building.
2+
3+
use crate::validation::ValidationError;
4+
use ic_base_types::NumBytes;
5+
use ic_types::{batch::ValidationContext, consensus::Payload, Height, Time};
6+
7+
/// A marker trait, indicating, that some struct is a section of the [`BatchPayload`].
8+
pub trait BatchPayloadSectionType: Default {
9+
/// The type of error that is returned, when the validation failed permanently.
10+
type PermanentValidationError;
11+
12+
/// The type of error that is returned, when the validation does not pass transitively,
13+
/// i.e. the payload may pass validation at a later state.
14+
type TransientValidationError;
15+
}
16+
17+
/// A [`BatchPayloadSectionBuilder`] is implemented by any artifact, that can be
18+
/// included into a [`BatchPayload`](ic_types::batch::BatchPayload).
19+
///
20+
/// # Invariants:
21+
/// It is **crucial** that any payload returned by
22+
/// [`build_payload`](BatchPayloadSectionBuilder::build_payload)
23+
/// succeeds when passed into
24+
/// [`validate_payload`](BatchPayloadSectionBuilder::validate_payload),
25+
/// given the same arguments for [`ValidationContext`] and `past_payloads`,
26+
/// and that the following monotony holds:
27+
///
28+
/// - Payload size returned by [`build_payload`](BatchPayloadSectionBuilder::build_payload)
29+
/// `<=` `max_size` passed into [`build_payload`](BatchPayloadSectionBuilder::build_payload)
30+
/// - Payload size returned by [`validate_payload`](BatchPayloadSectionBuilder::validate_payload)
31+
/// `<=` payload size returned by [`build_payload`](BatchPayloadSectionBuilder::build_payload)
32+
///
33+
/// It is advised to call the validation function after building the payload to be 100% sure.
34+
// [build_payload]: (BatchPayloadSectionBuilder::build_payload)
35+
// [validate_payload]: (BatchPayloadSectionBuilder::validate_payload)
36+
pub trait BatchPayloadSectionBuilder<T>
37+
where
38+
T: BatchPayloadSectionType,
39+
{
40+
/// Called to build the payload.
41+
///
42+
/// # Arguments:
43+
/// - `validation_context`: The [`ValidationContext`], under which the payload must be valid.
44+
/// - `max_size`: The maximum size in [`NumBytes`], that the payload section has available in the current batch.
45+
/// - `priority`: The order in which the individual [`BatchPayloadSectionBuilder`] have been called.
46+
/// - `past_payloads`: All [`BatchPayload`]s from the certified height to the tip.
47+
///
48+
/// # Returns:
49+
/// The [`BatchPayloadSectionType`]. If there is no suitable payload, return [`Default`].
50+
/// The size of the payload in [`NumBytes`].
51+
fn build_payload(
52+
&self,
53+
validation_context: &ValidationContext,
54+
max_size: NumBytes,
55+
priority: usize,
56+
past_payloads: &[(Height, Time, Payload)],
57+
) -> (T, NumBytes);
58+
59+
/// Called to validate the payload.
60+
///
61+
/// # Argument:
62+
/// - `payload`: The payload to verify.
63+
/// - `validation_context`: The [`ValidationContext`], under which to validate the payload.
64+
/// - `past_payloads`: All [`Payload`]s from the certified height to the tip.
65+
///
66+
/// # Returns:
67+
/// **On success:** The size of the section as [`NumBytes`].
68+
/// **On error:**: Either a transient or permantent error.
69+
fn validate_payload(
70+
&self,
71+
payload: &T,
72+
validation_context: &ValidationContext,
73+
past_payloads: &[(Height, Time, Payload)],
74+
) -> Result<NumBytes, ValidationError<T::PermanentValidationError, T::TransientValidationError>>;
75+
}

rs/interfaces/src/self_validating_payload.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::validation::ValidationError;
1+
use crate::{payload::BatchPayloadSectionType, validation::ValidationError};
22

33
use ic_types::{
44
batch::{SelfValidatingPayload, ValidationContext},
@@ -18,6 +18,11 @@ pub enum SelfValidatingTransientValidationError {}
1818
pub type SelfValidatingPayloadValidationError =
1919
ValidationError<InvalidSelfValidatingPayload, SelfValidatingTransientValidationError>;
2020

21+
impl BatchPayloadSectionType for SelfValidatingPayload {
22+
type PermanentValidationError = InvalidSelfValidatingPayload;
23+
type TransientValidationError = SelfValidatingTransientValidationError;
24+
}
25+
2126
pub trait SelfValidatingPayloadBuilder: Send + Sync {
2227
/// Produces a `SelfValidatingPayload` of maximum byte size `byte_limit`
2328
/// that is valid given a `ValidationContext` (certified height plus

0 commit comments

Comments
 (0)