Skip to content

Commit

Permalink
Add invalid previous ATX malfeasance proof.
Browse files Browse the repository at this point in the history
  • Loading branch information
fasmat committed Sep 13, 2024
1 parent 2d71518 commit de6c8a8
Show file tree
Hide file tree
Showing 12 changed files with 576 additions and 167 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,6 @@ jobs:
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

unittests:
runs-on: ${{ matrix.os }}
Expand Down
7 changes: 4 additions & 3 deletions activation/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ type handlerMocks struct {
mValidator *MocknipostValidator
mbeacon *MockAtxReceiver
mtortoise *mocks.MockTortoise
mMalPublish *MockmalfeasancePublisher
mMalPublish *MockatxMalfeasancePublisher
}

type testHandler struct {
Expand Down Expand Up @@ -190,7 +190,7 @@ func newTestHandlerMocks(tb testing.TB, golden types.ATXID) handlerMocks {
mValidator: NewMocknipostValidator(ctrl),
mbeacon: NewMockAtxReceiver(ctrl),
mtortoise: mocks.NewMockTortoise(ctrl),
mMalPublish: NewMockmalfeasancePublisher(ctrl),
mMalPublish: NewMockatxMalfeasancePublisher(ctrl),
}
}

Expand Down Expand Up @@ -306,7 +306,8 @@ func TestHandler_PostMalfeasanceProofs(t *testing.T) {
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())
Expand Down
98 changes: 57 additions & 41 deletions activation/handler_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,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 @@ -691,14 +691,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 @@ -762,31 +754,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 @@ -828,22 +795,71 @@ 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
var wireAtxV2 *wire.ActivationTxV2

collisionCheck:
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:
if wireAtxV2 == nil {
wireAtxV2 = &wire.ActivationTxV2{}
codec.MustDecode(blob.Bytes, wireAtxV2)
break collisionCheck // if we have one v2 ATX we can create the proof
}
default:
h.logger.Fatal("Failed to create invalid previous ATX proof: unknown ATX version",
zap.Stringer("atx_id", collision),
)
}
}

if wireAtxV2 != nil {
// 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", wireAtxV2.ID()),
log.ZShortStringer("atx2", atx.ActivationTxV2.ID()),
)
proof, err := wire.NewInvalidPrevAtxProofV2(tx, atx.ActivationTxV2, wireAtxV2, id)
if err != nil {
return true, fmt.Errorf("creating invalid previous ATX proof: %w", err)
}
return true, h.malPublisher.Publish(ctx, id, proof)
}

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(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
75 changes: 7 additions & 68 deletions activation/handler_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (

"github.com/spacemeshos/go-spacemesh/activation/wire"
"github.com/spacemeshos/go-spacemesh/atxsdata"
"github.com/spacemeshos/go-spacemesh/codec"
"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/datastore"
"github.com/spacemeshos/go-spacemesh/fetch"
Expand Down Expand Up @@ -1721,8 +1720,6 @@ func Test_Marriages(t *testing.T) {
nId, err := malProof.Valid(atxHandler.edVerifier)
require.NoError(t, err)
require.Equal(t, sig.NodeID(), nId)
b := codec.MustEncode(malProof)
_ = b
return nil
})
err = atxHandler.processATX(context.Background(), "", atx2, time.Now())
Expand Down Expand Up @@ -1813,64 +1810,6 @@ func Test_MarryingMalicious(t *testing.T) {
}
}

func TestContextualValidation_DoublePost(t *testing.T) {
t.Parallel()
golden := types.RandomATXID()
sig, err := signing.NewEdSigner()
require.NoError(t, err)

atxHandler := newV2TestHandler(t, golden)

// marry
otherSig, err := signing.NewEdSigner()
require.NoError(t, err)
othersAtx := atxHandler.createAndProcessInitial(t, otherSig)

mATX := newInitialATXv2(t, golden)
mATX.Marriages = []wire.MarriageCertificate{
{
Signature: sig.Sign(signing.MARRIAGE, sig.NodeID().Bytes()),
},
{
ReferenceAtx: othersAtx.ID(),
Signature: otherSig.Sign(signing.MARRIAGE, sig.NodeID().Bytes()),
},
}
mATX.Sign(sig)

atxHandler.expectInitialAtxV2(mATX)
err = atxHandler.processATX(context.Background(), "", mATX, time.Now())
require.NoError(t, err)

// publish merged
merged := newSoloATXv2(t, mATX.PublishEpoch+2, mATX.ID(), mATX.ID())
post := wire.SubPostV2{
MarriageIndex: 1,
NumUnits: othersAtx.TotalNumUnits(),
PrevATXIndex: 1,
}
merged.NiPosts[0].Posts = append(merged.NiPosts[0].Posts, post)

mATXID := mATX.ID()
merged.MarriageATX = &mATXID

merged.PreviousATXs = []types.ATXID{mATX.ID(), othersAtx.ID()}
merged.Sign(sig)

atxHandler.expectMergedAtxV2(merged, []types.NodeID{sig.NodeID(), otherSig.NodeID()}, []uint64{poetLeaves})
err = atxHandler.processATX(context.Background(), "", merged, time.Now())
require.NoError(t, err)

// The otherSig tries to publish alone in the same epoch.
// This is malfeasance as it tries include his PoST twice.
doubled := newSoloATXv2(t, merged.PublishEpoch, othersAtx.ID(), othersAtx.ID())
doubled.Sign(otherSig)
atxHandler.expectAtxV2(doubled)
atxHandler.mMalPublish.EXPECT().Publish(gomock.Any(), otherSig.NodeID(), gomock.Any())
err = atxHandler.processATX(context.Background(), "", doubled, time.Now())
require.NoError(t, err)
}

func Test_CalculatingUnits(t *testing.T) {
t.Parallel()
t.Run("units on 1 nipost must not overflow", func(t *testing.T) {
Expand Down Expand Up @@ -1899,7 +1838,7 @@ func Test_CalculatingUnits(t *testing.T) {

func TestContextual_PreviousATX(t *testing.T) {
golden := types.RandomATXID()
atxHndlr := newV2TestHandler(t, golden)
atxHdlr := newV2TestHandler(t, golden)
var (
signers []*signing.EdSigner
eqSet []types.NodeID
Expand All @@ -1911,13 +1850,13 @@ func TestContextual_PreviousATX(t *testing.T) {
eqSet = append(eqSet, sig.NodeID())
}

mATX, otherAtxs := marryIDs(t, atxHndlr, signers, golden)
mATX, otherAtxs := marryIDs(t, atxHdlr, signers, golden)

// signer 1 creates a solo ATX
soloAtx := newSoloATXv2(t, mATX.PublishEpoch+1, otherAtxs[0].ID(), mATX.ID())
soloAtx.Sign(signers[1])
atxHndlr.expectAtxV2(soloAtx)
err := atxHndlr.processATX(context.Background(), "", soloAtx, time.Now())
atxHdlr.expectAtxV2(soloAtx)
err := atxHdlr.processATX(context.Background(), "", soloAtx, time.Now())
require.NoError(t, err)

// create a MergedATX for all IDs
Expand All @@ -1935,9 +1874,9 @@ func TestContextual_PreviousATX(t *testing.T) {
merged.MarriageATX = &matxID
merged.Sign(signers[0])

atxHndlr.expectMergedAtxV2(merged, eqSet, []uint64{100})
atxHndlr.mMalPublish.EXPECT().Publish(gomock.Any(), signers[1].NodeID(), gomock.Any())
err = atxHndlr.processATX(context.Background(), "", merged, time.Now())
atxHdlr.expectMergedAtxV2(merged, eqSet, []uint64{100})
atxHdlr.mMalPublish.EXPECT().Publish(gomock.Any(), signers[1].NodeID(), gomock.Any())
err = atxHdlr.processATX(context.Background(), "", merged, time.Now())
require.NoError(t, err)
}

Expand Down
4 changes: 2 additions & 2 deletions activation/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ type syncer interface {
RegisterForATXSynced() <-chan struct{}
}

// malfeasancePublisher is an interface for publishing malfeasance proofs.
// atxMalfeasancePublisher is an interface for publishing malfeasance proofs.
// This interface is used to publish proofs in V2.
//
// The provider of that interface ensures that only valid proofs are published (invalid ones return an error).
// Proofs against an identity that is managed by the node will also return an error and will not be gossiped.
//
// Additionally the publisher will only gossip proofs when the node is in sync, otherwise it will only store them.
// and mark the associated identity as malfeasant.
type malfeasancePublisher interface {
type atxMalfeasancePublisher interface {
Publish(ctx context.Context, id types.NodeID, proof wire.Proof) error
}

Expand Down
40 changes: 20 additions & 20 deletions activation/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit de6c8a8

Please sign in to comment.