Skip to content
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

feat: add leaf format forking logic for ODRewardSubmissions #178

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 61 additions & 25 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"errors"
"fmt"
"slices"
"strconv"
"strings"
"time"
Expand All @@ -28,15 +29,18 @@ const (
Chain_Holesky Chain = "holesky"
Chain_Preprod Chain = "preprod"

Fork_Nile ForkName = "nile"
Fork_Amazon ForkName = "amazon"
Fork_Panama ForkName = "panama"
Fork_Arno ForkName = "arno"
Fork_Trinity ForkName = "trinity"

ENV_PREFIX = "SIDECAR"
)

// Rewards forks named after rivers
const (
RewardsFork_Nile ForkName = "nile"
RewardsFork_Amazon ForkName = "amazon"
RewardsFork_Panama ForkName = "panama"
RewardsFork_Arno ForkName = "arno"
RewardsFork_Trinity ForkName = "trinity"
)

func normalizeFlagName(name string) string {
return strings.ReplaceAll(name, "-", "_")
}
Expand Down Expand Up @@ -188,6 +192,10 @@ type ContractAddresses struct {
AvsDirectory string
}

func (c *Config) ChainIsOneOf(chains ...Chain) bool {
return slices.Contains(chains, c.Chain)
}

func (c *Config) GetContractsMapForChain() *ContractAddresses {
if c.Chain == Chain_Preprod {
return &ContractAddresses{
Expand Down Expand Up @@ -249,34 +257,62 @@ func (c *Config) GetGenesisBlockNumber() uint64 {

type ForkMap map[ForkName]string

func (c *Config) GetForkDates() (ForkMap, error) {
func (c *Config) GetRewardsSqlForkDates() (ForkMap, error) {
switch c.Chain {
case Chain_Preprod:
return ForkMap{
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on preprod as we backfilled
Fork_Nile: "2024-08-14", // Last calculation end timestamp was 8-13: https://holesky.etherscan.io/tx/0xb5a6855e88c79312b7c0e1c9f59ae9890b97f157ea27e69e4f0fadada4712b64#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-13",
Fork_Trinity: "2025-01-09",
RewardsFork_Amazon: "1970-01-01", // Amazon hard fork was never on preprod as we backfilled
RewardsFork_Nile: "2024-08-14", // Last calculation end timestamp was 8-13: https://holesky.etherscan.io/tx/0xb5a6855e88c79312b7c0e1c9f59ae9890b97f157ea27e69e4f0fadada4712b64#eventlog
RewardsFork_Panama: "2024-10-01",
RewardsFork_Arno: "2024-12-11",
RewardsFork_Trinity: "2025-01-09",
}, nil
case Chain_Holesky:
return ForkMap{
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on testnet as we backfilled
Fork_Nile: "2024-08-13", // Last calculation end timestamp was 8-12: https://holesky.etherscan.io/tx/0x5fc81b5ed2a78b017ef313c181d8627737a97fef87eee85acedbe39fc8708c56#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-13",
Fork_Trinity: "2025-01-09",
RewardsFork_Amazon: "1970-01-01", // Amazon hard fork was never on testnet as we backfilled
RewardsFork_Nile: "2024-08-13", // Last calculation end timestamp was 8-12: https://holesky.etherscan.io/tx/0x5fc81b5ed2a78b017ef313c181d8627737a97fef87eee85acedbe39fc8708c56#eventlog
RewardsFork_Panama: "2024-10-01",
RewardsFork_Arno: "2024-12-13",
RewardsFork_Trinity: "2025-01-09",
}, nil
case Chain_Mainnet:
return ForkMap{
Fork_Amazon: "2024-08-02", // Last calculation end timestamp was 8-01: https://etherscan.io/tx/0x2aff6f7b0132092c05c8f6f41a5e5eeeb208aa0d95ebcc9022d7823e343dd012#eventlog
Fork_Nile: "2024-08-12", // Last calculation end timestamp was 8-11: https://etherscan.io/tx/0x922d29d93c02d189fc2332041f01a80e0007cd7a625a5663ef9d30082f7ef66f#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2025-01-21",
Fork_Trinity: "2025-01-21",
RewardsFork_Amazon: "2024-08-02", // Last calculation end timestamp was 8-01: https://etherscan.io/tx/0x2aff6f7b0132092c05c8f6f41a5e5eeeb208aa0d95ebcc9022d7823e343dd012#eventlog
RewardsFork_Nile: "2024-08-12", // Last calculation end timestamp was 8-11: https://etherscan.io/tx/0x922d29d93c02d189fc2332041f01a80e0007cd7a625a5663ef9d30082f7ef66f#eventlog
RewardsFork_Panama: "2024-10-01",
RewardsFork_Arno: "2025-01-21",
RewardsFork_Trinity: "2025-01-21",
}, nil
}
return nil, errors.New("unsupported chain")
}

type ModelForkMap map[ForkName]uint64

// Model forks, named after US capitols
const (
// ModelFork_Austin changes the formatting for merkel leaves in: ODRewardSubmissions, OperatorAVSSplits, and
// OperatorPISplits based on feedback from the rewards-v2 audit
ModelFork_Austin ForkName = "austin"
)

func (c *Config) GetModelForks() (ModelForkMap, error) {
switch c.Chain {
case Chain_Preprod:
return ModelForkMap{
ModelFork_Austin: 3113600,
}, nil
case Chain_Holesky:
return ModelForkMap{
ModelFork_Austin: 3113600,
}, nil
case Chain_Mainnet:
return ModelForkMap{
ModelFork_Austin: 0, // doesnt apply to mainnet
seanmcgary marked this conversation as resolved.
Show resolved Hide resolved
}, nil
}
return nil, errors.New("unsupported chain")

}

func (c *Config) GetEigenLayerGenesisBlockHeight() (uint64, error) {
Expand All @@ -302,17 +338,17 @@ func (c *Config) GetOperatorRestakedStrategiesStartBlock() uint64 {
}

func (c *Config) IsRewardsV2EnabledForCutoffDate(cutoffDate string) (bool, error) {
forks, err := c.GetForkDates()
forks, err := c.GetRewardsSqlForkDates()
if err != nil {
return false, err
}
cutoffDateTime, err := time.Parse(time.DateOnly, cutoffDate)
if err != nil {
return false, errors.Join(fmt.Errorf("failed to parse cutoff date %s", cutoffDate), err)
}
arnoForkDateTime, err := time.Parse(time.DateOnly, forks[Fork_Arno])
arnoForkDateTime, err := time.Parse(time.DateOnly, forks[RewardsFork_Arno])
if err != nil {
return false, errors.Join(fmt.Errorf("failed to parse Arno fork date %s", forks[Fork_Arno]), err)
return false, errors.Join(fmt.Errorf("failed to parse Arno fork date %s", forks[RewardsFork_Arno]), err)
}

return cutoffDateTime.Compare(arnoForkDateTime) >= 0, nil
Expand Down
43 changes: 39 additions & 4 deletions pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go
seanmcgary marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ func (oas *OperatorAVSSplitModel) GenerateStateRoot(blockNumber uint64) ([]byte,
return nil, err
}

inputs := oas.sortValuesForMerkleTree(inserts)
inputs, err := oas.sortValuesForMerkleTree(inserts)
if err != nil {
return nil, err
}

if len(inputs) == 0 {
return nil, nil
Expand All @@ -252,11 +255,43 @@ func (oas *OperatorAVSSplitModel) GenerateStateRoot(blockNumber uint64) ([]byte,
return fullTree.Root(), nil
}

func (oas *OperatorAVSSplitModel) sortValuesForMerkleTree(splits []*OperatorAVSSplit) []*base.MerkleTreeInput {
func (oas *OperatorAVSSplitModel) formatMerkleLeafValue(
blockNumber uint64,
operator string,
avs string,
activatedAt *time.Time,
oldOperatorAVSSplitBips uint64,
newOperatorAVSSplitBips uint64,
) (string, error) {
modelForks, err := oas.globalConfig.GetModelForks()
if err != nil {
return "", err
}
if oas.globalConfig.ChainIsOneOf(config.Chain_Holesky, config.Chain_Preprod) && blockNumber < modelForks[config.ModelFork_Austin] {
seanmcgary marked this conversation as resolved.
Show resolved Hide resolved
// This format was used on preprod and testnet for rewards-v2 before launching to mainnet
return fmt.Sprintf("%s_%s_%d_%d_%d", operator, avs, activatedAt.Unix(), oldOperatorAVSSplitBips, newOperatorAVSSplitBips), nil
}

return fmt.Sprintf("%s_%s_%016x_%016x_%016x", operator, avs, activatedAt.Unix(), oldOperatorAVSSplitBips, newOperatorAVSSplitBips), nil
}

func (oas *OperatorAVSSplitModel) sortValuesForMerkleTree(splits []*OperatorAVSSplit) ([]*base.MerkleTreeInput, error) {
inputs := make([]*base.MerkleTreeInput, 0)
for _, split := range splits {
slotID := base.NewSlotID(split.TransactionHash, split.LogIndex)
value := fmt.Sprintf("%s_%s_%016x_%016x_%016x", split.Operator, split.Avs, split.ActivatedAt.Unix(), split.OldOperatorAVSSplitBips, split.NewOperatorAVSSplitBips)
value, err := oas.formatMerkleLeafValue(split.BlockNumber, split.Operator, split.Avs, split.ActivatedAt, split.OldOperatorAVSSplitBips, split.NewOperatorAVSSplitBips)
if err != nil {
oas.logger.Sugar().Errorw("Failed to format merkle leaf value",
zap.Error(err),
zap.Uint64("blockNumber", split.BlockNumber),
zap.String("operator", split.Operator),
zap.String("avs", split.Avs),
zap.Time("activatedAt", *split.ActivatedAt),
seanmcgary marked this conversation as resolved.
Show resolved Hide resolved
zap.Uint64("oldOperatorAVSSplitBips", split.OldOperatorAVSSplitBips),
zap.Uint64("newOperatorAVSSplitBips", split.NewOperatorAVSSplitBips),
)
return nil, err
}
inputs = append(inputs, &base.MerkleTreeInput{
SlotID: slotID,
Value: []byte(value),
Expand All @@ -267,7 +302,7 @@ func (oas *OperatorAVSSplitModel) sortValuesForMerkleTree(splits []*OperatorAVSS
return strings.Compare(string(i.SlotID), string(j.SlotID))
})

return inputs
return inputs, nil
}

func (oas *OperatorAVSSplitModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ type OperatorDirectedRewardSubmission struct {
LogIndex uint64
}

func NewSlotID(transactionHash string, logIndex uint64, rewardHash string, strategyIndex uint64, operatorIndex uint64) types.SlotID {
return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%016x_%016x", rewardHash, strategyIndex, operatorIndex))
}

type OperatorDirectedRewardSubmissionsModel struct {
base.BaseEigenState
StateTransitions types.StateTransitions[[]*OperatorDirectedRewardSubmission]
Expand Down Expand Up @@ -80,6 +76,25 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GetModelName() string {
return "OperatorDirectedRewardSubmissionsModel"
}

func (odrs *OperatorDirectedRewardSubmissionsModel) NewSlotID(
blockNumber uint64,
transactionHash string,
logIndex uint64,
rewardHash string,
strategyIndex uint64,
operatorIndex uint64,
) (types.SlotID, error) {
forks, err := odrs.globalConfig.GetModelForks()
if err != nil {
return "", err
}
if odrs.globalConfig.ChainIsOneOf(config.Chain_Holesky, config.Chain_Preprod) && blockNumber < forks[config.ModelFork_Austin] {
// This format was used on preprod and testnet for rewards-v2 before launching to mainnet
return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%d_%d", rewardHash, strategyIndex, operatorIndex)), nil
}
return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%016x_%016x", rewardHash, strategyIndex, operatorIndex)), nil
}

type operatorDirectedRewardData struct {
StrategiesAndMultipliers []struct {
Strategy string `json:"strategy"`
Expand Down Expand Up @@ -178,7 +193,19 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types
}

for _, rewardSubmission := range rewardSubmissions {
slotId := NewSlotID(rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex, rewardSubmission.OperatorIndex)
slotId, err := odrs.NewSlotID(log.BlockNumber, rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex, rewardSubmission.OperatorIndex)
if err != nil {
odrs.logger.Sugar().Errorw("Failed to create slot ID",
zap.Uint64("blockNumber", log.BlockNumber),
zap.String("transactionHash", log.TransactionHash),
zap.Uint64("logIndex", log.LogIndex),
zap.String("rewardHash", rewardSubmission.RewardHash),
zap.Uint64("strategyIndex", rewardSubmission.StrategyIndex),
zap.Uint64("operatorIndex", rewardSubmission.OperatorIndex),
zap.Error(err),
)
return nil, err
}

_, ok := odrs.stateAccumulator[log.BlockNumber][slotId]
if ok {
Expand Down Expand Up @@ -313,24 +340,70 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GenerateStateRoot(blockNumbe
return fullTree.Root(), nil
}

func (odrs *OperatorDirectedRewardSubmissionsModel) formatMerkleLeafValue(
blockNumber uint64,
rewardHash string,
strategy string,
multiplier string,
operator string,
amount string,
) (string, error) {
modelForks, err := odrs.globalConfig.GetModelForks()
if err != nil {
return "", err
}

if odrs.globalConfig.ChainIsOneOf(config.Chain_Holesky, config.Chain_Preprod) && blockNumber < modelForks[config.ModelFork_Austin] {
seanmcgary marked this conversation as resolved.
Show resolved Hide resolved
// This format was used on preprod and testnet for rewards-v2 before launching to mainnet
return fmt.Sprintf("%s_%s_%s_%s_%s", rewardHash, strategy, multiplier, operator, amount), nil
}
// Following was fixed as part of the rewards-v2 audit feedback before launching on mainnet.
//
// Multiplier is a uint96 in the contracts, which translates to 24 hex characters
// Amount is a uint256 in the contracts, which translates to 64 hex characters
multiplierBig, success := new(big.Int).SetString(multiplier, 10)
if !success {
return "", fmt.Errorf("failed to parse multiplier to BigInt: %s", multiplier)
}

amountBig, success := new(big.Int).SetString(amount, 10)
if !success {
return "", fmt.Errorf("failed to parse amount to BigInt: %s", amount)
}

return fmt.Sprintf("%s_%s_%024x_%s_%064x", rewardHash, strategy, multiplierBig, operator, amountBig), nil
}

func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(submissions []*OperatorDirectedRewardSubmission) ([]*base.MerkleTreeInput, error) {
inputs := make([]*base.MerkleTreeInput, 0)
for _, submission := range submissions {
slotID := NewSlotID(submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex, submission.OperatorIndex)

multiplierBig, success := new(big.Int).SetString(submission.Multiplier, 10)
if !success {
return nil, fmt.Errorf("failed to parse multiplier to BigInt: %s", submission.Multiplier)
slotID, err := odrs.NewSlotID(submission.BlockNumber, submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex, submission.OperatorIndex)
if err != nil {
odrs.logger.Sugar().Errorw("Failed to create slot ID",
zap.Uint64("blockNumber", submission.BlockNumber),
zap.String("transactionHash", submission.TransactionHash),
zap.Uint64("logIndex", submission.LogIndex),
zap.String("rewardHash", submission.RewardHash),
zap.Uint64("strategyIndex", submission.StrategyIndex),
zap.Uint64("operatorIndex", submission.OperatorIndex),
zap.Error(err),
)
return nil, err
}

amountBig, success := new(big.Int).SetString(submission.Amount, 10)
if !success {
return nil, fmt.Errorf("failed to parse amount to BigInt: %s", submission.Amount)
value, err := odrs.formatMerkleLeafValue(submission.BlockNumber, submission.RewardHash, submission.Strategy, submission.Multiplier, submission.Operator, submission.Amount)
if err != nil {
odrs.Logger.Sugar().Errorw("Failed to format merkle leaf value",
zap.Error(err),
zap.Uint64("blockNumber", submission.BlockNumber),
zap.String("rewardHash", submission.RewardHash),
zap.String("strategy", submission.Strategy),
zap.String("multiplier", submission.Multiplier),
zap.String("operator", submission.Operator),
zap.String("amount", submission.Amount),
)
return nil, err
}

// Multiplier is a uint96 in the contracts, which translates to 24 hex characters
// Amount is a uint256 in the contracts, which translates to 64 hex characters
value := fmt.Sprintf("%s_%s_%024x_%s_%064x", submission.RewardHash, submission.Strategy, multiplierBig, submission.Operator, amountBig)
inputs = append(inputs, &base.MerkleTreeInput{
SlotID: slotID,
Value: []byte(value),
Expand Down
Loading
Loading