diff --git a/pkg/clmimicry/event.go b/pkg/clmimicry/event.go index 05e0004d8..dae0b2769 100644 --- a/pkg/clmimicry/event.go +++ b/pkg/clmimicry/event.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "slices" "github.com/ethpandaops/xatu/pkg/proto/libp2p" "github.com/ethpandaops/xatu/pkg/proto/xatu" @@ -57,15 +56,15 @@ const ( // - "HANDLE_STATUS": Processing of status requests // HandleHermesEvent processes a Hermes trace event and routes it to the appropriate handler -func (p *Processor) HandleHermesEvent(ctx context.Context, event *TraceEvent) error { +func (p *Processor) HandleHermesEvent(ctx context.Context, event TraceEvent) error { if event == nil { return errors.New("event is nil") } - p.log.WithField("type", event.Type).Trace("Received Hermes event") + p.log.WithField("type", fmt.Sprintf("%T", event)).Trace("Received Hermes event") traceMeta := &libp2p.TraceEventMetadata{ - PeerId: wrapperspb.String(event.PeerID.String()), + PeerId: wrapperspb.String(event.GetPeerID().String()), } clientMeta, err := p.metaProvider.GetClientMeta(ctx) @@ -73,26 +72,66 @@ func (p *Processor) HandleHermesEvent(ctx context.Context, event *TraceEvent) er return fmt.Errorf("failed to get client meta: %w", err) } - // Route the event to the appropriate handler based on its category. - switch { - // GossipSub protocol events. - case isGossipSubEvent(event): - return p.handleHermesGossipSubEvent(ctx, event, clientMeta, traceMeta) - - // libp2p pubsub protocol level events. - case isLibp2pEvent(event): - return p.handleHermesLibp2pEvent(ctx, event, clientMeta, traceMeta) - - // libp2p core networking events. - case isLibp2pCoreEvent(event): - return p.handleHermesLibp2pCoreEvent(ctx, event, clientMeta, traceMeta) - - // Request/Response (RPC) protocol events. - case isRpcEvent(event): - return p.handleHermesRPCEvent(ctx, event, clientMeta, traceMeta) + // Route the event to the appropriate handler based on its type. + switch e := event.(type) { + // GossipSub events + case *BeaconBlockEvent: + return p.handleBeaconBlockEvent(ctx, e, clientMeta, traceMeta) + case *AttestationEvent: + return p.handleAttestationEvent(ctx, e, clientMeta, traceMeta) + case *AggregateAndProofEvent: + return p.handleAggregateAndProofEvent(ctx, e, clientMeta, traceMeta) + case *BlobSidecarEvent: + return p.handleBlobSidecarEvent(ctx, e, clientMeta, traceMeta) + case *DataColumnSidecarEvent: + return p.handleDataColumnSidecarEvent(ctx, e, clientMeta, traceMeta) + + // libp2p trace events + case *AddPeerEvent: + return p.handleAddPeerEvent(ctx, e, clientMeta, traceMeta) + case *RemovePeerEvent: + return p.handleRemovePeerEvent(ctx, e, clientMeta, traceMeta) + case *JoinEvent: + return p.handleJoinEvent(ctx, e, clientMeta, traceMeta) + case *LeaveEvent: + return p.handleLeaveEvent(ctx, e, clientMeta, traceMeta) + case *GraftEvent: + return p.handleGraftEvent(ctx, e, clientMeta, traceMeta) + case *PruneEvent: + return p.handlePruneEvent(ctx, e, clientMeta, traceMeta) + case *PublishMessageEvent: + return p.handlePublishMessageEvent(ctx, e, clientMeta, traceMeta) + case *RejectMessageEvent: + return p.handleRejectMessageEvent(ctx, e, clientMeta, traceMeta) + case *DuplicateMessageEvent: + return p.handleDuplicateMessageEvent(ctx, e, clientMeta, traceMeta) + case *DeliverMessageEvent: + return p.handleDeliverMessageEvent(ctx, e, clientMeta, traceMeta) + case *RecvRPCEvent: + return p.handleRecvRPCEvent(ctx, e, clientMeta, traceMeta) + case *SendRPCEvent: + return p.handleSendRPCEvent(ctx, e, clientMeta, traceMeta) + case *DropRPCEvent: + return p.handleDropRPCEvent(ctx, e, clientMeta, traceMeta) + + // libp2p core events + case *ConnectedEvent: + return p.handleConnectedEvent(ctx, e, clientMeta, traceMeta) + case *DisconnectedEvent: + return p.handleDisconnectedEvent(ctx, e, clientMeta, traceMeta) + case *SyntheticHeartbeatEvent: + return p.handleSyntheticHeartbeatEvent(ctx, e, clientMeta, traceMeta) + + // RPC events + case *HandleMetadataEvent: + return p.handleMetadataEvent(ctx, e, clientMeta, traceMeta) + case *HandleStatusEvent: + return p.handleStatusEvent(ctx, e, clientMeta, traceMeta) + case *CustodyProbeEvent: + return p.handleCustodyProbeEvent(ctx, e, clientMeta, traceMeta) default: - p.log.WithField("type", event.Type).Debug("unsupported Hermes event") + p.log.WithField("type", fmt.Sprintf("%T", event)).Debug("unsupported event type") return nil } @@ -111,29 +150,3 @@ func getNetworkID(clientMeta *xatu.ClientMeta) string { return networkStr } - -// isRpcEvent checks if the event is a RPC event. -func isRpcEvent(event *TraceEvent) bool { - _, exists := rpcToXatuEventMap[event.Type] - - return exists -} - -// isLibp2pCoreEvent checks if the event is a libp2p core event. -func isLibp2pCoreEvent(event *TraceEvent) bool { - _, exists := libp2pCoreToXatuEventMap[event.Type] - - return exists -} - -// isLibp2pEvent checks if the event is a libp2p event. -func isLibp2pEvent(event *TraceEvent) bool { - _, exists := libp2pToXatuEventMap[event.Type] - - return exists -} - -// isGossipSubEvent checks if the event is a gossipsub event. -func isGossipSubEvent(event *TraceEvent) bool { - return slices.Contains(gossipsubEventTypes, event.Type) -} diff --git a/pkg/clmimicry/event_gossipsub.go b/pkg/clmimicry/event_gossipsub.go index c357a3d45..e17a11c78 100644 --- a/pkg/clmimicry/event_gossipsub.go +++ b/pkg/clmimicry/event_gossipsub.go @@ -3,7 +3,6 @@ package clmimicry import ( "context" "fmt" - "strings" "github.com/pkg/errors" @@ -11,181 +10,151 @@ import ( "github.com/ethpandaops/xatu/pkg/proto/xatu" ) -// Gossip topic message type constants. -// These match the values from github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/topics.go -// but are defined locally to avoid import cycles. -const ( - gossipAttestationMessage = "beacon_attestation" - gossipBlockMessage = "beacon_block" - gossipAggregateAndProofMessage = "beacon_aggregate_and_proof" - gossipBlobSidecarMessage = "blob_sidecar" - gossipDataColumnSidecarMessage = "data_column_sidecar" -) - -// Define a slice of all gossipsub event types. -var gossipsubEventTypes = []string{ - TraceEvent_HANDLE_MESSAGE, -} - -// Map of gossipsub topic substrings to Xatu event types. -var gossipsubTopicToXatuEventMap = map[string]string{ - gossipAttestationMessage: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_ATTESTATION.String(), - gossipBlockMessage: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_BLOCK.String(), - gossipBlobSidecarMessage: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BLOB_SIDECAR.String(), - gossipAggregateAndProofMessage: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_AGGREGATE_AND_PROOF.String(), - gossipDataColumnSidecarMessage: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_DATA_COLUMN_SIDECAR.String(), -} - -// handleHermesGossipSubEvent handles GossipSub protocol events. -// This includes HANDLE_MESSAGE events which are further categorized by topic. -func (p *Processor) handleHermesGossipSubEvent( +// handleBeaconBlockEvent handles beacon block gossipsub events. +func (p *Processor) handleBeaconBlockEvent( ctx context.Context, - event *TraceEvent, + event *BeaconBlockEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, ) error { - if event.Type != TraceEvent_HANDLE_MESSAGE { + if !p.events.GossipSubBeaconBlockEnabled { return nil } - // We route based on the topic of the message. - topic := event.Topic - if topic == "" { - return errors.New("missing topic in handleHermesGossipSubEvent event") - } - - // Map gossipsub event to Xatu event. - xatuEvent, err := mapGossipSubEventToXatuEvent(topic) - if err != nil { - p.log.WithField("topic", topic).Tracef("unsupported topic in handleHermesGossipSubEvent event") + // Record that we received this event + networkStr := getNetworkID(clientMeta) + xatuEvent := xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_BLOCK.String() + p.metrics.AddEvent(xatuEvent, networkStr) - //nolint:nilerr // we don't want to return an error here. + // Check if we should process this message based on trace/sharding config. + if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { return nil } - switch xatuEvent { - case xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_ATTESTATION.String(): - if !p.events.GossipSubAttestationEnabled { - return nil - } - - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this message based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } + if err := p.handleGossipBeaconBlock(ctx, event, clientMeta, traceMeta); err != nil { + return errors.Wrap(err, "failed to handle gossipsub beacon block") + } - switch payload := event.Payload.(type) { - case *TraceEventAttestation: - if err := p.handleGossipAttestation(ctx, clientMeta, event, payload); err != nil { - return errors.Wrap(err, "failed to handle gossipsub beacon attestation") - } - case *TraceEventSingleAttestation: - if err := p.handleGossipSingleAttestation(ctx, clientMeta, event, payload); err != nil { - return errors.Wrap(err, "failed to handle gossipsub single beacon attestation") - } - default: - return fmt.Errorf("invalid payload type for HandleMessage event: %T", event.Payload) - } + return nil +} - case xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_BLOCK.String(): - if !p.events.GossipSubBeaconBlockEnabled { - return nil - } +// handleAttestationEvent handles attestation gossipsub events. +func (p *Processor) handleAttestationEvent( + ctx context.Context, + event *AttestationEvent, + clientMeta *xatu.ClientMeta, + traceMeta *libp2p.TraceEventMetadata, +) error { + if !p.events.GossipSubAttestationEnabled { + return nil + } - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) + // Record that we received this event + networkStr := getNetworkID(clientMeta) + xatuEvent := xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_ATTESTATION.String() + p.metrics.AddEvent(xatuEvent, networkStr) - // Check if we should process this message based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } + // Check if we should process this message based on trace/sharding config. + if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { + return nil + } - if err := p.handleGossipBeaconBlock(ctx, clientMeta, event, event.Payload); err != nil { - return errors.Wrap(err, "failed to handle gossipsub beacon block") + switch event.Payload.(type) { + case *TraceEventAttestation: + if err := p.handleGossipAttestation(ctx, event, clientMeta, traceMeta); err != nil { + return errors.Wrap(err, "failed to handle gossipsub beacon attestation") } - - case xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BLOB_SIDECAR.String(): - if !p.events.GossipSubBlobSidecarEnabled { - return nil + case *TraceEventSingleAttestation: + if err := p.handleGossipSingleAttestation(ctx, event, clientMeta, traceMeta); err != nil { + return errors.Wrap(err, "failed to handle gossipsub single beacon attestation") } + default: + return fmt.Errorf("invalid payload type for attestation event: %T", event.Payload) + } - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) + return nil +} - // Check if we should process this message based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } +// handleAggregateAndProofEvent handles aggregate and proof gossipsub events. +func (p *Processor) handleAggregateAndProofEvent( + ctx context.Context, + event *AggregateAndProofEvent, + clientMeta *xatu.ClientMeta, + traceMeta *libp2p.TraceEventMetadata, +) error { + if !p.events.GossipSubAggregateAndProofEnabled { + return nil + } - payload, ok := event.Payload.(*TraceEventBlobSidecar) - if !ok { - return errors.New("invalid payload type for HandleMessage event") - } + // Record that we received this event + networkStr := getNetworkID(clientMeta) + xatuEvent := xatu.Event_LIBP2P_TRACE_GOSSIPSUB_AGGREGATE_AND_PROOF.String() + p.metrics.AddEvent(xatuEvent, networkStr) - if err := p.handleGossipBlobSidecar(ctx, clientMeta, event, payload); err != nil { - return errors.Wrap(err, "failed to handle gossipsub blob sidecar") - } + // Check if we should process this message based on trace/sharding config. + if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { + return nil + } - case xatu.Event_LIBP2P_TRACE_GOSSIPSUB_AGGREGATE_AND_PROOF.String(): - if !p.events.GossipSubAggregateAndProofEnabled { - return nil - } + if err := p.handleGossipAggregateAndProof(ctx, event, clientMeta, traceMeta); err != nil { + return errors.Wrap(err, "failed to handle gossipsub aggregate and proof") + } - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) + return nil +} - // Check if we should process this message based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } +// handleBlobSidecarEvent handles blob sidecar gossipsub events. +func (p *Processor) handleBlobSidecarEvent( + ctx context.Context, + event *BlobSidecarEvent, + clientMeta *xatu.ClientMeta, + traceMeta *libp2p.TraceEventMetadata, +) error { + if !p.events.GossipSubBlobSidecarEnabled { + return nil + } - if err := p.handleGossipAggregateAndProof(ctx, clientMeta, event, event.Payload); err != nil { - return errors.Wrap(err, "failed to handle gossipsub aggregate and proof") - } + // Record that we received this event + networkStr := getNetworkID(clientMeta) + xatuEvent := xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BLOB_SIDECAR.String() + p.metrics.AddEvent(xatuEvent, networkStr) - case xatu.Event_LIBP2P_TRACE_GOSSIPSUB_DATA_COLUMN_SIDECAR.String(): - if !p.events.GossipSubDataColumnSidecarEnabled { - return nil - } + // Check if we should process this message based on trace/sharding config. + if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { + return nil + } - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) + if err := p.handleGossipBlobSidecar(ctx, event, clientMeta, traceMeta); err != nil { + return errors.Wrap(err, "failed to handle gossipsub blob sidecar") + } - // Check if we should process this message based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } + return nil +} - payload, ok := event.Payload.(*TraceEventDataColumnSidecar) - if !ok { - return errors.New("invalid payload type for HandleMessage event") - } +// handleDataColumnSidecarEvent handles data column sidecar gossipsub events. +func (p *Processor) handleDataColumnSidecarEvent( + ctx context.Context, + event *DataColumnSidecarEvent, + clientMeta *xatu.ClientMeta, + traceMeta *libp2p.TraceEventMetadata, +) error { + if !p.events.GossipSubDataColumnSidecarEnabled { + return nil + } - if err := p.handleGossipDataColumnSidecar(ctx, clientMeta, event, payload); err != nil { - return errors.Wrap(err, "failed to handle data column sidecar") - } + // Record that we received this event + networkStr := getNetworkID(clientMeta) + xatuEvent := xatu.Event_LIBP2P_TRACE_GOSSIPSUB_DATA_COLUMN_SIDECAR.String() + p.metrics.AddEvent(xatuEvent, networkStr) - default: - p.log.WithField("topic", topic).Trace("Unsupported topic in HandleMessage event") + // Check if we should process this message based on trace/sharding config. + if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { + return nil } - return nil -} - -func mapGossipSubEventToXatuEvent(topic string) (string, error) { - for topicSubstr, xatuEvent := range gossipsubTopicToXatuEventMap { - if strings.Contains(topic, topicSubstr) { - return xatuEvent, nil - } + if err := p.handleGossipDataColumnSidecar(ctx, event, clientMeta, traceMeta); err != nil { + return errors.Wrap(err, "failed to handle data column sidecar") } - return "", fmt.Errorf("unknown gossipsub event: %s", topic) + return nil } diff --git a/pkg/clmimicry/event_libp2p.go b/pkg/clmimicry/event_libp2p.go index d734c19e8..5265828b0 100644 --- a/pkg/clmimicry/event_libp2p.go +++ b/pkg/clmimicry/event_libp2p.go @@ -3,9 +3,9 @@ package clmimicry import ( "context" "fmt" + "time" "github.com/google/uuid" - pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/pkg/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" @@ -15,216 +15,18 @@ import ( "github.com/ethpandaops/xatu/pkg/proto/xatu" ) -// Map of libp2p event types to Xatu event types. -// This serves both for mapping and for checking if an event is a libp2p event. -var libp2pToXatuEventMap = map[string]string{ - pubsubpb.TraceEvent_PUBLISH_MESSAGE.String(): xatu.Event_LIBP2P_TRACE_PUBLISH_MESSAGE.String(), - pubsubpb.TraceEvent_REJECT_MESSAGE.String(): xatu.Event_LIBP2P_TRACE_REJECT_MESSAGE.String(), - pubsubpb.TraceEvent_DUPLICATE_MESSAGE.String(): xatu.Event_LIBP2P_TRACE_DUPLICATE_MESSAGE.String(), - pubsubpb.TraceEvent_DELIVER_MESSAGE.String(): xatu.Event_LIBP2P_TRACE_DELIVER_MESSAGE.String(), - pubsubpb.TraceEvent_ADD_PEER.String(): xatu.Event_LIBP2P_TRACE_ADD_PEER.String(), - pubsubpb.TraceEvent_REMOVE_PEER.String(): xatu.Event_LIBP2P_TRACE_REMOVE_PEER.String(), - pubsubpb.TraceEvent_RECV_RPC.String(): xatu.Event_LIBP2P_TRACE_RECV_RPC.String(), - pubsubpb.TraceEvent_SEND_RPC.String(): xatu.Event_LIBP2P_TRACE_SEND_RPC.String(), - pubsubpb.TraceEvent_DROP_RPC.String(): xatu.Event_LIBP2P_TRACE_DROP_RPC.String(), - pubsubpb.TraceEvent_JOIN.String(): xatu.Event_LIBP2P_TRACE_JOIN.String(), - pubsubpb.TraceEvent_LEAVE.String(): xatu.Event_LIBP2P_TRACE_LEAVE.String(), - pubsubpb.TraceEvent_GRAFT.String(): xatu.Event_LIBP2P_TRACE_GRAFT.String(), - pubsubpb.TraceEvent_PRUNE.String(): xatu.Event_LIBP2P_TRACE_PRUNE.String(), -} - -// handleHermesLibp2pEvent handles libp2p pubsub protocol level events. -// -//nolint:gocyclo // This function handles multiple event types and is intentionally complex -func (p *Processor) handleHermesLibp2pEvent( +func (p *Processor) handleRemovePeerEvent( ctx context.Context, - event *TraceEvent, + event *RemovePeerEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, ) error { - // Map libp2p event to Xatu event. - xatuEvent, err := mapLibp2pEventToXatuEvent(event.Type) - if err != nil { - p.log.WithField("event", event.Type).Tracef("unsupported event in handleHermesLibp2pEvent event") - - //nolint:nilerr // we don't want to return an error here. + if !p.events.RemovePeerEnabled { return nil } - networkStr := getNetworkID(clientMeta) - - switch xatuEvent { - case xatu.Event_LIBP2P_TRACE_ADD_PEER.String(): - if !p.events.AddPeerEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleAddPeerEvent(ctx, clientMeta, traceMeta, event) - - case xatu.Event_LIBP2P_TRACE_RECV_RPC.String(): - // Always process RPC events to extract child events, even if parent is disabled - // This allows child events (like IHAVE) to be captured independently - return p.handleRecvRPCEvent(ctx, clientMeta, traceMeta, event, xatuEvent, networkStr) - - case xatu.Event_LIBP2P_TRACE_DROP_RPC.String(): - // Always process RPC events to extract child events, even if parent is disabled - // This allows child events (like IHAVE) to be captured independently - return p.handleDropRPCEvent(ctx, clientMeta, traceMeta, event, xatuEvent, networkStr) - - case xatu.Event_LIBP2P_TRACE_SEND_RPC.String(): - // Always process RPC events to extract child events, even if parent is disabled - // This allows child events (like IHAVE) to be captured independently - return p.handleSendRPCEvent(ctx, clientMeta, traceMeta, event, xatuEvent, networkStr) - - case xatu.Event_LIBP2P_TRACE_REMOVE_PEER.String(): - if !p.events.RemovePeerEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleRemovePeerEvent(ctx, clientMeta, traceMeta, event) - - case xatu.Event_LIBP2P_TRACE_JOIN.String(): - if !p.events.JoinEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleJoinEvent(ctx, clientMeta, traceMeta, event) - case xatu.Event_LIBP2P_TRACE_LEAVE.String(): - if !p.events.LeaveEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleLeaveEvent(ctx, clientMeta, traceMeta, event) - case xatu.Event_LIBP2P_TRACE_GRAFT.String(): - if !p.events.GraftEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleGraftEvent(ctx, clientMeta, traceMeta, event) - case xatu.Event_LIBP2P_TRACE_PRUNE.String(): - if !p.events.PruneEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handlePruneEvent(ctx, clientMeta, traceMeta, event) - case xatu.Event_LIBP2P_TRACE_PUBLISH_MESSAGE.String(): - if !p.events.PublishMessageEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handlePublishMessageEvent(ctx, clientMeta, traceMeta, event) - case xatu.Event_LIBP2P_TRACE_REJECT_MESSAGE.String(): - if !p.events.RejectMessageEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleRejectMessageEvent(ctx, clientMeta, traceMeta, event) - case xatu.Event_LIBP2P_TRACE_DUPLICATE_MESSAGE.String(): - if !p.events.DuplicateMessageEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleDuplicateMessageEvent(ctx, clientMeta, traceMeta, event) - case xatu.Event_LIBP2P_TRACE_DELIVER_MESSAGE.String(): - if !p.events.DeliverMessageEnabled { - return nil - } - - // Record that we received this event - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleDeliverMessageEvent(ctx, clientMeta, traceMeta, event) - } - - return nil -} - -func (p *Processor) handleRemovePeerEvent( - ctx context.Context, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, -) error { - data, err := TraceEventToRemovePeer(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to remove peer event") + data := &libp2p.RemovePeer{ + PeerId: wrapperspb.String(event.GetPeerID().String()), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -241,7 +43,7 @@ func (p *Processor) handleRemovePeerEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_REMOVE_PEER, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -257,13 +59,16 @@ func (p *Processor) handleRemovePeerEvent( func (p *Processor) handleJoinEvent( ctx context.Context, + event *JoinEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToJoin(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to join event") + if !p.events.JoinEnabled { + return nil + } + + data := &libp2p.Join{ + Topic: wrapperspb.String(event.Topic), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -280,7 +85,7 @@ func (p *Processor) handleJoinEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_JOIN, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -296,13 +101,16 @@ func (p *Processor) handleJoinEvent( func (p *Processor) handleLeaveEvent( ctx context.Context, + event *LeaveEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToLeave(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to leave event") + if !p.events.LeaveEnabled { + return nil + } + + data := &libp2p.Leave{ + Topic: wrapperspb.String(event.Topic), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -319,7 +127,7 @@ func (p *Processor) handleLeaveEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_LEAVE, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -335,13 +143,17 @@ func (p *Processor) handleLeaveEvent( func (p *Processor) handleGraftEvent( ctx context.Context, + event *GraftEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToGraft(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to graft event") + if !p.events.GraftEnabled { + return nil + } + + data := &libp2p.Graft{ + Topic: wrapperspb.String(event.Topic), + PeerId: wrapperspb.String(event.GetPeerID().String()), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -358,7 +170,7 @@ func (p *Processor) handleGraftEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GRAFT, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -374,13 +186,17 @@ func (p *Processor) handleGraftEvent( func (p *Processor) handlePruneEvent( ctx context.Context, + event *PruneEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToPrune(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to prune event") + if !p.events.PruneEnabled { + return nil + } + + data := &libp2p.Prune{ + Topic: wrapperspb.String(event.Topic), + PeerId: wrapperspb.String(event.GetPeerID().String()), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -397,7 +213,7 @@ func (p *Processor) handlePruneEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_PRUNE, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -413,21 +229,20 @@ func (p *Processor) handlePruneEvent( func (p *Processor) handleSendRPCEvent( ctx context.Context, + event *SendRPCEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - xatuEvent string, - networkStr string, ) error { var ( rootEventID = uuid.New().String() decoratedEvents []*xatu.DecoratedEvent + xatuEvent = xatu.Event_LIBP2P_TRACE_SEND_RPC.String() + networkStr = getNetworkID(clientMeta) ) - data, err := TraceEventToSendRPC(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to deliver message event") - } + peerID := event.GetPeerID().String() + + rpcMeta := convertRPCMetaToProto(event.Meta, peerID) metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) if !ok { @@ -444,7 +259,7 @@ func (p *Processor) handleSendRPCEvent( rootRPCEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_SEND_RPC, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: rootEventID, }, Meta: &xatu.Meta{ @@ -455,22 +270,22 @@ func (p *Processor) handleSendRPCEvent( // events into multiple messages, we can remove the meta level messages from the root event. Data: &xatu.DecoratedEvent_Libp2PTraceSendRpc{ Libp2PTraceSendRpc: &libp2p.SendRPC{ - PeerId: data.GetPeerId(), + PeerId: wrapperspb.String(peerID), Meta: &libp2p.RPCMeta{ - PeerId: data.GetPeerId(), + PeerId: wrapperspb.String(peerID), }, }, }, } // 2. RPC meta level messages. - rpcMetaDecoratedEvents, err := p.parseRPCMeta( + rpcMetaDecoratedEvents, err := p.parseRPCMetaFromTypedEvent( rootEventID, - data.GetPeerId().GetValue(), + peerID, clientMeta, traceMeta, - event, - data.GetMeta(), + event.GetTimestamp(), + rpcMeta, ) if err != nil { return errors.Wrapf(err, "failed to parse rpc meta") @@ -500,13 +315,17 @@ func (p *Processor) handleSendRPCEvent( func (p *Processor) handleAddPeerEvent( ctx context.Context, + event *AddPeerEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToAddPeer(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to add_peer event") + if !p.events.AddPeerEnabled { + return nil + } + + data := &libp2p.AddPeer{ + PeerId: wrapperspb.String(event.GetPeerID().String()), + Protocol: wrapperspb.String(string(event.Protocol)), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -523,7 +342,7 @@ func (p *Processor) handleAddPeerEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_ADD_PEER, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -539,21 +358,20 @@ func (p *Processor) handleAddPeerEvent( func (p *Processor) handleRecvRPCEvent( ctx context.Context, + event *RecvRPCEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - xatuEvent string, - networkStr string, ) error { var ( rootEventID = uuid.New().String() decoratedEvents []*xatu.DecoratedEvent + xatuEvent = xatu.Event_LIBP2P_TRACE_RECV_RPC.String() + networkStr = getNetworkID(clientMeta) ) - data, err := TraceEventToRecvRPC(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to deliver message event") - } + peerID := event.GetPeerID().String() + + rpcMeta := convertRPCMetaToProto(event.Meta, peerID) metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) if !ok { @@ -570,7 +388,7 @@ func (p *Processor) handleRecvRPCEvent( rootRPCEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_RECV_RPC, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: rootEventID, }, Meta: &xatu.Meta{ @@ -581,22 +399,22 @@ func (p *Processor) handleRecvRPCEvent( // events into multiple messages, we can remove the meta level messages from the root event. Data: &xatu.DecoratedEvent_Libp2PTraceRecvRpc{ Libp2PTraceRecvRpc: &libp2p.RecvRPC{ - PeerId: data.GetPeerId(), + PeerId: wrapperspb.String(peerID), Meta: &libp2p.RPCMeta{ - PeerId: data.GetPeerId(), + PeerId: wrapperspb.String(peerID), }, }, }, } // 2. RPC meta level messages. - rpcMetaDecoratedEvents, err := p.parseRPCMeta( + rpcMetaDecoratedEvents, err := p.parseRPCMetaFromTypedEvent( rootEventID, - data.GetPeerId().GetValue(), + peerID, clientMeta, traceMeta, - event, - data.GetMeta(), + event.GetTimestamp(), + rpcMeta, ) if err != nil { return errors.Wrapf(err, "failed to parse rpc meta") @@ -626,21 +444,20 @@ func (p *Processor) handleRecvRPCEvent( func (p *Processor) handleDropRPCEvent( ctx context.Context, + event *DropRPCEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - xatuEvent string, - networkStr string, ) error { var ( rootEventID = uuid.New().String() decoratedEvents []*xatu.DecoratedEvent + xatuEvent = xatu.Event_LIBP2P_TRACE_DROP_RPC.String() + networkStr = getNetworkID(clientMeta) ) - data, err := TraceEventToDropRPC(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to deliver message event") - } + peerID := event.GetPeerID().String() + + rpcMeta := convertRPCMetaToProto(event.Meta, peerID) metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) if !ok { @@ -657,7 +474,7 @@ func (p *Processor) handleDropRPCEvent( rootRPCEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_DROP_RPC, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: rootEventID, }, Meta: &xatu.Meta{ @@ -668,22 +485,22 @@ func (p *Processor) handleDropRPCEvent( // events into multiple messages, we can remove the meta level messages from the root event. Data: &xatu.DecoratedEvent_Libp2PTraceDropRpc{ Libp2PTraceDropRpc: &libp2p.DropRPC{ - PeerId: data.GetPeerId(), + PeerId: wrapperspb.String(peerID), Meta: &libp2p.RPCMeta{ - PeerId: data.GetPeerId(), + PeerId: wrapperspb.String(peerID), }, }, }, } // 2. RPC meta level messages. - rpcMetaDecoratedEvents, err := p.parseRPCMeta( + rpcMetaDecoratedEvents, err := p.parseRPCMetaFromTypedEvent( rootEventID, - data.GetPeerId().GetValue(), + peerID, clientMeta, traceMeta, - event, - data.GetMeta(), + event.GetTimestamp(), + rpcMeta, ) if err != nil { return errors.Wrapf(err, "failed to parse rpc meta") @@ -713,13 +530,17 @@ func (p *Processor) handleDropRPCEvent( func (p *Processor) handlePublishMessageEvent( ctx context.Context, + event *PublishMessageEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToPublishMessage(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to publish message event") + if !p.events.PublishMessageEnabled { + return nil + } + + data := &libp2p.PublishMessage{ + MsgId: wrapperspb.String(event.MsgID), + Topic: wrapperspb.String(event.Topic), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -736,7 +557,7 @@ func (p *Processor) handlePublishMessageEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_PUBLISH_MESSAGE, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -752,13 +573,22 @@ func (p *Processor) handlePublishMessageEvent( func (p *Processor) handleRejectMessageEvent( ctx context.Context, + event *RejectMessageEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToRejectMessage(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to reject message event") + if !p.events.RejectMessageEnabled { + return nil + } + + data := &libp2p.RejectMessage{ + MsgId: wrapperspb.String(event.MsgID), + PeerId: wrapperspb.String(event.GetPeerID().String()), + Topic: wrapperspb.String(event.Topic), + Reason: wrapperspb.String(event.Reason), + Local: wrapperspb.Bool(event.Local), + MsgSize: wrapperspb.UInt32(uint32(event.MsgSize)), //nolint:gosec // fine. + SeqNumber: wrapperspb.UInt64(event.SeqNumber), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -775,7 +605,7 @@ func (p *Processor) handleRejectMessageEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_REJECT_MESSAGE, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -791,13 +621,21 @@ func (p *Processor) handleRejectMessageEvent( func (p *Processor) handleDuplicateMessageEvent( ctx context.Context, + event *DuplicateMessageEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToDuplicateMessage(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to duplicate message event") + if !p.events.DuplicateMessageEnabled { + return nil + } + + data := &libp2p.DuplicateMessage{ + MsgId: wrapperspb.String(event.MsgID), + PeerId: wrapperspb.String(event.GetPeerID().String()), + Topic: wrapperspb.String(event.Topic), + Local: wrapperspb.Bool(event.Local), + MsgSize: wrapperspb.UInt32(uint32(event.MsgSize)), //nolint:gosec // fine. + SeqNumber: wrapperspb.UInt64(event.SeqNumber), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -814,7 +652,7 @@ func (p *Processor) handleDuplicateMessageEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_DUPLICATE_MESSAGE, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -830,13 +668,21 @@ func (p *Processor) handleDuplicateMessageEvent( func (p *Processor) handleDeliverMessageEvent( ctx context.Context, + event *DeliverMessageEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToDeliverMessage(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to deliver message event") + if !p.events.DeliverMessageEnabled { + return nil + } + + data := &libp2p.DeliverMessage{ + MsgId: wrapperspb.String(event.MsgID), + PeerId: wrapperspb.String(event.GetPeerID().String()), + Topic: wrapperspb.String(event.Topic), + Local: wrapperspb.Bool(event.Local), + MsgSize: wrapperspb.UInt32(uint32(event.MsgSize)), //nolint:gosec // fine. + SeqNumber: wrapperspb.UInt64(event.SeqNumber), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -853,7 +699,7 @@ func (p *Processor) handleDeliverMessageEvent( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_DELIVER_MESSAGE, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -867,46 +713,63 @@ func (p *Processor) handleDeliverMessageEvent( return p.output.HandleDecoratedEvent(ctx, decoratedEvent) } -func (p *Processor) parseRPCMeta( +// convertRPCMetaToProto converts RpcMeta to libp2p.RPCMeta protobuf format. +func convertRPCMetaToProto(meta *RpcMeta, peerID string) *libp2p.RPCMeta { + if meta == nil { + return &libp2p.RPCMeta{ + PeerId: wrapperspb.String(peerID), + } + } + + return &libp2p.RPCMeta{ + PeerId: wrapperspb.String(peerID), + Messages: convertRPCMessages(meta.Messages), + Subscriptions: convertRPCSubscriptions(meta.Subscriptions), + Control: convertRPCControl(meta.Control), + } +} + +// parseRPCMetaFromTypedEvent parses RPC meta from typed events. +func (p *Processor) parseRPCMetaFromTypedEvent( rootEventID, peerID string, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, + timestamp time.Time, data *libp2p.RPCMeta, ) ([]*xatu.DecoratedEvent, error) { var decoratedEvents []*xatu.DecoratedEvent - controlEvents, err := p.parseRPCMetaControl( + controlEvents, err := p.parseRPCMetaControlFromTypedEvent( rootEventID, peerID, clientMeta, traceMeta, - event, + timestamp, data, ) if err != nil { return nil, errors.Wrapf(err, "failed to parse rpc meta control") } - subscriptionEvents, err := p.parseRPCMetaSubscriptions( + subscriptionEvents, err := p.parseRPCMetaSubscriptionsFromTypedEvent( rootEventID, peerID, clientMeta, traceMeta, - event, + timestamp, data, ) if err != nil { return nil, errors.Wrapf(err, "failed to parse rpc meta subscription") } - messageEvents, err := p.parseRPCMetaMessages( + messageEvents, err := p.parseRPCMetaMessagesFromTypedEvent( rootEventID, peerID, clientMeta, traceMeta, - event, + timestamp, data, ) if err != nil { @@ -920,23 +783,24 @@ func (p *Processor) parseRPCMeta( return decoratedEvents, nil } -func (p *Processor) parseRPCMetaControl( +// parseRPCMetaControlFromTypedEvent parses RPC control metadata from typed events. +func (p *Processor) parseRPCMetaControlFromTypedEvent( rootEventID, peerID string, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, + timestamp time.Time, data *libp2p.RPCMeta, ) ([]*xatu.DecoratedEvent, error) { var decoratedEvents []*xatu.DecoratedEvent if data.GetControl().GetIhave() != nil && p.events.RpcMetaControlIHaveEnabled { - ihave, err := p.parseRPCMetaControlIHave( + ihave, err := p.parseRPCMetaControlIHaveFromTypedEvent( rootEventID, peerID, clientMeta, traceMeta, - event, + timestamp, data, ) if err != nil { @@ -947,13 +811,14 @@ func (p *Processor) parseRPCMetaControl( } if data.GetControl().GetIwant() != nil && p.events.RpcMetaControlIWantEnabled { - iwant, err := p.parseRPCMetaControlIWant( - rootEventID, + iwant, err := p.parseRPCMetaControlIWantFromTypedEvent( + context.Background(), + timestamp, + "LIBP2P_TRACE_RPC_META_CONTROL_IWANT", peerID, - clientMeta, - traceMeta, - event, data, + rootEventID, + clientMeta, ) if err != nil { return nil, errors.Wrapf(err, "failed to parse rpc meta control i want") @@ -963,13 +828,14 @@ func (p *Processor) parseRPCMetaControl( } if data.GetControl().GetIdontwant() != nil && p.events.RpcMetaControlIDontWantEnabled { - idontwant, err := p.parseRPCMetaControlIDontWant( - rootEventID, + idontwant, err := p.parseRPCMetaControlIDontWantFromTypedEvent( + context.Background(), + timestamp, + "LIBP2P_TRACE_RPC_META_CONTROL_IDONTWANT", peerID, - clientMeta, - traceMeta, - event, data, + rootEventID, + clientMeta, ) if err != nil { return nil, errors.Wrapf(err, "failed to parse rpc meta control idontwant") @@ -979,13 +845,14 @@ func (p *Processor) parseRPCMetaControl( } if data.GetControl().GetGraft() != nil && p.events.RpcMetaControlGraftEnabled { - graft, err := p.parseRPCMetaControlGraft( - rootEventID, + graft, err := p.parseRPCMetaControlGraftFromTypedEvent( + context.Background(), + timestamp, + "LIBP2P_TRACE_RPC_META_CONTROL_GRAFT", peerID, - clientMeta, - traceMeta, - event, data, + rootEventID, + clientMeta, ) if err != nil { return nil, errors.Wrapf(err, "failed to parse rpc meta control graft") @@ -995,13 +862,14 @@ func (p *Processor) parseRPCMetaControl( } if data.GetControl().GetPrune() != nil && p.events.RpcMetaControlPruneEnabled { - prune, err := p.parseRPCMetaControlPrune( - rootEventID, + prune, err := p.parseRPCMetaControlPruneFromTypedEvent( + context.Background(), + timestamp, + "LIBP2P_TRACE_RPC_META_CONTROL_PRUNE", peerID, - clientMeta, - traceMeta, - event, data, + rootEventID, + clientMeta, ) if err != nil { return nil, errors.Wrapf(err, "failed to parse rpc meta control prune") @@ -1013,12 +881,84 @@ func (p *Processor) parseRPCMetaControl( return decoratedEvents, nil } -func (p *Processor) parseRPCMetaControlIHave( +// parseRPCMetaSubscriptionsFromTypedEvent parses RPC subscriptions from typed events. +func (p *Processor) parseRPCMetaSubscriptionsFromTypedEvent( rootEventID, peerID string, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, + timestamp time.Time, + data *libp2p.RPCMeta, +) ([]*xatu.DecoratedEvent, error) { + var decoratedEvents []*xatu.DecoratedEvent + + // Check if RPC meta subscriptions are enabled before processing + if !p.events.RpcMetaSubscriptionEnabled { + return decoratedEvents, nil + } + + if data.GetSubscriptions() != nil { + subscriptions, err := p.parseRPCMetaSubscriptionFromTypedEvent( + context.Background(), + timestamp, + "LIBP2P_TRACE_RPC_META_SUBSCRIPTION", + peerID, + data, + rootEventID, + clientMeta, + ) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse rpc meta subscription") + } + + decoratedEvents = append(decoratedEvents, subscriptions...) + } + + return decoratedEvents, nil +} + +// parseRPCMetaMessagesFromTypedEvent parses RPC messages from typed events. +func (p *Processor) parseRPCMetaMessagesFromTypedEvent( + rootEventID, + peerID string, + clientMeta *xatu.ClientMeta, + traceMeta *libp2p.TraceEventMetadata, + timestamp time.Time, + data *libp2p.RPCMeta, +) ([]*xatu.DecoratedEvent, error) { + var decoratedEvents []*xatu.DecoratedEvent + + // Check if RPC meta messages are enabled before processing + if !p.events.RpcMetaMessageEnabled { + return decoratedEvents, nil + } + + if data.GetMessages() != nil { + messages, err := p.parseRPCMetaMessageFromTypedEvent( + context.Background(), + timestamp, + "LIBP2P_TRACE_RPC_META_MESSAGE", + peerID, + data, + rootEventID, + clientMeta, + ) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse rpc meta message") + } + + decoratedEvents = append(decoratedEvents, messages...) + } + + return decoratedEvents, nil +} + +func (p *Processor) parseRPCMetaControlIHaveFromTypedEvent( + rootEventID, + peerID string, + clientMeta *xatu.ClientMeta, + traceMeta *libp2p.TraceEventMetadata, + timestamp time.Time, data *libp2p.RPCMeta, ) ([]*xatu.DecoratedEvent, error) { var decoratedEvents []*xatu.DecoratedEvent @@ -1065,7 +1005,7 @@ func (p *Processor) parseRPCMetaControlIHave( decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: eventType, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -1088,50 +1028,45 @@ func (p *Processor) parseRPCMetaControlIHave( return decoratedEvents, nil } -func (p *Processor) parseRPCMetaControlIWant( - rootEventID, +func (p *Processor) parseRPCMetaControlIWantFromTypedEvent( + ctx context.Context, + timestamp time.Time, + eventType string, peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, + rpcMeta *libp2p.RPCMeta, + rootEventID string, + metadata *xatu.ClientMeta, ) ([]*xatu.DecoratedEvent, error) { - var decoratedEvents []*xatu.DecoratedEvent - - metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) - if !ok { - return nil, fmt.Errorf("failed to clone client metadata") + if rpcMeta.Control == nil { + return nil, nil } - metadata.AdditionalData = &xatu.ClientMeta_Libp2PTraceRpcMetaControlIwant{ - Libp2PTraceRpcMetaControlIwant: &xatu.ClientMeta_AdditionalLibP2PTraceRPCMetaControlIWantData{ - Metadata: traceMeta, - }, - } + decoratedEvents := make([]*xatu.DecoratedEvent, 0) - for controlIndex, iwant := range data.GetControl().GetIwant() { - eventType := xatu.Event_LIBP2P_TRACE_RPC_META_CONTROL_IWANT + for controlIndex, iwant := range rpcMeta.Control.Iwant { + if iwant == nil { + continue + } - // Filter message IDs based on trace/sharding config, preserving original indices. - filteredMsgIDsWithIndex, err := p.ShouldTraceRPCMetaMessages( - clientMeta, - eventType.String(), - iwant.GetMessageIds(), - ) - if err != nil { - return nil, fmt.Errorf("failed to check trace config for iwant: %w", err) + messageInfos := make([]RPCMetaMessageInfo, 0, len(iwant.MessageIds)) + for _, msgID := range iwant.MessageIds { + if msgID != nil { + messageInfos = append(messageInfos, RPCMetaMessageInfo{ + MessageID: msgID, + }) + } } - // Skip this control message if no message IDs remain after filtering. - if len(filteredMsgIDsWithIndex) == 0 { - continue + filteredMessages, err := p.ShouldTraceRPCMetaMessages(metadata, eventType, messageInfos) + if err != nil { + return nil, fmt.Errorf("failed to filter IWANT messages: %w", err) } - for _, msgWithIndex := range filteredMsgIDsWithIndex { + for _, msgWithIndex := range filteredMessages { decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ Event: &xatu.Event{ - Name: eventType, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + Name: xatu.Event_Name(xatu.Event_Name_value[eventType]), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -1153,50 +1088,45 @@ func (p *Processor) parseRPCMetaControlIWant( return decoratedEvents, nil } -func (p *Processor) parseRPCMetaControlIDontWant( - rootEventID, +func (p *Processor) parseRPCMetaControlIDontWantFromTypedEvent( + ctx context.Context, + timestamp time.Time, + eventType string, peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, + rpcMeta *libp2p.RPCMeta, + rootEventID string, + metadata *xatu.ClientMeta, ) ([]*xatu.DecoratedEvent, error) { - var decoratedEvents []*xatu.DecoratedEvent - - metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) - if !ok { - return nil, fmt.Errorf("failed to clone client metadata") + if rpcMeta.Control == nil { + return nil, nil } - metadata.AdditionalData = &xatu.ClientMeta_Libp2PTraceRpcMetaControlIdontwant{ - Libp2PTraceRpcMetaControlIdontwant: &xatu.ClientMeta_AdditionalLibP2PTraceRPCMetaControlIDontWantData{ - Metadata: traceMeta, - }, - } + decoratedEvents := make([]*xatu.DecoratedEvent, 0) - for controlIndex, idontwant := range data.GetControl().GetIdontwant() { - eventType := xatu.Event_LIBP2P_TRACE_RPC_META_CONTROL_IDONTWANT + for controlIndex, idontwant := range rpcMeta.Control.Idontwant { + if idontwant == nil { + continue + } - // Filter message IDs based on trace/sharding config, preserving original indices. - filteredMsgIDsWithIndex, err := p.ShouldTraceRPCMetaMessages( - clientMeta, - eventType.String(), - idontwant.GetMessageIds(), - ) - if err != nil { - return nil, fmt.Errorf("failed to check trace config for idontwant: %w", err) + messageInfos := make([]RPCMetaMessageInfo, 0, len(idontwant.MessageIds)) + for _, msgID := range idontwant.MessageIds { + if msgID != nil { + messageInfos = append(messageInfos, RPCMetaMessageInfo{ + MessageID: msgID, + }) + } } - // Skip this control message if no message IDs remain after filtering. - if len(filteredMsgIDsWithIndex) == 0 { - continue + filteredMessages, err := p.ShouldTraceRPCMetaMessages(metadata, eventType, messageInfos) + if err != nil { + return nil, fmt.Errorf("failed to filter IDONTWANT messages: %w", err) } - for _, msgWithIndex := range filteredMsgIDsWithIndex { + for _, msgWithIndex := range filteredMessages { decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ Event: &xatu.Event{ - Name: eventType, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + Name: xatu.Event_Name(xatu.Event_Name_value[eventType]), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -1218,165 +1148,94 @@ func (p *Processor) parseRPCMetaControlIDontWant( return decoratedEvents, nil } -func (p *Processor) parseRPCMetaControlGraft( - rootEventID, +func (p *Processor) parseRPCMetaControlGraftFromTypedEvent( + ctx context.Context, + timestamp time.Time, + eventType string, peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, + rpcMeta *libp2p.RPCMeta, + rootEventID string, + metadata *xatu.ClientMeta, ) ([]*xatu.DecoratedEvent, error) { - decoratedEvents := make([]*xatu.DecoratedEvent, 0) - - metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) - if !ok { - return nil, fmt.Errorf("failed to clone client metadata") - } - - metadata.AdditionalData = &xatu.ClientMeta_Libp2PTraceRpcMetaControlGraft{ - Libp2PTraceRpcMetaControlGraft: &xatu.ClientMeta_AdditionalLibP2PTraceRPCMetaControlGraftData{ - Metadata: traceMeta, - }, + if rpcMeta.Control == nil { + return nil, nil } - for controlIndex, graft := range data.GetControl().GetGraft() { - eventType := xatu.Event_LIBP2P_TRACE_RPC_META_CONTROL_GRAFT - - // Create topic info for Group B event sharding (topic-based) - topicInfo := RPCMetaTopicInfo{ - Topic: graft.TopicId, - } - - // Filter based on trace/sharding config using the same method as other RPC meta messages. - // GRAFT is a Group B event (sharded by topic only). - filteredTopicsWithIndex, err := p.ShouldTraceRPCMetaMessages( - clientMeta, - eventType.String(), - []RPCMetaTopicInfo{topicInfo}, - ) - if err != nil { - return nil, fmt.Errorf("failed to check trace config for graft: %w", err) - } + decoratedEvents := make([]*xatu.DecoratedEvent, 0) - // Skip this control message if filtered out. - if len(filteredTopicsWithIndex) == 0 { + for controlIndex, graft := range rpcMeta.Control.Graft { + if graft == nil { continue } - decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ - Event: &xatu.Event{ - Name: xatu.Event_LIBP2P_TRACE_RPC_META_CONTROL_GRAFT, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), - Id: uuid.New().String(), - }, - Meta: &xatu.Meta{ - Client: metadata, - }, - Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaControlGraft{ - Libp2PTraceRpcMetaControlGraft: &libp2p.ControlGraftMetaItem{ - RootEventId: wrapperspb.String(rootEventID), - PeerId: wrapperspb.String(peerID), - Topic: graft.TopicId, - ControlIndex: wrapperspb.UInt32(uint32(controlIndex)), //nolint:gosec // conversion fine. - }, - }, - }) - } - - return decoratedEvents, nil -} - -func (p *Processor) parseRPCMetaControlPrune( - rootEventID, - peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, -) ([]*xatu.DecoratedEvent, error) { - var decoratedEvents []*xatu.DecoratedEvent - - metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) - if !ok { - return nil, fmt.Errorf("failed to clone client metadata") - } - - metadata.AdditionalData = &xatu.ClientMeta_Libp2PTraceRpcMetaControlPrune{ - Libp2PTraceRpcMetaControlPrune: &xatu.ClientMeta_AdditionalLibP2PTraceRPCMetaControlPruneData{ - Metadata: traceMeta, - }, - } - - for controlIndex, prune := range data.GetControl().GetPrune() { - eventType := xatu.Event_LIBP2P_TRACE_RPC_META_CONTROL_PRUNE - - // Create topic info for Group B event sharding (topic-based) - topicInfo := RPCMetaTopicInfo{ - Topic: prune.TopicId, - } + topicInfos := []RPCMetaTopicInfo{{Topic: graft.TopicId}} - // Filter based on trace/sharding config using the same method as other RPC meta messages. - // PRUNE is a Group B event (sharded by topic only). - filteredTopicsWithIndex, err := p.ShouldTraceRPCMetaMessages( - clientMeta, - eventType.String(), - []RPCMetaTopicInfo{topicInfo}, - ) + filteredTopics, err := p.ShouldTraceRPCMetaMessages(metadata, eventType, topicInfos) if err != nil { - return nil, fmt.Errorf("failed to check trace config for prune: %w", err) + return nil, fmt.Errorf("failed to filter GRAFT topics: %w", err) } - // Skip this control message if filtered out. - if len(filteredTopicsWithIndex) == 0 { - continue - } - - // PRUNE messages may or may not contain peer IDs depending on whether mesh participants - // have opted into GossipSub PX (Peer eXchange) and are running v1.1 or higher. - // - // When peer IDs are present: - // - We create one event per peer ID to maintain granular tracking - // - Each event includes the specific peer ID. - // - // When peer IDs are absent (most common case): - // - We still need to record the PRUNE event for network analysis - // - We create a single event with an empty GraftPeerId - // - This ensures we capture all PRUNE activity even without PX data - - peerIds := prune.GetPeerIds() - if len(peerIds) == 0 { - // No peer IDs present - create a single event with empty GraftPeerId + for range filteredTopics { decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ Event: &xatu.Event{ - Name: eventType, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + Name: xatu.Event_Name(xatu.Event_Name_value[eventType]), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ Client: metadata, }, - Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaControlPrune{ - Libp2PTraceRpcMetaControlPrune: &libp2p.ControlPruneMetaItem{ + Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaControlGraft{ + Libp2PTraceRpcMetaControlGraft: &libp2p.ControlGraftMetaItem{ RootEventId: wrapperspb.String(rootEventID), - GraftPeerId: nil, // Explicitly set to nil when no peer IDs are provided PeerId: wrapperspb.String(peerID), - Topic: prune.TopicId, - PeerIndex: wrapperspb.UInt32(0), // Always 0 when no peer IDs + Topic: graft.TopicId, ControlIndex: wrapperspb.UInt32(uint32(controlIndex)), //nolint:gosec // conversion fine. }, }, }) - } else { - // Peer IDs present - create one event per peer ID - for peerIndex, prunePeerID := range peerIds { - if prunePeerID == nil || prunePeerID.GetValue() == "" { - continue - } + } + } + + return decoratedEvents, nil +} + +func (p *Processor) parseRPCMetaControlPruneFromTypedEvent( + ctx context.Context, + timestamp time.Time, + eventType string, + peerID string, + rpcMeta *libp2p.RPCMeta, + rootEventID string, + metadata *xatu.ClientMeta, +) ([]*xatu.DecoratedEvent, error) { + if rpcMeta.Control == nil { + return nil, nil + } + + decoratedEvents := make([]*xatu.DecoratedEvent, 0) + + for controlIndex, prune := range rpcMeta.Control.Prune { + if prune == nil { + continue + } + topicInfos := []RPCMetaTopicInfo{{Topic: prune.TopicId}} + + filteredTopics, err := p.ShouldTraceRPCMetaMessages(metadata, eventType, topicInfos) + if err != nil { + return nil, fmt.Errorf("failed to filter PRUNE topics: %w", err) + } + + // Create one event per peer in the prune message + // If no peer IDs are present, still emit one event for the topic + if len(filteredTopics) > 0 { + if len(prune.PeerIds) == 0 { + // No peer IDs - emit one event for the topic decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ Event: &xatu.Event{ - Name: eventType, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + Name: xatu.Event_Name(xatu.Event_Name_value[eventType]), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -1385,14 +1244,36 @@ func (p *Processor) parseRPCMetaControlPrune( Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaControlPrune{ Libp2PTraceRpcMetaControlPrune: &libp2p.ControlPruneMetaItem{ RootEventId: wrapperspb.String(rootEventID), - GraftPeerId: prunePeerID, PeerId: wrapperspb.String(peerID), Topic: prune.TopicId, - PeerIndex: wrapperspb.UInt32(uint32(peerIndex)), //nolint:gosec // conversion fine. ControlIndex: wrapperspb.UInt32(uint32(controlIndex)), //nolint:gosec // conversion fine. }, }, }) + } else { + // One event per peer ID + for peerIndex, graftPeerID := range prune.PeerIds { + decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ + Event: &xatu.Event{ + Name: xatu.Event_Name(xatu.Event_Name_value[eventType]), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), + Id: uuid.New().String(), + }, + Meta: &xatu.Meta{ + Client: metadata, + }, + Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaControlPrune{ + Libp2PTraceRpcMetaControlPrune: &libp2p.ControlPruneMetaItem{ + RootEventId: wrapperspb.String(rootEventID), + PeerId: wrapperspb.String(peerID), + GraftPeerId: graftPeerID, + Topic: prune.TopicId, + ControlIndex: wrapperspb.UInt32(uint32(controlIndex)), //nolint:gosec // conversion fine. + PeerIndex: wrapperspb.UInt32(uint32(peerIndex)), //nolint:gosec // conversion fine. + }, + }, + }) + } } } } @@ -1400,101 +1281,98 @@ func (p *Processor) parseRPCMetaControlPrune( return decoratedEvents, nil } -func (p *Processor) parseRPCMetaSubscriptions( - rootEventID, +func (p *Processor) parseRPCMetaSubscriptionFromTypedEvent( + ctx context.Context, + timestamp time.Time, + eventType string, peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, + rpcMeta *libp2p.RPCMeta, + rootEventID string, + metadata *xatu.ClientMeta, ) ([]*xatu.DecoratedEvent, error) { - var decoratedEvents []*xatu.DecoratedEvent + decoratedEvents := make([]*xatu.DecoratedEvent, 0) - // Check if RPC meta subscriptions are enabled before processing - if !p.events.RpcMetaSubscriptionEnabled { - return decoratedEvents, nil - } + for subIndex, sub := range rpcMeta.Subscriptions { + if sub == nil { + continue + } - if data.GetSubscriptions() != nil { - subscriptions, err := p.parseRPCMetaSubscription( - rootEventID, - peerID, - clientMeta, - traceMeta, - event, - data, - ) + topicInfos := []RPCMetaTopicInfo{{Topic: sub.TopicId}} + + filteredTopics, err := p.ShouldTraceRPCMetaMessages(metadata, eventType, topicInfos) if err != nil { - return nil, errors.Wrapf(err, "failed to parse rpc meta subscription") + return nil, fmt.Errorf("failed to filter subscription topics: %w", err) } - decoratedEvents = append(decoratedEvents, subscriptions...) + for range filteredTopics { + decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ + Event: &xatu.Event{ + Name: xatu.Event_Name(xatu.Event_Name_value[eventType]), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), + Id: uuid.New().String(), + }, + Meta: &xatu.Meta{ + Client: metadata, + }, + Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaSubscription{ + Libp2PTraceRpcMetaSubscription: &libp2p.SubMetaItem{ + RootEventId: wrapperspb.String(rootEventID), + PeerId: wrapperspb.String(peerID), + TopicId: sub.TopicId, + Subscribe: sub.Subscribe, + ControlIndex: wrapperspb.UInt32(uint32(subIndex)), //nolint:gosec // conversion fine. + }, + }, + }) + } } return decoratedEvents, nil } -func (p *Processor) parseRPCMetaSubscription( - rootEventID, +func (p *Processor) parseRPCMetaMessageFromTypedEvent( + ctx context.Context, + timestamp time.Time, + eventType string, peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, + rpcMeta *libp2p.RPCMeta, + rootEventID string, + metadata *xatu.ClientMeta, ) ([]*xatu.DecoratedEvent, error) { - var decoratedEvents []*xatu.DecoratedEvent + decoratedEvents := make([]*xatu.DecoratedEvent, 0) - metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) - if !ok { - return nil, fmt.Errorf("failed to clone client metadata") + messageInfos := make([]RPCMetaMessageInfo, 0, len(rpcMeta.Messages)) + for _, msg := range rpcMeta.Messages { + if msg != nil { + messageInfos = append(messageInfos, RPCMetaMessageInfo{ + MessageID: msg.MessageId, + Topic: msg.TopicId, + }) + } } - metadata.AdditionalData = &xatu.ClientMeta_Libp2PTraceRpcMetaSubscription{ - Libp2PTraceRpcMetaSubscription: &xatu.ClientMeta_AdditionalLibP2PTraceRPCMetaSubscriptionData{ - Metadata: traceMeta, - }, + filteredMessages, err := p.ShouldTraceRPCMetaMessages(metadata, eventType, messageInfos) + if err != nil { + return nil, fmt.Errorf("failed to filter RPC messages: %w", err) } - for controlIndex, subscription := range data.GetSubscriptions() { - eventType := xatu.Event_LIBP2P_TRACE_RPC_META_SUBSCRIPTION - - // Create topic info for Group B event sharding (topic-based) - topicInfo := RPCMetaTopicInfo{ - Topic: subscription.TopicId, - } - - // Filter based on trace/sharding config using the same method as other RPC meta messages. - // SUBSCRIPTION is a Group B event (sharded by topic only). - filteredTopicsWithIndex, err := p.ShouldTraceRPCMetaMessages( - clientMeta, - eventType.String(), - []RPCMetaTopicInfo{topicInfo}, - ) - if err != nil { - return nil, fmt.Errorf("failed to check trace config for subscription: %w", err) - } - - // Skip this control message if filtered out. - if len(filteredTopicsWithIndex) == 0 { - continue - } - + for _, msgWithIndex := range filteredMessages { decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ Event: &xatu.Event{ - Name: eventType, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + Name: xatu.Event_Name(xatu.Event_Name_value[eventType]), + DateTime: timestamppb.New(timestamp.Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ Client: metadata, }, - Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaSubscription{ - Libp2PTraceRpcMetaSubscription: &libp2p.SubMetaItem{ + Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaMessage{ + Libp2PTraceRpcMetaMessage: &libp2p.MessageMetaItem{ RootEventId: wrapperspb.String(rootEventID), PeerId: wrapperspb.String(peerID), - TopicId: subscription.TopicId, - Subscribe: subscription.Subscribe, - ControlIndex: wrapperspb.UInt32(uint32(controlIndex)), //nolint:gosec // conversion fine. + MessageId: msgWithIndex.MessageID, + TopicId: rpcMeta.Messages[msgWithIndex.OriginalIndex].TopicId, + ControlIndex: wrapperspb.UInt32(msgWithIndex.OriginalIndex), }, }, }) @@ -1503,115 +1381,119 @@ func (p *Processor) parseRPCMetaSubscription( return decoratedEvents, nil } -func (p *Processor) parseRPCMetaMessages( - rootEventID, - peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, -) ([]*xatu.DecoratedEvent, error) { - var decoratedEvents []*xatu.DecoratedEvent +func convertRPCMessages(messages []RpcMetaMsg) []*libp2p.MessageMeta { + ourMessages := make([]*libp2p.MessageMeta, len(messages)) - // Check if RPC meta messages are enabled before processing - if !p.events.RpcMetaMessageEnabled { - return decoratedEvents, nil + for i, msg := range messages { + ourMessages[i] = &libp2p.MessageMeta{ + MessageId: wrapperspb.String(msg.MsgID), + TopicId: wrapperspb.String(msg.Topic), + } } - if data.GetMessages() != nil { - messages, err := p.parseRPCMetaMessage( - rootEventID, - peerID, - clientMeta, - traceMeta, - event, - data, - ) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse rpc meta message") + return ourMessages +} + +func convertRPCSubscriptions(subs []RpcMetaSub) []*libp2p.SubMeta { + ourSubs := make([]*libp2p.SubMeta, len(subs)) + + for i, sub := range subs { + ourSubs[i] = &libp2p.SubMeta{ + Subscribe: wrapperspb.Bool(sub.Subscribe), + TopicId: wrapperspb.String(sub.TopicID), } + } - decoratedEvents = append(decoratedEvents, messages...) + return ourSubs +} + +func convertRPCControl(ctrl *RpcMetaControl) *libp2p.ControlMeta { + if ctrl == nil { + return nil } - return decoratedEvents, nil + return &libp2p.ControlMeta{ + Ihave: convertControlIHaveMeta(ctrl.IHave), + Iwant: convertControlIWantMeta(ctrl.IWant), + Graft: convertControlGraftMeta(ctrl.Graft), + Prune: convertControlPruneMeta(ctrl.Prune), + Idontwant: convertControlIDontWantMeta(ctrl.Idontwant), + } } -func (p *Processor) parseRPCMetaMessage( - rootEventID, - peerID string, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, - data *libp2p.RPCMeta, -) ([]*xatu.DecoratedEvent, error) { - var decoratedEvents []*xatu.DecoratedEvent +func convertControlIHaveMeta(ihave []RpcControlIHave) []*libp2p.ControlIHaveMeta { + converted := make([]*libp2p.ControlIHaveMeta, len(ihave)) - metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) - if !ok { - return nil, fmt.Errorf("failed to clone client metadata") + for i, item := range ihave { + converted[i] = &libp2p.ControlIHaveMeta{ + TopicId: wrapperspb.String(item.TopicID), + MessageIds: convertStringValues(item.MsgIDs), + } } - metadata.AdditionalData = &xatu.ClientMeta_Libp2PTraceRpcMetaMessage{ - Libp2PTraceRpcMetaMessage: &xatu.ClientMeta_AdditionalLibP2PTraceRPCMetaMessageData{ - Metadata: traceMeta, - }, + return converted +} + +func convertControlIWantMeta(iwant []RpcControlIWant) []*libp2p.ControlIWantMeta { + converted := make([]*libp2p.ControlIWantMeta, len(iwant)) + + for i, item := range iwant { + converted[i] = &libp2p.ControlIWantMeta{ + MessageIds: convertStringValues(item.MsgIDs), + } } - for controlIndex, message := range data.GetMessages() { - eventType := xatu.Event_LIBP2P_TRACE_RPC_META_MESSAGE + return converted +} - // Filter messages with topic-aware hierarchical sharding support. - filteredMsgIDsWithIndex, err := p.ShouldTraceRPCMetaMessages( - clientMeta, - eventType.String(), - []RPCMetaMessageInfo{ - { - MessageID: message.MessageId, - Topic: message.TopicId, // Include topic information for hierarchical filtering - }, - }, - ) - if err != nil { - return nil, fmt.Errorf("failed to check trace config for message: %w", err) +func convertControlIDontWantMeta(idontwant []RpcControlIdontWant) []*libp2p.ControlIDontWantMeta { + converted := make([]*libp2p.ControlIDontWantMeta, len(idontwant)) + + for i, item := range idontwant { + converted[i] = &libp2p.ControlIDontWantMeta{ + MessageIds: convertStringValues(item.MsgIDs), } + } - // Skip this control message if no message IDs remain after filtering. - if len(filteredMsgIDsWithIndex) == 0 { - continue + return converted +} + +func convertControlGraftMeta(graft []RpcControlGraft) []*libp2p.ControlGraftMeta { + converted := make([]*libp2p.ControlGraftMeta, len(graft)) + + for i, item := range graft { + converted[i] = &libp2p.ControlGraftMeta{ + TopicId: wrapperspb.String(item.TopicID), } + } - for range filteredMsgIDsWithIndex { - decoratedEvents = append(decoratedEvents, &xatu.DecoratedEvent{ - Event: &xatu.Event{ - Name: eventType, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), - Id: uuid.New().String(), - }, - Meta: &xatu.Meta{ - Client: metadata, - }, - Data: &xatu.DecoratedEvent_Libp2PTraceRpcMetaMessage{ - Libp2PTraceRpcMetaMessage: &libp2p.MessageMetaItem{ - RootEventId: wrapperspb.String(rootEventID), - PeerId: wrapperspb.String(peerID), - MessageId: message.MessageId, - TopicId: message.TopicId, - ControlIndex: wrapperspb.UInt32(uint32(controlIndex)), //nolint:gosec // conversion fine. - }, - }, - }) + return converted +} + +func convertControlPruneMeta(prune []RpcControlPrune) []*libp2p.ControlPruneMeta { + converted := make([]*libp2p.ControlPruneMeta, len(prune)) + + for i, item := range prune { + peerIds := make([]string, 0, len(item.PeerIDs)) + for _, peer := range item.PeerIDs { + peerIds = append(peerIds, peer.String()) + } + + converted[i] = &libp2p.ControlPruneMeta{ + TopicId: wrapperspb.String(item.TopicID), + PeerIds: convertStringValues(peerIds), } } - return decoratedEvents, nil + return converted } -// mapLibp2pEventToXatuEvent maps libp2p events to Xatu events. -func mapLibp2pEventToXatuEvent(event string) (string, error) { - if xatuEvent, exists := libp2pToXatuEventMap[event]; exists { - return xatuEvent, nil +func convertStringValues(strings []string) []*wrapperspb.StringValue { + converted := make([]*wrapperspb.StringValue, len(strings)) + + for i, s := range strings { + converted[i] = wrapperspb.String(s) } - return "", fmt.Errorf("unknown libp2p event: %s", event) + return converted } diff --git a/pkg/clmimicry/event_libp2p_core.go b/pkg/clmimicry/event_libp2p_core.go index 7ae4e5943..faf508a72 100644 --- a/pkg/clmimicry/event_libp2p_core.go +++ b/pkg/clmimicry/event_libp2p_core.go @@ -5,94 +5,32 @@ import ( "fmt" "github.com/google/uuid" - "github.com/pkg/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/ethpandaops/xatu/pkg/proto/libp2p" "github.com/ethpandaops/xatu/pkg/proto/xatu" ) -// Map of libp2p core event types to Xatu event types -var libp2pCoreToXatuEventMap = map[string]string{ - TraceEvent_CONNECTED: xatu.Event_LIBP2P_TRACE_CONNECTED.String(), - TraceEvent_DISCONNECTED: xatu.Event_LIBP2P_TRACE_DISCONNECTED.String(), - TraceEvent_SYNTHETIC_HEARTBEAT: xatu.Event_LIBP2P_TRACE_SYNTHETIC_HEARTBEAT.String(), -} - -// handleHermesLibp2pCoreEvent handles libp2p core networking events. -// This includes CONNECTED and DISCONNECTED events from libp2p's network.Notify system. -func (p *Processor) handleHermesLibp2pCoreEvent(ctx context.Context, event *TraceEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata) error { - // Map libp2p core event to Xatu event. - xatuEvent, err := mapLibp2pCoreEventToXatuEvent(event.Type) - if err != nil { - p.log.WithField("event", event.Type).Tracef("unsupported event in handleHermesLibp2pCoreEvent event") - - //nolint:nilerr // we don't want to return an error here. - return nil - } - - switch xatuEvent { - case xatu.Event_LIBP2P_TRACE_CONNECTED.String(): - if !p.events.ConnectedEnabled { - return nil - } - - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleConnectedEvent(ctx, clientMeta, traceMeta, event) - - case xatu.Event_LIBP2P_TRACE_DISCONNECTED.String(): - if !p.events.DisconnectedEnabled { - return nil - } - - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleDisconnectedEvent(ctx, clientMeta, traceMeta, event) - - case xatu.Event_LIBP2P_TRACE_SYNTHETIC_HEARTBEAT.String(): - if !p.events.SyntheticHeartbeatEnabled { - return nil - } - - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleSyntheticHeartbeatEvent(ctx, clientMeta, traceMeta, event) - } - - return nil -} - func (p *Processor) handleConnectedEvent(ctx context.Context, + event *ConnectedEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToConnected(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to connected event") + if !p.events.ConnectedEnabled { + return nil + } + + // Build protobuf message directly from typed event + data := &libp2p.Connected{ + RemotePeer: wrapperspb.String(event.RemotePeer), + RemoteMaddrs: wrapperspb.String(event.RemoteMaddrs.String()), + AgentVersion: wrapperspb.String(event.AgentVersion), + Direction: wrapperspb.String(event.Direction), + Opened: timestamppb.New(event.Opened), + Limited: wrapperspb.Bool(event.Limited), + Transient: wrapperspb.Bool(event.Limited), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -124,13 +62,23 @@ func (p *Processor) handleConnectedEvent(ctx context.Context, } func (p *Processor) handleDisconnectedEvent(ctx context.Context, + event *DisconnectedEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToDisconnected(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to disconnected event") + if !p.events.DisconnectedEnabled { + return nil + } + + // Build protobuf message directly from typed event + data := &libp2p.Disconnected{ + RemotePeer: wrapperspb.String(event.RemotePeer), + RemoteMaddrs: wrapperspb.String(event.RemoteMaddrs.String()), + AgentVersion: wrapperspb.String(event.AgentVersion), + Direction: wrapperspb.String(event.Direction), + Opened: timestamppb.New(event.Opened), + Limited: wrapperspb.Bool(event.Limited), + Transient: wrapperspb.Bool(event.Limited), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -162,13 +110,24 @@ func (p *Processor) handleDisconnectedEvent(ctx context.Context, } func (p *Processor) handleSyntheticHeartbeatEvent(ctx context.Context, + event *SyntheticHeartbeatEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToSyntheticHeartbeat(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to heartbeat event") + if !p.events.SyntheticHeartbeatEnabled { + return nil + } + + // Build protobuf message directly from typed event + data := &libp2p.SyntheticHeartbeat{ + Timestamp: timestamppb.New(event.Timestamp), + RemotePeer: wrapperspb.String(event.RemotePeer), + RemoteMaddrs: wrapperspb.String(event.RemoteMaddrs.String()), + LatencyMs: wrapperspb.Int64(event.LatencyMs), + AgentVersion: wrapperspb.String(event.AgentVersion), + Direction: wrapperspb.UInt32(event.Direction), + Protocols: event.Protocols, + ConnectionAgeNs: wrapperspb.Int64(event.ConnectionAgeNs), } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -198,11 +157,3 @@ func (p *Processor) handleSyntheticHeartbeatEvent(ctx context.Context, return p.output.HandleDecoratedEvent(ctx, decoratedEvent) } - -func mapLibp2pCoreEventToXatuEvent(event string) (string, error) { - if xatuEvent, exists := libp2pCoreToXatuEventMap[event]; exists { - return xatuEvent, nil - } - - return "", fmt.Errorf("unknown libp2p core event: %s", event) -} diff --git a/pkg/clmimicry/event_libp2p_core_test.go b/pkg/clmimicry/event_libp2p_core_test.go index 2b833c7c2..3d2f13767 100644 --- a/pkg/clmimicry/event_libp2p_core_test.go +++ b/pkg/clmimicry/event_libp2p_core_test.go @@ -14,52 +14,8 @@ import ( "go.uber.org/mock/gomock" ) -func Test_mapLibp2pCoreEventToXatuEvent(t *testing.T) { - tests := []struct { - name string - event string - want string - expectError bool - }{ - { - name: "Map CONNECTED event", - event: TraceEvent_CONNECTED, - want: xatu.Event_LIBP2P_TRACE_CONNECTED.String(), - expectError: false, - }, - { - name: "Map DISCONNECTED event", - event: TraceEvent_DISCONNECTED, - want: xatu.Event_LIBP2P_TRACE_DISCONNECTED.String(), - expectError: false, - }, - { - name: "Unknown event type", - event: "UNKNOWN_EVENT", - want: "", - expectError: true, - }, - { - name: "Empty event type", - event: "", - want: "", - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := mapLibp2pCoreEventToXatuEvent(tt.event) - if tt.expectError { - assert.Error(t, err) - assert.Contains(t, err.Error(), "unknown libp2p core event") - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - } - }) - } -} +// Test removed: mapLibp2pCoreEventToXatuEvent no longer exists. +// Event mapping is now handled via type switches on typed events. func Test_handleConnectedEvent(t *testing.T) { // Create a valid peer ID for testing @@ -73,7 +29,7 @@ func Test_handleConnectedEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event *ConnectedEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -85,26 +41,16 @@ func Test_handleConnectedEvent(t *testing.T) { ConnectedEnabled: true, }, }, - event: &TraceEvent{ - Type: TraceEvent_CONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "prysm/v4.0.0", - Direction: "inbound", - Opened: time.Now(), - Limited: false, - }, - }, + event: NewConnectedEvent( + time.Now(), + peerID, + "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", + maddr, + "prysm/v4.0.0", + "inbound", + time.Now(), + false, + ), expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { expectEventWithValidation(t, mockSink, func(t *testing.T, event *xatu.DecoratedEvent) { @@ -136,26 +82,16 @@ func Test_handleConnectedEvent(t *testing.T) { ConnectedEnabled: true, }, }, - event: &TraceEvent{ - Type: TraceEvent_CONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "lighthouse/v4.0.0", - Direction: "outbound", - Opened: time.Now(), - Limited: true, - }, - }, + event: NewConnectedEvent( + time.Now(), + peerID, + "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", + maddr, + "lighthouse/v4.0.0", + "outbound", + time.Now(), + true, + ), expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { expectEventWithValidation(t, mockSink, func(t *testing.T, event *xatu.DecoratedEvent) { @@ -181,45 +117,19 @@ func Test_handleConnectedEvent(t *testing.T) { ConnectedEnabled: false, }, }, - event: &TraceEvent{ - Type: TraceEvent_CONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "prysm/v4.0.0", - Direction: "inbound", - Opened: time.Now(), - Limited: false, - }, - }, + event: NewConnectedEvent( + time.Now(), + peerID, + "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", + maddr, + "prysm/v4.0.0", + "inbound", + time.Now(), + false, + ), expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "CONNECTED with invalid payload", - config: &Config{ - Events: EventConfig{ - ConnectedEnabled: true, - }, - }, - event: &TraceEvent{ - Type: TraceEvent_CONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: "invalid payload type", - }, - expectError: true, - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -231,11 +141,9 @@ func Test_handleConnectedEvent(t *testing.T) { tt.setupMockCalls(mockSink) mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - // Call handleHermesLibp2pCoreEvent which routes to handleConnectedEvent - err := mimicry.GetProcessor().handleHermesLibp2pCoreEvent(context.Background(), tt.event, clientMeta, traceMeta) + // Call HandleHermesEvent which routes to handleConnectedEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -258,7 +166,7 @@ func Test_handleDisconnectedEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event *DisconnectedEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -270,26 +178,16 @@ func Test_handleDisconnectedEvent(t *testing.T) { DisconnectedEnabled: true, }, }, - event: &TraceEvent{ - Type: TraceEvent_DISCONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "teku/v23.0.0", - Direction: "inbound", - Opened: time.Now().Add(-5 * time.Minute), - Limited: false, - }, - }, + event: NewDisconnectedEvent( + time.Now(), + peerID, + "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", + maddr, + "teku/v23.0.0", + "inbound", + time.Now().Add(-5*time.Minute), + false, + ), expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { expectEventWithValidation(t, mockSink, func(t *testing.T, event *xatu.DecoratedEvent) { @@ -321,26 +219,16 @@ func Test_handleDisconnectedEvent(t *testing.T) { DisconnectedEnabled: true, }, }, - event: &TraceEvent{ - Type: TraceEvent_DISCONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "nimbus/v23.0.0", - Direction: "outbound", - Opened: time.Now().Add(-10 * time.Minute), - Limited: true, - }, - }, + event: NewDisconnectedEvent( + time.Now(), + peerID, + "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", + maddr, + "nimbus/v23.0.0", + "outbound", + time.Now().Add(-10*time.Minute), + true, + ), expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { expectEventWithValidation(t, mockSink, func(t *testing.T, event *xatu.DecoratedEvent) { @@ -366,45 +254,19 @@ func Test_handleDisconnectedEvent(t *testing.T) { DisconnectedEnabled: false, }, }, - event: &TraceEvent{ - Type: TraceEvent_DISCONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "prysm/v4.0.0", - Direction: "inbound", - Opened: time.Now(), - Limited: false, - }, - }, + event: NewDisconnectedEvent( + time.Now(), + peerID, + "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", + maddr, + "prysm/v4.0.0", + "inbound", + time.Now(), + false, + ), expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "DISCONNECTED with invalid payload", - config: &Config{ - Events: EventConfig{ - DisconnectedEnabled: true, - }, - }, - event: &TraceEvent{ - Type: TraceEvent_DISCONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{"invalid": "payload"}, - }, - expectError: true, - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -416,11 +278,9 @@ func Test_handleDisconnectedEvent(t *testing.T) { tt.setupMockCalls(mockSink) mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - // Call handleHermesLibp2pCoreEvent which routes to handleDisconnectedEvent - err := mimicry.GetProcessor().handleHermesLibp2pCoreEvent(context.Background(), tt.event, clientMeta, traceMeta) + // Call HandleHermesEvent which routes to handleDisconnectedEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -431,195 +291,7 @@ func Test_handleDisconnectedEvent(t *testing.T) { } } -func Test_handleHermesLibp2pCoreEvent(t *testing.T) { - // Create a valid peer ID for testing - peerID, err := peer.Decode(examplePeerID) - require.NoError(t, err) - - // Create a multiaddr - maddr, err := ma.NewMultiaddr("/ip4/10.0.0.1/tcp/30303") - require.NoError(t, err) - - tests := []struct { - name string - config *Config - event *TraceEvent - expectError bool - validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) - setupMockCalls func(*mock.MockSink) - }{ - { - name: "Handle CONNECTED event through main handler", - config: &Config{ - Events: EventConfig{ - ConnectedEnabled: true, - }, - }, - event: &TraceEvent{ - Type: TraceEvent_CONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "prysm/v4.0.0", - Direction: "inbound", - Opened: time.Now(), - Limited: false, - }, - }, - expectError: false, - setupMockCalls: func(mockSink *mock.MockSink) { - expectEventWithValidation(t, mockSink, func(t *testing.T, event *xatu.DecoratedEvent) { - t.Helper() - - assert.Equal(t, xatu.Event_LIBP2P_TRACE_CONNECTED, event.Event.Name) - }) - }, - }, - { - name: "Handle DISCONNECTED event through main handler", - config: &Config{ - Events: EventConfig{ - DisconnectedEnabled: true, - }, - }, - event: &TraceEvent{ - Type: TraceEvent_DISCONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "lighthouse/v4.0.0", - Direction: "outbound", - Opened: time.Now().Add(-30 * time.Minute), - Limited: false, - }, - }, - expectError: false, - setupMockCalls: func(mockSink *mock.MockSink) { - expectEventWithValidation(t, mockSink, func(t *testing.T, event *xatu.DecoratedEvent) { - t.Helper() - - assert.Equal(t, xatu.Event_LIBP2P_TRACE_DISCONNECTED, event.Event.Name) - }) - }, - }, - { - name: "Handle unknown event type", - config: &Config{ - Events: EventConfig{ - ConnectedEnabled: true, - DisconnectedEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "UNKNOWN_CORE_EVENT", - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct{}{}, - }, - expectError: false, // Should not error, just log and skip - setupMockCalls: func(mockSink *mock.MockSink) { - // No events should be sent - }, - }, - { - name: "CONNECTED event disabled through config", - config: &Config{ - Events: EventConfig{ - ConnectedEnabled: false, - }, - }, - event: &TraceEvent{ - Type: TraceEvent_CONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "prysm/v4.0.0", - Direction: "inbound", - Opened: time.Now(), - Limited: false, - }, - }, - expectError: false, - setupMockCalls: expectNoEvents, - }, - { - name: "DISCONNECTED event disabled through config", - config: &Config{ - Events: EventConfig{ - DisconnectedEnabled: false, - }, - }, - event: &TraceEvent{ - Type: TraceEvent_DISCONNECTED, - PeerID: peerID, - Timestamp: time.Now(), - Payload: struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }{ - RemotePeer: "16Uiu2HAm7QJpRnfGoEeJwqdFaKeVLJm8GhKVYC6fiVBvJnpaXCnq", - RemoteMaddrs: maddr, - AgentVersion: "prysm/v4.0.0", - Direction: "inbound", - Opened: time.Now(), - Limited: false, - }, - }, - expectError: false, - setupMockCalls: expectNoEvents, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockSink := mock.NewMockSink(ctrl) - tt.setupMockCalls(mockSink) - - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - err := mimicry.GetProcessor().handleHermesLibp2pCoreEvent(context.Background(), tt.event, clientMeta, traceMeta) - - if tt.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} +// Test removed: handleHermesLibp2pCoreEvent no longer exists. +// Event handling is now done through HandleHermesEvent with typed events. +// Individual event type tests (Test_handleConnectedEvent, Test_handleDisconnectedEvent) +// provide sufficient coverage. diff --git a/pkg/clmimicry/event_libp2p_decoupled_test.go b/pkg/clmimicry/event_libp2p_decoupled_test.go index e3c364fb9..583f497f8 100644 --- a/pkg/clmimicry/event_libp2p_decoupled_test.go +++ b/pkg/clmimicry/event_libp2p_decoupled_test.go @@ -132,11 +132,12 @@ func TestDecoupledSharding(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - event := &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event := &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -157,14 +158,12 @@ func TestDecoupledSharding(t *testing.T) { PeerId: wrapperspb.String(examplePeerID), } - // Call the new handleRecvRPCEvent with the additional parameters + // Call handleRecvRPCEvent with the typed event err = mimicry.GetProcessor().handleRecvRPCEvent( context.Background(), + event, clientMeta, traceMeta, - event, - xatu.Event_LIBP2P_TRACE_RECV_RPC.String(), - "1", // network ID ) // Should not error @@ -205,11 +204,12 @@ func TestDecoupledShardingSendRPC(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - event := &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event := &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -229,11 +229,9 @@ func TestDecoupledShardingSendRPC(t *testing.T) { err = mimicry.GetProcessor().handleSendRPCEvent( context.Background(), + event, clientMeta, traceMeta, - event, - xatu.Event_LIBP2P_TRACE_SEND_RPC.String(), - "1", ) assert.NoError(t, err) @@ -271,11 +269,12 @@ func TestDecoupledShardingDropRPC(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - event := &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event := &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -295,11 +294,9 @@ func TestDecoupledShardingDropRPC(t *testing.T) { err = mimicry.GetProcessor().handleDropRPCEvent( context.Background(), + event, clientMeta, traceMeta, - event, - xatu.Event_LIBP2P_TRACE_DROP_RPC.String(), - "1", ) assert.NoError(t, err) @@ -369,11 +366,12 @@ func TestDecoupledShardingComplexScenario(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - event := &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event := &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -400,6 +398,7 @@ func TestDecoupledShardingComplexScenario(t *testing.T) { Prune: []RpcControlPrune{ { TopicID: "/eth2/test/beacon_attestation_0/ssz_snappy", + PeerIDs: []peer.ID{peerID}, }, }, }, @@ -413,11 +412,9 @@ func TestDecoupledShardingComplexScenario(t *testing.T) { err = mimicry.GetProcessor().handleRecvRPCEvent( context.Background(), + event, clientMeta, traceMeta, - event, - xatu.Event_LIBP2P_TRACE_RECV_RPC.String(), - "1", ) assert.NoError(t, err) diff --git a/pkg/clmimicry/event_libp2p_test.go b/pkg/clmimicry/event_libp2p_test.go index 04480a915..8c9442141 100644 --- a/pkg/clmimicry/event_libp2p_test.go +++ b/pkg/clmimicry/event_libp2p_test.go @@ -72,7 +72,7 @@ func Test_handleRecvRPCEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event *RecvRPCEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -111,11 +111,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlIWantEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IWant: []RpcControlIWant{ @@ -141,11 +142,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlIDontWantEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Idontwant: []RpcControlIdontWant{ @@ -171,11 +173,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlGraftEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Graft: []RpcControlGraft{ @@ -204,11 +207,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Prune: []RpcControlPrune{ @@ -238,11 +242,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Prune: []RpcControlPrune{ @@ -269,11 +274,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Prune: []RpcControlPrune{ @@ -360,11 +366,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: false, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -403,11 +410,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlIHaveEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -430,11 +438,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlIHaveEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -480,11 +489,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaSubscriptionEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -534,11 +544,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Messages: []RpcMetaMsg{ { @@ -590,11 +601,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlGraftEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -635,11 +647,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaSubscriptionEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{}, // Empty subscriptions }, @@ -655,11 +668,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Messages: []RpcMetaMsg{}, // Empty messages }, @@ -681,11 +695,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: false, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -720,11 +735,12 @@ func Test_handleRecvRPCEvent(t *testing.T) { RpcMetaSubscriptionEnabled: true, }, }, - event: &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -791,7 +807,7 @@ func Test_handleRecvRPCEvent(t *testing.T) { clientMeta := createTestClientMeta() traceMeta := createTestTraceMeta() - err := mimicry.GetProcessor().handleRecvRPCEvent(context.Background(), clientMeta, traceMeta, tt.event, xatu.Event_LIBP2P_TRACE_RECV_RPC.String(), "1") + err := mimicry.GetProcessor().handleRecvRPCEvent(context.Background(), tt.event, clientMeta, traceMeta) if tt.expectError { assert.Error(t, err) @@ -807,14 +823,10 @@ func Test_handleAddPeerEvent(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - // Create another peer ID for the added peer - addedPeerID, err := peer.Decode("16Uiu2HAm7w1pZrYUj9Jkfn5KvgTqkmaLFvFVNxS1BFiw6U5MsKXZ") - require.NoError(t, err) - tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -826,14 +838,12 @@ func Test_handleAddPeerEvent(t *testing.T) { AddPeerEnabled: true, }, }, - event: &TraceEvent{ - Type: "ADD_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": addedPeerID, - "Protocol": protocol.ID("/meshsub/1.2.0"), + event: &AddPeerEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Protocol: protocol.ID("/meshsub/1.2.0"), }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -844,7 +854,7 @@ func Test_handleAddPeerEvent(t *testing.T) { addPeerData := event.GetLibp2PTraceAddPeer() assert.NotNil(t, addPeerData, "Expected ADD_PEER data to be set") - assert.Equal(t, addedPeerID.String(), addPeerData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), addPeerData.PeerId.GetValue(), "Expected peer ID to match") assert.Equal(t, "/meshsub/1.2.0", addPeerData.Protocol.GetValue(), "Expected protocol to match") }) }, @@ -856,14 +866,12 @@ func Test_handleAddPeerEvent(t *testing.T) { AddPeerEnabled: true, }, }, - event: &TraceEvent{ - Type: "ADD_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": addedPeerID, - "Protocol": protocol.ID("/meshsub/1.1.0"), + event: &AddPeerEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Protocol: protocol.ID("/meshsub/1.1.0"), }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -874,7 +882,7 @@ func Test_handleAddPeerEvent(t *testing.T) { addPeerData := event.GetLibp2PTraceAddPeer() assert.NotNil(t, addPeerData, "Expected ADD_PEER data to be set") - assert.Equal(t, addedPeerID.String(), addPeerData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), addPeerData.PeerId.GetValue(), "Expected peer ID to match") assert.Equal(t, "/meshsub/1.1.0", addPeerData.Protocol.GetValue(), "Expected protocol to match") }) }, @@ -886,14 +894,12 @@ func Test_handleAddPeerEvent(t *testing.T) { AddPeerEnabled: false, }, }, - event: &TraceEvent{ - Type: "ADD_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": addedPeerID, - "Protocol": protocol.ID("/meshsub/1.2.0"), + event: &AddPeerEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Protocol: protocol.ID("/meshsub/1.2.0"), }, expectError: false, setupMockCalls: expectNoEvents, @@ -905,14 +911,12 @@ func Test_handleAddPeerEvent(t *testing.T) { AddPeerEnabled: true, }, }, - event: &TraceEvent{ - Type: "ADD_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": addedPeerID, - "Protocol": protocol.ID("/meshsub/1.2.0"), + event: &AddPeerEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Protocol: protocol.ID("/meshsub/1.2.0"), }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -923,42 +927,6 @@ func Test_handleAddPeerEvent(t *testing.T) { }) }, }, - { - name: "ADD_PEER with invalid payload - missing PeerID", - config: &Config{ - Events: EventConfig{ - AddPeerEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "ADD_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "Protocol": protocol.ID("/meshsub/1.2.0"), - }, - }, - expectError: true, // TraceEventToAddPeer returns an error - setupMockCalls: expectNoEvents, - }, - { - name: "ADD_PEER with invalid payload - missing Protocol", - config: &Config{ - Events: EventConfig{ - AddPeerEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "ADD_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": addedPeerID, - }, - }, - expectError: true, // TraceEventToAddPeer returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -969,12 +937,8 @@ func Test_handleAddPeerEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleAddPeerEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleAddPeerEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -990,14 +954,10 @@ func Test_handleRemovePeerEvent(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - // Create another peer ID for the removed peer - removedPeerID, err := peer.Decode("16Uiu2HAm7w1pZrYUj9Jkfn5KvgTqkmaLFvFVNxS1BFiw6U5MsKXZ") - require.NoError(t, err) - tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1009,12 +969,10 @@ func Test_handleRemovePeerEvent(t *testing.T) { RemovePeerEnabled: true, }, }, - event: &TraceEvent{ - Type: "REMOVE_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": removedPeerID, + event: &RemovePeerEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, }, expectError: false, @@ -1026,7 +984,7 @@ func Test_handleRemovePeerEvent(t *testing.T) { removePeerData := event.GetLibp2PTraceRemovePeer() assert.NotNil(t, removePeerData, "Expected REMOVE_PEER data to be set") - assert.Equal(t, removedPeerID.String(), removePeerData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), removePeerData.PeerId.GetValue(), "Expected peer ID to match") }) }, }, @@ -1037,33 +995,15 @@ func Test_handleRemovePeerEvent(t *testing.T) { RemovePeerEnabled: false, }, }, - event: &TraceEvent{ - Type: "REMOVE_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": removedPeerID, + event: &RemovePeerEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "REMOVE_PEER with invalid payload - missing PeerID", - config: &Config{ - Events: EventConfig{ - RemovePeerEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "REMOVE_PEER", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{}, - }, - expectError: true, // TraceEventToRemovePeer returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1074,12 +1014,8 @@ func Test_handleRemovePeerEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleRemovePeerEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleRemovePeerEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1098,7 +1034,7 @@ func Test_handleJoinEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1110,14 +1046,12 @@ func Test_handleJoinEvent(t *testing.T) { JoinEnabled: true, }, }, - event: &TraceEvent{ - Type: "JOIN", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", + event: &JoinEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1139,34 +1073,16 @@ func Test_handleJoinEvent(t *testing.T) { JoinEnabled: false, }, }, - event: &TraceEvent{ - Type: "JOIN", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", + event: &JoinEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "JOIN with invalid payload - missing Topic", - config: &Config{ - Events: EventConfig{ - JoinEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "JOIN", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{}, - }, - expectError: true, // TraceEventToJoin returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1177,12 +1093,8 @@ func Test_handleJoinEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleJoinEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleJoinEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1201,7 +1113,7 @@ func Test_handleLeaveEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1213,14 +1125,12 @@ func Test_handleLeaveEvent(t *testing.T) { LeaveEnabled: true, }, }, - event: &TraceEvent{ - Type: "LEAVE", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", + event: &LeaveEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1242,34 +1152,16 @@ func Test_handleLeaveEvent(t *testing.T) { LeaveEnabled: false, }, }, - event: &TraceEvent{ - Type: "LEAVE", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", + event: &LeaveEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "LEAVE with invalid payload - missing Topic", - config: &Config{ - Events: EventConfig{ - LeaveEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "LEAVE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{}, - }, - expectError: true, // TraceEventToLeave returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1280,12 +1172,8 @@ func Test_handleLeaveEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleLeaveEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleLeaveEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1301,14 +1189,10 @@ func Test_handleGraftEvent(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - // Create another peer ID for the grafted peer - graftedPeerID, err := peer.Decode("16Uiu2HAm7w1pZrYUj9Jkfn5KvgTqkmaLFvFVNxS1BFiw6U5MsKXZ") - require.NoError(t, err) - tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1320,14 +1204,12 @@ func Test_handleGraftEvent(t *testing.T) { GraftEnabled: true, }, }, - event: &TraceEvent{ - Type: "GRAFT", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": graftedPeerID, - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + event: &GraftEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1338,7 +1220,7 @@ func Test_handleGraftEvent(t *testing.T) { graftData := event.GetLibp2PTraceGraft() assert.NotNil(t, graftData, "Expected GRAFT data to be set") - assert.Equal(t, graftedPeerID.String(), graftData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), graftData.PeerId.GetValue(), "Expected peer ID to match") assert.Equal(t, "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", graftData.Topic.GetValue(), "Expected topic to match") }) }, @@ -1350,54 +1232,16 @@ func Test_handleGraftEvent(t *testing.T) { GraftEnabled: false, }, }, - event: &TraceEvent{ - Type: "GRAFT", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": graftedPeerID, - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + event: &GraftEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "GRAFT with invalid payload - missing PeerID", - config: &Config{ - Events: EventConfig{ - GraftEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "GRAFT", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - }, - }, - expectError: true, // TraceEventToGraft returns an error - setupMockCalls: expectNoEvents, - }, - { - name: "GRAFT with invalid payload - missing Topic", - config: &Config{ - Events: EventConfig{ - GraftEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "GRAFT", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": graftedPeerID, - }, - }, - expectError: true, // TraceEventToGraft returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1408,12 +1252,8 @@ func Test_handleGraftEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleGraftEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleGraftEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1429,14 +1269,10 @@ func Test_handlePruneEvent(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - // Create another peer ID for the pruned peer - prunedPeerID, err := peer.Decode("16Uiu2HAm7w1pZrYUj9Jkfn5KvgTqkmaLFvFVNxS1BFiw6U5MsKXZ") - require.NoError(t, err) - tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1448,14 +1284,12 @@ func Test_handlePruneEvent(t *testing.T) { PruneEnabled: true, }, }, - event: &TraceEvent{ - Type: "PRUNE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": prunedPeerID, - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + event: &PruneEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1466,7 +1300,7 @@ func Test_handlePruneEvent(t *testing.T) { pruneData := event.GetLibp2PTracePrune() assert.NotNil(t, pruneData, "Expected PRUNE data to be set") - assert.Equal(t, prunedPeerID.String(), pruneData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), pruneData.PeerId.GetValue(), "Expected peer ID to match") assert.Equal(t, "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", pruneData.Topic.GetValue(), "Expected topic to match") }) }, @@ -1478,54 +1312,16 @@ func Test_handlePruneEvent(t *testing.T) { PruneEnabled: false, }, }, - event: &TraceEvent{ - Type: "PRUNE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": prunedPeerID, - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + event: &PruneEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "PRUNE with invalid payload - missing PeerID", - config: &Config{ - Events: EventConfig{ - PruneEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "PRUNE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - }, - }, - expectError: true, // TraceEventToPrune returns an error - setupMockCalls: expectNoEvents, - }, - { - name: "PRUNE with invalid payload - missing Topic", - config: &Config{ - Events: EventConfig{ - PruneEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "PRUNE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "PeerID": prunedPeerID, - }, - }, - expectError: true, // TraceEventToPrune returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1536,12 +1332,8 @@ func Test_handlePruneEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handlePruneEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handlePruneEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1560,7 +1352,7 @@ func Test_handlePublishMessageEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1572,14 +1364,13 @@ func Test_handlePublishMessageEvent(t *testing.T) { PublishMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "PUBLISH_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg123", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + event: &PublishMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg123", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1602,54 +1393,17 @@ func Test_handlePublishMessageEvent(t *testing.T) { PublishMessageEnabled: false, }, }, - event: &TraceEvent{ - Type: "PUBLISH_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg123", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + event: &PublishMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg123", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "PUBLISH_MESSAGE with invalid payload - missing MsgID", - config: &Config{ - Events: EventConfig{ - PublishMessageEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "PUBLISH_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - }, - }, - expectError: true, // TraceEventToPublishMessage returns an error - setupMockCalls: expectNoEvents, - }, - { - name: "PUBLISH_MESSAGE with invalid payload - missing Topic", - config: &Config{ - Events: EventConfig{ - PublishMessageEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "PUBLISH_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg123", - }, - }, - expectError: true, // TraceEventToPublishMessage returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1660,12 +1414,8 @@ func Test_handlePublishMessageEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handlePublishMessageEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handlePublishMessageEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1681,14 +1431,10 @@ func Test_handleRejectMessageEvent(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - // Create another peer ID for the rejected message - rejectedPeerID, err := peer.Decode("16Uiu2HAm7w1pZrYUj9Jkfn5KvgTqkmaLFvFVNxS1BFiw6U5MsKXZ") - require.NoError(t, err) - tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1700,19 +1446,17 @@ func Test_handleRejectMessageEvent(t *testing.T) { RejectMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "REJECT_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg456", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - "PeerID": rejectedPeerID, - "Reason": "validation failed", - "Local": true, - "MsgSize": 1024, - "Seq": "01", + event: &RejectMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg456", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + Reason: "validation failed", + Local: true, + MsgSize: 1024, + SeqNumber: 1, }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1725,7 +1469,7 @@ func Test_handleRejectMessageEvent(t *testing.T) { assert.NotNil(t, rejectData, "Expected REJECT_MESSAGE data to be set") assert.Equal(t, "msg456", rejectData.MsgId.GetValue(), "Expected message ID to match") assert.Equal(t, "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", rejectData.Topic.GetValue(), "Expected topic to match") - assert.Equal(t, rejectedPeerID.String(), rejectData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), rejectData.PeerId.GetValue(), "Expected peer ID to match") assert.Equal(t, "validation failed", rejectData.Reason.GetValue(), "Expected reason to match") assert.True(t, rejectData.Local.GetValue(), "Expected local to be true") assert.Equal(t, uint32(1024), rejectData.MsgSize.GetValue(), "Expected message size to match") @@ -1740,46 +1484,21 @@ func Test_handleRejectMessageEvent(t *testing.T) { RejectMessageEnabled: false, }, }, - event: &TraceEvent{ - Type: "REJECT_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg456", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - "PeerID": rejectedPeerID, - "Reason": "validation failed", - "Local": true, - "MsgSize": 1024, - "Seq": "01", + event: &RejectMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg456", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + Reason: "validation failed", + Local: true, + MsgSize: 1024, + SeqNumber: 0, // Seq "01" - convert as needed }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "REJECT_MESSAGE with invalid payload - missing MsgID", - config: &Config{ - Events: EventConfig{ - RejectMessageEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "REJECT_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - "PeerID": rejectedPeerID, - "Reason": "validation failed", - "Local": true, - "MsgSize": 1024, - "Seq": "01", - }, - }, - expectError: true, // TraceEventToRejectMessage returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1790,12 +1509,8 @@ func Test_handleRejectMessageEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleRejectMessageEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleRejectMessageEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1811,14 +1526,10 @@ func Test_handleDeliverMessageEvent(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - // Create another peer ID for the delivered message - deliveredPeerID, err := peer.Decode("16Uiu2HAm7w1pZrYUj9Jkfn5KvgTqkmaLFvFVNxS1BFiw6U5MsKXZ") - require.NoError(t, err) - tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1830,18 +1541,16 @@ func Test_handleDeliverMessageEvent(t *testing.T) { DeliverMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "DELIVER_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg789", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - "PeerID": deliveredPeerID, - "Local": false, - "MsgSize": 2048, - "Seq": "02", + event: &DeliverMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg789", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + Local: false, + MsgSize: 2048, + SeqNumber: 2, }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1854,7 +1563,7 @@ func Test_handleDeliverMessageEvent(t *testing.T) { assert.NotNil(t, deliverData, "Expected DELIVER_MESSAGE data to be set") assert.Equal(t, "msg789", deliverData.MsgId.GetValue(), "Expected message ID to match") assert.Equal(t, "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", deliverData.Topic.GetValue(), "Expected topic to match") - assert.Equal(t, deliveredPeerID.String(), deliverData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), deliverData.PeerId.GetValue(), "Expected peer ID to match") assert.False(t, deliverData.Local.GetValue(), "Expected local to be false") assert.Equal(t, uint32(2048), deliverData.MsgSize.GetValue(), "Expected message size to match") assert.Equal(t, uint64(2), deliverData.SeqNumber.GetValue(), "Expected sequence number to match") @@ -1868,44 +1577,20 @@ func Test_handleDeliverMessageEvent(t *testing.T) { DeliverMessageEnabled: false, }, }, - event: &TraceEvent{ - Type: "DELIVER_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg789", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - "PeerID": deliveredPeerID, - "Local": false, - "MsgSize": 2048, - "Seq": "02", + event: &DeliverMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg789", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", + Local: false, + MsgSize: 2048, + SeqNumber: 0, // Seq "02" - convert as needed }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "DELIVER_MESSAGE with invalid payload - missing MsgID", - config: &Config{ - Events: EventConfig{ - DeliverMessageEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "DELIVER_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_block/ssz_snappy", - "PeerID": deliveredPeerID, - "Local": false, - "MsgSize": 2048, - "Seq": "02", - }, - }, - expectError: true, // TraceEventToDeliverMessage returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -1916,12 +1601,8 @@ func Test_handleDeliverMessageEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleDeliverMessageEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleDeliverMessageEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -1937,14 +1618,10 @@ func Test_handleDuplicateMessageEvent(t *testing.T) { peerID, err := peer.Decode(examplePeerID) require.NoError(t, err) - // Create another peer ID for the duplicate message - duplicatePeerID, err := peer.Decode("16Uiu2HAm7w1pZrYUj9Jkfn5KvgTqkmaLFvFVNxS1BFiw6U5MsKXZ") - require.NoError(t, err) - tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -1956,18 +1633,16 @@ func Test_handleDuplicateMessageEvent(t *testing.T) { DuplicateMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "DUPLICATE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg999", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", - "PeerID": duplicatePeerID, - "Local": false, - "MsgSize": 512, - "Seq": "03", + event: &DuplicateMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg999", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", + Local: false, + MsgSize: 512, + SeqNumber: 3, }, expectError: false, setupMockCalls: func(mockSink *mock.MockSink) { @@ -1980,7 +1655,7 @@ func Test_handleDuplicateMessageEvent(t *testing.T) { assert.NotNil(t, duplicateData, "Expected DUPLICATE_MESSAGE data to be set") assert.Equal(t, "msg999", duplicateData.MsgId.GetValue(), "Expected message ID to match") assert.Equal(t, "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", duplicateData.Topic.GetValue(), "Expected topic to match") - assert.Equal(t, duplicatePeerID.String(), duplicateData.PeerId.GetValue(), "Expected peer ID to match") + assert.Equal(t, peerID.String(), duplicateData.PeerId.GetValue(), "Expected peer ID to match") assert.False(t, duplicateData.Local.GetValue(), "Expected local to be false") assert.Equal(t, uint32(512), duplicateData.MsgSize.GetValue(), "Expected message size to match") assert.Equal(t, uint64(3), duplicateData.SeqNumber.GetValue(), "Expected sequence number to match") @@ -1994,44 +1669,20 @@ func Test_handleDuplicateMessageEvent(t *testing.T) { DuplicateMessageEnabled: false, }, }, - event: &TraceEvent{ - Type: "DUPLICATE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "MsgID": "msg999", - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", - "PeerID": duplicatePeerID, - "Local": false, - "MsgSize": 512, - "Seq": "03", + event: &DuplicateMessageEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), }, + MsgID: "msg999", + Topic: "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", + Local: false, + MsgSize: 512, + SeqNumber: 0, // Seq "03" - convert as needed }, expectError: false, setupMockCalls: expectNoEvents, }, - { - name: "DUPLICATE_MESSAGE with invalid payload - missing MsgID", - config: &Config{ - Events: EventConfig{ - DuplicateMessageEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "DUPLICATE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Payload: map[string]any{ - "Topic": "/eth2/12D3KooWLRPJAA5o6fuqoxn4zqfLVmT6BnfgTdqEGmjPHY1u5KGR/beacon_attestation_1/ssz_snappy", - "PeerID": duplicatePeerID, - "Local": false, - "MsgSize": 512, - "Seq": "03", - }, - }, - expectError: true, // TraceEventToDuplicateMessage returns an error - setupMockCalls: expectNoEvents, - }, } for _, tt := range tests { @@ -2042,12 +1693,8 @@ func Test_handleDuplicateMessageEvent(t *testing.T) { mockSink := mock.NewMockSink(ctrl) tt.setupMockCalls(mockSink) - mimicry := createTestMimicry(t, tt.config, mockSink) - clientMeta := createTestClientMeta() - traceMeta := createTestTraceMeta() - - // Call handleHermesLibp2pEvent which routes to handleDuplicateMessageEvent - err := mimicry.GetProcessor().handleHermesLibp2pEvent(context.Background(), tt.event, clientMeta, traceMeta) + mimicry := createTestMimicry(t, tt.config, mockSink) // Call handleHermesLibp2pEvent which routes to handleDuplicateMessageEvent + err := mimicry.GetProcessor().HandleHermesEvent(context.Background(), tt.event) if tt.expectError { assert.Error(t, err) @@ -2066,7 +1713,7 @@ func Test_handleSendRPCEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -2079,11 +1726,12 @@ func Test_handleSendRPCEvent(t *testing.T) { RpcMetaControlIHaveEnabled: true, }, }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -2111,11 +1759,12 @@ func Test_handleSendRPCEvent(t *testing.T) { RpcMetaSubscriptionEnabled: true, }, }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -2169,11 +1818,12 @@ func Test_handleSendRPCEvent(t *testing.T) { RpcMetaMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Messages: []RpcMetaMsg{ { @@ -2202,11 +1852,12 @@ func Test_handleSendRPCEvent(t *testing.T) { SendRPCEnabled: false, }, }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IHave: []RpcControlIHave{ @@ -2231,11 +1882,12 @@ func Test_handleSendRPCEvent(t *testing.T) { RpcMetaControlIHaveEnabled: false, }, }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -2262,22 +1914,6 @@ func Test_handleSendRPCEvent(t *testing.T) { expectError: false, setupMockCalls: expectNoEvents, // No events because no child events are enabled }, - { - name: "SEND_RPC with invalid payload", - config: &Config{ - Events: EventConfig{ - SendRPCEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: "invalid payload", - }, - expectError: true, // TraceEventToSendRPC returns an error - setupMockCalls: expectNoEvents, - }, { name: "SEND_RPC with mixed control messages", config: &Config{ @@ -2287,11 +1923,12 @@ func Test_handleSendRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: true, }, }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Graft: []RpcControlGraft{ @@ -2326,11 +1963,12 @@ func Test_handleSendRPCEvent(t *testing.T) { RpcMetaMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "SendRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &SendRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Messages: []RpcMetaMsg{}, // Empty messages Subscriptions: []RpcMetaSub{}, // Empty subscriptions @@ -2354,7 +1992,10 @@ func Test_handleSendRPCEvent(t *testing.T) { clientMeta := createTestClientMeta() traceMeta := createTestTraceMeta() - err := mimicry.GetProcessor().handleSendRPCEvent(context.Background(), clientMeta, traceMeta, tt.event, xatu.Event_LIBP2P_TRACE_SEND_RPC.String(), "1") + sendRPCEvent, ok := tt.event.(*SendRPCEvent) + require.True(t, ok, "event must be *SendRPCEvent") + + err := mimicry.GetProcessor().handleSendRPCEvent(context.Background(), sendRPCEvent, clientMeta, traceMeta) if tt.expectError { assert.Error(t, err) @@ -2373,7 +2014,7 @@ func Test_handleDropRPCEvent(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event TraceEvent expectError bool validateCalls func(t *testing.T, events []*xatu.DecoratedEvent) setupMockCalls func(*mock.MockSink) @@ -2386,11 +2027,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaControlIWantEnabled: true, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ IWant: []RpcControlIWant{ @@ -2417,11 +2059,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaSubscriptionEnabled: true, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -2455,11 +2098,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaMessageEnabled: true, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Messages: []RpcMetaMsg{ { @@ -2484,11 +2128,12 @@ func Test_handleDropRPCEvent(t *testing.T) { DropRPCEnabled: false, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Messages: []RpcMetaMsg{ { @@ -2515,11 +2160,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: false, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -2546,22 +2192,6 @@ func Test_handleDropRPCEvent(t *testing.T) { expectError: false, setupMockCalls: expectNoEvents, // No events because no child events are enabled }, - { - name: "DROP_RPC with invalid payload", - config: &Config{ - Events: EventConfig{ - DropRPCEnabled: true, - }, - }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: "invalid payload", - }, - expectError: true, // TraceEventToDropRPC returns an error - setupMockCalls: expectNoEvents, - }, { name: "DROP_RPC with PRUNE control messages", config: &Config{ @@ -2570,11 +2200,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaControlPruneEnabled: true, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Prune: []RpcControlPrune{ @@ -2606,11 +2237,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaControlIDontWantEnabled: true, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Control: &RpcMetaControl{ Idontwant: []RpcControlIdontWant{ @@ -2639,11 +2271,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaControlGraftEnabled: true, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, Subscriptions: []RpcMetaSub{ { @@ -2688,11 +2321,12 @@ func Test_handleDropRPCEvent(t *testing.T) { RpcMetaControlIHaveEnabled: true, }, }, - event: &TraceEvent{ - Type: "DropRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: &RpcMeta{ + event: &DropRPCEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Meta: &RpcMeta{ PeerID: peerID, }, }, @@ -2713,7 +2347,10 @@ func Test_handleDropRPCEvent(t *testing.T) { clientMeta := createTestClientMeta() traceMeta := createTestTraceMeta() - err := mimicry.GetProcessor().handleDropRPCEvent(context.Background(), clientMeta, traceMeta, tt.event, xatu.Event_LIBP2P_TRACE_DROP_RPC.String(), "1") + dropRPCEvent, ok := tt.event.(*DropRPCEvent) + require.True(t, ok, "event must be *DropRPCEvent") + + err := mimicry.GetProcessor().handleDropRPCEvent(context.Background(), dropRPCEvent, clientMeta, traceMeta) if tt.expectError { assert.Error(t, err) @@ -2735,15 +2372,24 @@ func countEventsByType(events []*xatu.DecoratedEvent) map[xatu.Event_Name]int { } // Helper function to create a basic RPC event -func createRPCEvent(peerID peer.ID, rpcMeta *RpcMeta) *TraceEvent { - return &TraceEvent{ - Type: "RecvRPC", - PeerID: peerID, - Timestamp: time.Now(), - Payload: rpcMeta, +func createRPCEvent(peerID peer.ID, rpcMeta *RpcMeta) *RecvRPCEvent { + return &RecvRPCEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: rpcMeta, } } +// testMetaProvider implements MetaProvider for tests. +type testMetaProvider struct{} + +// GetClientMeta returns test client metadata. +func (t *testMetaProvider) GetClientMeta(_ context.Context) (*xatu.ClientMeta, error) { + return createTestClientMeta(), nil +} + // Helper function to create test client metadata func createTestClientMeta() *xatu.ClientMeta { return &xatu.ClientMeta{ @@ -2812,7 +2458,7 @@ func createTestMimicry(t *testing.T, config *Config, sink output.Sink) *testMimi nil, // DutiesProvider - not used in these tests &testOutputHandler{sink: sink}, // OutputHandler wrapping the mock sink mimicry.metrics, // MetricsCollector - nil, // MetaProvider - not used in these tests + &testMetaProvider{}, // MetaProvider for tests mimicry.sharder, // UnifiedSharder NewEventCategorizer(), // EventCategorizer wallclock, // EthereumBeaconChain diff --git a/pkg/clmimicry/event_rpc.go b/pkg/clmimicry/event_rpc.go index 44640ae2a..e141fc729 100644 --- a/pkg/clmimicry/event_rpc.go +++ b/pkg/clmimicry/event_rpc.go @@ -15,90 +15,31 @@ import ( "github.com/ethpandaops/xatu/pkg/proto/xatu" ) -// Map of RPC event types to Xatu event types. -var rpcToXatuEventMap = map[string]string{ - TraceEvent_HANDLE_METADATA: xatu.Event_LIBP2P_TRACE_HANDLE_METADATA.String(), - TraceEvent_HANDLE_STATUS: xatu.Event_LIBP2P_TRACE_HANDLE_STATUS.String(), - TraceEvent_CUSTODY_PROBE: xatu.Event_LIBP2P_TRACE_RPC_DATA_COLUMN_CUSTODY_PROBE.String(), -} - -// handleHermesRPCEvent handles Request/Response (RPC) protocol events. -func (p *Processor) handleHermesRPCEvent( - ctx context.Context, - event *TraceEvent, +func (p *Processor) handleMetadataEvent(ctx context.Context, + event *HandleMetadataEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, ) error { - // Map libp2p event to Xatu event. - xatuEvent, err := mapRPCEventToXatuEvent(event.Type) - if err != nil { - p.log.WithField("event", event.Type).Tracef("unsupported event in handleHermesRPCEvent event") - - //nolint:nilerr // we don't want to return an error here. + if !p.events.HandleMetadataEnabled { return nil } - switch xatuEvent { - case xatu.Event_LIBP2P_TRACE_HANDLE_METADATA.String(): - if !p.events.HandleMetadataEnabled { - return nil - } - - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleHandleMetadataEvent(ctx, clientMeta, traceMeta, event) - - case xatu.Event_LIBP2P_TRACE_HANDLE_STATUS.String(): - if !p.events.HandleStatusEnabled { - return nil - } - - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleHandleStatusEvent(ctx, clientMeta, traceMeta, event) - - case xatu.Event_LIBP2P_TRACE_RPC_DATA_COLUMN_CUSTODY_PROBE.String(): - if !p.events.CustodyProbeEnabled { - return nil - } - - // Record that we received this event - networkStr := getNetworkID(clientMeta) - p.metrics.AddEvent(xatuEvent, networkStr) - - // Check if we should process this event based on trace/sharding config. - if !p.ShouldTraceMessage(event, clientMeta, xatuEvent) { - return nil - } - - return p.handleCustodyProbeEvent(ctx, clientMeta, traceMeta, event) + // Build protobuf message directly from typed event + data := &libp2p.HandleMetadata{ + PeerId: wrapperspb.String(event.PeerID.String()), + ProtocolId: wrapperspb.String(string(event.ProtocolID)), + Latency: wrapperspb.Float(float32(event.LatencyS)), + Metadata: &libp2p.Metadata{ + SeqNumber: wrapperspb.UInt64(event.SeqNumber), + Attnets: wrapperspb.String(event.Attnets), + Syncnets: wrapperspb.String(event.Syncnets), + CustodyGroupCount: wrapperspb.UInt64(event.CustodyGroupCount), + }, + Direction: wrapperspb.String(event.Direction), } - return nil -} - -func (p *Processor) handleHandleMetadataEvent(ctx context.Context, - clientMeta *xatu.ClientMeta, - traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, -) error { - data, err := TraceEventToHandleMetadata(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to handle metadata event") + if event.Error != "" { + data.Error = wrapperspb.String(event.Error) } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -129,14 +70,45 @@ func (p *Processor) handleHandleMetadataEvent(ctx context.Context, return p.output.HandleDecoratedEvent(ctx, decoratedEvent) } -func (p *Processor) handleHandleStatusEvent(ctx context.Context, +func (p *Processor) handleStatusEvent(ctx context.Context, + event *HandleStatusEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToHandleStatus(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to handle status event") + if !p.events.HandleStatusEnabled { + return nil + } + + // Build protobuf message directly from typed event + data := &libp2p.HandleStatus{ + PeerId: wrapperspb.String(event.PeerID.String()), + ProtocolId: wrapperspb.String(string(event.ProtocolID)), + Latency: wrapperspb.Float(float32(event.LatencyS)), + Direction: wrapperspb.String(event.Direction), + } + + if event.Request != nil { + data.Request = &libp2p.Status{ + ForkDigest: wrapperspb.String(event.Request.ForkDigest), + FinalizedRoot: wrapperspb.String(event.Request.FinalizedRoot), + FinalizedEpoch: wrapperspb.UInt64(event.Request.FinalizedEpoch), + HeadRoot: wrapperspb.String(event.Request.HeadRoot), + HeadSlot: wrapperspb.UInt64(event.Request.HeadSlot), + } + } + + if event.Response != nil { + data.Response = &libp2p.Status{ + ForkDigest: wrapperspb.String(event.Response.ForkDigest), + FinalizedRoot: wrapperspb.String(event.Response.FinalizedRoot), + FinalizedEpoch: wrapperspb.UInt64(event.Response.FinalizedEpoch), + HeadRoot: wrapperspb.String(event.Response.HeadRoot), + HeadSlot: wrapperspb.UInt64(event.Response.HeadSlot), + } + } + + if event.Error != "" { + data.Error = wrapperspb.String(event.Error) } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -168,13 +140,30 @@ func (p *Processor) handleHandleStatusEvent(ctx context.Context, } func (p *Processor) handleCustodyProbeEvent(ctx context.Context, + event *CustodyProbeEvent, clientMeta *xatu.ClientMeta, traceMeta *libp2p.TraceEventMetadata, - event *TraceEvent, ) error { - data, err := TraceEventToCustodyProbe(event) - if err != nil { - return errors.Wrapf(err, "failed to convert event to custody probe event") + if !p.events.CustodyProbeEnabled { + return nil + } + + // Build protobuf message directly from typed event + //nolint:gosec // G115: int -> uint32 conversions are safe for slot/epoch/index values + data := &libp2p.DataColumnCustodyProbe{ + JobStartTimestamp: timestamppb.New(event.JobStartTimestamp), + PeerId: wrapperspb.String(event.PeerIDStr), + Slot: wrapperspb.UInt32(uint32(event.Slot)), + Epoch: wrapperspb.UInt32(uint32(event.Epoch)), + ColumnIndex: wrapperspb.UInt32(uint32(event.ColumnIndex)), + Result: wrapperspb.String(event.Result), + ResponseTimeMs: wrapperspb.Int64(event.DurationMs), + BeaconBlockRoot: wrapperspb.String(event.BlockHash), + ColumnRowsCount: wrapperspb.UInt32(uint32(event.ColumnSize)), + } + + if event.Error != "" { + data.Error = wrapperspb.String(event.Error) } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -209,7 +198,7 @@ func (p *Processor) handleCustodyProbeEvent(ctx context.Context, } func (p *Processor) deriveAdditionalDataForCustodyProbeEvent( - event *TraceEvent, + event *CustodyProbeEvent, data *libp2p.DataColumnCustodyProbe, traceMeta *libp2p.TraceEventMetadata, ) (*xatu.ClientMeta_AdditionalLibP2PTraceRpcDataColumnCustodyProbeData, error) { @@ -246,11 +235,3 @@ func (p *Processor) deriveAdditionalDataForCustodyProbeEvent( return extra, nil } - -func mapRPCEventToXatuEvent(event string) (string, error) { - if xatuEvent, exists := rpcToXatuEventMap[event]; exists { - return xatuEvent, nil - } - - return "", fmt.Errorf("unknown libp2p rpc event: %s", event) -} diff --git a/pkg/clmimicry/events_gossipsub.go b/pkg/clmimicry/events_gossipsub.go new file mode 100644 index 000000000..c12e97f08 --- /dev/null +++ b/pkg/clmimicry/events_gossipsub.go @@ -0,0 +1,112 @@ +package clmimicry + +// Gossipsub event types that wrap specific payload types from hermes_event_payload.go. +// Each event embeds TraceEventBase and implements the TopicEvent interface. + +// BeaconBlockEvent represents a beacon block gossipsub event. +// The Payload can be one of the various block types depending on the fork. +type BeaconBlockEvent struct { + TraceEventBase + Topic string + MsgID string + Payload any // Union of TraceEvent*Block types from different forks +} + +// GetTopic returns the gossipsub topic for this event. +func (e *BeaconBlockEvent) GetTopic() string { + return e.Topic +} + +// GetMsgID returns the message ID for this event. +func (e *BeaconBlockEvent) GetMsgID() string { + return e.MsgID +} + +var _ TopicEvent = (*BeaconBlockEvent)(nil) +var _ MessageEvent = (*BeaconBlockEvent)(nil) + +// AttestationEvent represents an attestation gossipsub event. +// The Payload can be TraceEventAttestation, TraceEventAttestationElectra, or TraceEventSingleAttestation. +type AttestationEvent struct { + TraceEventBase + Topic string + MsgID string + Payload any // Union of attestation types +} + +// GetTopic returns the gossipsub topic for this event. +func (e *AttestationEvent) GetTopic() string { + return e.Topic +} + +// GetMsgID returns the message ID for this event. +func (e *AttestationEvent) GetMsgID() string { + return e.MsgID +} + +var _ TopicEvent = (*AttestationEvent)(nil) +var _ MessageEvent = (*AttestationEvent)(nil) + +// AggregateAndProofEvent represents an aggregate and proof gossipsub event. +// The Payload can be TraceEventSignedAggregateAttestationAndProof or TraceEventSignedAggregateAttestationAndProofElectra. +type AggregateAndProofEvent struct { + TraceEventBase + Topic string + MsgID string + Payload any // Union of aggregate and proof types +} + +// GetTopic returns the gossipsub topic for this event. +func (e *AggregateAndProofEvent) GetTopic() string { + return e.Topic +} + +// GetMsgID returns the message ID for this event. +func (e *AggregateAndProofEvent) GetMsgID() string { + return e.MsgID +} + +var _ TopicEvent = (*AggregateAndProofEvent)(nil) +var _ MessageEvent = (*AggregateAndProofEvent)(nil) + +// BlobSidecarEvent represents a blob sidecar gossipsub event. +type BlobSidecarEvent struct { + TraceEventBase + Topic string + MsgID string + BlobSidecar *TraceEventBlobSidecar +} + +// GetTopic returns the gossipsub topic for this event. +func (e *BlobSidecarEvent) GetTopic() string { + return e.Topic +} + +// GetMsgID returns the message ID for this event. +func (e *BlobSidecarEvent) GetMsgID() string { + return e.MsgID +} + +var _ TopicEvent = (*BlobSidecarEvent)(nil) +var _ MessageEvent = (*BlobSidecarEvent)(nil) + +// DataColumnSidecarEvent represents a data column sidecar gossipsub event. +type DataColumnSidecarEvent struct { + TraceEventBase + Topic string + MsgID string + DataColumnSidecar *TraceEventDataColumnSidecar +} + +// GetTopic returns the gossipsub topic for this event. +func (e *DataColumnSidecarEvent) GetTopic() string { + return e.Topic +} + +// GetMsgID returns the message ID for this event. +func (e *DataColumnSidecarEvent) GetMsgID() string { + return e.MsgID +} + +var _ TopicEvent = (*DataColumnSidecarEvent)(nil) +var _ MessageEvent = (*DataColumnSidecarEvent)(nil) diff --git a/pkg/clmimicry/events_libp2p_core.go b/pkg/clmimicry/events_libp2p_core.go new file mode 100644 index 000000000..e8d289220 --- /dev/null +++ b/pkg/clmimicry/events_libp2p_core.go @@ -0,0 +1,131 @@ +package clmimicry + +import ( + "time" + + "github.com/libp2p/go-libp2p/core/peer" + ma "github.com/multiformats/go-multiaddr" +) + +// ConnectedEvent represents a libp2p peer connection event. +// This event is emitted when a connection to a remote peer is established. +type ConnectedEvent struct { + TraceEventBase + RemotePeer string + RemoteMaddrs ma.Multiaddr + AgentVersion string + Direction string + Opened time.Time + Limited bool +} + +// Verify interface compliance at compile time +var _ TraceEvent = (*ConnectedEvent)(nil) + +// DisconnectedEvent represents a libp2p peer disconnection event. +// This event is emitted when a connection to a remote peer is closed. +type DisconnectedEvent struct { + TraceEventBase + RemotePeer string + RemoteMaddrs ma.Multiaddr + AgentVersion string + Direction string + Opened time.Time + Limited bool +} + +// Verify interface compliance at compile time +var _ TraceEvent = (*DisconnectedEvent)(nil) + +// SyntheticHeartbeatEvent represents a synthetic heartbeat event for active connections. +// This event is periodically generated to track the health and status of peer connections. +type SyntheticHeartbeatEvent struct { + TraceEventBase + RemotePeer string + RemoteMaddrs ma.Multiaddr + LatencyMs int64 + AgentVersion string + Direction uint32 + Protocols []string + ConnectionAgeNs int64 +} + +// Verify interface compliance at compile time +var _ TraceEvent = (*SyntheticHeartbeatEvent)(nil) + +// NewConnectedEvent creates a new ConnectedEvent with the provided fields. +func NewConnectedEvent( + timestamp time.Time, + peerID peer.ID, + remotePeer string, + remoteMaddrs ma.Multiaddr, + agentVersion string, + direction string, + opened time.Time, + limited bool, +) *ConnectedEvent { + return &ConnectedEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: timestamp, + PeerID: peerID, + }, + RemotePeer: remotePeer, + RemoteMaddrs: remoteMaddrs, + AgentVersion: agentVersion, + Direction: direction, + Opened: opened, + Limited: limited, + } +} + +// NewDisconnectedEvent creates a new DisconnectedEvent with the provided fields. +func NewDisconnectedEvent( + timestamp time.Time, + peerID peer.ID, + remotePeer string, + remoteMaddrs ma.Multiaddr, + agentVersion string, + direction string, + opened time.Time, + limited bool, +) *DisconnectedEvent { + return &DisconnectedEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: timestamp, + PeerID: peerID, + }, + RemotePeer: remotePeer, + RemoteMaddrs: remoteMaddrs, + AgentVersion: agentVersion, + Direction: direction, + Opened: opened, + Limited: limited, + } +} + +// NewSyntheticHeartbeatEvent creates a new SyntheticHeartbeatEvent with the provided fields. +func NewSyntheticHeartbeatEvent( + timestamp time.Time, + peerID peer.ID, + remotePeer string, + remoteMaddrs ma.Multiaddr, + latencyMs int64, + agentVersion string, + direction uint32, + protocols []string, + connectionAgeNs int64, +) *SyntheticHeartbeatEvent { + return &SyntheticHeartbeatEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: timestamp, + PeerID: peerID, + }, + RemotePeer: remotePeer, + RemoteMaddrs: remoteMaddrs, + LatencyMs: latencyMs, + AgentVersion: agentVersion, + Direction: direction, + Protocols: protocols, + ConnectionAgeNs: connectionAgeNs, + } +} diff --git a/pkg/clmimicry/events_libp2p_trace.go b/pkg/clmimicry/events_libp2p_trace.go new file mode 100644 index 000000000..d3540be40 --- /dev/null +++ b/pkg/clmimicry/events_libp2p_trace.go @@ -0,0 +1,203 @@ +package clmimicry + +import ( + "github.com/libp2p/go-libp2p/core/protocol" +) + +// AddPeerEvent represents an event when a peer is added to the libp2p network. +type AddPeerEvent struct { + TraceEventBase + Protocol protocol.ID +} + +// RemovePeerEvent represents an event when a peer is removed from the libp2p network. +type RemovePeerEvent struct { + TraceEventBase +} + +// JoinEvent represents an event when the local node joins a GossipSub topic. +type JoinEvent struct { + TraceEventBase + Topic string +} + +// GetTopic returns the topic that was joined. +func (e JoinEvent) GetTopic() string { + return e.Topic +} + +// LeaveEvent represents an event when the local node leaves a GossipSub topic. +type LeaveEvent struct { + TraceEventBase + Topic string +} + +// GetTopic returns the topic that was left. +func (e LeaveEvent) GetTopic() string { + return e.Topic +} + +// GraftEvent represents a GossipSub GRAFT event. +type GraftEvent struct { + TraceEventBase + Topic string +} + +// GetTopic returns the topic for the graft operation. +func (e GraftEvent) GetTopic() string { + return e.Topic +} + +// PruneEvent represents a GossipSub PRUNE event. +type PruneEvent struct { + TraceEventBase + Topic string +} + +// GetTopic returns the topic for the prune operation. +func (e PruneEvent) GetTopic() string { + return e.Topic +} + +// PublishMessageEvent represents an event when a message is published to GossipSub. +type PublishMessageEvent struct { + TraceEventBase + MsgID string + Topic string +} + +// GetMsgID returns the message ID. +func (e PublishMessageEvent) GetMsgID() string { + return e.MsgID +} + +// GetTopic returns the topic the message was published to. +func (e PublishMessageEvent) GetTopic() string { + return e.Topic +} + +// RejectMessageEvent represents an event when a GossipSub message is rejected. +type RejectMessageEvent struct { + TraceEventBase + MsgID string + Topic string + Reason string + Local bool + MsgSize int + SeqNumber uint64 +} + +// GetMsgID returns the message ID. +func (e *RejectMessageEvent) GetMsgID() string { + return e.MsgID +} + +// GetTopic returns the topic the message was rejected from. +func (e *RejectMessageEvent) GetTopic() string { + return e.Topic +} + +// DuplicateMessageEvent represents an event when a duplicate GossipSub message is received. +type DuplicateMessageEvent struct { + TraceEventBase + MsgID string + Topic string + Local bool + MsgSize int + SeqNumber uint64 +} + +// GetMsgID returns the message ID. +func (e *DuplicateMessageEvent) GetMsgID() string { + return e.MsgID +} + +// GetTopic returns the topic the duplicate message was received on. +func (e *DuplicateMessageEvent) GetTopic() string { + return e.Topic +} + +// DeliverMessageEvent represents an event when a GossipSub message is delivered. +type DeliverMessageEvent struct { + TraceEventBase + MsgID string + Topic string + Local bool + MsgSize int + SeqNumber uint64 +} + +// GetMsgID returns the message ID. +func (e *DeliverMessageEvent) GetMsgID() string { + return e.MsgID +} + +// GetTopic returns the topic the message was delivered on. +func (e *DeliverMessageEvent) GetTopic() string { + return e.Topic +} + +// RecvRPCEvent represents an event when an RPC message is received. +type RecvRPCEvent struct { + TraceEventBase + Meta *RpcMeta +} + +// GetRPCMeta returns the RPC metadata. +func (e *RecvRPCEvent) GetRPCMeta() *RpcMeta { + return e.Meta +} + +// SendRPCEvent represents an event when an RPC message is sent. +type SendRPCEvent struct { + TraceEventBase + Meta *RpcMeta +} + +// GetRPCMeta returns the RPC metadata. +func (e *SendRPCEvent) GetRPCMeta() *RpcMeta { + return e.Meta +} + +// DropRPCEvent represents an event when an RPC message is dropped. +type DropRPCEvent struct { + TraceEventBase + Meta *RpcMeta +} + +// GetRPCMeta returns the RPC metadata. +func (e *DropRPCEvent) GetRPCMeta() *RpcMeta { + return e.Meta +} + +// Compile-time interface compliance checks +var ( + _ TraceEvent = (*AddPeerEvent)(nil) + _ TraceEvent = (*RemovePeerEvent)(nil) + _ TraceEvent = (*JoinEvent)(nil) + _ TopicEvent = (*JoinEvent)(nil) + _ TraceEvent = (*LeaveEvent)(nil) + _ TopicEvent = (*LeaveEvent)(nil) + _ TraceEvent = (*GraftEvent)(nil) + _ TopicEvent = (*GraftEvent)(nil) + _ TraceEvent = (*PruneEvent)(nil) + _ TopicEvent = (*PruneEvent)(nil) + _ TraceEvent = (*PublishMessageEvent)(nil) + _ MessageEvent = (*PublishMessageEvent)(nil) + _ TopicEvent = (*PublishMessageEvent)(nil) + _ TraceEvent = (*RejectMessageEvent)(nil) + _ MessageEvent = (*RejectMessageEvent)(nil) + _ TopicEvent = (*RejectMessageEvent)(nil) + _ TraceEvent = (*DuplicateMessageEvent)(nil) + _ MessageEvent = (*DuplicateMessageEvent)(nil) + _ TopicEvent = (*DuplicateMessageEvent)(nil) + _ TraceEvent = (*DeliverMessageEvent)(nil) + _ MessageEvent = (*DeliverMessageEvent)(nil) + _ TopicEvent = (*DeliverMessageEvent)(nil) + _ TraceEvent = (*RecvRPCEvent)(nil) + _ RPCMetaEvent = (*RecvRPCEvent)(nil) + _ TraceEvent = (*SendRPCEvent)(nil) + _ RPCMetaEvent = (*SendRPCEvent)(nil) + _ TraceEvent = (*DropRPCEvent)(nil) + _ RPCMetaEvent = (*DropRPCEvent)(nil) +) diff --git a/pkg/clmimicry/events_rpc.go b/pkg/clmimicry/events_rpc.go new file mode 100644 index 000000000..d05bf7d66 --- /dev/null +++ b/pkg/clmimicry/events_rpc.go @@ -0,0 +1,151 @@ +package clmimicry + +import ( + "time" + + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" +) + +// HandleMetadataEvent represents a metadata exchange event in the request/response protocol. +type HandleMetadataEvent struct { + TraceEventBase + ProtocolID protocol.ID + LatencyS float64 + SeqNumber uint64 + Attnets string + Syncnets string + CustodyGroupCount uint64 + Error string + Direction string +} + +// StatusData contains the status information exchanged in a status request/response. +type StatusData struct { + ForkDigest string + FinalizedRoot string + FinalizedEpoch uint64 + HeadRoot string + HeadSlot uint64 + EarliestAvailableSlot uint64 +} + +// HandleStatusEvent represents a status exchange event in the request/response protocol. +type HandleStatusEvent struct { + TraceEventBase + ProtocolID protocol.ID + LatencyS float64 + Direction string + Request *StatusData + Response *StatusData + Error string +} + +// CustodyProbeEvent represents a data column custody probe event. +type CustodyProbeEvent struct { + TraceEventBase + JobStartTimestamp time.Time + PeerIDStr string // PeerID as string (different from TraceEventBase.PeerID) + Slot uint64 + Epoch uint64 + ColumnIndex uint64 + Result string + DurationMs int64 + Error string + BlockHash string + ColumnSize int +} + +// Compile-time interface compliance checks. +var ( + _ TraceEvent = (*HandleMetadataEvent)(nil) + _ TraceEvent = (*HandleStatusEvent)(nil) + _ TraceEvent = (*CustodyProbeEvent)(nil) +) + +// NewHandleMetadataEvent creates a new HandleMetadataEvent with the provided fields. +func NewHandleMetadataEvent( + timestamp time.Time, + peerID peer.ID, + protocolID protocol.ID, + latencyS float64, + seqNumber uint64, + attnets string, + syncnets string, + custodyGroupCount uint64, + errorStr string, + direction string, +) *HandleMetadataEvent { + return &HandleMetadataEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: timestamp, + PeerID: peerID, + }, + ProtocolID: protocolID, + LatencyS: latencyS, + SeqNumber: seqNumber, + Attnets: attnets, + Syncnets: syncnets, + CustodyGroupCount: custodyGroupCount, + Error: errorStr, + Direction: direction, + } +} + +// NewHandleStatusEvent creates a new HandleStatusEvent with the provided fields. +func NewHandleStatusEvent( + timestamp time.Time, + peerID peer.ID, + protocolID protocol.ID, + latencyS float64, + direction string, + request *StatusData, + response *StatusData, + errorStr string, +) *HandleStatusEvent { + return &HandleStatusEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: timestamp, + PeerID: peerID, + }, + ProtocolID: protocolID, + LatencyS: latencyS, + Direction: direction, + Request: request, + Response: response, + Error: errorStr, + } +} + +// NewCustodyProbeEvent creates a new CustodyProbeEvent with the provided fields. +func NewCustodyProbeEvent( + timestamp time.Time, + peerID peer.ID, + jobStartTimestamp time.Time, + peerIDStr string, + slot uint64, + epoch uint64, + columnIndex uint64, + result string, + durationMs int64, + errorStr string, + blockHash string, + columnSize int, +) *CustodyProbeEvent { + return &CustodyProbeEvent{ + TraceEventBase: TraceEventBase{ + Timestamp: timestamp, + PeerID: peerID, + }, + JobStartTimestamp: jobStartTimestamp, + PeerIDStr: peerIDStr, + Slot: slot, + Epoch: epoch, + ColumnIndex: columnIndex, + Result: result, + DurationMs: durationMs, + Error: errorStr, + BlockHash: blockHash, + ColumnSize: columnSize, + } +} diff --git a/pkg/clmimicry/gossipsub_aggregate_and_proof.go b/pkg/clmimicry/gossipsub_aggregate_and_proof.go index c2b3beb60..468d171d6 100644 --- a/pkg/clmimicry/gossipsub_aggregate_and_proof.go +++ b/pkg/clmimicry/gossipsub_aggregate_and_proof.go @@ -31,24 +31,25 @@ func deriveCommitteeIndexFromBits(committeeBits []byte) uint64 { func (p *Processor) handleGossipAggregateAndProof( ctx context.Context, + event *AggregateAndProofEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, - payload any, + traceMeta *libp2p.TraceEventMetadata, ) error { - switch evt := payload.(type) { + switch evt := event.Payload.(type) { case *TraceEventSignedAggregateAttestationAndProof: - return p.handleAggregateAndProofFromAttestation(ctx, clientMeta, event, evt) + return p.handleAggregateAndProofFromAttestation(ctx, event, clientMeta, traceMeta, evt) case *TraceEventSignedAggregateAttestationAndProofElectra: - return p.handleAggregateAndProofFromAttestationElectra(ctx, clientMeta, event, evt) + return p.handleAggregateAndProofFromAttestationElectra(ctx, event, clientMeta, traceMeta, evt) default: - return fmt.Errorf("unsupported payload type for aggregate and proof: %T", payload) + return fmt.Errorf("unsupported payload type for aggregate and proof: %T", event.Payload) } } func (p *Processor) handleAggregateAndProofFromAttestation( ctx context.Context, + event *AggregateAndProofEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, + traceMeta *libp2p.TraceEventMetadata, payload *TraceEventSignedAggregateAttestationAndProof, ) error { if payload.SignedAggregateAttestationAndProof == nil || payload.SignedAggregateAttestationAndProof.GetMessage() == nil { @@ -94,7 +95,7 @@ func (p *Processor) handleAggregateAndProofFromAttestation( return fmt.Errorf("failed to clone client metadata") } - additionalData, err := p.createAdditionalGossipSubAggregateAndProofData(ctx, event, aggregateAndProof, phase0.Slot(attestationData.GetSlot()), uint64(message.GetAggregatorIndex()), payload.PeerID, payload.Topic, payload.MsgID, payload.MsgSize) + additionalData, err := p.createAdditionalGossipSubAggregateAndProofData(ctx, &event.TraceEventBase, aggregateAndProof, phase0.Slot(attestationData.GetSlot()), uint64(message.GetAggregatorIndex()), payload.PeerID, payload.Topic, payload.MsgID, payload.MsgSize) if err != nil { return fmt.Errorf("failed to create additional data: %w", err) } @@ -106,7 +107,7 @@ func (p *Processor) handleAggregateAndProofFromAttestation( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_AGGREGATE_AND_PROOF, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -122,8 +123,9 @@ func (p *Processor) handleAggregateAndProofFromAttestation( func (p *Processor) handleAggregateAndProofFromAttestationElectra( ctx context.Context, + event *AggregateAndProofEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, + traceMeta *libp2p.TraceEventMetadata, payload *TraceEventSignedAggregateAttestationAndProofElectra, ) error { if payload.SignedAggregateAttestationAndProofElectra == nil || payload.SignedAggregateAttestationAndProofElectra.GetMessage() == nil { @@ -170,7 +172,7 @@ func (p *Processor) handleAggregateAndProofFromAttestationElectra( return fmt.Errorf("failed to clone client metadata") } - additionalData, err := p.createAdditionalGossipSubAggregateAndProofData(ctx, event, aggregateAndProof, phase0.Slot(attestationData.GetSlot()), uint64(message.GetAggregatorIndex()), payload.PeerID, payload.Topic, payload.MsgID, payload.MsgSize) + additionalData, err := p.createAdditionalGossipSubAggregateAndProofData(ctx, &event.TraceEventBase, aggregateAndProof, phase0.Slot(attestationData.GetSlot()), uint64(message.GetAggregatorIndex()), payload.PeerID, payload.Topic, payload.MsgID, payload.MsgSize) if err != nil { return fmt.Errorf("failed to create additional data: %w", err) } @@ -182,7 +184,7 @@ func (p *Processor) handleAggregateAndProofFromAttestationElectra( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_AGGREGATE_AND_PROOF, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -198,7 +200,7 @@ func (p *Processor) handleAggregateAndProofFromAttestationElectra( func (p *Processor) createAdditionalGossipSubAggregateAndProofData( ctx context.Context, - event *TraceEvent, + event *TraceEventBase, aggregateAndProof *v1.SignedAggregateAttestationAndProofV2, slotNumber phase0.Slot, aggregatorIndex uint64, @@ -207,14 +209,14 @@ func (p *Processor) createAdditionalGossipSubAggregateAndProofData( msgID string, msgSize int, ) (*xatu.ClientMeta_AdditionalLibP2PTraceGossipSubAggregateAndProofData, error) { - wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.Timestamp) + wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.GetTimestamp()) if err != nil { return nil, fmt.Errorf("failed to get wallclock time: %w", err) } slot := p.wallclock.Slots().FromNumber(uint64(slotNumber)) epoch := p.wallclock.Epochs().FromSlot(uint64(slotNumber)) - timestampAdjusted := event.Timestamp.Add(p.clockDrift) + timestampAdjusted := event.GetTimestamp().Add(p.clockDrift) // Calculate propagation timing diff := timestampAdjusted.Sub(slot.TimeWindow().Start()).Milliseconds() diff --git a/pkg/clmimicry/gossipsub_attestation.go b/pkg/clmimicry/gossipsub_attestation.go index c74867fe6..751bbbb91 100644 --- a/pkg/clmimicry/gossipsub_attestation.go +++ b/pkg/clmimicry/gossipsub_attestation.go @@ -17,10 +17,15 @@ import ( func (p *Processor) handleGossipAttestation( ctx context.Context, + event *AttestationEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, - payload *TraceEventAttestation, + traceMeta *libp2p.TraceEventMetadata, ) error { + payload, ok := event.Payload.(*TraceEventAttestation) + if !ok { + return fmt.Errorf("handleGossipAttestation() called with invalid payload type: %T", event.Payload) + } + if payload.Attestation == nil || payload.Attestation.GetData() == nil { return fmt.Errorf("handleGossipAttestation() called with nil attestation") } @@ -49,7 +54,7 @@ func (p *Processor) handleGossipAttestation( return fmt.Errorf("failed to clone client metadata") } - additionalData, err := p.createAdditionalGossipSubAttestationData(payload, attestationData, event) + additionalData, err := p.createAdditionalGossipSubAttestationData(payload, attestationData, &event.TraceEventBase) if err != nil { return fmt.Errorf("failed to create additional data: %w", err) } @@ -61,7 +66,7 @@ func (p *Processor) handleGossipAttestation( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_ATTESTATION, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -79,15 +84,15 @@ func (p *Processor) handleGossipAttestation( func (p *Processor) createAdditionalGossipSubAttestationData( payload *TraceEventAttestation, attestationData *ethtypes.AttestationData, - event *TraceEvent, + event *TraceEventBase, ) (*xatu.ClientMeta_AdditionalLibP2PTraceGossipSubBeaconAttestationData, error) { - wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.Timestamp) + wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.GetTimestamp()) if err != nil { return nil, fmt.Errorf("failed to get wallclock time: %w", err) } // Add Clock Drift - timestampAdjusted := event.Timestamp.Add(p.clockDrift) + timestampAdjusted := event.GetTimestamp().Add(p.clockDrift) attestionSlot := p.wallclock.Slots().FromNumber(uint64(attestationData.GetSlot())) epoch := p.wallclock.Epochs().FromSlot(uint64(attestationData.GetSlot())) diff --git a/pkg/clmimicry/gossipsub_beacon_block.go b/pkg/clmimicry/gossipsub_beacon_block.go index 23186e72c..383c526be 100644 --- a/pkg/clmimicry/gossipsub_beacon_block.go +++ b/pkg/clmimicry/gossipsub_beacon_block.go @@ -16,9 +16,9 @@ import ( func (p *Processor) handleGossipBeaconBlock( ctx context.Context, + event *BeaconBlockEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, - payload any, + traceMeta *libp2p.TraceEventMetadata, ) error { var ( err error @@ -27,7 +27,7 @@ func (p *Processor) handleGossipBeaconBlock( proposerIndex primitives.ValidatorIndex ) - switch evt := payload.(type) { + switch evt := event.Payload.(type) { case *TraceEventPhase0Block: root, err = evt.Block.GetBlock().HashTreeRoot() slot = evt.Block.GetBlock().GetSlot() @@ -75,7 +75,7 @@ func (p *Processor) handleGossipBeaconBlock( return fmt.Errorf("failed to clone client metadata") } - additionalData, err := p.createAdditionalGossipSubBeaconBlockData(payload, slot, event) + additionalData, err := p.createAdditionalGossipSubBeaconBlockData(event.Payload, slot, &event.TraceEventBase) if err != nil { return fmt.Errorf("failed to create additional data: %w", err) } @@ -87,7 +87,7 @@ func (p *Processor) handleGossipBeaconBlock( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_BLOCK, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -105,9 +105,9 @@ func (p *Processor) handleGossipBeaconBlock( func (p *Processor) createAdditionalGossipSubBeaconBlockData( payload any, slotNumber primitives.Slot, - event *TraceEvent, + event *TraceEventBase, ) (*xatu.ClientMeta_AdditionalLibP2PTraceGossipSubBeaconBlockData, error) { - wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.Timestamp) + wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.GetTimestamp()) if err != nil { return nil, fmt.Errorf("failed to get wallclock time: %w", err) } @@ -124,7 +124,7 @@ func (p *Processor) createAdditionalGossipSubBeaconBlockData( } // Add Clock Drift - timestampAdjusted := event.Timestamp.Add(p.clockDrift) + timestampAdjusted := event.GetTimestamp().Add(p.clockDrift) slot := p.wallclock.Slots().FromNumber(uint64(slotNumber)) epoch := p.wallclock.Epochs().FromSlot(uint64(slotNumber)) diff --git a/pkg/clmimicry/gossipsub_blob_sidecar.go b/pkg/clmimicry/gossipsub_blob_sidecar.go index 85bf4505c..f538097b0 100644 --- a/pkg/clmimicry/gossipsub_blob_sidecar.go +++ b/pkg/clmimicry/gossipsub_blob_sidecar.go @@ -18,15 +18,15 @@ import ( func (p *Processor) handleGossipBlobSidecar( ctx context.Context, + event *BlobSidecarEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, - payload *TraceEventBlobSidecar, + traceMeta *libp2p.TraceEventMetadata, ) error { - if payload.BlobSidecar == nil { + if event.BlobSidecar == nil || event.BlobSidecar.BlobSidecar == nil { return fmt.Errorf("handleGossipBlobSidecar() called with nil blob sidecar") } - header := payload.BlobSidecar.GetSignedBlockHeader().GetHeader() + header := event.BlobSidecar.BlobSidecar.GetSignedBlockHeader().GetHeader() blockRoot, err := header.HashTreeRoot() if err != nil { @@ -34,7 +34,7 @@ func (p *Processor) handleGossipBlobSidecar( } data := &gossipsub.BlobSidecar{ - Index: wrapperspb.UInt64(payload.BlobSidecar.GetIndex()), + Index: wrapperspb.UInt64(event.BlobSidecar.BlobSidecar.GetIndex()), Slot: wrapperspb.UInt64(uint64(header.GetSlot())), ProposerIndex: wrapperspb.UInt64(uint64(header.GetProposerIndex())), StateRoot: wrapperspb.String(hex.EncodeToString(header.GetStateRoot())), @@ -47,7 +47,7 @@ func (p *Processor) handleGossipBlobSidecar( return fmt.Errorf("failed to clone client metadata") } - additionalData, err := p.createAdditionalGossipSubBlobSidecarData(payload, event.Timestamp) + additionalData, err := p.createAdditionalGossipSubBlobSidecarData(event.BlobSidecar, event.GetTimestamp()) if err != nil { return fmt.Errorf("failed to create additional data: %w", err) } @@ -59,7 +59,7 @@ func (p *Processor) handleGossipBlobSidecar( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BLOB_SIDECAR, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ diff --git a/pkg/clmimicry/gossipsub_data_column_sidecar.go b/pkg/clmimicry/gossipsub_data_column_sidecar.go index 7a60370ed..b6f4d1320 100644 --- a/pkg/clmimicry/gossipsub_data_column_sidecar.go +++ b/pkg/clmimicry/gossipsub_data_column_sidecar.go @@ -18,15 +18,15 @@ import ( func (p *Processor) handleGossipDataColumnSidecar( ctx context.Context, + event *DataColumnSidecarEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, - payload *TraceEventDataColumnSidecar, + traceMeta *libp2p.TraceEventMetadata, ) error { - if payload.DataColumnSidecar == nil { + if event.DataColumnSidecar == nil || event.DataColumnSidecar.DataColumnSidecar == nil { return fmt.Errorf("handleGossipDataColumnSidecar() called with nil data column sidecar") } - header := payload.DataColumnSidecar.GetSignedBlockHeader().GetHeader() + header := event.DataColumnSidecar.DataColumnSidecar.GetSignedBlockHeader().GetHeader() blockRoot, err := header.HashTreeRoot() if err != nil { @@ -34,13 +34,13 @@ func (p *Processor) handleGossipDataColumnSidecar( } data := &gossipsub.DataColumnSidecar{ - Index: wrapperspb.UInt64(payload.DataColumnSidecar.GetIndex()), + Index: wrapperspb.UInt64(event.DataColumnSidecar.DataColumnSidecar.GetIndex()), Slot: wrapperspb.UInt64(uint64(header.GetSlot())), ProposerIndex: wrapperspb.UInt64(uint64(header.GetProposerIndex())), StateRoot: wrapperspb.String(hex.EncodeToString(header.GetStateRoot())), ParentRoot: wrapperspb.String(hex.EncodeToString(header.GetParentRoot())), BlockRoot: wrapperspb.String(hex.EncodeToString(blockRoot[:])), - KzgCommitmentsCount: wrapperspb.UInt32(uint32(len(payload.DataColumnSidecar.GetKzgCommitments()))), //nolint:gosec // conversion fine. + KzgCommitmentsCount: wrapperspb.UInt32(uint32(len(event.DataColumnSidecar.DataColumnSidecar.GetKzgCommitments()))), //nolint:gosec // conversion fine. } metadata, ok := proto.Clone(clientMeta).(*xatu.ClientMeta) @@ -48,7 +48,7 @@ func (p *Processor) handleGossipDataColumnSidecar( return fmt.Errorf("failed to clone client metadata") } - additionalData, err := p.createAdditionalGossipSubDataColumnSidecarData(payload, event.Timestamp) + additionalData, err := p.createAdditionalGossipSubDataColumnSidecarData(event.DataColumnSidecar, event.GetTimestamp()) if err != nil { return fmt.Errorf("failed to create additional data: %w", err) } @@ -60,7 +60,7 @@ func (p *Processor) handleGossipDataColumnSidecar( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_DATA_COLUMN_SIDECAR, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ diff --git a/pkg/clmimicry/gossipsub_data_column_sidecar_test.go b/pkg/clmimicry/gossipsub_data_column_sidecar_test.go index 1c900446c..9981c3acb 100644 --- a/pkg/clmimicry/gossipsub_data_column_sidecar_test.go +++ b/pkg/clmimicry/gossipsub_data_column_sidecar_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethpandaops/ethwallclock" "github.com/ethpandaops/xatu/pkg/output" "github.com/ethpandaops/xatu/pkg/output/mock" + "github.com/ethpandaops/xatu/pkg/proto/libp2p" "github.com/ethpandaops/xatu/pkg/proto/xatu" "github.com/google/uuid" "github.com/libp2p/go-libp2p/core/peer" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/wrapperspb" ) func TestDataColumnSidecarIntegration(t *testing.T) { @@ -69,11 +71,6 @@ func TestDataColumnSidecarIntegration(t *testing.T) { // Process each sidecar for _, sidecar := range sidecars { - event := &TraceEvent{ - Type: "HANDLE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - } payload := &TraceEventDataColumnSidecar{ TraceEventPayloadMetaData: TraceEventPayloadMetaData{ @@ -100,11 +97,22 @@ func TestDataColumnSidecarIntegration(t *testing.T) { }, } + // Create a DataColumnSidecarEvent with the payload + dataColumnEvent := &DataColumnSidecarEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Topic: payload.Topic, + MsgID: payload.MsgID, + DataColumnSidecar: payload, + } + err = mimicry.processor.handleGossipDataColumnSidecar( context.Background(), + dataColumnEvent, createTestClientMeta(), - event, - payload, + createTestTraceMeta(), ) assert.NoError(t, err) } @@ -236,15 +244,19 @@ func TestDataColumnSidecarEdgeCases(t *testing.T) { }, } + traceMeta := &libp2p.TraceEventMetadata{PeerId: wrapperspb.String(peerID.String())} err = mimicry.processor.handleGossipDataColumnSidecar( context.Background(), - createTestClientMeta(), - &TraceEvent{ - Type: "HANDLE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), + &DataColumnSidecarEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Topic: "/eth2/test/data_column_sidecar_0/ssz_snappy", + DataColumnSidecar: payload, }, - payload, + createTestClientMeta(), + traceMeta, ) assert.NoError(t, err) }) @@ -286,15 +298,19 @@ func TestDataColumnSidecarEdgeCases(t *testing.T) { }, } + traceMeta := &libp2p.TraceEventMetadata{PeerId: wrapperspb.String(peerID.String())} err = mimicry.processor.handleGossipDataColumnSidecar( context.Background(), - createTestClientMeta(), - &TraceEvent{ - Type: "HANDLE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), + &DataColumnSidecarEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Topic: "/eth2/test/data_column_sidecar_127/ssz_snappy", + DataColumnSidecar: payload, }, - payload, + createTestClientMeta(), + traceMeta, ) assert.NoError(t, err) }) @@ -337,7 +353,7 @@ func createTestMimicryWithWallclock(t *testing.T, config *Config, sink output.Si nil, // DutiesProvider - not used in these tests &testOutputHandler{sink: sink}, // OutputHandler wrapping the mock sink mimicry.metrics, // MetricsCollector - nil, // MetaProvider - not used in these tests + &testMetaProvider{}, // MetaProvider for tests mimicry.sharder, // UnifiedSharder NewEventCategorizer(), // EventCategorizer wallclock, // EthereumBeaconChain @@ -362,7 +378,7 @@ func Test_handleGossipDataColumnSidecar(t *testing.T) { tests := []struct { name string config *Config - event *TraceEvent + event *DataColumnSidecarEvent payload *TraceEventDataColumnSidecar expectError bool errorMessage string @@ -376,11 +392,12 @@ func Test_handleGossipDataColumnSidecar(t *testing.T) { GossipSubDataColumnSidecarEnabled: true, }, }, - event: &TraceEvent{ - Type: "HANDLE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", + event: &DataColumnSidecarEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", }, payload: &TraceEventDataColumnSidecar{ TraceEventPayloadMetaData: TraceEventPayloadMetaData{ @@ -459,11 +476,12 @@ func Test_handleGossipDataColumnSidecar(t *testing.T) { GossipSubDataColumnSidecarEnabled: true, }, }, - event: &TraceEvent{ - Type: "HANDLE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", + event: &DataColumnSidecarEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", }, payload: &TraceEventDataColumnSidecar{ TraceEventPayloadMetaData: TraceEventPayloadMetaData{ @@ -484,11 +502,12 @@ func Test_handleGossipDataColumnSidecar(t *testing.T) { GossipSubDataColumnSidecarEnabled: false, }, }, - event: &TraceEvent{ - Type: "HANDLE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", + event: &DataColumnSidecarEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", }, payload: &TraceEventDataColumnSidecar{ TraceEventPayloadMetaData: TraceEventPayloadMetaData{ @@ -519,11 +538,12 @@ func Test_handleGossipDataColumnSidecar(t *testing.T) { GossipSubDataColumnSidecarEnabled: true, }, }, - event: &TraceEvent{ - Type: "HANDLE_MESSAGE", - PeerID: peerID, - Timestamp: time.Now(), - Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", + event: &DataColumnSidecarEvent{ + TraceEventBase: TraceEventBase{ + PeerID: peerID, + Timestamp: time.Now(), + }, + Topic: "/eth2/fc90fcde/data_column_sidecar_0/ssz_snappy", }, payload: &TraceEventDataColumnSidecar{ TraceEventPayloadMetaData: TraceEventPayloadMetaData{ @@ -588,20 +608,14 @@ func Test_handleGossipDataColumnSidecar(t *testing.T) { tt.setupMockCalls(mockSink) } - // Create client metadata - clientMeta := createTestClientMeta() - - // Set the payload in the event - tt.event.Payload = tt.payload + // Embed the payload in the event + tt.event.DataColumnSidecar = tt.payload + tt.event.Topic = tt.payload.Topic + tt.event.MsgID = tt.payload.MsgID // Call the gossipsub event handler which routes to the data column sidecar handler // This ensures the event enabled check is properly applied - err := mimicry.processor.handleHermesGossipSubEvent( - context.Background(), - tt.event, - clientMeta, - createTestTraceMeta(), - ) + err := mimicry.processor.HandleHermesEvent(context.Background(), tt.event) // Validate error expectations if tt.expectError { diff --git a/pkg/clmimicry/gossipsub_single_attestation.go b/pkg/clmimicry/gossipsub_single_attestation.go index d728c0a24..722753a39 100644 --- a/pkg/clmimicry/gossipsub_single_attestation.go +++ b/pkg/clmimicry/gossipsub_single_attestation.go @@ -16,10 +16,15 @@ import ( func (p *Processor) handleGossipSingleAttestation( ctx context.Context, + event *AttestationEvent, clientMeta *xatu.ClientMeta, - event *TraceEvent, - payload *TraceEventSingleAttestation, + traceMeta *libp2p.TraceEventMetadata, ) error { + payload, ok := event.Payload.(*TraceEventSingleAttestation) + if !ok { + return fmt.Errorf("handleGossipSingleAttestation() called with invalid payload type: %T", event.Payload) + } + if payload.SingleAttestation == nil || payload.SingleAttestation.GetData() == nil { return fmt.Errorf("handleGossipSingleAttestation() called with nil attestation") } @@ -48,7 +53,7 @@ func (p *Processor) handleGossipSingleAttestation( return fmt.Errorf("failed to clone client metadata") } - additionalData, err := p.createAdditionalGossipSubSingleAttestationData(payload, attestationData, event) + additionalData, err := p.createAdditionalGossipSubSingleAttestationData(payload, attestationData, &event.TraceEventBase) if err != nil { return fmt.Errorf("failed to create additional data: %w", err) } @@ -60,7 +65,7 @@ func (p *Processor) handleGossipSingleAttestation( decoratedEvent := &xatu.DecoratedEvent{ Event: &xatu.Event{ Name: xatu.Event_LIBP2P_TRACE_GOSSIPSUB_BEACON_ATTESTATION, - DateTime: timestamppb.New(event.Timestamp.Add(p.clockDrift)), + DateTime: timestamppb.New(event.GetTimestamp().Add(p.clockDrift)), Id: uuid.New().String(), }, Meta: &xatu.Meta{ @@ -78,15 +83,15 @@ func (p *Processor) handleGossipSingleAttestation( func (p *Processor) createAdditionalGossipSubSingleAttestationData( payload *TraceEventSingleAttestation, attestationData *ethtypes.AttestationData, - event *TraceEvent, + event *TraceEventBase, ) (*xatu.ClientMeta_AdditionalLibP2PTraceGossipSubBeaconAttestationData, error) { - wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.Timestamp) + wallclockSlot, wallclockEpoch, err := p.wallclock.FromTime(event.GetTimestamp()) if err != nil { return nil, fmt.Errorf("failed to get wallclock time: %w", err) } // Add Clock Drift - timestampAdjusted := event.Timestamp.Add(p.clockDrift) + timestampAdjusted := event.GetTimestamp().Add(p.clockDrift) attestionSlot := p.wallclock.Slots().FromNumber(uint64(attestationData.GetSlot())) epoch := p.wallclock.Epochs().FromSlot(uint64(attestationData.GetSlot())) diff --git a/pkg/clmimicry/hermes_trace_event.go b/pkg/clmimicry/hermes_trace_event.go index fb8ea920a..eca962461 100644 --- a/pkg/clmimicry/hermes_trace_event.go +++ b/pkg/clmimicry/hermes_trace_event.go @@ -4,24 +4,6 @@ package clmimicry -import ( - "time" - - "github.com/libp2p/go-libp2p/core/peer" -) - -// TraceEvent represents a trace event from the libp2p network. -// Extracted from github.com/probe-lab/hermes/host. -// -//nolint:tagliatelle // JSON tags match Hermes format for compatibility -type TraceEvent struct { - Type string - Topic string - PeerID peer.ID - Timestamp time.Time - Payload any `json:"Data"` // cannot use field "Data" because of gk.Record method -} - // TraceEventPayloadMetaData contains metadata for trace event payloads. // Extracted from github.com/probe-lab/hermes/host. // diff --git a/pkg/clmimicry/trace.go b/pkg/clmimicry/trace.go index d7afd8a1c..20b23aed6 100644 --- a/pkg/clmimicry/trace.go +++ b/pkg/clmimicry/trace.go @@ -38,7 +38,7 @@ func IsShardActive(shard uint64, activeShards []uint64) bool { // ShouldTraceMessage determines whether a message with the given MsgID should be included // in the sample based on the configured trace settings. func (p *Processor) ShouldTraceMessage( - event *TraceEvent, + event TraceEvent, clientMeta *xatu.ClientMeta, xatuEventType string, ) bool { @@ -57,13 +57,12 @@ func (p *Processor) ShouldTraceMessage( // Extract message ID and topics using the existing helper functions. msgID = GetMsgID(event) - // Get all topics from the event. - topics = GetGossipTopics(event) - topic string + // Get topic from the event if it implements TopicEvent. + topic string ) - if len(topics) > 0 { - topic = topics[0] // Use the first topic if available + if te, ok := event.(TopicEvent); ok { + topic = te.GetTopic() } // Use the unified sharder to determine if we should process this event diff --git a/pkg/clmimicry/trace_convert.go b/pkg/clmimicry/trace_convert.go deleted file mode 100644 index a6c416e20..000000000 --- a/pkg/clmimicry/trace_convert.go +++ /dev/null @@ -1,808 +0,0 @@ -package clmimicry - -import ( - "encoding/hex" - "fmt" - "time" - - "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - ma "github.com/multiformats/go-multiaddr" - "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" - - "github.com/ethpandaops/xatu/pkg/proto/libp2p" -) - -// Helper function to convert a Hermes TraceEvent to a libp2p AddPeer -func TraceEventToAddPeer(event *TraceEvent) (*libp2p.AddPeer, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for AddPeer") - } - - peerID, ok := payload["PeerID"].(peer.ID) - if !ok { - return nil, fmt.Errorf("peerID is required for AddPeer") - } - - protoc, ok := payload["Protocol"].(protocol.ID) - if !ok { - return nil, fmt.Errorf("protocol is required for AddPeer") - } - - return &libp2p.AddPeer{ - PeerId: wrapperspb.String(peerID.String()), - Protocol: wrapperspb.String(string(protoc)), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p RemovePeer -func TraceEventToRemovePeer(event *TraceEvent) (*libp2p.RemovePeer, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for RemovePeer") - } - - peerID, ok := payload["PeerID"].(peer.ID) - if !ok { - return nil, fmt.Errorf("peerID is required for RemovePeer") - } - - return &libp2p.RemovePeer{ - PeerId: wrapperspb.String(peerID.String()), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p Join -func TraceEventToJoin(event *TraceEvent) (*libp2p.Join, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for Join") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for Join") - } - - return &libp2p.Join{ - Topic: wrapperspb.String(topic), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p Leave -func TraceEventToLeave(event *TraceEvent) (*libp2p.Leave, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for Leave") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for Leave") - } - - return &libp2p.Leave{ - Topic: wrapperspb.String(topic), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p Graft. -func TraceEventToGraft(event *TraceEvent) (*libp2p.Graft, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for Graft") - } - - peerID, ok := payload["PeerID"].(peer.ID) - if !ok { - return nil, fmt.Errorf("peerID is required for Graft") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for Graft") - } - - return &libp2p.Graft{ - Topic: wrapperspb.String(topic), - PeerId: wrapperspb.String(peerID.String()), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p Prune. -func TraceEventToPrune(event *TraceEvent) (*libp2p.Prune, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for Prune") - } - - peerID, ok := payload["PeerID"].(peer.ID) - if !ok { - return nil, fmt.Errorf("peerID is required for Prune") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for Prune") - } - - return &libp2p.Prune{ - Topic: wrapperspb.String(topic), - PeerId: wrapperspb.String(peerID.String()), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p RecvRPC. -func TraceEventToRecvRPC(event *TraceEvent) (*libp2p.RecvRPC, error) { - payload, ok := event.Payload.(*RpcMeta) - if !ok { - return nil, fmt.Errorf("invalid payload type for rpc") - } - - r := &libp2p.RecvRPC{ - PeerId: wrapperspb.String(payload.PeerID.String()), - Meta: &libp2p.RPCMeta{ - PeerId: wrapperspb.String(payload.PeerID.String()), - Messages: convertRPCMessages(payload.Messages), - Subscriptions: convertRPCSubscriptions(payload.Subscriptions), - Control: convertRPCControl(payload.Control), - }, - } - - return r, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p SendRPC. -func TraceEventToSendRPC(event *TraceEvent) (*libp2p.SendRPC, error) { - payload, ok := event.Payload.(*RpcMeta) - if !ok { - return nil, fmt.Errorf("invalid payload type for rpc") - } - - r := &libp2p.SendRPC{ - PeerId: wrapperspb.String(payload.PeerID.String()), - Meta: &libp2p.RPCMeta{ - PeerId: wrapperspb.String(payload.PeerID.String()), - Messages: convertRPCMessages(payload.Messages), - Subscriptions: convertRPCSubscriptions(payload.Subscriptions), - Control: convertRPCControl(payload.Control), - }, - } - - return r, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p DropRPC. -func TraceEventToDropRPC(event *TraceEvent) (*libp2p.DropRPC, error) { - payload, ok := event.Payload.(*RpcMeta) - if !ok { - return nil, fmt.Errorf("invalid payload type for rpc") - } - - r := &libp2p.DropRPC{ - PeerId: wrapperspb.String(payload.PeerID.String()), - Meta: &libp2p.RPCMeta{ - PeerId: wrapperspb.String(payload.PeerID.String()), - Messages: convertRPCMessages(payload.Messages), - Subscriptions: convertRPCSubscriptions(payload.Subscriptions), - Control: convertRPCControl(payload.Control), - }, - } - - return r, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p PublishMessage. -func TraceEventToPublishMessage(event *TraceEvent) (*libp2p.PublishMessage, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for PublishMessage") - } - - msgID, ok := payload["MsgID"].(string) - if !ok { - return nil, fmt.Errorf("msgID is required for PublishMessage") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for PublishMessage") - } - - return &libp2p.PublishMessage{ - MsgId: wrapperspb.String(msgID), - Topic: wrapperspb.String(topic), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p RejectMessage. -func TraceEventToRejectMessage(event *TraceEvent) (*libp2p.RejectMessage, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for RejectMessage") - } - - msgID, ok := payload["MsgID"].(string) - if !ok { - return nil, fmt.Errorf("msgID is required for RejectMessage") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for RejectMessage") - } - - peerID, ok := payload["PeerID"].(peer.ID) - if !ok { - return nil, fmt.Errorf("peerID is required for RejectMessage") - } - - reason, ok := payload["Reason"].(string) - if !ok { - return nil, fmt.Errorf("reason is required for RejectMessage") - } - - local, ok := payload["Local"].(bool) - if !ok { - return nil, fmt.Errorf("local is required for RejectMessage") - } - - msgSize, ok := payload["MsgSize"].(int) - if !ok { - return nil, fmt.Errorf("msgSize is required for RejectMessage") - } - - seqHex, ok := payload["Seq"].(string) - if !ok { - return nil, fmt.Errorf("seq is required for RejectMessage") - } - - // Parse hex sequence number. - seqBytes, err := hex.DecodeString(seqHex) - if err != nil { - return nil, fmt.Errorf("failed to decode Seq hex: %w", err) - } - - var seqNumber uint64 - - if len(seqBytes) > 0 { - for _, b := range seqBytes { - seqNumber = (seqNumber << 8) | uint64(b) - } - } - - return &libp2p.RejectMessage{ - MsgId: wrapperspb.String(msgID), - PeerId: wrapperspb.String(peerID.String()), - Topic: wrapperspb.String(topic), - Reason: wrapperspb.String(reason), - Local: wrapperspb.Bool(local), - MsgSize: wrapperspb.UInt32(uint32(msgSize)), //nolint:gosec // fine. - SeqNumber: wrapperspb.UInt64(seqNumber), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p DuplicateMessage. -func TraceEventToDuplicateMessage(event *TraceEvent) (*libp2p.DuplicateMessage, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for DuplicateMessage") - } - - msgID, ok := payload["MsgID"].(string) - if !ok { - return nil, fmt.Errorf("msgID is required for DuplicateMessage") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for DuplicateMessage") - } - - peerID, ok := payload["PeerID"].(peer.ID) - if !ok { - return nil, fmt.Errorf("peerID is required for DuplicateMessage") - } - - local, ok := payload["Local"].(bool) - if !ok { - return nil, fmt.Errorf("local is required for DuplicateMessage") - } - - msgSize, ok := payload["MsgSize"].(int) - if !ok { - return nil, fmt.Errorf("msgSize is required for DuplicateMessage") - } - - seqHex, ok := payload["Seq"].(string) - if !ok { - return nil, fmt.Errorf("seq is required for DuplicateMessage") - } - - // Parse hex sequence number - seqBytes, err := hex.DecodeString(seqHex) - if err != nil { - return nil, fmt.Errorf("failed to decode Seq hex: %w", err) - } - - var seqNumber uint64 - - if len(seqBytes) > 0 { - for _, b := range seqBytes { - seqNumber = (seqNumber << 8) | uint64(b) - } - } - - return &libp2p.DuplicateMessage{ - MsgId: wrapperspb.String(msgID), - PeerId: wrapperspb.String(peerID.String()), - Topic: wrapperspb.String(topic), - Local: wrapperspb.Bool(local), - MsgSize: wrapperspb.UInt32(uint32(msgSize)), //nolint:gosec // fine. - SeqNumber: wrapperspb.UInt64(seqNumber), - }, nil -} - -// Helper function to convert a Hermes TraceEvent to a libp2p DeliverMessage. -func TraceEventToDeliverMessage(event *TraceEvent) (*libp2p.DeliverMessage, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for DeliverMessage") - } - - msgID, ok := payload["MsgID"].(string) - if !ok { - return nil, fmt.Errorf("msgID is required for DeliverMessage") - } - - topic, ok := payload["Topic"].(string) - if !ok { - return nil, fmt.Errorf("topic is required for DeliverMessage") - } - - peerID, ok := payload["PeerID"].(peer.ID) - if !ok { - return nil, fmt.Errorf("peerID is required for DeliverMessage") - } - - local, ok := payload["Local"].(bool) - if !ok { - return nil, fmt.Errorf("local is required for DeliverMessage") - } - - msgSize, ok := payload["MsgSize"].(int) - if !ok { - return nil, fmt.Errorf("msgSize is required for DeliverMessage") - } - - seqHex, ok := payload["Seq"].(string) - if !ok { - return nil, fmt.Errorf("seq is required for DeliverMessage") - } - - // Parse hex sequence number - seqBytes, err := hex.DecodeString(seqHex) - if err != nil { - return nil, fmt.Errorf("failed to decode Seq hex: %w", err) - } - - var seqNumber uint64 - - if len(seqBytes) > 0 { - for _, b := range seqBytes { - seqNumber = (seqNumber << 8) | uint64(b) - } - } - - return &libp2p.DeliverMessage{ - MsgId: wrapperspb.String(msgID), - PeerId: wrapperspb.String(peerID.String()), - Topic: wrapperspb.String(topic), - Local: wrapperspb.Bool(local), - MsgSize: wrapperspb.UInt32(uint32(msgSize)), //nolint:gosec // fine. - SeqNumber: wrapperspb.UInt64(seqNumber), - }, nil -} - -func convertRPCMessages(messages []RpcMetaMsg) []*libp2p.MessageMeta { - ourMessages := make([]*libp2p.MessageMeta, len(messages)) - - for i, msg := range messages { - ourMessages[i] = &libp2p.MessageMeta{ - MessageId: wrapperspb.String(msg.MsgID), - TopicId: wrapperspb.String(msg.Topic), - } - } - - return ourMessages -} - -func convertRPCSubscriptions(subs []RpcMetaSub) []*libp2p.SubMeta { - ourSubs := make([]*libp2p.SubMeta, len(subs)) - - for i, sub := range subs { - ourSubs[i] = &libp2p.SubMeta{ - Subscribe: wrapperspb.Bool(sub.Subscribe), - TopicId: wrapperspb.String(sub.TopicID), - } - } - - return ourSubs -} - -func convertRPCControl(ctrl *RpcMetaControl) *libp2p.ControlMeta { - if ctrl == nil { - return nil - } - - return &libp2p.ControlMeta{ - Ihave: convertControlIHaveMeta(ctrl.IHave), - Iwant: convertControlIWantMeta(ctrl.IWant), - Graft: convertControlGraftMeta(ctrl.Graft), - Prune: convertControlPruneMeta(ctrl.Prune), - Idontwant: convertControlIDontWantMeta(ctrl.Idontwant), - } -} - -func convertControlIHaveMeta(ihave []RpcControlIHave) []*libp2p.ControlIHaveMeta { - converted := make([]*libp2p.ControlIHaveMeta, len(ihave)) - - for i, item := range ihave { - converted[i] = &libp2p.ControlIHaveMeta{ - TopicId: wrapperspb.String(item.TopicID), - MessageIds: convertStringValues(item.MsgIDs), - } - } - - return converted -} - -func convertControlIWantMeta(iwant []RpcControlIWant) []*libp2p.ControlIWantMeta { - converted := make([]*libp2p.ControlIWantMeta, len(iwant)) - - for i, item := range iwant { - converted[i] = &libp2p.ControlIWantMeta{ - MessageIds: convertStringValues(item.MsgIDs), - } - } - - return converted -} - -func convertControlIDontWantMeta(idontwant []RpcControlIdontWant) []*libp2p.ControlIDontWantMeta { - converted := make([]*libp2p.ControlIDontWantMeta, len(idontwant)) - - for i, item := range idontwant { - converted[i] = &libp2p.ControlIDontWantMeta{ - MessageIds: convertStringValues(item.MsgIDs), - } - } - - return converted -} - -func convertControlGraftMeta(graft []RpcControlGraft) []*libp2p.ControlGraftMeta { - converted := make([]*libp2p.ControlGraftMeta, len(graft)) - - for i, item := range graft { - converted[i] = &libp2p.ControlGraftMeta{ - TopicId: wrapperspb.String(item.TopicID), - } - } - - return converted -} - -func convertControlPruneMeta(prune []RpcControlPrune) []*libp2p.ControlPruneMeta { - converted := make([]*libp2p.ControlPruneMeta, len(prune)) - - for i, item := range prune { - peerIds := make([]string, 0, len(item.PeerIDs)) - for _, peer := range item.PeerIDs { - peerIds = append(peerIds, peer.String()) - } - - converted[i] = &libp2p.ControlPruneMeta{ - TopicId: wrapperspb.String(item.TopicID), - PeerIds: convertStringValues(peerIds), - } - } - - return converted -} - -func convertStringValues(strings []string) []*wrapperspb.StringValue { - converted := make([]*wrapperspb.StringValue, len(strings)) - - for i, s := range strings { - converted[i] = wrapperspb.String(s) - } - - return converted -} - -func TraceEventToConnected(event *TraceEvent) (*libp2p.Connected, error) { - payload, ok := event.Payload.(struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }) - if !ok { - return nil, fmt.Errorf("invalid payload type for Connected") - } - - return &libp2p.Connected{ - RemotePeer: wrapperspb.String(payload.RemotePeer), - RemoteMaddrs: wrapperspb.String(payload.RemoteMaddrs.String()), - AgentVersion: wrapperspb.String(payload.AgentVersion), - Direction: wrapperspb.String(payload.Direction), - Opened: timestamppb.New(payload.Opened), - Limited: wrapperspb.Bool(payload.Limited), - Transient: wrapperspb.Bool(payload.Limited), - }, nil -} - -func TraceEventToDisconnected(event *TraceEvent) (*libp2p.Disconnected, error) { - payload, ok := event.Payload.(struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - AgentVersion string - Direction string - Opened time.Time - Limited bool - }) - if !ok { - return nil, fmt.Errorf("invalid payload type for Disconnected") - } - - return &libp2p.Disconnected{ - RemotePeer: wrapperspb.String(payload.RemotePeer), - RemoteMaddrs: wrapperspb.String(payload.RemoteMaddrs.String()), - AgentVersion: wrapperspb.String(payload.AgentVersion), - Direction: wrapperspb.String(payload.Direction), - Opened: timestamppb.New(payload.Opened), - Limited: wrapperspb.Bool(payload.Limited), - Transient: wrapperspb.Bool(payload.Limited), - }, nil -} - -func TraceEventToHandleMetadata(event *TraceEvent) (*libp2p.HandleMetadata, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for HandleMetadata") - } - - metadata := &libp2p.HandleMetadata{} - - if peerID, ok := payload["PeerID"].(peer.ID); ok { - metadata.PeerId = wrapperspb.String(peerID.String()) - } - - if protocolID, ok := payload["ProtocolID"].(protocol.ID); ok { - metadata.ProtocolId = wrapperspb.String(string(protocolID)) - } - - if latencyS, ok := payload["LatencyS"].(float64); ok { - metadata.Latency = wrapperspb.Float(float32(latencyS)) - } - - metadata.Metadata = &libp2p.Metadata{} - - if seqNumber, ok := payload["SeqNumber"].(uint64); ok { - metadata.Metadata.SeqNumber = wrapperspb.UInt64(seqNumber) - } - - if attnets, ok := payload["Attnets"].(string); ok { - metadata.Metadata.Attnets = wrapperspb.String(attnets) - } - - if syncnets, ok := payload["Syncnets"].(string); ok { - metadata.Metadata.Syncnets = wrapperspb.String(syncnets) - } - - if errorStr, ok := payload["Error"].(string); ok { - metadata.Error = wrapperspb.String(errorStr) - } - - if direction, ok := payload["Direction"].(string); ok { - metadata.Direction = wrapperspb.String(direction) - } - - if custodyGroupCount, ok := payload["CustodyGroupCount"].(uint64); ok { - metadata.Metadata.CustodyGroupCount = wrapperspb.UInt64(custodyGroupCount) - } - - return metadata, nil -} - -func TraceEventToHandleStatus(event *TraceEvent) (*libp2p.HandleStatus, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for HandleStatus") - } - - status := &libp2p.HandleStatus{} - - if peerID, ok := payload["PeerID"].(peer.ID); ok { - status.PeerId = wrapperspb.String(peerID.String()) - } - - if protocolID, ok := payload["ProtocolID"].(protocol.ID); ok { - status.ProtocolId = wrapperspb.String(string(protocolID)) - } - - if latencyS, ok := payload["LatencyS"].(float64); ok { - status.Latency = wrapperspb.Float(float32(latencyS)) - } - - if direction, ok := payload["Direction"].(string); ok { - status.Direction = wrapperspb.String(direction) - } - - if requestData, ok := payload["Request"].(map[string]any); ok { - request, err := parseStatus(requestData) - if err != nil { - return nil, err - } - - status.Request = &libp2p.Status{ - ForkDigest: wrapperspb.String(request.ForkDigest), - FinalizedRoot: wrapperspb.String(request.FinalizedRoot), - FinalizedEpoch: wrapperspb.UInt64(request.FinalizedEpoch), - HeadRoot: wrapperspb.String(request.HeadRoot), - HeadSlot: wrapperspb.UInt64(request.HeadSlot), - EarliestAvailableSlot: wrapperspb.UInt64(request.EarliestAvailableSlot), - } - } - - if responseData, ok := payload["Response"].(map[string]any); ok { - response, err := parseStatus(responseData) - if err != nil { - return nil, err - } - - status.Response = &libp2p.Status{ - ForkDigest: wrapperspb.String(response.ForkDigest), - FinalizedRoot: wrapperspb.String(response.FinalizedRoot), - FinalizedEpoch: wrapperspb.UInt64(response.FinalizedEpoch), - HeadRoot: wrapperspb.String(response.HeadRoot), - HeadSlot: wrapperspb.UInt64(response.HeadSlot), - EarliestAvailableSlot: wrapperspb.UInt64(response.EarliestAvailableSlot), - } - } - - if errorStr, ok := payload["Error"].(string); ok { - status.Error = wrapperspb.String(errorStr) - } - - return status, nil -} - -type statusFields struct { - ForkDigest string - FinalizedRoot string - FinalizedEpoch uint64 - HeadRoot string - HeadSlot uint64 - EarliestAvailableSlot uint64 -} - -func parseStatus(data map[string]any) (*statusFields, error) { - status := &statusFields{} - - if forkDigest, ok := data["ForkDigest"].(string); ok { - status.ForkDigest = forkDigest - } - - if finalizedRoot, ok := data["FinalizedRoot"].(string); ok { - status.FinalizedRoot = finalizedRoot - } - - if finalizedEpoch, ok := data["FinalizedEpoch"].(primitives.Epoch); ok { - status.FinalizedEpoch = uint64(finalizedEpoch) - } - - if headRoot, ok := data["HeadRoot"].(string); ok { - status.HeadRoot = headRoot - } - - if headSlot, ok := data["HeadSlot"].(primitives.Slot); ok { - status.HeadSlot = uint64(headSlot) - } - - if earliestAvailableSlot, ok := data["EarliestAvailableSlot"].(primitives.Slot); ok { - status.EarliestAvailableSlot = uint64(earliestAvailableSlot) - } - - return status, nil -} - -// TraceEventToSyntheticHeartbeat converts a Hermes TraceEvent to a SyntheticHeartbeat protobuf message -func TraceEventToSyntheticHeartbeat(event *TraceEvent) (*libp2p.SyntheticHeartbeat, error) { - // The payload structure for heartbeat events from Hermes - payload, ok := event.Payload.(struct { - RemotePeer string - RemoteMaddrs ma.Multiaddr - LatencyMs int64 - AgentVersion string - Direction uint32 - Protocols []string - ConnectionAgeNs int64 - }) - if !ok { - return nil, fmt.Errorf("invalid payload type for SyntheticHeartbeat") - } - - return &libp2p.SyntheticHeartbeat{ - Timestamp: timestamppb.New(event.Timestamp), - RemotePeer: wrapperspb.String(payload.RemotePeer), - RemoteMaddrs: wrapperspb.String(payload.RemoteMaddrs.String()), - LatencyMs: wrapperspb.Int64(payload.LatencyMs), - AgentVersion: wrapperspb.String(payload.AgentVersion), - Direction: wrapperspb.UInt32(payload.Direction), - Protocols: payload.Protocols, - ConnectionAgeNs: wrapperspb.Int64(payload.ConnectionAgeNs), - }, nil -} - -// TraceEventToCustodyProbe converts a Hermes TraceEvent to a DataColumnCustodyProbe protobuf message -func TraceEventToCustodyProbe(event *TraceEvent) (*libp2p.DataColumnCustodyProbe, error) { - payload, ok := event.Payload.(map[string]any) - if !ok { - return nil, fmt.Errorf("invalid payload type for CustodyProbe") - } - - probe := &libp2p.DataColumnCustodyProbe{} - - if jobStartTimestamp, ok := payload["JobStartTimestamp"].(time.Time); ok { - probe.JobStartTimestamp = timestamppb.New(jobStartTimestamp) - } - - if peerID, ok := payload["PeerID"].(string); ok { - probe.PeerId = wrapperspb.String(peerID) - } - - if slot, ok := payload["Slot"].(uint64); ok { - //nolint:gosec // conversion fine. - probe.Slot = wrapperspb.UInt32(uint32(slot)) - } - - if epoch, ok := payload["Epoch"].(uint64); ok { - //nolint:gosec // conversion fine. - probe.Epoch = wrapperspb.UInt32(uint32(epoch)) - } - - if columnIndex, ok := payload["ColumnIndex"].(uint64); ok { - //nolint:gosec // conversion fine. - probe.ColumnIndex = wrapperspb.UInt32(uint32(columnIndex)) - } - - if result, ok := payload["Result"].(string); ok { - probe.Result = wrapperspb.String(result) - } - - if responseTimeMs, ok := payload["DurationMs"].(int64); ok { - probe.ResponseTimeMs = wrapperspb.Int64(responseTimeMs) - } - - if errorStr, ok := payload["Error"].(string); ok { - probe.Error = wrapperspb.String(errorStr) - } - - if beaconBlockRoot, ok := payload["BlockHash"].(string); ok { - probe.BeaconBlockRoot = wrapperspb.String(beaconBlockRoot) - } - - if columnRowsCount, ok := payload["ColumnSize"].(int); ok { - //nolint:gosec // conversion fine. - probe.ColumnRowsCount = wrapperspb.UInt32(uint32(columnRowsCount)) - } - - return probe, nil -} diff --git a/pkg/clmimicry/trace_convert_test.go b/pkg/clmimicry/trace_convert_test.go deleted file mode 100644 index 726425598..000000000 --- a/pkg/clmimicry/trace_convert_test.go +++ /dev/null @@ -1,582 +0,0 @@ -package clmimicry - -import ( - "testing" - - "github.com/ethpandaops/xatu/pkg/proto/libp2p" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" -) - -const ( - peerIDStr = "16Uiu2HAm68jFpjEsRyc1rksPWCorrqwoyR7qdPSvHcinzssnMXJq" -) - -// TestTraceEventToRPC tests both TraceEventToRecvRPC and TraceEventToSendRPC methods. -// They both have the same structure and behavior. -func TestTraceEventToRPC(t *testing.T) { - peerID, err := peer.Decode(peerIDStr) - require.NoError(t, err) - - type traceEventFunc func(*TraceEvent) (interface{}, error) - - traceEventFuncs := map[string]traceEventFunc{ - "RecvRPC": func(event *TraceEvent) (interface{}, error) { - return TraceEventToRecvRPC(event) - }, - "SendRPC": func(event *TraceEvent) (interface{}, error) { - return TraceEventToSendRPC(event) - }, - } - - // Define test cases - tests := []struct { - name string - input *TraceEvent - expectError bool - }{ - { - name: "valid with messages and subscriptions", - input: createTraceEventWithMessages(peerID), - }, - { - name: "valid with ihave control messages", - input: createTraceEventWithIHave(peerID), - }, - { - name: "valid with iwant control messages", - input: createTraceEventWithIWant(peerID), - }, - { - name: "valid with idontwant control messages", - input: createTraceEventWithIDontWant(peerID), - }, - { - name: "valid with graft control messages", - input: createTraceEventWithGraft(peerID), - }, - { - name: "valid with prune control messages", - input: createTraceEventWithPrune(peerID), - }, - { - name: "valid with all control messages", - input: createTraceEventWithAllControls(peerID), - }, - { - name: "invalid payload type", - input: &TraceEvent{ - PeerID: peerID, - Payload: "not an RpcMeta", - }, - expectError: true, - }, - { - name: "nil control", - input: createTraceEventWithNilControl(peerID), - }, - } - - // Run tests for each conversion function - for name, fn := range traceEventFuncs { - t.Run(name, func(t *testing.T) { - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := fn(tt.input) - - if tt.expectError { - assert.Error(t, err) - - return - } - - require.NoError(t, err) - - // Create expected output based on the function type. - var expected interface{} - - payload, ok := tt.input.Payload.(*RpcMeta) - require.True(t, ok) - - meta := createExpectedRPCMeta(peerIDStr, payload) - - if name == "RecvRPC" { - expected = &libp2p.RecvRPC{ - PeerId: wrapperspb.String(peerIDStr), - Meta: meta, - } - - actual, ok := result.(*libp2p.RecvRPC) - require.True(t, ok) - - // Safe type assertion since we created expected as *RecvRPC above - expectedRecv, ok := expected.(*libp2p.RecvRPC) - require.True(t, ok) - - assertRPCEquals(t, expectedRecv, actual) - } else { - expected = &libp2p.SendRPC{ - PeerId: wrapperspb.String(peerIDStr), - Meta: meta, - } - - actual, ok := result.(*libp2p.SendRPC) - require.True(t, ok) - - // Safe type assertion since we created expected as *SendRPC above - expectedSend, ok := expected.(*libp2p.SendRPC) - require.True(t, ok) - - assertRPCEquals(t, expectedSend, actual) - } - }) - } - }) - } -} - -// Helper function to create expected RPCMeta from a RpcMeta -func createExpectedRPCMeta(peerIDStr string, payload *RpcMeta) *libp2p.RPCMeta { - meta := &libp2p.RPCMeta{ - PeerId: wrapperspb.String(peerIDStr), - } - - // Add messages if any - if len(payload.Messages) > 0 { - meta.Messages = createExpectedMessages() - } else { - meta.Messages = []*libp2p.MessageMeta{} - } - - // Add subscriptions if any - if len(payload.Subscriptions) > 0 { - meta.Subscriptions = createExpectedSubscriptions() - } - - // Add control if any - if payload.Control != nil { - meta.Control = &libp2p.ControlMeta{} - - // Add IHave if any - if len(payload.Control.IHave) > 0 { - meta.Control.Ihave = []*libp2p.ControlIHaveMeta{ - { - TopicId: wrapperspb.String("topic1"), - MessageIds: convertStringValues([]string{"msg1", "msg2"}), - }, - } - } - - // Add IWant if any - if len(payload.Control.IWant) > 0 { - meta.Control.Iwant = []*libp2p.ControlIWantMeta{ - { - MessageIds: convertStringValues([]string{"msg1", "msg2"}), - }, - } - - // Special case for all controls test - if len(payload.Control.IHave) > 0 && - len(payload.Control.Graft) > 0 && - len(payload.Control.Prune) > 0 { - meta.Control.Iwant = []*libp2p.ControlIWantMeta{ - { - MessageIds: convertStringValues([]string{"msg3", "msg4"}), - }, - } - } - } - - // Add IDontWant if any - if len(payload.Control.Idontwant) > 0 { - meta.Control.Idontwant = []*libp2p.ControlIDontWantMeta{ - { - MessageIds: convertStringValues([]string{"msg1", "msg2"}), - }, - } - - // Special case for all controls test - if len(payload.Control.IHave) > 0 && - len(payload.Control.IWant) > 0 && - len(payload.Control.Graft) > 0 { - meta.Control.Idontwant = []*libp2p.ControlIDontWantMeta{ - { - MessageIds: convertStringValues([]string{"msg5", "msg6"}), - }, - } - } - } - - // Add Graft if any - if len(payload.Control.Graft) > 0 { - topicID := "topic1" - if len(payload.Control.IHave) > 0 && - len(payload.Control.IWant) > 0 && - len(payload.Control.Prune) > 0 { - topicID = "topic2" - } - - meta.Control.Graft = []*libp2p.ControlGraftMeta{ - { - TopicId: wrapperspb.String(topicID), - }, - } - } - - // Add Prune if any - if len(payload.Control.Prune) > 0 { - topicID := "topic1" - if len(payload.Control.IHave) > 0 && - len(payload.Control.IWant) > 0 && - len(payload.Control.Graft) > 0 { - topicID = "topic3" - } - - meta.Control.Prune = []*libp2p.ControlPruneMeta{ - { - TopicId: wrapperspb.String(topicID), - PeerIds: convertStringValues([]string{peerIDStr}), - }, - } - } - } - - return meta -} - -// Generic assertion function that works for both RecvRPC and SendRPC -func assertRPCEquals(t *testing.T, expected, actual interface{}) { - t.Helper() - - var expectedPeerID, actualPeerID string - - var expectedMeta, actualMeta *libp2p.RPCMeta - - switch e := expected.(type) { - case *libp2p.RecvRPC: - expectedPeerID = e.PeerId.GetValue() - expectedMeta = e.Meta - - a, ok := actual.(*libp2p.RecvRPC) - require.True(t, ok) - - actualPeerID = a.PeerId.GetValue() - actualMeta = a.Meta - case *libp2p.SendRPC: - expectedPeerID = e.PeerId.GetValue() - expectedMeta = e.Meta - - a, ok := actual.(*libp2p.SendRPC) - require.True(t, ok) - - actualPeerID = a.PeerId.GetValue() - actualMeta = a.Meta - default: - t.Fatalf("Unexpected type for expected: %T", expected) - } - - assert.Equal(t, expectedPeerID, actualPeerID) - assertRPCMetaEquals(t, expectedMeta, actualMeta) -} - -// Helper function to assert equality between expected and actual RPCMeta objects -func assertRPCMetaEquals(t *testing.T, expected, actual *libp2p.RPCMeta) { - t.Helper() - - if expected.Control != nil { - require.NotNil(t, actual.Control) - - // Check ihave - assertControlIHaveEquals(t, expected.Control.Ihave, actual.Control.Ihave) - - // Check iwant - assertControlIWantEquals(t, expected.Control.Iwant, actual.Control.Iwant) - - // Check idontwant - assertControlIDontWantEquals(t, expected.Control.Idontwant, actual.Control.Idontwant) - - // Check graft - assertControlGraftEquals(t, expected.Control.Graft, actual.Control.Graft) - - // Check prune - assertControlPruneEquals(t, expected.Control.Prune, actual.Control.Prune) - } else { - assert.Nil(t, actual.Control) - } - - // Check messages - assertMessagesEquals(t, expected.Messages, actual.Messages) - - // Check subscriptions - assertSubscriptionsEquals(t, expected.Subscriptions, actual.Subscriptions) -} - -func createTraceEventWithMessages(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Messages: []RpcMetaMsg{ - { - MsgID: "msg1", - Topic: "topic1", - }, - }, - Subscriptions: []RpcMetaSub{ - { - Subscribe: true, - TopicID: "topic1", - }, - }, - }, - } -} - -func createTraceEventWithIHave(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Control: &RpcMetaControl{ - IHave: []RpcControlIHave{ - { - TopicID: "topic1", - MsgIDs: []string{"msg1", "msg2"}, - }, - }, - }, - }, - } -} - -func createTraceEventWithIWant(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Control: &RpcMetaControl{ - IWant: []RpcControlIWant{ - { - MsgIDs: []string{"msg1", "msg2"}, - }, - }, - }, - }, - } -} - -func createTraceEventWithIDontWant(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Control: &RpcMetaControl{ - Idontwant: []RpcControlIdontWant{ - { - MsgIDs: []string{"msg1", "msg2"}, - }, - }, - }, - }, - } -} - -func createTraceEventWithGraft(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Control: &RpcMetaControl{ - Graft: []RpcControlGraft{ - { - TopicID: "topic1", - }, - }, - }, - }, - } -} - -func createTraceEventWithPrune(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Control: &RpcMetaControl{ - Prune: []RpcControlPrune{ - { - TopicID: "topic1", - PeerIDs: []peer.ID{peerID}, - }, - }, - }, - }, - } -} - -func createTraceEventWithAllControls(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Control: &RpcMetaControl{ - IHave: []RpcControlIHave{ - { - TopicID: "topic1", - MsgIDs: []string{"msg1", "msg2"}, - }, - }, - IWant: []RpcControlIWant{ - { - MsgIDs: []string{"msg3", "msg4"}, - }, - }, - Idontwant: []RpcControlIdontWant{ - { - MsgIDs: []string{"msg5", "msg6"}, - }, - }, - Graft: []RpcControlGraft{ - { - TopicID: "topic2", - }, - }, - Prune: []RpcControlPrune{ - { - TopicID: "topic3", - PeerIDs: []peer.ID{peerID}, - }, - }, - }, - }, - } -} - -func createTraceEventWithNilControl(peerID peer.ID) *TraceEvent { - return &TraceEvent{ - PeerID: peerID, - Payload: &RpcMeta{ - PeerID: peerID, - Control: nil, - Messages: []RpcMetaMsg{}, - }, - } -} - -func createExpectedMessages() []*libp2p.MessageMeta { - return []*libp2p.MessageMeta{ - { - MessageId: wrapperspb.String("msg1"), - TopicId: wrapperspb.String("topic1"), - }, - } -} - -func createExpectedSubscriptions() []*libp2p.SubMeta { - return []*libp2p.SubMeta{ - { - Subscribe: wrapperspb.Bool(true), - TopicId: wrapperspb.String("topic1"), - }, - } -} - -func assertControlIHaveEquals(t *testing.T, expected, actual []*libp2p.ControlIHaveMeta) { - t.Helper() - - if len(expected) > 0 { - require.Equal(t, len(expected), len(actual)) - - for i, exp := range expected { - assert.Equal(t, exp.TopicId.GetValue(), actual[i].TopicId.GetValue()) - assertStringValuesEqual(t, exp.MessageIds, actual[i].MessageIds) - } - } -} - -func assertControlIWantEquals(t *testing.T, expected, actual []*libp2p.ControlIWantMeta) { - t.Helper() - - if len(expected) > 0 { - require.Equal(t, len(expected), len(actual)) - - for i, exp := range expected { - assertStringValuesEqual(t, exp.MessageIds, actual[i].MessageIds) - } - } -} - -func assertControlIDontWantEquals(t *testing.T, expected, actual []*libp2p.ControlIDontWantMeta) { - t.Helper() - - if len(expected) > 0 { - require.Equal(t, len(expected), len(actual)) - - for i, exp := range expected { - assertStringValuesEqual(t, exp.MessageIds, actual[i].MessageIds) - } - } -} - -func assertControlGraftEquals(t *testing.T, expected, actual []*libp2p.ControlGraftMeta) { - t.Helper() - - if len(expected) > 0 { - require.Equal(t, len(expected), len(actual)) - - for i, exp := range expected { - assert.Equal(t, exp.TopicId.GetValue(), actual[i].TopicId.GetValue()) - } - } -} - -func assertControlPruneEquals(t *testing.T, expected, actual []*libp2p.ControlPruneMeta) { - t.Helper() - - if len(expected) > 0 { - require.Equal(t, len(expected), len(actual)) - - for i, exp := range expected { - assert.Equal(t, exp.TopicId.GetValue(), actual[i].TopicId.GetValue()) - assertStringValuesEqual(t, exp.PeerIds, actual[i].PeerIds) - } - } -} - -func assertMessagesEquals(t *testing.T, expected, actual []*libp2p.MessageMeta) { - t.Helper() - - if len(expected) > 0 { - require.Equal(t, len(expected), len(actual)) - - for i, exp := range expected { - assert.Equal(t, exp.MessageId.GetValue(), actual[i].MessageId.GetValue()) - assert.Equal(t, exp.TopicId.GetValue(), actual[i].TopicId.GetValue()) - } - } -} - -func assertSubscriptionsEquals(t *testing.T, expected, actual []*libp2p.SubMeta) { - t.Helper() - - if len(expected) > 0 { - require.Equal(t, len(expected), len(actual)) - - for i, exp := range expected { - assert.Equal(t, exp.Subscribe.GetValue(), actual[i].Subscribe.GetValue()) - assert.Equal(t, exp.TopicId.GetValue(), actual[i].TopicId.GetValue()) - } - } -} - -func assertStringValuesEqual(t *testing.T, expected, actual []*wrapperspb.StringValue) { - t.Helper() - - assert.Equal(t, len(expected), len(actual)) - - for j, val := range expected { - assert.Equal(t, val.GetValue(), actual[j].GetValue()) - } -} diff --git a/pkg/clmimicry/trace_event.go b/pkg/clmimicry/trace_event.go new file mode 100644 index 000000000..73a7b77d3 --- /dev/null +++ b/pkg/clmimicry/trace_event.go @@ -0,0 +1,51 @@ +package clmimicry + +import ( + "time" + + "github.com/libp2p/go-libp2p/core/peer" +) + +// TraceEvent is the interface for all trace events from the consensus layer P2P network. +// Each concrete event type embeds TraceEventBase and adds its specific fields. +type TraceEvent interface { + // GetTimestamp returns when the event occurred. + GetTimestamp() time.Time + // GetPeerID returns the peer ID associated with this event. + GetPeerID() peer.ID +} + +// TraceEventBase contains common fields for all trace events. +// Embed this in concrete event types to satisfy the TraceEvent interface. +type TraceEventBase struct { + Timestamp time.Time + PeerID peer.ID +} + +// GetTimestamp returns when the event occurred. +func (b TraceEventBase) GetTimestamp() time.Time { + return b.Timestamp +} + +// GetPeerID returns the peer ID associated with this event. +func (b TraceEventBase) GetPeerID() peer.ID { + return b.PeerID +} + +// TopicEvent is an optional interface for events that have a topic. +type TopicEvent interface { + TraceEvent + GetTopic() string +} + +// MessageEvent is an optional interface for events that have a message ID. +type MessageEvent interface { + TraceEvent + GetMsgID() string +} + +// RPCMetaEvent is an optional interface for RPC events that contain RpcMeta. +type RPCMetaEvent interface { + TraceEvent + GetRPCMeta() *RpcMeta +} diff --git a/pkg/clmimicry/trace_shard_key.go b/pkg/clmimicry/trace_shard_key.go index 55a2cb2a2..0eb4ff179 100644 --- a/pkg/clmimicry/trace_shard_key.go +++ b/pkg/clmimicry/trace_shard_key.go @@ -1,189 +1,106 @@ package clmimicry -import ( - "reflect" - -) - // GetMsgID extracts the message ID from the event for sharding. // We only shard based on message IDs, not peer IDs. -func GetMsgID(event *TraceEvent) string { +func GetMsgID(event TraceEvent) string { if event == nil { return "" } - // Handle map[string]any payloads (used by deliver_message, duplicate_message, etc.) - if mapPayload, ok := event.Payload.(map[string]any); ok { - if msgID, found := mapPayload["MsgID"]; found { - if msgIDStr, ok := msgID.(string); ok { - return msgIDStr - } - } - - return "" - } - - // Try to access the MsgID field using reflection. - v := reflect.ValueOf(event.Payload) - if v.Kind() != reflect.Ptr || v.IsNil() { - return "" - } - - // Dereference the pointer and check if it's a struct. - v = v.Elem() - if v.Kind() != reflect.Struct { - return "" - } - - // Try to find the MsgID field. - msgIDField := v.FieldByName("MsgID") - if !msgIDField.IsValid() || msgIDField.Kind() != reflect.String { - return "" + if me, ok := event.(MessageEvent); ok { + return me.GetMsgID() } - return msgIDField.String() + return "" } // GetGossipTopics extracts all gossip topics from a trace event if available. // Returns a slice of unique topics found in the event. -func GetGossipTopics(event *TraceEvent) []string { +func GetGossipTopics(event TraceEvent) []string { if event == nil { return nil } - topicSet := make(map[string]bool) - - // First check if the event itself has a Topic field (used by gossipsub events) - if event.Topic != "" { - topicSet[event.Topic] = true - } - - // Handle different payload types - switch payload := event.Payload.(type) { - case *RpcMeta: - extractTopicsFromRpcMeta(payload, topicSet) - case map[string]any: - extractTopicsFromMapPayload(payload, topicSet) - default: - extractTopicsFromReflection(event.Payload, topicSet) - } - - return convertTopicSetToSlice(topicSet) -} - -// extractTopicsFromRpcMeta extracts topics from RPC meta payload. -func extractTopicsFromRpcMeta(rpcMeta *RpcMeta, topicSet map[string]bool) { - // Extract topics from messages - for _, msg := range rpcMeta.Messages { - if msg.Topic != "" { - topicSet[msg.Topic] = true + // Handle single-topic events first + if te, ok := event.(TopicEvent); ok { + topic := te.GetTopic() + if topic != "" { + return []string{topic} } } - // Extract topics from subscriptions - for _, sub := range rpcMeta.Subscriptions { - if sub.TopicID != "" { - topicSet[sub.TopicID] = true - } + // Handle RPC events with multiple topics in RpcMeta + if rpc, ok := event.(RPCMetaEvent); ok { + return extractTopicsFromRPCMeta(rpc.GetRPCMeta()) } - // Extract topics from control messages - if rpcMeta.Control != nil { - extractTopicsFromControlMessages(rpcMeta.Control, topicSet) - } + return []string{} } -// extractTopicsFromControlMessages extracts topics from RPC control messages. -func extractTopicsFromControlMessages(control *RpcMetaControl, topicSet map[string]bool) { - // Extract from IHave messages - for _, ihave := range control.IHave { - if ihave.TopicID != "" { - topicSet[ihave.TopicID] = true - } +// extractTopicsFromRPCMeta extracts unique topics from RpcMeta. +func extractTopicsFromRPCMeta(meta *RpcMeta) []string { + if meta == nil { + return []string{} } - // Extract from Graft messages - for _, graft := range control.Graft { - if graft.TopicID != "" { - topicSet[graft.TopicID] = true - } - } + seen := make(map[string]struct{}) + topics := make([]string, 0) - // Extract from Prune messages - for _, prune := range control.Prune { - if prune.TopicID != "" { - topicSet[prune.TopicID] = true + // Extract from Messages + for _, msg := range meta.Messages { + if msg.Topic != "" { + if _, exists := seen[msg.Topic]; !exists { + seen[msg.Topic] = struct{}{} + topics = append(topics, msg.Topic) + } } } -} -// extractTopicsFromMapPayload extracts topics from map-style payloads. -func extractTopicsFromMapPayload(mapPayload map[string]any, topicSet map[string]bool) { - if topic, found := mapPayload["Topic"]; found { - if topicStr, ok := topic.(string); ok && topicStr != "" { - topicSet[topicStr] = true + // Extract from Subscriptions + for _, sub := range meta.Subscriptions { + if sub.TopicID != "" { + if _, exists := seen[sub.TopicID]; !exists { + seen[sub.TopicID] = struct{}{} + topics = append(topics, sub.TopicID) + } } } -} - -// extractTopicsFromReflection uses reflection to extract topics from protobuf structures. -func extractTopicsFromReflection(payload interface{}, topicSet map[string]bool) { - if payload == nil { - return - } - - v := reflect.ValueOf(payload) - if v.Kind() != reflect.Ptr || v.IsNil() { - return - } - // Dereference the pointer and check if it's a struct - v = v.Elem() - if v.Kind() != reflect.Struct { - return - } - - // Try to extract topic from both "Topic" and "TopicId" fields - extractTopicFromField(v, "Topic", topicSet) - extractTopicFromField(v, "TopicId", topicSet) -} + // Extract from Control messages + if meta.Control != nil { + // IHave + for _, ihave := range meta.Control.IHave { + if ihave.TopicID != "" { + if _, exists := seen[ihave.TopicID]; !exists { + seen[ihave.TopicID] = struct{}{} + topics = append(topics, ihave.TopicID) + } + } + } -// extractTopicFromField extracts a topic from a specific struct field using reflection. -func extractTopicFromField(structValue reflect.Value, fieldName string, topicSet map[string]bool) { - topicField := structValue.FieldByName(fieldName) - if !topicField.IsValid() { - return - } + // IWant has no topic field - // Check if the field type supports IsNil() before calling it - switch topicField.Kind() { - case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface, reflect.Chan, reflect.Func: - if topicField.IsNil() { - return + // Graft + for _, graft := range meta.Control.Graft { + if graft.TopicID != "" { + if _, exists := seen[graft.TopicID]; !exists { + seen[graft.TopicID] = struct{}{} + topics = append(topics, graft.TopicID) + } + } } - } - // Handle *wrapperspb.StringValue fields - if topicField.Kind() == reflect.Ptr && !topicField.IsNil() { - // Check if it has a GetValue method (wrapperspb.StringValue) - getValue := topicField.MethodByName("GetValue") - if getValue.IsValid() { - results := getValue.Call(nil) - if len(results) > 0 && results[0].Kind() == reflect.String { - topicStr := results[0].String() - if topicStr != "" { - topicSet[topicStr] = true + // Prune + for _, prune := range meta.Control.Prune { + if prune.TopicID != "" { + if _, exists := seen[prune.TopicID]; !exists { + seen[prune.TopicID] = struct{}{} + topics = append(topics, prune.TopicID) } } } - } -} -// convertTopicSetToSlice converts a topic set to a sorted slice. -func convertTopicSetToSlice(topicSet map[string]bool) []string { - topics := make([]string, 0, len(topicSet)) - for topic := range topicSet { - topics = append(topics, topic) + // IDontWant has no topic field } return topics diff --git a/pkg/clmimicry/trace_shard_key_test.go b/pkg/clmimicry/trace_shard_key_test.go index a893add8b..40599e7fb 100644 --- a/pkg/clmimicry/trace_shard_key_test.go +++ b/pkg/clmimicry/trace_shard_key_test.go @@ -2,132 +2,106 @@ package clmimicry_test import ( "testing" + "time" "github.com/ethpandaops/xatu/pkg/clmimicry" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/wrapperspb" ) func TestGetMsgID(t *testing.T) { - t.Run("from map payload", func(t *testing.T) { - tests := []struct { - name string - payload map[string]any - expected string - }{ - { - name: "valid MsgID", - payload: map[string]any{ - "MsgID": "msg-123", - "Topic": "test-topic", - }, - expected: "msg-123", - }, - { - name: "MsgID not string", - payload: map[string]any{ - "MsgID": 123, // not a string - }, - expected: "", - }, - { - name: "no MsgID field", - payload: map[string]any{"Topic": "test-topic"}, - expected: "", - }, - { - name: "empty map", - payload: map[string]any{}, - expected: "", - }, - { - name: "nil map", - payload: nil, - expected: "", + peerID, err := peer.Decode("16Uiu2HAm68jFpjEsRyc1rksPWCorrqwoyR7qdPSvHcinzssnMXJq") + require.NoError(t, err) + + t.Run("from PublishMessageEvent", func(t *testing.T) { + event := &clmimicry.PublishMessageEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + MsgID: "msg-123", + Topic: "/eth2/test/beacon_block/ssz_snappy", } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - event := &clmimicry.TraceEvent{ - Payload: tt.payload, - } - result := clmimicry.GetMsgID(event) - assert.Equal(t, tt.expected, result) - }) - } + result := clmimicry.GetMsgID(event) + assert.Equal(t, "msg-123", result) }) - t.Run("from struct payload via reflection", func(t *testing.T) { - // Create a test struct that mimics protobuf structs with MsgID field - type TestPayload struct { - MsgID string - Topic string + t.Run("from DeliverMessageEvent", func(t *testing.T) { + event := &clmimicry.DeliverMessageEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + MsgID: "deliver-msg-456", + Topic: "/eth2/test/beacon_block/ssz_snappy", } - event := &clmimicry.TraceEvent{ - Payload: &TestPayload{ - MsgID: "struct-msg-id", - Topic: "struct-topic", + result := clmimicry.GetMsgID(event) + assert.Equal(t, "deliver-msg-456", result) + }) + + t.Run("from RejectMessageEvent", func(t *testing.T) { + event := &clmimicry.RejectMessageEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + MsgID: "reject-msg-789", + Topic: "/eth2/test/beacon_block/ssz_snappy", + Reason: "validation_failed", } result := clmimicry.GetMsgID(event) - assert.Equal(t, "struct-msg-id", result) + assert.Equal(t, "reject-msg-789", result) }) - t.Run("edge cases", func(t *testing.T) { - tests := []struct { - name string - event *clmimicry.TraceEvent - expected string - }{ - { - name: "nil event", - event: nil, - expected: "", - }, - { - name: "nil payload", - event: &clmimicry.TraceEvent{Payload: nil}, - expected: "", - }, - { - name: "non-pointer struct payload", - event: &clmimicry.TraceEvent{Payload: struct{ MsgID string }{MsgID: "test"}}, - expected: "", - }, - { - name: "string payload", - event: &clmimicry.TraceEvent{Payload: "not-a-struct"}, - expected: "", - }, - { - name: "int payload", - event: &clmimicry.TraceEvent{Payload: 123}, - expected: "", + t.Run("from DuplicateMessageEvent", func(t *testing.T) { + event := &clmimicry.DuplicateMessageEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + MsgID: "dup-msg-000", + Topic: "/eth2/test/beacon_block/ssz_snappy", } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := clmimicry.GetMsgID(tt.event) - assert.Equal(t, tt.expected, result) - }) + result := clmimicry.GetMsgID(event) + assert.Equal(t, "dup-msg-000", result) + }) + + t.Run("from non-message event (GraftEvent)", func(t *testing.T) { + event := &clmimicry.GraftEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Topic: "/eth2/test/beacon_block/ssz_snappy", } + + // GraftEvent doesn't implement MessageEvent, so should return empty string + result := clmimicry.GetMsgID(event) + assert.Equal(t, "", result) + }) + + t.Run("nil event", func(t *testing.T) { + result := clmimicry.GetMsgID(nil) + assert.Equal(t, "", result) }) } func TestGetGossipTopics(t *testing.T) { - t.Run("from RpcMeta payload", func(t *testing.T) { - peerID, _ := peer.Decode("16Uiu2HAm68jFpjEsRyc1rksPWCorrqwoyR7qdPSvHcinzssnMXJq") - - event := &clmimicry.TraceEvent{ - Type: "LIBP2P_TRACE_RECV_RPC", - PeerID: peerID, - Payload: &clmimicry.RpcMeta{ + peerID, err := peer.Decode("16Uiu2HAm68jFpjEsRyc1rksPWCorrqwoyR7qdPSvHcinzssnMXJq") + require.NoError(t, err) + + t.Run("from RPC event with RpcMeta", func(t *testing.T) { + event := &clmimicry.RecvRPCEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &clmimicry.RpcMeta{ PeerID: peerID, Messages: []clmimicry.RpcMetaMsg{ {MsgID: "msg1", Topic: "/eth2/4a26c58b/beacon_block/ssz_snappy"}, @@ -171,87 +145,76 @@ func TestGetGossipTopics(t *testing.T) { } }) - t.Run("from map payload", func(t *testing.T) { - tests := []struct { - name string - payload map[string]any - expected []string - }{ - { - name: "single topic", - payload: map[string]any{ - "Topic": "/eth2/4a26c58b/beacon_block/ssz_snappy", - "MsgID": "msg-123", - }, - expected: []string{"/eth2/4a26c58b/beacon_block/ssz_snappy"}, - }, - { - name: "no topic field", - payload: map[string]any{"MsgID": "msg-123"}, - expected: []string{}, - }, - { - name: "empty topic string", - payload: map[string]any{"Topic": ""}, - expected: []string{}, - }, - { - name: "topic not string", - payload: map[string]any{"Topic": 123}, - expected: []string{}, + t.Run("from TopicEvent (JoinEvent)", func(t *testing.T) { + event := &clmimicry.JoinEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + Topic: "/eth2/4a26c58b/beacon_block/ssz_snappy", } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - event := &clmimicry.TraceEvent{Payload: tt.payload} - topics := clmimicry.GetGossipTopics(event) - assert.Equal(t, tt.expected, topics) - }) - } + topics := clmimicry.GetGossipTopics(event) + assert.Equal(t, []string{"/eth2/4a26c58b/beacon_block/ssz_snappy"}, topics) }) - t.Run("from struct with Topic field via reflection", func(t *testing.T) { - // Test struct with plain Topic field - type PayloadWithTopic struct { - Topic string - MsgID string + t.Run("from TopicEvent (LeaveEvent)", func(t *testing.T) { + event := &clmimicry.LeaveEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Topic: "/eth2/4a26c58b/beacon_attestation_5/ssz_snappy", } - event := &clmimicry.TraceEvent{ - Payload: &PayloadWithTopic{ - Topic: "/eth2/test/topic", - MsgID: "msg-123", + topics := clmimicry.GetGossipTopics(event) + assert.Equal(t, []string{"/eth2/4a26c58b/beacon_attestation_5/ssz_snappy"}, topics) + }) + + t.Run("from TopicEvent (GraftEvent)", func(t *testing.T) { + event := &clmimicry.GraftEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + Topic: "/eth2/4a26c58b/voluntary_exit/ssz_snappy", } topics := clmimicry.GetGossipTopics(event) - // The reflection code doesn't extract plain string Topic fields - assert.Equal(t, []string{}, topics) + assert.Equal(t, []string{"/eth2/4a26c58b/voluntary_exit/ssz_snappy"}, topics) }) - t.Run("from struct with TopicId field via reflection", func(t *testing.T) { - // Test struct with wrapperspb.StringValue TopicId field (like protobuf structs) - type PayloadWithTopicId struct { - TopicId *wrapperspb.StringValue - MsgID string + t.Run("from TopicEvent (PruneEvent)", func(t *testing.T) { + event := &clmimicry.PruneEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Topic: "/eth2/4a26c58b/proposer_slashing/ssz_snappy", } - event := &clmimicry.TraceEvent{ - Payload: &PayloadWithTopicId{ - TopicId: wrapperspb.String("/eth2/test/topic_id"), - MsgID: "msg-123", + topics := clmimicry.GetGossipTopics(event) + assert.Equal(t, []string{"/eth2/4a26c58b/proposer_slashing/ssz_snappy"}, topics) + }) + + t.Run("from TopicEvent (PublishMessageEvent)", func(t *testing.T) { + event := &clmimicry.PublishMessageEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + MsgID: "msg-123", + Topic: "/eth2/4a26c58b/beacon_block/ssz_snappy", } topics := clmimicry.GetGossipTopics(event) - assert.Equal(t, []string{"/eth2/test/topic_id"}, topics) + assert.Equal(t, []string{"/eth2/4a26c58b/beacon_block/ssz_snappy"}, topics) }) t.Run("edge cases", func(t *testing.T) { tests := []struct { name string - event *clmimicry.TraceEvent + event clmimicry.TraceEvent expected []string }{ { @@ -260,27 +223,24 @@ func TestGetGossipTopics(t *testing.T) { expected: nil, }, { - name: "nil payload", - event: &clmimicry.TraceEvent{Payload: nil}, - expected: []string{}, - }, - { - name: "empty RpcMeta", - event: &clmimicry.TraceEvent{Payload: &clmimicry.RpcMeta{}}, - expected: []string{}, - }, - { - name: "gossipsub event with topic in event", - event: &clmimicry.TraceEvent{ - Topic: "/eth2/12345678/beacon_attestation_1/ssz_snappy", - Payload: map[string]any{"MsgID": "msg-123"}, + name: "empty RpcMeta", + event: &clmimicry.RecvRPCEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &clmimicry.RpcMeta{}, }, - expected: []string{"/eth2/12345678/beacon_attestation_1/ssz_snappy"}, + expected: []string{}, }, { name: "RpcMeta with nil control", - event: &clmimicry.TraceEvent{ - Payload: &clmimicry.RpcMeta{ + event: &clmimicry.RecvRPCEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &clmimicry.RpcMeta{ Messages: []clmimicry.RpcMetaMsg{ {Topic: "/eth2/test/topic"}, }, @@ -291,8 +251,12 @@ func TestGetGossipTopics(t *testing.T) { }, { name: "RpcMeta with empty topics", - event: &clmimicry.TraceEvent{ - Payload: &clmimicry.RpcMeta{ + event: &clmimicry.RecvRPCEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &clmimicry.RpcMeta{ Messages: []clmimicry.RpcMetaMsg{ {Topic: ""}, // empty topic }, @@ -303,6 +267,16 @@ func TestGetGossipTopics(t *testing.T) { }, expected: []string{}, }, + { + name: "event without Topic (AddPeerEvent)", + event: &clmimicry.AddPeerEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + }, + expected: []string{}, + }, } for _, tt := range tests { @@ -312,52 +286,20 @@ func TestGetGossipTopics(t *testing.T) { }) } }) - - t.Run("reflection safety - no panic on various types", func(t *testing.T) { - // Test that reflection code doesn't panic on various field types - type TestStruct struct { - Topic string - TopicPtr *string - TopicId *wrapperspb.StringValue - TopicSlice []string - TopicMap map[string]string - TopicChan chan string - TopicFunc func() string - TopicInt int - } - - topicStr := "/eth2/test/topic" - event := &clmimicry.TraceEvent{ - Payload: &TestStruct{ - Topic: topicStr, - TopicPtr: &topicStr, - TopicId: wrapperspb.String("/eth2/test/topic_id"), - TopicSlice: []string{topicStr}, - TopicMap: map[string]string{"key": topicStr}, - TopicChan: nil, // nil channel - TopicFunc: nil, // nil func - TopicInt: 123, - }, - } - - // Should not panic - require.NotPanics(t, func() { - topics := clmimicry.GetGossipTopics(event) - // Should extract only the TopicId field (wrapperspb.StringValue) - // The plain string Topic field is not extracted by the reflection code - assert.Equal(t, []string{"/eth2/test/topic_id"}, topics) - }) - }) } // Benchmark tests func BenchmarkGetMsgID(b *testing.B) { - b.Run("map payload", func(b *testing.B) { - event := &clmimicry.TraceEvent{ - Payload: map[string]any{ - "MsgID": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - "Topic": "/eth2/4a26c58b/beacon_block/ssz_snappy", + peerID, _ := peer.Decode("16Uiu2HAm68jFpjEsRyc1rksPWCorrqwoyR7qdPSvHcinzssnMXJq") + + b.Run("PublishMessageEvent", func(b *testing.B) { + event := &clmimicry.PublishMessageEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + MsgID: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + Topic: "/eth2/4a26c58b/beacon_block/ssz_snappy", } b.ResetTimer() @@ -366,17 +308,14 @@ func BenchmarkGetMsgID(b *testing.B) { } }) - b.Run("struct payload", func(b *testing.B) { - type TestPayload struct { - MsgID string - Topic string - } - - event := &clmimicry.TraceEvent{ - Payload: &TestPayload{ - MsgID: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - Topic: "/eth2/4a26c58b/beacon_block/ssz_snappy", + b.Run("DeliverMessageEvent", func(b *testing.B) { + event := &clmimicry.DeliverMessageEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, }, + MsgID: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + Topic: "/eth2/4a26c58b/beacon_block/ssz_snappy", } b.ResetTimer() @@ -398,10 +337,12 @@ func BenchmarkGetGossipTopics_Large(b *testing.B) { } } - event := &clmimicry.TraceEvent{ - Type: "LIBP2P_TRACE_RECV_RPC", - PeerID: peerID, - Payload: &clmimicry.RpcMeta{ + event := &clmimicry.RecvRPCEvent{ + TraceEventBase: clmimicry.TraceEventBase{ + Timestamp: time.Now(), + PeerID: peerID, + }, + Meta: &clmimicry.RpcMeta{ PeerID: peerID, Messages: messages, }, diff --git a/pkg/clmimicry/trace_test.go b/pkg/clmimicry/trace_test.go index 356eba1a8..77fc8c000 100644 --- a/pkg/clmimicry/trace_test.go +++ b/pkg/clmimicry/trace_test.go @@ -11,12 +11,9 @@ import ( func TestShouldTraceMessage(t *testing.T) { // Test the helper functions that are used by ShouldTraceMessage t.Run("GetMsgID extracts message ID correctly", func(t *testing.T) { - event := &TraceEvent{ - Type: "LIBP2P_TRACE_PUBLISH_MESSAGE", - Payload: map[string]any{ - "MsgID": "test-message-id", - "Topic": "/eth2/test/beacon_block/ssz_snappy", - }, + event := &PublishMessageEvent{ + MsgID: "test-message-id", + Topic: "/eth2/test/beacon_block/ssz_snappy", } msgID := GetMsgID(event) @@ -24,11 +21,8 @@ func TestShouldTraceMessage(t *testing.T) { }) t.Run("GetMsgID returns empty for missing MsgID", func(t *testing.T) { - event := &TraceEvent{ - Type: "LIBP2P_TRACE_PUBLISH_MESSAGE", - Payload: map[string]any{ - "Topic": "/eth2/test/beacon_block/ssz_snappy", - }, + event := &GraftEvent{ + Topic: "/eth2/test/beacon_block/ssz_snappy", } msgID := GetMsgID(event) @@ -36,12 +30,9 @@ func TestShouldTraceMessage(t *testing.T) { }) t.Run("GetGossipTopics extracts topics correctly", func(t *testing.T) { - event := &TraceEvent{ - Type: "LIBP2P_TRACE_PUBLISH_MESSAGE", - Payload: map[string]any{ - "MsgID": "test-message-id", - "Topic": "/eth2/test/beacon_block/ssz_snappy", - }, + event := &PublishMessageEvent{ + MsgID: "test-message-id", + Topic: "/eth2/test/beacon_block/ssz_snappy", } topics := GetGossipTopics(event)