diff --git a/CHANGELOG.md b/CHANGELOG.md index ec1ccafa96..29a851e1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/activation/handler.go b/activation/handler.go index 497fd190cc..ec1d989c73 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/bits" "slices" "time" @@ -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, @@ -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, }, } @@ -157,8 +167,8 @@ func NewHandler( enc.AppendString(fmt.Sprintf("v%v from epoch %d", v.AtxVersion, v.publish)) } return nil - }))) - + })), + ) return h } @@ -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 +} diff --git a/activation/handler_test.go b/activation/handler_test.go index e071e5691f..c03f2e8084 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "slices" "sort" "testing" @@ -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 } @@ -152,9 +154,13 @@ 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)) @@ -162,7 +168,7 @@ func (h *handlerMocks) expectAtxV1(atx *wire.ActivationTxV1, nodeId types.NodeID 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, @@ -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()) @@ -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) + }) + } +} diff --git a/activation/handler_v1.go b/activation/handler_v1.go index 0e1e6bff11..85790279c6 100644 --- a/activation/handler_v1.go +++ b/activation/handler_v1.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "math/bits" "time" "github.com/libp2p/go-libp2p/core/peer" @@ -64,20 +63,21 @@ type nipostValidatorV1 interface { // HandlerV1 processes ATXs version 1. type HandlerV1 struct { - local p2p.Peer - cdb *datastore.CachedDB - atxsdata *atxsdata.Data - edVerifier *signing.EdVerifier - clock layerClock - tickSize uint64 - goldenATXID types.ATXID - nipostValidator nipostValidatorV1 - beacon atxReceiver - tortoise system.Tortoise - logger *zap.Logger - fetcher system.Fetcher - malPublisher legacyMalfeasancePublisher - malPublisher2 atxMalfeasancePublisher + local p2p.Peer + cdb *datastore.CachedDB + atxsdata *atxsdata.Data + edVerifier *signing.EdVerifier + clock layerClock + tickSize uint64 + bonusWeightEpoch types.EpochID + goldenATXID types.ATXID + nipostValidator nipostValidatorV1 + beacon atxReceiver + tortoise system.Tortoise + logger *zap.Logger + fetcher system.Fetcher + malPublisher legacyMalfeasancePublisher + malPublisher2 atxMalfeasancePublisher } func (h *HandlerV1) syntacticallyValidate(ctx context.Context, atx *wire.ActivationTxV1) error { @@ -145,11 +145,26 @@ func (h *HandlerV1) syntacticallyValidate(ctx context.Context, atx *wire.Activat } // Obtain the commitment ATX ID for the given ATX. -func (h *HandlerV1) commitment(atx *wire.ActivationTxV1) (types.ATXID, error) { - if atx.PrevATXID == types.EmptyATXID { - return *atx.CommitmentATXID, nil +func (h *HandlerV1) commitment(watx *wire.ActivationTxV1) (types.ATXID, types.EpochID, error) { + var id types.ATXID + switch { + case watx.PrevATXID == types.EmptyATXID: // initial ATX + id = *watx.CommitmentATXID + default: // non-initial ATX + var err error + id, err = atxs.CommitmentATX(h.cdb, watx.SmesherID) + if err != nil { + return types.EmptyATXID, 0, fmt.Errorf("fetching commitment atx ID: %w", err) + } + } + if id == h.goldenATXID { + return id, 0, nil } - return atxs.CommitmentATX(h.cdb, atx.SmesherID) + atx, err := atxs.Get(h.cdb, id) + if err != nil { + return types.EmptyATXID, 0, fmt.Errorf("fetching commitment atx %s: %w", id, err) + } + return id, atx.PublishEpoch, nil } func (h *HandlerV1) syntacticallyValidateDeps( @@ -157,7 +172,7 @@ func (h *HandlerV1) syntacticallyValidateDeps( watx *wire.ActivationTxV1, received time.Time, ) (*types.ActivationTx, error) { - commitmentATX, err := h.commitment(watx) + commitmentATX, commitmentEpoch, err := h.commitment(watx) if err != nil { return nil, fmt.Errorf("commitment atx for %s not found: %w", watx.SmesherID, err) } @@ -266,9 +281,15 @@ func (h *HandlerV1) syntacticallyValidateDeps( atx.NumUnits = effectiveNumUnits atx.BaseTickHeight = baseTickHeight atx.TickCount = leaves / h.tickSize - hi, weight := bits.Mul64(uint64(atx.NumUnits), atx.TickCount) - if hi != 0 { - return nil, errors.New("atx weight would overflow uint64") + weight, err := calcWeight( + uint64(atx.NumUnits), + atx.TickCount, + h.bonusWeightEpoch, + commitmentEpoch, + atx.PublishEpoch, + ) + if err != nil { + return nil, err } atx.Weight = weight return atx, nil diff --git a/activation/handler_v1_test.go b/activation/handler_v1_test.go index 3e908af56b..9f091744cf 100644 --- a/activation/handler_v1_test.go +++ b/activation/handler_v1_test.go @@ -42,19 +42,20 @@ func newV1TestHandler(tb testing.TB, goldenATXID types.ATXID) *v1TestHandler { mocks := newTestHandlerMocks(tb, goldenATXID) return &v1TestHandler{ HandlerV1: &HandlerV1{ - local: "localID", - cdb: cdb, - atxsdata: atxsdata.New(), - edVerifier: signing.NewEdVerifier(), - clock: mocks.mClock, - tickSize: 1, - goldenATXID: goldenATXID, - nipostValidator: mocks.mValidator, - logger: lg, - fetcher: mocks.mockFetch, - beacon: mocks.mBeacon, - tortoise: mocks.mTortoise, - malPublisher: mocks.mLegacyMalPublish, + local: "localID", + cdb: cdb, + atxsdata: atxsdata.New(), + edVerifier: signing.NewEdVerifier(), + clock: mocks.mClock, + tickSize: tickSize, + bonusWeightEpoch: 0, + goldenATXID: goldenATXID, + nipostValidator: mocks.mValidator, + logger: lg, + fetcher: mocks.mockFetch, + beacon: mocks.mBeacon, + tortoise: mocks.mTortoise, + malPublisher: mocks.mLegacyMalPublish, }, handlerMocks: mocks, } @@ -90,7 +91,6 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr, prevAtx, posAtx := setup(t) watx := newChainedActivationTxV1(t, prevAtx, posAtx.ID()) - watx.PositioningATXID = posAtx.ID() watx.Sign(sig) atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) @@ -113,6 +113,51 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) }) + t.Run("valid atx with bonus weight", func(t *testing.T) { + t.Parallel() + atxHdlr := newV1TestHandler(t, goldenATXID) + atxHdlr.bonusWeightEpoch = 5 + + otherSig, err := signing.NewEdSigner() + require.NoError(t, err) + + commitmentATX := newInitialATXv1(t, goldenATXID) + commitmentATX.PublishEpoch = 7 + commitmentATX.Sign(otherSig) + ctxID := commitmentATX.ID() + atxHdlr.expectAtxV1(commitmentATX, otherSig.NodeID()) + require.NoError(t, atxHdlr.processATX(t.Context(), p2p.NoPeer, commitmentATX, time.Now())) + + prevAtx := newInitialATXv1(t, ctxID) + prevAtx.NumUnits = 100 + prevAtx.PublishEpoch = 9 + prevAtx.Sign(sig) + atxHdlr.expectAtxV1(prevAtx, sig.NodeID()) + require.NoError(t, atxHdlr.processATX(t.Context(), p2p.NoPeer, prevAtx, time.Now())) + + watx := newChainedActivationTxV1(t, prevAtx, prevAtx.ID()) + watx.Sign(sig) + + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + require.NoError(t, atxHdlr.syntacticallyValidate(t.Context(), watx)) + + atxHdlr.mValidator.EXPECT(). + NIPost(gomock.Any(), watx.SmesherID, ctxID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(1234, nil) + atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), watx.SmesherID) + atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, gomock.Any()) + atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) + received := time.Now() + atx, err := atxHdlr.syntacticallyValidateDeps(t.Context(), watx, received) + require.NoError(t, err) + require.Equal(t, types.Valid, atx.Validity()) + require.Equal(t, received, atx.Received()) + require.EqualValues(t, atx.VRFNonce, *prevAtx.VRFNonce) + require.Equal(t, watx.NumUnits, atx.NumUnits) + require.Equal(t, uint64(1234)/atxHdlr.tickSize, atx.TickCount) + require.Equal(t, uint64(float64(atx.NumUnits)*float64(atx.TickCount)*1.6), atx.Weight) // 60% bonus + }) + t.Run("valid atx with new VRF nonce", func(t *testing.T) { t.Parallel() atxHdlr, prevAtx, posAtx := setup(t) @@ -126,7 +171,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.NoError(t, atxHdlr.syntacticallyValidate(t.Context(), watx)) atxHdlr.mValidator.EXPECT(). - NIPost(gomock.Any(), gomock.Any(), goldenATXID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(1234, nil) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), gomock.Any()) atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) @@ -155,7 +200,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(t.Context(), watx)) atxHdlr.mValidator.EXPECT(). - NIPost(gomock.Any(), gomock.Any(), goldenATXID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(uint64(1234), nil) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), gomock.Any()) atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) @@ -182,7 +227,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) require.NoError(t, atxHdlr.syntacticallyValidate(t.Context(), watx)) atxHdlr.mValidator.EXPECT(). - NIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + NIPost(gomock.Any(), watx.SmesherID, goldenATXID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(uint64(1234), nil) atxHdlr.mValidator.EXPECT().NIPostChallengeV1(gomock.Any(), gomock.Any(), gomock.Any()) atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), gomock.Any(), gomock.Any()) @@ -224,8 +269,7 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr, _, posAtx := setup(t) ctxID := posAtx.ID() - watx := newInitialATXv1(t, goldenATXID) - watx.CommitmentATXID = &ctxID + watx := newInitialATXv1(t, ctxID) watx.Sign(sig) atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) @@ -236,8 +280,8 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { atxHdlr.mValidator.EXPECT().InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), goldenATXID) atxHdlr.mValidator.EXPECT(). - NIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(uint64(777), nil) + NIPost(gomock.Any(), watx.SmesherID, ctxID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(777, nil) atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) received := time.Now() @@ -251,6 +295,49 @@ func TestHandlerV1_SyntacticallyValidateAtx(t *testing.T) { require.Equal(t, uint64(atx.NumUnits)*atx.TickCount, atx.Weight) }) + t.Run("valid initial atx with bonus weight", func(t *testing.T) { + t.Parallel() + atxHdlr := newV1TestHandler(t, goldenATXID) + atxHdlr.bonusWeightEpoch = 5 + + otherSig, err := signing.NewEdSigner() + require.NoError(t, err) + + commitmentATX := newInitialATXv1(t, goldenATXID) + commitmentATX.PublishEpoch = 5 + commitmentATX.Sign(otherSig) + ctxID := commitmentATX.ID() + atxHdlr.expectAtxV1(commitmentATX, otherSig.NodeID()) + require.NoError(t, atxHdlr.processATX(t.Context(), p2p.NoPeer, commitmentATX, time.Now())) + + watx := newInitialATXv1(t, ctxID) + watx.PublishEpoch = 6 + watx.NumUnits = 100 + watx.Sign(sig) + + atxHdlr.mClock.EXPECT().CurrentLayer().Return(watx.PublishEpoch.FirstLayer()) + atxHdlr.mValidator.EXPECT(). + Post(gomock.Any(), gomock.Any(), ctxID, gomock.Any(), gomock.Any(), watx.NumUnits, gomock.Any()) + atxHdlr.mValidator.EXPECT().VRFNonce(sig.NodeID(), ctxID, *watx.VRFNonce, gomock.Any(), watx.NumUnits) + require.NoError(t, atxHdlr.syntacticallyValidate(t.Context(), watx)) + + atxHdlr.mValidator.EXPECT().InitialNIPostChallengeV1(gomock.Any(), gomock.Any(), goldenATXID) + atxHdlr.mValidator.EXPECT(). + NIPost(gomock.Any(), watx.SmesherID, ctxID, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(777, nil) + atxHdlr.mValidator.EXPECT().PositioningAtx(watx.PositioningATXID, gomock.Any(), goldenATXID, watx.PublishEpoch) + atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(true) + received := time.Now() + atx, err := atxHdlr.syntacticallyValidateDeps(t.Context(), watx, received) + require.NoError(t, err) + require.Equal(t, types.Valid, atx.Validity()) + require.Equal(t, received, atx.Received()) + require.EqualValues(t, atx.VRFNonce, *watx.VRFNonce) + require.Equal(t, watx.NumUnits, atx.NumUnits) + require.Equal(t, uint64(777)/atxHdlr.tickSize, atx.TickCount) + require.Equal(t, uint64(float64(atx.NumUnits)*float64(atx.TickCount)*1.2), atx.Weight) // 20% bonus + }) + t.Run("atx targeting wrong publish epoch", func(t *testing.T) { t.Parallel() atxHdlr, prevAtx, posAtx := setup(t) diff --git a/activation/handler_v2.go b/activation/handler_v2.go index 607108a217..832307047f 100644 --- a/activation/handler_v2.go +++ b/activation/handler_v2.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "math" - "math/bits" "slices" "time" @@ -57,19 +56,20 @@ type nipostValidatorV2 interface { } type HandlerV2 struct { - local p2p.Peer - cdb *datastore.CachedDB - atxsdata *atxsdata.Data - edVerifier *signing.EdVerifier - clock layerClock - tickSize uint64 - goldenATXID types.ATXID - nipostValidator nipostValidatorV2 - beacon atxReceiver - tortoise system.Tortoise - logger *zap.Logger - fetcher system.Fetcher - malPublisher atxMalfeasancePublisher + local p2p.Peer + cdb *datastore.CachedDB + atxsdata *atxsdata.Data + edVerifier *signing.EdVerifier + clock layerClock + tickSize uint64 + bonusWeightEpoch types.EpochID + goldenATXID types.ATXID + nipostValidator nipostValidatorV2 + beacon atxReceiver + tortoise system.Tortoise + logger *zap.Logger + fetcher system.Fetcher + malPublisher atxMalfeasancePublisher } func (h *HandlerV2) processATX( @@ -458,15 +458,8 @@ type activationTx struct { type nipostSize struct { units uint32 ticks uint64 -} -func (n *nipostSize) addUnits(units uint32) error { - sum, carry := bits.Add32(n.units, units, 0) - if carry != 0 { - return errors.New("units overflow") - } - n.units = sum - return nil + commitmentEpoch types.EpochID } type nipostSizes []*nipostSize @@ -475,15 +468,15 @@ func (n nipostSizes) minTicks() uint64 { return slices.MinFunc(n, func(a, b *nipostSize) int { return cmp.Compare(a.ticks, b.ticks) }).ticks } -func (n nipostSizes) sumUp() (units uint32, weight uint64, err error) { +func (n nipostSizes) sumUp(bonusWeightEpoch, publishEpoch types.EpochID) (units uint32, weight uint64, err error) { var totalUnits uint64 var totalWeight uint64 for _, ns := range n { totalUnits += uint64(ns.units) - hi, weight := bits.Mul64(uint64(ns.units), ns.ticks) - if hi != 0 { - return 0, 0, fmt.Errorf("weight overflow (%d * %d)", ns.units, ns.ticks) + weight, err := calcWeight(uint64(ns.units), ns.ticks, bonusWeightEpoch, ns.commitmentEpoch, publishEpoch) + if err != nil { + return 0, 0, err } totalWeight += weight } @@ -541,30 +534,8 @@ func (h *HandlerV2) syntacticallyValidateDeps( } // validate previous ATXs - nipostSizes := make(nipostSizes, len(atx.NIPosts)) - for i, niPosts := range atx.NIPosts { - nipostSizes[i] = new(nipostSize) - for _, post := range niPosts.Posts { - if post.MarriageIndex >= uint32(len(equivocationSet)) { - err := fmt.Errorf("marriage index out of bounds: %d > %d", post.MarriageIndex, len(equivocationSet)-1) - return nil, err - } - - id := equivocationSet[post.MarriageIndex] - effectiveNumUnits := post.NumUnits - if atx.Initial == nil { - var err error - effectiveNumUnits, err = h.validatePreviousAtx(id, &post, previousAtxs) - if err != nil { - return nil, fmt.Errorf("validating previous atx: %w", err) - } - } - nipostSizes[i].addUnits(effectiveNumUnits) - } - } - - // validate poet membership proofs - for i, niPosts := range atx.NIPosts { + nipostSizes := make(nipostSizes, 0) + for _, niPosts := range atx.NIPosts { // verify PoET memberships in a single go indexedChallenges := make(map[uint64][]byte) @@ -599,21 +570,51 @@ func (h *HandlerV2) syntacticallyValidateDeps( if err != nil { return nil, fmt.Errorf("validating poet membership: %w", err) } - nipostSizes[i].ticks = leaves / h.tickSize + + ticks := leaves / h.tickSize + + for _, post := range niPosts.Posts { + if post.MarriageIndex >= uint32(len(equivocationSet)) { + err := fmt.Errorf("marriage index out of bounds: %d > %d", post.MarriageIndex, len(equivocationSet)-1) + return nil, err + } + + id := equivocationSet[post.MarriageIndex] + effectiveNumUnits := post.NumUnits + if atx.Initial == nil { + var err error + effectiveNumUnits, err = h.validatePreviousAtx(id, &post, previousAtxs) + if err != nil { + return nil, fmt.Errorf("validating previous atx: %w", err) + } + } + + _, commitmentEpoch, err := h.commitment(atx, id) + if err != nil { + return nil, fmt.Errorf("fetching commitment atx: %w", err) + } + + size := &nipostSize{ + units: effectiveNumUnits, + ticks: ticks, + commitmentEpoch: commitmentEpoch, + } + nipostSizes = append(nipostSizes, size) + } } - result.effectiveUnits, result.weight, err = nipostSizes.sumUp() + result.effectiveUnits, result.weight, err = nipostSizes.sumUp(h.bonusWeightEpoch, atx.PublishEpoch) if err != nil { return nil, err } // validate all NIPoSTs if atx.Initial != nil { - commitment := atx.Initial.CommitmentATX + commitmentATX := atx.Initial.CommitmentATX nipostIdx := 0 challenge := atx.NIPosts[nipostIdx].Challenge post := atx.NIPosts[nipostIdx].Posts[0] - if err := h.validatePost(ctx, atx.SmesherID, atx, peer, commitment, challenge, post, nipostIdx); err != nil { + if err := h.validatePost(ctx, atx.SmesherID, atx, peer, commitmentATX, challenge, post, nipostIdx); err != nil { return nil, err } result.ids[atx.SmesherID] = idData{ @@ -629,14 +630,14 @@ func (h *HandlerV2) syntacticallyValidateDeps( for idx, niPosts := range atx.NIPosts { for _, post := range niPosts.Posts { id := equivocationSet[post.MarriageIndex] - commitment, err := atxs.CommitmentATX(h.cdb, id) + commitmentATX, _, err := h.commitment(atx, id) if err != nil { return nil, fmt.Errorf("commitment atx not found for ID %s: %w", id, err) } if id == atx.SmesherID { - smesherCommitment = &commitment + smesherCommitment = &commitmentATX } - if err := h.validatePost(ctx, id, atx, peer, commitment, niPosts.Challenge, post, idx); err != nil { + if err := h.validatePost(ctx, id, atx, peer, commitmentATX, niPosts.Challenge, post, idx); err != nil { return nil, err } result.ids[id] = idData{ @@ -659,6 +660,29 @@ func (h *HandlerV2) syntacticallyValidateDeps( return &result, nil } +// Obtain the commitment ATX ID for the given ATX. +func (h *HandlerV2) commitment(watx *wire.ActivationTxV2, nodeID types.NodeID) (types.ATXID, types.EpochID, error) { + var id types.ATXID + switch { + case watx.Initial != nil: // initial ATX + id = watx.Initial.CommitmentATX + default: // non-initial ATX + var err error + id, err = atxs.CommitmentATX(h.cdb, nodeID) + if err != nil { + return types.EmptyATXID, 0, fmt.Errorf("fetching commitment atx ID: %w", err) + } + } + if id == h.goldenATXID { + return id, 0, nil + } + atx, err := atxs.Get(h.cdb, id) + if err != nil { + return types.EmptyATXID, 0, fmt.Errorf("fetching commitment atx %s: %w", id, err) + } + return id, atx.PublishEpoch, nil +} + func (h *HandlerV2) validatePost( ctx context.Context, nodeID types.NodeID, diff --git a/activation/handler_v2_test.go b/activation/handler_v2_test.go index 084bd955d0..15d33bae6f 100644 --- a/activation/handler_v2_test.go +++ b/activation/handler_v2_test.go @@ -64,19 +64,20 @@ func newV2TestHandler(tb testing.TB, golden types.ATXID) *v2TestHandler { mocks := newTestHandlerMocks(tb, golden) return &v2TestHandler{ HandlerV2: &HandlerV2{ - local: "localID", - cdb: cdb, - atxsdata: atxsdata.New(), - edVerifier: signing.NewEdVerifier(), - clock: mocks.mClock, - tickSize: tickSize, - goldenATXID: golden, - nipostValidator: mocks.mValidator, - logger: logger, - fetcher: mocks.mockFetch, - beacon: mocks.mBeacon, - tortoise: mocks.mTortoise, - malPublisher: mocks.mMalPublish, + local: "localID", + cdb: cdb, + atxsdata: atxsdata.New(), + edVerifier: signing.NewEdVerifier(), + clock: mocks.mClock, + tickSize: tickSize, + bonusWeightEpoch: 0, + goldenATXID: golden, + nipostValidator: mocks.mValidator, + logger: logger, + fetcher: mocks.mockFetch, + beacon: mocks.mBeacon, + tortoise: mocks.mTortoise, + malPublisher: mocks.mMalPublish, }, tb: tb, observedLogs: observedLogs, @@ -93,6 +94,15 @@ func (h *handlerMocks) expectFetchDeps(atx *wire.ActivationTxV2) { } } +func (h *handlerMocks) expectVerifyPoetMembership(atx *wire.ActivationTxV2) { + h.mValidator.EXPECT().PoetMembership( + gomock.Any(), + gomock.Any(), + atx.NIPosts[0].Challenge, + gomock.Any(), + ).Return(poetLeaves, nil) +} + func (h *handlerMocks) expectVerifyNIPoST(atx *wire.ActivationTxV2) { h.mValidator.EXPECT().PostV2( gomock.Any(), @@ -103,20 +113,27 @@ func (h *handlerMocks) expectVerifyNIPoST(atx *wire.ActivationTxV2) { atx.NIPosts[0].Posts[0].NumUnits, gomock.Any(), ) - h.mValidator.EXPECT().PoetMembership( - gomock.Any(), - gomock.Any(), - atx.NIPosts[0].Challenge, - gomock.Any(), - ).Return(poetLeaves, nil) } -func (h *handlerMocks) expectVerifyNIPoSTs( +func (h *handlerMocks) expectVerifyPoetMemberships( atx *wire.ActivationTxV2, - equivocationSet []types.NodeID, poetLeaves []uint64, ) { for i, nipost := range atx.NIPosts { + h.mValidator.EXPECT().PoetMembership( + gomock.Any(), + gomock.Any(), + nipost.Challenge, + gomock.Any(), + ).Return(poetLeaves[i], nil) + } +} + +func (h *handlerMocks) expectVerifyNIPoSTs( + atx *wire.ActivationTxV2, + equivocationSet []types.NodeID, +) { + for _, nipost := range atx.NIPosts { for _, post := range nipost.Posts { h.mValidator.EXPECT().PostV2( gomock.Any(), @@ -128,12 +145,6 @@ func (h *handlerMocks) expectVerifyNIPoSTs( gomock.Any(), ) } - h.mValidator.EXPECT().PoetMembership( - gomock.Any(), - gomock.Any(), - nipost.Challenge, - gomock.Any(), - ).Return(poetLeaves[i], nil) } } @@ -164,6 +175,7 @@ func (h *handlerMocks) expectInitialAtxV2(atx *wire.ActivationTxV2) { ) h.expectFetchDeps(atx) + h.expectVerifyPoetMembership(atx) h.expectVerifyNIPoST(atx) h.expectStoreAtxV2(atx) } @@ -177,6 +189,7 @@ func (h *handlerMocks) expectAtxV2(atx *wire.ActivationTxV2) { atx.NIPosts[0].Posts[0].NumUnits, ) h.expectFetchDeps(atx) + h.expectVerifyPoetMembership(atx) h.expectVerifyNIPoST(atx) h.expectStoreAtxV2(atx) } @@ -194,7 +207,8 @@ func (h *handlerMocks) expectMergedAtxV2( atx.VRFNonce, atx.TotalNumUnits(), ) - h.expectVerifyNIPoSTs(atx, equivocationSet, poetLeaves) + h.expectVerifyPoetMemberships(atx, poetLeaves) + h.expectVerifyNIPoSTs(atx, equivocationSet) h.expectStoreAtxV2(atx) } @@ -501,6 +515,50 @@ func TestHandlerV2_ProcessSoloATX(t *testing.T) { err = atxHandler.processATX(t.Context(), atxHandler.local, atx, time.Now()) require.ErrorIs(t, err, errKnownAtx) }) + t.Run("initial ATX bonus weight", func(t *testing.T) { + t.Parallel() + atxHandler := newV2TestHandler(t, golden) + atxHandler.tickSize = tickSize + atxHandler.bonusWeightEpoch = 10 + + otherSig, err := signing.NewEdSigner() + require.NoError(t, err) + + commitmentAtx := newInitialATXv2(t, golden) + commitmentAtx.PublishEpoch = 8 + commitmentAtx.Sign(otherSig) + + atxHandler.expectInitialAtxV2(commitmentAtx) + err = atxHandler.processATX(t.Context(), atxHandler.local, commitmentAtx, time.Now()) + require.NoError(t, err) + + atx := newInitialATXv2(t, commitmentAtx.ID()) + atx.PublishEpoch = 10 + atx.Sign(sig) + + atxHandler.expectInitialAtxV2(atx) + err = atxHandler.processATX(t.Context(), atxHandler.local, atx, time.Now()) + require.NoError(t, err) + + cAtxFromDb, err := atxs.Get(atxHandler.cdb, commitmentAtx.ID()) + require.NoError(t, err) + + atxFromDb, err := atxs.Get(atxHandler.cdb, atx.ID()) + require.NoError(t, err) + + require.Equal(t, atx.ID(), atxFromDb.ID()) + require.Equal(t, atx.Coinbase, atxFromDb.Coinbase) + require.EqualValues(t, poetLeaves/tickSize, atxFromDb.TickCount) + require.EqualValues(t, cAtxFromDb.TickCount+atxFromDb.TickCount, atxFromDb.TickHeight()) + require.Equal(t, atx.NIPosts[0].Posts[0].NumUnits, atxFromDb.NumUnits) + + // 10% bonus weight + require.Equal(t, uint64(float64(atx.NIPosts[0].Posts[0].NumUnits*poetLeaves/tickSize)*1.1), atxFromDb.Weight) + + // processing ATX for the second time should skip checks + err = atxHandler.processATX(t.Context(), atxHandler.local, atx, time.Now()) + require.ErrorIs(t, err, errKnownAtx) + }) t.Run("second ATX", func(t *testing.T) { t.Parallel() atxHandler := newV2TestHandler(t, golden) @@ -524,13 +582,62 @@ func TestHandlerV2_ProcessSoloATX(t *testing.T) { require.Equal(t, atx.NIPosts[0].Posts[0].NumUnits, atxFromDb.NumUnits) require.EqualValues(t, atx.NIPosts[0].Posts[0].NumUnits*poetLeaves/tickSize, atxFromDb.Weight) }) + t.Run("second ATX bonus weight", func(t *testing.T) { + t.Parallel() + atxHandler := newV2TestHandler(t, golden) + atxHandler.bonusWeightEpoch = 10 + + otherSig, err := signing.NewEdSigner() + require.NoError(t, err) + + commitmentAtx := newInitialATXv2(t, golden) + commitmentAtx.PublishEpoch = 8 + commitmentAtx.Sign(otherSig) + + atxHandler.expectInitialAtxV2(commitmentAtx) + err = atxHandler.processATX(t.Context(), atxHandler.local, commitmentAtx, time.Now()) + require.NoError(t, err) + + prev := newInitialATXv2(t, commitmentAtx.ID()) + prev.PublishEpoch = 10 + prev.Sign(sig) + err = atxHandler.processInitial(prev) + require.NoError(t, err) + + atx := newSoloATXv2(t, prev.PublishEpoch+1, prev.ID(), prev.ID()) + atx.Sign(sig) + + atxHandler.expectAtxV2(atx) + err = atxHandler.processATX(t.Context(), atxHandler.local, atx, time.Now()) + require.NoError(t, err) + + prevAtx, err := atxs.Get(atxHandler.cdb, prev.ID()) + require.NoError(t, err) + atxFromDb, err := atxs.Get(atxHandler.cdb, atx.ID()) + require.NoError(t, err) + require.EqualValues(t, poetLeaves/tickSize, atxFromDb.TickCount) + require.EqualValues(t, prevAtx.TickHeight(), atxFromDb.BaseTickHeight) + require.EqualValues(t, prevAtx.TickHeight()+atxFromDb.TickCount, atxFromDb.TickHeight()) + require.Equal(t, atx.NIPosts[0].Posts[0].NumUnits, atxFromDb.NumUnits) + + // 20% bonus weight + require.Equal(t, uint64(float64(atx.NIPosts[0].Posts[0].NumUnits*poetLeaves/tickSize)*1.2), atxFromDb.Weight) + }) t.Run("second ATX, previous checkpointed", func(t *testing.T) { t.Parallel() atxHandler := newV2TestHandler(t, golden) - prev := atxs.CheckpointAtx{ + cATX := atxs.CheckpointAtx{ ID: types.RandomATXID(), CommitmentATX: types.RandomATXID(), + SmesherID: types.RandomNodeID(), + NumUnits: 10, + Units: map[types.NodeID]uint32{}, + } + require.NoError(t, atxs.AddCheckpointed(atxHandler.cdb, &cATX)) + prev := atxs.CheckpointAtx{ + ID: types.RandomATXID(), + CommitmentATX: cATX.ID, SmesherID: sig.NodeID(), NumUnits: 100, Units: map[types.NodeID]uint32{sig.NodeID(): 100}, @@ -577,6 +684,7 @@ func TestHandlerV2_ProcessSoloATX(t *testing.T) { atx.Sign(sig) atxHandler.mClock.EXPECT().CurrentLayer().Return(postGenesisEpoch.FirstLayer()) atxHandler.expectFetchDeps(atx) + atxHandler.expectVerifyPoetMembership(atx) atxHandler.expectVerifyNIPoST(atx) atxHandler.mValidator.EXPECT().VRFNonceV2( sig.NodeID(), @@ -634,6 +742,7 @@ func marryIDs( atxHandler *v2TestHandler, signers []*signing.EdSigner, golden types.ATXID, + bonusSigners ...*signing.EdSigner, ) (marriage *wire.ActivationTxV2, other []*wire.ActivationTxV2) { sig := signers[0] mATX := newInitialATXv2(tb, golden) @@ -650,6 +759,37 @@ func marryIDs( }) } + var commitmentAtxID types.ATXID + if len(bonusSigners) > 0 { + otherSig, err := signing.NewEdSigner() + require.NoError(tb, err) + + commitmentAtx := newInitialATXv2(tb, golden) + commitmentAtx.PublishEpoch = atxHandler.bonusWeightEpoch - 2 + commitmentAtx.Sign(otherSig) + + atxHandler.expectInitialAtxV2(commitmentAtx) + err = atxHandler.processATX(tb.Context(), atxHandler.local, commitmentAtx, time.Now()) + require.NoError(tb, err) + + commitmentAtxID = commitmentAtx.ID() + } + + for _, signer := range bonusSigners { + atx := newInitialATXv2(tb, commitmentAtxID) + atx.PublishEpoch = atxHandler.bonusWeightEpoch + atx.Sign(signer) + err := atxHandler.processInitial(atx) + require.NoError(tb, err) + + other = append(other, atx) + mATX.Marriages = append(mATX.Marriages, wire.MarriageCertificate{ + ReferenceAtx: atx.ID(), + Signature: signer.Sign(signing.MARRIAGE, sig.NodeID().Bytes()), + }) + } + mATX.PublishEpoch = atxHandler.bonusWeightEpoch + 1 + mATX.Sign(sig) atxHandler.expectInitialAtxV2(mATX) err := atxHandler.processATX(tb.Context(), atxHandler.local, mATX, time.Now()) @@ -710,6 +850,58 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { require.Equal(t, sig.NodeID(), atx.SmesherID) require.EqualValues(t, totalNumUnits*poetLeaves/tickSize, atx.Weight) }) + t.Run("happy case with bonus weight", func(t *testing.T) { + atxHandler := newV2TestHandler(t, golden) + atxHandler.bonusWeightEpoch = 10 + + normalSigners := signers[:3] + bonusSigners := signers[3:] + + // Marry IDs + mATX, otherATXs := marryIDs(t, atxHandler, normalSigners, golden, bonusSigners...) + previousATXs := []types.ATXID{mATX.ID()} + for _, atx := range otherATXs { + previousATXs = append(previousATXs, atx.ID()) + } + + // Process a merged ATX + merged := newSoloATXv2(t, mATX.PublishEpoch+2, mATX.ID(), mATX.ID()) + totalNumUnits := merged.NIPosts[0].Posts[0].NumUnits + totalWeight := merged.NIPosts[0].Posts[0].NumUnits * poetLeaves / tickSize + for i, atx := range otherATXs { + post := wire.SubPostV2{ + MarriageIndex: uint32(i + 1), + NumUnits: atx.TotalNumUnits(), + PrevATXIndex: uint32(i + 1), + } + factor := 1.0 + if slices.ContainsFunc(bonusSigners, func(sig *signing.EdSigner) bool { + return atx.SmesherID == sig.NodeID() + }) { + // merged ATX in `bonusWeightEpoch+3` + // so the weight should be 40% higher for eligible identities + factor = 1.4 + } + totalNumUnits += post.NumUnits + totalWeight += uint32(float64(post.NumUnits*poetLeaves/tickSize) * factor) + merged.NIPosts[0].Posts = append(merged.NIPosts[0].Posts, post) + } + mATXID := mATX.ID() + merged.MarriageATX = &mATXID + + merged.PreviousATXs = previousATXs + merged.Sign(sig) + + atxHandler.expectMergedAtxV2(merged, equivocationSet, []uint64{poetLeaves}) + err := atxHandler.processATX(t.Context(), atxHandler.local, merged, time.Now()) + require.NoError(t, err) + + atx, err := atxs.Get(atxHandler.cdb, merged.ID()) + require.NoError(t, err) + require.Equal(t, totalNumUnits, atx.NumUnits) + require.Equal(t, sig.NodeID(), atx.SmesherID) + require.EqualValues(t, totalWeight, atx.Weight) + }) t.Run("merged IDs on 4 poets", func(t *testing.T) { const tickSize = 33 atxHandler := newV2TestHandler(t, golden) @@ -812,7 +1004,8 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) - atxHandler.expectVerifyNIPoSTs(merged, equivocationSet, []uint64{200}) + atxHandler.expectVerifyPoetMemberships(merged, []uint64{200}) + atxHandler.expectVerifyNIPoSTs(merged, equivocationSet) err := atxHandler.processATX(t.Context(), atxHandler.local, merged, time.Now()) require.ErrorContains(t, err, "ATX signer not present in merged ATX") @@ -877,6 +1070,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) + atxHandler.expectVerifyPoetMemberships(merged, []uint64{200}) err := atxHandler.processATX(t.Context(), atxHandler.local, merged, time.Now()) require.ErrorIs(t, err, pubsub.ErrValidationReject) }) @@ -886,10 +1080,19 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { // Marry IDs mATX, _ := marryIDs(t, atxHandler, signers, golden) + cATX := atxs.CheckpointAtx{ + ID: types.RandomATXID(), + CommitmentATX: types.RandomATXID(), + SmesherID: types.RandomNodeID(), + NumUnits: 10, + Units: make(map[types.NodeID]uint32), + } + require.NoError(t, atxs.AddCheckpointed(atxHandler.cdb, &cATX)) + prev := atxs.CheckpointAtx{ Epoch: mATX.PublishEpoch + 1, ID: types.RandomATXID(), - CommitmentATX: types.RandomATXID(), + CommitmentATX: cATX.ID, SmesherID: sig.NodeID(), NumUnits: 10, Units: make(map[types.NodeID]uint32), @@ -941,6 +1144,7 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { atxHandler.mClock.EXPECT().CurrentLayer().Return(merged.PublishEpoch.FirstLayer()) atxHandler.expectFetchDeps(merged) + atxHandler.expectVerifyPoetMemberships(merged, []uint64{200}) err = atxHandler.processATX(t.Context(), atxHandler.local, merged, time.Now()) require.ErrorIs(t, err, pubsub.ErrValidationReject) }) @@ -1002,7 +1206,8 @@ func TestHandlerV2_ProcessMergedATX(t *testing.T) { merged.TotalNumUnits(), ) atxHandler.expectFetchDeps(merged) - atxHandler.expectVerifyNIPoSTs(merged, equivocationSet, []uint64{100}) + atxHandler.expectVerifyPoetMemberships(merged, []uint64{100}) + atxHandler.expectVerifyNIPoSTs(merged, equivocationSet) err = atxHandler.processATX(t.Context(), atxHandler.local, merged, time.Now()) require.ErrorContains(t, err, fmt.Sprintf("multiple ATXs with the same marriage ATX %s published in epoch %d", @@ -1677,6 +1882,7 @@ func TestHandlerV2_SyntacticallyValidateDeps(t *testing.T) { atx := newSoloATXv2(t, 2, prev.ID(), golden) atx.Sign(sig) + atxHandler.expectVerifyPoetMembership(atx) _, err = atxHandler.syntacticallyValidateDeps(t.Context(), atx, atxHandler.local) require.Error(t, err) }) @@ -2352,6 +2558,7 @@ func Test_Marriages(t *testing.T) { atx2.NIPosts[0].Posts[0].NumUnits, ) atxHandler.expectFetchDeps(atx2) + atxHandler.expectVerifyPoetMembership(atx2) atxHandler.expectVerifyNIPoST(atx2) err = atxHandler.processATX(t.Context(), atxHandler.local, atx2, time.Now()) @@ -2512,6 +2719,7 @@ func Test_Marriages(t *testing.T) { atx2.NIPosts[0].Posts[0].NumUnits, ) atxHandler.expectFetchDeps(atx2) + atxHandler.expectVerifyPoetMembership(atx2) atxHandler.expectVerifyNIPoST(atx2) err = atxHandler.processATX(t.Context(), atxHandler.local, atx2, time.Now()) @@ -2677,6 +2885,7 @@ func Test_Marriages(t *testing.T) { atx2.NIPosts[0].Posts[0].NumUnits, ) atxHandler.expectFetchDeps(atx2) + atxHandler.expectVerifyPoetMembership(atx2) atxHandler.expectVerifyNIPoST(atx2) err = atxHandler.processATX(t.Context(), atxHandler.local, atx2, time.Now()) @@ -2823,25 +3032,18 @@ func Test_MarryingMalicious(t *testing.T) { func Test_CalculatingUnits(t *testing.T) { t.Parallel() - t.Run("units on 1 nipost must not overflow", func(t *testing.T) { - t.Parallel() - ns := nipostSize{} - require.NoError(t, ns.addUnits(1)) - require.EqualValues(t, 1, ns.units) - require.Error(t, ns.addUnits(math.MaxUint32)) - }) t.Run("total units on all niposts must not overflow", func(t *testing.T) { t.Parallel() ns := make(nipostSizes, 0) ns = append(ns, &nipostSize{units: 11}, &nipostSize{units: math.MaxUint32 - 10}) - _, _, err := ns.sumUp() + _, _, err := ns.sumUp(0, 0) require.Error(t, err) }) t.Run("units = sum of units on every nipost", func(t *testing.T) { t.Parallel() ns := make(nipostSizes, 0) ns = append(ns, &nipostSize{units: 1}, &nipostSize{units: 10}) - u, _, err := ns.sumUp() + u, _, err := ns.sumUp(0, 0) require.NoError(t, err) require.EqualValues(t, 1+10, u) }) @@ -2952,7 +3154,8 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.expectFetchDeps(merged) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(false) atxHdlr.mValidator.EXPECT().VRFNonceV2(merged.SmesherID, gomock.Any(), merged.VRFNonce, merged.TotalNumUnits()) - atxHdlr.expectVerifyNIPoSTs(merged, eqSet, []uint64{100}) + atxHdlr.expectVerifyPoetMemberships(merged, []uint64{100}) + atxHdlr.expectVerifyNIPoSTs(merged, eqSet) err = atxHdlr.processATX(t.Context(), atxHdlr.local, merged, time.Now()) require.ErrorContains(t, err, fmt.Sprintf("multiple ATXs with the same previous ATX %s published by %s", @@ -3096,7 +3299,8 @@ func TestContextual_PreviousATX(t *testing.T) { atxHdlr.expectFetchDeps(merged) atxHdlr.mValidator.EXPECT().IsVerifyingFullPost().Return(false) atxHdlr.mValidator.EXPECT().VRFNonceV2(merged.SmesherID, gomock.Any(), merged.VRFNonce, merged.TotalNumUnits()) - atxHdlr.expectVerifyNIPoSTs(merged, []types.NodeID{sig1.NodeID(), sig2.NodeID()}, []uint64{100}) + atxHdlr.expectVerifyPoetMemberships(merged, []uint64{100}) + atxHdlr.expectVerifyNIPoSTs(merged, []types.NodeID{sig1.NodeID(), sig2.NodeID()}) err = atxHdlr.v2.processATX(t.Context(), atxHdlr.local, merged, time.Now()) require.ErrorContains(t, err, fmt.Sprintf("multiple ATXs with the same previous ATX %s published by %s", @@ -3241,6 +3445,7 @@ func TestContextual_PreviousATX(t *testing.T) { doubled.NIPosts[0].Posts[0].NumUnits, ) atxHdlr.expectFetchDeps(doubled) + atxHdlr.expectVerifyPoetMembership(doubled) atxHdlr.expectVerifyNIPoST(doubled) err = atxHdlr.processATX(t.Context(), atxHdlr.local, doubled, time.Now()) @@ -3256,14 +3461,56 @@ func Test_CalculatingWeight(t *testing.T) { t.Parallel() ns := make(nipostSizes, 0) ns = append(ns, &nipostSize{units: 1, ticks: 100}, &nipostSize{units: 10, ticks: math.MaxUint64}) - _, _, err := ns.sumUp() + _, _, err := ns.sumUp(0, 0) require.Error(t, err) }) t.Run("weight = sum of weight on every nipost", func(t *testing.T) { t.Parallel() ns := make(nipostSizes, 0) ns = append(ns, &nipostSize{units: 1, ticks: 100}, &nipostSize{units: 10, ticks: 1000}) - _, w, err := ns.sumUp() + _, w, err := ns.sumUp(0, 0) + require.NoError(t, err) + require.EqualValues(t, 1*100+10*1000, w) + }) + t.Run("weight is not increased for non-eligible identities", func(t *testing.T) { + t.Parallel() + const bonusWeightEpoch = 10 + + ns := make(nipostSizes, 0) + ns = append( + ns, + &nipostSize{units: 1, ticks: 100, commitmentEpoch: 5}, + &nipostSize{units: 10, ticks: 1000, commitmentEpoch: 5}, + ) + _, w, err := ns.sumUp(bonusWeightEpoch, bonusWeightEpoch) + require.NoError(t, err) + require.EqualValues(t, 1*100+10*1000, w) + }) + t.Run("weight is increased for eligible identities", func(t *testing.T) { + t.Parallel() + const bonusWeightEpoch = 10 + + ns := make(nipostSizes, 0) + ns = append( + ns, + &nipostSize{units: 1, ticks: 100, commitmentEpoch: 5}, + &nipostSize{units: 10, ticks: 1000, commitmentEpoch: 8}, + ) + _, w, err := ns.sumUp(bonusWeightEpoch, bonusWeightEpoch+1) + require.NoError(t, err) + require.EqualValues(t, 1*100+uint64(10*1000*1.2), w) // second identity gets 20% bonus + }) + t.Run("weight is not increased for eligible identities before bonus epoch", func(t *testing.T) { + t.Parallel() + const bonusWeightEpoch = 10 + + ns := make(nipostSizes, 0) + ns = append( + ns, + &nipostSize{units: 1, ticks: 100, commitmentEpoch: 5}, + &nipostSize{units: 10, ticks: 1000, commitmentEpoch: 8}, + ) + _, w, err := ns.sumUp(bonusWeightEpoch, bonusWeightEpoch-1) require.NoError(t, err) require.EqualValues(t, 1*100+10*1000, w) }) diff --git a/activation/validation_test.go b/activation/validation_test.go index 65e6c8ffc8..7ea113d102 100644 --- a/activation/validation_test.go +++ b/activation/validation_test.go @@ -678,6 +678,7 @@ func TestVerifyChainDepsAfterCheckpoint(t *testing.T) { NumUnits: vCheckpointedAtx.NumUnits, BaseTickHeight: vCheckpointedAtx.BaseTickHeight, TickCount: vCheckpointedAtx.TickCount, + Weight: vCheckpointedAtx.Weight, SmesherID: vCheckpointedAtx.SmesherID, Sequence: vCheckpointedAtx.Sequence, Coinbase: vCheckpointedAtx.Coinbase, diff --git a/checkpoint/checkpointdata.json b/checkpoint/checkpointdata.json index a2082ae276..1d8612f64a 100644 --- a/checkpoint/checkpointdata.json +++ b/checkpoint/checkpointdata.json @@ -11,6 +11,7 @@ "vrfNonce": 6637, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "ACC97STWCRc+fWqHI0wJub1eOJ8BrBY6gA67kDGPm6Y=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -26,6 +27,7 @@ "vrfNonce": 6637, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "ACC97STWCRc+fWqHI0wJub1eOJ8BrBY6gA67kDGPm6Y=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -41,6 +43,7 @@ "vrfNonce": 21089, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "AUv/xfD3TEfFuh4fAUv1w3SEvxu8j2nRcaLThno0AW8=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -56,6 +59,7 @@ "vrfNonce": 21089, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "AUv/xfD3TEfFuh4fAUv1w3SEvxu8j2nRcaLThno0AW8=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -71,6 +75,7 @@ "vrfNonce": 13207, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "AooC1fiukLEwg1MVyMzBvSPAU3fv1ofIKVASTPqjZ2k=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -86,6 +91,7 @@ "vrfNonce": 13207, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "AooC1fiukLEwg1MVyMzBvSPAU3fv1ofIKVASTPqjZ2k=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -101,6 +107,7 @@ "vrfNonce": 12343, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "ArEQOhpAwiUNpATNhXPIY3HIHkabho1kASKsNNdKdrE=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -116,6 +123,7 @@ "vrfNonce": 12343, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "ArEQOhpAwiUNpATNhXPIY3HIHkabho1kASKsNNdKdrE=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -131,6 +139,7 @@ "vrfNonce": 15243, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "A+FBnUB2mBCTBkZ/E0JuONQ4xEsXKJmvj0ubJjZ/zBU=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -146,6 +155,7 @@ "vrfNonce": 15243, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "A+FBnUB2mBCTBkZ/E0JuONQ4xEsXKJmvj0ubJjZ/zBU=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -161,6 +171,7 @@ "vrfNonce": 6570, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "A/2tBH5znWAlCDJngpx8t/JZuLyiMANppmocJrYpPkM=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -176,6 +187,7 @@ "vrfNonce": 6570, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "A/2tBH5znWAlCDJngpx8t/JZuLyiMANppmocJrYpPkM=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -191,6 +203,7 @@ "vrfNonce": 30840, "baseTickHeight": 0, "tickCount": 5909, + "weight": 194997, "publicKey": "BAFb70vXDTH7UXJFIYnz5kL5PUYHK8BlP4zDaVUM2r4=", "sequence": 0, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -206,6 +219,7 @@ "vrfNonce": 22475, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "BRIqQP4O16gY5ddbIfToK0vESEwpcO3Ky0RIb72D3QE=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -221,6 +235,7 @@ "vrfNonce": 22475, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "BRIqQP4O16gY5ddbIfToK0vESEwpcO3Ky0RIb72D3QE=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -236,6 +251,7 @@ "vrfNonce": 39956, "baseTickHeight": 11821, "tickCount": 4787, + "weight": 478700, "publicKey": "Bcl1FXmzkjAUmyac546RFNGqyrtDI7dki9L8nW7rTcs=", "sequence": 0, "coinbase": "AAAAAAHAd/lY2IYJI8eC1f9WpvJAJXEY", @@ -251,6 +267,7 @@ "vrfNonce": 30668, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "BdGU5/LH7x6kyR8lmYf3o3zyxr+P3ijeE+5gPBSLY/0=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -266,6 +283,7 @@ "vrfNonce": 30668, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "BdGU5/LH7x6kyR8lmYf3o3zyxr+P3ijeE+5gPBSLY/0=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -281,6 +299,7 @@ "vrfNonce": 3885, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "Bhe+OatEVxQ0TpMcowLWe1ZGA90JMzVfk3XEKwsupT4=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -296,6 +315,7 @@ "vrfNonce": 3885, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "Bhe+OatEVxQ0TpMcowLWe1ZGA90JMzVfk3XEKwsupT4=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -311,6 +331,7 @@ "vrfNonce": 690, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "BmuZafq227zV95QKGpdWNqfV+lniAndpD7gXfaMtNCg=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -326,6 +347,7 @@ "vrfNonce": 690, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "BmuZafq227zV95QKGpdWNqfV+lniAndpD7gXfaMtNCg=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -341,6 +363,7 @@ "vrfNonce": 11475, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "B9JKogxn8v0o9xHuJhsd2tH3eI4npW6nB1fKOpZLwfM=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -356,6 +379,7 @@ "vrfNonce": 11475, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "B9JKogxn8v0o9xHuJhsd2tH3eI4npW6nB1fKOpZLwfM=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -371,6 +395,7 @@ "vrfNonce": 23399, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "CEgz/V3ovWdQTQXGNmP8sfzZo8TwwLkXlABmvBMEwuw=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -386,6 +411,7 @@ "vrfNonce": 23399, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "CEgz/V3ovWdQTQXGNmP8sfzZo8TwwLkXlABmvBMEwuw=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -401,6 +427,7 @@ "vrfNonce": 32241, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "Cob8IKp/r+AviRD6k5aqEhiNL3DB8yl0lXeCRi8bvcE=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -416,6 +443,7 @@ "vrfNonce": 32241, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "Cob8IKp/r+AviRD6k5aqEhiNL3DB8yl0lXeCRi8bvcE=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -431,6 +459,7 @@ "vrfNonce": 18075, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "Co0dYdJWOa/oWQ7sNnFpd0txWWf7X4imqTBBaFTG1NE=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -446,6 +475,7 @@ "vrfNonce": 18075, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "Co0dYdJWOa/oWQ7sNnFpd0txWWf7X4imqTBBaFTG1NE=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -461,6 +491,7 @@ "vrfNonce": 24726, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "CpJgf2aYfGvPzj71QNxuUeaQKbGXDh7XJbGiju5toIU=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -476,6 +507,7 @@ "vrfNonce": 24726, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "CpJgf2aYfGvPzj71QNxuUeaQKbGXDh7XJbGiju5toIU=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -491,6 +523,7 @@ "vrfNonce": 23717, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "Cr02ZM2IWdxToYodz2vXvZQqEIVt8tm9mTbCzv1lPmA=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -506,6 +539,7 @@ "vrfNonce": 23717, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "Cr02ZM2IWdxToYodz2vXvZQqEIVt8tm9mTbCzv1lPmA=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -521,6 +555,7 @@ "vrfNonce": 22040, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "C0pHznKOU/MbCW3pfUBlkR7QMmOCIoYPs+KZRPd7k/k=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -536,6 +571,7 @@ "vrfNonce": 22040, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "C0pHznKOU/MbCW3pfUBlkR7QMmOCIoYPs+KZRPd7k/k=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -551,6 +587,7 @@ "vrfNonce": 15322, "baseTickHeight": 0, "tickCount": 5909, + "weight": 194997, "publicKey": "C4iiN5H06+0Y2w7mGqdxUPSlDtOxqA+gp6r6nDVoD8s=", "sequence": 0, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -566,6 +603,7 @@ "vrfNonce": 23673, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "C5nht7r1IApif0neIQwWNzBMajX7uXRMxt05giYXhnE=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -581,6 +619,7 @@ "vrfNonce": 23673, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "C5nht7r1IApif0neIQwWNzBMajX7uXRMxt05giYXhnE=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -596,6 +635,7 @@ "vrfNonce": 26008, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "C+PrXQcTh3wbdmMpLi37vRhAoZAJE4f6/XstKX//9oU=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -611,6 +651,7 @@ "vrfNonce": 26008, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "C+PrXQcTh3wbdmMpLi37vRhAoZAJE4f6/XstKX//9oU=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -626,6 +667,7 @@ "vrfNonce": 22684, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "DAKMJyRfCnl2MeJvu87cTq4gC7PVtTd7V7k4W174hWU=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -641,6 +683,7 @@ "vrfNonce": 22684, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "DAKMJyRfCnl2MeJvu87cTq4gC7PVtTd7V7k4W174hWU=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -656,6 +699,7 @@ "vrfNonce": 12265, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "DN9/1ySs9MOgeU0E/5qOz/JiQxjxssb+f+5SKK+qL9Q=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -671,6 +715,7 @@ "vrfNonce": 12265, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "DN9/1ySs9MOgeU0E/5qOz/JiQxjxssb+f+5SKK+qL9Q=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -686,6 +731,7 @@ "vrfNonce": 22674, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "DOYm61QwL7dRW1jbLSJqWRz0f+qa0CWCsFy2nPBLjno=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -701,6 +747,7 @@ "vrfNonce": 22674, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "DOYm61QwL7dRW1jbLSJqWRz0f+qa0CWCsFy2nPBLjno=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -716,6 +763,7 @@ "vrfNonce": 4922, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "DT5rESAWYlrwdWb6yxNdzmBz8h+r2pT5ZcR1C24Tx/k=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -731,6 +779,7 @@ "vrfNonce": 4922, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "DT5rESAWYlrwdWb6yxNdzmBz8h+r2pT5ZcR1C24Tx/k=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -746,6 +795,7 @@ "vrfNonce": 16077, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "DciYS9tzU+k6iEdTuFRbWsP+XyXQsjZc+VnU6+H9u4s=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -761,6 +811,7 @@ "vrfNonce": 16077, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "DciYS9tzU+k6iEdTuFRbWsP+XyXQsjZc+VnU6+H9u4s=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -776,6 +827,7 @@ "vrfNonce": 9675, "baseTickHeight": 11821, "tickCount": 4787, + "weight": 478700, "publicKey": "Dcket7piCC4pYPLmSW+kyOSJVMJwSVOK0tjL9YVzPCo=", "sequence": 0, "coinbase": "AAAAAAHAd/lY2IYJI8eC1f9WpvJAJXEY", @@ -791,6 +843,7 @@ "vrfNonce": 58506, "baseTickHeight": 11821, "tickCount": 4787, + "weight": 478700, "publicKey": "DiFP9U4LWkak2kEvWlv/vq0oJzq1rVaA9R1tF4peXlQ=", "sequence": 2, "coinbase": "AAAAAGESmyCRIgdK34zMqo6c3mPx08gk", @@ -806,6 +859,7 @@ "vrfNonce": 58506, "baseTickHeight": 5909, "tickCount": 4738, + "weight": 473800, "publicKey": "DiFP9U4LWkak2kEvWlv/vq0oJzq1rVaA9R1tF4peXlQ=", "sequence": 1, "coinbase": "AAAAAGESmyCRIgdK34zMqo6c3mPx08gk", @@ -821,6 +875,7 @@ "vrfNonce": 7966, "baseTickHeight": 11821, "tickCount": 4787, + "weight": 478700, "publicKey": "Dv1yHWybFg0snsR7l2+ytj9oTMWRW7TeZJ5zI4oaBSo=", "sequence": 0, "coinbase": "AAAAAAHAd/lY2IYJI8eC1f9WpvJAJXEY", @@ -836,6 +891,7 @@ "vrfNonce": 23535, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "D6p6WsfM/IgCUSUPAA0/SXMl1TQypSD9y/YBgRnP9GM=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -851,6 +907,7 @@ "vrfNonce": 23535, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "D6p6WsfM/IgCUSUPAA0/SXMl1TQypSD9y/YBgRnP9GM=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -866,6 +923,7 @@ "vrfNonce": 32634, "baseTickHeight": 0, "tickCount": 5909, + "weight": 194997, "publicKey": "EDfbWmIKHweeRzVwqKazchfvA5D6peR5SrwzW1Kspq8=", "sequence": 0, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -881,6 +939,7 @@ "vrfNonce": 7705, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "EGstlX/VPlNT4hQ9KX60ZgrOiuVlJxON/3bn3TVnlL0=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -896,6 +955,7 @@ "vrfNonce": 7705, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "EGstlX/VPlNT4hQ9KX60ZgrOiuVlJxON/3bn3TVnlL0=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -911,6 +971,7 @@ "vrfNonce": 17002, "baseTickHeight": 11821, "tickCount": 4787, + "weight": 478700, "publicKey": "EIcsZQ566PgTeHCHZuL/2DU5wJga8CYgplfaHI7dm74=", "sequence": 0, "coinbase": "AAAAAAHAd/lY2IYJI8eC1f9WpvJAJXEY", @@ -926,6 +987,7 @@ "vrfNonce": 94029, "baseTickHeight": 11821, "tickCount": 4787, + "weight": 478700, "publicKey": "EMAIBBWiUbOVERLDoqxCJ0cR77AbE5rL7oS/zYn/QcI=", "sequence": 0, "coinbase": "AAAAAAHAd/lY2IYJI8eC1f9WpvJAJXEY", @@ -941,6 +1003,7 @@ "vrfNonce": 14750, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "EQPH+3yXmod4ytUJ6e+dm+bhS4xy//ILsy32RQqlX2s=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -956,6 +1019,7 @@ "vrfNonce": 14750, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "EQPH+3yXmod4ytUJ6e+dm+bhS4xy//ILsy32RQqlX2s=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -971,6 +1035,7 @@ "vrfNonce": 32319, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "ESjbBN+t7xbFXgctEIJ4feolbXXSFk8i5q3h++Mrq9U=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -986,6 +1051,7 @@ "vrfNonce": 32319, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "ESjbBN+t7xbFXgctEIJ4feolbXXSFk8i5q3h++Mrq9U=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1001,6 +1067,7 @@ "vrfNonce": 16699, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "ETG4uCa9tKcGWH0H2d7g5wPVglAm7Zhqb240LblwCa4=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1016,6 +1083,7 @@ "vrfNonce": 16699, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "ETG4uCa9tKcGWH0H2d7g5wPVglAm7Zhqb240LblwCa4=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1031,6 +1099,7 @@ "vrfNonce": 31738, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "EYAQ4iynbSb5We+vPV727FlL5Exdl6scHAoROHb65Oo=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1046,6 +1115,7 @@ "vrfNonce": 31738, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "EYAQ4iynbSb5We+vPV727FlL5Exdl6scHAoROHb65Oo=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1061,6 +1131,7 @@ "vrfNonce": 6091, "baseTickHeight": 11821, "tickCount": 4787, + "weight": 478700, "publicKey": "EoRzhZBmKrGknTfXtE8MKdfCyc6cr9Z0FVxkqj17QLA=", "sequence": 0, "coinbase": "AAAAAAHAd/lY2IYJI8eC1f9WpvJAJXEY", @@ -1076,6 +1147,7 @@ "vrfNonce": 1323, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "EpllxhigTIVayGrULrE5AFb85bwBsXmsS0WeBBsQ48w=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1091,6 +1163,7 @@ "vrfNonce": 1323, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "EpllxhigTIVayGrULrE5AFb85bwBsXmsS0WeBBsQ48w=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1106,6 +1179,7 @@ "vrfNonce": 33164, "baseTickHeight": 11821, "tickCount": 5913, + "weight": 195129, "publicKey": "Ew1eFmCcpVSCD37NB6JQ1OU6dLWTASB78E03u2xO+hI=", "sequence": 2, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", @@ -1121,6 +1195,7 @@ "vrfNonce": 33164, "baseTickHeight": 5909, "tickCount": 5912, + "weight": 195096, "publicKey": "Ew1eFmCcpVSCD37NB6JQ1OU6dLWTASB78E03u2xO+hI=", "sequence": 1, "coinbase": "AAAAAEWFYdzEv+mfQmEIRhtXR7IavXQl", diff --git a/checkpoint/recovery.go b/checkpoint/recovery.go index f740eaa0df..280764d369 100644 --- a/checkpoint/recovery.go +++ b/checkpoint/recovery.go @@ -371,6 +371,7 @@ func checkpointData(fs afero.Fs, file string, newGenesis types.LayerID) (*recove cAtx.VRFNonce = types.VRFPostIndex(atx.VrfNonce) cAtx.BaseTickHeight = atx.BaseTickHeight cAtx.TickCount = atx.TickCount + cAtx.Weight = atx.Weight cAtx.Sequence = atx.Sequence copy(cAtx.Coinbase[:], atx.Coinbase) cAtx.Units = atx.Units diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index 7c7dc96842..02c3c0e271 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -58,6 +58,7 @@ func atxEqual( require.Equal(tb, sAtx.NumUnits, vAtx.NumUnits) require.Equal(tb, sAtx.BaseTickHeight, vAtx.BaseTickHeight) require.Equal(tb, sAtx.TickCount, vAtx.TickCount) + require.Equal(tb, sAtx.Weight, vAtx.Weight) require.True(tb, bytes.Equal(sAtx.PublicKey, vAtx.SmesherID.Bytes())) require.Equal(tb, sAtx.Sequence, vAtx.Sequence) require.True(tb, bytes.Equal(sAtx.Coinbase, vAtx.Coinbase.Bytes())) @@ -808,6 +809,7 @@ func TestRecover_OwnAtxNotInCheckpoint_Preserve_DepIsGolden(t *testing.T) { CommitmentATX: *golden.CommitmentATXID, VRFNonce: types.VRFPostIndex(*golden.VRFNonce), NumUnits: golden.NumUnits, + Weight: uint64(golden.NumUnits * 2), SmesherID: golden.SmesherID, Sequence: golden.Sequence, Coinbase: golden.Coinbase, diff --git a/checkpoint/runner.go b/checkpoint/runner.go index fa7fb9fc9f..a5248d0571 100644 --- a/checkpoint/runner.go +++ b/checkpoint/runner.go @@ -98,6 +98,7 @@ func checkpointDB( NumUnits: catx.NumUnits, BaseTickHeight: catx.BaseTickHeight, TickCount: catx.TickCount, + Weight: catx.Weight, PublicKey: catx.SmesherID.Bytes(), Sequence: catx.Sequence, Coinbase: catx.Coinbase.Bytes(), @@ -158,6 +159,7 @@ func checkpointDB( NumUnits: atx.NumUnits, BaseTickHeight: atx.BaseTickHeight, TickCount: atx.TickCount, + Weight: atx.Weight, PublicKey: atx.SmesherID.Bytes(), Sequence: atx.Sequence, Coinbase: atx.Coinbase.Bytes(), diff --git a/checkpoint/runner_test.go b/checkpoint/runner_test.go index c60ee76dce..73ad16667a 100644 --- a/checkpoint/runner_test.go +++ b/checkpoint/runner_test.go @@ -276,6 +276,7 @@ func asAtxSnapshot(v *types.ActivationTx, cmt *types.ATXID) types.AtxSnapshot { NumUnits: v.NumUnits, BaseTickHeight: v.BaseTickHeight, TickCount: v.TickCount, + Weight: v.Weight, PublicKey: v.SmesherID.Bytes(), Sequence: v.Sequence, Coinbase: v.Coinbase.Bytes(), diff --git a/checkpoint/schema.json b/checkpoint/schema.json index 88254a0b80..85c70be281 100644 --- a/checkpoint/schema.json +++ b/checkpoint/schema.json @@ -55,6 +55,9 @@ "tickCount": { "type": "integer" }, + "weight": { + "type": "integer" + }, "publicKey": { "type": "string" }, @@ -74,7 +77,7 @@ } } }, - "required": ["id", "epoch", "commitmentAtx", "vrfNonce", "baseTickHeight", "tickCount", "publicKey", "sequence", "coinbase", "numUnits", "units"] + "required": ["id", "epoch", "commitmentAtx", "vrfNonce", "baseTickHeight", "tickCount", "weight", "publicKey", "sequence", "coinbase", "numUnits", "units"] }, "accounts": { "description": "accounts snapshot", diff --git a/checkpoint/util_test.go b/checkpoint/util_test.go index 06840488a0..f3c27a45df 100644 --- a/checkpoint/util_test.go +++ b/checkpoint/util_test.go @@ -31,38 +31,43 @@ func TestValidateSchema(t *testing.T) { desc: "missing atx", fail: true, data: ` -{"version":"https://spacemesh.io/checkpoint.schema.json.1.0", -"data":{ - "id":"snapshot-15-restore-18", - "accounts":[{ - "address":"00000000073af7bec018e8d2e379fa47df6a9fa07a6a8344", - "balance":100000000000000000, - "nonce":0, - "template":"", - "state":"" - }]} -`, +{ + "version":"https://spacemesh.io/checkpoint.schema.json.1.0", + "data":{ + "id":"snapshot-15-restore-18", + "accounts":[{ + "address":"00000000073af7bec018e8d2e379fa47df6a9fa07a6a8344", + "balance":100000000000000000, + "nonce":0, + "template":"", + "state":"" + }] + } +}`, }, { desc: "missing accounts", fail: true, data: ` -{"version":"https://spacemesh.io/checkpoint.schema.json.1.0", -"data":{ - "id":"snapshot-15-restore-18", - "atxs":[{ - "id":"d1ef13c8deb8970c19af780f6ce8bbdabfad368afe219ed052fde6766e121cbb", - "epoch":3, - "commitmentAtx":"c146f83c2b0f670c7e34e30699536a60b6d5eb13d8f0b63adb6084872c4f3b8d", - "vrfNonce":144, - "numUnits":2, - "baseTickHeight":12401, - "tickCount":6183, - "publicKey":"0655283aa44b67e7dcbde46be857334033f5b9af79ec269f45e8e57e7913ed21", - "sequence":2, - "coinbase":"000000003100000000000000000000000000000000000000" - }]} -`, +{ + "version":"https://spacemesh.io/checkpoint.schema.json.1.0", + "data":{ + "id":"snapshot-15-restore-18", + "atxs":[{ + "id":"d1ef13c8deb8970c19af780f6ce8bbdabfad368afe219ed052fde6766e121cbb", + "epoch":3, + "commitmentAtx":"c146f83c2b0f670c7e34e30699536a60b6d5eb13d8f0b63adb6084872c4f3b8d", + "vrfNonce":144, + "numUnits":2, + "baseTickHeight":12401, + "tickCount":6183, + "weight":12366, + "publicKey":"0655283aa44b67e7dcbde46be857334033f5b9af79ec269f45e8e57e7913ed21", + "sequence":2, + "coinbase":"000000003100000000000000000000000000000000000000" + }] + } +}`, }, } for _, tc := range tcs { diff --git a/common/types/activation.go b/common/types/activation.go index ba7c684bf3..2142bf3b76 100644 --- a/common/types/activation.go +++ b/common/types/activation.go @@ -183,7 +183,7 @@ type ActivationTx struct { VRFNonce VRFPostIndex SmesherID NodeID // Weight of the ATX. The total weight of the epoch is expected to fit in a uint64. - // The total ATX weight is sum(NumUnits * TickCount) for identity it holds. + // The total ATX weight is sum(NumUnits * TickCount) for old identities and 2x as much for newer identities. // Space Units sizes are chosen such that NumUnits for all ATXs in an epoch is expected to be < 10^6. // PoETs should produce ~10k ticks at genesis, but are expected due to technological advances // to produce more over time. A uint64 should be large enough to hold the total weight of an epoch, diff --git a/common/types/checkpoint.go b/common/types/checkpoint.go index 5ae397d08f..d0d8ff3eb6 100644 --- a/common/types/checkpoint.go +++ b/common/types/checkpoint.go @@ -21,6 +21,7 @@ type AtxSnapshot struct { VrfNonce uint64 `json:"vrfNonce"` BaseTickHeight uint64 `json:"baseTickHeight"` TickCount uint64 `json:"tickCount"` + Weight uint64 `json:"weight"` PublicKey []byte `json:"publicKey"` Sequence uint64 `json:"sequence"` Coinbase []byte `json:"coinbase"` diff --git a/config/config.go b/config/config.go index e5c2033133..2070857aea 100644 --- a/config/config.go +++ b/config/config.go @@ -113,6 +113,14 @@ type BaseConfig struct { OptFilterThreshold int `mapstructure:"optimistic-filtering-threshold"` TickSize uint64 `mapstructure:"tick-size"` + // BonusWeightEpoch is the epoch at which the bonus weight added for newly created identities. + // The bonus applies from this epoch, if the commitment ATX that was used to create the identity + // isn't older than `BonusWeightEpoch-2`. + // + // Example: if the bonus weight epoch is 10, any identity created with a commitment ATX that was created + // in epoch 8 or later will receive the bonus weight. + BonusWeightEpoch types.EpochID `mapstructure:"bonus-weight-epoch"` + DatabaseConnections int `mapstructure:"db-connections"` DatabaseLatencyMetering bool `mapstructure:"db-latency-metering"` DatabaseSizeMeteringInterval time.Duration `mapstructure:"db-size-metering-interval"` diff --git a/config/mainnet.go b/config/mainnet.go index 1b6ceac0d8..40550eddea 100644 --- a/config/mainnet.go +++ b/config/mainnet.go @@ -104,6 +104,8 @@ func MainnetConfig() Config { LayerAvgSize: 50, LayersPerEpoch: 4032, + BonusWeightEpoch: 50, // TODO(mafa): set to correct value + TxsPerProposal: 700, // https://github.com/spacemeshos/go-spacemesh/issues/4559 BlockGasLimit: 100107000, // 3000 of spends diff --git a/config/presets/fastnet.go b/config/presets/fastnet.go index 6ea97952f5..80aa2c3e44 100644 --- a/config/presets/fastnet.go +++ b/config/presets/fastnet.go @@ -33,6 +33,8 @@ func fastnet() config.Config { types.EpochID(2): types.AtxV2, } + conf.BaseConfig.BonusWeightEpoch = 3 + // node will select atxs that were received at least 4 seconds before start of the epoch // for activeset. // if some atxs weren't received on time it will skew eligibility distribution diff --git a/mesh/mesh.go b/mesh/mesh.go index 2326d1947a..9beb3b1684 100644 --- a/mesh/mesh.go +++ b/mesh/mesh.go @@ -145,7 +145,8 @@ func (msh *Mesh) recoverFromDB(latest types.LayerID) { } msh.logger.Info("recovered mesh from disk", zap.Stringer("latest", msh.LatestLayer()), - zap.Stringer("processed", msh.ProcessedLayer())) + zap.Stringer("processed", msh.ProcessedLayer()), + ) } // LatestLayerInState returns the latest layer we applied to state. diff --git a/node/node.go b/node/node.go index a5299b0e68..4974485997 100644 --- a/node/node.go +++ b/node/node.go @@ -890,6 +890,7 @@ func (app *App) initServices(ctx context.Context) error { trtl, app.addLogger(ATXHandlerLogger, lg).Zap(), activation.WithTickSize(app.Config.TickSize), + activation.WithBonusWeightEpoch(app.Config.BonusWeightEpoch), activation.WithAtxVersions(app.Config.AtxVersions), ) diff --git a/sql/atxs/atxs.go b/sql/atxs/atxs.go index 3653769f21..41ba1b13aa 100644 --- a/sql/atxs/atxs.go +++ b/sql/atxs/atxs.go @@ -485,8 +485,8 @@ func Add(db sql.Executor, atx *types.ActivationTx, blob types.AtxBlob) error { _, err := db.Exec(` insert into atxs (id, epoch, effective_num_units, commitment_atx, nonce, - pubkey, received, base_tick_height, tick_count, sequence, coinbase, - validity, weight, marriage_atx) + pubkey, received, base_tick_height, tick_count, sequence, coinbase, + validity, weight, marriage_atx) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)`, enc, nil) if err != nil { return fmt.Errorf("insert ATX ID %v: %w", atx.ID(), err) @@ -589,6 +589,7 @@ type CheckpointAtx struct { Coinbase types.Address // total effective units NumUnits uint32 + Weight uint64 // actual units of each included smesher Units map[types.NodeID]uint32 } @@ -614,18 +615,19 @@ func LatestN(db sql.Executor, n int) ([]CheckpointAtx, error) { catx.MarriageATX = new(types.ATXID) stmt.ColumnBytes(9, catx.MarriageATX[:]) } + catx.Weight = uint64(stmt.ColumnInt64(10)) rst = append(rst, catx) return true } rows, err := db.Exec(` - select - id, epoch, effective_num_units, base_tick_height, tick_count, pubkey, sequence, coinbase, nonce, marriage_atx - from ( - select row_number() over (partition by pubkey order by epoch desc) RowNum, + SELECT id, epoch, effective_num_units, base_tick_height, tick_count, pubkey, sequence, coinbase, nonce, + marriage_atx, weight + FROM ( + SELECT row_number() over (partition by pubkey order by epoch desc) RowNum, id, epoch, effective_num_units, base_tick_height, tick_count, pubkey, sequence, coinbase, nonce, - marriage_atx - from atxs + marriage_atx, weight + FROM atxs ) where RowNum <= ?1 order by pubkey;`, enc, dec) switch { @@ -660,12 +662,13 @@ func AddCheckpointed(db sql.Executor, catx *CheckpointAtx) error { if catx.MarriageATX != nil { stmt.BindBytes(11, catx.MarriageATX.Bytes()) } + stmt.BindInt64(12, int64(catx.Weight)) } _, err := db.Exec(` - insert into atxs (id, epoch, effective_num_units, commitment_atx, nonce, - base_tick_height, tick_count, sequence, pubkey, coinbase, marriage_atx, received) - values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, 0)`, enc, nil) + INSERT INTO atxs (id, epoch, effective_num_units, commitment_atx, nonce, + base_tick_height, tick_count, sequence, pubkey, coinbase, marriage_atx, weight, received) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, 0)`, enc, nil) if err != nil { return fmt.Errorf("insert checkpoint ATX %v: %w", catx.ID, err) } @@ -731,27 +734,28 @@ func IterateAtxsData( nonce types.VRFPostIndex, ) bool, ) error { - _, err := db.Exec( - `SELECT id, pubkey, epoch, coinbase, effective_num_units, base_tick_height, tick_count, nonce FROM atxs - WHERE epoch between ?1 and ?2`, + _, err := db.Exec(` + SELECT id, pubkey, epoch, coinbase, weight, base_tick_height, tick_count, nonce + FROM atxs + WHERE epoch BETWEEN ?1 AND ?2`, // filtering in CODE is no longer effective on some machines in epoch 29 func(stmt *sql.Statement) { stmt.BindInt64(1, int64(from.Uint32())) stmt.BindInt64(2, int64(to.Uint32())) }, func(stmt *sql.Statement) bool { - epoch := types.EpochID(uint32(stmt.ColumnInt64(2))) var id types.ATXID stmt.ColumnBytes(0, id[:]) var node types.NodeID stmt.ColumnBytes(1, node[:]) + epoch := types.EpochID(stmt.ColumnInt64(2)) var coinbase types.Address stmt.ColumnBytes(3, coinbase[:]) - effectiveUnits := uint64(stmt.ColumnInt64(4)) + weight := uint64(stmt.ColumnInt64(4)) baseHeight := uint64(stmt.ColumnInt64(5)) ticks := uint64(stmt.ColumnInt64(6)) nonce := types.VRFPostIndex(stmt.ColumnInt64(7)) - return fn(id, node, epoch, coinbase, effectiveUnits*ticks, baseHeight, baseHeight+ticks, nonce) + return fn(id, node, epoch, coinbase, weight, baseHeight, baseHeight+ticks, nonce) }, ) if err != nil { @@ -805,7 +809,7 @@ func IterateForGrading( fn func(id types.ATXID, atxTime, proofTime int64, weight uint64) bool, ) error { if _, err := db.Exec(` - SELECT atxs.id, atxs.received, effective_num_units, tick_count, identities.received, malfeasance.received + SELECT atxs.id, atxs.received, weight, identities.received, malfeasance.received FROM atxs LEFT JOIN identities ON atxs.pubkey = identities.pubkey LEFT JOIN malfeasance ON atxs.pubkey = malfeasance.pubkey @@ -816,22 +820,21 @@ func IterateForGrading( id := types.ATXID{} stmt.ColumnBytes(0, id[:]) atxTime := stmt.ColumnInt64(1) - units := uint64(stmt.ColumnInt64(2)) - ticks := uint64(stmt.ColumnInt64(3)) + weight := uint64(stmt.ColumnInt64(2)) switch { - case stmt.ColumnType(4) == sqlite.SQLITE_NULL && stmt.ColumnType(5) == sqlite.SQLITE_NULL: + case stmt.ColumnType(3) == sqlite.SQLITE_NULL && stmt.ColumnType(4) == sqlite.SQLITE_NULL: // no malfeasance - return fn(id, atxTime, 0, units*ticks) - case stmt.ColumnType(4) != sqlite.SQLITE_NULL && stmt.ColumnType(5) == sqlite.SQLITE_NULL: + return fn(id, atxTime, 0, weight) + case stmt.ColumnType(3) != sqlite.SQLITE_NULL && stmt.ColumnType(4) == sqlite.SQLITE_NULL: // legacy malfeasance - return fn(id, atxTime, stmt.ColumnInt64(4), units*ticks) - case stmt.ColumnType(4) == sqlite.SQLITE_NULL && stmt.ColumnType(5) != sqlite.SQLITE_NULL: + return fn(id, atxTime, stmt.ColumnInt64(3), weight) + case stmt.ColumnType(3) == sqlite.SQLITE_NULL && stmt.ColumnType(4) != sqlite.SQLITE_NULL: // new malfeasance - return fn(id, atxTime, stmt.ColumnInt64(5), units*ticks) + return fn(id, atxTime, stmt.ColumnInt64(4), weight) default: // both legacy and new malfeasance, take oldest one - proofTime := min(stmt.ColumnInt64(4), stmt.ColumnInt64(5)) - return fn(id, atxTime, proofTime, units*ticks) + proofTime := min(stmt.ColumnInt64(3), stmt.ColumnInt64(4)) + return fn(id, atxTime, proofTime, weight) } }); err != nil { return fmt.Errorf("iterate for grading: %w", err) diff --git a/sql/atxs/atxs_test.go b/sql/atxs/atxs_test.go index 32ec839a61..97cb70d763 100644 --- a/sql/atxs/atxs_test.go +++ b/sql/atxs/atxs_test.go @@ -400,9 +400,9 @@ func Test_IterateAtxForGrading(t *testing.T) { require.NoError(t, err) received := time.Now().Add(-time.Hour) - atx1 := newAtx(t, sig, withPublishEpoch(1), withTicks(100), withNumUnits(4), withReceived(received)) - atx2 := newAtx(t, sig, withPublishEpoch(2), withTicks(100), withNumUnits(4), withReceived(received)) - atx3 := newAtx(t, sig, withPublishEpoch(3), withTicks(100), withNumUnits(4), withReceived(received)) + atx1 := newAtx(t, sig, withPublishEpoch(1), withWeight(400), withReceived(received)) + atx2 := newAtx(t, sig, withPublishEpoch(2), withWeight(400), withReceived(received)) + atx3 := newAtx(t, sig, withPublishEpoch(3), withWeight(400), withReceived(received)) for _, atx := range []*types.ActivationTx{atx1, atx2, atx3} { require.NoError(t, atxs.Add(db, atx, types.AtxBlob{})) @@ -414,7 +414,7 @@ func Test_IterateAtxForGrading(t *testing.T) { ids = append(ids, id) require.Equal(t, atx1.Received().UnixNano(), atxTime) require.Zero(t, proofTime) - require.Equal(t, uint64(atx1.NumUnits)*atx1.TickCount, weight) + require.Equal(t, atx1.Weight, weight) return true }, ) @@ -1075,9 +1075,10 @@ func TestCheckpointATX(t *testing.T) { VRFNonce: types.VRFPostIndex(119), NumUnits: atx.NumUnits, BaseTickHeight: 1000, - TickCount: atx.TickCount + 1, + TickCount: atx.TickCount, + Weight: atx.Weight, SmesherID: sig.NodeID(), - Sequence: atx.Sequence + 1, + Sequence: atx.Sequence, Coinbase: types.Address{3, 2, 1}, } require.NoError(t, atxs.AddCheckpointed(db, catx)) @@ -1088,6 +1089,7 @@ func TestCheckpointATX(t *testing.T) { require.Equal(t, catx.NumUnits, got.NumUnits) require.Equal(t, catx.BaseTickHeight, got.BaseTickHeight) require.Equal(t, catx.TickCount, got.TickCount) + require.Equal(t, catx.Weight, got.Weight) require.Equal(t, catx.SmesherID, got.SmesherID) require.Equal(t, catx.Sequence, got.Sequence) require.Equal(t, catx.Coinbase, got.Coinbase) @@ -1165,6 +1167,12 @@ func withNumUnits(units uint32) createAtxOpt { } } +func withWeight(weight uint64) createAtxOpt { + return func(atx *types.ActivationTx) { + atx.Weight = weight + } +} + func withReceived(received time.Time) createAtxOpt { return func(atx *types.ActivationTx) { atx.SetReceived(received) @@ -1176,6 +1184,7 @@ func newAtx(tb testing.TB, signer *signing.EdSigner, opts ...createAtxOpt) *type atx := &types.ActivationTx{ NumUnits: 2, TickCount: 1, + Weight: 2, VRFNonce: types.VRFPostIndex(123), SmesherID: signer.NodeID(), } diff --git a/systest/Makefile b/systest/Makefile index f21940bba7..ec3eb156c4 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -10,7 +10,7 @@ poet_image ?= $(org)/poet:v0.10.12 post_service_image ?= $(org)/post-service:v0.8.5 post_init_image ?= $(org)/postcli:v0.13.1 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) -old_smesher_image ?= $(org)/go-spacemesh-dev:v1.8.1 +old_smesher_image ?= $(org)/go-spacemesh-dev:ce5d7d5 # update to newest release bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) test_id ?= systest-$(version_info) diff --git a/systest/cluster/cluster.go b/systest/cluster/cluster.go index 63ca0f74fe..f2de97782c 100644 --- a/systest/cluster/cluster.go +++ b/systest/cluster/cluster.go @@ -597,7 +597,6 @@ func NoDefaultPoets() DeploymentOpt { } } -// AddSmeshers ... func (c *Cluster) AddSmeshers(tctx *testcontext.Context, n int, opts ...DeploymentOpt) error { if err := c.resourceControl(tctx, n); err != nil { return err diff --git a/systest/tests/checkpoint_test.go b/systest/tests/checkpoint_test.go index a220ad2325..0054371307 100644 --- a/systest/tests/checkpoint_test.go +++ b/systest/tests/checkpoint_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" pb "github.com/spacemeshos/api/release/go/spacemesh/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -97,8 +98,9 @@ func TestCheckpoint(t *testing.T) { if !bytes.Equal(checkpoints[0], checkpoints[i]) { diffs = append(diffs, cl.Client(i).Name) tctx.Log.Errorw("diff checkpoint data", - fmt.Sprintf("reference %v", cl.Client(0).Name), string(checkpoints[0]), - fmt.Sprintf("client %v", cl.Client(i).Name), string(checkpoints[i]), + "reference", cl.Client(0).Name, + "client", cl.Client(i).Name, + "diff", cmp.Diff(string(checkpoints[0]), string(checkpoints[i])), ) } } diff --git a/systest/tests/nodes_test.go b/systest/tests/nodes_test.go index 63f7f7ac2f..0156ceed19 100644 --- a/systest/tests/nodes_test.go +++ b/systest/tests/nodes_test.go @@ -44,7 +44,6 @@ func TestAddNodes(t *testing.T) { "n", addedLater, "layer", layer.Layer.Number, ) - // the new smeshers will use the old sync protocol return false, cl.AddSmeshers(tctx, addedLater) } return true, nil