-
Notifications
You must be signed in to change notification settings - Fork 199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ejector #493
Ejector #493
Changes from 9 commits
07c85a0
f52d82e
4cef860
8daf921
f2b9bce
550cd7f
2eeb406
fe6688d
bd13a5f
2c2df55
3b6176b
514c240
a28b6f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package dataapi | ||
|
||
import ( | ||
"context" | ||
"sort" | ||
"sync" | ||
|
||
"github.com/Layr-Labs/eigenda/core" | ||
"github.com/Layr-Labs/eigensdk-go/logging" | ||
) | ||
|
||
// The caller should ensure "stakeShare" is in range (0, 1]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
so that it gets registered by godoc |
||
func stakeShareToSLA(stakeShare float64) float64 { | ||
switch { | ||
case stakeShare > 0.1: | ||
return 0.975 | ||
case stakeShare > 0.05: | ||
return 0.95 | ||
default: | ||
return 0.9 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update to new SLA |
||
} | ||
} | ||
|
||
// operatorPerfScore scores an operator based on its stake share and nonsigning rate. The | ||
// performance score will be in range [0, 1], with higher score indicating better performance. | ||
func operatorPerfScore(stakeShare float64, nonsigningRate float64) float64 { | ||
if nonsigningRate == 0 { | ||
return 1.0 | ||
} | ||
sla := stakeShareToSLA(stakeShare) | ||
perf := (1 - sla) / nonsigningRate | ||
return perf / (1.0 + perf) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit uncertain about whether this scoring will give us the right ordering across SLA classes. Interested to know if there was a rationale for this particular scoring. I'll put some further thought into it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The breakdown will be:
|
||
} | ||
|
||
func computePerfScore(metric *OperatorNonsigningPercentageMetrics) float64 { | ||
return operatorPerfScore(metric.StakePercentage, metric.Percentage/100.0) | ||
} | ||
|
||
type ejector struct { | ||
logger logging.Logger | ||
transactor core.Transactor | ||
metrics *Metrics | ||
|
||
// For serializing the ejection requests. | ||
mu sync.Mutex | ||
} | ||
|
||
func newEjector(logger logging.Logger, tx core.Transactor, metrics *Metrics) *ejector { | ||
return &ejector{ | ||
logger: logger.With("component", "Ejector"), | ||
transactor: tx, | ||
metrics: metrics, | ||
} | ||
} | ||
|
||
func (e *ejector) eject(ctx context.Context, nonsigningRate *OperatorsNonsigningPercentage, mode string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it'll be used after fixing the TODO to update metrics There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually this isn't needed even after fixing TODO -- removed it! |
||
e.mu.Lock() | ||
defer e.mu.Unlock() | ||
|
||
nonsigners := make([]*OperatorNonsigningPercentageMetrics, 0) | ||
for _, metric := range nonsigningRate.Data { | ||
// Collect only the nonsigners who violate the SLA. | ||
if metric.Percentage/100.0 > 1-stakeShareToSLA(metric.StakePercentage) { | ||
nonsigners = append(nonsigners, metric) | ||
} | ||
} | ||
|
||
// Rank the operators for each quorum by the operator performance score. | ||
// The operators with lower perf score will get ejected with priority in case of | ||
// rate limiting. | ||
sort.Slice(nonsigners, func(i, j int) bool { | ||
if nonsigners[i].QuorumId == nonsigners[j].QuorumId { | ||
if computePerfScore(nonsigners[i]) == computePerfScore(nonsigners[j]) { | ||
return float64(nonsigners[i].TotalUnsignedBatches)*nonsigners[i].StakePercentage > float64(nonsigners[j].TotalUnsignedBatches)*nonsigners[j].StakePercentage | ||
} | ||
return computePerfScore(nonsigners[i]) < computePerfScore(nonsigners[j]) | ||
} | ||
return nonsigners[i].QuorumId < nonsigners[j].QuorumId | ||
}) | ||
|
||
operatorsByQuorum, err := e.convertOperators(nonsigners) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
receipt, err := e.transactor.EjectOperators(ctx, operatorsByQuorum) | ||
if err != nil { | ||
e.logger.Error("Ejection transaction failed", "err", err) | ||
return err | ||
} | ||
e.logger.Info("Ejection transaction succeeded", "receipt", receipt) | ||
|
||
e.metrics.UpdateEjectionGasUsed(receipt.GasUsed) | ||
|
||
// TODO: get the txn response and update the metrics. | ||
|
||
return nil | ||
} | ||
|
||
func (e *ejector) convertOperators(nonsigners []*OperatorNonsigningPercentageMetrics) ([][]core.OperatorID, error) { | ||
var maxQuorumId uint8 | ||
for _, metric := range nonsigners { | ||
if metric.QuorumId > maxQuorumId { | ||
maxQuorumId = metric.QuorumId | ||
} | ||
} | ||
|
||
numOperatorByQuorum := make(map[uint8]int) | ||
stakeShareByQuorum := make(map[uint8]float64) | ||
|
||
result := make([][]core.OperatorID, maxQuorumId+1) | ||
for _, metric := range nonsigners { | ||
id, err := core.OperatorIDFromHex(metric.OperatorId) | ||
if err != nil { | ||
return nil, err | ||
} | ||
result[metric.QuorumId] = append(result[metric.QuorumId], id) | ||
numOperatorByQuorum[metric.QuorumId]++ | ||
stakeShareByQuorum[metric.QuorumId] += metric.StakePercentage | ||
} | ||
e.metrics.UpdateRequestedOperatorMetric(numOperatorByQuorum, stakeShareByQuorum) | ||
|
||
return result, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a test for ejector