Skip to content

Commit c7614c0

Browse files
authored
refactor(staker): move trasition, housekeeping and status check to staker (#1259)
1 parent b28050b commit c7614c0

15 files changed

Lines changed: 185 additions & 253 deletions

builtin/staker/delegations_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ func Test_Delegator_DisableAutoRenew_PendingLocked(t *testing.T) {
230230

231231
// Then the delegation can't signal an exit until it has started
232232
assert.ErrorContains(t, staker.SignalDelegationExit(id), "delegation has not started yet")
233-
_, _, err = staker.Housekeep(validator.Period)
233+
_, err = staker.Housekeep(validator.Period)
234234
assert.NoError(t, err)
235235
assert.NoError(t, staker.SignalDelegationExit(id))
236236
aggregation, err = staker.aggregationService.GetAggregation(validator.ID)
@@ -239,7 +239,7 @@ func Test_Delegator_DisableAutoRenew_PendingLocked(t *testing.T) {
239239
assert.Equal(t, stake, aggregation.ExitingVET) // ExitingVET takes effect in next staking period
240240

241241
// When the staking period is completed
242-
_, _, err = staker.Housekeep(validator.Period)
242+
_, err = staker.Housekeep(validator.Period)
243243
assert.NoError(t, err)
244244
aggregation, err = staker.aggregationService.GetAggregation(validator.ID)
245245
assert.NoError(t, err)
@@ -330,7 +330,7 @@ func Test_Delegator_DisableAutoRenew_InAStakingPeriod(t *testing.T) {
330330
assert.Equal(t, weight, queuedWeight)
331331

332332
// And the first staking period has occurred
333-
_, _, err = staker.Housekeep(validator.Period)
333+
_, err = staker.Housekeep(validator.Period)
334334
assert.NoError(t, err)
335335
aggregation, err := staker.aggregationService.GetAggregation(validator.ID)
336336
assert.NoError(t, err)
@@ -353,7 +353,7 @@ func Test_Delegator_DisableAutoRenew_InAStakingPeriod(t *testing.T) {
353353
assert.Equal(t, big.NewInt(0).String(), queuedWeight.String())
354354

355355
// And the funds should be withdrawable after the next iteration
356-
_, _, err = staker.Housekeep(2 * validator.Period)
356+
_, err = staker.Housekeep(2 * validator.Period)
357357
assert.NoError(t, err)
358358
_, err = staker.aggregationService.GetAggregation(validator.ID)
359359
assert.NoError(t, err)
@@ -373,7 +373,7 @@ func Test_Delegator_AutoRenew_ValidatorExits(t *testing.T) {
373373
assert.NoError(t, err)
374374

375375
// And the first staking period has occurred
376-
_, _, err = staker.Housekeep(validator.Period)
376+
_, err = staker.Housekeep(validator.Period)
377377
assert.NoError(t, err)
378378
aggregation, err := staker.aggregationService.GetAggregation(validator.ID)
379379
assert.NoError(t, err)
@@ -383,7 +383,7 @@ func Test_Delegator_AutoRenew_ValidatorExits(t *testing.T) {
383383
assert.NoError(t, staker.SignalExit(validator.ID, validator.Endorser))
384384

385385
// And the next staking period is over
386-
_, _, err = staker.Housekeep(validator.Period * 2)
386+
_, err = staker.Housekeep(validator.Period * 2)
387387
assert.NoError(t, err)
388388
_, err = staker.aggregationService.GetAggregation(validator.ID)
389389
assert.NoError(t, err)
@@ -588,7 +588,7 @@ func Test_Delegations_EnableAutoRenew_MatchStakeReached(t *testing.T) {
588588
assert.False(t, delegation1.Started(validation))
589589

590590
// Delegation should become active
591-
_, _, err = staker.Housekeep(validator.Period)
591+
_, err = staker.Housekeep(validator.Period)
592592
assert.NoError(t, err)
593593
delegation1, _, err = staker.GetDelegation(delegationID)
594594
assert.NoError(t, err)
@@ -647,7 +647,7 @@ func TestStaker_DelegationExitingVET(t *testing.T) {
647647
assert.Equal(t, delStake, qStake)
648648
assert.Equal(t, delWeight, qWeight)
649649

650-
_, _, err = staker.Housekeep(MediumStakingPeriod.Get())
650+
_, err = staker.Housekeep(MediumStakingPeriod.Get())
651651
assert.NoError(t, err)
652652

653653
delegation, validation, err = staker.GetDelegation(delegationID)
@@ -657,6 +657,6 @@ func TestStaker_DelegationExitingVET(t *testing.T) {
657657
assert.NoError(t, staker.SignalDelegationExit(delegationID))
658658
assert.NoError(t, staker.SignalExit(*firstActive, validation.Endorser))
659659

660-
_, _, err = staker.Housekeep(MediumStakingPeriod.Get() * 2)
660+
_, err = staker.Housekeep(MediumStakingPeriod.Get() * 2)
661661
assert.NoError(t, err)
662662
}

builtin/staker/housekeep.go

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@ import (
2020
//
2121

2222
type EpochTransition struct {
23-
Block uint32
24-
Renewals []ValidatorRenewal
25-
ExitValidator *thor.Address
26-
ActivationCount int64
27-
ActiveValidators map[thor.Address]*validation.Validation
23+
Block uint32
24+
Renewals []ValidatorRenewal
25+
ExitValidator *thor.Address
26+
ActivationCount int64
2827
}
2928

3029
type ValidatorRenewal struct {
@@ -35,31 +34,28 @@ type ValidatorRenewal struct {
3534
}
3635

3736
// Housekeep performs epoch transitions at epoch boundaries
38-
func (s *Staker) Housekeep(currentBlock uint32) (bool, map[thor.Address]*validation.Validation, error) {
37+
func (s *Staker) Housekeep(currentBlock uint32) (bool, error) {
3938
if currentBlock%EpochLength.Get() != 0 {
40-
return false, nil, nil
39+
return false, nil
4140
}
4241

4342
logger.Info("🏠performing housekeeping", "block", currentBlock)
4443

4544
transition, err := s.computeEpochTransition(currentBlock)
4645
if err != nil {
47-
return false, nil, err
46+
return false, err
4847
}
4948

5049
if transition == nil || (len(transition.Renewals) == 0 && transition.ExitValidator == nil && transition.ActivationCount == 0) {
51-
return false, nil, nil
50+
return false, nil
5251
}
5352

5453
if err := s.applyEpochTransition(transition); err != nil {
55-
return false, nil, err
54+
return false, err
5655
}
5756

58-
// Build active validators map
59-
activeValidators := transition.ActiveValidators
60-
6157
logger.Info("performed housekeeping", "block", currentBlock, "updates", true)
62-
return true, activeValidators, nil
58+
return true, nil
6359
}
6460

6561
// computeEpochTransition calculates all state changes needed for an epoch transition
@@ -92,7 +88,6 @@ func (s *Staker) computeEpochTransition(currentBlock uint32) (*EpochTransition,
9288
if err != nil {
9389
return nil, err
9490
}
95-
transition.ActiveValidators = active
9691

9792
return transition, nil
9893
}
@@ -247,7 +242,6 @@ func (s *Staker) applyEpochTransition(transition *EpochTransition) error {
247242
if err := s.globalStatsService.ApplyExit(exit.Add(aggExit)); err != nil {
248243
return err
249244
}
250-
delete(transition.ActiveValidators, *transition.ExitValidator)
251245
}
252246

253247
// Apply activations using existing method
@@ -257,15 +251,10 @@ func (s *Staker) applyEpochTransition(transition *EpochTransition) error {
257251
}
258252

259253
for range transition.ActivationCount {
260-
address, err := s.activateNextValidation(transition.Block, maxLeaderGroupSize)
261-
if err != nil {
262-
return err
263-
}
264-
validator, err := s.Get(*address)
254+
_, err := s.activateNextValidation(transition.Block, maxLeaderGroupSize)
265255
if err != nil {
266256
return err
267257
}
268-
transition.ActiveValidators[*address] = validator
269258
}
270259

271260
return nil

builtin/staker/protocol.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) 2025 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+
"fmt"
10+
11+
"github.com/vechain/thor/v2/thor"
12+
)
13+
14+
type Status struct {
15+
Active bool // indicates if the staker contract is currently active
16+
Updates bool // indicates if there are updates to the staker contract
17+
}
18+
19+
// SyncPOS checks the status of the staker contract and updates its state based on the current block number.
20+
// It returns a Status object containing the activation status and the current leader group.
21+
// If the staker contract is not active, it attempts to transition to dPoS on transition blocks.
22+
// If the staker contract is active, it performs housekeeping on epoch blocks.
23+
func (s *Staker) SyncPOS(forkConfig *thor.ForkConfig, current uint32) (Status, error) {
24+
status := Status{}
25+
// still on PoA
26+
if forkConfig.HAYABUSA+forkConfig.HAYABUSA_TP > current {
27+
return status, nil
28+
}
29+
30+
var err error
31+
var activated bool
32+
33+
// check if the staker contract is active
34+
status.Active, err = s.IsPoSActive()
35+
if err != nil {
36+
return status, err
37+
}
38+
39+
// attempt to transition if we're on a transition block and the staker contract is not active
40+
if !status.Active && current%forkConfig.HAYABUSA_TP == 0 {
41+
activated, err = s.transition(current)
42+
if err != nil {
43+
return status, fmt.Errorf("failed to transition to dPoS: %w", err)
44+
}
45+
if activated {
46+
status.Active = true
47+
status.Updates = true
48+
}
49+
}
50+
51+
// perform housekeeping if the staker contract is active
52+
if status.Active && !activated {
53+
status.Updates, err = s.Housekeep(current)
54+
if err != nil {
55+
return status, err
56+
}
57+
}
58+
59+
return status, nil
60+
}

builtin/staker/staker.go

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -329,30 +329,9 @@ func (s *Staker) WithdrawStake(validator thor.Address, endorser thor.Address, cu
329329
return stake, nil
330330
}
331331

332-
func (s *Staker) SetOnline(validator thor.Address, blockNum uint32, online bool) (bool, error) {
332+
func (s *Staker) SetOnline(validator thor.Address, blockNum uint32, online bool) error {
333333
logger.Debug("set node online", "validator", validator, "online", online)
334-
entry, err := s.validationService.GetValidation(validator)
335-
if err != nil {
336-
return false, err
337-
}
338-
hasChanged := entry.OfflineBlock == nil != online
339-
if hasChanged {
340-
s.setOfflineBlock(validator, online, blockNum, entry)
341-
err = s.validationService.SetValidation(validator, entry, false)
342-
} else {
343-
err = nil
344-
}
345-
return hasChanged, err
346-
}
347-
348-
func (s *Staker) setOfflineBlock(validator thor.Address, online bool, blockNum uint32, val *validation.Validation) {
349-
if online {
350-
logger.Info("clearing offline block", "validator", validator, "online", online)
351-
val.OfflineBlock = nil
352-
} else {
353-
logger.Info("setting offline block", "validator", validator, "online", online, "offline block", blockNum)
354-
val.OfflineBlock = &blockNum
355-
}
334+
return s.validationService.UpdateOfflineBlock(validator, blockNum, online)
356335
}
357336

358337
func (s *Staker) SetBeneficiary(validator, endorser, beneficiary thor.Address) error {

builtin/staker/staker_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func (ts *TestSequence) AssertWithdrawable(
167167
}
168168

169169
func (ts *TestSequence) SetOnline(id thor.Address, blockNum uint32, online bool) *TestSequence {
170-
_, err := ts.staker.SetOnline(id, blockNum, online)
170+
err := ts.staker.SetOnline(id, blockNum, online)
171171
assert.NoError(ts.t, err, "failed to set online status for validator %s: %v", id.String(), err)
172172
return ts
173173
}
@@ -278,13 +278,13 @@ func (ts *TestSequence) ActivateNext(block uint32) *TestSequence {
278278
}
279279

280280
func (ts *TestSequence) Housekeep(block uint32) *TestSequence {
281-
_, _, err := ts.staker.Housekeep(block)
281+
_, err := ts.staker.Housekeep(block)
282282
assert.NoError(ts.t, err, "failed to perform housekeeping at block %d", block)
283283
return ts
284284
}
285285

286286
func (ts *TestSequence) Transition(block uint32) *TestSequence {
287-
_, err := ts.staker.Transition(block)
287+
_, err := ts.staker.transition(block)
288288
assert.NoError(ts.t, err, "failed to transition at block %d", block)
289289
return ts
290290
}

builtin/staker/transition.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
"github.com/vechain/thor/v2/thor"
1212
)
1313

14-
// Transition activates the staker contract when sufficient validators are queued
15-
func (s *Staker) Transition(currentBlock uint32) (bool, error) {
14+
// transition activates the staker contract when sufficient validators are queued
15+
func (s *Staker) transition(currentBlock uint32) (bool, error) {
1616
if currentBlock%EpochLength.Get() != 0 {
1717
return false, nil // No transition needed
1818
}

builtin/staker/validation/service.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,21 @@ func (s *Service) ActivateValidator(
492492
return validatorRenewal, nil
493493
}
494494

495+
// UpdateOfflineBlock updates the offline block for a validator.
496+
func (s *Service) UpdateOfflineBlock(validator thor.Address, block uint32, online bool) error {
497+
validation, err := s.GetExistingValidation(validator)
498+
if err != nil {
499+
return err
500+
}
501+
if online {
502+
validation.OfflineBlock = nil
503+
} else {
504+
validation.OfflineBlock = &block
505+
}
506+
507+
return s.SetValidation(validator, validation, false)
508+
}
509+
495510
//
496511
// Repository methods
497512
//

0 commit comments

Comments
 (0)