Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ See [RELEASE](./RELEASE.md) for workflow instructions.

## v1.8.3

### Upgrade information

This release changes weight calculation for identities that have been created after epoch 48 and use a commitment ATX
from epoch 48 or later. Those identities will gradually receive more weight over 10 epochs until they reach 2x the
weight of an identity with the same space commitment that was created before epoch 48.

### Improvements

* [#6823](https://github.com/spacemeshos/go-spacemesh/pull/6823) Fixed an issue where the node would repeatably
broadcast ATXs it already has seen, causing unnecessary network traffic.
* [#6817](https://github.com/spacemeshos/go-spacemesh/pull/6817) Updated weight calculation function for new identities.

## v1.8.2

Expand Down
102 changes: 73 additions & 29 deletions activation/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math/bits"
"slices"
"time"

Expand Down Expand Up @@ -91,6 +92,13 @@ func WithTickSize(tickSize uint64) HandlerOption {
}
}

func WithBonusWeightEpoch(epoch types.EpochID) HandlerOption {
return func(h *Handler) {
h.v1.bonusWeightEpoch = epoch
h.v2.bonusWeightEpoch = epoch
}
}

// NewHandler returns a data handler for ATX.
func NewHandler(
local p2p.Peer,
Expand All @@ -114,36 +122,38 @@ func NewHandler(
versions: []atxVersion{{0, types.AtxV1}},

v1: &HandlerV1{
local: local,
cdb: cdb,
atxsdata: atxsdata,
edVerifier: edVerifier,
clock: c,
tickSize: 1,
goldenATXID: goldenATXID,
nipostValidator: nipostValidator,
logger: lg,
fetcher: fetcher,
beacon: beacon,
tortoise: tortoise,
malPublisher: legacyMalPublisher,
malPublisher2: malPublisher,
local: local,
cdb: cdb,
atxsdata: atxsdata,
edVerifier: edVerifier,
clock: c,
tickSize: 1,
bonusWeightEpoch: 0,
goldenATXID: goldenATXID,
nipostValidator: nipostValidator,
logger: lg,
fetcher: fetcher,
beacon: beacon,
tortoise: tortoise,
malPublisher: legacyMalPublisher,
malPublisher2: malPublisher,
},

v2: &HandlerV2{
local: local,
cdb: cdb,
atxsdata: atxsdata,
edVerifier: edVerifier,
clock: c,
tickSize: 1,
goldenATXID: goldenATXID,
nipostValidator: nipostValidator,
logger: lg,
fetcher: fetcher,
beacon: beacon,
tortoise: tortoise,
malPublisher: malPublisher,
local: local,
cdb: cdb,
atxsdata: atxsdata,
edVerifier: edVerifier,
clock: c,
tickSize: 1,
bonusWeightEpoch: 0,
goldenATXID: goldenATXID,
nipostValidator: nipostValidator,
logger: lg,
fetcher: fetcher,
beacon: beacon,
tortoise: tortoise,
malPublisher: malPublisher,
},
}

Expand All @@ -157,8 +167,8 @@ func NewHandler(
enc.AppendString(fmt.Sprintf("v%v from epoch %d", v.AtxVersion, v.publish))
}
return nil
})))

})),
)
return h
}

Expand Down Expand Up @@ -286,3 +296,37 @@ func (h *Handler) handleAtx(ctx context.Context, expHash types.Hash32, peer p2p.
h.inProgress.Forget(key)
return err
}

func calcWeight(
numUnits, tickCount uint64,
bonusWeightEpoch, commitmentEpoch, publishEpoch types.EpochID,
) (uint64, error) {
hi, weight := bits.Mul64(numUnits, tickCount)
if hi != 0 {
return 0, fmt.Errorf("weight overflow (%d * %d)", numUnits, tickCount)
}
if bonusWeightEpoch == 0 {
// no bonus epoch configured
return weight, nil
}
if commitmentEpoch < bonusWeightEpoch-2 {
// An identity selecting a commitment in epoch X will init in epoch X and create an initial post. Now there are
// two scenarios:
// 1. The identity has enough time to register at PoET during the cyclegap of epoch X, and the initial ATX will
// be published in epoch X+1.
// 2. The cyclegap already closed in epoch X and the identity will publish the initial ATX in epoch X+2.
//
// since 2) is the more common case (most ATXs that could be selected for commitment are published during the
// cyclegap) we allow a 2 epoch gap between the commitment and the reward bonus epoch.
return weight, nil
}
if publishEpoch < bonusWeightEpoch { // bonus hasn't started yet
return weight, nil
}
epochsSinceBonus := uint64(min(publishEpoch-bonusWeightEpoch+1, 10)) // we scale the bonus over 10 epochs ...
hi, bonusWeight := bits.Mul64(weight, epochsSinceBonus)
if hi != 0 {
return 0, fmt.Errorf("bonus weight overflow (%d * %d)", weight, epochsSinceBonus)
}
return weight + (bonusWeight / 10), nil // ... linearly to 100% extra weight
}
121 changes: 117 additions & 4 deletions activation/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math"
"slices"
"sort"
"testing"
Expand Down Expand Up @@ -114,6 +115,7 @@ func toAtx(tb testing.TB, watx *wire.ActivationTxV1) *types.ActivationTx {
atx.SetReceived(time.Now())
atx.BaseTickHeight = uint64(atx.PublishEpoch)
atx.TickCount = 1
atx.Weight = 10
return atx
}

Expand Down Expand Up @@ -152,17 +154,21 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID
}
h.mClock.EXPECT().CurrentLayer().Return(atx.PublishEpoch.FirstLayer())

cAtx := h.goldenATXID
if atx.CommitmentATXID != nil {
cAtx = *atx.CommitmentATXID
}

if atx.VRFNonce != nil {
h.mValidator.EXPECT().
VRFNonce(nodeId, h.goldenATXID, *atx.VRFNonce, atx.NIPost.PostMetadata.LabelsPerUnit, atx.NumUnits)
h.mValidator.EXPECT().VRFNonce(nodeId, cAtx, *atx.VRFNonce, atx.NIPost.PostMetadata.LabelsPerUnit, atx.NumUnits)
}
h.mockFetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any())
h.mockFetch.EXPECT().GetPoetProof(gomock.Any(), types.BytesToHash(atx.NIPost.PostMetadata.Challenge))
deps := []types.ATXID{atx.PrevATXID, atx.PositioningATXID}
if atx.PrevATXID == types.EmptyATXID {
h.mValidator.EXPECT().InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), h.goldenATXID)
h.mValidator.EXPECT().
Post(gomock.Any(), nodeId, h.goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
Post(gomock.Any(), nodeId, cAtx, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
DoAndReturn(func(
_ context.Context, _ types.NodeID, _ types.ATXID, _ *types.Post,
_ *types.PostMetadata, _ uint32, _ ...validatorOption,
Expand All @@ -183,7 +189,7 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID
}
h.mValidator.EXPECT().PositioningAtx(atx.PositioningATXID, gomock.Any(), h.goldenATXID, atx.PublishEpoch)
h.mValidator.EXPECT().
NIPost(gomock.Any(), nodeId, h.goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
NIPost(gomock.Any(), nodeId, cAtx, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
Return(settings.poetLeaves, nil)
h.mValidator.EXPECT().IsVerifyingFullPost().Return(!settings.distributedPost)
h.mBeacon.EXPECT().OnAtx(gomock.Any())
Expand Down Expand Up @@ -956,3 +962,110 @@ func TestHandler_DecodeATX(t *testing.T) {
require.ErrorIs(t, err, pubsub.ErrValidationReject)
})
}

func TestCalcWeight_NoBonusEpoch(t *testing.T) {
t.Parallel()

tt := []struct {
name string
units uint32
ticks uint64
weight uint64
error error

rewardEpoch types.EpochID
commitmentEpoch types.EpochID
publishEpoch types.EpochID
}{
{
name: "weight overflow",
units: 10,
ticks: math.MaxUint64,
weight: 0, // overflow

publishEpoch: 11,
},
{
name: "no bonus configured",
units: 2,
ticks: 3,
weight: 6,

publishEpoch: 15,
},
{
name: "commitment too old for bonus",
units: 4,
ticks: 3,
weight: 12,

rewardEpoch: 10,
commitmentEpoch: 4,
publishEpoch: 15,
},
{
name: "eligible but before bonus epoch",
units: 5,
ticks: 7,
weight: 35,

rewardEpoch: 10,
commitmentEpoch: 8,
publishEpoch: 9,
},
{
name: "eligible for bonus in first reward epoch",
units: 10,
ticks: 14,
weight: 154, // 10% extra weight

rewardEpoch: 10,
commitmentEpoch: 8,
publishEpoch: 10,
},
{
name: "eligible for bonus in second reward epoch",
units: 10,
ticks: 14,
weight: 168, // 20% extra weight

rewardEpoch: 10,
commitmentEpoch: 8,
publishEpoch: 11,
},
{
name: "full bonus given",
units: 10,
ticks: 14,
weight: 280, // 100% extra weight

rewardEpoch: 10,
commitmentEpoch: 12,
publishEpoch: 20,
},
{
name: "bonus overflow",
units: 10,
ticks: math.MaxUint64 / 15,
weight: 0, // overflow

rewardEpoch: 10,
commitmentEpoch: 12,
publishEpoch: 20,
},
}

for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
weight, err := calcWeight(uint64(tc.units), tc.ticks, tc.rewardEpoch, tc.commitmentEpoch, tc.publishEpoch)
if tc.weight == 0 {
require.Error(t, err)
return
}

require.NoError(t, err)
require.Equal(t, tc.weight, weight)
})
}
}
Loading
Loading