Skip to content

Commit dee8191

Browse files
Refactor Transition (#1212)
* Update validation names + reorder staker ops * moved withdrawable calculations to validation * Decouple calculation and application in housekeep * added todo + cleanup * Decouple calculation and application in housekeep * added todo + cleanup * ensure activations use calculated exits * Refactor staker transitions * move validation ops to validation service * reuse housekeeping approach * removing public AddLeaderGroup * Decouple calculation and application in housekeep * added todo + cleanup * Update validation names + reorder staker ops * moved withdrawable calculations to validation * Decouple calculation and application in housekeep * added todo + cleanup * ensure activations use calculated exits * Refactor staker transitions * move validation ops to validation service * reuse housekeeping approach * removing public AddLeaderGroup * revert e2e test * add ll todo * revert e2e change * ActivateNextValidator as a public method * Update builtin/staker/validation/service.go Co-authored-by: Paolo Galli <paolo.galli@vechain.org> * pr comments --------- Co-authored-by: Paolo Galli <paolo.galli@vechain.org>
1 parent a76daa6 commit dee8191

4 files changed

Lines changed: 157 additions & 139 deletions

File tree

builtin/staker/housekeep.go

Lines changed: 19 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import (
1515
"github.com/vechain/thor/v2/thor"
1616
)
1717

18+
//
1819
// State transition types
20+
//
21+
1922
type EpochTransition struct {
2023
Block uint32
2124
Renewals []ValidatorRenewal
@@ -38,7 +41,7 @@ func (s *Staker) Housekeep(currentBlock uint32) (bool, map[thor.Address]*validat
3841

3942
logger.Info("🏠performing housekeeping", "block", currentBlock)
4043

41-
transition, err := s.ComputeEpochTransition(currentBlock)
44+
transition, err := s.computeEpochTransition(currentBlock)
4245
if err != nil {
4346
return false, nil, err
4447
}
@@ -47,7 +50,7 @@ func (s *Staker) Housekeep(currentBlock uint32) (bool, map[thor.Address]*validat
4750
return false, nil, nil
4851
}
4952

50-
if err := s.ApplyEpochTransition(transition); err != nil {
53+
if err := s.applyEpochTransition(transition); err != nil {
5154
return false, nil, err
5255
}
5356

@@ -58,12 +61,9 @@ func (s *Staker) Housekeep(currentBlock uint32) (bool, map[thor.Address]*validat
5861
return true, activeValidators, nil
5962
}
6063

61-
// ComputeEpochTransition calculates all state changes needed for an epoch transition
62-
func (s *Staker) ComputeEpochTransition(currentBlock uint32) (*EpochTransition, error) {
64+
// computeEpochTransition calculates all state changes needed for an epoch transition
65+
func (s *Staker) computeEpochTransition(currentBlock uint32) (*EpochTransition, error) {
6366
var err error
64-
if currentBlock%epochLength != 0 {
65-
return nil, nil // No transition needed
66-
}
6767

6868
transition := &EpochTransition{Block: currentBlock}
6969

@@ -80,7 +80,7 @@ func (s *Staker) ComputeEpochTransition(currentBlock uint32) (*EpochTransition,
8080
}
8181

8282
// 3. Compute all activations
83-
transition.ActivationCount, err = s.computeActivations(transition.ExitValidator != nil)
83+
transition.ActivationCount, err = s.computeActivationCount(transition.ExitValidator != nil)
8484
if err != nil {
8585
return nil, err
8686
}
@@ -156,7 +156,8 @@ func (s *Staker) computeExits(currentBlock uint32) (*thor.Address, error) {
156156
return nil, nil
157157
}
158158

159-
func (s *Staker) computeActivations(hasValidatorExited bool) (int64, error) {
159+
// computeActivationCount calculates how many validators can be activated
160+
func (s *Staker) computeActivationCount(hasValidatorExited bool) (int64, error) {
160161
// Calculate how many validators can be activated
161162
queuedSize, err := s.QueuedGroupSize()
162163
if err != nil {
@@ -193,8 +194,8 @@ func (s *Staker) computeActivations(hasValidatorExited bool) (int64, error) {
193194
return queuedCount, nil
194195
}
195196

196-
// ApplyEpochTransition applies all computed changes
197-
func (s *Staker) ApplyEpochTransition(transition *EpochTransition) error {
197+
// applyEpochTransition applies all computed changes
198+
func (s *Staker) applyEpochTransition(transition *EpochTransition) error {
198199
logger.Info("applying epoch transition", "block", transition.Block)
199200

200201
// Apply renewals
@@ -243,8 +244,7 @@ func (s *Staker) ApplyEpochTransition(transition *EpochTransition) error {
243244
}
244245

245246
for range transition.ActivationCount {
246-
_, err := s.ActivateNextValidator(transition.Block, maxLeaderGroupSize)
247-
if err != nil {
247+
if _, err := s.ActivateNextValidator(transition.Block, maxLeaderGroupSize); err != nil {
248248
return err
249249
}
250250
}
@@ -273,134 +273,27 @@ func (s *Staker) buildActiveValidatorsFromTransition(transition *EpochTransition
273273
return activeValidators
274274
}
275275

276-
// Transition activates the staker contract when sufficient validators are queued
277-
func (s *Staker) Transition(currentBlock uint32) (bool, error) {
278-
active, err := s.IsPoSActive()
279-
if err != nil {
280-
return false, err
281-
}
282-
if active {
283-
return false, nil
284-
}
285-
286-
maxProposers, err := s.params.Get(thor.KeyMaxBlockProposers)
287-
if err != nil || maxProposers.Cmp(big.NewInt(0)) == 0 {
288-
maxProposers = big.NewInt(0).SetUint64(thor.InitialMaxBlockProposers)
289-
}
290-
291-
queueSize, err := s.validationService.QueuedGroupSize()
292-
if err != nil {
293-
return false, err
294-
}
295-
296-
// if the queue size is not AT LEAST 2/3 of the maxProposers, then return nil
297-
minimum := big.NewFloat(0).SetInt(maxProposers)
298-
minimum.Mul(minimum, big.NewFloat(2))
299-
minimum.Quo(minimum, big.NewFloat(3))
300-
if big.NewFloat(0).SetInt(queueSize).Cmp(minimum) < 0 {
301-
return false, nil
302-
}
303-
304-
// Use existing activateValidators method for transition
305-
ids, err := s.activateValidators(currentBlock)
306-
if err != nil {
307-
return false, err
308-
}
309-
logger.Info("activated validations", "count", len(ids))
310-
311-
return true, nil
312-
}
313-
314-
// activateValidators is kept for the Transition method
315-
func (s *Staker) activateValidators(currentBlock uint32) ([]*thor.Address, error) {
316-
queuedSize, err := s.QueuedGroupSize()
317-
if err != nil {
318-
return nil, err
319-
}
320-
leaderSize, err := s.LeaderGroupSize()
321-
if err != nil {
322-
return nil, err
323-
}
324-
maxSize, err := s.params.Get(thor.KeyMaxBlockProposers)
325-
if err != nil {
326-
return nil, err
327-
}
328-
if leaderSize.Cmp(maxSize) >= 0 {
329-
return nil, nil
330-
}
331-
332-
// no one is in the queue
333-
if queuedSize.Cmp(big.NewInt(0)) <= 0 {
334-
return nil, nil
335-
}
336-
337-
queuedCount := queuedSize.Int64()
338-
leaderDelta := maxSize.Int64() - leaderSize.Int64()
339-
if leaderDelta > 0 {
340-
if leaderDelta < queuedCount {
341-
queuedCount = leaderDelta
342-
}
343-
} else {
344-
return nil, nil
345-
}
346-
347-
activated := make([]*thor.Address, queuedCount)
348-
maxLeaderGroupSize, err := s.params.Get(thor.KeyMaxBlockProposers)
349-
if err != nil {
350-
return nil, err
351-
}
352-
353-
for i := int64(0); i < queuedCount; i++ {
354-
id, err := s.ActivateNextValidator(currentBlock, maxLeaderGroupSize)
355-
if err != nil {
356-
return nil, err
357-
}
358-
activated[i] = id
359-
}
360-
361-
return activated, nil
362-
}
363-
276+
// TODO this should be a public method, it's ever only used in the pause test
364277
func (s *Staker) ActivateNextValidator(currentBlk uint32, maxLeaderGroupSize *big.Int) (*thor.Address, error) {
365-
validatorID, val, err := s.validationService.NextToActivate(maxLeaderGroupSize)
278+
validatorID, err := s.validationService.NextToActivate(maxLeaderGroupSize)
366279
if err != nil {
367280
return nil, err
368281
}
369282
logger.Debug("activating validator", "validatorID", validatorID, "block", currentBlk)
370283

284+
// renew the current delegations aggregation
371285
aggRenew, err := s.aggregationService.Renew(*validatorID)
372286
if err != nil {
373287
return nil, err
374288
}
375289

376-
// update the validator values
377-
// TODO move this to the validatorservice at some point
378-
validatorLocked := big.NewInt(0).Add(val.LockedVET, val.QueuedVET)
379-
val.QueuedVET = big.NewInt(0)
380-
val.LockedVET = validatorLocked
381-
// x2 multiplier for validator's stake
382-
validatorWeight := big.NewInt(0).Mul(validatorLocked, validatorWeightMultiplier)
383-
val.Weight = big.NewInt(0).Add(validatorWeight, aggRenew.NewLockedWeight)
384-
385-
// update the validator statuses
386-
val.Status = validation.StatusActive
387-
val.Online = true
388-
val.StartBlock = currentBlk
389-
// add to the active list
390-
added, err := s.validationService.AddLeaderGroup(*validatorID, val)
290+
// Activate the validator using the validation service
291+
validatorRenewal, err := s.validationService.ActivateValidator(*validatorID, currentBlk, aggRenew)
391292
if err != nil {
392293
return nil, err
393294
}
394-
if !added {
395-
return nil, errors.New("failed to add validator to active list")
396-
}
397295

398-
validatorRenewal := &delta.Renewal{
399-
NewLockedVET: val.LockedVET,
400-
NewLockedWeight: val.Weight,
401-
QueuedDecrease: val.LockedVET,
402-
QueuedDecreaseWeight: big.NewInt(0).Mul(val.LockedVET, validatorWeightMultiplier), // Only decrease validator's own weight
403-
}
296+
// Update global stats with both validator and delegation renewals
404297
if err = s.globalStatsService.UpdateTotals(validatorRenewal, aggRenew); err != nil {
405298
return nil, err
406299
}

builtin/staker/transition.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) 2018 The VeChainThor developers
2+
3+
// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
4+
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>
5+
6+
package staker
7+
8+
import (
9+
"math/big"
10+
11+
"github.com/vechain/thor/v2/thor"
12+
)
13+
14+
// Transition activates the staker contract when sufficient validators are queued
15+
func (s *Staker) Transition(currentBlock uint32) (bool, error) {
16+
// TODO review how to change this elegantly for unit tests
17+
// if this check is enabled the epochLength is defaulted to 180 blocks
18+
// which breaks most of tests that rely on a HAYABUSA_TP = 1
19+
//
20+
//if currentBlock%epochLength != 0 {
21+
// return false, nil // No transition needed
22+
//}
23+
24+
active, err := s.IsPoSActive()
25+
if err != nil {
26+
return false, err
27+
}
28+
if active {
29+
return false, nil
30+
}
31+
32+
maxProposers, err := s.params.Get(thor.KeyMaxBlockProposers)
33+
if err != nil {
34+
return false, err
35+
}
36+
37+
if maxProposers.Cmp(big.NewInt(0)) == 0 {
38+
maxProposers = big.NewInt(0).SetUint64(thor.InitialMaxBlockProposers)
39+
}
40+
41+
queueSize, err := s.validationService.QueuedGroupSize()
42+
if err != nil {
43+
return false, err
44+
}
45+
46+
// Transitions is not possible if the queue size is not AT LEAST 2/3 of the maxProposers
47+
// queueSize >= 2/3 * maxProposers
48+
// queueSize * 3 >= maxProposers * 2
49+
if queueSize.Int64()*3 < maxProposers.Int64()*2 { // these figures will never surpass int64
50+
return false, nil
51+
}
52+
53+
// Use the epoch transition pattern as housekeeping
54+
transition, err := s.computeEpochTransition(currentBlock)
55+
if err != nil {
56+
return false, err
57+
}
58+
59+
// Apply the transition
60+
if err := s.applyEpochTransition(transition); err != nil {
61+
return false, err
62+
}
63+
64+
logger.Info("activated validations", "count", transition.ActivationCount)
65+
66+
return true, nil
67+
}

builtin/staker/validation/linked_list.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func (l *LinkedList) Remove(validator thor.Address, validation *Validation) (rem
8181
return false, err
8282
}
8383
prevEntry.Next = next
84+
// TODO keep the LL separated from the Validation storage ops
8485
if err := l.repo.SetValidation(*prev, prevEntry, false); err != nil {
8586
return false, err
8687
}
@@ -94,6 +95,7 @@ func (l *LinkedList) Remove(validator thor.Address, validation *Validation) (rem
9495
return false, err
9596
}
9697
nextEntry.Prev = prev
98+
// TODO keep the LL separated from the Validation storage ops
9799
if err := l.repo.SetValidation(*next, nextEntry, false); err != nil {
98100
return false, err
99101
}
@@ -128,6 +130,7 @@ func (l *LinkedList) Add(newTail thor.Address, validation *Validation) (added bo
128130
// list is currently empty, set this entry to head & tail
129131
l.head.Set(&newTail, false)
130132
l.tail.Set(&newTail, false)
133+
// TODO keep the LL separated from the Validation storage ops
131134
return true, l.repo.SetValidation(newTail, validation, false)
132135
}
133136

@@ -138,6 +141,7 @@ func (l *LinkedList) Add(newTail thor.Address, validation *Validation) (added bo
138141
oldTail.Next = &newTail
139142
validation.Prev = &oldTailID
140143

144+
// TODO keep the LL separated from the Validation storage ops
141145
if err := l.repo.SetValidation(oldTailID, oldTail, false); err != nil {
142146
return false, err
143147
}

0 commit comments

Comments
 (0)