diff --git a/api/grpcserver/grpcserver_test.go b/api/grpcserver/grpcserver_test.go index 5d6eb290777..6ba1b85ed66 100644 --- a/api/grpcserver/grpcserver_test.go +++ b/api/grpcserver/grpcserver_test.go @@ -50,6 +50,7 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/accounts" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/identities" "github.com/spacemeshos/go-spacemesh/system" @@ -58,7 +59,6 @@ import ( const ( labelsPerUnit = 2048 - bitsPerLabel = 8 numUnits = 2 genTimeUnix = 1000000 layerDuration = 10 * time.Second @@ -71,7 +71,6 @@ const ( accountBalance = 8675301 accountCounter = 0 rewardAmount = 5551234 - activesetSize = 10001 ) var ( @@ -214,8 +213,7 @@ func TestMain(m *testing.M) { // These create circular dependencies so they have to be initialized // after the global vars ballot1.AtxID = globalAtx.ID() - ballot1.EpochData = &types.EpochData{} - ballot1.ActiveSet = []types.ATXID{globalAtx.ID(), globalAtx2.ID()} + ballot1.EpochData = &types.EpochData{ActiveSetHash: types.ATXIDList{globalAtx.ID(), globalAtx2.ID()}.Hash()} globalTx = NewTx(0, addr1, signer1) globalTx2 = NewTx(1, addr2, signer2) @@ -1043,7 +1041,9 @@ func TestMeshService(t *testing.T) { genesis := time.Unix(genTimeUnix, 0) genTime.EXPECT().GenesisTime().Return(genesis) genTime.EXPECT().CurrentLayer().Return(layerCurrent).AnyTimes() - grpcService := NewMeshService(datastore.NewCachedDB(sql.InMemory(), logtest.New(t)), meshAPIMock, conStateAPI, genTime, layersPerEpoch, types.Hash20{}, layerDuration, layerAvgSize, txsPerProposal) + db := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) + grpcService := NewMeshService(db, meshAPIMock, conStateAPI, genTime, layersPerEpoch, types.Hash20{}, layerDuration, layerAvgSize, txsPerProposal) + require.NoError(t, activesets.Add(db, ballot1.EpochData.ActiveSetHash, &types.EpochActiveSet{Set: types.ATXIDList{globalAtx.ID(), globalAtx2.ID()}})) t.Cleanup(launchServer(t, cfg, grpcService)) ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -2163,7 +2163,9 @@ func TestLayerStream_comprehensive(t *testing.T) { ctrl := gomock.NewController(t) genTime := NewMockgenesisTimeAPI(ctrl) - grpcService := NewMeshService(datastore.NewCachedDB(sql.InMemory(), logtest.New(t)), meshAPIMock, conStateAPI, genTime, layersPerEpoch, types.Hash20{}, layerDuration, layerAvgSize, txsPerProposal) + db := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) + grpcService := NewMeshService(db, meshAPIMock, conStateAPI, genTime, layersPerEpoch, types.Hash20{}, layerDuration, layerAvgSize, txsPerProposal) + require.NoError(t, activesets.Add(db, ballot1.EpochData.ActiveSetHash, &types.EpochActiveSet{Set: types.ATXIDList{globalAtx.ID(), globalAtx2.ID()}})) t.Cleanup(launchServer(t, cfg, grpcService)) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) diff --git a/api/grpcserver/mesh_service.go b/api/grpcserver/mesh_service.go index 1c0bec36d2c..c8a9a8aa814 100644 --- a/api/grpcserver/mesh_service.go +++ b/api/grpcserver/mesh_service.go @@ -18,6 +18,7 @@ import ( "github.com/spacemeshos/go-spacemesh/datastore" "github.com/spacemeshos/go-spacemesh/events" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" ) // MeshService exposes mesh data such as accounts, blocks, and transactions. @@ -124,8 +125,6 @@ func (s MeshService) getFilteredTransactions(from types.LayerID, address types.A func (s MeshService) getFilteredActivations(ctx context.Context, startLayer types.LayerID, addr types.Address) (activations []*types.VerifiedActivationTx, err error) { // We have no way to look up activations by coinbase so we have no choice but to read all of them. - // TODO: index activations by layer (and maybe by coinbase) - // See https://github.com/spacemeshos/go-spacemesh/issues/2064. var atxids []types.ATXID for l := startLayer; !l.After(s.mesh.LatestLayer()); l = l.Add(1) { layer, err := s.mesh.GetLayer(l) @@ -133,8 +132,12 @@ func (s MeshService) getFilteredActivations(ctx context.Context, startLayer type return nil, status.Errorf(codes.Internal, "error retrieving layer data") } for _, b := range layer.Ballots() { - if b.EpochData != nil && b.ActiveSet != nil { - atxids = append(atxids, b.ActiveSet...) + if b.EpochData != nil { + actives, err := activesets.Get(s.cdb, b.EpochData.ActiveSetHash) + if err != nil { + return nil, status.Errorf(codes.Internal, "error retrieving active set %s (%s)", b.ID().String(), b.EpochData.ActiveSetHash.ShortString()) + } + atxids = append(atxids, actives.Set...) } } } @@ -334,8 +337,12 @@ func (s MeshService) readLayer(ctx context.Context, layerID types.LayerID, layer } for _, b := range layer.Ballots() { - if b.EpochData != nil && b.ActiveSet != nil { - activations = append(activations, b.ActiveSet...) + if b.EpochData != nil { + actives, err := activesets.Get(s.cdb, b.EpochData.ActiveSetHash) + if err != nil { + return nil, status.Errorf(codes.Internal, "error retrieving active set %s (%s)", b.ID().String(), b.EpochData.ActiveSetHash.ShortString()) + } + activations = append(activations, actives.Set...) } } diff --git a/blocks/generator_test.go b/blocks/generator_test.go index b86bdb619cc..2ad8415bd01 100644 --- a/blocks/generator_test.go +++ b/blocks/generator_test.go @@ -25,6 +25,7 @@ import ( "github.com/spacemeshos/go-spacemesh/log/logtest" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/ballots" "github.com/spacemeshos/go-spacemesh/sql/layers" @@ -192,10 +193,10 @@ func createProposal( EpochData: &types.EpochData{ Beacon: types.RandomBeacon(), EligibilityCount: uint32(layerSize * epochSize / len(activeSet)), + ActiveSetHash: activeSet.Hash(), }, }, EligibilityProofs: make([]types.VotingEligibility, numEligibility), - ActiveSet: activeSet, }, TxIDs: txIDs, MeshHash: meshHash, @@ -207,6 +208,7 @@ func createProposal( require.NoError(t, p.Initialize()) require.NoError(t, ballots.Add(db, &p.Ballot)) require.NoError(t, proposals.Add(db, p)) + activesets.Add(db, activeSet.Hash(), &types.EpochActiveSet{Set: activeSet}) return p } diff --git a/blocks/utils_test.go b/blocks/utils_test.go index fba44371b74..6a9a6ea742d 100644 --- a/blocks/utils_test.go +++ b/blocks/utils_test.go @@ -13,6 +13,7 @@ import ( "github.com/spacemeshos/go-spacemesh/log/logtest" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/layers" ) @@ -103,7 +104,7 @@ func Test_getProposalMetadata(t *testing.T) { cfg := Config{OptFilterThreshold: 70} lid := types.LayerID(111) _, atxs := createATXs(t, cdb, (lid.GetEpoch() - 1).FirstLayer(), 10) - actives := types.ToATXIDs(atxs) + actives := types.ATXIDList(types.ToATXIDs(atxs)) props := make([]*types.Proposal, 0, 10) hash1 := types.Hash32{1, 2, 3} hash2 := types.Hash32{3, 2, 1} @@ -119,11 +120,14 @@ func Test_getProposalMetadata(t *testing.T) { for j := 0; j <= i; j++ { p.EligibilityProofs = append(p.EligibilityProofs, types.VotingEligibility{J: uint32(j + 1)}) } - p.EpochData = &types.EpochData{} - p.ActiveSet = actives + p.EpochData = &types.EpochData{ActiveSetHash: actives.Hash()} p.EpochData.EligibilityCount = uint32(i + 1) props = append(props, &p) } + require.NoError(t, activesets.Add(cdb, actives.Hash(), &types.EpochActiveSet{ + Epoch: lid.GetEpoch(), + Set: actives, + })) require.NoError(t, layers.SetMeshHash(cdb, lid-1, hash2)) // only 5 / 10 proposals has the same state diff --git a/common/types/ballot.go b/common/types/ballot.go index 6d8ca6574b3..47768c81062 100644 --- a/common/types/ballot.go +++ b/common/types/ballot.go @@ -289,12 +289,12 @@ func (b *Ballot) IsMalicious() bool { // MarshalLogObject implements logging encoder for Ballot. func (b *Ballot) MarshalLogObject(encoder log.ObjectEncoder) error { var ( - activeSetSize = 0 - beacon Beacon + activeHash Hash32 + beacon Beacon ) if b.EpochData != nil { - activeSetSize = len(b.ActiveSet) + activeHash = b.EpochData.ActiveSetHash beacon = b.EpochData.Beacon } @@ -309,7 +309,7 @@ func (b *Ballot) MarshalLogObject(encoder log.ObjectEncoder) error { encoder.AddInt("abstain", len(b.Votes.Abstain)) encoder.AddString("atx_id", b.AtxID.String()) encoder.AddString("ref_ballot", b.RefBallot.String()) - encoder.AddInt("active_set_size", activeSetSize) + encoder.AddString("active set hash", activeHash.ShortString()) encoder.AddString("beacon", beacon.ShortString()) encoder.AddObject("votes", &b.Votes) return nil diff --git a/config/mainnet.go b/config/mainnet.go index d8ca3930d9f..cf9e01ca7a8 100644 --- a/config/mainnet.go +++ b/config/mainnet.go @@ -80,7 +80,6 @@ func MainnetConfig() Config { // 1000 - is assumed minimal number of units // 5000 - half of the expected poet ticks MinimalActiveSetWeight: 1000 * 5000, - EmitEmptyActiveSet: 20000, }, HARE: hareConfig.Config{ N: 200, diff --git a/hare/eligibility/oracle.go b/hare/eligibility/oracle.go index 88ea9a0ba71..93ceae87ec0 100644 --- a/hare/eligibility/oracle.go +++ b/hare/eligibility/oracle.go @@ -19,6 +19,7 @@ import ( "github.com/spacemeshos/go-spacemesh/miner" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/ballots" "github.com/spacemeshos/go-spacemesh/system" ) @@ -462,11 +463,20 @@ func (o *Oracle) activeSetFromRefBallots(epoch types.EpochID) ([]types.ATXID, er o.Log.With().Debug("beacon mismatch", log.Stringer("local", beacon), log.Object("ballot", ballot)) continue } - for _, id := range ballot.ActiveSet { + actives, err := activesets.Get(o.cdb, ballot.EpochData.ActiveSetHash) + if err != nil { + o.Log.With().Error("active set missing from ref ballot", log.Inline(ballot)) + continue + } + for _, id := range actives.Set { activeMap[id] = struct{}{} } } - o.Log.With().Warning("using tortoise active set", log.Uint32("epoch", epoch.Uint32()), log.Stringer("beacon", beacon)) + o.Log.With().Warning("using tortoise active set", + log.Int("actives size", len(activeMap)), + log.Uint32("epoch", epoch.Uint32()), + log.Stringer("beacon", beacon), + ) return maps.Keys(activeMap), nil } diff --git a/hare/eligibility/oracle_test.go b/hare/eligibility/oracle_test.go index 6357f6dbed0..e4b5d74a023 100644 --- a/hare/eligibility/oracle_test.go +++ b/hare/eligibility/oracle_test.go @@ -23,6 +23,7 @@ import ( "github.com/spacemeshos/go-spacemesh/log/logtest" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/ballots" "github.com/spacemeshos/go-spacemesh/sql/blocks" @@ -64,7 +65,7 @@ func defaultOracle(t testing.TB) *testOracle { return to } -func createBallots(tb testing.TB, cdb *datastore.CachedDB, lid types.LayerID, activeSet []types.ATXID, miners []types.NodeID) []*types.Ballot { +func createBallots(tb testing.TB, cdb *datastore.CachedDB, lid types.LayerID, activeSet types.ATXIDList, miners []types.NodeID) []*types.Ballot { tb.Helper() numBallots := ballotsPerLayer if len(activeSet) < numBallots { @@ -76,12 +77,15 @@ func createBallots(tb testing.TB, cdb *datastore.CachedDB, lid types.LayerID, ac b.Layer = lid b.AtxID = activeSet[i] b.RefBallot = types.EmptyBallotID - b.EpochData = &types.EpochData{} - b.ActiveSet = activeSet + b.EpochData = &types.EpochData{ActiveSetHash: activeSet.Hash()} b.Signature = types.RandomEdSignature() b.SmesherID = miners[i] require.NoError(tb, b.Initialize()) require.NoError(tb, ballots.Add(cdb, b)) + activesets.Add(cdb, b.EpochData.ActiveSetHash, &types.EpochActiveSet{ + Epoch: lid.GetEpoch(), + Set: activeSet, + }) result = append(result, b) } return result @@ -659,11 +663,10 @@ func TestActiveSetDD(t *testing.T) { t.Parallel() target := types.EpochID(4) - bgen := func(id types.BallotID, lid types.LayerID, node types.NodeID, beacon types.Beacon, atxs []types.ATXID, option ...func(*types.Ballot)) types.Ballot { + bgen := func(id types.BallotID, lid types.LayerID, node types.NodeID, beacon types.Beacon, atxs types.ATXIDList, option ...func(*types.Ballot)) types.Ballot { ballot := types.Ballot{} ballot.Layer = lid - ballot.EpochData = &types.EpochData{Beacon: beacon} - ballot.ActiveSet = atxs + ballot.EpochData = &types.EpochData{Beacon: beacon, ActiveSetHash: atxs.Hash()} ballot.SmesherID = node ballot.SetID(id) for _, opt := range option { @@ -693,12 +696,13 @@ func TestActiveSetDD(t *testing.T) { beacon types.Beacon // local beacon ballots []types.Ballot atxs []*types.VerifiedActivationTx + actives []types.ATXIDList expect any }{ { - "merged activesets", - types.Beacon{1}, - []types.Ballot{ + desc: "merged activesets", + beacon: types.Beacon{1}, + ballots: []types.Ballot{ bgen( types.BallotID{1}, target.FirstLayer(), @@ -714,17 +718,18 @@ func TestActiveSetDD(t *testing.T) { []types.ATXID{{2}, {3}}, ), }, - []*types.VerifiedActivationTx{ + atxs: []*types.VerifiedActivationTx{ agen(types.ATXID{1}, types.NodeID{1}), agen(types.ATXID{2}, types.NodeID{2}), agen(types.ATXID{3}, types.NodeID{3}), }, - []types.ATXID{{1}, {2}, {3}}, + actives: []types.ATXIDList{{{1}, {2}}, {{2}, {3}}}, + expect: []types.ATXID{{1}, {2}, {3}}, }, { - "filter by beacon", - types.Beacon{1}, - []types.Ballot{ + desc: "filter by beacon", + beacon: types.Beacon{1}, + ballots: []types.Ballot{ bgen( types.BallotID{1}, target.FirstLayer(), @@ -740,16 +745,17 @@ func TestActiveSetDD(t *testing.T) { []types.ATXID{{2}, {3}}, ), }, - []*types.VerifiedActivationTx{ + atxs: []*types.VerifiedActivationTx{ agen(types.ATXID{1}, types.NodeID{1}), agen(types.ATXID{2}, types.NodeID{2}), }, - []types.ATXID{{1}, {2}}, + actives: []types.ATXIDList{{{1}, {2}}, {{2}, {3}}}, + expect: []types.ATXID{{1}, {2}}, }, { - "no local beacon", - types.EmptyBeacon, - []types.Ballot{ + desc: "no local beacon", + beacon: types.EmptyBeacon, + ballots: []types.Ballot{ bgen( types.BallotID{1}, target.FirstLayer(), @@ -765,13 +771,14 @@ func TestActiveSetDD(t *testing.T) { []types.ATXID{{2}, {3}}, ), }, - []*types.VerifiedActivationTx{}, - "not found", + atxs: []*types.VerifiedActivationTx{}, + actives: []types.ATXIDList{{{1}, {2}}, {{2}, {3}}}, + expect: "not found", }, { - "unknown atxs", - types.Beacon{1}, - []types.Ballot{ + desc: "unknown atxs", + beacon: types.Beacon{1}, + ballots: []types.Ballot{ bgen( types.BallotID{1}, target.FirstLayer(), @@ -787,13 +794,14 @@ func TestActiveSetDD(t *testing.T) { []types.ATXID{{2}, {3}}, ), }, - []*types.VerifiedActivationTx{}, - "get ATX", + atxs: []*types.VerifiedActivationTx{}, + actives: []types.ATXIDList{{{1}, {2}}, {{2}, {3}}}, + expect: "get ATX", }, { - "ballot no epoch data", - types.Beacon{1}, - []types.Ballot{ + desc: "ballot no epoch data", + beacon: types.Beacon{1}, + ballots: []types.Ballot{ bgen( types.BallotID{1}, target.FirstLayer(), @@ -812,16 +820,17 @@ func TestActiveSetDD(t *testing.T) { []types.ATXID{{2}, {3}}, ), }, - []*types.VerifiedActivationTx{ + atxs: []*types.VerifiedActivationTx{ agen(types.ATXID{2}, types.NodeID{2}), agen(types.ATXID{3}, types.NodeID{3}), }, - []types.ATXID{{2}, {3}}, + actives: []types.ATXIDList{{{2}, {3}}}, + expect: []types.ATXID{{2}, {3}}, }, { - "wrong target epoch", - types.Beacon{1}, - []types.Ballot{ + desc: "wrong target epoch", + beacon: types.Beacon{1}, + ballots: []types.Ballot{ bgen( types.BallotID{1}, target.FirstLayer(), @@ -830,18 +839,22 @@ func TestActiveSetDD(t *testing.T) { []types.ATXID{{1}}, ), }, - []*types.VerifiedActivationTx{ + atxs: []*types.VerifiedActivationTx{ agen(types.ATXID{1}, types.NodeID{1}, func(verified *types.VerifiedActivationTx) { verified.PublishEpoch = target }), }, - "no epoch atx found", + actives: []types.ATXIDList{{{1}}}, + expect: "no epoch atx found", }, } { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() oracle := defaultOracle(t) + for _, actives := range tc.actives { + require.NoError(t, activesets.Add(oracle.cdb, actives.Hash(), &types.EpochActiveSet{Set: actives})) + } for _, ballot := range tc.ballots { require.NoError(t, ballots.Add(oracle.cdb, &ballot)) } diff --git a/hare/hare_test.go b/hare/hare_test.go index 9ff221ddbfc..3a56618c221 100644 --- a/hare/hare_test.go +++ b/hare/hare_test.go @@ -88,11 +88,9 @@ func randomProposal(lyrID types.LayerID, beacon types.Beacon) *types.Proposal { Layer: lyrID, AtxID: types.RandomATXID(), EpochData: &types.EpochData{ - ActiveSetHash: types.RandomHash(), - Beacon: beacon, + Beacon: beacon, }, }, - ActiveSet: types.RandomActiveSet(10), }, }, } @@ -570,11 +568,6 @@ func TestHare_goodProposals(t *testing.T) { pList[i].EpochData = nil pList[i].RefBallot = refBallot.ID() mockMesh.EXPECT().Ballot(pList[1].RefBallot).Return(refBallot, nil) - for _, id := range refBallot.ActiveSet { - nodeID := types.RandomNodeID() - mockMesh.EXPECT().GetAtxHeader(id).Return(&types.ActivationTxHeader{BaseTickHeight: 11, TickCount: 1, NodeID: nodeID}, nil).AnyTimes() - mockMesh.EXPECT().GetMalfeasanceProof(nodeID).AnyTimes() - } } } for i, p := range pList { @@ -585,13 +578,6 @@ func TestHare_goodProposals(t *testing.T) { } else { mockMesh.EXPECT().GetAtxHeader(p.AtxID).Return(&types.ActivationTxHeader{BaseTickHeight: tc.baseHeights[i], TickCount: 1}, nil) } - if p.EpochData != nil { - for _, id := range p.ActiveSet { - nodeID := types.RandomNodeID() - mockMesh.EXPECT().GetAtxHeader(id).Return(&types.ActivationTxHeader{BaseTickHeight: 11, TickCount: 1, NodeID: nodeID}, nil).AnyTimes() - mockMesh.EXPECT().GetMalfeasanceProof(nodeID).AnyTimes() - } - } } nodeID := types.NodeID{1, 2, 3} mockMesh.EXPECT().GetEpochAtx(lyrID.GetEpoch()-1, nodeID).Return(&types.ActivationTxHeader{BaseTickHeight: nodeBaseHeight, TickCount: 1}, nil) diff --git a/hare3/hare_test.go b/hare3/hare_test.go index 2e5c7633158..3f6d8518b32 100644 --- a/hare3/hare_test.go +++ b/hare3/hare_test.go @@ -325,7 +325,7 @@ func (cl *lockstepCluster) nogossip() { } } -func (cl *lockstepCluster) activeSet() []types.ATXID { +func (cl *lockstepCluster) activeSet() types.ATXIDList { var ids []types.ATXID unique := map[types.ATXID]struct{}{} for _, n := range cl.nodes { @@ -350,9 +350,9 @@ func (cl *lockstepCluster) genProposals(lid types.LayerID) { } proposal := &types.Proposal{} proposal.Layer = lid - proposal.ActiveSet = active proposal.EpochData = &types.EpochData{ - Beacon: cl.t.beacon, + Beacon: cl.t.beacon, + ActiveSetHash: active.Hash(), } proposal.AtxID = n.atx.ID() proposal.SmesherID = n.signer.NodeID() diff --git a/miner/oracle.go b/miner/oracle.go index a683e039494..685aae859ce 100644 --- a/miner/oracle.go +++ b/miner/oracle.go @@ -14,6 +14,7 @@ import ( "github.com/spacemeshos/go-spacemesh/proposals" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/ballots" "github.com/spacemeshos/go-spacemesh/system" ) @@ -216,7 +217,11 @@ func (o *Oracle) calcEligibilityProofs(lid types.LayerID, epoch types.EpochID, b if ref == nil { minerWeight, totalWeight, ownAtx, activeSet, err = o.activeSet(epoch) } else { - activeSet = ref.ActiveSet + if epochActives, err := activesets.Get(o.cdb, ref.EpochData.ActiveSetHash); err != nil { + return nil, err + } else { + activeSet = epochActives.Set + } o.log.With().Info("use active set from ref ballot", ref.ID(), log.Int("num atx", len(activeSet)), diff --git a/miner/oracle_test.go b/miner/oracle_test.go index 0ebdb6345de..c945ddb944f 100644 --- a/miner/oracle_test.go +++ b/miner/oracle_test.go @@ -16,6 +16,7 @@ import ( "github.com/spacemeshos/go-spacemesh/proposals" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/ballots" "github.com/spacemeshos/go-spacemesh/sql/blocks" @@ -102,7 +103,6 @@ func genBallotWithEligibility( EligibilityCount: ee.Slots, }, }, - ActiveSet: ee.ActiveSet, EligibilityProofs: ee.Proofs[lid], } ballot.Signature = signer.Sign(signing.BALLOT, ballot.SignedBytes()) @@ -216,6 +216,7 @@ func testMinerOracleAndProposalValidator(t *testing.T, layerSize, layersPerEpoch require.True(t, ok) ee, err := o.ProposalEligibility(layer, info.beacon, nonce) require.NoError(t, err) + activesets.Add(o.cdb, ee.ActiveSet.Hash(), &types.EpochActiveSet{Epoch: ee.Epoch, Set: ee.ActiveSet}) for _, proof := range ee.Proofs[layer] { b := genBallotWithEligibility(t, o.edSigner, info.beacon, layer, ee) @@ -223,7 +224,7 @@ func testMinerOracleAndProposalValidator(t *testing.T, layerSize, layersPerEpoch o.mClock.EXPECT().CurrentLayer().Return(layer) mbc.EXPECT().ReportBeaconFromBallot(layer.GetEpoch(), b, info.beacon, gomock.Any()).Times(1) nonceFetcher.EXPECT().VRFNonce(b.SmesherID, layer.GetEpoch()).Return(nonce, nil).Times(1) - eligible, err := validator.CheckEligibility(context.Background(), b) + eligible, err := validator.CheckEligibility(context.Background(), b, ee.ActiveSet) require.NoError(t, err, "at layer %d, with layersPerEpoch %d", layer, layersPerEpoch) require.True(t, eligible, "should be eligible at layer %d, but isn't", layer) counterValuesSeen[proof.J]++ @@ -380,11 +381,16 @@ func createBallots(tb testing.TB, cdb *datastore.CachedDB, lid types.LayerID, nu b.Layer = lid b.AtxID = types.RandomATXID() b.RefBallot = types.EmptyBallotID - b.EpochData = &types.EpochData{} - b.ActiveSet = common - b.ActiveSet = append(b.ActiveSet, b.AtxID) + b.EpochData = &types.EpochData{ + ActiveSetHash: types.RandomHash(), + } b.Signature = types.RandomEdSignature() b.SmesherID = types.RandomNodeID() + actives := append(common, b.AtxID) + require.NoError(tb, activesets.Add(cdb, b.EpochData.ActiveSetHash, &types.EpochActiveSet{ + Epoch: lid.GetEpoch(), + Set: actives, + })) require.NoError(tb, b.Initialize()) require.NoError(tb, ballots.Add(cdb, b)) result = append(result, b) @@ -480,10 +486,14 @@ func TestRefBallot(t *testing.T) { ballot.Layer = layer ballot.AtxID = atx.ID() ballot.SmesherID = o.edSigner.NodeID() - ballot.EpochData = &types.EpochData{EligibilityCount: 1} - ballot.ActiveSet = []types.ATXID{ballot.AtxID} + actives := types.ATXIDList{ballot.AtxID} + ballot.EpochData = &types.EpochData{EligibilityCount: 1, ActiveSetHash: actives.Hash()} ballot.SetID(types.BallotID{1}) require.NoError(t, ballots.Add(o.cdb, &ballot)) + activesets.Add(o.cdb, ballot.EpochData.ActiveSetHash, &types.EpochActiveSet{ + Epoch: layer.GetEpoch(), + Set: actives, + }) genATXForTargetEpochs(t, o.cdb, layer.GetEpoch(), layer.GetEpoch()+1, o.edSigner, layersPerEpoch, time.Now().Add(-1*time.Hour)) ee, err := o.calcEligibilityProofs(layer, layer.GetEpoch(), types.Beacon{}, types.VRFPostIndex(101)) @@ -491,5 +501,5 @@ func TestRefBallot(t *testing.T) { require.NotEmpty(t, ee) require.Equal(t, 1, int(ee.Slots)) require.Equal(t, atx.ID(), ee.Atx) - require.ElementsMatch(t, ballot.ActiveSet, ee.ActiveSet) + require.ElementsMatch(t, actives, ee.ActiveSet) } diff --git a/miner/proposal_builder.go b/miner/proposal_builder.go index 2c597f151b2..e69e0db7425 100644 --- a/miner/proposal_builder.go +++ b/miner/proposal_builder.go @@ -66,7 +66,6 @@ type config struct { layersPerEpoch uint32 hdist uint32 minActiveSetWeight uint64 - emitEmptyActiveSet types.LayerID nodeID types.NodeID networkDelay time.Duration @@ -79,7 +78,6 @@ func (c *config) MarshalLogObject(encoder log.ObjectEncoder) error { encoder.AddUint32("epoch size", c.layersPerEpoch) encoder.AddUint32("hdist", c.hdist) encoder.AddUint64("min active weight", c.minActiveSetWeight) - encoder.AddUint32("emit empty set", c.emitEmptyActiveSet.Uint32()) encoder.AddDuration("network delay", c.networkDelay) encoder.AddInt("good atx percent", c.goodAtxPct) return nil @@ -152,12 +150,6 @@ func WithMinGoodAtxPct(pct int) Opt { } } -func WithEmitEmptyActiveSet(lid types.LayerID) Opt { - return func(pb *ProposalBuilder) { - pb.cfg.emitEmptyActiveSet = lid - } -} - func withOracle(o proposalOracle) Opt { return func(pb *ProposalBuilder) { pb.proposalOracle = o @@ -302,9 +294,6 @@ func (pb *ProposalBuilder) createProposal( MeshHash: pb.decideMeshHash(ctx, layerID), }, } - if p.EpochData != nil && layerID < pb.cfg.emitEmptyActiveSet { - p.ActiveSet = epochEligibility.ActiveSet - } p.Ballot.Signature = pb.signer.Sign(signing.BALLOT, p.Ballot.SignedBytes()) p.SmesherID = pb.signer.NodeID() p.Signature = pb.signer.Sign(signing.PROPOSAL, p.SignedBytes()) diff --git a/miner/util.go b/miner/util.go index be360094bd4..764c09a9213 100644 --- a/miner/util.go +++ b/miner/util.go @@ -7,6 +7,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/ballots" "github.com/spacemeshos/go-spacemesh/sql/blocks" "github.com/spacemeshos/go-spacemesh/sql/layers" @@ -33,7 +34,11 @@ func activeSetFromBlock(db sql.Executor, bid types.BlockID) ([]types.ATXID, erro if err != nil { return nil, fmt.Errorf("actives get ballot: %w", err) } - for _, id := range ballot.ActiveSet { + actives, err := activesets.Get(db, ballot.EpochData.ActiveSetHash) + if err != nil { + return nil, fmt.Errorf("actives get active hash for ballot %s: %w", ballot.ID().String(), err) + } + for _, id := range actives.Set { activeMap[id] = struct{}{} } } diff --git a/node/node.go b/node/node.go index c00aaa34bc5..4d3c27d4cdf 100644 --- a/node/node.go +++ b/node/node.go @@ -675,7 +675,6 @@ func (app *App) initServices(ctx context.Context) error { MaxExceptions: trtlCfg.MaxExceptions, Hdist: trtlCfg.Hdist, MinimalActiveSetWeight: trtlCfg.MinimalActiveSetWeight, - AllowEmptyActiveSet: trtlCfg.EmitEmptyActiveSet, }), ) @@ -805,7 +804,6 @@ func (app *App) initServices(ctx context.Context) error { miner.WithLayerSize(layerSize), miner.WithLayerPerEpoch(layersPerEpoch), miner.WithMinimalActiveSetWeight(app.Config.Tortoise.MinimalActiveSetWeight), - miner.WithEmitEmptyActiveSet(app.Config.Tortoise.EmitEmptyActiveSet), miner.WithHdist(app.Config.Tortoise.Hdist), miner.WithNetworkDelay(app.Config.HARE.WakeupDelta), miner.WithMinGoodAtxPct(minerGoodAtxPct), diff --git a/proposals/eligibility_validator.go b/proposals/eligibility_validator.go index 4174b3106f7..ca95088ad00 100644 --- a/proposals/eligibility_validator.go +++ b/proposals/eligibility_validator.go @@ -70,7 +70,7 @@ func NewEligibilityValidator( } // CheckEligibility checks that a ballot is eligible in the layer that it specifies. -func (v *Validator) CheckEligibility(ctx context.Context, ballot *types.Ballot) (bool, error) { +func (v *Validator) CheckEligibility(ctx context.Context, ballot *types.Ballot, actives []types.ATXID) (bool, error) { if len(ballot.EligibilityProofs) == 0 { return false, fmt.Errorf("empty eligibility list is invalid (ballot %s)", ballot.ID()) } @@ -92,7 +92,7 @@ func (v *Validator) CheckEligibility(ctx context.Context, ballot *types.Ballot) var data *types.EpochData if ballot.EpochData != nil && ballot.Layer.GetEpoch() == v.clock.CurrentLayer().GetEpoch() { var err error - data, err = v.validateReference(ballot, owned) + data, err = v.validateReference(ballot, actives, owned) if err != nil { return false, err } @@ -136,15 +136,15 @@ func (v *Validator) CheckEligibility(ctx context.Context, ballot *types.Ballot) } // validateReference executed for reference ballots in latest epoch. -func (v *Validator) validateReference(ballot *types.Ballot, owned *types.ActivationTxHeader) (*types.EpochData, error) { +func (v *Validator) validateReference(ballot *types.Ballot, actives []types.ATXID, owned *types.ActivationTxHeader) (*types.EpochData, error) { if ballot.EpochData.Beacon == types.EmptyBeacon { return nil, fmt.Errorf("%w: ref ballot %v", errMissingBeacon, ballot.ID()) } - if len(ballot.ActiveSet) == 0 { + if len(actives) == 0 { return nil, fmt.Errorf("%w: ref ballot %v", errEmptyActiveSet, ballot.ID()) } var totalWeight uint64 - for _, atxID := range ballot.ActiveSet { + for _, atxID := range actives { atx, err := v.cdb.GetAtxHeader(atxID) if err != nil { return nil, fmt.Errorf("atx in active set is missing %v: %w", atxID, err) diff --git a/proposals/eligibility_validator_test.go b/proposals/eligibility_validator_test.go index ecdc6ec4c6e..ce4a866e6af 100644 --- a/proposals/eligibility_validator_test.go +++ b/proposals/eligibility_validator_test.go @@ -64,14 +64,15 @@ func gatxNilNonce(id types.ATXID, epoch types.EpochID, smesher types.NodeID, uni return *verified } -func gdata(slots uint32, beacon types.Beacon) *types.EpochData { +func gdata(slots uint32, beacon types.Beacon, hash types.Hash32) *types.EpochData { return &types.EpochData{ Beacon: beacon, EligibilityCount: slots, + ActiveSetHash: hash, } } -func gactiveset(atxs ...types.ATXID) []types.ATXID { +func gactiveset(atxs ...types.ATXID) types.ATXIDList { return atxs } @@ -89,7 +90,7 @@ func geligibilityWithSig(j uint32, sig string) []types.VotingEligibility { return []types.VotingEligibility{el} } -func gballot(id types.BallotID, atxid types.ATXID, activeset []types.ATXID, smesher types.NodeID, layer types.LayerID, +func gballot(id types.BallotID, atxid types.ATXID, smesher types.NodeID, layer types.LayerID, edata *types.EpochData, eligibilities []types.VotingEligibility, ) types.Ballot { ballot := types.Ballot{} @@ -97,7 +98,6 @@ func gballot(id types.BallotID, atxid types.ATXID, activeset []types.ATXID, smes ballot.EpochData = edata ballot.AtxID = atxid ballot.EligibilityProofs = eligibilities - ballot.ActiveSet = activeset ballot.SmesherID = smesher ballot.SetID(id) return ballot @@ -128,6 +128,7 @@ func TestEligibilityValidator(t *testing.T) { current types.LayerID minWeight uint64 atxs []types.VerifiedActivationTx + actives types.ATXIDList ballots []types.Ballot vrfFailed bool executed types.Ballot @@ -141,9 +142,10 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish, types.NodeID{1}, 10, 10), gatx(types.ATXID{2}, publish, types.NodeID{2}, 10, 10), }, + actives: gactiveset(types.ATXID{1}, types.ATXID{2}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}, types.ATXID{2}), - types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}, gactiveset(types.ATXID{1}, types.ATXID{2}).Hash()), geligibilities(1, 2), ), }, @@ -155,9 +157,10 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish, types.NodeID{1}, 10, 10), gatx(types.ATXID{2}, publish, types.NodeID{2}, 10, 10), }, + actives: gactiveset(types.ATXID{1}, types.ATXID{2}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}, types.ATXID{2}), - types.NodeID{1}, epoch.FirstLayer(), gdata(3, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(3, types.Beacon{1}, gactiveset(types.ATXID{1}, types.ATXID{2}).Hash()), geligibilities(1, 2), ), }, @@ -167,9 +170,10 @@ func TestEligibilityValidator(t *testing.T) { atxs: []types.VerifiedActivationTx{ gatxZeroHeight(types.ATXID{1}, publish, types.NodeID{1}, 10, 10), }, + actives: gactiveset(types.ATXID{1}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}), - types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}, gactiveset(types.ATXID{1}).Hash()), geligibilities(1, 2), ), fail: true, @@ -182,16 +186,16 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish-1, types.NodeID{1}, 10, 10), }, executed: gballot( - types.BallotID{1}, types.ATXID{1}, nil, - types.NodeID{1}, publish.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, publish.FirstLayer(), gdata(15, types.Beacon{1}, types.Hash32{}), geligibilities(1, 2), ), }, { desc: "no eligibilities", executed: gballot( - types.BallotID{1}, types.ATXID{1}, nil, - types.NodeID{1}, publish.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, publish.FirstLayer(), gdata(15, types.Beacon{1}, types.Hash32{}), nil, ), fail: true, @@ -200,8 +204,8 @@ func TestEligibilityValidator(t *testing.T) { { desc: "no atx", executed: gballot( - types.BallotID{1}, types.ATXID{1}, nil, - types.NodeID{1}, publish.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, publish.FirstLayer(), gdata(15, types.Beacon{1}, types.Hash32{}), geligibilities(1, 2), ), fail: true, @@ -214,7 +218,7 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish-1, types.NodeID{1}, 10, 10), }, executed: gballot( - types.BallotID{1}, types.ATXID{1}, nil, + types.BallotID{1}, types.ATXID{1}, types.NodeID{1}, publish.FirstLayer(), nil, geligibilities(1, 2), ), @@ -228,7 +232,7 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, epoch-1, types.NodeID{1}, 10, 10), }, executed: gballot( - types.BallotID{1}, types.ATXID{1}, nil, + types.BallotID{1}, types.ATXID{1}, types.NodeID{1}, epoch.FirstLayer(), nil, geligibilities(1, 2), ), @@ -241,9 +245,10 @@ func TestEligibilityValidator(t *testing.T) { atxs: []types.VerifiedActivationTx{ gatx(types.ATXID{1}, epoch-1, types.NodeID{1}, 10, 10), }, + actives: types.ATXIDList{}, executed: gballot( - types.BallotID{1}, types.ATXID{1}, nil, - types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}, types.ATXIDList{}.Hash()), geligibilities(1, 2), ), fail: true, @@ -256,8 +261,8 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, epoch-1, types.NodeID{1}, 10, 10), }, executed: gballot( - types.BallotID{1}, types.ATXID{1}, nil, - types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.EmptyBeacon), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.EmptyBeacon, types.Hash32{}), geligibilities(1, 2), ), fail: true, @@ -269,9 +274,10 @@ func TestEligibilityValidator(t *testing.T) { atxs: []types.VerifiedActivationTx{ gatx(types.ATXID{1}, epoch-1, types.NodeID{1}, 10, 10), }, + actives: gactiveset(types.ATXID{1}, types.ATXID{2}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}, types.ATXID{2}), - types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}, gactiveset(types.ATXID{1}, types.ATXID{2}).Hash()), geligibilities(1, 2), ), fail: true, @@ -283,9 +289,10 @@ func TestEligibilityValidator(t *testing.T) { atxs: []types.VerifiedActivationTx{ gatx(types.ATXID{1}, epoch-1, types.NodeID{1}, 10, 0), }, + actives: gactiveset(types.ATXID{1}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}), - types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}, gactiveset(types.ATXID{1}).Hash()), geligibilities(1, 2), ), fail: true, @@ -297,9 +304,10 @@ func TestEligibilityValidator(t *testing.T) { atxs: []types.VerifiedActivationTx{ gatx(types.ATXID{1}, epoch-1, types.NodeID{1}, 10, 0), }, + actives: gactiveset(types.ATXID{1}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}), - types.NodeID{1}, (epoch + 1).FirstLayer(), gdata(10, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, (epoch + 1).FirstLayer(), gdata(10, types.Beacon{1}, gactiveset(types.ATXID{1}).Hash()), geligibilities(1, 2), ), fail: true, @@ -311,9 +319,10 @@ func TestEligibilityValidator(t *testing.T) { atxs: []types.VerifiedActivationTx{ gatx(types.ATXID{1}, epoch-1, types.NodeID{2}, 10, 0), }, + actives: gactiveset(types.ATXID{1}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}), - types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}, gactiveset(types.ATXID{1}).Hash()), geligibilities(1, 2), ), fail: true, @@ -325,9 +334,10 @@ func TestEligibilityValidator(t *testing.T) { atxs: []types.VerifiedActivationTx{ gatxNilNonce(types.ATXID{1}, epoch-1, types.NodeID{1}, 10), }, + actives: gactiveset(types.ATXID{1}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}), - types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(10, types.Beacon{1}, gactiveset(types.ATXID{1}).Hash()), geligibilities(1, 2), ), fail: true, @@ -342,10 +352,9 @@ func TestEligibilityValidator(t *testing.T) { ballots: []types.Ballot{ gballot( types.BallotID{1}, types.ATXID{1}, - nil, types.NodeID{1, 1, 1}, epoch.FirstLayer(), - gdata(10, types.Beacon{1}), + gdata(10, types.Beacon{1}, types.Hash32{}), nil, ), }, @@ -398,10 +407,9 @@ func TestEligibilityValidator(t *testing.T) { ballots: []types.Ballot{ gballot( types.BallotID{1}, types.ATXID{2}, - nil, types.NodeID{1, 1, 1}, epoch.FirstLayer(), - gdata(10, types.Beacon{1}), + gdata(10, types.Beacon{1}, types.Hash32{}), nil, ), }, @@ -424,10 +432,9 @@ func TestEligibilityValidator(t *testing.T) { ballots: []types.Ballot{ gballot( types.BallotID{1}, types.ATXID{1}, - nil, types.NodeID{2, 2, 2}, epoch.FirstLayer(), - gdata(10, types.Beacon{1}), + gdata(10, types.Beacon{1}, types.Hash32{}), nil, ), }, @@ -450,10 +457,9 @@ func TestEligibilityValidator(t *testing.T) { ballots: []types.Ballot{ gballot( types.BallotID{1}, types.ATXID{1}, - nil, types.NodeID{1, 1, 1}, (epoch + 1).FirstLayer(), - gdata(10, types.Beacon{1}), + gdata(10, types.Beacon{1}, types.Hash32{}), nil, ), }, @@ -474,9 +480,10 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish, types.NodeID{1}, 10, 10), gatx(types.ATXID{2}, publish, types.NodeID{2}, 10, 10), }, + actives: gactiveset(types.ATXID{1}, types.ATXID{2}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}, types.ATXID{2}), - types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}, gactiveset(types.ATXID{1}, types.ATXID{2}).Hash()), geligibilities(2, 1, 3), ), fail: true, @@ -489,9 +496,10 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish, types.NodeID{1}, 10, 10), gatx(types.ATXID{2}, publish, types.NodeID{2}, 10, 10), }, + actives: gactiveset(types.ATXID{1}, types.ATXID{2}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}, types.ATXID{2}), - types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}, gactiveset(types.ATXID{1}, types.ATXID{2}).Hash()), geligibilities(15), ), fail: true, @@ -504,9 +512,10 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish, types.NodeID{1}, 10, 10), gatx(types.ATXID{2}, publish, types.NodeID{2}, 10, 10), }, + actives: gactiveset(types.ATXID{1}, types.ATXID{2}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}, types.ATXID{2}), - types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}, gactiveset(types.ATXID{1}, types.ATXID{2}).Hash()), geligibilities(14), ), vrfFailed: true, @@ -520,9 +529,10 @@ func TestEligibilityValidator(t *testing.T) { gatx(types.ATXID{1}, publish, types.NodeID{1}, 10, 10), gatx(types.ATXID{2}, publish, types.NodeID{2}, 10, 10), }, + actives: gactiveset(types.ATXID{1}, types.ATXID{2}), executed: gballot( - types.BallotID{1}, types.ATXID{1}, gactiveset(types.ATXID{1}, types.ATXID{2}), - types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}), + types.BallotID{1}, types.ATXID{1}, + types.NodeID{1}, epoch.FirstLayer(), gdata(15, types.Beacon{1}, gactiveset(types.ATXID{1}, types.ATXID{2}).Hash()), geligibilityWithSig(1, "adjust layer"), ), fail: true, @@ -564,7 +574,7 @@ func TestEligibilityValidator(t *testing.T) { if !tc.fail { ms.mbc.EXPECT().ReportBeaconFromBallot(tc.executed.Layer.GetEpoch(), &tc.executed, gomock.Any(), gomock.Any()) } - rst, err := tv.CheckEligibility(context.Background(), &tc.executed) + rst, err := tv.CheckEligibility(context.Background(), &tc.executed, tc.actives) assert.Equal(t, !tc.fail, rst) if len(tc.err) == 0 { assert.Empty(t, err) diff --git a/proposals/handler.go b/proposals/handler.go index 4ee8787b931..0ef1ccc3710 100644 --- a/proposals/handler.go +++ b/proposals/handler.go @@ -66,7 +66,6 @@ type Config struct { MaxExceptions int Hdist uint32 MinimalActiveSetWeight uint64 - AllowEmptyActiveSet types.LayerID } // defaultConfig for BlockHandler. @@ -271,13 +270,6 @@ func (h *Handler) handleProposal(ctx context.Context, expHash types.Hash32, peer latency := receivedTime.Sub(h.clock.LayerToTime(p.Layer)) metrics.ReportMessageLatency(pubsub.ProposalProtocol, pubsub.ProposalProtocol, latency) - set := p.ActiveSet - // on sync path activeset is downloaded together with proposal - // but starting at layer AllowEmptyActiveSet proposals are signed without activeset - if p.Layer >= h.cfg.AllowEmptyActiveSet { - p.ActiveSet = nil - } - if !h.edVerifier.Verify(signing.PROPOSAL, p.SmesherID, p.SignedBytes(), p.Signature) { badSigProposal.Inc() return fmt.Errorf("failed to verify proposal signature") @@ -288,8 +280,6 @@ func (h *Handler) handleProposal(ctx context.Context, expHash types.Hash32, peer } // set the proposal ID when received - // It mustn't contain the active set if layer >= AllowEmptyActiveSet - // (p.Initialize uses SignedBytes again). if err := p.Initialize(); err != nil { failedInit.Inc() return errInitialize @@ -297,7 +287,6 @@ func (h *Handler) handleProposal(ctx context.Context, expHash types.Hash32, peer if expHash != (types.Hash32{}) && p.ID().AsHash32() != expHash { return fmt.Errorf("%w: proposal want %s, got %s", errWrongHash, expHash.ShortString(), p.ID().AsHash32().ShortString()) } - p.ActiveSet = set if p.AtxID == types.EmptyATXID || p.AtxID == h.cfg.GoldenATXID { badData.Inc() @@ -392,6 +381,7 @@ func (h *Handler) processBallot(ctx context.Context, logger log.Log, b *types.Ba if err != nil { return nil, err } + b.ActiveSet = nil t1 := time.Now() proof, err := h.mesh.AddBallot(ctx, b) @@ -415,7 +405,8 @@ func (h *Handler) processBallot(ctx context.Context, logger log.Log, b *types.Ba func (h *Handler) checkBallotSyntacticValidity(ctx context.Context, logger log.Log, b *types.Ballot) (*tortoise.DecodedBallot, error) { t0 := time.Now() - if err := h.checkBallotDataIntegrity(ctx, b); err != nil { + actives, err := h.checkBallotDataIntegrity(ctx, b) + if err != nil { badData.Inc() return nil, err } @@ -450,7 +441,7 @@ func (h *Handler) checkBallotSyntacticValidity(ctx context.Context, logger log.L ballotDuration.WithLabelValues(votes).Observe(float64(time.Since(t3))) t4 := time.Now() - if eligible, err := h.validator.CheckEligibility(ctx, b); err != nil || !eligible { + if eligible, err := h.validator.CheckEligibility(ctx, b, actives); err != nil || !eligible { notEligible.Inc() var reason string if err != nil { @@ -464,45 +455,43 @@ func (h *Handler) checkBallotSyntacticValidity(ctx context.Context, logger log.L return decoded, nil } -func (h *Handler) checkBallotDataIntegrity(ctx context.Context, b *types.Ballot) error { +func (h *Handler) checkBallotDataIntegrity(ctx context.Context, b *types.Ballot) ([]types.ATXID, error) { + var actives []types.ATXID if b.RefBallot == types.EmptyBallotID { // this is the smesher's first Ballot in this epoch, should contain EpochData if b.EpochData == nil { - return errMissingEpochData + return nil, errMissingEpochData } if b.EpochData.Beacon == types.EmptyBeacon { - return errMissingBeacon - } - if len(b.ActiveSet) == 0 && b.Layer < h.cfg.AllowEmptyActiveSet { - return fmt.Errorf("%w: empty active set ballot %s", pubsub.ErrValidationReject, b.ID().String()) + return nil, errMissingBeacon } + // TODO: remove after the network no longer populate ActiveSet in ballot. if len(b.ActiveSet) != 0 { - if err := h.handleSet(ctx, b.EpochData.ActiveSetHash, types.EpochActiveSet{ + set := types.EpochActiveSet{ Epoch: b.Layer.GetEpoch(), Set: b.ActiveSet, - }); err != nil { - return err } + if err := h.handleSet(ctx, b.EpochData.ActiveSetHash, set); err != nil { + return nil, err + } + actives = set.Set } else { if err := h.fetcher.GetActiveSet(ctx, b.EpochData.ActiveSetHash); err != nil { - return err + return nil, err } set, err := activesets.Get(h.cdb, b.EpochData.ActiveSetHash) if err != nil { - return err + return nil, err } if len(set.Set) == 0 { - return fmt.Errorf("%w: empty active set ballot %s", pubsub.ErrValidationReject, b.ID().String()) + return nil, fmt.Errorf("%w: empty active set ballot %s", pubsub.ErrValidationReject, b.ID().String()) } - // NOTE(dshulyak) sidecar is still stored in reference ballot, so that - // nodes that won't update on time will be able to download it - // with sync - b.ActiveSet = set.Set + actives = set.Set } } else if b.EpochData != nil { - return errUnexpectedEpochData + return nil, errUnexpectedEpochData } - return nil + return actives, nil } func (h *Handler) checkVotesConsistency(ctx context.Context, b *types.Ballot) error { diff --git a/proposals/handler_test.go b/proposals/handler_test.go index 79fc2e74114..f491b6e87fe 100644 --- a/proposals/handler_test.go +++ b/proposals/handler_test.go @@ -163,10 +163,9 @@ func withLayer(lid types.LayerID) createBallotOpt { } } -func withAnyRefData() createBallotOpt { +func withRefData(activeSet types.ATXIDList) createBallotOpt { return func(b *types.Ballot) { b.RefBallot = types.EmptyBallotID - activeSet := types.ATXIDList{types.RandomATXID(), types.RandomATXID()} sort.Slice(activeSet, func(i, j int) bool { return bytes.Compare(activeSet[i].Bytes(), activeSet[j].Bytes()) < 0 }) @@ -174,7 +173,6 @@ func withAnyRefData() createBallotOpt { ActiveSetHash: activeSet.Hash(), Beacon: types.RandomBeacon(), } - b.ActiveSet = activeSet } } @@ -256,16 +254,14 @@ func signAndInit(tb testing.TB, b *types.Ballot) *types.Ballot { return b } -func createRefBallot(t *testing.T) *types.Ballot { +func createRefBallot(t *testing.T, actives types.ATXIDList) *types.Ballot { t.Helper() b := types.RandomBallot() b.RefBallot = types.EmptyBallotID - activeSet := types.ATXIDList{types.ATXID{1, 2, 3}, types.ATXID{2, 3, 4}} b.EpochData = &types.EpochData{ - ActiveSetHash: activeSet.Hash(), + ActiveSetHash: actives.Hash(), Beacon: types.RandomBeacon(), } - b.ActiveSet = activeSet return b } @@ -347,7 +343,7 @@ func TestBallot_GoldenATXID(t *testing.T) { func TestBallot_RefBallotMissingEpochData(t *testing.T) { th := createTestHandlerNoopDecoder(t) - b := createRefBallot(t) + b := createRefBallot(t, types.ATXIDList{{1}, {2}}) b.EpochData = nil signAndInit(t, b) createAtx(t, th.cdb.Database, b.Layer.GetEpoch()-1, b.AtxID, b.SmesherID) @@ -359,7 +355,9 @@ func TestBallot_RefBallotMissingEpochData(t *testing.T) { func TestBallot_RefBallotMissingBeacon(t *testing.T) { th := createTestHandlerNoopDecoder(t) - b := createRefBallot(t) + activeSet := types.ATXIDList{{1}, {2}} + b := createRefBallot(t, activeSet) + require.NoError(t, activesets.Add(th.cdb, activeSet.Hash(), &types.EpochActiveSet{Set: activeSet})) b.EpochData.Beacon = types.EmptyBeacon signAndInit(t, b) createAtx(t, th.cdb.Database, b.Layer.GetEpoch()-1, b.AtxID, b.SmesherID) @@ -371,8 +369,8 @@ func TestBallot_RefBallotMissingBeacon(t *testing.T) { func TestBallot_RefBallotEmptyActiveSet(t *testing.T) { th := createTestHandlerNoopDecoder(t) - b := createRefBallot(t) - b.ActiveSet = nil + activeSet := types.ATXIDList{{1}, {2}} + b := createRefBallot(t, activeSet) signAndInit(t, b) data := codec.MustEncode(b) createAtx(t, th.cdb.Database, b.Layer.GetEpoch()-1, b.AtxID, b.SmesherID) @@ -382,10 +380,12 @@ func TestBallot_RefBallotEmptyActiveSet(t *testing.T) { require.ErrorIs(t, th.HandleSyncedBallot(context.Background(), b.ID().AsHash32(), peer, data), sql.ErrNotFound) } +// TODO: remove after the network no longer populate ActiveSet in ballot. func TestBallot_RefBallotDuplicateInActiveSet(t *testing.T) { th := createTestHandlerNoopDecoder(t) - b := createRefBallot(t) - b.ActiveSet = append(b.ActiveSet, b.ActiveSet[0]) + activeSet := types.ATXIDList{{1}, {2}, {1}} + b := createRefBallot(t, activeSet) + b.ActiveSet = activeSet signAndInit(t, b) createAtx(t, th.cdb.Database, b.Layer.GetEpoch()-1, b.AtxID, b.SmesherID) data := codec.MustEncode(b) @@ -394,13 +394,12 @@ func TestBallot_RefBallotDuplicateInActiveSet(t *testing.T) { require.ErrorContains(t, th.HandleSyncedBallot(context.Background(), b.ID().AsHash32(), peer, data), "not sorted") } +// TODO: remove after the network no longer populate ActiveSet in ballot. func TestBallot_RefBallotActiveSetNotSorted(t *testing.T) { th := createTestHandlerNoopDecoder(t) - b := createRefBallot(t) - b.ActiveSet = types.RandomActiveSet(11) - sort.Slice(b.ActiveSet, func(i, j int) bool { - return bytes.Compare(b.ActiveSet[i].Bytes(), b.ActiveSet[j].Bytes()) > 0 - }) + activeSet := types.ATXIDList(types.RandomActiveSet(11)) + b := createRefBallot(t, activeSet) + b.ActiveSet = activeSet signAndInit(t, b) createAtx(t, th.cdb.Database, b.Layer.GetEpoch()-1, b.AtxID, b.SmesherID) data := codec.MustEncode(b) @@ -409,18 +408,6 @@ func TestBallot_RefBallotActiveSetNotSorted(t *testing.T) { require.ErrorContains(t, th.HandleSyncedBallot(context.Background(), b.ID().AsHash32(), peer, data), "not sorted") } -func TestBallot_RefBallotBadActiveSetHash(t *testing.T) { - th := createTestHandlerNoopDecoder(t) - b := createRefBallot(t) - b.EpochData.ActiveSetHash = types.Hash32{} - signAndInit(t, b) - createAtx(t, th.cdb.Database, b.Layer.GetEpoch()-1, b.AtxID, b.SmesherID) - data := codec.MustEncode(b) - peer := p2p.Peer("buddy") - th.mf.EXPECT().RegisterPeerHashes(peer, collectHashes(*b)) - require.ErrorContains(t, th.HandleSyncedBallot(context.Background(), b.ID().AsHash32(), peer, data), "wrong hash") -} - func TestBallot_NotRefBallotButHasEpochData(t *testing.T) { th := createTestHandlerNoopDecoder(t) b := types.RandomBallot() @@ -507,8 +494,8 @@ func TestBallot_BallotDoubleVotedOutsideHdist(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{b.Votes.Base, b.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{b.AtxID}).Return(types.ATXIDList{b.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{b.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(_ context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, b.ID(), ballot.ID()) return true, nil }) @@ -666,8 +653,8 @@ func TestBallot_ErrorCheckingEligible(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{b.Votes.Base, b.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{b.AtxID}).Return(types.ATXIDList{b.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{b.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, b.ID(), ballot.ID()) return false, errors.New("unknown") }) @@ -695,8 +682,8 @@ func TestBallot_NotEligible(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{b.Votes.Base, b.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{b.AtxID}).Return(types.ATXIDList{b.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{b.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, b.ID(), ballot.ID()) return false, nil }) @@ -725,8 +712,8 @@ func TestBallot_Success(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{b.Votes.Base, b.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{b.AtxID}).Return(types.ATXIDList{b.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{b.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(_ context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, b.ID(), ballot.ID()) return true, nil }) @@ -759,8 +746,8 @@ func TestBallot_MaliciousProofIgnoredInSyncFlow(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{b.Votes.Base, b.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{b.AtxID}).Return(types.ATXIDList{b.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{b.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(_ context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, b.ID(), ballot.ID()) return true, nil }) @@ -778,10 +765,11 @@ func TestBallot_RefBallot(t *testing.T) { types.NewExistingBlock(types.BlockID{1}, types.InnerBlock{LayerIndex: lid.Sub(1)}), types.NewExistingBlock(types.BlockID{2}, types.InnerBlock{LayerIndex: lid.Sub(2)}), } + activeSet := types.ATXIDList{{1}, {2}, {3}} b := createBallot(t, withLayer(lid), withSupportBlocks(supported...), - withAnyRefData(), + withRefData(activeSet), ) createAtx(t, th.cdb.Database, b.Layer.GetEpoch()-1, b.AtxID, b.SmesherID) for _, blk := range supported { @@ -792,21 +780,19 @@ func TestBallot_RefBallot(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{b.Votes.Base}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(b.Layer.GetEpoch(), []types.ATXID{b.AtxID}).Return([]types.ATXID{b.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), []types.ATXID{b.AtxID}) - th.mf.EXPECT().GetActiveSet(gomock.Any(), b.EpochData.ActiveSetHash).DoAndReturn(func(_ context.Context, hash types.Hash32) error { + th.mf.EXPECT().GetActiveSet(gomock.Any(), activeSet.Hash()).DoAndReturn(func(_ context.Context, hash types.Hash32) error { return activesets.Add(th.cdb, hash, &types.EpochActiveSet{ Epoch: b.Layer.GetEpoch(), - Set: b.ActiveSet, + Set: activeSet, }) }) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), activeSet).DoAndReturn( + func(_ context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, b.ID(), ballot.ID()) return true, nil }) th.mm.EXPECT().AddBallot(context.Background(), b).Return(nil, nil) - received := *b - received.ActiveSet = nil - require.NoError(t, th.HandleSyncedBallot(context.Background(), b.ID().AsHash32(), peer, codec.MustEncode(&received))) + require.NoError(t, th.HandleSyncedBallot(context.Background(), b.ID().AsHash32(), peer, codec.MustEncode(b))) } func TestBallot_DecodeBeforeVotesConsistency(t *testing.T) { @@ -847,7 +833,7 @@ func TestBallot_DecodedStoreFailure(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{b.Votes.Base, b.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{b.AtxID}).Return(types.ATXIDList{b.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{b.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).Return(true, nil).Times(1) + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).Return(true, nil).Times(1) decoded := &tortoise.DecodedBallot{BallotTortoiseData: b.ToTortoiseData()} th.md.EXPECT().DecodeBallot(decoded.BallotTortoiseData).Return(decoded, nil) @@ -972,8 +958,8 @@ func TestProposal_DuplicateTXs(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{p.Votes.Base, p.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{p.AtxID}).Return(types.ATXIDList{p.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{p.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, p.Ballot.ID(), ballot.ID()) return true, nil }) @@ -1007,8 +993,8 @@ func TestProposal_TXsNotAvailable(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{p.Votes.Base, p.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{p.AtxID}).Return(types.ATXIDList{p.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{p.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, p.Ballot.ID(), ballot.ID()) return true, nil }) @@ -1045,8 +1031,8 @@ func TestProposal_FailedToAddProposalTXs(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{p.Votes.Base, p.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{p.AtxID}).Return(types.ATXIDList{p.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{p.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, p.Ballot.ID(), ballot.ID()) return true, nil }) @@ -1086,8 +1072,8 @@ func TestProposal_ProposalGossip_Concurrent(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{p.Votes.Base, p.RefBallot}).Return(nil).MinTimes(1).MaxTimes(2) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{p.AtxID}).Return(types.ATXIDList{p.AtxID}).MinTimes(1).MaxTimes(2) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{p.AtxID}).Return(nil).MinTimes(1).MaxTimes(2) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, p.Ballot.ID(), ballot.ID()) return true, nil }).MinTimes(1).MaxTimes(2) @@ -1146,8 +1132,8 @@ func TestProposal_BroadcastMaliciousGossip(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{pMal.Votes.Base, pMal.RefBallot}) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{pMal.AtxID}).Return(types.ATXIDList{pMal.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{pMal.AtxID}) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, pMal.Ballot.ID(), ballot.ID()) return true, nil }) @@ -1220,8 +1206,8 @@ func TestProposal_ProposalGossip_Fetched(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{p.Votes.Base, p.RefBallot}).Return(nil) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{p.AtxID}).Return(types.ATXIDList{p.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{p.AtxID}).Return(nil) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, p.Ballot.ID(), ballot.ID()) if tc.propFetched { // a separate goroutine fetched the propFetched and saved it to database @@ -1265,8 +1251,8 @@ func TestProposal_ValidProposal(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{p.Votes.Base, p.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{p.AtxID}).Return(types.ATXIDList{p.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{p.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, p.Ballot.ID(), ballot.ID()) return true, nil }) @@ -1302,8 +1288,8 @@ func TestMetrics(t *testing.T) { th.mf.EXPECT().GetBallots(gomock.Any(), []types.BallotID{p.Votes.Base, p.RefBallot}).Return(nil).Times(1) th.md.EXPECT().GetMissingActiveSet(gomock.Any(), types.ATXIDList{p.AtxID}).Return(types.ATXIDList{p.AtxID}) th.mf.EXPECT().GetAtxs(gomock.Any(), types.ATXIDList{p.AtxID}).Return(nil).Times(1) - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, ballot *types.Ballot) (bool, error) { + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), nil).DoAndReturn( + func(ctx context.Context, ballot *types.Ballot, _ []types.ATXID) (bool, error) { require.Equal(t, p.Ballot.ID(), ballot.ID()) return true, nil }) @@ -1416,13 +1402,12 @@ func TestHandleActiveSet(t *testing.T) { } } -func gproposal(signer *signing.EdSigner, atxid types.ATXID, activeset []types.ATXID, +func gproposal(signer *signing.EdSigner, atxid types.ATXID, layer types.LayerID, edata *types.EpochData, ) types.Proposal { p := types.Proposal{} p.Layer = layer p.AtxID = atxid - p.ActiveSet = activeset p.EpochData = edata p.Ballot.Signature = signer.Sign(signing.BALLOT, p.Ballot.SignedBytes()) p.Ballot.SmesherID = signer.NodeID() @@ -1435,85 +1420,35 @@ func TestHandleSyncedProposalActiveSet(t *testing.T) { signer, err := signing.NewEdSigner() require.NoError(t, err) - set := []types.ATXID{{1}, {2}} - const acceptEmpty = 20 - good := gproposal(signer, types.ATXID{1}, set, acceptEmpty-1, &types.EpochData{ - ActiveSetHash: types.ATXIDList(set).Hash(), + set := types.ATXIDList{{1}, {2}} + lid := types.LayerID(20) + good := gproposal(signer, types.ATXID{1}, lid, &types.EpochData{ + ActiveSetHash: set.Hash(), Beacon: types.Beacon{1}, }) require.NoError(t, good.Initialize()) - - woActive := good - woActive.ActiveSet = nil - - notSigned := gproposal(signer, types.ATXID{1}, nil, acceptEmpty, &types.EpochData{ - ActiveSetHash: types.ATXIDList(set).Hash(), - Beacon: types.Beacon{1}, - }) - require.NoError(t, notSigned.Initialize()) - notSigned.ActiveSet = set - - notSignedEmpty := notSigned - notSignedEmpty.ActiveSet = nil - for _, tc := range []struct { - desc string - data []byte - id types.Hash32 - requestSet []types.ATXID - err string - }{ - { - desc: "before empty signed active set", - data: codec.MustEncode(&good), - id: good.ID().AsHash32(), - }, - { - desc: "before empty without active set", - data: codec.MustEncode(&woActive), - id: woActive.ID().AsHash32(), - err: "failed to verify proposal signature", - }, - { - desc: "after empty not signed active set", - data: codec.MustEncode(¬Signed), - id: notSigned.ID().AsHash32(), - }, - { - desc: "after empty not signed active set empty", - data: codec.MustEncode(¬SignedEmpty), - id: notSigned.ID().AsHash32(), - requestSet: set, + th := createTestHandler(t) + pid := p2p.Peer("any") + + th.mm.EXPECT().ProcessedLayer().Return(lid - 2).AnyTimes() + th.mclock.EXPECT().LayerToTime(gomock.Any()) + th.mf.EXPECT().RegisterPeerHashes(pid, gomock.Any()).AnyTimes() + th.md.EXPECT().GetMissingActiveSet(gomock.Any(), gomock.Any()).AnyTimes() + th.mf.EXPECT().GetActiveSet(gomock.Any(), set.Hash()).DoAndReturn( + func(_ context.Context, got types.Hash32) error { + require.NoError(t, activesets.Add(th.cdb, got, &types.EpochActiveSet{ + Set: set, + })) + return nil }, - } { - t.Run(tc.desc, func(t *testing.T) { - th := createTestHandler(t) - th.mm.EXPECT().ProcessedLayer().Return(acceptEmpty - 2).AnyTimes() - th.cfg.AllowEmptyActiveSet = acceptEmpty - pid := p2p.Peer("any") + ) + th.mf.EXPECT().GetAtxs(gomock.Any(), gomock.Any()).AnyTimes() + th.mf.EXPECT().GetBallots(gomock.Any(), gomock.Any()).AnyTimes() + th.mockSet.decodeAnyBallots() + th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(true, nil) + th.mm.EXPECT().AddBallot(gomock.Any(), gomock.Any()).AnyTimes() + th.mm.EXPECT().AddTXsFromProposal(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() - th.mclock.EXPECT().LayerToTime(gomock.Any()) - th.mf.EXPECT().RegisterPeerHashes(pid, gomock.Any()).AnyTimes() - th.md.EXPECT().GetMissingActiveSet(gomock.Any(), gomock.Any()).AnyTimes() - if tc.requestSet != nil { - id := types.ATXIDList(tc.requestSet).Hash() - th.mf.EXPECT().GetActiveSet(gomock.Any(), id) - require.NoError(t, activesets.Add(th.cdb, id, &types.EpochActiveSet{ - Set: tc.requestSet, - })) - } - th.mf.EXPECT().GetAtxs(gomock.Any(), gomock.Any()).AnyTimes() - th.mf.EXPECT().GetBallots(gomock.Any(), gomock.Any()).AnyTimes() - th.mockSet.decodeAnyBallots() - th.mv.EXPECT().CheckEligibility(gomock.Any(), gomock.Any()).AnyTimes().Return(true, nil) - th.mm.EXPECT().AddBallot(gomock.Any(), gomock.Any()).AnyTimes() - th.mm.EXPECT().AddTXsFromProposal(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() - - err := th.HandleSyncedProposal(context.Background(), tc.id, pid, tc.data) - if len(tc.err) > 0 { - require.ErrorContains(t, err, tc.err) - } else { - require.NoError(t, err) - } - }) - } + err = th.HandleSyncedProposal(context.Background(), good.ID().AsHash32(), pid, codec.MustEncode(&good)) + require.NoError(t, err) } diff --git a/proposals/interface.go b/proposals/interface.go index 32072abd8ee..2b33cd3a3c0 100644 --- a/proposals/interface.go +++ b/proposals/interface.go @@ -17,7 +17,7 @@ type meshProvider interface { } type eligibilityValidator interface { - CheckEligibility(context.Context, *types.Ballot) (bool, error) + CheckEligibility(context.Context, *types.Ballot, []types.ATXID) (bool, error) } type tortoiseProvider interface { diff --git a/proposals/mocks.go b/proposals/mocks.go index 4d24033af2d..72d52582e68 100644 --- a/proposals/mocks.go +++ b/proposals/mocks.go @@ -176,18 +176,18 @@ func (m *MockeligibilityValidator) EXPECT() *MockeligibilityValidatorMockRecorde } // CheckEligibility mocks base method. -func (m *MockeligibilityValidator) CheckEligibility(arg0 context.Context, arg1 *types.Ballot) (bool, error) { +func (m *MockeligibilityValidator) CheckEligibility(arg0 context.Context, arg1 *types.Ballot, arg2 []types.ATXID) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CheckEligibility", arg0, arg1) + ret := m.ctrl.Call(m, "CheckEligibility", arg0, arg1, arg2) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } // CheckEligibility indicates an expected call of CheckEligibility. -func (mr *MockeligibilityValidatorMockRecorder) CheckEligibility(arg0, arg1 interface{}) *eligibilityValidatorCheckEligibilityCall { +func (mr *MockeligibilityValidatorMockRecorder) CheckEligibility(arg0, arg1, arg2 interface{}) *eligibilityValidatorCheckEligibilityCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckEligibility", reflect.TypeOf((*MockeligibilityValidator)(nil).CheckEligibility), arg0, arg1) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckEligibility", reflect.TypeOf((*MockeligibilityValidator)(nil).CheckEligibility), arg0, arg1, arg2) return &eligibilityValidatorCheckEligibilityCall{Call: call} } @@ -203,13 +203,13 @@ func (c *eligibilityValidatorCheckEligibilityCall) Return(arg0 bool, arg1 error) } // Do rewrite *gomock.Call.Do -func (c *eligibilityValidatorCheckEligibilityCall) Do(f func(context.Context, *types.Ballot) (bool, error)) *eligibilityValidatorCheckEligibilityCall { +func (c *eligibilityValidatorCheckEligibilityCall) Do(f func(context.Context, *types.Ballot, []types.ATXID) (bool, error)) *eligibilityValidatorCheckEligibilityCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *eligibilityValidatorCheckEligibilityCall) DoAndReturn(f func(context.Context, *types.Ballot) (bool, error)) *eligibilityValidatorCheckEligibilityCall { +func (c *eligibilityValidatorCheckEligibilityCall) DoAndReturn(f func(context.Context, *types.Ballot, []types.ATXID) (bool, error)) *eligibilityValidatorCheckEligibilityCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/proposals/util/util.go b/proposals/util/util.go index 56c4618106d..55d5405f691 100644 --- a/proposals/util/util.go +++ b/proposals/util/util.go @@ -8,6 +8,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/ballots" ) @@ -61,16 +62,20 @@ func ComputeWeightPerEligibility( return nil, fmt.Errorf("%w: missing ref ballot %s (for %s)", err, ballot.RefBallot, ballot.ID()) } } - if len(refBallot.ActiveSet) == 0 { - return nil, fmt.Errorf("ref ballot missing active set %s (for %s)", ballot.RefBallot, ballot.ID()) - } if refBallot.EpochData == nil { return nil, fmt.Errorf("epoch data is nil on ballot %d/%s", refBallot.Layer, refBallot.ID()) } if refBallot.EpochData.EligibilityCount == 0 { return nil, fmt.Errorf("eligibility count is 0 on ballot %d/%s", refBallot.Layer, refBallot.ID()) } - for _, atxID := range refBallot.ActiveSet { + actives, err := activesets.Get(cdb, refBallot.EpochData.ActiveSetHash) + if err != nil { + return nil, fmt.Errorf("get active set %s (%s)", refBallot.EpochData.ActiveSetHash.ShortString(), refBallot.ID()) + } + if len(actives.Set) == 0 { + return nil, fmt.Errorf("empty active set %s (%s)", refBallot.EpochData.ActiveSetHash.ShortString(), refBallot.ID()) + } + for _, atxID := range actives.Set { hdr, err = cdb.GetAtxHeader(atxID) if err != nil { return nil, fmt.Errorf("%w: missing atx %s in active set of %s (for %s)", err, atxID, refBallot.ID(), ballot.ID()) diff --git a/proposals/util_test.go b/proposals/util_test.go index 16916b96fb4..196dc286204 100644 --- a/proposals/util_test.go +++ b/proposals/util_test.go @@ -16,6 +16,7 @@ import ( putil "github.com/spacemeshos/go-spacemesh/proposals/util" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/activesets" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/ballots" ) @@ -72,7 +73,6 @@ func createBallots(tb testing.TB, signer *signing.EdSigner, activeSet types.ATXI Beacon: beacon, EligibilityCount: eligibleSlots, } - b.ActiveSet = activeSet } else { b.RefBallot = blts[0].ID() } @@ -90,12 +90,14 @@ func TestComputeWeightPerEligibility(t *testing.T) { signing.WithKeyFromRand(rand.New(rand.NewSource(1001))), ) require.NoError(t, err) + cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) beacon := types.Beacon{1, 1, 1} - blts := createBallots(t, signer, genActiveSet(), beacon) + actives := genActiveSet() + blts := createBallots(t, signer, actives, beacon) rb := blts[0] - cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) + activesets.Add(cdb, rb.EpochData.ActiveSetHash, &types.EpochActiveSet{Set: actives}) require.NoError(t, ballots.Add(cdb, rb)) - for _, id := range rb.ActiveSet { + for _, id := range actives { atx := &types.ActivationTx{InnerActivationTx: types.InnerActivationTx{ NIPostChallenge: types.NIPostChallenge{ PublishEpoch: epoch - 1, @@ -158,9 +160,12 @@ func TestComputeWeightPerEligibility_FailATX(t *testing.T) { ) require.NoError(t, err) beacon := types.Beacon{1, 1, 1} - blts := createBallots(t, signer, genActiveSet(), beacon) cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(t)) - got, err := ComputeWeightPerEligibility(cdb, blts[0]) + actives := genActiveSet() + blts := createBallots(t, signer, actives, beacon) + rb := blts[0] + activesets.Add(cdb, rb.EpochData.ActiveSetHash, &types.EpochActiveSet{Set: actives}) + got, err := ComputeWeightPerEligibility(cdb, rb) require.ErrorIs(t, err, sql.ErrNotFound) require.True(t, strings.Contains(err.Error(), "missing atx")) require.Nil(t, got) diff --git a/tortoise/algorithm.go b/tortoise/algorithm.go index 77069b4bcef..4a3998610a2 100644 --- a/tortoise/algorithm.go +++ b/tortoise/algorithm.go @@ -27,8 +27,7 @@ type Config struct { // MinimalActiveSetWeight denotes weight that will replace weight // recorded in the first ballot, if that weight is less than minimal // for purposes of eligibility computation. - MinimalActiveSetWeight uint64 `mapstructure:"tortoise-activeset-weight"` - EmitEmptyActiveSet types.LayerID `mapstructure:"emit-empty-active-set"` + MinimalActiveSetWeight uint64 `mapstructure:"tortoise-activeset-weight"` LayerSize uint32 } diff --git a/tortoise/tortoise_test.go b/tortoise/tortoise_test.go index b3adfe2be5e..f884ee00571 100644 --- a/tortoise/tortoise_test.go +++ b/tortoise/tortoise_test.go @@ -861,7 +861,6 @@ func TestDecodeVotes(t *testing.T) { require.NoError(t, err) ballot := types.NewExistingBallot(types.BallotID{3, 3, 3}, types.EmptyEdSignature, types.EmptyNodeID, ballots[0].Layer) ballot.InnerBallot = ballots[0].InnerBallot - ballot.ActiveSet = ballots[0].ActiveSet hasher := opinionhash.New() supported := types.BlockID{2, 2, 2} hasher.WriteSupport(supported, 0) @@ -2284,7 +2283,6 @@ func TestSwitchMode(t *testing.T) { ballot := types.NewExistingBallot(types.BallotID{byte(i)}, types.EmptyEdSignature, types.EmptyNodeID, template.Layer) ballot.InnerBallot = template.InnerBallot ballot.EligibilityProofs = template.EligibilityProofs - ballot.ActiveSet = template.ActiveSet tortoise.OnBallot(ballot.ToTortoiseData()) } tortoise.TallyVotes(ctx, last) @@ -2332,7 +2330,6 @@ func TestOnBallotComputeOpinion(t *testing.T) { ballot := types.NewExistingBallot(id, types.EmptyEdSignature, types.EmptyNodeID, rst[0].Layer) ballot.InnerBallot = rst[0].InnerBallot ballot.EligibilityProofs = rst[0].EligibilityProofs - ballot.ActiveSet = rst[0].ActiveSet ballot.Votes.Base = types.EmptyBallotID ballot.Votes.Support = nil ballot.Votes.Against = nil