From f08f25a8f56be9c50d40eabab89400e4f6d6c13a Mon Sep 17 00:00:00 2001 From: k <30611210+countvonzero@users.noreply.github.com> Date: Mon, 4 Sep 2023 23:10:43 +0000 Subject: [PATCH] miner: do not omit own atx in active set (#4952) ## Motivation syncing from devnet-405, malicious identities filter out own atx and cause other nodes to fail processing the ballot ``` 2023-09-02T14:58:23.344-0700 WARN 5fbb1.sync failed fetching new ballots {"node_id": "5fbb17185b5850709b3c545e3be4fbea25a30d3362a0627d974495e96d8b640b", "module": "sync", "sessionId": "6123c073-483d-41ad-b514-77e47167f48f", "layer_id": 11549, "ballot_ids": ["6a70a5a8d0", "f3205c8f79", "1efadf7bbe", "dc0881473b", "57577ea37f", "1f3110b841", "c9f0910457", "f2e8dac11e", "c97b5ff911", "f824639d3b"], "errmsg": "hint: ballotDB, hash: 0x1f3110b8411255368ffc35e90c2f17ad3a898a13000000000000000000000000, err: fetch ballots: hint: ballotDB, hash: 0x926af68ad85b7db9d5a0fbd57ddefdbb32838149000000000000000000000000, err: ballot not eligible: atx 086658fac6 from ballot 926af68ad8 (refballot 926af68ad8) is not included into the active set\nhint: ballotDB, hash: 0xc9f0910457dfab80334c80e77cf6673fe408adcb000000000000000000000000, err: fetch ballots: hint: ballotDB, hash: 0x37df2b14296708054511e2627f1094f5b8528eb3000000000000000000000000, err: fetch ballots: hint: ballotDB, hash: 0x5b91d74480e89f741045aa9c4f95501b02fc3d53000000000000000000000000, err: ballot not eligible: atx e9aff052ae from ballot 5b91d74480 (refballot 5b91d74480) is not included into the active set", "name": "sync"} ``` --- miner/oracle.go | 2 +- miner/oracle_test.go | 120 +++++++++++++++++++++++++------------------ proposals/handler.go | 6 ++- 3 files changed, 76 insertions(+), 52 deletions(-) diff --git a/miner/oracle.go b/miner/oracle.go index 0e7242a355..9b12cb2656 100644 --- a/miner/oracle.go +++ b/miner/oracle.go @@ -155,7 +155,7 @@ func (o *Oracle) activeSet(targetEpoch types.EpochID) (uint64, uint64, types.ATX if err != nil { return err } - if grade != Good { + if grade != Good && header.NodeID != o.cfg.nodeID { o.log.With().Info("atx omitted from active set", header.ID, log.Int("grade", int(grade)), diff --git a/miner/oracle_test.go b/miner/oracle_test.go index ca4b06d0cc..79b8aed712 100644 --- a/miner/oracle_test.go +++ b/miner/oracle_test.go @@ -300,57 +300,77 @@ func TestOracle_MinimalActiveSetWeight(t *testing.T) { } func TestOracle_ATXGrade(t *testing.T) { - avgLayerSize := uint32(50) - layersPerEpoch := uint32(10) - o := createTestOracle(t, avgLayerSize, layersPerEpoch, 0) - lid := types.LayerID(layersPerEpoch * 3) - epochStart := time.Now() - o.mClock.EXPECT().LayerToTime(lid).Return(epochStart) - - goodTime := epochStart.Add(-4*networkDelay - time.Nanosecond) - okTime := epochStart.Add(-3*networkDelay - time.Nanosecond) - evilTime := epochStart.Add(-3 * networkDelay) - publishLayer := (lid.GetEpoch() - 1).FirstLayer() - ownAtx := genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, o.edSigner, goodTime) - expected := []types.ATXID{ownAtx.ID()} - for i := 1; i < activeSetSize; i++ { - sig, err := signing.NewEdSigner() - require.NoError(t, err) - atx := genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, goodTime) - expected = append(expected, atx.ID()) - } - // add some atx that have good timing, with malicious proof arriving before epoch start - for i := 0; i < activeSetSize; i++ { - sig, err := signing.NewEdSigner() - require.NoError(t, err) - atx := genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, goodTime) - genMinerMalfeasance(t, o.cdb, sig.NodeID(), epochStart) - expected = append(expected, atx.ID()) - } - // add some atx that have good timing, with malfeasance proof arriving after epoch start - for i := 0; i < activeSetSize; i++ { - sig, err := signing.NewEdSigner() - require.NoError(t, err) - genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, goodTime) - genMinerMalfeasance(t, o.cdb, sig.NodeID(), epochStart.Add(-1*time.Nanosecond)) - } - // add some atx that are acceptable - for i := 0; i < activeSetSize; i++ { - sig, err := signing.NewEdSigner() - require.NoError(t, err) - genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, okTime) - } - // add some atx that are evil - for i := 0; i < activeSetSize; i++ { - sig, err := signing.NewEdSigner() - require.NoError(t, err) - genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, evilTime) + for _, tc := range []struct { + desc string + ownMalicious bool + }{ + { + desc: "own atx is good", + }, + { + desc: "own atx is malicious", + ownMalicious: true, + }, + } { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + avgLayerSize := uint32(50) + layersPerEpoch := uint32(10) + o := createTestOracle(t, avgLayerSize, layersPerEpoch, 0) + lid := types.LayerID(layersPerEpoch * 3) + epochStart := time.Now() + o.mClock.EXPECT().LayerToTime(lid).Return(epochStart) + + goodTime := epochStart.Add(-4*networkDelay - time.Nanosecond) + okTime := epochStart.Add(-3*networkDelay - time.Nanosecond) + evilTime := epochStart.Add(-3 * networkDelay) + publishLayer := (lid.GetEpoch() - 1).FirstLayer() + received := goodTime + if tc.ownMalicious { + received = evilTime + } + ownAtx := genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, o.edSigner, received) + expected := []types.ATXID{ownAtx.ID()} + for i := 1; i < activeSetSize; i++ { + sig, err := signing.NewEdSigner() + require.NoError(t, err) + atx := genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, goodTime) + expected = append(expected, atx.ID()) + } + // add some atx that have good timing, with malicious proof arriving before epoch start + for i := 0; i < activeSetSize; i++ { + sig, err := signing.NewEdSigner() + require.NoError(t, err) + atx := genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, goodTime) + genMinerMalfeasance(t, o.cdb, sig.NodeID(), epochStart) + expected = append(expected, atx.ID()) + } + // add some atx that have good timing, with malfeasance proof arriving after epoch start + for i := 0; i < activeSetSize; i++ { + sig, err := signing.NewEdSigner() + require.NoError(t, err) + genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, goodTime) + genMinerMalfeasance(t, o.cdb, sig.NodeID(), epochStart.Add(-1*time.Nanosecond)) + } + // add some atx that are acceptable + for i := 0; i < activeSetSize; i++ { + sig, err := signing.NewEdSigner() + require.NoError(t, err) + genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, okTime) + } + // add some atx that are evil + for i := 0; i < activeSetSize; i++ { + sig, err := signing.NewEdSigner() + require.NoError(t, err) + genMinerATX(t, o.cdb, types.RandomATXID(), publishLayer, sig, evilTime) + } + ee, err := o.ProposalEligibility(lid, types.RandomBeacon(), types.VRFPostIndex(1)) + require.NoError(t, err) + require.Equal(t, ownAtx.ID(), ee.Atx) + require.ElementsMatch(t, expected, ee.ActiveSet) + require.NotEmpty(t, ee.Proofs) + }) } - ee, err := o.ProposalEligibility(lid, types.RandomBeacon(), types.VRFPostIndex(1)) - require.NoError(t, err) - require.Equal(t, ownAtx.ID(), ee.Atx) - require.ElementsMatch(t, expected, ee.ActiveSet) - require.NotEmpty(t, ee.Proofs) } func createBallots(tb testing.TB, cdb *datastore.CachedDB, lid types.LayerID, numBallots int, common []types.ATXID) []*types.Ballot { diff --git a/proposals/handler.go b/proposals/handler.go index 7f1b0e61ff..046732b048 100644 --- a/proposals/handler.go +++ b/proposals/handler.go @@ -410,7 +410,11 @@ func (h *Handler) checkBallotSyntacticValidity(ctx context.Context, logger log.L t4 := time.Now() if eligible, err := h.validator.CheckEligibility(ctx, b); err != nil || !eligible { notEligible.Inc() - return nil, errNotEligible + var reason string + if err != nil { + reason = err.Error() + } + return nil, fmt.Errorf("%w: %v", errNotEligible, reason) } ballotDuration.WithLabelValues(eligible).Observe(float64(time.Since(t4)))