Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiP: Add invalid prev ATX proof #6310

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 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
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 @@
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 @@
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 @@
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 @@
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)

Check warning on line 803 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L803

Added line #L803 was not covered by tests
}

var wireAtxV1 *wire.ActivationTxV1
var wireAtxV2 *wire.ActivationTxV2

collisionCheck:
for _, collision := range collisions {
if collision == atx.ID() {
continue

Check warning on line 812 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L812

Added line #L812 was not covered by tests
}
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)

Check warning on line 817 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L817

Added line #L817 was not covered by tests
}
switch v {
case types.AtxV1:
if wireAtxV1 == nil {

Check warning on line 821 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L820-L821

Added lines #L820 - L821 were not covered by tests
// 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)

Check warning on line 825 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L824-L825

Added lines #L824 - L825 were not covered by tests
}
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),
)

Check warning on line 836 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L833-L836

Added lines #L833 - L836 were not covered by tests
}
}

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)

Check warning on line 849 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L849

Added line #L849 was not covered by tests
}
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()),

Check warning on line 857 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L856-L857

Added lines #L856 - L857 were not covered by tests
)

// 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)

Check warning on line 861 in activation/handler_v2.go

View check run for this annotation

Codecov / codecov/patch

activation/handler_v2.go#L859-L861

Added lines #L859 - L861 were not covered by tests
}
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about keeping the test to make sure this kind of malfeasance is detected?

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
Loading