-
Notifications
You must be signed in to change notification settings - Fork 258
/
stake.go
113 lines (100 loc) · 3.24 KB
/
stake.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package txsim
import (
"context"
"math/rand"
"github.com/celestiaorg/celestia-app/pkg/appconsts"
"github.com/cosmos/cosmos-sdk/types"
distribution "github.com/cosmos/cosmos-sdk/x/distribution/types"
staking "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/gogo/protobuf/grpc"
)
var _ Sequence = &StakeSequence{}
// StakeSequence sets up an endless sequence whereby an account delegates to a validator, continuously claims
// the reward, and occasionally redelegates to another validator at random. The account only ever delegates
// to a single validator at a time. TODO: Allow for multiple delegations
type StakeSequence struct {
initialStake int
redelegatePropability int
delegatedTo string
account types.AccAddress
}
func NewStakeSequence(initialStake int) *StakeSequence {
return &StakeSequence{
initialStake: initialStake,
redelegatePropability: 10, // 1 in every 10
}
}
func (s *StakeSequence) Clone(n int) []Sequence {
sequenceGroup := make([]Sequence, n)
for i := 0; i < n; i++ {
sequenceGroup[i] = NewStakeSequence(s.initialStake)
}
return sequenceGroup
}
func (s *StakeSequence) Init(_ context.Context, _ grpc.ClientConn, allocateAccounts AccountAllocator, _ *rand.Rand, useFeegrant bool) {
funds := fundsForGas
if useFeegrant {
funds = 1
}
s.account = allocateAccounts(1, s.initialStake+funds)[0]
}
func (s *StakeSequence) Next(ctx context.Context, querier grpc.ClientConn, rand *rand.Rand) (Operation, error) {
var op Operation
// for the first operation, the account delegates to a validator
if s.delegatedTo == "" {
val, err := getRandomValidator(ctx, querier, rand)
if err != nil {
return Operation{}, err
}
s.delegatedTo = val.OperatorAddress
return Operation{
Msgs: []types.Msg{
&staking.MsgDelegate{
DelegatorAddress: s.account.String(),
ValidatorAddress: s.delegatedTo,
Amount: types.NewInt64Coin(appconsts.BondDenom, int64(s.initialStake)),
},
},
}, nil
}
// occasionally redelegate the initial stake to another validator at random
if rand.Intn(s.redelegatePropability) == 0 {
val, err := getRandomValidator(ctx, querier, rand)
if err != nil {
return Operation{}, err
}
if val.OperatorAddress != s.delegatedTo {
op = Operation{
Msgs: []types.Msg{
&staking.MsgBeginRedelegate{
DelegatorAddress: s.account.String(),
ValidatorSrcAddress: s.delegatedTo,
ValidatorDstAddress: val.OperatorAddress,
// NOTE: only the initial stake is redelgated (not the entire balance)
Amount: types.NewInt64Coin(appconsts.BondDenom, int64(s.initialStake)),
},
},
}
s.delegatedTo = val.OperatorAddress
return op, nil
}
}
// claim pending rewards
op = Operation{
Msgs: []types.Msg{
&distribution.MsgWithdrawDelegatorReward{
DelegatorAddress: s.account.String(),
ValidatorAddress: s.delegatedTo,
},
},
Delay: rand.Int63n(20),
}
return op, nil
}
func getRandomValidator(ctx context.Context, conn grpc.ClientConn, rand *rand.Rand) (staking.Validator, error) {
resp, err := staking.NewQueryClient(conn).Validators(ctx, &staking.QueryValidatorsRequest{})
if err != nil {
return staking.Validator{}, err
}
return resp.Validators[rand.Intn(len(resp.Validators))], nil
}