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 block proposal #4717

Closed
Closed
Show file tree
Hide file tree
Changes from all 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) {
fasmat marked this conversation as resolved.
Show resolved Hide resolved
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
38 changes: 38 additions & 0 deletions miner/atx_grader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package miner

import (
"errors"
"time"

"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/sql"
)

// AtxGrade describes the grade of an ATX as described in
// https://community.spacemesh.io/t/grading-atxs-for-the-active-set/335
//
// let s be the start of the epoch, and δ the network propagation time.
// grade 0: ATX was received at time t >= s-3δ, or an equivocation proof was received by time s-δ.
// grade 1: ATX was received at time t < s-3δ before the start of the epoch, and no equivocation proof was received by time s-δ.
// grade 2: ATX was received at time t < s-4δ, and no equivocation proof was received for that id until time s.
type AtxGrade int

const (
Evil AtxGrade = iota
Acceptable
Good
)

func GradeAtx(msh mesh, nodeID types.NodeID, atxReceived, epochStart time.Time, delta time.Duration) (AtxGrade, error) {
proof, err := msh.GetMalfeasanceProof(nodeID)
if err != nil && !errors.Is(err, sql.ErrNotFound) {
return Good, err
}

Check warning on line 30 in miner/atx_grader.go

View check run for this annotation

Codecov / codecov/patch

miner/atx_grader.go#L29-L30

Added lines #L29 - L30 were not covered by tests
if atxReceived.Before(epochStart.Add(-4*delta)) && (proof == nil || !proof.Received().Before(epochStart)) {
return Good, nil
}
if atxReceived.Before(epochStart.Add(-3*delta)) && (proof == nil || !proof.Received().Before(epochStart.Add(-delta))) {
return Acceptable, nil
}
return Evil, nil
}
109 changes: 109 additions & 0 deletions miner/atx_grader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package miner_test

import (
"os"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/hare/mocks"
"github.com/spacemeshos/go-spacemesh/miner"
"github.com/spacemeshos/go-spacemesh/sql"
)

const layersPerEpoch = 3

func TestMain(m *testing.M) {
types.SetLayersPerEpoch(layersPerEpoch)

res := m.Run()
os.Exit(res)
}

func TestGradeAtx(t *testing.T) {
const delta = 10
for _, tc := range []struct {
desc string
malicious bool
// distance in second from the epoch start time
atxReceived, malReceived int
result miner.AtxGrade
}{
{
desc: "very early atx",
atxReceived: -41,
result: miner.Good,
},
{
desc: "very early atx, late malfeasance",
atxReceived: -41,
malicious: true,
malReceived: 0,
result: miner.Good,
},
{
desc: "very early atx, malicious",
atxReceived: -41,
malicious: true,
malReceived: -10,
result: miner.Acceptable,
},
{
desc: "very early atx, early malicious",
atxReceived: -41,
malicious: true,
malReceived: -11,
result: miner.Evil,
},
{
desc: "early atx",
atxReceived: -31,
result: miner.Acceptable,
},
{
desc: "early atx, late malicious",
atxReceived: -31,
malicious: true,
malReceived: -10,
result: miner.Acceptable,
},
{
desc: "early atx, early malicious",
atxReceived: -31,
malicious: true,
malReceived: -11,
result: miner.Evil,
},
{
desc: "late atx",
atxReceived: -30,
result: miner.Evil,
},
{
desc: "very late atx",
atxReceived: 0,
result: miner.Evil,
},
} {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
mockMsh := mocks.NewMockmesh(gomock.NewController(t))
epochStart := time.Now()
nodeID := types.RandomNodeID()
if tc.malicious {
proof := &types.MalfeasanceProof{}
proof.SetReceived(epochStart.Add(time.Duration(tc.malReceived) * time.Second))
mockMsh.EXPECT().GetMalfeasanceProof(nodeID).Return(proof, nil)
} else {
mockMsh.EXPECT().GetMalfeasanceProof(nodeID).Return(nil, sql.ErrNotFound)
}
atxReceived := epochStart.Add(time.Duration(tc.atxReceived) * time.Second)
got, err := miner.GradeAtx(mockMsh, nodeID, atxReceived, epochStart, delta*time.Second)
require.NoError(t, err)
require.Equal(t, tc.result, got)
})
}
}
6 changes: 5 additions & 1 deletion miner/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
//go:generate mockgen -package=miner -destination=./mocks.go -source=./interface.go

type proposalOracle interface {
GetProposalEligibility(types.LayerID, types.Beacon, types.VRFPostIndex) (*EpochEligibility, error)
ProposalEligibility(types.LayerID, types.Beacon, types.VRFPostIndex) (*EpochEligibility, error)
fasmat marked this conversation as resolved.
Show resolved Hide resolved
}

type conservativeState interface {
Expand All @@ -28,6 +28,10 @@ type nonceFetcher interface {
VRFNonce(types.NodeID, types.EpochID) (types.VRFPostIndex, error)
}

type mesh interface {
GetMalfeasanceProof(nodeID types.NodeID) (*types.MalfeasanceProof, error)
}

type layerClock interface {
AwaitLayer(layerID types.LayerID) <-chan struct{}
CurrentLayer() types.LayerID
Expand Down
50 changes: 44 additions & 6 deletions miner/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.