Cell Dissemination (Partial messages) (#8314)

- https://github.com/ethereum/consensus-specs/pull/4558
- https://eips.ethereum.org/EIPS/eip-8136


  


Co-Authored-By: Daniel Knopik <daniel@dknopik.de>

Co-Authored-By: Pawan Dhananjay <pawandhananjay@gmail.com>

Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>
This commit is contained in:
Daniel Knopik
2026-04-23 20:52:28 +02:00
committed by GitHub
parent e086628efe
commit 8a384ff445
54 changed files with 4797 additions and 630 deletions

View File

@@ -14,17 +14,19 @@ use crate::rpc::{
GoodbyeReason, HandlerErr, InboundRequestId, Protocol, RPC, RPCError, RPCMessage, RPCReceived,
RequestType, ResponseTermination, RpcResponse, RpcSuccessResponse,
};
use crate::service::partial_column_header_tracker::PartialColumnHeaderTracker;
use crate::types::{
GossipEncoding, GossipKind, GossipTopic, SnappyTransform, Subnet, SubnetDiscovery,
all_topics_at_fork, core_topics_to_subscribe, is_fork_non_core_topic, subnet_from_topic_hash,
GossipEncoding, GossipKind, GossipTopic, OutgoingPartialColumn, SnappyTransform, Subnet,
SubnetDiscovery, all_topics_at_fork, core_topics_to_subscribe, is_fork_non_core_topic,
subnet_from_topic_hash,
};
use crate::{Enr, NetworkGlobals, PubsubMessage, TopicHash, metrics};
use crate::{Enr, NetworkGlobals, PubsubMessage, TopicHash, decode_partial, metrics};
use api_types::{AppRequestId, Response};
use futures::stream::StreamExt;
use gossipsub_scoring_parameters::{PeerScoreSettings, lighthouse_gossip_thresholds};
use libp2p::gossipsub::{
self, IdentTopic as Topic, MessageAcceptance, MessageAuthenticity, MessageId, PublishError,
TopicScoreParams,
self, Event, IdentTopic as Topic, MessageAcceptance, MessageAuthenticity, MessageId,
PublishError, TopicScoreParams,
};
use libp2p::identity::Keypair;
use libp2p::multiaddr::{self, Multiaddr, Protocol as MProtocol};
@@ -40,16 +42,18 @@ use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use tracing::{debug, error, info, trace, warn};
use types::{ChainSpec, ForkName};
use types::{
EnrForkId, EthSpec, ForkContext, Slot, SubnetId, consts::altair::SYNC_COMMITTEE_SUBNET_COUNT,
ChainSpec, DataColumnSubnetId, EnrForkId, EthSpec, ForkContext, ForkName, PartialDataColumn,
PartialDataColumnHeader, Slot, SubnetId, consts::altair::SYNC_COMMITTEE_SUBNET_COUNT,
};
use utils::{Context as ServiceContext, build_transport, strip_peer_id};
pub mod api_types;
mod gossip_cache;
pub mod gossipsub_scoring_parameters;
mod partial_column_header_tracker;
pub mod utils;
/// The number of peers we target per subnet for discovery queries.
pub const TARGET_SUBNET_PEERS: usize = 3;
@@ -99,6 +103,15 @@ pub enum NetworkEvent<E: EthSpec> {
/// The message itself.
message: PubsubMessage<E>,
},
/// A partial data column sidecar received via gossipsub partial protocol.
PartialDataColumnSidecar {
/// The peer from which we received this message.
source: PeerId,
/// The partial column data.
column: Box<PartialDataColumn<E>>,
/// The topic that this message was sent on.
topic: GossipTopic,
},
/// Inform the network to send a Status to this peer.
StatusPeer(PeerId),
NewListenAddr(Multiaddr),
@@ -162,6 +175,7 @@ pub struct Network<E: EthSpec> {
/// The interval for updating gossipsub scores
update_gossipsub_scores: tokio::time::Interval,
gossip_cache: GossipCache,
partial_column_header_tracker: PartialColumnHeaderTracker,
/// This node's PeerId.
pub local_peer_id: PeerId,
}
@@ -505,6 +519,7 @@ impl<E: EthSpec> Network<E> {
score_settings,
update_gossipsub_scores,
gossip_cache,
partial_column_header_tracker: PartialColumnHeaderTracker::new(),
local_peer_id,
};
@@ -804,9 +819,18 @@ impl<E: EthSpec> Network<E> {
.write()
.insert(topic.clone());
let partial = topic
.kind()
.use_partial_messages(self.network_globals.config.as_ref());
let topic: Topic = topic.into();
match self.gossipsub_mut().subscribe(&topic) {
let subscribe_result = if partial {
self.gossipsub_mut().subscribe_partial(&topic, true)
} else {
self.gossipsub_mut().subscribe(&topic)
};
match subscribe_result {
Err(e) => {
warn!(%topic, error = ?e, "Failed to subscribe to topic");
false
@@ -849,6 +873,16 @@ impl<E: EthSpec> Network<E> {
"Attempted to publish duplicate message"
);
}
PublishError::NoPeersSubscribedToTopic
if topic
.kind()
.use_partial_messages(self.network_globals.config.as_ref()) =>
{
debug!(
kind = %topic.kind(),
"No peers supporting full messages"
);
}
ref e => {
warn!(
error = ?e,
@@ -886,6 +920,66 @@ impl<E: EthSpec> Network<E> {
}
}
/// Publishes partial data column sidecars to the gossipsub network.
pub fn publish_partial(
&mut self,
columns: Vec<Arc<PartialDataColumn<E>>>,
header: Arc<PartialDataColumnHeader<E>>,
) {
if !self.network_globals.config.enable_partial_columns {
return;
}
debug!(
count = columns.len(),
"Sending partial data column sidecars"
);
for column in columns {
let subnet =
DataColumnSubnetId::from_column_index(column.index, &self.fork_context.spec);
let topic = GossipTopic::new(
GossipKind::DataColumnSidecar(subnet),
GossipEncoding::default(),
self.enr_fork_id.fork_digest,
);
let header_sent_set = self
.partial_column_header_tracker
.get_for_block(column.block_root);
let partial_message = OutgoingPartialColumn::new(column, &header, header_sent_set);
let publish_topic: Topic = topic.clone().into();
if let Err(e) = self
.gossipsub_mut()
.publish_partial(publish_topic, partial_message)
{
match e {
PublishError::NoPeersSubscribedToTopic => {
debug!(
kind = %topic.kind(),
"No peers supporting partial messages"
);
}
ref e => {
warn!(
error = ?e,
kind = %topic.kind(),
"Could not publish partial message"
);
}
}
// add to metrics
if let Some(v) = metrics::get_int_gauge(
&metrics::FAILED_PARTIAL_PUBLISHES_PER_MAIN_TOPIC,
&[&format!("{:?}", topic.kind())],
) {
v.inc()
};
}
}
}
/// Informs the gossipsub about the result of a message validation.
/// If the message is valid it will get propagated by gossipsub.
pub fn report_message_validation_result(
@@ -918,6 +1012,29 @@ impl<E: EthSpec> Network<E> {
);
}
/// Informs the gossipsub about the failure of a partial message validation.
pub fn report_partial_message_validation_failure(
&mut self,
propagation_source: PeerId,
topic: GossipTopic,
) {
if let Some(client) = self
.network_globals
.peers
.read()
.peer_info(&propagation_source)
.map(|info| info.client().kind.as_ref())
{
metrics::inc_counter_vec(
&metrics::GOSSIP_UNACCEPTED_MESSAGES_PER_CLIENT,
&[client, "reject"],
)
}
self.gossipsub_mut()
.report_invalid_partial(propagation_source, &TopicHash::from(Topic::from(topic)));
}
/// Updates the current gossipsub scoring parameters based on the validator count and current
/// slot.
pub fn update_gossipsub_parameters(
@@ -1290,6 +1407,56 @@ impl<E: EthSpec> Network<E> {
}
}
}
Event::Partial {
topic_hash,
peer_id,
group_id,
message,
..
} => {
let topic = GossipTopic::decode(topic_hash.as_str())
.inspect_err(|error| {
debug!(
topic = ?topic_hash,
error,
"Could not decode gossipsub partial message topic"
);
// punish the peer
self.gossipsub_mut()
.report_invalid_partial(peer_id, &topic_hash);
})
.ok()?;
if let Some(message) = message {
match decode_partial::<E>(&topic, &group_id, &message) {
Err(error) => {
debug!(
topic = ?topic_hash,
error,
"Could not decode gossipsub partial message"
);
//reject the message
self.gossipsub_mut()
.report_invalid_partial(peer_id, &topic_hash);
}
Ok(column) => {
debug!(
block_root = %column.block_root,
index = column.index,
%peer_id,
cells_present = %column.sidecar.cells_present_bitmap,
"Decoded partial message"
);
// Notify the network
return Some(NetworkEvent::PartialDataColumnSidecar {
source: peer_id,
column: Box::new(column),
topic,
});
}
}
}
}
gossipsub::Event::Subscribed { peer_id, topic } => {
if let Ok(topic) = GossipTopic::decode(topic.as_str()) {
if let Some(subnet_id) = topic.subnet_id() {

View File

@@ -0,0 +1,28 @@
use crate::types::HeaderSentSet;
use lru::LruCache;
use parking_lot::Mutex;
use std::collections::HashSet;
use std::num::NonZeroUsize;
use std::sync::Arc;
use types::core::Hash256;
const MAX_BLOCKS: NonZeroUsize = NonZeroUsize::new(4).unwrap();
pub struct PartialColumnHeaderTracker {
blocks: LruCache<Hash256, HeaderSentSet>,
}
impl PartialColumnHeaderTracker {
pub fn new() -> Self {
PartialColumnHeaderTracker {
blocks: LruCache::new(MAX_BLOCKS),
}
}
pub fn get_for_block(&mut self, hash: Hash256) -> HeaderSentSet {
Arc::clone(
self.blocks
.get_or_insert(hash, || Arc::new(Mutex::new(HashSet::new()))),
)
}
}