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

[Merged by Bors] - use atx grading for hare #4718

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions beacon/beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ func (pd *ProtocolDriver) initEpochStateIfNotPresent(logger log.Log, epoch types
ontime = pd.clock.LayerToTime(epoch.FirstLayer())
early = ontime.Add(-1 * pd.config.GracePeriodDuration)
)
if err := pd.cdb.IterateEpochATXHeaders(epoch, func(header *types.ActivationTxHeader) bool {
if err := pd.cdb.IterateEpochATXHeaders(epoch, func(header *types.ActivationTxHeader) error {
epochWeight += header.GetWeight()
if _, ok := miners[header.NodeID]; !ok {
miners[header.NodeID] = header.ID
Expand All @@ -563,7 +563,7 @@ func (pd *ProtocolDriver) initEpochStateIfNotPresent(logger log.Log, epoch types
if header.NodeID == pd.nodeID {
active = true
}
return true
return nil
}); err != nil {
return nil, err
}
Expand Down
6 changes: 0 additions & 6 deletions datastore/interface.go

This file was deleted.

12 changes: 6 additions & 6 deletions datastore/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,18 @@
weight uint64
ids []types.ATXID
)
if err := db.IterateEpochATXHeaders(epoch, func(header *types.ActivationTxHeader) bool {
if err := db.IterateEpochATXHeaders(epoch, func(header *types.ActivationTxHeader) error {
weight += header.GetWeight()
ids = append(ids, header.ID)
return true
return nil
}); err != nil {
return 0, nil, err
}
return weight, ids, nil
}

// IterateEpochATXHeaders iterates over ActivationTxs that target an epoch.
func (db *CachedDB) IterateEpochATXHeaders(epoch types.EpochID, iter func(*types.ActivationTxHeader) bool) error {
func (db *CachedDB) IterateEpochATXHeaders(epoch types.EpochID, iter func(*types.ActivationTxHeader) error) error {
ids, err := atxs.GetIDsByEpoch(db, epoch-1)
if err != nil {
return err
Expand All @@ -221,8 +221,8 @@
if err != nil {
return err
}
if !iter(header) {
return nil
if err := iter(header); err != nil {
return err

Check warning on line 225 in datastore/store.go

View check run for this annotation

Codecov / codecov/patch

datastore/store.go#L225

Added line #L225 was not covered by tests
}
}
return nil
Expand All @@ -239,7 +239,7 @@
}
}

// GetEpochAtx gets the atx header of specified node ID in the specified epoch.
// GetEpochAtx gets the atx header of specified node ID published in the specified epoch.
func (db *CachedDB) GetEpochAtx(epoch types.EpochID, nodeID types.NodeID) (*types.ActivationTxHeader, error) {
vatx, err := atxs.GetByEpochAndNodeID(db, epoch, nodeID)
if err != nil {
Expand Down
29 changes: 5 additions & 24 deletions hare/eligibility/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import (
"github.com/spacemeshos/go-spacemesh/datastore"
"github.com/spacemeshos/go-spacemesh/hare/eligibility/config"
"github.com/spacemeshos/go-spacemesh/log"
"github.com/spacemeshos/go-spacemesh/miner"
"github.com/spacemeshos/go-spacemesh/signing"
"github.com/spacemeshos/go-spacemesh/sql"
"github.com/spacemeshos/go-spacemesh/sql/ballots"
"github.com/spacemeshos/go-spacemesh/sql/blocks"
"github.com/spacemeshos/go-spacemesh/sql/certificates"
"github.com/spacemeshos/go-spacemesh/system"
)

Expand Down Expand Up @@ -393,14 +392,15 @@ func (o *Oracle) computeActiveSet(ctx context.Context, targetEpoch types.EpochID
)
return activeSet, nil
}
bid, err := certificates.FirstInEpoch(o.cdb, targetEpoch)

activeSet, err := miner.ActiveSetFromBlock(o.cdb, targetEpoch)
if err != nil && !errors.Is(err, sql.ErrNotFound) {
return nil, err
}
if bid == types.EmptyBlockID {
if len(activeSet) == 0 {
return o.activeSetFromRefBallots(targetEpoch)
}
return o.activeSetFromBlock(bid)
return activeSet, nil
}

func (o *Oracle) computeActiveWeights(targetEpoch types.EpochID, activeSet []types.ATXID) (map[types.NodeID]uint64, error) {
Expand All @@ -415,25 +415,6 @@ func (o *Oracle) computeActiveWeights(targetEpoch types.EpochID, activeSet []typ
return weightedActiveSet, nil
}

func (o *Oracle) activeSetFromBlock(bid types.BlockID) ([]types.ATXID, error) {
block, err := blocks.Get(o.cdb, bid)
if err != nil {
return nil, fmt.Errorf("actives get block: %w", err)
}
activeMap := make(map[types.ATXID]struct{})
for _, r := range block.Rewards {
// only the reference ballots record the active set
ballot, err := ballots.FirstInEpoch(o.cdb, r.AtxID, block.LayerIndex.GetEpoch())
if err != nil {
return nil, fmt.Errorf("actives get ballot: %w", err)
}
for _, id := range ballot.ActiveSet {
activeMap[id] = struct{}{}
}
}
return maps.Keys(activeMap), nil
}

func (o *Oracle) activeSetFromRefBallots(epoch types.EpochID) ([]types.ATXID, error) {
beacon, err := o.beacons.GetBeacon(epoch)
if err != nil {
Expand Down
10 changes: 2 additions & 8 deletions hare/flows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,7 @@ func Test_multipleCPs(t *testing.T) {
pList := make(map[types.LayerID][]*types.Proposal)
for j := types.GetEffectiveGenesis().Add(1); !j.After(finalLyr); j = j.Add(1) {
for i := uint64(0); i < 20; i++ {
p := genLayerProposal(j, []types.TransactionID{})
p.EpochData = &types.EpochData{
Beacon: types.EmptyBeacon,
}
p := randomProposal(j, types.EmptyBeacon)
pList[j] = append(pList[j], p)
}
}
Expand Down Expand Up @@ -378,10 +375,7 @@ func Test_multipleCPsAndIterations(t *testing.T) {
pList := make(map[types.LayerID][]*types.Proposal)
for j := types.GetEffectiveGenesis().Add(1); !j.After(finalLyr); j = j.Add(1) {
for i := uint64(0); i < 20; i++ {
p := genLayerProposal(j, []types.TransactionID{})
p.EpochData = &types.EpochData{
Beacon: types.EmptyBeacon,
}
p := randomProposal(j, types.EmptyBeacon)
pList[j] = append(pList[j], p)
}
}
Expand Down
85 changes: 79 additions & 6 deletions hare/hare.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"github.com/spacemeshos/go-spacemesh/hare/config"
"github.com/spacemeshos/go-spacemesh/log"
"github.com/spacemeshos/go-spacemesh/malfeasance"
"github.com/spacemeshos/go-spacemesh/miner"
"github.com/spacemeshos/go-spacemesh/p2p/pubsub"
"github.com/spacemeshos/go-spacemesh/signing"
"github.com/spacemeshos/go-spacemesh/sql"
Expand Down Expand Up @@ -388,7 +389,7 @@
report: h.outputChan,
wc: h.wcChan,
}
props := goodProposals(ctx, h.Log, h.msh, h.nodeID, lid, beacon)
props := goodProposals(ctx, h.Log, h.msh, h.nodeID, lid, beacon, h.layerClock.LayerToTime(lid.GetEpoch().FirstLayer()), h.config.WakeupDelta)
preNumProposals.Add(float64(len(props)))
set := NewSet(props)
cp := h.factory(ctx, h.config, lid, set, h.rolacle, et, h.sign, h.publisher, comm, clock)
Expand Down Expand Up @@ -441,9 +442,21 @@
}

// goodProposals finds the "good proposals" for the specified layer. a proposal is good if
// it has the same beacon value as the node's beacon value.
// - it has the same beacon value as the node's beacon value.
// - its miner is not malicious
// - its active set contains only grade 1 or grade 2 atxs
// see (https://community.spacemesh.io/t/grading-atxs-for-the-active-set/335#proposal-voting-4)
// any error encountered will be ignored and an empty set is returned.
func goodProposals(ctx context.Context, logger log.Log, msh mesh, nodeID types.NodeID, lid types.LayerID, epochBeacon types.Beacon) []types.ProposalID {
func goodProposals(
fasmat marked this conversation as resolved.
Show resolved Hide resolved
ctx context.Context,
logger log.Log,
msh mesh,
nodeID types.NodeID,
lid types.LayerID,
epochBeacon types.Beacon,
epochStart time.Time,
networkDelay time.Duration,
) []types.ProposalID {
props, err := msh.Proposals(lid)
if err != nil {
if errors.Is(err, sql.ErrNotFound) {
Expand All @@ -456,6 +469,7 @@

var (
beacon types.Beacon
activeSet []types.ATXID
result []types.ProposalID
ownHdr *types.ActivationTxHeader
ownTickHeight = uint64(math.MaxUint64)
Expand Down Expand Up @@ -508,17 +522,59 @@
}
if p.EpochData != nil {
beacon = p.EpochData.Beacon
activeSet = p.ActiveSet
} else if p.RefBallot == types.EmptyBallotID {
logger.With().Error("proposal missing ref ballot", p.ID())
logger.With().Error("proposal missing ref ballot",
log.Context(ctx),
lid,
p.ID(),
)

Check warning on line 531 in hare/hare.go

View check run for this annotation

Codecov / codecov/patch

hare/hare.go#L527-L531

Added lines #L527 - L531 were not covered by tests
return []types.ProposalID{}
} else if refBallot, err := msh.Ballot(p.RefBallot); err != nil {
logger.With().Error("failed to get ref ballot", p.ID(), p.RefBallot, log.Err(err))
logger.With().Error("failed to get ref ballot",
log.Context(ctx),
lid,
p.ID(),
p.RefBallot,
log.Err(err))

Check warning on line 539 in hare/hare.go

View check run for this annotation

Codecov / codecov/patch

hare/hare.go#L534-L539

Added lines #L534 - L539 were not covered by tests
return []types.ProposalID{}
} else if refBallot.EpochData == nil {
logger.With().Error("ref ballot missing epoch data", log.Context(ctx), lid, refBallot.ID())
logger.With().Error("ref ballot missing epoch data",
log.Context(ctx),
p.ID(),
lid,
refBallot.ID(),
)

Check warning on line 547 in hare/hare.go

View check run for this annotation

Codecov / codecov/patch

hare/hare.go#L542-L547

Added lines #L542 - L547 were not covered by tests
return []types.ProposalID{}
} else {
beacon = refBallot.EpochData.Beacon
activeSet = refBallot.ActiveSet
}

if len(activeSet) == 0 {
logger.With().Error("proposal missing active set",
log.Context(ctx),
p.ID(),
lid,
)
return []types.ProposalID{}
}

Check warning on line 561 in hare/hare.go

View check run for this annotation

Codecov / codecov/patch

hare/hare.go#L555-L561

Added lines #L555 - L561 were not covered by tests
if evil, err := gradeActiveSet(activeSet, msh, epochStart, networkDelay); err != nil {
logger.With().Error("failed to grade active set",
log.Context(ctx),
lid,
p.ID(),
log.Err(err),
)
return []types.ProposalID{}

Check warning on line 569 in hare/hare.go

View check run for this annotation

Codecov / codecov/patch

hare/hare.go#L563-L569

Added lines #L563 - L569 were not covered by tests
} else if evil != types.EmptyATXID {
logger.With().Warning("proposal has grade 0 active set",
log.Context(ctx),
lid,
p.ID(),
log.Stringer("evil atx", evil),
)
continue
}

if beacon == epochBeacon {
Expand All @@ -535,6 +591,23 @@
return result
}

func gradeActiveSet(activeSet []types.ATXID, msh mesh, epochStart time.Time, networkDelay time.Duration) (types.ATXID, error) {
for _, id := range activeSet {
hdr, err := msh.GetAtxHeader(id)
if err != nil {
return types.EmptyATXID, fmt.Errorf("get header %v: %s", id, err)
}

Check warning on line 599 in hare/hare.go

View check run for this annotation

Codecov / codecov/patch

hare/hare.go#L598-L599

Added lines #L598 - L599 were not covered by tests
grade, err := miner.GradeAtx(msh, hdr.NodeID, hdr.Received, epochStart, networkDelay)
if err != nil {
return types.EmptyATXID, fmt.Errorf("grade %v: %w", id, err)
}

Check warning on line 603 in hare/hare.go

View check run for this annotation

Codecov / codecov/patch

hare/hare.go#L602-L603

Added lines #L602 - L603 were not covered by tests
if grade == miner.Evil {
return id, nil
}
}
return types.EmptyATXID, nil
}

var (
errTooOld = errors.New("layer has already been evacuated from buffer")
errNoResult = errors.New("no result for the requested layer")
Expand Down
5 changes: 3 additions & 2 deletions hare/hare_rounds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ func runNodesFor(t *testing.T, ctx context.Context, nodes, leaders, maxLayers, l
mockMesh := mocks.NewMockmesh(gomock.NewController(t))
if createProposal {
for lid := types.GetEffectiveGenesis().Add(1); !lid.After(types.GetEffectiveGenesis().Add(uint32(maxLayers))); lid = lid.Add(1) {
p := genLayerProposal(lid, []types.TransactionID{})
p := randomProposal(lid, types.RandomBeacon())
mockMesh.EXPECT().Ballot(p.Ballot.ID()).Return(&p.Ballot, nil).AnyTimes()
mockMesh.EXPECT().Proposals(lid).Return([]*types.Proposal{p}, nil).AnyTimes()
mockMesh.EXPECT().GetAtxHeader(p.AtxID).Return(&types.ActivationTxHeader{BaseTickHeight: 11, TickCount: 1}, nil).AnyTimes()
mockMesh.EXPECT().GetAtxHeader(p.AtxID).Return(&types.ActivationTxHeader{BaseTickHeight: 11, TickCount: 1, NodeID: p.SmesherID}, nil).AnyTimes()
mockMesh.EXPECT().GetMalfeasanceProof(p.SmesherID)
}
} else {
mockMesh.EXPECT().Proposals(gomock.Any()).Return([]*types.Proposal{}, nil).AnyTimes()
Expand Down