From bbcb675251a2ec1004c920faeefa1b042ca2bd05 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:04:24 +0000 Subject: [PATCH 01/10] WiP --- hare3/hare.go | 4 +-- malfeasance/handler.go | 53 ++++++++++--------------------------- malfeasance/publisher.go | 56 ++++++++++++++++++++++++++++++++++++++++ node/node.go | 26 +++++++++++-------- p2p/pubsub/pubsub.go | 7 +++-- 5 files changed, 92 insertions(+), 54 deletions(-) create mode 100644 malfeasance/publisher.go diff --git a/hare3/hare.go b/hare3/hare.go index f9f7c9a9f2..7d436124ad 100644 --- a/hare3/hare.go +++ b/hare3/hare.go @@ -162,7 +162,7 @@ type nodeclock interface { func New( nodeclock nodeclock, - pubsub pubsub.PublishSubsciber, + pubsub pubsub.PublishSubscriber, db *sql.Database, atxsdata *atxsdata.Data, proposals *store.Store, @@ -224,7 +224,7 @@ type Hare struct { // dependencies nodeclock nodeclock - pubsub pubsub.PublishSubsciber + pubsub pubsub.PublishSubscriber db *sql.Database atxsdata *atxsdata.Data proposals *store.Store diff --git a/malfeasance/handler.go b/malfeasance/handler.go index 0476dc1464..b08e059f6e 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -44,7 +44,6 @@ const ( InvalidActivation MalfeasanceType = iota + 10 InvalidBallot InvalidHareMsg - DoubleMarry = MalfeasanceType(wire.DoubleMarry) ) // Handler processes MalfeasanceProof from gossip and, if deems it valid, propagates it to peers. @@ -53,7 +52,6 @@ type Handler struct { cdb *datastore.CachedDB handlersV1 map[MalfeasanceType]HandlerV1 - handlersV2 map[MalfeasanceType]HandlerV2 self p2p.Peer nodeIDs []types.NodeID @@ -75,7 +73,6 @@ func NewHandler( tortoise: tortoise, handlersV1: make(map[MalfeasanceType]HandlerV1), - handlersV2: make(map[MalfeasanceType]HandlerV2), } } @@ -83,10 +80,6 @@ func (h *Handler) RegisterHandlerV1(malfeasanceType MalfeasanceType, handler Han h.handlersV1[malfeasanceType] = handler } -func (h *Handler) RegisterHandlerV2(malfeasanceType MalfeasanceType, handler HandlerV2) { - h.handlersV2[malfeasanceType] = handler -} - func (h *Handler) reportMalfeasance(smesher types.NodeID, mp *wire.MalfeasanceProof) { h.tortoise.OnMalfeasance(smesher) events.ReportMalfeasance(smesher, mp) @@ -116,7 +109,7 @@ func (h *Handler) HandleSyncedMalfeasanceProof( h.logger.Error("malformed message (sync)", log.ZContext(ctx), zap.Error(err)) return errMalformedData } - nodeID, err := h.validateAndSave(ctx, &wire.MalfeasanceGossip{MalfeasanceProof: p}) + nodeID, err := h.validateAndSave(ctx, &p) if err == nil && types.Hash32(nodeID) != expHash { return fmt.Errorf( "%w: malfeasance proof want %s, got %s", @@ -136,51 +129,33 @@ func (h *Handler) HandleMalfeasanceProof(ctx context.Context, peer p2p.Peer, dat h.logger.Error("malformed message", log.ZContext(ctx), zap.Error(err)) return errMalformedData } - if peer == h.self { - id, err := h.Validate(ctx, &p) - if err != nil { - h.countInvalidProof(&p.MalfeasanceProof) - return err - } - h.reportMalfeasance(id, &p.MalfeasanceProof) - // node saves malfeasance proof eagerly/atomically with the malicious data. - // it has validated the proof before saving to db. - h.countProof(&p.MalfeasanceProof) - return nil + if p.Eligibility != nil { + return fmt.Errorf("%w: eligibility field was deprecated with hare3", pubsub.ErrValidationReject) } - _, err := h.validateAndSave(ctx, &p) + _, err := h.validateAndSave(ctx, &p.MalfeasanceProof) return err } -func (h *Handler) validateAndSave(ctx context.Context, p *wire.MalfeasanceGossip) (types.NodeID, error) { - if p.Eligibility != nil { - return types.EmptyNodeID, fmt.Errorf( - "%w: eligibility field was deprecated with hare3", - pubsub.ErrValidationReject, - ) - } +func (h *Handler) validateAndSave(ctx context.Context, p *wire.MalfeasanceProof) (types.NodeID, error) { nodeID, err := h.Validate(ctx, p) switch { case errors.Is(err, errInvalidProof): numMalformed.Inc() return types.EmptyNodeID, err case err != nil: - h.countInvalidProof(&p.MalfeasanceProof) + h.countInvalidProof(p) return types.EmptyNodeID, err } if err := h.cdb.WithTx(ctx, func(dbtx *sql.Tx) error { malicious, err := identities.IsMalicious(dbtx, nodeID) if err != nil { return fmt.Errorf("check known malicious: %w", err) - } else if malicious { + } + if malicious { h.logger.Debug("known malicious identity", log.ZContext(ctx), zap.Stringer("smesher", nodeID)) return ErrKnownProof } - encoded, err := codec.Encode(&p.MalfeasanceProof) - if err != nil { - h.logger.Panic("failed to encode MalfeasanceProof", zap.Error(err)) - } - if err := identities.SetMalicious(dbtx, nodeID, encoded, time.Now()); err != nil { + if err := identities.SetMalicious(dbtx, nodeID, codec.MustEncode(p), time.Now()); err != nil { return fmt.Errorf("add malfeasance proof: %w", err) } return nil @@ -193,11 +168,11 @@ func (h *Handler) validateAndSave(ctx context.Context, p *wire.MalfeasanceGossip zap.Error(err), ) } - return types.EmptyNodeID, err + return nodeID, err } - h.reportMalfeasance(nodeID, &p.MalfeasanceProof) - h.cdb.CacheMalfeasanceProof(nodeID, &p.MalfeasanceProof) - h.countProof(&p.MalfeasanceProof) + h.reportMalfeasance(nodeID, p) + h.cdb.CacheMalfeasanceProof(nodeID, p) + h.countProof(p) h.logger.Info("new malfeasance proof", log.ZContext(ctx), zap.Stringer("smesher", nodeID), @@ -206,7 +181,7 @@ func (h *Handler) validateAndSave(ctx context.Context, p *wire.MalfeasanceGossip return nodeID, nil } -func (h *Handler) Validate(ctx context.Context, p *wire.MalfeasanceGossip) (types.NodeID, error) { +func (h *Handler) Validate(ctx context.Context, p *wire.MalfeasanceProof) (types.NodeID, error) { mh, ok := h.handlersV1[MalfeasanceType(p.Proof.Type)] if !ok { return types.EmptyNodeID, fmt.Errorf("%w: unknown malfeasance type", errInvalidProof) diff --git a/malfeasance/publisher.go b/malfeasance/publisher.go new file mode 100644 index 0000000000..14398fc922 --- /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, 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/node/node.go b/node/node.go index 969e7c21d2..86ac67b8c3 100644 --- a/node/node.go +++ b/node/node.go @@ -1157,13 +1157,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 } @@ -1173,45 +1173,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, malfeasanceHandler.HandleMalfeasanceProof), + pubsub.ChainGossipHandler(checkAtxSynced, malfeasanceHandler.HandleMalfeasanceProof), + ) + app.host.Register( + pubsub.MalfeasanceProof2, + pubsub.ChainGossipHandler(checkAtxSynced, malfeasanceHandler.HandleMalfeasanceProof), ) app.proposalBuilder = proposalBuilder diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index c27d33ae37..a467422050 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. @@ -133,8 +136,8 @@ var ( WithValidatorConcurrency = pubsub.WithValidatorConcurrency ) -// PublishSubsciber common interface for publisher and subscribing. -type PublishSubsciber interface { +// PublishSubscriber common interface for publisher and subscribing. +type PublishSubscriber interface { Publisher Subscriber } From 9903b2266165a7b8aed2aabe4fc475915e73efc8 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:23:43 +0000 Subject: [PATCH 02/10] Wip --- activation/poet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activation/poet.go b/activation/poet.go index c16cf1b481..494d442f3a 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -488,7 +488,7 @@ func (c *poetService) Submit( case err == nil: return round, nil case errors.Is(err, ErrUnauthorized): - logger.Warn("failed to submit challenge as unathorized - recertifying", zap.Error(err)) + logger.Warn("failed to submit challenge as unauthorized - recertifying", zap.Error(err)) auth.PoetCert, err = c.recertify(ctx, nodeID) if err != nil { return nil, fmt.Errorf("recertifying: %w", err) From 4d67bfbae59b58bc1fbe69fe85ce762a1b9620ac Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 26 Jul 2024 08:54:14 +0000 Subject: [PATCH 03/10] WiP --- 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 +- hare3/hare_test.go | 4 +- malfeasance/handler.go | 28 ++--- malfeasance/handler_test.go | 28 ++--- malfeasance/interface.go | 6 +- malfeasance/mocks.go | 138 +++++++---------------- node/node.go | 10 +- p2p/pubsub/mocks/publisher.go | 58 +++++----- 18 files changed, 130 insertions(+), 204 deletions(-) diff --git a/activation/e2e/atx_merge_test.go b/activation/e2e/atx_merge_test.go index fda7593427..169eb0fbd2 100644 --- a/activation/e2e/atx_merge_test.go +++ b/activation/e2e/atx_merge_test.go @@ -271,7 +271,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 9c56b565be..d7215bb6d6 100644 --- a/activation/e2e/builds_atx_v2_test.go +++ b/activation/e2e/builds_atx_v2_test.go @@ -116,7 +116,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 cc5cfb00bd..6f0675fc1c 100644 --- a/activation/e2e/checkpoint_merged_test.go +++ b/activation/e2e/checkpoint_merged_test.go @@ -106,7 +106,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 048469b2ff..54e272e564 100644 --- a/activation/e2e/checkpoint_test.go +++ b/activation/e2e/checkpoint_test.go @@ -102,7 +102,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 b1d39d6807..b092fdf94e 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 09f6e21752..752c5e4648 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -123,7 +123,7 @@ type handlerMocks struct { mpub *pubsubmocks.MockPublisher mockFetch *mocks.MockFetcher mValidator *MocknipostValidator - mbeacon *MockAtxReceiver + mbeacon *MockatxReceiver mtortoise *mocks.MockTortoise } @@ -186,7 +186,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), } } diff --git a/activation/handler_v1.go b/activation/handler_v1.go index c159c73d3a..c7b772b689 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 ba96c0c96b..635b75f308 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -63,7 +63,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 a4f16782ee..4ad8307bdf 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -18,7 +18,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 8f7bd80c76..2df4ee1f50 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 b21f9e9d06..5fe2533300 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -250,7 +250,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/hare3/hare_test.go b/hare3/hare_test.go index dd8139ab12..ae26bca4f0 100644 --- a/hare3/hare_test.go +++ b/hare3/hare_test.go @@ -122,7 +122,7 @@ type node struct { proposals *store.Store ctrl *gomock.Controller - mpublisher *pmocks.MockPublishSubsciber + mpublisher *pmocks.MockPublishSubscriber msyncer *smocks.MockSyncStateProvider patrol *layerpatrol.LayerPatrol tracer *testTracer @@ -204,7 +204,7 @@ func (n *node) withOracle() *node { } func (n *node) withPublisher() *node { - n.mpublisher = pmocks.NewMockPublishSubsciber(n.ctrl) + n.mpublisher = pmocks.NewMockPublishSubscriber(n.ctrl) n.mpublisher.EXPECT().Register(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() return n } diff --git a/malfeasance/handler.go b/malfeasance/handler.go index b08e059f6e..541418d369 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -32,30 +32,22 @@ var ( type MalfeasanceType byte const ( - // V1 types. MultipleATXs MalfeasanceType = MalfeasanceType(wire.MultipleATXs) MultipleBallots = MalfeasanceType(wire.MultipleBallots) HareEquivocation = MalfeasanceType(wire.HareEquivocation) InvalidPostIndex = MalfeasanceType(wire.InvalidPostIndex) InvalidPrevATX = MalfeasanceType(wire.InvalidPrevATX) - - // V2 types - // TODO(mafa): for future use. - InvalidActivation MalfeasanceType = iota + 10 - InvalidBallot - InvalidHareMsg ) // Handler processes MalfeasanceProof from gossip and, if deems it valid, propagates it to peers. type Handler struct { - logger *zap.Logger - cdb *datastore.CachedDB - - handlersV1 map[MalfeasanceType]HandlerV1 - + logger *zap.Logger + cdb *datastore.CachedDB self p2p.Peer nodeIDs []types.NodeID tortoise tortoise + + handlers map[MalfeasanceType]MalfeasanceHandler } func NewHandler( @@ -72,12 +64,12 @@ func NewHandler( nodeIDs: nodeID, tortoise: tortoise, - handlersV1: make(map[MalfeasanceType]HandlerV1), + handlers: make(map[MalfeasanceType]MalfeasanceHandler), } } -func (h *Handler) RegisterHandlerV1(malfeasanceType MalfeasanceType, handler HandlerV1) { - h.handlersV1[malfeasanceType] = handler +func (h *Handler) RegisterHandler(malfeasanceType MalfeasanceType, handler MalfeasanceHandler) { + h.handlers[malfeasanceType] = handler } func (h *Handler) reportMalfeasance(smesher types.NodeID, mp *wire.MalfeasanceProof) { @@ -89,11 +81,11 @@ func (h *Handler) reportMalfeasance(smesher types.NodeID, mp *wire.MalfeasancePr } func (h *Handler) countProof(mp *wire.MalfeasanceProof) { - h.handlersV1[MalfeasanceType(mp.Proof.Type)].ReportProof(numProofs) + h.handlers[MalfeasanceType(mp.Proof.Type)].ReportProof(numProofs) } func (h *Handler) countInvalidProof(p *wire.MalfeasanceProof) { - h.handlersV1[MalfeasanceType(p.Proof.Type)].ReportInvalidProof(numInvalidProofs) + h.handlers[MalfeasanceType(p.Proof.Type)].ReportInvalidProof(numInvalidProofs) } // HandleSyncedMalfeasanceProof is the sync validator for MalfeasanceProof. @@ -182,7 +174,7 @@ func (h *Handler) validateAndSave(ctx context.Context, p *wire.MalfeasanceProof) } func (h *Handler) Validate(ctx context.Context, p *wire.MalfeasanceProof) (types.NodeID, error) { - mh, ok := h.handlersV1[MalfeasanceType(p.Proof.Type)] + mh, ok := h.handlers[MalfeasanceType(p.Proof.Type)] if !ok { return types.EmptyNodeID, fmt.Errorf("%w: unknown malfeasance type", errInvalidProof) } diff --git a/malfeasance/handler_test.go b/malfeasance/handler_test.go index 2384a4e841..303df4a36f 100644 --- a/malfeasance/handler_test.go +++ b/malfeasance/handler_test.go @@ -91,7 +91,7 @@ func TestHandler_HandleMalfeasanceProof(t *testing.T) { h := newHandler(t) ctrl := gomock.NewController(t) - handler := NewMockHandlerV1(ctrl) + handler := NewMockMalfeasanceHandler(ctrl) handler.EXPECT().Validate(gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, data wire.ProofData) (types.NodeID, error) { require.IsType(t, &wire.AtxProof{}, data) @@ -99,7 +99,7 @@ func TestHandler_HandleMalfeasanceProof(t *testing.T) { }, ) handler.EXPECT().ReportInvalidProof(gomock.Any()) - h.RegisterHandlerV1(MultipleATXs, handler) + h.RegisterHandler(MultipleATXs, handler) gossip := &wire.MalfeasanceGossip{ MalfeasanceProof: wire.MalfeasanceProof{ @@ -120,7 +120,7 @@ func TestHandler_HandleMalfeasanceProof(t *testing.T) { nodeID := types.RandomNodeID() ctrl := gomock.NewController(t) - handler := NewMockHandlerV1(ctrl) + handler := NewMockMalfeasanceHandler(ctrl) handler.EXPECT().Validate(gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, data wire.ProofData) (types.NodeID, error) { require.IsType(t, &wire.AtxProof{}, data) @@ -128,7 +128,7 @@ func TestHandler_HandleMalfeasanceProof(t *testing.T) { }, ) handler.EXPECT().ReportProof(gomock.Any()) - h.RegisterHandlerV1(MultipleATXs, handler) + h.RegisterHandler(MultipleATXs, handler) gossip := &wire.MalfeasanceGossip{ MalfeasanceProof: wire.MalfeasanceProof{ @@ -163,14 +163,14 @@ func TestHandler_HandleMalfeasanceProof(t *testing.T) { identities.SetMalicious(h.db, nodeID, codec.MustEncode(proof), time.Now()) ctrl := gomock.NewController(t) - handler := NewMockHandlerV1(ctrl) + handler := NewMockMalfeasanceHandler(ctrl) handler.EXPECT().Validate(gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, data wire.ProofData) (types.NodeID, error) { require.IsType(t, &wire.AtxProof{}, data) return nodeID, nil }, ) - h.RegisterHandlerV1(MultipleATXs, handler) + h.RegisterHandler(MultipleATXs, handler) gossip := &wire.MalfeasanceGossip{ MalfeasanceProof: wire.MalfeasanceProof{ @@ -232,7 +232,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { nodeID := types.RandomNodeID() ctrl := gomock.NewController(t) - handler := NewMockHandlerV1(ctrl) + handler := NewMockMalfeasanceHandler(ctrl) handler.EXPECT().Validate(gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, data wire.ProofData) (types.NodeID, error) { require.IsType(t, &wire.AtxProof{}, data) @@ -240,7 +240,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { }, ) handler.EXPECT().ReportProof(gomock.Any()) - h.RegisterHandlerV1(MultipleATXs, handler) + h.RegisterHandler(MultipleATXs, handler) proof := &wire.MalfeasanceProof{ Layer: types.LayerID(22), @@ -266,7 +266,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { nodeID := types.RandomNodeID() ctrl := gomock.NewController(t) - handler := NewMockHandlerV1(ctrl) + handler := NewMockMalfeasanceHandler(ctrl) handler.EXPECT().Validate(gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, data wire.ProofData) (types.NodeID, error) { require.IsType(t, &wire.AtxProof{}, data) @@ -274,7 +274,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { }, ) handler.EXPECT().ReportInvalidProof(gomock.Any()) - h.RegisterHandlerV1(MultipleATXs, handler) + h.RegisterHandler(MultipleATXs, handler) proof := &wire.MalfeasanceProof{ Layer: types.LayerID(22), @@ -298,7 +298,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { nodeID := types.RandomNodeID() ctrl := gomock.NewController(t) - handler := NewMockHandlerV1(ctrl) + handler := NewMockMalfeasanceHandler(ctrl) handler.EXPECT().Validate(gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, data wire.ProofData) (types.NodeID, error) { require.IsType(t, &wire.AtxProof{}, data) @@ -306,7 +306,7 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { }, ) handler.EXPECT().ReportProof(gomock.Any()) - h.RegisterHandlerV1(MultipleATXs, handler) + h.RegisterHandler(MultipleATXs, handler) proof := &wire.MalfeasanceProof{ Layer: types.LayerID(22), @@ -344,14 +344,14 @@ func TestHandler_HandleSyncedMalfeasanceProof(t *testing.T) { identities.SetMalicious(h.db, nodeID, codec.MustEncode(proof), time.Now()) ctrl := gomock.NewController(t) - handler := NewMockHandlerV1(ctrl) + handler := NewMockMalfeasanceHandler(ctrl) handler.EXPECT().Validate(gomock.Any(), gomock.Any()).DoAndReturn( func(ctx context.Context, data wire.ProofData) (types.NodeID, error) { require.IsType(t, &wire.AtxProof{}, data) return nodeID, nil }, ) - h.RegisterHandlerV1(MultipleATXs, handler) + h.RegisterHandler(MultipleATXs, handler) newProof := &wire.MalfeasanceProof{ Layer: types.LayerID(22), diff --git a/malfeasance/interface.go b/malfeasance/interface.go index 46b3cdb9f9..ab6d8bcf6c 100644 --- a/malfeasance/interface.go +++ b/malfeasance/interface.go @@ -15,12 +15,8 @@ type tortoise interface { OnMalfeasance(types.NodeID) } -type HandlerV1 interface { +type MalfeasanceHandler interface { Validate(ctx context.Context, data wire.ProofData) (types.NodeID, error) ReportProof(vec *prometheus.CounterVec) ReportInvalidProof(vec *prometheus.CounterVec) } - -type HandlerV2 interface { - Validate(ctx context.Context, data []byte) (types.NodeID, error) -} diff --git a/malfeasance/mocks.go b/malfeasance/mocks.go index d0be0c3a1a..093bf9a91d 100644 --- a/malfeasance/mocks.go +++ b/malfeasance/mocks.go @@ -78,103 +78,103 @@ func (c *MocktortoiseOnMalfeasanceCall) DoAndReturn(f func(types.NodeID)) *Mockt return c } -// MockHandlerV1 is a mock of HandlerV1 interface. -type MockHandlerV1 struct { +// MockMalfeasanceHandler is a mock of MalfeasanceHandler interface. +type MockMalfeasanceHandler struct { ctrl *gomock.Controller - recorder *MockHandlerV1MockRecorder + recorder *MockMalfeasanceHandlerMockRecorder } -// MockHandlerV1MockRecorder is the mock recorder for MockHandlerV1. -type MockHandlerV1MockRecorder struct { - mock *MockHandlerV1 +// MockMalfeasanceHandlerMockRecorder is the mock recorder for MockMalfeasanceHandler. +type MockMalfeasanceHandlerMockRecorder struct { + mock *MockMalfeasanceHandler } -// NewMockHandlerV1 creates a new mock instance. -func NewMockHandlerV1(ctrl *gomock.Controller) *MockHandlerV1 { - mock := &MockHandlerV1{ctrl: ctrl} - mock.recorder = &MockHandlerV1MockRecorder{mock} +// 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 *MockHandlerV1) EXPECT() *MockHandlerV1MockRecorder { +func (m *MockMalfeasanceHandler) EXPECT() *MockMalfeasanceHandlerMockRecorder { return m.recorder } // ReportInvalidProof mocks base method. -func (m *MockHandlerV1) ReportInvalidProof(vec *prometheus.CounterVec) { +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 *MockHandlerV1MockRecorder) ReportInvalidProof(vec any) *MockHandlerV1ReportInvalidProofCall { +func (mr *MockMalfeasanceHandlerMockRecorder) ReportInvalidProof(vec any) *MockMalfeasanceHandlerReportInvalidProofCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportInvalidProof", reflect.TypeOf((*MockHandlerV1)(nil).ReportInvalidProof), vec) - return &MockHandlerV1ReportInvalidProofCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportInvalidProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportInvalidProof), vec) + return &MockMalfeasanceHandlerReportInvalidProofCall{Call: call} } -// MockHandlerV1ReportInvalidProofCall wrap *gomock.Call -type MockHandlerV1ReportInvalidProofCall struct { +// MockMalfeasanceHandlerReportInvalidProofCall wrap *gomock.Call +type MockMalfeasanceHandlerReportInvalidProofCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockHandlerV1ReportInvalidProofCall) Return() *MockHandlerV1ReportInvalidProofCall { +func (c *MockMalfeasanceHandlerReportInvalidProofCall) Return() *MockMalfeasanceHandlerReportInvalidProofCall { c.Call = c.Call.Return() return c } // Do rewrite *gomock.Call.Do -func (c *MockHandlerV1ReportInvalidProofCall) Do(f func(*prometheus.CounterVec)) *MockHandlerV1ReportInvalidProofCall { +func (c *MockMalfeasanceHandlerReportInvalidProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockHandlerV1ReportInvalidProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockHandlerV1ReportInvalidProofCall { +func (c *MockMalfeasanceHandlerReportInvalidProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportInvalidProofCall { c.Call = c.Call.DoAndReturn(f) return c } // ReportProof mocks base method. -func (m *MockHandlerV1) ReportProof(vec *prometheus.CounterVec) { +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 *MockHandlerV1MockRecorder) ReportProof(vec any) *MockHandlerV1ReportProofCall { +func (mr *MockMalfeasanceHandlerMockRecorder) ReportProof(vec any) *MockMalfeasanceHandlerReportProofCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportProof", reflect.TypeOf((*MockHandlerV1)(nil).ReportProof), vec) - return &MockHandlerV1ReportProofCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportProof", reflect.TypeOf((*MockMalfeasanceHandler)(nil).ReportProof), vec) + return &MockMalfeasanceHandlerReportProofCall{Call: call} } -// MockHandlerV1ReportProofCall wrap *gomock.Call -type MockHandlerV1ReportProofCall struct { +// MockMalfeasanceHandlerReportProofCall wrap *gomock.Call +type MockMalfeasanceHandlerReportProofCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockHandlerV1ReportProofCall) Return() *MockHandlerV1ReportProofCall { +func (c *MockMalfeasanceHandlerReportProofCall) Return() *MockMalfeasanceHandlerReportProofCall { c.Call = c.Call.Return() return c } // Do rewrite *gomock.Call.Do -func (c *MockHandlerV1ReportProofCall) Do(f func(*prometheus.CounterVec)) *MockHandlerV1ReportProofCall { +func (c *MockMalfeasanceHandlerReportProofCall) Do(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockHandlerV1ReportProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockHandlerV1ReportProofCall { +func (c *MockMalfeasanceHandlerReportProofCall) DoAndReturn(f func(*prometheus.CounterVec)) *MockMalfeasanceHandlerReportProofCall { c.Call = c.Call.DoAndReturn(f) return c } // Validate mocks base method. -func (m *MockHandlerV1) Validate(ctx context.Context, data wire.ProofData) (types.NodeID, error) { +func (m *MockMalfeasanceHandler) Validate(ctx context.Context, data wire.ProofData) (types.NodeID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Validate", ctx, data) ret0, _ := ret[0].(types.NodeID) @@ -183,93 +183,31 @@ func (m *MockHandlerV1) Validate(ctx context.Context, data wire.ProofData) (type } // Validate indicates an expected call of Validate. -func (mr *MockHandlerV1MockRecorder) Validate(ctx, data any) *MockHandlerV1ValidateCall { +func (mr *MockMalfeasanceHandlerMockRecorder) Validate(ctx, data any) *MockMalfeasanceHandlerValidateCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockHandlerV1)(nil).Validate), ctx, data) - return &MockHandlerV1ValidateCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockMalfeasanceHandler)(nil).Validate), ctx, data) + return &MockMalfeasanceHandlerValidateCall{Call: call} } -// MockHandlerV1ValidateCall wrap *gomock.Call -type MockHandlerV1ValidateCall struct { +// MockMalfeasanceHandlerValidateCall wrap *gomock.Call +type MockMalfeasanceHandlerValidateCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockHandlerV1ValidateCall) Return(arg0 types.NodeID, arg1 error) *MockHandlerV1ValidateCall { +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 *MockHandlerV1ValidateCall) Do(f func(context.Context, wire.ProofData) (types.NodeID, error)) *MockHandlerV1ValidateCall { +func (c *MockMalfeasanceHandlerValidateCall) Do(f func(context.Context, wire.ProofData) (types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockHandlerV1ValidateCall) DoAndReturn(f func(context.Context, wire.ProofData) (types.NodeID, error)) *MockHandlerV1ValidateCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - -// MockHandlerV2 is a mock of HandlerV2 interface. -type MockHandlerV2 struct { - ctrl *gomock.Controller - recorder *MockHandlerV2MockRecorder -} - -// MockHandlerV2MockRecorder is the mock recorder for MockHandlerV2. -type MockHandlerV2MockRecorder struct { - mock *MockHandlerV2 -} - -// NewMockHandlerV2 creates a new mock instance. -func NewMockHandlerV2(ctrl *gomock.Controller) *MockHandlerV2 { - mock := &MockHandlerV2{ctrl: ctrl} - mock.recorder = &MockHandlerV2MockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockHandlerV2) EXPECT() *MockHandlerV2MockRecorder { - return m.recorder -} - -// Validate mocks base method. -func (m *MockHandlerV2) 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 *MockHandlerV2MockRecorder) Validate(ctx, data any) *MockHandlerV2ValidateCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockHandlerV2)(nil).Validate), ctx, data) - return &MockHandlerV2ValidateCall{Call: call} -} - -// MockHandlerV2ValidateCall wrap *gomock.Call -type MockHandlerV2ValidateCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockHandlerV2ValidateCall) Return(arg0 types.NodeID, arg1 error) *MockHandlerV2ValidateCall { - c.Call = c.Call.Return(arg0, arg1) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockHandlerV2ValidateCall) Do(f func(context.Context, []byte) (types.NodeID, error)) *MockHandlerV2ValidateCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockHandlerV2ValidateCall) DoAndReturn(f func(context.Context, []byte) (types.NodeID, error)) *MockHandlerV2ValidateCall { +func (c *MockMalfeasanceHandlerValidateCall) DoAndReturn(f func(context.Context, wire.ProofData) (types.NodeID, error)) *MockMalfeasanceHandlerValidateCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/node/node.go b/node/node.go index 8a7bfd7384..74c3809ab6 100644 --- a/node/node.go +++ b/node/node.go @@ -1101,11 +1101,11 @@ func (app *App) initServices(ctx context.Context) error { nodeIDs, trtl, ) - malfeasanceHandler.RegisterHandlerV1(malfeasance.MultipleATXs, activationMH) - malfeasanceHandler.RegisterHandlerV1(malfeasance.MultipleBallots, meshMH) - malfeasanceHandler.RegisterHandlerV1(malfeasance.HareEquivocation, hareMH) - malfeasanceHandler.RegisterHandlerV1(malfeasance.InvalidPostIndex, invalidPostMH) - malfeasanceHandler.RegisterHandlerV1(malfeasance.InvalidPrevATX, invalidPrevMH) + malfeasanceHandler.RegisterHandler(malfeasance.MultipleATXs, activationMH) + malfeasanceHandler.RegisterHandler(malfeasance.MultipleBallots, meshMH) + malfeasanceHandler.RegisterHandler(malfeasance.HareEquivocation, hareMH) + malfeasanceHandler.RegisterHandler(malfeasance.InvalidPostIndex, invalidPostMH) + malfeasanceHandler.RegisterHandler(malfeasance.InvalidPrevATX, invalidPrevMH) fetcher.SetValidators( fetch.ValidatorFunc( diff --git a/p2p/pubsub/mocks/publisher.go b/p2p/pubsub/mocks/publisher.go index 57b67d9db6..a94e468ae0 100644 --- a/p2p/pubsub/mocks/publisher.go +++ b/p2p/pubsub/mocks/publisher.go @@ -142,31 +142,31 @@ func (c *MockSubscriberRegisterCall) DoAndReturn(f func(string, pubsub.GossipHan return c } -// MockPublishSubsciber is a mock of PublishSubsciber interface. -type MockPublishSubsciber struct { +// MockPublishSubscriber is a mock of PublishSubscriber interface. +type MockPublishSubscriber struct { ctrl *gomock.Controller - recorder *MockPublishSubsciberMockRecorder + recorder *MockPublishSubscriberMockRecorder } -// MockPublishSubsciberMockRecorder is the mock recorder for MockPublishSubsciber. -type MockPublishSubsciberMockRecorder struct { - mock *MockPublishSubsciber +// MockPublishSubscriberMockRecorder is the mock recorder for MockPublishSubscriber. +type MockPublishSubscriberMockRecorder struct { + mock *MockPublishSubscriber } -// NewMockPublishSubsciber creates a new mock instance. -func NewMockPublishSubsciber(ctrl *gomock.Controller) *MockPublishSubsciber { - mock := &MockPublishSubsciber{ctrl: ctrl} - mock.recorder = &MockPublishSubsciberMockRecorder{mock} +// NewMockPublishSubscriber creates a new mock instance. +func NewMockPublishSubscriber(ctrl *gomock.Controller) *MockPublishSubscriber { + mock := &MockPublishSubscriber{ctrl: ctrl} + mock.recorder = &MockPublishSubscriberMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPublishSubsciber) EXPECT() *MockPublishSubsciberMockRecorder { +func (m *MockPublishSubscriber) EXPECT() *MockPublishSubscriberMockRecorder { return m.recorder } // Publish mocks base method. -func (m *MockPublishSubsciber) Publish(arg0 context.Context, arg1 string, arg2 []byte) error { +func (m *MockPublishSubscriber) Publish(arg0 context.Context, arg1 string, arg2 []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Publish", arg0, arg1, arg2) ret0, _ := ret[0].(error) @@ -174,37 +174,37 @@ func (m *MockPublishSubsciber) Publish(arg0 context.Context, arg1 string, arg2 [ } // Publish indicates an expected call of Publish. -func (mr *MockPublishSubsciberMockRecorder) Publish(arg0, arg1, arg2 any) *MockPublishSubsciberPublishCall { +func (mr *MockPublishSubscriberMockRecorder) Publish(arg0, arg1, arg2 any) *MockPublishSubscriberPublishCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockPublishSubsciber)(nil).Publish), arg0, arg1, arg2) - return &MockPublishSubsciberPublishCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockPublishSubscriber)(nil).Publish), arg0, arg1, arg2) + return &MockPublishSubscriberPublishCall{Call: call} } -// MockPublishSubsciberPublishCall wrap *gomock.Call -type MockPublishSubsciberPublishCall struct { +// MockPublishSubscriberPublishCall wrap *gomock.Call +type MockPublishSubscriberPublishCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockPublishSubsciberPublishCall) Return(arg0 error) *MockPublishSubsciberPublishCall { +func (c *MockPublishSubscriberPublishCall) Return(arg0 error) *MockPublishSubscriberPublishCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockPublishSubsciberPublishCall) Do(f func(context.Context, string, []byte) error) *MockPublishSubsciberPublishCall { +func (c *MockPublishSubscriberPublishCall) Do(f func(context.Context, string, []byte) error) *MockPublishSubscriberPublishCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockPublishSubsciberPublishCall) DoAndReturn(f func(context.Context, string, []byte) error) *MockPublishSubsciberPublishCall { +func (c *MockPublishSubscriberPublishCall) DoAndReturn(f func(context.Context, string, []byte) error) *MockPublishSubscriberPublishCall { c.Call = c.Call.DoAndReturn(f) return c } // Register mocks base method. -func (m *MockPublishSubsciber) Register(arg0 string, arg1 pubsub.GossipHandler, arg2 ...pubsub.ValidatorOpt) { +func (m *MockPublishSubscriber) Register(arg0 string, arg1 pubsub.GossipHandler, arg2 ...pubsub.ValidatorOpt) { m.ctrl.T.Helper() varargs := []any{arg0, arg1} for _, a := range arg2 { @@ -214,32 +214,32 @@ func (m *MockPublishSubsciber) Register(arg0 string, arg1 pubsub.GossipHandler, } // Register indicates an expected call of Register. -func (mr *MockPublishSubsciberMockRecorder) Register(arg0, arg1 any, arg2 ...any) *MockPublishSubsciberRegisterCall { +func (mr *MockPublishSubscriberMockRecorder) Register(arg0, arg1 any, arg2 ...any) *MockPublishSubscriberRegisterCall { mr.mock.ctrl.T.Helper() varargs := append([]any{arg0, arg1}, arg2...) - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockPublishSubsciber)(nil).Register), varargs...) - return &MockPublishSubsciberRegisterCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockPublishSubscriber)(nil).Register), varargs...) + return &MockPublishSubscriberRegisterCall{Call: call} } -// MockPublishSubsciberRegisterCall wrap *gomock.Call -type MockPublishSubsciberRegisterCall struct { +// MockPublishSubscriberRegisterCall wrap *gomock.Call +type MockPublishSubscriberRegisterCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockPublishSubsciberRegisterCall) Return() *MockPublishSubsciberRegisterCall { +func (c *MockPublishSubscriberRegisterCall) Return() *MockPublishSubscriberRegisterCall { c.Call = c.Call.Return() return c } // Do rewrite *gomock.Call.Do -func (c *MockPublishSubsciberRegisterCall) Do(f func(string, pubsub.GossipHandler, ...pubsub.ValidatorOpt)) *MockPublishSubsciberRegisterCall { +func (c *MockPublishSubscriberRegisterCall) Do(f func(string, pubsub.GossipHandler, ...pubsub.ValidatorOpt)) *MockPublishSubscriberRegisterCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockPublishSubsciberRegisterCall) DoAndReturn(f func(string, pubsub.GossipHandler, ...pubsub.ValidatorOpt)) *MockPublishSubsciberRegisterCall { +func (c *MockPublishSubscriberRegisterCall) DoAndReturn(f func(string, pubsub.GossipHandler, ...pubsub.ValidatorOpt)) *MockPublishSubscriberRegisterCall { c.Call = c.Call.DoAndReturn(f) return c } From 25a6e91a39964cf9201ae419c45f95bb2c90e3a6 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 26 Jul 2024 08:54:24 +0000 Subject: [PATCH 04/10] Add malfeasance2 package --- malfeasance2/handler.go | 89 ++++++++++++++++++++++++ malfeasance2/interface.go | 17 +++++ malfeasance2/mocks.go | 139 +++++++++++++++++++++++++++++++++++++ malfeasance2/wire.go | 43 ++++++++++++ malfeasance2/wire_scale.go | 126 +++++++++++++++++++++++++++++++++ 5 files changed, 414 insertions(+) create mode 100644 malfeasance2/handler.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 diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go new file mode 100644 index 0000000000..04d9016821 --- /dev/null +++ b/malfeasance2/handler.go @@ -0,0 +1,89 @@ +package malfeasance2 + +import ( + "context" + "fmt" + "slices" + + "go.uber.org/zap" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/p2p" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" +) + +var errWrongHash = fmt.Errorf("%w: incorrect hash", pubsub.ErrValidationReject) + +type Handler struct { + logger *zap.Logger + cdb *datastore.CachedDB + self p2p.Peer + tortoise tortoise + + handlers map[ProofDomain]MalfeasanceHandler +} + +func NewHandler( + cdb *datastore.CachedDB, + lg *zap.Logger, + self p2p.Peer, + tortoise tortoise, +) *Handler { + return &Handler{ + cdb: cdb, + logger: lg, + self: self, + tortoise: tortoise, + + 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 { + nodeIDs, err := h.handleProof(ctx, msg) + if err != nil { + h.logger.Warn("failed to handle synced malfeasance proof", + zap.Stringer("exp_hash", expHash), + zap.Error(err), + ) + return err + } + + if !slices.Contains(nodeIDs, types.NodeID(expHash)) { + h.logger.Warn("synced malfeasance proof invalid for requested nodeID", + zap.Stringer("exp_hash", expHash), + ) + return errWrongHash + } + + if err := h.storeProof(ctx, InvalidActivation, msg); err != nil { + h.logger.Warn("failed to store synced malfeasance proof", + zap.Stringer("exp_hash", expHash), + zap.Error(err), + ) + return 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 + } + + return nil +} + +func (h *Handler) handleProof(ctx context.Context, data []byte) ([]types.NodeID, error) { + return nil, nil +} + +func (h *Handler) storeProof(ctx context.Context, domain ProofDomain, proof []byte) error { + return nil +} diff --git a/malfeasance2/interface.go b/malfeasance2/interface.go new file mode 100644 index 0000000000..7ec43d8bf3 --- /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..aa46cf60cc --- /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 +} From 8444492ff979f3d76a7c85690ba7805136970284 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:44:25 +0000 Subject: [PATCH 05/10] Extend handler and add tests --- malfeasance2/handler.go | 71 +++++++++-- malfeasance2/handler_test.go | 225 +++++++++++++++++++++++++++++++++++ malfeasance2/interface.go | 2 +- malfeasance2/mocks.go | 10 +- 4 files changed, 292 insertions(+), 16 deletions(-) create mode 100644 malfeasance2/handler_test.go diff --git a/malfeasance2/handler.go b/malfeasance2/handler.go index 04d9016821..f9490228da 100644 --- a/malfeasance2/handler.go +++ b/malfeasance2/handler.go @@ -7,17 +7,23 @@ import ( "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" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/sql" ) -var errWrongHash = fmt.Errorf("%w: incorrect hash", pubsub.ErrValidationReject) +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 - cdb *datastore.CachedDB + db sql.Executor self p2p.Peer tortoise tortoise @@ -25,13 +31,13 @@ type Handler struct { } func NewHandler( - cdb *datastore.CachedDB, + db sql.Executor, lg *zap.Logger, self p2p.Peer, tortoise tortoise, ) *Handler { return &Handler{ - cdb: cdb, + db: db, logger: lg, self: self, tortoise: tortoise, @@ -45,7 +51,16 @@ func (h *Handler) RegisterHandler(malfeasanceType ProofDomain, handler Malfeasan } func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, _ p2p.Peer, msg []byte) error { - nodeIDs, err := h.handleProof(ctx, msg) + var proof MalfeasanceProof + if err := codec.Decode(msg, &proof); err != nil { + h.logger.Warn("failed to decode malfeasance proof", + zap.Stringer("exp_hash", expHash), + zap.Error(err), + ) + return ErrMalformedData + } + + nodeIDs, err := h.handleProof(ctx, proof) if err != nil { h.logger.Warn("failed to handle synced malfeasance proof", zap.Stringer("exp_hash", expHash), @@ -58,10 +73,10 @@ func (h *Handler) HandleSynced(ctx context.Context, expHash types.Hash32, _ p2p. h.logger.Warn("synced malfeasance proof invalid for requested nodeID", zap.Stringer("exp_hash", expHash), ) - return errWrongHash + return ErrWrongHash } - if err := h.storeProof(ctx, InvalidActivation, msg); err != nil { + if err := h.storeProof(ctx, proof.Domain, msg); err != nil { h.logger.Warn("failed to store synced malfeasance proof", zap.Stringer("exp_hash", expHash), zap.Error(err), @@ -77,11 +92,47 @@ func (h *Handler) HandleGossip(ctx context.Context, peer p2p.Peer, msg []byte) e return nil } + var proof MalfeasanceProof + if err := codec.Decode(msg, &proof); err != nil { + h.logger.Warn("failed to decode malfeasance proof", + zap.Stringer("peer", peer), + zap.Error(err), + ) + return ErrMalformedData + } + + _, err := h.handleProof(ctx, proof) + if err != nil { + h.logger.Warn("failed to handle gossiped malfeasance proof", + zap.Stringer("peer", peer), + zap.Error(err), + ) + return err + } + + if err := h.storeProof(ctx, proof.Domain, msg); err != nil { + h.logger.Warn("failed to store synced malfeasance proof", + zap.Error(err), + ) + return err + } + return nil } -func (h *Handler) handleProof(ctx context.Context, data []byte) ([]types.NodeID, error) { - return nil, 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 { diff --git a/malfeasance2/handler_test.go b/malfeasance2/handler_test.go new file mode 100644 index 0000000000..79edff4625 --- /dev/null +++ b/malfeasance2/handler_test.go @@ -0,0 +1,225 @@ +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" +) + +type testHandler struct { + *malfeasance2.Handler + + observedLogs *observer.ObservedLogs + db *sql.Database + self p2p.Peer + mockTrt *malfeasance2.Mocktortoise +} + +func newTestHandler(tb testing.TB) *testHandler { + db := sql.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 index 7ec43d8bf3..33f68921e5 100644 --- a/malfeasance2/interface.go +++ b/malfeasance2/interface.go @@ -13,5 +13,5 @@ type tortoise interface { } type MalfeasanceHandler interface { - Validate(ctx context.Context, data []byte) (types.NodeID, error) + Validate(ctx context.Context, data []byte) ([]types.NodeID, error) } diff --git a/malfeasance2/mocks.go b/malfeasance2/mocks.go index aa46cf60cc..b8f89fa37b 100644 --- a/malfeasance2/mocks.go +++ b/malfeasance2/mocks.go @@ -100,10 +100,10 @@ func (m *MockMalfeasanceHandler) EXPECT() *MockMalfeasanceHandlerMockRecorder { } // 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 } @@ -121,19 +121,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 } From 023697eca87ebd2298aa65d1a08357943fb78304 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:01:08 +0000 Subject: [PATCH 06/10] Cleanup --- systest/Makefile | 8 ++------ systest/cluster/nodes.go | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/systest/Makefile b/systest/Makefile index 9a2cb71dac..d5f0bf4865 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -36,12 +36,8 @@ ifeq ($(configname),$(test_job_name)) run_deps = config endif -command := tests -test.v -test.count=$(count) -test.timeout=0 -test.run=$(test_name) -clusters=$(clusters) \ --level=$(level) -configname=$(configname) - -ifeq ($(failfast),true) - command := $(command) -test.failfast -endif +command := tests -test.v -test.count=$(count) -test.timeout=60m -test.run=$(test_name) -test.parallel=$(clusters) \ + -test.failfast=$(failfast) -clusters=$(clusters) -level=$(level) -configname=$(configname) .PHONY: docker docker: diff --git a/systest/cluster/nodes.go b/systest/cluster/nodes.go index aca5764a1e..cba322d326 100644 --- a/systest/cluster/nodes.go +++ b/systest/cluster/nodes.go @@ -186,7 +186,7 @@ func (n *NodeClient) ensurePubConn(ctx context.Context) (*grpc.ClientConn, error if err != nil { return nil, err } - if err := n.waitForConnectionReady(context.Background(), conn); err != nil { + if err := n.waitForConnectionReady(ctx, conn); err != nil { return nil, err } n.pubConn = conn From 3018145170a8ec31129b8a3dbab06bcf4bc6c58a Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 26 Jul 2024 20:07:33 +0000 Subject: [PATCH 07/10] Add new malfeasance database --- sql/identities/identities.go | 2 +- sql/malfeasance/malfeasance.go | 177 ++++++++++++++++++++++ sql/migrations/state/0022_malfeasance.sql | 18 +++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 sql/malfeasance/malfeasance.go create mode 100644 sql/migrations/state/0022_malfeasance.sql diff --git a/sql/identities/identities.go b/sql/identities/identities.go index 28f7bdabf5..9c406810b0 100644 --- a/sql/identities/identities.go +++ b/sql/identities/identities.go @@ -199,7 +199,7 @@ func Marriage(db sql.Executor, id types.NodeID) (*MarriageData, error) { } // Set marriage inserts marriage data for given identity. -// If identitty doesn't exist - create it. +// If identity doesn't exist - create it. func SetMarriage(db sql.Executor, id types.NodeID, m *MarriageData) error { _, err := db.Exec(` INSERT INTO identities (pubkey, marriage_atx, marriage_idx, marriage_target, marriage_signature) diff --git a/sql/malfeasance/malfeasance.go b/sql/malfeasance/malfeasance.go new file mode 100644 index 0000000000..7a8c6ac0dc --- /dev/null +++ b/sql/malfeasance/malfeasance.go @@ -0,0 +1,177 @@ +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.BindBytes(2, proof) + stmt.BindInt64(3, received.UnixNano()) + }, 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 { + // check if marriedTo has a proof + rows, err := db.Exec(` + SELECT 1 FROM malfeasance + WHERE pubkey = ?1 AND proof IS NOT NULL;`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, marriedTo.Bytes()) + }, nil, + ) + if err != nil { + return fmt.Errorf("add married %s: %w", nodeID, err) + } + if rows == 0 { + return fmt.Errorf("add married %s: parent has no proof", nodeID) + } + + _, err = db.Exec(` + INSERT INTO malfeasance (pubkey, received, married_to) + VALUES (?1, ?2, ?3);`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + stmt.BindBytes(2, marriedTo.Bytes()) + stmt.BindInt64(3, received.UnixNano()) + }, 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 +} + +func Proof(db sql.Executor, nodeID types.NodeID) ([]byte, error) { + var proof []byte + _, err := db.Exec(` + SELECT proof FROM malfeasance + WHERE pubkey = ?1;`, + func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + }, func(stmt *sql.Statement) bool { + proof = make([]byte, stmt.ColumnLen(0)) + stmt.ColumnBytes(0, proof) + return true + }, + ) + if err != nil { + return nil, fmt.Errorf("proof %v: %w", nodeID, err) + } + if proof != nil { + return proof, nil + } + + _, err = db.Exec(` + SELECT 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 { + proof = make([]byte, stmt.ColumnLen(0)) + stmt.ColumnBytes(0, proof) + return true + }, + ) + if err != nil { + return nil, fmt.Errorf("proof %v: %w", nodeID, err) + } + + return 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/migrations/state/0022_malfeasance.sql b/sql/migrations/state/0022_malfeasance.sql new file mode 100644 index 0000000000..d2faeb6add --- /dev/null +++ b/sql/migrations/state/0022_malfeasance.sql @@ -0,0 +1,18 @@ +-- 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 + -- check the identity that is referenced for the proof + married_to CHAR(32), -- the pubkey of identity in the marriage set that was proven to be malicious + FOREIGN KEY (married_to) REFERENCES malfeasance (pubkey), -- ensure that the married_to field is a valid pubkey already in the table + + -- 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 +) WITHOUT ROWID; From 3c744797f8f689f55f35c17660f8e7c06bea86b6 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 26 Jul 2024 21:09:19 +0000 Subject: [PATCH 08/10] Add tests --- sql/database.go | 4 ++ sql/malfeasance/malfeasance.go | 26 ++------ sql/malfeasance/malfeasance_test.go | 75 +++++++++++++++++++++++ sql/migrations/state/0022_malfeasance.sql | 10 +-- 4 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 sql/malfeasance/malfeasance_test.go diff --git a/sql/database.go b/sql/database.go index 5f86f0bda5..a2e73ee560 100644 --- a/sql/database.go +++ b/sql/database.go @@ -203,6 +203,10 @@ func Open(uri string, opts ...Opt) (*Database, error) { 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) + } //nolint:nestif if config.migrations != nil { before, err := version(db) diff --git a/sql/malfeasance/malfeasance.go b/sql/malfeasance/malfeasance.go index 7a8c6ac0dc..8c2a604ef7 100644 --- a/sql/malfeasance/malfeasance.go +++ b/sql/malfeasance/malfeasance.go @@ -15,8 +15,9 @@ func Add(db sql.Executor, nodeID types.NodeID, domain byte, proof []byte, receiv VALUES (?1, ?2, ?3, ?4);`, func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) - stmt.BindBytes(2, proof) - stmt.BindInt64(3, received.UnixNano()) + stmt.BindInt64(2, received.UnixNano()) + stmt.BindInt64(3, int64(domain)) + stmt.BindBytes(4, proof) }, nil, ) if err != nil { @@ -26,28 +27,13 @@ func Add(db sql.Executor, nodeID types.NodeID, domain byte, proof []byte, receiv } func AddMarried(db sql.Executor, nodeID, marriedTo types.NodeID, received time.Time) error { - // check if marriedTo has a proof - rows, err := db.Exec(` - SELECT 1 FROM malfeasance - WHERE pubkey = ?1 AND proof IS NOT NULL;`, - func(stmt *sql.Statement) { - stmt.BindBytes(1, marriedTo.Bytes()) - }, nil, - ) - if err != nil { - return fmt.Errorf("add married %s: %w", nodeID, err) - } - if rows == 0 { - return fmt.Errorf("add married %s: parent has no proof", nodeID) - } - - _, err = db.Exec(` + _, err := db.Exec(` INSERT INTO malfeasance (pubkey, received, married_to) VALUES (?1, ?2, ?3);`, func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) - stmt.BindBytes(2, marriedTo.Bytes()) - stmt.BindInt64(3, received.UnixNano()) + stmt.BindInt64(2, received.UnixNano()) + stmt.BindBytes(3, marriedTo.Bytes()) }, nil, ) if err != nil { diff --git a/sql/malfeasance/malfeasance_test.go b/sql/malfeasance/malfeasance_test.go new file mode 100644 index 0000000000..a2e50716c7 --- /dev/null +++ b/sql/malfeasance/malfeasance_test.go @@ -0,0 +1,75 @@ +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" +) + +func TestAdd(t *testing.T) { + t.Parallel() + + db := sql.InMemory() + + 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 := sql.InMemory() + + 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 := sql.InMemory() + + 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) +} diff --git a/sql/migrations/state/0022_malfeasance.sql b/sql/migrations/state/0022_malfeasance.sql index d2faeb6add..83d824ed56 100644 --- a/sql/migrations/state/0022_malfeasance.sql +++ b/sql/migrations/state/0022_malfeasance.sql @@ -8,11 +8,13 @@ CREATE TABLE malfeasance received INT NOT NULL, -- unix timestamp -- if the following field is not null, then domain and proof are null - -- check the identity that is referenced for the proof married_to CHAR(32), -- the pubkey of identity in the marriage set that was proven to be malicious - FOREIGN KEY (married_to) REFERENCES malfeasance (pubkey), -- ensure that the married_to field is a valid pubkey already in the table -- 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 -) WITHOUT ROWID; + 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) +); + From 817b0ebd2b5b399fe66a3ffb502879396947929c Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Fri, 26 Jul 2024 21:26:31 +0000 Subject: [PATCH 09/10] More tests --- sql/malfeasance/malfeasance.go | 33 +++++++----- sql/malfeasance/malfeasance_test.go | 80 +++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/sql/malfeasance/malfeasance.go b/sql/malfeasance/malfeasance.go index 8c2a604ef7..28809cf544 100644 --- a/sql/malfeasance/malfeasance.go +++ b/sql/malfeasance/malfeasance.go @@ -56,28 +56,32 @@ func IsMalicious(db sql.Executor, nodeID types.NodeID) (bool, error) { return rows > 0, nil } -func Proof(db sql.Executor, nodeID types.NodeID) ([]byte, error) { +// 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 proof FROM malfeasance + SELECT domain, proof FROM malfeasance WHERE pubkey = ?1;`, 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.ColumnInt(0)) + proof = make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(1, proof) return true }, ) if err != nil { - return nil, fmt.Errorf("proof %v: %w", nodeID, err) + return types.EmptyNodeID, 0, nil, fmt.Errorf("proof %v: %w", nodeID, err) } - if proof != nil { - return proof, nil + if len(proof) > 0 { + return nodeID, domain, proof, nil } _, err = db.Exec(` - SELECT proof FROM malfeasance + SELECT pubkey, domain, proof FROM malfeasance WHERE pubkey = ( SELECT married_to FROM malfeasance WHERE pubkey = ?1 @@ -85,16 +89,21 @@ func Proof(db sql.Executor, nodeID types.NodeID) ([]byte, error) { func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) }, func(stmt *sql.Statement) bool { - proof = make([]byte, stmt.ColumnLen(0)) - stmt.ColumnBytes(0, proof) + 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 nil, fmt.Errorf("proof %v: %w", nodeID, err) + 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 proof, nil + return nodeID, domain, proof, nil } // TODO(mafa): it seems that this is again needed by the fetcher. diff --git a/sql/malfeasance/malfeasance_test.go b/sql/malfeasance/malfeasance_test.go index a2e50716c7..d9aee45be2 100644 --- a/sql/malfeasance/malfeasance_test.go +++ b/sql/malfeasance/malfeasance_test.go @@ -73,3 +73,83 @@ func TestAddMarriedMissing(t *testing.T) { require.NoError(t, err) require.False(t, mal) } + +func TestProof(t *testing.T) { + t.Parallel() + + db := sql.InMemory() + + 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 := sql.InMemory() + + 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 := sql.InMemory() + + 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) +} From 5018c9b68b0a5a9105cd248e739bc00cfeb995d3 Mon Sep 17 00:00:00 2001 From: Matthias <5011972+fasmat@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:05:52 +0000 Subject: [PATCH 10/10] make generate --- p2p/pubsub/pubsub.go | 2 +- sql/statesql/schema/schema.sql | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index a467422050..db67c4904b 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -76,7 +76,7 @@ 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 is the protocol id for malfeasance proofs (soon to be deprecated). MalfeasanceProof = "mp1" // MalfeasanceProof2 is the protocol id for V2 malfeasance proofs. MalfeasanceProof2 = "mp2" diff --git a/sql/statesql/schema/schema.sql b/sql/statesql/schema/schema.sql index 3eee310600..cbc917c77a 100755 --- a/sql/statesql/schema/schema.sql +++ b/sql/statesql/schema/schema.sql @@ -1,4 +1,4 @@ -PRAGMA user_version = 22; +PRAGMA user_version = 23; CREATE TABLE accounts ( address CHAR(24), @@ -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,