Skip to content

Commit

Permalink
- handle undelegation messages
Browse files Browse the repository at this point in the history
- add custom actions to return delegations and rewards
  • Loading branch information
mmsinclair committed Nov 1, 2022
1 parent 39d271e commit 30fd28a
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 10 deletions.
31 changes: 31 additions & 0 deletions database/nym_mixnet_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
dbtypes "github.com/forbole/bdjuno/v3/database/types"
"github.com/lib/pq"
"github.com/rs/zerolog/log"
"github.com/shopspring/decimal"

cosmosTypes "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -68,6 +69,24 @@ ON CONFLICT (identity_key) DO UPDATE
return nil
}

func (db *Db) GetNymMixnetV1MixnodeEvent(eventKind string, identityKey string, sender *string, height *int64, executedAt *string) ([]dbtypes.NyxNymMixnetV1MixnodeEventsRow, error) {
filter := fmt.Sprintf("WHERE event_kind = '%s' AND identity_key = '%s'", eventKind, identityKey)
order := "height ASC, executed_at ASC"
if sender != nil {
filter = fmt.Sprintf("%s AND sender = '%s'", filter, *sender)
}
if height != nil {
filter = fmt.Sprintf("%s AND height >= %d", filter, *height)
} else if executedAt != nil {
filter = fmt.Sprintf("%s AND executed_at >= '%s'", filter, *executedAt)
}
stmt := fmt.Sprintf(`SELECT * FROM nyx_nym_mixnet_v1_mixnode_events %s ORDER BY %s`, filter, order)

var rows []dbtypes.NyxNymMixnetV1MixnodeEventsRow
err := db.Sqlx.Select(&rows, stmt)
return rows, err
}

// SaveNymMixnetV1MixnodeEvent allows to store the wasm contract events
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 {
stmt := `
Expand Down Expand Up @@ -107,6 +126,18 @@ SELECT COUNT(height) FROM nyx_nym_mixnet_v1_mixnode_reward WHERE identity_key =
return count > 0, err
}

func (db *Db) GetNymMixnetV1MixnodeRewardEvent(identityKey string, heightMin uint64, heightMax *uint64) ([]dbtypes.NyxNymMixnetV1MixnodeRewardRow, error) {
stmt := fmt.Sprintf(`SELECT * FROM nyx_nym_mixnet_v1_mixnode_reward WHERE height >= %d AND identity_key = '%s'`, heightMin, identityKey)
if heightMax != nil && *heightMax > 0 {
stmt = fmt.Sprintf("%s AND height <= %d", stmt, *heightMax)
}
stmt = fmt.Sprintf("%s ORDER BY height ASC", stmt)
var rows []dbtypes.NyxNymMixnetV1MixnodeRewardRow
err := db.Sqlx.Select(&rows, stmt)
log.Info().Int("count", len(rows)).Err(err).Msg(stmt)
return rows, err
}

// SaveNymMixnetV1MixnodeRewardingEvent allows to store the mixnode rewarding events
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 {
stmt := `
Expand Down
46 changes: 46 additions & 0 deletions database/types/nym_mixnet_v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package types

import "time"

// NyxNymMixnetV1MixnodeEventsRow represents a single row inside the nyx_nym_mixnet_v1_mixnode_events table
type NyxNymMixnetV1MixnodeEventsRow struct {
EventKind string `db:"event_kind"`
Actor string `db:"actor"`
Sender string `db:"sender"`
Proxy *string `db:"proxy"`
IdentityKey string `db:"identity_key"`

ContractAddress string `db:"contract_address"`
EventType string `db:"event_type"`
Hash string `db:"hash"`

Attributes interface{} `db:"attributes"`
ExecutedAt time.Time `db:"executed_at"`

Fee *DbCoins `db:"fee"`
Amount *DbCoins `db:"amount"`
Height int64 `db:"height"`
}

// NyxNymMixnetV1MixnodeRewardRow represents a single row inside the nyx_nym_mixnet_v1_mixnode_reward table
type NyxNymMixnetV1MixnodeRewardRow struct {
Sender string `db:"sender"`
IdentityKey string `db:"identity_key"`

TotalNodeReward DbCoins `db:"total_node_reward"`
TotalDelegations DbCoins `db:"total_delegations"`
OperatorReward DbCoins `db:"operator_reward"`
UnitDelegatorReward uint64 `db:"unit_delegator_reward"` // TODO: should be a decimal type
Apy float64 `db:"apy"`
StakingSupply DbCoins `db:"staking_supply"`
ProfitMarginPercentage uint64 `db:"profit_margin_percentage"`

ContractAddress string `db:"contract_address"`
EventType string `db:"event_type"`

Attributes interface{} `db:"attributes"`
ExecutedAt time.Time `db:"executed_at"`

Height int64 `db:"height"`
Hash string `db:"hash"`
}
11 changes: 10 additions & 1 deletion modules/actions/handle_additional_operations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package actions

import (
"github.com/rs/zerolog/log"
"os"
"os/signal"
"sync"
Expand All @@ -15,8 +16,10 @@ var (
)

func (m *Module) RunAdditionalOperations() error {
log.Info().Msg("Starting actions worker...")

// Build the worker
context := actionstypes.NewContext(m.node, m.sources)
context := actionstypes.NewContext(m.node, m.sources, m.db)
worker := actionstypes.NewActionsWorker(context)

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

// -- Nyx --
// -- Nym --
// -- Mixnet v1 --
worker.RegisterHandler("/nyx/nym/mixnet/v1/mixnode/delegations", handlers.NyxNymMixnetV1DelegationsHandler)
worker.RegisterHandler("/nyx/nym/mixnet/v1/mixnode/rewards", handlers.NyxNymMixnetV1RewardsHandler)

// Listen for and trap any OS signal to gracefully shutdown and exit
m.trapSignal()

Expand Down
195 changes: 195 additions & 0 deletions modules/actions/handlers/nyx_nym_mixnet_v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package handlers

import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
dbtypes "github.com/forbole/bdjuno/v3/database/types"
"github.com/forbole/bdjuno/v3/modules/actions/types"
"github.com/rs/zerolog/log"
"time"
)

func NyxNymMixnetV1DelegationsHandler(ctx *types.Context, payload *types.Payload) (interface{}, error) {
address := payload.GetAddress()
identityKey := payload.GetIdentityKey()
height := payload.GetHeight()

log.Debug().Str("address", address).
Str("identity_key", *identityKey).
Interface("height", height).
Msg("executing NyxNymMixnetV1DelegationsHandler action")

if identityKey == nil {
return nil, fmt.Errorf("identity key not specified")
}

delegations, err := getDelegations(ctx, *identityKey, address, height)

log.Debug().Interface("delegations", delegations).Msg("Got delegations")

return delegations, err
}

func NyxNymMixnetV1RewardsHandler(ctx *types.Context, payload *types.Payload) (interface{}, error) {
address := payload.GetAddress()
identityKey := payload.GetIdentityKey()
height := payload.GetHeight()

log.Debug().Str("address", address).
Str("identity_key", *identityKey).
Interface("height", height).
Msg("executing NyxNymMixnetV1RewardsHandler action")

if identityKey == nil {
return nil, fmt.Errorf("identity key not specified")
}

delegations, err := getDelegations(ctx, *identityKey, address, height)
if err != nil {
return nil, err
}

rewards := make([]types.NyxNymMixnetV1Rewards, len(delegations))

for i, delegation := range delegations {
var heightEnd uint64

if delegation.End != nil {
heightEnd = delegation.End.Height
}

log.Debug().Str("identity", *identityKey).Uint64("start", delegation.Start.Height).Uint64("end", heightEnd).Msg("Getting rewards")

rewardEventsDb, err := ctx.Db.GetNymMixnetV1MixnodeRewardEvent(*identityKey, delegation.Start.Height, &heightEnd)
if err != nil {
return nil, fmt.Errorf("failed to get reward events: %s", err)
}

rewardEvents := make([]types.NyxNymMixnetV1Reward, len(rewardEventsDb))
totalRewards := sdk.NewDec(0)
delegationDec := sdk.MustNewDecFromStr(delegation.Delegation.Amount)
for j, event := range rewardEventsDb {
totalNodeReward := event.TotalNodeReward.ToCoins()[0]
unitDelegatorReward := sdk.NewDec(int64(event.UnitDelegatorReward)).Quo(sdk.NewDec(1_000_000_000_000))

//reward := decimal.NewFromInt(int64(event.UnitDelegatorReward)).Mul(decimal.RequireFromString(delegation.Delegation.Amount)).Div(decimal.NewFromInt(1_000_000_000_000))
reward := delegationDec.Mul(unitDelegatorReward)
rewardAsInt := reward.RoundInt64()
totalRewards = totalRewards.Add(sdk.NewDec(rewardAsInt))

rewardEvents[j] = types.NyxNymMixnetV1Reward{
Timestamp: event.ExecutedAt,
Height: uint64(event.Height),
TotalNodeReward: types.Coin{
Amount: totalNodeReward.Amount.String(),
Denom: totalNodeReward.Denom,
},
Reward: types.Coin{
Amount: reward.RoundInt().String(),
Denom: totalNodeReward.Denom,
},
EpochApy: event.Apy,
}
}

endTS := time.Now()
if delegation.End != nil {
endTS = delegation.End.Timestamp
}
duration := endTS.Sub(delegation.Start.Timestamp)
durationSecs := int64(duration.Seconds())
returnPerSecond := totalRewards.Quo(delegationDec).QuoInt64(durationSecs)
returnPerYear := returnPerSecond.MulInt64(365 * 24 * 60 * 60)

rewards[i] = types.NyxNymMixnetV1Rewards{
DelegatorAddress: delegation.DelegatorAddress,
MixnodeIdentityKey: delegation.MixnodeIdentityKey,
Start: delegation.Start,
End: delegation.End,
Delegation: delegation.Delegation,
Rewards: rewardEvents,
TotalRewards: types.Coin{
Amount: totalRewards.Quo(sdk.NewDec(1_000_000)).String(),
Denom: "nym",
},
APY: returnPerYear.MustFloat64(),
}
}

return rewards, nil
}

func getDelegations(ctx *types.Context, identityKey string, address string, height *int64) ([]types.NyxNymMixnetV1Delegation, error) {
dbDelegations, err := ctx.Db.GetNymMixnetV1MixnodeEvent("delegate_to_mixnode", identityKey, &address, height, nil)
if err != nil {
return nil, fmt.Errorf("error while getting event rows: %s", err)
}

log.Debug().Str("identityKey", identityKey).Str("address", address).Int64("height", *height).Int("count delegations", len(dbDelegations)).Msg("Got delegations")

dbUndelegations, err := ctx.Db.GetNymMixnetV1MixnodeEvent("undelegation", identityKey, &address, height, nil)
if err != nil {
return nil, fmt.Errorf("error while getting event rows: %s", err)
}

log.Debug().Int("count undelegations", len(dbUndelegations)).Msg("Got undelegations")

delegations := make([]types.NyxNymMixnetV1Delegation, len(dbDelegations))

for i, delegation := range dbDelegations {
undelegation, j := contains(dbUndelegations, delegation.IdentityKey)

// undelegation must occur after delegation
if undelegation != nil && undelegation.Height <= delegation.Height {
dbUndelegations = remove(dbUndelegations, j)
undelegation = nil
}

amountCoins := delegation.Amount.ToCoins()
amount := "0"
denom := "unym"

if len(amount) > 0 {
amount = amountCoins[0].Amount.String()
denom = amountCoins[0].Denom
} else {
log.Warn().Interface("delegation", delegation).Msg("Zero delegation")
}

delegations[i] = types.NyxNymMixnetV1Delegation{
DelegatorAddress: delegation.Sender,
MixnodeIdentityKey: delegation.IdentityKey,
Start: types.NyxNymMixnetV1DelegationTimestamp{
Timestamp: delegation.ExecutedAt,
Height: uint64(delegation.Height),
},
End: nil,
Delegation: types.Coin{
Amount: amount,
Denom: denom,
},
}
if undelegation != nil {
delegations[i].End = &types.NyxNymMixnetV1DelegationTimestamp{
Timestamp: undelegation.ExecutedAt,
Height: uint64(undelegation.Height),
}
dbUndelegations = remove(dbUndelegations, j)
}
}

return delegations, nil
}

func remove(slice []dbtypes.NyxNymMixnetV1MixnodeEventsRow, indexToRemove int) []dbtypes.NyxNymMixnetV1MixnodeEventsRow {
return append(slice[:indexToRemove], slice[indexToRemove+1:]...)
}

func contains(arr []dbtypes.NyxNymMixnetV1MixnodeEventsRow, identityKey string) (*dbtypes.NyxNymMixnetV1MixnodeEventsRow, int) {
for i, item := range arr {
if item.IdentityKey == identityKey {
return &item, i
}
}
return nil, 0
}
5 changes: 4 additions & 1 deletion modules/actions/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package actions

import (
"github.com/cosmos/cosmos-sdk/simapp/params"
"github.com/forbole/bdjuno/v3/database"
"github.com/forbole/juno/v3/modules"
"github.com/forbole/juno/v3/node"
"github.com/forbole/juno/v3/node/builder"
Expand All @@ -24,9 +25,10 @@ type Module struct {
cfg *Config
node node.Node
sources *modulestypes.Sources
db *database.Db
}

func NewModule(cfg config.Config, encodingConfig *params.EncodingConfig) *Module {
func NewModule(cfg config.Config, encodingConfig *params.EncodingConfig, db *database.Db) *Module {
bz, err := cfg.GetBytes()
if err != nil {
panic(err)
Expand Down Expand Up @@ -58,6 +60,7 @@ func NewModule(cfg config.Config, encodingConfig *params.EncodingConfig) *Module
cfg: actionsCfg,
node: junoNode,
sources: sources,
db: db,
}
}

Expand Down
5 changes: 4 additions & 1 deletion modules/actions/types/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"fmt"
"github.com/forbole/bdjuno/v3/database"

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

Expand All @@ -12,13 +13,15 @@ import (
type Context struct {
node node.Node
Sources *modulestypes.Sources
Db *database.Db
}

// NewContext returns a new Context instance
func NewContext(node node.Node, sources *modulestypes.Sources) *Context {
func NewContext(node node.Node, sources *modulestypes.Sources, db *database.Db) *Context {
return &Context{
node: node,
Sources: sources,
Db: db,
}
}

Expand Down
Loading

0 comments on commit 30fd28a

Please sign in to comment.