From 8534e625237b89f5ae8bc81229107bf15ae5b866 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:38:03 +0000 Subject: [PATCH 01/66] Add malfeasance2 package --- activation/e2e/atx_merge_test.go | 2 +- activation/e2e/builds_atx_v2_test.go | 2 +- activation/e2e/checkpoint_merged_test.go | 2 +- activation/e2e/checkpoint_test.go | 2 +- activation/handler.go | 2 +- activation/handler_test.go | 4 +- activation/handler_v1.go | 2 +- activation/handler_v2.go | 2 +- activation/interface.go | 2 +- activation/mocks.go | 40 ++-- checkpoint/recovery_test.go | 2 +- malfeasance/publisher.go | 56 +++++ malfeasance/publisher_test.go | 3 + malfeasance2/handler.go | 113 +++++++++ malfeasance2/handler_test.go | 226 ++++++++++++++++++ malfeasance2/interface.go | 17 ++ malfeasance2/mocks.go | 139 +++++++++++ malfeasance2/wire.go | 43 ++++ malfeasance2/wire_scale.go | 126 ++++++++++ node/node.go | 26 +- p2p/pubsub/pubsub.go | 3 + sql/database.go | 5 +- sql/malfeasance/malfeasance.go | 172 +++++++++++++ sql/malfeasance/malfeasance_test.go | 156 ++++++++++++ .../schema/migrations/0023_malfeasance.sql | 19 ++ sql/statesql/schema/schema.sql | 15 ++ 26 files changed, 1138 insertions(+), 43 deletions(-) create mode 100644 malfeasance/publisher.go create mode 100644 malfeasance/publisher_test.go create mode 100644 malfeasance2/handler.go create mode 100644 malfeasance2/handler_test.go create mode 100644 malfeasance2/interface.go create mode 100644 malfeasance2/mocks.go create mode 100644 malfeasance2/wire.go create mode 100644 malfeasance2/wire_scale.go create mode 100644 sql/malfeasance/malfeasance.go create mode 100644 sql/malfeasance/malfeasance_test.go create mode 100644 sql/statesql/schema/migrations/0023_malfeasance.sql diff --git a/activation/e2e/atx_merge_test.go b/activation/e2e/atx_merge_test.go index 3243dd7b7e..6b7aceefc7 100644 --- a/activation/e2e/atx_merge_test.go +++ b/activation/e2e/atx_merge_test.go @@ -272,7 +272,7 @@ func Test_MarryAndMerge(t *testing.T) { mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) tickSize := uint64(3) diff --git a/activation/e2e/builds_atx_v2_test.go b/activation/e2e/builds_atx_v2_test.go index 607f99b4b8..55812edbd4 100644 --- a/activation/e2e/builds_atx_v2_test.go +++ b/activation/e2e/builds_atx_v2_test.go @@ -117,7 +117,7 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { edVerifier := signing.NewEdVerifier() mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( diff --git a/activation/e2e/checkpoint_merged_test.go b/activation/e2e/checkpoint_merged_test.go index 21e5c0af27..cfd2bfda91 100644 --- a/activation/e2e/checkpoint_merged_test.go +++ b/activation/e2e/checkpoint_merged_test.go @@ -107,7 +107,7 @@ func Test_CheckpointAfterMerge(t *testing.T) { mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( diff --git a/activation/e2e/checkpoint_test.go b/activation/e2e/checkpoint_test.go index a9018c8fcb..e7404a1abc 100644 --- a/activation/e2e/checkpoint_test.go +++ b/activation/e2e/checkpoint_test.go @@ -103,7 +103,7 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { edVerifier := signing.NewEdVerifier() mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( diff --git a/activation/handler.go b/activation/handler.go index da99dd999d..1dd084c59a 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -106,7 +106,7 @@ func NewHandler( fetcher system.Fetcher, goldenATXID types.ATXID, nipostValidator nipostValidator, - beacon AtxReceiver, + beacon atxReceiver, tortoise system.Tortoise, lg *zap.Logger, opts ...HandlerOption, diff --git a/activation/handler_test.go b/activation/handler_test.go index 9b734f057f..7b944e6299 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -124,7 +124,7 @@ type handlerMocks struct { mpub *pubsubmocks.MockPublisher mockFetch *mocks.MockFetcher mValidator *MocknipostValidator - mbeacon *MockAtxReceiver + mbeacon *MockatxReceiver mtortoise *mocks.MockTortoise mMalPublish *MockmalfeasancePublisher } @@ -188,7 +188,7 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { mpub: pubsubmocks.NewMockPublisher(ctrl), mockFetch: mocks.NewMockFetcher(ctrl), mValidator: NewMocknipostValidator(ctrl), - mbeacon: NewMockAtxReceiver(ctrl), + mbeacon: NewMockatxReceiver(ctrl), mtortoise: mocks.NewMockTortoise(ctrl), mMalPublish: NewMockmalfeasancePublisher(ctrl), } diff --git a/activation/handler_v1.go b/activation/handler_v1.go index 647cd7cdb5..faa5e21f8f 100644 --- a/activation/handler_v1.go +++ b/activation/handler_v1.go @@ -76,7 +76,7 @@ type HandlerV1 struct { tickSize uint64 goldenATXID types.ATXID nipostValidator nipostValidatorV1 - beacon AtxReceiver + beacon atxReceiver tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher diff --git a/activation/handler_v2.go b/activation/handler_v2.go index f675eb2c7e..7930ecbe91 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -64,7 +64,7 @@ type HandlerV2 struct { tickSize uint64 goldenATXID types.ATXID nipostValidator nipostValidatorV2 - beacon AtxReceiver + beacon atxReceiver tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher diff --git a/activation/interface.go b/activation/interface.go index 0d112dd648..04b2d9e0c3 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -19,7 +19,7 @@ import ( //go:generate mockgen -typed -package=activation -destination=./mocks.go -source=./interface.go -type AtxReceiver interface { +type atxReceiver interface { OnAtx(*types.ActivationTx) } diff --git a/activation/mocks.go b/activation/mocks.go index 38a1a47206..7809780388 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -24,61 +24,61 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockAtxReceiver is a mock of AtxReceiver interface. -type MockAtxReceiver struct { +// MockatxReceiver is a mock of atxReceiver interface. +type MockatxReceiver struct { ctrl *gomock.Controller - recorder *MockAtxReceiverMockRecorder + recorder *MockatxReceiverMockRecorder } -// MockAtxReceiverMockRecorder is the mock recorder for MockAtxReceiver. -type MockAtxReceiverMockRecorder struct { - mock *MockAtxReceiver +// MockatxReceiverMockRecorder is the mock recorder for MockatxReceiver. +type MockatxReceiverMockRecorder struct { + mock *MockatxReceiver } -// NewMockAtxReceiver creates a new mock instance. -func NewMockAtxReceiver(ctrl *gomock.Controller) *MockAtxReceiver { - mock := &MockAtxReceiver{ctrl: ctrl} - mock.recorder = &MockAtxReceiverMockRecorder{mock} +// NewMockatxReceiver creates a new mock instance. +func NewMockatxReceiver(ctrl *gomock.Controller) *MockatxReceiver { + mock := &MockatxReceiver{ctrl: ctrl} + mock.recorder = &MockatxReceiverMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAtxReceiver) EXPECT() *MockAtxReceiverMockRecorder { +func (m *MockatxReceiver) EXPECT() *MockatxReceiverMockRecorder { return m.recorder } // OnAtx mocks base method. -func (m *MockAtxReceiver) OnAtx(arg0 *types.ActivationTx) { +func (m *MockatxReceiver) OnAtx(arg0 *types.ActivationTx) { m.ctrl.T.Helper() m.ctrl.Call(m, "OnAtx", arg0) } // OnAtx indicates an expected call of OnAtx. -func (mr *MockAtxReceiverMockRecorder) OnAtx(arg0 any) *MockAtxReceiverOnAtxCall { +func (mr *MockatxReceiverMockRecorder) OnAtx(arg0 any) *MockatxReceiverOnAtxCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAtx", reflect.TypeOf((*MockAtxReceiver)(nil).OnAtx), arg0) - return &MockAtxReceiverOnAtxCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAtx", reflect.TypeOf((*MockatxReceiver)(nil).OnAtx), arg0) + return &MockatxReceiverOnAtxCall{Call: call} } -// MockAtxReceiverOnAtxCall wrap *gomock.Call -type MockAtxReceiverOnAtxCall struct { +// MockatxReceiverOnAtxCall wrap *gomock.Call +type MockatxReceiverOnAtxCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockAtxReceiverOnAtxCall) Return() *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) Return() *MockatxReceiverOnAtxCall { c.Call = c.Call.Return() return c } // Do rewrite *gomock.Call.Do -func (c *MockAtxReceiverOnAtxCall) Do(f func(*types.ActivationTx)) *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) Do(f func(*types.ActivationTx)) *MockatxReceiverOnAtxCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockAtxReceiverOnAtxCall) DoAndReturn(f func(*types.ActivationTx)) *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) DoAndReturn(f func(*types.ActivationTx)) *MockatxReceiverOnAtxCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index eb0e6d0a58..ae01570c86 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -251,7 +251,7 @@ func validateAndPreserveData( mclock := activation.NewMocklayerClock(ctrl) mfetch := smocks.NewMockFetcher(ctrl) mvalidator := activation.NewMocknipostValidator(ctrl) - mreceiver := activation.NewMockAtxReceiver(ctrl) + mreceiver := activation.NewMockatxReceiver(ctrl) mtrtl := smocks.NewMockTortoise(ctrl) cdb := datastore.NewCachedDB(db, lg) atxHandler := activation.NewHandler( diff --git a/malfeasance/publisher.go b/malfeasance/publisher.go new file mode 100644 index 0000000000..9fe72a6bc0 --- /dev/null +++ b/malfeasance/publisher.go @@ -0,0 +1,56 @@ +package malfeasance + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/malfeasance/wire" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/sql/identities" +) + +type Publisher struct { + logger *zap.Logger + cdb *datastore.CachedDB + tortoise tortoise + publisher pubsub.Publisher +} + +func NewPublisher( + logger *zap.Logger, + cdb *datastore.CachedDB, + tortoise tortoise, + publisher pubsub.Publisher, +) *Publisher { + return &Publisher{ + logger: logger, + cdb: cdb, + tortoise: tortoise, + publisher: publisher, + } +} + +// Publishes a malfeasance proof to the network. +func (p *Publisher) PublishProof(ctx context.Context, smesherID types.NodeID, proof *wire.MalfeasanceProof) error { + err := identities.SetMalicious(p.cdb, smesherID, codec.MustEncode(proof), time.Now()) + if err != nil { + return fmt.Errorf("adding malfeasance proof: %w", err) + } + p.cdb.CacheMalfeasanceProof(smesherID, codec.MustEncode(proof)) + p.tortoise.OnMalfeasance(smesherID) + + gossip := wire.MalfeasanceGossip{ + MalfeasanceProof: *proof, + } + if err = p.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { + p.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) + return fmt.Errorf("broadcast atx malfeasance proof: %w", err) + } + return nil +} diff --git a/malfeasance/publisher_test.go b/malfeasance/publisher_test.go new file mode 100644 index 0000000000..7da6c95512 --- /dev/null +++ b/malfeasance/publisher_test.go @@ -0,0 +1,3 @@ +package malfeasance + +// TODO(mafa): implement me diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go new file mode 100644 index 0000000000..12aa8532ee --- /dev/null +++ b/malfeasance2/handler.go @@ -0,0 +1,113 @@ +package malfeasance2 + +import ( + "context" + "fmt" + "slices" + + "go.uber.org/zap" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/p2p" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/sql" +) + +var ( + ErrMalformedData = fmt.Errorf("%w: malformed data", pubsub.ErrValidationReject) + ErrWrongHash = fmt.Errorf("%w: incorrect hash", pubsub.ErrValidationReject) + ErrUnknownVersion = fmt.Errorf("%w: unknown version", pubsub.ErrValidationReject) + ErrUnknownDomain = fmt.Errorf("%w: unknown domain", pubsub.ErrValidationReject) +) + +type Handler struct { + logger *zap.Logger + db sql.Executor + self p2p.Peer + tortoise tortoise + + handlers map[ProofDomain]MalfeasanceHandler +} + +func NewHandler( + db sql.Executor, + lg *zap.Logger, + self p2p.Peer, + tortoise tortoise, +) *Handler { + return &Handler{ + db: db, + logger: lg, + self: self, + tortoise: tortoise, // TODO(mafa): needed to call OnMalfeasance after receiving & storing a valid proof + + handlers: make(map[ProofDomain]MalfeasanceHandler), + } +} + +func (h *Handler) RegisterHandler(malfeasanceType ProofDomain, handler MalfeasanceHandler) { + h.handlers[malfeasanceType] = handler +} + +func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, _ p2p.Peer, msg []byte) error { + var proof MalfeasanceProof + if err := codec.Decode(msg, &proof); err != nil { + return ErrMalformedData + } + + nodeIDs, err := h.handleProof(ctx, proof) + if err != nil { + return err + } + + if !slices.Contains(nodeIDs, types.NodeID(expHash)) { + return ErrWrongHash + } + + if err := h.storeProof(ctx, proof.Domain, msg); err != nil { + return fmt.Errorf("store synced malfeasance proof: %w", err) + } + return nil +} + +func (h *Handler) HandleGossip(ctx context.Context, peer p2p.Peer, msg []byte) error { + if peer == h.self { + // ignore messages from self, we already validate and persist proofs when publishing + return nil + } + + var proof MalfeasanceProof + if err := codec.Decode(msg, &proof); err != nil { + return ErrMalformedData + } + + _, err := h.handleProof(ctx, proof) + if err != nil { + return err + } + + if err := h.storeProof(ctx, proof.Domain, msg); err != nil { + return fmt.Errorf("store synced malfeasance proof: %w", err) + } + return nil +} + +func (h *Handler) handleProof(ctx context.Context, proof MalfeasanceProof) ([]types.NodeID, error) { + if proof.Version != 0 { + // unsupported proof version + return nil, ErrUnknownVersion + } + + handler, ok := h.handlers[proof.Domain] + if !ok { + // unknown proof domain + return nil, fmt.Errorf("%w: %d", ErrUnknownDomain, proof.Domain) + } + + return handler.Validate(ctx, proof.Proof) +} + +func (h *Handler) storeProof(ctx context.Context, domain ProofDomain, proof []byte) error { + return nil +} diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go new file mode 100644 index 0000000000..de4d4c7f3d --- /dev/null +++ b/malfeasance2/handler_test.go @@ -0,0 +1,226 @@ +package malfeasance2_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" + "go.uber.org/zap/zaptest/observer" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/malfeasance2" + "github.com/spacemeshos/go-spacemesh/p2p" + "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/statesql" +) + +type testHandler struct { + *malfeasance2.Handler + + observedLogs *observer.ObservedLogs + db sql.StateDatabase + self p2p.Peer + mockTrt *malfeasance2.Mocktortoise +} + +func newTestHandler(tb testing.TB) *testHandler { + db := statesql.InMemory() + observer, observedLogs := observer.New(zap.WarnLevel) + logger := zaptest.NewLogger(tb, zaptest.WrapOptions(zap.WrapCore( + func(core zapcore.Core) zapcore.Core { + return zapcore.NewTee(core, observer) + }, + ))) + + ctrl := gomock.NewController(tb) + mockTrt := malfeasance2.NewMocktortoise(ctrl) + + h := malfeasance2.NewHandler( + db, + logger, + "self", + mockTrt, + ) + return &testHandler{ + Handler: h, + + observedLogs: observedLogs, + db: db, + self: "self", + mockTrt: mockTrt, + } +} + +// TODO(mafa): missing tests +// - new proof for same identity is no-op +// - new proof with bigger certificate list only updates certificate list +// - all identities in certificates are marked as malicious +// - invalid certificates are ignored if proof is valid + +func TestHandler_HandleSync(t *testing.T) { + t.Run("malformed data", func(t *testing.T) { + h := newTestHandler(t) + + err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", []byte("malformed")) + require.ErrorIs(t, err, malfeasance2.ErrMalformedData) + }) + + t.Run("unknown version", func(t *testing.T) { + h := newTestHandler(t) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 42, + } + + err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", codec.MustEncode(proof)) + require.ErrorIs(t, err, malfeasance2.ErrUnknownVersion) + }) + + t.Run("unknown domain", func(t *testing.T) { + h := newTestHandler(t) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: 42, + } + + err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", codec.MustEncode(proof)) + require.ErrorIs(t, err, malfeasance2.ErrUnknownDomain) + }) + + t.Run("invalid proof", func(t *testing.T) { + h := newTestHandler(t) + invalidProof := []byte("invalid") + handlerError := errors.New("invalid proof") + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(nil, handlerError) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: malfeasance2.InvalidActivation, + Proof: invalidProof, + } + + err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", codec.MustEncode(proof)) + require.ErrorIs(t, err, handlerError) + }) + + t.Run("valid proof", func(t *testing.T) { + h := newTestHandler(t) + validProof := []byte("valid") + nodeID := types.RandomNodeID() + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return([]types.NodeID{nodeID}, nil) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: malfeasance2.InvalidActivation, + Proof: validProof, + } + + err := h.HandleSynced(context.Background(), types.Hash32(nodeID), "peer", codec.MustEncode(proof)) + require.NoError(t, err) + }) + + t.Run("valid proof, wrong hash", func(t *testing.T) { + h := newTestHandler(t) + validProof := []byte("valid") + nodeID := types.RandomNodeID() + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return([]types.NodeID{nodeID}, nil) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: malfeasance2.InvalidActivation, + Proof: validProof, + } + + err := h.HandleSynced(context.Background(), types.Hash32(types.RandomNodeID()), "peer", codec.MustEncode(proof)) + require.ErrorIs(t, err, malfeasance2.ErrWrongHash) + }) +} + +func TestHandler_HandleGossip(t *testing.T) { + t.Run("malformed data", func(t *testing.T) { + h := newTestHandler(t) + + err := h.HandleGossip(context.Background(), "peer", []byte("malformed")) + require.ErrorIs(t, err, malfeasance2.ErrMalformedData) + }) + + t.Run("self peer", func(t *testing.T) { + h := newTestHandler(t) + + // ignore messages from self + err := h.HandleGossip(context.Background(), h.self, []byte("malformed")) + require.NoError(t, err) + }) + + t.Run("unknown version", func(t *testing.T) { + h := newTestHandler(t) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 42, + } + + err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) + require.ErrorIs(t, err, malfeasance2.ErrUnknownVersion) + }) + + t.Run("unknown domain", func(t *testing.T) { + h := newTestHandler(t) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: 42, + } + + err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) + require.ErrorIs(t, err, malfeasance2.ErrUnknownDomain) + }) + + t.Run("invalid proof", func(t *testing.T) { + h := newTestHandler(t) + invalidProof := []byte("invalid") + handlerError := errors.New("invalid proof") + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(nil, handlerError) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: malfeasance2.InvalidActivation, + Proof: invalidProof, + } + + err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) + require.ErrorIs(t, err, handlerError) + }) + + t.Run("valid proof", func(t *testing.T) { + h := newTestHandler(t) + validProof := []byte("valid") + nodeID := types.RandomNodeID() + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return([]types.NodeID{nodeID}, nil) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: malfeasance2.InvalidActivation, + Proof: validProof, + } + + err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) + require.NoError(t, err) + }) +} diff --git a/malfeasance2/interface.go b/malfeasance2/interface.go new file mode 100644 index 0000000000..33f68921e5 --- /dev/null +++ b/malfeasance2/interface.go @@ -0,0 +1,17 @@ +package malfeasance2 + +import ( + "context" + + "github.com/spacemeshos/go-spacemesh/common/types" +) + +//go:generate mockgen -typed -package=malfeasance2 -destination=./mocks.go -source=./interface.go + +type tortoise interface { + OnMalfeasance(types.NodeID) +} + +type MalfeasanceHandler interface { + Validate(ctx context.Context, data []byte) ([]types.NodeID, error) +} diff --git a/malfeasance2/mocks.go b/malfeasance2/mocks.go new file mode 100644 index 0000000000..b8f89fa37b --- /dev/null +++ b/malfeasance2/mocks.go @@ -0,0 +1,139 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go +// +// Generated by this command: +// +// mockgen -typed -package=malfeasance2 -destination=./mocks.go -source=./interface.go +// + +// Package malfeasance2 is a generated GoMock package. +package malfeasance2 + +import ( + context "context" + reflect "reflect" + + types "github.com/spacemeshos/go-spacemesh/common/types" + gomock "go.uber.org/mock/gomock" +) + +// Mocktortoise is a mock of tortoise interface. +type Mocktortoise struct { + ctrl *gomock.Controller + recorder *MocktortoiseMockRecorder +} + +// MocktortoiseMockRecorder is the mock recorder for Mocktortoise. +type MocktortoiseMockRecorder struct { + mock *Mocktortoise +} + +// NewMocktortoise creates a new mock instance. +func NewMocktortoise(ctrl *gomock.Controller) *Mocktortoise { + mock := &Mocktortoise{ctrl: ctrl} + mock.recorder = &MocktortoiseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocktortoise) EXPECT() *MocktortoiseMockRecorder { + return m.recorder +} + +// OnMalfeasance mocks base method. +func (m *Mocktortoise) OnMalfeasance(arg0 types.NodeID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnMalfeasance", arg0) +} + +// OnMalfeasance indicates an expected call of OnMalfeasance. +func (mr *MocktortoiseMockRecorder) OnMalfeasance(arg0 any) *MocktortoiseOnMalfeasanceCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnMalfeasance", reflect.TypeOf((*Mocktortoise)(nil).OnMalfeasance), arg0) + return &MocktortoiseOnMalfeasanceCall{Call: call} +} + +// MocktortoiseOnMalfeasanceCall wrap *gomock.Call +type MocktortoiseOnMalfeasanceCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocktortoiseOnMalfeasanceCall) Return() *MocktortoiseOnMalfeasanceCall { + c.Call = c.Call.Return() + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocktortoiseOnMalfeasanceCall) Do(f func(types.NodeID)) *MocktortoiseOnMalfeasanceCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocktortoiseOnMalfeasanceCall) DoAndReturn(f func(types.NodeID)) *MocktortoiseOnMalfeasanceCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MockMalfeasanceHandler is a mock of MalfeasanceHandler interface. +type MockMalfeasanceHandler struct { + ctrl *gomock.Controller + recorder *MockMalfeasanceHandlerMockRecorder +} + +// MockMalfeasanceHandlerMockRecorder is the mock recorder for MockMalfeasanceHandler. +type MockMalfeasanceHandlerMockRecorder struct { + mock *MockMalfeasanceHandler +} + +// NewMockMalfeasanceHandler creates a new mock instance. +func NewMockMalfeasanceHandler(ctrl *gomock.Controller) *MockMalfeasanceHandler { + mock := &MockMalfeasanceHandler{ctrl: ctrl} + mock.recorder = &MockMalfeasanceHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMalfeasanceHandler) EXPECT() *MockMalfeasanceHandlerMockRecorder { + return m.recorder +} + +// Validate mocks base method. +func (m *MockMalfeasanceHandler) Validate(ctx context.Context, data []byte) ([]types.NodeID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", ctx, data) + ret0, _ := ret[0].([]types.NodeID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Validate indicates an expected call of Validate. +func (mr *MockMalfeasanceHandlerMockRecorder) Validate(ctx, data any) *MockMalfeasanceHandlerValidateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockMalfeasanceHandler)(nil).Validate), ctx, data) + return &MockMalfeasanceHandlerValidateCall{Call: call} +} + +// MockMalfeasanceHandlerValidateCall wrap *gomock.Call +type MockMalfeasanceHandlerValidateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockMalfeasanceHandlerValidateCall) Return(arg0 []types.NodeID, arg1 error) *MockMalfeasanceHandlerValidateCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockMalfeasanceHandlerValidateCall) Do(f func(context.Context, []byte) ([]types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockMalfeasanceHandlerValidateCall) DoAndReturn(f func(context.Context, []byte) ([]types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/malfeasance2/wire.go b/malfeasance2/wire.go new file mode 100644 index 0000000000..86b0c62572 --- /dev/null +++ b/malfeasance2/wire.go @@ -0,0 +1,43 @@ +package malfeasance2 + +import "github.com/spacemeshos/go-spacemesh/common/types" + +//go:generate scalegen + +// ProofDomain encodes the type of malfeasance proof. It is used to decide which domain generated the proof. +type ProofDomain byte + +const ( + InvalidActivation ProofDomain = iota + InvalidBallot + InvalidHareMsg +) + +// ProofVersion encodes the version of the malfeasance proof. +// At the moment this will always be 0. +type ProofVersion byte + +type MalfeasanceProof struct { + // Version is the version identifier of the proof. This can be used to extend the malfeasance proof in the future. + Version ProofVersion + + // Certificates is a slice of marriage certificates showing which identities belong to the same marriage set as + // the one proven to be malfeasant. Up to 1024 can be put into a single proof, since by repeatedly marrying other + // identities there can be much more than 256 in a malfeasant marriage set. Beyond that a second proof could be + // provided to show that additional identities are part of the same malfeasant marriage set. + Certificates []ProofCertificate `scale:"max=1024"` + + // Domain encodes the domain for which the proof was created + Domain ProofDomain + // Proof is the domain specific proof. Its type depends on the ProofDomain. + Proof []byte `scale:"max=1048576"` // max size of proof is 1MiB +} + +type ProofCertificate struct { + // TargetID is the identity that was married to by the smesher. + TargetID types.NodeID + // SmesherID is the identity that signed the certificate. + SmesherID types.NodeID + // Signature is the signature of the certificate. + Signature types.EdSignature +} diff --git a/malfeasance2/wire_scale.go b/malfeasance2/wire_scale.go new file mode 100644 index 0000000000..c193bf0097 --- /dev/null +++ b/malfeasance2/wire_scale.go @@ -0,0 +1,126 @@ +// Code generated by github.com/spacemeshos/go-scale/scalegen. DO NOT EDIT. + +// nolint +package malfeasance2 + +import ( + "github.com/spacemeshos/go-scale" +) + +func (t *MalfeasanceProof) EncodeScale(enc *scale.Encoder) (total int, err error) { + { + n, err := scale.EncodeCompact8(enc, uint8(t.Version)) + if err != nil { + return total, err + } + total += n + } + { + n, err := scale.EncodeStructSliceWithLimit(enc, t.Certificates, 1024) + if err != nil { + return total, err + } + total += n + } + { + n, err := scale.EncodeCompact8(enc, uint8(t.Domain)) + if err != nil { + return total, err + } + total += n + } + { + n, err := scale.EncodeByteSliceWithLimit(enc, t.Proof, 1048576) + if err != nil { + return total, err + } + total += n + } + return total, nil +} + +func (t *MalfeasanceProof) DecodeScale(dec *scale.Decoder) (total int, err error) { + { + field, n, err := scale.DecodeCompact8(dec) + if err != nil { + return total, err + } + total += n + t.Version = ProofVersion(field) + } + { + field, n, err := scale.DecodeStructSliceWithLimit[ProofCertificate](dec, 1024) + if err != nil { + return total, err + } + total += n + t.Certificates = field + } + { + field, n, err := scale.DecodeCompact8(dec) + if err != nil { + return total, err + } + total += n + t.Domain = ProofDomain(field) + } + { + field, n, err := scale.DecodeByteSliceWithLimit(dec, 1048576) + if err != nil { + return total, err + } + total += n + t.Proof = field + } + return total, nil +} + +func (t *ProofCertificate) EncodeScale(enc *scale.Encoder) (total int, err error) { + { + n, err := scale.EncodeByteArray(enc, t.TargetID[:]) + if err != nil { + return total, err + } + total += n + } + { + n, err := scale.EncodeByteArray(enc, t.SmesherID[:]) + if err != nil { + return total, err + } + total += n + } + { + n, err := scale.EncodeByteArray(enc, t.Signature[:]) + if err != nil { + return total, err + } + total += n + } + return total, nil +} + +func (t *ProofCertificate) DecodeScale(dec *scale.Decoder) (total int, err error) { + { + n, err := scale.DecodeByteArray(dec, t.TargetID[:]) + if err != nil { + return total, err + } + total += n + } + { + n, err := scale.DecodeByteArray(dec, t.SmesherID[:]) + if err != nil { + return total, err + } + total += n + } + { + n, err := scale.DecodeByteArray(dec, t.Signature[:]) + if err != nil { + return total, err + } + total += n + } + return total, nil +} diff --git a/node/node.go b/node/node.go index 0690bcf9e6..8f6227d0c3 100644 --- a/node/node.go +++ b/node/node.go @@ -1202,13 +1202,13 @@ func (app *App) initServices(ctx context.Context) error { ), ) - syncHandler := func(_ context.Context, _ p2p.Peer, _ []byte) error { + checkSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { if newSyncer.ListenToGossip() { return nil } return errors.New("not synced for gossip") } - atxSyncHandler := func(_ context.Context, _ p2p.Peer, _ []byte) error { + checkAtxSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { if newSyncer.ListenToATXGossip() { return nil } @@ -1218,45 +1218,49 @@ func (app *App) initServices(ctx context.Context) error { if app.Config.Beacon.RoundsNumber > 0 { app.host.Register( pubsub.BeaconWeakCoinProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleWeakCoinProposal), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleWeakCoinProposal), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconProposalProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleProposal), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleProposal), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconFirstVotesProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleFirstVotes), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleFirstVotes), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconFollowingVotesProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleFollowingVotes), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleFollowingVotes), pubsub.WithValidatorInline(true), ) } app.host.Register( pubsub.ProposalProtocol, - pubsub.ChainGossipHandler(syncHandler, proposalListener.HandleProposal), + pubsub.ChainGossipHandler(checkSynced, proposalListener.HandleProposal), ) app.host.Register( pubsub.AtxProtocol, - pubsub.ChainGossipHandler(atxSyncHandler, atxHandler.HandleGossipAtx), + pubsub.ChainGossipHandler(checkAtxSynced, atxHandler.HandleGossipAtx), pubsub.WithValidatorConcurrency(app.Config.P2P.GossipAtxValidationThrottle), ) app.host.Register( pubsub.TxProtocol, - pubsub.ChainGossipHandler(syncHandler, app.txHandler.HandleGossipTransaction), + pubsub.ChainGossipHandler(checkSynced, app.txHandler.HandleGossipTransaction), ) app.host.Register( pubsub.BlockCertify, - pubsub.ChainGossipHandler(syncHandler, app.certifier.HandleCertifyMessage), + pubsub.ChainGossipHandler(checkSynced, app.certifier.HandleCertifyMessage), ) app.host.Register( pubsub.MalfeasanceProof, - pubsub.ChainGossipHandler(atxSyncHandler, app.malfeasanceHandler.HandleMalfeasanceProof), + pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), + ) + app.host.Register( + pubsub.MalfeasanceProof2, + pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), ) app.proposalBuilder = proposalBuilder diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 9d181b2313..2bb8fd855f 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -76,7 +76,10 @@ const ( // BeaconFollowingVotesProtocol is the protocol id for beacon following votes. BeaconFollowingVotesProtocol = "bo1" + // MalfeasanceProof is the protocol id for malfeasance proofs (soon to be deprecated). MalfeasanceProof = "mp1" + // MalfeasanceProof2 is the protocol id for V2 malfeasance proofs. + MalfeasanceProof2 = "mp2" ) // DefaultConfig for PubSub. diff --git a/sql/database.go b/sql/database.go index a647086f4a..599a775f3a 100644 --- a/sql/database.go +++ b/sql/database.go @@ -309,7 +309,10 @@ func prepareDB(logger *zap.Logger, db *sqliteDatabase, config *conf, freshDB boo if config.enableLatency { db.latency = newQueryLatency() } - + if _, err := db.Exec("PRAGMA foreign_keys = ON;", nil, nil); err != nil { + db.Close() + return nil, fmt.Errorf("enable foreign keys: %w", err) + } if config.temp { // Temporary database is used for migration and is deleted if migrations // fail, so we make it faster by disabling journaling and synchronous diff --git a/sql/malfeasance/malfeasance.go b/sql/malfeasance/malfeasance.go new file mode 100644 index 0000000000..28809cf544 --- /dev/null +++ b/sql/malfeasance/malfeasance.go @@ -0,0 +1,172 @@ +package malfeasance + +import ( + "context" + "fmt" + "time" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql" +) + +func Add(db sql.Executor, nodeID types.NodeID, domain byte, proof []byte, received time.Time) error { + _, err := db.Exec(` + INSERT INTO malfeasance (pubkey, received, domain, proof) + VALUES (?1, ?2, ?3, ?4);`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + stmt.BindInt64(2, received.UnixNano()) + stmt.BindInt64(3, int64(domain)) + stmt.BindBytes(4, proof) + }, nil, + ) + if err != nil { + return fmt.Errorf("add malfeasance %s: %w", nodeID, err) + } + return nil +} + +func AddMarried(db sql.Executor, nodeID, marriedTo types.NodeID, received time.Time) error { + _, err := db.Exec(` + INSERT INTO malfeasance (pubkey, received, married_to) + VALUES (?1, ?2, ?3);`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + stmt.BindInt64(2, received.UnixNano()) + stmt.BindBytes(3, marriedTo.Bytes()) + }, nil, + ) + if err != nil { + return fmt.Errorf("add married %s: %w", nodeID, err) + } + return nil +} + +func IsMalicious(db sql.Executor, nodeID types.NodeID) (bool, error) { + rows, err := db.Exec(` + SELECT 1 FROM malfeasance + WHERE pubkey = ?1;`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + }, nil, + ) + if err != nil { + return false, fmt.Errorf("is malicious %s: %w", nodeID, err) + } + return rows > 0, nil +} + +// Proof returns a proof for the given node ID. It will not necessarily return the proof for the given node ID, +// but might return the proof for the node ID the given node ID is married to. +func Proof(db sql.Executor, nodeID types.NodeID) (types.NodeID, byte, []byte, error) { + var domain byte + var proof []byte + _, err := db.Exec(` + SELECT domain, proof FROM malfeasance + WHERE pubkey = ?1;`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + }, func(stmt *sql.Statement) bool { + domain = byte(stmt.ColumnInt(0)) + proof = make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(1, proof) + return true + }, + ) + if err != nil { + return types.EmptyNodeID, 0, nil, fmt.Errorf("proof %v: %w", nodeID, err) + } + if len(proof) > 0 { + return nodeID, domain, proof, nil + } + + _, err = db.Exec(` + SELECT pubkey, domain, proof FROM malfeasance + WHERE pubkey = ( + SELECT married_to FROM malfeasance + WHERE pubkey = ?1 + );`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + }, func(stmt *sql.Statement) bool { + stmt.ColumnBytes(0, nodeID[:]) + domain = byte(stmt.ColumnInt(1)) + proof = make([]byte, stmt.ColumnLen(2)) + stmt.ColumnBytes(2, proof) + return true + }, + ) + if err != nil { + return types.EmptyNodeID, 0, nil, fmt.Errorf("proof %v: %w", nodeID, err) + } + if proof == nil { + return types.EmptyNodeID, 0, nil, fmt.Errorf("proof %v: %w", nodeID, sql.ErrNotFound) + } + + return nodeID, domain, proof, nil +} + +// TODO(mafa): it seems that this is again needed by the fetcher. +// +// The problem here is that iterate will iterate over all identities known to be malfeasant, +// independent of their marriage set. I believe we have to stick with this behavior, +// since we might have a different view on the marriage set than our peers, so we have to include +// ALL known malicious identities. +func Iterate(db sql.Executor, callback func(total int, id types.NodeID) error) error { + var callbackErr error + dec := func(stmt *sql.Statement) bool { + var id types.NodeID + total := stmt.ColumnInt(0) + stmt.ColumnBytes(1, id[:]) + if err := callback(total, id); err != nil { + return false + } + return true + } + + _, err := db.Exec(` + SELECT (SELECT count(*) FROM malfeasance) as total, + pubkey FROM malfeasance;`, + nil, dec, + ) + if err != nil { + return fmt.Errorf("iterate malfeasance: %w", err) + } + return callbackErr +} + +// All retrieves all malicious node IDs from the database. +func All(db sql.Executor) ([]types.NodeID, error) { + var nodeIDs []types.NodeID + err := Iterate(db, func(total int, id types.NodeID) error { + if nodeIDs == nil { + nodeIDs = make([]types.NodeID, 0, total) + } + nodeIDs = append(nodeIDs, id) + return nil + }) + if err != nil { + return nil, err + } + if len(nodeIDs) != cap(nodeIDs) { + panic("BUG: bad malicious node ID count") + } + return nodeIDs, nil +} + +// TODO(mafa): it looks like the fetcher needs this function? +// Implementing this is not trivial, as the blob size depends on how many identities are in the marriage set +// and the encoded proof might be for a different identity then requested. +// +// This query could be significantly slower than other "GetBlobSizes" queries. +func BlobSizes(db sql.Executor, ids [][]byte) (sizes []int, err error) { + panic("implement me") +} + +// TODO(mafa): it looks like the fetcher needs this function? +// +// Same as above - loading the blob from DB is not trivial, since it requires re-encoding a +// possibly different identity's proof with certificates from the current knowledge about the marriage set. +func LoadBlob(ctx context.Context, db sql.Executor, nodeID []byte, blob *sql.Blob) error { + panic("implement me") +} diff --git a/sql/malfeasance/malfeasance_test.go b/sql/malfeasance/malfeasance_test.go new file mode 100644 index 0000000000..889a219e99 --- /dev/null +++ b/sql/malfeasance/malfeasance_test.go @@ -0,0 +1,156 @@ +package malfeasance_test + +import ( + "testing" + "time" + + sqlite "github.com/go-llsqlite/crawshaw" + "github.com/stretchr/testify/require" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" + "github.com/spacemeshos/go-spacemesh/sql/statesql" +) + +func TestAdd(t *testing.T) { + t.Parallel() + + db := statesql.InMemoryTest(t) + + id := types.RandomNodeID() + domain := byte(1) + proof := []byte{1, 2, 3} + received := time.Now() + + require.NoError(t, malfeasance.Add(db, id, domain, proof, received)) + + mal, err := malfeasance.IsMalicious(db, id) + require.NoError(t, err) + require.True(t, mal) +} + +func TestAddMarried(t *testing.T) { + t.Parallel() + + db := statesql.InMemoryTest(t) + + id := types.RandomNodeID() + marriedTo := types.RandomNodeID() + received := time.Now() + + require.NoError(t, malfeasance.Add(db, marriedTo, 1, []byte{1, 2, 3}, received)) + + require.NoError(t, malfeasance.AddMarried(db, id, marriedTo, received)) + + mal, err := malfeasance.IsMalicious(db, id) + require.NoError(t, err) + require.True(t, mal) + + mal, err = malfeasance.IsMalicious(db, marriedTo) + require.NoError(t, err) + require.True(t, mal) +} + +func TestAddMarriedMissing(t *testing.T) { + t.Parallel() + + db := statesql.InMemoryTest(t) + + id := types.RandomNodeID() + marriedTo := types.RandomNodeID() + received := time.Now() + + err := malfeasance.AddMarried(db, id, marriedTo, received) + sqlError := &sqlite.Error{} + require.ErrorAs(t, err, sqlError) + require.Equal(t, sqlite.SQLITE_CONSTRAINT_FOREIGNKEY, sqlError.Code) + + mal, err := malfeasance.IsMalicious(db, id) + require.NoError(t, err) + require.False(t, mal) + + mal, err = malfeasance.IsMalicious(db, marriedTo) + require.NoError(t, err) + require.False(t, mal) +} + +func TestProof(t *testing.T) { + t.Parallel() + + db := statesql.InMemoryTest(t) + + id := types.RandomNodeID() + domain := byte(1) + proof := []byte{1, 2, 3} + received := time.Now() + + gotId, gotDomain, gotProof, err := malfeasance.Proof(db, id) + require.ErrorIs(t, err, sql.ErrNotFound) + require.Zero(t, gotId) + require.Zero(t, gotDomain) + require.Nil(t, gotProof) + + require.NoError(t, malfeasance.Add(db, id, domain, proof, received)) + + gotId, gotDomain, gotProof, err = malfeasance.Proof(db, id) + require.NoError(t, err) + require.Equal(t, id, gotId) + require.Equal(t, domain, gotDomain) + require.Equal(t, proof, gotProof) +} + +func TestProofMarried(t *testing.T) { + t.Parallel() + + db := statesql.InMemoryTest(t) + + id := types.RandomNodeID() + marriedTo := types.RandomNodeID() + domain := byte(1) + proof := []byte{1, 2, 3} + received := time.Now() + + require.NoError(t, malfeasance.Add(db, marriedTo, domain, proof, received)) + + require.NoError(t, malfeasance.AddMarried(db, id, marriedTo, received)) + + gotId, gotDomain, gotProof, err := malfeasance.Proof(db, marriedTo) + require.NoError(t, err) + require.Equal(t, marriedTo, gotId) + require.Equal(t, domain, gotDomain) + require.Equal(t, proof, gotProof) + + gotId, gotDomain, gotProof, err = malfeasance.Proof(db, id) + require.NoError(t, err) + require.Equal(t, marriedTo, gotId) + require.Equal(t, domain, gotDomain) + require.Equal(t, proof, gotProof) +} + +func TestAll(t *testing.T) { + t.Parallel() + + db := statesql.InMemoryTest(t) + + ids := make([]types.NodeID, 3) + domain := byte(1) + proof := []byte{1, 2, 3} + received := time.Now() + + for i := range ids { + ids[i] = types.RandomNodeID() + require.NoError(t, malfeasance.Add(db, ids[i], domain, proof, received)) + } + + marriedIds := make([]types.NodeID, 3) + for i := range marriedIds { + marriedIds[i] = types.RandomNodeID() + require.NoError(t, malfeasance.AddMarried(db, marriedIds[i], ids[i], received)) + } + + expected := append(ids, marriedIds...) + all, err := malfeasance.All(db) + require.NoError(t, err) + require.ElementsMatch(t, expected, all) +} diff --git a/sql/statesql/schema/migrations/0023_malfeasance.sql b/sql/statesql/schema/migrations/0023_malfeasance.sql new file mode 100644 index 0000000000..1dba7f1e7c --- /dev/null +++ b/sql/statesql/schema/migrations/0023_malfeasance.sql @@ -0,0 +1,19 @@ +-- adds new table for v2 malfeasance proofs +-- TODO(mafa): in the future add a migration to convert old malfeasance proofs to the new format +-- and then remove proof, received from the old table + +CREATE TABLE malfeasance +( + pubkey CHAR(32) PRIMARY KEY, + received INT NOT NULL, -- unix timestamp + + -- if the following field is not null, then domain and proof are null + married_to CHAR(32), -- the pubkey of identity in the marriage set that was proven to be malicious + + -- if the following fields are not null, then married_to is null + domain INT, -- domain of the proof + proof BLOB, -- proof of the identity to be malicious + + -- ensure the identity referenced already exists in this table + FOREIGN KEY (married_to) REFERENCES malfeasance(pubkey) +); diff --git a/sql/statesql/schema/schema.sql b/sql/statesql/schema/schema.sql index cf71312a53..fdfec0f6f0 100755 --- a/sql/statesql/schema/schema.sql +++ b/sql/statesql/schema/schema.sql @@ -96,6 +96,21 @@ CREATE TABLE layers aggregated_hash CHAR(32) ) WITHOUT ROWID; CREATE INDEX layers_by_processed ON layers (processed); +CREATE TABLE malfeasance +( + pubkey CHAR(32) PRIMARY KEY, + received INT NOT NULL, + + + married_to CHAR(32), + + + domain INT, + proof BLOB, + + + FOREIGN KEY (married_to) REFERENCES malfeasance(pubkey) +); CREATE TABLE poets ( ref VARCHAR PRIMARY KEY, From b421adbb19fa12fc766d169d155ed4483d165feb Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:51:48 +0000 Subject: [PATCH 02/66] Rename malfeasance files --- activation/{malfeasance2.go => malfeasance2_publisher.go} | 0 activation/{malfeasance.go => malfeasance_handler.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename activation/{malfeasance2.go => malfeasance2_publisher.go} (100%) rename activation/{malfeasance.go => malfeasance_handler.go} (100%) diff --git a/activation/malfeasance2.go b/activation/malfeasance2_publisher.go similarity index 100% rename from activation/malfeasance2.go rename to activation/malfeasance2_publisher.go diff --git a/activation/malfeasance.go b/activation/malfeasance_handler.go similarity index 100% rename from activation/malfeasance.go rename to activation/malfeasance_handler.go From 697d736adc4087f14a5162b24fc73b9ba5232ca9 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:24:40 +0000 Subject: [PATCH 03/66] Cleanups --- activation/handler.go | 3 +- activation/handler_test.go | 4 +- activation/handler_v2.go | 4 +- activation/handler_v2_test.go | 6 +- activation/interface.go | 8 +- activation/malfeasance2_handler.go | 121 ++++++++++++++++++++++ activation/malfeasance2_handler_test.go | 3 + activation/malfeasance2_publisher.go | 6 +- activation/malfeasance2_publisher_test.go | 3 + activation/mocks.go | 18 ++-- activation/post_verifier_test.go | 4 +- activation/wire/malfeasance.go | 9 +- activation/wire/wire_v2.go | 17 +-- 13 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 activation/malfeasance2_handler.go create mode 100644 activation/malfeasance2_handler_test.go create mode 100644 activation/malfeasance2_publisher_test.go diff --git a/activation/handler.go b/activation/handler.go index 1dd084c59a..7ff770c255 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -146,7 +146,7 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, - malPublisher: &MalfeasancePublisher{}, + malPublisher: &ATXMalfeasancePublisher{}, }, } @@ -286,6 +286,7 @@ func (h *Handler) handleAtx( case *wire.ActivationTxV1: return h.v1.processATX(ctx, peer, atx, receivedTime) case *wire.ActivationTxV2: + // TODO(mafa): change function signature to not return proofs any more return (*mwire.MalfeasanceProof)(nil), h.v2.processATX(ctx, peer, atx, receivedTime) default: panic("unreachable") diff --git a/activation/handler_test.go b/activation/handler_test.go index 7b944e6299..e136d95396 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -126,7 +126,7 @@ type handlerMocks struct { mValidator *MocknipostValidator mbeacon *MockatxReceiver mtortoise *mocks.MockTortoise - mMalPublish *MockmalfeasancePublisher + mMalPublish *MockatxMalfeasancePublisher } type testHandler struct { @@ -190,7 +190,7 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { mValidator: NewMocknipostValidator(ctrl), mbeacon: NewMockatxReceiver(ctrl), mtortoise: mocks.NewMockTortoise(ctrl), - mMalPublish: NewMockmalfeasancePublisher(ctrl), + mMalPublish: NewMockatxmalfeasancePublisher(ctrl), } } diff --git a/activation/handler_v2.go b/activation/handler_v2.go index 7930ecbe91..3bebf1b6c6 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -68,7 +68,7 @@ type HandlerV2 struct { tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher - malPublisher malfeasancePublisher + malPublisher atxMalfeasancePublisher } func (h *HandlerV2) processATX( @@ -528,7 +528,7 @@ func (h *HandlerV2) syntacticallyValidateDeps( return nil, fmt.Errorf("fetching previous atx: %w", err) } if prevAtx.PublishEpoch >= atx.PublishEpoch { - err := fmt.Errorf("previous atx is too new (%d >= %d) (%s) ", prevAtx.PublishEpoch, atx.PublishEpoch, prev) + err := fmt.Errorf("previous atx (%s) is too new (%d >= %d)", prev, prevAtx.PublishEpoch, atx.PublishEpoch) return nil, err } previousAtxs[i] = prevAtx diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index c2299164ed..c1e0e527e0 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -3,6 +3,7 @@ package activation import ( "context" "errors" + "fmt" "math" "slices" "testing" @@ -17,7 +18,6 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/atxsdata" - "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/fetch" @@ -1511,7 +1511,7 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atx.Sign(sig) _, err := atxHandler.syntacticallyValidateDeps(context.Background(), atx) - require.ErrorContains(t, err, "previous atx is too new") + require.ErrorContains(t, err, fmt.Sprintf("previous atx (%s) is too new", prev.ID())) }) t.Run("previous ATX by different smesher", func(t *testing.T) { atxHandler := newV2TestHandler(t, golden) @@ -1721,8 +1721,6 @@ func Test_Marriages(t *testing.T) { nId, err := malProof.Valid(atxHandler.edVerifier) require.NoError(t, err) require.Equal(t, sig.NodeID(), nId) - b := codec.MustEncode(malProof) - _ = b return nil }) err = atxHandler.processATX(context.Background(), "", atx2, time.Now()) diff --git a/activation/interface.go b/activation/interface.go index 04b2d9e0c3..9a42e837c1 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -92,7 +92,7 @@ type syncer interface { RegisterForATXSynced() <-chan struct{} } -// malfeasancePublisher is an interface for publishing malfeasance proofs. +// atxMalfeasancePublisher is an interface for publishing malfeasance proofs. // This interface is used to publish proofs in V2. // // The provider of that interface ensures that only valid proofs are published (invalid ones return an error). @@ -100,10 +100,14 @@ type syncer interface { // // Additionally the publisher will only gossip proofs when the node is in sync, otherwise it will only store them // and mark the associated identity as malfeasant. -type malfeasancePublisher interface { +type atxMalfeasancePublisher interface { Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error } +type malfeasancePublisher interface { + Publish(ctx context.Context, id types.NodeID, proof []byte) error +} + type atxProvider interface { GetAtx(id types.ATXID) (*types.ActivationTx, error) } diff --git a/activation/malfeasance2_handler.go b/activation/malfeasance2_handler.go new file mode 100644 index 0000000000..ef28902566 --- /dev/null +++ b/activation/malfeasance2_handler.go @@ -0,0 +1,121 @@ +package activation + +import ( + "context" + "fmt" + + "github.com/spacemeshos/go-spacemesh/activation/wire" + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/system" +) + +type MalfeasanceHandlerV2 struct { + syncer syncer + clock layerClock + publisher malfeasancePublisher + cdb *datastore.CachedDB + tortoise system.Tortoise + edVerifier *signing.EdVerifier +} + +func NewMalfeasanceHandlerV2( + syncer syncer, + layerClock layerClock, + malPublisher malfeasancePublisher, + cdb *datastore.CachedDB, + tortoise system.Tortoise, + edVerifier *signing.EdVerifier, +) *MalfeasanceHandlerV2 { + return &MalfeasanceHandlerV2{ + syncer: syncer, + clock: layerClock, + publisher: malPublisher, // TODO(mafa): implement malfeasancePublisher in `malfeasance` package + cdb: cdb, + tortoise: tortoise, + edVerifier: edVerifier, + } +} + +// TODO(mafa): call this validate in the handler for publish/gossip. +// TODO(mafa): extend this validate to return nil if `peer` == self. +func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) ([]types.NodeID, error) { + var decoded wire.ATXProof + if err := codec.Decode(data, &decoded); err != nil { + return nil, fmt.Errorf("decoding ATX malfeasance proof: %w", err) + } + + var proof wire.Proof + switch decoded.ProofType { + case wire.DoubleMarry: + p := &wire.ProofDoubleMarry{} + if err := codec.Decode(decoded.Proof, p); err != nil { + return nil, fmt.Errorf("decoding ATX double marry proof: %w", err) + } + proof = p + case wire.InvalidPost: + // TODO(mafa): implement this + default: + return nil, fmt.Errorf("unknown ATX malfeasance proof type: %d", decoded.ProofType) + } + + id, err := proof.Valid(mh.edVerifier) + if err != nil { + return nil, fmt.Errorf("validating ATX malfeasance proof: %w", err) + } + + // TODO(mafa): do this in the general handler + // validIDs := make([]types.NodeID, 0, len(decoded.Certificates)+1) + // validIDs = append(validIDs, id) // id has already been proven to be malfeasant + + // // check certificates provided with the proof + // // TODO(mafa): only works if the main identity becomes malfeasant - try different approach with merkle proofs + // for _, cert := range decoded.Certificates { + // if id != cert.Target { + // continue + // } + // if !mh.edVerifier.Verify(signing.MARRIAGE, cert.Target, cert.ID.Bytes(), cert.Signature) { + // continue + // } + // validIDs = append(validIDs, cert.ID) + // } + // return validIDs, nil + return []types.NodeID{id}, nil +} + +// TODO(mafa): this roughly how the general publisher looks like +// +// func Publish(ctx context.Context, smesherID types.NodeID, data []byte) error { +// // Combine IDs from the present equivocation set for atx.SmesherID and IDs in atx.Marriages. +// set, err := identities.EquivocationSet(mh.cdb, nodeID) +// if err != nil { +// return fmt.Errorf("getting equivocation set: %w", err) +// } +// for _, id := range set { +// if err := identities.SetMalicious(mh.cdb, id, encoded, time.Now()); err != nil { +// return fmt.Errorf("adding malfeasance proof: %w", err) +// } + +// mh.cdb.CacheMalfeasanceProof(id, proof) +// mh.tortoise.OnMalfeasance(id) +// } + +// if !mh.syncer.ListenToATXGossip() { +// // we are not gossiping proofs when we are not listening to ATX gossip +// return nil +// } + +// gossip := mwire.MalfeasanceProofV2{ +// Layer: mh.clock.CurrentLayer(), +// ProofType: mwire.InvalidActivation, +// Proof: data, +// } + +// if err := mh.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { +// mh.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) +// return fmt.Errorf("broadcast atx malfeasance proof: %w", err) +// } +// return nil +// } diff --git a/activation/malfeasance2_handler_test.go b/activation/malfeasance2_handler_test.go new file mode 100644 index 0000000000..385374da3c --- /dev/null +++ b/activation/malfeasance2_handler_test.go @@ -0,0 +1,3 @@ +package activation + +// TODO(mafa): implement me diff --git a/activation/malfeasance2_publisher.go b/activation/malfeasance2_publisher.go index ff44452b35..b71d18a289 100644 --- a/activation/malfeasance2_publisher.go +++ b/activation/malfeasance2_publisher.go @@ -7,10 +7,10 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" ) -// MalfeasancePublisher is the publisher for ATX proofs. -type MalfeasancePublisher struct{} +// ATXMalfeasancePublisher is the publisher for ATX proofs. +type ATXMalfeasancePublisher struct{} -func (p *MalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { +func (p *ATXMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { // TODO(mafa): implement me return nil } diff --git a/activation/malfeasance2_publisher_test.go b/activation/malfeasance2_publisher_test.go new file mode 100644 index 0000000000..385374da3c --- /dev/null +++ b/activation/malfeasance2_publisher_test.go @@ -0,0 +1,3 @@ +package activation + +// TODO(mafa): implement me diff --git a/activation/mocks.go b/activation/mocks.go index 7809780388..5379591c2d 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1085,31 +1085,31 @@ func (c *MocksyncerRegisterForATXSyncedCall) DoAndReturn(f func() <-chan struct{ return c } -// MockmalfeasancePublisher is a mock of malfeasancePublisher interface. -type MockmalfeasancePublisher struct { +// MockatxMalfeasancePublisher is a mock of malfeasancePublisher interface. +type MockatxMalfeasancePublisher struct { ctrl *gomock.Controller recorder *MockmalfeasancePublisherMockRecorder } // MockmalfeasancePublisherMockRecorder is the mock recorder for MockmalfeasancePublisher. type MockmalfeasancePublisherMockRecorder struct { - mock *MockmalfeasancePublisher + mock *MockatxMalfeasancePublisher } -// NewMockmalfeasancePublisher creates a new mock instance. -func NewMockmalfeasancePublisher(ctrl *gomock.Controller) *MockmalfeasancePublisher { - mock := &MockmalfeasancePublisher{ctrl: ctrl} +// NewMockatxmalfeasancePublisher creates a new mock instance. +func NewMockatxmalfeasancePublisher(ctrl *gomock.Controller) *MockatxMalfeasancePublisher { + mock := &MockatxMalfeasancePublisher{ctrl: ctrl} mock.recorder = &MockmalfeasancePublisherMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockmalfeasancePublisher) EXPECT() *MockmalfeasancePublisherMockRecorder { +func (m *MockatxMalfeasancePublisher) EXPECT() *MockmalfeasancePublisherMockRecorder { return m.recorder } // Publish mocks base method. -func (m *MockmalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { +func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Publish", ctx, id, proof) ret0, _ := ret[0].(error) @@ -1119,7 +1119,7 @@ func (m *MockmalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, // Publish indicates an expected call of Publish. func (mr *MockmalfeasancePublisherMockRecorder) Publish(ctx, id, proof any) *MockmalfeasancePublisherPublishCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockmalfeasancePublisher)(nil).Publish), ctx, id, proof) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, id, proof) return &MockmalfeasancePublisherPublishCall{Call: call} } diff --git a/activation/post_verifier_test.go b/activation/post_verifier_test.go index c02b329387..d5bf603d5c 100644 --- a/activation/post_verifier_test.go +++ b/activation/post_verifier_test.go @@ -107,9 +107,7 @@ func TestPostVerifierPrioritization(t *testing.T) { verifier := NewMockPostVerifier(gomock.NewController(t)) v := newOffloadingPostVerifier(verifier, 2, zaptest.NewLogger(t), nodeID) - verifier.EXPECT(). - Verify(gomock.Any(), gomock.Any(), &shared.ProofMetadata{NodeId: nodeID.Bytes()}, gomock.Any()). - Return(nil) + verifier.EXPECT().Verify(gomock.Any(), gomock.Any(), &shared.ProofMetadata{NodeId: nodeID.Bytes()}, gomock.Any()) err := v.Verify(context.Background(), &shared.Proof{}, &shared.ProofMetadata{NodeId: nodeID.Bytes()}) require.NoError(t, err) diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index c857dd075b..980c6e27d9 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -33,11 +33,10 @@ const ( LegacyInvalidPost ProofType = 0x01 LegacyInvalidPrevATX ProofType = 0x02 - DoublePublish ProofType = 0x10 - DoubleMarry ProofType = 0x11 - DoubleMerge ProofType = 0x12 - InvalidPost ProofType = 0x13 - InvalidPrevious ProofType = 0x14 + DoubleMarry ProofType = 0x10 + DoubleMerge ProofType = 0x11 + InvalidPost ProofType = 0x12 + InvalidPrevious ProofType = 0x13 ) // ProofVersion is an identifier for the version of the proof that is encoded in the ATXProof. diff --git a/activation/wire/wire_v2.go b/activation/wire/wire_v2.go index 5990589d30..b3a5b11602 100644 --- a/activation/wire/wire_v2.go +++ b/activation/wire/wire_v2.go @@ -199,6 +199,9 @@ type MarriageCertificate struct { // An ATX of the ID that marries. It proves that the ID exists. // Note: the reference ATX does not need to be from the previous epoch. // It only needs to prove the existence of the ID. + // + // In the case of a self signed certificate that is included in the Marriage ATX by the Smesher signing the ATX, + // this can be `types.EmptyATXID`. ReferenceAtx types.ATXID // Signature over the other ID that this ID marries with // If Alice marries Bob, then Alice signs Bob's ID @@ -229,7 +232,7 @@ type SubPostV2 struct { // Can be used to extract the nodeID and verify if it is married with the smesher of the ATX. // Must be 0 for non-merged ATXs. MarriageIndex uint32 - PrevATXIndex uint32 // Index of the previous ATX in the `InnerActivationTxV2.PreviousATXs` slice + PrevATXIndex uint32 // Index of the previous ATX in the `ActivationTxV2.PreviousATXs` slice // Index of the leaf for this ID's challenge in the poet membership tree. // IDs might shared the same index if their nipost challenges are equal. // This happens when the IDs are continuously merged (they share the previous ATX). @@ -245,9 +248,9 @@ func (sp *SubPostV2) Root(prevATXs []types.ATXID) []byte { if err != nil { panic(err) } - marriageIndex := make([]byte, 4) - binary.LittleEndian.PutUint32(marriageIndex, sp.MarriageIndex) - tree.AddLeaf(marriageIndex) + var marriageIndex types.Hash32 + binary.LittleEndian.PutUint32(marriageIndex[:], sp.MarriageIndex) + tree.AddLeaf(marriageIndex.Bytes()) if int(sp.PrevATXIndex) >= len(prevATXs) { return nil // invalid index, root cannot be generated @@ -260,9 +263,9 @@ func (sp *SubPostV2) Root(prevATXs []types.ATXID) []byte { tree.AddLeaf(sp.Post.Root()) - numUnits := make([]byte, 4) - binary.LittleEndian.PutUint32(numUnits, sp.NumUnits) - tree.AddLeaf(numUnits) + var numUnits types.Hash32 + binary.LittleEndian.PutUint32(numUnits[:], sp.NumUnits) + tree.AddLeaf(numUnits.Bytes()) return tree.Root() } From 421c2ad7fc4373ed88ad3f347cb399b61eca6445 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:39:26 +0000 Subject: [PATCH 04/66] Add malfeasance publisher --- activation/handler_v1.go | 7 +-- activation/malfeasance2_publisher.go | 12 +++- malfeasance2/publisher.go | 83 ++++++++++++++++++++++++++++ malfeasance2/publisher_test.go | 3 + node/node.go | 81 ++++++++++++++++----------- 5 files changed, 147 insertions(+), 39 deletions(-) create mode 100644 malfeasance2/publisher.go create mode 100644 malfeasance2/publisher_test.go diff --git a/activation/handler_v1.go b/activation/handler_v1.go index faa5e21f8f..66f1040222 100644 --- a/activation/handler_v1.go +++ b/activation/handler_v1.go @@ -57,12 +57,7 @@ type nipostValidatorV1 interface { opts ...validatorOption, ) error - VRFNonce( - nodeId types.NodeID, - commitmentAtxId types.ATXID, - vrfNonce, labelsPerUnit uint64, - numUnits uint32, - ) error + VRFNonce(nodeId types.NodeID, commitmentAtxId types.ATXID, vrfNonce, labelsPerUnit uint64, numUnits uint32) error PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubepoch types.EpochID) error } diff --git a/activation/malfeasance2_publisher.go b/activation/malfeasance2_publisher.go index b71d18a289..7f67c944b1 100644 --- a/activation/malfeasance2_publisher.go +++ b/activation/malfeasance2_publisher.go @@ -8,7 +8,17 @@ import ( ) // ATXMalfeasancePublisher is the publisher for ATX proofs. -type ATXMalfeasancePublisher struct{} +type ATXMalfeasancePublisher struct { + malPublisher malfeasancePublisher +} + +func NewATXMalfeasancePublisher( + malPublisher malfeasancePublisher, +) *ATXMalfeasancePublisher { + return &ATXMalfeasancePublisher{ + malPublisher: malPublisher, + } +} func (p *ATXMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { // TODO(mafa): implement me diff --git a/malfeasance2/publisher.go b/malfeasance2/publisher.go new file mode 100644 index 0000000000..c2b36c081b --- /dev/null +++ b/malfeasance2/publisher.go @@ -0,0 +1,83 @@ +package malfeasance2 + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/sql/identities" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" +) + +type Publisher struct { + logger *zap.Logger + cdb *datastore.CachedDB + tortoise tortoise + publisher pubsub.Publisher +} + +func NewPublisher( + logger *zap.Logger, + cdb *datastore.CachedDB, + tortoise tortoise, + publisher pubsub.Publisher, +) *Publisher { + return &Publisher{ + logger: logger, + cdb: cdb, + tortoise: tortoise, + publisher: publisher, + } +} + +func (p *Publisher) PublishV2ATXProof( + ctx context.Context, + smesherIDs []types.NodeID, + domain ProofDomain, + proof []byte, +) error { + // Combine IDs from the present equivocation set for atx.SmesherID and IDs in atx.Marriages. + allMalicious := make(map[types.NodeID]struct{}) + + for _, id := range smesherIDs { + set, err := identities.EquivocationSet(p.cdb, id) + if err != nil { + return fmt.Errorf("getting equivocation set: %w", err) + } + for _, id := range set { + allMalicious[id] = struct{}{} + } + for _, id := range smesherIDs { + allMalicious[id] = struct{}{} + } + } + + for id := range allMalicious { + if err := malfeasance.Add(p.cdb, id, byte(domain), proof, time.Now()); err != nil { + return fmt.Errorf("setting malfeasance proof: %w", err) + } + // TODO(mafa): cache proof + // p.cdb.CacheMalfeasanceProof(id, proof) + p.tortoise.OnMalfeasance(id) + } + + // TODO(mafa): check if we are in sync before publishing, if not just return + + malfeasanceProof := &MalfeasanceProof{ + Version: 0, + Domain: domain, + Proof: proof, + } + if err := p.publisher.Publish(ctx, pubsub.MalfeasanceProof2, codec.MustEncode(malfeasanceProof)); err != nil { + p.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) + return fmt.Errorf("broadcast atx malfeasance proof: %w", err) + } + + return nil +} diff --git a/malfeasance2/publisher_test.go b/malfeasance2/publisher_test.go new file mode 100644 index 0000000000..a27b89ee5f --- /dev/null +++ b/malfeasance2/publisher_test.go @@ -0,0 +1,3 @@ +package malfeasance2 + +// TODO(mafa): implement me diff --git a/node/node.go b/node/node.go index 8f6227d0c3..d993c46f83 100644 --- a/node/node.go +++ b/node/node.go @@ -727,7 +727,44 @@ func (app *App) initServices(ctx context.Context) error { return nil }) - fetcherWrapped := &layerFetcher{} + proposalsStore := store.New( + store.WithEvictedLayer(app.clock.CurrentLayer()), + store.WithLogger(app.addLogger(ProposalStoreLogger, lg).Zap()), + store.WithCapacity(app.Config.Tortoise.Zdist+1), + ) + + flog := app.addLogger(Fetcher, lg) + fetcher, err := fetch.NewFetch(app.cachedDB, proposalsStore, app.host, + fetch.WithContext(ctx), + fetch.WithConfig(app.Config.FETCH), + fetch.WithLogger(flog.Zap()), + ) + if err != nil { + return fmt.Errorf("creating fetcher: %w", err) + } + app.eg.Go(func() error { + return blockssync.Sync(ctx, flog.Zap(), msh.MissingBlocks(), fetcher) + }) + + malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() + // malfeasancePublisher := malfeasance.NewPublisher( + // malfeasanceLogger, + // app.cachedDB, + // trtl, + // app.host, + // ) + + // malfeasancePublisher2 := malfeasance2.NewPublisher( + // malfeasanceLogger, + // app.cachedDB, + // trtl, + // app.host, + // ) + + // atxMalPublisher := activation.NewATXMalfeasancePublisher( + // malfeasancePublisher, + // malfeasancePublisher2, + // ) atxHandler := activation.NewHandler( app.host.ID(), @@ -736,10 +773,11 @@ func (app *App) initServices(ctx context.Context) error { app.edVerifier, app.clock, app.host, - fetcherWrapped, + fetcher, goldenATXID, validator, beaconProtocol, + // atxMalPublisher, trtl, app.addLogger(ATXHandlerLogger, lg).Zap(), activation.WithTickSize(app.Config.TickSize), @@ -750,7 +788,6 @@ func (app *App) initServices(ctx context.Context) error { } // we can't have an epoch offset which is greater/equal than the number of layers in an epoch - if app.Config.HareEligibility.ConfidenceParam >= app.Config.BaseConfig.LayersPerEpoch { return fmt.Errorf( "confidence param should be smaller than layers per epoch. eligibility-confidence-param: %d. "+ @@ -760,8 +797,13 @@ func (app *App) initServices(ctx context.Context) error { ) } - blockHandler := blocks.NewHandler(fetcherWrapped, app.db, trtl, msh, - blocks.WithLogger(app.addLogger(BlockHandlerLogger, lg).Zap())) + blockHandler := blocks.NewHandler( + fetcher, + app.db, + trtl, + msh, + blocks.WithLogger(app.addLogger(BlockHandlerLogger, lg).Zap()), + ) app.txHandler = txs.NewTxHandler( app.conState, @@ -811,26 +853,6 @@ func (app *App) initServices(ctx context.Context) error { app.certifier.Register(sig) } - proposalsStore := store.New( - store.WithEvictedLayer(app.clock.CurrentLayer()), - store.WithLogger(app.addLogger(ProposalStoreLogger, lg).Zap()), - store.WithCapacity(app.Config.Tortoise.Zdist+1), - ) - - flog := app.addLogger(Fetcher, lg) - fetcher, err := fetch.NewFetch(app.cachedDB, proposalsStore, app.host, - fetch.WithContext(ctx), - fetch.WithConfig(app.Config.FETCH), - fetch.WithLogger(flog.Zap()), - ) - if err != nil { - return fmt.Errorf("create fetcher: %w", err) - } - fetcherWrapped.Fetcher = fetcher - app.eg.Go(func() error { - return blockssync.Sync(ctx, flog.Zap(), msh.MissingBlocks(), fetcher) - }) - patrol := layerpatrol.New() syncerConf := app.Config.Sync syncerConf.HareDelayLayers = app.Config.Tortoise.Zdist @@ -947,7 +969,7 @@ func (app *App) initServices(ctx context.Context) error { propHare, app.edVerifier, app.host, - fetcherWrapped, + fetcher, beaconProtocol, msh, trtl, @@ -970,7 +992,7 @@ func (app *App) initServices(ctx context.Context) error { proposalsStore, executor, msh, - fetcherWrapped, + fetcher, app.certifier, patrol, blocks.WithConfig(blocks.Config{ @@ -1112,7 +1134,6 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("init post service: %w", err) } - malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() activationMH := activation.NewMalfeasanceHandler( app.cachedDB, malfeasanceLogger, @@ -2266,10 +2287,6 @@ func (app *App) Host() *p2p.Host { return app.host } -type layerFetcher struct { - system.Fetcher -} - func decodeLoggerLevel(cfg *config.Config, name string) (zap.AtomicLevel, error) { lvl := zap.NewAtomicLevel() loggers := map[string]string{} From a7a88de1fe5b931e407831f4d7e9d6e753542ca3 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:23:53 +0000 Subject: [PATCH 05/66] make generate --- activation/handler_test.go | 2 +- activation/mocks.go | 81 +++++++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/activation/handler_test.go b/activation/handler_test.go index e136d95396..32b3c8a491 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -190,7 +190,7 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { mValidator: NewMocknipostValidator(ctrl), mbeacon: NewMockatxReceiver(ctrl), mtortoise: mocks.NewMockTortoise(ctrl), - mMalPublish: NewMockatxmalfeasancePublisher(ctrl), + mMalPublish: NewMockatxMalfeasancePublisher(ctrl), } } diff --git a/activation/mocks.go b/activation/mocks.go index 5379591c2d..0acf353a25 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1085,31 +1085,92 @@ func (c *MocksyncerRegisterForATXSyncedCall) DoAndReturn(f func() <-chan struct{ return c } -// MockatxMalfeasancePublisher is a mock of malfeasancePublisher interface. +// MockatxMalfeasancePublisher is a mock of atxMalfeasancePublisher interface. type MockatxMalfeasancePublisher struct { + ctrl *gomock.Controller + recorder *MockatxMalfeasancePublisherMockRecorder +} + +// MockatxMalfeasancePublisherMockRecorder is the mock recorder for MockatxMalfeasancePublisher. +type MockatxMalfeasancePublisherMockRecorder struct { + mock *MockatxMalfeasancePublisher +} + +// NewMockatxMalfeasancePublisher creates a new mock instance. +func NewMockatxMalfeasancePublisher(ctrl *gomock.Controller) *MockatxMalfeasancePublisher { + mock := &MockatxMalfeasancePublisher{ctrl: ctrl} + mock.recorder = &MockatxMalfeasancePublisherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockatxMalfeasancePublisher) EXPECT() *MockatxMalfeasancePublisherMockRecorder { + return m.recorder +} + +// Publish mocks base method. +func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Publish", ctx, id, proof) + ret0, _ := ret[0].(error) + return ret0 +} + +// Publish indicates an expected call of Publish. +func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, id, proof any) *MockatxMalfeasancePublisherPublishCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, id, proof) + return &MockatxMalfeasancePublisherPublishCall{Call: call} +} + +// MockatxMalfeasancePublisherPublishCall wrap *gomock.Call +type MockatxMalfeasancePublisherPublishCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockatxMalfeasancePublisherPublishCall) Return(arg0 error) *MockatxMalfeasancePublisherPublishCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockatxMalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockatxMalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MockmalfeasancePublisher is a mock of malfeasancePublisher interface. +type MockmalfeasancePublisher struct { ctrl *gomock.Controller recorder *MockmalfeasancePublisherMockRecorder } // MockmalfeasancePublisherMockRecorder is the mock recorder for MockmalfeasancePublisher. type MockmalfeasancePublisherMockRecorder struct { - mock *MockatxMalfeasancePublisher + mock *MockmalfeasancePublisher } -// NewMockatxmalfeasancePublisher creates a new mock instance. -func NewMockatxmalfeasancePublisher(ctrl *gomock.Controller) *MockatxMalfeasancePublisher { - mock := &MockatxMalfeasancePublisher{ctrl: ctrl} +// NewMockmalfeasancePublisher creates a new mock instance. +func NewMockmalfeasancePublisher(ctrl *gomock.Controller) *MockmalfeasancePublisher { + mock := &MockmalfeasancePublisher{ctrl: ctrl} mock.recorder = &MockmalfeasancePublisherMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockatxMalfeasancePublisher) EXPECT() *MockmalfeasancePublisherMockRecorder { +func (m *MockmalfeasancePublisher) EXPECT() *MockmalfeasancePublisherMockRecorder { return m.recorder } // Publish mocks base method. -func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { +func (m *MockmalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Publish", ctx, id, proof) ret0, _ := ret[0].(error) @@ -1119,7 +1180,7 @@ func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, id types.Node // Publish indicates an expected call of Publish. func (mr *MockmalfeasancePublisherMockRecorder) Publish(ctx, id, proof any) *MockmalfeasancePublisherPublishCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, id, proof) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockmalfeasancePublisher)(nil).Publish), ctx, id, proof) return &MockmalfeasancePublisherPublishCall{Call: call} } @@ -1135,13 +1196,13 @@ func (c *MockmalfeasancePublisherPublishCall) Return(arg0 error) *Mockmalfeasanc } // Do rewrite *gomock.Call.Do -func (c *MockmalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, wire.Proof) error) *MockmalfeasancePublisherPublishCall { +func (c *MockmalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, []byte) error) *MockmalfeasancePublisherPublishCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockmalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, wire.Proof) error) *MockmalfeasancePublisherPublishCall { +func (c *MockmalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, []byte) error) *MockmalfeasancePublisherPublishCall { c.Call = c.Call.DoAndReturn(f) return c } From 52ab7f93b99eb7982db620849e223ab962fa4544 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:00:10 +0000 Subject: [PATCH 06/66] Cleanup --- activation/handler_v2_test.go | 14 +++++++------- activation/wire/malfeasance_double_marry.go | 4 ++-- activation/wire/wire_v2.go | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index c1e0e527e0..d99b4e4c9c 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -1897,7 +1897,7 @@ func Test_CalculatingUnits(t *testing.T) { func TestContextual_PreviousATX(t *testing.T) { golden := types.RandomATXID() - atxHndlr := newV2TestHandler(t, golden) + atxHdlr := newV2TestHandler(t, golden) var ( signers []*signing.EdSigner eqSet []types.NodeID @@ -1909,13 +1909,13 @@ func TestContextual_PreviousATX(t *testing.T) { eqSet = append(eqSet, sig.NodeID()) } - mATX, otherAtxs := marryIDs(t, atxHndlr, signers, golden) + mATX, otherAtxs := marryIDs(t, atxHdlr, signers, golden) // signer 1 creates a solo ATX soloAtx := newSoloATXv2(t, mATX.PublishEpoch+1, otherAtxs[0].ID(), mATX.ID()) soloAtx.Sign(signers[1]) - atxHndlr.expectAtxV2(soloAtx) - err := atxHndlr.processATX(context.Background(), "", soloAtx, time.Now()) + atxHdlr.expectAtxV2(soloAtx) + err := atxHdlr.processATX(context.Background(), "", soloAtx, time.Now()) require.NoError(t, err) // create a MergedATX for all IDs @@ -1933,9 +1933,9 @@ func TestContextual_PreviousATX(t *testing.T) { merged.MarriageATX = &matxID merged.Sign(signers[0]) - atxHndlr.expectMergedAtxV2(merged, eqSet, []uint64{100}) - atxHndlr.mMalPublish.EXPECT().Publish(gomock.Any(), signers[1].NodeID(), gomock.Any()) - err = atxHndlr.processATX(context.Background(), "", merged, time.Now()) + atxHdlr.expectMergedAtxV2(merged, eqSet, []uint64{100}) + atxHdlr.mMalPublish.EXPECT().Publish(gomock.Any(), signers[1].NodeID(), gomock.Any()) + err = atxHdlr.processATX(context.Background(), "", merged, time.Now()) require.NoError(t, err) } diff --git a/activation/wire/malfeasance_double_marry.go b/activation/wire/malfeasance_double_marry.go index ea946b29ea..f0fb9d3628 100644 --- a/activation/wire/malfeasance_double_marry.go +++ b/activation/wire/malfeasance_double_marry.go @@ -58,7 +58,7 @@ func NewDoubleMarryProof(db sql.Executor, atx1, atx2 *ActivationTxV2, nodeID typ func createMarryProof(db sql.Executor, atx *ActivationTxV2, nodeID types.NodeID) (MarryProof, error) { marriageProof, err := marriageProof(atx) if err != nil { - return MarryProof{}, fmt.Errorf("failed to create proof for ATX 1: %w", err) + return MarryProof{}, fmt.Errorf("create proof for ATX: %w", err) } marriageIndex := slices.IndexFunc(atx.Marriages, func(cert MarriageCertificate) bool { @@ -77,7 +77,7 @@ func createMarryProof(db sql.Executor, atx *ActivationTxV2, nodeID types.NodeID) } certProof, err := certificateProof(atx.Marriages, uint64(marriageIndex)) if err != nil { - return MarryProof{}, fmt.Errorf("failed to create certificate proof for ATX 1: %w", err) + return MarryProof{}, fmt.Errorf("create certificate proof for ATX: %w", err) } proof := MarryProof{ diff --git a/activation/wire/wire_v2.go b/activation/wire/wire_v2.go index b3a5b11602..72c64d00f9 100644 --- a/activation/wire/wire_v2.go +++ b/activation/wire/wire_v2.go @@ -156,14 +156,14 @@ func (atx *ActivationTxV2) TotalNumUnits() uint32 { type MarriageCertificates []MarriageCertificate func (mcs MarriageCertificates) Root() []byte { - marriagesTree, err := merkle.NewTreeBuilder(). + tree, err := merkle.NewTreeBuilder(). WithHashFunc(atxTreeHash). Build() if err != nil { panic(err) } - mcs.merkleTree(marriagesTree) - return marriagesTree.Root() + mcs.merkleTree(tree) + return tree.Root() } func (mcs MarriageCertificates) merkleTree(tree *merkle.Tree) { From 24e7c4da1b88a58a15232580d42529fb4b4765de Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:48:56 +0000 Subject: [PATCH 07/66] Make generate --- sql/statesql/schema/schema.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/statesql/schema/schema.sql b/sql/statesql/schema/schema.sql index fdfec0f6f0..2756d7469c 100755 --- a/sql/statesql/schema/schema.sql +++ b/sql/statesql/schema/schema.sql @@ -125,9 +125,8 @@ CREATE TABLE posts ( prev_atxid CHAR(32), prev_atx_index INT, units INT NOT NULL -, publish_epoch UNSIGNED INT); +); CREATE UNIQUE INDEX posts_by_atxid_by_pubkey ON posts (atxid, pubkey); -CREATE INDEX posts_by_atxid_by_pubkey_epoch ON posts (pubkey, publish_epoch); CREATE INDEX posts_by_atxid_by_pubkey_prev_atxid ON posts (atxid, pubkey, prev_atxid); CREATE TABLE proposal_transactions ( From 9dc9f370b939c189fe08a777fca390ea26c99dce Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:57:58 +0000 Subject: [PATCH 08/66] Remove unneeded foreign key statement --- sql/database.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sql/database.go b/sql/database.go index 8f721a82a5..17e41c399f 100644 --- a/sql/database.go +++ b/sql/database.go @@ -311,10 +311,7 @@ func prepareDB(logger *zap.Logger, db *sqliteDatabase, config *conf, freshDB boo if config.enableLatency { db.latency = newQueryLatency() } - if _, err := db.Exec("PRAGMA foreign_keys = ON;", nil, nil); err != nil { - db.Close() - return nil, fmt.Errorf("enable foreign keys: %w", err) - } + if config.temp { // Temporary database is used for migration and is deleted if migrations // fail, so we make it faster by disabling journaling and synchronous From e3b99bedba75cc62c36d3cf7f7507ea3ff2af3cb Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:31:57 +0000 Subject: [PATCH 09/66] Add code for post validation to activation malfeasance handler --- activation/malfeasance2_handler.go | 15 +++++++++++++++ activation/validation.go | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/activation/malfeasance2_handler.go b/activation/malfeasance2_handler.go index ef28902566..cdba69b600 100644 --- a/activation/malfeasance2_handler.go +++ b/activation/malfeasance2_handler.go @@ -19,6 +19,7 @@ type MalfeasanceHandlerV2 struct { cdb *datastore.CachedDB tortoise system.Tortoise edVerifier *signing.EdVerifier + validator nipostValidatorV2 } func NewMalfeasanceHandlerV2( @@ -28,6 +29,7 @@ func NewMalfeasanceHandlerV2( cdb *datastore.CachedDB, tortoise system.Tortoise, edVerifier *signing.EdVerifier, + validator nipostValidatorV2, ) *MalfeasanceHandlerV2 { return &MalfeasanceHandlerV2{ syncer: syncer, @@ -36,9 +38,22 @@ func NewMalfeasanceHandlerV2( cdb: cdb, tortoise: tortoise, edVerifier: edVerifier, + validator: validator, } } +func (mh *MalfeasanceHandlerV2) PostV2Idx( + ctx context.Context, + nodeId types.NodeID, + commitmentAtxId types.ATXID, + post *types.Post, + challenge []byte, + numUnits uint32, + idx int, +) error { + return mh.validator.PostV2(ctx, nodeId, commitmentAtxId, post, challenge, numUnits, PostIndex(idx)) +} + // TODO(mafa): call this validate in the handler for publish/gossip. // TODO(mafa): extend this validate to return nil if `peer` == self. func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) ([]types.NodeID, error) { diff --git a/activation/validation.go b/activation/validation.go index d6d070d895..54b79fdbf2 100644 --- a/activation/validation.go +++ b/activation/validation.go @@ -41,6 +41,7 @@ func (e *ErrAtxNotFound) Is(target error) bool { } type validatorOptions struct { + postIdx *int postSubsetSeed []byte prioritized bool } @@ -53,6 +54,14 @@ func PostSubset(seed []byte) validatorOption { } } +// PostIndex configures the validator to validate only the POST index at the given `idx`. +func PostIndex(idx int) validatorOption { + return func(o *validatorOptions) { + o.postIdx = new(int) + *o.postIdx = idx + } +} + func PrioritizeCall() validatorOption { return func(o *validatorOptions) { o.prioritized = true @@ -204,6 +213,9 @@ func (v *Validator) Post( } verifyOpts := []verifying.OptionFunc{verifying.WithLabelScryptParams(v.scrypt)} + if options.postIdx != nil { + verifyOpts = append(verifyOpts, verifying.SelectedIndex(*options.postIdx)) + } if options.postSubsetSeed != nil { verifyOpts = append(verifyOpts, verifying.Subset(v.cfg.K3, options.postSubsetSeed)) } From bccceea3f01b92c160af09dc88bbc47d11d23cd4 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:24:38 +0000 Subject: [PATCH 10/66] Fix compiler complaints --- activation/malfeasance2_handler.go | 33 +++++++++++++----------------- activation/wire/malfeasance.go | 26 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/activation/malfeasance2_handler.go b/activation/malfeasance2_handler.go index cdba69b600..4a5cfaeb72 100644 --- a/activation/malfeasance2_handler.go +++ b/activation/malfeasance2_handler.go @@ -42,41 +42,36 @@ func NewMalfeasanceHandlerV2( } } -func (mh *MalfeasanceHandlerV2) PostV2Idx( +func (mh *MalfeasanceHandlerV2) PostIndex( ctx context.Context, - nodeId types.NodeID, - commitmentAtxId types.ATXID, + smesherID types.NodeID, + commitment types.ATXID, post *types.Post, challenge []byte, numUnits uint32, idx int, ) error { - return mh.validator.PostV2(ctx, nodeId, commitmentAtxId, post, challenge, numUnits, PostIndex(idx)) + return mh.validator.PostV2(ctx, smesherID, commitment, post, challenge, numUnits, PostIndex(idx)) +} + +func (mh *MalfeasanceHandlerV2) Signature(d signing.Domain, nodeID types.NodeID, m []byte, sig types.EdSignature) bool { + return mh.edVerifier.Verify(d, nodeID, m, sig) } // TODO(mafa): call this validate in the handler for publish/gossip. // TODO(mafa): extend this validate to return nil if `peer` == self. func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) ([]types.NodeID, error) { - var decoded wire.ATXProof - if err := codec.Decode(data, &decoded); err != nil { + var atxProof wire.ATXProof + if err := codec.Decode(data, &atxProof); err != nil { return nil, fmt.Errorf("decoding ATX malfeasance proof: %w", err) } - var proof wire.Proof - switch decoded.ProofType { - case wire.DoubleMarry: - p := &wire.ProofDoubleMarry{} - if err := codec.Decode(decoded.Proof, p); err != nil { - return nil, fmt.Errorf("decoding ATX double marry proof: %w", err) - } - proof = p - case wire.InvalidPost: - // TODO(mafa): implement this - default: - return nil, fmt.Errorf("unknown ATX malfeasance proof type: %d", decoded.ProofType) + proof, err := atxProof.Decode() + if err != nil { + return nil, fmt.Errorf("decoding ATX malfeasance proof: %w", err) } - id, err := proof.Valid(mh.edVerifier) + id, err := proof.Valid(ctx, mh) if err != nil { return nil, fmt.Errorf("validating ATX malfeasance proof: %w", err) } diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index 7e2aa97b98..6e044a6425 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -2,9 +2,12 @@ package wire import ( "context" + "errors" + "fmt" "github.com/spacemeshos/go-scale" + "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" ) @@ -84,6 +87,29 @@ type ATXProof struct { Proof []byte `scale:"max=1048576"` // max size of proof is 1MiB } +func (p *ATXProof) Decode() (Proof, error) { + switch p.ProofType { + case DoubleMarry: + rst := &ProofDoubleMarry{} + if err := codec.Decode(p.Proof, rst); err != nil { + return nil, fmt.Errorf("decoding ATX double marry proof: %w", err) + } + return rst, nil + case DoubleMerge: + return nil, errors.New("double merge proof is not supported") + case InvalidPost: + rst := &ProofInvalidPost{} + if err := codec.Decode(p.Proof, rst); err != nil { + return nil, fmt.Errorf("decoding ATX invalid post proof: %w", err) + } + return rst, nil + case InvalidPrevious: + return nil, errors.New("invalid previous proof is not supported") + default: + return nil, fmt.Errorf("unknown ATX malfeasance proof type: %d", p.ProofType) + } +} + // Proof is an interface for all types of proofs that can be provided in an ATXProof. // Generally the proof should be able to validate itself and be scale encoded. type Proof interface { From a4fef64e4b62f5f020604752ba0e9c7c0458a6ba Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:27:35 +0000 Subject: [PATCH 11/66] Fix missing decoder for malfeasance activation wire type --- activation/wire/malfeasance.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index 6e044a6425..abaafdd890 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -96,7 +96,11 @@ func (p *ATXProof) Decode() (Proof, error) { } return rst, nil case DoubleMerge: - return nil, errors.New("double merge proof is not supported") + rst := &ProofDoubleMerge{} + if err := codec.Decode(p.Proof, rst); err != nil { + return nil, fmt.Errorf("decoding ATX double merge proof: %w", err) + } + return rst, nil case InvalidPost: rst := &ProofInvalidPost{} if err := codec.Decode(p.Proof, rst); err != nil { From 8627bcbf99b18ac7417086fa31c3ce3640325883 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:05:15 +0000 Subject: [PATCH 12/66] Work in progress --- activation/handler_v2.go | 34 ++++++------ activation/handler_v2_test.go | 24 +++------ activation/interface.go | 17 +++--- activation/malfeasance2_publisher.go | 15 ++++-- activation/mocks.go | 36 ++++++------- activation/wire/malfeasance.go | 54 +++++++++---------- activation/wire/malfeasance_double_marry.go | 8 +++ activation/wire/malfeasance_double_merge.go | 8 +++ activation/wire/malfeasance_invalid_post.go | 8 +++ .../wire/malfeasance_invalid_prev_atx.go | 16 ++++++ 10 files changed, 130 insertions(+), 90 deletions(-) diff --git a/activation/handler_v2.go b/activation/handler_v2.go index 84378355bb..5f78a7c40d 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -495,8 +495,8 @@ func (n nipostSizes) sumUp() (units uint32, weight uint64, err error) { func (h *HandlerV2) verifyIncludedIDsUniqueness(atx *wire.ActivationTxV2) error { seen := make(map[uint32]struct{}) - for _, niposts := range atx.NIPosts { - for _, post := range niposts.Posts { + for _, niPosts := range atx.NIPosts { + for _, post := range niPosts.Posts { if _, ok := seen[post.MarriageIndex]; ok { return fmt.Errorf("ID present twice (duplicated marriage index): %d", post.MarriageIndex) } @@ -541,9 +541,9 @@ func (h *HandlerV2) syntacticallyValidateDeps( // validate previous ATXs nipostSizes := make(nipostSizes, len(atx.NIPosts)) - for i, niposts := range atx.NIPosts { + for i, niPosts := range atx.NIPosts { nipostSizes[i] = new(nipostSize) - for _, post := range niposts.Posts { + for _, post := range niPosts.Posts { if post.MarriageIndex >= uint32(len(equivocationSet)) { err := fmt.Errorf("marriage index out of bounds: %d > %d", post.MarriageIndex, len(equivocationSet)-1) return nil, err @@ -563,11 +563,11 @@ func (h *HandlerV2) syntacticallyValidateDeps( } // validate poet membership proofs - for i, niposts := range atx.NIPosts { + for i, niPosts := range atx.NIPosts { // verify PoET memberships in a single go indexedChallenges := make(map[uint64][]byte) - for _, post := range niposts.Posts { + for _, post := range niPosts.Posts { if _, ok := indexedChallenges[post.MembershipLeafIndex]; ok { continue } @@ -591,10 +591,10 @@ func (h *HandlerV2) syntacticallyValidateDeps( } membership := types.MultiMerkleProof{ - Nodes: niposts.Membership.Nodes, + Nodes: niPosts.Membership.Nodes, LeafIndices: leafIndices, } - leaves, err := h.nipostValidator.PoetMembership(ctx, &membership, niposts.Challenge, poetChallenges) + leaves, err := h.nipostValidator.PoetMembership(ctx, &membership, niPosts.Challenge, poetChallenges) if err != nil { return nil, fmt.Errorf("validating poet membership: %w", err) } @@ -606,7 +606,7 @@ func (h *HandlerV2) syntacticallyValidateDeps( return nil, err } - // validate all niposts + // validate all NIPoSTs if atx.Initial != nil { commitment := atx.Initial.CommitmentATX nipostIdx := 0 @@ -625,8 +625,8 @@ func (h *HandlerV2) syntacticallyValidateDeps( } var smesherCommitment *types.ATXID - for idx, niposts := range atx.NIPosts { - for _, post := range niposts.Posts { + for idx, niPosts := range atx.NIPosts { + for _, post := range niPosts.Posts { id := equivocationSet[post.MarriageIndex] commitment, err := atxs.CommitmentATX(h.cdb, id) if err != nil { @@ -635,7 +635,7 @@ func (h *HandlerV2) syntacticallyValidateDeps( if id == atx.SmesherID { smesherCommitment = &commitment } - if err := h.validatePost(ctx, id, atx, commitment, niposts.Challenge, post, idx); err != nil { + if err := h.validatePost(ctx, id, atx, commitment, niPosts.Challenge, post, idx); err != nil { return nil, err } result.ids[id] = idData{ @@ -721,7 +721,7 @@ func (h *HandlerV2) validatePost( if err != nil { return fmt.Errorf("creating invalid post proof: %w", err) } - if err := h.malPublisher.Publish(ctx, nodeID, proof); err != nil { + if err := h.malPublisher.Publish(ctx, proof); err != nil { return fmt.Errorf("publishing malfeasance proof for invalid post: %w", err) } return fmt.Errorf("invalid post for ID %s: %w", nodeID.ShortString(), errInvalid) @@ -802,7 +802,7 @@ func (h *HandlerV2) checkDoubleMarry(ctx context.Context, tx sql.Transaction, at if err != nil { return true, fmt.Errorf("creating double marry proof: %w", err) } - return true, h.malPublisher.Publish(ctx, m.id, proof) + return true, h.malPublisher.Publish(ctx, proof) } return false, nil } @@ -847,7 +847,7 @@ func (h *HandlerV2) checkDoubleMerge(ctx context.Context, tx sql.Transaction, at if err != nil { return true, fmt.Errorf("creating double merge proof: %w", err) } - return true, h.malPublisher.Publish(ctx, atx.SmesherID, proof) + return true, h.malPublisher.Publish(ctx, proof) } func (h *HandlerV2) checkPrevAtx(ctx context.Context, tx sql.Transaction, atx *activationTx) (bool, error) { @@ -905,7 +905,7 @@ func (h *HandlerV2) checkPrevAtx(ctx context.Context, tx sql.Transaction, atx *a if err != nil { return true, fmt.Errorf("creating invalid previous ATX proof: %w", err) } - return true, h.malPublisher.Publish(ctx, id, proof) + return true, h.malPublisher.Publish(ctx, proof) default: h.logger.Fatal("Failed to create invalid previous ATX proof: unknown ATX version", zap.Stringer("atx_id", collision), @@ -923,7 +923,7 @@ func (h *HandlerV2) checkPrevAtx(ctx context.Context, tx sql.Transaction, atx *a if err != nil { return true, fmt.Errorf("creating invalid previous ATX proof: %w", err) } - return true, h.malPublisher.Publish(ctx, id, proof) + return true, h.malPublisher.Publish(ctx, proof) } return false, nil } diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index 1f902eb17c..6b8a0bbd01 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -994,9 +994,8 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { atxHandler.expectMergedAtxV2(merged, equivocationSet, []uint64{100}) atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), - merged.SmesherID, gomock.AssignableToTypeOf(&wire.ProofDoubleMerge{}), - ).DoAndReturn(func(ctx context.Context, id types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofDoubleMerge) nId, err := malProof.Valid(context.Background(), verifier) require.NoError(t, err) @@ -1619,9 +1618,8 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), - sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPost{}), - ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPost) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -1714,9 +1712,8 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), - sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPost{}), - ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPost) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -1836,9 +1833,8 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), - sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPost{}), - ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPost) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -1968,9 +1964,8 @@ func Test_Marriages(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), - sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofDoubleMarry{}), - ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofDoubleMarry) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -2175,9 +2170,8 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.mMalPublish.EXPECT().Publish( gomock.Any(), - signers[1].NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPrevAtxV2{}), - ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPrevAtxV2) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -2256,9 +2250,8 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.mMalPublish.EXPECT().Publish( gomock.Any(), - sig1.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPrevAtxV1{}), - ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPrevAtxV1) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -2332,9 +2325,8 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.mMalPublish.EXPECT().Publish( gomock.Any(), - otherSig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPrevAtxV2{}), - ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPrevAtxV2) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) diff --git a/activation/interface.go b/activation/interface.go index 64ee1b90ad..72d9fe4713 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -92,20 +92,23 @@ type syncer interface { RegisterForATXSynced() <-chan struct{} } -// atxMalfeasancePublisher is an interface for publishing malfeasance proofs. -// This interface is used to publish proofs in V2. +// atxMalfeasancePublisher is an interface for publishing atx malfeasance proofs. +// +// It encapsulates a specific malfeasance proof into a generic ATX malfeasance proof and publishes it by calling +// the underlying malfeasancePublisher. +type atxMalfeasancePublisher interface { + Publish(ctx context.Context, proof wire.Proof) error +} + +// malfeasancePublisher is an interface for publishing malfeasance proofs. // // The provider of that interface ensures that only valid proofs are published (invalid ones return an error). // Proofs against an identity that is managed by the node will also return an error and will not be gossiped. // // Additionally the publisher will only gossip proofs when the node is in sync, otherwise it will only store them // and mark the associated identity as malfeasant. -type atxMalfeasancePublisher interface { - Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error -} - type malfeasancePublisher interface { - Publish(ctx context.Context, id types.NodeID, proof []byte) error + PublishATXProof(ctx context.Context, proof []byte) error } type atxProvider interface { diff --git a/activation/malfeasance2_publisher.go b/activation/malfeasance2_publisher.go index 7f67c944b1..14ef1e0bbb 100644 --- a/activation/malfeasance2_publisher.go +++ b/activation/malfeasance2_publisher.go @@ -4,7 +4,7 @@ import ( "context" "github.com/spacemeshos/go-spacemesh/activation/wire" - "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/codec" ) // ATXMalfeasancePublisher is the publisher for ATX proofs. @@ -20,7 +20,14 @@ func NewATXMalfeasancePublisher( } } -func (p *ATXMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { - // TODO(mafa): implement me - return nil +// Publish publishes an ATX proof by encoding it and sending it to the malfeasance publisher. +func (p *ATXMalfeasancePublisher) Publish(ctx context.Context, proof wire.Proof) error { + atxProof := &wire.ATXProof{ + Version: 0x01, // for now we only have one version + ProofType: proof.Type(), + + Proof: codec.MustEncode(proof), + } + + return p.malPublisher.PublishATXProof(ctx, codec.MustEncode(atxProof)) } diff --git a/activation/mocks.go b/activation/mocks.go index 97ed85e811..c04d5e7ed6 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1117,17 +1117,17 @@ func (m *MockatxMalfeasancePublisher) EXPECT() *MockatxMalfeasancePublisherMockR } // Publish mocks base method. -func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { +func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, proof wire.Proof) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Publish", ctx, id, proof) + ret := m.ctrl.Call(m, "Publish", ctx, proof) ret0, _ := ret[0].(error) return ret0 } // Publish indicates an expected call of Publish. -func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, id, proof any) *MockatxMalfeasancePublisherPublishCall { +func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, proof any) *MockatxMalfeasancePublisherPublishCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, id, proof) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, proof) return &MockatxMalfeasancePublisherPublishCall{Call: call} } @@ -1143,13 +1143,13 @@ func (c *MockatxMalfeasancePublisherPublishCall) Return(arg0 error) *MockatxMalf } // Do rewrite *gomock.Call.Do -func (c *MockatxMalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { +func (c *MockatxMalfeasancePublisherPublishCall) Do(f func(context.Context, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockatxMalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { +func (c *MockatxMalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1178,40 +1178,40 @@ func (m *MockmalfeasancePublisher) EXPECT() *MockmalfeasancePublisherMockRecorde return m.recorder } -// Publish mocks base method. -func (m *MockmalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof []byte) error { +// PublishATXProof mocks base method. +func (m *MockmalfeasancePublisher) PublishATXProof(ctx context.Context, proof []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Publish", ctx, id, proof) + ret := m.ctrl.Call(m, "PublishATXProof", ctx, proof) ret0, _ := ret[0].(error) return ret0 } -// Publish indicates an expected call of Publish. -func (mr *MockmalfeasancePublisherMockRecorder) Publish(ctx, id, proof any) *MockmalfeasancePublisherPublishCall { +// PublishATXProof indicates an expected call of PublishATXProof. +func (mr *MockmalfeasancePublisherMockRecorder) PublishATXProof(ctx, proof any) *MockmalfeasancePublisherPublishATXProofCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockmalfeasancePublisher)(nil).Publish), ctx, id, proof) - return &MockmalfeasancePublisherPublishCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishATXProof", reflect.TypeOf((*MockmalfeasancePublisher)(nil).PublishATXProof), ctx, proof) + return &MockmalfeasancePublisherPublishATXProofCall{Call: call} } -// MockmalfeasancePublisherPublishCall wrap *gomock.Call -type MockmalfeasancePublisherPublishCall struct { +// MockmalfeasancePublisherPublishATXProofCall wrap *gomock.Call +type MockmalfeasancePublisherPublishATXProofCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockmalfeasancePublisherPublishCall) Return(arg0 error) *MockmalfeasancePublisherPublishCall { +func (c *MockmalfeasancePublisherPublishATXProofCall) Return(arg0 error) *MockmalfeasancePublisherPublishATXProofCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockmalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, []byte) error) *MockmalfeasancePublisherPublishCall { +func (c *MockmalfeasancePublisherPublishATXProofCall) Do(f func(context.Context, []byte) error) *MockmalfeasancePublisherPublishATXProofCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockmalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, []byte) error) *MockmalfeasancePublisherPublishCall { +func (c *MockmalfeasancePublisherPublishATXProofCall) DoAndReturn(f func(context.Context, []byte) error) *MockmalfeasancePublisherPublishATXProofCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index abaafdd890..0ab62761e6 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -2,7 +2,6 @@ package wire import ( "context" - "errors" "fmt" "github.com/spacemeshos/go-scale" @@ -69,12 +68,23 @@ const ( LegacyInvalidPost ProofType = 0x01 LegacyInvalidPrevATX ProofType = 0x02 - DoubleMarry ProofType = 0x10 - DoubleMerge ProofType = 0x11 - InvalidPost ProofType = 0x12 - InvalidPrevious ProofType = 0x13 + DoubleMarry ProofType = 0x10 + DoubleMerge ProofType = 0x11 + InvalidPost ProofType = 0x12 + InvalidPreviousV1 ProofType = 0x13 + InvalidPreviousV2 ProofType = 0x14 ) +var proofTypes = map[ProofType]Proof{ + // TODO(mafa): legacy proofs + + DoubleMarry: &ProofDoubleMarry{}, + DoubleMerge: &ProofDoubleMerge{}, + InvalidPost: &ProofInvalidPost{}, + InvalidPreviousV1: &ProofInvalidPrevAtxV1{}, + InvalidPreviousV2: &ProofInvalidPrevAtxV2{}, +} + // ProofVersion is an identifier for the version of the proof that is encoded in the ATXProof. type ProofVersion byte @@ -83,41 +93,29 @@ type ATXProof struct { Version ProofVersion // ProofType is the type of proof that is being provided. ProofType ProofType + // Proof is the actual proof. Its type depends on the ProofType. Proof []byte `scale:"max=1048576"` // max size of proof is 1MiB } func (p *ATXProof) Decode() (Proof, error) { - switch p.ProofType { - case DoubleMarry: - rst := &ProofDoubleMarry{} - if err := codec.Decode(p.Proof, rst); err != nil { - return nil, fmt.Errorf("decoding ATX double marry proof: %w", err) - } - return rst, nil - case DoubleMerge: - rst := &ProofDoubleMerge{} - if err := codec.Decode(p.Proof, rst); err != nil { - return nil, fmt.Errorf("decoding ATX double merge proof: %w", err) - } - return rst, nil - case InvalidPost: - rst := &ProofInvalidPost{} - if err := codec.Decode(p.Proof, rst); err != nil { - return nil, fmt.Errorf("decoding ATX invalid post proof: %w", err) - } - return rst, nil - case InvalidPrevious: - return nil, errors.New("invalid previous proof is not supported") - default: - return nil, fmt.Errorf("unknown ATX malfeasance proof type: %d", p.ProofType) + rst, ok := proofTypes[p.ProofType] + if !ok { + return nil, fmt.Errorf("unknown ATX malfeasance proof type: 0x%x", p.ProofType) + } + if err := codec.Decode(p.Proof, rst); err != nil { + return nil, fmt.Errorf("decoding ATX malfeasance proof of type 0x%x: %w", p.ProofType, err) } + return rst, nil } // Proof is an interface for all types of proofs that can be provided in an ATXProof. // Generally the proof should be able to validate itself and be scale encoded. type Proof interface { scale.Encodable + scale.Decodable + fmt.Stringer + Type() ProofType Valid(ctx context.Context, malHandler MalfeasanceValidator) (types.NodeID, error) } diff --git a/activation/wire/malfeasance_double_marry.go b/activation/wire/malfeasance_double_marry.go index fc2a98e545..8778517501 100644 --- a/activation/wire/malfeasance_double_marry.go +++ b/activation/wire/malfeasance_double_marry.go @@ -42,6 +42,14 @@ type ProofDoubleMarry struct { Proof2 MarryProof } +func (p ProofDoubleMarry) String() string { + return "Double Marry Proof" +} + +func (p ProofDoubleMarry) Type() ProofType { + return DoubleMarry +} + var _ Proof = &ProofDoubleMarry{} func NewDoubleMarryProof(db sql.Executor, atx1, atx2 *ActivationTxV2, nodeID types.NodeID) (*ProofDoubleMarry, error) { diff --git a/activation/wire/malfeasance_double_merge.go b/activation/wire/malfeasance_double_merge.go index 3b3f73194a..689566b889 100644 --- a/activation/wire/malfeasance_double_merge.go +++ b/activation/wire/malfeasance_double_merge.go @@ -59,6 +59,14 @@ type ProofDoubleMerge struct { SmesherID2MarryProof MarryProof } +func (p ProofDoubleMerge) String() string { + return "Double Merge Proof" +} + +func (p ProofDoubleMerge) Type() ProofType { + return DoubleMerge +} + var _ Proof = &ProofDoubleMerge{} func NewDoubleMergeProof(db sql.Executor, atx1, atx2 *ActivationTxV2) (*ProofDoubleMerge, error) { diff --git a/activation/wire/malfeasance_invalid_post.go b/activation/wire/malfeasance_invalid_post.go index ddb792c9f0..11d75c1232 100644 --- a/activation/wire/malfeasance_invalid_post.go +++ b/activation/wire/malfeasance_invalid_post.go @@ -38,6 +38,14 @@ type ProofInvalidPost struct { InvalidPostProof InvalidPostProof } +func (p ProofInvalidPost) String() string { + return "Invalid PoST Proof" +} + +func (p ProofInvalidPost) Type() ProofType { + return InvalidPost +} + var _ Proof = &ProofInvalidPost{} func NewInvalidPostProof( diff --git a/activation/wire/malfeasance_invalid_prev_atx.go b/activation/wire/malfeasance_invalid_prev_atx.go index abb5acb5f6..ba06b65dc0 100644 --- a/activation/wire/malfeasance_invalid_prev_atx.go +++ b/activation/wire/malfeasance_invalid_prev_atx.go @@ -32,6 +32,14 @@ type ProofInvalidPrevAtxV2 struct { Proofs [2]InvalidPrevAtxProof } +func (p ProofInvalidPrevAtxV2) String() string { + return "Invalid Previous ATX Proof V2" +} + +func (p ProofInvalidPrevAtxV2) Type() ProofType { + return InvalidPreviousV2 +} + var _ Proof = &ProofInvalidPrevAtxV2{} func NewInvalidPrevAtxProofV2( @@ -190,6 +198,14 @@ type ProofInvalidPrevAtxV1 struct { ATXv1 ActivationTxV1 } +func (p ProofInvalidPrevAtxV1) String() string { + return "Invalid Previous ATX Proof V1" +} + +func (p ProofInvalidPrevAtxV1) Type() ProofType { + return InvalidPreviousV1 +} + var _ Proof = &ProofInvalidPrevAtxV1{} func NewInvalidPrevAtxProofV1( From 14de6bf3111eee5c053997ebd309caba7a4214a2 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:18:26 +0000 Subject: [PATCH 13/66] Work in progress --- activation/e2e/atx_merge_test.go | 2 + activation/e2e/builds_atx_v2_test.go | 2 + activation/e2e/checkpoint_merged_test.go | 3 + activation/e2e/checkpoint_test.go | 3 + activation/handler.go | 3 +- activation/handler_test.go | 3 +- activation/handler_v2.go | 10 +- activation/handler_v2_test.go | 24 ++- activation/interface.go | 4 +- ...alfeasance2_handler.go => malfeasance2.go} | 71 ++++----- activation/malfeasance2_publisher.go | 33 ---- activation/malfeasance2_publisher_test.go | 3 - ...2_handler_test.go => malfeasance2_test.go} | 0 activation/mocks.go | 24 +-- checkpoint/recovery_test.go | 42 ++--- malfeasance/handler.go | 6 +- malfeasance2/handler.go | 148 ++++++++++++++++-- malfeasance2/handler_test.go | 22 ++- malfeasance2/interface.go | 7 +- malfeasance2/metrics.go | 31 ++++ malfeasance2/mocks.go | 122 ++++++++++++++- malfeasance2/publisher.go | 31 ++-- node/node.go | 126 ++++++++------- 23 files changed, 481 insertions(+), 239 deletions(-) rename activation/{malfeasance2_handler.go => malfeasance2.go} (59%) delete mode 100644 activation/malfeasance2_publisher.go delete mode 100644 activation/malfeasance2_publisher_test.go rename activation/{malfeasance2_handler_test.go => malfeasance2_test.go} (100%) create mode 100644 malfeasance2/metrics.go diff --git a/activation/e2e/atx_merge_test.go b/activation/e2e/atx_merge_test.go index 0580686392..311f1bafcc 100644 --- a/activation/e2e/atx_merge_test.go +++ b/activation/e2e/atx_merge_test.go @@ -277,6 +277,7 @@ func Test_MarryAndMerge(t *testing.T) { mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -291,6 +292,7 @@ func Test_MarryAndMerge(t *testing.T) { mFetch, goldenATX, validator, + mMalPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/builds_atx_v2_test.go b/activation/e2e/builds_atx_v2_test.go index a537a159a2..99bdac8c16 100644 --- a/activation/e2e/builds_atx_v2_test.go +++ b/activation/e2e/builds_atx_v2_test.go @@ -118,6 +118,7 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { edVerifier := signing.NewEdVerifier() mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -131,6 +132,7 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { mFetch, goldenATX, validator, + mMalPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/checkpoint_merged_test.go b/activation/e2e/checkpoint_merged_test.go index 2467336407..f00b952a9c 100644 --- a/activation/e2e/checkpoint_merged_test.go +++ b/activation/e2e/checkpoint_merged_test.go @@ -108,6 +108,7 @@ func Test_CheckpointAfterMerge(t *testing.T) { mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -121,6 +122,7 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch, goldenATX, validator, + mMalPublish, mBeacon, mTortoise, logger, @@ -300,6 +302,7 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch, goldenATX, validator, + mMalPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/checkpoint_test.go b/activation/e2e/checkpoint_test.go index 9083d9c6b7..f5d425a1c7 100644 --- a/activation/e2e/checkpoint_test.go +++ b/activation/e2e/checkpoint_test.go @@ -104,6 +104,7 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { edVerifier := signing.NewEdVerifier() mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -117,6 +118,7 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { mFetch, goldenATX, validator, + mMalPublish, mBeacon, mTortoise, logger, @@ -206,6 +208,7 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { mFetch, goldenATX, validator, + mMalPublish, mBeacon, mTortoise, logger, diff --git a/activation/handler.go b/activation/handler.go index 78f46eb1fb..219829721d 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -106,6 +106,7 @@ func NewHandler( fetcher system.Fetcher, goldenATXID types.ATXID, nipostValidator nipostValidator, + malPublisher atxMalfeasancePublisher, beacon atxReceiver, tortoise system.Tortoise, lg *zap.Logger, @@ -146,7 +147,7 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, - malPublisher: &ATXMalfeasancePublisher{}, // TODO(mafa): pass real publisher when available + malPublisher: malPublisher, }, } diff --git a/activation/handler_test.go b/activation/handler_test.go index cd2e346ad2..94d0143cbe 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -215,8 +215,6 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio edVerifier := signing.NewEdVerifier() mocks := newTestHandlerMocks(tb, goldenATXID) - // TODO(mafa): make mandatory parameter when real publisher is available - opts = append(opts, func(h *Handler) { h.v2.malPublisher = mocks.mMalPublish }) atxHdlr := NewHandler( "localID", cdb, @@ -227,6 +225,7 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio mocks.mockFetch, goldenATXID, mocks.mValidator, + mocks.mMalPublish, mocks.mbeacon, mocks.mtortoise, lg, diff --git a/activation/handler_v2.go b/activation/handler_v2.go index 5f78a7c40d..4c396a0e7a 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -721,7 +721,7 @@ func (h *HandlerV2) validatePost( if err != nil { return fmt.Errorf("creating invalid post proof: %w", err) } - if err := h.malPublisher.Publish(ctx, proof); err != nil { + if err := h.malPublisher.Publish(ctx, nodeID, proof); err != nil { return fmt.Errorf("publishing malfeasance proof for invalid post: %w", err) } return fmt.Errorf("invalid post for ID %s: %w", nodeID.ShortString(), errInvalid) @@ -802,7 +802,7 @@ func (h *HandlerV2) checkDoubleMarry(ctx context.Context, tx sql.Transaction, at if err != nil { return true, fmt.Errorf("creating double marry proof: %w", err) } - return true, h.malPublisher.Publish(ctx, proof) + return true, h.malPublisher.Publish(ctx, m.id, proof) } return false, nil } @@ -847,7 +847,7 @@ func (h *HandlerV2) checkDoubleMerge(ctx context.Context, tx sql.Transaction, at if err != nil { return true, fmt.Errorf("creating double merge proof: %w", err) } - return true, h.malPublisher.Publish(ctx, proof) + return true, h.malPublisher.Publish(ctx, atx.ActivationTxV2.SmesherID, proof) } func (h *HandlerV2) checkPrevAtx(ctx context.Context, tx sql.Transaction, atx *activationTx) (bool, error) { @@ -905,7 +905,7 @@ func (h *HandlerV2) checkPrevAtx(ctx context.Context, tx sql.Transaction, atx *a if err != nil { return true, fmt.Errorf("creating invalid previous ATX proof: %w", err) } - return true, h.malPublisher.Publish(ctx, proof) + return true, h.malPublisher.Publish(ctx, id, proof) default: h.logger.Fatal("Failed to create invalid previous ATX proof: unknown ATX version", zap.Stringer("atx_id", collision), @@ -923,7 +923,7 @@ func (h *HandlerV2) checkPrevAtx(ctx context.Context, tx sql.Transaction, atx *a if err != nil { return true, fmt.Errorf("creating invalid previous ATX proof: %w", err) } - return true, h.malPublisher.Publish(ctx, proof) + return true, h.malPublisher.Publish(ctx, id, proof) } return false, nil } diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index 6b8a0bbd01..e4dcf94953 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -994,8 +994,9 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { atxHandler.expectMergedAtxV2(merged, equivocationSet, []uint64{100}) atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), + merged.SmesherID, gomock.AssignableToTypeOf(&wire.ProofDoubleMerge{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofDoubleMerge) nId, err := malProof.Valid(context.Background(), verifier) require.NoError(t, err) @@ -1618,8 +1619,9 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), + sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPost{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPost) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -1712,8 +1714,9 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), + sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPost{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPost) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -1833,8 +1836,9 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), + sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPost{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPost) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -1964,8 +1968,9 @@ func Test_Marriages(t *testing.T) { atxHandler.mMalPublish.EXPECT().Publish( gomock.Any(), + sig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofDoubleMarry{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofDoubleMarry) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -2170,8 +2175,9 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.mMalPublish.EXPECT().Publish( gomock.Any(), + signers[1].NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPrevAtxV2{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPrevAtxV2) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -2250,8 +2256,9 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.mMalPublish.EXPECT().Publish( gomock.Any(), + sig1.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPrevAtxV1{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPrevAtxV1) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) @@ -2325,8 +2332,9 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.mMalPublish.EXPECT().Publish( gomock.Any(), + otherSig.NodeID(), gomock.AssignableToTypeOf(&wire.ProofInvalidPrevAtxV2{}), - ).DoAndReturn(func(ctx context.Context, proof wire.Proof) error { + ).DoAndReturn(func(ctx context.Context, _ types.NodeID, proof wire.Proof) error { malProof := proof.(*wire.ProofInvalidPrevAtxV2) nId, err := malProof.Valid(ctx, verifier) require.NoError(t, err) diff --git a/activation/interface.go b/activation/interface.go index 72d9fe4713..8e24eb6009 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -97,7 +97,7 @@ type syncer interface { // It encapsulates a specific malfeasance proof into a generic ATX malfeasance proof and publishes it by calling // the underlying malfeasancePublisher. type atxMalfeasancePublisher interface { - Publish(ctx context.Context, proof wire.Proof) error + Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error } // malfeasancePublisher is an interface for publishing malfeasance proofs. @@ -108,7 +108,7 @@ type atxMalfeasancePublisher interface { // Additionally the publisher will only gossip proofs when the node is in sync, otherwise it will only store them // and mark the associated identity as malfeasant. type malfeasancePublisher interface { - PublishATXProof(ctx context.Context, proof []byte) error + PublishATXProof(ctx context.Context, nodeID types.NodeID, proof []byte) error } type atxProvider interface { diff --git a/activation/malfeasance2_handler.go b/activation/malfeasance2.go similarity index 59% rename from activation/malfeasance2_handler.go rename to activation/malfeasance2.go index 4a5cfaeb72..9035575e73 100644 --- a/activation/malfeasance2_handler.go +++ b/activation/malfeasance2.go @@ -7,41 +7,39 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/signing" - "github.com/spacemeshos/go-spacemesh/system" ) type MalfeasanceHandlerV2 struct { - syncer syncer - clock layerClock - publisher malfeasancePublisher - cdb *datastore.CachedDB - tortoise system.Tortoise - edVerifier *signing.EdVerifier - validator nipostValidatorV2 + malPublisher malfeasancePublisher + edVerifier *signing.EdVerifier + validator nipostValidatorV2 } func NewMalfeasanceHandlerV2( - syncer syncer, - layerClock layerClock, malPublisher malfeasancePublisher, - cdb *datastore.CachedDB, - tortoise system.Tortoise, edVerifier *signing.EdVerifier, validator nipostValidatorV2, ) *MalfeasanceHandlerV2 { return &MalfeasanceHandlerV2{ - syncer: syncer, - clock: layerClock, - publisher: malPublisher, // TODO(mafa): implement malfeasancePublisher in `malfeasance` package - cdb: cdb, - tortoise: tortoise, - edVerifier: edVerifier, - validator: validator, + malPublisher: malPublisher, + edVerifier: edVerifier, + validator: validator, } } +// Publish publishes an ATX proof by encoding it and sending it to the malfeasance publisher. +func (p *MalfeasanceHandlerV2) Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error { + atxProof := &wire.ATXProof{ + Version: 0x01, // for now we only have one version + ProofType: proof.Type(), + + Proof: codec.MustEncode(proof), + } + + return p.malPublisher.PublishATXProof(ctx, nodeID, codec.MustEncode(atxProof)) +} + func (mh *MalfeasanceHandlerV2) PostIndex( ctx context.Context, smesherID types.NodeID, @@ -58,41 +56,26 @@ func (mh *MalfeasanceHandlerV2) Signature(d signing.Domain, nodeID types.NodeID, return mh.edVerifier.Verify(d, nodeID, m, sig) } -// TODO(mafa): call this validate in the handler for publish/gossip. -// TODO(mafa): extend this validate to return nil if `peer` == self. -func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) ([]types.NodeID, error) { +// TODO(mafa): call this validate in the malfeasance handler in `malfeasance` package for publish/gossip: +// - do not publishing proofs for identities managed by node +// - validate and persist before publishing +// - do not handle incoming proofs from peer == `self` +func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) (types.NodeID, error) { var atxProof wire.ATXProof if err := codec.Decode(data, &atxProof); err != nil { - return nil, fmt.Errorf("decoding ATX malfeasance proof: %w", err) + return types.EmptyNodeID, fmt.Errorf("decoding ATX malfeasance proof: %w", err) } proof, err := atxProof.Decode() if err != nil { - return nil, fmt.Errorf("decoding ATX malfeasance proof: %w", err) + return types.EmptyNodeID, fmt.Errorf("decoding ATX malfeasance proof: %w", err) } id, err := proof.Valid(ctx, mh) if err != nil { - return nil, fmt.Errorf("validating ATX malfeasance proof: %w", err) + return types.EmptyNodeID, fmt.Errorf("validating ATX malfeasance proof: %w", err) } - - // TODO(mafa): do this in the general handler - // validIDs := make([]types.NodeID, 0, len(decoded.Certificates)+1) - // validIDs = append(validIDs, id) // id has already been proven to be malfeasant - - // // check certificates provided with the proof - // // TODO(mafa): only works if the main identity becomes malfeasant - try different approach with merkle proofs - // for _, cert := range decoded.Certificates { - // if id != cert.Target { - // continue - // } - // if !mh.edVerifier.Verify(signing.MARRIAGE, cert.Target, cert.ID.Bytes(), cert.Signature) { - // continue - // } - // validIDs = append(validIDs, cert.ID) - // } - // return validIDs, nil - return []types.NodeID{id}, nil + return id, nil } // TODO(mafa): this roughly how the general publisher looks like diff --git a/activation/malfeasance2_publisher.go b/activation/malfeasance2_publisher.go deleted file mode 100644 index 14ef1e0bbb..0000000000 --- a/activation/malfeasance2_publisher.go +++ /dev/null @@ -1,33 +0,0 @@ -package activation - -import ( - "context" - - "github.com/spacemeshos/go-spacemesh/activation/wire" - "github.com/spacemeshos/go-spacemesh/codec" -) - -// ATXMalfeasancePublisher is the publisher for ATX proofs. -type ATXMalfeasancePublisher struct { - malPublisher malfeasancePublisher -} - -func NewATXMalfeasancePublisher( - malPublisher malfeasancePublisher, -) *ATXMalfeasancePublisher { - return &ATXMalfeasancePublisher{ - malPublisher: malPublisher, - } -} - -// Publish publishes an ATX proof by encoding it and sending it to the malfeasance publisher. -func (p *ATXMalfeasancePublisher) Publish(ctx context.Context, proof wire.Proof) error { - atxProof := &wire.ATXProof{ - Version: 0x01, // for now we only have one version - ProofType: proof.Type(), - - Proof: codec.MustEncode(proof), - } - - return p.malPublisher.PublishATXProof(ctx, codec.MustEncode(atxProof)) -} diff --git a/activation/malfeasance2_publisher_test.go b/activation/malfeasance2_publisher_test.go deleted file mode 100644 index 385374da3c..0000000000 --- a/activation/malfeasance2_publisher_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package activation - -// TODO(mafa): implement me diff --git a/activation/malfeasance2_handler_test.go b/activation/malfeasance2_test.go similarity index 100% rename from activation/malfeasance2_handler_test.go rename to activation/malfeasance2_test.go diff --git a/activation/mocks.go b/activation/mocks.go index c04d5e7ed6..ed56a3d9cc 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1117,17 +1117,17 @@ func (m *MockatxMalfeasancePublisher) EXPECT() *MockatxMalfeasancePublisherMockR } // Publish mocks base method. -func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, proof wire.Proof) error { +func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Publish", ctx, proof) + ret := m.ctrl.Call(m, "Publish", ctx, nodeID, proof) ret0, _ := ret[0].(error) return ret0 } // Publish indicates an expected call of Publish. -func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, proof any) *MockatxMalfeasancePublisherPublishCall { +func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, nodeID, proof any) *MockatxMalfeasancePublisherPublishCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, proof) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, nodeID, proof) return &MockatxMalfeasancePublisherPublishCall{Call: call} } @@ -1143,13 +1143,13 @@ func (c *MockatxMalfeasancePublisherPublishCall) Return(arg0 error) *MockatxMalf } // Do rewrite *gomock.Call.Do -func (c *MockatxMalfeasancePublisherPublishCall) Do(f func(context.Context, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { +func (c *MockatxMalfeasancePublisherPublishCall) Do(f func(context.Context, types.NodeID, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockatxMalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { +func (c *MockatxMalfeasancePublisherPublishCall) DoAndReturn(f func(context.Context, types.NodeID, wire.Proof) error) *MockatxMalfeasancePublisherPublishCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1179,17 +1179,17 @@ func (m *MockmalfeasancePublisher) EXPECT() *MockmalfeasancePublisherMockRecorde } // PublishATXProof mocks base method. -func (m *MockmalfeasancePublisher) PublishATXProof(ctx context.Context, proof []byte) error { +func (m *MockmalfeasancePublisher) PublishATXProof(ctx context.Context, nodeID types.NodeID, proof []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PublishATXProof", ctx, proof) + ret := m.ctrl.Call(m, "PublishATXProof", ctx, nodeID, proof) ret0, _ := ret[0].(error) return ret0 } // PublishATXProof indicates an expected call of PublishATXProof. -func (mr *MockmalfeasancePublisherMockRecorder) PublishATXProof(ctx, proof any) *MockmalfeasancePublisherPublishATXProofCall { +func (mr *MockmalfeasancePublisherMockRecorder) PublishATXProof(ctx, nodeID, proof any) *MockmalfeasancePublisherPublishATXProofCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishATXProof", reflect.TypeOf((*MockmalfeasancePublisher)(nil).PublishATXProof), ctx, proof) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishATXProof", reflect.TypeOf((*MockmalfeasancePublisher)(nil).PublishATXProof), ctx, nodeID, proof) return &MockmalfeasancePublisherPublishATXProofCall{Call: call} } @@ -1205,13 +1205,13 @@ func (c *MockmalfeasancePublisherPublishATXProofCall) Return(arg0 error) *Mockma } // Do rewrite *gomock.Call.Do -func (c *MockmalfeasancePublisherPublishATXProofCall) Do(f func(context.Context, []byte) error) *MockmalfeasancePublisherPublishATXProofCall { +func (c *MockmalfeasancePublisherPublishATXProofCall) Do(f func(context.Context, types.NodeID, []byte) error) *MockmalfeasancePublisherPublishATXProofCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockmalfeasancePublisherPublishATXProofCall) DoAndReturn(f func(context.Context, []byte) error) *MockmalfeasancePublisherPublishATXProofCall { +func (c *MockmalfeasancePublisherPublishATXProofCall) DoAndReturn(f func(context.Context, types.NodeID, []byte) error) *MockmalfeasancePublisherPublishATXProofCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index 6b464f70ee..a025945ce8 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -249,10 +249,11 @@ func validateAndPreserveData( lg := zaptest.NewLogger(tb) ctrl := gomock.NewController(tb) mclock := activation.NewMocklayerClock(ctrl) - mfetch := smocks.NewMockFetcher(ctrl) - mvalidator := activation.NewMocknipostValidator(ctrl) - mreceiver := activation.NewMockatxReceiver(ctrl) - mtrtl := smocks.NewMockTortoise(ctrl) + mFetch := smocks.NewMockFetcher(ctrl) + mValidator := activation.NewMocknipostValidator(ctrl) + mMalPublisher := activation.NewMockatxMalfeasancePublisher(ctrl) + mReceiver := activation.NewMockatxReceiver(ctrl) + mTortoise := smocks.NewMockTortoise(ctrl) cdb := datastore.NewCachedDB(db, lg) tb.Cleanup(func() { assert.NoError(tb, cdb.Close()) }) atxHandler := activation.NewHandler( @@ -262,25 +263,26 @@ func validateAndPreserveData( signing.NewEdVerifier(), mclock, nil, - mfetch, + mFetch, goldenAtx, - mvalidator, - mreceiver, - mtrtl, + mValidator, + mMalPublisher, + mReceiver, + mTortoise, lg, ) - mfetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() for _, dep := range deps { var atx wire.ActivationTxV1 require.NoError(tb, codec.Decode(dep.Blob, &atx)) mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) - mfetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any()) - mfetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) + mFetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any()) + mFetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) if atx.PrevATXID == types.EmptyATXID { - mvalidator.EXPECT(). + mValidator.EXPECT(). InitialNIPostChallengeV1(&atx.NIPostChallengeV1, gomock.Any(), goldenAtx). AnyTimes() - mvalidator.EXPECT().Post( + mValidator.EXPECT().Post( gomock.Any(), atx.SmesherID, *atx.CommitmentATXID, @@ -289,7 +291,7 @@ func validateAndPreserveData( atx.NumUnits, gomock.Any(), ) - mvalidator.EXPECT().VRFNonce( + mValidator.EXPECT().VRFNonce( atx.SmesherID, *atx.CommitmentATXID, *atx.VRFNonce, @@ -297,7 +299,7 @@ func validateAndPreserveData( atx.NumUnits, ) } else { - mvalidator.EXPECT().NIPostChallengeV1( + mValidator.EXPECT().NIPostChallengeV1( &atx.NIPostChallengeV1, gomock.Cond(func(prev *types.ActivationTx) bool { return prev.ID() == atx.PrevATXID @@ -306,13 +308,13 @@ func validateAndPreserveData( ) } - mvalidator.EXPECT().PositioningAtx(atx.PositioningATXID, cdb, goldenAtx, atx.PublishEpoch) - mvalidator.EXPECT(). + mValidator.EXPECT().PositioningAtx(atx.PositioningATXID, cdb, goldenAtx, atx.PublishEpoch) + mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, gomock.Any(), gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(uint64(1111111), nil) - mvalidator.EXPECT().IsVerifyingFullPost().AnyTimes().Return(true) - mreceiver.EXPECT().OnAtx(gomock.Any()) - mtrtl.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) + mValidator.EXPECT().IsVerifyingFullPost().AnyTimes().Return(true) + mReceiver.EXPECT().OnAtx(gomock.Any()) + mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) require.NoError(tb, atxHandler.HandleSyncedAtx(context.Background(), atx.ID().Hash32(), "self", dep.Blob)) } } diff --git a/malfeasance/handler.go b/malfeasance/handler.go index ded77bd507..b15eb2aa1f 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -55,14 +55,14 @@ func NewHandler( cdb *datastore.CachedDB, lg *zap.Logger, self p2p.Peer, - nodeID []types.NodeID, + nodeIDs []types.NodeID, tortoise tortoise, ) *Handler { return &Handler{ logger: lg, cdb: cdb, self: self, - nodeIDs: nodeID, + nodeIDs: nodeIDs, tortoise: tortoise, handlers: make(map[MalfeasanceType]MalfeasanceHandler), @@ -130,9 +130,9 @@ func (h *Handler) HandleSyncedMalfeasanceProof( // but only log "validation ignored" instead of the error we return h.logger.Warn("malfeasance proof for wrong identity", log.ZContext(ctx), + zap.Stringer("peer", peer), log.ZShortStringer("expected", expHash), log.ZShortStringer("got", nodeID), - zap.Stringer("peer", peer), ) return fmt.Errorf( "%w: malfeasance proof want %s, got %s", diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go index 12aa8532ee..a4dbbfcb79 100644 --- a/malfeasance2/handler.go +++ b/malfeasance2/handler.go @@ -4,13 +4,18 @@ import ( "context" "fmt" "slices" + "strconv" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/events" + "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" ) @@ -22,10 +27,12 @@ var ( ) type Handler struct { - logger *zap.Logger - db sql.Executor - self p2p.Peer - tortoise tortoise + logger *zap.Logger + db sql.Executor + self p2p.Peer + nodeIDs []types.NodeID + edVerifier *signing.EdVerifier + tortoise tortoise handlers map[ProofDomain]MalfeasanceHandler } @@ -34,25 +41,66 @@ func NewHandler( db sql.Executor, lg *zap.Logger, self p2p.Peer, + nodeIDs []types.NodeID, + edVerifier *signing.EdVerifier, tortoise tortoise, ) *Handler { return &Handler{ - db: db, - logger: lg, - self: self, - tortoise: tortoise, // TODO(mafa): needed to call OnMalfeasance after receiving & storing a valid proof + db: db, + logger: lg, + self: self, + nodeIDs: nodeIDs, + edVerifier: edVerifier, + tortoise: tortoise, handlers: make(map[ProofDomain]MalfeasanceHandler), } } func (h *Handler) RegisterHandler(malfeasanceType ProofDomain, handler MalfeasanceHandler) { + if _, ok := h.handlers[malfeasanceType]; ok { + h.logger.Panic("handler already registered", zap.Int("malfeasanceType", int(malfeasanceType))) + } h.handlers[malfeasanceType] = handler } -func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, _ p2p.Peer, msg []byte) error { +func (h *Handler) countProof(mp MalfeasanceProof) { + h.handlers[mp.Domain].ReportProof(numProofs) +} + +func (h *Handler) countInvalidProof(mp MalfeasanceProof) { + h.handlers[mp.Domain].ReportInvalidProof(numInvalidProofs) +} + +func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { + h.tortoise.OnMalfeasance(smesher) + events.ReportMalfeasance(smesher, proof) + if slices.Contains(h.nodeIDs, smesher) { + events.EmitOwnMalfeasanceProof(smesher, proof) + } +} + +func (h *Handler) Info(data []byte) (map[string]string, error) { + var p MalfeasanceProof + if err := codec.Decode(data, &p); err != nil { + return nil, fmt.Errorf("decode malfeasance proof: %w", err) + } + mh, ok := h.handlers[p.Domain] + if !ok { + return nil, fmt.Errorf("unknown malfeasance domain %d", p.Domain) + } + properties, err := mh.Info(p.Proof) + if err != nil { + return nil, fmt.Errorf("malfeasance info: %w", err) + } + properties["domain"] = strconv.FormatUint(uint64(p.Domain), 10) + return properties, nil +} + +func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, peer p2p.Peer, msg []byte) error { var proof MalfeasanceProof if err := codec.Decode(msg, &proof); err != nil { + numMalformed.Inc() return ErrMalformedData } @@ -60,14 +108,46 @@ func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, _ p2p. if err != nil { return err } - if !slices.Contains(nodeIDs, types.NodeID(expHash)) { - return ErrWrongHash + // we log & return because libp2p will ignore the message if we return an error, + // but only log "validation ignored" instead of the error we return + h.logger.Warn("malfeasance proof for wrong identity", + log.ZContext(ctx), + zap.Stringer("peer", peer), + log.ZShortStringer("expected", expHash), + zap.Array("got", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { + for _, id := range nodeIDs { + arr.AppendString(id.ShortString()) + } + return nil + })), + ) + h.countInvalidProof(proof) + return fmt.Errorf( + "%w: malfeasance proof not valid for %s", + ErrWrongHash, + expHash.ShortString(), + ) } if err := h.storeProof(ctx, proof.Domain, msg); err != nil { return fmt.Errorf("store synced malfeasance proof: %w", err) } + + for _, id := range nodeIDs { + h.reportMalfeasance(id, msg) + } + h.countProof(proof) + h.logger.Debug("synced malfeasance proof", + log.ZContext(ctx), + log.ZShortStringer("requested", expHash), + zap.Array("valid_for", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { + for _, id := range nodeIDs { + arr.AppendString(id.ShortString()) + } + return nil + })), + ) return nil } @@ -79,17 +159,32 @@ func (h *Handler) HandleGossip(ctx context.Context, peer p2p.Peer, msg []byte) e var proof MalfeasanceProof if err := codec.Decode(msg, &proof); err != nil { + numMalformed.Inc() return ErrMalformedData } - _, err := h.handleProof(ctx, proof) + nodeIDs, err := h.handleProof(ctx, proof) if err != nil { return err } if err := h.storeProof(ctx, proof.Domain, msg); err != nil { - return fmt.Errorf("store synced malfeasance proof: %w", err) + return fmt.Errorf("store gossiped malfeasance proof: %w", err) } + + for _, id := range nodeIDs { + h.reportMalfeasance(id, msg) + } + h.countProof(proof) + h.logger.Debug("received gossiped malfeasance proof", + log.ZContext(ctx), + zap.Array("valid_for", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { + for _, id := range nodeIDs { + arr.AppendString(id.ShortString()) + } + return nil + })), + ) return nil } @@ -105,9 +200,34 @@ func (h *Handler) handleProof(ctx context.Context, proof MalfeasanceProof) ([]ty return nil, fmt.Errorf("%w: %d", ErrUnknownDomain, proof.Domain) } - return handler.Validate(ctx, proof.Proof) + id, err := handler.Validate(ctx, proof.Proof) + if err != nil { + h.countInvalidProof(proof) + return nil, err + } + + validIDs := make([]types.NodeID, 0, len(proof.Certificates)+1) + validIDs = append(validIDs, id) // id has already been proven to be malfeasant + + // check certificates provided with the proof + // TODO(mafa): only works if the main identity becomes malfeasant - try different approach with merkle proofs + for _, cert := range proof.Certificates { + if id != cert.TargetID { + continue + } + if !h.edVerifier.Verify(signing.MARRIAGE, cert.TargetID, cert.SmesherID.Bytes(), cert.Signature) { + continue + } + validIDs = append(validIDs, cert.SmesherID) + } + + return validIDs, nil } +// TODO(mafa): store proof in db by +// - updating marriage information if needed (e.g. new smesher in the malfeasant marriage certificate set) +// - storing the proof for the identity that was proven to be malicious func (h *Handler) storeProof(ctx context.Context, domain ProofDomain, proof []byte) error { + _ = h.db return nil } diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index de4d4c7f3d..56f2632443 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -16,6 +16,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/malfeasance2" "github.com/spacemeshos/go-spacemesh/p2p" + "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/statesql" ) @@ -41,10 +42,14 @@ func newTestHandler(tb testing.TB) *testHandler { ctrl := gomock.NewController(tb) mockTrt := malfeasance2.NewMocktortoise(ctrl) + edVerifier := signing.NewEdVerifier() + h := malfeasance2.NewHandler( db, logger, "self", + []types.NodeID{types.RandomNodeID()}, + edVerifier, mockTrt, ) return &testHandler{ @@ -99,7 +104,8 @@ func TestHandler_HandleSync(t *testing.T) { invalidProof := []byte("invalid") handlerError := errors.New("invalid proof") mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) - mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(nil, handlerError) + mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(types.EmptyNodeID, handlerError) + mockHandler.EXPECT().ReportInvalidProof(gomock.Any()) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -117,8 +123,10 @@ func TestHandler_HandleSync(t *testing.T) { validProof := []byte("valid") nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) - mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return([]types.NodeID{nodeID}, nil) + mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) + mockHandler.EXPECT().ReportProof(gomock.Any()) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + h.mockTrt.EXPECT().OnMalfeasance(nodeID) proof := &malfeasance2.MalfeasanceProof{ Version: 0, @@ -135,7 +143,8 @@ func TestHandler_HandleSync(t *testing.T) { validProof := []byte("valid") nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) - mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return([]types.NodeID{nodeID}, nil) + mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) + mockHandler.EXPECT().ReportInvalidProof(gomock.Any()) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -193,7 +202,8 @@ func TestHandler_HandleGossip(t *testing.T) { invalidProof := []byte("invalid") handlerError := errors.New("invalid proof") mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) - mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(nil, handlerError) + mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(types.EmptyNodeID, handlerError) + mockHandler.EXPECT().ReportInvalidProof(gomock.Any()) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -211,8 +221,10 @@ func TestHandler_HandleGossip(t *testing.T) { validProof := []byte("valid") nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) - mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return([]types.NodeID{nodeID}, nil) + mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) + mockHandler.EXPECT().ReportProof(gomock.Any()) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + h.mockTrt.EXPECT().OnMalfeasance(nodeID) proof := &malfeasance2.MalfeasanceProof{ Version: 0, diff --git a/malfeasance2/interface.go b/malfeasance2/interface.go index 33f68921e5..954d06dfc8 100644 --- a/malfeasance2/interface.go +++ b/malfeasance2/interface.go @@ -3,6 +3,8 @@ package malfeasance2 import ( "context" + "github.com/prometheus/client_golang/prometheus" + "github.com/spacemeshos/go-spacemesh/common/types" ) @@ -13,5 +15,8 @@ type tortoise interface { } type MalfeasanceHandler interface { - Validate(ctx context.Context, data []byte) ([]types.NodeID, error) + Validate(ctx context.Context, data []byte) (types.NodeID, error) + Info(data []byte) (map[string]string, error) + ReportProof(vec *prometheus.CounterVec) // TODO(mafa): don't pass vectors along, use one defined in package + ReportInvalidProof(vec *prometheus.CounterVec) // TODO(mafa): don't pass vectors along, use one defined in package } diff --git a/malfeasance2/metrics.go b/malfeasance2/metrics.go new file mode 100644 index 0000000000..66cc8149c0 --- /dev/null +++ b/malfeasance2/metrics.go @@ -0,0 +1,31 @@ +package malfeasance2 + +import "github.com/spacemeshos/go-spacemesh/metrics" + +const ( + namespace = "malfeasance2" + + typeLabel = "type" +) + +var ( + numProofs = metrics.NewCounter( + "num_proofs", + namespace, + "number of malfeasance proofs", + []string{ + typeLabel, + }, + ) + + numInvalidProofs = metrics.NewCounter( + "num_invalid_proofs", + namespace, + "number of invalid malfeasance proofs", + []string{ + typeLabel, + }, + ) + + numMalformed = numInvalidProofs.WithLabelValues("mal") +) diff --git a/malfeasance2/mocks.go b/malfeasance2/mocks.go index aa67e44288..a24d32879b 100644 --- a/malfeasance2/mocks.go +++ b/malfeasance2/mocks.go @@ -13,6 +13,7 @@ import ( context "context" reflect "reflect" + prometheus "github.com/prometheus/client_golang/prometheus" types "github.com/spacemeshos/go-spacemesh/common/types" gomock "go.uber.org/mock/gomock" ) @@ -101,11 +102,122 @@ func (m *MockMalfeasanceHandler) EXPECT() *MockMalfeasanceHandlerMockRecorder { return m.recorder } +// Info mocks base method. +func (m *MockMalfeasanceHandler) Info(data []byte) (map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Info", data) + ret0, _ := ret[0].(map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Info indicates an expected call of Info. +func (mr *MockMalfeasanceHandlerMockRecorder) Info(data any) *MockMalfeasanceHandlerInfoCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockMalfeasanceHandler)(nil).Info), data) + return &MockMalfeasanceHandlerInfoCall{Call: call} +} + +// MockMalfeasanceHandlerInfoCall wrap *gomock.Call +type MockMalfeasanceHandlerInfoCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockMalfeasanceHandlerInfoCall) Return(arg0 map[string]string, arg1 error) *MockMalfeasanceHandlerInfoCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockMalfeasanceHandlerInfoCall) Do(f func([]byte) (map[string]string, error)) *MockMalfeasanceHandlerInfoCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockMalfeasanceHandlerInfoCall) DoAndReturn(f func([]byte) (map[string]string, error)) *MockMalfeasanceHandlerInfoCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ReportInvalidProof mocks base method. +func (m *MockMalfeasanceHandler) ReportInvalidProof(vec *prometheus.CounterVec) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ReportInvalidProof", vec) +} + +// ReportInvalidProof indicates an expected call of ReportInvalidProof. +func (mr *MockMalfeasanceHandlerMockRecorder) ReportInvalidProof(vec any) *MockMalfeasanceHandlerReportInvalidProofCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportInvalidProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportInvalidProof), vec) + return &MockMalfeasanceHandlerReportInvalidProofCall{Call: call} +} + +// MockMalfeasanceHandlerReportInvalidProofCall wrap *gomock.Call +type MockMalfeasanceHandlerReportInvalidProofCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockMalfeasanceHandlerReportInvalidProofCall) Return() *MockMalfeasanceHandlerReportInvalidProofCall { + c.Call = c.Call.Return() + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockMalfeasanceHandlerReportInvalidProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockMalfeasanceHandlerReportInvalidProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ReportProof mocks base method. +func (m *MockMalfeasanceHandler) ReportProof(vec *prometheus.CounterVec) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ReportProof", vec) +} + +// ReportProof indicates an expected call of ReportProof. +func (mr *MockMalfeasanceHandlerMockRecorder) ReportProof(vec any) *MockMalfeasanceHandlerReportProofCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportProof), vec) + return &MockMalfeasanceHandlerReportProofCall{Call: call} +} + +// MockMalfeasanceHandlerReportProofCall wrap *gomock.Call +type MockMalfeasanceHandlerReportProofCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockMalfeasanceHandlerReportProofCall) Return() *MockMalfeasanceHandlerReportProofCall { + c.Call = c.Call.Return() + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockMalfeasanceHandlerReportProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockMalfeasanceHandlerReportProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // Validate mocks base method. -func (m *MockMalfeasanceHandler) Validate(ctx context.Context, data []byte) ([]types.NodeID, error) { +func (m *MockMalfeasanceHandler) Validate(ctx context.Context, data []byte) (types.NodeID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Validate", ctx, data) - ret0, _ := ret[0].([]types.NodeID) + ret0, _ := ret[0].(types.NodeID) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -123,19 +235,19 @@ type MockMalfeasanceHandlerValidateCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockMalfeasanceHandlerValidateCall) Return(arg0 []types.NodeID, arg1 error) *MockMalfeasanceHandlerValidateCall { +func (c *MockMalfeasanceHandlerValidateCall) Return(arg0 types.NodeID, arg1 error) *MockMalfeasanceHandlerValidateCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockMalfeasanceHandlerValidateCall) Do(f func(context.Context, []byte) ([]types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { +func (c *MockMalfeasanceHandlerValidateCall) Do(f func(context.Context, []byte) (types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockMalfeasanceHandlerValidateCall) DoAndReturn(f func(context.Context, []byte) ([]types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { +func (c *MockMalfeasanceHandlerValidateCall) DoAndReturn(f func(context.Context, []byte) (types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/malfeasance2/publisher.go b/malfeasance2/publisher.go index 5f0fa39cd6..4273c436e3 100644 --- a/malfeasance2/publisher.go +++ b/malfeasance2/publisher.go @@ -36,31 +36,28 @@ func NewPublisher( } } -func (p *Publisher) PublishV2ATXProof( +func (p *Publisher) PublishATXProof( ctx context.Context, - smesherIDs []types.NodeID, - domain ProofDomain, + nodeID types.NodeID, proof []byte, ) error { // Combine IDs from the present equivocation set for atx.SmesherID and IDs in atx.Marriages. allMalicious := make(map[types.NodeID]struct{}) - for _, id := range smesherIDs { - marriageID, err := marriage.FindIDByNodeID(p.cdb, id) - if err != nil { - return fmt.Errorf("getting equivocation set: %w", err) - } - set, err := marriage.NodeIDsByID(p.cdb, marriageID) - for _, id := range set { - allMalicious[id] = struct{}{} - } - for _, id := range smesherIDs { - allMalicious[id] = struct{}{} - } + marriageID, err := marriage.FindIDByNodeID(p.cdb, nodeID) + if err != nil { + return fmt.Errorf("getting equivocation set: %w", err) + } + set, err := marriage.NodeIDsByID(p.cdb, marriageID) + if err != nil { + return fmt.Errorf("getting equivocation set: %w", err) + } + for _, id := range set { + allMalicious[id] = struct{}{} } for id := range allMalicious { - if err := malfeasance.AddProof(p.cdb, id, nil, proof, byte(domain), time.Now()); err != nil { + if err := malfeasance.AddProof(p.cdb, id, nil, proof, byte(InvalidActivation), time.Now()); err != nil { return fmt.Errorf("setting malfeasance proof: %w", err) } // TODO(mafa): cache proof @@ -72,7 +69,7 @@ func (p *Publisher) PublishV2ATXProof( malfeasanceProof := &MalfeasanceProof{ Version: 0, - Domain: domain, + Domain: InvalidActivation, Proof: proof, } if err := p.publisher.Publish(ctx, pubsub.MalfeasanceProof2, codec.MustEncode(malfeasanceProof)); err != nil { diff --git a/node/node.go b/node/node.go index 80c88271ce..645d240242 100644 --- a/node/node.go +++ b/node/node.go @@ -60,6 +60,7 @@ import ( "github.com/spacemeshos/go-spacemesh/layerpatrol" "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/malfeasance" + "github.com/spacemeshos/go-spacemesh/malfeasance2" "github.com/spacemeshos/go-spacemesh/mesh" "github.com/spacemeshos/go-spacemesh/metrics" "github.com/spacemeshos/go-spacemesh/metrics/public" @@ -136,6 +137,7 @@ const ( ConStateLogger = "conState" ExecutorLogger = "executor" MalfeasanceLogger = "malfeasance" + Malfeasance2Logger = "malfeasance2" BootstrapLogger = "bootstrap" ) @@ -377,50 +379,52 @@ func New(opts ...Option) *App { // App is the cli app singleton. type App struct { *cobra.Command - fileLock *flock.Flock - signers []*signing.EdSigner - Config *config.Config - db sql.StateDatabase - apiDB sql.StateDatabase - cachedDB *datastore.CachedDB - dbMetrics *dbmetrics.DBMetricsCollector - localDB sql.LocalDatabase - grpcPublicServer *grpcserver.Server - grpcPrivateServer *grpcserver.Server - grpcPostServer *grpcserver.Server - grpcTLSServer *grpcserver.Server - jsonAPIServer *grpcserver.JSONHTTPServer - grpcServices map[grpcserver.Service]grpcserver.ServiceAPI - pprofService *http.Server - profilerService *pyroscope.Profiler - syncer *syncer.Syncer - proposalBuilder *miner.ProposalBuilder - mesh *mesh.Mesh - atxsdata *atxsdata.Data - clock *timesync.NodeClock - hare3 *hare3.Hare - hare4 *hare4.Hare - hareResultsChan chan hare4.ConsensusOutput - hOracle *eligibility.Oracle - blockGen *blocks.Generator - certifier *blocks.Certifier - atxBuilder *activation.Builder - atxHandler *activation.Handler - txHandler *txs.TxHandler - validator *activation.Validator - edVerifier *signing.EdVerifier - beaconProtocol *beacon.ProtocolDriver - log log.Log - syncLogger log.Log - conState *txs.ConservativeState - fetcher *fetch.Fetch - ptimesync *peersync.Sync - updater *bootstrap.Updater - poetDb *activation.PoetDb - postVerifier activation.PostVerifier - postSupervisor *activation.PostSupervisor - malfeasanceHandler *malfeasance.Handler - errCh chan error + fileLock *flock.Flock + signers []*signing.EdSigner + Config *config.Config + db sql.StateDatabase + apiDB sql.StateDatabase + cachedDB *datastore.CachedDB + dbMetrics *dbmetrics.DBMetricsCollector + localDB sql.LocalDatabase + grpcPublicServer *grpcserver.Server + grpcPrivateServer *grpcserver.Server + grpcPostServer *grpcserver.Server + grpcTLSServer *grpcserver.Server + jsonAPIServer *grpcserver.JSONHTTPServer + grpcServices map[grpcserver.Service]grpcserver.ServiceAPI + pprofService *http.Server + profilerService *pyroscope.Profiler + syncer *syncer.Syncer + proposalBuilder *miner.ProposalBuilder + mesh *mesh.Mesh + atxsdata *atxsdata.Data + clock *timesync.NodeClock + hare3 *hare3.Hare + hare4 *hare4.Hare + hareResultsChan chan hare4.ConsensusOutput + hOracle *eligibility.Oracle + blockGen *blocks.Generator + certifier *blocks.Certifier + atxBuilder *activation.Builder + atxHandler *activation.Handler + txHandler *txs.TxHandler + validator *activation.Validator + edVerifier *signing.EdVerifier + beaconProtocol *beacon.ProtocolDriver + log log.Log + syncLogger log.Log + conState *txs.ConservativeState + fetcher *fetch.Fetch + ptimesync *peersync.Sync + updater *bootstrap.Updater + poetDb *activation.PoetDb + postVerifier activation.PostVerifier + postSupervisor *activation.PostSupervisor + malfeasanceHandler *malfeasance.Handler + malfeasance2Handler *malfeasance2.Handler + malfeasance2Publisher *malfeasance2.Publisher + errCh chan error host *p2p.Host @@ -754,26 +758,19 @@ func (app *App) initServices(ctx context.Context) error { return blockssync.Sync(ctx, flog.Zap(), msh.MissingBlocks(), fetcher) }) - malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() - // malfeasancePublisher := malfeasance.NewPublisher( - // malfeasanceLogger, - // app.cachedDB, - // trtl, - // app.host, - // ) - - // malfeasancePublisher2 := malfeasance2.NewPublisher( - // malfeasanceLogger, - // app.cachedDB, - // trtl, - // app.host, - // ) - - // atxMalPublisher := activation.NewATXMalfeasancePublisher( - // malfeasancePublisher, - // malfeasancePublisher2, - // ) + malfeasanceLogger2 := app.addLogger(MalfeasanceLogger, lg).Zap() + app.malfeasance2Publisher = malfeasance2.NewPublisher( + malfeasanceLogger2, + app.cachedDB, + trtl, + app.host, + ) + atxMalPublisher := activation.NewMalfeasanceHandlerV2( + app.malfeasance2Publisher, + app.edVerifier, + validator, + ) atxHandler := activation.NewHandler( app.host.ID(), app.cachedDB, @@ -784,8 +781,8 @@ func (app *App) initServices(ctx context.Context) error { fetcher, goldenATXID, validator, + atxMalPublisher, beaconProtocol, - // atxMalPublisher, trtl, app.addLogger(ATXHandlerLogger, lg).Zap(), activation.WithTickSize(app.Config.TickSize), @@ -1142,6 +1139,7 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("init post service: %w", err) } + malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() activationMH := activation.NewMalfeasanceHandler( app.cachedDB, malfeasanceLogger, From 558971c38781734990399dceb8100757825fc043 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:14:57 +0000 Subject: [PATCH 14/66] Add legacy malfeasance publisher service --- activation/e2e/atx_merge_test.go | 5 +- activation/e2e/builds_atx_v2_test.go | 3 +- activation/e2e/checkpoint_merged_test.go | 7 +- activation/e2e/checkpoint_test.go | 5 +- activation/handler.go | 90 ++++++------ activation/handler_test.go | 130 +++++++++-------- activation/handler_v1.go | 89 +++++------- activation/handler_v1_test.go | 173 ++++++++++------------- activation/handler_v2_test.go | 6 +- activation/interface.go | 9 ++ activation/mocks.go | 63 +++++++++ activation/poetdb_test.go | 6 +- checkpoint/recovery_test.go | 3 +- malfeasance/interface.go | 4 + malfeasance/mocks.go | 62 ++++++++ malfeasance/publisher.go | 24 +++- node/node.go | 170 +++++++++++----------- 17 files changed, 491 insertions(+), 358 deletions(-) diff --git a/activation/e2e/atx_merge_test.go b/activation/e2e/atx_merge_test.go index 311f1bafcc..46ccddd533 100644 --- a/activation/e2e/atx_merge_test.go +++ b/activation/e2e/atx_merge_test.go @@ -25,7 +25,6 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" @@ -275,9 +274,9 @@ func Test_MarryAndMerge(t *testing.T) { ) require.NoError(t, err) - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -288,11 +287,11 @@ func Test_MarryAndMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/builds_atx_v2_test.go b/activation/e2e/builds_atx_v2_test.go index 99bdac8c16..f3992b6b49 100644 --- a/activation/e2e/builds_atx_v2_test.go +++ b/activation/e2e/builds_atx_v2_test.go @@ -119,6 +119,7 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -128,11 +129,11 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { atxsdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/checkpoint_merged_test.go b/activation/e2e/checkpoint_merged_test.go index f00b952a9c..c22196b931 100644 --- a/activation/e2e/checkpoint_merged_test.go +++ b/activation/e2e/checkpoint_merged_test.go @@ -23,7 +23,6 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/accounts" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -106,9 +105,9 @@ func Test_CheckpointAfterMerge(t *testing.T) { ) require.NoError(t, err) - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -118,11 +117,11 @@ func Test_CheckpointAfterMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -298,11 +297,11 @@ func Test_CheckpointAfterMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/checkpoint_test.go b/activation/e2e/checkpoint_test.go index f5d425a1c7..9f53321837 100644 --- a/activation/e2e/checkpoint_test.go +++ b/activation/e2e/checkpoint_test.go @@ -105,6 +105,7 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -114,11 +115,11 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -204,11 +205,11 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/handler.go b/activation/handler.go index 219829721d..b3013a2ba3 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -17,7 +17,6 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/log" - mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" @@ -67,10 +66,9 @@ func (v AtxVersions) Validate() error { // Handler processes the atxs received from all nodes and their validity status. type Handler struct { - local p2p.Peer - publisher pubsub.Publisher - logger *zap.Logger - versions []atxVersion + local p2p.Peer + logger *zap.Logger + versions []atxVersion // inProgress is used to avoid processing the same ATX multiple times in parallel. inProgress singleflight.Group @@ -102,21 +100,20 @@ func NewHandler( atxsdata *atxsdata.Data, edVerifier *signing.EdVerifier, c layerClock, - pub pubsub.Publisher, fetcher system.Fetcher, goldenATXID types.ATXID, nipostValidator nipostValidator, malPublisher atxMalfeasancePublisher, + legacyMalPublisher legacyMalfeasancePublisher, beacon atxReceiver, tortoise system.Tortoise, lg *zap.Logger, opts ...HandlerOption, ) *Handler { h := &Handler{ - local: local, - publisher: pub, - logger: lg, - versions: []atxVersion{{0, types.AtxV1}}, + local: local, + logger: lg, + versions: []atxVersion{{0, types.AtxV1}}, v1: &HandlerV1{ local: local, @@ -131,6 +128,7 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, + malPublisher: legacyMalPublisher, signers: make(map[types.NodeID]*signing.EdSigner), }, @@ -172,48 +170,52 @@ func (h *Handler) Register(sig *signing.EdSigner) { // HandleSyncedAtx handles atxs received by sync. func (h *Handler) HandleSyncedAtx(ctx context.Context, expHash types.Hash32, peer p2p.Peer, data []byte) error { - _, err := h.handleAtx(ctx, expHash, peer, data) - if err != nil && !errors.Is(err, errMalformedData) && !errors.Is(err, errKnownAtx) { + err := h.handleAtx(ctx, expHash, peer, data) + switch { + case errors.Is(err, errKnownAtx): + return nil + case errors.Is(err, errMalformedData): + h.logger.Debug("malformed atx", + log.ZContext(ctx), + zap.Stringer("sender", peer), + zap.Error(err), + ) + return err + case err != nil: h.logger.Warn("failed to process synced atx", log.ZContext(ctx), zap.Stringer("sender", peer), zap.Error(err), ) + return err } - if errors.Is(err, errKnownAtx) { - return nil - } - return err + return nil } // HandleGossipAtx handles the atx gossip data channel. func (h *Handler) HandleGossipAtx(ctx context.Context, peer p2p.Peer, msg []byte) error { - proof, err := h.handleAtx(ctx, types.EmptyHash32, peer, msg) - if err != nil && !errors.Is(err, errMalformedData) && !errors.Is(err, errKnownAtx) { + err := h.handleAtx(ctx, types.EmptyHash32, peer, msg) + switch { + case errors.Is(err, errKnownAtx) && peer == h.local: + return nil + case errors.Is(err, errKnownAtx): + return errKnownAtx + case errors.Is(err, errMalformedData): + h.logger.Debug("malformed atx gossip", + log.ZContext(ctx), + zap.Stringer("sender", peer), + zap.Error(err), + ) + return err + case err != nil: h.logger.Warn("failed to process atx gossip", log.ZContext(ctx), zap.Stringer("sender", peer), zap.Error(err), ) + return err } - if errors.Is(err, errKnownAtx) && peer == h.local { - return nil - } - - // broadcast malfeasance proof last as the verification of the proof will take place - // in the same goroutine - if proof != nil { - gossip := mwire.MalfeasanceGossip{ - MalfeasanceProof: *proof, - } - encodedProof := codec.MustEncode(&gossip) - if err = h.publisher.Publish(ctx, pubsub.MalfeasanceProof, encodedProof); err != nil { - h.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) - return fmt.Errorf("broadcast atx malfeasance proof: %w", err) - } - return errMaliciousATX - } - return err + return nil } func (h *Handler) determineVersion(msg []byte) (*types.AtxVersion, error) { @@ -262,21 +264,21 @@ func (h *Handler) handleAtx( expHash types.Hash32, peer p2p.Peer, msg []byte, -) (*mwire.MalfeasanceProof, error) { +) error { receivedTime := time.Now() opaqueAtx, err := h.decodeATX(msg) if err != nil { - return nil, fmt.Errorf("%w: decoding ATX: %w", pubsub.ErrValidationReject, err) + return fmt.Errorf("%w: decoding ATX: %w", pubsub.ErrValidationReject, err) } id := opaqueAtx.ID() if (expHash != types.Hash32{}) && id.Hash32() != expHash { - return nil, fmt.Errorf("%w: atx want %s, got %s", errWrongHash, expHash.ShortString(), id.ShortString()) + return fmt.Errorf("%w: atx want %s, got %s", errWrongHash, expHash.ShortString(), id.ShortString()) } key := string(id.Bytes()) - proof, err, _ := h.inProgress.Do(key, func() (any, error) { + _, err, _ = h.inProgress.Do(key, func() (any, error) { h.logger.Debug("handling incoming atx", log.ZContext(ctx), zap.Stringer("atx_id", id), @@ -285,17 +287,15 @@ func (h *Handler) handleAtx( switch atx := opaqueAtx.(type) { case *wire.ActivationTxV1: - return h.v1.processATX(ctx, peer, atx, receivedTime) + return nil, h.v1.processATX(ctx, peer, atx, receivedTime) case *wire.ActivationTxV2: - // TODO(mafa): change function signature to not return proofs any more - return (*mwire.MalfeasanceProof)(nil), h.v2.processATX(ctx, peer, atx, receivedTime) + return nil, h.v2.processATX(ctx, peer, atx, receivedTime) default: panic("unreachable") } }) h.inProgress.Forget(key) - - return proof.(*mwire.MalfeasanceProof), err + return err } // Obtain the atxSignature of the given ATX. diff --git a/activation/handler_test.go b/activation/handler_test.go index 94d0143cbe..98f2244322 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/merkle-tree" poetShared "github.com/spacemeshos/poet/shared" + "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,7 +28,6 @@ import ( mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" - pubsubmocks "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -123,13 +123,13 @@ type handlerMocks struct { ctrl *gomock.Controller goldenATXID types.ATXID - mclock *MocklayerClock - mpub *pubsubmocks.MockPublisher - mockFetch *mocks.MockFetcher - mValidator *MocknipostValidator - mbeacon *MockatxReceiver - mtortoise *mocks.MockTortoise - mMalPublish *MockatxMalfeasancePublisher + mclock *MocklayerClock + mockFetch *mocks.MockFetcher + mValidator *MocknipostValidator + mbeacon *MockatxReceiver + mtortoise *mocks.MockTortoise + mLegacyMalPublish *MocklegacyMalfeasancePublisher + mMalPublish *MockatxMalfeasancePublisher } type testHandler struct { @@ -197,14 +197,14 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { return handlerMocks{ ctrl: ctrl, - goldenATXID: golden, - mclock: NewMocklayerClock(ctrl), - mpub: pubsubmocks.NewMockPublisher(ctrl), - mockFetch: mocks.NewMockFetcher(ctrl), - mValidator: NewMocknipostValidator(ctrl), - mbeacon: NewMockatxReceiver(ctrl), - mtortoise: mocks.NewMockTortoise(ctrl), - mMalPublish: NewMockatxMalfeasancePublisher(ctrl), + goldenATXID: golden, + mclock: NewMocklayerClock(ctrl), + mockFetch: mocks.NewMockFetcher(ctrl), + mValidator: NewMocknipostValidator(ctrl), + mbeacon: NewMockatxReceiver(ctrl), + mtortoise: mocks.NewMockTortoise(ctrl), + mLegacyMalPublish: NewMocklegacyMalfeasancePublisher(ctrl), + mMalPublish: NewMockatxMalfeasancePublisher(ctrl), } } @@ -221,11 +221,11 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio atxsdata.New(), edVerifier, mocks.mclock, - mocks.mpub, mocks.mockFetch, goldenATXID, mocks.mValidator, mocks.mMalPublish, + mocks.mLegacyMalPublish, mocks.mbeacon, mocks.mtortoise, lg, @@ -266,15 +266,26 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(gomock.Any()) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) + + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) + postVerifier.EXPECT(). + Verify(context.Background(), (*shared.Proof)(atx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) msg := codec.MustEncode(atx) require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.Hash32{}, p2p.NoPeer, msg)) - - // identity is still marked as malicious - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) }) t.Run("produced and published during gossip", func(t *testing.T) { @@ -302,32 +313,25 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(gomock.Any()) - msg := codec.MustEncode(atx) - postVerifier := NewMockPostVerifier(gomock.NewController(t)) - mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) - atxHdlr.mpub.EXPECT().Publish(gomock.Any(), pubsub.MalfeasanceProof, gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, data []byte) error { - var got mwire.MalfeasanceGossip - require.NoError(t, codec.Decode(data, &got)) - require.Equal(t, mwire.InvalidPostIndex, got.Proof.Type) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) + + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) postVerifier.EXPECT(). - Verify(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(errors.New("invalid")) - nodeID, err := mh.Validate(context.Background(), got.Proof.Data) + Verify(context.Background(), (*shared.Proof)(atx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) require.NoError(t, err) require.Equal(t, sig.NodeID(), nodeID) - p, ok := got.Proof.Data.(*mwire.InvalidPostIndexProof) - require.True(t, ok) - require.EqualValues(t, 2, p.InvalidIdx) return nil }) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg), errMaliciousATX) - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + msg := codec.MustEncode(atx) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg)) }) } @@ -452,14 +456,20 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx2.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) + + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + msg := codec.MustEncode(atx2) require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.Hash32{}, "", msg)) - - // identity is still marked as malicious - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) }) t.Run("produced and published during gossip", func(t *testing.T) { @@ -483,25 +493,21 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { }) atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - msg := codec.MustEncode(atx2) - mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) - atxHdlr.mpub.EXPECT().Publish(gomock.Any(), pubsub.MalfeasanceProof, gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, data []byte) error { - var got mwire.MalfeasanceGossip - require.NoError(t, codec.Decode(data, &got)) - require.Equal(t, mwire.MultipleATXs, got.Proof.Type) - nodeID, err := mh.Validate(context.Background(), got.Proof.Data) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx2.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) + + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) require.NoError(t, err) require.Equal(t, sig.NodeID(), nodeID) return nil - }) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg), errMaliciousATX) + }, + ) - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + msg := codec.MustEncode(atx2) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg)) }) } diff --git a/activation/handler_v1.go b/activation/handler_v1.go index e1c3fe4937..cddf8feed4 100644 --- a/activation/handler_v1.go +++ b/activation/handler_v1.go @@ -75,6 +75,7 @@ type HandlerV1 struct { tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher + malPublisher legacyMalfeasancePublisher signerMtx sync.Mutex signers map[types.NodeID]*signing.EdSigner @@ -168,10 +169,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( ctx context.Context, watx *wire.ActivationTxV1, received time.Time, -) (*types.ActivationTx, *mwire.MalfeasanceProof, error) { +) (*types.ActivationTx, error) { commitmentATX, err := h.commitment(watx) if err != nil { - return nil, nil, fmt.Errorf("commitment atx for %s not found: %w", watx.SmesherID, err) + return nil, fmt.Errorf("commitment atx for %s not found: %w", watx.SmesherID, err) } var effectiveNumUnits uint32 @@ -179,29 +180,29 @@ func (h *HandlerV1) syntacticallyValidateDeps( if watx.PrevATXID == types.EmptyATXID { err := h.nipostValidator.InitialNIPostChallengeV1(&watx.NIPostChallengeV1, h.cdb, h.goldenATXID) if err != nil { - return nil, nil, err + return nil, err } effectiveNumUnits = watx.NumUnits vrfNonce = *watx.VRFNonce } else { previous, err := atxs.Get(h.cdb, watx.PrevATXID) if err != nil { - return nil, nil, fmt.Errorf("fetching previous atx %s: %w", watx.PrevATXID, err) + return nil, fmt.Errorf("fetching previous atx %s: %w", watx.PrevATXID, err) } vrfNonce, err = h.validateNonInitialAtx(ctx, watx, previous, commitmentATX) if err != nil { - return nil, nil, err + return nil, err } prevUnits, err := atxs.Units(h.cdb, watx.PrevATXID, watx.SmesherID) if err != nil { - return nil, nil, fmt.Errorf("fetching previous atx units: %w", err) + return nil, fmt.Errorf("fetching previous atx units: %w", err) } effectiveNumUnits = min(prevUnits, watx.NumUnits) } err = h.nipostValidator.PositioningAtx(watx.PositioningATXID, h.cdb, h.goldenATXID, watx.PublishEpoch) if err != nil { - return nil, nil, err + return nil, err } expectedChallengeHash := watx.NIPostChallengeV1.Hash() @@ -229,10 +230,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( ) malicious, err := identities.IsMalicious(h.cdb, watx.SmesherID) if err != nil { - return nil, nil, fmt.Errorf("check if smesher is malicious: %w", err) + return nil, fmt.Errorf("check if smesher is malicious: %w", err) } if malicious { - return nil, nil, fmt.Errorf("smesher %s is known malfeasant", watx.SmesherID.ShortString()) + return nil, fmt.Errorf("smesher %s is known malfeasant", watx.SmesherID.ShortString()) } proof := &mwire.MalfeasanceProof{ Layer: watx.PublishEpoch.FirstLayer(), @@ -244,23 +245,20 @@ func (h *HandlerV1) syntacticallyValidateDeps( }, }, } - encodedProof := codec.MustEncode(proof) - if err := identities.SetMalicious(h.cdb, watx.SmesherID, encodedProof, time.Now()); err != nil { - return nil, nil, fmt.Errorf("adding malfeasance proof: %w", err) + if err := h.malPublisher.PublishProof(ctx, watx.SmesherID, proof); err != nil { + return nil, fmt.Errorf("publishing malfeasance proof: %w", err) } - h.cdb.CacheMalfeasanceProof(watx.SmesherID, encodedProof) - h.tortoise.OnMalfeasance(watx.SmesherID) - return nil, proof, nil + return nil, errMaliciousATX } if err != nil { - return nil, nil, fmt.Errorf("validating nipost: %w", err) + return nil, fmt.Errorf("validating nipost: %w", err) } var baseTickHeight uint64 if watx.PositioningATXID != h.goldenATXID { posAtx, err := h.cdb.GetAtx(watx.PositioningATXID) if err != nil { - return nil, nil, fmt.Errorf("failed to get positioning atx %s: %w", watx.PositioningATXID, err) + return nil, fmt.Errorf("failed to get positioning atx %s: %w", watx.PositioningATXID, err) } baseTickHeight = posAtx.TickHeight() } @@ -276,10 +274,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( atx.TickCount = leaves / h.tickSize hi, weight := bits.Mul64(uint64(atx.NumUnits), atx.TickCount) if hi != 0 { - return nil, nil, errors.New("atx weight would overflow uint64") + return nil, errors.New("atx weight would overflow uint64") } atx.Weight = weight - return atx, nil, nil + return atx, nil } func (h *HandlerV1) validateNonInitialAtx( @@ -379,9 +377,6 @@ func (h *HandlerV1) checkDoublePublish( Data: &atxProof, }, } - if err := identities.SetMalicious(tx, atx.SmesherID, codec.MustEncode(proof), time.Now()); err != nil { - return nil, fmt.Errorf("add malfeasance proof: %w", err) - } h.logger.Debug("smesher produced more than one atx in the same epoch", log.ZContext(ctx), @@ -460,10 +455,6 @@ func (h *HandlerV1) checkWrongPrevAtx( }, } - if err := identities.SetMalicious(tx, atx.SmesherID, codec.MustEncode(proof), time.Now()); err != nil { - return nil, fmt.Errorf("add malfeasance proof: %w", err) - } - h.logger.Debug("smesher referenced the wrong previous in published ATX", log.ZContext(ctx), zap.Stringer("smesher", atx.SmesherID), @@ -490,7 +481,7 @@ func (h *HandlerV1) storeAtx( ctx context.Context, atx *types.ActivationTx, watx *wire.ActivationTxV1, -) (*mwire.MalfeasanceProof, error) { +) error { var ( proof *mwire.MalfeasanceProof malicious bool @@ -519,18 +510,18 @@ func (h *HandlerV1) storeAtx( return nil }); err != nil { - return nil, fmt.Errorf("store atx: %w", err) + return fmt.Errorf("store atx: %w", err) } - atxs.AtxAdded(h.cdb, atx) if proof != nil { - h.cdb.CacheMalfeasanceProof(atx.SmesherID, codec.MustEncode(proof)) - h.tortoise.OnMalfeasance(atx.SmesherID) + if err := h.malPublisher.PublishProof(ctx, atx.SmesherID, proof); err != nil { + return fmt.Errorf("publishing malfeasance proof: %w", err) + } } - added := h.cacheAtx(ctx, atx, malicious || proof != nil) + atxs.AtxAdded(h.cdb, atx) h.beacon.OnAtx(atx) - if added != nil { + if added := h.cacheAtx(ctx, atx, malicious || proof != nil); added != nil { h.tortoise.OnAtx(atx.TargetEpoch(), atx.ID(), added) } @@ -538,7 +529,7 @@ func (h *HandlerV1) storeAtx( zap.Stringer("atx_id", atx.ID()), zap.Uint32("epoch_id", atx.PublishEpoch.Uint32()), ) - return proof, nil + return nil } func (h *HandlerV1) processATX( @@ -546,14 +537,14 @@ func (h *HandlerV1) processATX( peer p2p.Peer, watx *wire.ActivationTxV1, received time.Time, -) (*mwire.MalfeasanceProof, error) { +) error { if !h.edVerifier.Verify(signing.ATX, watx.SmesherID, watx.SignedBytes(), watx.Signature) { - return nil, fmt.Errorf("%w: invalid atx signature: %w", pubsub.ErrValidationReject, errMalformedData) + return fmt.Errorf("%w: invalid atx signature: %w", pubsub.ErrValidationReject, errMalformedData) } existing, _ := h.cdb.GetAtx(watx.ID()) if existing != nil { - return nil, fmt.Errorf("%w: %s", errKnownAtx, watx.ID()) + return fmt.Errorf("%w: %s", errKnownAtx, watx.ID()) } h.logger.Debug("processing atx", @@ -565,26 +556,25 @@ func (h *HandlerV1) processATX( err := h.syntacticallyValidate(ctx, watx) if err != nil { - return nil, fmt.Errorf("%w: validating atx %s: %w", pubsub.ErrValidationReject, watx.ID(), err) + return fmt.Errorf("%w: validating atx %s: %w", pubsub.ErrValidationReject, watx.ID(), err) } poetRef, atxIDs := collectAtxDeps(h.goldenATXID, watx) h.registerHashes(peer, poetRef, atxIDs) if err := h.fetchReferences(ctx, poetRef, atxIDs); err != nil { - return nil, fmt.Errorf("fetching references for atx %s: %w", watx.ID(), err) + return fmt.Errorf("fetching references for atx %s: %w", watx.ID(), err) } - atx, proof, err := h.syntacticallyValidateDeps(ctx, watx, received) - if err != nil { - return nil, fmt.Errorf("%w: validating atx %s (deps): %w", pubsub.ErrValidationReject, watx.ID(), err) - } - if proof != nil { - return proof, nil + atx, err := h.syntacticallyValidateDeps(ctx, watx, received) + switch { + case errors.Is(err, errMaliciousATX): + return nil + case err != nil: + return fmt.Errorf("%w: validating atx %s (deps): %w", pubsub.ErrValidationReject, watx.ID(), err) } - proof, err = h.storeAtx(ctx, atx, watx) - if err != nil { - return nil, fmt.Errorf("cannot store atx %s: %w", atx.ShortString(), err) + if err := h.storeAtx(ctx, atx, watx); err != nil { + return fmt.Errorf("cannot store atx %s: %w", atx.ShortString(), err) } if err := events.ReportNewActivation(atx); err != nil { @@ -597,9 +587,8 @@ func (h *HandlerV1) processATX( h.logger.Debug("new atx", log.ZContext(ctx), zap.Inline(atx), - zap.Bool("malicious", proof != nil), ) - return proof, err + return err } // registerHashes registers that the given peer should be asked for diff --git a/activation/handler_v1_test.go b/activation/handler_v1_test.go index 01ce917af0..e1fd1965d0 100644 --- a/activation/handler_v1_test.go +++ b/activation/handler_v1_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -52,6 +53,7 @@ func newV1TestHandler(tb testing.TB, goldenATXID types.ATXID) *v1TestHandler { fetcher: mocks.mockFetch, beacon: mocks.mbeacon, tortoise: mocks.mtortoise, + malPublisher: mocks.mLegacyMalPublish, signers: make(map[types.NodeID]*signing.EdSigner), }, handlerMocks: mocks, @@ -71,8 +73,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { prevAtx.NumUnits = 100 prevAtx.Sign(sig) atxHdlr.expectAtxV1(prevAtx, sig.NodeID()) - _, err := atxHdlr.processATX(context.Background(), "", prevAtx, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.processATX(context.Background(), "", prevAtx, time.Now())) otherSig, err := signing.NewEdSigner() require.NoError(t, err) @@ -80,8 +81,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { posAtx := newInitialATXv1(t, goldenATXID) posAtx.Sign(otherSig) atxHdlr.expectAtxV1(posAtx, otherSig.NodeID()) - _, err = atxHdlr.processATX(context.Background(), "", posAtx, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.processATX(context.Background(), "", posAtx, time.Now())) return atxHdlr, prevAtx, posAtx } @@ -103,7 +103,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, gomock.Any()) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -111,7 +111,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("valid atx with new VRF nonce", func(t *testing.T) { @@ -135,7 +134,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { VRFNonce(gomock.Any(), goldenATXID, newNonce, gomock.Any(), watx.NumUnits) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -143,7 +142,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("valid atx with decreasing num units", func(t *testing.T) { @@ -163,7 +161,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -171,7 +169,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx with increasing num units, no new VRF, old valid", func(t *testing.T) { @@ -192,7 +189,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().VRFNonce(gomock.Any(), goldenATXID, *prevAtx.VRFNonce, gomock.Any(), watx.NumUnits) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -200,7 +197,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, prevAtx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx with increasing num units, no new VRF, old invalid for new size", func(t *testing.T) { @@ -219,9 +215,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { VRFNonce(gomock.Any(), goldenATXID, *prevAtx.VRFNonce, gomock.Any(), watx.NumUnits). Return(errors.New("invalid VRF")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.ErrorContains(t, err, "invalid VRF") - require.Nil(t, proof) }) t.Run("valid initial atx", func(t *testing.T) { @@ -246,7 +241,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -254,7 +249,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(777)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx targeting wrong publish epoch", func(t *testing.T) { @@ -283,9 +277,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID). Return(errors.New("nipost error")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "nipost error") - require.Nil(t, proof) }) t.Run("failing positioning atx validation", func(t *testing.T) { @@ -303,9 +296,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch). Return(errors.New("bad positioning atx")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "bad positioning atx") - require.Nil(t, proof) }) t.Run("bad initial nipost challenge", func(t *testing.T) { @@ -326,9 +318,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), goldenATXID). Return(errors.New("bad initial nipost")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "bad initial nipost") - require.Nil(t, proof) }) t.Run("bad NIPoST", func(t *testing.T) { @@ -347,9 +338,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, errors.New("bad nipost")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "validating nipost: bad nipost") - require.Nil(t, proof) }) t.Run("invalid NIPoST", func(t *testing.T) { @@ -367,12 +357,27 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(watx.SmesherID) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), watx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) + + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) + postVerifier.EXPECT(). + Verify(context.Background(), (*shared.Proof)(watx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.InvalidPostIndex, proof.Proof.Type) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + require.ErrorIs(t, err, errMaliciousATX) }) t.Run("invalid NIPoST of known malfeasant", func(t *testing.T) { @@ -392,10 +397,10 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) + received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, fmt.Sprintf("smesher %s is known malfeasant", watx.SmesherID.ShortString())) - require.Nil(t, proof) }) t.Run("missing NodeID in initial atx", func(t *testing.T) { @@ -578,9 +583,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) atxFromDb, err := atxs.Get(atxHdlr.cdb, atx.ID()) require.NoError(t, err) @@ -599,17 +602,13 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) // Note: tortoise is not informed about the same ATX again - proof, err = atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) }) t.Run("stores ATX of malicious identity", func(t *testing.T) { @@ -627,9 +626,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) atxFromDb, err := atxs.Get(atxHdlr.cdb, atx.ID()) require.NoError(t, err) @@ -648,9 +645,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx0.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx0, watx0) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx0, watx0)) watx1 := newInitialATXv1(t, goldenATXID) watx1.Coinbase = types.GenerateAddress([]byte("aaaa")) @@ -661,20 +656,20 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx1.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.MultipleATXs, proof.Proof.Type) - mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) - nodeID, err := mh.Validate(context.Background(), proof.Proof.Data) - require.NoError(t, err) - require.Equal(t, sig.NodeID(), nodeID) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), sig.NodeID(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) }) t.Run("another atx for the same epoch for registered ID doesn't create a malfeasance proof", func(t *testing.T) { @@ -689,25 +684,17 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx0.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx0, watx0) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx0, watx0)) watx1 := newInitialATXv1(t, goldenATXID) watx1.Coinbase = types.GenerateAddress([]byte("aaaa")) watx1.Sign(sig) atx1 := toAtx(t, watx1) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) require.ErrorContains(t, - err, + atxHdlr.storeAtx(context.Background(), atx1, watx1), fmt.Sprintf("%s already published an ATX", sig.NodeID().ShortString()), ) - require.Nil(t, proof) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.False(t, malicious) }) t.Run("another atx with the same prevatx is considered malicious", func(t *testing.T) { @@ -721,9 +708,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == initialATX.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(initialATX.PublishEpoch+1, initialATX.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), wInitialATX, initialATX) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), wInitialATX, initialATX)) // valid first non-initial ATX watx1 := newChainedActivationTxV1(t, initialATX, goldenATXID) @@ -734,9 +719,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx1.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) watx2 := newChainedActivationTxV1(t, watx1, goldenATXID) watx2.Sign(sig) @@ -746,9 +729,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx2.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx2.PublishEpoch+1, watx2.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx2, watx2) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx2, watx2)) // third non-initial ATX references initial ATX as prevATX watx3 := newChainedActivationTxV1(t, initialATX, goldenATXID) @@ -760,16 +741,20 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx3.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx3.PublishEpoch+1, watx3.ID(), gomock.Any()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - proof, err = atxHdlr.storeAtx(context.Background(), atx3, watx3) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.InvalidPrevATX, proof.Proof.Type) - mh := NewInvalidPrevATXHandler(atxHdlr.cdb, atxHdlr.edVerifier) - nodeID, err := mh.Validate(context.Background(), proof.Proof.Data) - require.NoError(t, err) - require.Equal(t, sig.NodeID(), nodeID) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), sig.NodeID(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPrevATX, mp.Proof.Type) + + mh := NewInvalidPrevATXHandler(atxHdlr.cdb, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx3, watx3)) }) t.Run("another atx with the same prevatx for registered ID doesn't create a malfeasance proof", func(t *testing.T) { @@ -785,9 +770,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == wInitialATX.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(wInitialATX.PublishEpoch+1, wInitialATX.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), initialAtx, wInitialATX) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), initialAtx, wInitialATX)) // valid first non-initial ATX watx1 := newChainedActivationTxV1(t, wInitialATX, goldenATXID) @@ -798,9 +781,7 @@ func TestHandlerV1_StoreAtx(t *testing.T) { return atx.ID() == watx1.ID() })) atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) // second non-initial ATX references empty as prevATX watx2 := newInitialATXv1(t, goldenATXID) @@ -808,16 +789,10 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx2.Sign(sig) atx2 := toAtx(t, watx2) - proof, err = atxHdlr.storeAtx(context.Background(), atx2, watx2) require.ErrorContains(t, - err, + atxHdlr.storeAtx(context.Background(), atx2, watx2), fmt.Sprintf("%s referenced incorrect previous ATX", sig.NodeID().ShortString()), ) - require.Nil(t, proof) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.False(t, malicious) }) } diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index e4dcf94953..bcb9dc5749 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -2200,13 +2200,11 @@ func TestContextual_PreviousATX(t *testing.T) { prevATX := newInitialATXv1(t, golden) prevATX.Sign(sig1) atxHdlr.expectAtxV1(prevATX, prevATX.SmesherID) - _, err = atxHdlr.v1.processATX(context.Background(), "", prevATX, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.v1.processATX(context.Background(), "", prevATX, time.Now())) atxv1 := newChainedActivationTxV1(t, prevATX, prevATX.ID()) atxv1.Sign(sig1) atxHdlr.expectAtxV1(atxv1, atxv1.SmesherID) - _, err = atxHdlr.v1.processATX(context.Background(), "", atxv1, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.v1.processATX(context.Background(), "", atxv1, time.Now())) soloAtx := newSoloATXv2(t, atxv1.PublishEpoch+1, atxv1.ID(), atxv1.ID()) soloAtx.Sign(sig1) diff --git a/activation/interface.go b/activation/interface.go index 8e24eb6009..25a664ddd9 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/common/types" + mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -92,6 +93,14 @@ type syncer interface { RegisterForATXSynced() <-chan struct{} } +// legacyMalfeasancePublisher is an interface for publishing legacy malfeasance proofs. +// +// It is used int he ATXv1 handler and will be replaced in the future by the atxMalfeasancePublisher, which will +// wrap legacy proofs into the new encoding structure. +type legacyMalfeasancePublisher interface { + PublishProof(ctx context.Context, smesherID types.NodeID, proof *mwire.MalfeasanceProof) error +} + // atxMalfeasancePublisher is an interface for publishing atx malfeasance proofs. // // It encapsulates a specific malfeasance proof into a generic ATX malfeasance proof and publishes it by calling diff --git a/activation/mocks.go b/activation/mocks.go index ed56a3d9cc..3b8d1732cd 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -17,6 +17,7 @@ import ( wire "github.com/spacemeshos/go-spacemesh/activation/wire" types "github.com/spacemeshos/go-spacemesh/common/types" + wire0 "github.com/spacemeshos/go-spacemesh/malfeasance/wire" signing "github.com/spacemeshos/go-spacemesh/signing" certifier "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" nipost "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -1092,6 +1093,68 @@ func (c *MocksyncerRegisterForATXSyncedCall) DoAndReturn(f func() <-chan struct{ return c } +// MocklegacyMalfeasancePublisher is a mock of legacyMalfeasancePublisher interface. +type MocklegacyMalfeasancePublisher struct { + ctrl *gomock.Controller + recorder *MocklegacyMalfeasancePublisherMockRecorder + isgomock struct{} +} + +// MocklegacyMalfeasancePublisherMockRecorder is the mock recorder for MocklegacyMalfeasancePublisher. +type MocklegacyMalfeasancePublisherMockRecorder struct { + mock *MocklegacyMalfeasancePublisher +} + +// NewMocklegacyMalfeasancePublisher creates a new mock instance. +func NewMocklegacyMalfeasancePublisher(ctrl *gomock.Controller) *MocklegacyMalfeasancePublisher { + mock := &MocklegacyMalfeasancePublisher{ctrl: ctrl} + mock.recorder = &MocklegacyMalfeasancePublisherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MocklegacyMalfeasancePublisher) EXPECT() *MocklegacyMalfeasancePublisherMockRecorder { + return m.recorder +} + +// PublishProof mocks base method. +func (m *MocklegacyMalfeasancePublisher) PublishProof(ctx context.Context, smesherID types.NodeID, proof *wire0.MalfeasanceProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublishProof", ctx, smesherID, proof) + ret0, _ := ret[0].(error) + return ret0 +} + +// PublishProof indicates an expected call of PublishProof. +func (mr *MocklegacyMalfeasancePublisherMockRecorder) PublishProof(ctx, smesherID, proof any) *MocklegacyMalfeasancePublisherPublishProofCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishProof", reflect.TypeOf((*MocklegacyMalfeasancePublisher)(nil).PublishProof), ctx, smesherID, proof) + return &MocklegacyMalfeasancePublisherPublishProofCall{Call: call} +} + +// MocklegacyMalfeasancePublisherPublishProofCall wrap *gomock.Call +type MocklegacyMalfeasancePublisherPublishProofCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocklegacyMalfeasancePublisherPublishProofCall) Return(arg0 error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocklegacyMalfeasancePublisherPublishProofCall) Do(f func(context.Context, types.NodeID, *wire0.MalfeasanceProof) error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocklegacyMalfeasancePublisherPublishProofCall) DoAndReturn(f func(context.Context, types.NodeID, *wire0.MalfeasanceProof) error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockatxMalfeasancePublisher is a mock of atxMalfeasancePublisher interface. type MockatxMalfeasancePublisher struct { ctrl *gomock.Controller diff --git a/activation/poetdb_test.go b/activation/poetdb_test.go index 592e37ee55..a6d03d65e5 100644 --- a/activation/poetdb_test.go +++ b/activation/poetdb_test.go @@ -20,7 +20,7 @@ import ( ) var ( - proof *types.PoetProofMessage + poetProofMsg *types.PoetProofMessage createProofOnce sync.Once ) @@ -47,7 +47,7 @@ func getPoetProof(tb testing.TB) types.PoetProofMessage { ) require.NoError(tb, err) - proof = &types.PoetProofMessage{ + poetProofMsg = &types.PoetProofMessage{ PoetProof: types.PoetProof{ MerkleProof: *merkleProof, LeafCount: leaves, @@ -57,7 +57,7 @@ func getPoetProof(tb testing.TB) types.PoetProofMessage { Statement: types.BytesToHash(challenge), } }) - return *proof + return *poetProofMsg } func TestPoetDbHappyFlow(t *testing.T) { diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index a025945ce8..0236727866 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -252,6 +252,7 @@ func validateAndPreserveData( mFetch := smocks.NewMockFetcher(ctrl) mValidator := activation.NewMocknipostValidator(ctrl) mMalPublisher := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mReceiver := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) cdb := datastore.NewCachedDB(db, lg) @@ -262,11 +263,11 @@ func validateAndPreserveData( atxsdata.New(), signing.NewEdVerifier(), mclock, - nil, mFetch, goldenAtx, mValidator, mMalPublisher, + mLegacyPublish, mReceiver, mTortoise, lg, diff --git a/malfeasance/interface.go b/malfeasance/interface.go index 3486d5878f..79883b45a8 100644 --- a/malfeasance/interface.go +++ b/malfeasance/interface.go @@ -15,6 +15,10 @@ type tortoise interface { OnMalfeasance(types.NodeID) } +type syncer interface { + ListenToATXGossip() bool +} + type MalfeasanceHandler interface { Validate(ctx context.Context, data wire.ProofData) (types.NodeID, error) Info(data wire.ProofData) (map[string]string, error) diff --git a/malfeasance/mocks.go b/malfeasance/mocks.go index 5ab467d295..18dc4e9ec5 100644 --- a/malfeasance/mocks.go +++ b/malfeasance/mocks.go @@ -79,6 +79,68 @@ func (c *MocktortoiseOnMalfeasanceCall) DoAndReturn(f func(types.NodeID)) *Mockt return c } +// Mocksyncer is a mock of syncer interface. +type Mocksyncer struct { + ctrl *gomock.Controller + recorder *MocksyncerMockRecorder + isgomock struct{} +} + +// MocksyncerMockRecorder is the mock recorder for Mocksyncer. +type MocksyncerMockRecorder struct { + mock *Mocksyncer +} + +// NewMocksyncer creates a new mock instance. +func NewMocksyncer(ctrl *gomock.Controller) *Mocksyncer { + mock := &Mocksyncer{ctrl: ctrl} + mock.recorder = &MocksyncerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocksyncer) EXPECT() *MocksyncerMockRecorder { + return m.recorder +} + +// ListenToATXGossip mocks base method. +func (m *Mocksyncer) ListenToATXGossip() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListenToATXGossip") + ret0, _ := ret[0].(bool) + return ret0 +} + +// ListenToATXGossip indicates an expected call of ListenToATXGossip. +func (mr *MocksyncerMockRecorder) ListenToATXGossip() *MocksyncerListenToATXGossipCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListenToATXGossip", reflect.TypeOf((*Mocksyncer)(nil).ListenToATXGossip)) + return &MocksyncerListenToATXGossipCall{Call: call} +} + +// MocksyncerListenToATXGossipCall wrap *gomock.Call +type MocksyncerListenToATXGossipCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocksyncerListenToATXGossipCall) Return(arg0 bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocksyncerListenToATXGossipCall) Do(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocksyncerListenToATXGossipCall) DoAndReturn(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockMalfeasanceHandler is a mock of MalfeasanceHandler interface. type MockMalfeasanceHandler struct { ctrl *gomock.Controller diff --git a/malfeasance/publisher.go b/malfeasance/publisher.go index 9fe72a6bc0..1969a18ce3 100644 --- a/malfeasance/publisher.go +++ b/malfeasance/publisher.go @@ -19,6 +19,7 @@ type Publisher struct { logger *zap.Logger cdb *datastore.CachedDB tortoise tortoise + sync syncer publisher pubsub.Publisher } @@ -26,31 +27,50 @@ func NewPublisher( logger *zap.Logger, cdb *datastore.CachedDB, tortoise tortoise, + sync syncer, publisher pubsub.Publisher, ) *Publisher { return &Publisher{ logger: logger, cdb: cdb, tortoise: tortoise, + sync: sync, publisher: publisher, } } // Publishes a malfeasance proof to the network. func (p *Publisher) PublishProof(ctx context.Context, smesherID types.NodeID, proof *wire.MalfeasanceProof) error { - err := identities.SetMalicious(p.cdb, smesherID, codec.MustEncode(proof), time.Now()) + malicious, err := identities.IsMalicious(p.cdb, smesherID) if err != nil { + return fmt.Errorf("check if smesher is malicious: %w", err) + } + if malicious { + p.logger.Debug("smesher is already marked as malicious", zap.String("smesher_id", smesherID.ShortString())) + return nil + } + + if err := identities.SetMalicious(p.cdb, smesherID, codec.MustEncode(proof), time.Now()); err != nil { return fmt.Errorf("adding malfeasance proof: %w", err) } + p.cdb.CacheMalfeasanceProof(smesherID, codec.MustEncode(proof)) p.tortoise.OnMalfeasance(smesherID) + // Only gossip the proof if we are synced (to not spam the network with proofs others probably already have). + if !p.sync.ListenToATXGossip() { + p.logger.Debug("not synced, not broadcasting malfeasance proof", + zap.String("smesher_id", smesherID.ShortString()), + ) + return nil + } gossip := wire.MalfeasanceGossip{ MalfeasanceProof: *proof, } - if err = p.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { + if err := p.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { p.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) return fmt.Errorf("broadcast atx malfeasance proof: %w", err) } + return nil } diff --git a/node/node.go b/node/node.go index 645d240242..da0869333b 100644 --- a/node/node.go +++ b/node/node.go @@ -379,52 +379,50 @@ func New(opts ...Option) *App { // App is the cli app singleton. type App struct { *cobra.Command - fileLock *flock.Flock - signers []*signing.EdSigner - Config *config.Config - db sql.StateDatabase - apiDB sql.StateDatabase - cachedDB *datastore.CachedDB - dbMetrics *dbmetrics.DBMetricsCollector - localDB sql.LocalDatabase - grpcPublicServer *grpcserver.Server - grpcPrivateServer *grpcserver.Server - grpcPostServer *grpcserver.Server - grpcTLSServer *grpcserver.Server - jsonAPIServer *grpcserver.JSONHTTPServer - grpcServices map[grpcserver.Service]grpcserver.ServiceAPI - pprofService *http.Server - profilerService *pyroscope.Profiler - syncer *syncer.Syncer - proposalBuilder *miner.ProposalBuilder - mesh *mesh.Mesh - atxsdata *atxsdata.Data - clock *timesync.NodeClock - hare3 *hare3.Hare - hare4 *hare4.Hare - hareResultsChan chan hare4.ConsensusOutput - hOracle *eligibility.Oracle - blockGen *blocks.Generator - certifier *blocks.Certifier - atxBuilder *activation.Builder - atxHandler *activation.Handler - txHandler *txs.TxHandler - validator *activation.Validator - edVerifier *signing.EdVerifier - beaconProtocol *beacon.ProtocolDriver - log log.Log - syncLogger log.Log - conState *txs.ConservativeState - fetcher *fetch.Fetch - ptimesync *peersync.Sync - updater *bootstrap.Updater - poetDb *activation.PoetDb - postVerifier activation.PostVerifier - postSupervisor *activation.PostSupervisor - malfeasanceHandler *malfeasance.Handler - malfeasance2Handler *malfeasance2.Handler - malfeasance2Publisher *malfeasance2.Publisher - errCh chan error + fileLock *flock.Flock + signers []*signing.EdSigner + Config *config.Config + db sql.StateDatabase + apiDB sql.StateDatabase + cachedDB *datastore.CachedDB + dbMetrics *dbmetrics.DBMetricsCollector + localDB sql.LocalDatabase + grpcPublicServer *grpcserver.Server + grpcPrivateServer *grpcserver.Server + grpcPostServer *grpcserver.Server + grpcTLSServer *grpcserver.Server + jsonAPIServer *grpcserver.JSONHTTPServer + grpcServices map[grpcserver.Service]grpcserver.ServiceAPI + pprofService *http.Server + profilerService *pyroscope.Profiler + syncer *syncer.Syncer + proposalBuilder *miner.ProposalBuilder + mesh *mesh.Mesh + atxsdata *atxsdata.Data + clock *timesync.NodeClock + hare3 *hare3.Hare + hare4 *hare4.Hare + hareResultsChan chan hare4.ConsensusOutput + hOracle *eligibility.Oracle + blockGen *blocks.Generator + certifier *blocks.Certifier + atxBuilder *activation.Builder + atxHandler *activation.Handler + txHandler *txs.TxHandler + validator *activation.Validator + edVerifier *signing.EdVerifier + beaconProtocol *beacon.ProtocolDriver + log log.Log + syncLogger log.Log + conState *txs.ConservativeState + fetcher *fetch.Fetch + ptimesync *peersync.Sync + updater *bootstrap.Updater + poetDb *activation.PoetDb + postVerifier activation.PostVerifier + postSupervisor *activation.PostSupervisor + malfeasanceHandler *malfeasance.Handler + errCh chan error host *p2p.Host @@ -758,16 +756,55 @@ func (app *App) initServices(ctx context.Context) error { return blockssync.Sync(ctx, flog.Zap(), msh.MissingBlocks(), fetcher) }) - malfeasanceLogger2 := app.addLogger(MalfeasanceLogger, lg).Zap() - app.malfeasance2Publisher = malfeasance2.NewPublisher( - malfeasanceLogger2, + patrol := layerpatrol.New() + syncerConf := app.Config.Sync + syncerConf.HareDelayLayers = app.Config.Tortoise.Zdist + syncerConf.SyncCertDistance = app.Config.Tortoise.Hdist + syncerConf.Standalone = app.Config.Standalone + + app.syncLogger = app.addLogger(SyncLogger, lg) + newSyncer := syncer.NewSyncer( + app.cachedDB, + app.clock, + msh, + trtl, + fetcher, + patrol, + app.certifier, + atxsync.New(fetcher, app.db, app.localDB, + atxsync.WithConfig(app.Config.Sync.AtxSync), + atxsync.WithLogger(app.syncLogger.Zap()), + ), + malsync.New(fetcher, app.db, app.localDB, + malsync.WithConfig(app.Config.Sync.MalSync), + malsync.WithLogger(app.syncLogger.Zap()), + malsync.WithPeerErrMetric(syncer.MalPeerError), + ), + syncer.WithConfig(syncerConf), + syncer.WithLogger(app.syncLogger.Zap()), + ) + // TODO(dshulyak) this needs to be improved, but dependency graph is a bit complicated + beaconProtocol.SetSyncState(newSyncer) + app.hOracle.SetSync(newSyncer) + + malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() + legacyMalPublisher := malfeasance.NewPublisher( + malfeasanceLogger, + app.cachedDB, + trtl, + newSyncer, + app.host, + ) + + malfeasance2Publisher := malfeasance2.NewPublisher( + app.addLogger(Malfeasance2Logger, lg).Zap(), app.cachedDB, trtl, app.host, ) atxMalPublisher := activation.NewMalfeasanceHandlerV2( - app.malfeasance2Publisher, + malfeasance2Publisher, app.edVerifier, validator, ) @@ -777,11 +814,11 @@ func (app *App) initServices(ctx context.Context) error { app.atxsdata, app.edVerifier, app.clock, - app.host, fetcher, goldenATXID, validator, atxMalPublisher, + legacyMalPublisher, beaconProtocol, trtl, app.addLogger(ATXHandlerLogger, lg).Zap(), @@ -858,39 +895,9 @@ func (app *App) initServices(ctx context.Context) error { app.certifier.Register(sig) } - patrol := layerpatrol.New() - syncerConf := app.Config.Sync - syncerConf.HareDelayLayers = app.Config.Tortoise.Zdist - syncerConf.SyncCertDistance = app.Config.Tortoise.Hdist - syncerConf.Standalone = app.Config.Standalone - if app.Config.P2P.MinPeers < app.Config.Sync.MalSync.MinSyncPeers { app.Config.Sync.MalSync.MinSyncPeers = max(1, app.Config.P2P.MinPeers) } - app.syncLogger = app.addLogger(SyncLogger, lg) - newSyncer := syncer.NewSyncer( - app.cachedDB, - app.clock, - msh, - trtl, - fetcher, - patrol, - app.certifier, - atxsync.New(fetcher, app.db, app.localDB, - atxsync.WithConfig(app.Config.Sync.AtxSync), - atxsync.WithLogger(app.syncLogger.Zap()), - ), - malsync.New(fetcher, app.db, app.localDB, - malsync.WithConfig(app.Config.Sync.MalSync), - malsync.WithLogger(app.syncLogger.Zap()), - malsync.WithPeerErrMetric(syncer.MalPeerError), - ), - syncer.WithConfig(syncerConf), - syncer.WithLogger(app.syncLogger.Zap()), - ) - // TODO(dshulyak) this needs to be improved, but dependency graph is a bit complicated - beaconProtocol.SetSyncState(newSyncer) - app.hOracle.SetSync(newSyncer) err = app.Config.HARE3.Validate(time.Duration(app.Config.Tortoise.Zdist) * app.Config.LayerDuration) if err != nil { @@ -1139,7 +1146,6 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("init post service: %w", err) } - malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() activationMH := activation.NewMalfeasanceHandler( app.cachedDB, malfeasanceLogger, From 81bd45d97cfbfed289168e7993439e727fc5c721 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:47:35 +0000 Subject: [PATCH 15/66] Fix nil pointer exception --- node/node.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/node.go b/node/node.go index da0869333b..e5be7e46fa 100644 --- a/node/node.go +++ b/node/node.go @@ -785,7 +785,6 @@ func (app *App) initServices(ctx context.Context) error { ) // TODO(dshulyak) this needs to be improved, but dependency graph is a bit complicated beaconProtocol.SetSyncState(newSyncer) - app.hOracle.SetSync(newSyncer) malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() legacyMalPublisher := malfeasance.NewPublisher( @@ -853,7 +852,7 @@ func (app *App) initServices(ctx context.Context) error { app.addLogger(TxHandlerLogger, lg).Zap(), ) - app.hOracle = eligibility.New( + hOracle := eligibility.New( beaconProtocol, app.db, app.atxsdata, @@ -862,7 +861,7 @@ func (app *App) initServices(ctx context.Context) error { eligibility.WithConfig(app.Config.HareEligibility), eligibility.WithLogger(app.addLogger(HareOracleLogger, lg).Zap()), ) - // TODO: genesisMinerWeight is set to app.Config.SpaceToCommit, because PoET ticks are currently hardcoded to 1 + hOracle.SetSync(newSyncer) bscfg := app.Config.Bootstrap bscfg.DataDir = app.Config.DataDir() @@ -1304,6 +1303,7 @@ func (app *App) initServices(ctx context.Context) error { app.poetDb = poetDb app.fetcher = fetcher app.beaconProtocol = beaconProtocol + app.hOracle = hOracle if !app.Config.TIME.Peersync.Disable { app.ptimesync = peersync.New( app.host, From 5b8a3b52394cfb00c5e8c0cec9c1825d926a27b1 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:01:59 +0000 Subject: [PATCH 16/66] Don't 0 index proofs (to avoid none = legacy double publish) --- activation/wire/malfeasance.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index 0ab62761e6..4220a7ee42 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -64,15 +64,15 @@ type ProofType byte const ( // TODO(mafa): legacy types for future migration to new malfeasance proofs. - LegacyDoublePublish ProofType = 0x00 - LegacyInvalidPost ProofType = 0x01 - LegacyInvalidPrevATX ProofType = 0x02 - - DoubleMarry ProofType = 0x10 - DoubleMerge ProofType = 0x11 - InvalidPost ProofType = 0x12 - InvalidPreviousV1 ProofType = 0x13 - InvalidPreviousV2 ProofType = 0x14 + LegacyDoublePublish ProofType = 0x01 + LegacyInvalidPost ProofType = 0x02 + LegacyInvalidPrevATX ProofType = 0x03 + + DoubleMarry ProofType = 0x11 + DoubleMerge ProofType = 0x12 + InvalidPost ProofType = 0x13 + InvalidPreviousV1 ProofType = 0x14 + InvalidPreviousV2 ProofType = 0x15 ) var proofTypes = map[ProofType]Proof{ From a2e45cc33e4388adfd616c73a3ac50c82118ea78 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:52:30 +0000 Subject: [PATCH 17/66] Update ATXv1 Handler to use dedicated malfeasance publisher --- activation/builder_v2_test.go | 3 +- activation/e2e/atx_merge_test.go | 20 +- activation/e2e/builds_atx_v2_test.go | 7 +- activation/e2e/checkpoint_merged_test.go | 21 +- activation/e2e/checkpoint_test.go | 12 +- activation/handler.go | 101 ++++---- activation/handler_test.go | 185 +++++++-------- activation/handler_v1.go | 136 +++++------ activation/handler_v1_test.go | 282 ++++++++++------------- activation/handler_v2.go | 2 +- activation/handler_v2_test.go | 69 +++--- activation/interface.go | 21 +- activation/mocks.go | 119 +++++++--- activation/poetdb_test.go | 6 +- checkpoint/recovery_test.go | 45 ++-- 15 files changed, 532 insertions(+), 497 deletions(-) diff --git a/activation/builder_v2_test.go b/activation/builder_v2_test.go index 0054147f4e..bb156484b7 100644 --- a/activation/builder_v2_test.go +++ b/activation/builder_v2_test.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -55,7 +56,7 @@ func TestBuilder_BuildsInitialAtxV2(t *testing.T) { atxHandler := newTestHandler(t, tab.goldenATXID, WithAtxVersions(AtxVersions{1: types.AtxV2})) atxHandler.expectInitialAtxV2(&atx) - require.NoError(t, atxHandler.HandleGossipAtx(context.Background(), "", got)) + require.NoError(t, atxHandler.HandleGossipAtx(context.Background(), p2p.NoPeer, got)) return nil }) require.Empty(t, atx.PreviousATXs) diff --git a/activation/e2e/atx_merge_test.go b/activation/e2e/atx_merge_test.go index a97c44b74e..2cbc7885d5 100644 --- a/activation/e2e/atx_merge_test.go +++ b/activation/e2e/atx_merge_test.go @@ -25,7 +25,7 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" @@ -275,9 +275,10 @@ func Test_MarryAndMerge(t *testing.T) { ) require.NoError(t, err) - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) tickSize := uint64(3) @@ -287,10 +288,11 @@ func Test_MarryAndMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, + mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -358,11 +360,11 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - return atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedIdAtx)) + return atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedIdAtx)) }) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(marriageATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(marriageATX)) require.NoError(t, err) // Verify marriage @@ -422,7 +424,7 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX)) require.NoError(t, err) // Step 3. verify the merged ATX @@ -473,7 +475,7 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX2)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX2)) require.NoError(t, err) atx, err = atxs.Get(db, mergedATX2.ID()) @@ -511,7 +513,7 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx)) require.NoError(t, err) atxFromDb, err := atxs.Get(db, atx.ID()) diff --git a/activation/e2e/builds_atx_v2_test.go b/activation/e2e/builds_atx_v2_test.go index 93832608eb..f3992b6b49 100644 --- a/activation/e2e/builds_atx_v2_test.go +++ b/activation/e2e/builds_atx_v2_test.go @@ -118,7 +118,9 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { edVerifier := signing.NewEdVerifier() mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( @@ -127,10 +129,11 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { atxsdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, + mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/checkpoint_merged_test.go b/activation/e2e/checkpoint_merged_test.go index 71f91691f3..88c2a87238 100644 --- a/activation/e2e/checkpoint_merged_test.go +++ b/activation/e2e/checkpoint_merged_test.go @@ -23,7 +23,7 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/accounts" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -106,9 +106,10 @@ func Test_CheckpointAfterMerge(t *testing.T) { ) require.NoError(t, err) - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( @@ -117,10 +118,11 @@ func Test_CheckpointAfterMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, + mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -183,9 +185,9 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), []types.ATXID{mergedIdAtx.ID()}, gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()).Times(2) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()).Times(2) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedIdAtx)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedIdAtx)) require.NoError(t, err) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(marriageATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(marriageATX)) require.NoError(t, err) // Step 2. Publish merged ATX together @@ -236,7 +238,7 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX)) require.NoError(t, err) // Step 3. Checkpoint @@ -296,10 +298,11 @@ func Test_CheckpointAfterMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, + mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -352,6 +355,6 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX2)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX2)) require.NoError(t, err) } diff --git a/activation/e2e/checkpoint_test.go b/activation/e2e/checkpoint_test.go index b7a2596cae..cf481facb7 100644 --- a/activation/e2e/checkpoint_test.go +++ b/activation/e2e/checkpoint_test.go @@ -102,9 +102,10 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata := atxsdata.New() atxVersions := activation.AtxVersions{0: types.AtxV2} edVerifier := signing.NewEdVerifier() - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( @@ -113,16 +114,18 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, + mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, activation.WithAtxVersions(atxVersions), ) + mpub := mocks.NewMockPublisher(ctrl) tab := activation.NewBuilder( activation.Config{GoldenATXID: goldenATX}, db, @@ -202,10 +205,11 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, + mMalPublish, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/handler.go b/activation/handler.go index 3960e484a7..b0ff36849c 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -17,7 +17,6 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/log" - mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" @@ -67,10 +66,9 @@ func (v AtxVersions) Validate() error { // Handler processes the atxs received from all nodes and their validity status. type Handler struct { - local p2p.Peer - publisher pubsub.Publisher - logger *zap.Logger - versions []atxVersion + local p2p.Peer + logger *zap.Logger + versions []atxVersion // inProgress is used to avoid processing the same ATX multiple times in parallel. inProgress singleflight.Group @@ -102,20 +100,20 @@ func NewHandler( atxsdata *atxsdata.Data, edVerifier *signing.EdVerifier, c layerClock, - pub pubsub.Publisher, fetcher system.Fetcher, goldenATXID types.ATXID, nipostValidator nipostValidator, - beacon AtxReceiver, + malPublisher atxMalfeasancePublisher, + legacyMalPublisher legacyMalfeasancePublisher, + beacon atxReceiver, tortoise system.Tortoise, lg *zap.Logger, opts ...HandlerOption, ) *Handler { h := &Handler{ - local: local, - publisher: pub, - logger: lg, - versions: []atxVersion{{0, types.AtxV1}}, + local: local, + logger: lg, + versions: []atxVersion{{0, types.AtxV1}}, v1: &HandlerV1{ local: local, @@ -130,6 +128,7 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, + malPublisher: legacyMalPublisher, signers: make(map[types.NodeID]*signing.EdSigner), }, @@ -146,7 +145,7 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, - malPublisher: &MalfeasancePublisher{}, // TODO(mafa): pass real publisher when available + malPublisher: malPublisher, }, } @@ -171,48 +170,52 @@ func (h *Handler) Register(sig *signing.EdSigner) { // HandleSyncedAtx handles atxs received by sync. func (h *Handler) HandleSyncedAtx(ctx context.Context, expHash types.Hash32, peer p2p.Peer, data []byte) error { - _, err := h.handleAtx(ctx, expHash, peer, data) - if err != nil && !errors.Is(err, errMalformedData) && !errors.Is(err, errKnownAtx) { + err := h.handleAtx(ctx, expHash, peer, data) + switch { + case errors.Is(err, errKnownAtx): + return nil + case errors.Is(err, errMalformedData): + h.logger.Debug("malformed atx", + log.ZContext(ctx), + zap.Stringer("sender", peer), + zap.Error(err), + ) + return err + case err != nil: h.logger.Warn("failed to process synced atx", log.ZContext(ctx), zap.Stringer("sender", peer), zap.Error(err), ) + return err } - if errors.Is(err, errKnownAtx) { - return nil - } - return err + return nil } // HandleGossipAtx handles the atx gossip data channel. func (h *Handler) HandleGossipAtx(ctx context.Context, peer p2p.Peer, msg []byte) error { - proof, err := h.handleAtx(ctx, types.EmptyHash32, peer, msg) - if err != nil && !errors.Is(err, errMalformedData) && !errors.Is(err, errKnownAtx) { + err := h.handleAtx(ctx, types.EmptyHash32, peer, msg) + switch { + case errors.Is(err, errKnownAtx) && peer == h.local: + return nil + case errors.Is(err, errKnownAtx): + return errKnownAtx + case errors.Is(err, errMalformedData): + h.logger.Debug("malformed atx gossip", + log.ZContext(ctx), + zap.Stringer("sender", peer), + zap.Error(err), + ) + return err + case err != nil: h.logger.Warn("failed to process atx gossip", log.ZContext(ctx), zap.Stringer("sender", peer), zap.Error(err), ) + return err } - if errors.Is(err, errKnownAtx) && peer == h.local { - return nil - } - - // broadcast malfeasance proof last as the verification of the proof will take place - // in the same goroutine - if proof != nil { - gossip := mwire.MalfeasanceGossip{ - MalfeasanceProof: *proof, - } - encodedProof := codec.MustEncode(&gossip) - if err = h.publisher.Publish(ctx, pubsub.MalfeasanceProof, encodedProof); err != nil { - h.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) - return fmt.Errorf("broadcast atx malfeasance proof: %w", err) - } - return errMaliciousATX - } - return err + return nil } func (h *Handler) determineVersion(msg []byte) (*types.AtxVersion, error) { @@ -256,26 +259,21 @@ func (h *Handler) decodeATX(msg []byte) (atx opaqueAtx, err error) { return atx, nil } -func (h *Handler) handleAtx( - ctx context.Context, - expHash types.Hash32, - peer p2p.Peer, - msg []byte, -) (*mwire.MalfeasanceProof, error) { +func (h *Handler) handleAtx(ctx context.Context, expHash types.Hash32, peer p2p.Peer, msg []byte) error { receivedTime := time.Now() opaqueAtx, err := h.decodeATX(msg) if err != nil { - return nil, fmt.Errorf("%w: decoding ATX: %w", pubsub.ErrValidationReject, err) + return fmt.Errorf("%w: decoding ATX: %w", pubsub.ErrValidationReject, err) } id := opaqueAtx.ID() - if (expHash != types.Hash32{}) && id.Hash32() != expHash { - return nil, fmt.Errorf("%w: atx want %s, got %s", errWrongHash, expHash.ShortString(), id.ShortString()) + if expHash != types.EmptyHash32 && id.Hash32() != expHash { + return fmt.Errorf("%w: atx want %s, got %s", errWrongHash, expHash.ShortString(), id.ShortString()) } key := string(id.Bytes()) - proof, err, _ := h.inProgress.Do(key, func() (any, error) { + _, err, _ = h.inProgress.Do(key, func() (any, error) { h.logger.Debug("handling incoming atx", log.ZContext(ctx), zap.Stringer("atx_id", id), @@ -284,16 +282,15 @@ func (h *Handler) handleAtx( switch atx := opaqueAtx.(type) { case *wire.ActivationTxV1: - return h.v1.processATX(ctx, peer, atx, receivedTime) + return nil, h.v1.processATX(ctx, peer, atx, receivedTime) case *wire.ActivationTxV2: - return (*mwire.MalfeasanceProof)(nil), h.v2.processATX(ctx, peer, atx, receivedTime) + return nil, h.v2.processATX(ctx, peer, atx, receivedTime) default: panic("unreachable") } }) h.inProgress.Forget(key) - - return proof.(*mwire.MalfeasanceProof), err + return err } // Obtain the atxSignature of the given ATX. diff --git a/activation/handler_test.go b/activation/handler_test.go index 7c30c86fd2..1ca409cdc2 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/merkle-tree" poetShared "github.com/spacemeshos/poet/shared" + "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,7 +28,6 @@ import ( mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" - pubsubmocks "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -123,13 +123,13 @@ type handlerMocks struct { ctrl *gomock.Controller goldenATXID types.ATXID - mclock *MocklayerClock - mpub *pubsubmocks.MockPublisher - mockFetch *mocks.MockFetcher - mValidator *MocknipostValidator - mbeacon *MockAtxReceiver - mtortoise *mocks.MockTortoise - mMalPublish *MockatxMalfeasancePublisher + mClock *MocklayerClock + mockFetch *mocks.MockFetcher + mValidator *MocknipostValidator + mBeacon *MockatxReceiver + mTortoise *mocks.MockTortoise + mLegacyMalPublish *MocklegacyMalfeasancePublisher + mMalPublish *MockatxMalfeasancePublisher } type testHandler struct { @@ -152,7 +152,7 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID for _, opt := range opts { opt(&settings) } - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) if atx.VRFNonce != nil { h.mValidator.EXPECT(). @@ -188,8 +188,8 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID NIPost(gomock.Any(), nodeId, h.goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(settings.poetLeaves, nil) h.mValidator.EXPECT().IsVerifyingFullPost().Return(!settings.distributedPost) - h.mbeacon.EXPECT().OnAtx(gomock.Any()) - h.mtortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) + h.mBeacon.EXPECT().OnAtx(gomock.Any()) + h.mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) } func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { @@ -197,14 +197,14 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { return handlerMocks{ ctrl: ctrl, - goldenATXID: golden, - mclock: NewMocklayerClock(ctrl), - mpub: pubsubmocks.NewMockPublisher(ctrl), - mockFetch: mocks.NewMockFetcher(ctrl), - mValidator: NewMocknipostValidator(ctrl), - mbeacon: NewMockAtxReceiver(ctrl), - mtortoise: mocks.NewMockTortoise(ctrl), - mMalPublish: NewMockatxMalfeasancePublisher(ctrl), + goldenATXID: golden, + mClock: NewMocklayerClock(ctrl), + mockFetch: mocks.NewMockFetcher(ctrl), + mValidator: NewMocknipostValidator(ctrl), + mBeacon: NewMockatxReceiver(ctrl), + mTortoise: mocks.NewMockTortoise(ctrl), + mLegacyMalPublish: NewMocklegacyMalfeasancePublisher(ctrl), + mMalPublish: NewMockatxMalfeasancePublisher(ctrl), } } @@ -215,20 +215,19 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio edVerifier := signing.NewEdVerifier() mocks := newTestHandlerMocks(tb, goldenATXID) - // TODO(mafa): make mandatory parameter when real publisher is available - opts = append(opts, func(h *Handler) { h.v2.malPublisher = mocks.mMalPublish }) atxHdlr := NewHandler( "localID", cdb, atxsdata.New(), edVerifier, - mocks.mclock, - mocks.mpub, + mocks.mClock, mocks.mockFetch, goldenATXID, mocks.mValidator, - mocks.mbeacon, - mocks.mtortoise, + mocks.mMalPublish, + mocks.mLegacyMalPublish, + mocks.mBeacon, + mocks.mTortoise, lg, opts..., ) @@ -256,7 +255,7 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT().VRFNonce(atx.SmesherID, goldenATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), gomock.Any(), *atx.CommitmentATXID, gomock.Any(), gomock.Any(), atx.NumUnits) @@ -267,15 +266,26 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(gomock.Any()) - msg := codec.MustEncode(atx) - require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.Hash32{}, p2p.NoPeer, msg)) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) - // identity is still marked as malicious - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) + postVerifier.EXPECT(). + Verify(context.Background(), (*shared.Proof)(atx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + + msg := codec.MustEncode(atx) + require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.EmptyHash32, p2p.NoPeer, msg)) }) t.Run("produced and published during gossip", func(t *testing.T) { @@ -292,7 +302,7 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT().VRFNonce(atx.SmesherID, goldenATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), gomock.Any(), *atx.CommitmentATXID, gomock.Any(), gomock.Any(), atx.NumUnits) @@ -303,32 +313,25 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(gomock.Any()) - msg := codec.MustEncode(atx) - postVerifier := NewMockPostVerifier(gomock.NewController(t)) - mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) - atxHdlr.mpub.EXPECT().Publish(gomock.Any(), pubsub.MalfeasanceProof, gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, data []byte) error { - var got mwire.MalfeasanceGossip - require.NoError(t, codec.Decode(data, &got)) - require.Equal(t, mwire.InvalidPostIndex, got.Proof.Type) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) + + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) postVerifier.EXPECT(). - Verify(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(errors.New("invalid")) - nodeID, err := mh.Validate(context.Background(), got.Proof.Data) + Verify(context.Background(), (*shared.Proof)(atx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) require.NoError(t, err) require.Equal(t, sig.NodeID(), nodeID) - p, ok := got.Proof.Data.(*mwire.InvalidPostIndexProof) - require.True(t, ok) - require.EqualValues(t, 2, p.InvalidIdx) return nil }) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg), errMaliciousATX) - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + msg := codec.MustEncode(atx) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg)) }) } @@ -342,7 +345,7 @@ func TestHandler_ProcessAtxStoresNewVRFNonce(t *testing.T) { atx1 := newInitialATXv1(t, goldenATXID) atx1.Sign(sig) atxHdlr.expectAtxV1(atx1, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1))) got, err := atxs.VRFNonce(atxHdlr.cdb, sig.NodeID(), atx1.PublishEpoch+1) require.NoError(t, err) @@ -353,7 +356,7 @@ func TestHandler_ProcessAtxStoresNewVRFNonce(t *testing.T) { atx2.VRFNonce = (*uint64)(&nonce2) atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx2))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx2))) got, err = atxs.VRFNonce(atxHdlr.cdb, sig.NodeID(), atx2.PublishEpoch+1) require.NoError(t, err) @@ -373,7 +376,7 @@ func TestHandler_HandleGossipAtx(t *testing.T) { second.Sign(sig) // the poet is missing - atxHdlr.mclock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) atxHdlr.mockFetch.EXPECT().RegisterPeerHashes( p2p.NoPeer, []types.Hash32{first.ID().Hash32(), types.Hash32(second.NIPost.PostMetadata.Challenge)}, @@ -382,27 +385,27 @@ func TestHandler_HandleGossipAtx(t *testing.T) { GetPoetProof(gomock.Any(), types.Hash32(second.NIPost.PostMetadata.Challenge)). Return(errors.New("missing poet proof")) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(second)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(second)) require.ErrorContains(t, err, "missing poet proof") // deps (prevATX, posATX, commitmentATX) are missing - atxHdlr.mclock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) atxHdlr.mockFetch.EXPECT().RegisterPeerHashes( p2p.NoPeer, []types.Hash32{first.ID().Hash32(), types.Hash32(second.NIPost.PostMetadata.Challenge)}, ) atxHdlr.mockFetch.EXPECT().GetPoetProof(gomock.Any(), types.Hash32(second.NIPost.PostMetadata.Challenge)) atxHdlr.mockFetch.EXPECT().GetAtxs(gomock.Any(), []types.ATXID{second.PrevATXID}, gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(second)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(second)) require.ErrorIs(t, err, sql.ErrNotFound) // valid first comes in atxHdlr.expectAtxV1(first, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(first))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(first))) // second is now valid (deps are in) atxHdlr.expectAtxV1(second, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(second))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(second))) } func TestHandler_HandleParallelGossipAtxV1(t *testing.T) { @@ -423,7 +426,7 @@ func TestHandler_HandleParallelGossipAtxV1(t *testing.T) { var eg errgroup.Group for range 10 { eg.Go(func() error { - return atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1)) + return atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1)) }) } @@ -441,7 +444,7 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { atx1 := newInitialATXv1(t, goldenATXID) atx1.Sign(sig) atxHdlr.expectAtxV1(atx1, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1))) malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) require.NoError(t, err) @@ -453,14 +456,20 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - msg := codec.MustEncode(atx2) - require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.Hash32{}, "", msg)) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx2.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) - // identity is still marked as malicious - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + + msg := codec.MustEncode(atx2) + require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.EmptyHash32, p2p.NoPeer, msg)) }) t.Run("produced and published during gossip", func(t *testing.T) { @@ -473,7 +482,7 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { atx1 := newInitialATXv1(t, goldenATXID) atx1.Sign(sig) atxHdlr.expectAtxV1(atx1, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1))) malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) require.NoError(t, err) @@ -484,25 +493,21 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { }) atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - msg := codec.MustEncode(atx2) - mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) - atxHdlr.mpub.EXPECT().Publish(gomock.Any(), pubsub.MalfeasanceProof, gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, data []byte) error { - var got mwire.MalfeasanceGossip - require.NoError(t, codec.Decode(data, &got)) - require.Equal(t, mwire.MultipleATXs, got.Proof.Type) - nodeID, err := mh.Validate(context.Background(), got.Proof.Data) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx2.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) + + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) require.NoError(t, err) require.Equal(t, sig.NodeID(), nodeID) return nil - }) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg), errMaliciousATX) + }, + ) - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + msg := codec.MustEncode(atx2) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg)) }) } @@ -536,8 +541,8 @@ func TestHandler_HandleSyncedAtx(t *testing.T) { atxHdlr := newTestHandler(t, goldenATXID) atxHdlr.expectAtxV1(atx, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", buf)) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), "", buf), errKnownAtx) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, buf)) + require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, buf), errKnownAtx) require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), atx.ID().Hash32(), p2p.NoPeer, buf)) }) @@ -725,7 +730,7 @@ func TestHandler_WrongHash(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - err = atxHdlr.HandleSyncedAtx(context.Background(), types.RandomHash(), "", codec.MustEncode(atx)) + err = atxHdlr.HandleSyncedAtx(context.Background(), types.RandomHash(), p2p.NoPeer, codec.MustEncode(atx)) require.ErrorIs(t, err, errWrongHash) require.ErrorIs(t, err, pubsub.ErrValidationReject) } @@ -744,7 +749,7 @@ func TestHandler_MarksAtxValid(t *testing.T) { atxHdlr := newTestHandler(t, goldenATXID) atxHdlr.expectAtxV1(atx, sig.NodeID(), func(o *atxHandleOpts) { o.distributedPost = false }) - err := atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx)) + err := atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx)) require.NoError(t, err) vatx, err := atxs.Get(atxHdlr.cdb, atx.ID()) @@ -759,7 +764,7 @@ func TestHandler_MarksAtxValid(t *testing.T) { atxHdlr := newTestHandler(t, goldenATXID) atxHdlr.expectAtxV1(atx, sig.NodeID(), func(o *atxHandleOpts) { o.distributedPost = true }) - err := atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx)) + err := atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx)) require.NoError(t, err) vatx, err := atxs.Get(atxHdlr.cdb, atx.ID()) diff --git a/activation/handler_v1.go b/activation/handler_v1.go index 5519573562..216e09bdec 100644 --- a/activation/handler_v1.go +++ b/activation/handler_v1.go @@ -57,13 +57,8 @@ type nipostValidatorV1 interface { opts ...validatorOption, ) error - VRFNonce( - nodeId types.NodeID, - commitmentAtxId types.ATXID, - vrfNonce, labelsPerUnit uint64, - numUnits uint32, - ) error - PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubepoch types.EpochID) error + VRFNonce(nodeId types.NodeID, commitmentAtxId types.ATXID, vrfNonce, labelsPerUnit uint64, numUnits uint32) error + PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubEpoch types.EpochID) error } // HandlerV1 processes ATXs version 1. @@ -76,10 +71,11 @@ type HandlerV1 struct { tickSize uint64 goldenATXID types.ATXID nipostValidator nipostValidatorV1 - beacon AtxReceiver + beacon atxReceiver tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher + malPublisher legacyMalfeasancePublisher signerMtx sync.Mutex signers map[types.NodeID]*signing.EdSigner @@ -173,10 +169,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( ctx context.Context, watx *wire.ActivationTxV1, received time.Time, -) (*types.ActivationTx, *mwire.MalfeasanceProof, error) { +) (*types.ActivationTx, error) { commitmentATX, err := h.commitment(watx) if err != nil { - return nil, nil, fmt.Errorf("commitment atx for %s not found: %w", watx.SmesherID, err) + return nil, fmt.Errorf("commitment atx for %s not found: %w", watx.SmesherID, err) } var effectiveNumUnits uint32 @@ -184,29 +180,29 @@ func (h *HandlerV1) syntacticallyValidateDeps( if watx.PrevATXID == types.EmptyATXID { err := h.nipostValidator.InitialNIPostChallengeV1(&watx.NIPostChallengeV1, h.cdb, h.goldenATXID) if err != nil { - return nil, nil, err + return nil, err } effectiveNumUnits = watx.NumUnits vrfNonce = *watx.VRFNonce } else { previous, err := atxs.Get(h.cdb, watx.PrevATXID) if err != nil { - return nil, nil, fmt.Errorf("fetching previous atx %s: %w", watx.PrevATXID, err) + return nil, fmt.Errorf("fetching previous atx %s: %w", watx.PrevATXID, err) } vrfNonce, err = h.validateNonInitialAtx(ctx, watx, previous, commitmentATX) if err != nil { - return nil, nil, err + return nil, err } prevUnits, err := atxs.Units(h.cdb, watx.PrevATXID, watx.SmesherID) if err != nil { - return nil, nil, fmt.Errorf("fetching previous atx units: %w", err) + return nil, fmt.Errorf("fetching previous atx units: %w", err) } effectiveNumUnits = min(prevUnits, watx.NumUnits) } err = h.nipostValidator.PositioningAtx(watx.PositioningATXID, h.cdb, h.goldenATXID, watx.PublishEpoch) if err != nil { - return nil, nil, err + return nil, err } expectedChallengeHash := watx.NIPostChallengeV1.Hash() @@ -234,10 +230,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( ) malicious, err := identities.IsMalicious(h.cdb, watx.SmesherID) if err != nil { - return nil, nil, fmt.Errorf("check if smesher is malicious: %w", err) + return nil, fmt.Errorf("check if smesher is malicious: %w", err) } if malicious { - return nil, nil, fmt.Errorf("smesher %s is known malfeasant", watx.SmesherID.ShortString()) + return nil, fmt.Errorf("smesher %s is known malfeasant", watx.SmesherID.ShortString()) } proof := &mwire.MalfeasanceProof{ Layer: watx.PublishEpoch.FirstLayer(), @@ -249,23 +245,20 @@ func (h *HandlerV1) syntacticallyValidateDeps( }, }, } - encodedProof := codec.MustEncode(proof) - if err := identities.SetMalicious(h.cdb, watx.SmesherID, encodedProof, time.Now()); err != nil { - return nil, nil, fmt.Errorf("adding malfeasance proof: %w", err) + if err := h.malPublisher.PublishProof(ctx, watx.SmesherID, proof); err != nil { + return nil, fmt.Errorf("publishing malfeasance proof: %w", err) } - h.cdb.CacheMalfeasanceProof(watx.SmesherID, encodedProof) - h.tortoise.OnMalfeasance(watx.SmesherID) - return nil, proof, nil + return nil, errMaliciousATX } if err != nil { - return nil, nil, fmt.Errorf("validating nipost: %w", err) + return nil, fmt.Errorf("validating nipost: %w", err) } var baseTickHeight uint64 if watx.PositioningATXID != h.goldenATXID { posAtx, err := h.cdb.GetAtx(watx.PositioningATXID) if err != nil { - return nil, nil, fmt.Errorf("failed to get positioning atx %s: %w", watx.PositioningATXID, err) + return nil, fmt.Errorf("failed to get positioning atx %s: %w", watx.PositioningATXID, err) } baseTickHeight = posAtx.TickHeight() } @@ -281,10 +274,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( atx.TickCount = leaves / h.tickSize hi, weight := bits.Mul64(uint64(atx.NumUnits), atx.TickCount) if hi != 0 { - return nil, nil, errors.New("atx weight would overflow uint64") + return nil, errors.New("atx weight would overflow uint64") } atx.Weight = weight - return atx, nil, nil + return atx, nil } func (h *HandlerV1) validateNonInitialAtx( @@ -355,6 +348,12 @@ func (h *HandlerV1) checkDoublePublish( return nil, fmt.Errorf("%s already published an ATX in epoch %d", atx.SmesherID.ShortString(), atx.PublishEpoch) } + h.logger.Debug("smesher produced more than one atx in the same epoch", + log.ZContext(ctx), + zap.Stringer("smesher", atx.SmesherID), + zap.Stringer("previous", prev), + zap.Stringer("current", atx.ID()), + ) prevSignature, err := atxSignature(ctx, tx, prev) if err != nil { return nil, fmt.Errorf("extracting signature for malfeasance proof: %w", err) @@ -377,25 +376,13 @@ func (h *HandlerV1) checkDoublePublish( Signature: atx.Signature, }}, } - proof := &mwire.MalfeasanceProof{ + return &mwire.MalfeasanceProof{ Layer: atx.PublishEpoch.FirstLayer(), Proof: mwire.Proof{ Type: mwire.MultipleATXs, Data: &atxProof, }, - } - if err := identities.SetMalicious(tx, atx.SmesherID, codec.MustEncode(proof), time.Now()); err != nil { - return nil, fmt.Errorf("add malfeasance proof: %w", err) - } - - h.logger.Debug("smesher produced more than one atx in the same epoch", - log.ZContext(ctx), - zap.Stringer("smesher", atx.SmesherID), - zap.Stringer("previous", prev), - zap.Stringer("current", atx.ID()), - ) - - return proof, nil + }, nil } // checkWrongPrevAtx verifies if the previous ATX referenced in the ATX is correct. @@ -425,6 +412,12 @@ func (h *HandlerV1) checkWrongPrevAtx( return nil, fmt.Errorf("%s referenced incorrect previous ATX", atx.SmesherID.ShortString()) } + h.logger.Debug("smesher referenced the wrong previous in published ATX", + log.ZContext(ctx), + zap.Stringer("smesher", atx.SmesherID), + log.ZShortStringer("actual", atx.PrevATXID), + log.ZShortStringer("expected", expectedPrevID), + ) atx2ID, err := atxs.AtxWithPrevious(tx, atx.PrevATXID, atx.SmesherID) switch { case errors.Is(err, sql.ErrNotFound): @@ -454,7 +447,7 @@ func (h *HandlerV1) checkWrongPrevAtx( return nil, fmt.Errorf("decoding previous atx: %w", err) } - proof := &mwire.MalfeasanceProof{ + return &mwire.MalfeasanceProof{ Layer: atx.PublishEpoch.FirstLayer(), Proof: mwire.Proof{ Type: mwire.InvalidPrevATX, @@ -463,19 +456,7 @@ func (h *HandlerV1) checkWrongPrevAtx( Atx2: watx2, }, }, - } - - if err := identities.SetMalicious(tx, atx.SmesherID, codec.MustEncode(proof), time.Now()); err != nil { - return nil, fmt.Errorf("add malfeasance proof: %w", err) - } - - h.logger.Debug("smesher referenced the wrong previous in published ATX", - log.ZContext(ctx), - zap.Stringer("smesher", atx.SmesherID), - log.ZShortStringer("actual", atx.PrevATXID), - log.ZShortStringer("expected", expectedPrevID), - ) - return proof, nil + }, nil } func (h *HandlerV1) checkMalicious( @@ -491,11 +472,7 @@ func (h *HandlerV1) checkMalicious( } // storeAtx stores an ATX and notifies subscribers of the ATXID. -func (h *HandlerV1) storeAtx( - ctx context.Context, - atx *types.ActivationTx, - watx *wire.ActivationTxV1, -) (*mwire.MalfeasanceProof, error) { +func (h *HandlerV1) storeAtx(ctx context.Context, atx *types.ActivationTx, watx *wire.ActivationTxV1) error { var ( proof *mwire.MalfeasanceProof malicious bool @@ -524,13 +501,14 @@ func (h *HandlerV1) storeAtx( return nil }); err != nil { - return nil, fmt.Errorf("store atx: %w", err) + return fmt.Errorf("store atx: %w", err) } atxs.AtxAdded(h.cdb, atx) if proof != nil { - h.cdb.CacheMalfeasanceProof(atx.SmesherID, codec.MustEncode(proof)) - h.tortoise.OnMalfeasance(atx.SmesherID) + if err := h.malPublisher.PublishProof(ctx, atx.SmesherID, proof); err != nil { + return fmt.Errorf("publishing malfeasance proof: %w", err) + } } added := h.cacheAtx(ctx, atx, malicious || proof != nil) @@ -543,7 +521,7 @@ func (h *HandlerV1) storeAtx( zap.Stringer("atx_id", atx.ID()), zap.Uint32("epoch_id", atx.PublishEpoch.Uint32()), ) - return proof, nil + return nil } func (h *HandlerV1) processATX( @@ -551,14 +529,14 @@ func (h *HandlerV1) processATX( peer p2p.Peer, watx *wire.ActivationTxV1, received time.Time, -) (*mwire.MalfeasanceProof, error) { +) error { if !h.edVerifier.Verify(signing.ATX, watx.SmesherID, watx.SignedBytes(), watx.Signature) { - return nil, fmt.Errorf("%w: invalid atx signature: %w", pubsub.ErrValidationReject, errMalformedData) + return fmt.Errorf("%w: invalid atx signature: %w", pubsub.ErrValidationReject, errMalformedData) } existing, _ := h.cdb.GetAtx(watx.ID()) if existing != nil { - return nil, fmt.Errorf("%w: %s", errKnownAtx, watx.ID()) + return fmt.Errorf("%w: %s", errKnownAtx, watx.ID()) } h.logger.Debug("processing atx", @@ -570,26 +548,25 @@ func (h *HandlerV1) processATX( err := h.syntacticallyValidate(ctx, watx) if err != nil { - return nil, fmt.Errorf("%w: validating atx %s: %w", pubsub.ErrValidationReject, watx.ID(), err) + return fmt.Errorf("%w: validating atx %s: %w", pubsub.ErrValidationReject, watx.ID(), err) } poetRef, atxIDs := collectAtxDeps(h.goldenATXID, watx) h.registerHashes(peer, poetRef, atxIDs) if err := h.fetchReferences(ctx, poetRef, atxIDs); err != nil { - return nil, fmt.Errorf("fetching references for atx %s: %w", watx.ID(), err) + return fmt.Errorf("fetching references for atx %s: %w", watx.ID(), err) } - atx, proof, err := h.syntacticallyValidateDeps(ctx, watx, received) - if err != nil { - return nil, fmt.Errorf("%w: validating atx %s (deps): %w", pubsub.ErrValidationReject, watx.ID(), err) - } - if proof != nil { - return proof, nil + atx, err := h.syntacticallyValidateDeps(ctx, watx, received) + switch { + case errors.Is(err, errMaliciousATX): + return nil + case err != nil: + return fmt.Errorf("%w: validating atx %s (deps): %w", pubsub.ErrValidationReject, watx.ID(), err) } - proof, err = h.storeAtx(ctx, atx, watx) - if err != nil { - return nil, fmt.Errorf("cannot store atx %s: %w", atx.ShortString(), err) + if err := h.storeAtx(ctx, atx, watx); err != nil { + return fmt.Errorf("cannot store atx %s: %w", atx.ShortString(), err) } if err := events.ReportNewActivation(atx); err != nil { @@ -602,9 +579,8 @@ func (h *HandlerV1) processATX( h.logger.Debug("new atx", log.ZContext(ctx), zap.Inline(atx), - zap.Bool("malicious", proof != nil), ) - return proof, err + return err } // registerHashes registers that the given peer should be asked for diff --git a/activation/handler_v1_test.go b/activation/handler_v1_test.go index 01ce917af0..27f20eb34e 100644 --- a/activation/handler_v1_test.go +++ b/activation/handler_v1_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -44,14 +45,15 @@ func newV1TestHandler(tb testing.TB, goldenATXID types.ATXID) *v1TestHandler { cdb: cdb, atxsdata: atxsdata.New(), edVerifier: signing.NewEdVerifier(), - clock: mocks.mclock, + clock: mocks.mClock, tickSize: 1, goldenATXID: goldenATXID, nipostValidator: mocks.mValidator, logger: lg, fetcher: mocks.mockFetch, - beacon: mocks.mbeacon, - tortoise: mocks.mtortoise, + beacon: mocks.mBeacon, + tortoise: mocks.mTortoise, + malPublisher: mocks.mLegacyMalPublish, signers: make(map[types.NodeID]*signing.EdSigner), }, handlerMocks: mocks, @@ -71,8 +73,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { prevAtx.NumUnits = 100 prevAtx.Sign(sig) atxHdlr.expectAtxV1(prevAtx, sig.NodeID()) - _, err := atxHdlr.processATX(context.Background(), "", prevAtx, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.processATX(context.Background(), p2p.NoPeer, prevAtx, time.Now())) otherSig, err := signing.NewEdSigner() require.NoError(t, err) @@ -80,8 +81,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { posAtx := newInitialATXv1(t, goldenATXID) posAtx.Sign(otherSig) atxHdlr.expectAtxV1(posAtx, otherSig.NodeID()) - _, err = atxHdlr.processATX(context.Background(), "", posAtx, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.processATX(context.Background(), p2p.NoPeer, posAtx, time.Now())) return atxHdlr, prevAtx, posAtx } @@ -93,7 +93,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.PositioningATXID = posAtx.ID() watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). @@ -103,7 +103,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, gomock.Any()) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -111,7 +111,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("valid atx with new VRF nonce", func(t *testing.T) { @@ -123,7 +122,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.VRFNonce = &newNonce watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). @@ -135,7 +134,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { VRFNonce(gomock.Any(), goldenATXID, newNonce, gomock.Any(), watx.NumUnits) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -143,7 +142,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("valid atx with decreasing num units", func(t *testing.T) { @@ -154,7 +152,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.NumUnits = prevAtx.NumUnits - 10 watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), gomock.Any(), goldenATXID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -163,7 +161,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -171,7 +169,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx with increasing num units, no new VRF, old valid", func(t *testing.T) { @@ -182,7 +179,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.NumUnits = prevAtx.NumUnits + 10 watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -192,7 +189,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().VRFNonce(gomock.Any(), goldenATXID, *prevAtx.VRFNonce, gomock.Any(), watx.NumUnits) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -200,7 +197,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, prevAtx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx with increasing num units, no new VRF, old invalid for new size", func(t *testing.T) { @@ -211,7 +207,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.NumUnits = prevAtx.NumUnits + 10 watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), gomock.Any()) @@ -219,9 +215,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { VRFNonce(gomock.Any(), goldenATXID, *prevAtx.VRFNonce, gomock.Any(), watx.NumUnits). Return(errors.New("invalid VRF")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.ErrorContains(t, err, "invalid VRF") - require.Nil(t, proof) }) t.Run("valid initial atx", func(t *testing.T) { @@ -233,7 +228,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.CommitmentATXID = &ctxID watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), gomock.Any(), ctxID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()) atxHdlr.mValidator.EXPECT().VRFNonce(sig.NodeID(), ctxID, *watx.VRFNonce, gomock.Any(), watx.NumUnits) @@ -246,7 +241,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -254,7 +249,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(777)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx targeting wrong publish epoch", func(t *testing.T) { @@ -264,7 +258,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx := newChainedActivationTxV1(t, prevAtx, posAtx.ID()) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return((atx.PublishEpoch - 2).FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return((atx.PublishEpoch - 2).FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "atx publish epoch is too far in the future") }) @@ -276,16 +270,15 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevAtx, posAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID). Return(errors.New("nipost error")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "nipost error") - require.Nil(t, proof) }) t.Run("failing positioning atx validation", func(t *testing.T) { @@ -295,7 +288,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevAtx, posAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -303,9 +296,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch). Return(errors.New("bad positioning atx")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "bad positioning atx") - require.Nil(t, proof) }) t.Run("bad initial nipost challenge", func(t *testing.T) { @@ -316,7 +308,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newInitialATXv1(t, cATX) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), sig.NodeID(), cATX, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()) atxHdlr.mValidator.EXPECT().VRFNonce(sig.NodeID(), cATX, *watx.VRFNonce, gomock.Any(), watx.NumUnits) @@ -326,9 +318,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), goldenATXID). Return(errors.New("bad initial nipost")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "bad initial nipost") - require.Nil(t, proof) }) t.Run("bad NIPoST", func(t *testing.T) { @@ -338,7 +329,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevATX, postAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -347,9 +338,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, errors.New("bad nipost")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "validating nipost: bad nipost") - require.Nil(t, proof) }) t.Run("invalid NIPoST", func(t *testing.T) { @@ -359,7 +349,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevATX, postAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -367,12 +357,27 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(watx.SmesherID) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), watx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) + + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) + postVerifier.EXPECT(). + Verify(context.Background(), (*shared.Proof)(watx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.InvalidPostIndex, proof.Proof.Type) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + require.ErrorIs(t, err, errMaliciousATX) }) t.Run("invalid NIPoST of known malfeasant", func(t *testing.T) { @@ -384,7 +389,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.NoError(t, identities.SetMalicious(atxHdlr.cdb, watx.SmesherID, []byte("proof"), time.Now())) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -392,10 +397,10 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) + received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, fmt.Sprintf("smesher %s is known malfeasant", watx.SmesherID.ShortString())) - require.Nil(t, proof) }) t.Run("missing NodeID in initial atx", func(t *testing.T) { @@ -406,7 +411,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.Signature = sig.Sign(signing.ATX, atx.SignedBytes()) atx.SmesherID = sig.NodeID() - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "node id is missing") }) @@ -419,7 +424,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.VRFNonce = nil atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "vrf nonce is missing") }) @@ -431,7 +436,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). VRFNonce(atx.SmesherID, *atx.CommitmentATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits). Return(errors.New("invalid VRF nonce")) @@ -447,7 +452,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.PrevATXID = types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "no prev atx declared, but initial post is not included") }) @@ -460,7 +465,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.CommitmentATXID = nil atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "no prev atx declared, but commitment atx is missing") }) @@ -473,7 +478,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.CommitmentATXID = &types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "empty commitment atx") }) @@ -486,7 +491,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.Sequence = 1 atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "no prev atx declared, but sequence number not zero") }) @@ -498,7 +503,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). VRFNonce(atx.SmesherID, *atx.CommitmentATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits) atxHdlr.mValidator.EXPECT(). @@ -516,7 +521,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.PositioningATXID = types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "empty positioning atx") }) @@ -529,7 +534,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.PrevATXID = prevAtx.ID() atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "prev atx declared, but initial post is included") }) @@ -542,7 +547,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.NodeID = &types.NodeID{1, 2, 3} atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "prev atx declared, but node id is included") }) @@ -555,7 +560,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.CommitmentATXID = &types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "prev atx declared, but commitment atx is included") }) @@ -574,13 +579,11 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx.Sign(sig) atx := toAtx(t, watx) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) atxFromDb, err := atxs.Get(atxHdlr.cdb, atx.ID()) require.NoError(t, err) @@ -595,21 +598,17 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx.Sign(sig) atx := toAtx(t, watx) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) // Note: tortoise is not informed about the same ATX again - proof, err = atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) }) t.Run("stores ATX of malicious identity", func(t *testing.T) { @@ -623,13 +622,11 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx.Sign(sig) atx := toAtx(t, watx) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) atxFromDb, err := atxs.Get(atxHdlr.cdb, atx.ID()) require.NoError(t, err) @@ -644,37 +641,34 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx0.Sign(sig) atx0 := toAtx(t, watx0) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx0.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx0, watx0) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx0, watx0)) watx1 := newInitialATXv1(t, goldenATXID) watx1.Coinbase = types.GenerateAddress([]byte("aaaa")) watx1.Sign(sig) atx1 := toAtx(t, watx1) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx1.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.MultipleATXs, proof.Proof.Type) - - mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) - nodeID, err := mh.Validate(context.Background(), proof.Proof.Data) - require.NoError(t, err) - require.Equal(t, sig.NodeID(), nodeID) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + atxHdlr.mTortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), sig.NodeID(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) + + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) }) t.Run("another atx for the same epoch for registered ID doesn't create a malfeasance proof", func(t *testing.T) { @@ -685,29 +679,21 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx0.Sign(sig) atx0 := toAtx(t, watx0) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx0.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx0, watx0) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx0, watx0)) watx1 := newInitialATXv1(t, goldenATXID) watx1.Coinbase = types.GenerateAddress([]byte("aaaa")) watx1.Sign(sig) atx1 := toAtx(t, watx1) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) require.ErrorContains(t, - err, + atxHdlr.storeAtx(context.Background(), atx1, watx1), fmt.Sprintf("%s already published an ATX", sig.NodeID().ShortString()), ) - require.Nil(t, proof) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.False(t, malicious) }) t.Run("another atx with the same prevatx is considered malicious", func(t *testing.T) { @@ -717,38 +703,32 @@ func TestHandlerV1_StoreAtx(t *testing.T) { initialATX.Sign(sig) wInitialATX := toAtx(t, initialATX) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == initialATX.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(initialATX.PublishEpoch+1, initialATX.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), wInitialATX, initialATX) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(initialATX.PublishEpoch+1, initialATX.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), wInitialATX, initialATX)) // valid first non-initial ATX watx1 := newChainedActivationTxV1(t, initialATX, goldenATXID) watx1.Sign(sig) atx1 := toAtx(t, watx1) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx1.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) watx2 := newChainedActivationTxV1(t, watx1, goldenATXID) watx2.Sign(sig) atx2 := toAtx(t, watx2) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx2.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx2.PublishEpoch+1, watx2.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx2, watx2) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx2.PublishEpoch+1, watx2.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx2, watx2)) // third non-initial ATX references initial ATX as prevATX watx3 := newChainedActivationTxV1(t, initialATX, goldenATXID) @@ -756,20 +736,24 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx3.Sign(sig) atx3 := toAtx(t, watx3) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx3.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx3.PublishEpoch+1, watx3.ID(), gomock.Any()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - proof, err = atxHdlr.storeAtx(context.Background(), atx3, watx3) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.InvalidPrevATX, proof.Proof.Type) + atxHdlr.mTortoise.EXPECT().OnAtx(watx3.PublishEpoch+1, watx3.ID(), gomock.Any()) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), sig.NodeID(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPrevATX, mp.Proof.Type) + + mh := NewInvalidPrevATXHandler(atxHdlr.cdb, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) - mh := NewInvalidPrevATXHandler(atxHdlr.cdb, atxHdlr.edVerifier) - nodeID, err := mh.Validate(context.Background(), proof.Proof.Data) - require.NoError(t, err) - require.Equal(t, sig.NodeID(), nodeID) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx3, watx3)) }) t.Run("another atx with the same prevatx for registered ID doesn't create a malfeasance proof", func(t *testing.T) { @@ -781,26 +765,22 @@ func TestHandlerV1_StoreAtx(t *testing.T) { wInitialATX.Sign(sig) initialAtx := toAtx(t, wInitialATX) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == wInitialATX.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(wInitialATX.PublishEpoch+1, wInitialATX.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), initialAtx, wInitialATX) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(wInitialATX.PublishEpoch+1, wInitialATX.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), initialAtx, wInitialATX)) // valid first non-initial ATX watx1 := newChainedActivationTxV1(t, wInitialATX, goldenATXID) watx1.Sign(sig) atx1 := toAtx(t, watx1) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx1.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) // second non-initial ATX references empty as prevATX watx2 := newInitialATXv1(t, goldenATXID) @@ -808,16 +788,10 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx2.Sign(sig) atx2 := toAtx(t, watx2) - proof, err = atxHdlr.storeAtx(context.Background(), atx2, watx2) require.ErrorContains(t, - err, + atxHdlr.storeAtx(context.Background(), atx2, watx2), fmt.Sprintf("%s referenced incorrect previous ATX", sig.NodeID().ShortString()), ) - require.Nil(t, proof) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.False(t, malicious) }) } diff --git a/activation/handler_v2.go b/activation/handler_v2.go index b7386a4dd3..858b9430b0 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -65,7 +65,7 @@ type HandlerV2 struct { tickSize uint64 goldenATXID types.ATXID nipostValidator nipostValidatorV2 - beacon AtxReceiver + beacon atxReceiver tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index 19e2981233..662b9ed33d 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -21,6 +21,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/fetch" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" @@ -58,14 +59,14 @@ func newV2TestHandler(tb testing.TB, golden types.ATXID) *v2TestHandler { cdb: cdb, atxsdata: atxsdata.New(), edVerifier: signing.NewEdVerifier(), - clock: mocks.mclock, + clock: mocks.mClock, tickSize: tickSize, goldenATXID: golden, nipostValidator: mocks.mValidator, logger: lg, fetcher: mocks.mockFetch, - beacon: mocks.mbeacon, - tortoise: mocks.mtortoise, + beacon: mocks.mBeacon, + tortoise: mocks.mTortoise, malPublisher: mocks.mMalPublish, }, tb: tb, @@ -127,15 +128,15 @@ func (h *handlerMocks) expectVerifyNIPoSTs( } func (h *handlerMocks) expectStoreAtxV2(atx *wire.ActivationTxV2) { - h.mbeacon.EXPECT().OnAtx(gomock.Cond(func(a *types.ActivationTx) bool { + h.mBeacon.EXPECT().OnAtx(gomock.Cond(func(a *types.ActivationTx) bool { return a.ID() == atx.ID() })) - h.mtortoise.EXPECT().OnAtx(atx.PublishEpoch+1, atx.ID(), gomock.Any()) + h.mTortoise.EXPECT().OnAtx(atx.PublishEpoch+1, atx.ID(), gomock.Any()) h.mValidator.EXPECT().IsVerifyingFullPost().Return(false) } func (h *handlerMocks) expectInitialAtxV2(atx *wire.ActivationTxV2) { - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) h.mValidator.EXPECT().VRFNonceV2( atx.SmesherID, atx.Initial.CommitmentATX, @@ -158,7 +159,7 @@ func (h *handlerMocks) expectInitialAtxV2(atx *wire.ActivationTxV2) { } func (h *handlerMocks) expectAtxV2(atx *wire.ActivationTxV2) { - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) h.mValidator.EXPECT().VRFNonceV2( atx.SmesherID, gomock.Any(), @@ -175,7 +176,7 @@ func (h *handlerMocks) expectMergedAtxV2( equivocationSet []types.NodeID, poetLeaves []uint64, ) { - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) h.expectFetchDeps(atx) h.mValidator.EXPECT().VRFNonceV2( atx.SmesherID, @@ -228,7 +229,7 @@ func TestHandlerV2_SyntacticallyValidate(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer().Return(0) + atxHandler.mClock.EXPECT().CurrentLayer().Return(0) err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "atx publish epoch is too far in the future") }) @@ -248,7 +249,7 @@ func TestHandlerV2_SyntacticallyValidate(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "previous atx[0] is the golden ATX") }) @@ -259,7 +260,7 @@ func TestHandlerV2_SyntacticallyValidate(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "previous atx[0] is empty") }) @@ -277,7 +278,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.mValidator.EXPECT().VRFNonceV2( sig.NodeID(), atx.Initial.CommitmentATX, @@ -301,14 +302,14 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx must not have previous atxs") atx.PreviousATXs = []types.ATXID{types.EmptyATXID} atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err = atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx must not have previous atxs") }) @@ -319,7 +320,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx cannot reference a marriage atx") }) @@ -330,7 +331,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx missing commitment atx") }) @@ -340,7 +341,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.mValidator.EXPECT(). VRFNonceV2( sig.NodeID(), @@ -358,7 +359,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.mValidator.EXPECT().VRFNonceV2( sig.NodeID(), atx.Initial.CommitmentATX, @@ -390,7 +391,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx := newSoloATXv2(t, 0, types.RandomATXID(), types.RandomATXID()) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.NoError(t, err) }) @@ -399,7 +400,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.PreviousATXs = append(atx.PreviousATXs, types.RandomATXID()) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx must have one previous atx") }) @@ -409,7 +410,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.NIPosts = append(atx.NIPosts, wire.NIPostV2{}) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx must have one nipost") }) @@ -419,7 +420,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.NIPosts[0].Posts = append(atx.NIPosts[0].Posts, wire.SubPostV2{}) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx must have one post") }) @@ -429,7 +430,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.NIPosts[0].Posts[0].PrevATXIndex = 1 atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx post must have prevATXIndex 0") }) @@ -452,7 +453,7 @@ func TestHandlerV2_SyntacticallyValidate_MergedAtx(t *testing.T) { }} atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err = atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "merged atx cannot have marriages") }) @@ -565,7 +566,7 @@ func TestHandlerV2_ProcessSoloATX(t *testing.T) { atx.NIPosts[0].Posts[0].NumUnits = prev.TotalNumUnits() * 10 atx.VRFNonce = 7779989 atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(postGenesisEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(postGenesisEpoch.FirstLayer()) atxHandler.expectFetchDeps(atx) atxHandler.expectVerifyNIPoST(atx) atxHandler.mValidator.EXPECT().VRFNonceV2( @@ -608,7 +609,7 @@ func TestHandlerV2_ProcessSoloATX(t *testing.T) { atx := newSoloATXv2(t, 0, types.RandomATXID(), types.RandomATXID()) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.expectFetchDeps(atx) err := atxHandler.processATX(context.Background(), peer, atx, time.Now()) require.ErrorContains(t, err, "validating positioning atx") @@ -801,7 +802,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.PreviousATXs = previousATXs merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) atxHandler.expectVerifyNIPoSTs(merged, equivocationSet, []uint64{200}) @@ -836,7 +837,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.PreviousATXs = previousATXs merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) err := atxHandler.processATX(context.Background(), "", merged, time.Now()) require.ErrorContains(t, err, "ID present twice (duplicated marriage index)") require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -866,7 +867,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.PreviousATXs = previousATXs merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) err := atxHandler.processATX(context.Background(), "", merged, time.Now()) require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -930,7 +931,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.MarriageATX = &mATXID merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) err = atxHandler.processATX(context.Background(), "", merged, time.Now()) require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -2043,7 +2044,7 @@ func Test_Marriages(t *testing.T) { } atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().AnyTimes() + atxHandler.mClock.EXPECT().CurrentLayer().AnyTimes() err = atxHandler.processATX(context.Background(), "", atx, time.Now()) require.ErrorContains(t, err, "signer must marry itself") require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -2199,13 +2200,11 @@ func TestContextual_PreviousATX(t *testing.T) { prevATX := newInitialATXv1(t, golden) prevATX.Sign(sig1) atxHdlr.expectAtxV1(prevATX, prevATX.SmesherID) - _, err = atxHdlr.v1.processATX(context.Background(), "", prevATX, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.v1.processATX(context.Background(), p2p.NoPeer, prevATX, time.Now())) atxv1 := newChainedActivationTxV1(t, prevATX, prevATX.ID()) atxv1.Sign(sig1) atxHdlr.expectAtxV1(atxv1, atxv1.SmesherID) - _, err = atxHdlr.v1.processATX(context.Background(), "", atxv1, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.v1.processATX(context.Background(), p2p.NoPeer, atxv1, time.Now())) soloAtx := newSoloATXv2(t, atxv1.PublishEpoch+1, atxv1.ID(), atxv1.ID()) soloAtx.Sign(sig1) diff --git a/activation/interface.go b/activation/interface.go index 38c8cf1332..ae410e6291 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/common/types" + mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -19,7 +20,7 @@ import ( //go:generate mockgen -typed -package=activation -destination=./mocks.go -source=./interface.go -type AtxReceiver interface { +type atxReceiver interface { OnAtx(*types.ActivationTx) } @@ -92,16 +93,20 @@ type syncer interface { RegisterForATXSynced() <-chan struct{} } -// atxMalfeasancePublisher is an interface for publishing malfeasance proofs. -// This interface is used to publish proofs in V2. +// legacyMalfeasancePublisher is an interface for publishing legacy malfeasance proofs. // -// The provider of that interface ensures that only valid proofs are published (invalid ones return an error). -// Proofs against an identity that is managed by the node will also return an error and will not be gossiped. +// It is used int he ATXv1 handler and will be replaced in the future by the atxMalfeasancePublisher, which will +// wrap legacy proofs into the new encoding structure. +type legacyMalfeasancePublisher interface { + PublishProof(ctx context.Context, smesherID types.NodeID, proof *mwire.MalfeasanceProof) error +} + +// atxMalfeasancePublisher is an interface for publishing atx malfeasance proofs. // -// Additionally the publisher will only gossip proofs when the node is in sync, otherwise it will only store them -// and mark the associated identity as malfeasant. +// It encapsulates a specific malfeasance proof into a generic ATX malfeasance proof and publishes it by calling +// the underlying malfeasancePublisher. type atxMalfeasancePublisher interface { - Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error + Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error } type atxProvider interface { diff --git a/activation/mocks.go b/activation/mocks.go index 0ab71a7524..00ba677320 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -17,6 +17,7 @@ import ( wire "github.com/spacemeshos/go-spacemesh/activation/wire" types "github.com/spacemeshos/go-spacemesh/common/types" + wire0 "github.com/spacemeshos/go-spacemesh/malfeasance/wire" signing "github.com/spacemeshos/go-spacemesh/signing" certifier "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" nipost "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -24,62 +25,62 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockAtxReceiver is a mock of AtxReceiver interface. -type MockAtxReceiver struct { +// MockatxReceiver is a mock of atxReceiver interface. +type MockatxReceiver struct { ctrl *gomock.Controller - recorder *MockAtxReceiverMockRecorder + recorder *MockatxReceiverMockRecorder isgomock struct{} } -// MockAtxReceiverMockRecorder is the mock recorder for MockAtxReceiver. -type MockAtxReceiverMockRecorder struct { - mock *MockAtxReceiver +// MockatxReceiverMockRecorder is the mock recorder for MockatxReceiver. +type MockatxReceiverMockRecorder struct { + mock *MockatxReceiver } -// NewMockAtxReceiver creates a new mock instance. -func NewMockAtxReceiver(ctrl *gomock.Controller) *MockAtxReceiver { - mock := &MockAtxReceiver{ctrl: ctrl} - mock.recorder = &MockAtxReceiverMockRecorder{mock} +// NewMockatxReceiver creates a new mock instance. +func NewMockatxReceiver(ctrl *gomock.Controller) *MockatxReceiver { + mock := &MockatxReceiver{ctrl: ctrl} + mock.recorder = &MockatxReceiverMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAtxReceiver) EXPECT() *MockAtxReceiverMockRecorder { +func (m *MockatxReceiver) EXPECT() *MockatxReceiverMockRecorder { return m.recorder } // OnAtx mocks base method. -func (m *MockAtxReceiver) OnAtx(arg0 *types.ActivationTx) { +func (m *MockatxReceiver) OnAtx(arg0 *types.ActivationTx) { m.ctrl.T.Helper() m.ctrl.Call(m, "OnAtx", arg0) } // OnAtx indicates an expected call of OnAtx. -func (mr *MockAtxReceiverMockRecorder) OnAtx(arg0 any) *MockAtxReceiverOnAtxCall { +func (mr *MockatxReceiverMockRecorder) OnAtx(arg0 any) *MockatxReceiverOnAtxCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAtx", reflect.TypeOf((*MockAtxReceiver)(nil).OnAtx), arg0) - return &MockAtxReceiverOnAtxCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAtx", reflect.TypeOf((*MockatxReceiver)(nil).OnAtx), arg0) + return &MockatxReceiverOnAtxCall{Call: call} } -// MockAtxReceiverOnAtxCall wrap *gomock.Call -type MockAtxReceiverOnAtxCall struct { +// MockatxReceiverOnAtxCall wrap *gomock.Call +type MockatxReceiverOnAtxCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockAtxReceiverOnAtxCall) Return() *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) Return() *MockatxReceiverOnAtxCall { c.Call = c.Call.Return() return c } // Do rewrite *gomock.Call.Do -func (c *MockAtxReceiverOnAtxCall) Do(f func(*types.ActivationTx)) *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) Do(f func(*types.ActivationTx)) *MockatxReceiverOnAtxCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockAtxReceiverOnAtxCall) DoAndReturn(f func(*types.ActivationTx)) *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) DoAndReturn(f func(*types.ActivationTx)) *MockatxReceiverOnAtxCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -509,17 +510,17 @@ func (c *MocknipostValidatorPoetMembershipCall) DoAndReturn(f func(context.Conte } // PositioningAtx mocks base method. -func (m *MocknipostValidator) PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubepoch types.EpochID) error { +func (m *MocknipostValidator) PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubEpoch types.EpochID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PositioningAtx", id, atxs, goldenATXID, pubepoch) + ret := m.ctrl.Call(m, "PositioningAtx", id, atxs, goldenATXID, pubEpoch) ret0, _ := ret[0].(error) return ret0 } // PositioningAtx indicates an expected call of PositioningAtx. -func (mr *MocknipostValidatorMockRecorder) PositioningAtx(id, atxs, goldenATXID, pubepoch any) *MocknipostValidatorPositioningAtxCall { +func (mr *MocknipostValidatorMockRecorder) PositioningAtx(id, atxs, goldenATXID, pubEpoch any) *MocknipostValidatorPositioningAtxCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PositioningAtx", reflect.TypeOf((*MocknipostValidator)(nil).PositioningAtx), id, atxs, goldenATXID, pubepoch) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PositioningAtx", reflect.TypeOf((*MocknipostValidator)(nil).PositioningAtx), id, atxs, goldenATXID, pubEpoch) return &MocknipostValidatorPositioningAtxCall{Call: call} } @@ -1092,6 +1093,68 @@ func (c *MocksyncerRegisterForATXSyncedCall) DoAndReturn(f func() <-chan struct{ return c } +// MocklegacyMalfeasancePublisher is a mock of legacyMalfeasancePublisher interface. +type MocklegacyMalfeasancePublisher struct { + ctrl *gomock.Controller + recorder *MocklegacyMalfeasancePublisherMockRecorder + isgomock struct{} +} + +// MocklegacyMalfeasancePublisherMockRecorder is the mock recorder for MocklegacyMalfeasancePublisher. +type MocklegacyMalfeasancePublisherMockRecorder struct { + mock *MocklegacyMalfeasancePublisher +} + +// NewMocklegacyMalfeasancePublisher creates a new mock instance. +func NewMocklegacyMalfeasancePublisher(ctrl *gomock.Controller) *MocklegacyMalfeasancePublisher { + mock := &MocklegacyMalfeasancePublisher{ctrl: ctrl} + mock.recorder = &MocklegacyMalfeasancePublisherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MocklegacyMalfeasancePublisher) EXPECT() *MocklegacyMalfeasancePublisherMockRecorder { + return m.recorder +} + +// PublishProof mocks base method. +func (m *MocklegacyMalfeasancePublisher) PublishProof(ctx context.Context, smesherID types.NodeID, proof *wire0.MalfeasanceProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublishProof", ctx, smesherID, proof) + ret0, _ := ret[0].(error) + return ret0 +} + +// PublishProof indicates an expected call of PublishProof. +func (mr *MocklegacyMalfeasancePublisherMockRecorder) PublishProof(ctx, smesherID, proof any) *MocklegacyMalfeasancePublisherPublishProofCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishProof", reflect.TypeOf((*MocklegacyMalfeasancePublisher)(nil).PublishProof), ctx, smesherID, proof) + return &MocklegacyMalfeasancePublisherPublishProofCall{Call: call} +} + +// MocklegacyMalfeasancePublisherPublishProofCall wrap *gomock.Call +type MocklegacyMalfeasancePublisherPublishProofCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocklegacyMalfeasancePublisherPublishProofCall) Return(arg0 error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocklegacyMalfeasancePublisherPublishProofCall) Do(f func(context.Context, types.NodeID, *wire0.MalfeasanceProof) error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocklegacyMalfeasancePublisherPublishProofCall) DoAndReturn(f func(context.Context, types.NodeID, *wire0.MalfeasanceProof) error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockatxMalfeasancePublisher is a mock of atxMalfeasancePublisher interface. type MockatxMalfeasancePublisher struct { ctrl *gomock.Controller @@ -1117,17 +1180,17 @@ func (m *MockatxMalfeasancePublisher) EXPECT() *MockatxMalfeasancePublisherMockR } // Publish mocks base method. -func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { +func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Publish", ctx, id, proof) + ret := m.ctrl.Call(m, "Publish", ctx, nodeID, proof) ret0, _ := ret[0].(error) return ret0 } // Publish indicates an expected call of Publish. -func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, id, proof any) *MockatxMalfeasancePublisherPublishCall { +func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, nodeID, proof any) *MockatxMalfeasancePublisherPublishCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, id, proof) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, nodeID, proof) return &MockatxMalfeasancePublisherPublishCall{Call: call} } diff --git a/activation/poetdb_test.go b/activation/poetdb_test.go index 592e37ee55..a6d03d65e5 100644 --- a/activation/poetdb_test.go +++ b/activation/poetdb_test.go @@ -20,7 +20,7 @@ import ( ) var ( - proof *types.PoetProofMessage + poetProofMsg *types.PoetProofMessage createProofOnce sync.Once ) @@ -47,7 +47,7 @@ func getPoetProof(tb testing.TB) types.PoetProofMessage { ) require.NoError(tb, err) - proof = &types.PoetProofMessage{ + poetProofMsg = &types.PoetProofMessage{ PoetProof: types.PoetProof{ MerkleProof: *merkleProof, LeafCount: leaves, @@ -57,7 +57,7 @@ func getPoetProof(tb testing.TB) types.PoetProofMessage { Statement: types.BytesToHash(challenge), } }) - return *proof + return *poetProofMsg } func TestPoetDbHappyFlow(t *testing.T) { diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index c3870d3521..0f9b4e0751 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -249,10 +249,12 @@ func validateAndPreserveData( lg := zaptest.NewLogger(tb) ctrl := gomock.NewController(tb) mclock := activation.NewMocklayerClock(ctrl) - mfetch := smocks.NewMockFetcher(ctrl) - mvalidator := activation.NewMocknipostValidator(ctrl) - mreceiver := activation.NewMockAtxReceiver(ctrl) - mtrtl := smocks.NewMockTortoise(ctrl) + mFetch := smocks.NewMockFetcher(ctrl) + mValidator := activation.NewMocknipostValidator(ctrl) + mMalPublisher := activation.NewMockatxMalfeasancePublisher(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) + mTortoise := smocks.NewMockTortoise(ctrl) cdb := datastore.NewCachedDB(db, lg) tb.Cleanup(func() { assert.NoError(tb, cdb.Close()) }) atxHandler := activation.NewHandler( @@ -261,26 +263,27 @@ func validateAndPreserveData( atxsdata.New(), signing.NewEdVerifier(), mclock, - nil, - mfetch, + mFetch, goldenAtx, - mvalidator, - mreceiver, - mtrtl, + mValidator, + mMalPublisher, + mLegacyPublish, + mBeacon, + mTortoise, lg, ) - mfetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() for _, dep := range deps { var atx wire.ActivationTxV1 require.NoError(tb, codec.Decode(dep.Blob, &atx)) mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) - mfetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any()) - mfetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) + mFetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any()) + mFetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) if atx.PrevATXID == types.EmptyATXID { - mvalidator.EXPECT(). + mValidator.EXPECT(). InitialNIPostChallengeV1(&atx.NIPostChallengeV1, gomock.Any(), goldenAtx). AnyTimes() - mvalidator.EXPECT().Post( + mValidator.EXPECT().Post( gomock.Any(), atx.SmesherID, *atx.CommitmentATXID, @@ -289,7 +292,7 @@ func validateAndPreserveData( atx.NumUnits, gomock.Any(), ) - mvalidator.EXPECT().VRFNonce( + mValidator.EXPECT().VRFNonce( atx.SmesherID, *atx.CommitmentATXID, *atx.VRFNonce, @@ -297,7 +300,7 @@ func validateAndPreserveData( atx.NumUnits, ) } else { - mvalidator.EXPECT().NIPostChallengeV1( + mValidator.EXPECT().NIPostChallengeV1( &atx.NIPostChallengeV1, gomock.Cond(func(prev *types.ActivationTx) bool { return prev.ID() == atx.PrevATXID @@ -306,13 +309,13 @@ func validateAndPreserveData( ) } - mvalidator.EXPECT().PositioningAtx(atx.PositioningATXID, cdb, goldenAtx, atx.PublishEpoch) - mvalidator.EXPECT(). + mValidator.EXPECT().PositioningAtx(atx.PositioningATXID, cdb, goldenAtx, atx.PublishEpoch) + mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, gomock.Any(), gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(uint64(1111111), nil) - mvalidator.EXPECT().IsVerifyingFullPost().AnyTimes().Return(true) - mreceiver.EXPECT().OnAtx(gomock.Any()) - mtrtl.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) + mValidator.EXPECT().IsVerifyingFullPost().AnyTimes().Return(true) + mBeacon.EXPECT().OnAtx(gomock.Any()) + mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) require.NoError(tb, atxHandler.HandleSyncedAtx(context.Background(), atx.ID().Hash32(), "self", dep.Blob)) } } From ce90c7a93d72a81ca5a4fa116a3c1e89e32b9d54 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:13:35 +0000 Subject: [PATCH 18/66] Use dedicated publisher for ATX v1 --- activation/e2e/atx_merge_test.go | 2 - activation/e2e/builds_atx_v2_test.go | 2 - activation/e2e/checkpoint_merged_test.go | 3 - activation/e2e/checkpoint_test.go | 3 - activation/handler.go | 3 +- activation/handler_test.go | 3 +- checkpoint/recovery_test.go | 2 - malfeasance/handler.go | 6 +- malfeasance/interface.go | 4 + malfeasance/mocks.go | 62 +++++++++++++ malfeasance/publisher.go | 76 +++++++++++++++ malfeasance/publisher_test.go | 3 + node/node.go | 112 +++++++++++++---------- 13 files changed, 213 insertions(+), 68 deletions(-) create mode 100644 malfeasance/publisher.go create mode 100644 malfeasance/publisher_test.go diff --git a/activation/e2e/atx_merge_test.go b/activation/e2e/atx_merge_test.go index 2cbc7885d5..3c52357aaa 100644 --- a/activation/e2e/atx_merge_test.go +++ b/activation/e2e/atx_merge_test.go @@ -276,7 +276,6 @@ func Test_MarryAndMerge(t *testing.T) { require.NoError(t, err) mFetch := smocks.NewMockFetcher(ctrl) - mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -291,7 +290,6 @@ func Test_MarryAndMerge(t *testing.T) { mFetch, goldenATX, validator, - mMalPublish, mLegacyPublish, mBeacon, mTortoise, diff --git a/activation/e2e/builds_atx_v2_test.go b/activation/e2e/builds_atx_v2_test.go index f3992b6b49..d7075ed982 100644 --- a/activation/e2e/builds_atx_v2_test.go +++ b/activation/e2e/builds_atx_v2_test.go @@ -118,7 +118,6 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { edVerifier := signing.NewEdVerifier() mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -132,7 +131,6 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { mFetch, goldenATX, validator, - mMalPublish, mLegacyPublish, mBeacon, mTortoise, diff --git a/activation/e2e/checkpoint_merged_test.go b/activation/e2e/checkpoint_merged_test.go index 88c2a87238..dcf2e49353 100644 --- a/activation/e2e/checkpoint_merged_test.go +++ b/activation/e2e/checkpoint_merged_test.go @@ -107,7 +107,6 @@ func Test_CheckpointAfterMerge(t *testing.T) { require.NoError(t, err) mFetch := smocks.NewMockFetcher(ctrl) - mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -121,7 +120,6 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch, goldenATX, validator, - mMalPublish, mLegacyPublish, mBeacon, mTortoise, @@ -301,7 +299,6 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch, goldenATX, validator, - mMalPublish, mLegacyPublish, mBeacon, mTortoise, diff --git a/activation/e2e/checkpoint_test.go b/activation/e2e/checkpoint_test.go index cf481facb7..f280718ba7 100644 --- a/activation/e2e/checkpoint_test.go +++ b/activation/e2e/checkpoint_test.go @@ -103,7 +103,6 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxVersions := activation.AtxVersions{0: types.AtxV2} edVerifier := signing.NewEdVerifier() mFetch := smocks.NewMockFetcher(ctrl) - mMalPublish := activation.NewMockatxMalfeasancePublisher(ctrl) mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -117,7 +116,6 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { mFetch, goldenATX, validator, - mMalPublish, mLegacyPublish, mBeacon, mTortoise, @@ -208,7 +206,6 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { mFetch, goldenATX, validator, - mMalPublish, mLegacyPublish, mBeacon, mTortoise, diff --git a/activation/handler.go b/activation/handler.go index b0ff36849c..e2bd9acd39 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -103,7 +103,6 @@ func NewHandler( fetcher system.Fetcher, goldenATXID types.ATXID, nipostValidator nipostValidator, - malPublisher atxMalfeasancePublisher, legacyMalPublisher legacyMalfeasancePublisher, beacon atxReceiver, tortoise system.Tortoise, @@ -145,7 +144,7 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, - malPublisher: malPublisher, + malPublisher: &MalfeasancePublisher{}, // TODO(mafa): pass real publisher when available }, } diff --git a/activation/handler_test.go b/activation/handler_test.go index 1ca409cdc2..63d7cdc123 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -215,6 +215,8 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio edVerifier := signing.NewEdVerifier() mocks := newTestHandlerMocks(tb, goldenATXID) + // TODO(mafa): make mandatory parameter when real publisher is available + opts = append(opts, func(h *Handler) { h.v2.malPublisher = mocks.mMalPublish }) atxHdlr := NewHandler( "localID", cdb, @@ -224,7 +226,6 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio mocks.mockFetch, goldenATXID, mocks.mValidator, - mocks.mMalPublish, mocks.mLegacyMalPublish, mocks.mBeacon, mocks.mTortoise, diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index 0f9b4e0751..4b22c57369 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -251,7 +251,6 @@ func validateAndPreserveData( mclock := activation.NewMocklayerClock(ctrl) mFetch := smocks.NewMockFetcher(ctrl) mValidator := activation.NewMocknipostValidator(ctrl) - mMalPublisher := activation.NewMockatxMalfeasancePublisher(ctrl) mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) @@ -266,7 +265,6 @@ func validateAndPreserveData( mFetch, goldenAtx, mValidator, - mMalPublisher, mLegacyPublish, mBeacon, mTortoise, diff --git a/malfeasance/handler.go b/malfeasance/handler.go index ded77bd507..b15eb2aa1f 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -55,14 +55,14 @@ func NewHandler( cdb *datastore.CachedDB, lg *zap.Logger, self p2p.Peer, - nodeID []types.NodeID, + nodeIDs []types.NodeID, tortoise tortoise, ) *Handler { return &Handler{ logger: lg, cdb: cdb, self: self, - nodeIDs: nodeID, + nodeIDs: nodeIDs, tortoise: tortoise, handlers: make(map[MalfeasanceType]MalfeasanceHandler), @@ -130,9 +130,9 @@ func (h *Handler) HandleSyncedMalfeasanceProof( // but only log "validation ignored" instead of the error we return h.logger.Warn("malfeasance proof for wrong identity", log.ZContext(ctx), + zap.Stringer("peer", peer), log.ZShortStringer("expected", expHash), log.ZShortStringer("got", nodeID), - zap.Stringer("peer", peer), ) return fmt.Errorf( "%w: malfeasance proof want %s, got %s", diff --git a/malfeasance/interface.go b/malfeasance/interface.go index 3486d5878f..79883b45a8 100644 --- a/malfeasance/interface.go +++ b/malfeasance/interface.go @@ -15,6 +15,10 @@ type tortoise interface { OnMalfeasance(types.NodeID) } +type syncer interface { + ListenToATXGossip() bool +} + type MalfeasanceHandler interface { Validate(ctx context.Context, data wire.ProofData) (types.NodeID, error) Info(data wire.ProofData) (map[string]string, error) diff --git a/malfeasance/mocks.go b/malfeasance/mocks.go index 5ab467d295..18dc4e9ec5 100644 --- a/malfeasance/mocks.go +++ b/malfeasance/mocks.go @@ -79,6 +79,68 @@ func (c *MocktortoiseOnMalfeasanceCall) DoAndReturn(f func(types.NodeID)) *Mockt return c } +// Mocksyncer is a mock of syncer interface. +type Mocksyncer struct { + ctrl *gomock.Controller + recorder *MocksyncerMockRecorder + isgomock struct{} +} + +// MocksyncerMockRecorder is the mock recorder for Mocksyncer. +type MocksyncerMockRecorder struct { + mock *Mocksyncer +} + +// NewMocksyncer creates a new mock instance. +func NewMocksyncer(ctrl *gomock.Controller) *Mocksyncer { + mock := &Mocksyncer{ctrl: ctrl} + mock.recorder = &MocksyncerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocksyncer) EXPECT() *MocksyncerMockRecorder { + return m.recorder +} + +// ListenToATXGossip mocks base method. +func (m *Mocksyncer) ListenToATXGossip() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListenToATXGossip") + ret0, _ := ret[0].(bool) + return ret0 +} + +// ListenToATXGossip indicates an expected call of ListenToATXGossip. +func (mr *MocksyncerMockRecorder) ListenToATXGossip() *MocksyncerListenToATXGossipCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListenToATXGossip", reflect.TypeOf((*Mocksyncer)(nil).ListenToATXGossip)) + return &MocksyncerListenToATXGossipCall{Call: call} +} + +// MocksyncerListenToATXGossipCall wrap *gomock.Call +type MocksyncerListenToATXGossipCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocksyncerListenToATXGossipCall) Return(arg0 bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocksyncerListenToATXGossipCall) Do(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocksyncerListenToATXGossipCall) DoAndReturn(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockMalfeasanceHandler is a mock of MalfeasanceHandler interface. type MockMalfeasanceHandler struct { ctrl *gomock.Controller diff --git a/malfeasance/publisher.go b/malfeasance/publisher.go new file mode 100644 index 0000000000..1969a18ce3 --- /dev/null +++ b/malfeasance/publisher.go @@ -0,0 +1,76 @@ +package malfeasance + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/malfeasance/wire" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/sql/identities" +) + +type Publisher struct { + logger *zap.Logger + cdb *datastore.CachedDB + tortoise tortoise + sync syncer + publisher pubsub.Publisher +} + +func NewPublisher( + logger *zap.Logger, + cdb *datastore.CachedDB, + tortoise tortoise, + sync syncer, + publisher pubsub.Publisher, +) *Publisher { + return &Publisher{ + logger: logger, + cdb: cdb, + tortoise: tortoise, + sync: sync, + publisher: publisher, + } +} + +// Publishes a malfeasance proof to the network. +func (p *Publisher) PublishProof(ctx context.Context, smesherID types.NodeID, proof *wire.MalfeasanceProof) error { + malicious, err := identities.IsMalicious(p.cdb, smesherID) + if err != nil { + return fmt.Errorf("check if smesher is malicious: %w", err) + } + if malicious { + p.logger.Debug("smesher is already marked as malicious", zap.String("smesher_id", smesherID.ShortString())) + return nil + } + + if err := identities.SetMalicious(p.cdb, smesherID, codec.MustEncode(proof), time.Now()); err != nil { + return fmt.Errorf("adding malfeasance proof: %w", err) + } + + p.cdb.CacheMalfeasanceProof(smesherID, codec.MustEncode(proof)) + p.tortoise.OnMalfeasance(smesherID) + + // Only gossip the proof if we are synced (to not spam the network with proofs others probably already have). + if !p.sync.ListenToATXGossip() { + p.logger.Debug("not synced, not broadcasting malfeasance proof", + zap.String("smesher_id", smesherID.ShortString()), + ) + return nil + } + gossip := wire.MalfeasanceGossip{ + MalfeasanceProof: *proof, + } + if err := p.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { + p.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) + return fmt.Errorf("broadcast atx malfeasance proof: %w", err) + } + + return nil +} diff --git a/malfeasance/publisher_test.go b/malfeasance/publisher_test.go new file mode 100644 index 0000000000..7da6c95512 --- /dev/null +++ b/malfeasance/publisher_test.go @@ -0,0 +1,3 @@ +package malfeasance + +// TODO(mafa): implement me diff --git a/node/node.go b/node/node.go index 002a127bb8..05ca9467e5 100644 --- a/node/node.go +++ b/node/node.go @@ -754,16 +754,55 @@ func (app *App) initServices(ctx context.Context) error { return blockssync.Sync(ctx, flog.Zap(), msh.MissingBlocks(), fetcher) }) + patrol := layerpatrol.New() + syncerConf := app.Config.Sync + syncerConf.HareDelayLayers = app.Config.Tortoise.Zdist + syncerConf.SyncCertDistance = app.Config.Tortoise.Hdist + syncerConf.Standalone = app.Config.Standalone + + app.syncLogger = app.addLogger(SyncLogger, lg) + newSyncer := syncer.NewSyncer( + app.cachedDB, + app.clock, + msh, + trtl, + fetcher, + patrol, + app.certifier, + atxsync.New(fetcher, app.db, app.localDB, + atxsync.WithConfig(app.Config.Sync.AtxSync), + atxsync.WithLogger(app.syncLogger.Zap()), + ), + malsync.New(fetcher, app.db, app.localDB, + malsync.WithConfig(app.Config.Sync.MalSync), + malsync.WithLogger(app.syncLogger.Zap()), + malsync.WithPeerErrMetric(syncer.MalPeerError), + ), + syncer.WithConfig(syncerConf), + syncer.WithLogger(app.syncLogger.Zap()), + ) + // TODO(dshulyak) this needs to be improved, but dependency graph is a bit complicated + beaconProtocol.SetSyncState(newSyncer) + + malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() + legacyMalPublisher := malfeasance.NewPublisher( + malfeasanceLogger, + app.cachedDB, + trtl, + newSyncer, + app.host, + ) + atxHandler := activation.NewHandler( app.host.ID(), app.cachedDB, app.atxsdata, app.edVerifier, app.clock, - app.host, fetcher, goldenATXID, validator, + legacyMalPublisher, beaconProtocol, trtl, app.addLogger(ATXHandlerLogger, lg).Zap(), @@ -775,7 +814,6 @@ func (app *App) initServices(ctx context.Context) error { } // we can't have an epoch offset which is greater/equal than the number of layers in an epoch - if app.Config.HareEligibility.ConfidenceParam >= app.Config.BaseConfig.LayersPerEpoch { return fmt.Errorf( "confidence param should be smaller than layers per epoch. eligibility-confidence-param: %d. "+ @@ -785,7 +823,11 @@ func (app *App) initServices(ctx context.Context) error { ) } - blockHandler := blocks.NewHandler(fetcher, app.db, trtl, msh, + blockHandler := blocks.NewHandler( + fetcher, + app.db, + trtl, + msh, blocks.WithLogger(app.addLogger(BlockHandlerLogger, lg).Zap()), ) @@ -795,7 +837,7 @@ func (app *App) initServices(ctx context.Context) error { app.addLogger(TxHandlerLogger, lg).Zap(), ) - app.hOracle = eligibility.New( + hOracle := eligibility.New( beaconProtocol, app.db, app.atxsdata, @@ -804,7 +846,7 @@ func (app *App) initServices(ctx context.Context) error { eligibility.WithConfig(app.Config.HareEligibility), eligibility.WithLogger(app.addLogger(HareOracleLogger, lg).Zap()), ) - // TODO: genesisMinerWeight is set to app.Config.SpaceToCommit, because PoET ticks are currently hardcoded to 1 + hOracle.SetSync(newSyncer) bscfg := app.Config.Bootstrap bscfg.DataDir = app.Config.DataDir() @@ -824,7 +866,7 @@ func (app *App) initServices(ctx context.Context) error { app.Config.Certificate.NumLayersToKeep = app.Config.Tortoise.Zdist * 2 app.certifier = blocks.NewCertifier( app.db, - app.hOracle, + hOracle, app.edVerifier, app.host, app.clock, @@ -837,39 +879,9 @@ func (app *App) initServices(ctx context.Context) error { app.certifier.Register(sig) } - patrol := layerpatrol.New() - syncerConf := app.Config.Sync - syncerConf.HareDelayLayers = app.Config.Tortoise.Zdist - syncerConf.SyncCertDistance = app.Config.Tortoise.Hdist - syncerConf.Standalone = app.Config.Standalone - if app.Config.P2P.MinPeers < app.Config.Sync.MalSync.MinSyncPeers { app.Config.Sync.MalSync.MinSyncPeers = max(1, app.Config.P2P.MinPeers) } - app.syncLogger = app.addLogger(SyncLogger, lg) - newSyncer := syncer.NewSyncer( - app.cachedDB, - app.clock, - msh, - trtl, - fetcher, - patrol, - app.certifier, - atxsync.New(fetcher, app.db, app.localDB, - atxsync.WithConfig(app.Config.Sync.AtxSync), - atxsync.WithLogger(app.syncLogger.Zap()), - ), - malsync.New(fetcher, app.db, app.localDB, - malsync.WithConfig(app.Config.Sync.MalSync), - malsync.WithLogger(app.syncLogger.Zap()), - malsync.WithPeerErrMetric(syncer.MalPeerError), - ), - syncer.WithConfig(syncerConf), - syncer.WithLogger(app.syncLogger.Zap()), - ) - // TODO(dshulyak) this needs to be improved, but dependency graph is a bit complicated - beaconProtocol.SetSyncState(newSyncer) - app.hOracle.SetSync(newSyncer) err = app.Config.HARE3.Validate(time.Duration(app.Config.Tortoise.Zdist) * app.Config.LayerDuration) if err != nil { @@ -887,7 +899,7 @@ func (app *App) initServices(ctx context.Context) error { app.atxsdata, proposalsStore, app.edVerifier, - app.hOracle, + hOracle, newSyncer, patrol, hare3.WithLogger(logger), @@ -917,7 +929,7 @@ func (app *App) initServices(ctx context.Context) error { app.atxsdata, proposalsStore, app.edVerifier, - app.hOracle, + hOracle, newSyncer, patrol, app.host, @@ -1118,7 +1130,6 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("init post service: %w", err) } - malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() activationMH := activation.NewMalfeasanceHandler( app.cachedDB, malfeasanceLogger, @@ -1208,13 +1219,13 @@ func (app *App) initServices(ctx context.Context) error { ), ) - syncHandler := func(_ context.Context, _ p2p.Peer, _ []byte) error { + checkSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { if newSyncer.ListenToGossip() { return nil } return errors.New("not synced for gossip") } - atxSyncHandler := func(_ context.Context, _ p2p.Peer, _ []byte) error { + checkAtxSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { if newSyncer.ListenToATXGossip() { return nil } @@ -1224,45 +1235,45 @@ func (app *App) initServices(ctx context.Context) error { if app.Config.Beacon.RoundsNumber > 0 { app.host.Register( pubsub.BeaconWeakCoinProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleWeakCoinProposal), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleWeakCoinProposal), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconProposalProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleProposal), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleProposal), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconFirstVotesProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleFirstVotes), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleFirstVotes), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconFollowingVotesProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleFollowingVotes), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleFollowingVotes), pubsub.WithValidatorInline(true), ) } app.host.Register( pubsub.ProposalProtocol, - pubsub.ChainGossipHandler(syncHandler, proposalListener.HandleProposal), + pubsub.ChainGossipHandler(checkSynced, proposalListener.HandleProposal), ) app.host.Register( pubsub.AtxProtocol, - pubsub.ChainGossipHandler(atxSyncHandler, atxHandler.HandleGossipAtx), + pubsub.ChainGossipHandler(checkAtxSynced, atxHandler.HandleGossipAtx), pubsub.WithValidatorConcurrency(app.Config.P2P.GossipAtxValidationThrottle), ) app.host.Register( pubsub.TxProtocol, - pubsub.ChainGossipHandler(syncHandler, app.txHandler.HandleGossipTransaction), + pubsub.ChainGossipHandler(checkSynced, app.txHandler.HandleGossipTransaction), ) app.host.Register( pubsub.BlockCertify, - pubsub.ChainGossipHandler(syncHandler, app.certifier.HandleCertifyMessage), + pubsub.ChainGossipHandler(checkSynced, app.certifier.HandleCertifyMessage), ) app.host.Register( pubsub.MalfeasanceProof, - pubsub.ChainGossipHandler(atxSyncHandler, app.malfeasanceHandler.HandleMalfeasanceProof), + pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), ) app.proposalBuilder = proposalBuilder @@ -1273,6 +1284,7 @@ func (app *App) initServices(ctx context.Context) error { app.poetDb = poetDb app.fetcher = fetcher app.beaconProtocol = beaconProtocol + app.hOracle = hOracle if !app.Config.TIME.Peersync.Disable { app.ptimesync = peersync.New( app.host, From baf130cdf40a90b513cc5f90101face97c02ba26 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:25:55 +0000 Subject: [PATCH 19/66] Fix dependencies during startup --- beacon/beacon.go | 10 ++---- hare3/eligibility/oracle.go | 8 ++--- node/node.go | 69 ++++++++++++++++++------------------- 3 files changed, 38 insertions(+), 49 deletions(-) diff --git a/beacon/beacon.go b/beacon/beacon.go index 692a92b8dd..1e41497ff4 100644 --- a/beacon/beacon.go +++ b/beacon/beacon.go @@ -85,6 +85,7 @@ func New( vrfVerifier vrfVerifier, cdb *datastore.CachedDB, clock layerClock, + syncer system.SyncStateProvider, opts ...Opt, ) *ProtocolDriver { pd := &ProtocolDriver{ @@ -96,6 +97,7 @@ func New( nonceFetcher: cdb, cdb: cdb, clock: clock, + sync: syncer, signers: make(map[types.NodeID]*signing.EdSigner), beacons: make(map[types.EpochID]types.Beacon), ballotsBeacons: make(map[types.EpochID]map[types.Beacon]*beaconWeight), @@ -207,14 +209,6 @@ type ProtocolDriver struct { metricsCollector *metrics.BeaconMetricsCollector } -// SetSyncState updates sync state provider. Must be executed only once. -func (pd *ProtocolDriver) SetSyncState(sync system.SyncStateProvider) { - if pd.sync != nil { - pd.logger.Fatal("sync state provider can be updated only once") - } - pd.sync = sync -} - // Start starts listening for layers and outputs. func (pd *ProtocolDriver) Start(ctx context.Context) { pd.startOnce.Do(func() { diff --git a/hare3/eligibility/oracle.go b/hare3/eligibility/oracle.go index d72693fa46..29e8125a8d 100644 --- a/hare3/eligibility/oracle.go +++ b/hare3/eligibility/oracle.go @@ -120,6 +120,7 @@ func New( db sql.Executor, atxsdata *atxsdata.Data, vrfVerifier vrfVerifier, + syncer system.SyncStateProvider, layersPerEpoch uint32, opts ...Opt, ) *Oracle { @@ -132,6 +133,7 @@ func New( db: db, atxsdata: atxsdata, vrfVerifier: vrfVerifier, + sync: syncer, activesCache: activesCache, fallback: map[types.EpochID][]types.ATXID{}, cfg: DefaultConfig(), @@ -154,12 +156,6 @@ type VrfMessage struct { Layer types.LayerID } -func (o *Oracle) SetSync(sync system.SyncStateProvider) { - o.mu.Lock() - defer o.mu.Unlock() - o.sync = sync -} - func (o *Oracle) resetCacheOnSynced(ctx context.Context) { if o.sync == nil { return diff --git a/node/node.go b/node/node.go index 05ca9467e5..631401c1dc 100644 --- a/node/node.go +++ b/node/node.go @@ -660,20 +660,6 @@ func (app *App) initServices(ctx context.Context) error { signing.WithVerifierPrefix(app.Config.Genesis.GenesisID().Bytes()), ) - vrfVerifier := signing.NewVRFVerifier() - beaconProtocol := beacon.New( - app.host, - app.edVerifier, - vrfVerifier, - app.cachedDB, - app.clock, - beacon.WithConfig(app.Config.Beacon), - beacon.WithLogger(app.addLogger(BeaconLogger, lg).Zap()), - ) - for _, sig := range app.signers { - beaconProtocol.Register(sig) - } - trtlCfg := app.Config.Tortoise trtlCfg.LayerSize = layerSize if trtlCfg.BadBeaconVoteDelayLayers == 0 { @@ -699,14 +685,6 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("can't recover tortoise state: %w", err) } app.log.With().Info("tortoise initialized", log.Duration("duration", time.Since(start))) - app.eg.Go(func() error { - for rst := range beaconProtocol.Results() { - events.EmitBeacon(rst.Epoch, rst.Beacon) - trtl.OnBeacon(rst.Epoch, rst.Beacon) - } - app.log.Debug("beacon results watcher exited") - return nil - }) executor := mesh.NewExecutor( app.db, @@ -761,7 +739,7 @@ func (app *App) initServices(ctx context.Context) error { syncerConf.Standalone = app.Config.Standalone app.syncLogger = app.addLogger(SyncLogger, lg) - newSyncer := syncer.NewSyncer( + syncer := syncer.NewSyncer( app.cachedDB, app.clock, msh, @@ -781,15 +759,36 @@ func (app *App) initServices(ctx context.Context) error { syncer.WithConfig(syncerConf), syncer.WithLogger(app.syncLogger.Zap()), ) - // TODO(dshulyak) this needs to be improved, but dependency graph is a bit complicated - beaconProtocol.SetSyncState(newSyncer) + + vrfVerifier := signing.NewVRFVerifier() + beaconProtocol := beacon.New( + app.host, + app.edVerifier, + vrfVerifier, + app.cachedDB, + app.clock, + syncer, + beacon.WithConfig(app.Config.Beacon), + beacon.WithLogger(app.addLogger(BeaconLogger, lg).Zap()), + ) + for _, sig := range app.signers { + beaconProtocol.Register(sig) + } + app.eg.Go(func() error { + for rst := range beaconProtocol.Results() { + events.EmitBeacon(rst.Epoch, rst.Beacon) + trtl.OnBeacon(rst.Epoch, rst.Beacon) + } + app.log.Debug("beacon results watcher exited") + return nil + }) malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() legacyMalPublisher := malfeasance.NewPublisher( malfeasanceLogger, app.cachedDB, trtl, - newSyncer, + syncer, app.host, ) @@ -842,11 +841,11 @@ func (app *App) initServices(ctx context.Context) error { app.db, app.atxsdata, vrfVerifier, + syncer, app.Config.LayersPerEpoch, eligibility.WithConfig(app.Config.HareEligibility), eligibility.WithLogger(app.addLogger(HareOracleLogger, lg).Zap()), ) - hOracle.SetSync(newSyncer) bscfg := app.Config.Bootstrap bscfg.DataDir = app.Config.DataDir() @@ -900,7 +899,7 @@ func (app *App) initServices(ctx context.Context) error { proposalsStore, app.edVerifier, hOracle, - newSyncer, + syncer, patrol, hare3.WithLogger(logger), hare3.WithConfig(app.Config.HARE3), @@ -930,7 +929,7 @@ func (app *App) initServices(ctx context.Context) error { proposalsStore, app.edVerifier, hOracle, - newSyncer, + syncer, patrol, app.host, hare4.WithLogger(logger), @@ -1010,7 +1009,7 @@ func (app *App) initServices(ctx context.Context) error { app.atxsdata, app.host, trtl, - newSyncer, + syncer, app.conState, miner.WithLayerSize(layerSize), miner.WithLayerPerEpoch(layersPerEpoch), @@ -1031,7 +1030,7 @@ func (app *App) initServices(ctx context.Context) error { app.db, app.atxsdata, goldenATXID, - newSyncer, + syncer, app.validator, activation.PostValidityDelay(app.Config.PostValidDelay), ) @@ -1095,7 +1094,7 @@ func (app *App) initServices(ctx context.Context) error { app.host, nipostBuilder, app.clock, - newSyncer, + syncer, app.addLogger(ATXBuilderLogger, lg).Zap(), activation.WithContext(ctx), activation.WithPoetConfig(app.Config.POET), @@ -1220,13 +1219,13 @@ func (app *App) initServices(ctx context.Context) error { ) checkSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { - if newSyncer.ListenToGossip() { + if syncer.ListenToGossip() { return nil } return errors.New("not synced for gossip") } checkAtxSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { - if newSyncer.ListenToATXGossip() { + if syncer.ListenToATXGossip() { return nil } return errors.New("not synced for gossip") @@ -1278,7 +1277,7 @@ func (app *App) initServices(ctx context.Context) error { app.proposalBuilder = proposalBuilder app.mesh = msh - app.syncer = newSyncer + app.syncer = syncer app.atxBuilder = atxBuilder app.atxHandler = atxHandler app.poetDb = poetDb From 1882f83f68de6d328411c7bad85f377f296a450a Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:27:08 +0000 Subject: [PATCH 20/66] Add more tests and fix failing ones --- beacon/beacon_test.go | 9 +- hare3/eligibility/oracle.go | 3 - hare3/eligibility/oracle_test.go | 37 +++++-- hare3/hare_test.go | 1 + hare4/eligibility/oracle.go | 11 +- hare4/eligibility/oracle_test.go | 34 ++++-- hare4/hare_test.go | 1 + malfeasance/publisher.go | 4 +- malfeasance/publisher_test.go | 178 ++++++++++++++++++++++++++++++- node/node.go | 2 +- 10 files changed, 243 insertions(+), 37 deletions(-) diff --git a/beacon/beacon_test.go b/beacon/beacon_test.go index 220ba93229..76ca87f462 100644 --- a/beacon/beacon_test.go +++ b/beacon/beacon_test.go @@ -95,12 +95,17 @@ func newTestDriver(tb testing.TB, cfg Config, p pubsub.Publisher, miners int, id tpd.cdb = datastore.NewCachedDB(statesql.InMemoryTest(tb), lg) tb.Cleanup(func() { assert.NoError(tb, tpd.cdb.Close()) }) - tpd.ProtocolDriver = New(p, signing.NewEdVerifier(), tpd.mVerifier, tpd.cdb, tpd.mClock, + tpd.ProtocolDriver = New( + p, + signing.NewEdVerifier(), + tpd.mVerifier, + tpd.cdb, + tpd.mClock, + tpd.mSync, WithConfig(cfg), WithLogger(lg), withWeakCoin(coinValueMock(tb, true)), ) - tpd.ProtocolDriver.SetSyncState(tpd.mSync) for i := 0; i < miners; i++ { edSgn, err := signing.NewEdSigner() require.NoError(tb, err) diff --git a/hare3/eligibility/oracle.go b/hare3/eligibility/oracle.go index 29e8125a8d..799ff1b398 100644 --- a/hare3/eligibility/oracle.go +++ b/hare3/eligibility/oracle.go @@ -157,9 +157,6 @@ type VrfMessage struct { } func (o *Oracle) resetCacheOnSynced(ctx context.Context) { - if o.sync == nil { - return - } synced := o.synced o.synced = o.sync.IsSynced(ctx) if !synced && o.synced { diff --git a/hare3/eligibility/oracle_test.go b/hare3/eligibility/oracle_test.go index fac51a5b6b..a028202522 100644 --- a/hare3/eligibility/oracle_test.go +++ b/hare3/eligibility/oracle_test.go @@ -51,6 +51,7 @@ type testOracle struct { atxsdata *atxsdata.Data mBeacon *mocks.MockBeaconGetter mVerifier *MockvrfVerifier + mSyncer *mocks.MockSyncStateProvider } func defaultOracle(tb testing.TB) *testOracle { @@ -60,13 +61,15 @@ func defaultOracle(tb testing.TB) *testOracle { ctrl := gomock.NewController(tb) mBeacon := mocks.NewMockBeaconGetter(ctrl) mVerifier := NewMockvrfVerifier(ctrl) + mSyncer := mocks.NewMockSyncStateProvider(ctrl) - to := &testOracle{ + return &testOracle{ Oracle: New( mBeacon, db, atxsdata, mVerifier, + mSyncer, defLayersPerEpoch, WithConfig(Config{ConfidenceParam: confidenceParam}), WithLogger(zaptest.NewLogger(tb)), @@ -74,10 +77,10 @@ func defaultOracle(tb testing.TB) *testOracle { tb: tb, mBeacon: mBeacon, mVerifier: mVerifier, + mSyncer: mSyncer, db: db, atxsdata: atxsdata, } - return to } func (t *testOracle) createBallots( @@ -185,6 +188,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active set", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -193,6 +197,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("miner not active", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() o.createLayerData(lid.Sub(defLayersPerEpoch), 11) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) @@ -206,6 +211,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) errUnknown := errors.New("unknown") o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.EmptyBeacon, errUnknown).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.ErrorIs(t, err, errUnknown) @@ -218,6 +224,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.RandomBeacon(), nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(false).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -227,6 +234,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active with fallback", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) lid := types.EpochID(5).FirstLayer().Add(o.cfg.ConfidenceParam) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -236,6 +244,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createActiveSet(types.EpochID(4).FirstLayer(), activeSet) o.UpdateActiveSet(5, activeSet) o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.RandomBeacon(), nil) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) _, err = o.CalcEligibility(context.Background(), lid, 1, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -267,6 +276,7 @@ func TestCalcEligibility(t *testing.T) { o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) res, err := o.CalcEligibility(context.Background(), lid, 1, 10, miners[0], vrfSig) require.NoError(t, err, vrf) require.Equal(t, exp, res, vrf) @@ -303,6 +313,7 @@ func TestCalcEligibilityWithSpaceUnit(t *testing.T) { sig := types.RandomVrfSignature() o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(2) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(4) res, err := o.CalcEligibility(context.Background(), lid, 1, committeeSize, nodeID, sig) require.NoError(t, err) @@ -363,6 +374,7 @@ func Test_VrfSignVerify(t *testing.T) { first := types.EpochID(5).FirstLayer() prevEpoch := lid.GetEpoch() - 1 o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.Beacon{1, 0, 0, 0}, nil).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() numMiners := 2 activeSet := types.RandomActiveSet(numMiners) @@ -437,6 +449,7 @@ func TestOracle_IsIdentityActive(t *testing.T) { o := defaultOracle(t) layer := types.LayerID(defLayersPerEpoch * 4) numMiners := 2 + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() miners := o.createLayerData(layer.Sub(defLayersPerEpoch), numMiners) for _, nodeID := range miners { v, err := o.IsIdentityActiveOnConsensusView(context.Background(), nodeID, layer) @@ -510,6 +523,7 @@ func TestActiveSet(t *testing.T) { o := defaultOracle(t) targetEpoch := types.EpochID(5) layer := targetEpoch.FirstLayer().Add(o.cfg.ConfidenceParam) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() o.createLayerData(targetEpoch.FirstLayer(), numMiners) aset, err := o.actives(context.Background(), layer) @@ -536,6 +550,7 @@ func TestActives(t *testing.T) { numMiners := 5 t.Run("genesis bootstrap", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) first := types.GetEffectiveGenesis().Add(1) bootstrap := types.RandomActiveSet(numMiners) o.createActiveSet(types.EpochID(1).FirstLayer(), bootstrap) @@ -559,6 +574,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() o.createLayerData(layer, numMiners) @@ -587,6 +603,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() end := layer.Add(o.cfg.ConfidenceParam) o.createLayerData(layer, numMiners) @@ -612,6 +629,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() old := types.GetEffectiveGenesis() types.SetEffectiveGenesis(layer.Uint32() - 1) @@ -663,6 +681,7 @@ func TestActives_ConcurrentCalls(t *testing.T) { var wg sync.WaitGroup wg.Add(102) runFn := func() { + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) _, err := o.actives(context.Background(), layer) r.NoError(err) wg.Done() @@ -912,7 +931,8 @@ func TestActiveSetMatrix(t *testing.T) { } else { oracle.mBeacon.EXPECT().GetBeacon(target).Return(types.EmptyBeacon, sql.ErrNotFound) } - rst, err := oracle.ActiveSet(context.TODO(), target) + oracle.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) + rst, err := oracle.ActiveSet(context.Background(), target) switch typed := tc.expect.(type) { case []types.ATXID: @@ -930,29 +950,26 @@ func TestActiveSetMatrix(t *testing.T) { func TestResetCache(t *testing.T) { oracle := defaultOracle(t) - ctrl := gomock.NewController(t) prev := oracle.activesCache prev.Add(1, nil) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync := mocks.NewMockSyncStateProvider(ctrl) - oracle.SetSync(sync) - - sync.EXPECT().IsSynced(gomock.Any()).Return(false) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.NotEqual(t, prev, oracle.activesCache) prev = oracle.activesCache prev.Add(1, nil) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) } diff --git a/hare3/hare_test.go b/hare3/hare_test.go index 6feae4678b..2cb4045d56 100644 --- a/hare3/hare_test.go +++ b/hare3/hare_test.go @@ -197,6 +197,7 @@ func (n *node) withOracle() *node { n.db, n.atxsdata, signing.NewVRFVerifier(), + n.msyncer, layersPerEpoch, ) return n diff --git a/hare4/eligibility/oracle.go b/hare4/eligibility/oracle.go index 67b2de7afa..38b61ffe6a 100644 --- a/hare4/eligibility/oracle.go +++ b/hare4/eligibility/oracle.go @@ -120,6 +120,7 @@ func New( db sql.Executor, atxsdata *atxsdata.Data, vrfVerifier vrfVerifier, + syncer system.SyncStateProvider, layersPerEpoch uint32, opts ...Opt, ) *Oracle { @@ -132,6 +133,7 @@ func New( db: db, atxsdata: atxsdata, vrfVerifier: vrfVerifier, + sync: syncer, activesCache: activesCache, fallback: map[types.EpochID][]types.ATXID{}, cfg: DefaultConfig(), @@ -154,16 +156,7 @@ type VrfMessage struct { Layer types.LayerID } -func (o *Oracle) SetSync(sync system.SyncStateProvider) { - o.mu.Lock() - defer o.mu.Unlock() - o.sync = sync -} - func (o *Oracle) resetCacheOnSynced(ctx context.Context) { - if o.sync == nil { - return - } synced := o.synced o.synced = o.sync.IsSynced(ctx) if !synced && o.synced { diff --git a/hare4/eligibility/oracle_test.go b/hare4/eligibility/oracle_test.go index fac51a5b6b..f42375c2a8 100644 --- a/hare4/eligibility/oracle_test.go +++ b/hare4/eligibility/oracle_test.go @@ -51,6 +51,7 @@ type testOracle struct { atxsdata *atxsdata.Data mBeacon *mocks.MockBeaconGetter mVerifier *MockvrfVerifier + mSyncer *mocks.MockSyncStateProvider } func defaultOracle(tb testing.TB) *testOracle { @@ -60,6 +61,7 @@ func defaultOracle(tb testing.TB) *testOracle { ctrl := gomock.NewController(tb) mBeacon := mocks.NewMockBeaconGetter(ctrl) mVerifier := NewMockvrfVerifier(ctrl) + mSyncer := mocks.NewMockSyncStateProvider(ctrl) to := &testOracle{ Oracle: New( @@ -67,6 +69,7 @@ func defaultOracle(tb testing.TB) *testOracle { db, atxsdata, mVerifier, + mSyncer, defLayersPerEpoch, WithConfig(Config{ConfidenceParam: confidenceParam}), WithLogger(zaptest.NewLogger(tb)), @@ -74,6 +77,7 @@ func defaultOracle(tb testing.TB) *testOracle { tb: tb, mBeacon: mBeacon, mVerifier: mVerifier, + mSyncer: mSyncer, db: db, atxsdata: atxsdata, } @@ -185,6 +189,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active set", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -193,6 +198,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("miner not active", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() o.createLayerData(lid.Sub(defLayersPerEpoch), 11) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) @@ -206,6 +212,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) errUnknown := errors.New("unknown") o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.EmptyBeacon, errUnknown).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.ErrorIs(t, err, errUnknown) @@ -218,6 +225,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.RandomBeacon(), nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(false).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -227,6 +235,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active with fallback", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) lid := types.EpochID(5).FirstLayer().Add(o.cfg.ConfidenceParam) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -236,6 +245,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createActiveSet(types.EpochID(4).FirstLayer(), activeSet) o.UpdateActiveSet(5, activeSet) o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.RandomBeacon(), nil) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) _, err = o.CalcEligibility(context.Background(), lid, 1, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -267,6 +277,7 @@ func TestCalcEligibility(t *testing.T) { o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) res, err := o.CalcEligibility(context.Background(), lid, 1, 10, miners[0], vrfSig) require.NoError(t, err, vrf) require.Equal(t, exp, res, vrf) @@ -303,6 +314,7 @@ func TestCalcEligibilityWithSpaceUnit(t *testing.T) { sig := types.RandomVrfSignature() o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(2) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(4) res, err := o.CalcEligibility(context.Background(), lid, 1, committeeSize, nodeID, sig) require.NoError(t, err) @@ -363,6 +375,7 @@ func Test_VrfSignVerify(t *testing.T) { first := types.EpochID(5).FirstLayer() prevEpoch := lid.GetEpoch() - 1 o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.Beacon{1, 0, 0, 0}, nil).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() numMiners := 2 activeSet := types.RandomActiveSet(numMiners) @@ -437,6 +450,7 @@ func TestOracle_IsIdentityActive(t *testing.T) { o := defaultOracle(t) layer := types.LayerID(defLayersPerEpoch * 4) numMiners := 2 + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() miners := o.createLayerData(layer.Sub(defLayersPerEpoch), numMiners) for _, nodeID := range miners { v, err := o.IsIdentityActiveOnConsensusView(context.Background(), nodeID, layer) @@ -510,6 +524,7 @@ func TestActiveSet(t *testing.T) { o := defaultOracle(t) targetEpoch := types.EpochID(5) layer := targetEpoch.FirstLayer().Add(o.cfg.ConfidenceParam) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() o.createLayerData(targetEpoch.FirstLayer(), numMiners) aset, err := o.actives(context.Background(), layer) @@ -536,6 +551,7 @@ func TestActives(t *testing.T) { numMiners := 5 t.Run("genesis bootstrap", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) first := types.GetEffectiveGenesis().Add(1) bootstrap := types.RandomActiveSet(numMiners) o.createActiveSet(types.EpochID(1).FirstLayer(), bootstrap) @@ -559,6 +575,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() o.createLayerData(layer, numMiners) @@ -587,6 +604,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() end := layer.Add(o.cfg.ConfidenceParam) o.createLayerData(layer, numMiners) @@ -612,6 +630,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() old := types.GetEffectiveGenesis() types.SetEffectiveGenesis(layer.Uint32() - 1) @@ -663,6 +682,7 @@ func TestActives_ConcurrentCalls(t *testing.T) { var wg sync.WaitGroup wg.Add(102) runFn := func() { + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) _, err := o.actives(context.Background(), layer) r.NoError(err) wg.Done() @@ -912,7 +932,8 @@ func TestActiveSetMatrix(t *testing.T) { } else { oracle.mBeacon.EXPECT().GetBeacon(target).Return(types.EmptyBeacon, sql.ErrNotFound) } - rst, err := oracle.ActiveSet(context.TODO(), target) + oracle.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) + rst, err := oracle.ActiveSet(context.Background(), target) switch typed := tc.expect.(type) { case []types.ATXID: @@ -930,29 +951,26 @@ func TestActiveSetMatrix(t *testing.T) { func TestResetCache(t *testing.T) { oracle := defaultOracle(t) - ctrl := gomock.NewController(t) prev := oracle.activesCache prev.Add(1, nil) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync := mocks.NewMockSyncStateProvider(ctrl) - oracle.SetSync(sync) - - sync.EXPECT().IsSynced(gomock.Any()).Return(false) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.NotEqual(t, prev, oracle.activesCache) prev = oracle.activesCache prev.Add(1, nil) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) } diff --git a/hare4/hare_test.go b/hare4/hare_test.go index bb30d023bf..cf5bc43e5f 100644 --- a/hare4/hare_test.go +++ b/hare4/hare_test.go @@ -208,6 +208,7 @@ func (n *node) withOracle() *node { n.db, n.atxsdata, signing.NewVRFVerifier(), + n.msyncer, layersPerEpoch, ) return n diff --git a/malfeasance/publisher.go b/malfeasance/publisher.go index 1969a18ce3..6e6f222c24 100644 --- a/malfeasance/publisher.go +++ b/malfeasance/publisher.go @@ -26,8 +26,8 @@ type Publisher struct { func NewPublisher( logger *zap.Logger, cdb *datastore.CachedDB, - tortoise tortoise, sync syncer, + tortoise tortoise, publisher pubsub.Publisher, ) *Publisher { return &Publisher{ @@ -68,9 +68,7 @@ func (p *Publisher) PublishProof(ctx context.Context, smesherID types.NodeID, pr MalfeasanceProof: *proof, } if err := p.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { - p.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) return fmt.Errorf("broadcast atx malfeasance proof: %w", err) } - return nil } diff --git a/malfeasance/publisher_test.go b/malfeasance/publisher_test.go index 7da6c95512..47efcfa61d 100644 --- a/malfeasance/publisher_test.go +++ b/malfeasance/publisher_test.go @@ -1,3 +1,179 @@ package malfeasance -// TODO(mafa): implement me +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "go.uber.org/zap/zaptest" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/malfeasance/wire" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" + "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/identities" + "github.com/spacemeshos/go-spacemesh/sql/statesql" +) + +type testMalPublisher struct { + *Publisher + + mSyncer *Mocksyncer + mTortoise *Mocktortoise + mPublisher *mocks.MockPublisher +} + +func newTestPublisher(tb testing.TB) *testMalPublisher { + logger := zaptest.NewLogger(tb) + + db := statesql.InMemoryTest(tb) + cdb := datastore.NewCachedDB(db, logger) + + ctrl := gomock.NewController(tb) + mSyncer := NewMocksyncer(ctrl) + mTortoise := NewMocktortoise(ctrl) + mPublisher := mocks.NewMockPublisher(ctrl) + + publisher := NewPublisher( + logger, + cdb, + mSyncer, + mTortoise, + mPublisher, + ) + + return &testMalPublisher{ + Publisher: publisher, + + mSyncer: mSyncer, + mTortoise: mTortoise, + mPublisher: mPublisher, + } +} + +func TestMalfeasancePublisher(t *testing.T) { + t.Run("PublishProof when in sync", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + proof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + malPublisher.mTortoise.EXPECT().OnMalfeasance(nodeID) + malPublisher.mSyncer.EXPECT().ListenToATXGossip().Return(true) + malPublisher.mPublisher.EXPECT(). + Publish(context.Background(), pubsub.MalfeasanceProof, gomock.Any()). + DoAndReturn(func(ctx context.Context, s string, b []byte) error { + var gossip wire.MalfeasanceGossip + codec.MustDecode(b, &gossip) + require.Equal(t, *proof, gossip.MalfeasanceProof) + return nil + }) + + err := malPublisher.PublishProof(context.Background(), nodeID, proof) + require.NoError(t, err) + + malicious, err := identities.IsMalicious(malPublisher.cdb, nodeID) + require.NoError(t, err) + require.True(t, malicious) + }) + + t.Run("PublishProof when not in sync", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + proof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + malPublisher.mTortoise.EXPECT().OnMalfeasance(nodeID) + malPublisher.mSyncer.EXPECT().ListenToATXGossip().Return(false) + + err := malPublisher.PublishProof(context.Background(), nodeID, proof) + require.NoError(t, err) + + // proof is only persisted but not published + malicious, err := identities.IsMalicious(malPublisher.cdb, nodeID) + require.NoError(t, err) + require.True(t, malicious) + }) + + t.Run("PublishProof when already marked as malicious", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + existingProof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + err := identities.SetMalicious(malPublisher.cdb, nodeID, codec.MustEncode(existingProof), time.Now()) + require.NoError(t, err) + + proof := &wire.MalfeasanceProof{ + Layer: 11, + Proof: wire.Proof{ + Type: wire.MultipleBallots, + Data: &wire.BallotProof{}, + }, + } + err = malPublisher.PublishProof(context.Background(), nodeID, proof) + require.NoError(t, err) + + // no new malfeasance proof is added + var blob sql.Blob + err = identities.LoadMalfeasanceBlob(context.Background(), malPublisher.cdb, nodeID.Bytes(), &blob) + require.NoError(t, err) + + dbProof := &wire.MalfeasanceProof{} + codec.MustDecode(blob.Bytes, dbProof) + + require.Equal(t, existingProof, dbProof) + }) + + t.Run("PublishProof when error occurs", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + proof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + malPublisher.mTortoise.EXPECT().OnMalfeasance(nodeID) + malPublisher.mSyncer.EXPECT().ListenToATXGossip().Return(true) + errPublish := errors.New("publish failed") + malPublisher.mPublisher.EXPECT(). + Publish(context.Background(), pubsub.MalfeasanceProof, gomock.Any()). + Return(errPublish) + + err := malPublisher.PublishProof(context.Background(), nodeID, proof) + require.ErrorIs(t, err, errPublish) + + // malfeasance proof is still added to db + malicious, err := identities.IsMalicious(malPublisher.cdb, nodeID) + require.NoError(t, err) + require.True(t, malicious) + }) +} diff --git a/node/node.go b/node/node.go index 631401c1dc..d79e73f40f 100644 --- a/node/node.go +++ b/node/node.go @@ -787,8 +787,8 @@ func (app *App) initServices(ctx context.Context) error { legacyMalPublisher := malfeasance.NewPublisher( malfeasanceLogger, app.cachedDB, - trtl, syncer, + trtl, app.host, ) From 0752c0f95ac79ae90380c92d83facbd125975888 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:50:12 +0000 Subject: [PATCH 21/66] Fix race in test --- hare3/eligibility/oracle_test.go | 4 ++-- hare4/eligibility/oracle_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hare3/eligibility/oracle_test.go b/hare3/eligibility/oracle_test.go index a028202522..eed72d63b9 100644 --- a/hare3/eligibility/oracle_test.go +++ b/hare3/eligibility/oracle_test.go @@ -678,17 +678,17 @@ func TestActives_ConcurrentCalls(t *testing.T) { mc.EXPECT().Add(layer.GetEpoch()-1, gomock.Any()) o.activesCache = mc + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(102) var wg sync.WaitGroup wg.Add(102) runFn := func() { - o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) _, err := o.actives(context.Background(), layer) r.NoError(err) wg.Done() } // outstanding probability for concurrent access to calc active set size - for i := 0; i < 100; i++ { + for range 100 { go runFn() } diff --git a/hare4/eligibility/oracle_test.go b/hare4/eligibility/oracle_test.go index f42375c2a8..32e32758e3 100644 --- a/hare4/eligibility/oracle_test.go +++ b/hare4/eligibility/oracle_test.go @@ -679,17 +679,17 @@ func TestActives_ConcurrentCalls(t *testing.T) { mc.EXPECT().Add(layer.GetEpoch()-1, gomock.Any()) o.activesCache = mc + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(102) var wg sync.WaitGroup wg.Add(102) runFn := func() { - o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) _, err := o.actives(context.Background(), layer) r.NoError(err) wg.Done() } // outstanding probability for concurrent access to calc active set size - for i := 0; i < 100; i++ { + for range 100 { go runFn() } From b1eee69a51c92dcef6eabeb0d56d598af694f6d5 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:02:37 +0000 Subject: [PATCH 22/66] Add TODO --- node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/node.go b/node/node.go index 6232202044..07f6a55b11 100644 --- a/node/node.go +++ b/node/node.go @@ -1290,7 +1290,7 @@ func (app *App) initServices(ctx context.Context) error { pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), ) app.host.Register( - pubsub.MalfeasanceProof2, + pubsub.MalfeasanceProof2, // TODO(mafa): pass correct handler pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), ) From 80bef7c9ea8b0f8f7291ce4b3898b45d22895e7a Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:05:45 +0000 Subject: [PATCH 23/66] Rename handler file --- activation/{malfeasance_handler.go => malfeasance.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename activation/{malfeasance_handler.go => malfeasance.go} (100%) diff --git a/activation/malfeasance_handler.go b/activation/malfeasance.go similarity index 100% rename from activation/malfeasance_handler.go rename to activation/malfeasance.go From a76a092989f0520c33cfb51111136db10d671c6c Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:34:52 +0000 Subject: [PATCH 24/66] Fix typo --- sync2/multipeer/multipeer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync2/multipeer/multipeer.go b/sync2/multipeer/multipeer.go index 8f5fd33adc..d206150430 100644 --- a/sync2/multipeer/multipeer.go +++ b/sync2/multipeer/multipeer.go @@ -104,7 +104,7 @@ func DefaultConfig() MultiPeerReconcilerConfig { } } -// MultiPeerReconciler reconcilies the local set against multiple remote sets. +// MultiPeerReconciler reconciles the local set against multiple remote sets. type MultiPeerReconciler struct { logger *zap.Logger cfg MultiPeerReconcilerConfig From 8c6bb28b027d4decd373dd52c007973143da168f Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:06:37 +0000 Subject: [PATCH 25/66] Update malfeasance.Info to fetch proof from db instead of passing it as argument --- api/grpcserver/activation_service.go | 1 - api/grpcserver/mesh_service.go | 9 +++-- api/grpcserver/v2alpha1/interface.go | 8 ++++- api/grpcserver/v2alpha1/malfeasance.go | 39 ++++++++++----------- api/grpcserver/v2alpha1/malfeasance_test.go | 6 ++-- api/grpcserver/v2alpha1/mocks.go | 14 ++++---- events/events.go | 6 +++- events/malfeasance.go | 2 +- malfeasance/handler.go | 9 +++-- malfeasance/handler_test.go | 20 +++++++---- malfeasance2/publisher.go | 2 +- 11 files changed, 70 insertions(+), 46 deletions(-) diff --git a/api/grpcserver/activation_service.go b/api/grpcserver/activation_service.go index 4b5bb0c95e..3db73e8625 100644 --- a/api/grpcserver/activation_service.go +++ b/api/grpcserver/activation_service.go @@ -75,7 +75,6 @@ func (s *activationService) Get(ctx context.Context, request *pb.GetRequest) (*p proof, err := s.atxProvider.MalfeasanceProof(atx.SmesherID) if err != nil && !errors.Is(err, sql.ErrNotFound) { ctxzap.Error(ctx, "failed to get malfeasance proof", - zap.Stringer("smesher", atx.SmesherID), zap.Stringer("smesher", atx.SmesherID), zap.Stringer("id", atxId), zap.Error(err), diff --git a/api/grpcserver/mesh_service.go b/api/grpcserver/mesh_service.go index a256cf1f19..d27693d4fa 100644 --- a/api/grpcserver/mesh_service.go +++ b/api/grpcserver/mesh_service.go @@ -614,6 +614,7 @@ func (s *MeshService) MalfeasanceQuery( if err != nil && !errors.Is(err, sql.ErrNotFound) { return nil, status.Error(codes.Internal, err.Error()) } + // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes return &pb.MalfeasanceResponse{ Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof), }, nil @@ -627,7 +628,7 @@ func (s *MeshService) MalfeasanceStream( if sub == nil { return status.Errorf(codes.FailedPrecondition, "event reporting is not enabled") } - eventch, fullch := consumeEvents[events.EventMalfeasance](stream.Context(), sub) + eventCh, fullCh := consumeEvents[events.EventMalfeasance](stream.Context(), sub) if err := stream.SendHeader(metadata.MD{}); err != nil { return status.Errorf(codes.Unavailable, "can't send header") } @@ -638,6 +639,7 @@ func (s *MeshService) MalfeasanceStream( case <-stream.Context().Done(): return nil default: + // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes res := &pb.MalfeasanceStreamResponse{ Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof), } @@ -651,9 +653,10 @@ func (s *MeshService) MalfeasanceStream( select { case <-stream.Context().Done(): return nil - case <-fullch: + case <-fullCh: return status.Errorf(codes.Canceled, "buffer is full") - case ev := <-eventch: + case ev := <-eventCh: + // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes if err := stream.Send(&pb.MalfeasanceStreamResponse{ Proof: events.ToMalfeasancePB(ev.Smesher, ev.Proof, req.IncludeProof), }); err != nil { diff --git a/api/grpcserver/v2alpha1/interface.go b/api/grpcserver/v2alpha1/interface.go index d0a03b528c..377d94937b 100644 --- a/api/grpcserver/v2alpha1/interface.go +++ b/api/grpcserver/v2alpha1/interface.go @@ -1,7 +1,13 @@ package v2alpha1 +import ( + "context" + + "github.com/spacemeshos/go-spacemesh/common/types" +) + //go:generate mockgen -typed -package=v2alpha1 -destination=./mocks.go -source=./interface.go type malfeasanceInfo interface { - Info(data []byte) (map[string]string, error) + Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) } diff --git a/api/grpcserver/v2alpha1/malfeasance.go b/api/grpcserver/v2alpha1/malfeasance.go index d2f9b5b41d..e2242eba7b 100644 --- a/api/grpcserver/v2alpha1/malfeasance.go +++ b/api/grpcserver/v2alpha1/malfeasance.go @@ -71,8 +71,8 @@ func (s *MalfeasanceService) List( } proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, request.Limit) - if err := identities.IterateOps(s.db, ops, func(id types.NodeID, proof []byte, received time.Time) bool { - rst := toProof(ctx, s.info, id, proof) + if err := identities.IterateOps(s.db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { + rst := toProof(ctx, s.info, id) if rst == nil { return true } @@ -149,7 +149,7 @@ func (s *MalfeasanceStreamService) Stream( select { // process events first case rst := <-eventsOut: - proof := toProof(stream.Context(), s.info, rst.Smesher, rst.Proof) + proof := toProof(stream.Context(), s.info, rst.Smesher) if proof == nil { continue } @@ -163,7 +163,7 @@ func (s *MalfeasanceStreamService) Stream( default: select { case rst := <-eventsOut: - proof := toProof(stream.Context(), s.info, rst.Smesher, rst.Proof) + proof := toProof(stream.Context(), s.info, rst.Smesher) if proof == nil { continue } @@ -209,22 +209,20 @@ func (s *MalfeasanceStreamService) fetchFromDB( go func() { defer close(dbChan) - if err := identities.IterateOps(s.db, ops, - func(id types.NodeID, proof []byte, received time.Time) bool { - rst := toProof(ctx, s.info, id, proof) - if rst == nil { - return true - } + if err := identities.IterateOps(s.db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { + rst := toProof(ctx, s.info, id) + if rst == nil { + return true + } - select { - case dbChan <- rst: - return true - case <-ctx.Done(): - // exit if the context is canceled - return false - } - }, - ); err != nil { + select { + case dbChan <- rst: + return true + case <-ctx.Done(): + // exit if the context is canceled + return false + } + }); err != nil { errChan <- status.Error(codes.Internal, err.Error()) } }() @@ -235,9 +233,8 @@ func toProof( ctx context.Context, info malfeasanceInfo, id types.NodeID, - proof []byte, ) *spacemeshv2alpha1.MalfeasanceProof { - properties, err := info.Info(proof) + properties, err := info.Info(ctx, id) if err != nil { ctxzap.Debug(ctx, "failed to get malfeasance info", zap.String("smesher", id.String()), diff --git a/api/grpcserver/v2alpha1/malfeasance_test.go b/api/grpcserver/v2alpha1/malfeasance_test.go index 22744ad648..00a10443cd 100644 --- a/api/grpcserver/v2alpha1/malfeasance_test.go +++ b/api/grpcserver/v2alpha1/malfeasance_test.go @@ -44,7 +44,7 @@ func TestMalfeasanceService_List(t *testing.T) { "type": strconv.FormatUint(uint64(i%4+1), 10), fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } - info.EXPECT().Info(proofs[i].Proof).Return(proofs[i].Properties, nil).AnyTimes() + info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) } @@ -121,7 +121,7 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { "type": strconv.FormatUint(uint64(i%4+1), 10), fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } - info.EXPECT().Info(proofs[i].Proof).Return(proofs[i].Properties, nil).AnyTimes() + info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) } @@ -182,7 +182,7 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { "type": strconv.FormatUint(uint64(i%4+1), 10), fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } - info.EXPECT().Info(streamed[i].Proof).Return(properties, nil).AnyTimes() + info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(properties, nil).AnyTimes() } request := &spacemeshv2alpha1.MalfeasanceStreamRequest{ diff --git a/api/grpcserver/v2alpha1/mocks.go b/api/grpcserver/v2alpha1/mocks.go index 6f7f88adf8..0a4529afab 100644 --- a/api/grpcserver/v2alpha1/mocks.go +++ b/api/grpcserver/v2alpha1/mocks.go @@ -10,8 +10,10 @@ package v2alpha1 import ( + context "context" reflect "reflect" + types "github.com/spacemeshos/go-spacemesh/common/types" gomock "go.uber.org/mock/gomock" ) @@ -40,18 +42,18 @@ func (m *MockmalfeasanceInfo) EXPECT() *MockmalfeasanceInfoMockRecorder { } // Info mocks base method. -func (m *MockmalfeasanceInfo) Info(data []byte) (map[string]string, error) { +func (m *MockmalfeasanceInfo) Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Info", data) + ret := m.ctrl.Call(m, "Info", ctx, nodeID) ret0, _ := ret[0].(map[string]string) ret1, _ := ret[1].(error) return ret0, ret1 } // Info indicates an expected call of Info. -func (mr *MockmalfeasanceInfoMockRecorder) Info(data any) *MockmalfeasanceInfoInfoCall { +func (mr *MockmalfeasanceInfoMockRecorder) Info(ctx, nodeID any) *MockmalfeasanceInfoInfoCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockmalfeasanceInfo)(nil).Info), data) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockmalfeasanceInfo)(nil).Info), ctx, nodeID) return &MockmalfeasanceInfoInfoCall{Call: call} } @@ -67,13 +69,13 @@ func (c *MockmalfeasanceInfoInfoCall) Return(arg0 map[string]string, arg1 error) } // Do rewrite *gomock.Call.Do -func (c *MockmalfeasanceInfoInfoCall) Do(f func([]byte) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { +func (c *MockmalfeasanceInfoInfoCall) Do(f func(context.Context, types.NodeID) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockmalfeasanceInfoInfoCall) DoAndReturn(f func([]byte) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { +func (c *MockmalfeasanceInfoInfoCall) DoAndReturn(f func(context.Context, types.NodeID) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/events/events.go b/events/events.go index 8a07fa5bc5..5804fb1c25 100644 --- a/events/events.go +++ b/events/events.go @@ -332,7 +332,7 @@ func EmitProposal(nodeID types.NodeID, layer types.LayerID, proposal types.Propo &pb.Event_Proposal{ Proposal: &pb.EventProposal{ Layer: layer.Uint32(), - Proposal: proposal[:], + Proposal: proposal.Bytes(), Smesher: nodeID.Bytes(), }, }, @@ -340,6 +340,7 @@ func EmitProposal(nodeID types.NodeID, layer types.LayerID, proposal types.Propo } func EmitOwnMalfeasanceProof(nodeID types.NodeID, proof []byte) { + // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes const help = "Node committed malicious behavior. Identity will be canceled." emitUserEvent( help, @@ -367,6 +368,9 @@ func emitUserEvent(help string, failure bool, details pb.IsEventDetails) { } } +// TODO (mafa): instead of passing along the proof bytes the API should query the malfeasance handler for the metadata +// of the proof if needed. +// The malfeasance handler should then take care of decoding the proof, caching if necessary and returning the metadata. func ToMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof { mp := &wire.MalfeasanceProof{} if err := codec.Decode(proof, mp); err != nil { diff --git a/events/malfeasance.go b/events/malfeasance.go index 4ea0ef6ea8..ac0f15e7ce 100644 --- a/events/malfeasance.go +++ b/events/malfeasance.go @@ -8,7 +8,7 @@ import ( // EventMalfeasance includes the malfeasance proof. type EventMalfeasance struct { Smesher types.NodeID - Proof []byte + Proof []byte // TODO(mafa): remove this field and fetch metadata via malfeasance handler } // SubscribeMalfeasance subscribes malfeasance events. diff --git a/malfeasance/handler.go b/malfeasance/handler.go index b15eb2aa1f..cc4014d0c5 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -93,9 +93,14 @@ func (h *Handler) countInvalidProof(p *wire.MalfeasanceProof) { h.handlers[MalfeasanceType(p.Proof.Type)].ReportInvalidProof(numInvalidProofs) } -func (h *Handler) Info(data []byte) (map[string]string, error) { +func (h *Handler) Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) { + var blob sql.Blob + if err := identities.LoadMalfeasanceBlob(ctx, h.cdb, nodeID.Bytes(), &blob); err != nil { + return nil, fmt.Errorf("load malfeasance proof: %w", err) + } + var p wire.MalfeasanceProof - if err := codec.Decode(data, &p); err != nil { + if err := codec.Decode(blob.Bytes, &p); err != nil { return nil, fmt.Errorf("decode malfeasance proof: %w", err) } mh, ok := h.handlers[MalfeasanceType(p.Proof.Type)] diff --git a/malfeasance/handler_test.go b/malfeasance/handler_test.go index adb1e5af55..9b9092ff22 100644 --- a/malfeasance/handler_test.go +++ b/malfeasance/handler_test.go @@ -374,11 +374,12 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { } func TestHandler_Info(t *testing.T) { - t.Run("malformed data", func(t *testing.T) { + t.Run("unknown identity", func(t *testing.T) { h := newHandler(t) - info, err := h.Info(types.RandomBytes(32)) - require.ErrorContains(t, err, "decode malfeasance proof:") + info, err := h.Info(context.Background(), types.RandomNodeID()) + require.ErrorContains(t, err, "load malfeasance proof:") + require.ErrorIs(t, err, sql.ErrNotFound) require.Nil(t, info) }) @@ -392,9 +393,11 @@ func TestHandler_Info(t *testing.T) { Data: &wire.AtxProof{}, }, } + nodeID := types.RandomNodeID() proofBytes := codec.MustEncode(proof) + identities.SetMalicious(h.db, nodeID, proofBytes, time.Now()) - info, err := h.Info(proofBytes) + info, err := h.Info(context.Background(), nodeID) require.ErrorContains(t, err, fmt.Sprintf("unknown malfeasance type %d", wire.MultipleATXs)) require.Nil(t, info) }) @@ -414,9 +417,11 @@ func TestHandler_Info(t *testing.T) { Data: &wire.AtxProof{}, }, } + nodeID := types.RandomNodeID() proofBytes := codec.MustEncode(proof) + identities.SetMalicious(h.db, nodeID, proofBytes, time.Now()) - info, err := h.Info(proofBytes) + info, err := h.Info(context.Background(), nodeID) require.ErrorContains(t, err, "invalid proof") require.Nil(t, info) }) @@ -440,7 +445,10 @@ func TestHandler_Info(t *testing.T) { Data: &wire.AtxProof{}, }, } + nodeID := types.RandomNodeID() proofBytes := codec.MustEncode(proof) + identities.SetMalicious(h.db, nodeID, proofBytes, time.Now()) + expectedProperties := map[string]string{ "domain": "0", "type": strconv.FormatUint(uint64(wire.MultipleATXs), 10), @@ -449,7 +457,7 @@ func TestHandler_Info(t *testing.T) { expectedProperties[k] = v } - info, err := h.Info(proofBytes) + info, err := h.Info(context.Background(), nodeID) require.NoError(t, err) require.Equal(t, expectedProperties, info) }) diff --git a/malfeasance2/publisher.go b/malfeasance2/publisher.go index 4273c436e3..2f38ae9e61 100644 --- a/malfeasance2/publisher.go +++ b/malfeasance2/publisher.go @@ -60,7 +60,7 @@ func (p *Publisher) PublishATXProof( if err := malfeasance.AddProof(p.cdb, id, nil, proof, byte(InvalidActivation), time.Now()); err != nil { return fmt.Errorf("setting malfeasance proof: %w", err) } - // TODO(mafa): cache proof + // TODO(mafa): cache proof, right now caching it would clash with legacy malfeasance proofs // p.cdb.CacheMalfeasanceProof(id, proof) p.tortoise.OnMalfeasance(id) } From ffde644201d0645b2e95bd4c68dcfc920d359d54 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:55:01 +0000 Subject: [PATCH 26/66] Extend publisher --- malfeasance/publisher.go | 2 +- malfeasance2/interface.go | 4 ++ malfeasance2/publisher.go | 108 +++++++++++++++++++++++++++++++------- node/node.go | 1 + sql/marriage/marriages.go | 2 +- 5 files changed, 97 insertions(+), 20 deletions(-) diff --git a/malfeasance/publisher.go b/malfeasance/publisher.go index 6e6f222c24..76fd8111bb 100644 --- a/malfeasance/publisher.go +++ b/malfeasance/publisher.go @@ -59,7 +59,7 @@ func (p *Publisher) PublishProof(ctx context.Context, smesherID types.NodeID, pr // Only gossip the proof if we are synced (to not spam the network with proofs others probably already have). if !p.sync.ListenToATXGossip() { - p.logger.Debug("not synced, not broadcasting malfeasance proof", + p.logger.Debug("not in sync, not broadcasting malfeasance proof", zap.String("smesher_id", smesherID.ShortString()), ) return nil diff --git a/malfeasance2/interface.go b/malfeasance2/interface.go index 954d06dfc8..6cf57b81c4 100644 --- a/malfeasance2/interface.go +++ b/malfeasance2/interface.go @@ -10,6 +10,10 @@ import ( //go:generate mockgen -typed -package=malfeasance2 -destination=./mocks.go -source=./interface.go +type syncer interface { + ListenToATXGossip() bool +} + type tortoise interface { OnMalfeasance(types.NodeID) } diff --git a/malfeasance2/publisher.go b/malfeasance2/publisher.go index 2f38ae9e61..1d6057070d 100644 --- a/malfeasance2/publisher.go +++ b/malfeasance2/publisher.go @@ -2,6 +2,7 @@ package malfeasance2 import ( "context" + "errors" "fmt" "time" @@ -11,6 +12,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/malfeasance" "github.com/spacemeshos/go-spacemesh/sql/marriage" ) @@ -18,6 +20,7 @@ import ( type Publisher struct { logger *zap.Logger cdb *datastore.CachedDB + sync syncer tortoise tortoise publisher pubsub.Publisher } @@ -25,52 +28,121 @@ type Publisher struct { func NewPublisher( logger *zap.Logger, cdb *datastore.CachedDB, + sync syncer, tortoise tortoise, publisher pubsub.Publisher, ) *Publisher { return &Publisher{ logger: logger, cdb: cdb, + sync: sync, tortoise: tortoise, publisher: publisher, } } -func (p *Publisher) PublishATXProof( - ctx context.Context, - nodeID types.NodeID, - proof []byte, -) error { - // Combine IDs from the present equivocation set for atx.SmesherID and IDs in atx.Marriages. - allMalicious := make(map[types.NodeID]struct{}) - +func (p *Publisher) PublishATXProof(ctx context.Context, nodeID types.NodeID, proof []byte) error { marriageID, err := marriage.FindIDByNodeID(p.cdb, nodeID) - if err != nil { + switch { + case errors.Is(err, sql.ErrNotFound): // smesher is not married + malicious, err := malfeasance.IsMalicious(p.cdb, nodeID) + if err != nil { + return fmt.Errorf("check if smesher is malicious: %w", err) + } + if malicious { + p.logger.Debug("smesher is already marked as malicious", zap.String("smesher_id", nodeID.ShortString())) + return nil + } + if err := malfeasance.AddProof(p.cdb, nodeID, nil, proof, byte(InvalidActivation), time.Now()); err != nil { + return fmt.Errorf("setting malfeasance proof: %w", err) + } + // TODO(mafa): cache proof, right now caching it would clash with legacy malfeasance proofs + // arguably this shouldn't be needed at all, API queries the handler for info about a proof + // handler can decided if this needs caching or not + // + // p.cdb.CacheMalfeasanceProof(nodeID, proof) + p.tortoise.OnMalfeasance(nodeID) + + return p.publish(ctx, nodeID, nil, proof) // pass nil for certificates + case err != nil: return fmt.Errorf("getting equivocation set: %w", err) + default: // smesher is married } + + // Combine IDs from the present equivocation set for atx.SmesherID and IDs in atx.Marriages. set, err := marriage.NodeIDsByID(p.cdb, marriageID) if err != nil { return fmt.Errorf("getting equivocation set: %w", err) } - for _, id := range set { - allMalicious[id] = struct{}{} - } - for id := range allMalicious { - if err := malfeasance.AddProof(p.cdb, id, nil, proof, byte(InvalidActivation), time.Now()); err != nil { + publish := false // whether to publish the proof + malicious, err := malfeasance.IsMalicious(p.cdb, nodeID) + if err != nil { + return fmt.Errorf("check if smesher is malicious: %w", err) + } + if !malicious { + err := malfeasance.AddProof(p.cdb, nodeID, &marriageID, proof, byte(InvalidActivation), time.Now()) + if err != nil { return fmt.Errorf("setting malfeasance proof: %w", err) } // TODO(mafa): cache proof, right now caching it would clash with legacy malfeasance proofs + // arguably this shouldn't be needed at all, API queries the handler for info about a proof + // handler can decided if this needs caching or not + // + // p.cdb.CacheMalfeasanceProof(nodeID, proof) + p.tortoise.OnMalfeasance(nodeID) + + publish = true + } + + for _, id := range set { + if id == nodeID { + // already handled + continue + } + malicious, err := malfeasance.IsMalicious(p.cdb, id) + if err != nil { + return fmt.Errorf("check if smesher is malicious: %w", err) + } + if malicious { + p.logger.Debug("smesher is already marked as malicious", zap.String("smesher_id", id.ShortString())) + continue + } + + publish = true + if err := malfeasance.SetMalicious(p.cdb, id, marriageID, time.Now()); err != nil { + return fmt.Errorf("setting malicious: %w", err) + } + // TODO(mafa): cache proof, right now caching it would clash with legacy malfeasance proofs + // arguably this shouldn't be needed at all, API queries the handler for info about a proof + // handler can decided if this needs caching or not + // // p.cdb.CacheMalfeasanceProof(id, proof) p.tortoise.OnMalfeasance(id) } - // TODO(mafa): check if we are in sync before publishing, if not just return + if !publish { + // all smeshers were already marked as malicious - no gossip to void spamming the network + return nil + } + + return p.publish(ctx, nodeID, nil, proof) // TODO(mafa): do not pass nil here for certificates +} + +func (p *Publisher) publish(ctx context.Context, nodeID types.NodeID, certs []ProofCertificate, proof []byte) error { + // Only gossip the proof if we are synced (to not spam the network with proofs others probably already have). + if !p.sync.ListenToATXGossip() { + p.logger.Debug("not in sync, not broadcasting malfeasance proof", + zap.String("smesher_id", nodeID.ShortString()), + ) + return nil + } malfeasanceProof := &MalfeasanceProof{ - Version: 0, - Domain: InvalidActivation, - Proof: proof, + Version: 0, + Certificates: certs, + Domain: InvalidActivation, + Proof: proof, } if err := p.publisher.Publish(ctx, pubsub.MalfeasanceProof2, codec.MustEncode(malfeasanceProof)); err != nil { p.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) diff --git a/node/node.go b/node/node.go index 318ef089a3..f8208635e3 100644 --- a/node/node.go +++ b/node/node.go @@ -839,6 +839,7 @@ func (app *App) initServices(ctx context.Context) error { malfeasance2Publisher := malfeasance2.NewPublisher( app.addLogger(Malfeasance2Logger, lg).Zap(), app.cachedDB, + syncer, trtl, app.host, ) diff --git a/sql/marriage/marriages.go b/sql/marriage/marriages.go index d114c1fa58..0991391402 100644 --- a/sql/marriage/marriages.go +++ b/sql/marriage/marriages.go @@ -138,7 +138,7 @@ func NodeIDsByID(db sql.Executor, id ID) ([]types.NodeID, error) { s.BindInt64(1, int64(id)) }, func(s *sql.Statement) bool { var nodeID types.NodeID - s.ColumnBytes(0, nodeID[:]) + s.ColumnBytes(0, nodeID.Bytes()) nodeIDs = append(nodeIDs, nodeID) return true }) From ee95c48dad05cd7a2f5d2e8fb9b88abfc86f4d75 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:08:50 +0000 Subject: [PATCH 27/66] Update systests --- systest/tests/smeshing_test.go | 43 ++++++++++++++++++++++++++-------- systest/tests/steps_test.go | 4 ++-- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/systest/tests/smeshing_test.go b/systest/tests/smeshing_test.go index 6ce1b74992..2d13a3f494 100644 --- a/systest/tests/smeshing_test.go +++ b/systest/tests/smeshing_test.go @@ -3,6 +3,7 @@ package tests import ( "bytes" "context" + "errors" "fmt" "os" "sort" @@ -74,7 +75,12 @@ func testSmeshing(t *testing.T, tctx *testcontext.Context, cl *cluster.Cluster) includedAll[i] = map[uint32][]*pb.Proposal{} } - eg, ctx := errgroup.WithContext(tctx) + layerDuration := testcontext.LayerDuration.Get(tctx.Parameters) + deadline := cl.Genesis().Add(time.Duration(last+2*layersPerEpoch) * layerDuration) // add 2 epochs of buffer + ctx, cancel := context.WithDeadline(tctx, deadline) + defer cancel() + + eg, ctx := errgroup.WithContext(ctx) for i := range cl.Total() { client := cl.Client(i) tctx.Log.Debugw("watching", "client", client.Name, "i", i) @@ -82,21 +88,38 @@ func testSmeshing(t *testing.T, tctx *testcontext.Context, cl *cluster.Cluster) if proposal.Layer.Number < first { return true, nil } - tctx.Log.Debugw("received proposal event", + if proposal.Layer.Number > last { + return false, nil + } + if proposal.Status == pb.Proposal_Created { + tctx.Log.Debugw("received proposal created event", + "client", client.Name, + "layer", proposal.Layer.Number, + "smesher", prettyHex(proposal.Smesher.Id), + "eligibilities", len(proposal.Eligibilities), + ) + select { + case createdCh <- proposal: + case <-ctx.Done(): + return false, ctx.Err() + default: + tctx.Log.Errorw("proposal channel is full", + "client", client.Name, + "layer", proposal.Layer.Number, + ) + return false, errors.New("proposal channel is full") + } + return true, nil + } + + tctx.Log.Debugw("received other proposal event", "client", client.Name, "layer", proposal.Layer.Number, "smesher", prettyHex(proposal.Smesher.Id), "eligibilities", len(proposal.Eligibilities), "status", pb.Proposal_Status_name[int32(proposal.Status)], ) - if proposal.Layer.Number > last { - return false, nil - } - if proposal.Status == pb.Proposal_Created { - createdCh <- proposal - } else { - includedAll[i][proposal.Layer.Number] = append(includedAll[i][proposal.Layer.Number], proposal) - } + includedAll[i][proposal.Layer.Number] = append(includedAll[i][proposal.Layer.Number], proposal) return true, nil }) } diff --git a/systest/tests/steps_test.go b/systest/tests/steps_test.go index dd1369b106..5d284ee264 100644 --- a/systest/tests/steps_test.go +++ b/systest/tests/steps_test.go @@ -189,10 +189,10 @@ func TestStepReplaceNodes(t *testing.T) { require.NoError(t, err) var ( - delete = rand.Intn(cctx.ClusterSize*2/10) + 1 + toDelete = rand.Intn(cctx.ClusterSize*2/10) + 1 deleting []*cluster.NodeClient ) - for i := cl.Bootnodes(); i < cl.Total() && len(deleting) < delete; i++ { + for i := cl.Bootnodes(); i < cl.Total() && len(deleting) < toDelete; i++ { node := cl.Client(i) // don't replace non-synced nodes if !isSynced(cctx, node) { From fa831651161b1ca78e3705997b5a06f5f47f2297 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:16:56 +0000 Subject: [PATCH 28/66] Cleanup ATX Mal Handler/Publisher --- activation/malfeasance2.go | 109 ++++++++++++++++++------------------- node/node.go | 28 ++++++---- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/activation/malfeasance2.go b/activation/malfeasance2.go index 9035575e73..ce7dafd1f9 100644 --- a/activation/malfeasance2.go +++ b/activation/malfeasance2.go @@ -3,20 +3,30 @@ package activation import ( "context" "fmt" + "sync" + + "go.uber.org/zap" "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/signing" ) type MalfeasanceHandlerV2 struct { + logger *zap.Logger + malPublisher malfeasancePublisher edVerifier *signing.EdVerifier validator nipostValidatorV2 + + smeshingMutex sync.Mutex + signers map[types.NodeID]*signing.EdSigner } func NewMalfeasanceHandlerV2( + logger *zap.Logger, malPublisher malfeasancePublisher, edVerifier *signing.EdVerifier, validator nipostValidatorV2, @@ -28,38 +38,46 @@ func NewMalfeasanceHandlerV2( } } +func (p *MalfeasanceHandlerV2) Register(sig *signing.EdSigner) { + p.smeshingMutex.Lock() + defer p.smeshingMutex.Unlock() + if _, exists := p.signers[sig.NodeID()]; exists { + p.logger.Error("signing key already registered", log.ZShortStringer("id", sig.NodeID())) + return + } + + p.logger.Info("registered signing key", log.ZShortStringer("id", sig.NodeID())) + p.signers[sig.NodeID()] = sig +} + // Publish publishes an ATX proof by encoding it and sending it to the malfeasance publisher. func (p *MalfeasanceHandlerV2) Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error { + proofNodeID, err := proof.Valid(ctx, p) + if err != nil { + return fmt.Errorf("publish ATX malfeasance proof: proof not valid: %w", err) + } + if proofNodeID != nodeID { + return fmt.Errorf("publish ATX malfeasance proof: proof for %s does not match node ID %s", proofNodeID, nodeID) + } + + p.smeshingMutex.Lock() + _, exists := p.signers[nodeID] + p.smeshingMutex.Unlock() + + if exists { + // do not publish proofs against one self + return fmt.Errorf("publish ATX malfeasance proof: node %s is managed by node", nodeID) + } + atxProof := &wire.ATXProof{ Version: 0x01, // for now we only have one version ProofType: proof.Type(), Proof: codec.MustEncode(proof), } - return p.malPublisher.PublishATXProof(ctx, nodeID, codec.MustEncode(atxProof)) } -func (mh *MalfeasanceHandlerV2) PostIndex( - ctx context.Context, - smesherID types.NodeID, - commitment types.ATXID, - post *types.Post, - challenge []byte, - numUnits uint32, - idx int, -) error { - return mh.validator.PostV2(ctx, smesherID, commitment, post, challenge, numUnits, PostIndex(idx)) -} - -func (mh *MalfeasanceHandlerV2) Signature(d signing.Domain, nodeID types.NodeID, m []byte, sig types.EdSignature) bool { - return mh.edVerifier.Verify(d, nodeID, m, sig) -} - -// TODO(mafa): call this validate in the malfeasance handler in `malfeasance` package for publish/gossip: -// - do not publishing proofs for identities managed by node -// - validate and persist before publishing -// - do not handle incoming proofs from peer == `self` func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) (types.NodeID, error) { var atxProof wire.ATXProof if err := codec.Decode(data, &atxProof); err != nil { @@ -78,37 +96,18 @@ func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) (type return id, nil } -// TODO(mafa): this roughly how the general publisher looks like -// -// func Publish(ctx context.Context, smesherID types.NodeID, data []byte) error { -// // Combine IDs from the present equivocation set for atx.SmesherID and IDs in atx.Marriages. -// set, err := identities.EquivocationSet(mh.cdb, nodeID) -// if err != nil { -// return fmt.Errorf("getting equivocation set: %w", err) -// } -// for _, id := range set { -// if err := identities.SetMalicious(mh.cdb, id, encoded, time.Now()); err != nil { -// return fmt.Errorf("adding malfeasance proof: %w", err) -// } - -// mh.cdb.CacheMalfeasanceProof(id, proof) -// mh.tortoise.OnMalfeasance(id) -// } - -// if !mh.syncer.ListenToATXGossip() { -// // we are not gossiping proofs when we are not listening to ATX gossip -// return nil -// } - -// gossip := mwire.MalfeasanceProofV2{ -// Layer: mh.clock.CurrentLayer(), -// ProofType: mwire.InvalidActivation, -// Proof: data, -// } - -// if err := mh.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { -// mh.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) -// return fmt.Errorf("broadcast atx malfeasance proof: %w", err) -// } -// return nil -// } +func (mh *MalfeasanceHandlerV2) PostIndex( + ctx context.Context, + smesherID types.NodeID, + commitment types.ATXID, + post *types.Post, + challenge []byte, + numUnits uint32, + idx int, +) error { + return mh.validator.PostV2(ctx, smesherID, commitment, post, challenge, numUnits, PostIndex(idx)) +} + +func (mh *MalfeasanceHandlerV2) Signature(d signing.Domain, nodeID types.NodeID, m []byte, sig types.EdSignature) bool { + return mh.edVerifier.Verify(d, nodeID, m, sig) +} diff --git a/node/node.go b/node/node.go index f8208635e3..881f0e5804 100644 --- a/node/node.go +++ b/node/node.go @@ -827,28 +827,32 @@ func (app *App) initServices(ctx context.Context) error { beaconProtocol.SetSyncState(syncer) hOracle.SetSync(syncer) - malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() + legacyMalLogger := app.addLogger(MalfeasanceLogger, lg).Zap() legacyMalPublisher := malfeasance.NewPublisher( - malfeasanceLogger, + legacyMalLogger, app.cachedDB, syncer, trtl, app.host, ) - malfeasance2Publisher := malfeasance2.NewPublisher( - app.addLogger(Malfeasance2Logger, lg).Zap(), + malfeasanceLogger := app.addLogger(Malfeasance2Logger, lg).Zap() + malfeasancePublisher := malfeasance2.NewPublisher( + malfeasanceLogger, app.cachedDB, syncer, trtl, app.host, ) - - atxMalPublisher := activation.NewMalfeasanceHandlerV2( - malfeasance2Publisher, + atxMalHandler := activation.NewMalfeasanceHandlerV2( + malfeasanceLogger, + malfeasancePublisher, app.edVerifier, validator, ) + for _, sig := range app.signers { + atxMalHandler.Register(sig) + } atxHandler := activation.NewHandler( app.host.ID(), app.cachedDB, @@ -858,7 +862,7 @@ func (app *App) initServices(ctx context.Context) error { fetcher, goldenATXID, validator, - atxMalPublisher, + atxMalHandler, legacyMalPublisher, beaconProtocol, trtl, @@ -1142,18 +1146,18 @@ func (app *App) initServices(ctx context.Context) error { activationMH := activation.NewMalfeasanceHandler( app.cachedDB, - malfeasanceLogger, + legacyMalLogger, app.edVerifier, ) meshMH := mesh.NewMalfeasanceHandler( app.cachedDB, app.edVerifier, - mesh.WithMalfeasanceLogger(malfeasanceLogger), + mesh.WithMalfeasanceLogger(legacyMalLogger), ) hareMH := hare3.NewMalfeasanceHandler( app.cachedDB, app.edVerifier, - hare3.WithMalfeasanceLogger(malfeasanceLogger), + hare3.WithMalfeasanceLogger(legacyMalLogger), ) invalidPostMH := activation.NewInvalidPostIndexHandler( app.cachedDB, @@ -1168,7 +1172,7 @@ func (app *App) initServices(ctx context.Context) error { } app.malfeasanceHandler = malfeasance.NewHandler( app.cachedDB, - malfeasanceLogger, + legacyMalLogger, app.host.ID(), nodeIDs, trtl, From 99f110cc4d6750210fc8ffd7f748852f15bfe71b Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:53:52 +0000 Subject: [PATCH 29/66] Fix typo --- api/grpcserver/debug_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/grpcserver/debug_service.go b/api/grpcserver/debug_service.go index 264bb9e984..787734920d 100644 --- a/api/grpcserver/debug_service.go +++ b/api/grpcserver/debug_service.go @@ -220,7 +220,7 @@ func castEventProposal(ev *events.EventProposal) *pb.Proposal { for _, el := range ev.Proposal.Ballot.EligibilityProofs { proposal.Eligibilities = append(proposal.Eligibilities, &pb.Eligibility{ J: el.J, - Signature: el.Sig[:], + Signature: el.Sig.Bytes(), }) } return proposal From f318a06c00b775eee5d1602f8c7cb45da121a231 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:54:01 +0000 Subject: [PATCH 30/66] activation malfeasance handler and tests --- activation/e2e/nipost_test.go | 2 +- activation/interface.go | 2 +- activation/malfeasance2.go | 25 +- activation/malfeasance2_test.go | 308 +++++++++++++++++- activation/mocks.go | 40 +-- activation/post_supervisor.go | 4 +- activation/post_supervisor_test.go | 18 +- activation/wire/interface.go | 14 + activation/wire/malfeasance.go | 17 - .../wire/malfeasance_double_marry_test.go | 38 +-- .../wire/malfeasance_double_merge_test.go | 66 ++-- .../wire/malfeasance_invalid_post_test.go | 42 +-- .../wire/malfeasance_invalid_prev_atx_test.go | 160 ++++----- activation/wire/malfeasance_shared_test.go | 62 ++-- activation/wire/mocks.go | 218 +++++++++++++ activation/wire/wire_v2_helpers.go | 114 +++++++ activation/wire/wire_v2_test.go | 106 ------ api/grpcserver/post_service_test.go | 4 +- malfeasance2/handler_test.go | 4 +- malfeasance2/mocks.go | 62 ++++ node/node_test.go | 2 +- .../distributed_post_verification_test.go | 2 +- 22 files changed, 953 insertions(+), 357 deletions(-) create mode 100644 activation/wire/wire_v2_helpers.go diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 927a89851d..a9a13970e9 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -97,7 +97,7 @@ func launchPostSupervisor( provingOpts := activation.DefaultPostProvingOpts() provingOpts.RandomXMode = activation.PostRandomXModeLight - builder := activation.NewMockAtxBuilder(gomock.NewController(tb)) + builder := activation.NewMockatxBuilder(gomock.NewController(tb)) builder.EXPECT().Register(gomock.Any()) ps := activation.NewPostSupervisor(log, postCfg, provingOpts, mgr, builder) require.NoError(tb, ps.Start(cmdCfg, postOpts, sig)) diff --git a/activation/interface.go b/activation/interface.go index e0ecd85bd5..ab9289ec3d 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -206,7 +206,7 @@ var ( ErrPostClientNotConnected = errors.New("post service not registered") ) -type AtxBuilder interface { +type atxBuilder interface { Register(sig *signing.EdSigner) } diff --git a/activation/malfeasance2.go b/activation/malfeasance2.go index ce7dafd1f9..5aae4602c9 100644 --- a/activation/malfeasance2.go +++ b/activation/malfeasance2.go @@ -32,9 +32,12 @@ func NewMalfeasanceHandlerV2( validator nipostValidatorV2, ) *MalfeasanceHandlerV2 { return &MalfeasanceHandlerV2{ + logger: logger, malPublisher: malPublisher, edVerifier: edVerifier, validator: validator, + + signers: make(map[types.NodeID]*signing.EdSigner), } } @@ -46,27 +49,29 @@ func (p *MalfeasanceHandlerV2) Register(sig *signing.EdSigner) { return } - p.logger.Info("registered signing key", log.ZShortStringer("id", sig.NodeID())) + p.logger.Debug("registered signing key", log.ZShortStringer("id", sig.NodeID())) p.signers[sig.NodeID()] = sig } // Publish publishes an ATX proof by encoding it and sending it to the malfeasance publisher. func (p *MalfeasanceHandlerV2) Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error { - proofNodeID, err := proof.Valid(ctx, p) - if err != nil { - return fmt.Errorf("publish ATX malfeasance proof: proof not valid: %w", err) - } - if proofNodeID != nodeID { - return fmt.Errorf("publish ATX malfeasance proof: proof for %s does not match node ID %s", proofNodeID, nodeID) - } - p.smeshingMutex.Lock() _, exists := p.signers[nodeID] p.smeshingMutex.Unlock() if exists { // do not publish proofs against one self - return fmt.Errorf("publish ATX malfeasance proof: node %s is managed by node", nodeID) + return fmt.Errorf("publish ATX malfeasance proof: identity %s is managed by node", nodeID) + } + + proofNodeID, err := proof.Valid(ctx, p) + if err != nil { + return fmt.Errorf("publish ATX malfeasance proof: proof not valid: %w", err) + } + if proofNodeID != nodeID { + return fmt.Errorf("publish ATX malfeasance proof: proof for %s does not match node ID %s", + proofNodeID.ShortString(), nodeID.ShortString(), + ) } atxProof := &wire.ATXProof{ diff --git a/activation/malfeasance2_test.go b/activation/malfeasance2_test.go index 385374da3c..692c728a2e 100644 --- a/activation/malfeasance2_test.go +++ b/activation/malfeasance2_test.go @@ -1,3 +1,309 @@ package activation -// TODO(mafa): implement me +import ( + "context" + "errors" + "fmt" + "math/rand/v2" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" + "go.uber.org/zap/zaptest/observer" + + "github.com/spacemeshos/go-spacemesh/activation/wire" + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/sql/statesql" +) + +type testMalHandler struct { + *MalfeasanceHandlerV2 + + observedLogs *observer.ObservedLogs + ctrl *gomock.Controller + mPublish *MockmalfeasancePublisher + mValidator *MocknipostValidator +} + +func newTestMalHandler(tb testing.TB) *testMalHandler { + edVerifier := signing.NewEdVerifier() + + observer, observedLogs := observer.New(zap.DebugLevel) + logger := zaptest.NewLogger(tb, zaptest.WrapOptions(zap.WrapCore( + func(core zapcore.Core) zapcore.Core { + return zapcore.NewTee(core, observer) + }, + ))) + + ctrl := gomock.NewController(tb) + mPublish := NewMockmalfeasancePublisher(ctrl) + mValidator := NewMocknipostValidator(ctrl) + + handler := NewMalfeasanceHandlerV2( + logger, + mPublish, + edVerifier, + mValidator, + ) + + return &testMalHandler{ + MalfeasanceHandlerV2: handler, + + observedLogs: observedLogs, + ctrl: ctrl, + mPublish: mPublish, + mValidator: mValidator, + } +} + +func TestRegister(t *testing.T) { + t.Parallel() + + t.Run("register", func(t *testing.T) { + t.Parallel() + th := newTestMalHandler(t) + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + th.Register(sig) + + require.Equal(t, 1, th.observedLogs.Len()) + require.Equal(t, zap.DebugLevel, th.observedLogs.All()[0].Level) + require.Contains(t, th.observedLogs.All()[0].Message, "registered signing key") + }) + + t.Run("already registered", func(t *testing.T) { + t.Parallel() + th := newTestMalHandler(t) + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + th.Register(sig) + th.Register(sig) + + logs := th.observedLogs.FilterLevelExact(zap.ErrorLevel) + + require.Equal(t, 1, logs.Len()) + require.Equal(t, zap.ErrorLevel, logs.All()[0].Level) + require.Contains(t, logs.All()[0].Message, "signing key already registered") + }) +} + +func TestPublish(t *testing.T) { + t.Parallel() + + t.Run("valid proof", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + + nodeID := types.RandomNodeID() + proof := wire.NewMockProof(th.ctrl) + + proof.EXPECT().Valid(context.Background(), th.MalfeasanceHandlerV2).Return(nodeID, nil) + proof.EXPECT().Type().Return(wire.DoubleMarry) + proof.EXPECT().EncodeScale(gomock.Any()) + + atxProof := &wire.ATXProof{ + Version: 0x01, // for now we only have one version + ProofType: wire.DoubleMarry, + + Proof: []byte{}, + } + th.mPublish.EXPECT().PublishATXProof(context.Background(), nodeID, codec.MustEncode(atxProof)).Return(nil) + + err := th.Publish(context.Background(), nodeID, proof) + require.NoError(t, err) + }) + + t.Run("invalid proof", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + + proof := wire.NewMockProof(th.ctrl) + nodeID := types.RandomNodeID() + errInvalidProof := errors.New("invalid proof") + proof.EXPECT().Valid(context.Background(), th.MalfeasanceHandlerV2).Return(types.EmptyNodeID, errInvalidProof) + + err := th.Publish(context.Background(), nodeID, proof) + require.ErrorIs(t, err, errInvalidProof) + require.ErrorContains(t, err, "proof not valid") + }) + + t.Run("proof for self", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + + sig1, err := signing.NewEdSigner() + require.NoError(t, err) + th.Register(sig1) + + proof := wire.NewMockProof(th.ctrl) + + err = th.Publish(context.Background(), sig1.NodeID(), proof) + require.ErrorContains(t, err, fmt.Sprintf("identity %s is managed by node", sig1.NodeID())) + }) + + t.Run("proof for different nodeID", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + + sig1 := types.RandomNodeID() + sig2 := types.RandomNodeID() + + proof := wire.NewMockProof(th.ctrl) + proof.EXPECT().Valid(context.Background(), th.MalfeasanceHandlerV2).Return(sig2, nil) + + err := th.Publish(context.Background(), sig1, proof) + require.ErrorContains(t, err, + fmt.Sprintf("proof for %s does not match node ID %s", sig2.ShortString(), sig1.ShortString()), + ) + }) +} + +func TestValidate(t *testing.T) { + t.Parallel() + + t.Run("proof fails decoding", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + + id, err := th.Validate(context.Background(), []byte{}) + require.ErrorContains(t, err, "decoding ATX malfeasance proof") + require.Equal(t, types.EmptyNodeID, id) + }) + + t.Run("unknown proof type", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + + atxProof := &wire.ATXProof{ + Version: 0x01, + ProofType: 0x42, // unknown proof type + } + + id, err := th.Validate(context.Background(), codec.MustEncode(atxProof)) + require.ErrorContains(t, err, "unknown ATX malfeasance proof type") + require.Equal(t, types.EmptyNodeID, id) + }) + + t.Run("atx proof fails decoding", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + + atxProof := &wire.ATXProof{ + Version: 0x01, + ProofType: wire.DoubleMarry, + Proof: []byte{}, // invalid proof + } + + id, err := th.Validate(context.Background(), codec.MustEncode(atxProof)) + require.ErrorContains(t, err, "decoding ATX malfeasance proof of type 0x11") + require.Equal(t, types.EmptyNodeID, id) + }) + + genProof := func(t *testing.T, sig *signing.EdSigner) *wire.ProofInvalidPost { + db := statesql.InMemoryTest(t) + + nipostChallenge := types.RandomHash() + const numUnits = uint32(11) + post := wire.PostV1{ + Nonce: rand.Uint32(), + Indices: types.RandomBytes(11), + Pow: rand.Uint64(), + } + atx := wire.NewTestActivationTxV2( + wire.WithNIPost( + wire.WithNIPostChallenge(nipostChallenge), + wire.WithNIPostSubPost(wire.SubPostV2{ + Post: post, + NumUnits: numUnits, + }), + ), + ) + atx.Sign(sig) + commitmentATX := types.RandomATXID() + + const invalidPostIdx = 7 + const validPostIdx = 15 + proof, err := wire.NewInvalidPostProof(db, atx, commitmentATX, sig.NodeID(), 0, invalidPostIdx, validPostIdx) + require.NoError(t, err) + return proof + } + + t.Run("valid proof", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + sig, err := signing.NewEdSigner() + require.NoError(t, err) + proof := genProof(t, sig) + + atxProof := &wire.ATXProof{ + Version: 0x01, // for now we only have one version + ProofType: proof.Type(), + Proof: codec.MustEncode(proof), + } + + th.mValidator.EXPECT().PostV2( + context.Background(), + proof.NodeID, + proof.InvalidPostProof.CommitmentATX, + wire.PostFromWireV1(&proof.InvalidPostProof.Post), + proof.InvalidPostProof.Challenge.Bytes(), + proof.InvalidPostProof.NumUnits, + gomock.Cond(func(opt validatorOption) bool { + opts := &validatorOptions{} + opt(opts) + return *opts.postIdx == int(proof.InvalidPostProof.InvalidPostIndex) + }), + ).Return(errors.New("invalid post")) + th.mValidator.EXPECT().PostV2( + context.Background(), + proof.NodeID, + proof.InvalidPostProof.CommitmentATX, + wire.PostFromWireV1(&proof.InvalidPostProof.Post), + proof.InvalidPostProof.Challenge.Bytes(), + proof.InvalidPostProof.NumUnits, + gomock.Cond(func(opt validatorOption) bool { + opts := &validatorOptions{} + opt(opts) + return *opts.postIdx == int(proof.InvalidPostProof.ValidPostIndex) + }), + ).Return(nil) + id, err := th.Validate(context.Background(), codec.MustEncode(atxProof)) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), id) + }) + + t.Run("invalid proof", func(t *testing.T) { + t.Parallel() + + th := newTestMalHandler(t) + sig, err := signing.NewEdSigner() + require.NoError(t, err) + proof := genProof(t, sig) + proof.NodeID = types.RandomNodeID() + + atxProof := &wire.ATXProof{ + Version: 0x01, // for now we only have one version + ProofType: proof.Type(), + Proof: codec.MustEncode(proof), + } + + id, err := th.Validate(context.Background(), codec.MustEncode(atxProof)) + require.ErrorContains(t, err, "validating ATX malfeasance proof:") + require.Equal(t, types.EmptyNodeID, id) + }) +} diff --git a/activation/mocks.go b/activation/mocks.go index d93b60a45c..71b5c01e0a 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -2291,62 +2291,62 @@ func (c *MockpoetDbAPIValidateAndStoreCall) DoAndReturn(f func(context.Context, return c } -// MockAtxBuilder is a mock of AtxBuilder interface. -type MockAtxBuilder struct { +// MockatxBuilder is a mock of atxBuilder interface. +type MockatxBuilder struct { ctrl *gomock.Controller - recorder *MockAtxBuilderMockRecorder + recorder *MockatxBuilderMockRecorder isgomock struct{} } -// MockAtxBuilderMockRecorder is the mock recorder for MockAtxBuilder. -type MockAtxBuilderMockRecorder struct { - mock *MockAtxBuilder +// MockatxBuilderMockRecorder is the mock recorder for MockatxBuilder. +type MockatxBuilderMockRecorder struct { + mock *MockatxBuilder } -// NewMockAtxBuilder creates a new mock instance. -func NewMockAtxBuilder(ctrl *gomock.Controller) *MockAtxBuilder { - mock := &MockAtxBuilder{ctrl: ctrl} - mock.recorder = &MockAtxBuilderMockRecorder{mock} +// NewMockatxBuilder creates a new mock instance. +func NewMockatxBuilder(ctrl *gomock.Controller) *MockatxBuilder { + mock := &MockatxBuilder{ctrl: ctrl} + mock.recorder = &MockatxBuilderMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAtxBuilder) EXPECT() *MockAtxBuilderMockRecorder { +func (m *MockatxBuilder) EXPECT() *MockatxBuilderMockRecorder { return m.recorder } // Register mocks base method. -func (m *MockAtxBuilder) Register(sig *signing.EdSigner) { +func (m *MockatxBuilder) Register(sig *signing.EdSigner) { m.ctrl.T.Helper() m.ctrl.Call(m, "Register", sig) } // Register indicates an expected call of Register. -func (mr *MockAtxBuilderMockRecorder) Register(sig any) *MockAtxBuilderRegisterCall { +func (mr *MockatxBuilderMockRecorder) Register(sig any) *MockatxBuilderRegisterCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockAtxBuilder)(nil).Register), sig) - return &MockAtxBuilderRegisterCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockatxBuilder)(nil).Register), sig) + return &MockatxBuilderRegisterCall{Call: call} } -// MockAtxBuilderRegisterCall wrap *gomock.Call -type MockAtxBuilderRegisterCall struct { +// MockatxBuilderRegisterCall wrap *gomock.Call +type MockatxBuilderRegisterCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockAtxBuilderRegisterCall) Return() *MockAtxBuilderRegisterCall { +func (c *MockatxBuilderRegisterCall) Return() *MockatxBuilderRegisterCall { c.Call = c.Call.Return() return c } // Do rewrite *gomock.Call.Do -func (c *MockAtxBuilderRegisterCall) Do(f func(*signing.EdSigner)) *MockAtxBuilderRegisterCall { +func (c *MockatxBuilderRegisterCall) Do(f func(*signing.EdSigner)) *MockatxBuilderRegisterCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockAtxBuilderRegisterCall) DoAndReturn(f func(*signing.EdSigner)) *MockAtxBuilderRegisterCall { +func (c *MockatxBuilderRegisterCall) DoAndReturn(f func(*signing.EdSigner)) *MockatxBuilderRegisterCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/post_supervisor.go b/activation/post_supervisor.go index bc49dd63f1..f3ea4b6300 100644 --- a/activation/post_supervisor.go +++ b/activation/post_supervisor.go @@ -70,7 +70,7 @@ type PostSupervisor struct { provingOpts PostProvingOpts postSetupProvider postSetupProvider - atxBuilder AtxBuilder + atxBuilder atxBuilder pid atomic.Int64 // pid of the running post service, only for tests. @@ -85,7 +85,7 @@ func NewPostSupervisor( postCfg PostConfig, provingOpts PostProvingOpts, postSetupProvider postSetupProvider, - atxBuilder AtxBuilder, + atxBuilder atxBuilder, ) *PostSupervisor { return &PostSupervisor{ logger: logger, diff --git a/activation/post_supervisor_test.go b/activation/post_supervisor_test.go index 597ce47fe2..07dc303293 100644 --- a/activation/post_supervisor_test.go +++ b/activation/post_supervisor_test.go @@ -105,7 +105,7 @@ func Test_PostSupervisor_Start_FailPrepare(t *testing.T) { mgr := NewMockpostSetupProvider(ctrl) testErr := errors.New("test error") mgr.EXPECT().PrepareInitializer(gomock.Any(), postOpts, sig.NodeID()).Return(testErr) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) require.NoError(t, ps.Start(cmdCfg, postOpts, sig)) @@ -141,7 +141,7 @@ func Test_PostSupervisor_Start_FailStartSession(t *testing.T) { mgr := NewMockpostSetupProvider(ctrl) mgr.EXPECT().PrepareInitializer(gomock.Any(), postOpts, sig.NodeID()).Return(nil) mgr.EXPECT().StartSession(gomock.Any(), sig.NodeID()).Return(errors.New("failed start session")) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) require.NoError(t, ps.Start(cmdCfg, postOpts, sig)) @@ -160,7 +160,7 @@ func Test_PostSupervisor_StartsServiceCmd(t *testing.T) { ctrl := gomock.NewController(t) mgr := newPostManager(t, postCfg, postOpts) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) builder.EXPECT().Register(sig) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) @@ -197,7 +197,7 @@ func Test_PostSupervisor_Restart_Possible(t *testing.T) { ctrl := gomock.NewController(t) mgr := newPostManager(t, postCfg, postOpts) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) builder.EXPECT().Register(sig) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) @@ -228,7 +228,7 @@ func Test_PostSupervisor_LogFatalOnCrash(t *testing.T) { ctrl := gomock.NewController(t) mgr := newPostManager(t, postCfg, postOpts) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) builder.EXPECT().Register(sig) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) @@ -261,7 +261,7 @@ func Test_PostSupervisor_LogFatalOnInvalidConfig(t *testing.T) { ctrl := gomock.NewController(t) mgr := newPostManager(t, postCfg, postOpts) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) builder.EXPECT().Register(sig) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) @@ -301,7 +301,7 @@ func Test_PostSupervisor_StopOnError(t *testing.T) { require.NoError(t, err) return nil }) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) builder.EXPECT().Register(sig) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) @@ -322,7 +322,7 @@ func Test_PostSupervisor_Providers_includesCPU(t *testing.T) { ctrl := gomock.NewController(t) mgr := NewMockpostSetupProvider(ctrl) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) providers, err := ps.Providers() @@ -344,7 +344,7 @@ func Test_PostSupervisor_Benchmark(t *testing.T) { ctrl := gomock.NewController(t) mgr := NewMockpostSetupProvider(ctrl) - builder := NewMockAtxBuilder(ctrl) + builder := NewMockatxBuilder(ctrl) ps := NewPostSupervisor(log.Named("supervisor"), postCfg, provingOpts, mgr, builder) providers, err := ps.Providers() diff --git a/activation/wire/interface.go b/activation/wire/interface.go index ba5006e3cd..4eb27da7f8 100644 --- a/activation/wire/interface.go +++ b/activation/wire/interface.go @@ -2,6 +2,9 @@ package wire import ( "context" + "fmt" + + "github.com/spacemeshos/go-scale" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/signing" @@ -25,3 +28,14 @@ type MalfeasanceValidator interface { // Signature validates the given signature against the given message and public key. Signature(d signing.Domain, nodeID types.NodeID, m []byte, sig types.EdSignature) bool } + +// Proof is an interface for all types of proofs that can be provided in an ATXProof. +// Generally the proof should be able to validate itself and be scale encoded. +type Proof interface { + scale.Encodable + scale.Decodable + fmt.Stringer + + Type() ProofType + Valid(ctx context.Context, malHandler MalfeasanceValidator) (types.NodeID, error) +} diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index 4220a7ee42..dcb471bada 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -1,17 +1,11 @@ package wire import ( - "context" "fmt" - "github.com/spacemeshos/go-scale" - "github.com/spacemeshos/go-spacemesh/codec" - "github.com/spacemeshos/go-spacemesh/common/types" ) -//go:generate scalegen - // MerkleTreeIndex is the index of the leaf containing the given field in the merkle tree. type MerkleTreeIndex uint64 @@ -108,14 +102,3 @@ func (p *ATXProof) Decode() (Proof, error) { } return rst, nil } - -// Proof is an interface for all types of proofs that can be provided in an ATXProof. -// Generally the proof should be able to validate itself and be scale encoded. -type Proof interface { - scale.Encodable - scale.Decodable - fmt.Stringer - - Type() ProofType - Valid(ctx context.Context, malHandler MalfeasanceValidator) (types.NodeID, error) -} diff --git a/activation/wire/malfeasance_double_marry_test.go b/activation/wire/malfeasance_double_marry_test.go index f52f8c8559..d53fc151ee 100644 --- a/activation/wire/malfeasance_double_marry_test.go +++ b/activation/wire/malfeasance_double_marry_test.go @@ -34,15 +34,15 @@ func Test_DoubleMarryProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + atx1 := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), - withMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), + atx2 := NewTestActivationTxV2( + WithMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), + WithMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), ) atx2.Sign(otherSig) @@ -71,15 +71,15 @@ func Test_DoubleMarryProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + atx1 := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), - withMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), + atx2 := NewTestActivationTxV2( + WithMarriageCertificate(otherSig, types.EmptyATXID, otherSig.NodeID()), + WithMarriageCertificate(sig, atx1.ID(), otherSig.NodeID()), ) atx2.Sign(otherSig) @@ -108,7 +108,7 @@ func Test_DoubleMarryProof(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - atx1 := newActivationTxV2() + atx1 := NewTestActivationTxV2() atx1.Sign(sig) proof, err := NewDoubleMarryProof(db, atx1, atx1, sig.NodeID()) @@ -138,15 +138,15 @@ func Test_DoubleMarryProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + atx1 := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(sig, atx1.ID(), sig.NodeID()), + atx2 := NewTestActivationTxV2( + WithMarriageCertificate(otherSig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(sig, atx1.ID(), sig.NodeID()), ) atx2.Sign(otherSig) diff --git a/activation/wire/malfeasance_double_merge_test.go b/activation/wire/malfeasance_double_merge_test.go index 83ba3a2b4a..bae6bd5150 100644 --- a/activation/wire/malfeasance_double_merge_test.go +++ b/activation/wire/malfeasance_double_merge_test.go @@ -29,8 +29,8 @@ func Test_DoubleMergeProof(t *testing.T) { edVerifier := signing.NewEdVerifier() setupMarriage := func(db sql.Executor) *ActivationTxV2 { - wInitialAtx1 := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wInitialAtx1 := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wInitialAtx1.Sign(sig) initialAtx1 := &types.ActivationTx{ @@ -40,8 +40,8 @@ func Test_DoubleMergeProof(t *testing.T) { initialAtx1.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, initialAtx1, wInitialAtx1.Blob())) - wInitialAtx2 := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wInitialAtx2 := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wInitialAtx2.Sign(otherSig) initialAtx2 := &types.ActivationTx{} @@ -49,10 +49,10 @@ func Test_DoubleMergeProof(t *testing.T) { initialAtx2.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, initialAtx2, wInitialAtx2.Blob())) - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), - withMarriageCertificate(sig, wInitialAtx1.ID(), marrySig.NodeID()), - withMarriageCertificate(otherSig, wInitialAtx2.ID(), marrySig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), + WithMarriageCertificate(sig, wInitialAtx1.ID(), marrySig.NodeID()), + WithMarriageCertificate(otherSig, wInitialAtx2.ID(), marrySig.NodeID()), ) wMarriageAtx.Sign(marrySig) @@ -76,15 +76,15 @@ func Test_DoubleMergeProof(t *testing.T) { marriageAtx := setupMarriage(db) - atx1 := newActivationTxV2( - withMarriageATX(marriageAtx.ID()), - withPublishEpoch(marriageAtx.PublishEpoch+1), + atx1 := NewTestActivationTxV2( + WithMarriageATX(marriageAtx.ID()), + WithPublishEpoch(marriageAtx.PublishEpoch+1), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withMarriageATX(marriageAtx.ID()), - withPublishEpoch(marriageAtx.PublishEpoch+1), + atx2 := NewTestActivationTxV2( + WithMarriageATX(marriageAtx.ID()), + WithPublishEpoch(marriageAtx.PublishEpoch+1), ) atx2.Sign(otherSig) @@ -108,9 +108,9 @@ func Test_DoubleMergeProof(t *testing.T) { marriageAtx := setupMarriage(db) - atx1 := newActivationTxV2( - withMarriageATX(marriageAtx.ID()), - withPublishEpoch(marriageAtx.PublishEpoch+1), + atx1 := NewTestActivationTxV2( + WithMarriageATX(marriageAtx.ID()), + WithPublishEpoch(marriageAtx.PublishEpoch+1), ) atx1.Sign(sig) @@ -130,10 +130,10 @@ func Test_DoubleMergeProof(t *testing.T) { t.Run("ATXs must have different signers", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - atx1 := newActivationTxV2() + atx1 := NewTestActivationTxV2() atx1.Sign(sig) - atx2 := newActivationTxV2() + atx2 := NewTestActivationTxV2() atx2.Sign(sig) proof, err := NewDoubleMergeProof(db, atx1, atx2) @@ -144,13 +144,13 @@ func Test_DoubleMergeProof(t *testing.T) { t.Run("ATXs must be published in the same epoch", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - atx := newActivationTxV2( - withPublishEpoch(1), + atx := NewTestActivationTxV2( + WithPublishEpoch(1), ) atx.Sign(sig) - atx2 := newActivationTxV2( - withPublishEpoch(2), + atx2 := NewTestActivationTxV2( + WithPublishEpoch(2), ) atx2.Sign(otherSig) proof, err := NewDoubleMergeProof(db, atx, atx2) @@ -162,13 +162,13 @@ func Test_DoubleMergeProof(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - atx := newActivationTxV2( - withPublishEpoch(1), + atx := NewTestActivationTxV2( + WithPublishEpoch(1), ) atx.Sign(sig) - atx2 := newActivationTxV2( - withPublishEpoch(1), + atx2 := NewTestActivationTxV2( + WithPublishEpoch(1), ) atx2.Sign(otherSig) @@ -211,15 +211,15 @@ func Test_DoubleMergeProof(t *testing.T) { marriageAtx := setupMarriage(db) - atx1 := newActivationTxV2( - withMarriageATX(marriageAtx.ID()), - withPublishEpoch(marriageAtx.PublishEpoch+1), + atx1 := NewTestActivationTxV2( + WithMarriageATX(marriageAtx.ID()), + WithPublishEpoch(marriageAtx.PublishEpoch+1), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withMarriageATX(marriageAtx.ID()), - withPublishEpoch(marriageAtx.PublishEpoch+1), + atx2 := NewTestActivationTxV2( + WithMarriageATX(marriageAtx.ID()), + WithPublishEpoch(marriageAtx.PublishEpoch+1), ) atx2.Sign(otherSig) diff --git a/activation/wire/malfeasance_invalid_post_test.go b/activation/wire/malfeasance_invalid_post_test.go index bed03b25eb..9d0880927a 100644 --- a/activation/wire/malfeasance_invalid_post_test.go +++ b/activation/wire/malfeasance_invalid_post_test.go @@ -42,10 +42,10 @@ func Test_InvalidPostProof(t *testing.T) { post PostV1, numUnits uint32, ) *ActivationTxV2 { - atx := newActivationTxV2( - withNIPost( - withNIPostChallenge(nipostChallenge), - withNIPostSubPost(SubPostV2{ + atx := NewTestActivationTxV2( + WithNIPost( + WithNIPostChallenge(nipostChallenge), + WithNIPostSubPost(SubPostV2{ Post: post, NumUnits: numUnits, }), @@ -61,8 +61,8 @@ func Test_InvalidPostProof(t *testing.T) { post PostV1, numUnits uint32, ) *ActivationTxV2 { - wInitialAtx := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wInitialAtx := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wInitialAtx.Sign(sig) initialAtx := &types.ActivationTx{ @@ -72,8 +72,8 @@ func Test_InvalidPostProof(t *testing.T) { initialAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, initialAtx, wInitialAtx.Blob())) - wPubInitialAtx := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wPubInitialAtx := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wPubInitialAtx.Sign(pubSig) pubInitialAtx := &types.ActivationTx{} @@ -83,10 +83,10 @@ func Test_InvalidPostProof(t *testing.T) { marryInitialAtx := types.RandomATXID() - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), - withMarriageCertificate(sig, wInitialAtx.ID(), marrySig.NodeID()), - withMarriageCertificate(pubSig, wPubInitialAtx.ID(), marrySig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), + WithMarriageCertificate(sig, wInitialAtx.ID(), marrySig.NodeID()), + WithMarriageCertificate(pubSig, wPubInitialAtx.ID(), marrySig.NodeID()), ) wMarriageAtx.Sign(marrySig) @@ -95,24 +95,24 @@ func Test_InvalidPostProof(t *testing.T) { marriageAtx.SmesherID = marrySig.NodeID() require.NoError(t, atxs.Add(db, marriageAtx, wMarriageAtx.Blob())) - atx := newActivationTxV2( - withPreviousATXs(marryInitialAtx, wInitialAtx.ID(), wPubInitialAtx.ID()), - withMarriageATX(wMarriageAtx.ID()), - withNIPost( - withNIPostChallenge(nipostChallenge), - withNIPostMembershipProof(MerkleProofV2{}), - withNIPostSubPost(SubPostV2{ + atx := NewTestActivationTxV2( + WithPreviousATXs(marryInitialAtx, wInitialAtx.ID(), wPubInitialAtx.ID()), + WithMarriageATX(wMarriageAtx.ID()), + WithNIPost( + WithNIPostChallenge(nipostChallenge), + WithNIPostMembershipProof(MerkleProofV2{}), + WithNIPostSubPost(SubPostV2{ MarriageIndex: 0, PrevATXIndex: 0, Post: PostV1{}, }), - withNIPostSubPost(SubPostV2{ + WithNIPostSubPost(SubPostV2{ MarriageIndex: 1, PrevATXIndex: 1, Post: post, NumUnits: numUnits, }), - withNIPostSubPost(SubPostV2{ + WithNIPostSubPost(SubPostV2{ MarriageIndex: 2, PrevATXIndex: 2, Post: PostV1{}, diff --git a/activation/wire/malfeasance_invalid_prev_atx_test.go b/activation/wire/malfeasance_invalid_prev_atx_test.go index 1c829e74eb..3047706ed0 100644 --- a/activation/wire/malfeasance_invalid_prev_atx_test.go +++ b/activation/wire/malfeasance_invalid_prev_atx_test.go @@ -37,8 +37,8 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { db sql.Executor, prevATX types.ATXID, ) *ActivationTxV2 { - wInitialAtx := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wInitialAtx := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wInitialAtx.Sign(sig) initialAtx := &types.ActivationTx{ @@ -48,8 +48,8 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { initialAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, initialAtx, wInitialAtx.Blob())) - wPubInitialAtx := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wPubInitialAtx := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wPubInitialAtx.Sign(pubSig) pubInitialAtx := &types.ActivationTx{} @@ -59,10 +59,10 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { marryInitialAtx := types.RandomATXID() - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), - withMarriageCertificate(sig, wInitialAtx.ID(), marrySig.NodeID()), - withMarriageCertificate(pubSig, wPubInitialAtx.ID(), marrySig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), + WithMarriageCertificate(sig, wInitialAtx.ID(), marrySig.NodeID()), + WithMarriageCertificate(pubSig, wPubInitialAtx.ID(), marrySig.NodeID()), ) wMarriageAtx.Sign(marrySig) @@ -71,20 +71,20 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { marriageAtx.SmesherID = marrySig.NodeID() require.NoError(t, atxs.Add(db, marriageAtx, wMarriageAtx.Blob())) - atx := newActivationTxV2( - withPreviousATXs(marryInitialAtx, wPubInitialAtx.ID(), prevATX), - withMarriageATX(wMarriageAtx.ID()), - withNIPost( - withNIPostMembershipProof(MerkleProofV2{}), - withNIPostSubPost(SubPostV2{ + atx := NewTestActivationTxV2( + WithPreviousATXs(marryInitialAtx, wPubInitialAtx.ID(), prevATX), + WithMarriageATX(wMarriageAtx.ID()), + WithNIPost( + WithNIPostMembershipProof(MerkleProofV2{}), + WithNIPostSubPost(SubPostV2{ MarriageIndex: 0, PrevATXIndex: 0, }), - withNIPostSubPost(SubPostV2{ + WithNIPostSubPost(SubPostV2{ MarriageIndex: 1, PrevATXIndex: 2, }), - withNIPostSubPost(SubPostV2{ + WithNIPostSubPost(SubPostV2{ MarriageIndex: 2, PrevATXIndex: 1, }), @@ -99,14 +99,14 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { db := statesql.InMemoryTest(t) prevATXID := types.RandomATXID() - atx1 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(5), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(5), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(7), + atx2 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(7), ) atx2.Sign(sig) @@ -135,9 +135,9 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { prevAtx.SetID(prevATXID) prevAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, prevAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(5), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(5), ) atx1.Sign(sig) atx2 := newMergedATXv2(db, prevATXID) @@ -164,8 +164,8 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - atx1 := newActivationTxV2( - withPreviousATXs(types.RandomATXID()), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(types.RandomATXID()), ) atx1.Sign(sig) @@ -179,12 +179,12 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { db := statesql.InMemoryTest(t) prevATX := types.RandomATXID() - atx1 := newActivationTxV2( - withPreviousATXs(prevATX), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(prevATX), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withPreviousATXs(prevATX), + atx2 := NewTestActivationTxV2( + WithPreviousATXs(prevATX), ) atx2.Sign(pubSig) @@ -209,9 +209,9 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { prevAtx.SetID(prevATXID) prevAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, prevAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(5), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(5), ) atx1.Sign(invalidSig) atx2 := newMergedATXv2(db, prevATXID) @@ -238,9 +238,9 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { prevAtx.SetID(prevATXID) prevAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, prevAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(5), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(5), ) atx1.Sign(sig) atx2 := newMergedATXv2(db, prevATXID) @@ -267,14 +267,14 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - atx1 := newActivationTxV2( - withPreviousATXs(types.RandomATXID()), - withPublishEpoch(5), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(types.RandomATXID()), + WithPublishEpoch(5), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withPreviousATXs(types.RandomATXID()), - withPublishEpoch(7), + atx2 := NewTestActivationTxV2( + WithPreviousATXs(types.RandomATXID()), + WithPublishEpoch(7), ) atx2.Sign(sig) @@ -288,14 +288,14 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { db := statesql.InMemoryTest(t) prevATXID := types.RandomATXID() - atx1 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(5), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(5), ) atx1.Sign(sig) - atx2 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(7), + atx2 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(7), ) atx2.Sign(sig) @@ -540,9 +540,9 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { prevAtx.SetID(prevATXID) prevAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, prevAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withPreviousATXs(prevATXID), - withPublishEpoch(5), + atx1 := NewTestActivationTxV2( + WithPreviousATXs(prevATXID), + WithPublishEpoch(5), ) atx1.Sign(sig) atx2 := newMergedATXv2(db, prevATXID) @@ -657,8 +657,8 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { db sql.Executor, prevATX types.ATXID, ) *ActivationTxV2 { - wInitialAtx := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wInitialAtx := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wInitialAtx.Sign(sig) initialAtx := &types.ActivationTx{ @@ -668,8 +668,8 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { initialAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, initialAtx, wInitialAtx.Blob())) - wPubInitialAtx := newActivationTxV2( - withInitial(types.RandomATXID(), PostV1{}), + wPubInitialAtx := NewTestActivationTxV2( + WithInitial(types.RandomATXID(), PostV1{}), ) wPubInitialAtx.Sign(pubSig) pubInitialAtx := &types.ActivationTx{} @@ -679,10 +679,10 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { marryInitialAtx := types.RandomATXID() - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), - withMarriageCertificate(sig, wInitialAtx.ID(), marrySig.NodeID()), - withMarriageCertificate(pubSig, wPubInitialAtx.ID(), marrySig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(marrySig, types.EmptyATXID, marrySig.NodeID()), + WithMarriageCertificate(sig, wInitialAtx.ID(), marrySig.NodeID()), + WithMarriageCertificate(pubSig, wPubInitialAtx.ID(), marrySig.NodeID()), ) wMarriageAtx.Sign(marrySig) @@ -691,20 +691,20 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { marriageAtx.SmesherID = marrySig.NodeID() require.NoError(t, atxs.Add(db, marriageAtx, wMarriageAtx.Blob())) - atx := newActivationTxV2( - withPreviousATXs(marryInitialAtx, wPubInitialAtx.ID(), prevATX), - withMarriageATX(wMarriageAtx.ID()), - withNIPost( - withNIPostMembershipProof(MerkleProofV2{}), - withNIPostSubPost(SubPostV2{ + atx := NewTestActivationTxV2( + WithPreviousATXs(marryInitialAtx, wPubInitialAtx.ID(), prevATX), + WithMarriageATX(wMarriageAtx.ID()), + WithNIPost( + WithNIPostMembershipProof(MerkleProofV2{}), + WithNIPostSubPost(SubPostV2{ MarriageIndex: 0, PrevATXIndex: 0, }), - withNIPostSubPost(SubPostV2{ + WithNIPostSubPost(SubPostV2{ MarriageIndex: 1, PrevATXIndex: 2, }), - withNIPostSubPost(SubPostV2{ + WithNIPostSubPost(SubPostV2{ MarriageIndex: 2, PrevATXIndex: 1, }), @@ -730,9 +730,9 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { } atxv1.Sign(sig) - atxv2 := newActivationTxV2( - withPreviousATXs(prevATX), - withPublishEpoch(7), + atxv2 := NewTestActivationTxV2( + WithPreviousATXs(prevATX), + WithPublishEpoch(7), ) atxv2.Sign(sig) @@ -802,9 +802,9 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { } atxv1.Sign(sig) - atxv2 := newActivationTxV2( - withPreviousATXs(prevATX), - withPublishEpoch(7), + atxv2 := NewTestActivationTxV2( + WithPreviousATXs(prevATX), + WithPublishEpoch(7), ) atxv2.Sign(pubSig) @@ -891,9 +891,9 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { } atxv1.Sign(sig) - atxv2 := newActivationTxV2( - withPreviousATXs(types.RandomATXID()), - withPublishEpoch(7), + atxv2 := NewTestActivationTxV2( + WithPreviousATXs(types.RandomATXID()), + WithPublishEpoch(7), ) atxv2.Sign(sig) @@ -918,9 +918,9 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { } atxv1.Sign(sig) - atxv2 := newActivationTxV2( - withPreviousATXs(prevATX), - withPublishEpoch(7), + atxv2 := NewTestActivationTxV2( + WithPreviousATXs(prevATX), + WithPublishEpoch(7), ) atxv2.Sign(sig) diff --git a/activation/wire/malfeasance_shared_test.go b/activation/wire/malfeasance_shared_test.go index 46fbccea11..b50181b1d5 100644 --- a/activation/wire/malfeasance_shared_test.go +++ b/activation/wire/malfeasance_shared_test.go @@ -34,9 +34,9 @@ func Test_MarryProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + atx1 := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) @@ -73,9 +73,9 @@ func Test_MarryProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + atx1 := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) @@ -96,9 +96,9 @@ func Test_MarryProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - atx1 := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + atx1 := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) atx1.Sign(sig) @@ -183,9 +183,9 @@ func Test_MarriageProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) wMarriageAtx.Sign(sig) marriageAtx := &types.ActivationTx{} @@ -193,8 +193,8 @@ func Test_MarriageProof(t *testing.T) { marriageAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, marriageAtx, wMarriageAtx.Blob())) - atx := newActivationTxV2( - withMarriageATX(wMarriageAtx.ID()), + atx := NewTestActivationTxV2( + WithMarriageATX(wMarriageAtx.ID()), ) atx.Sign(sig) @@ -222,9 +222,9 @@ func Test_MarriageProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) wMarriageAtx.Sign(sig) marriageAtx := &types.ActivationTx{} @@ -232,8 +232,8 @@ func Test_MarriageProof(t *testing.T) { marriageAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, marriageAtx, wMarriageAtx.Blob())) - atx := newActivationTxV2( - withMarriageATX(wMarriageAtx.ID()), + atx := NewTestActivationTxV2( + WithMarriageATX(wMarriageAtx.ID()), ) atx.Sign(sig) @@ -247,8 +247,8 @@ func Test_MarriageProof(t *testing.T) { db := statesql.InMemoryTest(t) - atx := newActivationTxV2( - withMarriageATX(types.RandomATXID()), + atx := NewTestActivationTxV2( + WithMarriageATX(types.RandomATXID()), ) atx.Sign(sig) @@ -266,9 +266,9 @@ func Test_MarriageProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) wMarriageAtx.Sign(sig) marriageAtx := &types.ActivationTx{} @@ -276,8 +276,8 @@ func Test_MarriageProof(t *testing.T) { marriageAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, marriageAtx, wMarriageAtx.Blob())) - atx := newActivationTxV2( - withMarriageATX(wMarriageAtx.ID()), + atx := NewTestActivationTxV2( + WithMarriageATX(wMarriageAtx.ID()), ) atx.Sign(sig) @@ -307,9 +307,9 @@ func Test_MarriageProof(t *testing.T) { otherAtx.SmesherID = otherSig.NodeID() require.NoError(t, atxs.Add(db, otherAtx, types.AtxBlob{})) - wMarriageAtx := newActivationTxV2( - withMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), - withMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), + wMarriageAtx := NewTestActivationTxV2( + WithMarriageCertificate(sig, types.EmptyATXID, sig.NodeID()), + WithMarriageCertificate(otherSig, otherAtx.ID(), sig.NodeID()), ) wMarriageAtx.Sign(sig) marriageAtx := &types.ActivationTx{} @@ -317,8 +317,8 @@ func Test_MarriageProof(t *testing.T) { marriageAtx.SmesherID = sig.NodeID() require.NoError(t, atxs.Add(db, marriageAtx, wMarriageAtx.Blob())) - atx := newActivationTxV2( - withMarriageATX(wMarriageAtx.ID()), + atx := NewTestActivationTxV2( + WithMarriageATX(wMarriageAtx.ID()), ) atx.Sign(sig) diff --git a/activation/wire/mocks.go b/activation/wire/mocks.go index ae0fd1be61..fab820f6be 100644 --- a/activation/wire/mocks.go +++ b/activation/wire/mocks.go @@ -13,6 +13,7 @@ import ( context "context" reflect "reflect" + scale "github.com/spacemeshos/go-scale" types "github.com/spacemeshos/go-spacemesh/common/types" signing "github.com/spacemeshos/go-spacemesh/signing" gomock "go.uber.org/mock/gomock" @@ -117,3 +118,220 @@ func (c *MockMalfeasanceValidatorSignatureCall) DoAndReturn(f func(signing.Domai c.Call = c.Call.DoAndReturn(f) return c } + +// MockProof is a mock of Proof interface. +type MockProof struct { + ctrl *gomock.Controller + recorder *MockProofMockRecorder + isgomock struct{} +} + +// MockProofMockRecorder is the mock recorder for MockProof. +type MockProofMockRecorder struct { + mock *MockProof +} + +// NewMockProof creates a new mock instance. +func NewMockProof(ctrl *gomock.Controller) *MockProof { + mock := &MockProof{ctrl: ctrl} + mock.recorder = &MockProofMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProof) EXPECT() *MockProofMockRecorder { + return m.recorder +} + +// DecodeScale mocks base method. +func (m *MockProof) DecodeScale(dec *scale.Decoder) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecodeScale", dec) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecodeScale indicates an expected call of DecodeScale. +func (mr *MockProofMockRecorder) DecodeScale(dec any) *MockProofDecodeScaleCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeScale", reflect.TypeOf((*MockProof)(nil).DecodeScale), dec) + return &MockProofDecodeScaleCall{Call: call} +} + +// MockProofDecodeScaleCall wrap *gomock.Call +type MockProofDecodeScaleCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProofDecodeScaleCall) Return(arg0 int, arg1 error) *MockProofDecodeScaleCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProofDecodeScaleCall) Do(f func(*scale.Decoder) (int, error)) *MockProofDecodeScaleCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProofDecodeScaleCall) DoAndReturn(f func(*scale.Decoder) (int, error)) *MockProofDecodeScaleCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// EncodeScale mocks base method. +func (m *MockProof) EncodeScale(enc *scale.Encoder) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EncodeScale", enc) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EncodeScale indicates an expected call of EncodeScale. +func (mr *MockProofMockRecorder) EncodeScale(enc any) *MockProofEncodeScaleCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncodeScale", reflect.TypeOf((*MockProof)(nil).EncodeScale), enc) + return &MockProofEncodeScaleCall{Call: call} +} + +// MockProofEncodeScaleCall wrap *gomock.Call +type MockProofEncodeScaleCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProofEncodeScaleCall) Return(arg0 int, arg1 error) *MockProofEncodeScaleCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProofEncodeScaleCall) Do(f func(*scale.Encoder) (int, error)) *MockProofEncodeScaleCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProofEncodeScaleCall) DoAndReturn(f func(*scale.Encoder) (int, error)) *MockProofEncodeScaleCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// String mocks base method. +func (m *MockProof) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockProofMockRecorder) String() *MockProofStringCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockProof)(nil).String)) + return &MockProofStringCall{Call: call} +} + +// MockProofStringCall wrap *gomock.Call +type MockProofStringCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProofStringCall) Return(arg0 string) *MockProofStringCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProofStringCall) Do(f func() string) *MockProofStringCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProofStringCall) DoAndReturn(f func() string) *MockProofStringCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Type mocks base method. +func (m *MockProof) Type() ProofType { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Type") + ret0, _ := ret[0].(ProofType) + return ret0 +} + +// Type indicates an expected call of Type. +func (mr *MockProofMockRecorder) Type() *MockProofTypeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockProof)(nil).Type)) + return &MockProofTypeCall{Call: call} +} + +// MockProofTypeCall wrap *gomock.Call +type MockProofTypeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProofTypeCall) Return(arg0 ProofType) *MockProofTypeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProofTypeCall) Do(f func() ProofType) *MockProofTypeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProofTypeCall) DoAndReturn(f func() ProofType) *MockProofTypeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Valid mocks base method. +func (m *MockProof) Valid(ctx context.Context, malHandler MalfeasanceValidator) (types.NodeID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Valid", ctx, malHandler) + ret0, _ := ret[0].(types.NodeID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Valid indicates an expected call of Valid. +func (mr *MockProofMockRecorder) Valid(ctx, malHandler any) *MockProofValidCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Valid", reflect.TypeOf((*MockProof)(nil).Valid), ctx, malHandler) + return &MockProofValidCall{Call: call} +} + +// MockProofValidCall wrap *gomock.Call +type MockProofValidCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProofValidCall) Return(arg0 types.NodeID, arg1 error) *MockProofValidCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProofValidCall) Do(f func(context.Context, MalfeasanceValidator) (types.NodeID, error)) *MockProofValidCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProofValidCall) DoAndReturn(f func(context.Context, MalfeasanceValidator) (types.NodeID, error)) *MockProofValidCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/activation/wire/wire_v2_helpers.go b/activation/wire/wire_v2_helpers.go new file mode 100644 index 0000000000..e30724d07e --- /dev/null +++ b/activation/wire/wire_v2_helpers.go @@ -0,0 +1,114 @@ +package wire + +import ( + "math/rand/v2" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/signing" +) + +type testAtxV2Opt func(*ActivationTxV2) + +func WithMarriageCertificate(sig *signing.EdSigner, refAtx types.ATXID, atxPublisher types.NodeID) testAtxV2Opt { + return func(atx *ActivationTxV2) { + certificate := MarriageCertificate{ + ReferenceAtx: refAtx, + Signature: sig.Sign(signing.MARRIAGE, atxPublisher.Bytes()), + } + atx.Marriages = append(atx.Marriages, certificate) + } +} + +func WithMarriageATX(id types.ATXID) testAtxV2Opt { + return func(atx *ActivationTxV2) { + atx.MarriageATX = &id + } +} + +func WithPublishEpoch(epoch types.EpochID) testAtxV2Opt { + return func(atx *ActivationTxV2) { + atx.PublishEpoch = epoch + } +} + +func WithInitial(commitAtx types.ATXID, post PostV1) testAtxV2Opt { + return func(atx *ActivationTxV2) { + atx.Initial = &InitialAtxPartsV2{ + CommitmentATX: commitAtx, + Post: post, + } + } +} + +func WithPreviousATXs(atxs ...types.ATXID) testAtxV2Opt { + return func(atx *ActivationTxV2) { + atx.PreviousATXs = atxs + } +} + +func WithNIPost(opts ...testNIPostV2Opt) testAtxV2Opt { + return func(atx *ActivationTxV2) { + nipost := &NIPostV2{} + for _, opt := range opts { + opt(nipost) + } + atx.NIPosts = append(atx.NIPosts, *nipost) + } +} + +type testNIPostV2Opt func(*NIPostV2) + +func WithNIPostChallenge(challenge types.Hash32) testNIPostV2Opt { + return func(nipost *NIPostV2) { + nipost.Challenge = challenge + } +} + +func WithNIPostMembershipProof(proof MerkleProofV2) testNIPostV2Opt { + return func(nipost *NIPostV2) { + nipost.Membership = proof + } +} + +func WithNIPostSubPost(subPost SubPostV2) testNIPostV2Opt { + return func(nipost *NIPostV2) { + nipost.Posts = append(nipost.Posts, subPost) + } +} + +// NewTestActivationTxV2 creates a new ActivationTxV2 with random values. +// ONLY FOR TESTING. +func NewTestActivationTxV2(opts ...testAtxV2Opt) *ActivationTxV2 { + atx := &ActivationTxV2{ + PublishEpoch: rand.N(types.EpochID(255)), + PositioningATX: types.RandomATXID(), + } + for _, opt := range opts { + opt(atx) + } + if atx.PreviousATXs == nil { + atx.PreviousATXs = make([]types.ATXID, 1+rand.IntN(255)) + } + if atx.NIPosts == nil { + atx.NIPosts = []NIPostV2{ + { + Membership: MerkleProofV2{ + Nodes: make([]types.Hash32, 32), + }, + Challenge: types.RandomHash(), + Posts: []SubPostV2{ + { + MarriageIndex: rand.Uint32N(256), + PrevATXIndex: 0, + Post: PostV1{ + Nonce: 0, + Indices: make([]byte, 800), + Pow: 0, + }, + }, + }, + }, + } + } + return atx +} diff --git a/activation/wire/wire_v2_test.go b/activation/wire/wire_v2_test.go index f71a6fba96..329d86ce5c 100644 --- a/activation/wire/wire_v2_test.go +++ b/activation/wire/wire_v2_test.go @@ -2,7 +2,6 @@ package wire import ( "fmt" - "math/rand/v2" "testing" fuzz "github.com/google/gofuzz" @@ -10,113 +9,8 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/signing" ) -type testAtxV2Opt func(*ActivationTxV2) - -func withMarriageCertificate(sig *signing.EdSigner, refAtx types.ATXID, atxPublisher types.NodeID) testAtxV2Opt { - return func(atx *ActivationTxV2) { - certificate := MarriageCertificate{ - ReferenceAtx: refAtx, - Signature: sig.Sign(signing.MARRIAGE, atxPublisher.Bytes()), - } - atx.Marriages = append(atx.Marriages, certificate) - } -} - -func withMarriageATX(id types.ATXID) testAtxV2Opt { - return func(atx *ActivationTxV2) { - atx.MarriageATX = &id - } -} - -func withPublishEpoch(epoch types.EpochID) testAtxV2Opt { - return func(atx *ActivationTxV2) { - atx.PublishEpoch = epoch - } -} - -func withInitial(commitAtx types.ATXID, post PostV1) testAtxV2Opt { - return func(atx *ActivationTxV2) { - atx.Initial = &InitialAtxPartsV2{ - CommitmentATX: commitAtx, - Post: post, - } - } -} - -func withPreviousATXs(atxs ...types.ATXID) testAtxV2Opt { - return func(atx *ActivationTxV2) { - atx.PreviousATXs = atxs - } -} - -func withNIPost(opts ...testNIPostV2Opt) testAtxV2Opt { - return func(atx *ActivationTxV2) { - nipost := &NIPostV2{} - for _, opt := range opts { - opt(nipost) - } - atx.NIPosts = append(atx.NIPosts, *nipost) - } -} - -type testNIPostV2Opt func(*NIPostV2) - -func withNIPostChallenge(challenge types.Hash32) testNIPostV2Opt { - return func(nipost *NIPostV2) { - nipost.Challenge = challenge - } -} - -func withNIPostMembershipProof(proof MerkleProofV2) testNIPostV2Opt { - return func(nipost *NIPostV2) { - nipost.Membership = proof - } -} - -func withNIPostSubPost(subPost SubPostV2) testNIPostV2Opt { - return func(nipost *NIPostV2) { - nipost.Posts = append(nipost.Posts, subPost) - } -} - -func newActivationTxV2(opts ...testAtxV2Opt) *ActivationTxV2 { - atx := &ActivationTxV2{ - PublishEpoch: rand.N(types.EpochID(255)), - PositioningATX: types.RandomATXID(), - } - for _, opt := range opts { - opt(atx) - } - if atx.PreviousATXs == nil { - atx.PreviousATXs = make([]types.ATXID, 1+rand.IntN(255)) - } - if atx.NIPosts == nil { - atx.NIPosts = []NIPostV2{ - { - Membership: MerkleProofV2{ - Nodes: make([]types.Hash32, 32), - }, - Challenge: types.RandomHash(), - Posts: []SubPostV2{ - { - MarriageIndex: rand.Uint32N(256), - PrevATXIndex: 0, - Post: PostV1{ - Nonce: 0, - Indices: make([]byte, 800), - Pow: 0, - }, - }, - }, - }, - } - } - return atx -} - func Benchmark_ATXv2ID(b *testing.B) { f := fuzz.New() b.ResetTimer() diff --git a/api/grpcserver/post_service_test.go b/api/grpcserver/post_service_test.go index df6b6b1717..856520dfa9 100644 --- a/api/grpcserver/post_service_test.go +++ b/api/grpcserver/post_service_test.go @@ -64,7 +64,7 @@ func launchPostSupervisor( require.NoError(tb, err) // start post supervisor - builder := activation.NewMockAtxBuilder(ctrl) + builder := activation.NewMockatxBuilder(ctrl) builder.EXPECT().Register(sig) ps := activation.NewPostSupervisor(log, postCfg, provingOpts, mgr, builder) require.NoError(tb, ps.Start(serviceCfg, postOpts, sig)) @@ -108,7 +108,7 @@ func launchPostSupervisorTLS( require.NoError(tb, err) // start post supervisor - builder := activation.NewMockAtxBuilder(ctrl) + builder := activation.NewMockatxBuilder(ctrl) builder.EXPECT().Register(sig) ps := activation.NewPostSupervisor(log, postCfg, provingOpts, mgr, builder) require.NoError(tb, ps.Start(serviceCfg, postOpts, sig)) diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index 56f2632443..5cd9b6e098 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -32,6 +32,8 @@ type testHandler struct { func newTestHandler(tb testing.TB) *testHandler { db := statesql.InMemory() + edVerifier := signing.NewEdVerifier() + observer, observedLogs := observer.New(zap.WarnLevel) logger := zaptest.NewLogger(tb, zaptest.WrapOptions(zap.WrapCore( func(core zapcore.Core) zapcore.Core { @@ -42,8 +44,6 @@ func newTestHandler(tb testing.TB) *testHandler { ctrl := gomock.NewController(tb) mockTrt := malfeasance2.NewMocktortoise(ctrl) - edVerifier := signing.NewEdVerifier() - h := malfeasance2.NewHandler( db, logger, diff --git a/malfeasance2/mocks.go b/malfeasance2/mocks.go index a24d32879b..51a1784ccc 100644 --- a/malfeasance2/mocks.go +++ b/malfeasance2/mocks.go @@ -18,6 +18,68 @@ import ( gomock "go.uber.org/mock/gomock" ) +// Mocksyncer is a mock of syncer interface. +type Mocksyncer struct { + ctrl *gomock.Controller + recorder *MocksyncerMockRecorder + isgomock struct{} +} + +// MocksyncerMockRecorder is the mock recorder for Mocksyncer. +type MocksyncerMockRecorder struct { + mock *Mocksyncer +} + +// NewMocksyncer creates a new mock instance. +func NewMocksyncer(ctrl *gomock.Controller) *Mocksyncer { + mock := &Mocksyncer{ctrl: ctrl} + mock.recorder = &MocksyncerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocksyncer) EXPECT() *MocksyncerMockRecorder { + return m.recorder +} + +// ListenToATXGossip mocks base method. +func (m *Mocksyncer) ListenToATXGossip() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListenToATXGossip") + ret0, _ := ret[0].(bool) + return ret0 +} + +// ListenToATXGossip indicates an expected call of ListenToATXGossip. +func (mr *MocksyncerMockRecorder) ListenToATXGossip() *MocksyncerListenToATXGossipCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListenToATXGossip", reflect.TypeOf((*Mocksyncer)(nil).ListenToATXGossip)) + return &MocksyncerListenToATXGossipCall{Call: call} +} + +// MocksyncerListenToATXGossipCall wrap *gomock.Call +type MocksyncerListenToATXGossipCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocksyncerListenToATXGossipCall) Return(arg0 bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocksyncerListenToATXGossipCall) Do(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocksyncerListenToATXGossipCall) DoAndReturn(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // Mocktortoise is a mock of tortoise interface. type Mocktortoise struct { ctrl *gomock.Controller diff --git a/node/node_test.go b/node/node_test.go index a777bfd0c9..835bb69fbb 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -1225,7 +1225,7 @@ func launchPostSupervisor( provingOpts := activation.DefaultPostProvingOpts() provingOpts.RandomXMode = activation.PostRandomXModeLight - builder := activation.NewMockAtxBuilder(gomock.NewController(tb)) + builder := activation.NewMockatxBuilder(gomock.NewController(tb)) builder.EXPECT().Register(sig) ps := activation.NewPostSupervisor(log, postCfg, provingOpts, mgr, builder) require.NoError(tb, ps.Start(cmdCfg, postOpts, sig)) diff --git a/systest/tests/distributed_post_verification_test.go b/systest/tests/distributed_post_verification_test.go index d5cbbcc2d0..2ec05f27a8 100644 --- a/systest/tests/distributed_post_verification_test.go +++ b/systest/tests/distributed_post_verification_test.go @@ -161,7 +161,7 @@ func TestPostMalfeasanceProof(t *testing.T) { ) require.NoError(t, err) - builder := activation.NewMockAtxBuilder(ctrl) + builder := activation.NewMockatxBuilder(ctrl) builder.EXPECT().Register(signer) postSupervisor := activation.NewPostSupervisor( logger.Named("post-supervisor"), From f979e9139850efe7fe2355937b125af4dc53e30e Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:27:09 +0000 Subject: [PATCH 31/66] More tests --- activation/wire/malfeasance_test.go | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 activation/wire/malfeasance_test.go diff --git a/activation/wire/malfeasance_test.go b/activation/wire/malfeasance_test.go new file mode 100644 index 0000000000..2cd41288a4 --- /dev/null +++ b/activation/wire/malfeasance_test.go @@ -0,0 +1,89 @@ +package wire + +import ( + "testing" + + fuzz "github.com/google/gofuzz" + "github.com/stretchr/testify/require" + + "github.com/spacemeshos/go-spacemesh/codec" +) + +func fuzzDecoding[T Proof](t *testing.T, data []byte, proof T) { + fuzzer := fuzz.NewFromGoFuzz(data) + fuzzer.Fuzz(proof) + + atxProof := &ATXProof{ + Version: 0x01, + ProofType: proof.Type(), + + Proof: codec.MustEncode(proof), + } + + encodedAtxProof := codec.MustEncode(atxProof) + decodedAtxProof := &ATXProof{} + codec.MustDecode(encodedAtxProof, decodedAtxProof) + + decodedProof, err := decodedAtxProof.Decode() + require.NoError(t, err) + + require.Equal(t, proof, decodedProof.(T)) +} + +func FuzzATXProofDecodeDoubleMarry(f *testing.F) { + f.Add([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) + f.Fuzz(func(t *testing.T, data []byte) { + fuzzDecoding(t, data, &ProofDoubleMarry{}) + }) +} + +func FuzzATXProofDecodeDoubleMerge(f *testing.F) { + f.Add([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) + f.Fuzz(func(t *testing.T, data []byte) { + fuzzDecoding(t, data, &ProofDoubleMerge{}) + }) +} + +func FuzzATXProofDecodeInvalidPost(f *testing.F) { + f.Add([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) + f.Fuzz(func(t *testing.T, data []byte) { + fuzzDecoding(t, data, &ProofInvalidPost{}) + }) +} + +func FuzzATXProofDecodeInvalidPrevAtxV1(f *testing.F) { + f.Add([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) + f.Fuzz(func(t *testing.T, data []byte) { + fuzzDecoding(t, data, &ProofInvalidPrevAtxV1{}) + }) +} + +func FuzzATXProofDecodeInvalidPrevAtxV2(f *testing.F) { + f.Add([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) + f.Fuzz(func(t *testing.T, data []byte) { + fuzzDecoding(t, data, &ProofInvalidPrevAtxV2{}) + }) +} + +func TestDecode(t *testing.T) { + t.Run("unknown proof type", func(t *testing.T) { + atxProof := &ATXProof{ + Version: 0x01, + ProofType: 0x42, // unknown proof type + } + + _, err := atxProof.Decode() + require.ErrorContains(t, err, "unknown ATX malfeasance proof type") + }) + + t.Run("atx proof fails decoding", func(t *testing.T) { + atxProof := &ATXProof{ + Version: 0x01, + ProofType: DoubleMarry, + Proof: []byte{}, // invalid proof + } + + _, err := atxProof.Decode() + require.ErrorContains(t, err, "decoding ATX malfeasance proof of type 0x11") + }) +} From e04ab99837ec9900e8e7eaf56e0657183cc5f400 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:52:15 +0000 Subject: [PATCH 32/66] Undo wrong change --- sql/marriage/marriages.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/marriage/marriages.go b/sql/marriage/marriages.go index 0991391402..d114c1fa58 100644 --- a/sql/marriage/marriages.go +++ b/sql/marriage/marriages.go @@ -138,7 +138,7 @@ func NodeIDsByID(db sql.Executor, id ID) ([]types.NodeID, error) { s.BindInt64(1, int64(id)) }, func(s *sql.Statement) bool { var nodeID types.NodeID - s.ColumnBytes(0, nodeID.Bytes()) + s.ColumnBytes(0, nodeID[:]) nodeIDs = append(nodeIDs, nodeID) return true }) From 21aeabf0df4603c090003a12d07f993599a1b1da Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:10:36 +0000 Subject: [PATCH 33/66] Remove obsolete TODOs --- activation/handler.go | 27 --------------------------- activation/handler_v1.go | 26 +++++++++++++++++++++++++- common/types/poet.go | 2 +- events/events.go | 2 +- fetch/fetch_test.go | 16 ++++++++-------- fetch/handler_test.go | 2 +- fetch/wire_types.go | 2 +- mesh/mesh_test.go | 4 ++-- miner/proposal_builder.go | 2 -- p2p/server/server.go | 2 +- timesync/peersync/sync_test.go | 4 ++-- tortoise/model/core.go | 2 +- 12 files changed, 43 insertions(+), 48 deletions(-) diff --git a/activation/handler.go b/activation/handler.go index b0ff36849c..32cfeed5bf 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -20,8 +20,6 @@ import ( "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" - "github.com/spacemeshos/go-spacemesh/sql" - "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/system" ) @@ -292,28 +290,3 @@ func (h *Handler) handleAtx(ctx context.Context, expHash types.Hash32, peer p2p. h.inProgress.Forget(key) return err } - -// Obtain the atxSignature of the given ATX. -func atxSignature(ctx context.Context, db sql.Executor, id types.ATXID) (types.EdSignature, error) { - var blob sql.Blob - v, err := atxs.LoadBlob(ctx, db, id.Bytes(), &blob) - if err != nil { - return types.EmptyEdSignature, err - } - - if len(blob.Bytes) == 0 { - // An empty blob indicates a golden ATX (after a checkpoint-recovery). - return types.EmptyEdSignature, fmt.Errorf("can't get signature for a golden (checkpointed) ATX: %s", id) - } - - // TODO: implement for ATX V2 - switch v { - case types.AtxV1: - var atx wire.ActivationTxV1 - if err := codec.Decode(blob.Bytes, &atx); err != nil { - return types.EmptyEdSignature, fmt.Errorf("decoding atx v1: %w", err) - } - return atx.Signature, nil - } - return types.EmptyEdSignature, fmt.Errorf("unsupported ATX version: %v", v) -} diff --git a/activation/handler_v1.go b/activation/handler_v1.go index 1ec7dd0289..42f911ad3e 100644 --- a/activation/handler_v1.go +++ b/activation/handler_v1.go @@ -438,7 +438,6 @@ func (h *HandlerV1) checkWrongPrevAtx( return nil, err } if v != types.AtxV1 { - // TODO(mafa): update when V2 is introduced return nil, fmt.Errorf("ATX %s with same prev ATX as %s is not version 1", atx2ID, atx.PrevATXID) } @@ -629,3 +628,28 @@ func collectAtxDeps(goldenAtxId types.ATXID, atx *wire.ActivationTxV1) (types.Ha return types.BytesToHash(atx.NIPost.PostMetadata.Challenge), maps.Keys(filtered) } + +// Obtain the atxSignature of the given ATXv1. +func atxSignature(ctx context.Context, db sql.Executor, id types.ATXID) (types.EdSignature, error) { + var blob sql.Blob + v, err := atxs.LoadBlob(ctx, db, id.Bytes(), &blob) + if err != nil { + return types.EmptyEdSignature, err + } + + if len(blob.Bytes) == 0 { + // An empty blob indicates a golden ATX (after a checkpoint-recovery). + return types.EmptyEdSignature, fmt.Errorf("can't get signature for a golden (checkpointed) ATX: %s", id) + } + + switch v { + case types.AtxV1: + var atx wire.ActivationTxV1 + if err := codec.Decode(blob.Bytes, &atx); err != nil { + return types.EmptyEdSignature, fmt.Errorf("decoding atx v1: %w", err) + } + return atx.Signature, nil + default: // only needed for V1 ATXs + return types.EmptyEdSignature, fmt.Errorf("unsupported ATX version: %v", v) + } +} diff --git a/common/types/poet.go b/common/types/poet.go index 27e5764243..62cd82e087 100644 --- a/common/types/poet.go +++ b/common/types/poet.go @@ -67,7 +67,7 @@ func (p *PoetProof) MarshalLogObject(encoder zapcore.ObjectEncoder) error { type PoetProofMessage struct { PoetProof PoetServiceID []byte `scale:"max=32"` // public key of the PoET service - RoundID string `scale:"max=32"` // TODO(mafa): convert to uint64 + RoundID string `scale:"max=32"` // round ID // The input to Poet's POSW. // It's the root of a merkle tree built from all of the members // that are included in the proof. diff --git a/events/events.go b/events/events.go index 5804fb1c25..8827f10274 100644 --- a/events/events.go +++ b/events/events.go @@ -368,7 +368,7 @@ func emitUserEvent(help string, failure bool, details pb.IsEventDetails) { } } -// TODO (mafa): instead of passing along the proof bytes the API should query the malfeasance handler for the metadata +// TODO(mafa): instead of passing along the proof bytes the API should query the malfeasance handler for the metadata // of the proof if needed. // The malfeasance handler should then take care of decoding the proof, caching if necessary and returning the metadata. func ToMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof { diff --git a/fetch/fetch_test.go b/fetch/fetch_test.go index 84cf24125b..a781c54757 100644 --- a/fetch/fetch_test.go +++ b/fetch/fetch_test.go @@ -153,14 +153,14 @@ func TestFetch_GetHash(t *testing.T) { hint2 := datastore.BallotDB // test hash aggregation - p0, err := f.getHash(context.TODO(), h1, hint, goodReceiver) + p0, err := f.getHash(context.Background(), h1, hint, goodReceiver) require.NoError(t, err) - p1, err := f.getHash(context.TODO(), h1, hint, goodReceiver) + p1, err := f.getHash(context.Background(), h1, hint, goodReceiver) require.NoError(t, err) require.Equal(t, p0.completed, p1.completed) h2 := types.RandomHash() - p2, err := f.getHash(context.TODO(), h2, hint2, goodReceiver) + p2, err := f.getHash(context.Background(), h2, hint2, goodReceiver) require.NoError(t, err) require.NotEqual(t, p1.completed, p2.completed) } @@ -227,10 +227,10 @@ func TestFetch_RequestHashBatchFromPeers(t *testing.T) { receiver = badReceiver } for i := 0; i < 2; i++ { - p, err := f.getHash(context.TODO(), hsh0, datastore.ProposalDB, receiver) + p, err := f.getHash(context.Background(), hsh0, datastore.ProposalDB, receiver) require.NoError(t, err) p0 = append(p0, p) - p, err = f.getHash(context.TODO(), hsh1, datastore.BlockDB, receiver) + p, err = f.getHash(context.Background(), hsh1, datastore.BlockDB, receiver) require.NoError(t, err) p1 = append(p1, p) } @@ -296,11 +296,11 @@ func TestFetch_Loop_BatchRequestMax(t *testing.T) { defer f.Stop() require.NoError(t, f.Start()) - p1, err := f.getHash(context.TODO(), h1, hint, goodReceiver) + p1, err := f.getHash(context.Background(), h1, hint, goodReceiver) require.NoError(t, err) - p2, err := f.getHash(context.TODO(), h2, hint, goodReceiver) + p2, err := f.getHash(context.Background(), h2, hint, goodReceiver) require.NoError(t, err) - p3, err := f.getHash(context.TODO(), h3, hint, goodReceiver) + p3, err := f.getHash(context.Background(), h3, hint, goodReceiver) require.NoError(t, err) for _, p := range []*promise{p1, p2, p3} { <-p.completed diff --git a/fetch/handler_test.go b/fetch/handler_test.go index b3176268a0..ae1eb64e5f 100644 --- a/fetch/handler_test.go +++ b/fetch/handler_test.go @@ -431,7 +431,7 @@ func TestHandleMaliciousIDsReq(t *testing.T) { require.NoError(t, identities.SetMalicious(th.cdb, nid, types.RandomBytes(11), time.Now())) } - out, err := th.handleMaliciousIDsReq(context.TODO(), p2p.Peer(""), []byte{}) + out, err := th.handleMaliciousIDsReq(context.Background(), p2p.Peer(""), []byte{}) require.NoError(t, err) var got MaliciousIDs require.NoError(t, codec.Decode(out, &got)) diff --git a/fetch/wire_types.go b/fetch/wire_types.go index 14c8eeafe3..bd28b52f13 100644 --- a/fetch/wire_types.go +++ b/fetch/wire_types.go @@ -29,7 +29,7 @@ func init() { // RequestMessage is sent to the peer for hash query. type RequestMessage struct { - Hint datastore.Hint `scale:"max=256"` // TODO(mafa): covert to an enum + Hint datastore.Hint `scale:"max=256"` Hash types.Hash32 } diff --git a/mesh/mesh_test.go b/mesh/mesh_test.go index 78ef3e9768..f76c1a77df 100644 --- a/mesh/mesh_test.go +++ b/mesh/mesh_test.go @@ -762,7 +762,7 @@ func TestProcessLayer(t *testing.T) { tm.mockTortoise.EXPECT().Updates().Return(c.updates) ensuresDatabaseConsistent(t, tm.cdb, c.updates) - err := tm.ProcessLayer(context.TODO(), lid) + err := tm.ProcessLayer(context.Background(), lid) if len(c.err) > 0 { require.ErrorContains(t, err, c.err) } else { @@ -962,7 +962,7 @@ func TestProcessLayerPerHareOutput(t *testing.T) { if c.onHare { tm.mockTortoise.EXPECT().OnHareOutput(c.lid, c.bid) } - err := tm.ProcessLayerPerHareOutput(context.TODO(), c.lid, c.bid, false) + err := tm.ProcessLayerPerHareOutput(context.Background(), c.lid, c.bid, false) if len(c.err) > 0 { require.ErrorContains(t, err, c.err) } diff --git a/miner/proposal_builder.go b/miner/proposal_builder.go index 1bd96bc192..d17d6ba04f 100644 --- a/miner/proposal_builder.go +++ b/miner/proposal_builder.go @@ -483,8 +483,6 @@ func (pb *ProposalBuilder) initSharedData(ctx context.Context, current types.Lay // // Additionally all activesets that are older than 2 epochs are deleted at the beginning of an epoch anyway, but // maybe we should revisit this when activesets are no longer bootstrapped. - // - // TODO(mafa): I'm still seeing SQL_BUSY errors in the logs, so for now I change this back to TxImmediate. return pb.db.WithTxImmediate(ctx, func(tx sql.Transaction) error { yes, err := activesets.Has(tx, pb.shared.active.id) if err != nil { diff --git a/p2p/server/server.go b/p2p/server/server.go index 3a3925735c..ebc424cc24 100644 --- a/p2p/server/server.go +++ b/p2p/server/server.go @@ -138,7 +138,7 @@ func (err *ServerError) Error() string { type Response struct { // keep in line with limit of ResponseMessage.Data in `fetch/wire_types.go` Data []byte `scale:"max=272629760"` // 260 MiB > 8.0 mio ATX * 32 bytes per ID - Error string `scale:"max=1024"` // TODO(mafa): make error code instead of string + Error string `scale:"max=1024"` } // Server for the Handler. diff --git a/timesync/peersync/sync_test.go b/timesync/peersync/sync_test.go index 11ad013e27..6654c3f1c6 100644 --- a/timesync/peersync/sync_test.go +++ b/timesync/peersync/sync_test.go @@ -51,7 +51,7 @@ func TestSyncGetOffset(t *testing.T) { require.NotNil(t, New(h, nil, WithTime(adjustedTime(peerResponse)))) } sync := New(mesh.Hosts()[0], nil, WithTime(tm)) - offset, err := sync.GetOffset(context.TODO(), 0, peers) + offset, err := sync.GetOffset(context.Background(), 0, peers) require.NoError(t, err) require.Equal(t, 5*time.Second, offset) }) @@ -70,7 +70,7 @@ func TestSyncGetOffset(t *testing.T) { } sync := New(mesh.Hosts()[0], nil, WithTime(tm)) - offset, err := sync.GetOffset(context.TODO(), 0, peers) + offset, err := sync.GetOffset(context.Background(), 0, peers) require.ErrorIs(t, err, errTimesyncFailed) require.Empty(t, offset) }) diff --git a/tortoise/model/core.go b/tortoise/model/core.go index d8bbd963d3..cd762fb102 100644 --- a/tortoise/model/core.go +++ b/tortoise/model/core.go @@ -100,7 +100,7 @@ func (c *core) OnMessage(m Messenger, event Message) { }) c.eligibilities = max(uint32(c.weight*layerSize/total), 1) } - votes, err := c.tortoise.EncodeVotes(context.TODO()) + votes, err := c.tortoise.EncodeVotes(context.Background()) if err != nil { panic(err) } From 619facab393317eb4d3679e0ccfa534c85f36d40 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:07:25 +0000 Subject: [PATCH 34/66] keep metrics in malfeasance package --- activation/malfeasance.go | 25 ++++---------- hare3/malfeasance.go | 9 ++--- malfeasance/handler.go | 6 ++-- malfeasance/handler_test.go | 10 +++--- malfeasance/interface.go | 10 +++--- malfeasance/mocks.go | 65 +++++++++---------------------------- mesh/malfeasance.go | 9 ++--- node/node.go | 15 +++++---- 8 files changed, 48 insertions(+), 101 deletions(-) diff --git a/activation/malfeasance.go b/activation/malfeasance.go index 042da18e03..8b249b436b 100644 --- a/activation/malfeasance.go +++ b/activation/malfeasance.go @@ -6,7 +6,6 @@ import ( "fmt" "strconv" - "github.com/prometheus/client_golang/prometheus" "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" "go.uber.org/zap" @@ -92,12 +91,8 @@ func (mh *MalfeasanceHandler) Validate(ctx context.Context, data wire.ProofData) return types.EmptyNodeID, errors.New("invalid atx malfeasance proof") } -func (mh *MalfeasanceHandler) ReportProof(numProofs *prometheus.CounterVec) { - numProofs.WithLabelValues(multiATXs).Inc() -} - -func (mh *MalfeasanceHandler) ReportInvalidProof(numInvalidProofs *prometheus.CounterVec) { - numInvalidProofs.WithLabelValues(multiATXs).Inc() +func (mh *MalfeasanceHandler) ReportLabel() string { + return multiATXs } type InvalidPostIndexHandler struct { @@ -169,12 +164,8 @@ func (mh *InvalidPostIndexHandler) Validate(ctx context.Context, data wire.Proof return types.EmptyNodeID, errors.New("invalid post index malfeasance proof - POST is valid") } -func (mh *InvalidPostIndexHandler) ReportProof(numProofs *prometheus.CounterVec) { - numProofs.WithLabelValues(invalidPostIndex).Inc() -} - -func (mh *InvalidPostIndexHandler) ReportInvalidProof(numInvalidProofs *prometheus.CounterVec) { - numInvalidProofs.WithLabelValues(invalidPostIndex).Inc() +func (mh *InvalidPostIndexHandler) ReportLabel() string { + return invalidPostIndex } type InvalidPrevATXHandler struct { @@ -241,10 +232,6 @@ func (mh *InvalidPrevATXHandler) Validate(ctx context.Context, data wire.ProofDa return atx1.SmesherID, nil } -func (mh *InvalidPrevATXHandler) ReportProof(numProofs *prometheus.CounterVec) { - numProofs.WithLabelValues(invalidPrevATX).Inc() -} - -func (mh *InvalidPrevATXHandler) ReportInvalidProof(numInvalidProofs *prometheus.CounterVec) { - numInvalidProofs.WithLabelValues(invalidPrevATX).Inc() +func (mh *InvalidPrevATXHandler) ReportLabel() string { + return invalidPrevATX } diff --git a/hare3/malfeasance.go b/hare3/malfeasance.go index e7128ea3bd..30193b2f1e 100644 --- a/hare3/malfeasance.go +++ b/hare3/malfeasance.go @@ -6,7 +6,6 @@ import ( "fmt" "strconv" - "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "github.com/spacemeshos/go-spacemesh/common/types" @@ -102,10 +101,6 @@ func (mh *MalfeasanceHandler) Validate(ctx context.Context, data wire.ProofData) return types.EmptyNodeID, errors.New("invalid hare malfeasance proof") } -func (mh *MalfeasanceHandler) ReportProof(numProofs *prometheus.CounterVec) { - numProofs.WithLabelValues(hareEquivocate).Inc() -} - -func (mh *MalfeasanceHandler) ReportInvalidProof(numInvalidProofs *prometheus.CounterVec) { - numInvalidProofs.WithLabelValues(hareEquivocate).Inc() +func (mh *MalfeasanceHandler) ReportLabel() string { + return hareEquivocate } diff --git a/malfeasance/handler.go b/malfeasance/handler.go index cc4014d0c5..be2abbc8cb 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -86,11 +86,13 @@ func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { } func (h *Handler) countProof(mp *wire.MalfeasanceProof) { - h.handlers[MalfeasanceType(mp.Proof.Type)].ReportProof(numProofs) + label := h.handlers[MalfeasanceType(mp.Proof.Type)].ReportLabel() + numProofs.WithLabelValues(label).Inc() } func (h *Handler) countInvalidProof(p *wire.MalfeasanceProof) { - h.handlers[MalfeasanceType(p.Proof.Type)].ReportInvalidProof(numInvalidProofs) + label := h.handlers[MalfeasanceType(p.Proof.Type)].ReportLabel() + numInvalidProofs.WithLabelValues(label).Inc() } func (h *Handler) Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) { diff --git a/malfeasance/handler_test.go b/malfeasance/handler_test.go index 9b9092ff22..558e213eb4 100644 --- a/malfeasance/handler_test.go +++ b/malfeasance/handler_test.go @@ -101,7 +101,7 @@ func TestHandler_HandleMalfeasanceProof(t *testing.T) { return types.EmptyNodeID, errors.New("invalid proof") }, ) - handler.EXPECT().ReportInvalidProof(gomock.Any()) + handler.EXPECT().ReportLabel().Return("multiATXs") h.RegisterHandler(MultipleATXs, handler) gossip := &wire.MalfeasanceGossip{ @@ -131,7 +131,7 @@ func TestHandler_HandleMalfeasanceProof(t *testing.T) { return nodeID, nil }, ) - handler.EXPECT().ReportProof(gomock.Any()) + handler.EXPECT().ReportLabel().Return("multiATXs") h.RegisterHandler(MultipleATXs, handler) gossip := &wire.MalfeasanceGossip{ @@ -242,7 +242,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { return nodeID, nil }, ) - handler.EXPECT().ReportProof(gomock.Any()) + handler.EXPECT().ReportLabel().Return("multiATXs") h.RegisterHandler(MultipleATXs, handler) proof := &wire.MalfeasanceProof{ @@ -276,7 +276,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { return types.EmptyNodeID, errors.New("invalid proof") }, ) - handler.EXPECT().ReportInvalidProof(gomock.Any()) + handler.EXPECT().ReportLabel().Return("multiATXs") h.RegisterHandler(MultipleATXs, handler) proof := &wire.MalfeasanceProof{ @@ -309,7 +309,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { return nodeID, nil }, ) - handler.EXPECT().ReportProof(gomock.Any()) + handler.EXPECT().ReportLabel().Return("multiATXs") h.RegisterHandler(MultipleATXs, handler) proof := &wire.MalfeasanceProof{ diff --git a/malfeasance/interface.go b/malfeasance/interface.go index 79883b45a8..5c01e96545 100644 --- a/malfeasance/interface.go +++ b/malfeasance/interface.go @@ -3,8 +3,6 @@ package malfeasance import ( "context" - "github.com/prometheus/client_golang/prometheus" - "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/malfeasance/wire" ) @@ -20,8 +18,12 @@ type syncer interface { } type MalfeasanceHandler interface { + // Validate the proof and return the node ID of the malicious node if the proof is valid Validate(ctx context.Context, data wire.ProofData) (types.NodeID, error) + + // Info returns a map of key-value pairs that serve as metadata for the proof Info(data wire.ProofData) (map[string]string, error) - ReportProof(vec *prometheus.CounterVec) - ReportInvalidProof(vec *prometheus.CounterVec) + + // ReportLabel returns the label for the prometheus counter of the given proof type + ReportLabel() string } diff --git a/malfeasance/mocks.go b/malfeasance/mocks.go index 18dc4e9ec5..8b001fbf7e 100644 --- a/malfeasance/mocks.go +++ b/malfeasance/mocks.go @@ -13,7 +13,6 @@ import ( context "context" reflect "reflect" - prometheus "github.com/prometheus/client_golang/prometheus" types "github.com/spacemeshos/go-spacemesh/common/types" wire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" gomock "go.uber.org/mock/gomock" @@ -204,74 +203,40 @@ func (c *MockMalfeasanceHandlerInfoCall) DoAndReturn(f func(wire.ProofData) (map return c } -// ReportInvalidProof mocks base method. -func (m *MockMalfeasanceHandler) ReportInvalidProof(vec *prometheus.CounterVec) { +// ReportLabel mocks base method. +func (m *MockMalfeasanceHandler) ReportLabel() string { m.ctrl.T.Helper() - m.ctrl.Call(m, "ReportInvalidProof", vec) -} - -// ReportInvalidProof indicates an expected call of ReportInvalidProof. -func (mr *MockMalfeasanceHandlerMockRecorder) ReportInvalidProof(vec any) *MockMalfeasanceHandlerReportInvalidProofCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportInvalidProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportInvalidProof), vec) - return &MockMalfeasanceHandlerReportInvalidProofCall{Call: call} -} - -// MockMalfeasanceHandlerReportInvalidProofCall wrap *gomock.Call -type MockMalfeasanceHandlerReportInvalidProofCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockMalfeasanceHandlerReportInvalidProofCall) Return() *MockMalfeasanceHandlerReportInvalidProofCall { - c.Call = c.Call.Return() - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockMalfeasanceHandlerReportInvalidProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockMalfeasanceHandlerReportInvalidProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - -// ReportProof mocks base method. -func (m *MockMalfeasanceHandler) ReportProof(vec *prometheus.CounterVec) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "ReportProof", vec) + ret := m.ctrl.Call(m, "ReportLabel") + ret0, _ := ret[0].(string) + return ret0 } -// ReportProof indicates an expected call of ReportProof. -func (mr *MockMalfeasanceHandlerMockRecorder) ReportProof(vec any) *MockMalfeasanceHandlerReportProofCall { +// ReportLabel indicates an expected call of ReportLabel. +func (mr *MockMalfeasanceHandlerMockRecorder) ReportLabel() *MockMalfeasanceHandlerReportLabelCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportProof), vec) - return &MockMalfeasanceHandlerReportProofCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportLabel", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportLabel)) + return &MockMalfeasanceHandlerReportLabelCall{Call: call} } -// MockMalfeasanceHandlerReportProofCall wrap *gomock.Call -type MockMalfeasanceHandlerReportProofCall struct { +// MockMalfeasanceHandlerReportLabelCall wrap *gomock.Call +type MockMalfeasanceHandlerReportLabelCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockMalfeasanceHandlerReportProofCall) Return() *MockMalfeasanceHandlerReportProofCall { - c.Call = c.Call.Return() +func (c *MockMalfeasanceHandlerReportLabelCall) Return(arg0 string) *MockMalfeasanceHandlerReportLabelCall { + c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockMalfeasanceHandlerReportProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { +func (c *MockMalfeasanceHandlerReportLabelCall) Do(f func() string) *MockMalfeasanceHandlerReportLabelCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockMalfeasanceHandlerReportProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { +func (c *MockMalfeasanceHandlerReportLabelCall) DoAndReturn(f func() string) *MockMalfeasanceHandlerReportLabelCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/mesh/malfeasance.go b/mesh/malfeasance.go index fc074aa221..6b9b08c76b 100644 --- a/mesh/malfeasance.go +++ b/mesh/malfeasance.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "github.com/spacemeshos/go-spacemesh/common/types" @@ -97,10 +96,6 @@ func (mh *MalfeasanceHandler) Validate(ctx context.Context, data wire.ProofData) return types.EmptyNodeID, errors.New("invalid ballot malfeasance proof") } -func (mh *MalfeasanceHandler) ReportProof(numProofs *prometheus.CounterVec) { - numProofs.WithLabelValues(multiBallots).Inc() -} - -func (mh *MalfeasanceHandler) ReportInvalidProof(numInvalidProofs *prometheus.CounterVec) { - numInvalidProofs.WithLabelValues(multiBallots).Inc() +func (mh *MalfeasanceHandler) ReportLabel() string { + return multiBallots } diff --git a/node/node.go b/node/node.go index 881f0e5804..4d43625f60 100644 --- a/node/node.go +++ b/node/node.go @@ -1170,18 +1170,18 @@ func (app *App) initServices(ctx context.Context) error { for _, s := range app.signers { nodeIDs = append(nodeIDs, s.NodeID()) } - app.malfeasanceHandler = malfeasance.NewHandler( + malHandler := malfeasance.NewHandler( app.cachedDB, legacyMalLogger, app.host.ID(), nodeIDs, trtl, ) - app.malfeasanceHandler.RegisterHandler(malfeasance.MultipleATXs, activationMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.MultipleBallots, meshMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.HareEquivocation, hareMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.InvalidPostIndex, invalidPostMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.InvalidPrevATX, invalidPrevMH) + malHandler.RegisterHandler(malfeasance.MultipleATXs, activationMH) + malHandler.RegisterHandler(malfeasance.MultipleBallots, meshMH) + malHandler.RegisterHandler(malfeasance.HareEquivocation, hareMH) + malHandler.RegisterHandler(malfeasance.InvalidPostIndex, invalidPostMH) + malHandler.RegisterHandler(malfeasance.InvalidPrevATX, invalidPrevMH) fetcher.SetValidators( fetch.ValidatorFunc( @@ -1226,7 +1226,7 @@ func (app *App) initServices(ctx context.Context) error { ), fetch.ValidatorFunc( pubsub.DropPeerOnSyncValidationReject( - app.malfeasanceHandler.HandleSyncedMalfeasanceProof, + malHandler.HandleSyncedMalfeasanceProof, app.host, lg.Zap(), ), @@ -1299,6 +1299,7 @@ func (app *App) initServices(ctx context.Context) error { app.syncer = syncer app.atxBuilder = atxBuilder app.atxHandler = atxHandler + app.malfeasanceHandler = malHandler app.poetDb = poetDb app.fetcher = fetcher app.beaconProtocol = beaconProtocol From c43c756ebac9353ea08f33839dd24985c4c1a4d0 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:08:00 +0000 Subject: [PATCH 35/66] Keep metrics in malfeasance package --- malfeasance2/handler.go | 9 +++-- malfeasance2/handler_test.go | 45 ++++++++++++++++++++++--- malfeasance2/interface.go | 10 +++--- malfeasance2/mocks.go | 65 +++++++++--------------------------- 4 files changed, 67 insertions(+), 62 deletions(-) diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go index a4dbbfcb79..1cb46e289f 100644 --- a/malfeasance2/handler.go +++ b/malfeasance2/handler.go @@ -2,6 +2,7 @@ package malfeasance2 import ( "context" + "errors" "fmt" "slices" "strconv" @@ -65,11 +66,13 @@ func (h *Handler) RegisterHandler(malfeasanceType ProofDomain, handler Malfeasan } func (h *Handler) countProof(mp MalfeasanceProof) { - h.handlers[mp.Domain].ReportProof(numProofs) + label := h.handlers[mp.Domain].ReportLabel() + numProofs.WithLabelValues(label).Inc() } func (h *Handler) countInvalidProof(mp MalfeasanceProof) { - h.handlers[mp.Domain].ReportInvalidProof(numInvalidProofs) + label := h.handlers[mp.Domain].ReportLabel() + numInvalidProofs.WithLabelValues(label).Inc() } func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { @@ -106,7 +109,7 @@ func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, peer p nodeIDs, err := h.handleProof(ctx, proof) if err != nil { - return err + return errors.Join(err, pubsub.ErrValidationReject) } if !slices.Contains(nodeIDs, types.NodeID(expHash)) { // we log & return because libp2p will ignore the message if we return an error, diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index 5cd9b6e098..bfae0deeb2 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "testing" + "time" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -16,8 +17,10 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/malfeasance2" "github.com/spacemeshos/go-spacemesh/p2p" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" "github.com/spacemeshos/go-spacemesh/sql/statesql" ) @@ -74,6 +77,7 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", []byte("malformed")) require.ErrorIs(t, err, malfeasance2.ErrMalformedData) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("unknown version", func(t *testing.T) { @@ -85,6 +89,7 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, malfeasance2.ErrUnknownVersion) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("unknown domain", func(t *testing.T) { @@ -97,6 +102,7 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, malfeasance2.ErrUnknownDomain) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("invalid proof", func(t *testing.T) { @@ -105,7 +111,7 @@ func TestHandler_HandleSync(t *testing.T) { handlerError := errors.New("invalid proof") mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(types.EmptyNodeID, handlerError) - mockHandler.EXPECT().ReportInvalidProof(gomock.Any()) + mockHandler.EXPECT().ReportLabel().Return("invalidPost") h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -116,6 +122,7 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, handlerError) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("valid proof", func(t *testing.T) { @@ -124,7 +131,7 @@ func TestHandler_HandleSync(t *testing.T) { nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) - mockHandler.EXPECT().ReportProof(gomock.Any()) + mockHandler.EXPECT().ReportLabel().Return("invalidPost") h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) h.mockTrt.EXPECT().OnMalfeasance(nodeID) @@ -136,6 +143,10 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.Hash32(nodeID), "peer", codec.MustEncode(proof)) require.NoError(t, err) + + malicious, err := malfeasance.IsMalicious(h.db, nodeID) + require.NoError(t, err) + require.True(t, malicious) }) t.Run("valid proof, wrong hash", func(t *testing.T) { @@ -144,7 +155,7 @@ func TestHandler_HandleSync(t *testing.T) { nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) - mockHandler.EXPECT().ReportInvalidProof(gomock.Any()) + mockHandler.EXPECT().ReportLabel().Return("invalidPost") h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -203,7 +214,7 @@ func TestHandler_HandleGossip(t *testing.T) { handlerError := errors.New("invalid proof") mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(types.EmptyNodeID, handlerError) - mockHandler.EXPECT().ReportInvalidProof(gomock.Any()) + mockHandler.EXPECT().ReportLabel().Return("invalidPost") h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -222,7 +233,7 @@ func TestHandler_HandleGossip(t *testing.T) { nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) - mockHandler.EXPECT().ReportProof(gomock.Any()) + mockHandler.EXPECT().ReportLabel().Return("invalidPost") h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) h.mockTrt.EXPECT().OnMalfeasance(nodeID) @@ -235,4 +246,28 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) require.NoError(t, err) }) + + t.Run("valid proof for known malicious identity", func(t *testing.T) { + h := newTestHandler(t) + validProof := []byte("valid") + nodeID := types.RandomNodeID() + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) + mockHandler.EXPECT().ReportLabel().Return("invalidPost") + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + h.mockTrt.EXPECT().OnMalfeasance(nodeID) + + proof := &malfeasance2.MalfeasanceProof{ + Version: 0, + Domain: malfeasance2.InvalidActivation, + Proof: validProof, + } + proofBytes := codec.MustEncode(proof) + + err := malfeasance.AddProof(h.db, nodeID, nil, proofBytes, byte(malfeasance2.InvalidActivation), time.Now()) + require.NoError(t, err) + + err = h.HandleGossip(context.Background(), "peer", proofBytes) + require.NoError(t, err) + }) } diff --git a/malfeasance2/interface.go b/malfeasance2/interface.go index 6cf57b81c4..9e7b909e6a 100644 --- a/malfeasance2/interface.go +++ b/malfeasance2/interface.go @@ -3,8 +3,6 @@ package malfeasance2 import ( "context" - "github.com/prometheus/client_golang/prometheus" - "github.com/spacemeshos/go-spacemesh/common/types" ) @@ -19,8 +17,12 @@ type tortoise interface { } type MalfeasanceHandler interface { + // Validate the proof and return the node ID of the malicious node if the proof is valid Validate(ctx context.Context, data []byte) (types.NodeID, error) + + // Info returns a map of key-value pairs that serve as metadata for the proof Info(data []byte) (map[string]string, error) - ReportProof(vec *prometheus.CounterVec) // TODO(mafa): don't pass vectors along, use one defined in package - ReportInvalidProof(vec *prometheus.CounterVec) // TODO(mafa): don't pass vectors along, use one defined in package + + // ReportLabel returns the label for the prometheus counter of the given proof type + ReportLabel() string } diff --git a/malfeasance2/mocks.go b/malfeasance2/mocks.go index 51a1784ccc..728210e00c 100644 --- a/malfeasance2/mocks.go +++ b/malfeasance2/mocks.go @@ -13,7 +13,6 @@ import ( context "context" reflect "reflect" - prometheus "github.com/prometheus/client_golang/prometheus" types "github.com/spacemeshos/go-spacemesh/common/types" gomock "go.uber.org/mock/gomock" ) @@ -203,74 +202,40 @@ func (c *MockMalfeasanceHandlerInfoCall) DoAndReturn(f func([]byte) (map[string] return c } -// ReportInvalidProof mocks base method. -func (m *MockMalfeasanceHandler) ReportInvalidProof(vec *prometheus.CounterVec) { +// ReportLabel mocks base method. +func (m *MockMalfeasanceHandler) ReportLabel() string { m.ctrl.T.Helper() - m.ctrl.Call(m, "ReportInvalidProof", vec) -} - -// ReportInvalidProof indicates an expected call of ReportInvalidProof. -func (mr *MockMalfeasanceHandlerMockRecorder) ReportInvalidProof(vec any) *MockMalfeasanceHandlerReportInvalidProofCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportInvalidProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportInvalidProof), vec) - return &MockMalfeasanceHandlerReportInvalidProofCall{Call: call} -} - -// MockMalfeasanceHandlerReportInvalidProofCall wrap *gomock.Call -type MockMalfeasanceHandlerReportInvalidProofCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockMalfeasanceHandlerReportInvalidProofCall) Return() *MockMalfeasanceHandlerReportInvalidProofCall { - c.Call = c.Call.Return() - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockMalfeasanceHandlerReportInvalidProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockMalfeasanceHandlerReportInvalidProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - -// ReportProof mocks base method. -func (m *MockMalfeasanceHandler) ReportProof(vec *prometheus.CounterVec) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "ReportProof", vec) + ret := m.ctrl.Call(m, "ReportLabel") + ret0, _ := ret[0].(string) + return ret0 } -// ReportProof indicates an expected call of ReportProof. -func (mr *MockMalfeasanceHandlerMockRecorder) ReportProof(vec any) *MockMalfeasanceHandlerReportProofCall { +// ReportLabel indicates an expected call of ReportLabel. +func (mr *MockMalfeasanceHandlerMockRecorder) ReportLabel() *MockMalfeasanceHandlerReportLabelCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportProof), vec) - return &MockMalfeasanceHandlerReportProofCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportLabel", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportLabel)) + return &MockMalfeasanceHandlerReportLabelCall{Call: call} } -// MockMalfeasanceHandlerReportProofCall wrap *gomock.Call -type MockMalfeasanceHandlerReportProofCall struct { +// MockMalfeasanceHandlerReportLabelCall wrap *gomock.Call +type MockMalfeasanceHandlerReportLabelCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockMalfeasanceHandlerReportProofCall) Return() *MockMalfeasanceHandlerReportProofCall { - c.Call = c.Call.Return() +func (c *MockMalfeasanceHandlerReportLabelCall) Return(arg0 string) *MockMalfeasanceHandlerReportLabelCall { + c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockMalfeasanceHandlerReportProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { +func (c *MockMalfeasanceHandlerReportLabelCall) Do(f func() string) *MockMalfeasanceHandlerReportLabelCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockMalfeasanceHandlerReportProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { +func (c *MockMalfeasanceHandlerReportLabelCall) DoAndReturn(f func() string) *MockMalfeasanceHandlerReportLabelCall { c.Call = c.Call.DoAndReturn(f) return c } From 0175079d38176d779cabbd144c11c3e11c2a70ca Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:51:00 +0000 Subject: [PATCH 36/66] Add metrics for malfeasance2 update tests --- activation/malfeasance2.go | 25 +++++++++++++++-- activation/wire/malfeasance_double_marry.go | 2 +- activation/wire/malfeasance_double_merge.go | 2 +- activation/wire/malfeasance_invalid_post.go | 2 +- .../wire/malfeasance_invalid_prev_atx.go | 4 +-- malfeasance/handler_test.go | 23 +++++++++++----- malfeasance2/handler.go | 10 +++---- malfeasance2/handler_test.go | 27 ++++++++++++++----- malfeasance2/interface.go | 2 +- malfeasance2/metrics.go | 7 +++-- malfeasance2/mocks.go | 26 +++++++++--------- node/node.go | 18 +++++++++++++ 12 files changed, 107 insertions(+), 41 deletions(-) diff --git a/activation/malfeasance2.go b/activation/malfeasance2.go index 5aae4602c9..ca7136100d 100644 --- a/activation/malfeasance2.go +++ b/activation/malfeasance2.go @@ -83,13 +83,21 @@ func (p *MalfeasanceHandlerV2) Publish(ctx context.Context, nodeID types.NodeID, return p.malPublisher.PublishATXProof(ctx, nodeID, codec.MustEncode(atxProof)) } -func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) (types.NodeID, error) { +func (mh *MalfeasanceHandlerV2) decodeProof(data []byte) (wire.Proof, error) { var atxProof wire.ATXProof if err := codec.Decode(data, &atxProof); err != nil { - return types.EmptyNodeID, fmt.Errorf("decoding ATX malfeasance proof: %w", err) + return nil, err } proof, err := atxProof.Decode() + if err != nil { + return nil, err + } + return proof, nil +} + +func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) (types.NodeID, error) { + proof, err := mh.decodeProof(data) if err != nil { return types.EmptyNodeID, fmt.Errorf("decoding ATX malfeasance proof: %w", err) } @@ -101,6 +109,19 @@ func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) (type return id, nil } +func (mh *MalfeasanceHandlerV2) Info(data []byte) (map[string]string, error) { + // TODO(mafa): implement me + return nil, nil +} + +func (mh *MalfeasanceHandlerV2) ReportLabels(data []byte) []string { + proof, err := mh.decodeProof(data) + if err != nil { + return nil + } + return []string{"ATX", proof.String()} +} + func (mh *MalfeasanceHandlerV2) PostIndex( ctx context.Context, smesherID types.NodeID, diff --git a/activation/wire/malfeasance_double_marry.go b/activation/wire/malfeasance_double_marry.go index 8778517501..23feb9e2d7 100644 --- a/activation/wire/malfeasance_double_marry.go +++ b/activation/wire/malfeasance_double_marry.go @@ -43,7 +43,7 @@ type ProofDoubleMarry struct { } func (p ProofDoubleMarry) String() string { - return "Double Marry Proof" + return "DoubleMarryProof" } func (p ProofDoubleMarry) Type() ProofType { diff --git a/activation/wire/malfeasance_double_merge.go b/activation/wire/malfeasance_double_merge.go index 689566b889..180c60633a 100644 --- a/activation/wire/malfeasance_double_merge.go +++ b/activation/wire/malfeasance_double_merge.go @@ -60,7 +60,7 @@ type ProofDoubleMerge struct { } func (p ProofDoubleMerge) String() string { - return "Double Merge Proof" + return "DoubleMergeProof" } func (p ProofDoubleMerge) Type() ProofType { diff --git a/activation/wire/malfeasance_invalid_post.go b/activation/wire/malfeasance_invalid_post.go index 15599f573d..d1542a35b9 100644 --- a/activation/wire/malfeasance_invalid_post.go +++ b/activation/wire/malfeasance_invalid_post.go @@ -39,7 +39,7 @@ type ProofInvalidPost struct { } func (p ProofInvalidPost) String() string { - return "Invalid PoST Proof" + return "InvalidPoSTProof" } func (p ProofInvalidPost) Type() ProofType { diff --git a/activation/wire/malfeasance_invalid_prev_atx.go b/activation/wire/malfeasance_invalid_prev_atx.go index ba06b65dc0..53f25fdd10 100644 --- a/activation/wire/malfeasance_invalid_prev_atx.go +++ b/activation/wire/malfeasance_invalid_prev_atx.go @@ -33,7 +33,7 @@ type ProofInvalidPrevAtxV2 struct { } func (p ProofInvalidPrevAtxV2) String() string { - return "Invalid Previous ATX Proof V2" + return "InvalidPreviousATXProofV2" } func (p ProofInvalidPrevAtxV2) Type() ProofType { @@ -199,7 +199,7 @@ type ProofInvalidPrevAtxV1 struct { } func (p ProofInvalidPrevAtxV1) String() string { - return "Invalid Previous ATX Proof V1" + return "InvalidPreviousATXProofV1" } func (p ProofInvalidPrevAtxV1) Type() ProofType { diff --git a/malfeasance/handler_test.go b/malfeasance/handler_test.go index 558e213eb4..42d0fcc2da 100644 --- a/malfeasance/handler_test.go +++ b/malfeasance/handler_test.go @@ -20,6 +20,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/malfeasance/wire" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/identities" @@ -29,13 +30,14 @@ import ( type testMalfeasanceHandler struct { *Handler - db sql.StateDatabase - mockTrt *Mocktortoise + observedLogs *observer.ObservedLogs + db sql.StateDatabase + mockTrt *Mocktortoise } func newHandler(tb testing.TB) *testMalfeasanceHandler { db := statesql.InMemoryTest(tb) - observer, _ := observer.New(zapcore.WarnLevel) + observer, observedLogs := observer.New(zapcore.WarnLevel) logger := zaptest.NewLogger(tb, zaptest.WrapOptions(zap.WrapCore( func(core zapcore.Core) zapcore.Core { return zapcore.NewTee(core, observer) @@ -58,8 +60,9 @@ func newHandler(tb testing.TB) *testMalfeasanceHandler { return &testMalfeasanceHandler{ Handler: h, - db: db, - mockTrt: trt, + observedLogs: observedLogs, + db: db, + mockTrt: trt, } } @@ -253,15 +256,23 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { }, } + expectedHash := types.RandomHash() h.mockTrt.EXPECT().OnMalfeasance(nodeID) err := h.HandleSyncedMalfeasanceProof( context.Background(), - types.RandomHash(), + expectedHash, "peer", codec.MustEncode(proof), ) require.ErrorIs(t, err, errWrongHash) require.ErrorIs(t, err, pubsub.ErrValidationReject) + + require.Equal(t, 1, h.observedLogs.Len()) + log := h.observedLogs.All()[0] + require.Equal(t, zap.WarnLevel, log.Level) + require.Contains(t, log.Message, "malfeasance proof for wrong identity") + require.Equal(t, expectedHash.ShortString(), log.ContextMap()["expected"]) + require.Equal(t, p2p.Peer("peer").String(), log.ContextMap()["peer"]) }) t.Run("invalid proof", func(t *testing.T) { diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go index 1cb46e289f..3b6bff489b 100644 --- a/malfeasance2/handler.go +++ b/malfeasance2/handler.go @@ -66,13 +66,13 @@ func (h *Handler) RegisterHandler(malfeasanceType ProofDomain, handler Malfeasan } func (h *Handler) countProof(mp MalfeasanceProof) { - label := h.handlers[mp.Domain].ReportLabel() - numProofs.WithLabelValues(label).Inc() + labels := h.handlers[mp.Domain].ReportLabels(mp.Proof) + numProofs.WithLabelValues(labels...).Inc() } func (h *Handler) countInvalidProof(mp MalfeasanceProof) { - label := h.handlers[mp.Domain].ReportLabel() - numInvalidProofs.WithLabelValues(label).Inc() + labels := h.handlers[mp.Domain].ReportLabels(mp.Proof) + numInvalidProofs.WithLabelValues(labels...).Inc() } func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { @@ -168,7 +168,7 @@ func (h *Handler) HandleGossip(ctx context.Context, peer p2p.Peer, msg []byte) e nodeIDs, err := h.handleProof(ctx, proof) if err != nil { - return err + return errors.Join(err, pubsub.ErrValidationReject) } if err := h.storeProof(ctx, proof.Domain, msg); err != nil { diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index bfae0deeb2..ef713fd5f1 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -111,7 +111,7 @@ func TestHandler_HandleSync(t *testing.T) { handlerError := errors.New("invalid proof") mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(types.EmptyNodeID, handlerError) - mockHandler.EXPECT().ReportLabel().Return("invalidPost") + mockHandler.EXPECT().ReportLabels(invalidProof).Return([]string{"ATX", "invalidPost"}) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -131,7 +131,7 @@ func TestHandler_HandleSync(t *testing.T) { nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) - mockHandler.EXPECT().ReportLabel().Return("invalidPost") + mockHandler.EXPECT().ReportLabels(validProof).Return([]string{"ATX", "invalidPost"}) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) h.mockTrt.EXPECT().OnMalfeasance(nodeID) @@ -155,7 +155,7 @@ func TestHandler_HandleSync(t *testing.T) { nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) - mockHandler.EXPECT().ReportLabel().Return("invalidPost") + mockHandler.EXPECT().ReportLabels(validProof).Return([]string{"ATX", "invalidPost"}) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -164,8 +164,17 @@ func TestHandler_HandleSync(t *testing.T) { Proof: validProof, } - err := h.HandleSynced(context.Background(), types.Hash32(types.RandomNodeID()), "peer", codec.MustEncode(proof)) + expectedHash := types.RandomHash() + err := h.HandleSynced(context.Background(), expectedHash, "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, malfeasance2.ErrWrongHash) + require.ErrorIs(t, err, pubsub.ErrValidationReject) + + require.Equal(t, 1, h.observedLogs.Len()) + log := h.observedLogs.All()[0] + require.Equal(t, zap.WarnLevel, log.Level) + require.Contains(t, log.Message, "malfeasance proof for wrong identity") + require.Equal(t, expectedHash.ShortString(), log.ContextMap()["expected"]) + require.Equal(t, p2p.Peer("peer").String(), log.ContextMap()["peer"]) }) } @@ -175,6 +184,7 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", []byte("malformed")) require.ErrorIs(t, err, malfeasance2.ErrMalformedData) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("self peer", func(t *testing.T) { @@ -194,6 +204,7 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, malfeasance2.ErrUnknownVersion) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("unknown domain", func(t *testing.T) { @@ -206,6 +217,7 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, malfeasance2.ErrUnknownDomain) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("invalid proof", func(t *testing.T) { @@ -214,7 +226,7 @@ func TestHandler_HandleGossip(t *testing.T) { handlerError := errors.New("invalid proof") mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), invalidProof).Return(types.EmptyNodeID, handlerError) - mockHandler.EXPECT().ReportLabel().Return("invalidPost") + mockHandler.EXPECT().ReportLabels(invalidProof).Return([]string{"ATX", "invalidPost"}) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) proof := &malfeasance2.MalfeasanceProof{ @@ -225,6 +237,7 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, handlerError) + require.ErrorIs(t, err, pubsub.ErrValidationReject) }) t.Run("valid proof", func(t *testing.T) { @@ -233,7 +246,7 @@ func TestHandler_HandleGossip(t *testing.T) { nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) - mockHandler.EXPECT().ReportLabel().Return("invalidPost") + mockHandler.EXPECT().ReportLabels(validProof).Return([]string{"ATX", "invalidPost"}) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) h.mockTrt.EXPECT().OnMalfeasance(nodeID) @@ -253,7 +266,7 @@ func TestHandler_HandleGossip(t *testing.T) { nodeID := types.RandomNodeID() mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) mockHandler.EXPECT().Validate(gomock.Any(), validProof).Return(nodeID, nil) - mockHandler.EXPECT().ReportLabel().Return("invalidPost") + mockHandler.EXPECT().ReportLabels(validProof).Return([]string{"ATX", "invalidPost"}) h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) h.mockTrt.EXPECT().OnMalfeasance(nodeID) diff --git a/malfeasance2/interface.go b/malfeasance2/interface.go index 9e7b909e6a..ca436ba27b 100644 --- a/malfeasance2/interface.go +++ b/malfeasance2/interface.go @@ -24,5 +24,5 @@ type MalfeasanceHandler interface { Info(data []byte) (map[string]string, error) // ReportLabel returns the label for the prometheus counter of the given proof type - ReportLabel() string + ReportLabels(data []byte) []string } diff --git a/malfeasance2/metrics.go b/malfeasance2/metrics.go index 66cc8149c0..5d7f6b4ac4 100644 --- a/malfeasance2/metrics.go +++ b/malfeasance2/metrics.go @@ -5,7 +5,8 @@ import "github.com/spacemeshos/go-spacemesh/metrics" const ( namespace = "malfeasance2" - typeLabel = "type" + domainLabel = "domain" + typeLabel = "type" ) var ( @@ -14,6 +15,7 @@ var ( namespace, "number of malfeasance proofs", []string{ + domainLabel, typeLabel, }, ) @@ -23,9 +25,10 @@ var ( namespace, "number of invalid malfeasance proofs", []string{ + domainLabel, typeLabel, }, ) - numMalformed = numInvalidProofs.WithLabelValues("mal") + numMalformed = numInvalidProofs.WithLabelValues("mal", "unknown") ) diff --git a/malfeasance2/mocks.go b/malfeasance2/mocks.go index 728210e00c..703197e020 100644 --- a/malfeasance2/mocks.go +++ b/malfeasance2/mocks.go @@ -202,40 +202,40 @@ func (c *MockMalfeasanceHandlerInfoCall) DoAndReturn(f func([]byte) (map[string] return c } -// ReportLabel mocks base method. -func (m *MockMalfeasanceHandler) ReportLabel() string { +// ReportLabels mocks base method. +func (m *MockMalfeasanceHandler) ReportLabels(data []byte) []string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReportLabel") - ret0, _ := ret[0].(string) + ret := m.ctrl.Call(m, "ReportLabels", data) + ret0, _ := ret[0].([]string) return ret0 } -// ReportLabel indicates an expected call of ReportLabel. -func (mr *MockMalfeasanceHandlerMockRecorder) ReportLabel() *MockMalfeasanceHandlerReportLabelCall { +// ReportLabels indicates an expected call of ReportLabels. +func (mr *MockMalfeasanceHandlerMockRecorder) ReportLabels(data any) *MockMalfeasanceHandlerReportLabelsCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportLabel", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportLabel)) - return &MockMalfeasanceHandlerReportLabelCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportLabels", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportLabels), data) + return &MockMalfeasanceHandlerReportLabelsCall{Call: call} } -// MockMalfeasanceHandlerReportLabelCall wrap *gomock.Call -type MockMalfeasanceHandlerReportLabelCall struct { +// MockMalfeasanceHandlerReportLabelsCall wrap *gomock.Call +type MockMalfeasanceHandlerReportLabelsCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockMalfeasanceHandlerReportLabelCall) Return(arg0 string) *MockMalfeasanceHandlerReportLabelCall { +func (c *MockMalfeasanceHandlerReportLabelsCall) Return(arg0 []string) *MockMalfeasanceHandlerReportLabelsCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockMalfeasanceHandlerReportLabelCall) Do(f func() string) *MockMalfeasanceHandlerReportLabelCall { +func (c *MockMalfeasanceHandlerReportLabelsCall) Do(f func([]byte) []string) *MockMalfeasanceHandlerReportLabelsCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockMalfeasanceHandlerReportLabelCall) DoAndReturn(f func() string) *MockMalfeasanceHandlerReportLabelCall { +func (c *MockMalfeasanceHandlerReportLabelsCall) DoAndReturn(f func([]byte) []string) *MockMalfeasanceHandlerReportLabelsCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/node/node.go b/node/node.go index 4d43625f60..4d1385d6a9 100644 --- a/node/node.go +++ b/node/node.go @@ -1183,6 +1183,16 @@ func (app *App) initServices(ctx context.Context) error { malHandler.RegisterHandler(malfeasance.InvalidPostIndex, invalidPostMH) malHandler.RegisterHandler(malfeasance.InvalidPrevATX, invalidPrevMH) + malHandler2 := malfeasance2.NewHandler( + app.cachedDB, + malfeasanceLogger, + app.host.ID(), + nodeIDs, + app.edVerifier, + trtl, + ) + malHandler2.RegisterHandler(malfeasance2.InvalidActivation, atxMalHandler) + fetcher.SetValidators( fetch.ValidatorFunc( pubsub.DropPeerOnSyncValidationReject(atxHandler.HandleSyncedAtx, app.host, lg.Zap()), @@ -1231,6 +1241,14 @@ func (app *App) initServices(ctx context.Context) error { lg.Zap(), ), ), + // TODO(mafa): add malfeasance2 handler to fetcher + // fetch.ValidatorFunc( + // pubsub.DropPeerOnSyncValidationReject( + // malHandler2.HandleSyncedMalfeasanceProof, + // app.host, + // lg.Zap(), + // ), + // ), ) checkSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { From fe08aa267ae951e2535078498a26c5d3ba19405b Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:59:19 +0000 Subject: [PATCH 37/66] Update TODOs --- node/node.go | 100 +++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/node/node.go b/node/node.go index 4d1385d6a9..838d48cdc0 100644 --- a/node/node.go +++ b/node/node.go @@ -379,50 +379,51 @@ func New(opts ...Option) *App { // App is the cli app singleton. type App struct { *cobra.Command - fileLock *flock.Flock - signers []*signing.EdSigner - Config *config.Config - db sql.StateDatabase - apiDB sql.StateDatabase - cachedDB *datastore.CachedDB - dbMetrics *dbmetrics.DBMetricsCollector - localDB sql.LocalDatabase - grpcPublicServer *grpcserver.Server - grpcPrivateServer *grpcserver.Server - grpcPostServer *grpcserver.Server - grpcTLSServer *grpcserver.Server - jsonAPIServer *grpcserver.JSONHTTPServer - grpcServices map[grpcserver.Service]grpcserver.ServiceAPI - pprofService *http.Server - profilerService *pyroscope.Profiler - syncer *syncer.Syncer - proposalBuilder *miner.ProposalBuilder - mesh *mesh.Mesh - atxsdata *atxsdata.Data - clock *timesync.NodeClock - hare3 *hare3.Hare - hare4 *hare4.Hare - hareResultsChan chan hare4.ConsensusOutput - hOracle *eligibility.Oracle - blockGen *blocks.Generator - certifier *blocks.Certifier - atxBuilder *activation.Builder - atxHandler *activation.Handler - txHandler *txs.TxHandler - validator *activation.Validator - edVerifier *signing.EdVerifier - beaconProtocol *beacon.ProtocolDriver - log log.Log - syncLogger log.Log - conState *txs.ConservativeState - fetcher *fetch.Fetch - ptimesync *peersync.Sync - updater *bootstrap.Updater - poetDb *activation.PoetDb - postVerifier activation.PostVerifier - postSupervisor *activation.PostSupervisor - malfeasanceHandler *malfeasance.Handler - errCh chan error + fileLock *flock.Flock + signers []*signing.EdSigner + Config *config.Config + db sql.StateDatabase + apiDB sql.StateDatabase + cachedDB *datastore.CachedDB + dbMetrics *dbmetrics.DBMetricsCollector + localDB sql.LocalDatabase + grpcPublicServer *grpcserver.Server + grpcPrivateServer *grpcserver.Server + grpcPostServer *grpcserver.Server + grpcTLSServer *grpcserver.Server + jsonAPIServer *grpcserver.JSONHTTPServer + grpcServices map[grpcserver.Service]grpcserver.ServiceAPI + pprofService *http.Server + profilerService *pyroscope.Profiler + syncer *syncer.Syncer + proposalBuilder *miner.ProposalBuilder + mesh *mesh.Mesh + atxsdata *atxsdata.Data + clock *timesync.NodeClock + hare3 *hare3.Hare + hare4 *hare4.Hare + hareResultsChan chan hare4.ConsensusOutput + hOracle *eligibility.Oracle + blockGen *blocks.Generator + certifier *blocks.Certifier + atxBuilder *activation.Builder + atxHandler *activation.Handler + txHandler *txs.TxHandler + validator *activation.Validator + edVerifier *signing.EdVerifier + beaconProtocol *beacon.ProtocolDriver + log log.Log + syncLogger log.Log + conState *txs.ConservativeState + fetcher *fetch.Fetch + ptimesync *peersync.Sync + updater *bootstrap.Updater + poetDb *activation.PoetDb + postVerifier activation.PostVerifier + postSupervisor *activation.PostSupervisor + malfeasanceHandler *malfeasance.Handler + malfeasance2Handler *malfeasance2.Handler + errCh chan error host *p2p.Host @@ -1305,11 +1306,11 @@ func (app *App) initServices(ctx context.Context) error { ) app.host.Register( pubsub.MalfeasanceProof, - pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), + pubsub.ChainGossipHandler(checkAtxSynced, malHandler.HandleMalfeasanceProof), ) app.host.Register( - pubsub.MalfeasanceProof2, // TODO(mafa): pass correct handler - pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), + pubsub.MalfeasanceProof2, + pubsub.ChainGossipHandler(checkAtxSynced, malHandler2.HandleGossip), ) app.proposalBuilder = proposalBuilder @@ -1318,6 +1319,7 @@ func (app *App) initServices(ctx context.Context) error { app.atxBuilder = atxBuilder app.atxHandler = atxHandler app.malfeasanceHandler = malHandler + app.malfeasance2Handler = malHandler2 app.poetDb = poetDb app.fetcher = fetcher app.beaconProtocol = beaconProtocol @@ -1605,10 +1607,14 @@ func (app *App) grpcService(svc grpcserver.Service, lg log.Log) (grpcserver.Serv app.grpcServices[svc] = service return service, nil case v2alpha1.Malfeasance: + // TODO(mafa): update to also use malfeasance2 handler + _ = app.malfeasance2Handler service := v2alpha1.NewMalfeasanceService(app.apiDB, app.malfeasanceHandler) app.grpcServices[svc] = service return service, nil case v2alpha1.MalfeasanceStream: + // TODO(mafa): update to also use malfeasance2 handler + _ = app.malfeasance2Handler service := v2alpha1.NewMalfeasanceStreamService(app.apiDB, app.malfeasanceHandler) app.grpcServices[svc] = service return service, nil From c7309844c9333bff40b986274a5a5538d4977be2 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:31:00 +0000 Subject: [PATCH 38/66] Update label for proofs that can't be decoded --- activation/malfeasance2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activation/malfeasance2.go b/activation/malfeasance2.go index ca7136100d..7d1b9d24df 100644 --- a/activation/malfeasance2.go +++ b/activation/malfeasance2.go @@ -117,7 +117,7 @@ func (mh *MalfeasanceHandlerV2) Info(data []byte) (map[string]string, error) { func (mh *MalfeasanceHandlerV2) ReportLabels(data []byte) []string { proof, err := mh.decodeProof(data) if err != nil { - return nil + return []string{"ATX", "unknown"} } return []string{"ATX", proof.String()} } From f61b416d3794d5ec2ae92d01240501ff9ff36d22 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:16:40 +0000 Subject: [PATCH 39/66] Update dependencies --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 64f92cf725..80bfcfc16e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( cloud.google.com/go/storage v1.48.0 github.com/ALTree/bigfloat v0.2.0 - github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241204110417-3c631bce206f + github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241207233122-cb35037e984f github.com/cosmos/btcutil v1.0.5 github.com/go-llsqlite/crawshaw v0.5.5 github.com/gofrs/flock v0.12.1 @@ -56,10 +56,10 @@ require ( github.com/zeebo/blake3 v0.2.4 go.uber.org/mock v0.5.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d + golang.org/x/exp v0.0.0-20241210194714-1829a127f884 golang.org/x/sync v0.10.0 golang.org/x/time v0.8.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 google.golang.org/grpc v1.68.1 google.golang.org/protobuf v1.35.2 k8s.io/api v0.32.0 diff --git a/go.sum b/go.sum index 63ce7e53ea..884b20c8d0 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241204110417-3c631bce206f h1:N6vSz68L9EguQPpPNcbRRb8JkEuhE3T4OckRxgM49xE= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241204110417-3c631bce206f/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241207233122-cb35037e984f h1:cRhVELxBpei+s7Lo1oZDJSHslfxQK3IxnueKyobuTWs= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241207233122-cb35037e984f/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -766,8 +766,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -939,8 +939,8 @@ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= From 7b28213a2755b2d37a264f0b0bc61649ff4bfe22 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:35:27 +0000 Subject: [PATCH 40/66] Lint tests --- sync2/fptree/nodepool_test.go | 1 - syncer/find_fork_test.go | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sync2/fptree/nodepool_test.go b/sync2/fptree/nodepool_test.go index 32dcb2bbd8..156c5bedb5 100644 --- a/sync2/fptree/nodepool_test.go +++ b/sync2/fptree/nodepool_test.go @@ -42,7 +42,6 @@ func TestNodePool(t *testing.T) { require.Equal(t, uint32(1), np.refCount(idx2)) require.Equal(t, nodeIndex(2), idx3) - require.Nil(t, nil, idx3) require.Equal(t, idx1, np.left(idx3)) require.Equal(t, idx2, np.right(idx3)) require.False(t, np.leaf(idx3)) diff --git a/syncer/find_fork_test.go b/syncer/find_fork_test.go index 7276cbda36..f714b82305 100644 --- a/syncer/find_fork_test.go +++ b/syncer/find_fork_test.go @@ -3,7 +3,6 @@ package syncer_test import ( "context" "encoding/binary" - "fmt" "math/rand/v2" "strconv" "testing" @@ -108,7 +107,7 @@ func serveHashReq(tb testing.TB, req *fetch.MeshHashRequest) (*fetch.MeshHashes, hashes = append(hashes, layerHash(int(req.To.Uint32()), true)) expCount := int(req.Count()) - require.Len(tb, hashes, expCount, fmt.Sprintf("%#v; count exp: %v, got %v", req, expCount, len(hashes))) + require.Lenf(tb, hashes, expCount, "%#v; count exp: %v, got %v", req, expCount, len(hashes)) mh := &fetch.MeshHashes{ Hashes: hashes, } @@ -133,7 +132,7 @@ func TestForkFinder_FindFork_Permutation(t *testing.T) { }).AnyTimes() fork, err := tf.FindFork(context.Background(), peer, types.LayerID(uint32(lid)), layerHash(lid, true)) - require.NoError(t, err, fmt.Sprintf("lid: %v", lid)) + require.NoErrorf(t, err, "lid: %v", lid) require.Equal(t, expected, int(fork)) } } From 4c5e16382a0ba43dd77c7df5076ad10fd9932a0c Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:20:28 +0000 Subject: [PATCH 41/66] Fix typo --- genvm/core/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genvm/core/types.go b/genvm/core/types.go index 7c56487754..41d55a75cb 100644 --- a/genvm/core/types.go +++ b/genvm/core/types.go @@ -27,7 +27,7 @@ type ( // Signature is an alias to types.EdSignature. Signature = types.EdSignature - // Account is an alis to types.Account. + // Account is an alias to types.Account. Account = types.Account // Header is an alias to types.TxHeader. Header = types.TxHeader From f9687db8305ece3b2af4b7d7cb6b4c8bb647b5c7 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:46:34 +0000 Subject: [PATCH 42/66] Add metadata info to malfeasance2 proofs --- activation/malfeasance2.go | 7 +++- activation/wire/interface.go | 1 + activation/wire/malfeasance_double_marry.go | 10 +++++ activation/wire/malfeasance_double_merge.go | 37 +++++++++++------- .../wire/malfeasance_double_merge_scale.go | 8 ++-- .../wire/malfeasance_double_merge_test.go | 16 ++++---- activation/wire/malfeasance_invalid_post.go | 22 ++++++++--- .../wire/malfeasance_invalid_post_scale.go | 4 +- .../wire/malfeasance_invalid_post_test.go | 8 ++-- .../wire/malfeasance_invalid_prev_atx.go | 22 +++++++++++ activation/wire/mocks.go | 38 +++++++++++++++++++ go.mod | 5 +++ 12 files changed, 139 insertions(+), 39 deletions(-) diff --git a/activation/malfeasance2.go b/activation/malfeasance2.go index 7d1b9d24df..7394074e45 100644 --- a/activation/malfeasance2.go +++ b/activation/malfeasance2.go @@ -110,8 +110,11 @@ func (mh *MalfeasanceHandlerV2) Validate(ctx context.Context, data []byte) (type } func (mh *MalfeasanceHandlerV2) Info(data []byte) (map[string]string, error) { - // TODO(mafa): implement me - return nil, nil + proof, err := mh.decodeProof(data) + if err != nil { + return nil, fmt.Errorf("decoding ATX malfeasance proof: %w", err) + } + return proof.Info(), nil } func (mh *MalfeasanceHandlerV2) ReportLabels(data []byte) []string { diff --git a/activation/wire/interface.go b/activation/wire/interface.go index 4eb27da7f8..9475113a60 100644 --- a/activation/wire/interface.go +++ b/activation/wire/interface.go @@ -37,5 +37,6 @@ type Proof interface { fmt.Stringer Type() ProofType + Info() map[string]string Valid(ctx context.Context, malHandler MalfeasanceValidator) (types.NodeID, error) } diff --git a/activation/wire/malfeasance_double_marry.go b/activation/wire/malfeasance_double_marry.go index 23feb9e2d7..1bc063c4f9 100644 --- a/activation/wire/malfeasance_double_marry.go +++ b/activation/wire/malfeasance_double_marry.go @@ -50,6 +50,16 @@ func (p ProofDoubleMarry) Type() ProofType { return DoubleMarry } +func (p ProofDoubleMarry) Info() map[string]string { + return map[string]string{ + "node_id": p.NodeID.String(), + "atx1": p.ATX1.String(), + "smesher_id1": p.SmesherID1.String(), + "atx2": p.ATX2.String(), + "smesher_id2": p.SmesherID2.String(), + } +} + var _ Proof = &ProofDoubleMarry{} func NewDoubleMarryProof(db sql.Executor, atx1, atx2 *ActivationTxV2, nodeID types.NodeID) (*ProofDoubleMarry, error) { diff --git a/activation/wire/malfeasance_double_merge.go b/activation/wire/malfeasance_double_merge.go index 180c60633a..7b5eb97665 100644 --- a/activation/wire/malfeasance_double_merge.go +++ b/activation/wire/malfeasance_double_merge.go @@ -32,8 +32,8 @@ type ProofDoubleMerge struct { // MarriageATXSmesherID is the ID of the smesher that published the marriage ATX. MarriageATXSmesherID types.NodeID - // ATXID1 is the ID of the ATX being proven. - ATXID1 types.ATXID + // ATX1 is the ID of the ATX being proven. + ATX1 types.ATXID // SmesherID1 is the ID of the smesher that published the ATX. SmesherID1 types.NodeID // Signature1 is the signature of the ATXID by the smesher. @@ -45,8 +45,8 @@ type ProofDoubleMerge struct { // SmesherID1MarryProof is the proof that they married in MarriageATX. SmesherID1MarryProof MarryProof - // ATXID2 is the ID of the ATX being proven. - ATXID2 types.ATXID + // ATX2 is the ID of the ATX being proven. + ATX2 types.ATXID // SmesherID is the ID of the smesher that published the ATX. SmesherID2 types.NodeID // Signature2 is the signature of the ATXID by the smesher. @@ -67,6 +67,17 @@ func (p ProofDoubleMerge) Type() ProofType { return DoubleMerge } +func (p ProofDoubleMerge) Info() map[string]string { + return map[string]string{ + "publish_epoch": p.PublishEpoch.String(), + "marriage_atx": p.MarriageATX.String(), + "atx1": p.ATX1.String(), + "smesher_id1": p.SmesherID1.String(), + "atx2": p.ATX2.String(), + "smesher_id2": p.SmesherID2.String(), + } +} + var _ Proof = &ProofDoubleMerge{} func NewDoubleMergeProof(db sql.Executor, atx1, atx2 *ActivationTxV2) (*ProofDoubleMerge, error) { @@ -116,14 +127,14 @@ func NewDoubleMergeProof(db sql.Executor, atx1, atx2 *ActivationTxV2) (*ProofDou MarriageATX: marriageATX.ID(), MarriageATXSmesherID: marriageATX.SmesherID, - ATXID1: atx1.ID(), + ATX1: atx1.ID(), SmesherID1: atx1.SmesherID, Signature1: atx1.Signature, PublishEpochProof1: atx1.PublishEpochProof(), MarriageATXProof1: atx1.MarriageATXProof(), SmesherID1MarryProof: marriageProof1, - ATXID2: atx2.ID(), + ATX2: atx2.ID(), SmesherID2: atx2.SmesherID, Signature2: atx2.Signature, PublishEpochProof2: atx2.PublishEpochProof(), @@ -136,35 +147,35 @@ func NewDoubleMergeProof(db sql.Executor, atx1, atx2 *ActivationTxV2) (*ProofDou func (p *ProofDoubleMerge) Valid(_ context.Context, edVerifier MalfeasanceValidator) (types.NodeID, error) { // 1. The ATXs have different IDs. - if p.ATXID1 == p.ATXID2 { + if p.ATX1 == p.ATX2 { return types.EmptyNodeID, errors.New("ATXs have the same ID") } // 2. Both ATXs have a valid signature. - if !edVerifier.Signature(signing.ATX, p.SmesherID1, p.ATXID1.Bytes(), p.Signature1) { + if !edVerifier.Signature(signing.ATX, p.SmesherID1, p.ATX1.Bytes(), p.Signature1) { return types.EmptyNodeID, errors.New("ATX 1 invalid signature") } - if !edVerifier.Signature(signing.ATX, p.SmesherID2, p.ATXID2.Bytes(), p.Signature2) { + if !edVerifier.Signature(signing.ATX, p.SmesherID2, p.ATX2.Bytes(), p.Signature2) { return types.EmptyNodeID, errors.New("ATX 2 invalid signature") } // 3. and 4. publish epoch is contained in the ATXs - if !p.PublishEpochProof1.Valid(p.ATXID1, p.PublishEpoch) { + if !p.PublishEpochProof1.Valid(p.ATX1, p.PublishEpoch) { return types.EmptyNodeID, errors.New("ATX 1 invalid publish epoch proof") } - if !p.PublishEpochProof2.Valid(p.ATXID2, p.PublishEpoch) { + if !p.PublishEpochProof2.Valid(p.ATX2, p.PublishEpoch) { return types.EmptyNodeID, errors.New("ATX 2 invalid publish epoch proof") } // 5. signers are married - if !p.MarriageATXProof1.Valid(p.ATXID1, p.MarriageATX) { + if !p.MarriageATXProof1.Valid(p.ATX1, p.MarriageATX) { return types.EmptyNodeID, errors.New("ATX 1 invalid marriage ATX proof") } err := p.SmesherID1MarryProof.Valid(edVerifier, p.MarriageATX, p.MarriageATXSmesherID, p.SmesherID1) if err != nil { return types.EmptyNodeID, errors.New("ATX 1 invalid marriage ATX proof") } - if !p.MarriageATXProof2.Valid(p.ATXID2, p.MarriageATX) { + if !p.MarriageATXProof2.Valid(p.ATX2, p.MarriageATX) { return types.EmptyNodeID, errors.New("ATX 2 invalid marriage ATX proof") } err = p.SmesherID2MarryProof.Valid(edVerifier, p.MarriageATX, p.MarriageATXSmesherID, p.SmesherID2) diff --git a/activation/wire/malfeasance_double_merge_scale.go b/activation/wire/malfeasance_double_merge_scale.go index d4d38b16f3..b4d1a55736 100644 --- a/activation/wire/malfeasance_double_merge_scale.go +++ b/activation/wire/malfeasance_double_merge_scale.go @@ -31,7 +31,7 @@ func (t *ProofDoubleMerge) EncodeScale(enc *scale.Encoder) (total int, err error total += n } { - n, err := scale.EncodeByteArray(enc, t.ATXID1[:]) + n, err := scale.EncodeByteArray(enc, t.ATX1[:]) if err != nil { return total, err } @@ -73,7 +73,7 @@ func (t *ProofDoubleMerge) EncodeScale(enc *scale.Encoder) (total int, err error total += n } { - n, err := scale.EncodeByteArray(enc, t.ATXID2[:]) + n, err := scale.EncodeByteArray(enc, t.ATX2[:]) if err != nil { return total, err } @@ -141,7 +141,7 @@ func (t *ProofDoubleMerge) DecodeScale(dec *scale.Decoder) (total int, err error total += n } { - n, err := scale.DecodeByteArray(dec, t.ATXID1[:]) + n, err := scale.DecodeByteArray(dec, t.ATX1[:]) if err != nil { return total, err } @@ -185,7 +185,7 @@ func (t *ProofDoubleMerge) DecodeScale(dec *scale.Decoder) (total int, err error total += n } { - n, err := scale.DecodeByteArray(dec, t.ATXID2[:]) + n, err := scale.DecodeByteArray(dec, t.ATX2[:]) if err != nil { return total, err } diff --git a/activation/wire/malfeasance_double_merge_test.go b/activation/wire/malfeasance_double_merge_test.go index bae6bd5150..0f7c43960f 100644 --- a/activation/wire/malfeasance_double_merge_test.go +++ b/activation/wire/malfeasance_double_merge_test.go @@ -119,8 +119,8 @@ func Test_DoubleMergeProof(t *testing.T) { require.Nil(t, proof) proof = &ProofDoubleMerge{ - ATXID1: atx1.ID(), - ATXID2: atx1.ID(), + ATX1: atx1.ID(), + ATX2: atx1.ID(), } id, err := proof.Valid(context.Background(), verifier) require.EqualError(t, err, "ATXs have the same ID") @@ -243,20 +243,20 @@ func Test_DoubleMergeProof(t *testing.T) { proof.MarriageATXSmesherID = smesherID // invalid ATX1 ID - id1 := proof.ATXID1 - proof.ATXID1 = types.RandomATXID() + id1 := proof.ATX1 + proof.ATX1 = types.RandomATXID() id, err = proof.Valid(context.Background(), verifier) require.EqualError(t, err, "ATX 1 invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATXID1 = id1 + proof.ATX1 = id1 // invalid ATX2 ID - id2 := proof.ATXID2 - proof.ATXID2 = types.RandomATXID() + id2 := proof.ATX2 + proof.ATX2 = types.RandomATXID() id, err = proof.Valid(context.Background(), verifier) require.EqualError(t, err, "ATX 2 invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATXID2 = id2 + proof.ATX2 = id2 // invalid ATX1 smesher ID smesherID1 := proof.SmesherID1 diff --git a/activation/wire/malfeasance_invalid_post.go b/activation/wire/malfeasance_invalid_post.go index d1542a35b9..81d2db2bde 100644 --- a/activation/wire/malfeasance_invalid_post.go +++ b/activation/wire/malfeasance_invalid_post.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "slices" + "strconv" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/signing" @@ -21,8 +22,8 @@ import ( // 3. The commitment ATX of NodeID used for the invalid PoST based on their initial ATX. // 4. The provided Post is invalid for the given NodeID. type ProofInvalidPost struct { - // ATXID is the ID of the ATX containing the invalid PoST. - ATXID types.ATXID + // ATX is the ID of the ATX containing the invalid PoST. + ATX types.ATXID // SmesherID is the ID of the smesher that published the ATX. SmesherID types.NodeID // Signature is the signature of the ATXID by the smesher. @@ -46,6 +47,15 @@ func (p ProofInvalidPost) Type() ProofType { return InvalidPost } +func (p ProofInvalidPost) Info() map[string]string { + return map[string]string{ + "atx": p.ATX.String(), + "index": strconv.FormatUint(uint64(p.InvalidPostProof.InvalidPostIndex), 10), + "post_node_id": p.NodeID.String(), + "smesher_id": p.SmesherID.String(), + } +} + var _ Proof = &ProofInvalidPost{} func NewInvalidPostProof( @@ -94,7 +104,7 @@ func NewInvalidPostProof( } return &ProofInvalidPost{ - ATXID: atx.ID(), + ATX: atx.ID(), SmesherID: atx.SmesherID, Signature: atx.Signature, @@ -107,7 +117,7 @@ func NewInvalidPostProof( } func (p ProofInvalidPost) Valid(ctx context.Context, malValidator MalfeasanceValidator) (types.NodeID, error) { - if !malValidator.Signature(signing.ATX, p.SmesherID, p.ATXID.Bytes(), p.Signature) { + if !malValidator.Signature(signing.ATX, p.SmesherID, p.ATX.Bytes(), p.Signature) { return types.EmptyNodeID, errors.New("invalid signature") } @@ -117,7 +127,7 @@ func (p ProofInvalidPost) Valid(ctx context.Context, malValidator MalfeasanceVal var marriageIndex *uint32 if p.MarriageProof != nil { - if err := p.MarriageProof.Valid(malValidator, p.ATXID, p.NodeID, p.SmesherID); err != nil { + if err := p.MarriageProof.Valid(malValidator, p.ATX, p.NodeID, p.SmesherID); err != nil { return types.EmptyNodeID, fmt.Errorf("invalid marriage proof: %w", err) } marriageIndex = &p.MarriageProof.NodeIDMarryProof.CertificateIndex @@ -126,7 +136,7 @@ func (p ProofInvalidPost) Valid(ctx context.Context, malValidator MalfeasanceVal if err := p.InvalidPostProof.Valid( ctx, malValidator, - p.ATXID, + p.ATX, p.NodeID, marriageIndex, ); err != nil { diff --git a/activation/wire/malfeasance_invalid_post_scale.go b/activation/wire/malfeasance_invalid_post_scale.go index ffcef75f16..0a2388b80c 100644 --- a/activation/wire/malfeasance_invalid_post_scale.go +++ b/activation/wire/malfeasance_invalid_post_scale.go @@ -10,7 +10,7 @@ import ( func (t *ProofInvalidPost) EncodeScale(enc *scale.Encoder) (total int, err error) { { - n, err := scale.EncodeByteArray(enc, t.ATXID[:]) + n, err := scale.EncodeByteArray(enc, t.ATX[:]) if err != nil { return total, err } @@ -56,7 +56,7 @@ func (t *ProofInvalidPost) EncodeScale(enc *scale.Encoder) (total int, err error func (t *ProofInvalidPost) DecodeScale(dec *scale.Decoder) (total int, err error) { { - n, err := scale.DecodeByteArray(dec, t.ATXID[:]) + n, err := scale.DecodeByteArray(dec, t.ATX[:]) if err != nil { return total, err } diff --git a/activation/wire/malfeasance_invalid_post_test.go b/activation/wire/malfeasance_invalid_post_test.go index 9d0880927a..27a6a2d4ba 100644 --- a/activation/wire/malfeasance_invalid_post_test.go +++ b/activation/wire/malfeasance_invalid_post_test.go @@ -434,11 +434,11 @@ func Test_InvalidPostProof(t *testing.T) { }).AnyTimes() // invalid ATXID - proof.ATXID = types.RandomATXID() + proof.ATX = types.RandomATXID() id, err := proof.Valid(context.Background(), verifier) require.EqualError(t, err, "invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATXID = atx.ID() + proof.ATX = atx.ID() // invalid smesher ID proof.SmesherID = types.RandomNodeID() @@ -618,11 +618,11 @@ func Test_InvalidPostProof(t *testing.T) { }).AnyTimes() // invalid ATXID - proof.ATXID = types.RandomATXID() + proof.ATX = types.RandomATXID() id, err := proof.Valid(context.Background(), verifier) require.EqualError(t, err, "invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATXID = atx.ID() + proof.ATX = atx.ID() // invalid smesher ID proof.SmesherID = types.RandomNodeID() diff --git a/activation/wire/malfeasance_invalid_prev_atx.go b/activation/wire/malfeasance_invalid_prev_atx.go index 53f25fdd10..fb5645e6d8 100644 --- a/activation/wire/malfeasance_invalid_prev_atx.go +++ b/activation/wire/malfeasance_invalid_prev_atx.go @@ -40,6 +40,17 @@ func (p ProofInvalidPrevAtxV2) Type() ProofType { return InvalidPreviousV2 } +func (p ProofInvalidPrevAtxV2) Info() map[string]string { + return map[string]string{ + "prev_atx": p.PrevATX.String(), + "node_id": p.NodeID.String(), + "atx1": p.Proofs[0].ATXID.String(), + "smesher_id1": p.Proofs[0].SmesherID.String(), + "atx2": p.Proofs[1].ATXID.String(), + "smesher_id2": p.Proofs[1].SmesherID.String(), + } +} + var _ Proof = &ProofInvalidPrevAtxV2{} func NewInvalidPrevAtxProofV2( @@ -206,6 +217,17 @@ func (p ProofInvalidPrevAtxV1) Type() ProofType { return InvalidPreviousV1 } +func (p ProofInvalidPrevAtxV1) Info() map[string]string { + return map[string]string{ + "prev_atx": p.PrevATX.String(), + "node_id": p.NodeID.String(), + "atx1": p.Proof.ATXID.String(), + "smesher_id1": p.Proof.SmesherID.String(), + "atx2": p.ATXv1.ID().String(), + "smesher_id2": p.ATXv1.SmesherID.String(), + } +} + var _ Proof = &ProofInvalidPrevAtxV1{} func NewInvalidPrevAtxProofV1( diff --git a/activation/wire/mocks.go b/activation/wire/mocks.go index fab820f6be..a6f97f8087 100644 --- a/activation/wire/mocks.go +++ b/activation/wire/mocks.go @@ -221,6 +221,44 @@ func (c *MockProofEncodeScaleCall) DoAndReturn(f func(*scale.Encoder) (int, erro return c } +// Info mocks base method. +func (m *MockProof) Info() map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Info") + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// Info indicates an expected call of Info. +func (mr *MockProofMockRecorder) Info() *MockProofInfoCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockProof)(nil).Info)) + return &MockProofInfoCall{Call: call} +} + +// MockProofInfoCall wrap *gomock.Call +type MockProofInfoCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockProofInfoCall) Return(arg0 map[string]string) *MockProofInfoCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockProofInfoCall) Do(f func() map[string]string) *MockProofInfoCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockProofInfoCall) DoAndReturn(f func() map[string]string) *MockProofInfoCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // String mocks base method. func (m *MockProof) String() string { m.ctrl.T.Helper() diff --git a/go.mod b/go.mod index 80bfcfc16e..4f904dad6e 100644 --- a/go.mod +++ b/go.mod @@ -260,3 +260,8 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +// temporary until this issue is resolved and cloud.google.com/go/storage has been updated +// https://github.com/googleapis/google-cloud-go/issues/11283 +exclude google.golang.org/grpc v1.69.0 + From 5b2dc7abcfa72b0a9955faaeb3f9c2d6761d66c4 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:34:36 +0000 Subject: [PATCH 43/66] Add more sql functions for malfeasance2 --- sql/malfeasance/malfeasance.go | 84 ++++++++++--- sql/malfeasance/malfeasance_test.go | 178 +++++++++++++++++++++++----- 2 files changed, 218 insertions(+), 44 deletions(-) diff --git a/sql/malfeasance/malfeasance.go b/sql/malfeasance/malfeasance.go index 32731b60c3..e858e7f133 100644 --- a/sql/malfeasance/malfeasance.go +++ b/sql/malfeasance/malfeasance.go @@ -9,20 +9,6 @@ import ( "github.com/spacemeshos/go-spacemesh/sql/marriage" ) -func IsMalicious(db sql.Executor, nodeID types.NodeID) (bool, error) { - rows, err := db.Exec(` - SELECT 1 - FROM malfeasance - WHERE pubkey = ?1 - `, func(stmt *sql.Statement) { - stmt.BindBytes(1, nodeID.Bytes()) - }, nil) - if err != nil { - return false, fmt.Errorf("is malicious %v: %w", nodeID, err) - } - return rows > 0, nil -} - func AddProof( db sql.Executor, nodeID types.NodeID, @@ -68,3 +54,73 @@ func SetMalicious(db sql.Executor, nodeID types.NodeID, marriageID marriage.ID, } return nil } + +func IsMalicious(db sql.Executor, nodeID types.NodeID) (bool, error) { + rows, err := db.Exec(` + SELECT 1 + FROM malfeasance + WHERE pubkey = ?1 + `, func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + }, nil) + if err != nil { + return false, fmt.Errorf("is malicious %v: %w", nodeID, err) + } + return rows > 0, nil +} + +// Proof returns the malfeasance proof for the given node ID. Returns sql.ErrNotFound if no proof for the given node ID +// exists. To return a proof for a marriage set use MarriageProof instead. +func NodeIDProof(db sql.Executor, nodeID types.NodeID) (byte, []byte, error) { + var ( + domain byte + proof []byte + ) + rows, err := db.Exec(` + SELECT proof, domain + FROM malfeasance + WHERE pubkey = ?1 AND marriage_id IS NULL + `, func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + }, func(stmt *sql.Statement) bool { + proof = make([]byte, stmt.ColumnLen(0)) + stmt.ColumnBytes(0, proof) + domain = byte(stmt.ColumnInt64(1)) + return false + }) + if err != nil { + return 0, nil, fmt.Errorf("proof %v: %w", nodeID, err) + } + if rows == 0 { + return 0, nil, sql.ErrNotFound + } + return domain, proof, nil +} + +// MarriageProof returns the malfeasance proof for the marriage set. Returns sql.ErrNotFound if no proof for the given +// marriage ID exists. To return a proof for a node ID use NodeIDProof instead. +func MarriageProof(db sql.Executor, marriageID marriage.ID) (byte, []byte, error) { + var ( + domain byte + proof []byte + ) + rows, err := db.Exec(` + SELECT proof, domain + FROM malfeasance + WHERE marriage_id = ?1 AND proof IS NOT NULL + `, func(stmt *sql.Statement) { + stmt.BindInt64(1, int64(marriageID)) + }, func(stmt *sql.Statement) bool { + proof = make([]byte, stmt.ColumnLen(0)) + stmt.ColumnBytes(0, proof) + domain = byte(stmt.ColumnInt64(1)) + return false + }) + if err != nil { + return 0, nil, fmt.Errorf("marriage proof %v: %w", marriageID, err) + } + if rows == 0 { + return 0, nil, sql.ErrNotFound + } + return domain, proof, nil +} diff --git a/sql/malfeasance/malfeasance_test.go b/sql/malfeasance/malfeasance_test.go index 65bc13f5ab..76738f106e 100644 --- a/sql/malfeasance/malfeasance_test.go +++ b/sql/malfeasance/malfeasance_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/malfeasance" "github.com/spacemeshos/go-spacemesh/sql/marriage" "github.com/spacemeshos/go-spacemesh/sql/statesql" @@ -19,7 +20,7 @@ func TestAdd(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - err := malfeasance.AddProof(db, types.RandomNodeID(), nil, nil, 0, time.Now()) + err := malfeasance.AddProof(db, types.RandomNodeID(), nil, nil, 1, time.Now()) require.Error(t, err) }) @@ -27,7 +28,7 @@ func TestAdd(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - err := malfeasance.AddProof(db, types.RandomNodeID(), nil, types.RandomBytes(100), 0, time.Now()) + err := malfeasance.AddProof(db, types.RandomNodeID(), nil, types.RandomBytes(100), 1, time.Now()) require.NoError(t, err) }) @@ -36,7 +37,7 @@ func TestAdd(t *testing.T) { db := statesql.InMemoryTest(t) id := marriage.ID(100) - err := malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 0, time.Now()) + err := malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 1, time.Now()) require.Error(t, err) }) @@ -58,7 +59,7 @@ func TestAdd(t *testing.T) { }) require.NoError(t, err) - err = malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 0, time.Now()) + err = malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 1, time.Now()) require.NoError(t, err) }) @@ -80,54 +81,70 @@ func TestAdd(t *testing.T) { }) require.NoError(t, err) - err = malfeasance.AddProof(db, types.RandomNodeID(), &id, types.RandomBytes(100), 0, time.Now()) + err = malfeasance.AddProof(db, types.RandomNodeID(), &id, types.RandomBytes(100), 1, time.Now()) require.NoError(t, err) }) } -func TestIsMalicious(t *testing.T) { +func TestSetMalicious(t *testing.T) { t.Parallel() - t.Run("unknown node is not malicious", func(t *testing.T) { + t.Run("identity cannot be set malicious with unknown marriage ID", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - mal, err := malfeasance.IsMalicious(db, types.RandomNodeID()) + nodeID := types.RandomNodeID() + err := malfeasance.SetMalicious(db, nodeID, marriage.ID(0), time.Now()) + require.Error(t, err) + + mal, err := malfeasance.IsMalicious(db, nodeID) require.NoError(t, err) require.False(t, mal) }) - t.Run("known node is malicious", func(t *testing.T) { + t.Run("identity can be set malicious with known marriage ID", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) + id, err := marriage.NewID(db) + require.NoError(t, err) + nodeID := types.RandomNodeID() - err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 0, time.Now()) + err = marriage.Add(db, marriage.Info{ + ID: id, + NodeID: nodeID, + ATX: types.RandomATXID(), + MarriageIndex: 0, + Target: types.RandomNodeID(), + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + + err = malfeasance.SetMalicious(db, nodeID, id, time.Now()) require.NoError(t, err) mal, err := malfeasance.IsMalicious(db, nodeID) require.NoError(t, err) require.True(t, mal) }) -} - -func TestSetMalicious(t *testing.T) { - t.Parallel() - t.Run("identity cannot be set malicious with unknown marriage ID", func(t *testing.T) { + t.Run("malfeasants marriage ID cannot be updated with SetMalicious to unknown ID", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) nodeID := types.RandomNodeID() - err := malfeasance.SetMalicious(db, nodeID, marriage.ID(0), time.Now()) + err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 1, time.Now()) + require.NoError(t, err) + + err = malfeasance.SetMalicious(db, nodeID, marriage.ID(0), time.Now()) require.Error(t, err) mal, err := malfeasance.IsMalicious(db, nodeID) require.NoError(t, err) - require.False(t, mal) + require.True(t, mal) }) - t.Run("identity can be set malicious with known marriage ID", func(t *testing.T) { + t.Run("malfeasants marriage ID can be updated with SetMalicious to known ID", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) @@ -135,9 +152,12 @@ func TestSetMalicious(t *testing.T) { require.NoError(t, err) nodeID := types.RandomNodeID() + err = malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 1, time.Now()) + require.NoError(t, err) + err = marriage.Add(db, marriage.Info{ ID: id, - NodeID: nodeID, + NodeID: types.RandomNodeID(), ATX: types.RandomATXID(), MarriageIndex: 0, Target: types.RandomNodeID(), @@ -152,24 +172,61 @@ func TestSetMalicious(t *testing.T) { require.NoError(t, err) require.True(t, mal) }) +} - t.Run("malfeasants marriage ID cannot be updated with SetMalicious to unknown ID", func(t *testing.T) { +func TestIsMalicious(t *testing.T) { + t.Parallel() + + t.Run("unknown node is not malicious", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - nodeID := types.RandomNodeID() - err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 0, time.Now()) + mal, err := malfeasance.IsMalicious(db, types.RandomNodeID()) require.NoError(t, err) + require.False(t, mal) + }) - err = malfeasance.SetMalicious(db, nodeID, marriage.ID(0), time.Now()) - require.Error(t, err) + t.Run("node with proof is malicious", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + nodeID := types.RandomNodeID() + err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 1, time.Now()) + require.NoError(t, err) mal, err := malfeasance.IsMalicious(db, nodeID) require.NoError(t, err) require.True(t, mal) }) +} - t.Run("malfeasants marriage ID can be updated with SetMalicious to known ID", func(t *testing.T) { +func TestNodeIDProof(t *testing.T) { + t.Parallel() + + t.Run("unknown node has no proof", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + _, _, err := malfeasance.NodeIDProof(db, types.RandomNodeID()) + require.ErrorIs(t, err, sql.ErrNotFound) + }) + + t.Run("node with proof has proof", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + nodeID := types.RandomNodeID() + proof := types.RandomBytes(100) + err := malfeasance.AddProof(db, nodeID, nil, proof, 1, time.Now()) + require.NoError(t, err) + + domain, p, err := malfeasance.NodeIDProof(db, nodeID) + require.NoError(t, err) + require.Equal(t, byte(1), domain) + require.Equal(t, proof, p) + }) + + t.Run("node with proof and marriage ID returns no proof", func(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) @@ -177,12 +234,34 @@ func TestSetMalicious(t *testing.T) { require.NoError(t, err) nodeID := types.RandomNodeID() - err = malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 0, time.Now()) + err = marriage.Add(db, marriage.Info{ + ID: id, + NodeID: nodeID, + ATX: types.RandomATXID(), + MarriageIndex: 0, + Target: types.RandomNodeID(), + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + + err = malfeasance.AddProof(db, nodeID, &id, types.RandomBytes(100), 1, time.Now()) + require.NoError(t, err) + + _, _, err = malfeasance.NodeIDProof(db, nodeID) + require.ErrorIs(t, err, sql.ErrNotFound) + }) + + t.Run("node without proof and marriage ID returns no proof", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + id, err := marriage.NewID(db) require.NoError(t, err) + nodeID := types.RandomNodeID() err = marriage.Add(db, marriage.Info{ ID: id, - NodeID: types.RandomNodeID(), + NodeID: nodeID, ATX: types.RandomATXID(), MarriageIndex: 0, Target: types.RandomNodeID(), @@ -190,11 +269,50 @@ func TestSetMalicious(t *testing.T) { }) require.NoError(t, err) - err = malfeasance.SetMalicious(db, nodeID, id, time.Now()) + err = malfeasance.AddProof(db, nodeID, &id, nil, 1, time.Now()) require.NoError(t, err) - mal, err := malfeasance.IsMalicious(db, nodeID) + _, _, err = malfeasance.NodeIDProof(db, nodeID) + require.ErrorIs(t, err, sql.ErrNotFound) + }) +} + +func TestMarriageProof(t *testing.T) { + t.Parallel() + + t.Run("unknown marriage ID has no proof", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + _, _, err := malfeasance.MarriageProof(db, marriage.ID(0)) + require.ErrorIs(t, err, sql.ErrNotFound) + }) + + t.Run("marriage ID with proof has proof", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + id, err := marriage.NewID(db) require.NoError(t, err) - require.True(t, mal) + + nodeID := types.RandomNodeID() + err = marriage.Add(db, marriage.Info{ + ID: id, + NodeID: nodeID, + ATX: types.RandomATXID(), + MarriageIndex: 0, + Target: types.RandomNodeID(), + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + + proof := types.RandomBytes(100) + err = malfeasance.AddProof(db, nodeID, &id, proof, 1, time.Now()) + require.NoError(t, err) + + domain, p, err := malfeasance.MarriageProof(db, id) + require.NoError(t, err) + require.Equal(t, byte(1), domain) + require.Equal(t, proof, p) }) } From a010379fd0126359cd66d09b5ea36776a8b14dc1 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:56:54 +0000 Subject: [PATCH 44/66] Add missing assertions --- sql/identities/identities_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/identities/identities_test.go b/sql/identities/identities_test.go index 8e357bc04f..2c1d859f04 100644 --- a/sql/identities/identities_test.go +++ b/sql/identities/identities_test.go @@ -70,7 +70,7 @@ func Test_GetMalicious(t *testing.T) { for i := 0; i < numBad; i++ { nid := types.NodeID{byte(i + 1)} bad = append(bad, nid) - require.NoError(t, identities.SetMalicious(db, nid, types.RandomBytes(11), time.Now().Local())) + require.NoError(t, identities.SetMalicious(db, nid, types.RandomBytes(11), time.Now())) } got, err = identities.AllMalicious(db) require.NoError(t, err) @@ -83,7 +83,7 @@ func TestLoadMalfeasanceBlob(t *testing.T) { nid1 := types.RandomNodeID() proof1 := types.RandomBytes(11) - identities.SetMalicious(db, nid1, proof1, time.Now().Local()) + require.NoError(t, identities.SetMalicious(db, nid1, proof1, time.Now())) var blob1 sql.Blob require.NoError(t, identities.LoadMalfeasanceBlob(ctx, db, nid1.Bytes(), &blob1)) @@ -95,7 +95,7 @@ func TestLoadMalfeasanceBlob(t *testing.T) { nid2 := types.RandomNodeID() proof2 := types.RandomBytes(12) - identities.SetMalicious(db, nid2, proof2, time.Now().Local()) + require.NoError(t, identities.SetMalicious(db, nid2, proof2, time.Now())) var blob2 sql.Blob require.NoError(t, identities.LoadMalfeasanceBlob(ctx, db, nid2.Bytes(), &blob2)) From bf5ea8145cc6843f9a6a4e51f2ed0fee9f6594ea Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:57:11 +0000 Subject: [PATCH 45/66] Don't use 0 as domain identifier --- malfeasance2/wire.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/malfeasance2/wire.go b/malfeasance2/wire.go index 86b0c62572..392d1985a5 100644 --- a/malfeasance2/wire.go +++ b/malfeasance2/wire.go @@ -8,9 +8,9 @@ import "github.com/spacemeshos/go-spacemesh/common/types" type ProofDomain byte const ( - InvalidActivation ProofDomain = iota - InvalidBallot - InvalidHareMsg + InvalidActivation ProofDomain = 0x01 + InvalidBallot ProofDomain = 0x02 + InvalidHareMsg ProofDomain = 0x03 ) // ProofVersion encodes the version of the malfeasance proof. From af3852537ca4c9bb6cf090717a3f271e24f8b985 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:57:35 +0000 Subject: [PATCH 46/66] Add malfeasance type info to properties --- activation/malfeasance2.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activation/malfeasance2.go b/activation/malfeasance2.go index 7394074e45..6a3320b363 100644 --- a/activation/malfeasance2.go +++ b/activation/malfeasance2.go @@ -114,7 +114,9 @@ func (mh *MalfeasanceHandlerV2) Info(data []byte) (map[string]string, error) { if err != nil { return nil, fmt.Errorf("decoding ATX malfeasance proof: %w", err) } - return proof.Info(), nil + info := proof.Info() + info["type"] = proof.String() + return info, nil } func (mh *MalfeasanceHandlerV2) ReportLabels(data []byte) []string { From 0c844f89e835d82c5634bf9fba619acedf655feb Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:58:09 +0000 Subject: [PATCH 47/66] Add tests for Info method of malfeasance handler --- malfeasance2/handler.go | 36 +++++++--- malfeasance2/handler_test.go | 125 +++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 8 deletions(-) diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go index 3b6bff489b..f10cda0fe6 100644 --- a/malfeasance2/handler.go +++ b/malfeasance2/handler.go @@ -18,6 +18,8 @@ import ( "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" + "github.com/spacemeshos/go-spacemesh/sql/marriage" ) var ( @@ -83,20 +85,38 @@ func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { } } -func (h *Handler) Info(data []byte) (map[string]string, error) { - var p MalfeasanceProof - if err := codec.Decode(data, &p); err != nil { - return nil, fmt.Errorf("decode malfeasance proof: %w", err) +func (h *Handler) Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) { + var ( + isMarried = false + domain byte + proof []byte + ) + marriageID, err := marriage.FindIDByNodeID(h.db, nodeID) + if err == nil { + isMarried = true + domain, proof, err = malfeasance.MarriageProof(h.db, marriageID) + if err != nil { + return nil, fmt.Errorf("get malfeasance proof for married node ID %s: %w", nodeID, err) + } + } else { + domain, proof, err = malfeasance.NodeIDProof(h.db, nodeID) + if err != nil { + return nil, fmt.Errorf("get malfeasance proof for node ID %s: %w", nodeID, err) + } } - mh, ok := h.handlers[p.Domain] + + mh, ok := h.handlers[ProofDomain(domain)] if !ok { - return nil, fmt.Errorf("unknown malfeasance domain %d", p.Domain) + return nil, fmt.Errorf("unknown malfeasance domain %d", domain) } - properties, err := mh.Info(p.Proof) + properties, err := mh.Info(proof) if err != nil { return nil, fmt.Errorf("malfeasance info: %w", err) } - properties["domain"] = strconv.FormatUint(uint64(p.Domain), 10) + properties["domain"] = strconv.FormatUint(uint64(domain), 10) + if isMarried { + properties["malicious_id"] = nodeID.String() + } return properties, nil } diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index ef713fd5f1..62e3829bd2 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -3,6 +3,7 @@ package malfeasance2_test import ( "context" "errors" + "strconv" "testing" "time" @@ -21,6 +22,7 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/malfeasance" + "github.com/spacemeshos/go-spacemesh/sql/marriage" "github.com/spacemeshos/go-spacemesh/sql/statesql" ) @@ -284,3 +286,126 @@ func TestHandler_HandleGossip(t *testing.T) { require.NoError(t, err) }) } + +func TestHandler_Info(t *testing.T) { + t.Run("unknown identity", func(t *testing.T) { + h := newTestHandler(t) + + info, err := h.Info(context.Background(), types.RandomNodeID()) + require.ErrorContains(t, err, "get malfeasance proof") + require.ErrorIs(t, err, sql.ErrNotFound) + require.Nil(t, info) + }) + + t.Run("unknown malfeasance type", func(t *testing.T) { + h := newTestHandler(t) + + nodeID := types.RandomNodeID() + proofBytes := types.RandomBytes(100) + err := malfeasance.AddProof(h.db, nodeID, nil, proofBytes, 1, time.Now()) + require.NoError(t, err) + + info, err := h.Info(context.Background(), nodeID) + require.ErrorContains(t, err, "unknown malfeasance domain 1") + require.Nil(t, info) + }) + + t.Run("invalid proof", func(t *testing.T) { + h := newTestHandler(t) + invalidProof := []byte("invalid") + infoError := errors.New("invalid proof") + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Info(invalidProof).Return(nil, infoError) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + nodeID := types.RandomNodeID() + err := malfeasance.AddProof(h.db, nodeID, nil, invalidProof, byte(malfeasance2.InvalidActivation), time.Now()) + require.NoError(t, err) + + info, err := h.Info(context.Background(), nodeID) + require.ErrorIs(t, err, infoError) + require.Nil(t, info) + }) + + t.Run("valid proof for node", func(t *testing.T) { + h := newTestHandler(t) + validProof := []byte("valid") + properties := map[string]string{ + "key": "value", + } + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Info(validProof).Return(properties, nil) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + nodeID := types.RandomNodeID() + err := malfeasance.AddProof(h.db, nodeID, nil, validProof, byte(malfeasance2.InvalidActivation), time.Now()) + require.NoError(t, err) + + expectedProperties := map[string]string{ + "domain": strconv.FormatUint(uint64(malfeasance2.InvalidActivation), 10), + "key": "value", + } + + for k, v := range properties { + expectedProperties[k] = v + } + + info, err := h.Info(context.Background(), nodeID) + require.NoError(t, err) + require.Equal(t, expectedProperties, info) + }) + + t.Run("valid proof for married node", func(t *testing.T) { + h := newTestHandler(t) + validProof := []byte("valid") + properties := map[string]string{ + "key": "value", + } + mockHandler := malfeasance2.NewMockMalfeasanceHandler(gomock.NewController(t)) + mockHandler.EXPECT().Info(validProof).Return(properties, nil) + h.RegisterHandler(malfeasance2.InvalidActivation, mockHandler) + + maliciousID := types.RandomNodeID() + nodeID := types.RandomNodeID() + + id, err := marriage.NewID(h.db) + require.NoError(t, err) + + err = marriage.Add(h.db, marriage.Info{ + ID: id, + NodeID: maliciousID, + ATX: types.RandomATXID(), + MarriageIndex: 0, + Target: types.RandomNodeID(), + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + + err = malfeasance.AddProof( + h.db, + nodeID, + &id, + validProof, + byte(malfeasance2.InvalidActivation), + time.Now(), + ) + require.NoError(t, err) + + err = malfeasance.SetMalicious(h.db, maliciousID, id, time.Now()) + require.NoError(t, err) + + expectedProperties := map[string]string{ + "domain": strconv.FormatUint(uint64(malfeasance2.InvalidActivation), 10), + "key": "value", + "malicious_id": maliciousID.String(), + } + + for k, v := range properties { + expectedProperties[k] = v + } + + info, err := h.Info(context.Background(), maliciousID) + require.NoError(t, err) + require.Equal(t, expectedProperties, info) + }) +} From a56b20159d6fa22c6b3238aa5f696cdb4fa67fe5 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:23:46 +0000 Subject: [PATCH 48/66] WiP: malfeasance2 grpc api --- api/grpcserver/v2alpha1/malfeasance.go | 40 ++++++++++------- api/grpcserver/v2alpha1/malfeasance_test.go | 50 ++++++++++++++++++++- go.mod | 1 - node/node.go | 8 +--- 4 files changed, 75 insertions(+), 24 deletions(-) diff --git a/api/grpcserver/v2alpha1/malfeasance.go b/api/grpcserver/v2alpha1/malfeasance.go index e2242eba7b..8af4b13b94 100644 --- a/api/grpcserver/v2alpha1/malfeasance.go +++ b/api/grpcserver/v2alpha1/malfeasance.go @@ -30,16 +30,18 @@ const ( MalfeasanceStream = "malfeasance_stream_v2alpha1" ) -func NewMalfeasanceService(db sql.Executor, malfeasanceHandler malfeasanceInfo) *MalfeasanceService { +func NewMalfeasanceService(db sql.Executor, malfeasanceHandler, legacyHandler malfeasanceInfo) *MalfeasanceService { return &MalfeasanceService{ - db: db, - info: malfeasanceHandler, + db: db, + info: malfeasanceHandler, + infoLegacy: legacyHandler, } } type MalfeasanceService struct { - db sql.Executor - info malfeasanceInfo + db sql.Executor + info malfeasanceInfo + infoLegacy malfeasanceInfo } func (s *MalfeasanceService) RegisterService(server *grpc.Server) { @@ -54,6 +56,7 @@ func (s *MalfeasanceService) String() string { return "MalfeasanceService" } +// TODO(mafa): add a ListV2 method to return the new malfeasance proofs. func (s *MalfeasanceService) List( ctx context.Context, request *spacemeshv2alpha1.MalfeasanceRequest, @@ -72,7 +75,7 @@ func (s *MalfeasanceService) List( proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, request.Limit) if err := identities.IterateOps(s.db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { - rst := toProof(ctx, s.info, id) + rst := toProof(ctx, s.infoLegacy, id) if rst == nil { return true } @@ -85,16 +88,22 @@ func (s *MalfeasanceService) List( return &spacemeshv2alpha1.MalfeasanceList{Proofs: proofs}, nil } -func NewMalfeasanceStreamService(db sql.Executor, malfeasanceHandler malfeasanceInfo) *MalfeasanceStreamService { +func NewMalfeasanceStreamService( + db sql.Executor, + malfeasanceHandler, + legacyHandler malfeasanceInfo, +) *MalfeasanceStreamService { return &MalfeasanceStreamService{ - db: db, - info: malfeasanceHandler, + db: db, + info: malfeasanceHandler, + infoLegacy: legacyHandler, } } type MalfeasanceStreamService struct { - db sql.Executor - info malfeasanceInfo + db sql.Executor + info malfeasanceInfo + infoLegacy malfeasanceInfo } func (s *MalfeasanceStreamService) RegisterService(server *grpc.Server) { @@ -109,6 +118,7 @@ func (s *MalfeasanceStreamService) String() string { return "MalfeasanceStreamService" } +// TODO(mafa): add a StreamV2 method to return the new malfeasance proofs func (s *MalfeasanceStreamService) Stream( request *spacemeshv2alpha1.MalfeasanceStreamRequest, stream spacemeshv2alpha1.MalfeasanceStreamService_StreamServer, @@ -149,7 +159,7 @@ func (s *MalfeasanceStreamService) Stream( select { // process events first case rst := <-eventsOut: - proof := toProof(stream.Context(), s.info, rst.Smesher) + proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { continue } @@ -163,7 +173,7 @@ func (s *MalfeasanceStreamService) Stream( default: select { case rst := <-eventsOut: - proof := toProof(stream.Context(), s.info, rst.Smesher) + proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { continue } @@ -210,7 +220,7 @@ func (s *MalfeasanceStreamService) fetchFromDB( go func() { defer close(dbChan) if err := identities.IterateOps(s.db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { - rst := toProof(ctx, s.info, id) + rst := toProof(ctx, s.infoLegacy, id) if rst == nil { return true } @@ -264,7 +274,7 @@ func toProof( delete(properties, "type") return &spacemeshv2alpha1.MalfeasanceProof{ Smesher: id.Bytes(), - Domain: spacemeshv2alpha1.MalfeasanceProof_MalfeasanceDomain(domain), + Domain: spacemeshv2alpha1.MalfeasanceProof_MalfeasanceDomain(domain), // TODO(mafa): add new domains Type: uint32(proofType), Properties: properties, } diff --git a/api/grpcserver/v2alpha1/malfeasance_test.go b/api/grpcserver/v2alpha1/malfeasance_test.go index 00a10443cd..dbb515ff47 100644 --- a/api/grpcserver/v2alpha1/malfeasance_test.go +++ b/api/grpcserver/v2alpha1/malfeasance_test.go @@ -49,7 +49,7 @@ func TestMalfeasanceService_List(t *testing.T) { require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) } - svc := NewMalfeasanceService(db, info) + svc := NewMalfeasanceService(db, nil, info) cfg, cleanup := launchServer(t, svc) t.Cleanup(cleanup) @@ -107,6 +107,52 @@ func TestMalfeasanceService_List(t *testing.T) { }) } +func TestMalfeasanceService_ListV2(t *testing.T) { + // TODO(mafa): add a ListV2 method to return the new malfeasance proofs + + // // 70 proofs are for individual identities + // for i := range 70 { + // proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + // proofs[i].Properties = map[string]string{ + // "domain": strconv.FormatUint(uint64(i%4+1), 10), + // "type": strconv.FormatUint(uint64(i%4+1), 10), + // fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + // } + // info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() + // legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + + // require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, nil, proofs[i].Proof, byte(i%4+1), time.Now())) + // } + // // last 20 proofs are from for a single marriage + // id, err := marriage.NewID(db) + // require.NoError(t, err) + // marriageATX := types.RandomATXID() + + // for i := 70; i < 90; i++ { + // proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + // proofs[i].Properties = map[string]string{ + // "domain": strconv.FormatUint(uint64(i%4+1), 10), + // "type": strconv.FormatUint(uint64(i%4+1), 10), + // fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + // "malicious_id": proofs[i].ID.String(), + // } + // info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() + // legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + + // err = marriage.Add(db, marriage.Info{ + // ID: id, + // NodeID: proofs[i].ID, + // ATX: marriageATX, + // MarriageIndex: i % 70, + // Target: proofs[70].ID, + // Signature: types.RandomEdSignature(), + // }) + // require.NoError(t, err) + + // require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, &id, proofs[i].Proof, byte(i%4+1), time.Now())) + // } +} + func TestMalfeasanceStreamService_Stream(t *testing.T) { setup := func( t *testing.T, @@ -126,7 +172,7 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) } - svc := NewMalfeasanceStreamService(db, info) + svc := NewMalfeasanceStreamService(db, nil, info) cfg, cleanup := launchServer(t, svc) t.Cleanup(cleanup) diff --git a/go.mod b/go.mod index 68b252522c..8674e8ea81 100644 --- a/go.mod +++ b/go.mod @@ -264,4 +264,3 @@ require ( // temporary until this issue is resolved and cloud.google.com/go/storage has been updated // https://github.com/googleapis/google-cloud-go/issues/11283 exclude google.golang.org/grpc v1.69.0 - diff --git a/node/node.go b/node/node.go index 0c4530b56b..b0b9871e3b 100644 --- a/node/node.go +++ b/node/node.go @@ -1615,15 +1615,11 @@ func (app *App) grpcService(svc grpcserver.Service, lg log.Log) (grpcserver.Serv app.grpcServices[svc] = service return service, nil case v2alpha1.Malfeasance: - // TODO(mafa): update to also use malfeasance2 handler - _ = app.malfeasance2Handler - service := v2alpha1.NewMalfeasanceService(app.apiDB, app.malfeasanceHandler) + service := v2alpha1.NewMalfeasanceService(app.apiDB, app.malfeasance2Handler, app.malfeasanceHandler) app.grpcServices[svc] = service return service, nil case v2alpha1.MalfeasanceStream: - // TODO(mafa): update to also use malfeasance2 handler - _ = app.malfeasance2Handler - service := v2alpha1.NewMalfeasanceStreamService(app.apiDB, app.malfeasanceHandler) + service := v2alpha1.NewMalfeasanceStreamService(app.apiDB, app.malfeasance2Handler, app.malfeasanceHandler) app.grpcServices[svc] = service return service, nil case v2alpha1.Network: From 43eb796299c9ee8d297b96092506a4cad787c41c Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:27:27 +0000 Subject: [PATCH 49/66] Use consistent naming --- activation/wire/malfeasance_double_marry.go | 26 +++++++------- .../wire/malfeasance_double_marry_scale.go | 8 ++--- .../wire/malfeasance_double_marry_test.go | 12 +++---- activation/wire/malfeasance_double_merge.go | 30 ++++++++-------- .../wire/malfeasance_double_merge_scale.go | 8 ++--- .../wire/malfeasance_double_merge_test.go | 16 ++++----- activation/wire/malfeasance_invalid_post.go | 14 ++++---- .../wire/malfeasance_invalid_post_scale.go | 4 +-- .../wire/malfeasance_invalid_post_test.go | 8 ++--- .../wire/malfeasance_invalid_prev_atx.go | 34 +++++++++---------- .../malfeasance_invalid_prev_atx_scale.go | 8 ++--- .../wire/malfeasance_invalid_prev_atx_test.go | 8 ++--- 12 files changed, 88 insertions(+), 88 deletions(-) diff --git a/activation/wire/malfeasance_double_marry.go b/activation/wire/malfeasance_double_marry.go index 1bc063c4f9..32b6cba763 100644 --- a/activation/wire/malfeasance_double_marry.go +++ b/activation/wire/malfeasance_double_marry.go @@ -23,8 +23,8 @@ type ProofDoubleMarry struct { // NodeID is the node ID that married twice. NodeID types.NodeID - // ATX1 is the ID of the ATX being proven to have the marriage certificate of interest. - ATX1 types.ATXID + // ATXID1 is the ID of the ATX being proven to have the marriage certificate of interest. + ATXID1 types.ATXID // SmesherID1 is the ID of the smesher that published ATX1. SmesherID1 types.NodeID // Signature1 is the signature of the ATXID by the smesher. @@ -32,8 +32,8 @@ type ProofDoubleMarry struct { // Proof1 is the proof that the marriage certificate is contained in the ATX1. Proof1 MarryProof - // ATX2 is the ID of the ATX being proven to have the marriage certificate of interest. - ATX2 types.ATXID + // ATXID2 is the ID of the ATX being proven to have the marriage certificate of interest. + ATXID2 types.ATXID // SmesherID2 is the ID of the smesher that published ATX2. SmesherID2 types.NodeID // Signature2 is the signature of the ATXID by the smesher. @@ -53,9 +53,9 @@ func (p ProofDoubleMarry) Type() ProofType { func (p ProofDoubleMarry) Info() map[string]string { return map[string]string{ "node_id": p.NodeID.String(), - "atx1": p.ATX1.String(), + "atx1": p.ATXID1.String(), "smesher_id1": p.SmesherID1.String(), - "atx2": p.ATX2.String(), + "atx2": p.ATXID2.String(), "smesher_id2": p.SmesherID2.String(), } } @@ -79,12 +79,12 @@ func NewDoubleMarryProof(db sql.Executor, atx1, atx2 *ActivationTxV2, nodeID typ return &ProofDoubleMarry{ NodeID: nodeID, - ATX1: atx1.ID(), + ATXID1: atx1.ID(), SmesherID1: atx1.SmesherID, Signature1: atx1.Signature, Proof1: proof1, - ATX2: atx2.ID(), + ATXID2: atx2.ID(), SmesherID2: atx2.SmesherID, Signature2: atx2.Signature, Proof2: proof2, @@ -92,19 +92,19 @@ func NewDoubleMarryProof(db sql.Executor, atx1, atx2 *ActivationTxV2, nodeID typ } func (p ProofDoubleMarry) Valid(_ context.Context, malValidator MalfeasanceValidator) (types.NodeID, error) { - if p.ATX1 == p.ATX2 { + if p.ATXID1 == p.ATXID2 { return types.EmptyNodeID, errors.New("proofs have the same ATX ID") } - if !malValidator.Signature(signing.ATX, p.SmesherID1, p.ATX1.Bytes(), p.Signature1) { + if !malValidator.Signature(signing.ATX, p.SmesherID1, p.ATXID1.Bytes(), p.Signature1) { return types.EmptyNodeID, errors.New("invalid signature for ATX1") } - if !malValidator.Signature(signing.ATX, p.SmesherID2, p.ATX2.Bytes(), p.Signature2) { + if !malValidator.Signature(signing.ATX, p.SmesherID2, p.ATXID2.Bytes(), p.Signature2) { return types.EmptyNodeID, errors.New("invalid signature for ATX2") } - if err := p.Proof1.Valid(malValidator, p.ATX1, p.SmesherID1, p.NodeID); err != nil { + if err := p.Proof1.Valid(malValidator, p.ATXID1, p.SmesherID1, p.NodeID); err != nil { return types.EmptyNodeID, fmt.Errorf("proof 1 is invalid: %w", err) } - if err := p.Proof2.Valid(malValidator, p.ATX2, p.SmesherID2, p.NodeID); err != nil { + if err := p.Proof2.Valid(malValidator, p.ATXID2, p.SmesherID2, p.NodeID); err != nil { return types.EmptyNodeID, fmt.Errorf("proof 2 is invalid: %w", err) } return p.NodeID, nil diff --git a/activation/wire/malfeasance_double_marry_scale.go b/activation/wire/malfeasance_double_marry_scale.go index d7f3855020..89dcf45a25 100644 --- a/activation/wire/malfeasance_double_marry_scale.go +++ b/activation/wire/malfeasance_double_marry_scale.go @@ -16,7 +16,7 @@ func (t *ProofDoubleMarry) EncodeScale(enc *scale.Encoder) (total int, err error total += n } { - n, err := scale.EncodeByteArray(enc, t.ATX1[:]) + n, err := scale.EncodeByteArray(enc, t.ATXID1[:]) if err != nil { return total, err } @@ -44,7 +44,7 @@ func (t *ProofDoubleMarry) EncodeScale(enc *scale.Encoder) (total int, err error total += n } { - n, err := scale.EncodeByteArray(enc, t.ATX2[:]) + n, err := scale.EncodeByteArray(enc, t.ATXID2[:]) if err != nil { return total, err } @@ -83,7 +83,7 @@ func (t *ProofDoubleMarry) DecodeScale(dec *scale.Decoder) (total int, err error total += n } { - n, err := scale.DecodeByteArray(dec, t.ATX1[:]) + n, err := scale.DecodeByteArray(dec, t.ATXID1[:]) if err != nil { return total, err } @@ -111,7 +111,7 @@ func (t *ProofDoubleMarry) DecodeScale(dec *scale.Decoder) (total int, err error total += n } { - n, err := scale.DecodeByteArray(dec, t.ATX2[:]) + n, err := scale.DecodeByteArray(dec, t.ATXID2[:]) if err != nil { return total, err } diff --git a/activation/wire/malfeasance_double_marry_test.go b/activation/wire/malfeasance_double_marry_test.go index d53fc151ee..e3e08975ce 100644 --- a/activation/wire/malfeasance_double_marry_test.go +++ b/activation/wire/malfeasance_double_marry_test.go @@ -117,8 +117,8 @@ func Test_DoubleMarryProof(t *testing.T) { // manually construct an invalid proof proof = &ProofDoubleMarry{ - ATX1: atx1.ID(), - ATX2: atx1.ID(), + ATXID1: atx1.ID(), + ATXID2: atx1.ID(), } ctrl := gomock.NewController(t) @@ -189,17 +189,17 @@ func Test_DoubleMarryProof(t *testing.T) { proof.SmesherID2 = atx2.SmesherID // invalid ATX ID for ATX1 - proof.ATX1 = types.RandomATXID() + proof.ATXID1 = types.RandomATXID() id, err = proof.Valid(context.Background(), verifier) require.ErrorContains(t, err, "invalid signature for ATX1") require.Equal(t, types.EmptyNodeID, id) - proof.ATX1 = atx1.ID() + proof.ATXID1 = atx1.ID() // invalid ATX ID for ATX2 - proof.ATX2 = types.RandomATXID() + proof.ATXID2 = types.RandomATXID() id, err = proof.Valid(context.Background(), verifier) require.ErrorContains(t, err, "invalid signature for ATX2") require.Equal(t, types.EmptyNodeID, id) - proof.ATX2 = atx2.ID() + proof.ATXID2 = atx2.ID() }) } diff --git a/activation/wire/malfeasance_double_merge.go b/activation/wire/malfeasance_double_merge.go index 7b5eb97665..b84f191d6c 100644 --- a/activation/wire/malfeasance_double_merge.go +++ b/activation/wire/malfeasance_double_merge.go @@ -32,8 +32,8 @@ type ProofDoubleMerge struct { // MarriageATXSmesherID is the ID of the smesher that published the marriage ATX. MarriageATXSmesherID types.NodeID - // ATX1 is the ID of the ATX being proven. - ATX1 types.ATXID + // ATXID1 is the ID of the ATX being proven. + ATXID1 types.ATXID // SmesherID1 is the ID of the smesher that published the ATX. SmesherID1 types.NodeID // Signature1 is the signature of the ATXID by the smesher. @@ -45,8 +45,8 @@ type ProofDoubleMerge struct { // SmesherID1MarryProof is the proof that they married in MarriageATX. SmesherID1MarryProof MarryProof - // ATX2 is the ID of the ATX being proven. - ATX2 types.ATXID + // ATXID2 is the ID of the ATX being proven. + ATXID2 types.ATXID // SmesherID is the ID of the smesher that published the ATX. SmesherID2 types.NodeID // Signature2 is the signature of the ATXID by the smesher. @@ -71,9 +71,9 @@ func (p ProofDoubleMerge) Info() map[string]string { return map[string]string{ "publish_epoch": p.PublishEpoch.String(), "marriage_atx": p.MarriageATX.String(), - "atx1": p.ATX1.String(), + "atx1": p.ATXID1.String(), "smesher_id1": p.SmesherID1.String(), - "atx2": p.ATX2.String(), + "atx2": p.ATXID2.String(), "smesher_id2": p.SmesherID2.String(), } } @@ -127,14 +127,14 @@ func NewDoubleMergeProof(db sql.Executor, atx1, atx2 *ActivationTxV2) (*ProofDou MarriageATX: marriageATX.ID(), MarriageATXSmesherID: marriageATX.SmesherID, - ATX1: atx1.ID(), + ATXID1: atx1.ID(), SmesherID1: atx1.SmesherID, Signature1: atx1.Signature, PublishEpochProof1: atx1.PublishEpochProof(), MarriageATXProof1: atx1.MarriageATXProof(), SmesherID1MarryProof: marriageProof1, - ATX2: atx2.ID(), + ATXID2: atx2.ID(), SmesherID2: atx2.SmesherID, Signature2: atx2.Signature, PublishEpochProof2: atx2.PublishEpochProof(), @@ -147,35 +147,35 @@ func NewDoubleMergeProof(db sql.Executor, atx1, atx2 *ActivationTxV2) (*ProofDou func (p *ProofDoubleMerge) Valid(_ context.Context, edVerifier MalfeasanceValidator) (types.NodeID, error) { // 1. The ATXs have different IDs. - if p.ATX1 == p.ATX2 { + if p.ATXID1 == p.ATXID2 { return types.EmptyNodeID, errors.New("ATXs have the same ID") } // 2. Both ATXs have a valid signature. - if !edVerifier.Signature(signing.ATX, p.SmesherID1, p.ATX1.Bytes(), p.Signature1) { + if !edVerifier.Signature(signing.ATX, p.SmesherID1, p.ATXID1.Bytes(), p.Signature1) { return types.EmptyNodeID, errors.New("ATX 1 invalid signature") } - if !edVerifier.Signature(signing.ATX, p.SmesherID2, p.ATX2.Bytes(), p.Signature2) { + if !edVerifier.Signature(signing.ATX, p.SmesherID2, p.ATXID2.Bytes(), p.Signature2) { return types.EmptyNodeID, errors.New("ATX 2 invalid signature") } // 3. and 4. publish epoch is contained in the ATXs - if !p.PublishEpochProof1.Valid(p.ATX1, p.PublishEpoch) { + if !p.PublishEpochProof1.Valid(p.ATXID1, p.PublishEpoch) { return types.EmptyNodeID, errors.New("ATX 1 invalid publish epoch proof") } - if !p.PublishEpochProof2.Valid(p.ATX2, p.PublishEpoch) { + if !p.PublishEpochProof2.Valid(p.ATXID2, p.PublishEpoch) { return types.EmptyNodeID, errors.New("ATX 2 invalid publish epoch proof") } // 5. signers are married - if !p.MarriageATXProof1.Valid(p.ATX1, p.MarriageATX) { + if !p.MarriageATXProof1.Valid(p.ATXID1, p.MarriageATX) { return types.EmptyNodeID, errors.New("ATX 1 invalid marriage ATX proof") } err := p.SmesherID1MarryProof.Valid(edVerifier, p.MarriageATX, p.MarriageATXSmesherID, p.SmesherID1) if err != nil { return types.EmptyNodeID, errors.New("ATX 1 invalid marriage ATX proof") } - if !p.MarriageATXProof2.Valid(p.ATX2, p.MarriageATX) { + if !p.MarriageATXProof2.Valid(p.ATXID2, p.MarriageATX) { return types.EmptyNodeID, errors.New("ATX 2 invalid marriage ATX proof") } err = p.SmesherID2MarryProof.Valid(edVerifier, p.MarriageATX, p.MarriageATXSmesherID, p.SmesherID2) diff --git a/activation/wire/malfeasance_double_merge_scale.go b/activation/wire/malfeasance_double_merge_scale.go index b4d1a55736..d4d38b16f3 100644 --- a/activation/wire/malfeasance_double_merge_scale.go +++ b/activation/wire/malfeasance_double_merge_scale.go @@ -31,7 +31,7 @@ func (t *ProofDoubleMerge) EncodeScale(enc *scale.Encoder) (total int, err error total += n } { - n, err := scale.EncodeByteArray(enc, t.ATX1[:]) + n, err := scale.EncodeByteArray(enc, t.ATXID1[:]) if err != nil { return total, err } @@ -73,7 +73,7 @@ func (t *ProofDoubleMerge) EncodeScale(enc *scale.Encoder) (total int, err error total += n } { - n, err := scale.EncodeByteArray(enc, t.ATX2[:]) + n, err := scale.EncodeByteArray(enc, t.ATXID2[:]) if err != nil { return total, err } @@ -141,7 +141,7 @@ func (t *ProofDoubleMerge) DecodeScale(dec *scale.Decoder) (total int, err error total += n } { - n, err := scale.DecodeByteArray(dec, t.ATX1[:]) + n, err := scale.DecodeByteArray(dec, t.ATXID1[:]) if err != nil { return total, err } @@ -185,7 +185,7 @@ func (t *ProofDoubleMerge) DecodeScale(dec *scale.Decoder) (total int, err error total += n } { - n, err := scale.DecodeByteArray(dec, t.ATX2[:]) + n, err := scale.DecodeByteArray(dec, t.ATXID2[:]) if err != nil { return total, err } diff --git a/activation/wire/malfeasance_double_merge_test.go b/activation/wire/malfeasance_double_merge_test.go index 0f7c43960f..bae6bd5150 100644 --- a/activation/wire/malfeasance_double_merge_test.go +++ b/activation/wire/malfeasance_double_merge_test.go @@ -119,8 +119,8 @@ func Test_DoubleMergeProof(t *testing.T) { require.Nil(t, proof) proof = &ProofDoubleMerge{ - ATX1: atx1.ID(), - ATX2: atx1.ID(), + ATXID1: atx1.ID(), + ATXID2: atx1.ID(), } id, err := proof.Valid(context.Background(), verifier) require.EqualError(t, err, "ATXs have the same ID") @@ -243,20 +243,20 @@ func Test_DoubleMergeProof(t *testing.T) { proof.MarriageATXSmesherID = smesherID // invalid ATX1 ID - id1 := proof.ATX1 - proof.ATX1 = types.RandomATXID() + id1 := proof.ATXID1 + proof.ATXID1 = types.RandomATXID() id, err = proof.Valid(context.Background(), verifier) require.EqualError(t, err, "ATX 1 invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATX1 = id1 + proof.ATXID1 = id1 // invalid ATX2 ID - id2 := proof.ATX2 - proof.ATX2 = types.RandomATXID() + id2 := proof.ATXID2 + proof.ATXID2 = types.RandomATXID() id, err = proof.Valid(context.Background(), verifier) require.EqualError(t, err, "ATX 2 invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATX2 = id2 + proof.ATXID2 = id2 // invalid ATX1 smesher ID smesherID1 := proof.SmesherID1 diff --git a/activation/wire/malfeasance_invalid_post.go b/activation/wire/malfeasance_invalid_post.go index 81d2db2bde..9dfd7a6cd8 100644 --- a/activation/wire/malfeasance_invalid_post.go +++ b/activation/wire/malfeasance_invalid_post.go @@ -22,8 +22,8 @@ import ( // 3. The commitment ATX of NodeID used for the invalid PoST based on their initial ATX. // 4. The provided Post is invalid for the given NodeID. type ProofInvalidPost struct { - // ATX is the ID of the ATX containing the invalid PoST. - ATX types.ATXID + // ATXID is the ID of the ATX containing the invalid PoST. + ATXID types.ATXID // SmesherID is the ID of the smesher that published the ATX. SmesherID types.NodeID // Signature is the signature of the ATXID by the smesher. @@ -49,7 +49,7 @@ func (p ProofInvalidPost) Type() ProofType { func (p ProofInvalidPost) Info() map[string]string { return map[string]string{ - "atx": p.ATX.String(), + "atx": p.ATXID.String(), "index": strconv.FormatUint(uint64(p.InvalidPostProof.InvalidPostIndex), 10), "post_node_id": p.NodeID.String(), "smesher_id": p.SmesherID.String(), @@ -104,7 +104,7 @@ func NewInvalidPostProof( } return &ProofInvalidPost{ - ATX: atx.ID(), + ATXID: atx.ID(), SmesherID: atx.SmesherID, Signature: atx.Signature, @@ -117,7 +117,7 @@ func NewInvalidPostProof( } func (p ProofInvalidPost) Valid(ctx context.Context, malValidator MalfeasanceValidator) (types.NodeID, error) { - if !malValidator.Signature(signing.ATX, p.SmesherID, p.ATX.Bytes(), p.Signature) { + if !malValidator.Signature(signing.ATX, p.SmesherID, p.ATXID.Bytes(), p.Signature) { return types.EmptyNodeID, errors.New("invalid signature") } @@ -127,7 +127,7 @@ func (p ProofInvalidPost) Valid(ctx context.Context, malValidator MalfeasanceVal var marriageIndex *uint32 if p.MarriageProof != nil { - if err := p.MarriageProof.Valid(malValidator, p.ATX, p.NodeID, p.SmesherID); err != nil { + if err := p.MarriageProof.Valid(malValidator, p.ATXID, p.NodeID, p.SmesherID); err != nil { return types.EmptyNodeID, fmt.Errorf("invalid marriage proof: %w", err) } marriageIndex = &p.MarriageProof.NodeIDMarryProof.CertificateIndex @@ -136,7 +136,7 @@ func (p ProofInvalidPost) Valid(ctx context.Context, malValidator MalfeasanceVal if err := p.InvalidPostProof.Valid( ctx, malValidator, - p.ATX, + p.ATXID, p.NodeID, marriageIndex, ); err != nil { diff --git a/activation/wire/malfeasance_invalid_post_scale.go b/activation/wire/malfeasance_invalid_post_scale.go index 0a2388b80c..ffcef75f16 100644 --- a/activation/wire/malfeasance_invalid_post_scale.go +++ b/activation/wire/malfeasance_invalid_post_scale.go @@ -10,7 +10,7 @@ import ( func (t *ProofInvalidPost) EncodeScale(enc *scale.Encoder) (total int, err error) { { - n, err := scale.EncodeByteArray(enc, t.ATX[:]) + n, err := scale.EncodeByteArray(enc, t.ATXID[:]) if err != nil { return total, err } @@ -56,7 +56,7 @@ func (t *ProofInvalidPost) EncodeScale(enc *scale.Encoder) (total int, err error func (t *ProofInvalidPost) DecodeScale(dec *scale.Decoder) (total int, err error) { { - n, err := scale.DecodeByteArray(dec, t.ATX[:]) + n, err := scale.DecodeByteArray(dec, t.ATXID[:]) if err != nil { return total, err } diff --git a/activation/wire/malfeasance_invalid_post_test.go b/activation/wire/malfeasance_invalid_post_test.go index 27a6a2d4ba..9d0880927a 100644 --- a/activation/wire/malfeasance_invalid_post_test.go +++ b/activation/wire/malfeasance_invalid_post_test.go @@ -434,11 +434,11 @@ func Test_InvalidPostProof(t *testing.T) { }).AnyTimes() // invalid ATXID - proof.ATX = types.RandomATXID() + proof.ATXID = types.RandomATXID() id, err := proof.Valid(context.Background(), verifier) require.EqualError(t, err, "invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATX = atx.ID() + proof.ATXID = atx.ID() // invalid smesher ID proof.SmesherID = types.RandomNodeID() @@ -618,11 +618,11 @@ func Test_InvalidPostProof(t *testing.T) { }).AnyTimes() // invalid ATXID - proof.ATX = types.RandomATXID() + proof.ATXID = types.RandomATXID() id, err := proof.Valid(context.Background(), verifier) require.EqualError(t, err, "invalid signature") require.Equal(t, types.EmptyNodeID, id) - proof.ATX = atx.ID() + proof.ATXID = atx.ID() // invalid smesher ID proof.SmesherID = types.RandomNodeID() diff --git a/activation/wire/malfeasance_invalid_prev_atx.go b/activation/wire/malfeasance_invalid_prev_atx.go index fb5645e6d8..69c8558538 100644 --- a/activation/wire/malfeasance_invalid_prev_atx.go +++ b/activation/wire/malfeasance_invalid_prev_atx.go @@ -26,8 +26,8 @@ type ProofInvalidPrevAtxV2 struct { // NodeID is the node ID that referenced the same previous ATX twice. NodeID types.NodeID - // PrevATX is the ATX that was referenced twice. - PrevATX types.ATXID + // PrevATXID is the ATX that was referenced twice. + PrevATXID types.ATXID Proofs [2]InvalidPrevAtxProof } @@ -42,7 +42,7 @@ func (p ProofInvalidPrevAtxV2) Type() ProofType { func (p ProofInvalidPrevAtxV2) Info() map[string]string { return map[string]string{ - "prev_atx": p.PrevATX.String(), + "prev_atx": p.PrevATXID.String(), "node_id": p.NodeID.String(), "atx1": p.Proofs[0].ATXID.String(), "smesher_id1": p.Proofs[0].SmesherID.String(), @@ -133,9 +133,9 @@ func NewInvalidPrevAtxProofV2( } proof := &ProofInvalidPrevAtxV2{ - NodeID: nodeID, - PrevATX: prevATX1, - Proofs: [2]InvalidPrevAtxProof{proof1, proof2}, + NodeID: nodeID, + PrevATXID: prevATX1, + Proofs: [2]InvalidPrevAtxProof{proof1, proof2}, } return proof, nil } @@ -180,10 +180,10 @@ func (p ProofInvalidPrevAtxV2) Valid(_ context.Context, malValidator Malfeasance if p.Proofs[0].ATXID == p.Proofs[1].ATXID { return types.EmptyNodeID, errors.New("proofs have the same ATX ID") } - if err := p.Proofs[0].Valid(p.PrevATX, p.NodeID, malValidator); err != nil { + if err := p.Proofs[0].Valid(p.PrevATXID, p.NodeID, malValidator); err != nil { return types.EmptyNodeID, fmt.Errorf("proof 1 is invalid: %w", err) } - if err := p.Proofs[1].Valid(p.PrevATX, p.NodeID, malValidator); err != nil { + if err := p.Proofs[1].Valid(p.PrevATXID, p.NodeID, malValidator); err != nil { return types.EmptyNodeID, fmt.Errorf("proof 2 is invalid: %w", err) } return p.NodeID, nil @@ -202,8 +202,8 @@ type ProofInvalidPrevAtxV1 struct { // NodeID is the node ID that referenced the same previous ATX twice. NodeID types.NodeID - // PrevATX is the ATX that was referenced twice. - PrevATX types.ATXID + // PrevATXID is the ATX that was referenced twice. + PrevATXID types.ATXID Proof InvalidPrevAtxProof ATXv1 ActivationTxV1 @@ -219,7 +219,7 @@ func (p ProofInvalidPrevAtxV1) Type() ProofType { func (p ProofInvalidPrevAtxV1) Info() map[string]string { return map[string]string{ - "prev_atx": p.PrevATX.String(), + "prev_atx": p.PrevATXID.String(), "node_id": p.NodeID.String(), "atx1": p.Proof.ATXID.String(), "smesher_id1": p.Proof.SmesherID.String(), @@ -278,15 +278,15 @@ func NewInvalidPrevAtxProofV1( } return &ProofInvalidPrevAtxV1{ - NodeID: nodeID, - PrevATX: prevATX1, - Proof: proof, - ATXv1: *atx2, + NodeID: nodeID, + PrevATXID: prevATX1, + Proof: proof, + ATXv1: *atx2, }, nil } func (p ProofInvalidPrevAtxV1) Valid(_ context.Context, malValidator MalfeasanceValidator) (types.NodeID, error) { - if err := p.Proof.Valid(p.PrevATX, p.NodeID, malValidator); err != nil { + if err := p.Proof.Valid(p.PrevATXID, p.NodeID, malValidator); err != nil { return types.EmptyNodeID, fmt.Errorf("proof is invalid: %w", err) } if !malValidator.Signature(signing.ATX, p.ATXv1.SmesherID, p.ATXv1.SignedBytes(), p.ATXv1.Signature) { @@ -295,7 +295,7 @@ func (p ProofInvalidPrevAtxV1) Valid(_ context.Context, malValidator Malfeasance if p.NodeID != p.ATXv1.SmesherID { return types.EmptyNodeID, errors.New("ATXv1 has not been signed by the same identity") } - if p.ATXv1.PrevATXID != p.PrevATX { + if p.ATXv1.PrevATXID != p.PrevATXID { return types.EmptyNodeID, errors.New("ATXv1 references a different previous ATX") } return p.NodeID, nil diff --git a/activation/wire/malfeasance_invalid_prev_atx_scale.go b/activation/wire/malfeasance_invalid_prev_atx_scale.go index 15b06acc28..f2c455b2cd 100644 --- a/activation/wire/malfeasance_invalid_prev_atx_scale.go +++ b/activation/wire/malfeasance_invalid_prev_atx_scale.go @@ -17,7 +17,7 @@ func (t *ProofInvalidPrevAtxV2) EncodeScale(enc *scale.Encoder) (total int, err total += n } { - n, err := scale.EncodeByteArray(enc, t.PrevATX[:]) + n, err := scale.EncodeByteArray(enc, t.PrevATXID[:]) if err != nil { return total, err } @@ -42,7 +42,7 @@ func (t *ProofInvalidPrevAtxV2) DecodeScale(dec *scale.Decoder) (total int, err total += n } { - n, err := scale.DecodeByteArray(dec, t.PrevATX[:]) + n, err := scale.DecodeByteArray(dec, t.PrevATXID[:]) if err != nil { return total, err } @@ -67,7 +67,7 @@ func (t *ProofInvalidPrevAtxV1) EncodeScale(enc *scale.Encoder) (total int, err total += n } { - n, err := scale.EncodeByteArray(enc, t.PrevATX[:]) + n, err := scale.EncodeByteArray(enc, t.PrevATXID[:]) if err != nil { return total, err } @@ -99,7 +99,7 @@ func (t *ProofInvalidPrevAtxV1) DecodeScale(dec *scale.Decoder) (total int, err total += n } { - n, err := scale.DecodeByteArray(dec, t.PrevATX[:]) + n, err := scale.DecodeByteArray(dec, t.PrevATXID[:]) if err != nil { return total, err } diff --git a/activation/wire/malfeasance_invalid_prev_atx_test.go b/activation/wire/malfeasance_invalid_prev_atx_test.go index 3047706ed0..9f40ad6425 100644 --- a/activation/wire/malfeasance_invalid_prev_atx_test.go +++ b/activation/wire/malfeasance_invalid_prev_atx_test.go @@ -317,11 +317,11 @@ func Test_InvalidPrevAtxProofV2(t *testing.T) { proof.Proofs[0].ATXID = atx1.ID() // invalid prev ATX - proof.PrevATX = types.RandomATXID() + proof.PrevATXID = types.RandomATXID() id, err = proof.Valid(context.Background(), verifier) require.ErrorContains(t, err, "invalid previous ATX proof") require.Equal(t, types.EmptyNodeID, id) - proof.PrevATX = prevATXID + proof.PrevATXID = prevATXID // invalid node ID proof.NodeID = types.RandomNodeID() @@ -935,11 +935,11 @@ func Test_InvalidPrevAtxProofV1(t *testing.T) { }).AnyTimes() // invalid PrevATX - proof.PrevATX = types.RandomATXID() + proof.PrevATXID = types.RandomATXID() id, err := proof.Valid(context.Background(), verifier) require.ErrorContains(t, err, "invalid previous ATX proof") require.Equal(t, types.EmptyNodeID, id) - proof.PrevATX = prevATX + proof.PrevATXID = prevATX // invalid SmesherID for atxv1 proof.ATXv1.SmesherID = types.RandomNodeID() From 7d4afd977de5f0f5b2c7f185e09398237b3b1a67 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:05:23 +0000 Subject: [PATCH 50/66] Just pass nodeID in malfeasance events and fetch proof from DB --- api/grpcserver/activation_service.go | 38 +++++++++++++++++- api/grpcserver/activation_service_test.go | 40 +++++++++---------- api/grpcserver/mesh_service.go | 17 +++++--- api/grpcserver/mesh_service_test.go | 12 +++--- api/grpcserver/v2alpha1/malfeasance_test.go | 4 +- events/events.go | 44 +++------------------ events/malfeasance.go | 5 +-- malfeasance/handler.go | 10 ++--- malfeasance2/handler.go | 10 ++--- 9 files changed, 93 insertions(+), 87 deletions(-) diff --git a/api/grpcserver/activation_service.go b/api/grpcserver/activation_service.go index 3db73e8625..0db997d970 100644 --- a/api/grpcserver/activation_service.go +++ b/api/grpcserver/activation_service.go @@ -14,8 +14,9 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/events" + "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/sql" ) @@ -85,7 +86,7 @@ func (s *activationService) Get(ctx context.Context, request *pb.GetRequest) (*p Atx: convertActivation(atx, prev), } if proof != nil { - resp.MalfeasanceProof = events.ToMalfeasancePB(atx.SmesherID, proof, false) + resp.MalfeasanceProof = toMalfeasancePB(atx.SmesherID, proof, false) } return resp, nil } @@ -116,3 +117,36 @@ func (s *activationService) Highest(ctx context.Context, req *emptypb.Empty) (*p Atx: convertActivation(atx, prev), }, nil } + +// TODO(mafa): instead of passing along the proof bytes the API should query the malfeasance handler for the metadata +// of the proof if needed. +// The malfeasance handler should then take care of decoding the proof, caching if necessary and returning the metadata. +func toMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof { + mp := &wire.MalfeasanceProof{} + if err := codec.Decode(proof, mp); err != nil { + return &pb.MalfeasanceProof{} + } + kind := pb.MalfeasanceProof_MALFEASANCE_UNSPECIFIED + switch mp.Proof.Type { + case wire.MultipleATXs: + kind = pb.MalfeasanceProof_MALFEASANCE_ATX + case wire.MultipleBallots: + kind = pb.MalfeasanceProof_MALFEASANCE_BALLOT + case wire.HareEquivocation: + kind = pb.MalfeasanceProof_MALFEASANCE_HARE + case wire.InvalidPostIndex: + kind = pb.MalfeasanceProof_MALFEASANCE_POST_INDEX + case wire.InvalidPrevATX: + kind = pb.MalfeasanceProof_MALFEASANCE_INCORRECT_PREV_ATX + } + result := &pb.MalfeasanceProof{ + SmesherId: &pb.SmesherId{Id: nodeID.Bytes()}, + Layer: &pb.LayerNumber{Number: mp.Layer.Uint32()}, + Kind: kind, + DebugInfo: wire.MalfeasanceInfo(nodeID, mp), + } + if includeProof { + result.Proof = proof + } + return result +} diff --git a/api/grpcserver/activation_service_test.go b/api/grpcserver/activation_service_test.go index b720026d96..6d65524c03 100644 --- a/api/grpcserver/activation_service_test.go +++ b/api/grpcserver/activation_service_test.go @@ -1,4 +1,4 @@ -package grpcserver_test +package grpcserver import ( "context" @@ -13,19 +13,17 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" - "github.com/spacemeshos/go-spacemesh/api/grpcserver" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/statesql" ) func Test_Highest_ReturnsGoldenAtxOnError(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) + atxProvider := NewMockatxProvider(ctrl) goldenAtx := types.ATXID{2, 3, 4} - activationService := grpcserver.NewActivationService(atxProvider, goldenAtx) + activationService := NewActivationService(atxProvider, goldenAtx) atxProvider.EXPECT().MaxHeightAtx().Return(types.EmptyATXID, errors.New("blah")) response, err := activationService.Highest(context.Background(), &emptypb.Empty{}) @@ -41,9 +39,9 @@ func Test_Highest_ReturnsGoldenAtxOnError(t *testing.T) { func Test_Highest_ReturnsMaxTickHeight(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) + atxProvider := NewMockatxProvider(ctrl) goldenAtx := types.ATXID{2, 3, 4} - activationService := grpcserver.NewActivationService(atxProvider, goldenAtx) + activationService := NewActivationService(atxProvider, goldenAtx) previous := types.RandomATXID() atx := types.ActivationTx{ @@ -71,8 +69,8 @@ func Test_Highest_ReturnsMaxTickHeight(t *testing.T) { func TestGet_RejectInvalidAtxID(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) _, err := activationService.Get(context.Background(), &pb.GetRequest{Id: []byte{1, 2, 3}}) require.Error(t, err) @@ -81,8 +79,8 @@ func TestGet_RejectInvalidAtxID(t *testing.T) { func TestGet_AtxNotPresent(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) id := types.RandomATXID() atxProvider.EXPECT().GetAtx(id).Return(nil, nil) @@ -94,8 +92,8 @@ func TestGet_AtxNotPresent(t *testing.T) { func TestGet_AtxProviderReturnsFailure(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) id := types.RandomATXID() atxProvider.EXPECT().GetAtx(id).Return(&types.ActivationTx{}, errors.New("")) @@ -107,8 +105,8 @@ func TestGet_AtxProviderReturnsFailure(t *testing.T) { func TestGet_AtxProviderFailsObtainPreviousAtxs(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) id := types.RandomATXID() atxProvider.EXPECT().GetAtx(id).Return(&types.ActivationTx{}, nil) @@ -121,8 +119,8 @@ func TestGet_AtxProviderFailsObtainPreviousAtxs(t *testing.T) { func TestGet_HappyPath(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) previous := []types.ATXID{types.RandomATXID(), types.RandomATXID()} id := types.RandomATXID() @@ -155,10 +153,10 @@ func TestGet_HappyPath(t *testing.T) { func TestGet_IdentityCanceled(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) - smesher, proof := grpcserver.BallotMalfeasance(t, statesql.InMemoryTest(t)) + smesher, proof := BallotMalfeasance(t, statesql.InMemoryTest(t)) previous := types.RandomATXID() id := types.RandomATXID() atx := types.ActivationTx{ @@ -185,5 +183,5 @@ func TestGet_IdentityCanceled(t *testing.T) { require.Equal(t, previous.Bytes(), response.Atx.PreviousAtxs[0].Id) require.Equal(t, atx.NumUnits, response.Atx.NumUnits) require.Equal(t, atx.Sequence, response.Atx.Sequence) - require.Equal(t, events.ToMalfeasancePB(smesher, codec.MustEncode(proof), false), response.MalfeasanceProof) + require.Equal(t, toMalfeasancePB(smesher, codec.MustEncode(proof), false), response.MalfeasanceProof) } diff --git a/api/grpcserver/mesh_service.go b/api/grpcserver/mesh_service.go index d27693d4fa..0632dcba33 100644 --- a/api/grpcserver/mesh_service.go +++ b/api/grpcserver/mesh_service.go @@ -21,6 +21,7 @@ import ( "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" + "github.com/spacemeshos/go-spacemesh/sql/identities" ) // MeshService exposes mesh data such as accounts, blocks, and transactions. @@ -614,9 +615,8 @@ func (s *MeshService) MalfeasanceQuery( if err != nil && !errors.Is(err, sql.ErrNotFound) { return nil, status.Error(codes.Internal, err.Error()) } - // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes return &pb.MalfeasanceResponse{ - Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof), + Proof: toMalfeasancePB(id, proof, req.IncludeProof), }, nil } @@ -639,9 +639,8 @@ func (s *MeshService) MalfeasanceStream( case <-stream.Context().Done(): return nil default: - // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes res := &pb.MalfeasanceStreamResponse{ - Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof), + Proof: toMalfeasancePB(id, proof, req.IncludeProof), } return stream.Send(res) } @@ -656,9 +655,15 @@ func (s *MeshService) MalfeasanceStream( case <-fullCh: return status.Errorf(codes.Canceled, "buffer is full") case ev := <-eventCh: - // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes + var blob sql.Blob + if err := identities.LoadMalfeasanceBlob(stream.Context(), s.cdb, ev.Smesher.Bytes(), &blob); err != nil { + return status.Error( + codes.Internal, + fmt.Errorf("load malfeasance proof for %s: %w", ev.Smesher.ShortString(), err).Error(), + ) + } if err := stream.Send(&pb.MalfeasanceStreamResponse{ - Proof: events.ToMalfeasancePB(ev.Smesher, ev.Proof, req.IncludeProof), + Proof: toMalfeasancePB(ev.Smesher, blob.Bytes, req.IncludeProof), }); err != nil { return status.Error(codes.Internal, fmt.Errorf("send to stream: %w", err).Error()) } diff --git a/api/grpcserver/mesh_service_test.go b/api/grpcserver/mesh_service_test.go index e08f23c045..d87f027261 100644 --- a/api/grpcserver/mesh_service_test.go +++ b/api/grpcserver/mesh_service_test.go @@ -177,7 +177,7 @@ func TestMeshService_MalfeasanceQuery(t *testing.T) { require.Equal(t, nodeID, types.BytesToNodeID(resp.Proof.SmesherId.Id)) require.EqualValues(t, layer, resp.Proof.Layer.Number) require.Equal(t, pb.MalfeasanceProof_MALFEASANCE_BALLOT, resp.Proof.Kind) - require.Equal(t, events.ToMalfeasancePB(nodeID, codec.MustEncode(proof), true), resp.Proof) + require.Equal(t, toMalfeasancePB(nodeID, codec.MustEncode(proof), true), resp.Proof) require.NotEmpty(t, resp.Proof.Proof) var got wire.MalfeasanceProof require.NoError(t, codec.Decode(resp.Proof.Proof, &got)) @@ -251,16 +251,18 @@ func TestMeshService_MalfeasanceStream(t *testing.T) { id, proof := AtxMalfeasance(t, db) proofBytes := codec.MustEncode(proof) - events.ReportMalfeasance(id, proofBytes) + require.NoError(t, identities.SetMalicious(db, id, proofBytes, time.Now())) + events.ReportMalfeasance(id) resp, err := stream.Recv() require.NoError(t, err) - require.Equal(t, events.ToMalfeasancePB(id, proofBytes, false), resp.Proof) + require.Equal(t, toMalfeasancePB(id, proofBytes, false), resp.Proof) id, proof = BallotMalfeasance(t, db) proofBytes = codec.MustEncode(proof) - events.ReportMalfeasance(id, proofBytes) + require.NoError(t, identities.SetMalicious(db, id, proofBytes, time.Now())) + events.ReportMalfeasance(id) resp, err = stream.Recv() require.NoError(t, err) - require.Equal(t, events.ToMalfeasancePB(id, proofBytes, false), resp.Proof) + require.Equal(t, toMalfeasancePB(id, proofBytes, false), resp.Proof) } type MeshAPIMockInstrumented struct { diff --git a/api/grpcserver/v2alpha1/malfeasance_test.go b/api/grpcserver/v2alpha1/malfeasance_test.go index dbb515ff47..cfd48acf20 100644 --- a/api/grpcserver/v2alpha1/malfeasance_test.go +++ b/api/grpcserver/v2alpha1/malfeasance_test.go @@ -221,7 +221,6 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { smesher := types.RandomNodeID() streamed = append(streamed, &events.EventMalfeasance{ Smesher: smesher, - Proof: types.RandomBytes(100), }) properties := map[string]string{ "domain": "0", @@ -242,7 +241,8 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { var expect []types.NodeID for _, rst := range streamed { - events.ReportMalfeasance(rst.Smesher, rst.Proof) + require.NoError(t, identities.SetMalicious(db, rst.Smesher, types.RandomBytes(100), time.Now())) + events.ReportMalfeasance(rst.Smesher) matcher := malfeasanceMatcher{request} if matcher.match(rst) { expect = append(expect, rst.Smesher) diff --git a/events/events.go b/events/events.go index 8827f10274..80b2f1ab67 100644 --- a/events/events.go +++ b/events/events.go @@ -7,10 +7,8 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/log" - "github.com/spacemeshos/go-spacemesh/malfeasance/wire" ) type UserEvent struct { @@ -339,15 +337,18 @@ func EmitProposal(nodeID types.NodeID, layer types.LayerID, proposal types.Propo ) } -func EmitOwnMalfeasanceProof(nodeID types.NodeID, proof []byte) { - // TODO(mafa): query malfeasance handler for data instead of extracting from proof bytes +func EmitOwnMalfeasanceProof(nodeID types.NodeID) { const help = "Node committed malicious behavior. Identity will be canceled." emitUserEvent( help, false, &pb.Event_Malfeasance{ Malfeasance: &pb.EventMalfeasance{ - Proof: ToMalfeasancePB(nodeID, proof, false), + Proof: &pb.MalfeasanceProof{ + SmesherId: &pb.SmesherId{Id: nodeID.Bytes()}, + Layer: &pb.LayerNumber{Number: uint32(0)}, + Kind: pb.MalfeasanceProof_MALFEASANCE_UNSPECIFIED, + }, }, }, ) @@ -367,36 +368,3 @@ func emitUserEvent(help string, failure bool, details pb.IsEventDetails) { } } } - -// TODO(mafa): instead of passing along the proof bytes the API should query the malfeasance handler for the metadata -// of the proof if needed. -// The malfeasance handler should then take care of decoding the proof, caching if necessary and returning the metadata. -func ToMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof { - mp := &wire.MalfeasanceProof{} - if err := codec.Decode(proof, mp); err != nil { - return &pb.MalfeasanceProof{} - } - kind := pb.MalfeasanceProof_MALFEASANCE_UNSPECIFIED - switch mp.Proof.Type { - case wire.MultipleATXs: - kind = pb.MalfeasanceProof_MALFEASANCE_ATX - case wire.MultipleBallots: - kind = pb.MalfeasanceProof_MALFEASANCE_BALLOT - case wire.HareEquivocation: - kind = pb.MalfeasanceProof_MALFEASANCE_HARE - case wire.InvalidPostIndex: - kind = pb.MalfeasanceProof_MALFEASANCE_POST_INDEX - case wire.InvalidPrevATX: - kind = pb.MalfeasanceProof_MALFEASANCE_INCORRECT_PREV_ATX - } - result := &pb.MalfeasanceProof{ - SmesherId: &pb.SmesherId{Id: nodeID.Bytes()}, - Layer: &pb.LayerNumber{Number: mp.Layer.Uint32()}, - Kind: kind, - DebugInfo: wire.MalfeasanceInfo(nodeID, mp), - } - if includeProof { - result.Proof = proof - } - return result -} diff --git a/events/malfeasance.go b/events/malfeasance.go index ac0f15e7ce..ba789881d1 100644 --- a/events/malfeasance.go +++ b/events/malfeasance.go @@ -8,7 +8,6 @@ import ( // EventMalfeasance includes the malfeasance proof. type EventMalfeasance struct { Smesher types.NodeID - Proof []byte // TODO(mafa): remove this field and fetch metadata via malfeasance handler } // SubscribeMalfeasance subscribes malfeasance events. @@ -26,11 +25,11 @@ func SubscribeMalfeasance() Subscription { } // ReportMalfeasance reports a malfeasance proof. -func ReportMalfeasance(nodeID types.NodeID, proof []byte) { +func ReportMalfeasance(nodeID types.NodeID) { mu.RLock() defer mu.RUnlock() if reporter != nil { - if err := reporter.malfeasanceEmitter.Emit(EventMalfeasance{Smesher: nodeID, Proof: proof}); err != nil { + if err := reporter.malfeasanceEmitter.Emit(EventMalfeasance{Smesher: nodeID}); err != nil { log.With().Error("failed to emit malfeasance proof", log.Err(err)) } } diff --git a/malfeasance/handler.go b/malfeasance/handler.go index be2abbc8cb..95ccbb3ad3 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -77,11 +77,11 @@ func (h *Handler) RegisterHandler(malfeasanceType MalfeasanceType, handler Malfe h.handlers[malfeasanceType] = handler } -func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { +func (h *Handler) reportMalfeasance(smesher types.NodeID) { h.tortoise.OnMalfeasance(smesher) - events.ReportMalfeasance(smesher, proof) + events.ReportMalfeasance(smesher) if slices.Contains(h.nodeIDs, smesher) { - events.EmitOwnMalfeasanceProof(smesher, proof) + events.EmitOwnMalfeasanceProof(smesher) } } @@ -177,7 +177,7 @@ func (h *Handler) HandleMalfeasanceProof(ctx context.Context, peer p2p.Peer, dat h.countInvalidProof(&p.MalfeasanceProof) return fmt.Errorf("%w: %s", pubsub.ErrValidationReject, err) } - h.reportMalfeasance(id, codec.MustEncode(&p.MalfeasanceProof)) + h.reportMalfeasance(id) // node saves malfeasance proof eagerly/atomically with the malicious data. // it has validated the proof before saving to db. h.countProof(&p.MalfeasanceProof) @@ -226,7 +226,7 @@ func (h *Handler) validateAndSave(ctx context.Context, p *wire.MalfeasanceProof) } return nodeID, err } - h.reportMalfeasance(nodeID, proofBytes) + h.reportMalfeasance(nodeID) h.cdb.CacheMalfeasanceProof(nodeID, proofBytes) h.countProof(p) h.logger.Debug("new malfeasance proof", diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go index f10cda0fe6..1c4750d396 100644 --- a/malfeasance2/handler.go +++ b/malfeasance2/handler.go @@ -77,11 +77,11 @@ func (h *Handler) countInvalidProof(mp MalfeasanceProof) { numInvalidProofs.WithLabelValues(labels...).Inc() } -func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { +func (h *Handler) reportMalfeasance(smesher types.NodeID) { h.tortoise.OnMalfeasance(smesher) - events.ReportMalfeasance(smesher, proof) + events.ReportMalfeasance(smesher) if slices.Contains(h.nodeIDs, smesher) { - events.EmitOwnMalfeasanceProof(smesher, proof) + events.EmitOwnMalfeasanceProof(smesher) } } @@ -158,7 +158,7 @@ func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, peer p } for _, id := range nodeIDs { - h.reportMalfeasance(id, msg) + h.reportMalfeasance(id) } h.countProof(proof) h.logger.Debug("synced malfeasance proof", @@ -196,7 +196,7 @@ func (h *Handler) HandleGossip(ctx context.Context, peer p2p.Peer, msg []byte) e } for _, id := range nodeIDs { - h.reportMalfeasance(id, msg) + h.reportMalfeasance(id) } h.countProof(proof) h.logger.Debug("received gossiped malfeasance proof", From c66ed360bb50c3dc292127558833af54ac0a366b Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:09:51 +0000 Subject: [PATCH 51/66] Cleanup --- api/grpcserver/activation_service.go | 3 --- api/grpcserver/mesh_service.go | 7 +++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/api/grpcserver/activation_service.go b/api/grpcserver/activation_service.go index 0db997d970..08fbecb2df 100644 --- a/api/grpcserver/activation_service.go +++ b/api/grpcserver/activation_service.go @@ -118,9 +118,6 @@ func (s *activationService) Highest(ctx context.Context, req *emptypb.Empty) (*p }, nil } -// TODO(mafa): instead of passing along the proof bytes the API should query the malfeasance handler for the metadata -// of the proof if needed. -// The malfeasance handler should then take care of decoding the proof, caching if necessary and returning the metadata. func toMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof { mp := &wire.MalfeasanceProof{} if err := codec.Decode(proof, mp); err != nil { diff --git a/api/grpcserver/mesh_service.go b/api/grpcserver/mesh_service.go index 0632dcba33..5243dbd411 100644 --- a/api/grpcserver/mesh_service.go +++ b/api/grpcserver/mesh_service.go @@ -21,7 +21,6 @@ import ( "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" - "github.com/spacemeshos/go-spacemesh/sql/identities" ) // MeshService exposes mesh data such as accounts, blocks, and transactions. @@ -655,15 +654,15 @@ func (s *MeshService) MalfeasanceStream( case <-fullCh: return status.Errorf(codes.Canceled, "buffer is full") case ev := <-eventCh: - var blob sql.Blob - if err := identities.LoadMalfeasanceBlob(stream.Context(), s.cdb, ev.Smesher.Bytes(), &blob); err != nil { + proof, err := s.cdb.MalfeasanceProof(ev.Smesher) + if err != nil { return status.Error( codes.Internal, fmt.Errorf("load malfeasance proof for %s: %w", ev.Smesher.ShortString(), err).Error(), ) } if err := stream.Send(&pb.MalfeasanceStreamResponse{ - Proof: toMalfeasancePB(ev.Smesher, blob.Bytes, req.IncludeProof), + Proof: toMalfeasancePB(ev.Smesher, proof, req.IncludeProof), }); err != nil { return status.Error(codes.Internal, fmt.Errorf("send to stream: %w", err).Error()) } From 9c27b746fe27ec4101036b1d8d89cfb86e7bdd78 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:52:39 +0000 Subject: [PATCH 52/66] Finalize GRPC Api --- api/grpcserver/v2alpha1/malfeasance.go | 234 +++++++++------ api/grpcserver/v2alpha1/malfeasance_test.go | 302 ++++++++++++++------ go.mod | 8 +- go.sum | 16 +- sql/identities/identities.go | 13 + sql/identities/identities_test.go | 15 + sql/malfeasance/malfeasance.go | 27 ++ sql/malfeasance/malfeasance_test.go | 108 +++++++ 8 files changed, 535 insertions(+), 188 deletions(-) diff --git a/api/grpcserver/v2alpha1/malfeasance.go b/api/grpcserver/v2alpha1/malfeasance.go index 8af4b13b94..d820d6e743 100644 --- a/api/grpcserver/v2alpha1/malfeasance.go +++ b/api/grpcserver/v2alpha1/malfeasance.go @@ -23,6 +23,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/builder" "github.com/spacemeshos/go-spacemesh/sql/identities" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" ) const ( @@ -56,7 +57,6 @@ func (s *MalfeasanceService) String() string { return "MalfeasanceService" } -// TODO(mafa): add a ListV2 method to return the new malfeasance proofs. func (s *MalfeasanceService) List( ctx context.Context, request *spacemeshv2alpha1.MalfeasanceRequest, @@ -68,24 +68,38 @@ func (s *MalfeasanceService) List( return nil, status.Error(codes.InvalidArgument, "limit must be set to <= 100") } - ops, err := toMalfeasanceOps(request) + legacyCount, err := identities.CountMalicious(s.db) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Internal, err.Error()) } - proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, request.Limit) - if err := identities.IterateOps(s.db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { - rst := toProof(ctx, s.infoLegacy, id) - if rst == nil { - return true + switch { + case request.Offset+request.Limit < legacyCount: // only legacy proofs + proofs, err := fetchLegacyFromDB(ctx, s.db, s.infoLegacy, request) + if err != nil { + return nil, err } - proofs = append(proofs, rst) - return true - }); err != nil { - return nil, status.Error(codes.Internal, err.Error()) + return &spacemeshv2alpha1.MalfeasanceList{Proofs: proofs}, nil + case request.Offset >= legacyCount: // only new proofs + request.Offset -= legacyCount + proofs, err := fetchFromDB(ctx, s.db, s.info, request) + if err != nil { + return nil, err + } + return &spacemeshv2alpha1.MalfeasanceList{Proofs: proofs}, nil + default: // both legacy and new proofs + legacyProofs, err := fetchLegacyFromDB(ctx, s.db, s.infoLegacy, request) + if err != nil { + return nil, err + } + request.Limit -= uint64(len(legacyProofs)) + request.Offset = 0 + proofs, err := fetchFromDB(ctx, s.db, s.info, request) + if err != nil { + return nil, err + } + return &spacemeshv2alpha1.MalfeasanceList{Proofs: append(legacyProofs, proofs...)}, nil } - - return &spacemeshv2alpha1.MalfeasanceList{Proofs: proofs}, nil } func NewMalfeasanceStreamService( @@ -118,52 +132,78 @@ func (s *MalfeasanceStreamService) String() string { return "MalfeasanceStreamService" } -// TODO(mafa): add a StreamV2 method to return the new malfeasance proofs func (s *MalfeasanceStreamService) Stream( request *spacemeshv2alpha1.MalfeasanceStreamRequest, stream spacemeshv2alpha1.MalfeasanceStreamService_StreamServer, ) error { - var sub *events.BufferedSubscription[events.EventMalfeasance] - if request.Watch { - matcher := malfeasanceMatcher{request} - var err error - sub, err = events.SubscribeMatched(matcher.match) - if err != nil { + if err := stream.SendHeader(metadata.MD{}); err != nil { + return status.Errorf(codes.Unavailable, "can't send header") + } + + legacyProofs, err := fetchLegacyFromDB( + stream.Context(), + s.db, + s.infoLegacy, + &spacemeshv2alpha1.MalfeasanceRequest{SmesherId: request.SmesherId}, + ) + if err != nil { + return err + } + for _, rst := range legacyProofs { + err := stream.Send(rst) + switch { + case errors.Is(err, io.EOF): + return nil + case err != nil: return status.Error(codes.Internal, err.Error()) } - defer sub.Close() - if err := stream.SendHeader(metadata.MD{}); err != nil { - return status.Errorf(codes.Unavailable, "can't send header") - } } - ops, err := toMalfeasanceOps(&spacemeshv2alpha1.MalfeasanceRequest{ - SmesherId: request.SmesherId, - }) + proofs, err := fetchFromDB( + stream.Context(), + s.db, + s.info, + &spacemeshv2alpha1.MalfeasanceRequest{SmesherId: request.SmesherId}, + ) if err != nil { - return status.Error(codes.InvalidArgument, err.Error()) + return err + } + for _, rst := range proofs { + err := stream.Send(rst) + switch { + case errors.Is(err, io.EOF): + return nil + case err != nil: + return status.Error(codes.Internal, err.Error()) + } } - ctx, cancel := context.WithCancel(stream.Context()) - defer cancel() - dbChan, errChan := s.fetchFromDB(ctx, ops) + if !request.Watch { + return nil + } - var eventsOut <-chan events.EventMalfeasance - var eventsFull <-chan struct{} - if sub != nil { - eventsOut = sub.Out() - eventsFull = sub.Full() + matcher := malfeasanceMatcher{request} + sub, err := events.SubscribeMatched(matcher.match) + if err != nil { + return status.Error(codes.Internal, err.Error()) } + defer sub.Close() + eventsOut := sub.Out() + eventsFull := sub.Full() for { select { - // process events first + // process pending events first case rst := <-eventsOut: proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { - continue + // try again with the new handler + proof = toProof(stream.Context(), s.info, rst.Smesher) + if proof == nil { + continue + } } - err = stream.Send(proof) + err := stream.Send(proof) switch { case errors.Is(err, io.EOF): return nil @@ -175,9 +215,13 @@ func (s *MalfeasanceStreamService) Stream( case rst := <-eventsOut: proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { - continue + // try again with the new handler + proof = toProof(stream.Context(), s.info, rst.Smesher) + if proof == nil { + continue + } } - err = stream.Send(proof) + err := stream.Send(proof) switch { case errors.Is(err, io.EOF): return nil @@ -186,23 +230,6 @@ func (s *MalfeasanceStreamService) Stream( } case <-eventsFull: return status.Error(codes.Canceled, "buffer overflow") - case rst, ok := <-dbChan: - if !ok { - dbChan = nil - if sub == nil { - return nil - } - continue - } - err = stream.Send(rst) - switch { - case errors.Is(err, io.EOF): - return nil - case err != nil: - return status.Error(codes.Internal, err.Error()) - } - case err := <-errChan: - return err case <-stream.Context().Done(): return nil } @@ -210,35 +237,6 @@ func (s *MalfeasanceStreamService) Stream( } } -func (s *MalfeasanceStreamService) fetchFromDB( - ctx context.Context, - ops builder.Operations, -) (<-chan *spacemeshv2alpha1.MalfeasanceProof, <-chan error) { - dbChan := make(chan *spacemeshv2alpha1.MalfeasanceProof) - errChan := make(chan error, 1) // buffered to avoid blocking, routine should exit immediately after sending an error - - go func() { - defer close(dbChan) - if err := identities.IterateOps(s.db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { - rst := toProof(ctx, s.infoLegacy, id) - if rst == nil { - return true - } - - select { - case dbChan <- rst: - return true - case <-ctx.Done(): - // exit if the context is canceled - return false - } - }); err != nil { - errChan <- status.Error(codes.Internal, err.Error()) - } - }() - return dbChan, errChan -} - func toProof( ctx context.Context, info malfeasanceInfo, @@ -280,12 +278,64 @@ func toProof( } } +func fetchFromDB( + ctx context.Context, + db sql.Executor, + info malfeasanceInfo, + request *spacemeshv2alpha1.MalfeasanceRequest, +) ([]*spacemeshv2alpha1.MalfeasanceProof, error) { + ops, err := toMalfeasanceOps(request) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + ids := make([]types.NodeID, 0, request.Limit) + if err := malfeasance.IterateOps(db, ops, func(id types.NodeID, _ []byte, _ byte, _ time.Time) bool { + ids = append(ids, id) + return true + }); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, len(ids)) + for _, id := range ids { + rst := toProof(ctx, info, id) + if rst == nil { + continue + } + proofs = append(proofs, rst) + } + return proofs, nil +} + +func fetchLegacyFromDB( + ctx context.Context, + db sql.Executor, + info malfeasanceInfo, + request *spacemeshv2alpha1.MalfeasanceRequest, +) ([]*spacemeshv2alpha1.MalfeasanceProof, error) { + ops, err := toMalfeasanceOps(request) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + ids := make([]types.NodeID, 0, request.Limit) + if err := identities.IterateOps(db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { + ids = append(ids, id) + return true + }); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, len(ids)) + for _, id := range ids { + rst := toProof(ctx, info, id) + if rst == nil { + continue + } + proofs = append(proofs, rst) + } + return proofs, nil +} + func toMalfeasanceOps(filter *spacemeshv2alpha1.MalfeasanceRequest) (builder.Operations, error) { ops := builder.Operations{} - ops.Filter = append(ops.Filter, builder.Op{ - Field: builder.Proof, - Token: builder.IsNotNull, - }) ops.Modifiers = append(ops.Modifiers, builder.Modifier{ Key: builder.OrderBy, Value: builder.Smesher, diff --git a/api/grpcserver/v2alpha1/malfeasance_test.go b/api/grpcserver/v2alpha1/malfeasance_test.go index cfd48acf20..4a5f86c369 100644 --- a/api/grpcserver/v2alpha1/malfeasance_test.go +++ b/api/grpcserver/v2alpha1/malfeasance_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "maps" "strconv" "testing" "time" @@ -20,6 +21,8 @@ import ( "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/identities" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" + "github.com/spacemeshos/go-spacemesh/sql/marriage" "github.com/spacemeshos/go-spacemesh/sql/statesql" ) @@ -31,34 +34,93 @@ type malInfo struct { } func TestMalfeasanceService_List(t *testing.T) { - setup := func(t *testing.T) (spacemeshv2alpha1.MalfeasanceServiceClient, []malInfo) { - db := statesql.InMemoryTest(t) - ctrl := gomock.NewController(t) - info := NewMockmalfeasanceInfo(ctrl) - - proofs := make([]malInfo, 90) - for i := range proofs { - proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} - proofs[i].Properties = map[string]string{ - "domain": "0", - "type": strconv.FormatUint(uint64(i%4+1), 10), - fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), - } - info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() - - require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) + db := statesql.InMemoryTest(t) + ctrl := gomock.NewController(t) + info := NewMockmalfeasanceInfo(ctrl) + legacyInfo := NewMockmalfeasanceInfo(ctrl) + + proofs := make([]malInfo, 90) + + // first 20 are legacy proofs + for i := range 20 { + proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + proofs[i].Properties = map[string]string{ + "domain": "0", + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } + info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) + } - svc := NewMalfeasanceService(db, nil, info) - cfg, cleanup := launchServer(t, svc) - t.Cleanup(cleanup) + // next 50 are proofs for individual identities + for i := 20; i < 70; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + proofs[i].Properties = map[string]string{ + "domain": strconv.FormatUint(uint64(i%4+1), 10), + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + } + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, nil, proofs[i].Proof, byte(i%4+1), time.Now())) + } - conn := dialGrpc(t, cfg) - return spacemeshv2alpha1.NewMalfeasanceServiceClient(conn), proofs + // last 20 are proofs for a single marriage + id, err := marriage.NewID(db) + require.NoError(t, err) + marriageATX := types.RandomATXID() + for i := 70; i < 90; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID()} + err := marriage.Add(db, marriage.Info{ + ID: id, + NodeID: proofs[i].ID, + ATX: marriageATX, + MarriageIndex: i % 70, + Target: proofs[70].ID, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + } + proofs[70].Proof = types.RandomBytes(100) + proofs[70].Properties = map[string]string{ + "domain": "1", + "type": "1", + "key": "value", } + info.EXPECT().Info(gomock.Any(), proofs[70].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[70].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[70].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[70].ID, &id, proofs[70].Proof, byte(1), time.Now())) + for i := 71; i < 90; i++ { + proofs[i] = malInfo{ID: proofs[i].ID, Proof: proofs[70].Proof} + proofs[i].Properties = maps.Clone(proofs[70].Properties) + proofs[i].Properties["malicious_id"] = proofs[i].ID.String() + + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + + require.NoError(t, malfeasance.SetMalicious(db, proofs[i].ID, id, time.Now())) + } + + svc := NewMalfeasanceService(db, info, legacyInfo) + cfg, cleanup := launchServer(t, svc) + t.Cleanup(cleanup) t.Run("limit set too high", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) _, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{Limit: 200}) require.Error(t, err) @@ -69,7 +131,7 @@ func TestMalfeasanceService_List(t *testing.T) { }) t.Run("no limit set", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) _, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{}) require.Error(t, err) @@ -80,7 +142,7 @@ func TestMalfeasanceService_List(t *testing.T) { }) t.Run("limit and offset", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) list, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{ Limit: 25, Offset: 50, @@ -90,14 +152,14 @@ func TestMalfeasanceService_List(t *testing.T) { }) t.Run("all", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) list, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{Limit: 100}) require.NoError(t, err) require.Len(t, list.Proofs, 90) }) t.Run("smesherId", func(t *testing.T) { - client, proofs := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) list, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{ Limit: 1, SmesherId: [][]byte{proofs[1].ID.Bytes()}, @@ -107,72 +169,89 @@ func TestMalfeasanceService_List(t *testing.T) { }) } -func TestMalfeasanceService_ListV2(t *testing.T) { - // TODO(mafa): add a ListV2 method to return the new malfeasance proofs - - // // 70 proofs are for individual identities - // for i := range 70 { - // proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} - // proofs[i].Properties = map[string]string{ - // "domain": strconv.FormatUint(uint64(i%4+1), 10), - // "type": strconv.FormatUint(uint64(i%4+1), 10), - // fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), - // } - // info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() - // legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() - - // require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, nil, proofs[i].Proof, byte(i%4+1), time.Now())) - // } - // // last 20 proofs are from for a single marriage - // id, err := marriage.NewID(db) - // require.NoError(t, err) - // marriageATX := types.RandomATXID() - - // for i := 70; i < 90; i++ { - // proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} - // proofs[i].Properties = map[string]string{ - // "domain": strconv.FormatUint(uint64(i%4+1), 10), - // "type": strconv.FormatUint(uint64(i%4+1), 10), - // fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), - // "malicious_id": proofs[i].ID.String(), - // } - // info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() - // legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() - - // err = marriage.Add(db, marriage.Info{ - // ID: id, - // NodeID: proofs[i].ID, - // ATX: marriageATX, - // MarriageIndex: i % 70, - // Target: proofs[70].ID, - // Signature: types.RandomEdSignature(), - // }) - // require.NoError(t, err) - - // require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, &id, proofs[i].Proof, byte(i%4+1), time.Now())) - // } -} - func TestMalfeasanceStreamService_Stream(t *testing.T) { setup := func( t *testing.T, db sql.Executor, info *MockmalfeasanceInfo, + legacyInfo *MockmalfeasanceInfo, ) spacemeshv2alpha1.MalfeasanceStreamServiceClient { proofs := make([]malInfo, 90) - for i := range proofs { + // first 20 are legacy proofs + for i := range 20 { proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} proofs[i].Properties = map[string]string{ "domain": "0", "type": strconv.FormatUint(uint64(i%4+1), 10), fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } - info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(proofs[i].Properties, nil).AnyTimes() - + info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) } - svc := NewMalfeasanceStreamService(db, nil, info) + // next 50 are proofs for individual identities + for i := 20; i < 70; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + proofs[i].Properties = map[string]string{ + "domain": strconv.FormatUint(uint64(i%4+1), 10), + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + } + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, nil, proofs[i].Proof, byte(i%4+1), time.Now())) + } + + // last 20 are proofs for a single marriage + id, err := marriage.NewID(db) + require.NoError(t, err) + marriageATX := types.RandomATXID() + for i := 70; i < 90; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID()} + err := marriage.Add(db, marriage.Info{ + ID: id, + NodeID: proofs[i].ID, + ATX: marriageATX, + MarriageIndex: i % 70, + Target: proofs[70].ID, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + } + proofs[70].Proof = types.RandomBytes(100) + proofs[70].Properties = map[string]string{ + "domain": "1", + "type": "1", + "key": "value", + } + info.EXPECT().Info(gomock.Any(), proofs[70].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[70].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[70].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[70].ID, &id, proofs[70].Proof, byte(1), time.Now())) + for i := 71; i < 90; i++ { + proofs[i] = malInfo{ID: proofs[i].ID, Proof: proofs[70].Proof} + proofs[i].Properties = maps.Clone(proofs[70].Properties) + proofs[i].Properties["malicious_id"] = proofs[i].ID.String() + + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + + require.NoError(t, malfeasance.SetMalicious(db, proofs[i].ID, id, time.Now())) + } + + svc := NewMalfeasanceStreamService(db, info, legacyInfo) cfg, cleanup := launchServer(t, svc) t.Cleanup(cleanup) @@ -184,9 +263,11 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { events.InitializeReporter() t.Cleanup(events.CloseEventReporter) + db := statesql.InMemoryTest(t) ctrl := gomock.NewController(t) info := NewMockmalfeasanceInfo(ctrl) - client := setup(t, statesql.InMemoryTest(t), info) + legacyInfo := NewMockmalfeasanceInfo(ctrl) + client := setup(t, db, info, legacyInfo) stream, err := client.Stream(context.Background(), &spacemeshv2alpha1.MalfeasanceStreamRequest{}) require.NoError(t, err) @@ -209,15 +290,18 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { db := statesql.InMemoryTest(t) ctrl := gomock.NewController(t) info := NewMockmalfeasanceInfo(ctrl) - client := setup(t, db, info) + legacyInfo := NewMockmalfeasanceInfo(ctrl) + client := setup(t, db, info, legacyInfo) const ( - start = 100 - n = 10 + start = 100 + nLegacy = 5 + nIndividual = 5 + nMarriage = 5 ) var streamed []*events.EventMalfeasance - for i := 0; i < n; i++ { + for i := 0; i < nLegacy; i++ { smesher := types.RandomNodeID() streamed = append(streamed, &events.EventMalfeasance{ Smesher: smesher, @@ -227,11 +311,62 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { "type": strconv.FormatUint(uint64(i%4+1), 10), fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } - info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(properties, nil).AnyTimes() + info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(nil, sql.ErrNotFound).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), streamed[i].Smesher).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(properties), nil + }).AnyTimes() + } + + for i := nLegacy; i < nLegacy+nIndividual; i++ { + smesher := types.RandomNodeID() + streamed = append(streamed, &events.EventMalfeasance{ + Smesher: smesher, + }) + properties := map[string]string{ + "domain": strconv.FormatUint(uint64(i%4+1), 10), + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + } + info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(nil, sql.ErrNotFound).AnyTimes() + } + + id, err := marriage.NewID(db) + require.NoError(t, err) + marriageATX := types.RandomATXID() + for i := nLegacy + nIndividual; i < nLegacy+nIndividual+nMarriage; i++ { + smesher := types.RandomNodeID() + streamed = append(streamed, &events.EventMalfeasance{ + Smesher: smesher, + }) + properties := map[string]string{ + "domain": "1", + "type": "1", + "key": "value", + } + info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(nil, sql.ErrNotFound).AnyTimes() + + err := marriage.Add(db, marriage.Info{ + ID: id, + NodeID: streamed[i].Smesher, + ATX: marriageATX, + MarriageIndex: i%nLegacy + nIndividual, + Target: streamed[nLegacy+nIndividual].Smesher, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) } request := &spacemeshv2alpha1.MalfeasanceStreamRequest{ - SmesherId: [][]byte{streamed[3].Smesher.Bytes()}, + SmesherId: [][]byte{streamed[3].Smesher.Bytes(), streamed[7].Smesher.Bytes(), streamed[12].Smesher.Bytes()}, Watch: true, } stream, err := client.Stream(context.Background(), request) @@ -241,7 +376,6 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { var expect []types.NodeID for _, rst := range streamed { - require.NoError(t, identities.SetMalicious(db, rst.Smesher, types.RandomBytes(100), time.Now())) events.ReportMalfeasance(rst.Smesher) matcher := malfeasanceMatcher{request} if matcher.match(rst) { diff --git a/go.mod b/go.mod index 8674e8ea81..6cefba4dc2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( cloud.google.com/go/storage v1.48.0 github.com/ALTree/bigfloat v0.2.0 - github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241207233122-cb35037e984f + github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241218070203-9f5baffb5c4e github.com/cosmos/btcutil v1.0.5 github.com/go-llsqlite/crawshaw v0.5.5 github.com/gofrs/flock v0.12.1 @@ -40,7 +40,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seehuhn/mt19937 v1.0.0 github.com/slok/go-http-metrics v0.13.0 - github.com/spacemeshos/api/release/go v1.57.0 + github.com/spacemeshos/api/release/go v1.59.0 github.com/spacemeshos/economics v0.1.4 github.com/spacemeshos/fixed v0.1.2 github.com/spacemeshos/go-scale v1.2.1 @@ -56,10 +56,10 @@ require ( github.com/zeebo/blake3 v0.2.4 go.uber.org/mock v0.5.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20241210194714-1829a127f884 + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 golang.org/x/sync v0.10.0 golang.org/x/time v0.8.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 google.golang.org/grpc v1.68.1 google.golang.org/protobuf v1.36.0 k8s.io/api v0.32.0 diff --git a/go.sum b/go.sum index b176d1cbce..39ad20bfab 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241207233122-cb35037e984f h1:cRhVELxBpei+s7Lo1oZDJSHslfxQK3IxnueKyobuTWs= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241207233122-cb35037e984f/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241218070203-9f5baffb5c4e h1:qQsmXUsyGWqaIejiynAfqqBPTs5VDefSkbKspV/pN+Q= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241218070203-9f5baffb5c4e/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -628,8 +628,8 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spacemeshos/api/release/go v1.57.0 h1:KE4zNmuI12q7MDcy2cDwQLSVWKpY3gXoDgX02URFzBk= -github.com/spacemeshos/api/release/go v1.57.0/go.mod h1:w/WRxR8JVmaeKqEEyL6DCoQxt3oyZOTv4uQSdMXlucM= +github.com/spacemeshos/api/release/go v1.59.0 h1:/2K3kzVKdzypaRdc2uS4dsAL8TVXWXQr66KpEVeB4Pw= +github.com/spacemeshos/api/release/go v1.59.0/go.mod h1:tJaxo2HaHFyL1726EmiCV1y6ab4LVwU2iB71bZkkfUQ= github.com/spacemeshos/economics v0.1.4 h1:twlawrcQhYNqPgyDv08+24EL/OgUKz3d7q+PvJIAND0= github.com/spacemeshos/economics v0.1.4/go.mod h1:6HKWKiKdxjVQcGa2z/wA0LR4M/DzKib856bP16yqNmQ= github.com/spacemeshos/fixed v0.1.2 h1:pENQ8pXFAqin3f15ZLoOVVeSgcmcFJ0IFdFm4+9u4SM= @@ -766,8 +766,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -939,8 +939,8 @@ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= diff --git a/sql/identities/identities.go b/sql/identities/identities.go index d0659fd982..99964b5de9 100644 --- a/sql/identities/identities.go +++ b/sql/identities/identities.go @@ -96,3 +96,16 @@ func AllMalicious(db sql.Executor) ([]types.NodeID, error) { } return ids, nil } + +// CountMalicious returns the number of malicious nodes. +func CountMalicious(db sql.Executor) (uint64, error) { + var count uint64 + _, err := db.Exec(` + SELECT COUNT(*) + FROM identities + `, nil, func(stmt *sql.Statement) bool { + count = uint64(stmt.ColumnInt64(0)) + return false + }) + return count, err +} diff --git a/sql/identities/identities_test.go b/sql/identities/identities_test.go index 2c1d859f04..e628c0cf35 100644 --- a/sql/identities/identities_test.go +++ b/sql/identities/identities_test.go @@ -77,6 +77,21 @@ func Test_GetMalicious(t *testing.T) { require.Equal(t, bad, got) } +func Test_CountMalicious(t *testing.T) { + db := statesql.InMemoryTest(t) + got, err := identities.CountMalicious(db) + require.NoError(t, err) + require.Zero(t, got) + + const numBad = 11 + for range numBad { + require.NoError(t, identities.SetMalicious(db, types.RandomNodeID(), types.RandomBytes(11), time.Now().Local())) + } + got, err = identities.CountMalicious(db) + require.NoError(t, err) + require.Equal(t, numBad, got) +} + func TestLoadMalfeasanceBlob(t *testing.T) { db := statesql.InMemoryTest(t) ctx := context.Background() diff --git a/sql/malfeasance/malfeasance.go b/sql/malfeasance/malfeasance.go index e858e7f133..970445c299 100644 --- a/sql/malfeasance/malfeasance.go +++ b/sql/malfeasance/malfeasance.go @@ -6,6 +6,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/builder" "github.com/spacemeshos/go-spacemesh/sql/marriage" ) @@ -124,3 +125,29 @@ func MarriageProof(db sql.Executor, marriageID marriage.ID) (byte, []byte, error } return domain, proof, nil } + +func IterateOps( + db sql.Executor, + operations builder.Operations, + fn func(types.NodeID, []byte, byte, time.Time) bool, +) error { + fullQuery := ` + SELECT pubkey, proof, domain, received + FROM malfeasance + ` + builder.FilterFrom(operations) + _, err := db.Exec(fullQuery, builder.BindingsFrom(operations), + func(stmt *sql.Statement) bool { + var id types.NodeID + stmt.ColumnBytes(0, id[:]) + var proof []byte + if stmt.ColumnLen(1) > 0 { + proof = make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(1, proof) + } + domain := byte(stmt.ColumnInt64(2)) + received := time.Unix(0, stmt.ColumnInt64(3)) + return fn(id, proof, domain, received) + }, + ) + return err +} diff --git a/sql/malfeasance/malfeasance_test.go b/sql/malfeasance/malfeasance_test.go index 76738f106e..375e30bab5 100644 --- a/sql/malfeasance/malfeasance_test.go +++ b/sql/malfeasance/malfeasance_test.go @@ -1,6 +1,7 @@ package malfeasance_test import ( + "math/rand/v2" "testing" "time" @@ -8,6 +9,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/builder" "github.com/spacemeshos/go-spacemesh/sql/malfeasance" "github.com/spacemeshos/go-spacemesh/sql/marriage" "github.com/spacemeshos/go-spacemesh/sql/statesql" @@ -316,3 +318,109 @@ func TestMarriageProof(t *testing.T) { require.Equal(t, proof, p) }) } + +func Test_IterateMaliciousOps(t *testing.T) { + db := statesql.InMemoryTest(t) + tt := []struct { + id types.NodeID + proof []byte + domain byte + }{ + { + types.RandomNodeID(), + types.RandomBytes(11), + byte(rand.IntN(255)), + }, + { + types.RandomNodeID(), + types.RandomBytes(11), + byte(rand.IntN(255)), + }, + { + types.RandomNodeID(), + types.RandomBytes(11), + byte(rand.IntN(255)), + }, + } + + for _, tc := range tt { + err := malfeasance.AddProof(db, tc.id, nil, tc.proof, tc.domain, time.Now()) + require.NoError(t, err) + } + + var got []struct { + id types.NodeID + proof []byte + domain byte + } + err := malfeasance.IterateOps(db, builder.Operations{}, + func(id types.NodeID, proof []byte, domain byte, _ time.Time) bool { + got = append(got, struct { + id types.NodeID + proof []byte + domain byte + }{id, proof, domain}) + return true + }) + require.NoError(t, err) + require.ElementsMatch(t, tt, got) +} + +func Test_IterateMaliciousOps_Married(t *testing.T) { + db := statesql.InMemoryTest(t) + + nodeID := types.RandomNodeID() + marriageATX := types.RandomATXID() + id, err := marriage.NewID(db) + require.NoError(t, err) + + err = marriage.Add(db, marriage.Info{ + ID: id, + NodeID: nodeID, + ATX: marriageATX, + MarriageIndex: 0, + Target: nodeID, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + + ids := make([]types.NodeID, 5) + ids[0] = nodeID + proof := types.RandomBytes(11) + err = malfeasance.AddProof(db, ids[0], &id, proof, 1, time.Now()) + require.NoError(t, err) + + for i := 1; i < len(ids); i++ { + ids[i] = types.RandomNodeID() + err := malfeasance.SetMalicious(db, ids[i], id, time.Now()) + require.NoError(t, err) + } + + var got []struct { + id types.NodeID + proof []byte + domain byte + } + err = malfeasance.IterateOps(db, builder.Operations{}, + func(id types.NodeID, proof []byte, domain byte, _ time.Time) bool { + got = append(got, struct { + id types.NodeID + proof []byte + domain byte + }{id, proof, domain}) + return true + }) + require.NoError(t, err) + require.Equal(t, len(ids), len(got)) + require.Equal(t, ids[0], got[0].id) + require.Equal(t, proof, got[0].proof) + require.Equal(t, byte(1), got[0].domain) + + ids = ids[1:] + got = got[1:] + for i, id := range ids { + require.Equal(t, id, got[i].id) + require.Nil(t, got[i].proof) + require.Zero(t, got[i].domain) + } +} From cf8bebfea40af06e31680a82ffe3bbb3d7b8febd Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:01:39 +0000 Subject: [PATCH 53/66] Update API dependency --- go.mod | 16 +++++++++------- go.sum | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index b7e614a469..7fc3fd08e8 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( cloud.google.com/go/storage v1.48.0 github.com/ALTree/bigfloat v0.2.0 - github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241204110417-3c631bce206f + github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241219054339-0b660c616ac8 github.com/cosmos/btcutil v1.0.5 github.com/go-llsqlite/crawshaw v0.5.5 github.com/gofrs/flock v0.12.1 @@ -40,7 +40,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seehuhn/mt19937 v1.0.0 github.com/slok/go-http-metrics v0.13.0 - github.com/spacemeshos/api/release/go v1.58.0 + github.com/spacemeshos/api/release/go v1.59.0 github.com/spacemeshos/economics v0.1.4 github.com/spacemeshos/fixed v0.1.2 github.com/spacemeshos/go-scale v1.2.1 @@ -56,11 +56,11 @@ require ( github.com/zeebo/blake3 v0.2.4 go.uber.org/mock v0.5.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 golang.org/x/sync v0.10.0 golang.org/x/time v0.8.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a - google.golang.org/grpc v1.68.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 + google.golang.org/grpc v1.68.1 google.golang.org/protobuf v1.36.0 k8s.io/api v0.32.0 k8s.io/apimachinery v0.32.0 @@ -234,7 +234,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.32.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect @@ -261,4 +261,6 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -exclude google.golang.org/grpc v1.69.0 // remove after cloud.google.com/** upgrades to grpc-go v1.67.3 +// temporary until this issue is resolved and cloud.google.com/go/storage has been updated +// https://github.com/googleapis/google-cloud-go/issues/11283 +exclude google.golang.org/grpc v1.69.0 diff --git a/go.sum b/go.sum index abc7ab25e2..c74f9e8746 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241204110417-3c631bce206f h1:N6vSz68L9EguQPpPNcbRRb8JkEuhE3T4OckRxgM49xE= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241204110417-3c631bce206f/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241219054339-0b660c616ac8 h1:5cnVh9ZaGC9xK1YUVORBWQdToAfj4ZEEdJN9MgTWVbY= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20241219054339-0b660c616ac8/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -628,8 +628,8 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spacemeshos/api/release/go v1.58.0 h1:me2c3cPrdPiQ/+HqblSzhKYMeMKHKk+oNE/EaEN8LlE= -github.com/spacemeshos/api/release/go v1.58.0/go.mod h1:tJaxo2HaHFyL1726EmiCV1y6ab4LVwU2iB71bZkkfUQ= +github.com/spacemeshos/api/release/go v1.59.0 h1:/2K3kzVKdzypaRdc2uS4dsAL8TVXWXQr66KpEVeB4Pw= +github.com/spacemeshos/api/release/go v1.59.0/go.mod h1:tJaxo2HaHFyL1726EmiCV1y6ab4LVwU2iB71bZkkfUQ= github.com/spacemeshos/economics v0.1.4 h1:twlawrcQhYNqPgyDv08+24EL/OgUKz3d7q+PvJIAND0= github.com/spacemeshos/economics v0.1.4/go.mod h1:6HKWKiKdxjVQcGa2z/wA0LR4M/DzKib856bP16yqNmQ= github.com/spacemeshos/fixed v0.1.2 h1:pENQ8pXFAqin3f15ZLoOVVeSgcmcFJ0IFdFm4+9u4SM= @@ -766,8 +766,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -808,8 +808,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -939,8 +939,8 @@ google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -950,8 +950,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= From f2e81e0738653ba9b7eed2d59d598aad08454410 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:44:38 +0000 Subject: [PATCH 54/66] Update Malfeasance GRPC API --- api/grpcserver/activation_service.go | 35 ++- api/grpcserver/activation_service_test.go | 40 ++- api/grpcserver/debug_service.go | 2 +- api/grpcserver/mesh_service.go | 19 +- api/grpcserver/mesh_service_test.go | 10 +- api/grpcserver/v2alpha1/interface.go | 8 +- api/grpcserver/v2alpha1/malfeasance.go | 271 ++++++++++++-------- api/grpcserver/v2alpha1/malfeasance_test.go | 258 ++++++++++++++++--- api/grpcserver/v2alpha1/mocks.go | 14 +- events/events.go | 42 +-- events/malfeasance.go | 5 +- malfeasance/handler.go | 19 +- malfeasance/handler_test.go | 43 +++- malfeasance2/handler.go | 44 ++++ malfeasance2/interface.go | 11 + malfeasance2/mocks.go | 77 ++++++ node/node.go | 121 +++++---- sql/identities/identities.go | 13 + sql/identities/identities_test.go | 21 +- sql/malfeasance/malfeasance.go | 55 +++- sql/malfeasance/malfeasance_test.go | 174 ++++++++++--- 21 files changed, 933 insertions(+), 349 deletions(-) create mode 100644 malfeasance2/handler.go create mode 100644 malfeasance2/interface.go create mode 100644 malfeasance2/mocks.go diff --git a/api/grpcserver/activation_service.go b/api/grpcserver/activation_service.go index 4b5bb0c95e..8dc745906b 100644 --- a/api/grpcserver/activation_service.go +++ b/api/grpcserver/activation_service.go @@ -14,8 +14,9 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/events" + "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/sql" ) @@ -86,7 +87,7 @@ func (s *activationService) Get(ctx context.Context, request *pb.GetRequest) (*p Atx: convertActivation(atx, prev), } if proof != nil { - resp.MalfeasanceProof = events.ToMalfeasancePB(atx.SmesherID, proof, false) + resp.MalfeasanceProof = toMalfeasancePB(atx.SmesherID, proof, false) } return resp, nil } @@ -117,3 +118,33 @@ func (s *activationService) Highest(ctx context.Context, req *emptypb.Empty) (*p Atx: convertActivation(atx, prev), }, nil } + +func toMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof { + mp := &wire.MalfeasanceProof{} + if err := codec.Decode(proof, mp); err != nil { + return &pb.MalfeasanceProof{} + } + kind := pb.MalfeasanceProof_MALFEASANCE_UNSPECIFIED + switch mp.Proof.Type { + case wire.MultipleATXs: + kind = pb.MalfeasanceProof_MALFEASANCE_ATX + case wire.MultipleBallots: + kind = pb.MalfeasanceProof_MALFEASANCE_BALLOT + case wire.HareEquivocation: + kind = pb.MalfeasanceProof_MALFEASANCE_HARE + case wire.InvalidPostIndex: + kind = pb.MalfeasanceProof_MALFEASANCE_POST_INDEX + case wire.InvalidPrevATX: + kind = pb.MalfeasanceProof_MALFEASANCE_INCORRECT_PREV_ATX + } + result := &pb.MalfeasanceProof{ + SmesherId: &pb.SmesherId{Id: nodeID.Bytes()}, + Layer: &pb.LayerNumber{Number: mp.Layer.Uint32()}, + Kind: kind, + DebugInfo: wire.MalfeasanceInfo(nodeID, mp), + } + if includeProof { + result.Proof = proof + } + return result +} diff --git a/api/grpcserver/activation_service_test.go b/api/grpcserver/activation_service_test.go index b720026d96..6d65524c03 100644 --- a/api/grpcserver/activation_service_test.go +++ b/api/grpcserver/activation_service_test.go @@ -1,4 +1,4 @@ -package grpcserver_test +package grpcserver import ( "context" @@ -13,19 +13,17 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" - "github.com/spacemeshos/go-spacemesh/api/grpcserver" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/statesql" ) func Test_Highest_ReturnsGoldenAtxOnError(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) + atxProvider := NewMockatxProvider(ctrl) goldenAtx := types.ATXID{2, 3, 4} - activationService := grpcserver.NewActivationService(atxProvider, goldenAtx) + activationService := NewActivationService(atxProvider, goldenAtx) atxProvider.EXPECT().MaxHeightAtx().Return(types.EmptyATXID, errors.New("blah")) response, err := activationService.Highest(context.Background(), &emptypb.Empty{}) @@ -41,9 +39,9 @@ func Test_Highest_ReturnsGoldenAtxOnError(t *testing.T) { func Test_Highest_ReturnsMaxTickHeight(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) + atxProvider := NewMockatxProvider(ctrl) goldenAtx := types.ATXID{2, 3, 4} - activationService := grpcserver.NewActivationService(atxProvider, goldenAtx) + activationService := NewActivationService(atxProvider, goldenAtx) previous := types.RandomATXID() atx := types.ActivationTx{ @@ -71,8 +69,8 @@ func Test_Highest_ReturnsMaxTickHeight(t *testing.T) { func TestGet_RejectInvalidAtxID(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) _, err := activationService.Get(context.Background(), &pb.GetRequest{Id: []byte{1, 2, 3}}) require.Error(t, err) @@ -81,8 +79,8 @@ func TestGet_RejectInvalidAtxID(t *testing.T) { func TestGet_AtxNotPresent(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) id := types.RandomATXID() atxProvider.EXPECT().GetAtx(id).Return(nil, nil) @@ -94,8 +92,8 @@ func TestGet_AtxNotPresent(t *testing.T) { func TestGet_AtxProviderReturnsFailure(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) id := types.RandomATXID() atxProvider.EXPECT().GetAtx(id).Return(&types.ActivationTx{}, errors.New("")) @@ -107,8 +105,8 @@ func TestGet_AtxProviderReturnsFailure(t *testing.T) { func TestGet_AtxProviderFailsObtainPreviousAtxs(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) id := types.RandomATXID() atxProvider.EXPECT().GetAtx(id).Return(&types.ActivationTx{}, nil) @@ -121,8 +119,8 @@ func TestGet_AtxProviderFailsObtainPreviousAtxs(t *testing.T) { func TestGet_HappyPath(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) previous := []types.ATXID{types.RandomATXID(), types.RandomATXID()} id := types.RandomATXID() @@ -155,10 +153,10 @@ func TestGet_HappyPath(t *testing.T) { func TestGet_IdentityCanceled(t *testing.T) { ctrl := gomock.NewController(t) - atxProvider := grpcserver.NewMockatxProvider(ctrl) - activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1}) + atxProvider := NewMockatxProvider(ctrl) + activationService := NewActivationService(atxProvider, types.ATXID{1}) - smesher, proof := grpcserver.BallotMalfeasance(t, statesql.InMemoryTest(t)) + smesher, proof := BallotMalfeasance(t, statesql.InMemoryTest(t)) previous := types.RandomATXID() id := types.RandomATXID() atx := types.ActivationTx{ @@ -185,5 +183,5 @@ func TestGet_IdentityCanceled(t *testing.T) { require.Equal(t, previous.Bytes(), response.Atx.PreviousAtxs[0].Id) require.Equal(t, atx.NumUnits, response.Atx.NumUnits) require.Equal(t, atx.Sequence, response.Atx.Sequence) - require.Equal(t, events.ToMalfeasancePB(smesher, codec.MustEncode(proof), false), response.MalfeasanceProof) + require.Equal(t, toMalfeasancePB(smesher, codec.MustEncode(proof), false), response.MalfeasanceProof) } diff --git a/api/grpcserver/debug_service.go b/api/grpcserver/debug_service.go index 264bb9e984..787734920d 100644 --- a/api/grpcserver/debug_service.go +++ b/api/grpcserver/debug_service.go @@ -220,7 +220,7 @@ func castEventProposal(ev *events.EventProposal) *pb.Proposal { for _, el := range ev.Proposal.Ballot.EligibilityProofs { proposal.Eligibilities = append(proposal.Eligibilities, &pb.Eligibility{ J: el.J, - Signature: el.Sig[:], + Signature: el.Sig.Bytes(), }) } return proposal diff --git a/api/grpcserver/mesh_service.go b/api/grpcserver/mesh_service.go index a256cf1f19..5243dbd411 100644 --- a/api/grpcserver/mesh_service.go +++ b/api/grpcserver/mesh_service.go @@ -615,7 +615,7 @@ func (s *MeshService) MalfeasanceQuery( return nil, status.Error(codes.Internal, err.Error()) } return &pb.MalfeasanceResponse{ - Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof), + Proof: toMalfeasancePB(id, proof, req.IncludeProof), }, nil } @@ -627,7 +627,7 @@ func (s *MeshService) MalfeasanceStream( if sub == nil { return status.Errorf(codes.FailedPrecondition, "event reporting is not enabled") } - eventch, fullch := consumeEvents[events.EventMalfeasance](stream.Context(), sub) + eventCh, fullCh := consumeEvents[events.EventMalfeasance](stream.Context(), sub) if err := stream.SendHeader(metadata.MD{}); err != nil { return status.Errorf(codes.Unavailable, "can't send header") } @@ -639,7 +639,7 @@ func (s *MeshService) MalfeasanceStream( return nil default: res := &pb.MalfeasanceStreamResponse{ - Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof), + Proof: toMalfeasancePB(id, proof, req.IncludeProof), } return stream.Send(res) } @@ -651,11 +651,18 @@ func (s *MeshService) MalfeasanceStream( select { case <-stream.Context().Done(): return nil - case <-fullch: + case <-fullCh: return status.Errorf(codes.Canceled, "buffer is full") - case ev := <-eventch: + case ev := <-eventCh: + proof, err := s.cdb.MalfeasanceProof(ev.Smesher) + if err != nil { + return status.Error( + codes.Internal, + fmt.Errorf("load malfeasance proof for %s: %w", ev.Smesher.ShortString(), err).Error(), + ) + } if err := stream.Send(&pb.MalfeasanceStreamResponse{ - Proof: events.ToMalfeasancePB(ev.Smesher, ev.Proof, req.IncludeProof), + Proof: toMalfeasancePB(ev.Smesher, proof, req.IncludeProof), }); err != nil { return status.Error(codes.Internal, fmt.Errorf("send to stream: %w", err).Error()) } diff --git a/api/grpcserver/mesh_service_test.go b/api/grpcserver/mesh_service_test.go index e08f23c045..988750b7cc 100644 --- a/api/grpcserver/mesh_service_test.go +++ b/api/grpcserver/mesh_service_test.go @@ -177,7 +177,7 @@ func TestMeshService_MalfeasanceQuery(t *testing.T) { require.Equal(t, nodeID, types.BytesToNodeID(resp.Proof.SmesherId.Id)) require.EqualValues(t, layer, resp.Proof.Layer.Number) require.Equal(t, pb.MalfeasanceProof_MALFEASANCE_BALLOT, resp.Proof.Kind) - require.Equal(t, events.ToMalfeasancePB(nodeID, codec.MustEncode(proof), true), resp.Proof) + require.Equal(t, toMalfeasancePB(nodeID, codec.MustEncode(proof), true), resp.Proof) require.NotEmpty(t, resp.Proof.Proof) var got wire.MalfeasanceProof require.NoError(t, codec.Decode(resp.Proof.Proof, &got)) @@ -251,16 +251,16 @@ func TestMeshService_MalfeasanceStream(t *testing.T) { id, proof := AtxMalfeasance(t, db) proofBytes := codec.MustEncode(proof) - events.ReportMalfeasance(id, proofBytes) + events.ReportMalfeasance(id) resp, err := stream.Recv() require.NoError(t, err) - require.Equal(t, events.ToMalfeasancePB(id, proofBytes, false), resp.Proof) + require.Equal(t, toMalfeasancePB(id, proofBytes, false), resp.Proof) id, proof = BallotMalfeasance(t, db) proofBytes = codec.MustEncode(proof) - events.ReportMalfeasance(id, proofBytes) + events.ReportMalfeasance(id) resp, err = stream.Recv() require.NoError(t, err) - require.Equal(t, events.ToMalfeasancePB(id, proofBytes, false), resp.Proof) + require.Equal(t, toMalfeasancePB(id, proofBytes, false), resp.Proof) } type MeshAPIMockInstrumented struct { diff --git a/api/grpcserver/v2alpha1/interface.go b/api/grpcserver/v2alpha1/interface.go index d0a03b528c..377d94937b 100644 --- a/api/grpcserver/v2alpha1/interface.go +++ b/api/grpcserver/v2alpha1/interface.go @@ -1,7 +1,13 @@ package v2alpha1 +import ( + "context" + + "github.com/spacemeshos/go-spacemesh/common/types" +) + //go:generate mockgen -typed -package=v2alpha1 -destination=./mocks.go -source=./interface.go type malfeasanceInfo interface { - Info(data []byte) (map[string]string, error) + Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) } diff --git a/api/grpcserver/v2alpha1/malfeasance.go b/api/grpcserver/v2alpha1/malfeasance.go index d2f9b5b41d..3f055892ee 100644 --- a/api/grpcserver/v2alpha1/malfeasance.go +++ b/api/grpcserver/v2alpha1/malfeasance.go @@ -23,6 +23,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/builder" "github.com/spacemeshos/go-spacemesh/sql/identities" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" ) const ( @@ -30,16 +31,18 @@ const ( MalfeasanceStream = "malfeasance_stream_v2alpha1" ) -func NewMalfeasanceService(db sql.Executor, malfeasanceHandler malfeasanceInfo) *MalfeasanceService { +func NewMalfeasanceService(db sql.Executor, malfeasanceHandler, legacyHandler malfeasanceInfo) *MalfeasanceService { return &MalfeasanceService{ - db: db, - info: malfeasanceHandler, + db: db, + info: malfeasanceHandler, + infoLegacy: legacyHandler, } } type MalfeasanceService struct { - db sql.Executor - info malfeasanceInfo + db sql.Executor + info malfeasanceInfo + infoLegacy malfeasanceInfo } func (s *MalfeasanceService) RegisterService(server *grpc.Server) { @@ -65,36 +68,56 @@ func (s *MalfeasanceService) List( return nil, status.Error(codes.InvalidArgument, "limit must be set to <= 100") } - ops, err := toMalfeasanceOps(request) + legacyCount, err := identities.CountMalicious(s.db) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Internal, err.Error()) } - proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, request.Limit) - if err := identities.IterateOps(s.db, ops, func(id types.NodeID, proof []byte, received time.Time) bool { - rst := toProof(ctx, s.info, id, proof) - if rst == nil { - return true + switch { + case request.Offset+request.Limit < legacyCount: // only legacy proofs + proofs, err := fetchLegacyFromDB(ctx, s.db, s.infoLegacy, request) + if err != nil { + return nil, err } - proofs = append(proofs, rst) - return true - }); err != nil { - return nil, status.Error(codes.Internal, err.Error()) + return &spacemeshv2alpha1.MalfeasanceList{Proofs: proofs}, nil + case request.Offset >= legacyCount: // only new proofs + request.Offset -= legacyCount + proofs, err := fetchFromDB(ctx, s.db, s.info, request) + if err != nil { + return nil, err + } + return &spacemeshv2alpha1.MalfeasanceList{Proofs: proofs}, nil + default: // both legacy and new proofs + legacyProofs, err := fetchLegacyFromDB(ctx, s.db, s.infoLegacy, request) + if err != nil { + return nil, err + } + request.Limit -= uint64(len(legacyProofs)) + request.Offset = 0 + proofs, err := fetchFromDB(ctx, s.db, s.info, request) + if err != nil { + return nil, err + } + return &spacemeshv2alpha1.MalfeasanceList{Proofs: append(legacyProofs, proofs...)}, nil } - - return &spacemeshv2alpha1.MalfeasanceList{Proofs: proofs}, nil } -func NewMalfeasanceStreamService(db sql.Executor, malfeasanceHandler malfeasanceInfo) *MalfeasanceStreamService { +func NewMalfeasanceStreamService( + db sql.Executor, + malfeasanceHandler, + legacyHandler malfeasanceInfo, +) *MalfeasanceStreamService { return &MalfeasanceStreamService{ - db: db, - info: malfeasanceHandler, + db: db, + info: malfeasanceHandler, + infoLegacy: legacyHandler, } } type MalfeasanceStreamService struct { - db sql.Executor - info malfeasanceInfo + db sql.Executor + info malfeasanceInfo + infoLegacy malfeasanceInfo } func (s *MalfeasanceStreamService) RegisterService(server *grpc.Server) { @@ -113,47 +136,74 @@ func (s *MalfeasanceStreamService) Stream( request *spacemeshv2alpha1.MalfeasanceStreamRequest, stream spacemeshv2alpha1.MalfeasanceStreamService_StreamServer, ) error { - var sub *events.BufferedSubscription[events.EventMalfeasance] - if request.Watch { - matcher := malfeasanceMatcher{request} - var err error - sub, err = events.SubscribeMatched(matcher.match) - if err != nil { + if err := stream.SendHeader(metadata.MD{}); err != nil { + return status.Errorf(codes.Unavailable, "can't send header") + } + + legacyProofs, err := fetchLegacyFromDB( + stream.Context(), + s.db, + s.infoLegacy, + &spacemeshv2alpha1.MalfeasanceRequest{SmesherId: request.SmesherId}, + ) + if err != nil { + return err + } + for _, rst := range legacyProofs { + err := stream.Send(rst) + switch { + case errors.Is(err, io.EOF): + return nil + case err != nil: return status.Error(codes.Internal, err.Error()) } - defer sub.Close() - if err := stream.SendHeader(metadata.MD{}); err != nil { - return status.Errorf(codes.Unavailable, "can't send header") - } } - ops, err := toMalfeasanceOps(&spacemeshv2alpha1.MalfeasanceRequest{ - SmesherId: request.SmesherId, - }) + proofs, err := fetchFromDB( + stream.Context(), + s.db, + s.info, + &spacemeshv2alpha1.MalfeasanceRequest{SmesherId: request.SmesherId}, + ) if err != nil { - return status.Error(codes.InvalidArgument, err.Error()) + return err + } + for _, rst := range proofs { + err := stream.Send(rst) + switch { + case errors.Is(err, io.EOF): + return nil + case err != nil: + return status.Error(codes.Internal, err.Error()) + } } - ctx, cancel := context.WithCancel(stream.Context()) - defer cancel() - dbChan, errChan := s.fetchFromDB(ctx, ops) + if !request.Watch { + return nil + } - var eventsOut <-chan events.EventMalfeasance - var eventsFull <-chan struct{} - if sub != nil { - eventsOut = sub.Out() - eventsFull = sub.Full() + matcher := malfeasanceMatcher{request} + sub, err := events.SubscribeMatched(matcher.match) + if err != nil { + return status.Error(codes.Internal, err.Error()) } + defer sub.Close() + eventsOut := sub.Out() + eventsFull := sub.Full() for { select { - // process events first + // process pending events first case rst := <-eventsOut: - proof := toProof(stream.Context(), s.info, rst.Smesher, rst.Proof) + proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { - continue + // try again with the new handler + proof = toProof(stream.Context(), s.info, rst.Smesher) + if proof == nil { + continue + } } - err = stream.Send(proof) + err := stream.Send(proof) switch { case errors.Is(err, io.EOF): return nil @@ -163,11 +213,15 @@ func (s *MalfeasanceStreamService) Stream( default: select { case rst := <-eventsOut: - proof := toProof(stream.Context(), s.info, rst.Smesher, rst.Proof) + proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { - continue + // try again with the new handler + proof = toProof(stream.Context(), s.info, rst.Smesher) + if proof == nil { + continue + } } - err = stream.Send(proof) + err := stream.Send(proof) switch { case errors.Is(err, io.EOF): return nil @@ -176,23 +230,6 @@ func (s *MalfeasanceStreamService) Stream( } case <-eventsFull: return status.Error(codes.Canceled, "buffer overflow") - case rst, ok := <-dbChan: - if !ok { - dbChan = nil - if sub == nil { - return nil - } - continue - } - err = stream.Send(rst) - switch { - case errors.Is(err, io.EOF): - return nil - case err != nil: - return status.Error(codes.Internal, err.Error()) - } - case err := <-errChan: - return err case <-stream.Context().Done(): return nil } @@ -200,44 +237,12 @@ func (s *MalfeasanceStreamService) Stream( } } -func (s *MalfeasanceStreamService) fetchFromDB( - ctx context.Context, - ops builder.Operations, -) (<-chan *spacemeshv2alpha1.MalfeasanceProof, <-chan error) { - dbChan := make(chan *spacemeshv2alpha1.MalfeasanceProof) - errChan := make(chan error, 1) // buffered to avoid blocking, routine should exit immediately after sending an error - - go func() { - defer close(dbChan) - if err := identities.IterateOps(s.db, ops, - func(id types.NodeID, proof []byte, received time.Time) bool { - rst := toProof(ctx, s.info, id, proof) - if rst == nil { - return true - } - - select { - case dbChan <- rst: - return true - case <-ctx.Done(): - // exit if the context is canceled - return false - } - }, - ); err != nil { - errChan <- status.Error(codes.Internal, err.Error()) - } - }() - return dbChan, errChan -} - func toProof( ctx context.Context, info malfeasanceInfo, id types.NodeID, - proof []byte, ) *spacemeshv2alpha1.MalfeasanceProof { - properties, err := info.Info(proof) + properties, err := info.Info(ctx, id) if err != nil { ctxzap.Debug(ctx, "failed to get malfeasance info", zap.String("smesher", id.String()), @@ -267,18 +272,70 @@ func toProof( delete(properties, "type") return &spacemeshv2alpha1.MalfeasanceProof{ Smesher: id.Bytes(), - Domain: spacemeshv2alpha1.MalfeasanceProof_MalfeasanceDomain(domain), + Domain: spacemeshv2alpha1.MalfeasanceProof_MalfeasanceDomain(domain), // TODO(mafa): add new domains Type: uint32(proofType), Properties: properties, } } +func fetchFromDB( + ctx context.Context, + db sql.Executor, + info malfeasanceInfo, + request *spacemeshv2alpha1.MalfeasanceRequest, +) ([]*spacemeshv2alpha1.MalfeasanceProof, error) { + ops, err := toMalfeasanceOps(request) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + ids := make([]types.NodeID, 0, request.Limit) + if err := malfeasance.IterateOps(db, ops, func(id types.NodeID, _ []byte, _ int, _ time.Time) bool { + ids = append(ids, id) + return true + }); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, len(ids)) + for _, id := range ids { + rst := toProof(ctx, info, id) + if rst == nil { + continue + } + proofs = append(proofs, rst) + } + return proofs, nil +} + +func fetchLegacyFromDB( + ctx context.Context, + db sql.Executor, + info malfeasanceInfo, + request *spacemeshv2alpha1.MalfeasanceRequest, +) ([]*spacemeshv2alpha1.MalfeasanceProof, error) { + ops, err := toMalfeasanceOps(request) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + ids := make([]types.NodeID, 0, request.Limit) + if err := identities.IterateOps(db, ops, func(id types.NodeID, _ []byte, _ time.Time) bool { + ids = append(ids, id) + return true + }); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, len(ids)) + for _, id := range ids { + rst := toProof(ctx, info, id) + if rst == nil { + continue + } + proofs = append(proofs, rst) + } + return proofs, nil +} + func toMalfeasanceOps(filter *spacemeshv2alpha1.MalfeasanceRequest) (builder.Operations, error) { ops := builder.Operations{} - ops.Filter = append(ops.Filter, builder.Op{ - Field: builder.Proof, - Token: builder.IsNotNull, - }) ops.Modifiers = append(ops.Modifiers, builder.Modifier{ Key: builder.OrderBy, Value: builder.Smesher, diff --git a/api/grpcserver/v2alpha1/malfeasance_test.go b/api/grpcserver/v2alpha1/malfeasance_test.go index 22744ad648..bc3904048d 100644 --- a/api/grpcserver/v2alpha1/malfeasance_test.go +++ b/api/grpcserver/v2alpha1/malfeasance_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "maps" "strconv" "testing" "time" @@ -20,6 +21,8 @@ import ( "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/identities" + "github.com/spacemeshos/go-spacemesh/sql/malfeasance" + "github.com/spacemeshos/go-spacemesh/sql/marriage" "github.com/spacemeshos/go-spacemesh/sql/statesql" ) @@ -31,34 +34,93 @@ type malInfo struct { } func TestMalfeasanceService_List(t *testing.T) { - setup := func(t *testing.T) (spacemeshv2alpha1.MalfeasanceServiceClient, []malInfo) { - db := statesql.InMemoryTest(t) - ctrl := gomock.NewController(t) - info := NewMockmalfeasanceInfo(ctrl) - - proofs := make([]malInfo, 90) - for i := range proofs { - proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} - proofs[i].Properties = map[string]string{ - "domain": "0", - "type": strconv.FormatUint(uint64(i%4+1), 10), - fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), - } - info.EXPECT().Info(proofs[i].Proof).Return(proofs[i].Properties, nil).AnyTimes() - - require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) + db := statesql.InMemoryTest(t) + ctrl := gomock.NewController(t) + info := NewMockmalfeasanceInfo(ctrl) + legacyInfo := NewMockmalfeasanceInfo(ctrl) + + proofs := make([]malInfo, 90) + + // first 20 are legacy proofs + for i := range 20 { + proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + proofs[i].Properties = map[string]string{ + "domain": "0", + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } + info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) + } - svc := NewMalfeasanceService(db, info) - cfg, cleanup := launchServer(t, svc) - t.Cleanup(cleanup) + // next 50 are proofs for individual identities + for i := 20; i < 70; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + proofs[i].Properties = map[string]string{ + "domain": strconv.FormatUint(uint64(i%4+1), 10), + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + } + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, nil, proofs[i].Proof, i%4+1, time.Now())) + } - conn := dialGrpc(t, cfg) - return spacemeshv2alpha1.NewMalfeasanceServiceClient(conn), proofs + // last 20 are proofs for a single marriage + id, err := marriage.NewID(db) + require.NoError(t, err) + marriageATX := types.RandomATXID() + for i := 70; i < 90; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID()} + err := marriage.Add(db, marriage.Info{ + ID: id, + NodeID: proofs[i].ID, + ATX: marriageATX, + MarriageIndex: i % 70, + Target: proofs[70].ID, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + } + proofs[70].Proof = types.RandomBytes(100) + proofs[70].Properties = map[string]string{ + "domain": "1", + "type": "1", + "key": "value", } + info.EXPECT().Info(gomock.Any(), proofs[70].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[70].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[70].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[70].ID, &id, proofs[70].Proof, 1, time.Now())) + for i := 71; i < 90; i++ { + proofs[i] = malInfo{ID: proofs[i].ID, Proof: proofs[70].Proof} + proofs[i].Properties = maps.Clone(proofs[70].Properties) + proofs[i].Properties["malicious_id"] = proofs[i].ID.String() + + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + + require.NoError(t, malfeasance.SetMalicious(db, proofs[i].ID, id, time.Now())) + } + + svc := NewMalfeasanceService(db, info, legacyInfo) + cfg, cleanup := launchServer(t, svc) + t.Cleanup(cleanup) t.Run("limit set too high", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) _, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{Limit: 200}) require.Error(t, err) @@ -69,7 +131,7 @@ func TestMalfeasanceService_List(t *testing.T) { }) t.Run("no limit set", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) _, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{}) require.Error(t, err) @@ -80,7 +142,7 @@ func TestMalfeasanceService_List(t *testing.T) { }) t.Run("limit and offset", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) list, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{ Limit: 25, Offset: 50, @@ -90,14 +152,14 @@ func TestMalfeasanceService_List(t *testing.T) { }) t.Run("all", func(t *testing.T) { - client, _ := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) list, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{Limit: 100}) require.NoError(t, err) require.Len(t, list.Proofs, 90) }) t.Run("smesherId", func(t *testing.T) { - client, proofs := setup(t) + client := spacemeshv2alpha1.NewMalfeasanceServiceClient(dialGrpc(t, cfg)) list, err := client.List(context.Background(), &spacemeshv2alpha1.MalfeasanceRequest{ Limit: 1, SmesherId: [][]byte{proofs[1].ID.Bytes()}, @@ -112,21 +174,84 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { t *testing.T, db sql.Executor, info *MockmalfeasanceInfo, + legacyInfo *MockmalfeasanceInfo, ) spacemeshv2alpha1.MalfeasanceStreamServiceClient { proofs := make([]malInfo, 90) - for i := range proofs { + // first 20 are legacy proofs + for i := range 20 { proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} proofs[i].Properties = map[string]string{ "domain": "0", "type": strconv.FormatUint(uint64(i%4+1), 10), fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } - info.EXPECT().Info(proofs[i].Proof).Return(proofs[i].Properties, nil).AnyTimes() - + info.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() require.NoError(t, identities.SetMalicious(db, proofs[i].ID, proofs[i].Proof, time.Now())) } - svc := NewMalfeasanceStreamService(db, info) + // next 50 are proofs for individual identities + for i := 20; i < 70; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID(), Proof: types.RandomBytes(100)} + proofs[i].Properties = map[string]string{ + "domain": strconv.FormatUint(uint64(i%4+1), 10), + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + } + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[i].ID, nil, proofs[i].Proof, i%4+1, time.Now())) + } + + // last 20 are proofs for a single marriage + id, err := marriage.NewID(db) + require.NoError(t, err) + marriageATX := types.RandomATXID() + for i := 70; i < 90; i++ { + proofs[i] = malInfo{ID: types.RandomNodeID()} + err := marriage.Add(db, marriage.Info{ + ID: id, + NodeID: proofs[i].ID, + ATX: marriageATX, + MarriageIndex: i % 70, + Target: proofs[70].ID, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + } + proofs[70].Proof = types.RandomBytes(100) + proofs[70].Properties = map[string]string{ + "domain": "1", + "type": "1", + "key": "value", + } + info.EXPECT().Info(gomock.Any(), proofs[70].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[70].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[70].ID).Return(nil, sql.ErrNotFound).AnyTimes() + require.NoError(t, malfeasance.AddProof(db, proofs[70].ID, &id, proofs[70].Proof, 1, time.Now())) + for i := 71; i < 90; i++ { + proofs[i] = malInfo{ID: proofs[i].ID, Proof: proofs[70].Proof} + proofs[i].Properties = maps.Clone(proofs[70].Properties) + proofs[i].Properties["malicious_id"] = proofs[i].ID.String() + + info.EXPECT().Info(gomock.Any(), proofs[i].ID).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(proofs[i].Properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), proofs[i].ID).Return(nil, sql.ErrNotFound).AnyTimes() + + require.NoError(t, malfeasance.SetMalicious(db, proofs[i].ID, id, time.Now())) + } + + svc := NewMalfeasanceStreamService(db, info, legacyInfo) cfg, cleanup := launchServer(t, svc) t.Cleanup(cleanup) @@ -138,9 +263,11 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { events.InitializeReporter() t.Cleanup(events.CloseEventReporter) + db := statesql.InMemoryTest(t) ctrl := gomock.NewController(t) info := NewMockmalfeasanceInfo(ctrl) - client := setup(t, statesql.InMemoryTest(t), info) + legacyInfo := NewMockmalfeasanceInfo(ctrl) + client := setup(t, db, info, legacyInfo) stream, err := client.Stream(context.Background(), &spacemeshv2alpha1.MalfeasanceStreamRequest{}) require.NoError(t, err) @@ -163,30 +290,83 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { db := statesql.InMemoryTest(t) ctrl := gomock.NewController(t) info := NewMockmalfeasanceInfo(ctrl) - client := setup(t, db, info) + legacyInfo := NewMockmalfeasanceInfo(ctrl) + client := setup(t, db, info, legacyInfo) const ( - start = 100 - n = 10 + start = 100 + nLegacy = 5 + nIndividual = 5 + nMarriage = 5 ) var streamed []*events.EventMalfeasance - for i := 0; i < n; i++ { + for i := 0; i < nLegacy; i++ { smesher := types.RandomNodeID() streamed = append(streamed, &events.EventMalfeasance{ Smesher: smesher, - Proof: types.RandomBytes(100), }) properties := map[string]string{ "domain": "0", "type": strconv.FormatUint(uint64(i%4+1), 10), fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), } - info.EXPECT().Info(streamed[i].Proof).Return(properties, nil).AnyTimes() + info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(nil, sql.ErrNotFound).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), streamed[i].Smesher).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(properties), nil + }).AnyTimes() + } + + for i := nLegacy; i < nLegacy+nIndividual; i++ { + smesher := types.RandomNodeID() + streamed = append(streamed, &events.EventMalfeasance{ + Smesher: smesher, + }) + properties := map[string]string{ + "domain": strconv.FormatUint(uint64(i%4+1), 10), + "type": strconv.FormatUint(uint64(i%4+1), 10), + fmt.Sprintf("key%d", i): fmt.Sprintf("value%d", i), + } + info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(nil, sql.ErrNotFound).AnyTimes() + } + + id, err := marriage.NewID(db) + require.NoError(t, err) + marriageATX := types.RandomATXID() + for i := nLegacy + nIndividual; i < nLegacy+nIndividual+nMarriage; i++ { + smesher := types.RandomNodeID() + streamed = append(streamed, &events.EventMalfeasance{ + Smesher: smesher, + }) + properties := map[string]string{ + "domain": "1", + "type": "1", + "key": "value", + } + info.EXPECT().Info(gomock.Any(), streamed[i].Smesher).DoAndReturn( + func(_ context.Context, id types.NodeID) (map[string]string, error) { + return maps.Clone(properties), nil + }).AnyTimes() + legacyInfo.EXPECT().Info(gomock.Any(), streamed[i].Smesher).Return(nil, sql.ErrNotFound).AnyTimes() + + err := marriage.Add(db, marriage.Info{ + ID: id, + NodeID: streamed[i].Smesher, + ATX: marriageATX, + MarriageIndex: i%nLegacy + nIndividual, + Target: streamed[nLegacy+nIndividual].Smesher, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) } request := &spacemeshv2alpha1.MalfeasanceStreamRequest{ - SmesherId: [][]byte{streamed[3].Smesher.Bytes()}, + SmesherId: [][]byte{streamed[3].Smesher.Bytes(), streamed[7].Smesher.Bytes(), streamed[12].Smesher.Bytes()}, Watch: true, } stream, err := client.Stream(context.Background(), request) @@ -196,7 +376,7 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { var expect []types.NodeID for _, rst := range streamed { - events.ReportMalfeasance(rst.Smesher, rst.Proof) + events.ReportMalfeasance(rst.Smesher) matcher := malfeasanceMatcher{request} if matcher.match(rst) { expect = append(expect, rst.Smesher) diff --git a/api/grpcserver/v2alpha1/mocks.go b/api/grpcserver/v2alpha1/mocks.go index 6f7f88adf8..0a4529afab 100644 --- a/api/grpcserver/v2alpha1/mocks.go +++ b/api/grpcserver/v2alpha1/mocks.go @@ -10,8 +10,10 @@ package v2alpha1 import ( + context "context" reflect "reflect" + types "github.com/spacemeshos/go-spacemesh/common/types" gomock "go.uber.org/mock/gomock" ) @@ -40,18 +42,18 @@ func (m *MockmalfeasanceInfo) EXPECT() *MockmalfeasanceInfoMockRecorder { } // Info mocks base method. -func (m *MockmalfeasanceInfo) Info(data []byte) (map[string]string, error) { +func (m *MockmalfeasanceInfo) Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Info", data) + ret := m.ctrl.Call(m, "Info", ctx, nodeID) ret0, _ := ret[0].(map[string]string) ret1, _ := ret[1].(error) return ret0, ret1 } // Info indicates an expected call of Info. -func (mr *MockmalfeasanceInfoMockRecorder) Info(data any) *MockmalfeasanceInfoInfoCall { +func (mr *MockmalfeasanceInfoMockRecorder) Info(ctx, nodeID any) *MockmalfeasanceInfoInfoCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockmalfeasanceInfo)(nil).Info), data) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockmalfeasanceInfo)(nil).Info), ctx, nodeID) return &MockmalfeasanceInfoInfoCall{Call: call} } @@ -67,13 +69,13 @@ func (c *MockmalfeasanceInfoInfoCall) Return(arg0 map[string]string, arg1 error) } // Do rewrite *gomock.Call.Do -func (c *MockmalfeasanceInfoInfoCall) Do(f func([]byte) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { +func (c *MockmalfeasanceInfoInfoCall) Do(f func(context.Context, types.NodeID) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockmalfeasanceInfoInfoCall) DoAndReturn(f func([]byte) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { +func (c *MockmalfeasanceInfoInfoCall) DoAndReturn(f func(context.Context, types.NodeID) (map[string]string, error)) *MockmalfeasanceInfoInfoCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/events/events.go b/events/events.go index 8a07fa5bc5..80b2f1ab67 100644 --- a/events/events.go +++ b/events/events.go @@ -7,10 +7,8 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/log" - "github.com/spacemeshos/go-spacemesh/malfeasance/wire" ) type UserEvent struct { @@ -332,21 +330,25 @@ func EmitProposal(nodeID types.NodeID, layer types.LayerID, proposal types.Propo &pb.Event_Proposal{ Proposal: &pb.EventProposal{ Layer: layer.Uint32(), - Proposal: proposal[:], + Proposal: proposal.Bytes(), Smesher: nodeID.Bytes(), }, }, ) } -func EmitOwnMalfeasanceProof(nodeID types.NodeID, proof []byte) { +func EmitOwnMalfeasanceProof(nodeID types.NodeID) { const help = "Node committed malicious behavior. Identity will be canceled." emitUserEvent( help, false, &pb.Event_Malfeasance{ Malfeasance: &pb.EventMalfeasance{ - Proof: ToMalfeasancePB(nodeID, proof, false), + Proof: &pb.MalfeasanceProof{ + SmesherId: &pb.SmesherId{Id: nodeID.Bytes()}, + Layer: &pb.LayerNumber{Number: uint32(0)}, + Kind: pb.MalfeasanceProof_MALFEASANCE_UNSPECIFIED, + }, }, }, ) @@ -366,33 +368,3 @@ func emitUserEvent(help string, failure bool, details pb.IsEventDetails) { } } } - -func ToMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof { - mp := &wire.MalfeasanceProof{} - if err := codec.Decode(proof, mp); err != nil { - return &pb.MalfeasanceProof{} - } - kind := pb.MalfeasanceProof_MALFEASANCE_UNSPECIFIED - switch mp.Proof.Type { - case wire.MultipleATXs: - kind = pb.MalfeasanceProof_MALFEASANCE_ATX - case wire.MultipleBallots: - kind = pb.MalfeasanceProof_MALFEASANCE_BALLOT - case wire.HareEquivocation: - kind = pb.MalfeasanceProof_MALFEASANCE_HARE - case wire.InvalidPostIndex: - kind = pb.MalfeasanceProof_MALFEASANCE_POST_INDEX - case wire.InvalidPrevATX: - kind = pb.MalfeasanceProof_MALFEASANCE_INCORRECT_PREV_ATX - } - result := &pb.MalfeasanceProof{ - SmesherId: &pb.SmesherId{Id: nodeID.Bytes()}, - Layer: &pb.LayerNumber{Number: mp.Layer.Uint32()}, - Kind: kind, - DebugInfo: wire.MalfeasanceInfo(nodeID, mp), - } - if includeProof { - result.Proof = proof - } - return result -} diff --git a/events/malfeasance.go b/events/malfeasance.go index 4ea0ef6ea8..ba789881d1 100644 --- a/events/malfeasance.go +++ b/events/malfeasance.go @@ -8,7 +8,6 @@ import ( // EventMalfeasance includes the malfeasance proof. type EventMalfeasance struct { Smesher types.NodeID - Proof []byte } // SubscribeMalfeasance subscribes malfeasance events. @@ -26,11 +25,11 @@ func SubscribeMalfeasance() Subscription { } // ReportMalfeasance reports a malfeasance proof. -func ReportMalfeasance(nodeID types.NodeID, proof []byte) { +func ReportMalfeasance(nodeID types.NodeID) { mu.RLock() defer mu.RUnlock() if reporter != nil { - if err := reporter.malfeasanceEmitter.Emit(EventMalfeasance{Smesher: nodeID, Proof: proof}); err != nil { + if err := reporter.malfeasanceEmitter.Emit(EventMalfeasance{Smesher: nodeID}); err != nil { log.With().Error("failed to emit malfeasance proof", log.Err(err)) } } diff --git a/malfeasance/handler.go b/malfeasance/handler.go index b15eb2aa1f..4f44c4b941 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -77,11 +77,11 @@ func (h *Handler) RegisterHandler(malfeasanceType MalfeasanceType, handler Malfe h.handlers[malfeasanceType] = handler } -func (h *Handler) reportMalfeasance(smesher types.NodeID, proof []byte) { +func (h *Handler) reportMalfeasance(smesher types.NodeID) { h.tortoise.OnMalfeasance(smesher) - events.ReportMalfeasance(smesher, proof) + events.ReportMalfeasance(smesher) if slices.Contains(h.nodeIDs, smesher) { - events.EmitOwnMalfeasanceProof(smesher, proof) + events.EmitOwnMalfeasanceProof(smesher) } } @@ -93,9 +93,14 @@ func (h *Handler) countInvalidProof(p *wire.MalfeasanceProof) { h.handlers[MalfeasanceType(p.Proof.Type)].ReportInvalidProof(numInvalidProofs) } -func (h *Handler) Info(data []byte) (map[string]string, error) { +func (h *Handler) Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) { + var blob sql.Blob + if err := identities.LoadMalfeasanceBlob(ctx, h.cdb, nodeID.Bytes(), &blob); err != nil { + return nil, fmt.Errorf("load malfeasance proof: %w", err) + } + var p wire.MalfeasanceProof - if err := codec.Decode(data, &p); err != nil { + if err := codec.Decode(blob.Bytes, &p); err != nil { return nil, fmt.Errorf("decode malfeasance proof: %w", err) } mh, ok := h.handlers[MalfeasanceType(p.Proof.Type)] @@ -170,7 +175,7 @@ func (h *Handler) HandleMalfeasanceProof(ctx context.Context, peer p2p.Peer, dat h.countInvalidProof(&p.MalfeasanceProof) return fmt.Errorf("%w: %s", pubsub.ErrValidationReject, err) } - h.reportMalfeasance(id, codec.MustEncode(&p.MalfeasanceProof)) + h.reportMalfeasance(id) // node saves malfeasance proof eagerly/atomically with the malicious data. // it has validated the proof before saving to db. h.countProof(&p.MalfeasanceProof) @@ -219,7 +224,7 @@ func (h *Handler) validateAndSave(ctx context.Context, p *wire.MalfeasanceProof) } return nodeID, err } - h.reportMalfeasance(nodeID, proofBytes) + h.reportMalfeasance(nodeID) h.cdb.CacheMalfeasanceProof(nodeID, proofBytes) h.countProof(p) h.logger.Debug("new malfeasance proof", diff --git a/malfeasance/handler_test.go b/malfeasance/handler_test.go index adb1e5af55..e08affc7de 100644 --- a/malfeasance/handler_test.go +++ b/malfeasance/handler_test.go @@ -20,6 +20,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/malfeasance/wire" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/identities" @@ -29,13 +30,14 @@ import ( type testMalfeasanceHandler struct { *Handler - db sql.StateDatabase - mockTrt *Mocktortoise + observedLogs *observer.ObservedLogs + db sql.StateDatabase + mockTrt *Mocktortoise } func newHandler(tb testing.TB) *testMalfeasanceHandler { db := statesql.InMemoryTest(tb) - observer, _ := observer.New(zapcore.WarnLevel) + observer, observedLogs := observer.New(zapcore.WarnLevel) logger := zaptest.NewLogger(tb, zaptest.WrapOptions(zap.WrapCore( func(core zapcore.Core) zapcore.Core { return zapcore.NewTee(core, observer) @@ -58,8 +60,9 @@ func newHandler(tb testing.TB) *testMalfeasanceHandler { return &testMalfeasanceHandler{ Handler: h, - db: db, - mockTrt: trt, + observedLogs: observedLogs, + db: db, + mockTrt: trt, } } @@ -253,15 +256,23 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { }, } + expectedHash := types.RandomHash() h.mockTrt.EXPECT().OnMalfeasance(nodeID) err := h.HandleSyncedMalfeasanceProof( context.Background(), - types.RandomHash(), + expectedHash, "peer", codec.MustEncode(proof), ) require.ErrorIs(t, err, errWrongHash) require.ErrorIs(t, err, pubsub.ErrValidationReject) + + require.Equal(t, 1, h.observedLogs.Len()) + log := h.observedLogs.All()[0] + require.Equal(t, zap.WarnLevel, log.Level) + require.Contains(t, log.Message, "malfeasance proof for wrong identity") + require.Equal(t, expectedHash.ShortString(), log.ContextMap()["expected"]) + require.Equal(t, p2p.Peer("peer").String(), log.ContextMap()["peer"]) }) t.Run("invalid proof", func(t *testing.T) { @@ -374,11 +385,12 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { } func TestHandler_Info(t *testing.T) { - t.Run("malformed data", func(t *testing.T) { + t.Run("unknown identity", func(t *testing.T) { h := newHandler(t) - info, err := h.Info(types.RandomBytes(32)) - require.ErrorContains(t, err, "decode malfeasance proof:") + info, err := h.Info(context.Background(), types.RandomNodeID()) + require.ErrorContains(t, err, "load malfeasance proof:") + require.ErrorIs(t, err, sql.ErrNotFound) require.Nil(t, info) }) @@ -392,9 +404,11 @@ func TestHandler_Info(t *testing.T) { Data: &wire.AtxProof{}, }, } + nodeID := types.RandomNodeID() proofBytes := codec.MustEncode(proof) + require.NoError(t, identities.SetMalicious(h.db, nodeID, proofBytes, time.Now())) - info, err := h.Info(proofBytes) + info, err := h.Info(context.Background(), nodeID) require.ErrorContains(t, err, fmt.Sprintf("unknown malfeasance type %d", wire.MultipleATXs)) require.Nil(t, info) }) @@ -414,9 +428,11 @@ func TestHandler_Info(t *testing.T) { Data: &wire.AtxProof{}, }, } + nodeID := types.RandomNodeID() proofBytes := codec.MustEncode(proof) + require.NoError(t, identities.SetMalicious(h.db, nodeID, proofBytes, time.Now())) - info, err := h.Info(proofBytes) + info, err := h.Info(context.Background(), nodeID) require.ErrorContains(t, err, "invalid proof") require.Nil(t, info) }) @@ -440,7 +456,10 @@ func TestHandler_Info(t *testing.T) { Data: &wire.AtxProof{}, }, } + nodeID := types.RandomNodeID() proofBytes := codec.MustEncode(proof) + require.NoError(t, identities.SetMalicious(h.db, nodeID, proofBytes, time.Now())) + expectedProperties := map[string]string{ "domain": "0", "type": strconv.FormatUint(uint64(wire.MultipleATXs), 10), @@ -449,7 +468,7 @@ func TestHandler_Info(t *testing.T) { expectedProperties[k] = v } - info, err := h.Info(proofBytes) + info, err := h.Info(context.Background(), nodeID) require.NoError(t, err) require.Equal(t, expectedProperties, info) }) diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go new file mode 100644 index 0000000000..392299a8a1 --- /dev/null +++ b/malfeasance2/handler.go @@ -0,0 +1,44 @@ +package malfeasance2 + +import ( + "context" + + "go.uber.org/zap" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/p2p" + "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/sql" +) + +// nolint:unused +type Handler struct { + logger *zap.Logger + db sql.Executor + self p2p.Peer + nodeIDs []types.NodeID + edVerifier *signing.EdVerifier + tortoise tortoise +} + +func NewHandler( + db sql.Executor, + lg *zap.Logger, + self p2p.Peer, + nodeIDs []types.NodeID, + edVerifier *signing.EdVerifier, + tortoise tortoise, +) *Handler { + return &Handler{ + db: db, + logger: lg, + self: self, + nodeIDs: nodeIDs, + edVerifier: edVerifier, + tortoise: tortoise, + } +} + +func (h *Handler) Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error) { + return nil, sql.ErrNotFound +} diff --git a/malfeasance2/interface.go b/malfeasance2/interface.go new file mode 100644 index 0000000000..12bd87e98a --- /dev/null +++ b/malfeasance2/interface.go @@ -0,0 +1,11 @@ +package malfeasance2 + +import ( + "github.com/spacemeshos/go-spacemesh/common/types" +) + +//go:generate mockgen -typed -package=malfeasance2 -destination=./mocks.go -source=./interface.go + +type tortoise interface { + OnMalfeasance(types.NodeID) +} diff --git a/malfeasance2/mocks.go b/malfeasance2/mocks.go new file mode 100644 index 0000000000..c1c68d418e --- /dev/null +++ b/malfeasance2/mocks.go @@ -0,0 +1,77 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go +// +// Generated by this command: +// +// mockgen -typed -package=malfeasance2 -destination=./mocks.go -source=./interface.go +// + +// Package malfeasance2 is a generated GoMock package. +package malfeasance2 + +import ( + reflect "reflect" + + types "github.com/spacemeshos/go-spacemesh/common/types" + gomock "go.uber.org/mock/gomock" +) + +// Mocktortoise is a mock of tortoise interface. +type Mocktortoise struct { + ctrl *gomock.Controller + recorder *MocktortoiseMockRecorder + isgomock struct{} +} + +// MocktortoiseMockRecorder is the mock recorder for Mocktortoise. +type MocktortoiseMockRecorder struct { + mock *Mocktortoise +} + +// NewMocktortoise creates a new mock instance. +func NewMocktortoise(ctrl *gomock.Controller) *Mocktortoise { + mock := &Mocktortoise{ctrl: ctrl} + mock.recorder = &MocktortoiseMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocktortoise) EXPECT() *MocktortoiseMockRecorder { + return m.recorder +} + +// OnMalfeasance mocks base method. +func (m *Mocktortoise) OnMalfeasance(arg0 types.NodeID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnMalfeasance", arg0) +} + +// OnMalfeasance indicates an expected call of OnMalfeasance. +func (mr *MocktortoiseMockRecorder) OnMalfeasance(arg0 any) *MocktortoiseOnMalfeasanceCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnMalfeasance", reflect.TypeOf((*Mocktortoise)(nil).OnMalfeasance), arg0) + return &MocktortoiseOnMalfeasanceCall{Call: call} +} + +// MocktortoiseOnMalfeasanceCall wrap *gomock.Call +type MocktortoiseOnMalfeasanceCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocktortoiseOnMalfeasanceCall) Return() *MocktortoiseOnMalfeasanceCall { + c.Call = c.Call.Return() + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocktortoiseOnMalfeasanceCall) Do(f func(types.NodeID)) *MocktortoiseOnMalfeasanceCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocktortoiseOnMalfeasanceCall) DoAndReturn(f func(types.NodeID)) *MocktortoiseOnMalfeasanceCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/node/node.go b/node/node.go index f8c88b015b..ab1c64c9af 100644 --- a/node/node.go +++ b/node/node.go @@ -61,6 +61,7 @@ import ( "github.com/spacemeshos/go-spacemesh/layerpatrol" "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/malfeasance" + "github.com/spacemeshos/go-spacemesh/malfeasance2" "github.com/spacemeshos/go-spacemesh/mesh" "github.com/spacemeshos/go-spacemesh/metrics" "github.com/spacemeshos/go-spacemesh/metrics/public" @@ -378,50 +379,51 @@ func New(opts ...Option) *App { // App is the cli app singleton. type App struct { *cobra.Command - fileLock *flock.Flock - signers []*signing.EdSigner - Config *config.Config - db sql.StateDatabase - apiDB sql.StateDatabase - cachedDB *datastore.CachedDB - dbMetrics *dbmetrics.DBMetricsCollector - localDB sql.LocalDatabase - grpcPublicServer *grpcserver.Server - grpcPrivateServer *grpcserver.Server - grpcPostServer *grpcserver.Server - grpcTLSServer *grpcserver.Server - jsonAPIServer *grpcserver.JSONHTTPServer - grpcServices map[grpcserver.Service]grpcserver.ServiceAPI - pprofService *http.Server - profilerService *pyroscope.Profiler - syncer *syncer.Syncer - proposalBuilder *miner.ProposalBuilder - mesh *mesh.Mesh - atxsdata *atxsdata.Data - clock *timesync.NodeClock - hare3 *hare3.Hare - hare4 *hare4.Hare - hareResultsChan chan hare4.ConsensusOutput - hOracle *eligibility.Oracle - blockGen *blocks.Generator - certifier *blocks.Certifier - atxBuilder *activation.Builder - atxHandler *activation.Handler - txHandler *txs.TxHandler - validator *activation.Validator - edVerifier *signing.EdVerifier - beaconProtocol *beacon.ProtocolDriver - log log.Log - syncLogger log.Log - conState *txs.ConservativeState - fetcher *fetch.Fetch - ptimesync *peersync.Sync - updater *bootstrap.Updater - poetDb *activation.PoetDb - postVerifier activation.PostVerifier - postSupervisor *activation.PostSupervisor - malfeasanceHandler *malfeasance.Handler - errCh chan error + fileLock *flock.Flock + signers []*signing.EdSigner + Config *config.Config + db sql.StateDatabase + apiDB sql.StateDatabase + cachedDB *datastore.CachedDB + dbMetrics *dbmetrics.DBMetricsCollector + localDB sql.LocalDatabase + grpcPublicServer *grpcserver.Server + grpcPrivateServer *grpcserver.Server + grpcPostServer *grpcserver.Server + grpcTLSServer *grpcserver.Server + jsonAPIServer *grpcserver.JSONHTTPServer + grpcServices map[grpcserver.Service]grpcserver.ServiceAPI + pprofService *http.Server + profilerService *pyroscope.Profiler + syncer *syncer.Syncer + proposalBuilder *miner.ProposalBuilder + mesh *mesh.Mesh + atxsdata *atxsdata.Data + clock *timesync.NodeClock + hare3 *hare3.Hare + hare4 *hare4.Hare + hareResultsChan chan hare4.ConsensusOutput + hOracle *eligibility.Oracle + blockGen *blocks.Generator + certifier *blocks.Certifier + atxBuilder *activation.Builder + atxHandler *activation.Handler + txHandler *txs.TxHandler + validator *activation.Validator + edVerifier *signing.EdVerifier + beaconProtocol *beacon.ProtocolDriver + log log.Log + syncLogger log.Log + conState *txs.ConservativeState + fetcher *fetch.Fetch + ptimesync *peersync.Sync + updater *bootstrap.Updater + poetDb *activation.PoetDb + postVerifier activation.PostVerifier + postSupervisor *activation.PostSupervisor + malfeasanceHandler *malfeasance.Handler + malfeasance2Handler *malfeasance2.Handler + errCh chan error host *p2p.Host @@ -1161,18 +1163,27 @@ func (app *App) initServices(ctx context.Context) error { for _, s := range app.signers { nodeIDs = append(nodeIDs, s.NodeID()) } - app.malfeasanceHandler = malfeasance.NewHandler( + malHandler := malfeasance.NewHandler( app.cachedDB, malfeasanceLogger, app.host.ID(), nodeIDs, trtl, ) - app.malfeasanceHandler.RegisterHandler(malfeasance.MultipleATXs, activationMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.MultipleBallots, meshMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.HareEquivocation, hareMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.InvalidPostIndex, invalidPostMH) - app.malfeasanceHandler.RegisterHandler(malfeasance.InvalidPrevATX, invalidPrevMH) + malHandler.RegisterHandler(malfeasance.MultipleATXs, activationMH) + malHandler.RegisterHandler(malfeasance.MultipleBallots, meshMH) + malHandler.RegisterHandler(malfeasance.HareEquivocation, hareMH) + malHandler.RegisterHandler(malfeasance.InvalidPostIndex, invalidPostMH) + malHandler.RegisterHandler(malfeasance.InvalidPrevATX, invalidPrevMH) + + malHandler2 := malfeasance2.NewHandler( + app.cachedDB, + malfeasanceLogger, + app.host.ID(), + nodeIDs, + app.edVerifier, + trtl, + ) fetcher.SetValidators( fetch.ValidatorFunc( @@ -1217,7 +1228,7 @@ func (app *App) initServices(ctx context.Context) error { ), fetch.ValidatorFunc( pubsub.DropPeerOnSyncValidationReject( - app.malfeasanceHandler.HandleSyncedMalfeasanceProof, + malHandler.HandleSyncedMalfeasanceProof, app.host, lg.Zap(), ), @@ -1278,7 +1289,7 @@ func (app *App) initServices(ctx context.Context) error { ) app.host.Register( pubsub.MalfeasanceProof, - pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), + pubsub.ChainGossipHandler(checkAtxSynced, malHandler.HandleMalfeasanceProof), ) app.proposalBuilder = proposalBuilder @@ -1286,6 +1297,8 @@ func (app *App) initServices(ctx context.Context) error { app.syncer = syncer app.atxBuilder = atxBuilder app.atxHandler = atxHandler + app.malfeasanceHandler = malHandler + app.malfeasance2Handler = malHandler2 app.poetDb = poetDb app.fetcher = fetcher app.beaconProtocol = beaconProtocol @@ -1573,11 +1586,11 @@ func (app *App) grpcService(svc grpcserver.Service, lg log.Log) (grpcserver.Serv app.grpcServices[svc] = service return service, nil case v2alpha1.Malfeasance: - service := v2alpha1.NewMalfeasanceService(app.apiDB, app.malfeasanceHandler) + service := v2alpha1.NewMalfeasanceService(app.apiDB, app.malfeasance2Handler, app.malfeasanceHandler) app.grpcServices[svc] = service return service, nil case v2alpha1.MalfeasanceStream: - service := v2alpha1.NewMalfeasanceStreamService(app.apiDB, app.malfeasanceHandler) + service := v2alpha1.NewMalfeasanceStreamService(app.apiDB, app.malfeasance2Handler, app.malfeasanceHandler) app.grpcServices[svc] = service return service, nil case v2alpha1.Network: diff --git a/sql/identities/identities.go b/sql/identities/identities.go index d0659fd982..99964b5de9 100644 --- a/sql/identities/identities.go +++ b/sql/identities/identities.go @@ -96,3 +96,16 @@ func AllMalicious(db sql.Executor) ([]types.NodeID, error) { } return ids, nil } + +// CountMalicious returns the number of malicious nodes. +func CountMalicious(db sql.Executor) (uint64, error) { + var count uint64 + _, err := db.Exec(` + SELECT COUNT(*) + FROM identities + `, nil, func(stmt *sql.Statement) bool { + count = uint64(stmt.ColumnInt64(0)) + return false + }) + return count, err +} diff --git a/sql/identities/identities_test.go b/sql/identities/identities_test.go index 8e357bc04f..9e0545cdaf 100644 --- a/sql/identities/identities_test.go +++ b/sql/identities/identities_test.go @@ -70,20 +70,35 @@ func Test_GetMalicious(t *testing.T) { for i := 0; i < numBad; i++ { nid := types.NodeID{byte(i + 1)} bad = append(bad, nid) - require.NoError(t, identities.SetMalicious(db, nid, types.RandomBytes(11), time.Now().Local())) + require.NoError(t, identities.SetMalicious(db, nid, types.RandomBytes(11), time.Now())) } got, err = identities.AllMalicious(db) require.NoError(t, err) require.Equal(t, bad, got) } +func Test_CountMalicious(t *testing.T) { + db := statesql.InMemoryTest(t) + got, err := identities.CountMalicious(db) + require.NoError(t, err) + require.Zero(t, got) + + const numBad = uint64(11) + for range numBad { + require.NoError(t, identities.SetMalicious(db, types.RandomNodeID(), types.RandomBytes(11), time.Now())) + } + got, err = identities.CountMalicious(db) + require.NoError(t, err) + require.Equal(t, numBad, got) +} + func TestLoadMalfeasanceBlob(t *testing.T) { db := statesql.InMemoryTest(t) ctx := context.Background() nid1 := types.RandomNodeID() proof1 := types.RandomBytes(11) - identities.SetMalicious(db, nid1, proof1, time.Now().Local()) + identities.SetMalicious(db, nid1, proof1, time.Now()) var blob1 sql.Blob require.NoError(t, identities.LoadMalfeasanceBlob(ctx, db, nid1.Bytes(), &blob1)) @@ -95,7 +110,7 @@ func TestLoadMalfeasanceBlob(t *testing.T) { nid2 := types.RandomNodeID() proof2 := types.RandomBytes(12) - identities.SetMalicious(db, nid2, proof2, time.Now().Local()) + identities.SetMalicious(db, nid2, proof2, time.Now()) var blob2 sql.Blob require.NoError(t, identities.LoadMalfeasanceBlob(ctx, db, nid2.Bytes(), &blob2)) diff --git a/sql/malfeasance/malfeasance.go b/sql/malfeasance/malfeasance.go index c45c47895d..416743e153 100644 --- a/sql/malfeasance/malfeasance.go +++ b/sql/malfeasance/malfeasance.go @@ -6,23 +6,10 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/builder" "github.com/spacemeshos/go-spacemesh/sql/marriage" ) -func IsMalicious(db sql.Executor, nodeID types.NodeID) (bool, error) { - rows, err := db.Exec(` - SELECT 1 - FROM malfeasance - WHERE pubkey = ?1 - `, func(stmt *sql.Statement) { - stmt.BindBytes(1, nodeID.Bytes()) - }, nil) - if err != nil { - return false, fmt.Errorf("is malicious %v: %w", nodeID, err) - } - return rows > 0, nil -} - func AddProof( db sql.Executor, nodeID types.NodeID, @@ -68,3 +55,43 @@ func SetMalicious(db sql.Executor, nodeID types.NodeID, marriageID marriage.ID, } return nil } + +func IsMalicious(db sql.Executor, nodeID types.NodeID) (bool, error) { + rows, err := db.Exec(` + SELECT 1 + FROM malfeasance + WHERE pubkey = ?1 + `, func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + }, nil) + if err != nil { + return false, fmt.Errorf("is malicious %v: %w", nodeID, err) + } + return rows > 0, nil +} + +func IterateOps( + db sql.Executor, + operations builder.Operations, + fn func(types.NodeID, []byte, int, time.Time) bool, +) error { + fullQuery := ` + SELECT pubkey, proof, domain, received + FROM malfeasance + ` + builder.FilterFrom(operations) + _, err := db.Exec(fullQuery, builder.BindingsFrom(operations), + func(stmt *sql.Statement) bool { + var id types.NodeID + stmt.ColumnBytes(0, id[:]) + var proof []byte + if stmt.ColumnLen(1) > 0 { + proof = make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(1, proof) + } + domain := int(stmt.ColumnInt64(2)) + received := time.Unix(0, stmt.ColumnInt64(3)) + return fn(id, proof, domain, received) + }, + ) + return err +} diff --git a/sql/malfeasance/malfeasance_test.go b/sql/malfeasance/malfeasance_test.go index 65bc13f5ab..e191bca7e6 100644 --- a/sql/malfeasance/malfeasance_test.go +++ b/sql/malfeasance/malfeasance_test.go @@ -1,12 +1,14 @@ package malfeasance_test import ( + "math/rand/v2" "testing" "time" "github.com/stretchr/testify/require" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql/builder" "github.com/spacemeshos/go-spacemesh/sql/malfeasance" "github.com/spacemeshos/go-spacemesh/sql/marriage" "github.com/spacemeshos/go-spacemesh/sql/statesql" @@ -19,7 +21,7 @@ func TestAdd(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - err := malfeasance.AddProof(db, types.RandomNodeID(), nil, nil, 0, time.Now()) + err := malfeasance.AddProof(db, types.RandomNodeID(), nil, nil, 1, time.Now()) require.Error(t, err) }) @@ -27,7 +29,7 @@ func TestAdd(t *testing.T) { t.Parallel() db := statesql.InMemoryTest(t) - err := malfeasance.AddProof(db, types.RandomNodeID(), nil, types.RandomBytes(100), 0, time.Now()) + err := malfeasance.AddProof(db, types.RandomNodeID(), nil, types.RandomBytes(100), 1, time.Now()) require.NoError(t, err) }) @@ -36,7 +38,7 @@ func TestAdd(t *testing.T) { db := statesql.InMemoryTest(t) id := marriage.ID(100) - err := malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 0, time.Now()) + err := malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 1, time.Now()) require.Error(t, err) }) @@ -58,7 +60,7 @@ func TestAdd(t *testing.T) { }) require.NoError(t, err) - err = malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 0, time.Now()) + err = malfeasance.AddProof(db, types.RandomNodeID(), &id, nil, 1, time.Now()) require.NoError(t, err) }) @@ -80,37 +82,11 @@ func TestAdd(t *testing.T) { }) require.NoError(t, err) - err = malfeasance.AddProof(db, types.RandomNodeID(), &id, types.RandomBytes(100), 0, time.Now()) + err = malfeasance.AddProof(db, types.RandomNodeID(), &id, types.RandomBytes(100), 1, time.Now()) require.NoError(t, err) }) } -func TestIsMalicious(t *testing.T) { - t.Parallel() - - t.Run("unknown node is not malicious", func(t *testing.T) { - t.Parallel() - db := statesql.InMemoryTest(t) - - mal, err := malfeasance.IsMalicious(db, types.RandomNodeID()) - require.NoError(t, err) - require.False(t, mal) - }) - - t.Run("known node is malicious", func(t *testing.T) { - t.Parallel() - db := statesql.InMemoryTest(t) - - nodeID := types.RandomNodeID() - err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 0, time.Now()) - require.NoError(t, err) - - mal, err := malfeasance.IsMalicious(db, nodeID) - require.NoError(t, err) - require.True(t, mal) - }) -} - func TestSetMalicious(t *testing.T) { t.Parallel() @@ -158,7 +134,7 @@ func TestSetMalicious(t *testing.T) { db := statesql.InMemoryTest(t) nodeID := types.RandomNodeID() - err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 0, time.Now()) + err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 1, time.Now()) require.NoError(t, err) err = malfeasance.SetMalicious(db, nodeID, marriage.ID(0), time.Now()) @@ -177,7 +153,7 @@ func TestSetMalicious(t *testing.T) { require.NoError(t, err) nodeID := types.RandomNodeID() - err = malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 0, time.Now()) + err = malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 1, time.Now()) require.NoError(t, err) err = marriage.Add(db, marriage.Info{ @@ -198,3 +174,135 @@ func TestSetMalicious(t *testing.T) { require.True(t, mal) }) } + +func TestIsMalicious(t *testing.T) { + t.Parallel() + + t.Run("unknown node is not malicious", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + mal, err := malfeasance.IsMalicious(db, types.RandomNodeID()) + require.NoError(t, err) + require.False(t, mal) + }) + + t.Run("known node is malicious", func(t *testing.T) { + t.Parallel() + db := statesql.InMemoryTest(t) + + nodeID := types.RandomNodeID() + err := malfeasance.AddProof(db, nodeID, nil, types.RandomBytes(100), 1, time.Now()) + require.NoError(t, err) + + mal, err := malfeasance.IsMalicious(db, nodeID) + require.NoError(t, err) + require.True(t, mal) + }) +} + +func Test_IterateMaliciousOps(t *testing.T) { + db := statesql.InMemoryTest(t) + tt := []struct { + id types.NodeID + proof []byte + domain int + }{ + { + types.RandomNodeID(), + types.RandomBytes(11), + rand.IntN(255), + }, + { + types.RandomNodeID(), + types.RandomBytes(11), + rand.IntN(255), + }, + { + types.RandomNodeID(), + types.RandomBytes(11), + rand.IntN(255), + }, + } + + for _, tc := range tt { + err := malfeasance.AddProof(db, tc.id, nil, tc.proof, tc.domain, time.Now()) + require.NoError(t, err) + } + + var got []struct { + id types.NodeID + proof []byte + domain int + } + err := malfeasance.IterateOps(db, builder.Operations{}, + func(id types.NodeID, proof []byte, domain int, _ time.Time) bool { + got = append(got, struct { + id types.NodeID + proof []byte + domain int + }{id, proof, domain}) + return true + }) + require.NoError(t, err) + require.ElementsMatch(t, tt, got) +} + +func Test_IterateMaliciousOps_Married(t *testing.T) { + db := statesql.InMemoryTest(t) + + nodeID := types.RandomNodeID() + marriageATX := types.RandomATXID() + id, err := marriage.NewID(db) + require.NoError(t, err) + + err = marriage.Add(db, marriage.Info{ + ID: id, + NodeID: nodeID, + ATX: marriageATX, + MarriageIndex: 0, + Target: nodeID, + Signature: types.RandomEdSignature(), + }) + require.NoError(t, err) + + ids := make([]types.NodeID, 5) + ids[0] = nodeID + proof := types.RandomBytes(11) + err = malfeasance.AddProof(db, ids[0], &id, proof, 1, time.Now()) + require.NoError(t, err) + + for i := 1; i < len(ids); i++ { + ids[i] = types.RandomNodeID() + err := malfeasance.SetMalicious(db, ids[i], id, time.Now()) + require.NoError(t, err) + } + + var got []struct { + id types.NodeID + proof []byte + domain int + } + err = malfeasance.IterateOps(db, builder.Operations{}, + func(id types.NodeID, proof []byte, domain int, _ time.Time) bool { + got = append(got, struct { + id types.NodeID + proof []byte + domain int + }{id, proof, domain}) + return true + }) + require.NoError(t, err) + require.Equal(t, len(ids), len(got)) + require.Equal(t, ids[0], got[0].id) + require.Equal(t, proof, got[0].proof) + require.Equal(t, 1, got[0].domain) + + ids = ids[1:] + got = got[1:] + for i, id := range ids { + require.Equal(t, id, got[i].id) + require.Nil(t, got[i].proof) + require.Zero(t, got[i].domain) + } +} From 07ba9f0e10aa96042ff6d645bcac756be4c84322 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:52:20 +0000 Subject: [PATCH 55/66] Fix flaky test --- api/grpcserver/v2alpha1/malfeasance.go | 8 +++---- api/grpcserver/v2alpha1/malfeasance_test.go | 5 ++-- api/grpcserver/v2alpha1/v2alpha1_test.go | 26 +++++++++++++++++++-- events/reporter.go | 9 +++++-- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/api/grpcserver/v2alpha1/malfeasance.go b/api/grpcserver/v2alpha1/malfeasance.go index d820d6e743..321748b014 100644 --- a/api/grpcserver/v2alpha1/malfeasance.go +++ b/api/grpcserver/v2alpha1/malfeasance.go @@ -136,10 +136,6 @@ func (s *MalfeasanceStreamService) Stream( request *spacemeshv2alpha1.MalfeasanceStreamRequest, stream spacemeshv2alpha1.MalfeasanceStreamService_StreamServer, ) error { - if err := stream.SendHeader(metadata.MD{}); err != nil { - return status.Errorf(codes.Unavailable, "can't send header") - } - legacyProofs, err := fetchLegacyFromDB( stream.Context(), s.db, @@ -191,6 +187,10 @@ func (s *MalfeasanceStreamService) Stream( eventsOut := sub.Out() eventsFull := sub.Full() + if err := stream.SendHeader(metadata.MD{}); err != nil { + return status.Errorf(codes.Unavailable, "can't send header") + } + for { select { // process pending events first diff --git a/api/grpcserver/v2alpha1/malfeasance_test.go b/api/grpcserver/v2alpha1/malfeasance_test.go index 4a5f86c369..cb6f7cab40 100644 --- a/api/grpcserver/v2alpha1/malfeasance_test.go +++ b/api/grpcserver/v2alpha1/malfeasance_test.go @@ -294,13 +294,12 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { client := setup(t, db, info, legacyInfo) const ( - start = 100 nLegacy = 5 nIndividual = 5 nMarriage = 5 ) - var streamed []*events.EventMalfeasance + streamed := make([]*events.EventMalfeasance, 0, nLegacy+nIndividual+nMarriage) for i := 0; i < nLegacy; i++ { smesher := types.RandomNodeID() streamed = append(streamed, &events.EventMalfeasance{ @@ -374,7 +373,7 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { _, err = stream.Header() require.NoError(t, err) - var expect []types.NodeID + expect := make([]types.NodeID, 0, len(request.SmesherId)) for _, rst := range streamed { events.ReportMalfeasance(rst.Smesher) matcher := malfeasanceMatcher{request} diff --git a/api/grpcserver/v2alpha1/v2alpha1_test.go b/api/grpcserver/v2alpha1/v2alpha1_test.go index 52ba08f556..fea6adf9ab 100644 --- a/api/grpcserver/v2alpha1/v2alpha1_test.go +++ b/api/grpcserver/v2alpha1/v2alpha1_test.go @@ -1,13 +1,15 @@ package v2alpha1 import ( + "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap/zaptest" + "go.uber.org/zap" "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials/insecure" "github.com/spacemeshos/go-spacemesh/api/grpcserver" @@ -20,7 +22,9 @@ const ( func launchServer(tb testing.TB, services ...grpcserver.ServiceAPI) (grpcserver.Config, func()) { cfg := grpcserver.DefaultTestConfig() - grpc, err := grpcserver.NewWithServices(cfg.PublicListener, zaptest.NewLogger(tb).Named("grpc"), cfg, services) + // logger := zaptest.NewLogger(tb).Named("grpc") + logger := zap.NewNop() + grpc, err := grpcserver.NewWithServices(cfg.PublicListener, logger, cfg, services) require.NoError(tb, err) // start gRPC server @@ -39,6 +43,24 @@ func dialGrpc(tb testing.TB, cfg grpcserver.Config) *grpc.ClientConn { grpc.WithTransportCredentials(insecure.NewCredentials()), ) require.NoError(tb, err) + + // block until the clientConn is ready. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + for { + s := conn.GetState() + if s == connectivity.Ready { + break + } + if s == connectivity.Idle { + conn.Connect() + } + if !conn.WaitForStateChange(ctx, s) { + tb.Fatalf("timeout waiting for connection to %s", conn.Target()) + return nil + } + } + tb.Cleanup(func() { require.NoError(tb, conn.Close()) }) return conn } diff --git a/events/reporter.go b/events/reporter.go index 6e5a22367e..35c02ce969 100644 --- a/events/reporter.go +++ b/events/reporter.go @@ -138,6 +138,9 @@ func ReportNodeStatusUpdate() error { // ReportResult reports creation or receipt of a new tx receipt. func ReportResult(rst types.TransactionWithResult) error { + mu.RLock() + defer mu.RUnlock() + if reporter != nil { return reporter.resultsEmitter.Emit(rst) } @@ -451,6 +454,7 @@ func CloseEventReporter() { mu.Lock() defer mu.Unlock() if reporter != nil { + close(reporter.stopChan) if err := reporter.transactionEmitter.Close(); err != nil { log.With().Panic("failed to close transactionEmitter", log.Err(err)) } @@ -481,8 +485,9 @@ func CloseEventReporter() { if err := reporter.malfeasanceEmitter.Close(); err != nil { log.With().Panic("failed to close malfeasanceEmitter", log.Err(err)) } - - close(reporter.stopChan) + if err := reporter.events.emitter.Close(); err != nil { + log.With().Panic("failed to close eventsEmitter", log.Err(err)) + } reporter = nil } } From 700873759425d55f4d87080ddcee9a09c92bf904 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:52:20 +0000 Subject: [PATCH 56/66] Fix flaky test --- api/grpcserver/v2alpha1/malfeasance.go | 8 +++---- api/grpcserver/v2alpha1/malfeasance_test.go | 5 ++-- api/grpcserver/v2alpha1/v2alpha1_test.go | 26 +++++++++++++++++++-- events/reporter.go | 9 +++++-- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/api/grpcserver/v2alpha1/malfeasance.go b/api/grpcserver/v2alpha1/malfeasance.go index 3f055892ee..3dfea3afb1 100644 --- a/api/grpcserver/v2alpha1/malfeasance.go +++ b/api/grpcserver/v2alpha1/malfeasance.go @@ -136,10 +136,6 @@ func (s *MalfeasanceStreamService) Stream( request *spacemeshv2alpha1.MalfeasanceStreamRequest, stream spacemeshv2alpha1.MalfeasanceStreamService_StreamServer, ) error { - if err := stream.SendHeader(metadata.MD{}); err != nil { - return status.Errorf(codes.Unavailable, "can't send header") - } - legacyProofs, err := fetchLegacyFromDB( stream.Context(), s.db, @@ -191,6 +187,10 @@ func (s *MalfeasanceStreamService) Stream( eventsOut := sub.Out() eventsFull := sub.Full() + if err := stream.SendHeader(metadata.MD{}); err != nil { + return status.Errorf(codes.Unavailable, "can't send header") + } + for { select { // process pending events first diff --git a/api/grpcserver/v2alpha1/malfeasance_test.go b/api/grpcserver/v2alpha1/malfeasance_test.go index bc3904048d..4ed7616e3c 100644 --- a/api/grpcserver/v2alpha1/malfeasance_test.go +++ b/api/grpcserver/v2alpha1/malfeasance_test.go @@ -294,13 +294,12 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { client := setup(t, db, info, legacyInfo) const ( - start = 100 nLegacy = 5 nIndividual = 5 nMarriage = 5 ) - var streamed []*events.EventMalfeasance + streamed := make([]*events.EventMalfeasance, 0, nLegacy+nIndividual+nMarriage) for i := 0; i < nLegacy; i++ { smesher := types.RandomNodeID() streamed = append(streamed, &events.EventMalfeasance{ @@ -374,7 +373,7 @@ func TestMalfeasanceStreamService_Stream(t *testing.T) { _, err = stream.Header() require.NoError(t, err) - var expect []types.NodeID + expect := make([]types.NodeID, 0, len(request.SmesherId)) for _, rst := range streamed { events.ReportMalfeasance(rst.Smesher) matcher := malfeasanceMatcher{request} diff --git a/api/grpcserver/v2alpha1/v2alpha1_test.go b/api/grpcserver/v2alpha1/v2alpha1_test.go index 52ba08f556..fea6adf9ab 100644 --- a/api/grpcserver/v2alpha1/v2alpha1_test.go +++ b/api/grpcserver/v2alpha1/v2alpha1_test.go @@ -1,13 +1,15 @@ package v2alpha1 import ( + "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap/zaptest" + "go.uber.org/zap" "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials/insecure" "github.com/spacemeshos/go-spacemesh/api/grpcserver" @@ -20,7 +22,9 @@ const ( func launchServer(tb testing.TB, services ...grpcserver.ServiceAPI) (grpcserver.Config, func()) { cfg := grpcserver.DefaultTestConfig() - grpc, err := grpcserver.NewWithServices(cfg.PublicListener, zaptest.NewLogger(tb).Named("grpc"), cfg, services) + // logger := zaptest.NewLogger(tb).Named("grpc") + logger := zap.NewNop() + grpc, err := grpcserver.NewWithServices(cfg.PublicListener, logger, cfg, services) require.NoError(tb, err) // start gRPC server @@ -39,6 +43,24 @@ func dialGrpc(tb testing.TB, cfg grpcserver.Config) *grpc.ClientConn { grpc.WithTransportCredentials(insecure.NewCredentials()), ) require.NoError(tb, err) + + // block until the clientConn is ready. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + for { + s := conn.GetState() + if s == connectivity.Ready { + break + } + if s == connectivity.Idle { + conn.Connect() + } + if !conn.WaitForStateChange(ctx, s) { + tb.Fatalf("timeout waiting for connection to %s", conn.Target()) + return nil + } + } + tb.Cleanup(func() { require.NoError(tb, conn.Close()) }) return conn } diff --git a/events/reporter.go b/events/reporter.go index 6e5a22367e..35c02ce969 100644 --- a/events/reporter.go +++ b/events/reporter.go @@ -138,6 +138,9 @@ func ReportNodeStatusUpdate() error { // ReportResult reports creation or receipt of a new tx receipt. func ReportResult(rst types.TransactionWithResult) error { + mu.RLock() + defer mu.RUnlock() + if reporter != nil { return reporter.resultsEmitter.Emit(rst) } @@ -451,6 +454,7 @@ func CloseEventReporter() { mu.Lock() defer mu.Unlock() if reporter != nil { + close(reporter.stopChan) if err := reporter.transactionEmitter.Close(); err != nil { log.With().Panic("failed to close transactionEmitter", log.Err(err)) } @@ -481,8 +485,9 @@ func CloseEventReporter() { if err := reporter.malfeasanceEmitter.Close(); err != nil { log.With().Panic("failed to close malfeasanceEmitter", log.Err(err)) } - - close(reporter.stopChan) + if err := reporter.events.emitter.Close(); err != nil { + log.With().Panic("failed to close eventsEmitter", log.Err(err)) + } reporter = nil } } From a30a55c8066633788accc4e79027a4d220de11ce Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:58:07 +0000 Subject: [PATCH 57/66] Review feedback --- api/grpcserver/v2alpha1/malfeasance.go | 26 ++++++++++++++---------- api/grpcserver/v2alpha1/v2alpha1_test.go | 26 ++---------------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/api/grpcserver/v2alpha1/malfeasance.go b/api/grpcserver/v2alpha1/malfeasance.go index 3dfea3afb1..207a1c29c3 100644 --- a/api/grpcserver/v2alpha1/malfeasance.go +++ b/api/grpcserver/v2alpha1/malfeasance.go @@ -195,11 +195,15 @@ func (s *MalfeasanceStreamService) Stream( select { // process pending events first case rst := <-eventsOut: - proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) + proof := fetchMetaData(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { // try again with the new handler - proof = toProof(stream.Context(), s.info, rst.Smesher) + proof = fetchMetaData(stream.Context(), s.info, rst.Smesher) if proof == nil { + ctxzap.Debug(stream.Context(), "failed to get malfeasance info", + zap.String("smesher", rst.Smesher.String()), + zap.Error(err), + ) continue } } @@ -213,11 +217,15 @@ func (s *MalfeasanceStreamService) Stream( default: select { case rst := <-eventsOut: - proof := toProof(stream.Context(), s.infoLegacy, rst.Smesher) + proof := fetchMetaData(stream.Context(), s.infoLegacy, rst.Smesher) if proof == nil { // try again with the new handler - proof = toProof(stream.Context(), s.info, rst.Smesher) + proof = fetchMetaData(stream.Context(), s.info, rst.Smesher) if proof == nil { + ctxzap.Debug(stream.Context(), "failed to get malfeasance info", + zap.String("smesher", rst.Smesher.String()), + zap.Error(err), + ) continue } } @@ -237,17 +245,13 @@ func (s *MalfeasanceStreamService) Stream( } } -func toProof( +func fetchMetaData( ctx context.Context, info malfeasanceInfo, id types.NodeID, ) *spacemeshv2alpha1.MalfeasanceProof { properties, err := info.Info(ctx, id) if err != nil { - ctxzap.Debug(ctx, "failed to get malfeasance info", - zap.String("smesher", id.String()), - zap.Error(err), - ) return nil } domain, err := strconv.ParseUint(properties["domain"], 10, 64) @@ -297,7 +301,7 @@ func fetchFromDB( } proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, len(ids)) for _, id := range ids { - rst := toProof(ctx, info, id) + rst := fetchMetaData(ctx, info, id) if rst == nil { continue } @@ -325,7 +329,7 @@ func fetchLegacyFromDB( } proofs := make([]*spacemeshv2alpha1.MalfeasanceProof, 0, len(ids)) for _, id := range ids { - rst := toProof(ctx, info, id) + rst := fetchMetaData(ctx, info, id) if rst == nil { continue } diff --git a/api/grpcserver/v2alpha1/v2alpha1_test.go b/api/grpcserver/v2alpha1/v2alpha1_test.go index fea6adf9ab..52ba08f556 100644 --- a/api/grpcserver/v2alpha1/v2alpha1_test.go +++ b/api/grpcserver/v2alpha1/v2alpha1_test.go @@ -1,15 +1,13 @@ package v2alpha1 import ( - "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap" + "go.uber.org/zap/zaptest" "google.golang.org/grpc" - "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials/insecure" "github.com/spacemeshos/go-spacemesh/api/grpcserver" @@ -22,9 +20,7 @@ const ( func launchServer(tb testing.TB, services ...grpcserver.ServiceAPI) (grpcserver.Config, func()) { cfg := grpcserver.DefaultTestConfig() - // logger := zaptest.NewLogger(tb).Named("grpc") - logger := zap.NewNop() - grpc, err := grpcserver.NewWithServices(cfg.PublicListener, logger, cfg, services) + grpc, err := grpcserver.NewWithServices(cfg.PublicListener, zaptest.NewLogger(tb).Named("grpc"), cfg, services) require.NoError(tb, err) // start gRPC server @@ -43,24 +39,6 @@ func dialGrpc(tb testing.TB, cfg grpcserver.Config) *grpc.ClientConn { grpc.WithTransportCredentials(insecure.NewCredentials()), ) require.NoError(tb, err) - - // block until the clientConn is ready. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - for { - s := conn.GetState() - if s == connectivity.Ready { - break - } - if s == connectivity.Idle { - conn.Connect() - } - if !conn.WaitForStateChange(ctx, s) { - tb.Fatalf("timeout waiting for connection to %s", conn.Target()) - return nil - } - } - tb.Cleanup(func() { require.NoError(tb, conn.Close()) }) return conn } From 9e4f7f5e7b5cf2701d85b4ffc49fc76dbb635a63 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:08:44 +0000 Subject: [PATCH 58/66] Also exclude other incompatible versions --- go.mod | 13 +++++++++---- go.sum | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 7fc3fd08e8..5c3173abe7 100644 --- a/go.mod +++ b/go.mod @@ -244,7 +244,7 @@ require ( gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/api v0.210.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -261,6 +261,11 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -// temporary until this issue is resolved and cloud.google.com/go/storage has been updated -// https://github.com/googleapis/google-cloud-go/issues/11283 -exclude google.golang.org/grpc v1.69.0 +exclude ( + // temporary until this issue is resolved and cloud.google.com/go/storage has been updated + // https://github.com/googleapis/google-cloud-go/issues/11283 + google.golang.org/grpc v1.68.2 + google.golang.org/grpc v1.69.0 + google.golang.org/grpc v1.69.1 + google.golang.org/grpc v1.69.2 +) diff --git a/go.sum b/go.sum index c74f9e8746..831a75c083 100644 --- a/go.sum +++ b/go.sum @@ -937,8 +937,8 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= From 252d0340fb72bde6f82af502bfa740b6e209aa66 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:52:56 +0000 Subject: [PATCH 59/66] cleanup --- api/grpcserver/activation_service.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/grpcserver/activation_service.go b/api/grpcserver/activation_service.go index 8dc745906b..08fbecb2df 100644 --- a/api/grpcserver/activation_service.go +++ b/api/grpcserver/activation_service.go @@ -76,7 +76,6 @@ func (s *activationService) Get(ctx context.Context, request *pb.GetRequest) (*p proof, err := s.atxProvider.MalfeasanceProof(atx.SmesherID) if err != nil && !errors.Is(err, sql.ErrNotFound) { ctxzap.Error(ctx, "failed to get malfeasance proof", - zap.Stringer("smesher", atx.SmesherID), zap.Stringer("smesher", atx.SmesherID), zap.Stringer("id", atxId), zap.Error(err), From 5e2d62d44c083f600da7b1d52654b2c99f7ec052 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:58:41 +0000 Subject: [PATCH 60/66] Add missing assertions --- sql/identities/identities_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/identities/identities_test.go b/sql/identities/identities_test.go index 9e0545cdaf..06c7a60d73 100644 --- a/sql/identities/identities_test.go +++ b/sql/identities/identities_test.go @@ -98,7 +98,7 @@ func TestLoadMalfeasanceBlob(t *testing.T) { nid1 := types.RandomNodeID() proof1 := types.RandomBytes(11) - identities.SetMalicious(db, nid1, proof1, time.Now()) + require.NoError(t, identities.SetMalicious(db, nid1, proof1, time.Now())) var blob1 sql.Blob require.NoError(t, identities.LoadMalfeasanceBlob(ctx, db, nid1.Bytes(), &blob1)) @@ -110,7 +110,7 @@ func TestLoadMalfeasanceBlob(t *testing.T) { nid2 := types.RandomNodeID() proof2 := types.RandomBytes(12) - identities.SetMalicious(db, nid2, proof2, time.Now()) + require.NoError(t, identities.SetMalicious(db, nid2, proof2, time.Now())) var blob2 sql.Blob require.NoError(t, identities.LoadMalfeasanceBlob(ctx, db, nid2.Bytes(), &blob2)) From 14f8c3a0d8f8c806e0a2920df5bef617945e22f9 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:54:24 +0000 Subject: [PATCH 61/66] Cleanup --- api/grpcserver/mesh_service_test.go | 2 -- api/grpcserver/v2alpha1/v2alpha1_test.go | 26 ++---------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/api/grpcserver/mesh_service_test.go b/api/grpcserver/mesh_service_test.go index d87f027261..988750b7cc 100644 --- a/api/grpcserver/mesh_service_test.go +++ b/api/grpcserver/mesh_service_test.go @@ -251,14 +251,12 @@ func TestMeshService_MalfeasanceStream(t *testing.T) { id, proof := AtxMalfeasance(t, db) proofBytes := codec.MustEncode(proof) - require.NoError(t, identities.SetMalicious(db, id, proofBytes, time.Now())) events.ReportMalfeasance(id) resp, err := stream.Recv() require.NoError(t, err) require.Equal(t, toMalfeasancePB(id, proofBytes, false), resp.Proof) id, proof = BallotMalfeasance(t, db) proofBytes = codec.MustEncode(proof) - require.NoError(t, identities.SetMalicious(db, id, proofBytes, time.Now())) events.ReportMalfeasance(id) resp, err = stream.Recv() require.NoError(t, err) diff --git a/api/grpcserver/v2alpha1/v2alpha1_test.go b/api/grpcserver/v2alpha1/v2alpha1_test.go index fea6adf9ab..52ba08f556 100644 --- a/api/grpcserver/v2alpha1/v2alpha1_test.go +++ b/api/grpcserver/v2alpha1/v2alpha1_test.go @@ -1,15 +1,13 @@ package v2alpha1 import ( - "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap" + "go.uber.org/zap/zaptest" "google.golang.org/grpc" - "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials/insecure" "github.com/spacemeshos/go-spacemesh/api/grpcserver" @@ -22,9 +20,7 @@ const ( func launchServer(tb testing.TB, services ...grpcserver.ServiceAPI) (grpcserver.Config, func()) { cfg := grpcserver.DefaultTestConfig() - // logger := zaptest.NewLogger(tb).Named("grpc") - logger := zap.NewNop() - grpc, err := grpcserver.NewWithServices(cfg.PublicListener, logger, cfg, services) + grpc, err := grpcserver.NewWithServices(cfg.PublicListener, zaptest.NewLogger(tb).Named("grpc"), cfg, services) require.NoError(tb, err) // start gRPC server @@ -43,24 +39,6 @@ func dialGrpc(tb testing.TB, cfg grpcserver.Config) *grpc.ClientConn { grpc.WithTransportCredentials(insecure.NewCredentials()), ) require.NoError(tb, err) - - // block until the clientConn is ready. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - for { - s := conn.GetState() - if s == connectivity.Ready { - break - } - if s == connectivity.Idle { - conn.Connect() - } - if !conn.WaitForStateChange(ctx, s) { - tb.Fatalf("timeout waiting for connection to %s", conn.Target()) - return nil - } - } - tb.Cleanup(func() { require.NoError(tb, conn.Close()) }) return conn } From eaa006cc6012c5c583d37179455f6ef8c89cbb03 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:31:26 +0000 Subject: [PATCH 62/66] Re-add missing go generate --- activation/wire/malfeasance.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activation/wire/malfeasance.go b/activation/wire/malfeasance.go index dcb471bada..c38b51baf7 100644 --- a/activation/wire/malfeasance.go +++ b/activation/wire/malfeasance.go @@ -6,6 +6,8 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" ) +//go:generate scalegen + // MerkleTreeIndex is the index of the leaf containing the given field in the merkle tree. type MerkleTreeIndex uint64 @@ -87,7 +89,6 @@ type ATXProof struct { Version ProofVersion // ProofType is the type of proof that is being provided. ProofType ProofType - // Proof is the actual proof. Its type depends on the ProofType. Proof []byte `scale:"max=1048576"` // max size of proof is 1MiB } From 47fd921b9d62c42ba786e12324976003152cb117 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:01:20 +0000 Subject: [PATCH 63/66] Test metrics for malfeasance2 --- malfeasance2/handler_test.go | 77 ++++++++++++++++++++++++++++++++++++ malfeasance2/metrics.go | 12 +++--- malfeasance2/metrics_test.go | 11 ++++++ 3 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 malfeasance2/metrics_test.go diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index dcf98ceab0..7e4f1289f1 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -5,9 +5,11 @@ import ( "errors" "maps" "strconv" + "strings" "testing" "time" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "go.uber.org/zap" @@ -51,6 +53,9 @@ func newTestHandler(tb testing.TB) *testHandler { ctrl := gomock.NewController(tb) mockTrt := malfeasance2.NewMocktortoise(ctrl) + malfeasance2.NumValidProofs().Reset() + malfeasance2.NumInvalidProofs().Reset() + h := malfeasance2.NewHandler( db, logger, @@ -158,6 +163,18 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, handlerError) require.ErrorIs(t, err, pubsub.ErrValidationReject) + + expected := ` +# HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs +# TYPE spacemesh_malfeasance2_num_invalid_proofs counter +spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 +` + err = testutil.CollectAndCompare( + malfeasance2.NumInvalidProofs().WithLabelValues("ATX", "invalidPost"), + strings.NewReader(expected), + "spacemesh_malfeasance2_num_invalid_proofs", + ) + require.NoError(t, err) }) t.Run("valid proof", func(t *testing.T) { @@ -179,6 +196,18 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.Hash32(nodeID), "peer", codec.MustEncode(proof)) require.NoError(t, err) + expected := ` +# HELP spacemesh_malfeasance2_num_proofs number of malfeasance proofs +# TYPE spacemesh_malfeasance2_num_proofs counter +spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 +` + err = testutil.CollectAndCompare( + malfeasance2.NumValidProofs().WithLabelValues("ATX", "invalidPost"), + strings.NewReader(expected), + "spacemesh_malfeasance2_num_proofs", + ) + require.NoError(t, err) + malicious, err := malfeasance.IsMalicious(h.db, nodeID) require.NoError(t, err) require.True(t, malicious) @@ -210,6 +239,18 @@ func TestHandler_HandleSync(t *testing.T) { require.Contains(t, log.Message, "malfeasance proof for wrong identity") require.Equal(t, expectedHash.ShortString(), log.ContextMap()["expected"]) require.Equal(t, p2p.Peer("peer").String(), log.ContextMap()["peer"]) + + expected := ` +# HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs +# TYPE spacemesh_malfeasance2_num_invalid_proofs counter +spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 +` + err = testutil.CollectAndCompare( + malfeasance2.NumInvalidProofs().WithLabelValues("ATX", "invalidPost"), + strings.NewReader(expected), + "spacemesh_malfeasance2_num_invalid_proofs", + ) + require.NoError(t, err) }) } @@ -273,6 +314,18 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) require.ErrorIs(t, err, handlerError) require.ErrorIs(t, err, pubsub.ErrValidationReject) + + expected := ` +# HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs +# TYPE spacemesh_malfeasance2_num_invalid_proofs counter +spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 +` + err = testutil.CollectAndCompare( + malfeasance2.NumInvalidProofs().WithLabelValues("ATX", "invalidPost"), + strings.NewReader(expected), + "spacemesh_malfeasance2_num_invalid_proofs", + ) + require.NoError(t, err) }) t.Run("valid proof", func(t *testing.T) { @@ -293,6 +346,18 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", codec.MustEncode(proof)) require.NoError(t, err) + + expected := ` +# HELP spacemesh_malfeasance2_num_proofs number of malfeasance proofs +# TYPE spacemesh_malfeasance2_num_proofs counter +spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 +` + err = testutil.CollectAndCompare( + malfeasance2.NumValidProofs().WithLabelValues("ATX", "invalidPost"), + strings.NewReader(expected), + "spacemesh_malfeasance2_num_proofs", + ) + require.NoError(t, err) }) t.Run("valid proof for known malicious identity", func(t *testing.T) { @@ -317,6 +382,18 @@ func TestHandler_HandleGossip(t *testing.T) { err = h.HandleGossip(context.Background(), "peer", proofBytes) require.NoError(t, err) + + expected := ` +# HELP spacemesh_malfeasance2_num_proofs number of malfeasance proofs +# TYPE spacemesh_malfeasance2_num_proofs counter +spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 +` + err = testutil.CollectAndCompare( + malfeasance2.NumValidProofs().WithLabelValues("ATX", "invalidPost"), + strings.NewReader(expected), + "spacemesh_malfeasance2_num_proofs", + ) + require.NoError(t, err) }) } diff --git a/malfeasance2/metrics.go b/malfeasance2/metrics.go index 5d7f6b4ac4..81a1fa693f 100644 --- a/malfeasance2/metrics.go +++ b/malfeasance2/metrics.go @@ -3,7 +3,9 @@ package malfeasance2 import "github.com/spacemeshos/go-spacemesh/metrics" const ( - namespace = "malfeasance2" + MetricNamespace = "malfeasance2" + MetricValidProofName = "num_proofs" + MetricInvalidProofName = "num_invalid_proofs" domainLabel = "domain" typeLabel = "type" @@ -11,8 +13,8 @@ const ( var ( numProofs = metrics.NewCounter( - "num_proofs", - namespace, + MetricValidProofName, + MetricNamespace, "number of malfeasance proofs", []string{ domainLabel, @@ -21,8 +23,8 @@ var ( ) numInvalidProofs = metrics.NewCounter( - "num_invalid_proofs", - namespace, + MetricInvalidProofName, + MetricNamespace, "number of invalid malfeasance proofs", []string{ domainLabel, diff --git a/malfeasance2/metrics_test.go b/malfeasance2/metrics_test.go new file mode 100644 index 0000000000..12b81d981d --- /dev/null +++ b/malfeasance2/metrics_test.go @@ -0,0 +1,11 @@ +package malfeasance2 + +import "github.com/prometheus/client_golang/prometheus" + +func NumValidProofs() *prometheus.CounterVec { + return numProofs +} + +func NumInvalidProofs() *prometheus.CounterVec { + return numInvalidProofs +} From 7d5aa945f0698e97083e8d26ee1bcca4218fc6bb Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:30:31 +0000 Subject: [PATCH 64/66] Simplify assertions --- malfeasance2/handler_test.go | 41 ++++++------------------------------ 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index 7e4f1289f1..6b0cefa9b6 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -169,12 +169,7 @@ func TestHandler_HandleSync(t *testing.T) { # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 ` - err = testutil.CollectAndCompare( - malfeasance2.NumInvalidProofs().WithLabelValues("ATX", "invalidPost"), - strings.NewReader(expected), - "spacemesh_malfeasance2_num_invalid_proofs", - ) - require.NoError(t, err) + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumInvalidProofs(), strings.NewReader(expected))) }) t.Run("valid proof", func(t *testing.T) { @@ -201,12 +196,7 @@ spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 # TYPE spacemesh_malfeasance2_num_proofs counter spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 ` - err = testutil.CollectAndCompare( - malfeasance2.NumValidProofs().WithLabelValues("ATX", "invalidPost"), - strings.NewReader(expected), - "spacemesh_malfeasance2_num_proofs", - ) - require.NoError(t, err) + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumValidProofs(), strings.NewReader(expected))) malicious, err := malfeasance.IsMalicious(h.db, nodeID) require.NoError(t, err) @@ -245,12 +235,7 @@ spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 ` - err = testutil.CollectAndCompare( - malfeasance2.NumInvalidProofs().WithLabelValues("ATX", "invalidPost"), - strings.NewReader(expected), - "spacemesh_malfeasance2_num_invalid_proofs", - ) - require.NoError(t, err) + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumInvalidProofs(), strings.NewReader(expected))) }) } @@ -320,12 +305,7 @@ func TestHandler_HandleGossip(t *testing.T) { # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 ` - err = testutil.CollectAndCompare( - malfeasance2.NumInvalidProofs().WithLabelValues("ATX", "invalidPost"), - strings.NewReader(expected), - "spacemesh_malfeasance2_num_invalid_proofs", - ) - require.NoError(t, err) + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumInvalidProofs(), strings.NewReader(expected))) }) t.Run("valid proof", func(t *testing.T) { @@ -352,11 +332,7 @@ spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 # TYPE spacemesh_malfeasance2_num_proofs counter spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 ` - err = testutil.CollectAndCompare( - malfeasance2.NumValidProofs().WithLabelValues("ATX", "invalidPost"), - strings.NewReader(expected), - "spacemesh_malfeasance2_num_proofs", - ) + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumValidProofs(), strings.NewReader(expected))) require.NoError(t, err) }) @@ -388,12 +364,7 @@ spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 # TYPE spacemesh_malfeasance2_num_proofs counter spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 ` - err = testutil.CollectAndCompare( - malfeasance2.NumValidProofs().WithLabelValues("ATX", "invalidPost"), - strings.NewReader(expected), - "spacemesh_malfeasance2_num_proofs", - ) - require.NoError(t, err) + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumValidProofs(), strings.NewReader(expected))) }) } From b5970e42973ffdc2ea9e3cab86f4bf326d206a29 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:46:33 +0000 Subject: [PATCH 65/66] Assert metrics of malformed malfeasance proofs --- malfeasance2/handler_test.go | 14 ++++++++++++++ malfeasance2/metrics.go | 14 +++++++------- malfeasance2/metrics_test.go | 4 ++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index 6b0cefa9b6..b34aab0877 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -118,6 +118,13 @@ func TestHandler_HandleSync(t *testing.T) { err := h.HandleSynced(context.Background(), types.EmptyHash32, "peer", []byte("malformed")) require.ErrorIs(t, err, malfeasance2.ErrMalformedData) require.ErrorIs(t, err, pubsub.ErrValidationReject) + + expected := ` +# HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs +# TYPE spacemesh_malfeasance2_num_invalid_proofs counter +spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 1 +` + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumMalProof(), strings.NewReader(expected))) }) t.Run("unknown version", func(t *testing.T) { @@ -246,6 +253,13 @@ func TestHandler_HandleGossip(t *testing.T) { err := h.HandleGossip(context.Background(), "peer", []byte("malformed")) require.ErrorIs(t, err, malfeasance2.ErrMalformedData) require.ErrorIs(t, err, pubsub.ErrValidationReject) + + expected := ` +# HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs +# TYPE spacemesh_malfeasance2_num_invalid_proofs counter +spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 1 +` + require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumMalProof(), strings.NewReader(expected))) }) t.Run("self peer", func(t *testing.T) { diff --git a/malfeasance2/metrics.go b/malfeasance2/metrics.go index 81a1fa693f..c5ea42c594 100644 --- a/malfeasance2/metrics.go +++ b/malfeasance2/metrics.go @@ -3,9 +3,9 @@ package malfeasance2 import "github.com/spacemeshos/go-spacemesh/metrics" const ( - MetricNamespace = "malfeasance2" - MetricValidProofName = "num_proofs" - MetricInvalidProofName = "num_invalid_proofs" + namespace = "malfeasance2" + validProofName = "num_proofs" + invalidProofName = "num_invalid_proofs" domainLabel = "domain" typeLabel = "type" @@ -13,8 +13,8 @@ const ( var ( numProofs = metrics.NewCounter( - MetricValidProofName, - MetricNamespace, + validProofName, + namespace, "number of malfeasance proofs", []string{ domainLabel, @@ -23,8 +23,8 @@ var ( ) numInvalidProofs = metrics.NewCounter( - MetricInvalidProofName, - MetricNamespace, + invalidProofName, + namespace, "number of invalid malfeasance proofs", []string{ domainLabel, diff --git a/malfeasance2/metrics_test.go b/malfeasance2/metrics_test.go index 12b81d981d..4a8dac851d 100644 --- a/malfeasance2/metrics_test.go +++ b/malfeasance2/metrics_test.go @@ -9,3 +9,7 @@ func NumValidProofs() *prometheus.CounterVec { func NumInvalidProofs() *prometheus.CounterVec { return numInvalidProofs } + +func NumMalProof() prometheus.Counter { + return numMalformed +} From 89eb6580e16736371d03a0338f726958fdfebb73 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:58:22 +0000 Subject: [PATCH 66/66] Don't use globals --- malfeasance/handler.go | 3 ++- malfeasance2/handler.go | 44 ++++++++++++++++++++++++++++++++---- malfeasance2/handler_test.go | 22 +++++++++--------- malfeasance2/metrics.go | 26 --------------------- malfeasance2/metrics_test.go | 12 +++++----- 5 files changed, 59 insertions(+), 48 deletions(-) diff --git a/malfeasance/handler.go b/malfeasance/handler.go index 965af0277e..005a90b86d 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -74,7 +74,8 @@ func NewHandler( }, []string{ typeLabel, - }) + }, + ) invalidProofCounter := prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: metrics.Namespace, diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go index 3f496985e5..65b0e11708 100644 --- a/malfeasance2/handler.go +++ b/malfeasance2/handler.go @@ -7,6 +7,7 @@ import ( "slices" "strconv" + "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -14,6 +15,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/log" + "github.com/spacemeshos/go-spacemesh/metrics" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" @@ -38,6 +40,11 @@ type Handler struct { tortoise tortoise handlers map[ProofDomain]MalfeasanceHandler + + // metrics + numProofs *prometheus.CounterVec + numInvalidProofs *prometheus.CounterVec + numMalformed prometheus.Counter } func NewHandler( @@ -48,6 +55,31 @@ func NewHandler( edVerifier *signing.EdVerifier, tortoise tortoise, ) *Handler { + proofCounter := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: metrics.Namespace, + Subsystem: namespace, + Name: validProofName, + Help: "number of malfeasance proofs", + }, + []string{ + domainLabel, + typeLabel, + }, + ) + invalidProofCounter := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: metrics.Namespace, + Subsystem: namespace, + Name: invalidProofName, + Help: "number of invalid malfeasance proofs", + }, + []string{ + domainLabel, + typeLabel, + }, + ) + return &Handler{ db: db, logger: lg, @@ -57,6 +89,10 @@ func NewHandler( tortoise: tortoise, handlers: make(map[ProofDomain]MalfeasanceHandler), + + numProofs: proofCounter, + numInvalidProofs: invalidProofCounter, + numMalformed: invalidProofCounter.WithLabelValues("mal", "unknown"), } } @@ -69,12 +105,12 @@ func (h *Handler) RegisterHandler(malfeasanceType ProofDomain, handler Malfeasan func (h *Handler) countProof(mp MalfeasanceProof) { labels := h.handlers[mp.Domain].ReportLabels(mp.Proof) - numProofs.WithLabelValues(labels...).Inc() + h.numProofs.WithLabelValues(labels...).Inc() } func (h *Handler) countInvalidProof(mp MalfeasanceProof) { labels := h.handlers[mp.Domain].ReportLabels(mp.Proof) - numInvalidProofs.WithLabelValues(labels...).Inc() + h.numInvalidProofs.WithLabelValues(labels...).Inc() } func (h *Handler) reportMalfeasance(smesher types.NodeID) { @@ -123,7 +159,7 @@ func (h *Handler) Info(ctx context.Context, nodeID types.NodeID) (map[string]str func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, peer p2p.Peer, msg []byte) error { var proof MalfeasanceProof if err := codec.Decode(msg, &proof); err != nil { - numMalformed.Inc() + h.numMalformed.Inc() return ErrMalformedData } @@ -182,7 +218,7 @@ func (h *Handler) HandleGossip(ctx context.Context, peer p2p.Peer, msg []byte) e var proof MalfeasanceProof if err := codec.Decode(msg, &proof); err != nil { - numMalformed.Inc() + h.numMalformed.Inc() return ErrMalformedData } diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go index b34aab0877..70ee74f8b7 100644 --- a/malfeasance2/handler_test.go +++ b/malfeasance2/handler_test.go @@ -53,9 +53,6 @@ func newTestHandler(tb testing.TB) *testHandler { ctrl := gomock.NewController(tb) mockTrt := malfeasance2.NewMocktortoise(ctrl) - malfeasance2.NumValidProofs().Reset() - malfeasance2.NumInvalidProofs().Reset() - h := malfeasance2.NewHandler( db, logger, @@ -124,7 +121,7 @@ func TestHandler_HandleSync(t *testing.T) { # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 1 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumMalProof(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumMalProof(), strings.NewReader(expected))) }) t.Run("unknown version", func(t *testing.T) { @@ -175,8 +172,9 @@ spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 1 # HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 +spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 0 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumInvalidProofs(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumInvalidProofs(), strings.NewReader(expected))) }) t.Run("valid proof", func(t *testing.T) { @@ -203,7 +201,7 @@ spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 # TYPE spacemesh_malfeasance2_num_proofs counter spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumValidProofs(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumValidProofs(), strings.NewReader(expected))) malicious, err := malfeasance.IsMalicious(h.db, nodeID) require.NoError(t, err) @@ -241,8 +239,9 @@ spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 # HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 +spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 0 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumInvalidProofs(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumInvalidProofs(), strings.NewReader(expected))) }) } @@ -259,7 +258,7 @@ func TestHandler_HandleGossip(t *testing.T) { # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 1 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumMalProof(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumMalProof(), strings.NewReader(expected))) }) t.Run("self peer", func(t *testing.T) { @@ -318,8 +317,9 @@ spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 1 # HELP spacemesh_malfeasance2_num_invalid_proofs number of invalid malfeasance proofs # TYPE spacemesh_malfeasance2_num_invalid_proofs counter spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 +spacemesh_malfeasance2_num_invalid_proofs{domain="mal",type="unknown"} 0 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumInvalidProofs(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumInvalidProofs(), strings.NewReader(expected))) }) t.Run("valid proof", func(t *testing.T) { @@ -346,7 +346,7 @@ spacemesh_malfeasance2_num_invalid_proofs{domain="ATX",type="invalidPost"} 1 # TYPE spacemesh_malfeasance2_num_proofs counter spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumValidProofs(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumValidProofs(), strings.NewReader(expected))) require.NoError(t, err) }) @@ -378,7 +378,7 @@ spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 # TYPE spacemesh_malfeasance2_num_proofs counter spacemesh_malfeasance2_num_proofs{domain="ATX",type="invalidPost"} 1 ` - require.NoError(t, testutil.CollectAndCompare(malfeasance2.NumValidProofs(), strings.NewReader(expected))) + require.NoError(t, testutil.CollectAndCompare(h.NumValidProofs(), strings.NewReader(expected))) }) } diff --git a/malfeasance2/metrics.go b/malfeasance2/metrics.go index c5ea42c594..a538b8676d 100644 --- a/malfeasance2/metrics.go +++ b/malfeasance2/metrics.go @@ -1,7 +1,5 @@ package malfeasance2 -import "github.com/spacemeshos/go-spacemesh/metrics" - const ( namespace = "malfeasance2" validProofName = "num_proofs" @@ -10,27 +8,3 @@ const ( domainLabel = "domain" typeLabel = "type" ) - -var ( - numProofs = metrics.NewCounter( - validProofName, - namespace, - "number of malfeasance proofs", - []string{ - domainLabel, - typeLabel, - }, - ) - - numInvalidProofs = metrics.NewCounter( - invalidProofName, - namespace, - "number of invalid malfeasance proofs", - []string{ - domainLabel, - typeLabel, - }, - ) - - numMalformed = numInvalidProofs.WithLabelValues("mal", "unknown") -) diff --git a/malfeasance2/metrics_test.go b/malfeasance2/metrics_test.go index 4a8dac851d..28b13020fa 100644 --- a/malfeasance2/metrics_test.go +++ b/malfeasance2/metrics_test.go @@ -2,14 +2,14 @@ package malfeasance2 import "github.com/prometheus/client_golang/prometheus" -func NumValidProofs() *prometheus.CounterVec { - return numProofs +func (h *Handler) NumValidProofs() *prometheus.CounterVec { + return h.numProofs } -func NumInvalidProofs() *prometheus.CounterVec { - return numInvalidProofs +func (h *Handler) NumInvalidProofs() *prometheus.CounterVec { + return h.numInvalidProofs } -func NumMalProof() prometheus.Counter { - return numMalformed +func (h *Handler) NumMalProof() prometheus.Counter { + return h.numMalformed }