Skip to content

Commit

Permalink
Ejections report cli
Browse files Browse the repository at this point in the history
Adds second table ordered by ejection txn

Sort results in descending order

Consolidate table logic

Add stake percentage calculation to ejection report

Add stake percentage to operator ejections data api

Update swagger

Fix id->address mapping

Lint
  • Loading branch information
pschork committed Oct 25, 2024
1 parent db3da32 commit 3fc378b
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 5 deletions.
6 changes: 6 additions & 0 deletions disperser/dataapi/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -992,12 +992,18 @@ const docTemplate = `{
"block_timestamp": {
"type": "string"
},
"operator_address": {
"type": "string"
},
"operator_id": {
"type": "string"
},
"quorum": {
"type": "integer"
},
"stake_percentage": {
"type": "number"
},
"transaction_hash": {
"type": "string"
}
Expand Down
6 changes: 6 additions & 0 deletions disperser/dataapi/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -988,12 +988,18 @@
"block_timestamp": {
"type": "string"
},
"operator_address": {
"type": "string"
},
"operator_id": {
"type": "string"
},
"quorum": {
"type": "integer"
},
"stake_percentage": {
"type": "number"
},
"transaction_hash": {
"type": "string"
}
Expand Down
4 changes: 4 additions & 0 deletions disperser/dataapi/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,14 @@ definitions:
type: integer
block_timestamp:
type: string
operator_address:
type: string
operator_id:
type: string
quorum:
type: integer
stake_percentage:
type: number
transaction_hash:
type: string
type: object
Expand Down
53 changes: 53 additions & 0 deletions disperser/dataapi/queried_operators_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package dataapi
import (
"context"
"fmt"
"math/big"
"net"
"sort"
"strings"
"time"

"github.com/Layr-Labs/eigenda/core"
Expand Down Expand Up @@ -106,6 +108,57 @@ func (s *server) getOperatorEjections(ctx context.Context, days int32, operatorI
return nil, err
}

stateCache := make(map[uint64]*core.OperatorState)
ejectedOperatorIds := make(map[core.OperatorID]struct{})
for _, ejection := range operatorEjections {
previouseBlock := ejection.BlockNumber - 1
if _, exists := stateCache[previouseBlock]; !exists {
state, err := s.chainState.GetOperatorState(context.Background(), uint(previouseBlock), []uint8{0, 1})
if err != nil {
return nil, err
}
stateCache[previouseBlock] = state
}

// construct a set of ejected operator ids for later batch address lookup
opID, err := core.OperatorIDFromHex(ejection.OperatorId)
if err != nil {
return nil, err
}
ejectedOperatorIds[opID] = struct{}{}
}

// resolve operator id to operator addresses mapping
operatorIDs := make([]core.OperatorID, 0, len(ejectedOperatorIds))
for opID := range ejectedOperatorIds {
operatorIDs = append(operatorIDs, opID)
}
operatorAddresses, err := s.transactor.BatchOperatorIDToAddress(ctx, operatorIDs)
if err != nil {
return nil, err
}
operatorIdToAddress := make(map[string]string)
for i := range operatorAddresses {
operatorIdToAddress["0x"+operatorIDs[i].Hex()] = strings.ToLower(operatorAddresses[i].Hex())
}

for _, ejection := range operatorEjections {
state := stateCache[ejection.BlockNumber-1]
opID, err := core.OperatorIDFromHex(ejection.OperatorId)
if err != nil {
return nil, err
}

stakePercentage := float64(0)
if stake, ok := state.Operators[ejection.Quorum][opID]; ok {
totalStake := new(big.Float).SetInt(state.Totals[ejection.Quorum].Stake)
operatorStake := new(big.Float).SetInt(stake.Stake)
stakePercentage, _ = new(big.Float).Mul(big.NewFloat(100), new(big.Float).Quo(operatorStake, totalStake)).Float64()
}
ejection.StakePercentage = stakePercentage
ejection.OperatorAddress = operatorIdToAddress[ejection.OperatorId]
}

s.logger.Info("Get operator ejections", "days", days, "operatorId", operatorId, "len", len(operatorEjections), "duration", time.Since(startTime))
return operatorEjections, nil
}
Expand Down
12 changes: 7 additions & 5 deletions disperser/dataapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,13 @@ type (
}

QueriedOperatorEjections struct {
OperatorId string `json:"operator_id"`
Quorum uint8 `json:"quorum"`
BlockNumber uint64 `json:"block_number"`
BlockTimestamp string `json:"block_timestamp"`
TransactionHash string `json:"transaction_hash"`
OperatorId string `json:"operator_id"`
OperatorAddress string `json:"operator_address"`
Quorum uint8 `json:"quorum"`
BlockNumber uint64 `json:"block_number"`
BlockTimestamp string `json:"block_timestamp"`
TransactionHash string `json:"transaction_hash"`
StakePercentage float64 `json:"stake_percentage"`
}
QueriedOperatorEjectionsResponse struct {
Ejections []*QueriedOperatorEjections `json:"ejections"`
Expand Down
9 changes: 9 additions & 0 deletions tools/ejections/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
build: clean
go mod tidy
go build -o ./bin/ejections ./cmd

clean:
rm -rf ./bin

run: build
./bin/ejections --help
166 changes: 166 additions & 0 deletions tools/ejections/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package main

Check failure on line 1 in tools/ejections/cmd/main.go

View workflow job for this annotation

GitHub Actions / Linter

: # github.com/Layr-Labs/eigenda/tools/ejections/cmd

import (
"context"
"errors"
"fmt"
"log"
"math/big"
"os"
"sort"
"strings"

"github.com/Layr-Labs/eigenda/common"
"github.com/Layr-Labs/eigenda/common/geth"
"github.com/Layr-Labs/eigenda/core"
"github.com/Layr-Labs/eigenda/core/eth"
"github.com/Layr-Labs/eigenda/disperser/dataapi"
"github.com/Layr-Labs/eigenda/disperser/dataapi/subgraph"
"github.com/Layr-Labs/eigenda/tools/ejections"
"github.com/Layr-Labs/eigenda/tools/ejections/flags"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/urfave/cli"

gethcommon "github.com/ethereum/go-ethereum/common"
)

var (
version = "1.0.0"
gitCommit = ""
gitDate = ""
)

func main() {
app := cli.NewApp()
app.Version = fmt.Sprintf("%s,%s,%s", version, gitCommit, gitDate)
app.Name = "ejections report"
app.Description = "operator ejections report"
app.Usage = ""
app.Flags = flags.Flags
app.Action = RunScan
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}

func RunScan(ctx *cli.Context) error {
config, err := ejections.NewConfig(ctx)
if err != nil {
return err
}

logger, err := common.NewLogger(config.LoggerConfig)
if err != nil {
return err
}

client, err := geth.NewMultiHomingClient(config.EthClientConfig, gethcommon.Address{}, logger)
if err != nil {
return err
}

tx, err := eth.NewTransactor(logger, client, config.BLSOperatorStateRetrieverAddr, config.EigenDAServiceManagerAddr)

Check failure on line 63 in tools/ejections/cmd/main.go

View workflow job for this annotation

GitHub Actions / Unit Tests

undefined: eth.NewTransactor

Check failure on line 63 in tools/ejections/cmd/main.go

View workflow job for this annotation

GitHub Actions / Linter

undefined: eth.NewTransactor (typecheck)
if err != nil {
return err
}

chainState := eth.NewChainState(tx, client)
if chainState == nil {
return errors.New("failed to create chain state")
}
subgraphApi := subgraph.NewApi(config.SubgraphEndpoint, config.SubgraphEndpoint)
subgraphClient := dataapi.NewSubgraphClient(subgraphApi, logger)

ejections, err := subgraphClient.QueryOperatorEjectionsForTimeWindow(context.Background(), int32(config.Days), config.OperatorId, config.First, config.Skip)
if err != nil {
logger.Warn("failed to fetch operator ejections", "operatorId", config.OperatorId, "error", err)
return errors.New("operator ejections not found")
}

rowConfigAutoMerge := table.RowConfig{AutoMerge: true}

operators := table.NewWriter()
operators.AppendHeader(table.Row{"Operator Address", "Quorum", "Stake %", "Timestamp", "Txn"}, rowConfigAutoMerge)
txns := table.NewWriter()
txns.AppendHeader(table.Row{"Txn", "Timestamp", "Operator Address", "Quorum", "Stake %"}, rowConfigAutoMerge)

sort.Slice(ejections, func(i, j int) bool {
return ejections[i].BlockTimestamp > ejections[j].BlockTimestamp
})

stateCache := make(map[uint64]*core.OperatorState)
ejectedOperatorIds := make(map[core.OperatorID]struct{})
for _, ejection := range ejections {
previouseBlock := ejection.BlockNumber - 1
if _, exists := stateCache[previouseBlock]; !exists {
state, err := chainState.GetOperatorState(context.Background(), uint(previouseBlock), []uint8{0, 1})
if err != nil {
return err
}
stateCache[previouseBlock] = state
}

// construct a set of ejected operator ids for later batch address lookup
opID, err := core.OperatorIDFromHex(ejection.OperatorId)
if err != nil {
return err
}
ejectedOperatorIds[opID] = struct{}{}
}

// resolve operator id to operator addresses mapping
operatorIDs := make([]core.OperatorID, 0, len(ejectedOperatorIds))
for opID := range ejectedOperatorIds {
operatorIDs = append(operatorIDs, opID)
}
operatorAddresses, err := tx.BatchOperatorIDToAddress(context.Background(), operatorIDs)
if err != nil {
return err
}
operatorIdToAddress := make(map[string]string)
for i := range operatorAddresses {
operatorIdToAddress["0x"+operatorIDs[i].Hex()] = strings.ToLower(operatorAddresses[i].Hex())
}

for _, ejection := range ejections {
state := stateCache[ejection.BlockNumber-1]
opID, err := core.OperatorIDFromHex(ejection.OperatorId)
if err != nil {
return err
}

stakePercentage := float64(0)
if stake, ok := state.Operators[ejection.Quorum][opID]; ok {
totalStake := new(big.Float).SetInt(state.Totals[ejection.Quorum].Stake)
operatorStake := new(big.Float).SetInt(stake.Stake)
stakePercentage, _ = new(big.Float).Mul(big.NewFloat(100), new(big.Float).Quo(operatorStake, totalStake)).Float64()
}

operatorAddress := operatorIdToAddress[ejection.OperatorId]
operators.AppendRow(table.Row{operatorAddress, ejection.Quorum, stakePercentage, ejection.BlockTimestamp, ejection.TransactionHash}, rowConfigAutoMerge)
txns.AppendRow(table.Row{ejection.TransactionHash, ejection.BlockTimestamp, operatorAddress, ejection.Quorum, stakePercentage}, rowConfigAutoMerge)
}

operators.SetAutoIndex(true)
operators.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, Align: text.AlignCenter},
})
operators.SetStyle(table.StyleLight)
operators.Style().Options.SeparateRows = true

txns.SetAutoIndex(true)
txns.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
{Number: 3, AutoMerge: true},
{Number: 4, Align: text.AlignCenter},
})
txns.SetStyle(table.StyleLight)
txns.Style().Options.SeparateRows = true

fmt.Println(operators.Render())
fmt.Println(txns.Render())
return nil
}
46 changes: 46 additions & 0 deletions tools/ejections/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ejections

import (
"github.com/Layr-Labs/eigenda/common"
"github.com/Layr-Labs/eigenda/common/geth"
"github.com/Layr-Labs/eigenda/tools/ejections/flags"
"github.com/urfave/cli"
)

type Config struct {
LoggerConfig common.LoggerConfig
Days int
OperatorId string
SubgraphEndpoint string
First uint
Skip uint

EthClientConfig geth.EthClientConfig
BLSOperatorStateRetrieverAddr string
EigenDAServiceManagerAddr string
}

func ReadConfig(ctx *cli.Context) *Config {
return &Config{
Days: ctx.Int(flags.DaysFlag.Name),
OperatorId: ctx.String(flags.OperatorIdFlag.Name),
SubgraphEndpoint: ctx.String(flags.SubgraphEndpointFlag.Name),
First: ctx.Uint(flags.FirstFlag.Name),
Skip: ctx.Uint(flags.SkipFlag.Name),
EthClientConfig: geth.ReadEthClientConfig(ctx),
BLSOperatorStateRetrieverAddr: ctx.GlobalString(flags.BlsOperatorStateRetrieverFlag.Name),
EigenDAServiceManagerAddr: ctx.GlobalString(flags.EigenDAServiceManagerFlag.Name),
}
}

func NewConfig(ctx *cli.Context) (*Config, error) {
loggerConfig, err := common.ReadLoggerCLIConfig(ctx, flags.FlagPrefix)
if err != nil {
return nil, err
}

config := ReadConfig(ctx)
config.LoggerConfig = *loggerConfig

return config, nil
}
Loading

0 comments on commit 3fc378b

Please sign in to comment.