diff --git a/activation/builder_v2_test.go b/activation/builder_v2_test.go index 0054147f4e9..bb156484b76 100644 --- a/activation/builder_v2_test.go +++ b/activation/builder_v2_test.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -55,7 +56,7 @@ func TestBuilder_BuildsInitialAtxV2(t *testing.T) { atxHandler := newTestHandler(t, tab.goldenATXID, WithAtxVersions(AtxVersions{1: types.AtxV2})) atxHandler.expectInitialAtxV2(&atx) - require.NoError(t, atxHandler.HandleGossipAtx(context.Background(), "", got)) + require.NoError(t, atxHandler.HandleGossipAtx(context.Background(), p2p.NoPeer, got)) return nil }) require.Empty(t, atx.PreviousATXs) diff --git a/activation/e2e/atx_merge_test.go b/activation/e2e/atx_merge_test.go index a97c44b74e9..3c52357aaae 100644 --- a/activation/e2e/atx_merge_test.go +++ b/activation/e2e/atx_merge_test.go @@ -25,7 +25,7 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" @@ -275,9 +275,9 @@ func Test_MarryAndMerge(t *testing.T) { ) require.NoError(t, err) - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) tickSize := uint64(3) @@ -287,10 +287,10 @@ func Test_MarryAndMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -358,11 +358,11 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - return atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedIdAtx)) + return atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedIdAtx)) }) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(marriageATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(marriageATX)) require.NoError(t, err) // Verify marriage @@ -422,7 +422,7 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX)) require.NoError(t, err) // Step 3. verify the merged ATX @@ -473,7 +473,7 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX2)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX2)) require.NoError(t, err) atx, err = atxs.Get(db, mergedATX2.ID()) @@ -511,7 +511,7 @@ func Test_MarryAndMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx)) require.NoError(t, err) atxFromDb, err := atxs.Get(db, atx.ID()) diff --git a/activation/e2e/builds_atx_v2_test.go b/activation/e2e/builds_atx_v2_test.go index 93832608eba..d7075ed9824 100644 --- a/activation/e2e/builds_atx_v2_test.go +++ b/activation/e2e/builds_atx_v2_test.go @@ -118,7 +118,8 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { edVerifier := signing.NewEdVerifier() mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( @@ -127,10 +128,10 @@ func TestBuilder_SwitchesToBuildV2(t *testing.T) { atxsdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/e2e/checkpoint_merged_test.go b/activation/e2e/checkpoint_merged_test.go index 71f91691f35..dcf2e49353d 100644 --- a/activation/e2e/checkpoint_merged_test.go +++ b/activation/e2e/checkpoint_merged_test.go @@ -23,7 +23,7 @@ import ( "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/accounts" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -106,9 +106,9 @@ func Test_CheckpointAfterMerge(t *testing.T) { ) require.NoError(t, err) - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( @@ -117,10 +117,10 @@ func Test_CheckpointAfterMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -183,9 +183,9 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), []types.ATXID{mergedIdAtx.ID()}, gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()).Times(2) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()).Times(2) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedIdAtx)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedIdAtx)) require.NoError(t, err) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(marriageATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(marriageATX)) require.NoError(t, err) // Step 2. Publish merged ATX together @@ -236,7 +236,7 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX)) require.NoError(t, err) // Step 3. Checkpoint @@ -296,10 +296,10 @@ func Test_CheckpointAfterMerge(t *testing.T) { atxsdata.New(), signing.NewEdVerifier(), clock, - mpub, mFetch, goldenATX, validator, + mLegacyPublish, mBeacon, mTortoise, logger, @@ -352,6 +352,6 @@ func Test_CheckpointAfterMerge(t *testing.T) { mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()) mBeacon.EXPECT().OnAtx(gomock.Any()) mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(mergedATX2)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(mergedATX2)) require.NoError(t, err) } diff --git a/activation/e2e/checkpoint_test.go b/activation/e2e/checkpoint_test.go index b7a2596cae4..f280718ba70 100644 --- a/activation/e2e/checkpoint_test.go +++ b/activation/e2e/checkpoint_test.go @@ -102,9 +102,9 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata := atxsdata.New() atxVersions := activation.AtxVersions{0: types.AtxV2} edVerifier := signing.NewEdVerifier() - mpub := mocks.NewMockPublisher(ctrl) mFetch := smocks.NewMockFetcher(ctrl) - mBeacon := activation.NewMockAtxReceiver(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) mTortoise := smocks.NewMockTortoise(ctrl) atxHdlr := activation.NewHandler( @@ -113,16 +113,17 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, + mLegacyPublish, mBeacon, mTortoise, logger, activation.WithAtxVersions(atxVersions), ) + mpub := mocks.NewMockPublisher(ctrl) tab := activation.NewBuilder( activation.Config{GoldenATXID: goldenATX}, db, @@ -202,10 +203,10 @@ func TestCheckpoint_PublishingSoloATXs(t *testing.T) { atxdata, edVerifier, clock, - mpub, mFetch, goldenATX, validator, + mLegacyPublish, mBeacon, mTortoise, logger, diff --git a/activation/handler.go b/activation/handler.go index 3960e484a79..e2bd9acd397 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -17,7 +17,6 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/log" - mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" @@ -67,10 +66,9 @@ func (v AtxVersions) Validate() error { // Handler processes the atxs received from all nodes and their validity status. type Handler struct { - local p2p.Peer - publisher pubsub.Publisher - logger *zap.Logger - versions []atxVersion + local p2p.Peer + logger *zap.Logger + versions []atxVersion // inProgress is used to avoid processing the same ATX multiple times in parallel. inProgress singleflight.Group @@ -102,20 +100,19 @@ func NewHandler( atxsdata *atxsdata.Data, edVerifier *signing.EdVerifier, c layerClock, - pub pubsub.Publisher, fetcher system.Fetcher, goldenATXID types.ATXID, nipostValidator nipostValidator, - beacon AtxReceiver, + legacyMalPublisher legacyMalfeasancePublisher, + beacon atxReceiver, tortoise system.Tortoise, lg *zap.Logger, opts ...HandlerOption, ) *Handler { h := &Handler{ - local: local, - publisher: pub, - logger: lg, - versions: []atxVersion{{0, types.AtxV1}}, + local: local, + logger: lg, + versions: []atxVersion{{0, types.AtxV1}}, v1: &HandlerV1{ local: local, @@ -130,6 +127,7 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, + malPublisher: legacyMalPublisher, signers: make(map[types.NodeID]*signing.EdSigner), }, @@ -171,48 +169,52 @@ func (h *Handler) Register(sig *signing.EdSigner) { // HandleSyncedAtx handles atxs received by sync. func (h *Handler) HandleSyncedAtx(ctx context.Context, expHash types.Hash32, peer p2p.Peer, data []byte) error { - _, err := h.handleAtx(ctx, expHash, peer, data) - if err != nil && !errors.Is(err, errMalformedData) && !errors.Is(err, errKnownAtx) { + err := h.handleAtx(ctx, expHash, peer, data) + switch { + case errors.Is(err, errKnownAtx): + return nil + case errors.Is(err, errMalformedData): + h.logger.Debug("malformed atx", + log.ZContext(ctx), + zap.Stringer("sender", peer), + zap.Error(err), + ) + return err + case err != nil: h.logger.Warn("failed to process synced atx", log.ZContext(ctx), zap.Stringer("sender", peer), zap.Error(err), ) + return err } - if errors.Is(err, errKnownAtx) { - return nil - } - return err + return nil } // HandleGossipAtx handles the atx gossip data channel. func (h *Handler) HandleGossipAtx(ctx context.Context, peer p2p.Peer, msg []byte) error { - proof, err := h.handleAtx(ctx, types.EmptyHash32, peer, msg) - if err != nil && !errors.Is(err, errMalformedData) && !errors.Is(err, errKnownAtx) { + err := h.handleAtx(ctx, types.EmptyHash32, peer, msg) + switch { + case errors.Is(err, errKnownAtx) && peer == h.local: + return nil + case errors.Is(err, errKnownAtx): + return errKnownAtx + case errors.Is(err, errMalformedData): + h.logger.Debug("malformed atx gossip", + log.ZContext(ctx), + zap.Stringer("sender", peer), + zap.Error(err), + ) + return err + case err != nil: h.logger.Warn("failed to process atx gossip", log.ZContext(ctx), zap.Stringer("sender", peer), zap.Error(err), ) + return err } - if errors.Is(err, errKnownAtx) && peer == h.local { - return nil - } - - // broadcast malfeasance proof last as the verification of the proof will take place - // in the same goroutine - if proof != nil { - gossip := mwire.MalfeasanceGossip{ - MalfeasanceProof: *proof, - } - encodedProof := codec.MustEncode(&gossip) - if err = h.publisher.Publish(ctx, pubsub.MalfeasanceProof, encodedProof); err != nil { - h.logger.Error("failed to broadcast malfeasance proof", zap.Error(err)) - return fmt.Errorf("broadcast atx malfeasance proof: %w", err) - } - return errMaliciousATX - } - return err + return nil } func (h *Handler) determineVersion(msg []byte) (*types.AtxVersion, error) { @@ -256,26 +258,21 @@ func (h *Handler) decodeATX(msg []byte) (atx opaqueAtx, err error) { return atx, nil } -func (h *Handler) handleAtx( - ctx context.Context, - expHash types.Hash32, - peer p2p.Peer, - msg []byte, -) (*mwire.MalfeasanceProof, error) { +func (h *Handler) handleAtx(ctx context.Context, expHash types.Hash32, peer p2p.Peer, msg []byte) error { receivedTime := time.Now() opaqueAtx, err := h.decodeATX(msg) if err != nil { - return nil, fmt.Errorf("%w: decoding ATX: %w", pubsub.ErrValidationReject, err) + return fmt.Errorf("%w: decoding ATX: %w", pubsub.ErrValidationReject, err) } id := opaqueAtx.ID() - if (expHash != types.Hash32{}) && id.Hash32() != expHash { - return nil, fmt.Errorf("%w: atx want %s, got %s", errWrongHash, expHash.ShortString(), id.ShortString()) + if expHash != types.EmptyHash32 && id.Hash32() != expHash { + return fmt.Errorf("%w: atx want %s, got %s", errWrongHash, expHash.ShortString(), id.ShortString()) } key := string(id.Bytes()) - proof, err, _ := h.inProgress.Do(key, func() (any, error) { + _, err, _ = h.inProgress.Do(key, func() (any, error) { h.logger.Debug("handling incoming atx", log.ZContext(ctx), zap.Stringer("atx_id", id), @@ -284,16 +281,15 @@ func (h *Handler) handleAtx( switch atx := opaqueAtx.(type) { case *wire.ActivationTxV1: - return h.v1.processATX(ctx, peer, atx, receivedTime) + return nil, h.v1.processATX(ctx, peer, atx, receivedTime) case *wire.ActivationTxV2: - return (*mwire.MalfeasanceProof)(nil), h.v2.processATX(ctx, peer, atx, receivedTime) + return nil, h.v2.processATX(ctx, peer, atx, receivedTime) default: panic("unreachable") } }) h.inProgress.Forget(key) - - return proof.(*mwire.MalfeasanceProof), err + return err } // Obtain the atxSignature of the given ATX. diff --git a/activation/handler_test.go b/activation/handler_test.go index 7c30c86fd26..63d7cdc1236 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/merkle-tree" poetShared "github.com/spacemeshos/poet/shared" + "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,7 +28,6 @@ import ( mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" - pubsubmocks "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -123,13 +123,13 @@ type handlerMocks struct { ctrl *gomock.Controller goldenATXID types.ATXID - mclock *MocklayerClock - mpub *pubsubmocks.MockPublisher - mockFetch *mocks.MockFetcher - mValidator *MocknipostValidator - mbeacon *MockAtxReceiver - mtortoise *mocks.MockTortoise - mMalPublish *MockatxMalfeasancePublisher + mClock *MocklayerClock + mockFetch *mocks.MockFetcher + mValidator *MocknipostValidator + mBeacon *MockatxReceiver + mTortoise *mocks.MockTortoise + mLegacyMalPublish *MocklegacyMalfeasancePublisher + mMalPublish *MockatxMalfeasancePublisher } type testHandler struct { @@ -152,7 +152,7 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID for _, opt := range opts { opt(&settings) } - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) if atx.VRFNonce != nil { h.mValidator.EXPECT(). @@ -188,8 +188,8 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID NIPost(gomock.Any(), nodeId, h.goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(settings.poetLeaves, nil) h.mValidator.EXPECT().IsVerifyingFullPost().Return(!settings.distributedPost) - h.mbeacon.EXPECT().OnAtx(gomock.Any()) - h.mtortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) + h.mBeacon.EXPECT().OnAtx(gomock.Any()) + h.mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) } func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { @@ -197,14 +197,14 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks { return handlerMocks{ ctrl: ctrl, - goldenATXID: golden, - mclock: NewMocklayerClock(ctrl), - mpub: pubsubmocks.NewMockPublisher(ctrl), - mockFetch: mocks.NewMockFetcher(ctrl), - mValidator: NewMocknipostValidator(ctrl), - mbeacon: NewMockAtxReceiver(ctrl), - mtortoise: mocks.NewMockTortoise(ctrl), - mMalPublish: NewMockatxMalfeasancePublisher(ctrl), + goldenATXID: golden, + mClock: NewMocklayerClock(ctrl), + mockFetch: mocks.NewMockFetcher(ctrl), + mValidator: NewMocknipostValidator(ctrl), + mBeacon: NewMockatxReceiver(ctrl), + mTortoise: mocks.NewMockTortoise(ctrl), + mLegacyMalPublish: NewMocklegacyMalfeasancePublisher(ctrl), + mMalPublish: NewMockatxMalfeasancePublisher(ctrl), } } @@ -222,13 +222,13 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio cdb, atxsdata.New(), edVerifier, - mocks.mclock, - mocks.mpub, + mocks.mClock, mocks.mockFetch, goldenATXID, mocks.mValidator, - mocks.mbeacon, - mocks.mtortoise, + mocks.mLegacyMalPublish, + mocks.mBeacon, + mocks.mTortoise, lg, opts..., ) @@ -256,7 +256,7 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT().VRFNonce(atx.SmesherID, goldenATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), gomock.Any(), *atx.CommitmentATXID, gomock.Any(), gomock.Any(), atx.NumUnits) @@ -267,15 +267,26 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(gomock.Any()) - msg := codec.MustEncode(atx) - require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.Hash32{}, p2p.NoPeer, msg)) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) - // identity is still marked as malicious - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) + postVerifier.EXPECT(). + Verify(context.Background(), (*shared.Proof)(atx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + + msg := codec.MustEncode(atx) + require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.EmptyHash32, p2p.NoPeer, msg)) }) t.Run("produced and published during gossip", func(t *testing.T) { @@ -292,7 +303,7 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT().VRFNonce(atx.SmesherID, goldenATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), gomock.Any(), *atx.CommitmentATXID, gomock.Any(), gomock.Any(), atx.NumUnits) @@ -303,32 +314,25 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(gomock.Any()) - msg := codec.MustEncode(atx) - postVerifier := NewMockPostVerifier(gomock.NewController(t)) - mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) - atxHdlr.mpub.EXPECT().Publish(gomock.Any(), pubsub.MalfeasanceProof, gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, data []byte) error { - var got mwire.MalfeasanceGossip - require.NoError(t, codec.Decode(data, &got)) - require.Equal(t, mwire.InvalidPostIndex, got.Proof.Type) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) + + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) postVerifier.EXPECT(). - Verify(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(errors.New("invalid")) - nodeID, err := mh.Validate(context.Background(), got.Proof.Data) + Verify(context.Background(), (*shared.Proof)(atx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) require.NoError(t, err) require.Equal(t, sig.NodeID(), nodeID) - p, ok := got.Proof.Data.(*mwire.InvalidPostIndexProof) - require.True(t, ok) - require.EqualValues(t, 2, p.InvalidIdx) return nil }) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg), errMaliciousATX) - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + msg := codec.MustEncode(atx) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg)) }) } @@ -342,7 +346,7 @@ func TestHandler_ProcessAtxStoresNewVRFNonce(t *testing.T) { atx1 := newInitialATXv1(t, goldenATXID) atx1.Sign(sig) atxHdlr.expectAtxV1(atx1, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1))) got, err := atxs.VRFNonce(atxHdlr.cdb, sig.NodeID(), atx1.PublishEpoch+1) require.NoError(t, err) @@ -353,7 +357,7 @@ func TestHandler_ProcessAtxStoresNewVRFNonce(t *testing.T) { atx2.VRFNonce = (*uint64)(&nonce2) atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx2))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx2))) got, err = atxs.VRFNonce(atxHdlr.cdb, sig.NodeID(), atx2.PublishEpoch+1) require.NoError(t, err) @@ -373,7 +377,7 @@ func TestHandler_HandleGossipAtx(t *testing.T) { second.Sign(sig) // the poet is missing - atxHdlr.mclock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) atxHdlr.mockFetch.EXPECT().RegisterPeerHashes( p2p.NoPeer, []types.Hash32{first.ID().Hash32(), types.Hash32(second.NIPost.PostMetadata.Challenge)}, @@ -382,27 +386,27 @@ func TestHandler_HandleGossipAtx(t *testing.T) { GetPoetProof(gomock.Any(), types.Hash32(second.NIPost.PostMetadata.Challenge)). Return(errors.New("missing poet proof")) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(second)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(second)) require.ErrorContains(t, err, "missing poet proof") // deps (prevATX, posATX, commitmentATX) are missing - atxHdlr.mclock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(second.PublishEpoch.FirstLayer()) atxHdlr.mockFetch.EXPECT().RegisterPeerHashes( p2p.NoPeer, []types.Hash32{first.ID().Hash32(), types.Hash32(second.NIPost.PostMetadata.Challenge)}, ) atxHdlr.mockFetch.EXPECT().GetPoetProof(gomock.Any(), types.Hash32(second.NIPost.PostMetadata.Challenge)) atxHdlr.mockFetch.EXPECT().GetAtxs(gomock.Any(), []types.ATXID{second.PrevATXID}, gomock.Any()) - err = atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(second)) + err = atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(second)) require.ErrorIs(t, err, sql.ErrNotFound) // valid first comes in atxHdlr.expectAtxV1(first, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(first))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(first))) // second is now valid (deps are in) atxHdlr.expectAtxV1(second, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(second))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(second))) } func TestHandler_HandleParallelGossipAtxV1(t *testing.T) { @@ -423,7 +427,7 @@ func TestHandler_HandleParallelGossipAtxV1(t *testing.T) { var eg errgroup.Group for range 10 { eg.Go(func() error { - return atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1)) + return atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1)) }) } @@ -441,7 +445,7 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { atx1 := newInitialATXv1(t, goldenATXID) atx1.Sign(sig) atxHdlr.expectAtxV1(atx1, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1))) malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) require.NoError(t, err) @@ -453,14 +457,20 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - msg := codec.MustEncode(atx2) - require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.Hash32{}, "", msg)) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx2.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) - // identity is still marked as malicious - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + + msg := codec.MustEncode(atx2) + require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), types.EmptyHash32, p2p.NoPeer, msg)) }) t.Run("produced and published during gossip", func(t *testing.T) { @@ -473,7 +483,7 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { atx1 := newInitialATXv1(t, goldenATXID) atx1.Sign(sig) atxHdlr.expectAtxV1(atx1, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx1))) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx1))) malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) require.NoError(t, err) @@ -484,25 +494,21 @@ func TestHandler_HandleMaliciousAtx(t *testing.T) { }) atx2.Sign(sig) atxHdlr.expectAtxV1(atx2, sig.NodeID()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - msg := codec.MustEncode(atx2) - mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) - atxHdlr.mpub.EXPECT().Publish(gomock.Any(), pubsub.MalfeasanceProof, gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, data []byte) error { - var got mwire.MalfeasanceGossip - require.NoError(t, codec.Decode(data, &got)) - require.Equal(t, mwire.MultipleATXs, got.Proof.Type) - nodeID, err := mh.Validate(context.Background(), got.Proof.Data) + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), atx2.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) + + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) require.NoError(t, err) require.Equal(t, sig.NodeID(), nodeID) return nil - }) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg), errMaliciousATX) + }, + ) - malicious, err = identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + msg := codec.MustEncode(atx2) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, msg)) }) } @@ -536,8 +542,8 @@ func TestHandler_HandleSyncedAtx(t *testing.T) { atxHdlr := newTestHandler(t, goldenATXID) atxHdlr.expectAtxV1(atx, sig.NodeID()) - require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", buf)) - require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), "", buf), errKnownAtx) + require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, buf)) + require.ErrorIs(t, atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, buf), errKnownAtx) require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), atx.ID().Hash32(), p2p.NoPeer, buf)) }) @@ -725,7 +731,7 @@ func TestHandler_WrongHash(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - err = atxHdlr.HandleSyncedAtx(context.Background(), types.RandomHash(), "", codec.MustEncode(atx)) + err = atxHdlr.HandleSyncedAtx(context.Background(), types.RandomHash(), p2p.NoPeer, codec.MustEncode(atx)) require.ErrorIs(t, err, errWrongHash) require.ErrorIs(t, err, pubsub.ErrValidationReject) } @@ -744,7 +750,7 @@ func TestHandler_MarksAtxValid(t *testing.T) { atxHdlr := newTestHandler(t, goldenATXID) atxHdlr.expectAtxV1(atx, sig.NodeID(), func(o *atxHandleOpts) { o.distributedPost = false }) - err := atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx)) + err := atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx)) require.NoError(t, err) vatx, err := atxs.Get(atxHdlr.cdb, atx.ID()) @@ -759,7 +765,7 @@ func TestHandler_MarksAtxValid(t *testing.T) { atxHdlr := newTestHandler(t, goldenATXID) atxHdlr.expectAtxV1(atx, sig.NodeID(), func(o *atxHandleOpts) { o.distributedPost = true }) - err := atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx)) + err := atxHdlr.HandleGossipAtx(context.Background(), p2p.NoPeer, codec.MustEncode(atx)) require.NoError(t, err) vatx, err := atxs.Get(atxHdlr.cdb, atx.ID()) diff --git a/activation/handler_v1.go b/activation/handler_v1.go index 55195735627..216e09bdec4 100644 --- a/activation/handler_v1.go +++ b/activation/handler_v1.go @@ -57,13 +57,8 @@ type nipostValidatorV1 interface { opts ...validatorOption, ) error - VRFNonce( - nodeId types.NodeID, - commitmentAtxId types.ATXID, - vrfNonce, labelsPerUnit uint64, - numUnits uint32, - ) error - PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubepoch types.EpochID) error + VRFNonce(nodeId types.NodeID, commitmentAtxId types.ATXID, vrfNonce, labelsPerUnit uint64, numUnits uint32) error + PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubEpoch types.EpochID) error } // HandlerV1 processes ATXs version 1. @@ -76,10 +71,11 @@ type HandlerV1 struct { tickSize uint64 goldenATXID types.ATXID nipostValidator nipostValidatorV1 - beacon AtxReceiver + beacon atxReceiver tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher + malPublisher legacyMalfeasancePublisher signerMtx sync.Mutex signers map[types.NodeID]*signing.EdSigner @@ -173,10 +169,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( ctx context.Context, watx *wire.ActivationTxV1, received time.Time, -) (*types.ActivationTx, *mwire.MalfeasanceProof, error) { +) (*types.ActivationTx, error) { commitmentATX, err := h.commitment(watx) if err != nil { - return nil, nil, fmt.Errorf("commitment atx for %s not found: %w", watx.SmesherID, err) + return nil, fmt.Errorf("commitment atx for %s not found: %w", watx.SmesherID, err) } var effectiveNumUnits uint32 @@ -184,29 +180,29 @@ func (h *HandlerV1) syntacticallyValidateDeps( if watx.PrevATXID == types.EmptyATXID { err := h.nipostValidator.InitialNIPostChallengeV1(&watx.NIPostChallengeV1, h.cdb, h.goldenATXID) if err != nil { - return nil, nil, err + return nil, err } effectiveNumUnits = watx.NumUnits vrfNonce = *watx.VRFNonce } else { previous, err := atxs.Get(h.cdb, watx.PrevATXID) if err != nil { - return nil, nil, fmt.Errorf("fetching previous atx %s: %w", watx.PrevATXID, err) + return nil, fmt.Errorf("fetching previous atx %s: %w", watx.PrevATXID, err) } vrfNonce, err = h.validateNonInitialAtx(ctx, watx, previous, commitmentATX) if err != nil { - return nil, nil, err + return nil, err } prevUnits, err := atxs.Units(h.cdb, watx.PrevATXID, watx.SmesherID) if err != nil { - return nil, nil, fmt.Errorf("fetching previous atx units: %w", err) + return nil, fmt.Errorf("fetching previous atx units: %w", err) } effectiveNumUnits = min(prevUnits, watx.NumUnits) } err = h.nipostValidator.PositioningAtx(watx.PositioningATXID, h.cdb, h.goldenATXID, watx.PublishEpoch) if err != nil { - return nil, nil, err + return nil, err } expectedChallengeHash := watx.NIPostChallengeV1.Hash() @@ -234,10 +230,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( ) malicious, err := identities.IsMalicious(h.cdb, watx.SmesherID) if err != nil { - return nil, nil, fmt.Errorf("check if smesher is malicious: %w", err) + return nil, fmt.Errorf("check if smesher is malicious: %w", err) } if malicious { - return nil, nil, fmt.Errorf("smesher %s is known malfeasant", watx.SmesherID.ShortString()) + return nil, fmt.Errorf("smesher %s is known malfeasant", watx.SmesherID.ShortString()) } proof := &mwire.MalfeasanceProof{ Layer: watx.PublishEpoch.FirstLayer(), @@ -249,23 +245,20 @@ func (h *HandlerV1) syntacticallyValidateDeps( }, }, } - encodedProof := codec.MustEncode(proof) - if err := identities.SetMalicious(h.cdb, watx.SmesherID, encodedProof, time.Now()); err != nil { - return nil, nil, fmt.Errorf("adding malfeasance proof: %w", err) + if err := h.malPublisher.PublishProof(ctx, watx.SmesherID, proof); err != nil { + return nil, fmt.Errorf("publishing malfeasance proof: %w", err) } - h.cdb.CacheMalfeasanceProof(watx.SmesherID, encodedProof) - h.tortoise.OnMalfeasance(watx.SmesherID) - return nil, proof, nil + return nil, errMaliciousATX } if err != nil { - return nil, nil, fmt.Errorf("validating nipost: %w", err) + return nil, fmt.Errorf("validating nipost: %w", err) } var baseTickHeight uint64 if watx.PositioningATXID != h.goldenATXID { posAtx, err := h.cdb.GetAtx(watx.PositioningATXID) if err != nil { - return nil, nil, fmt.Errorf("failed to get positioning atx %s: %w", watx.PositioningATXID, err) + return nil, fmt.Errorf("failed to get positioning atx %s: %w", watx.PositioningATXID, err) } baseTickHeight = posAtx.TickHeight() } @@ -281,10 +274,10 @@ func (h *HandlerV1) syntacticallyValidateDeps( atx.TickCount = leaves / h.tickSize hi, weight := bits.Mul64(uint64(atx.NumUnits), atx.TickCount) if hi != 0 { - return nil, nil, errors.New("atx weight would overflow uint64") + return nil, errors.New("atx weight would overflow uint64") } atx.Weight = weight - return atx, nil, nil + return atx, nil } func (h *HandlerV1) validateNonInitialAtx( @@ -355,6 +348,12 @@ func (h *HandlerV1) checkDoublePublish( return nil, fmt.Errorf("%s already published an ATX in epoch %d", atx.SmesherID.ShortString(), atx.PublishEpoch) } + h.logger.Debug("smesher produced more than one atx in the same epoch", + log.ZContext(ctx), + zap.Stringer("smesher", atx.SmesherID), + zap.Stringer("previous", prev), + zap.Stringer("current", atx.ID()), + ) prevSignature, err := atxSignature(ctx, tx, prev) if err != nil { return nil, fmt.Errorf("extracting signature for malfeasance proof: %w", err) @@ -377,25 +376,13 @@ func (h *HandlerV1) checkDoublePublish( Signature: atx.Signature, }}, } - proof := &mwire.MalfeasanceProof{ + return &mwire.MalfeasanceProof{ Layer: atx.PublishEpoch.FirstLayer(), Proof: mwire.Proof{ Type: mwire.MultipleATXs, Data: &atxProof, }, - } - if err := identities.SetMalicious(tx, atx.SmesherID, codec.MustEncode(proof), time.Now()); err != nil { - return nil, fmt.Errorf("add malfeasance proof: %w", err) - } - - h.logger.Debug("smesher produced more than one atx in the same epoch", - log.ZContext(ctx), - zap.Stringer("smesher", atx.SmesherID), - zap.Stringer("previous", prev), - zap.Stringer("current", atx.ID()), - ) - - return proof, nil + }, nil } // checkWrongPrevAtx verifies if the previous ATX referenced in the ATX is correct. @@ -425,6 +412,12 @@ func (h *HandlerV1) checkWrongPrevAtx( return nil, fmt.Errorf("%s referenced incorrect previous ATX", atx.SmesherID.ShortString()) } + h.logger.Debug("smesher referenced the wrong previous in published ATX", + log.ZContext(ctx), + zap.Stringer("smesher", atx.SmesherID), + log.ZShortStringer("actual", atx.PrevATXID), + log.ZShortStringer("expected", expectedPrevID), + ) atx2ID, err := atxs.AtxWithPrevious(tx, atx.PrevATXID, atx.SmesherID) switch { case errors.Is(err, sql.ErrNotFound): @@ -454,7 +447,7 @@ func (h *HandlerV1) checkWrongPrevAtx( return nil, fmt.Errorf("decoding previous atx: %w", err) } - proof := &mwire.MalfeasanceProof{ + return &mwire.MalfeasanceProof{ Layer: atx.PublishEpoch.FirstLayer(), Proof: mwire.Proof{ Type: mwire.InvalidPrevATX, @@ -463,19 +456,7 @@ func (h *HandlerV1) checkWrongPrevAtx( Atx2: watx2, }, }, - } - - if err := identities.SetMalicious(tx, atx.SmesherID, codec.MustEncode(proof), time.Now()); err != nil { - return nil, fmt.Errorf("add malfeasance proof: %w", err) - } - - h.logger.Debug("smesher referenced the wrong previous in published ATX", - log.ZContext(ctx), - zap.Stringer("smesher", atx.SmesherID), - log.ZShortStringer("actual", atx.PrevATXID), - log.ZShortStringer("expected", expectedPrevID), - ) - return proof, nil + }, nil } func (h *HandlerV1) checkMalicious( @@ -491,11 +472,7 @@ func (h *HandlerV1) checkMalicious( } // storeAtx stores an ATX and notifies subscribers of the ATXID. -func (h *HandlerV1) storeAtx( - ctx context.Context, - atx *types.ActivationTx, - watx *wire.ActivationTxV1, -) (*mwire.MalfeasanceProof, error) { +func (h *HandlerV1) storeAtx(ctx context.Context, atx *types.ActivationTx, watx *wire.ActivationTxV1) error { var ( proof *mwire.MalfeasanceProof malicious bool @@ -524,13 +501,14 @@ func (h *HandlerV1) storeAtx( return nil }); err != nil { - return nil, fmt.Errorf("store atx: %w", err) + return fmt.Errorf("store atx: %w", err) } atxs.AtxAdded(h.cdb, atx) if proof != nil { - h.cdb.CacheMalfeasanceProof(atx.SmesherID, codec.MustEncode(proof)) - h.tortoise.OnMalfeasance(atx.SmesherID) + if err := h.malPublisher.PublishProof(ctx, atx.SmesherID, proof); err != nil { + return fmt.Errorf("publishing malfeasance proof: %w", err) + } } added := h.cacheAtx(ctx, atx, malicious || proof != nil) @@ -543,7 +521,7 @@ func (h *HandlerV1) storeAtx( zap.Stringer("atx_id", atx.ID()), zap.Uint32("epoch_id", atx.PublishEpoch.Uint32()), ) - return proof, nil + return nil } func (h *HandlerV1) processATX( @@ -551,14 +529,14 @@ func (h *HandlerV1) processATX( peer p2p.Peer, watx *wire.ActivationTxV1, received time.Time, -) (*mwire.MalfeasanceProof, error) { +) error { if !h.edVerifier.Verify(signing.ATX, watx.SmesherID, watx.SignedBytes(), watx.Signature) { - return nil, fmt.Errorf("%w: invalid atx signature: %w", pubsub.ErrValidationReject, errMalformedData) + return fmt.Errorf("%w: invalid atx signature: %w", pubsub.ErrValidationReject, errMalformedData) } existing, _ := h.cdb.GetAtx(watx.ID()) if existing != nil { - return nil, fmt.Errorf("%w: %s", errKnownAtx, watx.ID()) + return fmt.Errorf("%w: %s", errKnownAtx, watx.ID()) } h.logger.Debug("processing atx", @@ -570,26 +548,25 @@ func (h *HandlerV1) processATX( err := h.syntacticallyValidate(ctx, watx) if err != nil { - return nil, fmt.Errorf("%w: validating atx %s: %w", pubsub.ErrValidationReject, watx.ID(), err) + return fmt.Errorf("%w: validating atx %s: %w", pubsub.ErrValidationReject, watx.ID(), err) } poetRef, atxIDs := collectAtxDeps(h.goldenATXID, watx) h.registerHashes(peer, poetRef, atxIDs) if err := h.fetchReferences(ctx, poetRef, atxIDs); err != nil { - return nil, fmt.Errorf("fetching references for atx %s: %w", watx.ID(), err) + return fmt.Errorf("fetching references for atx %s: %w", watx.ID(), err) } - atx, proof, err := h.syntacticallyValidateDeps(ctx, watx, received) - if err != nil { - return nil, fmt.Errorf("%w: validating atx %s (deps): %w", pubsub.ErrValidationReject, watx.ID(), err) - } - if proof != nil { - return proof, nil + atx, err := h.syntacticallyValidateDeps(ctx, watx, received) + switch { + case errors.Is(err, errMaliciousATX): + return nil + case err != nil: + return fmt.Errorf("%w: validating atx %s (deps): %w", pubsub.ErrValidationReject, watx.ID(), err) } - proof, err = h.storeAtx(ctx, atx, watx) - if err != nil { - return nil, fmt.Errorf("cannot store atx %s: %w", atx.ShortString(), err) + if err := h.storeAtx(ctx, atx, watx); err != nil { + return fmt.Errorf("cannot store atx %s: %w", atx.ShortString(), err) } if err := events.ReportNewActivation(atx); err != nil { @@ -602,9 +579,8 @@ func (h *HandlerV1) processATX( h.logger.Debug("new atx", log.ZContext(ctx), zap.Inline(atx), - zap.Bool("malicious", proof != nil), ) - return proof, err + return err } // registerHashes registers that the given peer should be asked for diff --git a/activation/handler_v1_test.go b/activation/handler_v1_test.go index 01ce917af0b..27f20eb34e0 100644 --- a/activation/handler_v1_test.go +++ b/activation/handler_v1_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -44,14 +45,15 @@ func newV1TestHandler(tb testing.TB, goldenATXID types.ATXID) *v1TestHandler { cdb: cdb, atxsdata: atxsdata.New(), edVerifier: signing.NewEdVerifier(), - clock: mocks.mclock, + clock: mocks.mClock, tickSize: 1, goldenATXID: goldenATXID, nipostValidator: mocks.mValidator, logger: lg, fetcher: mocks.mockFetch, - beacon: mocks.mbeacon, - tortoise: mocks.mtortoise, + beacon: mocks.mBeacon, + tortoise: mocks.mTortoise, + malPublisher: mocks.mLegacyMalPublish, signers: make(map[types.NodeID]*signing.EdSigner), }, handlerMocks: mocks, @@ -71,8 +73,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { prevAtx.NumUnits = 100 prevAtx.Sign(sig) atxHdlr.expectAtxV1(prevAtx, sig.NodeID()) - _, err := atxHdlr.processATX(context.Background(), "", prevAtx, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.processATX(context.Background(), p2p.NoPeer, prevAtx, time.Now())) otherSig, err := signing.NewEdSigner() require.NoError(t, err) @@ -80,8 +81,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { posAtx := newInitialATXv1(t, goldenATXID) posAtx.Sign(otherSig) atxHdlr.expectAtxV1(posAtx, otherSig.NodeID()) - _, err = atxHdlr.processATX(context.Background(), "", posAtx, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.processATX(context.Background(), p2p.NoPeer, posAtx, time.Now())) return atxHdlr, prevAtx, posAtx } @@ -93,7 +93,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.PositioningATXID = posAtx.ID() watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). @@ -103,7 +103,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, gomock.Any()) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -111,7 +111,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("valid atx with new VRF nonce", func(t *testing.T) { @@ -123,7 +122,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.VRFNonce = &newNonce watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). @@ -135,7 +134,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { VRFNonce(gomock.Any(), goldenATXID, newNonce, gomock.Any(), watx.NumUnits) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -143,7 +142,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("valid atx with decreasing num units", func(t *testing.T) { @@ -154,7 +152,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.NumUnits = prevAtx.NumUnits - 10 watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), gomock.Any(), goldenATXID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -163,7 +161,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -171,7 +169,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx with increasing num units, no new VRF, old valid", func(t *testing.T) { @@ -182,7 +179,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.NumUnits = prevAtx.NumUnits + 10 watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -192,7 +189,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().VRFNonce(gomock.Any(), goldenATXID, *prevAtx.VRFNonce, gomock.Any(), watx.NumUnits) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -200,7 +197,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, prevAtx.NumUnits, atx.NumUnits) require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx with increasing num units, no new VRF, old invalid for new size", func(t *testing.T) { @@ -211,7 +207,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.NumUnits = prevAtx.NumUnits + 10 watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), gomock.Any()) @@ -219,9 +215,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { VRFNonce(gomock.Any(), goldenATXID, *prevAtx.VRFNonce, gomock.Any(), watx.NumUnits). Return(errors.New("invalid VRF")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.ErrorContains(t, err, "invalid VRF") - require.Nil(t, proof) }) t.Run("valid initial atx", func(t *testing.T) { @@ -233,7 +228,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx.CommitmentATXID = &ctxID watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), gomock.Any(), ctxID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()) atxHdlr.mValidator.EXPECT().VRFNonce(sig.NodeID(), ctxID, *watx.VRFNonce, gomock.Any(), watx.NumUnits) @@ -246,7 +241,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() - atx, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + atx, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.NoError(t, err) require.Equal(t, types.Valid, atx.Validity()) require.Equal(t, received, atx.Received()) @@ -254,7 +249,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, watx.NumUnits, atx.NumUnits) require.Equal(t, uint64(777)/atxHdlr.tickSize, atx.TickCount) require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) - require.Nil(t, proof) }) t.Run("atx targeting wrong publish epoch", func(t *testing.T) { @@ -264,7 +258,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx := newChainedActivationTxV1(t, prevAtx, posAtx.ID()) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return((atx.PublishEpoch - 2).FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return((atx.PublishEpoch - 2).FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "atx publish epoch is too far in the future") }) @@ -276,16 +270,15 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevAtx, posAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT(). NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID). Return(errors.New("nipost error")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "nipost error") - require.Nil(t, proof) }) t.Run("failing positioning atx validation", func(t *testing.T) { @@ -295,7 +288,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevAtx, posAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -303,9 +296,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch). Return(errors.New("bad positioning atx")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "bad positioning atx") - require.Nil(t, proof) }) t.Run("bad initial nipost challenge", func(t *testing.T) { @@ -316,7 +308,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newInitialATXv1(t, cATX) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). Post(gomock.Any(), sig.NodeID(), cATX, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()) atxHdlr.mValidator.EXPECT().VRFNonce(sig.NodeID(), cATX, *watx.VRFNonce, gomock.Any(), watx.NumUnits) @@ -326,9 +318,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), goldenATXID). Return(errors.New("bad initial nipost")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "bad initial nipost") - require.Nil(t, proof) }) t.Run("bad NIPoST", func(t *testing.T) { @@ -338,7 +329,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevATX, postAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -347,9 +338,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, errors.New("bad nipost")) received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, "validating nipost: bad nipost") - require.Nil(t, proof) }) t.Run("invalid NIPoST", func(t *testing.T) { @@ -359,7 +349,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { watx := newChainedActivationTxV1(t, prevATX, postAtx.ID()) watx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -367,12 +357,27 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(watx.SmesherID) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), watx.SmesherID, gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPostIndex, mp.Proof.Type) + + postVerifier := NewMockPostVerifier(atxHdlr.ctrl) + postVerifier.EXPECT(). + Verify(context.Background(), (*shared.Proof)(watx.NIPost.Post), gomock.Any(), gomock.Any()). + Return(&verifying.ErrInvalidIndex{Index: 2}) + + mh := NewInvalidPostIndexHandler(atxHdlr.cdb, atxHdlr.edVerifier, postVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.InvalidPostIndex, proof.Proof.Type) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + require.ErrorIs(t, err, errMaliciousATX) }) t.Run("invalid NIPoST of known malfeasant", func(t *testing.T) { @@ -384,7 +389,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.NoError(t, identities.SetMalicious(atxHdlr.cdb, watx.SmesherID, []byte("proof"), time.Now())) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(context.Background(), watx)) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) @@ -392,10 +397,10 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT(). NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()). Return(0, &verifying.ErrInvalidIndex{Index: 2}) + received := time.Now() - _, proof, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) + _, err := atxHdlr.syntacticallyValidateDeps(context.Background(), watx, received) require.EqualError(t, err, fmt.Sprintf("smesher %s is known malfeasant", watx.SmesherID.ShortString())) - require.Nil(t, proof) }) t.Run("missing NodeID in initial atx", func(t *testing.T) { @@ -406,7 +411,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.Signature = sig.Sign(signing.ATX, atx.SignedBytes()) atx.SmesherID = sig.NodeID() - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "node id is missing") }) @@ -419,7 +424,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.VRFNonce = nil atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "vrf nonce is missing") }) @@ -431,7 +436,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). VRFNonce(atx.SmesherID, *atx.CommitmentATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits). Return(errors.New("invalid VRF nonce")) @@ -447,7 +452,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.PrevATXID = types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "no prev atx declared, but initial post is not included") }) @@ -460,7 +465,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.CommitmentATXID = nil atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "no prev atx declared, but commitment atx is missing") }) @@ -473,7 +478,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.CommitmentATXID = &types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "empty commitment atx") }) @@ -486,7 +491,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.Sequence = 1 atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "no prev atx declared, but sequence number not zero") }) @@ -498,7 +503,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx := newInitialATXv1(t, goldenATXID) atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) atxHdlr.mValidator.EXPECT(). VRFNonce(atx.SmesherID, *atx.CommitmentATXID, *atx.VRFNonce, gomock.Any(), atx.NumUnits) atxHdlr.mValidator.EXPECT(). @@ -516,7 +521,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.PositioningATXID = types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "empty positioning atx") }) @@ -529,7 +534,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.PrevATXID = prevAtx.ID() atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "prev atx declared, but initial post is included") }) @@ -542,7 +547,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.NodeID = &types.NodeID{1, 2, 3} atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "prev atx declared, but node id is included") }) @@ -555,7 +560,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atx.CommitmentATXID = &types.EmptyATXID atx.Sign(sig) - atxHdlr.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + atxHdlr.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) err := atxHdlr.syntacticallyValidate(context.Background(), atx) require.EqualError(t, err, "prev atx declared, but commitment atx is included") }) @@ -574,13 +579,11 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx.Sign(sig) atx := toAtx(t, watx) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) atxFromDb, err := atxs.Get(atxHdlr.cdb, atx.ID()) require.NoError(t, err) @@ -595,21 +598,17 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx.Sign(sig) atx := toAtx(t, watx) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) // Note: tortoise is not informed about the same ATX again - proof, err = atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) }) t.Run("stores ATX of malicious identity", func(t *testing.T) { @@ -623,13 +622,11 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx.Sign(sig) atx := toAtx(t, watx) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx, watx) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx.PublishEpoch+1, watx.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx, watx)) atxFromDb, err := atxs.Get(atxHdlr.cdb, atx.ID()) require.NoError(t, err) @@ -644,37 +641,34 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx0.Sign(sig) atx0 := toAtx(t, watx0) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx0.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx0, watx0) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx0, watx0)) watx1 := newInitialATXv1(t, goldenATXID) watx1.Coinbase = types.GenerateAddress([]byte("aaaa")) watx1.Sign(sig) atx1 := toAtx(t, watx1) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx1.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.MultipleATXs, proof.Proof.Type) - - mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) - nodeID, err := mh.Validate(context.Background(), proof.Proof.Data) - require.NoError(t, err) - require.Equal(t, sig.NodeID(), nodeID) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.True(t, malicious) + atxHdlr.mTortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), sig.NodeID(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.MultipleATXs, mp.Proof.Type) + + mh := NewMalfeasanceHandler(atxHdlr.cdb, atxHdlr.logger, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) }) t.Run("another atx for the same epoch for registered ID doesn't create a malfeasance proof", func(t *testing.T) { @@ -685,29 +679,21 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx0.Sign(sig) atx0 := toAtx(t, watx0) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx0.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), atx0, watx0) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx0.PublishEpoch+1, watx0.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx0, watx0)) watx1 := newInitialATXv1(t, goldenATXID) watx1.Coinbase = types.GenerateAddress([]byte("aaaa")) watx1.Sign(sig) atx1 := toAtx(t, watx1) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) require.ErrorContains(t, - err, + atxHdlr.storeAtx(context.Background(), atx1, watx1), fmt.Sprintf("%s already published an ATX", sig.NodeID().ShortString()), ) - require.Nil(t, proof) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.False(t, malicious) }) t.Run("another atx with the same prevatx is considered malicious", func(t *testing.T) { @@ -717,38 +703,32 @@ func TestHandlerV1_StoreAtx(t *testing.T) { initialATX.Sign(sig) wInitialATX := toAtx(t, initialATX) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == initialATX.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(initialATX.PublishEpoch+1, initialATX.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), wInitialATX, initialATX) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(initialATX.PublishEpoch+1, initialATX.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), wInitialATX, initialATX)) // valid first non-initial ATX watx1 := newChainedActivationTxV1(t, initialATX, goldenATXID) watx1.Sign(sig) atx1 := toAtx(t, watx1) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx1.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) watx2 := newChainedActivationTxV1(t, watx1, goldenATXID) watx2.Sign(sig) atx2 := toAtx(t, watx2) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx2.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx2.PublishEpoch+1, watx2.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx2, watx2) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx2.PublishEpoch+1, watx2.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx2, watx2)) // third non-initial ATX references initial ATX as prevATX watx3 := newChainedActivationTxV1(t, initialATX, goldenATXID) @@ -756,20 +736,24 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx3.Sign(sig) atx3 := toAtx(t, watx3) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx3.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx3.PublishEpoch+1, watx3.ID(), gomock.Any()) - atxHdlr.mtortoise.EXPECT().OnMalfeasance(sig.NodeID()) - proof, err = atxHdlr.storeAtx(context.Background(), atx3, watx3) - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, mwire.InvalidPrevATX, proof.Proof.Type) + atxHdlr.mTortoise.EXPECT().OnAtx(watx3.PublishEpoch+1, watx3.ID(), gomock.Any()) + + atxHdlr.mLegacyMalPublish.EXPECT().PublishProof(context.Background(), sig.NodeID(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ types.NodeID, mp *mwire.MalfeasanceProof) error { + require.Equal(t, mwire.InvalidPrevATX, mp.Proof.Type) + + mh := NewInvalidPrevATXHandler(atxHdlr.cdb, atxHdlr.edVerifier) + nodeID, err := mh.Validate(context.Background(), mp.Proof.Data) + require.NoError(t, err) + require.Equal(t, sig.NodeID(), nodeID) + return nil + }, + ) - mh := NewInvalidPrevATXHandler(atxHdlr.cdb, atxHdlr.edVerifier) - nodeID, err := mh.Validate(context.Background(), proof.Proof.Data) - require.NoError(t, err) - require.Equal(t, sig.NodeID(), nodeID) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx3, watx3)) }) t.Run("another atx with the same prevatx for registered ID doesn't create a malfeasance proof", func(t *testing.T) { @@ -781,26 +765,22 @@ func TestHandlerV1_StoreAtx(t *testing.T) { wInitialATX.Sign(sig) initialAtx := toAtx(t, wInitialATX) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == wInitialATX.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(wInitialATX.PublishEpoch+1, wInitialATX.ID(), gomock.Any()) - proof, err := atxHdlr.storeAtx(context.Background(), initialAtx, wInitialATX) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(wInitialATX.PublishEpoch+1, wInitialATX.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), initialAtx, wInitialATX)) // valid first non-initial ATX watx1 := newChainedActivationTxV1(t, wInitialATX, goldenATXID) watx1.Sign(sig) atx1 := toAtx(t, watx1) - atxHdlr.mbeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { + atxHdlr.mBeacon.EXPECT().OnAtx(gomock.Cond(func(atx *types.ActivationTx) bool { return atx.ID() == watx1.ID() })) - atxHdlr.mtortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) - proof, err = atxHdlr.storeAtx(context.Background(), atx1, watx1) - require.NoError(t, err) - require.Nil(t, proof) + atxHdlr.mTortoise.EXPECT().OnAtx(watx1.PublishEpoch+1, watx1.ID(), gomock.Any()) + require.NoError(t, atxHdlr.storeAtx(context.Background(), atx1, watx1)) // second non-initial ATX references empty as prevATX watx2 := newInitialATXv1(t, goldenATXID) @@ -808,16 +788,10 @@ func TestHandlerV1_StoreAtx(t *testing.T) { watx2.Sign(sig) atx2 := toAtx(t, watx2) - proof, err = atxHdlr.storeAtx(context.Background(), atx2, watx2) require.ErrorContains(t, - err, + atxHdlr.storeAtx(context.Background(), atx2, watx2), fmt.Sprintf("%s referenced incorrect previous ATX", sig.NodeID().ShortString()), ) - require.Nil(t, proof) - - malicious, err := identities.IsMalicious(atxHdlr.cdb, sig.NodeID()) - require.NoError(t, err) - require.False(t, malicious) }) } diff --git a/activation/handler_v2.go b/activation/handler_v2.go index b7386a4dd32..858b9430b08 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -65,7 +65,7 @@ type HandlerV2 struct { tickSize uint64 goldenATXID types.ATXID nipostValidator nipostValidatorV2 - beacon AtxReceiver + beacon atxReceiver tortoise system.Tortoise logger *zap.Logger fetcher system.Fetcher diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index 19e29812337..662b9ed33d7 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -21,6 +21,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/fetch" + "github.com/spacemeshos/go-spacemesh/p2p" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" @@ -58,14 +59,14 @@ func newV2TestHandler(tb testing.TB, golden types.ATXID) *v2TestHandler { cdb: cdb, atxsdata: atxsdata.New(), edVerifier: signing.NewEdVerifier(), - clock: mocks.mclock, + clock: mocks.mClock, tickSize: tickSize, goldenATXID: golden, nipostValidator: mocks.mValidator, logger: lg, fetcher: mocks.mockFetch, - beacon: mocks.mbeacon, - tortoise: mocks.mtortoise, + beacon: mocks.mBeacon, + tortoise: mocks.mTortoise, malPublisher: mocks.mMalPublish, }, tb: tb, @@ -127,15 +128,15 @@ func (h *handlerMocks) expectVerifyNIPoSTs( } func (h *handlerMocks) expectStoreAtxV2(atx *wire.ActivationTxV2) { - h.mbeacon.EXPECT().OnAtx(gomock.Cond(func(a *types.ActivationTx) bool { + h.mBeacon.EXPECT().OnAtx(gomock.Cond(func(a *types.ActivationTx) bool { return a.ID() == atx.ID() })) - h.mtortoise.EXPECT().OnAtx(atx.PublishEpoch+1, atx.ID(), gomock.Any()) + h.mTortoise.EXPECT().OnAtx(atx.PublishEpoch+1, atx.ID(), gomock.Any()) h.mValidator.EXPECT().IsVerifyingFullPost().Return(false) } func (h *handlerMocks) expectInitialAtxV2(atx *wire.ActivationTxV2) { - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) h.mValidator.EXPECT().VRFNonceV2( atx.SmesherID, atx.Initial.CommitmentATX, @@ -158,7 +159,7 @@ func (h *handlerMocks) expectInitialAtxV2(atx *wire.ActivationTxV2) { } func (h *handlerMocks) expectAtxV2(atx *wire.ActivationTxV2) { - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) h.mValidator.EXPECT().VRFNonceV2( atx.SmesherID, gomock.Any(), @@ -175,7 +176,7 @@ func (h *handlerMocks) expectMergedAtxV2( equivocationSet []types.NodeID, poetLeaves []uint64, ) { - h.mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) + h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) h.expectFetchDeps(atx) h.mValidator.EXPECT().VRFNonceV2( atx.SmesherID, @@ -228,7 +229,7 @@ func TestHandlerV2_SyntacticallyValidate(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer().Return(0) + atxHandler.mClock.EXPECT().CurrentLayer().Return(0) err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "atx publish epoch is too far in the future") }) @@ -248,7 +249,7 @@ func TestHandlerV2_SyntacticallyValidate(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "previous atx[0] is the golden ATX") }) @@ -259,7 +260,7 @@ func TestHandlerV2_SyntacticallyValidate(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "previous atx[0] is empty") }) @@ -277,7 +278,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.mValidator.EXPECT().VRFNonceV2( sig.NodeID(), atx.Initial.CommitmentATX, @@ -301,14 +302,14 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx must not have previous atxs") atx.PreviousATXs = []types.ATXID{types.EmptyATXID} atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err = atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx must not have previous atxs") }) @@ -319,7 +320,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx cannot reference a marriage atx") }) @@ -330,7 +331,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "initial atx missing commitment atx") }) @@ -340,7 +341,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.mValidator.EXPECT(). VRFNonceV2( sig.NodeID(), @@ -358,7 +359,7 @@ func TestHandlerV2_SyntacticallyValidate_InitialAtx(t *testing.T) { atx.Sign(sig) atxHandler := newV2TestHandler(t, golden) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.mValidator.EXPECT().VRFNonceV2( sig.NodeID(), atx.Initial.CommitmentATX, @@ -390,7 +391,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx := newSoloATXv2(t, 0, types.RandomATXID(), types.RandomATXID()) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.NoError(t, err) }) @@ -399,7 +400,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.PreviousATXs = append(atx.PreviousATXs, types.RandomATXID()) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx must have one previous atx") }) @@ -409,7 +410,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.NIPosts = append(atx.NIPosts, wire.NIPostV2{}) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx must have one nipost") }) @@ -419,7 +420,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.NIPosts[0].Posts = append(atx.NIPosts[0].Posts, wire.SubPostV2{}) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx must have one post") }) @@ -429,7 +430,7 @@ func TestHandlerV2_SyntacticallyValidate_SoloAtx(t *testing.T) { atx.NIPosts[0].Posts[0].PrevATXIndex = 1 atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err := atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "solo atx post must have prevATXIndex 0") }) @@ -452,7 +453,7 @@ func TestHandlerV2_SyntacticallyValidate_MergedAtx(t *testing.T) { }} atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() err = atxHandler.syntacticallyValidate(context.Background(), atx) require.ErrorContains(t, err, "merged atx cannot have marriages") }) @@ -565,7 +566,7 @@ func TestHandlerV2_ProcessSoloATX(t *testing.T) { atx.NIPosts[0].Posts[0].NumUnits = prev.TotalNumUnits() * 10 atx.VRFNonce = 7779989 atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(postGenesisEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(postGenesisEpoch.FirstLayer()) atxHandler.expectFetchDeps(atx) atxHandler.expectVerifyNIPoST(atx) atxHandler.mValidator.EXPECT().VRFNonceV2( @@ -608,7 +609,7 @@ func TestHandlerV2_ProcessSoloATX(t *testing.T) { atx := newSoloATXv2(t, 0, types.RandomATXID(), types.RandomATXID()) atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer() + atxHandler.mClock.EXPECT().CurrentLayer() atxHandler.expectFetchDeps(atx) err := atxHandler.processATX(context.Background(), peer, atx, time.Now()) require.ErrorContains(t, err, "validating positioning atx") @@ -801,7 +802,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.PreviousATXs = previousATXs merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) atxHandler.expectVerifyNIPoSTs(merged, equivocationSet, []uint64{200}) @@ -836,7 +837,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.PreviousATXs = previousATXs merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) err := atxHandler.processATX(context.Background(), "", merged, time.Now()) require.ErrorContains(t, err, "ID present twice (duplicated marriage index)") require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -866,7 +867,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.PreviousATXs = previousATXs merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) err := atxHandler.processATX(context.Background(), "", merged, time.Now()) require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -930,7 +931,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.MarriageATX = &mATXID merged.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) + atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) err = atxHandler.processATX(context.Background(), "", merged, time.Now()) require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -2043,7 +2044,7 @@ func Test_Marriages(t *testing.T) { } atx.Sign(sig) - atxHandler.mclock.EXPECT().CurrentLayer().AnyTimes() + atxHandler.mClock.EXPECT().CurrentLayer().AnyTimes() err = atxHandler.processATX(context.Background(), "", atx, time.Now()) require.ErrorContains(t, err, "signer must marry itself") require.ErrorIs(t, err, pubsub.ErrValidationReject) @@ -2199,13 +2200,11 @@ func TestContextual_PreviousATX(t *testing.T) { prevATX := newInitialATXv1(t, golden) prevATX.Sign(sig1) atxHdlr.expectAtxV1(prevATX, prevATX.SmesherID) - _, err = atxHdlr.v1.processATX(context.Background(), "", prevATX, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.v1.processATX(context.Background(), p2p.NoPeer, prevATX, time.Now())) atxv1 := newChainedActivationTxV1(t, prevATX, prevATX.ID()) atxv1.Sign(sig1) atxHdlr.expectAtxV1(atxv1, atxv1.SmesherID) - _, err = atxHdlr.v1.processATX(context.Background(), "", atxv1, time.Now()) - require.NoError(t, err) + require.NoError(t, atxHdlr.v1.processATX(context.Background(), p2p.NoPeer, atxv1, time.Now())) soloAtx := newSoloATXv2(t, atxv1.PublishEpoch+1, atxv1.ID(), atxv1.ID()) soloAtx.Sign(sig1) diff --git a/activation/interface.go b/activation/interface.go index 38c8cf13327..ae410e6291e 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/common/types" + mwire "github.com/spacemeshos/go-spacemesh/malfeasance/wire" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -19,7 +20,7 @@ import ( //go:generate mockgen -typed -package=activation -destination=./mocks.go -source=./interface.go -type AtxReceiver interface { +type atxReceiver interface { OnAtx(*types.ActivationTx) } @@ -92,16 +93,20 @@ type syncer interface { RegisterForATXSynced() <-chan struct{} } -// atxMalfeasancePublisher is an interface for publishing malfeasance proofs. -// This interface is used to publish proofs in V2. +// legacyMalfeasancePublisher is an interface for publishing legacy malfeasance proofs. // -// The provider of that interface ensures that only valid proofs are published (invalid ones return an error). -// Proofs against an identity that is managed by the node will also return an error and will not be gossiped. +// It is used int he ATXv1 handler and will be replaced in the future by the atxMalfeasancePublisher, which will +// wrap legacy proofs into the new encoding structure. +type legacyMalfeasancePublisher interface { + PublishProof(ctx context.Context, smesherID types.NodeID, proof *mwire.MalfeasanceProof) error +} + +// atxMalfeasancePublisher is an interface for publishing atx malfeasance proofs. // -// Additionally the publisher will only gossip proofs when the node is in sync, otherwise it will only store them -// and mark the associated identity as malfeasant. +// It encapsulates a specific malfeasance proof into a generic ATX malfeasance proof and publishes it by calling +// the underlying malfeasancePublisher. type atxMalfeasancePublisher interface { - Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error + Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error } type atxProvider interface { diff --git a/activation/mocks.go b/activation/mocks.go index 0ab71a75240..00ba6773208 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -17,6 +17,7 @@ import ( wire "github.com/spacemeshos/go-spacemesh/activation/wire" types "github.com/spacemeshos/go-spacemesh/common/types" + wire0 "github.com/spacemeshos/go-spacemesh/malfeasance/wire" signing "github.com/spacemeshos/go-spacemesh/signing" certifier "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" nipost "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -24,62 +25,62 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockAtxReceiver is a mock of AtxReceiver interface. -type MockAtxReceiver struct { +// MockatxReceiver is a mock of atxReceiver interface. +type MockatxReceiver struct { ctrl *gomock.Controller - recorder *MockAtxReceiverMockRecorder + recorder *MockatxReceiverMockRecorder isgomock struct{} } -// MockAtxReceiverMockRecorder is the mock recorder for MockAtxReceiver. -type MockAtxReceiverMockRecorder struct { - mock *MockAtxReceiver +// MockatxReceiverMockRecorder is the mock recorder for MockatxReceiver. +type MockatxReceiverMockRecorder struct { + mock *MockatxReceiver } -// NewMockAtxReceiver creates a new mock instance. -func NewMockAtxReceiver(ctrl *gomock.Controller) *MockAtxReceiver { - mock := &MockAtxReceiver{ctrl: ctrl} - mock.recorder = &MockAtxReceiverMockRecorder{mock} +// NewMockatxReceiver creates a new mock instance. +func NewMockatxReceiver(ctrl *gomock.Controller) *MockatxReceiver { + mock := &MockatxReceiver{ctrl: ctrl} + mock.recorder = &MockatxReceiverMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAtxReceiver) EXPECT() *MockAtxReceiverMockRecorder { +func (m *MockatxReceiver) EXPECT() *MockatxReceiverMockRecorder { return m.recorder } // OnAtx mocks base method. -func (m *MockAtxReceiver) OnAtx(arg0 *types.ActivationTx) { +func (m *MockatxReceiver) OnAtx(arg0 *types.ActivationTx) { m.ctrl.T.Helper() m.ctrl.Call(m, "OnAtx", arg0) } // OnAtx indicates an expected call of OnAtx. -func (mr *MockAtxReceiverMockRecorder) OnAtx(arg0 any) *MockAtxReceiverOnAtxCall { +func (mr *MockatxReceiverMockRecorder) OnAtx(arg0 any) *MockatxReceiverOnAtxCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAtx", reflect.TypeOf((*MockAtxReceiver)(nil).OnAtx), arg0) - return &MockAtxReceiverOnAtxCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAtx", reflect.TypeOf((*MockatxReceiver)(nil).OnAtx), arg0) + return &MockatxReceiverOnAtxCall{Call: call} } -// MockAtxReceiverOnAtxCall wrap *gomock.Call -type MockAtxReceiverOnAtxCall struct { +// MockatxReceiverOnAtxCall wrap *gomock.Call +type MockatxReceiverOnAtxCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockAtxReceiverOnAtxCall) Return() *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) Return() *MockatxReceiverOnAtxCall { c.Call = c.Call.Return() return c } // Do rewrite *gomock.Call.Do -func (c *MockAtxReceiverOnAtxCall) Do(f func(*types.ActivationTx)) *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) Do(f func(*types.ActivationTx)) *MockatxReceiverOnAtxCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockAtxReceiverOnAtxCall) DoAndReturn(f func(*types.ActivationTx)) *MockAtxReceiverOnAtxCall { +func (c *MockatxReceiverOnAtxCall) DoAndReturn(f func(*types.ActivationTx)) *MockatxReceiverOnAtxCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -509,17 +510,17 @@ func (c *MocknipostValidatorPoetMembershipCall) DoAndReturn(f func(context.Conte } // PositioningAtx mocks base method. -func (m *MocknipostValidator) PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubepoch types.EpochID) error { +func (m *MocknipostValidator) PositioningAtx(id types.ATXID, atxs atxProvider, goldenATXID types.ATXID, pubEpoch types.EpochID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PositioningAtx", id, atxs, goldenATXID, pubepoch) + ret := m.ctrl.Call(m, "PositioningAtx", id, atxs, goldenATXID, pubEpoch) ret0, _ := ret[0].(error) return ret0 } // PositioningAtx indicates an expected call of PositioningAtx. -func (mr *MocknipostValidatorMockRecorder) PositioningAtx(id, atxs, goldenATXID, pubepoch any) *MocknipostValidatorPositioningAtxCall { +func (mr *MocknipostValidatorMockRecorder) PositioningAtx(id, atxs, goldenATXID, pubEpoch any) *MocknipostValidatorPositioningAtxCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PositioningAtx", reflect.TypeOf((*MocknipostValidator)(nil).PositioningAtx), id, atxs, goldenATXID, pubepoch) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PositioningAtx", reflect.TypeOf((*MocknipostValidator)(nil).PositioningAtx), id, atxs, goldenATXID, pubEpoch) return &MocknipostValidatorPositioningAtxCall{Call: call} } @@ -1092,6 +1093,68 @@ func (c *MocksyncerRegisterForATXSyncedCall) DoAndReturn(f func() <-chan struct{ return c } +// MocklegacyMalfeasancePublisher is a mock of legacyMalfeasancePublisher interface. +type MocklegacyMalfeasancePublisher struct { + ctrl *gomock.Controller + recorder *MocklegacyMalfeasancePublisherMockRecorder + isgomock struct{} +} + +// MocklegacyMalfeasancePublisherMockRecorder is the mock recorder for MocklegacyMalfeasancePublisher. +type MocklegacyMalfeasancePublisherMockRecorder struct { + mock *MocklegacyMalfeasancePublisher +} + +// NewMocklegacyMalfeasancePublisher creates a new mock instance. +func NewMocklegacyMalfeasancePublisher(ctrl *gomock.Controller) *MocklegacyMalfeasancePublisher { + mock := &MocklegacyMalfeasancePublisher{ctrl: ctrl} + mock.recorder = &MocklegacyMalfeasancePublisherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MocklegacyMalfeasancePublisher) EXPECT() *MocklegacyMalfeasancePublisherMockRecorder { + return m.recorder +} + +// PublishProof mocks base method. +func (m *MocklegacyMalfeasancePublisher) PublishProof(ctx context.Context, smesherID types.NodeID, proof *wire0.MalfeasanceProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublishProof", ctx, smesherID, proof) + ret0, _ := ret[0].(error) + return ret0 +} + +// PublishProof indicates an expected call of PublishProof. +func (mr *MocklegacyMalfeasancePublisherMockRecorder) PublishProof(ctx, smesherID, proof any) *MocklegacyMalfeasancePublisherPublishProofCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishProof", reflect.TypeOf((*MocklegacyMalfeasancePublisher)(nil).PublishProof), ctx, smesherID, proof) + return &MocklegacyMalfeasancePublisherPublishProofCall{Call: call} +} + +// MocklegacyMalfeasancePublisherPublishProofCall wrap *gomock.Call +type MocklegacyMalfeasancePublisherPublishProofCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocklegacyMalfeasancePublisherPublishProofCall) Return(arg0 error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocklegacyMalfeasancePublisherPublishProofCall) Do(f func(context.Context, types.NodeID, *wire0.MalfeasanceProof) error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocklegacyMalfeasancePublisherPublishProofCall) DoAndReturn(f func(context.Context, types.NodeID, *wire0.MalfeasanceProof) error) *MocklegacyMalfeasancePublisherPublishProofCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockatxMalfeasancePublisher is a mock of atxMalfeasancePublisher interface. type MockatxMalfeasancePublisher struct { ctrl *gomock.Controller @@ -1117,17 +1180,17 @@ func (m *MockatxMalfeasancePublisher) EXPECT() *MockatxMalfeasancePublisherMockR } // Publish mocks base method. -func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error { +func (m *MockatxMalfeasancePublisher) Publish(ctx context.Context, nodeID types.NodeID, proof wire.Proof) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Publish", ctx, id, proof) + ret := m.ctrl.Call(m, "Publish", ctx, nodeID, proof) ret0, _ := ret[0].(error) return ret0 } // Publish indicates an expected call of Publish. -func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, id, proof any) *MockatxMalfeasancePublisherPublishCall { +func (mr *MockatxMalfeasancePublisherMockRecorder) Publish(ctx, nodeID, proof any) *MockatxMalfeasancePublisherPublishCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, id, proof) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockatxMalfeasancePublisher)(nil).Publish), ctx, nodeID, proof) return &MockatxMalfeasancePublisherPublishCall{Call: call} } diff --git a/activation/poetdb_test.go b/activation/poetdb_test.go index 592e37ee557..a6d03d65e52 100644 --- a/activation/poetdb_test.go +++ b/activation/poetdb_test.go @@ -20,7 +20,7 @@ import ( ) var ( - proof *types.PoetProofMessage + poetProofMsg *types.PoetProofMessage createProofOnce sync.Once ) @@ -47,7 +47,7 @@ func getPoetProof(tb testing.TB) types.PoetProofMessage { ) require.NoError(tb, err) - proof = &types.PoetProofMessage{ + poetProofMsg = &types.PoetProofMessage{ PoetProof: types.PoetProof{ MerkleProof: *merkleProof, LeafCount: leaves, @@ -57,7 +57,7 @@ func getPoetProof(tb testing.TB) types.PoetProofMessage { Statement: types.BytesToHash(challenge), } }) - return *proof + return *poetProofMsg } func TestPoetDbHappyFlow(t *testing.T) { diff --git a/beacon/beacon.go b/beacon/beacon.go index 692a92b8dd4..1e41497ff4d 100644 --- a/beacon/beacon.go +++ b/beacon/beacon.go @@ -85,6 +85,7 @@ func New( vrfVerifier vrfVerifier, cdb *datastore.CachedDB, clock layerClock, + syncer system.SyncStateProvider, opts ...Opt, ) *ProtocolDriver { pd := &ProtocolDriver{ @@ -96,6 +97,7 @@ func New( nonceFetcher: cdb, cdb: cdb, clock: clock, + sync: syncer, signers: make(map[types.NodeID]*signing.EdSigner), beacons: make(map[types.EpochID]types.Beacon), ballotsBeacons: make(map[types.EpochID]map[types.Beacon]*beaconWeight), @@ -207,14 +209,6 @@ type ProtocolDriver struct { metricsCollector *metrics.BeaconMetricsCollector } -// SetSyncState updates sync state provider. Must be executed only once. -func (pd *ProtocolDriver) SetSyncState(sync system.SyncStateProvider) { - if pd.sync != nil { - pd.logger.Fatal("sync state provider can be updated only once") - } - pd.sync = sync -} - // Start starts listening for layers and outputs. func (pd *ProtocolDriver) Start(ctx context.Context) { pd.startOnce.Do(func() { diff --git a/beacon/beacon_test.go b/beacon/beacon_test.go index 220ba932290..76ca87f4620 100644 --- a/beacon/beacon_test.go +++ b/beacon/beacon_test.go @@ -95,12 +95,17 @@ func newTestDriver(tb testing.TB, cfg Config, p pubsub.Publisher, miners int, id tpd.cdb = datastore.NewCachedDB(statesql.InMemoryTest(tb), lg) tb.Cleanup(func() { assert.NoError(tb, tpd.cdb.Close()) }) - tpd.ProtocolDriver = New(p, signing.NewEdVerifier(), tpd.mVerifier, tpd.cdb, tpd.mClock, + tpd.ProtocolDriver = New( + p, + signing.NewEdVerifier(), + tpd.mVerifier, + tpd.cdb, + tpd.mClock, + tpd.mSync, WithConfig(cfg), WithLogger(lg), withWeakCoin(coinValueMock(tb, true)), ) - tpd.ProtocolDriver.SetSyncState(tpd.mSync) for i := 0; i < miners; i++ { edSgn, err := signing.NewEdSigner() require.NoError(tb, err) diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index c3870d35215..4b22c57369b 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -249,10 +249,11 @@ func validateAndPreserveData( lg := zaptest.NewLogger(tb) ctrl := gomock.NewController(tb) mclock := activation.NewMocklayerClock(ctrl) - mfetch := smocks.NewMockFetcher(ctrl) - mvalidator := activation.NewMocknipostValidator(ctrl) - mreceiver := activation.NewMockAtxReceiver(ctrl) - mtrtl := smocks.NewMockTortoise(ctrl) + mFetch := smocks.NewMockFetcher(ctrl) + mValidator := activation.NewMocknipostValidator(ctrl) + mLegacyPublish := activation.NewMocklegacyMalfeasancePublisher(ctrl) + mBeacon := activation.NewMockatxReceiver(ctrl) + mTortoise := smocks.NewMockTortoise(ctrl) cdb := datastore.NewCachedDB(db, lg) tb.Cleanup(func() { assert.NoError(tb, cdb.Close()) }) atxHandler := activation.NewHandler( @@ -261,26 +262,26 @@ func validateAndPreserveData( atxsdata.New(), signing.NewEdVerifier(), mclock, - nil, - mfetch, + mFetch, goldenAtx, - mvalidator, - mreceiver, - mtrtl, + mValidator, + mLegacyPublish, + mBeacon, + mTortoise, lg, ) - mfetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() for _, dep := range deps { var atx wire.ActivationTxV1 require.NoError(tb, codec.Decode(dep.Blob, &atx)) mclock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer()) - mfetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any()) - mfetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) + mFetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any()) + mFetch.EXPECT().GetPoetProof(gomock.Any(), gomock.Any()) if atx.PrevATXID == types.EmptyATXID { - mvalidator.EXPECT(). + mValidator.EXPECT(). InitialNIPostChallengeV1(&atx.NIPostChallengeV1, gomock.Any(), goldenAtx). AnyTimes() - mvalidator.EXPECT().Post( + mValidator.EXPECT().Post( gomock.Any(), atx.SmesherID, *atx.CommitmentATXID, @@ -289,7 +290,7 @@ func validateAndPreserveData( atx.NumUnits, gomock.Any(), ) - mvalidator.EXPECT().VRFNonce( + mValidator.EXPECT().VRFNonce( atx.SmesherID, *atx.CommitmentATXID, *atx.VRFNonce, @@ -297,7 +298,7 @@ func validateAndPreserveData( atx.NumUnits, ) } else { - mvalidator.EXPECT().NIPostChallengeV1( + mValidator.EXPECT().NIPostChallengeV1( &atx.NIPostChallengeV1, gomock.Cond(func(prev *types.ActivationTx) bool { return prev.ID() == atx.PrevATXID @@ -306,13 +307,13 @@ func validateAndPreserveData( ) } - mvalidator.EXPECT().PositioningAtx(atx.PositioningATXID, cdb, goldenAtx, atx.PublishEpoch) - mvalidator.EXPECT(). + mValidator.EXPECT().PositioningAtx(atx.PositioningATXID, cdb, goldenAtx, atx.PublishEpoch) + mValidator.EXPECT(). NIPost(gomock.Any(), atx.SmesherID, gomock.Any(), gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()). Return(uint64(1111111), nil) - mvalidator.EXPECT().IsVerifyingFullPost().AnyTimes().Return(true) - mreceiver.EXPECT().OnAtx(gomock.Any()) - mtrtl.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) + mValidator.EXPECT().IsVerifyingFullPost().AnyTimes().Return(true) + mBeacon.EXPECT().OnAtx(gomock.Any()) + mTortoise.EXPECT().OnAtx(gomock.Any(), gomock.Any(), gomock.Any()) require.NoError(tb, atxHandler.HandleSyncedAtx(context.Background(), atx.ID().Hash32(), "self", dep.Blob)) } } diff --git a/hare3/eligibility/oracle.go b/hare3/eligibility/oracle.go index d72693fa460..799ff1b398e 100644 --- a/hare3/eligibility/oracle.go +++ b/hare3/eligibility/oracle.go @@ -120,6 +120,7 @@ func New( db sql.Executor, atxsdata *atxsdata.Data, vrfVerifier vrfVerifier, + syncer system.SyncStateProvider, layersPerEpoch uint32, opts ...Opt, ) *Oracle { @@ -132,6 +133,7 @@ func New( db: db, atxsdata: atxsdata, vrfVerifier: vrfVerifier, + sync: syncer, activesCache: activesCache, fallback: map[types.EpochID][]types.ATXID{}, cfg: DefaultConfig(), @@ -154,16 +156,7 @@ type VrfMessage struct { Layer types.LayerID } -func (o *Oracle) SetSync(sync system.SyncStateProvider) { - o.mu.Lock() - defer o.mu.Unlock() - o.sync = sync -} - func (o *Oracle) resetCacheOnSynced(ctx context.Context) { - if o.sync == nil { - return - } synced := o.synced o.synced = o.sync.IsSynced(ctx) if !synced && o.synced { diff --git a/hare3/eligibility/oracle_test.go b/hare3/eligibility/oracle_test.go index fac51a5b6b7..eed72d63b9e 100644 --- a/hare3/eligibility/oracle_test.go +++ b/hare3/eligibility/oracle_test.go @@ -51,6 +51,7 @@ type testOracle struct { atxsdata *atxsdata.Data mBeacon *mocks.MockBeaconGetter mVerifier *MockvrfVerifier + mSyncer *mocks.MockSyncStateProvider } func defaultOracle(tb testing.TB) *testOracle { @@ -60,13 +61,15 @@ func defaultOracle(tb testing.TB) *testOracle { ctrl := gomock.NewController(tb) mBeacon := mocks.NewMockBeaconGetter(ctrl) mVerifier := NewMockvrfVerifier(ctrl) + mSyncer := mocks.NewMockSyncStateProvider(ctrl) - to := &testOracle{ + return &testOracle{ Oracle: New( mBeacon, db, atxsdata, mVerifier, + mSyncer, defLayersPerEpoch, WithConfig(Config{ConfidenceParam: confidenceParam}), WithLogger(zaptest.NewLogger(tb)), @@ -74,10 +77,10 @@ func defaultOracle(tb testing.TB) *testOracle { tb: tb, mBeacon: mBeacon, mVerifier: mVerifier, + mSyncer: mSyncer, db: db, atxsdata: atxsdata, } - return to } func (t *testOracle) createBallots( @@ -185,6 +188,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active set", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -193,6 +197,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("miner not active", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() o.createLayerData(lid.Sub(defLayersPerEpoch), 11) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) @@ -206,6 +211,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) errUnknown := errors.New("unknown") o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.EmptyBeacon, errUnknown).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.ErrorIs(t, err, errUnknown) @@ -218,6 +224,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.RandomBeacon(), nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(false).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -227,6 +234,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active with fallback", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) lid := types.EpochID(5).FirstLayer().Add(o.cfg.ConfidenceParam) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -236,6 +244,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createActiveSet(types.EpochID(4).FirstLayer(), activeSet) o.UpdateActiveSet(5, activeSet) o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.RandomBeacon(), nil) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) _, err = o.CalcEligibility(context.Background(), lid, 1, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -267,6 +276,7 @@ func TestCalcEligibility(t *testing.T) { o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) res, err := o.CalcEligibility(context.Background(), lid, 1, 10, miners[0], vrfSig) require.NoError(t, err, vrf) require.Equal(t, exp, res, vrf) @@ -303,6 +313,7 @@ func TestCalcEligibilityWithSpaceUnit(t *testing.T) { sig := types.RandomVrfSignature() o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(2) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(4) res, err := o.CalcEligibility(context.Background(), lid, 1, committeeSize, nodeID, sig) require.NoError(t, err) @@ -363,6 +374,7 @@ func Test_VrfSignVerify(t *testing.T) { first := types.EpochID(5).FirstLayer() prevEpoch := lid.GetEpoch() - 1 o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.Beacon{1, 0, 0, 0}, nil).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() numMiners := 2 activeSet := types.RandomActiveSet(numMiners) @@ -437,6 +449,7 @@ func TestOracle_IsIdentityActive(t *testing.T) { o := defaultOracle(t) layer := types.LayerID(defLayersPerEpoch * 4) numMiners := 2 + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() miners := o.createLayerData(layer.Sub(defLayersPerEpoch), numMiners) for _, nodeID := range miners { v, err := o.IsIdentityActiveOnConsensusView(context.Background(), nodeID, layer) @@ -510,6 +523,7 @@ func TestActiveSet(t *testing.T) { o := defaultOracle(t) targetEpoch := types.EpochID(5) layer := targetEpoch.FirstLayer().Add(o.cfg.ConfidenceParam) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() o.createLayerData(targetEpoch.FirstLayer(), numMiners) aset, err := o.actives(context.Background(), layer) @@ -536,6 +550,7 @@ func TestActives(t *testing.T) { numMiners := 5 t.Run("genesis bootstrap", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) first := types.GetEffectiveGenesis().Add(1) bootstrap := types.RandomActiveSet(numMiners) o.createActiveSet(types.EpochID(1).FirstLayer(), bootstrap) @@ -559,6 +574,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() o.createLayerData(layer, numMiners) @@ -587,6 +603,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() end := layer.Add(o.cfg.ConfidenceParam) o.createLayerData(layer, numMiners) @@ -612,6 +629,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() old := types.GetEffectiveGenesis() types.SetEffectiveGenesis(layer.Uint32() - 1) @@ -660,6 +678,7 @@ func TestActives_ConcurrentCalls(t *testing.T) { mc.EXPECT().Add(layer.GetEpoch()-1, gomock.Any()) o.activesCache = mc + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(102) var wg sync.WaitGroup wg.Add(102) runFn := func() { @@ -669,7 +688,7 @@ func TestActives_ConcurrentCalls(t *testing.T) { } // outstanding probability for concurrent access to calc active set size - for i := 0; i < 100; i++ { + for range 100 { go runFn() } @@ -912,7 +931,8 @@ func TestActiveSetMatrix(t *testing.T) { } else { oracle.mBeacon.EXPECT().GetBeacon(target).Return(types.EmptyBeacon, sql.ErrNotFound) } - rst, err := oracle.ActiveSet(context.TODO(), target) + oracle.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) + rst, err := oracle.ActiveSet(context.Background(), target) switch typed := tc.expect.(type) { case []types.ATXID: @@ -930,29 +950,26 @@ func TestActiveSetMatrix(t *testing.T) { func TestResetCache(t *testing.T) { oracle := defaultOracle(t) - ctrl := gomock.NewController(t) prev := oracle.activesCache prev.Add(1, nil) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync := mocks.NewMockSyncStateProvider(ctrl) - oracle.SetSync(sync) - - sync.EXPECT().IsSynced(gomock.Any()).Return(false) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.NotEqual(t, prev, oracle.activesCache) prev = oracle.activesCache prev.Add(1, nil) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) } diff --git a/hare3/hare_test.go b/hare3/hare_test.go index 6feae4678b0..2cb4045d562 100644 --- a/hare3/hare_test.go +++ b/hare3/hare_test.go @@ -197,6 +197,7 @@ func (n *node) withOracle() *node { n.db, n.atxsdata, signing.NewVRFVerifier(), + n.msyncer, layersPerEpoch, ) return n diff --git a/hare4/eligibility/oracle.go b/hare4/eligibility/oracle.go index 67b2de7afaa..38b61ffe6a0 100644 --- a/hare4/eligibility/oracle.go +++ b/hare4/eligibility/oracle.go @@ -120,6 +120,7 @@ func New( db sql.Executor, atxsdata *atxsdata.Data, vrfVerifier vrfVerifier, + syncer system.SyncStateProvider, layersPerEpoch uint32, opts ...Opt, ) *Oracle { @@ -132,6 +133,7 @@ func New( db: db, atxsdata: atxsdata, vrfVerifier: vrfVerifier, + sync: syncer, activesCache: activesCache, fallback: map[types.EpochID][]types.ATXID{}, cfg: DefaultConfig(), @@ -154,16 +156,7 @@ type VrfMessage struct { Layer types.LayerID } -func (o *Oracle) SetSync(sync system.SyncStateProvider) { - o.mu.Lock() - defer o.mu.Unlock() - o.sync = sync -} - func (o *Oracle) resetCacheOnSynced(ctx context.Context) { - if o.sync == nil { - return - } synced := o.synced o.synced = o.sync.IsSynced(ctx) if !synced && o.synced { diff --git a/hare4/eligibility/oracle_test.go b/hare4/eligibility/oracle_test.go index fac51a5b6b7..32e32758e3d 100644 --- a/hare4/eligibility/oracle_test.go +++ b/hare4/eligibility/oracle_test.go @@ -51,6 +51,7 @@ type testOracle struct { atxsdata *atxsdata.Data mBeacon *mocks.MockBeaconGetter mVerifier *MockvrfVerifier + mSyncer *mocks.MockSyncStateProvider } func defaultOracle(tb testing.TB) *testOracle { @@ -60,6 +61,7 @@ func defaultOracle(tb testing.TB) *testOracle { ctrl := gomock.NewController(tb) mBeacon := mocks.NewMockBeaconGetter(ctrl) mVerifier := NewMockvrfVerifier(ctrl) + mSyncer := mocks.NewMockSyncStateProvider(ctrl) to := &testOracle{ Oracle: New( @@ -67,6 +69,7 @@ func defaultOracle(tb testing.TB) *testOracle { db, atxsdata, mVerifier, + mSyncer, defLayersPerEpoch, WithConfig(Config{ConfidenceParam: confidenceParam}), WithLogger(zaptest.NewLogger(tb)), @@ -74,6 +77,7 @@ func defaultOracle(tb testing.TB) *testOracle { tb: tb, mBeacon: mBeacon, mVerifier: mVerifier, + mSyncer: mSyncer, db: db, atxsdata: atxsdata, } @@ -185,6 +189,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active set", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -193,6 +198,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("miner not active", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) lid := types.EpochID(5).FirstLayer() o.createLayerData(lid.Sub(defLayersPerEpoch), 11) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) @@ -206,6 +212,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) errUnknown := errors.New("unknown") o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.EmptyBeacon, errUnknown).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.ErrorIs(t, err, errUnknown) @@ -218,6 +225,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createLayerData(layer.Sub(defLayersPerEpoch), 5) o.mBeacon.EXPECT().GetBeacon(layer.GetEpoch()).Return(types.RandomBeacon(), nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(false).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) res, err := o.CalcEligibility(context.Background(), layer, 0, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -227,6 +235,7 @@ func TestCalcEligibility(t *testing.T) { t.Run("empty active with fallback", func(t *testing.T) { o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) lid := types.EpochID(5).FirstLayer().Add(o.cfg.ConfidenceParam) res, err := o.CalcEligibility(context.Background(), lid, 1, 1, nid, types.EmptyVrfSignature) require.ErrorIs(t, err, errEmptyActiveSet) @@ -236,6 +245,7 @@ func TestCalcEligibility(t *testing.T) { miners := o.createActiveSet(types.EpochID(4).FirstLayer(), activeSet) o.UpdateActiveSet(5, activeSet) o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.RandomBeacon(), nil) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) _, err = o.CalcEligibility(context.Background(), lid, 1, 1, miners[0], types.EmptyVrfSignature) require.NoError(t, err) @@ -267,6 +277,7 @@ func TestCalcEligibility(t *testing.T) { o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(1) o.mVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).Times(1) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(2) res, err := o.CalcEligibility(context.Background(), lid, 1, 10, miners[0], vrfSig) require.NoError(t, err, vrf) require.Equal(t, exp, res, vrf) @@ -303,6 +314,7 @@ func TestCalcEligibilityWithSpaceUnit(t *testing.T) { sig := types.RandomVrfSignature() o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(beacon, nil).Times(2) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(4) res, err := o.CalcEligibility(context.Background(), lid, 1, committeeSize, nodeID, sig) require.NoError(t, err) @@ -363,6 +375,7 @@ func Test_VrfSignVerify(t *testing.T) { first := types.EpochID(5).FirstLayer() prevEpoch := lid.GetEpoch() - 1 o.mBeacon.EXPECT().GetBeacon(lid.GetEpoch()).Return(types.Beacon{1, 0, 0, 0}, nil).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() numMiners := 2 activeSet := types.RandomActiveSet(numMiners) @@ -437,6 +450,7 @@ func TestOracle_IsIdentityActive(t *testing.T) { o := defaultOracle(t) layer := types.LayerID(defLayersPerEpoch * 4) numMiners := 2 + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() miners := o.createLayerData(layer.Sub(defLayersPerEpoch), numMiners) for _, nodeID := range miners { v, err := o.IsIdentityActiveOnConsensusView(context.Background(), nodeID, layer) @@ -510,6 +524,7 @@ func TestActiveSet(t *testing.T) { o := defaultOracle(t) targetEpoch := types.EpochID(5) layer := targetEpoch.FirstLayer().Add(o.cfg.ConfidenceParam) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() o.createLayerData(targetEpoch.FirstLayer(), numMiners) aset, err := o.actives(context.Background(), layer) @@ -536,6 +551,7 @@ func TestActives(t *testing.T) { numMiners := 5 t.Run("genesis bootstrap", func(t *testing.T) { o := defaultOracle(t) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) first := types.GetEffectiveGenesis().Add(1) bootstrap := types.RandomActiveSet(numMiners) o.createActiveSet(types.EpochID(1).FirstLayer(), bootstrap) @@ -559,6 +575,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()) + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() o.createLayerData(layer, numMiners) @@ -587,6 +604,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() end := layer.Add(o.cfg.ConfidenceParam) o.createLayerData(layer, numMiners) @@ -612,6 +630,7 @@ func TestActives(t *testing.T) { numMiners++ o := defaultOracle(t) o.mBeacon.EXPECT().GetBeacon(gomock.Any()).AnyTimes() + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).AnyTimes() layer := types.EpochID(4).FirstLayer() old := types.GetEffectiveGenesis() types.SetEffectiveGenesis(layer.Uint32() - 1) @@ -660,6 +679,7 @@ func TestActives_ConcurrentCalls(t *testing.T) { mc.EXPECT().Add(layer.GetEpoch()-1, gomock.Any()) o.activesCache = mc + o.mSyncer.EXPECT().IsSynced(context.Background()).Return(false).Times(102) var wg sync.WaitGroup wg.Add(102) runFn := func() { @@ -669,7 +689,7 @@ func TestActives_ConcurrentCalls(t *testing.T) { } // outstanding probability for concurrent access to calc active set size - for i := 0; i < 100; i++ { + for range 100 { go runFn() } @@ -912,7 +932,8 @@ func TestActiveSetMatrix(t *testing.T) { } else { oracle.mBeacon.EXPECT().GetBeacon(target).Return(types.EmptyBeacon, sql.ErrNotFound) } - rst, err := oracle.ActiveSet(context.TODO(), target) + oracle.mSyncer.EXPECT().IsSynced(context.Background()).Return(false) + rst, err := oracle.ActiveSet(context.Background(), target) switch typed := tc.expect.(type) { case []types.ATXID: @@ -930,29 +951,26 @@ func TestActiveSetMatrix(t *testing.T) { func TestResetCache(t *testing.T) { oracle := defaultOracle(t) - ctrl := gomock.NewController(t) prev := oracle.activesCache prev.Add(1, nil) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync := mocks.NewMockSyncStateProvider(ctrl) - oracle.SetSync(sync) - - sync.EXPECT().IsSynced(gomock.Any()).Return(false) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(false) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.NotEqual(t, prev, oracle.activesCache) prev = oracle.activesCache prev.Add(1, nil) - sync.EXPECT().IsSynced(gomock.Any()).Return(true) + oracle.mSyncer.EXPECT().IsSynced(gomock.Any()).Return(true) oracle.resetCacheOnSynced(context.Background()) require.Equal(t, prev, oracle.activesCache) } diff --git a/hare4/hare_test.go b/hare4/hare_test.go index bb30d023bfa..cf5bc43e5f5 100644 --- a/hare4/hare_test.go +++ b/hare4/hare_test.go @@ -208,6 +208,7 @@ func (n *node) withOracle() *node { n.db, n.atxsdata, signing.NewVRFVerifier(), + n.msyncer, layersPerEpoch, ) return n diff --git a/malfeasance/handler.go b/malfeasance/handler.go index ded77bd507b..b15eb2aa1ff 100644 --- a/malfeasance/handler.go +++ b/malfeasance/handler.go @@ -55,14 +55,14 @@ func NewHandler( cdb *datastore.CachedDB, lg *zap.Logger, self p2p.Peer, - nodeID []types.NodeID, + nodeIDs []types.NodeID, tortoise tortoise, ) *Handler { return &Handler{ logger: lg, cdb: cdb, self: self, - nodeIDs: nodeID, + nodeIDs: nodeIDs, tortoise: tortoise, handlers: make(map[MalfeasanceType]MalfeasanceHandler), @@ -130,9 +130,9 @@ func (h *Handler) HandleSyncedMalfeasanceProof( // but only log "validation ignored" instead of the error we return h.logger.Warn("malfeasance proof for wrong identity", log.ZContext(ctx), + zap.Stringer("peer", peer), log.ZShortStringer("expected", expHash), log.ZShortStringer("got", nodeID), - zap.Stringer("peer", peer), ) return fmt.Errorf( "%w: malfeasance proof want %s, got %s", diff --git a/malfeasance/interface.go b/malfeasance/interface.go index 3486d5878f5..79883b45a8b 100644 --- a/malfeasance/interface.go +++ b/malfeasance/interface.go @@ -15,6 +15,10 @@ type tortoise interface { OnMalfeasance(types.NodeID) } +type syncer interface { + ListenToATXGossip() bool +} + type MalfeasanceHandler interface { Validate(ctx context.Context, data wire.ProofData) (types.NodeID, error) Info(data wire.ProofData) (map[string]string, error) diff --git a/malfeasance/mocks.go b/malfeasance/mocks.go index 5ab467d2955..18dc4e9ec5a 100644 --- a/malfeasance/mocks.go +++ b/malfeasance/mocks.go @@ -79,6 +79,68 @@ func (c *MocktortoiseOnMalfeasanceCall) DoAndReturn(f func(types.NodeID)) *Mockt return c } +// Mocksyncer is a mock of syncer interface. +type Mocksyncer struct { + ctrl *gomock.Controller + recorder *MocksyncerMockRecorder + isgomock struct{} +} + +// MocksyncerMockRecorder is the mock recorder for Mocksyncer. +type MocksyncerMockRecorder struct { + mock *Mocksyncer +} + +// NewMocksyncer creates a new mock instance. +func NewMocksyncer(ctrl *gomock.Controller) *Mocksyncer { + mock := &Mocksyncer{ctrl: ctrl} + mock.recorder = &MocksyncerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocksyncer) EXPECT() *MocksyncerMockRecorder { + return m.recorder +} + +// ListenToATXGossip mocks base method. +func (m *Mocksyncer) ListenToATXGossip() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListenToATXGossip") + ret0, _ := ret[0].(bool) + return ret0 +} + +// ListenToATXGossip indicates an expected call of ListenToATXGossip. +func (mr *MocksyncerMockRecorder) ListenToATXGossip() *MocksyncerListenToATXGossipCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListenToATXGossip", reflect.TypeOf((*Mocksyncer)(nil).ListenToATXGossip)) + return &MocksyncerListenToATXGossipCall{Call: call} +} + +// MocksyncerListenToATXGossipCall wrap *gomock.Call +type MocksyncerListenToATXGossipCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocksyncerListenToATXGossipCall) Return(arg0 bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocksyncerListenToATXGossipCall) Do(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocksyncerListenToATXGossipCall) DoAndReturn(f func() bool) *MocksyncerListenToATXGossipCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockMalfeasanceHandler is a mock of MalfeasanceHandler interface. type MockMalfeasanceHandler struct { ctrl *gomock.Controller diff --git a/malfeasance/publisher.go b/malfeasance/publisher.go new file mode 100644 index 00000000000..6e6f222c24b --- /dev/null +++ b/malfeasance/publisher.go @@ -0,0 +1,74 @@ +package malfeasance + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/malfeasance/wire" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/sql/identities" +) + +type Publisher struct { + logger *zap.Logger + cdb *datastore.CachedDB + tortoise tortoise + sync syncer + publisher pubsub.Publisher +} + +func NewPublisher( + logger *zap.Logger, + cdb *datastore.CachedDB, + sync syncer, + tortoise tortoise, + publisher pubsub.Publisher, +) *Publisher { + return &Publisher{ + logger: logger, + cdb: cdb, + tortoise: tortoise, + sync: sync, + publisher: publisher, + } +} + +// Publishes a malfeasance proof to the network. +func (p *Publisher) PublishProof(ctx context.Context, smesherID types.NodeID, proof *wire.MalfeasanceProof) error { + malicious, err := identities.IsMalicious(p.cdb, smesherID) + if err != nil { + return fmt.Errorf("check if smesher is malicious: %w", err) + } + if malicious { + p.logger.Debug("smesher is already marked as malicious", zap.String("smesher_id", smesherID.ShortString())) + return nil + } + + if err := identities.SetMalicious(p.cdb, smesherID, codec.MustEncode(proof), time.Now()); err != nil { + return fmt.Errorf("adding malfeasance proof: %w", err) + } + + p.cdb.CacheMalfeasanceProof(smesherID, codec.MustEncode(proof)) + p.tortoise.OnMalfeasance(smesherID) + + // Only gossip the proof if we are synced (to not spam the network with proofs others probably already have). + if !p.sync.ListenToATXGossip() { + p.logger.Debug("not synced, not broadcasting malfeasance proof", + zap.String("smesher_id", smesherID.ShortString()), + ) + return nil + } + gossip := wire.MalfeasanceGossip{ + MalfeasanceProof: *proof, + } + if err := p.publisher.Publish(ctx, pubsub.MalfeasanceProof, codec.MustEncode(&gossip)); err != nil { + return fmt.Errorf("broadcast atx malfeasance proof: %w", err) + } + return nil +} diff --git a/malfeasance/publisher_test.go b/malfeasance/publisher_test.go new file mode 100644 index 00000000000..47efcfa61d6 --- /dev/null +++ b/malfeasance/publisher_test.go @@ -0,0 +1,179 @@ +package malfeasance + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "go.uber.org/zap/zaptest" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/malfeasance/wire" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub" + "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" + "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/identities" + "github.com/spacemeshos/go-spacemesh/sql/statesql" +) + +type testMalPublisher struct { + *Publisher + + mSyncer *Mocksyncer + mTortoise *Mocktortoise + mPublisher *mocks.MockPublisher +} + +func newTestPublisher(tb testing.TB) *testMalPublisher { + logger := zaptest.NewLogger(tb) + + db := statesql.InMemoryTest(tb) + cdb := datastore.NewCachedDB(db, logger) + + ctrl := gomock.NewController(tb) + mSyncer := NewMocksyncer(ctrl) + mTortoise := NewMocktortoise(ctrl) + mPublisher := mocks.NewMockPublisher(ctrl) + + publisher := NewPublisher( + logger, + cdb, + mSyncer, + mTortoise, + mPublisher, + ) + + return &testMalPublisher{ + Publisher: publisher, + + mSyncer: mSyncer, + mTortoise: mTortoise, + mPublisher: mPublisher, + } +} + +func TestMalfeasancePublisher(t *testing.T) { + t.Run("PublishProof when in sync", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + proof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + malPublisher.mTortoise.EXPECT().OnMalfeasance(nodeID) + malPublisher.mSyncer.EXPECT().ListenToATXGossip().Return(true) + malPublisher.mPublisher.EXPECT(). + Publish(context.Background(), pubsub.MalfeasanceProof, gomock.Any()). + DoAndReturn(func(ctx context.Context, s string, b []byte) error { + var gossip wire.MalfeasanceGossip + codec.MustDecode(b, &gossip) + require.Equal(t, *proof, gossip.MalfeasanceProof) + return nil + }) + + err := malPublisher.PublishProof(context.Background(), nodeID, proof) + require.NoError(t, err) + + malicious, err := identities.IsMalicious(malPublisher.cdb, nodeID) + require.NoError(t, err) + require.True(t, malicious) + }) + + t.Run("PublishProof when not in sync", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + proof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + malPublisher.mTortoise.EXPECT().OnMalfeasance(nodeID) + malPublisher.mSyncer.EXPECT().ListenToATXGossip().Return(false) + + err := malPublisher.PublishProof(context.Background(), nodeID, proof) + require.NoError(t, err) + + // proof is only persisted but not published + malicious, err := identities.IsMalicious(malPublisher.cdb, nodeID) + require.NoError(t, err) + require.True(t, malicious) + }) + + t.Run("PublishProof when already marked as malicious", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + existingProof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + err := identities.SetMalicious(malPublisher.cdb, nodeID, codec.MustEncode(existingProof), time.Now()) + require.NoError(t, err) + + proof := &wire.MalfeasanceProof{ + Layer: 11, + Proof: wire.Proof{ + Type: wire.MultipleBallots, + Data: &wire.BallotProof{}, + }, + } + err = malPublisher.PublishProof(context.Background(), nodeID, proof) + require.NoError(t, err) + + // no new malfeasance proof is added + var blob sql.Blob + err = identities.LoadMalfeasanceBlob(context.Background(), malPublisher.cdb, nodeID.Bytes(), &blob) + require.NoError(t, err) + + dbProof := &wire.MalfeasanceProof{} + codec.MustDecode(blob.Bytes, dbProof) + + require.Equal(t, existingProof, dbProof) + }) + + t.Run("PublishProof when error occurs", func(t *testing.T) { + malPublisher := newTestPublisher(t) + + nodeID := types.RandomNodeID() + proof := &wire.MalfeasanceProof{ + Layer: 1, + Proof: wire.Proof{ + Type: wire.MultipleATXs, + Data: &wire.AtxProof{}, + }, + } + + malPublisher.mTortoise.EXPECT().OnMalfeasance(nodeID) + malPublisher.mSyncer.EXPECT().ListenToATXGossip().Return(true) + errPublish := errors.New("publish failed") + malPublisher.mPublisher.EXPECT(). + Publish(context.Background(), pubsub.MalfeasanceProof, gomock.Any()). + Return(errPublish) + + err := malPublisher.PublishProof(context.Background(), nodeID, proof) + require.ErrorIs(t, err, errPublish) + + // malfeasance proof is still added to db + malicious, err := identities.IsMalicious(malPublisher.cdb, nodeID) + require.NoError(t, err) + require.True(t, malicious) + }) +} diff --git a/node/node.go b/node/node.go index 002a127bb86..d79e73f40fa 100644 --- a/node/node.go +++ b/node/node.go @@ -660,20 +660,6 @@ func (app *App) initServices(ctx context.Context) error { signing.WithVerifierPrefix(app.Config.Genesis.GenesisID().Bytes()), ) - vrfVerifier := signing.NewVRFVerifier() - beaconProtocol := beacon.New( - app.host, - app.edVerifier, - vrfVerifier, - app.cachedDB, - app.clock, - beacon.WithConfig(app.Config.Beacon), - beacon.WithLogger(app.addLogger(BeaconLogger, lg).Zap()), - ) - for _, sig := range app.signers { - beaconProtocol.Register(sig) - } - trtlCfg := app.Config.Tortoise trtlCfg.LayerSize = layerSize if trtlCfg.BadBeaconVoteDelayLayers == 0 { @@ -699,14 +685,6 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("can't recover tortoise state: %w", err) } app.log.With().Info("tortoise initialized", log.Duration("duration", time.Since(start))) - app.eg.Go(func() error { - for rst := range beaconProtocol.Results() { - events.EmitBeacon(rst.Epoch, rst.Beacon) - trtl.OnBeacon(rst.Epoch, rst.Beacon) - } - app.log.Debug("beacon results watcher exited") - return nil - }) executor := mesh.NewExecutor( app.db, @@ -754,16 +732,76 @@ func (app *App) initServices(ctx context.Context) error { return blockssync.Sync(ctx, flog.Zap(), msh.MissingBlocks(), fetcher) }) + patrol := layerpatrol.New() + syncerConf := app.Config.Sync + syncerConf.HareDelayLayers = app.Config.Tortoise.Zdist + syncerConf.SyncCertDistance = app.Config.Tortoise.Hdist + syncerConf.Standalone = app.Config.Standalone + + app.syncLogger = app.addLogger(SyncLogger, lg) + syncer := syncer.NewSyncer( + app.cachedDB, + app.clock, + msh, + trtl, + fetcher, + patrol, + app.certifier, + atxsync.New(fetcher, app.db, app.localDB, + atxsync.WithConfig(app.Config.Sync.AtxSync), + atxsync.WithLogger(app.syncLogger.Zap()), + ), + malsync.New(fetcher, app.db, app.localDB, + malsync.WithConfig(app.Config.Sync.MalSync), + malsync.WithLogger(app.syncLogger.Zap()), + malsync.WithPeerErrMetric(syncer.MalPeerError), + ), + syncer.WithConfig(syncerConf), + syncer.WithLogger(app.syncLogger.Zap()), + ) + + vrfVerifier := signing.NewVRFVerifier() + beaconProtocol := beacon.New( + app.host, + app.edVerifier, + vrfVerifier, + app.cachedDB, + app.clock, + syncer, + beacon.WithConfig(app.Config.Beacon), + beacon.WithLogger(app.addLogger(BeaconLogger, lg).Zap()), + ) + for _, sig := range app.signers { + beaconProtocol.Register(sig) + } + app.eg.Go(func() error { + for rst := range beaconProtocol.Results() { + events.EmitBeacon(rst.Epoch, rst.Beacon) + trtl.OnBeacon(rst.Epoch, rst.Beacon) + } + app.log.Debug("beacon results watcher exited") + return nil + }) + + malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() + legacyMalPublisher := malfeasance.NewPublisher( + malfeasanceLogger, + app.cachedDB, + syncer, + trtl, + app.host, + ) + atxHandler := activation.NewHandler( app.host.ID(), app.cachedDB, app.atxsdata, app.edVerifier, app.clock, - app.host, fetcher, goldenATXID, validator, + legacyMalPublisher, beaconProtocol, trtl, app.addLogger(ATXHandlerLogger, lg).Zap(), @@ -775,7 +813,6 @@ func (app *App) initServices(ctx context.Context) error { } // we can't have an epoch offset which is greater/equal than the number of layers in an epoch - if app.Config.HareEligibility.ConfidenceParam >= app.Config.BaseConfig.LayersPerEpoch { return fmt.Errorf( "confidence param should be smaller than layers per epoch. eligibility-confidence-param: %d. "+ @@ -785,7 +822,11 @@ func (app *App) initServices(ctx context.Context) error { ) } - blockHandler := blocks.NewHandler(fetcher, app.db, trtl, msh, + blockHandler := blocks.NewHandler( + fetcher, + app.db, + trtl, + msh, blocks.WithLogger(app.addLogger(BlockHandlerLogger, lg).Zap()), ) @@ -795,16 +836,16 @@ func (app *App) initServices(ctx context.Context) error { app.addLogger(TxHandlerLogger, lg).Zap(), ) - app.hOracle = eligibility.New( + hOracle := eligibility.New( beaconProtocol, app.db, app.atxsdata, vrfVerifier, + syncer, app.Config.LayersPerEpoch, eligibility.WithConfig(app.Config.HareEligibility), eligibility.WithLogger(app.addLogger(HareOracleLogger, lg).Zap()), ) - // TODO: genesisMinerWeight is set to app.Config.SpaceToCommit, because PoET ticks are currently hardcoded to 1 bscfg := app.Config.Bootstrap bscfg.DataDir = app.Config.DataDir() @@ -824,7 +865,7 @@ func (app *App) initServices(ctx context.Context) error { app.Config.Certificate.NumLayersToKeep = app.Config.Tortoise.Zdist * 2 app.certifier = blocks.NewCertifier( app.db, - app.hOracle, + hOracle, app.edVerifier, app.host, app.clock, @@ -837,39 +878,9 @@ func (app *App) initServices(ctx context.Context) error { app.certifier.Register(sig) } - patrol := layerpatrol.New() - syncerConf := app.Config.Sync - syncerConf.HareDelayLayers = app.Config.Tortoise.Zdist - syncerConf.SyncCertDistance = app.Config.Tortoise.Hdist - syncerConf.Standalone = app.Config.Standalone - if app.Config.P2P.MinPeers < app.Config.Sync.MalSync.MinSyncPeers { app.Config.Sync.MalSync.MinSyncPeers = max(1, app.Config.P2P.MinPeers) } - app.syncLogger = app.addLogger(SyncLogger, lg) - newSyncer := syncer.NewSyncer( - app.cachedDB, - app.clock, - msh, - trtl, - fetcher, - patrol, - app.certifier, - atxsync.New(fetcher, app.db, app.localDB, - atxsync.WithConfig(app.Config.Sync.AtxSync), - atxsync.WithLogger(app.syncLogger.Zap()), - ), - malsync.New(fetcher, app.db, app.localDB, - malsync.WithConfig(app.Config.Sync.MalSync), - malsync.WithLogger(app.syncLogger.Zap()), - malsync.WithPeerErrMetric(syncer.MalPeerError), - ), - syncer.WithConfig(syncerConf), - syncer.WithLogger(app.syncLogger.Zap()), - ) - // TODO(dshulyak) this needs to be improved, but dependency graph is a bit complicated - beaconProtocol.SetSyncState(newSyncer) - app.hOracle.SetSync(newSyncer) err = app.Config.HARE3.Validate(time.Duration(app.Config.Tortoise.Zdist) * app.Config.LayerDuration) if err != nil { @@ -887,8 +898,8 @@ func (app *App) initServices(ctx context.Context) error { app.atxsdata, proposalsStore, app.edVerifier, - app.hOracle, - newSyncer, + hOracle, + syncer, patrol, hare3.WithLogger(logger), hare3.WithConfig(app.Config.HARE3), @@ -917,8 +928,8 @@ func (app *App) initServices(ctx context.Context) error { app.atxsdata, proposalsStore, app.edVerifier, - app.hOracle, - newSyncer, + hOracle, + syncer, patrol, app.host, hare4.WithLogger(logger), @@ -998,7 +1009,7 @@ func (app *App) initServices(ctx context.Context) error { app.atxsdata, app.host, trtl, - newSyncer, + syncer, app.conState, miner.WithLayerSize(layerSize), miner.WithLayerPerEpoch(layersPerEpoch), @@ -1019,7 +1030,7 @@ func (app *App) initServices(ctx context.Context) error { app.db, app.atxsdata, goldenATXID, - newSyncer, + syncer, app.validator, activation.PostValidityDelay(app.Config.PostValidDelay), ) @@ -1083,7 +1094,7 @@ func (app *App) initServices(ctx context.Context) error { app.host, nipostBuilder, app.clock, - newSyncer, + syncer, app.addLogger(ATXBuilderLogger, lg).Zap(), activation.WithContext(ctx), activation.WithPoetConfig(app.Config.POET), @@ -1118,7 +1129,6 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("init post service: %w", err) } - malfeasanceLogger := app.addLogger(MalfeasanceLogger, lg).Zap() activationMH := activation.NewMalfeasanceHandler( app.cachedDB, malfeasanceLogger, @@ -1208,14 +1218,14 @@ func (app *App) initServices(ctx context.Context) error { ), ) - syncHandler := func(_ context.Context, _ p2p.Peer, _ []byte) error { - if newSyncer.ListenToGossip() { + checkSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { + if syncer.ListenToGossip() { return nil } return errors.New("not synced for gossip") } - atxSyncHandler := func(_ context.Context, _ p2p.Peer, _ []byte) error { - if newSyncer.ListenToATXGossip() { + checkAtxSynced := func(_ context.Context, _ p2p.Peer, _ []byte) error { + if syncer.ListenToATXGossip() { return nil } return errors.New("not synced for gossip") @@ -1224,55 +1234,56 @@ func (app *App) initServices(ctx context.Context) error { if app.Config.Beacon.RoundsNumber > 0 { app.host.Register( pubsub.BeaconWeakCoinProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleWeakCoinProposal), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleWeakCoinProposal), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconProposalProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleProposal), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleProposal), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconFirstVotesProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleFirstVotes), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleFirstVotes), pubsub.WithValidatorInline(true), ) app.host.Register( pubsub.BeaconFollowingVotesProtocol, - pubsub.ChainGossipHandler(syncHandler, beaconProtocol.HandleFollowingVotes), + pubsub.ChainGossipHandler(checkSynced, beaconProtocol.HandleFollowingVotes), pubsub.WithValidatorInline(true), ) } app.host.Register( pubsub.ProposalProtocol, - pubsub.ChainGossipHandler(syncHandler, proposalListener.HandleProposal), + pubsub.ChainGossipHandler(checkSynced, proposalListener.HandleProposal), ) app.host.Register( pubsub.AtxProtocol, - pubsub.ChainGossipHandler(atxSyncHandler, atxHandler.HandleGossipAtx), + pubsub.ChainGossipHandler(checkAtxSynced, atxHandler.HandleGossipAtx), pubsub.WithValidatorConcurrency(app.Config.P2P.GossipAtxValidationThrottle), ) app.host.Register( pubsub.TxProtocol, - pubsub.ChainGossipHandler(syncHandler, app.txHandler.HandleGossipTransaction), + pubsub.ChainGossipHandler(checkSynced, app.txHandler.HandleGossipTransaction), ) app.host.Register( pubsub.BlockCertify, - pubsub.ChainGossipHandler(syncHandler, app.certifier.HandleCertifyMessage), + pubsub.ChainGossipHandler(checkSynced, app.certifier.HandleCertifyMessage), ) app.host.Register( pubsub.MalfeasanceProof, - pubsub.ChainGossipHandler(atxSyncHandler, app.malfeasanceHandler.HandleMalfeasanceProof), + pubsub.ChainGossipHandler(checkAtxSynced, app.malfeasanceHandler.HandleMalfeasanceProof), ) app.proposalBuilder = proposalBuilder app.mesh = msh - app.syncer = newSyncer + app.syncer = syncer app.atxBuilder = atxBuilder app.atxHandler = atxHandler app.poetDb = poetDb app.fetcher = fetcher app.beaconProtocol = beaconProtocol + app.hOracle = hOracle if !app.Config.TIME.Peersync.Disable { app.ptimesync = peersync.New( app.host, diff --git a/systest/cluster/nodes.go b/systest/cluster/nodes.go index 19848176063..ab193c5a737 100644 --- a/systest/cluster/nodes.go +++ b/systest/cluster/nodes.go @@ -20,10 +20,12 @@ import ( "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" + apiappsv1 "k8s.io/api/apps/v1" apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/watch" appsv1 "k8s.io/client-go/applyconfigurations/apps/v1" corev1 "k8s.io/client-go/applyconfigurations/core/v1" metav1 "k8s.io/client-go/applyconfigurations/meta/v1" @@ -705,6 +707,9 @@ func deployNodes(ctx *testcontext.Context, kind string, from, to int, opts ...De if err := deployNode(ctx, id, key, cfg.image, "local.key", labels, finalFlags); err != nil { return err } + if err := watchDeployment(ctx, id); err != nil { + return err + } clients <- &NodeClient{ session: ctx, Node: Node{ @@ -784,6 +789,9 @@ func deployRemoteNodes( return err } deployNodeSvc(ctx, id) + if err := watchDeployment(ctx, id); err != nil { + return err + } clients <- &NodeClient{ session: ctx, Node: Node{ @@ -959,6 +967,45 @@ func deployNode( return nil } +func watchDeployment(ctx *testcontext.Context, id string) error { + ctx.Log.Debugf("watching deployment %s", id) + // Watch the Deployment + watcher, err := ctx.Client.AppsV1().Deployments(ctx.Namespace).Watch(ctx, apimetav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%s", id), + }) + if err != nil { + return fmt.Errorf("failed to watch deployment: %w", err) + } + defer watcher.Stop() + + // Process events + for event := range watcher.ResultChan() { + switch event.Type { + case watch.Added, watch.Modified: + deployment, ok := event.Object.(*apiappsv1.Deployment) + if !ok { + return fmt.Errorf("watching deployment %s: unexpected object type", id) + } + + // Check deployment status + availableReplicas := deployment.Status.AvailableReplicas + desiredReplicas := *deployment.Spec.Replicas + ctx.Log.Debugf("Deployment %s: %d/%d replicas available\n", id, availableReplicas, desiredReplicas) + + // Exit when the deployment is ready + if availableReplicas == desiredReplicas { + ctx.Log.Debugf("Deployment %s is ready", id) + return nil + } + case watch.Deleted: + return fmt.Errorf("deployment %s was deleted", id) + case watch.Error: + return fmt.Errorf("watching deployment %s: %v", id, event.Object) + } + } + return nil +} + func deployPostService( ctx *testcontext.Context, id string,