Skip to content

Commit

Permalink
use atx grading for hare (#4718)
Browse files Browse the repository at this point in the history
## Motivation
<!-- Please mention the issue fixed by this PR or detailed motivation -->
Closes #4089
Closes #4757

## Changes
- hare only vote for proposals with grade 1/acceptable and 2/good atxs in the activeset
  • Loading branch information
countvonzero committed Aug 1, 2023
1 parent 6a20160 commit c3b7250
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 101 deletions.
2 changes: 1 addition & 1 deletion hare/eligibility/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ func (o *Oracle) computeActiveSet(ctx context.Context, targetEpoch types.EpochID
return activeSet, nil
}

activeSet, err := miner.ActiveSetFromBlock(o.cdb, targetEpoch)
activeSet, err := miner.ActiveSetFromEpochFirstBlock(o.cdb, targetEpoch)
if err != nil && !errors.Is(err, sql.ErrNotFound) {
return nil, err
}
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
94 changes: 87 additions & 7 deletions hare/hare.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"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 @@ func (h *Hare) onTick(ctx context.Context, lid types.LayerID) (bool, error) {
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 @@ func (h *Hare) removeCP(ctx context.Context, lid types.LayerID) {
}

// 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(
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,13 +469,14 @@ func goodProposals(ctx context.Context, logger log.Log, msh mesh, nodeID types.N

var (
beacon types.Beacon
activeSet []types.ATXID
result []types.ProposalID
ownHdr *types.ActivationTxHeader
ownTickHeight = uint64(math.MaxUint64)
)
// a non-smesher will not filter out any proposals, as it doesn't have voting power
// and only observes the consensus process.
ownHdr, err = msh.GetEpochAtx(lid.GetEpoch(), nodeID)
ownHdr, err = msh.GetEpochAtx(lid.GetEpoch()-1, nodeID)
if err != nil && !errors.Is(err, sql.ErrNotFound) {
logger.With().Error("failed to get own atx", log.Context(ctx), lid, log.Err(err))
return []types.ProposalID{}
Expand All @@ -474,6 +488,7 @@ func goodProposals(ctx context.Context, logger log.Log, msh mesh, nodeID types.N
for _, p := range props {
atxs[p.AtxID]++
}
cache := map[types.ATXID]miner.AtxGrade{}
for _, p := range props {
if p.IsMalicious() {
logger.With().Warning("not voting on proposal from malicious identity",
Expand Down Expand Up @@ -508,17 +523,59 @@ func goodProposals(ctx context.Context, logger log.Log, msh mesh, nodeID types.N
}
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(),
)
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))
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(),
)
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{}
}
if evil, err := gradeActiveSet(cache, 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{}
} 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 +592,29 @@ func goodProposals(ctx context.Context, logger log.Log, msh mesh, nodeID types.N
return result
}

func gradeActiveSet(cache map[types.ATXID]miner.AtxGrade, activeSet []types.ATXID, msh mesh, epochStart time.Time, networkDelay time.Duration) (types.ATXID, error) {
for _, id := range activeSet {
var grade miner.AtxGrade
if g, ok := cache[id]; ok {
grade = g
} else {
hdr, err := msh.GetAtxHeader(id)
if err != nil {
return types.EmptyATXID, fmt.Errorf("get header %v: %s", id, err)
}
grade, err = miner.GradeAtx(msh, hdr.NodeID, hdr.Received, epochStart, networkDelay)
if err != nil {
return types.EmptyATXID, fmt.Errorf("grade %v: %w", id, err)
}
cache[id] = grade
}
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

0 comments on commit c3b7250

Please sign in to comment.