Skip to content

Commit 2235502

Browse files
authored
fix: authority balance check in packer (#1288)
1 parent af0c80b commit 2235502

11 files changed

Lines changed: 167 additions & 402 deletions

File tree

builtin/authority/authority.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
package authority
77

88
import (
9-
"math/big"
10-
119
"github.com/ethereum/go-ethereum/rlp"
1210

1311
"github.com/vechain/thor/v2/state"
@@ -200,8 +198,10 @@ func (a *Authority) Update(nodeMaster thor.Address, active bool) (bool, error) {
200198
return true, nil
201199
}
202200

201+
type BalanceChecker func(master, endorser thor.Address) (bool, error)
202+
203203
// Candidates picks a batch of candidates up to limit, that satisfy given endorsement.
204-
func (a *Authority) Candidates(endorsement *big.Int, limit uint64) ([]*Candidate, error) {
204+
func (a *Authority) Candidates(checker BalanceChecker, limit uint64) ([]*Candidate, error) {
205205
ptr, err := a.getAddressPtr(headKey)
206206
if err != nil {
207207
return nil, err
@@ -212,11 +212,11 @@ func (a *Authority) Candidates(endorsement *big.Int, limit uint64) ([]*Candidate
212212
if err != nil {
213213
return nil, err
214214
}
215-
bal, err := a.state.GetBalance(entry.Endorsor)
215+
ok, err := checker(*ptr, entry.Endorsor)
216216
if err != nil {
217217
return nil, err
218218
}
219-
if bal.Cmp(endorsement) >= 0 {
219+
if ok {
220220
candidates = append(candidates, &Candidate{
221221
NodeMaster: *ptr,
222222
Endorsor: entry.Endorsor,

builtin/authority/authority_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ func TestAuthority(t *testing.T) {
3333
st.SetBalance(p3, big.NewInt(30))
3434

3535
aut := New(thor.BytesToAddress([]byte("aut")), st)
36+
37+
checker := func(amount *big.Int) BalanceChecker {
38+
return func(master, endorser thor.Address) (bool, error) {
39+
bal, err := st.GetBalance(endorser)
40+
if err != nil {
41+
return false, err
42+
}
43+
return bal.Cmp(amount) >= 0, nil
44+
}
45+
}
46+
3647
tests := []struct {
3748
ret any
3849
expected any
@@ -41,16 +52,16 @@ func TestAuthority(t *testing.T) {
4152
{M(aut.Get(p1)), M(true, p1, thor.Bytes32{}, true, nil)},
4253
{M(aut.Add(p2, p2, thor.Bytes32{})), M(true, nil)},
4354
{M(aut.Add(p3, p3, thor.Bytes32{})), M(true, nil)},
44-
{M(aut.Candidates(big.NewInt(10), thor.InitialMaxBlockProposers)), M(
55+
{M(aut.Candidates(checker(big.NewInt(10)), thor.InitialMaxBlockProposers)), M(
4556
[]*Candidate{{p1, p1, thor.Bytes32{}, true}, {p2, p2, thor.Bytes32{}, true}, {p3, p3, thor.Bytes32{}, true}}, nil,
4657
)},
47-
{M(aut.Candidates(big.NewInt(20), thor.InitialMaxBlockProposers)), M(
58+
{M(aut.Candidates(checker(big.NewInt(20)), thor.InitialMaxBlockProposers)), M(
4859
[]*Candidate{{p2, p2, thor.Bytes32{}, true}, {p3, p3, thor.Bytes32{}, true}}, nil,
4960
)},
50-
{M(aut.Candidates(big.NewInt(30), thor.InitialMaxBlockProposers)), M(
61+
{M(aut.Candidates(checker(big.NewInt(30)), thor.InitialMaxBlockProposers)), M(
5162
[]*Candidate{{p3, p3, thor.Bytes32{}, true}}, nil,
5263
)},
53-
{M(aut.Candidates(big.NewInt(10), 2)), M(
64+
{M(aut.Candidates(checker(big.NewInt(10)), 2)), M(
5465
[]*Candidate{{p1, p1, thor.Bytes32{}, true}, {p2, p2, thor.Bytes32{}, true}}, nil,
5566
)},
5667
{M(aut.Get(p1)), M(true, p1, thor.Bytes32{}, true, nil)},
@@ -60,7 +71,7 @@ func TestAuthority(t *testing.T) {
6071
{M(aut.Get(p1)), M(true, p1, thor.Bytes32{}, true, nil)},
6172
{M(aut.Revoke(p1)), M(true, nil)},
6273
{M(aut.Get(p1)), M(false, p1, thor.Bytes32{}, false, nil)},
63-
{M(aut.Candidates(&big.Int{}, thor.InitialMaxBlockProposers)), M(
74+
{M(aut.Candidates(checker(&big.Int{}), thor.InitialMaxBlockProposers)), M(
6475
[]*Candidate{{p2, p2, thor.Bytes32{}, true}, {p3, p3, thor.Bytes32{}, true}}, nil,
6576
)},
6677
{

builtin/staker/staker.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func SetLogger(l log.Logger) {
4040
// Staker implements native methods of `Staker` contract.
4141
type Staker struct {
4242
params *params.Params
43+
state *state.State
4344

4445
aggregationService *aggregation.Service
4546
globalStatsService *globalstats.Service
@@ -53,6 +54,7 @@ func New(addr thor.Address, state *state.State, params *params.Params, charger *
5354

5455
return &Staker{
5556
params: params,
57+
state: state,
5658

5759
aggregationService: aggregation.New(sctx),
5860
globalStatsService: globalstats.New(sctx),

builtin/staker/transition.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package staker
88
import (
99
"math/big"
1010

11+
"github.com/vechain/thor/v2/builtin/authority"
1112
"github.com/vechain/thor/v2/thor"
1213
)
1314

@@ -61,3 +62,37 @@ func (s *Staker) transition(currentBlock uint32) (bool, error) {
6162

6263
return true, nil
6364
}
65+
66+
var bigE18 = big.NewInt(1e18)
67+
68+
// TransitionPeriodBalanceCheck returns a BalanceChecker function that checks if an endorser has enough VET soft staked
69+
// for the transition period whereby the endorser can leverage queued VET to meet the requirement.
70+
// It defaults to checking the account balance first and then checks the queued VET if in transition period.
71+
func (s *Staker) TransitionPeriodBalanceCheck(fc *thor.ForkConfig, currentBlock uint32, endorsement *big.Int) authority.BalanceChecker {
72+
return func(validator, endorser thor.Address) (bool, error) {
73+
balance, err := s.state.GetBalance(endorser)
74+
if err != nil {
75+
return false, err
76+
}
77+
if balance.Cmp(endorsement) >= 0 {
78+
return true, nil
79+
}
80+
if currentBlock < fc.HAYABUSA { // before HAYABUSA fork, we only check the account balance
81+
return false, nil
82+
}
83+
validation, err := s.validationService.GetValidation(validator)
84+
if err != nil {
85+
return false, err
86+
}
87+
if validation.IsEmpty() {
88+
return false, nil
89+
}
90+
if validation.Endorser != endorser {
91+
return false, nil // endorser mismatch
92+
}
93+
queuedVET := big.NewInt(0).SetUint64(validation.QueuedVET)
94+
queuedVET.Mul(queuedVET, bigE18) // convert to wei
95+
96+
return queuedVET.Cmp(endorsement) >= 0, nil
97+
}
98+
}

builtin/staker/transition_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,84 @@ func TestTransition(t *testing.T) {
7272
st.SetRawStorage(stakerAddr, activeCountSlot, rlp.RawValue{0x0})
7373
st.SetRawStorage(stakerAddr, queuedCountSlot, rlp.RawValue{0xFF})
7474
}
75+
76+
func TestStaker_TransitionPeriodBalanceCheck(t *testing.T) {
77+
fc := &thor.ForkConfig{
78+
HAYABUSA: 10,
79+
}
80+
tp := uint32(10)
81+
thor.SetConfig(thor.Config{HayabusaTP: &tp})
82+
bigE18 := big.NewInt(1e18)
83+
84+
endorsement := big.NewInt(1000)
85+
endorser := datagen.RandAddress()
86+
master := datagen.RandAddress()
87+
88+
tests := []struct {
89+
name string
90+
currentBlock uint32
91+
preTestHook func(staker *Staker)
92+
ok bool
93+
}{
94+
{
95+
name: "before hayabusa, validator has greater than funds",
96+
currentBlock: 5,
97+
preTestHook: func(staker *Staker) {
98+
assert.NoError(t, staker.state.SetBalance(endorser, big.NewInt(2000)))
99+
},
100+
ok: true,
101+
},
102+
{
103+
name: "before hayabusa, validator funds are too low",
104+
currentBlock: 5,
105+
preTestHook: func(staker *Staker) {
106+
assert.NoError(t, staker.state.SetBalance(endorser, big.NewInt(500)))
107+
},
108+
ok: false,
109+
},
110+
{
111+
name: "before hayabusa, validator has exactly enough funds",
112+
currentBlock: 5,
113+
preTestHook: func(staker *Staker) {
114+
assert.NoError(t, staker.state.SetBalance(endorser, big.NewInt(1000)))
115+
},
116+
ok: true,
117+
},
118+
{
119+
name: "during transition period, validator has not staked, has enough funds",
120+
currentBlock: 15,
121+
preTestHook: func(staker *Staker) {
122+
assert.NoError(t, staker.state.SetBalance(endorser, big.NewInt(2000)))
123+
},
124+
ok: true,
125+
},
126+
{
127+
name: "during transition period, validator has not staked, has insufficient funds",
128+
currentBlock: 15,
129+
preTestHook: func(staker *Staker) {
130+
assert.NoError(t, staker.state.SetBalance(endorser, big.NewInt(500)))
131+
},
132+
ok: false,
133+
},
134+
{
135+
name: "during transition period, validator has staked",
136+
currentBlock: 15,
137+
preTestHook: func(staker *Staker) {
138+
stake := big.NewInt(0).Div(MinStake, bigE18).Uint64()
139+
assert.NoError(t, staker.AddValidation(master, endorser, thor.MediumStakingPeriod(), stake))
140+
},
141+
ok: true,
142+
},
143+
}
144+
145+
for _, tt := range tests {
146+
t.Run(tt.name, func(t *testing.T) {
147+
staker, _ := newStaker(t, 1, 1, false)
148+
tt.preTestHook(staker)
149+
balanceCheck := staker.TransitionPeriodBalanceCheck(fc, tt.currentBlock, endorsement)
150+
ok, err := balanceCheck(master, endorser)
151+
assert.NoError(t, err)
152+
assert.Equal(t, tt.ok, ok)
153+
})
154+
}
155+
}

consensus/poa_validator.go

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package consensus
77

88
import (
99
"fmt"
10-
"math/big"
1110

1211
"github.com/vechain/thor/v2/block"
1312
"github.com/vechain/thor/v2/builtin"
@@ -34,8 +33,14 @@ func (c *Consensus) validateAuthorityProposer(header *block.Header, parent *bloc
3433
}
3534
candidates = poa.NewCandidates(list)
3635
}
36+
staker := builtin.Staker.Native(st)
37+
endorsement, err := builtin.Params.Native(st).Get(thor.KeyProposerEndorsement)
38+
if err != nil {
39+
return nil, err
40+
}
41+
balanceCheck := staker.TransitionPeriodBalanceCheck(c.forkConfig, header.Number(), endorsement)
3742

38-
proposers, err := candidates.Pick(st, c.authorityBalanceCheck(header, st, signer))
43+
proposers, err := candidates.Pick(st, balanceCheck)
3944
if err != nil {
4045
return nil, err
4146
}
@@ -125,36 +130,3 @@ func (c *Consensus) authorityCacheHandler(candidates *poa.Candidates, header *bl
125130

126131
return nil
127132
}
128-
129-
func (c *Consensus) authorityBalanceCheck(header *block.Header, st *state.State, signer thor.Address) poa.BalancerChecker {
130-
transitionPeriod := header.Number() >= c.forkConfig.HAYABUSA
131-
staker := builtin.Staker.Native(st)
132-
133-
return func(endorsor thor.Address, minBalance *big.Int) (bool, error) {
134-
bal, err := st.GetBalance(endorsor)
135-
if err != nil {
136-
return false, err
137-
}
138-
hasAccountBalance := bal.Cmp(minBalance) >= 0
139-
if hasAccountBalance {
140-
return true, nil
141-
}
142-
if !transitionPeriod { // before HAYABUSA fork, we only check the account balance
143-
return false, nil
144-
}
145-
// `signer` is the node master, not the endorsor
146-
// We are checking if the signer of the block has a `Validation` entry with a stake
147-
// NOT if the given endorsor has a staked
148-
validator, err := staker.GetValidation(signer)
149-
if err != nil {
150-
return false, err
151-
}
152-
if validator.IsEmpty() || validator.QueuedVET == 0 {
153-
return false, nil
154-
}
155-
156-
// convert the queued VET to wei
157-
queuedVET := new(big.Int).Mul(new(big.Int).SetUint64(validator.QueuedVET), big.NewInt(1e18))
158-
return queuedVET.Cmp(minBalance) >= 0, nil
159-
}
160-
}

0 commit comments

Comments
 (0)