Skip to content

Commit

Permalink
Add invalid prev ATX proof (#6310)
Browse files Browse the repository at this point in the history
## Motivation

Closes #6309 by adding a proof for an ATX referencing the same previous ATX as another by the same smesher. I recommend to review this after #6441.
  • Loading branch information
fasmat committed Nov 14, 2024
1 parent 8e1c3c9 commit dec6a7a
Show file tree
Hide file tree
Showing 19 changed files with 2,905 additions and 762 deletions.
2 changes: 1 addition & 1 deletion activation/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func NewHandler(
fetcher: fetcher,
beacon: beacon,
tortoise: tortoise,
malPublisher: &MalfeasancePublisher{},
malPublisher: &MalfeasancePublisher{}, // TODO(mafa): pass real publisher when available
},
}

Expand Down
19 changes: 14 additions & 5 deletions activation/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"slices"
"sort"
"testing"
"testing/quick"
Expand Down Expand Up @@ -128,7 +129,7 @@ type handlerMocks struct {
mValidator *MocknipostValidator
mbeacon *MockAtxReceiver
mtortoise *mocks.MockTortoise
mMalPublish *MockmalfeasancePublisher
mMalPublish *MockatxMalfeasancePublisher
}

type testHandler struct {
Expand Down Expand Up @@ -159,6 +160,7 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID
}
h.mockFetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any())
h.mockFetch.EXPECT().GetPoetProof(gomock.Any(), types.BytesToHash(atx.NIPost.PostMetadata.Challenge))
deps := []types.ATXID{atx.PrevATXID, atx.PositioningATXID}
if atx.PrevATXID == types.EmptyATXID {
h.mValidator.EXPECT().InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), h.goldenATXID)
h.mValidator.EXPECT().
Expand All @@ -170,9 +172,17 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID
time.Sleep(settings.postVerificationDuration)
return nil
})
deps = append(deps, *atx.CommitmentATXID)
} else {
h.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), nodeId)
}
deps = slices.Compact(deps)
deps = slices.DeleteFunc(deps, func(dep types.ATXID) bool {
return dep == types.EmptyATXID || dep == h.goldenATXID
})
if len(deps) > 0 {
h.mockFetch.EXPECT().GetAtxs(gomock.Any(), deps, gomock.Any())
}
h.mValidator.EXPECT().PositioningAtx(atx.PositioningATXID, gomock.Any(), h.goldenATXID, atx.PublishEpoch)
h.mValidator.EXPECT().
NIPost(gomock.Any(), nodeId, h.goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
Expand All @@ -194,7 +204,7 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks {
mValidator: NewMocknipostValidator(ctrl),
mbeacon: NewMockAtxReceiver(ctrl),
mtortoise: mocks.NewMockTortoise(ctrl),
mMalPublish: NewMockmalfeasancePublisher(ctrl),
mMalPublish: NewMockatxMalfeasancePublisher(ctrl),
}
}

Expand All @@ -205,6 +215,8 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID, opts ...HandlerOptio
edVerifier := signing.NewEdVerifier()

mocks := newTestHandlerMocks(tb, goldenATXID)
// TODO(mafa): make mandatory parameter when real publisher is available
opts = append(opts, func(h *Handler) { h.v2.malPublisher = mocks.mMalPublish })
atxHdlr := NewHandler(
"localID",
cdb,
Expand Down Expand Up @@ -341,7 +353,6 @@ func TestHandler_ProcessAtxStoresNewVRFNonce(t *testing.T) {
atx2.VRFNonce = (*uint64)(&nonce2)
atx2.Sign(sig)
atxHdlr.expectAtxV1(atx2, sig.NodeID())
atxHdlr.mockFetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any())
require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(atx2)))

got, err = atxs.VRFNonce(atxHdlr.cdb, sig.NodeID(), atx2.PublishEpoch+1)
Expand Down Expand Up @@ -391,7 +402,6 @@ func TestHandler_HandleGossipAtx(t *testing.T) {

// second is now valid (deps are in)
atxHdlr.expectAtxV1(second, sig.NodeID())
atxHdlr.mockFetch.EXPECT().GetAtxs(gomock.Any(), []types.ATXID{second.PrevATXID}, gomock.Any())
require.NoError(t, atxHdlr.HandleGossipAtx(context.Background(), "", codec.MustEncode(second)))
}

Expand Down Expand Up @@ -695,7 +705,6 @@ func TestHandler_AtxWeight(t *testing.T) {
buf = codec.MustEncode(atx2)

atxHdlr.expectAtxV1(atx2, sig.NodeID(), func(o *atxHandleOpts) { o.poetLeaves = leaves })
atxHdlr.mockFetch.EXPECT().GetAtxs(gomock.Any(), []types.ATXID{atx1.ID()}, gomock.Any())
require.NoError(t, atxHdlr.HandleSyncedAtx(context.Background(), atx2.ID().Hash32(), peer, buf))

stored2, err := atxHdlr.cdb.GetAtx(atx2.ID())
Expand Down
90 changes: 49 additions & 41 deletions activation/handler_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type HandlerV2 struct {
tortoise system.Tortoise
logger *zap.Logger
fetcher system.Fetcher
malPublisher malfeasancePublisher
malPublisher atxMalfeasancePublisher
}

func (h *HandlerV2) processATX(
Expand Down Expand Up @@ -744,14 +744,6 @@ func (h *HandlerV2) checkMalicious(ctx context.Context, tx sql.Transaction, atx
return true, nil
}

malicious, err = h.checkDoublePost(ctx, tx, atx)
if err != nil {
return malicious, fmt.Errorf("checking double post: %w", err)
}
if malicious {
return true, nil
}

malicious, err = h.checkDoubleMerge(ctx, tx, atx)
if err != nil {
return malicious, fmt.Errorf("checking double merge: %w", err)
Expand Down Expand Up @@ -815,31 +807,6 @@ func (h *HandlerV2) checkDoubleMarry(ctx context.Context, tx sql.Transaction, at
return false, nil
}

func (h *HandlerV2) checkDoublePost(ctx context.Context, tx sql.Transaction, atx *activationTx) (bool, error) {
for id := range atx.ids {
atxIDs, err := atxs.FindDoublePublish(tx, id, atx.PublishEpoch)
switch {
case errors.Is(err, sql.ErrNotFound):
continue
case err != nil:
return false, fmt.Errorf("searching for double publish: %w", err)
}
otherAtxId := slices.IndexFunc(atxIDs, func(other types.ATXID) bool { return other != atx.ID() })
otherAtx := atxIDs[otherAtxId]
h.logger.Debug(
"found ID that has already contributed its PoST in this epoch",
zap.Stringer("node_id", id),
zap.Stringer("atx_id", atx.ID()),
zap.Stringer("other_atx_id", otherAtx),
zap.Uint32("epoch", atx.PublishEpoch.Uint32()),
)
// TODO(mafa): finish proof
var proof wire.Proof
return true, h.malPublisher.Publish(ctx, id, proof)
}
return false, nil
}

func (h *HandlerV2) checkDoubleMerge(ctx context.Context, tx sql.Transaction, atx *activationTx) (bool, error) {
if atx.MarriageATX == nil {
return false, nil
Expand Down Expand Up @@ -899,22 +866,63 @@ func (h *HandlerV2) checkPrevAtx(ctx context.Context, tx sql.Transaction, atx *a
log.ZShortStringer("expected", expectedPrevID),
)

atx1, atx2, err := atxs.PrevATXCollision(tx, data.previous, id)
collisions, err := atxs.PrevATXCollisions(tx, data.previous, id)
switch {
case errors.Is(err, sql.ErrNotFound):
continue
case err != nil:
return false, fmt.Errorf("checking for previous ATX collision: %w", err)
return true, fmt.Errorf("checking for previous ATX collision: %w", err)
}

var wireAtxV1 *wire.ActivationTxV1
for _, collision := range collisions {
if collision == atx.ID() {
continue
}
var blob sql.Blob
v, err := atxs.LoadBlob(ctx, tx, collision.Bytes(), &blob)
if err != nil {
return true, fmt.Errorf("get atx blob %s: %w", id.ShortString(), err)
}
switch v {
case types.AtxV1:
if wireAtxV1 == nil {
// we have at least one v2 ATX (the one we are validating right now) so we only need one
// v1 ATX to create the proof if no other v2 ATXs are found
wireAtxV1 = &wire.ActivationTxV1{}
codec.MustDecode(blob.Bytes, wireAtxV1)
}
case types.AtxV2:
wireAtx := &wire.ActivationTxV2{}
codec.MustDecode(blob.Bytes, wireAtx)
// prefer creating a proof with 2 ATXs of version 2
h.logger.Debug("creating a malfeasance proof for invalid previous ATX",
log.ZShortStringer("smesherID", id),
log.ZShortStringer("atx1", wireAtx.ID()),
log.ZShortStringer("atx2", atx.ActivationTxV2.ID()),
)
proof, err := wire.NewInvalidPrevAtxProofV2(tx, atx.ActivationTxV2, wireAtx, id)
if err != nil {
return true, fmt.Errorf("creating invalid previous ATX proof: %w", err)
}
return true, h.malPublisher.Publish(ctx, id, proof)
default:
h.logger.Fatal("Failed to create invalid previous ATX proof: unknown ATX version",
zap.Stringer("atx_id", collision),
)
}
}

// no ATXv2 found, create a proof with an ATXv1
h.logger.Debug("creating a malfeasance proof for invalid previous ATX",
log.ZShortStringer("smesherID", id),
log.ZShortStringer("atx1", atx1),
log.ZShortStringer("atx2", atx2),
log.ZShortStringer("atx1", wireAtxV1.ID()),
log.ZShortStringer("atx2", atx.ActivationTxV2.ID()),
)

// TODO(mafa): finish proof
var proof wire.Proof
proof, err := wire.NewInvalidPrevAtxProofV1(tx, atx.ActivationTxV2, wireAtxV1, id)
if err != nil {
return true, fmt.Errorf("creating invalid previous ATX proof: %w", err)
}
return true, h.malPublisher.Publish(ctx, id, proof)
}
return false, nil
Expand Down
Loading

0 comments on commit dec6a7a

Please sign in to comment.