Skip to content

Commit 07152e0

Browse files
authored
Merge pull request #28 from axone-protocol/feat/delegator-shares-filters
Feat/delegator shares filters
2 parents 72c601a + d4900c6 commit 07152e0

File tree

8 files changed

+157
-45
lines changed

8 files changed

+157
-45
lines changed

cmd/delegators.go

+52-8
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,27 @@ import (
44
"github.com/axone-protocol/cosmos-extractor/pkg/delegators"
55
"github.com/spf13/cobra"
66
"github.com/teambenny/goetl"
7+
8+
"cosmossdk.io/math"
79
)
810

911
const (
10-
flagHrp = "hrp"
12+
flagHrp = "hrp"
13+
flagMinShares = "min-shares"
14+
flagMaxShares = "max-shares"
1115
)
1216

1317
var extractDelegatorsCmd = &cobra.Command{
1418
Use: "delegators [source]",
1519
Short: "Extract all delegators",
1620
Args: cobra.ExactArgs(1),
1721
RunE: func(cmd *cobra.Command, args []string) error {
18-
chainName, _ := cmd.Flags().GetString(flagChainName)
19-
src := args[0]
20-
21-
processors := []goetl.Processor{}
22-
23-
read, err := delegators.NewDelegatorsReader(chainName, src, logger)
22+
read, err := newDelegatorsReader(cmd, args)
2423
if err != nil {
2524
return err
2625
}
26+
27+
processors := []goetl.Processor{}
2728
processors = append(processors, read)
2829

2930
hrps, err := cmd.Flags().GetStringSlice(flagHrp)
@@ -52,13 +53,56 @@ var extractDelegatorsCmd = &cobra.Command{
5253
},
5354
}
5455

56+
func newDelegatorsReader(cmd *cobra.Command, args []string) (goetl.Processor, error) {
57+
chainName, _ := cmd.Flags().GetString(flagChainName)
58+
src := args[0]
59+
60+
delegatorsReaderOpts := []delegators.ReaderOption{
61+
delegators.WithChainName(chainName),
62+
delegators.WithLogger(logger),
63+
}
64+
65+
v, err := getShares(cmd, flagMinShares)
66+
if err != nil {
67+
return nil, err
68+
}
69+
if !v.IsNil() {
70+
delegatorsReaderOpts = append(delegatorsReaderOpts, delegators.WithMinSharesFilter(v))
71+
}
72+
73+
v, err = getShares(cmd, flagMaxShares)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
if !v.IsNil() {
79+
delegatorsReaderOpts = append(delegatorsReaderOpts, delegators.WithMaxSharesFilter(v))
80+
}
81+
82+
return delegators.NewDelegatorsReader(src, delegatorsReaderOpts...)
83+
}
84+
85+
func getShares(cmd *cobra.Command, flag string) (math.LegacyDec, error) {
86+
shares, err := cmd.Flags().GetString(flag)
87+
if err != nil {
88+
return math.LegacyDec{}, err
89+
}
90+
if shares == "" {
91+
return math.LegacyDec{}, nil
92+
}
93+
return math.LegacyNewDecFromStr(shares)
94+
}
95+
5596
func init() {
5697
extractCmd.AddCommand(extractDelegatorsCmd)
5798

5899
extractDelegatorsCmd.Flags().StringSliceP(
59100
flagHrp,
60101
"p",
61102
[]string{},
62-
"One or more Human-Readable Parts (HRPs) to append delegator addresses in the given Bech32 formats (e.g., cosmos, osmo). "+
103+
"one or more Human-Readable Parts (HRPs) to append delegator addresses in the given Bech32 formats (e.g., cosmos, osmo). "+
63104
"Can be used multiple times for different HRPs.")
105+
106+
extractDelegatorsCmd.Flags().String(flagMinShares, "", "filter delegators with minimum shares")
107+
extractDelegatorsCmd.Flags().String(flagMaxShares, "", "filter delegators with maximum shares")
64108
}

cmd/extract.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ var extractCmd = &cobra.Command{
2121
func init() {
2222
rootCmd.AddCommand(extractCmd)
2323

24-
extractCmd.PersistentFlags().StringP(flagChainName, "n", "cosmos", "Name of the chain")
25-
extractCmd.PersistentFlags().StringP(flagOutput, "o", "", "Output file (defaults to stdout)")
24+
extractCmd.PersistentFlags().StringP(flagChainName, "n", "cosmos", "name of the chain")
25+
extractCmd.PersistentFlags().StringP(flagOutput, "o", "", "output file (defaults to stdout)")
2626
}
2727

2828
func newCSVWriter(cmd *cobra.Command, _ []string) (goetl.Processor, error) {

cmd/root.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ func Execute() {
4242
}
4343

4444
func init() {
45-
rootCmd.PersistentFlags().String(flagLogLevel, "info", "The logging level (trace|debug|info|warn|error|fatal|panic)")
45+
rootCmd.PersistentFlags().String(flagLogLevel, "warn", "logging level (trace|debug|info|warn|error|fatal|panic)")
4646
}

cmd/version.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ var versionCmd = &cobra.Command{
5252
func init() {
5353
rootCmd.AddCommand(versionCmd)
5454

55-
versionCmd.Flags().Bool(flagLong, false, "Print long version information")
56-
versionCmd.Flags().StringP(flagFormat, "f", "text", "Output format (text|json)")
55+
versionCmd.Flags().Bool(flagLong, false, "print long version information")
56+
versionCmd.Flags().StringP(flagFormat, "f", "text", "output format (text|json)")
5757
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ require (
146146
github.com/rs/zerolog v1.33.0 // indirect
147147
github.com/sagikazarmark/locafero v0.4.0 // indirect
148148
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
149+
github.com/samber/lo v1.47.0 // indirect
149150
github.com/sasha-s/go-deadlock v0.3.5 // indirect
150151
github.com/satori/go.uuid v1.2.0 // indirect
151152
github.com/smarty/assertions v1.15.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
798798
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
799799
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
800800
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
801+
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
802+
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
801803
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
802804
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
803805
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=

pkg/delegators/enhancer.go

+5-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"strings"
66

7+
"github.com/samber/lo"
78
"github.com/teambenny/goetl"
89
"github.com/teambenny/goetl/etldata"
910

@@ -21,16 +22,13 @@ type addressEnhancer struct {
2122

2223
// NewAddressEnhancer returns a new processor that enrich the data with addresses with the given prefixes.
2324
func NewAddressEnhancer(prefixes []string, logger log.Logger) (goetl.Processor, error) {
24-
keys := make([]string, len(prefixes))
25-
for i, prefix := range prefixes {
26-
keys[i] = fmt.Sprintf("delegator-%s-address", prefix)
27-
}
28-
2925
return &addressEnhancer{
3026
prefixes: prefixes,
3127
logger: logger,
32-
keys: keys,
33-
name: fmt.Sprintf("AddressEnhancer(%s)", strings.Join(prefixes, ",")),
28+
keys: lo.Map(prefixes, func(prefix string, _ int) string {
29+
return fmt.Sprintf("delegator_%s_address", prefix)
30+
}),
31+
name: fmt.Sprintf("AddressEnhancer(%s)", strings.Join(prefixes, ",")),
3432
}, nil
3533
}
3634

pkg/delegators/reader.go

+92-25
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,74 @@ import (
99

1010
"github.com/axone-protocol/cosmos-extractor/pkg/keeper"
1111
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
12+
"github.com/samber/lo"
1213
"github.com/teambenny/goetl"
1314
"github.com/teambenny/goetl/etldata"
14-
"github.com/teambenny/goetl/etlutil"
1515

1616
"cosmossdk.io/collections"
1717
"cosmossdk.io/log"
1818
"cosmossdk.io/math"
1919

2020
sdk "github.com/cosmos/cosmos-sdk/types"
2121
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
22+
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
2223
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
2324
)
2425

26+
type ReaderOption func(*delegatorsReader) error
27+
28+
func WithChainName(chainName string) ReaderOption {
29+
return func(r *delegatorsReader) error {
30+
r.chainName = chainName
31+
return nil
32+
}
33+
}
34+
35+
func WithLogger(logger log.Logger) ReaderOption {
36+
return func(r *delegatorsReader) error {
37+
r.logger = logger
38+
return nil
39+
}
40+
}
41+
42+
func WithMinSharesFilter(minShares math.LegacyDec) ReaderOption {
43+
return func(r *delegatorsReader) error {
44+
r.minSharesFilter = minShares
45+
return nil
46+
}
47+
}
48+
49+
func WithMaxSharesFilter(maxShares math.LegacyDec) ReaderOption {
50+
return func(r *delegatorsReader) error {
51+
r.maxSharesFilter = maxShares
52+
return nil
53+
}
54+
}
55+
2556
type delegatorsReader struct {
26-
chainName string
27-
src string
28-
logger log.Logger
29-
closer io.Closer
57+
chainName string
58+
src string
59+
logger log.Logger
60+
closer io.Closer
61+
minSharesFilter math.LegacyDec
62+
maxSharesFilter math.LegacyDec
3063
}
3164

3265
// NewDelegatorsReader returns a new Reader that reads delegators from a blockchain data stores.
33-
func NewDelegatorsReader(chainName, src string, logger log.Logger) (goetl.Processor, error) {
34-
return &delegatorsReader{
35-
chainName: chainName,
66+
func NewDelegatorsReader(src string, options ...ReaderOption) (goetl.Processor, error) {
67+
r := &delegatorsReader{
68+
chainName: "mystery",
3669
src: src,
37-
logger: logger,
38-
}, nil
70+
logger: log.NewNopLogger(),
71+
}
72+
73+
for _, option := range options {
74+
if err := option(r); err != nil {
75+
return nil, err
76+
}
77+
}
78+
79+
return r, nil
3980
}
4081

4182
func (r *delegatorsReader) ProcessData(_ etldata.Payload, outputChan chan etldata.Payload, killChan chan error) {
@@ -65,22 +106,17 @@ func (r *delegatorsReader) ProcessData(_ etldata.Payload, outputChan chan etldat
65106

66107
configureSdk(prefix)
67108

68-
err = IterateAllAddresses(ctx, keepers.Bank, func(addr sdk.AccAddress) (stop bool) {
69-
for _, val := range validators {
70-
valAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress)
71-
etlutil.KillPipelineIfErr(err, killChan)
72-
73-
delegation, err := keepers.Staking.GetDelegation(ctx, addr, valAddr)
74-
if err != nil {
75-
if errors.Is(err, stakingtypes.ErrNoDelegation) {
76-
continue
77-
}
109+
err = iterateAllAddresses(ctx, keepers.Bank, func(addr sdk.AccAddress) (stop bool) {
110+
delegations := lo.RejectMap(validators,
111+
extractDelegations(ctx, addr, r.logger, keepers.Staking, killChan))
112+
shares := lo.Reduce(delegations, computeShares(), math.LegacyZeroDec())
78113

79-
r.logger.Error(err.Error())
80-
killChan <- err
81-
return true
82-
}
114+
if (!r.maxSharesFilter.IsNil() && shares.GT(r.maxSharesFilter)) ||
115+
(!r.minSharesFilter.IsNil() && shares.LT(r.minSharesFilter)) {
116+
return false
117+
}
83118

119+
for _, delegation := range delegations {
84120
payload := Delegation{
85121
ChainName: r.chainName,
86122
DelegatorNativeAddr: delegation.DelegatorAddress,
@@ -122,7 +158,7 @@ func (r *delegatorsReader) String() string {
122158

123159
// IterateAllAddresses iterates over all the accounts that are provided to a callback.
124160
// If true is returned from the callback, iteration is halted.
125-
func IterateAllAddresses(ctx context.Context, bankKeeper bankkeeper.BaseKeeper, cb func(sdk.AccAddress) bool) error {
161+
func iterateAllAddresses(ctx context.Context, bankKeeper bankkeeper.BaseKeeper, cb func(sdk.AccAddress) bool) error {
126162
lastSeenAddr := ""
127163
err := bankKeeper.Balances.Walk(ctx, nil, func(key collections.Pair[sdk.AccAddress, string], _ math.Int) (stop bool, err error) {
128164
addr := key.K1()
@@ -137,6 +173,37 @@ func IterateAllAddresses(ctx context.Context, bankKeeper bankkeeper.BaseKeeper,
137173
return err
138174
}
139175

176+
func extractDelegations(
177+
ctx context.Context, address sdk.AccAddress, logger log.Logger, stakingKeeper *stakingkeeper.Keeper, killChan chan error,
178+
) func(item stakingtypes.Validator, index int) (stakingtypes.Delegation, bool) {
179+
return func(item stakingtypes.Validator, _ int) (stakingtypes.Delegation, bool) {
180+
valAddr, err := sdk.ValAddressFromBech32(item.OperatorAddress)
181+
if err != nil {
182+
logger.Error(err.Error())
183+
killChan <- err
184+
return stakingtypes.Delegation{}, true
185+
}
186+
187+
delegation, err := stakingKeeper.GetDelegation(ctx, address, valAddr)
188+
if err != nil {
189+
if errors.Is(err, stakingtypes.ErrNoDelegation) {
190+
return stakingtypes.Delegation{}, true
191+
}
192+
193+
logger.Error(err.Error())
194+
killChan <- err
195+
return stakingtypes.Delegation{}, true
196+
}
197+
return delegation, false
198+
}
199+
}
200+
201+
func computeShares() func(acc math.LegacyDec, delegation stakingtypes.Delegation, _ int) math.LegacyDec {
202+
return func(acc math.LegacyDec, delegation stakingtypes.Delegation, _ int) math.LegacyDec {
203+
return acc.Add(delegation.Shares)
204+
}
205+
}
206+
140207
func guessPrefixFromValoper(valoper string) (string, error) {
141208
if idx := strings.Index(valoper, "valoper"); idx != -1 {
142209
return valoper[:idx], nil

0 commit comments

Comments
 (0)