From 6443a9196ea8cdd50c48d63f6649f5e576be3b3e Mon Sep 17 00:00:00 2001 From: Nicholas Molnar <65710+neekolas@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:14:28 -0300 Subject: [PATCH] Add LoggingMisbehaviorService --- pkg/misbehavior/interface.go | 6 ++++ pkg/misbehavior/misbehavior.go | 34 ++++++++++++++++++++++ pkg/misbehavior/misbehavior_test.go | 44 +++++++++++++++++++++++++++++ pkg/misbehavior/report.go | 41 +++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 pkg/misbehavior/interface.go create mode 100644 pkg/misbehavior/misbehavior.go create mode 100644 pkg/misbehavior/misbehavior_test.go create mode 100644 pkg/misbehavior/report.go diff --git a/pkg/misbehavior/interface.go b/pkg/misbehavior/interface.go new file mode 100644 index 00000000..15d72e0d --- /dev/null +++ b/pkg/misbehavior/interface.go @@ -0,0 +1,6 @@ +package misbehavior + +type MisbehaviorService interface { + SafetyFailure(report *SafetyFailureReport) error + // TODO:nm add liveness failures +} diff --git a/pkg/misbehavior/misbehavior.go b/pkg/misbehavior/misbehavior.go new file mode 100644 index 00000000..ee797623 --- /dev/null +++ b/pkg/misbehavior/misbehavior.go @@ -0,0 +1,34 @@ +package misbehavior + +import ( + "errors" + + "go.uber.org/zap" +) + +// LoggingMisbehaviorService provides an implementation of the MisbehaviorService interface that +// logs misbehavior reports without storing them or forwarding to the network. +type LoggingMisbehaviorService struct { + log *zap.Logger +} + +func NewLoggingMisbehaviorService(log *zap.Logger) *LoggingMisbehaviorService { + return &LoggingMisbehaviorService{ + log: log.Named("misbehavior"), + } +} + +func (m *LoggingMisbehaviorService) SafetyFailure(report *SafetyFailureReport) error { + if report == nil { + return errors.New("report is nil") + } + m.log.Warn( + "misbehavior detected", + zap.String("misbehavior_type", report.misbehaviorType.String()), + zap.Uint32("misbehaving_node_id", report.misbehavingNodeId), + zap.Bool("submitted_by_node", report.submittedByNode), + zap.Any("envelopes", report.envelopes), + ) + + return nil +} diff --git a/pkg/misbehavior/misbehavior_test.go b/pkg/misbehavior/misbehavior_test.go new file mode 100644 index 00000000..9c83d9a9 --- /dev/null +++ b/pkg/misbehavior/misbehavior_test.go @@ -0,0 +1,44 @@ +package misbehavior + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xmtp/xmtpd/pkg/envelopes" + proto "github.com/xmtp/xmtpd/pkg/proto/xmtpv4/message_api" + testEnvelopes "github.com/xmtp/xmtpd/pkg/testutils/envelopes" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestLoggingMisbehaviorService(t *testing.T) { + env1, err := envelopes.NewOriginatorEnvelope(testEnvelopes.CreateOriginatorEnvelope(t, 1, 1)) + require.NoError(t, err) + env2, err := envelopes.NewOriginatorEnvelope(testEnvelopes.CreateOriginatorEnvelope(t, 1, 2)) + require.NoError(t, err) + + report, err := NewSafetyFailureReport( + 1, + proto.Misbehavior_MISBEHAVIOR_DUPLICATE_SEQUENCE_ID, + true, + []*envelopes.OriginatorEnvelope{env1, env2}, + ) + require.NoError(t, err) + + core, observedLogs := observer.New(zapcore.DebugLevel) + logger := zap.New(core) + service := NewLoggingMisbehaviorService(logger) + + err = service.SafetyFailure(report) + require.NoError(t, err) + + logs := observedLogs.All() + require.Len(t, logs, 1) + logEntry := logs[0] + require.Equal(t, "misbehavior detected", logEntry.Message) + require.Equal(t, "MISBEHAVIOR_DUPLICATE_SEQUENCE_ID", logEntry.ContextMap()["misbehavior_type"]) + require.Equal(t, uint32(1), logEntry.ContextMap()["misbehaving_node_id"]) + require.Equal(t, true, logEntry.ContextMap()["submitted_by_node"]) + require.Len(t, logEntry.ContextMap()["envelopes"], 2) +} diff --git a/pkg/misbehavior/report.go b/pkg/misbehavior/report.go new file mode 100644 index 00000000..69a2f543 --- /dev/null +++ b/pkg/misbehavior/report.go @@ -0,0 +1,41 @@ +package misbehavior + +import ( + "errors" + + "github.com/xmtp/xmtpd/pkg/envelopes" + proto "github.com/xmtp/xmtpd/pkg/proto/xmtpv4/message_api" +) + +type SafetyFailureReport struct { + misbehavingNodeId uint32 + misbehaviorType proto.Misbehavior + submittedByNode bool + envelopes []*envelopes.OriginatorEnvelope +} + +func NewSafetyFailureReport( + misbehavingNodeId uint32, + misbehaviorType proto.Misbehavior, + submittedByNode bool, + envs []*envelopes.OriginatorEnvelope, +) (*SafetyFailureReport, error) { + if len(envs) == 0 { + return nil, errors.New("no envelopes provided") + } + + if misbehavingNodeId == 0 { + return nil, errors.New("misbehaving node id is required") + } + + if misbehaviorType == proto.Misbehavior_MISBEHAVIOR_UNSPECIFIED { + return nil, errors.New("misbehavior type is required") + } + + return &SafetyFailureReport{ + misbehavingNodeId: misbehavingNodeId, + misbehaviorType: misbehaviorType, + submittedByNode: submittedByNode, + envelopes: envs, + }, nil +}