Skip to content

Commit 30fd28a

Browse files
committed
- handle undelegation messages
- add custom actions to return delegations and rewards
1 parent 39d271e commit 30fd28a

10 files changed

+376
-10
lines changed

database/nym_mixnet_v1.go

+31
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
dbtypes "github.com/forbole/bdjuno/v3/database/types"
77
"github.com/lib/pq"
8+
"github.com/rs/zerolog/log"
89
"github.com/shopspring/decimal"
910

1011
cosmosTypes "github.com/cosmos/cosmos-sdk/types"
@@ -68,6 +69,24 @@ ON CONFLICT (identity_key) DO UPDATE
6869
return nil
6970
}
7071

72+
func (db *Db) GetNymMixnetV1MixnodeEvent(eventKind string, identityKey string, sender *string, height *int64, executedAt *string) ([]dbtypes.NyxNymMixnetV1MixnodeEventsRow, error) {
73+
filter := fmt.Sprintf("WHERE event_kind = '%s' AND identity_key = '%s'", eventKind, identityKey)
74+
order := "height ASC, executed_at ASC"
75+
if sender != nil {
76+
filter = fmt.Sprintf("%s AND sender = '%s'", filter, *sender)
77+
}
78+
if height != nil {
79+
filter = fmt.Sprintf("%s AND height >= %d", filter, *height)
80+
} else if executedAt != nil {
81+
filter = fmt.Sprintf("%s AND executed_at >= '%s'", filter, *executedAt)
82+
}
83+
stmt := fmt.Sprintf(`SELECT * FROM nyx_nym_mixnet_v1_mixnode_events %s ORDER BY %s`, filter, order)
84+
85+
var rows []dbtypes.NyxNymMixnetV1MixnodeEventsRow
86+
err := db.Sqlx.Select(&rows, stmt)
87+
return rows, err
88+
}
89+
7190
// SaveNymMixnetV1MixnodeEvent allows to store the wasm contract events
7291
func (db *Db) SaveNymMixnetV1MixnodeEvent(eventKind string, actor string, proxy *string, identityKey string, amount *cosmosTypes.Coins, dataType string, dataJson string, executeContract types.WasmExecuteContract, tx *juno.Tx) error {
7392
stmt := `
@@ -107,6 +126,18 @@ SELECT COUNT(height) FROM nyx_nym_mixnet_v1_mixnode_reward WHERE identity_key =
107126
return count > 0, err
108127
}
109128

129+
func (db *Db) GetNymMixnetV1MixnodeRewardEvent(identityKey string, heightMin uint64, heightMax *uint64) ([]dbtypes.NyxNymMixnetV1MixnodeRewardRow, error) {
130+
stmt := fmt.Sprintf(`SELECT * FROM nyx_nym_mixnet_v1_mixnode_reward WHERE height >= %d AND identity_key = '%s'`, heightMin, identityKey)
131+
if heightMax != nil && *heightMax > 0 {
132+
stmt = fmt.Sprintf("%s AND height <= %d", stmt, *heightMax)
133+
}
134+
stmt = fmt.Sprintf("%s ORDER BY height ASC", stmt)
135+
var rows []dbtypes.NyxNymMixnetV1MixnodeRewardRow
136+
err := db.Sqlx.Select(&rows, stmt)
137+
log.Info().Int("count", len(rows)).Err(err).Msg(stmt)
138+
return rows, err
139+
}
140+
110141
// SaveNymMixnetV1MixnodeRewardingEvent allows to store the mixnode rewarding events
111142
func (db *Db) SaveNymMixnetV1MixnodeRewardingEvent(identityKey string, totalNodeReward cosmosTypes.Coins, totalDelegations cosmosTypes.Coins, operatorReward cosmosTypes.Coins, unitDelegatorReward decimal.Decimal, apy float64, stakingSupply cosmosTypes.Coins, profitMarginPercentage int, event cosmosTypes.StringEvent, executeContract types.WasmExecuteContract, tx *juno.Tx) error {
112143
stmt := `

database/types/nym_mixnet_v1.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package types
2+
3+
import "time"
4+
5+
// NyxNymMixnetV1MixnodeEventsRow represents a single row inside the nyx_nym_mixnet_v1_mixnode_events table
6+
type NyxNymMixnetV1MixnodeEventsRow struct {
7+
EventKind string `db:"event_kind"`
8+
Actor string `db:"actor"`
9+
Sender string `db:"sender"`
10+
Proxy *string `db:"proxy"`
11+
IdentityKey string `db:"identity_key"`
12+
13+
ContractAddress string `db:"contract_address"`
14+
EventType string `db:"event_type"`
15+
Hash string `db:"hash"`
16+
17+
Attributes interface{} `db:"attributes"`
18+
ExecutedAt time.Time `db:"executed_at"`
19+
20+
Fee *DbCoins `db:"fee"`
21+
Amount *DbCoins `db:"amount"`
22+
Height int64 `db:"height"`
23+
}
24+
25+
// NyxNymMixnetV1MixnodeRewardRow represents a single row inside the nyx_nym_mixnet_v1_mixnode_reward table
26+
type NyxNymMixnetV1MixnodeRewardRow struct {
27+
Sender string `db:"sender"`
28+
IdentityKey string `db:"identity_key"`
29+
30+
TotalNodeReward DbCoins `db:"total_node_reward"`
31+
TotalDelegations DbCoins `db:"total_delegations"`
32+
OperatorReward DbCoins `db:"operator_reward"`
33+
UnitDelegatorReward uint64 `db:"unit_delegator_reward"` // TODO: should be a decimal type
34+
Apy float64 `db:"apy"`
35+
StakingSupply DbCoins `db:"staking_supply"`
36+
ProfitMarginPercentage uint64 `db:"profit_margin_percentage"`
37+
38+
ContractAddress string `db:"contract_address"`
39+
EventType string `db:"event_type"`
40+
41+
Attributes interface{} `db:"attributes"`
42+
ExecutedAt time.Time `db:"executed_at"`
43+
44+
Height int64 `db:"height"`
45+
Hash string `db:"hash"`
46+
}

modules/actions/handle_additional_operations.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package actions
22

33
import (
4+
"github.com/rs/zerolog/log"
45
"os"
56
"os/signal"
67
"sync"
@@ -15,8 +16,10 @@ var (
1516
)
1617

1718
func (m *Module) RunAdditionalOperations() error {
19+
log.Info().Msg("Starting actions worker...")
20+
1821
// Build the worker
19-
context := actionstypes.NewContext(m.node, m.sources)
22+
context := actionstypes.NewContext(m.node, m.sources, m.db)
2023
worker := actionstypes.NewActionsWorker(context)
2124

2225
// Register the endpoints
@@ -41,6 +44,12 @@ func (m *Module) RunAdditionalOperations() error {
4144
worker.RegisterHandler("/validator_redelegations_from", handlers.ValidatorRedelegationsFromHandler)
4245
worker.RegisterHandler("/validator_unbonding_delegations", handlers.ValidatorUnbondingDelegationsHandler)
4346

47+
// -- Nyx --
48+
// -- Nym --
49+
// -- Mixnet v1 --
50+
worker.RegisterHandler("/nyx/nym/mixnet/v1/mixnode/delegations", handlers.NyxNymMixnetV1DelegationsHandler)
51+
worker.RegisterHandler("/nyx/nym/mixnet/v1/mixnode/rewards", handlers.NyxNymMixnetV1RewardsHandler)
52+
4453
// Listen for and trap any OS signal to gracefully shutdown and exit
4554
m.trapSignal()
4655

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package handlers
2+
3+
import (
4+
"fmt"
5+
sdk "github.com/cosmos/cosmos-sdk/types"
6+
dbtypes "github.com/forbole/bdjuno/v3/database/types"
7+
"github.com/forbole/bdjuno/v3/modules/actions/types"
8+
"github.com/rs/zerolog/log"
9+
"time"
10+
)
11+
12+
func NyxNymMixnetV1DelegationsHandler(ctx *types.Context, payload *types.Payload) (interface{}, error) {
13+
address := payload.GetAddress()
14+
identityKey := payload.GetIdentityKey()
15+
height := payload.GetHeight()
16+
17+
log.Debug().Str("address", address).
18+
Str("identity_key", *identityKey).
19+
Interface("height", height).
20+
Msg("executing NyxNymMixnetV1DelegationsHandler action")
21+
22+
if identityKey == nil {
23+
return nil, fmt.Errorf("identity key not specified")
24+
}
25+
26+
delegations, err := getDelegations(ctx, *identityKey, address, height)
27+
28+
log.Debug().Interface("delegations", delegations).Msg("Got delegations")
29+
30+
return delegations, err
31+
}
32+
33+
func NyxNymMixnetV1RewardsHandler(ctx *types.Context, payload *types.Payload) (interface{}, error) {
34+
address := payload.GetAddress()
35+
identityKey := payload.GetIdentityKey()
36+
height := payload.GetHeight()
37+
38+
log.Debug().Str("address", address).
39+
Str("identity_key", *identityKey).
40+
Interface("height", height).
41+
Msg("executing NyxNymMixnetV1RewardsHandler action")
42+
43+
if identityKey == nil {
44+
return nil, fmt.Errorf("identity key not specified")
45+
}
46+
47+
delegations, err := getDelegations(ctx, *identityKey, address, height)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
rewards := make([]types.NyxNymMixnetV1Rewards, len(delegations))
53+
54+
for i, delegation := range delegations {
55+
var heightEnd uint64
56+
57+
if delegation.End != nil {
58+
heightEnd = delegation.End.Height
59+
}
60+
61+
log.Debug().Str("identity", *identityKey).Uint64("start", delegation.Start.Height).Uint64("end", heightEnd).Msg("Getting rewards")
62+
63+
rewardEventsDb, err := ctx.Db.GetNymMixnetV1MixnodeRewardEvent(*identityKey, delegation.Start.Height, &heightEnd)
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to get reward events: %s", err)
66+
}
67+
68+
rewardEvents := make([]types.NyxNymMixnetV1Reward, len(rewardEventsDb))
69+
totalRewards := sdk.NewDec(0)
70+
delegationDec := sdk.MustNewDecFromStr(delegation.Delegation.Amount)
71+
for j, event := range rewardEventsDb {
72+
totalNodeReward := event.TotalNodeReward.ToCoins()[0]
73+
unitDelegatorReward := sdk.NewDec(int64(event.UnitDelegatorReward)).Quo(sdk.NewDec(1_000_000_000_000))
74+
75+
//reward := decimal.NewFromInt(int64(event.UnitDelegatorReward)).Mul(decimal.RequireFromString(delegation.Delegation.Amount)).Div(decimal.NewFromInt(1_000_000_000_000))
76+
reward := delegationDec.Mul(unitDelegatorReward)
77+
rewardAsInt := reward.RoundInt64()
78+
totalRewards = totalRewards.Add(sdk.NewDec(rewardAsInt))
79+
80+
rewardEvents[j] = types.NyxNymMixnetV1Reward{
81+
Timestamp: event.ExecutedAt,
82+
Height: uint64(event.Height),
83+
TotalNodeReward: types.Coin{
84+
Amount: totalNodeReward.Amount.String(),
85+
Denom: totalNodeReward.Denom,
86+
},
87+
Reward: types.Coin{
88+
Amount: reward.RoundInt().String(),
89+
Denom: totalNodeReward.Denom,
90+
},
91+
EpochApy: event.Apy,
92+
}
93+
}
94+
95+
endTS := time.Now()
96+
if delegation.End != nil {
97+
endTS = delegation.End.Timestamp
98+
}
99+
duration := endTS.Sub(delegation.Start.Timestamp)
100+
durationSecs := int64(duration.Seconds())
101+
returnPerSecond := totalRewards.Quo(delegationDec).QuoInt64(durationSecs)
102+
returnPerYear := returnPerSecond.MulInt64(365 * 24 * 60 * 60)
103+
104+
rewards[i] = types.NyxNymMixnetV1Rewards{
105+
DelegatorAddress: delegation.DelegatorAddress,
106+
MixnodeIdentityKey: delegation.MixnodeIdentityKey,
107+
Start: delegation.Start,
108+
End: delegation.End,
109+
Delegation: delegation.Delegation,
110+
Rewards: rewardEvents,
111+
TotalRewards: types.Coin{
112+
Amount: totalRewards.Quo(sdk.NewDec(1_000_000)).String(),
113+
Denom: "nym",
114+
},
115+
APY: returnPerYear.MustFloat64(),
116+
}
117+
}
118+
119+
return rewards, nil
120+
}
121+
122+
func getDelegations(ctx *types.Context, identityKey string, address string, height *int64) ([]types.NyxNymMixnetV1Delegation, error) {
123+
dbDelegations, err := ctx.Db.GetNymMixnetV1MixnodeEvent("delegate_to_mixnode", identityKey, &address, height, nil)
124+
if err != nil {
125+
return nil, fmt.Errorf("error while getting event rows: %s", err)
126+
}
127+
128+
log.Debug().Str("identityKey", identityKey).Str("address", address).Int64("height", *height).Int("count delegations", len(dbDelegations)).Msg("Got delegations")
129+
130+
dbUndelegations, err := ctx.Db.GetNymMixnetV1MixnodeEvent("undelegation", identityKey, &address, height, nil)
131+
if err != nil {
132+
return nil, fmt.Errorf("error while getting event rows: %s", err)
133+
}
134+
135+
log.Debug().Int("count undelegations", len(dbUndelegations)).Msg("Got undelegations")
136+
137+
delegations := make([]types.NyxNymMixnetV1Delegation, len(dbDelegations))
138+
139+
for i, delegation := range dbDelegations {
140+
undelegation, j := contains(dbUndelegations, delegation.IdentityKey)
141+
142+
// undelegation must occur after delegation
143+
if undelegation != nil && undelegation.Height <= delegation.Height {
144+
dbUndelegations = remove(dbUndelegations, j)
145+
undelegation = nil
146+
}
147+
148+
amountCoins := delegation.Amount.ToCoins()
149+
amount := "0"
150+
denom := "unym"
151+
152+
if len(amount) > 0 {
153+
amount = amountCoins[0].Amount.String()
154+
denom = amountCoins[0].Denom
155+
} else {
156+
log.Warn().Interface("delegation", delegation).Msg("Zero delegation")
157+
}
158+
159+
delegations[i] = types.NyxNymMixnetV1Delegation{
160+
DelegatorAddress: delegation.Sender,
161+
MixnodeIdentityKey: delegation.IdentityKey,
162+
Start: types.NyxNymMixnetV1DelegationTimestamp{
163+
Timestamp: delegation.ExecutedAt,
164+
Height: uint64(delegation.Height),
165+
},
166+
End: nil,
167+
Delegation: types.Coin{
168+
Amount: amount,
169+
Denom: denom,
170+
},
171+
}
172+
if undelegation != nil {
173+
delegations[i].End = &types.NyxNymMixnetV1DelegationTimestamp{
174+
Timestamp: undelegation.ExecutedAt,
175+
Height: uint64(undelegation.Height),
176+
}
177+
dbUndelegations = remove(dbUndelegations, j)
178+
}
179+
}
180+
181+
return delegations, nil
182+
}
183+
184+
func remove(slice []dbtypes.NyxNymMixnetV1MixnodeEventsRow, indexToRemove int) []dbtypes.NyxNymMixnetV1MixnodeEventsRow {
185+
return append(slice[:indexToRemove], slice[indexToRemove+1:]...)
186+
}
187+
188+
func contains(arr []dbtypes.NyxNymMixnetV1MixnodeEventsRow, identityKey string) (*dbtypes.NyxNymMixnetV1MixnodeEventsRow, int) {
189+
for i, item := range arr {
190+
if item.IdentityKey == identityKey {
191+
return &item, i
192+
}
193+
}
194+
return nil, 0
195+
}

modules/actions/module.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package actions
22

33
import (
44
"github.com/cosmos/cosmos-sdk/simapp/params"
5+
"github.com/forbole/bdjuno/v3/database"
56
"github.com/forbole/juno/v3/modules"
67
"github.com/forbole/juno/v3/node"
78
"github.com/forbole/juno/v3/node/builder"
@@ -24,9 +25,10 @@ type Module struct {
2425
cfg *Config
2526
node node.Node
2627
sources *modulestypes.Sources
28+
db *database.Db
2729
}
2830

29-
func NewModule(cfg config.Config, encodingConfig *params.EncodingConfig) *Module {
31+
func NewModule(cfg config.Config, encodingConfig *params.EncodingConfig, db *database.Db) *Module {
3032
bz, err := cfg.GetBytes()
3133
if err != nil {
3234
panic(err)
@@ -58,6 +60,7 @@ func NewModule(cfg config.Config, encodingConfig *params.EncodingConfig) *Module
5860
cfg: actionsCfg,
5961
node: junoNode,
6062
sources: sources,
63+
db: db,
6164
}
6265
}
6366

modules/actions/types/handler.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package types
22

33
import (
44
"fmt"
5+
"github.com/forbole/bdjuno/v3/database"
56

67
"github.com/forbole/juno/v3/node"
78

@@ -12,13 +13,15 @@ import (
1213
type Context struct {
1314
node node.Node
1415
Sources *modulestypes.Sources
16+
Db *database.Db
1517
}
1618

1719
// NewContext returns a new Context instance
18-
func NewContext(node node.Node, sources *modulestypes.Sources) *Context {
20+
func NewContext(node node.Node, sources *modulestypes.Sources, db *database.Db) *Context {
1921
return &Context{
2022
node: node,
2123
Sources: sources,
24+
Db: db,
2225
}
2326
}
2427

0 commit comments

Comments
 (0)