Skip to content

Commit 898b66d

Browse files
Try #6817:
2 parents a373c51 + 21e50b1 commit 898b66d

28 files changed

+913
-248
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
See [RELEASE](./RELEASE.md) for workflow instructions.
44

5+
## v1.8.3
6+
7+
### Upgrade information
8+
9+
This release changes weight calculation for identities that have been created after epoch 48 and use a commitment ATX
10+
from epoch 48 or later. Those identities will gradually receive more weight over 10 epochs until they reach 2x the
11+
weight of an identity with the same space commitment that was created before epoch 48.
12+
13+
### Improvements
14+
15+
* [#6817](https://github.com/spacemeshos/go-spacemesh/pull/6817) Updated weight calculation function for new identities.
16+
517
## v1.8.2
618

719
### Improvements

activation/handler.go

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"math/bits"
78
"slices"
89
"time"
910

@@ -91,6 +92,13 @@ func WithTickSize(tickSize uint64) HandlerOption {
9192
}
9293
}
9394

95+
func WithBonusWeightEpoch(epoch types.EpochID) HandlerOption {
96+
return func(h *Handler) {
97+
h.v1.bonusWeightEpoch = epoch
98+
h.v2.bonusWeightEpoch = epoch
99+
}
100+
}
101+
94102
// NewHandler returns a data handler for ATX.
95103
func NewHandler(
96104
local p2p.Peer,
@@ -114,36 +122,38 @@ func NewHandler(
114122
versions: []atxVersion{{0, types.AtxV1}},
115123

116124
v1: &HandlerV1{
117-
local: local,
118-
cdb: cdb,
119-
atxsdata: atxsdata,
120-
edVerifier: edVerifier,
121-
clock: c,
122-
tickSize: 1,
123-
goldenATXID: goldenATXID,
124-
nipostValidator: nipostValidator,
125-
logger: lg,
126-
fetcher: fetcher,
127-
beacon: beacon,
128-
tortoise: tortoise,
129-
malPublisher: legacyMalPublisher,
130-
malPublisher2: malPublisher,
125+
local: local,
126+
cdb: cdb,
127+
atxsdata: atxsdata,
128+
edVerifier: edVerifier,
129+
clock: c,
130+
tickSize: 1,
131+
bonusWeightEpoch: 0,
132+
goldenATXID: goldenATXID,
133+
nipostValidator: nipostValidator,
134+
logger: lg,
135+
fetcher: fetcher,
136+
beacon: beacon,
137+
tortoise: tortoise,
138+
malPublisher: legacyMalPublisher,
139+
malPublisher2: malPublisher,
131140
},
132141

133142
v2: &HandlerV2{
134-
local: local,
135-
cdb: cdb,
136-
atxsdata: atxsdata,
137-
edVerifier: edVerifier,
138-
clock: c,
139-
tickSize: 1,
140-
goldenATXID: goldenATXID,
141-
nipostValidator: nipostValidator,
142-
logger: lg,
143-
fetcher: fetcher,
144-
beacon: beacon,
145-
tortoise: tortoise,
146-
malPublisher: malPublisher,
143+
local: local,
144+
cdb: cdb,
145+
atxsdata: atxsdata,
146+
edVerifier: edVerifier,
147+
clock: c,
148+
tickSize: 1,
149+
bonusWeightEpoch: 0,
150+
goldenATXID: goldenATXID,
151+
nipostValidator: nipostValidator,
152+
logger: lg,
153+
fetcher: fetcher,
154+
beacon: beacon,
155+
tortoise: tortoise,
156+
malPublisher: malPublisher,
147157
},
148158
}
149159

@@ -157,8 +167,8 @@ func NewHandler(
157167
enc.AppendString(fmt.Sprintf("v%v from epoch %d", v.AtxVersion, v.publish))
158168
}
159169
return nil
160-
})))
161-
170+
})),
171+
)
162172
return h
163173
}
164174

@@ -286,3 +296,37 @@ func (h *Handler) handleAtx(ctx context.Context, expHash types.Hash32, peer p2p.
286296
h.inProgress.Forget(key)
287297
return err
288298
}
299+
300+
func calcWeight(
301+
numUnits, tickCount uint64,
302+
bonusWeightEpoch, commitmentEpoch, publishEpoch types.EpochID,
303+
) (uint64, error) {
304+
hi, weight := bits.Mul64(numUnits, tickCount)
305+
if hi != 0 {
306+
return 0, fmt.Errorf("weight overflow (%d * %d)", numUnits, tickCount)
307+
}
308+
if bonusWeightEpoch == 0 {
309+
// no bonus epoch configured
310+
return weight, nil
311+
}
312+
if commitmentEpoch < bonusWeightEpoch-2 {
313+
// An identity selecting a commitment in epoch X will init in epoch X and create an initial post. Now there are
314+
// two scenarios:
315+
// 1. The identity has enough time to register at PoET during the cyclegap of epoch X, and the initial ATX will
316+
// be published in epoch X+1.
317+
// 2. The cyclegap already closed in epoch X and the identity will publish the initial ATX in epoch X+2.
318+
//
319+
// since 2) is the more common case (most ATXs that could be selected for commitment are published during the
320+
// cyclegap) we allow a 2 epoch gap between the commitment and the reward bonus epoch.
321+
return weight, nil
322+
}
323+
if publishEpoch < bonusWeightEpoch { // bonus hasn't started yet
324+
return weight, nil
325+
}
326+
epochsSinceBonus := uint64(min(publishEpoch-bonusWeightEpoch+1, 10)) // we scale the bonus over 10 epochs ...
327+
hi, bonusWeight := bits.Mul64(weight, epochsSinceBonus)
328+
if hi != 0 {
329+
return 0, fmt.Errorf("bonus weight overflow (%d * %d)", weight, epochsSinceBonus)
330+
}
331+
return weight + (bonusWeight / 10), nil // ... linearly to 100% extra weight
332+
}

activation/handler_test.go

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"math"
78
"slices"
89
"sort"
910
"testing"
@@ -114,6 +115,7 @@ func toAtx(tb testing.TB, watx *wire.ActivationTxV1) *types.ActivationTx {
114115
atx.SetReceived(time.Now())
115116
atx.BaseTickHeight = uint64(atx.PublishEpoch)
116117
atx.TickCount = 1
118+
atx.Weight = 10
117119
return atx
118120
}
119121

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

157+
cAtx := h.goldenATXID
158+
if atx.CommitmentATXID != nil {
159+
cAtx = *atx.CommitmentATXID
160+
}
161+
155162
if atx.VRFNonce != nil {
156-
h.mValidator.EXPECT().
157-
VRFNonce(nodeId, h.goldenATXID, *atx.VRFNonce, atx.NIPost.PostMetadata.LabelsPerUnit, atx.NumUnits)
163+
h.mValidator.EXPECT().VRFNonce(nodeId, cAtx, *atx.VRFNonce, atx.NIPost.PostMetadata.LabelsPerUnit, atx.NumUnits)
158164
}
159165
h.mockFetch.EXPECT().RegisterPeerHashes(gomock.Any(), gomock.Any())
160166
h.mockFetch.EXPECT().GetPoetProof(gomock.Any(), types.BytesToHash(atx.NIPost.PostMetadata.Challenge))
161167
deps := []types.ATXID{atx.PrevATXID, atx.PositioningATXID}
162168
if atx.PrevATXID == types.EmptyATXID {
163169
h.mValidator.EXPECT().InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), h.goldenATXID)
164170
h.mValidator.EXPECT().
165-
Post(gomock.Any(), nodeId, h.goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
171+
Post(gomock.Any(), nodeId, cAtx, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
166172
DoAndReturn(func(
167173
_ context.Context, _ types.NodeID, _ types.ATXID, _ *types.Post,
168174
_ *types.PostMetadata, _ uint32, _ ...validatorOption,
@@ -183,7 +189,7 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID
183189
}
184190
h.mValidator.EXPECT().PositioningAtx(atx.PositioningATXID, gomock.Any(), h.goldenATXID, atx.PublishEpoch)
185191
h.mValidator.EXPECT().
186-
NIPost(gomock.Any(), nodeId, h.goldenATXID, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
192+
NIPost(gomock.Any(), nodeId, cAtx, gomock.Any(), gomock.Any(), atx.NumUnits, gomock.Any()).
187193
Return(settings.poetLeaves, nil)
188194
h.mValidator.EXPECT().IsVerifyingFullPost().Return(!settings.distributedPost)
189195
h.mBeacon.EXPECT().OnAtx(gomock.Any())
@@ -956,3 +962,110 @@ func TestHandler_DecodeATX(t *testing.T) {
956962
require.ErrorIs(t, err, pubsub.ErrValidationReject)
957963
})
958964
}
965+
966+
func TestCalcWeight_NoBonusEpoch(t *testing.T) {
967+
t.Parallel()
968+
969+
tt := []struct {
970+
name string
971+
units uint32
972+
ticks uint64
973+
weight uint64
974+
error error
975+
976+
rewardEpoch types.EpochID
977+
commitmentEpoch types.EpochID
978+
publishEpoch types.EpochID
979+
}{
980+
{
981+
name: "weight overflow",
982+
units: 10,
983+
ticks: math.MaxUint64,
984+
weight: 0, // overflow
985+
986+
publishEpoch: 11,
987+
},
988+
{
989+
name: "no bonus configured",
990+
units: 2,
991+
ticks: 3,
992+
weight: 6,
993+
994+
publishEpoch: 15,
995+
},
996+
{
997+
name: "commitment too old for bonus",
998+
units: 4,
999+
ticks: 3,
1000+
weight: 12,
1001+
1002+
rewardEpoch: 10,
1003+
commitmentEpoch: 4,
1004+
publishEpoch: 15,
1005+
},
1006+
{
1007+
name: "eligible but before bonus epoch",
1008+
units: 5,
1009+
ticks: 7,
1010+
weight: 35,
1011+
1012+
rewardEpoch: 10,
1013+
commitmentEpoch: 8,
1014+
publishEpoch: 9,
1015+
},
1016+
{
1017+
name: "eligible for bonus in first reward epoch",
1018+
units: 10,
1019+
ticks: 14,
1020+
weight: 154, // 10% extra weight
1021+
1022+
rewardEpoch: 10,
1023+
commitmentEpoch: 8,
1024+
publishEpoch: 10,
1025+
},
1026+
{
1027+
name: "eligible for bonus in second reward epoch",
1028+
units: 10,
1029+
ticks: 14,
1030+
weight: 168, // 20% extra weight
1031+
1032+
rewardEpoch: 10,
1033+
commitmentEpoch: 8,
1034+
publishEpoch: 11,
1035+
},
1036+
{
1037+
name: "full bonus given",
1038+
units: 10,
1039+
ticks: 14,
1040+
weight: 280, // 100% extra weight
1041+
1042+
rewardEpoch: 10,
1043+
commitmentEpoch: 12,
1044+
publishEpoch: 20,
1045+
},
1046+
{
1047+
name: "bonus overflow",
1048+
units: 10,
1049+
ticks: math.MaxUint64 / 15,
1050+
weight: 0, // overflow
1051+
1052+
rewardEpoch: 10,
1053+
commitmentEpoch: 12,
1054+
publishEpoch: 20,
1055+
},
1056+
}
1057+
1058+
for _, tc := range tt {
1059+
t.Run(tc.name, func(t *testing.T) {
1060+
t.Parallel()
1061+
weight, err := calcWeight(uint64(tc.units), tc.ticks, tc.rewardEpoch, tc.commitmentEpoch, tc.publishEpoch)
1062+
if tc.weight == 0 {
1063+
require.Error(t, err)
1064+
return
1065+
}
1066+
1067+
require.NoError(t, err)
1068+
require.Equal(t, tc.weight, weight)
1069+
})
1070+
}
1071+
}

0 commit comments

Comments
 (0)