diff --git a/Cargo.lock b/Cargo.lock index b71bb99eaf5f0..1ee2154e1b015 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20991,6 +20991,7 @@ dependencies = [ "parking_lot 0.12.3", "sc-client-api", "sc-keystore", + "sc-network-statement", "sp-api", "sp-blockchain", "sp-core 28.0.0", diff --git a/prdoc/pr_9965.prdoc b/prdoc/pr_9965.prdoc new file mode 100644 index 0000000000000..7c40ff0a8399c --- /dev/null +++ b/prdoc/pr_9965.prdoc @@ -0,0 +1,9 @@ +title: Limit the size of the statement for further gossiping +doc: +- audience: Node Dev + description: "Limits the size of statements that are further gossiped over the network to prevent skipping oversized messages. The limit is set to match the network protocol's `MAX_STATEMENT_NOTIFICATION_SIZE` (1 MB), accounting for 1-byte vector length overhead because statements are sent as `Vec`." +crates: +- name: sc-network-statement + bump: minor +- name: sc-statement-store + bump: minor diff --git a/substrate/client/network/statement/src/config.rs b/substrate/client/network/statement/src/config.rs index 6613b78debb24..4cc186f0d3c86 100644 --- a/substrate/client/network/statement/src/config.rs +++ b/substrate/client/network/statement/src/config.rs @@ -27,7 +27,7 @@ pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis pub(crate) const MAX_KNOWN_STATEMENTS: usize = 4 * 1024 * 1024; // * 32 bytes for hash = 128 MB per peer /// Maximum allowed size for a statement notification. -pub(crate) const MAX_STATEMENT_NOTIFICATION_SIZE: u64 = 1024 * 1024; +pub const MAX_STATEMENT_NOTIFICATION_SIZE: u64 = 1024 * 1024; /// Maximum number of statement validation request we keep at any moment. pub(crate) const MAX_PENDING_STATEMENTS: usize = 2 * 1024 * 1024; diff --git a/substrate/client/statement-store/Cargo.toml b/substrate/client/statement-store/Cargo.toml index 04edc6d0e74b9..d62622401494b 100644 --- a/substrate/client/statement-store/Cargo.toml +++ b/substrate/client/statement-store/Cargo.toml @@ -22,6 +22,7 @@ parking_lot = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } +sc-network-statement = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/client/statement-store/src/lib.rs b/substrate/client/statement-store/src/lib.rs index d53efd401971c..d074af1ed315b 100644 --- a/substrate/client/statement-store/src/lib.rs +++ b/substrate/client/statement-store/src/lib.rs @@ -83,6 +83,10 @@ pub const DEFAULT_MAX_TOTAL_STATEMENTS: usize = 4 * 1024 * 1024; // ~4 million /// The maximum amount of data the statement store can hold, regardless of the number of /// statements from which the data originates. pub const DEFAULT_MAX_TOTAL_SIZE: usize = 2 * 1024 * 1024 * 1024; // 2GiB +/// The maximum size of a single statement in bytes. +/// Accounts for the 1-byte vector length prefix when statements are gossiped as `Vec`. +pub const MAX_STATEMENT_SIZE: usize = + sc_network_statement::config::MAX_STATEMENT_NOTIFICATION_SIZE as usize - 1; const MAINTENANCE_PERIOD: std::time::Duration = std::time::Duration::from_secs(30); @@ -890,6 +894,18 @@ impl StatementStore for Store { /// Submit a statement to the store. Validates the statement and returns validation result. fn submit(&self, statement: Statement, source: StatementSource) -> SubmitResult { let hash = statement.hash(); + let encoded_size = statement.encoded_size(); + if encoded_size > MAX_STATEMENT_SIZE { + log::debug!( + target: LOG_TARGET, + "Statement is too big for propogation: {:?} ({}/{} bytes)", + HexDisplay::from(&hash), + statement.encoded_size(), + MAX_STATEMENT_SIZE + ); + return SubmitResult::Ignored + } + match self.index.read().query(&hash) { IndexQuery::Expired => if !source.can_be_resubmitted() { @@ -1079,6 +1095,7 @@ mod tests { Some(a) if a == account(2) => (2, 1000), Some(a) if a == account(3) => (3, 1000), Some(a) if a == account(4) => (4, 1000), + Some(a) if a == account(42) => (42, 42 * crate::MAX_STATEMENT_SIZE as u32), _ => (2, 2000), }; Ok(ValidStatement{ max_count, max_size }) @@ -1385,6 +1402,28 @@ mod tests { assert_eq!(expected_statements, statements); } + #[test] + fn max_statement_size_for_gossiping() { + let (store, _temp) = test_store(); + store.index.write().options.max_total_size = 42 * crate::MAX_STATEMENT_SIZE; + + assert_eq!( + store.submit( + statement(42, 1, Some(1), crate::MAX_STATEMENT_SIZE - 500), + StatementSource::Local + ), + SubmitResult::New(NetworkPriority::High) + ); + + assert_eq!( + store.submit( + statement(42, 2, Some(1), 2 * crate::MAX_STATEMENT_SIZE), + StatementSource::Local + ), + SubmitResult::Ignored + ); + } + #[test] fn expired_statements_are_purged() { use super::DEFAULT_PURGE_AFTER_SEC;