Skip to content

Commit

Permalink
feat: add leaf format forking logic for ODRewardSubmissions
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmcgary committed Jan 9, 2025
1 parent fa1b3fc commit 4b78c79
Show file tree
Hide file tree
Showing 15 changed files with 204 additions and 110 deletions.
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-13",
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: 3118055,
}, nil
case Chain_Holesky:
return ModelForkMap{
ModelFork_Austin: 3118055,
}, nil
case Chain_Mainnet:
return ModelForkMap{
ModelFork_Austin: 0, // doesnt apply to mainnet
}, 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
34 changes: 33 additions & 1 deletion pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,43 @@ func (oas *OperatorAVSSplitModel) GenerateStateRoot(blockNumber uint64) ([]byte,
return fullTree.Root(), nil
}

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] {
// 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 {
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),
zap.Uint64("oldOperatorAVSSplitBips", split.OldOperatorAVSSplitBips),
zap.Uint64("newOperatorAVSSplitBips", split.NewOperatorAVSSplitBips),
)
continue
}
inputs = append(inputs, &base.MerkleTreeInput{
SlotID: slotID,
Value: []byte(value),
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,14 @@ 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 {
if odrs.globalConfig.ChainIsOneOf(config.Chain_Holesky, config.Chain_Preprod) && blockNumber < 3118055 {
// 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))
}
return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%016x_%016x", rewardHash, strategyIndex, operatorIndex))
}

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

for _, rewardSubmission := range rewardSubmissions {
slotId := NewSlotID(rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex, rewardSubmission.OperatorIndex)
slotId := odrs.NewSlotID(log.BlockNumber, rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex, rewardSubmission.OperatorIndex)

_, ok := odrs.stateAccumulator[log.BlockNumber][slotId]
if ok {
Expand Down Expand Up @@ -313,24 +317,58 @@ 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] {
// 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 := odrs.NewSlotID(submission.BlockNumber, submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex, submission.OperatorIndex)

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
32 changes: 31 additions & 1 deletion pkg/eigenState/operatorPISplits/operatorPISplits.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,41 @@ func (ops *OperatorPISplitModel) GenerateStateRoot(blockNumber uint64) ([]byte,
return fullTree.Root(), nil
}

func (ops *OperatorPISplitModel) formatMerkleLeafValue(
blockNumber uint64,
operator string,
activatedAt *time.Time,
oldOperatorPISplitBips uint64,
newOperatorPISplitBips uint64,
) (string, error) {
modelForks, err := ops.globalConfig.GetModelForks()
if err != nil {
return "", err
}
if ops.globalConfig.ChainIsOneOf(config.Chain_Holesky, config.Chain_Preprod) && blockNumber < modelForks[config.ModelFork_Austin] {
// This format was used on preprod and testnet for rewards-v2 before launching to mainnet
return fmt.Sprintf("%s_%d_%d_%d", operator, activatedAt.Unix(), oldOperatorPISplitBips, newOperatorPISplitBips), nil
}

return fmt.Sprintf("%s_%016x_%016x_%016x", operator, activatedAt.Unix(), oldOperatorPISplitBips, newOperatorPISplitBips), nil
}

func (ops *OperatorPISplitModel) sortValuesForMerkleTree(splits []*OperatorPISplit) []*base.MerkleTreeInput {
inputs := make([]*base.MerkleTreeInput, 0)
for _, split := range splits {
slotID := base.NewSlotID(split.TransactionHash, split.LogIndex)
value := fmt.Sprintf("%s_%016x_%016x_%016x", split.Operator, split.ActivatedAt.Unix(), split.OldOperatorPISplitBips, split.NewOperatorPISplitBips)
value, err := ops.formatMerkleLeafValue(split.BlockNumber, split.Operator, split.ActivatedAt, split.OldOperatorPISplitBips, split.NewOperatorPISplitBips)
if err != nil {
ops.logger.Sugar().Errorw("Failed to format merkle leaf value",
zap.Error(err),
zap.Uint64("blockNumber", split.BlockNumber),
zap.String("operator", split.Operator),
zap.Time("activatedAt", *split.ActivatedAt),
zap.Uint64("oldOperatorPISplitBips", split.OldOperatorPISplitBips),
zap.Uint64("newOperatorPISplitBips", split.NewOperatorPISplitBips),
)
continue
}
inputs = append(inputs, &base.MerkleTreeInput{
SlotID: slotID,
Value: []byte(value),
Expand Down

This file was deleted.

2 changes: 0 additions & 2 deletions pkg/postgres/migrations/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import (
_202412091100_fixOperatorPiSplitsFields "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412091100_fixOperatorPiSplitsFields"
_202501061029_addDescription "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501061029_addDescription"
_202501061422_defaultOperatorSplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501061422_defaultOperatorSplits"
_202501061613_reindexTestnetForStaterootChange "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501061613_reindexTestnetForStaterootChange"
_202501071401_defaultOperatorSplitSnapshots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501071401_defaultOperatorSplitSnapshots"

"go.uber.org/zap"
Expand Down Expand Up @@ -132,7 +131,6 @@ func (m *Migrator) MigrateAll() error {
&_202411221331_operatorPISplitSnapshots.Migration{},
&_202412091100_fixOperatorPiSplitsFields.Migration{},
&_202501061029_addDescription.Migration{},
&_202501061613_reindexTestnetForStaterootChange.Migration{},
&_202501061422_defaultOperatorSplits.Migration{},
&_202501071401_defaultOperatorSplitSnapshots.Migration{},
}
Expand Down
Loading

0 comments on commit 4b78c79

Please sign in to comment.