From ead084d9068bc2d87da70e3aa2125118245f1320 Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Wed, 13 Nov 2024 11:41:16 -0600 Subject: [PATCH 01/21] Release: Rewards v2 From 17423892e69a9738e92a269e9a62983b170bc4bc Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 12 Nov 2024 18:31:57 -0500 Subject: [PATCH 02/21] Initial changes --- pkg/eigenState/eigenState.go | 23 +- .../operatorDirectedRewardSubmissions.go | 400 ++++++++++++++++++ 2 files changed, 414 insertions(+), 9 deletions(-) create mode 100644 pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go diff --git a/pkg/eigenState/eigenState.go b/pkg/eigenState/eigenState.go index b95f4ed..07ab2f9 100644 --- a/pkg/eigenState/eigenState.go +++ b/pkg/eigenState/eigenState.go @@ -1,15 +1,16 @@ package eigenState import ( - "github.com/Layr-Labs/sidecar/internal/config" - "github.com/Layr-Labs/sidecar/pkg/eigenState/avsOperators" - "github.com/Layr-Labs/sidecar/pkg/eigenState/disabledDistributionRoots" - "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorShares" - "github.com/Layr-Labs/sidecar/pkg/eigenState/rewardSubmissions" - "github.com/Layr-Labs/sidecar/pkg/eigenState/stakerDelegations" - "github.com/Layr-Labs/sidecar/pkg/eigenState/stakerShares" - "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" - "github.com/Layr-Labs/sidecar/pkg/eigenState/submittedDistributionRoots" + "github.com/Layr-Labs/go-sidecar/internal/config" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/avsOperators" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/disabledDistributionRoots" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/operatorDirectedRewardSubmissions" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/operatorShares" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/rewardSubmissions" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stakerDelegations" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stakerShares" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/submittedDistributionRoots" "go.uber.org/zap" "gorm.io/gorm" ) @@ -48,5 +49,9 @@ func LoadEigenStateModels( l.Sugar().Errorw("Failed to create DisabledDistributionRootsModel", zap.Error(err)) return err } + if _, err := operatorDirectedRewardSubmissions.NewOperatorDirectedRewardSubmissionsModel(sm, grm, l, cfg); err != nil { + l.Sugar().Errorw("Failed to create OperatorDirectedRewardSubmissionsModel", zap.Error(err)) + return err + } return nil } diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go new file mode 100644 index 0000000..9df3216 --- /dev/null +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -0,0 +1,400 @@ +package operatorDirectedRewardSubmissions + +import ( + "database/sql" + "encoding/json" + "fmt" + "slices" + "sort" + "strings" + "time" + + "github.com/Layr-Labs/go-sidecar/pkg/storage" + "github.com/Layr-Labs/go-sidecar/pkg/types/numbers" + "github.com/Layr-Labs/go-sidecar/pkg/utils" + + "github.com/Layr-Labs/go-sidecar/internal/config" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/base" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/go-sidecar/pkg/eigenState/types" + "go.uber.org/zap" + "golang.org/x/xerrors" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type OperatorDirectedRewardSubmission struct { + Avs string + RewardHash string + Token string + Operator string + OperatorIndex uint64 + Amount string + Strategy string + StrategyIndex uint64 + Multiplier string + StartTimestamp *time.Time + EndTimestamp *time.Time + Duration uint64 + BlockNumber uint64 +} + +type RewardSubmissionDiff struct { + OperatorDirectedRewardSubmission *OperatorDirectedRewardSubmission + IsNew bool + IsNoLongerActive bool +} + +type OperatorDirectedRewardSubmissions struct { + Submissions []*OperatorDirectedRewardSubmission +} + +func NewSlotID(rewardHash string, strategy string, operator string) types.SlotID { + return types.SlotID(fmt.Sprintf("%s_%s_%s", rewardHash, strategy, operator)) +} + +type OperatorDirectedRewardSubmissionsModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[OperatorDirectedRewardSubmission] + DB *gorm.DB + Network config.Network + Environment config.Environment + logger *zap.Logger + globalConfig *config.Config + + // Accumulates state changes for SlotIds, grouped by block number + stateAccumulator map[uint64]map[types.SlotID]*OperatorDirectedRewardSubmission +} + +func NewOperatorDirectedRewardSubmissionsModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) (*OperatorDirectedRewardSubmissionsModel, error) { + model := &OperatorDirectedRewardSubmissionsModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + DB: grm, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorDirectedRewardSubmission), + } + + esm.RegisterState(model, 7) + return model, nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) GetModelName() string { + return "OperatorDirectedRewardSubmissionsModel" +} + +type genericRewardPaymentData struct { + Token string + Amount json.Number + StartTimestamp uint64 + Duration uint64 + StrategiesAndMultipliers []struct { + Strategy string + Multiplier json.Number + } `json:"strategiesAndMultipliers"` +} + +type rewardSubmissionOutputData struct { + RewardsSubmission *genericRewardPaymentData `json:"rewardsSubmission"` + RangePayment *genericRewardPaymentData `json:"rangePayment"` +} + +func parseRewardSubmissionOutputData(outputDataStr string) (*rewardSubmissionOutputData, error) { + outputData := &rewardSubmissionOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + + return outputData, err +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreatedEvent(log *storage.TransactionLog) (*OperatorDirectedRewardSubmissions, error) { + arguments, err := odrs.ParseLogArguments(log) + if err != nil { + return nil, err + } + + outputData, err := parseRewardSubmissionOutputData(log.OutputData) + if err != nil { + return nil, err + } + + var actualOuputData *genericRewardPaymentData + if log.EventName == "RangePaymentCreated" || log.EventName == "RangePaymentForAllCreated" { + actualOuputData = outputData.RangePayment + } else { + actualOuputData = outputData.RewardsSubmission + } + + rewardSubmissions := make([]*OperatorDirectedRewardSubmission, 0) + + for _, strategyAndMultiplier := range actualOuputData.StrategiesAndMultipliers { + startTimestamp := time.Unix(int64(actualOuputData.StartTimestamp), 0) + endTimestamp := startTimestamp.Add(time.Duration(actualOuputData.Duration) * time.Second) + + amountBig, success := numbers.NewBig257().SetString(actualOuputData.Amount.String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to parse amount to Big257: %s", actualOuputData.Amount.String()) + } + + multiplierBig, success := numbers.NewBig257().SetString(strategyAndMultiplier.Multiplier.String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to parse multiplier to Big257: %s", actualOuputData.Amount.String()) + } + + var rewardType string + if log.EventName == "RewardsSubmissionForAllCreated" || log.EventName == "RangePaymentForAllCreated" { + rewardType = "all_stakers" + } else if log.EventName == "RangePaymentCreated" || log.EventName == "AVSRewardsSubmissionCreated" { + rewardType = "avs" + } else if log.EventName == "RewardsSubmissionForAllEarnersCreated" { + rewardType = "all_earners" + } else { + return nil, xerrors.Errorf("Unknown event name: %s", log.EventName) + } + + rewardSubmission := &OperatorDirectedRewardSubmission{ + Avs: strings.ToLower(arguments[0].Value.(string)), + RewardHash: strings.ToLower(arguments[2].Value.(string)), + Token: strings.ToLower(actualOuputData.Token), + Amount: amountBig.String(), + Strategy: strategyAndMultiplier.Strategy, + Multiplier: multiplierBig.String(), + StartTimestamp: &startTimestamp, + EndTimestamp: &endTimestamp, + Duration: actualOuputData.Duration, + BlockNumber: log.BlockNumber, + RewardType: rewardType, + } + rewardSubmissions = append(rewardSubmissions, rewardSubmission) + } + + return &OperatorDirectedRewardSubmissions{Submissions: rewardSubmissions}, nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types.StateTransitions[OperatorDirectedRewardSubmissions], []uint64) { + stateChanges := make(types.StateTransitions[OperatorDirectedRewardSubmissions]) + + stateChanges[0] = func(log *storage.TransactionLog) (*OperatorDirectedRewardSubmissions, error) { + rewardSubmissions, err := odrs.handleRewardSubmissionCreatedEvent(log) + if err != nil { + return nil, err + } + + for _, rewardSubmission := range rewardSubmissions.Submissions { + slotId := NewSlotID(rewardSubmission.RewardHash, rewardSubmission.Strategy) + + _, ok := odrs.stateAccumulator[log.BlockNumber][slotId] + if ok { + err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) + odrs.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) + return nil, err + } + + odrs.stateAccumulator[log.BlockNumber][slotId] = rewardSubmission + } + + return rewardSubmissions, nil + } + + // Create an ordered list of block numbers + blockNumbers := make([]uint64, 0) + for blockNumber := range stateChanges { + blockNumbers = append(blockNumbers, blockNumber) + } + sort.Slice(blockNumbers, func(i, j int) bool { + return blockNumbers[i] < blockNumbers[j] + }) + slices.Reverse(blockNumbers) + + return stateChanges, blockNumbers +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) getContractAddressesForEnvironment() map[string][]string { + contracts := odrs.globalConfig.GetContractsMapForChain() + return map[string][]string{ + contracts.RewardsCoordinator: { + "OperatorDirectedAVSRewardsSubmissionCreated", + }, + } +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := odrs.getContractAddressesForEnvironment() + return odrs.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) SetupStateForBlock(blockNumber uint64) error { + odrs.stateAccumulator[blockNumber] = make(map[types.SlotID]*OperatorDirectedRewardSubmission) + return nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) CleanupProcessedStateForBlock(blockNumber uint64) error { + delete(odrs.stateAccumulator, blockNumber) + return nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) HandleStateChange(log *storage.TransactionLog) (interface{}, error) { + stateChanges, sortedBlockNumbers := odrs.GetStateTransitions() + + for _, blockNumber := range sortedBlockNumbers { + if log.BlockNumber >= blockNumber { + odrs.logger.Sugar().Debugw("Handling state change", zap.Uint64("blockNumber", log.BlockNumber)) + + change, err := stateChanges[blockNumber](log) + if err != nil { + return nil, err + } + if change == nil { + return nil, nil + } + return change, nil + } + } + return nil, nil +} + +// prepareState prepares the state for commit by adding the new state to the existing state. +func (odrs *OperatorDirectedRewardSubmissionsModel) prepareState(blockNumber uint64) ([]*RewardSubmissionDiff, []*RewardSubmissionDiff, error) { + accumulatedState, ok := odrs.stateAccumulator[blockNumber] + if !ok { + err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) + odrs.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, nil, err + } + + currentBlock := &storage.Block{} + err := odrs.DB.Where("number = ?", blockNumber).First(currentBlock).Error + if err != nil { + odrs.logger.Sugar().Errorw("Failed to fetch block", zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, nil, err + } + + inserts := make([]*RewardSubmissionDiff, 0) + for _, change := range accumulatedState { + if change == nil { + continue + } + + inserts = append(inserts, &RewardSubmissionDiff{ + OperatorDirectedRewardSubmission: change, + IsNew: true, + }) + } + + // find all the records that are no longer active + noLongerActiveSubmissions := make([]*OperatorDirectedRewardSubmission, 0) + query := ` + select + * + from reward_submissions + where + block_number = @previousBlock + and end_timestamp <= @blockTime + ` + res := odrs.DB. + Model(&OperatorDirectedRewardSubmission{}). + Raw(query, + sql.Named("previousBlock", blockNumber-1), + sql.Named("blockTime", currentBlock.BlockTime), + ). + Find(&noLongerActiveSubmissions) + + if res.Error != nil { + odrs.logger.Sugar().Errorw("Failed to fetch no longer active submissions", zap.Error(res.Error)) + return nil, nil, res.Error + } + + deletes := make([]*RewardSubmissionDiff, 0) + for _, submission := range noLongerActiveSubmissions { + deletes = append(deletes, &RewardSubmissionDiff{ + OperatorDirectedRewardSubmission: submission, + IsNoLongerActive: true, + }) + } + return inserts, deletes, nil +} + +// CommitFinalState commits the final state for the given block number. +func (odrs *OperatorDirectedRewardSubmissionsModel) CommitFinalState(blockNumber uint64) error { + recordsToInsert, _, err := odrs.prepareState(blockNumber) + if err != nil { + return err + } + + if len(recordsToInsert) > 0 { + for _, record := range recordsToInsert { + res := odrs.DB.Model(&OperatorDirectedRewardSubmission{}).Clauses(clause.Returning{}).Create(&record.OperatorDirectedRewardSubmission) + if res.Error != nil { + odrs.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) + return res.Error + } + } + } + return nil +} + +// GenerateStateRoot generates the state root for the given block number using the results of the state changes. +func (odrs *OperatorDirectedRewardSubmissionsModel) GenerateStateRoot(blockNumber uint64) (types.StateRoot, error) { + inserts, deletes, err := odrs.prepareState(blockNumber) + if err != nil { + return "", err + } + + combinedResults := make([]*RewardSubmissionDiff, 0) + combinedResults = append(combinedResults, inserts...) + combinedResults = append(combinedResults, deletes...) + + inputs := odrs.sortValuesForMerkleTree(combinedResults) + + if len(inputs) == 0 { + return "", nil + } + + fullTree, err := odrs.MerkleizeState(blockNumber, inputs) + if err != nil { + odrs.logger.Sugar().Errorw("Failed to create merkle tree", + zap.Error(err), + zap.Uint64("blockNumber", blockNumber), + zap.Any("inputs", inputs), + ) + return "", err + } + return types.StateRoot(utils.ConvertBytesToString(fullTree.Root())), nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(submissions []*RewardSubmissionDiff) []*base.MerkleTreeInput { + inputs := make([]*base.MerkleTreeInput, 0) + for _, submission := range submissions { + slotID := NewSlotID(submission.OperatorDirectedRewardSubmission.RewardHash, submission.OperatorDirectedRewardSubmission.Strategy, submission.OperatorDirectedRewardSubmission.Operator) + value := "added" + if submission.IsNoLongerActive { + value = "removed" + } + inputs = append(inputs, &base.MerkleTreeInput{ + SlotID: slotID, + Value: []byte(value), + }) + } + + slices.SortFunc(inputs, func(i, j *base.MerkleTreeInput) int { + return strings.Compare(string(i.SlotID), string(j.SlotID)) + }) + + return inputs +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return odrs.BaseEigenState.DeleteState("operator_directed_reward_submissions", startBlockNumber, endBlockNumber, odrs.DB) +} From fe2219c7de1043a18e211be20a1e6b435ed898d6 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 12 Nov 2024 19:18:19 -0500 Subject: [PATCH 03/21] small fixes --- .../operatorDirectedRewardSubmissions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index 9df3216..d136e00 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -193,7 +193,7 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types } for _, rewardSubmission := range rewardSubmissions.Submissions { - slotId := NewSlotID(rewardSubmission.RewardHash, rewardSubmission.Strategy) + slotId := NewSlotID(rewardSubmission.RewardHash, rewardSubmission.Strategy, rewardSubmission.Operator) _, ok := odrs.stateAccumulator[log.BlockNumber][slotId] if ok { From ff5306d5819c4980ba76aeb7bb4ef89d19ec7925 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Fri, 15 Nov 2024 15:14:36 -0500 Subject: [PATCH 04/21] cleanup --- pkg/eigenState/eigenState.go | 20 +- .../operatorDirectedRewardSubmissions.go | 192 +++++++----------- 2 files changed, 79 insertions(+), 133 deletions(-) diff --git a/pkg/eigenState/eigenState.go b/pkg/eigenState/eigenState.go index 07ab2f9..db33994 100644 --- a/pkg/eigenState/eigenState.go +++ b/pkg/eigenState/eigenState.go @@ -1,16 +1,16 @@ package eigenState import ( - "github.com/Layr-Labs/go-sidecar/internal/config" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/avsOperators" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/disabledDistributionRoots" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/operatorDirectedRewardSubmissions" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/operatorShares" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/rewardSubmissions" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stakerDelegations" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stakerShares" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stateManager" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/submittedDistributionRoots" + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/pkg/eigenState/avsOperators" + "github.com/Layr-Labs/sidecar/pkg/eigenState/disabledDistributionRoots" + "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorDirectedRewardSubmissions" + "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorShares" + "github.com/Layr-Labs/sidecar/pkg/eigenState/rewardSubmissions" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stakerDelegations" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stakerShares" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/sidecar/pkg/eigenState/submittedDistributionRoots" "go.uber.org/zap" "gorm.io/gorm" ) diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index d136e00..4f222dd 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -1,7 +1,6 @@ package operatorDirectedRewardSubmissions import ( - "database/sql" "encoding/json" "fmt" "slices" @@ -9,14 +8,14 @@ import ( "strings" "time" - "github.com/Layr-Labs/go-sidecar/pkg/storage" - "github.com/Layr-Labs/go-sidecar/pkg/types/numbers" - "github.com/Layr-Labs/go-sidecar/pkg/utils" + "github.com/Layr-Labs/sidecar/pkg/storage" + "github.com/Layr-Labs/sidecar/pkg/types/numbers" + "github.com/Layr-Labs/sidecar/pkg/utils" - "github.com/Layr-Labs/go-sidecar/internal/config" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/base" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/stateManager" - "github.com/Layr-Labs/go-sidecar/pkg/eigenState/types" + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/pkg/eigenState/base" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/sidecar/pkg/eigenState/types" "go.uber.org/zap" "golang.org/x/xerrors" "gorm.io/gorm" @@ -24,38 +23,29 @@ import ( ) type OperatorDirectedRewardSubmission struct { - Avs string - RewardHash string - Token string - Operator string - OperatorIndex uint64 - Amount string - Strategy string - StrategyIndex uint64 - Multiplier string - StartTimestamp *time.Time - EndTimestamp *time.Time - Duration uint64 - BlockNumber uint64 + Avs string + RewardHash string + Token string + Amount string + Strategy string + StrategyIndex uint64 + Multiplier string + StartTimestamp *time.Time + EndTimestamp *time.Time + Duration uint64 + BlockNumber uint64 + RewardType string // avs, all_stakers, all_earners + TransactionHash string + LogIndex uint64 } -type RewardSubmissionDiff struct { - OperatorDirectedRewardSubmission *OperatorDirectedRewardSubmission - IsNew bool - IsNoLongerActive bool -} - -type OperatorDirectedRewardSubmissions struct { - Submissions []*OperatorDirectedRewardSubmission -} - -func NewSlotID(rewardHash string, strategy string, operator string) types.SlotID { - return types.SlotID(fmt.Sprintf("%s_%s_%s", rewardHash, strategy, operator)) +func NewSlotID(transactionHash string, logIndex uint64, rewardHash string, strategyIndex uint64) types.SlotID { + return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%d", rewardHash, strategyIndex)) } type OperatorDirectedRewardSubmissionsModel struct { base.BaseEigenState - StateTransitions types.StateTransitions[OperatorDirectedRewardSubmission] + StateTransitions types.StateTransitions[[]*OperatorDirectedRewardSubmission] DB *gorm.DB Network config.Network Environment config.Environment @@ -82,7 +72,7 @@ func NewOperatorDirectedRewardSubmissionsModel( stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorDirectedRewardSubmission), } - esm.RegisterState(model, 7) + esm.RegisterState(model, 5) return model, nil } @@ -101,13 +91,13 @@ type genericRewardPaymentData struct { } `json:"strategiesAndMultipliers"` } -type rewardSubmissionOutputData struct { +type operatorDirectedRewardSubmissionOutputData struct { RewardsSubmission *genericRewardPaymentData `json:"rewardsSubmission"` RangePayment *genericRewardPaymentData `json:"rangePayment"` } -func parseRewardSubmissionOutputData(outputDataStr string) (*rewardSubmissionOutputData, error) { - outputData := &rewardSubmissionOutputData{} +func parseRewardSubmissionOutputData(outputDataStr string) (*operatorDirectedRewardSubmissionOutputData, error) { + outputData := &operatorDirectedRewardSubmissionOutputData{} decoder := json.NewDecoder(strings.NewReader(outputDataStr)) decoder.UseNumber() @@ -119,7 +109,7 @@ func parseRewardSubmissionOutputData(outputDataStr string) (*rewardSubmissionOut return outputData, err } -func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreatedEvent(log *storage.TransactionLog) (*OperatorDirectedRewardSubmissions, error) { +func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreatedEvent(log *storage.TransactionLog) ([]*OperatorDirectedRewardSubmission, error) { arguments, err := odrs.ParseLogArguments(log) if err != nil { return nil, err @@ -139,7 +129,7 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreate rewardSubmissions := make([]*OperatorDirectedRewardSubmission, 0) - for _, strategyAndMultiplier := range actualOuputData.StrategiesAndMultipliers { + for i, strategyAndMultiplier := range actualOuputData.StrategiesAndMultipliers { startTimestamp := time.Unix(int64(actualOuputData.StartTimestamp), 0) endTimestamp := startTimestamp.Add(time.Duration(actualOuputData.Duration) * time.Second) @@ -165,38 +155,42 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreate } rewardSubmission := &OperatorDirectedRewardSubmission{ - Avs: strings.ToLower(arguments[0].Value.(string)), - RewardHash: strings.ToLower(arguments[2].Value.(string)), - Token: strings.ToLower(actualOuputData.Token), - Amount: amountBig.String(), - Strategy: strategyAndMultiplier.Strategy, - Multiplier: multiplierBig.String(), - StartTimestamp: &startTimestamp, - EndTimestamp: &endTimestamp, - Duration: actualOuputData.Duration, - BlockNumber: log.BlockNumber, - RewardType: rewardType, + Avs: strings.ToLower(arguments[0].Value.(string)), + RewardHash: strings.ToLower(arguments[2].Value.(string)), + Token: strings.ToLower(actualOuputData.Token), + Amount: amountBig.String(), + Strategy: strategyAndMultiplier.Strategy, + Multiplier: multiplierBig.String(), + StartTimestamp: &startTimestamp, + EndTimestamp: &endTimestamp, + Duration: actualOuputData.Duration, + BlockNumber: log.BlockNumber, + RewardType: rewardType, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, + StrategyIndex: uint64(i), } rewardSubmissions = append(rewardSubmissions, rewardSubmission) } - return &OperatorDirectedRewardSubmissions{Submissions: rewardSubmissions}, nil + return rewardSubmissions, nil } -func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types.StateTransitions[OperatorDirectedRewardSubmissions], []uint64) { - stateChanges := make(types.StateTransitions[OperatorDirectedRewardSubmissions]) +func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types.StateTransitions[[]*OperatorDirectedRewardSubmission], []uint64) { + stateChanges := make(types.StateTransitions[[]*OperatorDirectedRewardSubmission]) - stateChanges[0] = func(log *storage.TransactionLog) (*OperatorDirectedRewardSubmissions, error) { + stateChanges[0] = func(log *storage.TransactionLog) ([]*OperatorDirectedRewardSubmission, error) { rewardSubmissions, err := odrs.handleRewardSubmissionCreatedEvent(log) if err != nil { return nil, err } - for _, rewardSubmission := range rewardSubmissions.Submissions { - slotId := NewSlotID(rewardSubmission.RewardHash, rewardSubmission.Strategy, rewardSubmission.Operator) + for _, rewardSubmission := range rewardSubmissions { + slotId := NewSlotID(rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex) _, ok := odrs.stateAccumulator[log.BlockNumber][slotId] if ok { + fmt.Printf("Submissions: %+v\n", odrs.stateAccumulator[log.BlockNumber]) err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) odrs.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) return nil, err @@ -225,7 +219,11 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) getContractAddressesForEnvir contracts := odrs.globalConfig.GetContractsMapForChain() return map[string][]string{ contracts.RewardsCoordinator: { - "OperatorDirectedAVSRewardsSubmissionCreated", + "RangePaymentForAllCreated", + "RewardsSubmissionForAllCreated", + "RangePaymentCreated", + "AVSRewardsSubmissionCreated", + "RewardsSubmissionForAllEarnersCreated", }, } } @@ -266,76 +264,31 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) HandleStateChange(log *stora } // prepareState prepares the state for commit by adding the new state to the existing state. -func (odrs *OperatorDirectedRewardSubmissionsModel) prepareState(blockNumber uint64) ([]*RewardSubmissionDiff, []*RewardSubmissionDiff, error) { +func (odrs *OperatorDirectedRewardSubmissionsModel) prepareState(blockNumber uint64) ([]*OperatorDirectedRewardSubmission, error) { accumulatedState, ok := odrs.stateAccumulator[blockNumber] if !ok { err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) odrs.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) - return nil, nil, err - } - - currentBlock := &storage.Block{} - err := odrs.DB.Where("number = ?", blockNumber).First(currentBlock).Error - if err != nil { - odrs.logger.Sugar().Errorw("Failed to fetch block", zap.Error(err), zap.Uint64("blockNumber", blockNumber)) - return nil, nil, err - } - - inserts := make([]*RewardSubmissionDiff, 0) - for _, change := range accumulatedState { - if change == nil { - continue - } - - inserts = append(inserts, &RewardSubmissionDiff{ - OperatorDirectedRewardSubmission: change, - IsNew: true, - }) - } - - // find all the records that are no longer active - noLongerActiveSubmissions := make([]*OperatorDirectedRewardSubmission, 0) - query := ` - select - * - from reward_submissions - where - block_number = @previousBlock - and end_timestamp <= @blockTime - ` - res := odrs.DB. - Model(&OperatorDirectedRewardSubmission{}). - Raw(query, - sql.Named("previousBlock", blockNumber-1), - sql.Named("blockTime", currentBlock.BlockTime), - ). - Find(&noLongerActiveSubmissions) - - if res.Error != nil { - odrs.logger.Sugar().Errorw("Failed to fetch no longer active submissions", zap.Error(res.Error)) - return nil, nil, res.Error + return nil, err } - deletes := make([]*RewardSubmissionDiff, 0) - for _, submission := range noLongerActiveSubmissions { - deletes = append(deletes, &RewardSubmissionDiff{ - OperatorDirectedRewardSubmission: submission, - IsNoLongerActive: true, - }) + recordsToInsert := make([]*OperatorDirectedRewardSubmission, 0) + for _, submission := range accumulatedState { + recordsToInsert = append(recordsToInsert, submission) } - return inserts, deletes, nil + return recordsToInsert, nil } // CommitFinalState commits the final state for the given block number. func (odrs *OperatorDirectedRewardSubmissionsModel) CommitFinalState(blockNumber uint64) error { - recordsToInsert, _, err := odrs.prepareState(blockNumber) + recordsToInsert, err := odrs.prepareState(blockNumber) if err != nil { return err } if len(recordsToInsert) > 0 { for _, record := range recordsToInsert { - res := odrs.DB.Model(&OperatorDirectedRewardSubmission{}).Clauses(clause.Returning{}).Create(&record.OperatorDirectedRewardSubmission) + res := odrs.DB.Model(&OperatorDirectedRewardSubmission{}).Clauses(clause.Returning{}).Create(&record) if res.Error != nil { odrs.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) return res.Error @@ -347,16 +300,12 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) CommitFinalState(blockNumber // GenerateStateRoot generates the state root for the given block number using the results of the state changes. func (odrs *OperatorDirectedRewardSubmissionsModel) GenerateStateRoot(blockNumber uint64) (types.StateRoot, error) { - inserts, deletes, err := odrs.prepareState(blockNumber) + inserts, err := odrs.prepareState(blockNumber) if err != nil { return "", err } - combinedResults := make([]*RewardSubmissionDiff, 0) - combinedResults = append(combinedResults, inserts...) - combinedResults = append(combinedResults, deletes...) - - inputs := odrs.sortValuesForMerkleTree(combinedResults) + inputs := odrs.sortValuesForMerkleTree(inserts) if len(inputs) == 0 { return "", nil @@ -374,14 +323,11 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GenerateStateRoot(blockNumbe return types.StateRoot(utils.ConvertBytesToString(fullTree.Root())), nil } -func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(submissions []*RewardSubmissionDiff) []*base.MerkleTreeInput { +func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(submissions []*OperatorDirectedRewardSubmission) []*base.MerkleTreeInput { inputs := make([]*base.MerkleTreeInput, 0) for _, submission := range submissions { - slotID := NewSlotID(submission.OperatorDirectedRewardSubmission.RewardHash, submission.OperatorDirectedRewardSubmission.Strategy, submission.OperatorDirectedRewardSubmission.Operator) + slotID := NewSlotID(submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex) value := "added" - if submission.IsNoLongerActive { - value = "removed" - } inputs = append(inputs, &base.MerkleTreeInput{ SlotID: slotID, Value: []byte(value), @@ -396,5 +342,5 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(subm } func (odrs *OperatorDirectedRewardSubmissionsModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { - return odrs.BaseEigenState.DeleteState("operator_directed_reward_submissions", startBlockNumber, endBlockNumber, odrs.DB) + return odrs.BaseEigenState.DeleteState("reward_submissions", startBlockNumber, endBlockNumber, odrs.DB) } From 828b0cc03598079883ecc7dfaaaca9b7c208d401 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Fri, 15 Nov 2024 16:07:42 -0500 Subject: [PATCH 05/21] refactoring --- .../operatorDirectedRewardSubmissions.go | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index 4f222dd..3ba0337 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -26,6 +26,8 @@ type OperatorDirectedRewardSubmission struct { Avs string RewardHash string Token string + Operator string + OperatorIndex uint64 Amount string Strategy string StrategyIndex uint64 @@ -34,13 +36,12 @@ type OperatorDirectedRewardSubmission struct { EndTimestamp *time.Time Duration uint64 BlockNumber uint64 - RewardType string // avs, all_stakers, all_earners TransactionHash string LogIndex uint64 } -func NewSlotID(transactionHash string, logIndex uint64, rewardHash string, strategyIndex uint64) types.SlotID { - return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%d", rewardHash, strategyIndex)) +func NewSlotID(transactionHash string, logIndex uint64, rewardHash string, strategyIndex uint64, operatorIndex uint64) types.SlotID { + return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%d_%d", rewardHash, strategyIndex, operatorIndex)) } type OperatorDirectedRewardSubmissionsModel struct { @@ -72,7 +73,7 @@ func NewOperatorDirectedRewardSubmissionsModel( stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorDirectedRewardSubmission), } - esm.RegisterState(model, 5) + esm.RegisterState(model, 7) return model, nil } @@ -165,7 +166,6 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreate EndTimestamp: &endTimestamp, Duration: actualOuputData.Duration, BlockNumber: log.BlockNumber, - RewardType: rewardType, TransactionHash: log.TransactionHash, LogIndex: log.LogIndex, StrategyIndex: uint64(i), @@ -186,7 +186,7 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types } for _, rewardSubmission := range rewardSubmissions { - slotId := NewSlotID(rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex) + slotId := NewSlotID(rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex, rewardSubmission.OperatorIndex) _, ok := odrs.stateAccumulator[log.BlockNumber][slotId] if ok { @@ -219,11 +219,7 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) getContractAddressesForEnvir contracts := odrs.globalConfig.GetContractsMapForChain() return map[string][]string{ contracts.RewardsCoordinator: { - "RangePaymentForAllCreated", - "RewardsSubmissionForAllCreated", - "RangePaymentCreated", - "AVSRewardsSubmissionCreated", - "RewardsSubmissionForAllEarnersCreated", + "OperatorDirectedAVSRewardsSubmissionCreated", }, } } @@ -326,7 +322,7 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GenerateStateRoot(blockNumbe func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(submissions []*OperatorDirectedRewardSubmission) []*base.MerkleTreeInput { inputs := make([]*base.MerkleTreeInput, 0) for _, submission := range submissions { - slotID := NewSlotID(submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex) + slotID := NewSlotID(submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex, submission.OperatorIndex) value := "added" inputs = append(inputs, &base.MerkleTreeInput{ SlotID: slotID, @@ -342,5 +338,5 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(subm } func (odrs *OperatorDirectedRewardSubmissionsModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { - return odrs.BaseEigenState.DeleteState("reward_submissions", startBlockNumber, endBlockNumber, odrs.DB) + return odrs.BaseEigenState.DeleteState("operator_directed_reward_submissions", startBlockNumber, endBlockNumber, odrs.DB) } From 5b20e3d043bd021a3304d0234f6eb419e5f17ec8 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Fri, 15 Nov 2024 16:08:06 -0500 Subject: [PATCH 06/21] more refactoring --- .../operatorDirectedRewardSubmissions.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index 3ba0337..aa57c5e 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -144,17 +144,6 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreate return nil, xerrors.Errorf("Failed to parse multiplier to Big257: %s", actualOuputData.Amount.String()) } - var rewardType string - if log.EventName == "RewardsSubmissionForAllCreated" || log.EventName == "RangePaymentForAllCreated" { - rewardType = "all_stakers" - } else if log.EventName == "RangePaymentCreated" || log.EventName == "AVSRewardsSubmissionCreated" { - rewardType = "avs" - } else if log.EventName == "RewardsSubmissionForAllEarnersCreated" { - rewardType = "all_earners" - } else { - return nil, xerrors.Errorf("Unknown event name: %s", log.EventName) - } - rewardSubmission := &OperatorDirectedRewardSubmission{ Avs: strings.ToLower(arguments[0].Value.(string)), RewardHash: strings.ToLower(arguments[2].Value.(string)), From ab7af905d9d5d4029738b21bda435806aedf1b5c Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Fri, 15 Nov 2024 17:26:27 -0500 Subject: [PATCH 07/21] rename --- .../operatorDirectedRewardSubmissions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index aa57c5e..5162a3e 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -110,7 +110,7 @@ func parseRewardSubmissionOutputData(outputDataStr string) (*operatorDirectedRew return outputData, err } -func (odrs *OperatorDirectedRewardSubmissionsModel) handleRewardSubmissionCreatedEvent(log *storage.TransactionLog) ([]*OperatorDirectedRewardSubmission, error) { +func (odrs *OperatorDirectedRewardSubmissionsModel) handleOperatorDirectedRewardSubmissionCreatedEvent(log *storage.TransactionLog) ([]*OperatorDirectedRewardSubmission, error) { arguments, err := odrs.ParseLogArguments(log) if err != nil { return nil, err @@ -169,7 +169,7 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types stateChanges := make(types.StateTransitions[[]*OperatorDirectedRewardSubmission]) stateChanges[0] = func(log *storage.TransactionLog) ([]*OperatorDirectedRewardSubmission, error) { - rewardSubmissions, err := odrs.handleRewardSubmissionCreatedEvent(log) + rewardSubmissions, err := odrs.handleOperatorDirectedRewardSubmissionCreatedEvent(log) if err != nil { return nil, err } From 4185804949c68b3d993d51b3a8a5325f0b0c4d3d Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Fri, 15 Nov 2024 18:38:42 -0500 Subject: [PATCH 08/21] operator directed rewards submission model --- .../operatorDirectedRewardSubmissions.go | 83 ++++++++++--------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index 5162a3e..cd8eaf4 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -81,20 +81,24 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GetModelName() string { return "OperatorDirectedRewardSubmissionsModel" } -type genericRewardPaymentData struct { - Token string - Amount json.Number - StartTimestamp uint64 - Duration uint64 +type operatorDirectedRewardData struct { StrategiesAndMultipliers []struct { - Strategy string - Multiplier json.Number + Strategy string `json:"strategy"` + Multiplier json.Number `json:"multiplier"` } `json:"strategiesAndMultipliers"` + Token string `json:"token"` + OperatorRewards []struct { + Operator string `json:"operator"` + Amount json.Number `json:"amount"` + } `json:"operatorRewards"` + StartTimestamp uint64 `json:"startTimestamp"` + Duration uint64 `json:"duration"` + Description string `json:"description"` } type operatorDirectedRewardSubmissionOutputData struct { - RewardsSubmission *genericRewardPaymentData `json:"rewardsSubmission"` - RangePayment *genericRewardPaymentData `json:"rangePayment"` + SubmissionNonce json.Number `json:"submissionNonce"` + OperatorDirectedRewardsSubmission *operatorDirectedRewardData `json:"operatorDirectedRewardsSubmission"` } func parseRewardSubmissionOutputData(outputDataStr string) (*operatorDirectedRewardSubmissionOutputData, error) { @@ -120,46 +124,45 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) handleOperatorDirectedReward if err != nil { return nil, err } - - var actualOuputData *genericRewardPaymentData - if log.EventName == "RangePaymentCreated" || log.EventName == "RangePaymentForAllCreated" { - actualOuputData = outputData.RangePayment - } else { - actualOuputData = outputData.RewardsSubmission - } + outputRewardData := outputData.OperatorDirectedRewardsSubmission rewardSubmissions := make([]*OperatorDirectedRewardSubmission, 0) - for i, strategyAndMultiplier := range actualOuputData.StrategiesAndMultipliers { - startTimestamp := time.Unix(int64(actualOuputData.StartTimestamp), 0) - endTimestamp := startTimestamp.Add(time.Duration(actualOuputData.Duration) * time.Second) - - amountBig, success := numbers.NewBig257().SetString(actualOuputData.Amount.String(), 10) - if !success { - return nil, xerrors.Errorf("Failed to parse amount to Big257: %s", actualOuputData.Amount.String()) - } + for i, strategyAndMultiplier := range outputRewardData.StrategiesAndMultipliers { + startTimestamp := time.Unix(int64(outputRewardData.StartTimestamp), 0) + endTimestamp := startTimestamp.Add(time.Duration(outputRewardData.Duration) * time.Second) multiplierBig, success := numbers.NewBig257().SetString(strategyAndMultiplier.Multiplier.String(), 10) if !success { - return nil, xerrors.Errorf("Failed to parse multiplier to Big257: %s", actualOuputData.Amount.String()) + return nil, xerrors.Errorf("Failed to parse multiplier to Big257: %s", strategyAndMultiplier.Multiplier.String()) } - rewardSubmission := &OperatorDirectedRewardSubmission{ - Avs: strings.ToLower(arguments[0].Value.(string)), - RewardHash: strings.ToLower(arguments[2].Value.(string)), - Token: strings.ToLower(actualOuputData.Token), - Amount: amountBig.String(), - Strategy: strategyAndMultiplier.Strategy, - Multiplier: multiplierBig.String(), - StartTimestamp: &startTimestamp, - EndTimestamp: &endTimestamp, - Duration: actualOuputData.Duration, - BlockNumber: log.BlockNumber, - TransactionHash: log.TransactionHash, - LogIndex: log.LogIndex, - StrategyIndex: uint64(i), + for j, operatorReward := range outputRewardData.OperatorRewards { + amountBig, success := numbers.NewBig257().SetString(operatorReward.Amount.String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to parse amount to Big257: %s", operatorReward.Amount.String()) + } + + rewardSubmission := &OperatorDirectedRewardSubmission{ + Avs: strings.ToLower(arguments[1].Value.(string)), + RewardHash: strings.ToLower(arguments[2].Value.(string)), + Token: strings.ToLower(outputRewardData.Token), + Operator: strings.ToLower(operatorReward.Operator), + OperatorIndex: uint64(j), + Amount: amountBig.String(), + Strategy: strings.ToLower(strategyAndMultiplier.Strategy), + StrategyIndex: uint64(i), + Multiplier: multiplierBig.String(), + StartTimestamp: &startTimestamp, + EndTimestamp: &endTimestamp, + Duration: outputRewardData.Duration, + BlockNumber: log.BlockNumber, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, + } + + rewardSubmissions = append(rewardSubmissions, rewardSubmission) } - rewardSubmissions = append(rewardSubmissions, rewardSubmission) } return rewardSubmissions, nil From 15048b465e9f61839653fcf1e0d4168e02342ccd Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Fri, 15 Nov 2024 19:45:29 -0500 Subject: [PATCH 09/21] migration --- .../up.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go diff --git a/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go b/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go new file mode 100644 index 0000000..1aea82d --- /dev/null +++ b/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go @@ -0,0 +1,41 @@ +package _202411151931_operatorDirectedRewardSubmissions + +import ( + "database/sql" + + "gorm.io/gorm" +) + +type Migration struct { +} + +func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { + query := ` + create table if not exists operator_directed_reward_submissions ( + avs varchar not null, + reward_hash varchar not null, + token varchar not null, + operator varchar not null, + operator_index integer not null, + amount numeric not null, + strategy varchar not null, + strategy_index integer not null, + multiplier numeric(78) not null, + start_timestamp timestamp(6) not null, + end_timestamp timestamp(6) not null, + duration bigint not null, + block_number bigint not null, + transaction_hash varchar not null, + log_index bigint not null, + unique(transaction_hash, log_index, block_number, reward_hash, strategy_index, operator_index) + ); + ` + if err := grm.Exec(query).Error; err != nil { + return err + } + return nil +} + +func (m *Migration) GetName() string { + return "202411151931_operatorDirectedRewardSubmissions" +} From 046cd8e9f60528f487ee30bcea4b95f0fc664bea Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Fri, 15 Nov 2024 19:51:57 -0500 Subject: [PATCH 10/21] migrator --- pkg/postgres/migrations/migrator.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/postgres/migrations/migrator.go b/pkg/postgres/migrations/migrator.go index da3bfbc..140486d 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -3,6 +3,8 @@ package migrations import ( "database/sql" "fmt" + "time" + _202409061249_bootstrapDb "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202409061249_bootstrapDb" _202409061250_eigenlayerStateTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202409061250_eigenlayerStateTables" _202409061720_operatorShareChanges "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202409061720_operatorShareChanges" @@ -32,10 +34,11 @@ import ( _202411120947_disabledDistributionRoots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411120947_disabledDistributionRoots" _202411130953_addHashColumns "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411130953_addHashColumns" _202411131200_eigenStateModelConstraints "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411131200_eigenStateModelConstraints" + _202411151931_operatorDirectedRewardSubmissions "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions" _202411191947_cleanupUnusedTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191947_cleanupUnusedTables" + "go.uber.org/zap" "gorm.io/gorm" - "time" ) type Migration interface { @@ -103,6 +106,7 @@ func (m *Migrator) MigrateAll() error { &_202411120947_disabledDistributionRoots.Migration{}, &_202411130953_addHashColumns.Migration{}, &_202411131200_eigenStateModelConstraints.Migration{}, + &_202411151931_operatorDirectedRewardSubmissions.Migration{}, &_202411191947_cleanupUnusedTables.Migration{}, } From 59629d250b04632b03c0c15996567457e0634e77 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 19 Nov 2024 12:18:53 -0500 Subject: [PATCH 11/21] test: operator directed rewards unit test --- .../operatorDirectedRewardSubmissions_test.go | 465 ++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go new file mode 100644 index 0000000..8f47ead --- /dev/null +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go @@ -0,0 +1,465 @@ +package operatorDirectedRewardSubmissions + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/storage" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/internal/logger" + "github.com/Layr-Labs/sidecar/internal/tests" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func setup() ( + string, + *gorm.DB, + *zap.Logger, + *config.Config, + error, +) { + cfg := config.NewConfig() + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: true}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, grm, l, cfg, nil +} + +func teardown(model *OperatorDirectedRewardSubmissionsModel) { + queries := []string{ + `truncate table operator_directed_reward_submissions`, + `truncate table blocks cascade`, + } + for _, query := range queries { + res := model.DB.Exec(query) + if res.Error != nil { + fmt.Printf("Failed to run query: %v\n", res.Error) + } + } +} + +func createBlock(model *OperatorDirectedRewardSubmissionsModel, blockNumber uint64) error { + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * time.Duration(blockNumber)), + } + res := model.DB.Model(&storage.Block{}).Create(block) + if res.Error != nil { + return res.Error + } + return nil +} + +func Test_OperatorDirectedRewardSubmissions(t *testing.T) { + _, grm, l, cfg, err := setup() + + if err != nil { + t.Fatal(err) + } + + t.Run("Test each event type", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + + model, err := NewOperatorDirectedRewardSubmissionsModel(esm, grm, l, cfg) + + submissionCounter := 0 + + t.Run("Handle an operator directed reward submission", func(t *testing.T) { + blockNumber := uint64(102) + + if err := createBlock(model, blockNumber); err != nil { + t.Fatal(err) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForChain().RewardsCoordinator, + Arguments: `[{"Name": "caller", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "avs", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "operatorDirectedRewardsSubmissionHash", "Type": "bytes32", "Value": "0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": false}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,(address,uint256)[],uint32,uint32,string)", "Value": null, "Indexed": false}]`, + EventName: "OperatorDirectedAVSRewardsSubmissionCreated", + LogIndex: big.NewInt(12).Uint64(), + OutputData: `{"submissionNonce": 0, "operatorDirectedRewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "operatorRewards": [{"operator": "0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "amount": 20000000000000000000000}], "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}]}}`, + } + + err = model.SetupStateForBlock(blockNumber) + assert.Nil(t, err) + + isInteresting := model.IsInterestingLog(log) + assert.True(t, isInteresting) + + change, err := model.HandleStateChange(log) + assert.Nil(t, err) + assert.NotNil(t, change) + + strategiesAndMultipliers := []struct { + Strategy string + Multiplier string + }{ + {"0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "1000000000000000000"}, + } + + operatorRewards := []struct { + Operator string + Amount string + }{ + {"0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "20000000000000000000000"}, + } + + typedChange := change.([]*OperatorDirectedRewardSubmission) + assert.Equal(t, len(strategiesAndMultipliers)*len(operatorRewards), len(typedChange)) + + for i, submission := range typedChange { + assert.Equal(t, strings.ToLower("0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101"), strings.ToLower(submission.Avs)) + assert.Equal(t, strings.ToLower("0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24"), strings.ToLower(submission.Token)) + assert.Equal(t, strings.ToLower("0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9"), strings.ToLower(submission.RewardHash)) + assert.Equal(t, uint64(2419200), submission.Duration) + assert.Equal(t, int64(1725494400), submission.StartTimestamp.Unix()) + assert.Equal(t, int64(2419200+1725494400), submission.EndTimestamp.Unix()) + + assert.Equal(t, strings.ToLower(strategiesAndMultipliers[i].Strategy), strings.ToLower(submission.Strategy)) + assert.Equal(t, strategiesAndMultipliers[i].Multiplier, submission.Multiplier) + + assert.Equal(t, strings.ToLower(operatorRewards[i].Operator), strings.ToLower(submission.Operator)) + assert.Equal(t, operatorRewards[i].Amount, submission.Amount) + } + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + rewards := make([]*OperatorDirectedRewardSubmission, 0) + query := `select * from operator_directed_reward_submissions where block_number = ?` + res := model.DB.Raw(query, blockNumber).Scan(&rewards) + assert.Nil(t, res.Error) + assert.Equal(t, len(strategiesAndMultipliers)*len(operatorRewards), len(rewards)) + + submissionCounter += len(strategiesAndMultipliers) * len(operatorRewards) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + teardown(model) + }) + }) + + // t.Run("multi-block test", func(t *testing.T) { + // esm := stateManager.NewEigenStateManager(l, grm) + + // model, err := NewOperatorDirectedRewardSubmissionsModel(esm, grm, l, cfg) + // assert.Nil(t, err) + + // blockNumber := uint64(100) + // // create first block + // if err := createBlock(model, blockNumber); err != nil { + // t.Fatal(err) + // } + + // // First RangePaymentCreated + // log := &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000000"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x58959fBe6661daEA647E20dF7c6d2c7F0d2215fB"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, + // EventName: "RangePaymentCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rangePayment": {"token": "0x94373a4919b3240d86ea41593d5eba789fef3848", "amount": 50000000000000000000, "duration": 2419200, "startTimestamp": 1712188800, "strategiesAndMultipliers": [{"strategy": "0x3c28437e610fb099cc3d6de4d9c707dfacd308ae", "multiplier": 1000000000000000000}, {"strategy": "0x3cb1fd19cfb178c1098f2fc1e11090a0642b2314", "multiplier": 2000000000000000000}, {"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 3000000000000000000}, {"strategy": "0x6dc6ce589f852f96ac86cb160ab0b15b9f56dedd", "multiplier": 4500000000000000000}, {"strategy": "0x87f6c7d24b109919eb38295e3f8298425e6331d9", "multiplier": 500000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 8000000000000000000}, {"strategy": "0xdccf401fd121d8c542e96bc1d0078884422afad2", "multiplier": 5000000000000000000}]}}`, + // } + + // err = model.SetupStateForBlock(blockNumber) + // assert.Nil(t, err) + + // isInteresting := model.IsInterestingLog(log) + // assert.True(t, isInteresting) + + // change, err := model.HandleStateChange(log) + // assert.Nil(t, err) + // assert.NotNil(t, change) + // typedChange := change.([]*OperatorDirectedRewardSubmission) + + // err = model.CommitFinalState(blockNumber) + // assert.Nil(t, err) + + // query := `select count(*) from reward_submissions where block_number = ?` + // var count int + // res := model.DB.Raw(query, blockNumber).Scan(&count) + + // assert.Nil(t, res.Error) + // assert.Equal(t, len(typedChange), count) + + // stateRoot, err := model.GenerateStateRoot(blockNumber) + // assert.Nil(t, err) + // assert.NotNil(t, stateRoot) + // assert.True(t, len(stateRoot) > 0) + + // // ----- + + // blockNumber = uint64(101) + // // create block + // if err := createBlock(model, blockNumber); err != nil { + // t.Fatal(err) + // } + + // // Second log: RangePaymentForAllCreated + // log = &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000001"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x69193C881C4BfA9015F1E9B2631e31238BedB93e"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, + // EventName: "RangePaymentForAllCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rangePayment": {"token": "0x3f1c547b21f65e10480de3ad8e19faac46c95034", "amount": 11000000000000000000, "duration": 2419200, "startTimestamp": 1713398400, "strategiesAndMultipliers": [{"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 1000000000000000000}, {"strategy": "0x7fa77c321bf66e42eabc9b10129304f7f90c5585", "multiplier": 2000000000000000000}, {"strategy": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", "multiplier": 3000000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 4500000000000000000}]}}`, + // } + + // err = model.SetupStateForBlock(blockNumber) + // assert.Nil(t, err) + + // isInteresting = model.IsInterestingLog(log) + // assert.True(t, isInteresting) + + // change, err = model.HandleStateChange(log) + // assert.Nil(t, err) + // assert.NotNil(t, change) + // typedChange = change.([]*OperatorDirectedRewardSubmission) + + // err = model.CommitFinalState(blockNumber) + // assert.Nil(t, err) + + // stateRoot, err = model.GenerateStateRoot(blockNumber) + // assert.Nil(t, err) + // assert.NotNil(t, stateRoot) + // assert.True(t, len(stateRoot) > 0) + + // query = `select count(*) from reward_submissions where block_number = ?` + // res = model.DB.Raw(query, blockNumber).Scan(&count) + + // assert.Nil(t, res.Error) + // assert.Equal(t, len(typedChange), count) + + // // ----- + + // blockNumber = uint64(102) + // // create block + // if err := createBlock(model, blockNumber); err != nil { + // t.Fatal(err) + // } + + // log = &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, + // EventName: "AVSRewardsSubmissionCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "amount": 10000000000000000000000, "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}]}}`, + // } + + // err = model.SetupStateForBlock(blockNumber) + // assert.Nil(t, err) + + // isInteresting = model.IsInterestingLog(log) + // assert.True(t, isInteresting) + + // change, err = model.HandleStateChange(log) + // assert.Nil(t, err) + // assert.NotNil(t, change) + // typedChange = change.([]*OperatorDirectedRewardSubmission) + + // err = model.CommitFinalState(blockNumber) + // assert.Nil(t, err) + + // stateRoot, err = model.GenerateStateRoot(blockNumber) + // assert.Nil(t, err) + // assert.NotNil(t, stateRoot) + // assert.True(t, len(stateRoot) > 0) + + // query = `select count(*) from reward_submissions where block_number = ?` + // res = model.DB.Raw(query, blockNumber).Scan(&count) + + // assert.Nil(t, res.Error) + // assert.Equal(t, len(typedChange), count) + + // // ----- + + // blockNumber = uint64(103) + // // create block + // if err := createBlock(model, blockNumber); err != nil { + // t.Fatal(err) + // } + + // log = &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x66ae7d7c4d492e4e012b95977f14715b74498bc5", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 3, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0x99ebccb0f68eedbf3dff04c7773d6ff94fc439e0eebdd80918b3785ae8099f96", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, + // EventName: "RewardsSubmissionForAllCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rewardsSubmission": {"token": "0x554c393923c753d146aa34608523ad7946b61662", "amount": 10000000000000000000, "duration": 1814400, "startTimestamp": 1717632000, "strategiesAndMultipliers": [{"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 1000000000000000000}, {"strategy": "0xdccf401fd121d8c542e96bc1d0078884422afad2", "multiplier": 2000000000000000000}]}}`, + // } + + // err = model.SetupStateForBlock(blockNumber) + // assert.Nil(t, err) + + // isInteresting = model.IsInterestingLog(log) + // assert.True(t, isInteresting) + + // change, err = model.HandleStateChange(log) + // assert.Nil(t, err) + // assert.NotNil(t, change) + // typedChange = change.([]*OperatorDirectedRewardSubmission) + + // err = model.CommitFinalState(blockNumber) + // assert.Nil(t, err) + + // stateRoot, err = model.GenerateStateRoot(blockNumber) + // assert.Nil(t, err) + // assert.NotNil(t, stateRoot) + // assert.True(t, len(stateRoot) > 0) + + // query = `select count(*) from reward_submissions where block_number = ?` + // res = model.DB.Raw(query, blockNumber).Scan(&count) + + // assert.Nil(t, res.Error) + // assert.Equal(t, len(typedChange), count) + + // t.Cleanup(func() { + // teardown(model) + // }) + // }) + + // t.Run("single block, multiple events", func(t *testing.T) { + // esm := stateManager.NewEigenStateManager(l, grm) + + // model, err := NewOperatorDirectedRewardSubmissionsModel(esm, grm, l, cfg) + // assert.Nil(t, err) + + // submissionCounter := 0 + + // blockNumber := uint64(100) + // // create first block + // if err := createBlock(model, blockNumber); err != nil { + // t.Fatal(err) + // } + + // err = model.SetupStateForBlock(blockNumber) + // assert.Nil(t, err) + + // handleLog := func(log *storage.TransactionLog) { + // isInteresting := model.IsInterestingLog(log) + // assert.True(t, isInteresting) + + // change, err := model.HandleStateChange(log) + // assert.Nil(t, err) + // assert.NotNil(t, change) + // typedChange := change.([]*OperatorDirectedRewardSubmission) + + // submissionCounter += len(typedChange) + // } + + // // First RangePaymentCreated + // rangePaymentCreatedLog := &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000000"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x58959fBe6661daEA647E20dF7c6d2c7F0d2215fB"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, + // EventName: "RangePaymentCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rangePayment": {"token": "0x94373a4919b3240d86ea41593d5eba789fef3848", "amount": 50000000000000000000, "duration": 2419200, "startTimestamp": 1712188800, "strategiesAndMultipliers": [{"strategy": "0x3c28437e610fb099cc3d6de4d9c707dfacd308ae", "multiplier": 1000000000000000000}, {"strategy": "0x3cb1fd19cfb178c1098f2fc1e11090a0642b2314", "multiplier": 2000000000000000000}, {"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 3000000000000000000}, {"strategy": "0x6dc6ce589f852f96ac86cb160ab0b15b9f56dedd", "multiplier": 4500000000000000000}, {"strategy": "0x87f6c7d24b109919eb38295e3f8298425e6331d9", "multiplier": 500000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 8000000000000000000}, {"strategy": "0xdccf401fd121d8c542e96bc1d0078884422afad2", "multiplier": 5000000000000000000}]}}`, + // } + // handleLog(rangePaymentCreatedLog) + + // rangePaymentForAllLog := &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000001"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x69193C881C4BfA9015F1E9B2631e31238BedB93e"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, + // EventName: "RangePaymentForAllCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rangePayment": {"token": "0x3f1c547b21f65e10480de3ad8e19faac46c95034", "amount": 11000000000000000000, "duration": 2419200, "startTimestamp": 1713398400, "strategiesAndMultipliers": [{"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 1000000000000000000}, {"strategy": "0x7fa77c321bf66e42eabc9b10129304f7f90c5585", "multiplier": 2000000000000000000}, {"strategy": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", "multiplier": 3000000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 4500000000000000000}]}}`, + // } + // handleLog(rangePaymentForAllLog) + + // rewardSubmissionCreatedLog := &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, + // EventName: "AVSRewardsSubmissionCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "amount": 10000000000000000000000, "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}]}}`, + // } + // handleLog(rewardSubmissionCreatedLog) + + // rewardsForAllLog := &storage.TransactionLog{ + // TransactionHash: "some hash", + // TransactionIndex: big.NewInt(100).Uint64(), + // BlockNumber: blockNumber, + // Address: cfg.GetContractsMapForChain().RewardsCoordinator, + // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x002b273d4459b5636f971cc7be6443e95517d394", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0xcb5e9dfd219cc5500e88a349d8f072b77241475b3266a0f2c6cf29b1e09d3211", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, + // EventName: "RewardsSubmissionForAllCreated", + // LogIndex: big.NewInt(12).Uint64(), + // OutputData: `{"rewardsSubmission": {"token": "0xdeeeee2b48c121e6728ed95c860e296177849932", "amount": 1000000000000000000000000000, "duration": 5443200, "startTimestamp": 1717027200, "strategiesAndMultipliers": [{"strategy": "0x05037a81bd7b4c9e0f7b430f1f2a22c31a2fd943", "multiplier": 1000000000000000000}, {"strategy": "0x31b6f59e1627cefc9fa174ad03859fc337666af7", "multiplier": 1000000000000000000}, {"strategy": "0x3a8fbdf9e77dfc25d09741f51d3e181b25d0c4e0", "multiplier": 1000000000000000000}, {"strategy": "0x46281e3b7fdcacdba44cadf069a94a588fd4c6ef", "multiplier": 1000000000000000000}, {"strategy": "0x70eb4d3c164a6b4a5f908d4fbb5a9caffb66bab6", "multiplier": 1000000000000000000}, {"strategy": "0x7673a47463f80c6a3553db9e54c8cdcd5313d0ac", "multiplier": 1000000000000000000}, {"strategy": "0x7d704507b76571a51d9cae8addabbfd0ba0e63d3", "multiplier": 1000000000000000000}, {"strategy": "0x80528d6e9a2babfc766965e0e26d5ab08d9cfaf9", "multiplier": 1000000000000000000}, {"strategy": "0x9281ff96637710cd9a5cacce9c6fad8c9f54631c", "multiplier": 1000000000000000000}, {"strategy": "0xaccc5a86732be85b5012e8614af237801636f8e5", "multiplier": 1000000000000000000}, {"strategy": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", "multiplier": 1000000000000000000}]}}`, + // } + // handleLog(rewardsForAllLog) + + // // check that we're starting with 0 rows + // query := `select count(*) from reward_submissions` + // var count int + // res := model.DB.Raw(query).Scan(&count) + // assert.Nil(t, res.Error) + // assert.Equal(t, 0, count) + + // // Commit the final state + // err = model.CommitFinalState(blockNumber) + // assert.Nil(t, err) + + // // Generate the stateroot + // stateRoot, err := model.GenerateStateRoot(blockNumber) + // assert.Nil(t, err) + // assert.NotNil(t, stateRoot) + // assert.True(t, len(stateRoot) > 0) + + // // Verify we have the right number of rows + // query = `select count(*) from reward_submissions where block_number = ?` + // res = model.DB.Raw(query, blockNumber).Scan(&count) + // assert.Nil(t, res.Error) + // assert.Equal(t, submissionCounter, count) + + // t.Cleanup(func() { + // teardown(model) + // }) + // }) + + t.Cleanup(func() { + // postgres.TeardownTestDatabase(dbName, cfg, grm, l) + }) +} From 78a2e88da56aaecf6a673460d91f0252cacfaed3 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 19 Nov 2024 12:35:48 -0500 Subject: [PATCH 12/21] updated test --- .../operatorDirectedRewardSubmissions_test.go | 310 +----------------- 1 file changed, 9 insertions(+), 301 deletions(-) diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go index 8f47ead..8021e6b 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go @@ -94,7 +94,7 @@ func Test_OperatorDirectedRewardSubmissions(t *testing.T) { Arguments: `[{"Name": "caller", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "avs", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "operatorDirectedRewardsSubmissionHash", "Type": "bytes32", "Value": "0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": false}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,(address,uint256)[],uint32,uint32,string)", "Value": null, "Indexed": false}]`, EventName: "OperatorDirectedAVSRewardsSubmissionCreated", LogIndex: big.NewInt(12).Uint64(), - OutputData: `{"submissionNonce": 0, "operatorDirectedRewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "operatorRewards": [{"operator": "0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "amount": 20000000000000000000000}], "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}]}}`, + OutputData: `{"submissionNonce": 0, "operatorDirectedRewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "operatorRewards": [{"operator": "0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "amount": 30000000000000000000000}, {"operator": "0xF50Cba7a66b5E615587157e43286DaA7aF94009e", "amount": 40000000000000000000000}], "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}, {"strategy": "0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc", "multiplier": 2000000000000000000}]}}`, } err = model.SetupStateForBlock(blockNumber) @@ -112,19 +112,21 @@ func Test_OperatorDirectedRewardSubmissions(t *testing.T) { Multiplier string }{ {"0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "1000000000000000000"}, + {"0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc", "2000000000000000000"}, } operatorRewards := []struct { Operator string Amount string }{ - {"0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "20000000000000000000000"}, + {"0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "30000000000000000000000"}, + {"0xF50Cba7a66b5E615587157e43286DaA7aF94009e", "40000000000000000000000"}, } typedChange := change.([]*OperatorDirectedRewardSubmission) assert.Equal(t, len(strategiesAndMultipliers)*len(operatorRewards), len(typedChange)) - for i, submission := range typedChange { + for _, submission := range typedChange { assert.Equal(t, strings.ToLower("0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101"), strings.ToLower(submission.Avs)) assert.Equal(t, strings.ToLower("0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24"), strings.ToLower(submission.Token)) assert.Equal(t, strings.ToLower("0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9"), strings.ToLower(submission.RewardHash)) @@ -132,11 +134,11 @@ func Test_OperatorDirectedRewardSubmissions(t *testing.T) { assert.Equal(t, int64(1725494400), submission.StartTimestamp.Unix()) assert.Equal(t, int64(2419200+1725494400), submission.EndTimestamp.Unix()) - assert.Equal(t, strings.ToLower(strategiesAndMultipliers[i].Strategy), strings.ToLower(submission.Strategy)) - assert.Equal(t, strategiesAndMultipliers[i].Multiplier, submission.Multiplier) + assert.Equal(t, strings.ToLower(strategiesAndMultipliers[submission.StrategyIndex].Strategy), strings.ToLower(submission.Strategy)) + assert.Equal(t, strategiesAndMultipliers[submission.StrategyIndex].Multiplier, submission.Multiplier) - assert.Equal(t, strings.ToLower(operatorRewards[i].Operator), strings.ToLower(submission.Operator)) - assert.Equal(t, operatorRewards[i].Amount, submission.Amount) + assert.Equal(t, strings.ToLower(operatorRewards[submission.OperatorIndex].Operator), strings.ToLower(submission.Operator)) + assert.Equal(t, operatorRewards[submission.OperatorIndex].Amount, submission.Amount) } err = model.CommitFinalState(blockNumber) @@ -165,300 +167,6 @@ func Test_OperatorDirectedRewardSubmissions(t *testing.T) { }) }) - // t.Run("multi-block test", func(t *testing.T) { - // esm := stateManager.NewEigenStateManager(l, grm) - - // model, err := NewOperatorDirectedRewardSubmissionsModel(esm, grm, l, cfg) - // assert.Nil(t, err) - - // blockNumber := uint64(100) - // // create first block - // if err := createBlock(model, blockNumber); err != nil { - // t.Fatal(err) - // } - - // // First RangePaymentCreated - // log := &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000000"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x58959fBe6661daEA647E20dF7c6d2c7F0d2215fB"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, - // EventName: "RangePaymentCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rangePayment": {"token": "0x94373a4919b3240d86ea41593d5eba789fef3848", "amount": 50000000000000000000, "duration": 2419200, "startTimestamp": 1712188800, "strategiesAndMultipliers": [{"strategy": "0x3c28437e610fb099cc3d6de4d9c707dfacd308ae", "multiplier": 1000000000000000000}, {"strategy": "0x3cb1fd19cfb178c1098f2fc1e11090a0642b2314", "multiplier": 2000000000000000000}, {"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 3000000000000000000}, {"strategy": "0x6dc6ce589f852f96ac86cb160ab0b15b9f56dedd", "multiplier": 4500000000000000000}, {"strategy": "0x87f6c7d24b109919eb38295e3f8298425e6331d9", "multiplier": 500000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 8000000000000000000}, {"strategy": "0xdccf401fd121d8c542e96bc1d0078884422afad2", "multiplier": 5000000000000000000}]}}`, - // } - - // err = model.SetupStateForBlock(blockNumber) - // assert.Nil(t, err) - - // isInteresting := model.IsInterestingLog(log) - // assert.True(t, isInteresting) - - // change, err := model.HandleStateChange(log) - // assert.Nil(t, err) - // assert.NotNil(t, change) - // typedChange := change.([]*OperatorDirectedRewardSubmission) - - // err = model.CommitFinalState(blockNumber) - // assert.Nil(t, err) - - // query := `select count(*) from reward_submissions where block_number = ?` - // var count int - // res := model.DB.Raw(query, blockNumber).Scan(&count) - - // assert.Nil(t, res.Error) - // assert.Equal(t, len(typedChange), count) - - // stateRoot, err := model.GenerateStateRoot(blockNumber) - // assert.Nil(t, err) - // assert.NotNil(t, stateRoot) - // assert.True(t, len(stateRoot) > 0) - - // // ----- - - // blockNumber = uint64(101) - // // create block - // if err := createBlock(model, blockNumber); err != nil { - // t.Fatal(err) - // } - - // // Second log: RangePaymentForAllCreated - // log = &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000001"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x69193C881C4BfA9015F1E9B2631e31238BedB93e"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, - // EventName: "RangePaymentForAllCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rangePayment": {"token": "0x3f1c547b21f65e10480de3ad8e19faac46c95034", "amount": 11000000000000000000, "duration": 2419200, "startTimestamp": 1713398400, "strategiesAndMultipliers": [{"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 1000000000000000000}, {"strategy": "0x7fa77c321bf66e42eabc9b10129304f7f90c5585", "multiplier": 2000000000000000000}, {"strategy": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", "multiplier": 3000000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 4500000000000000000}]}}`, - // } - - // err = model.SetupStateForBlock(blockNumber) - // assert.Nil(t, err) - - // isInteresting = model.IsInterestingLog(log) - // assert.True(t, isInteresting) - - // change, err = model.HandleStateChange(log) - // assert.Nil(t, err) - // assert.NotNil(t, change) - // typedChange = change.([]*OperatorDirectedRewardSubmission) - - // err = model.CommitFinalState(blockNumber) - // assert.Nil(t, err) - - // stateRoot, err = model.GenerateStateRoot(blockNumber) - // assert.Nil(t, err) - // assert.NotNil(t, stateRoot) - // assert.True(t, len(stateRoot) > 0) - - // query = `select count(*) from reward_submissions where block_number = ?` - // res = model.DB.Raw(query, blockNumber).Scan(&count) - - // assert.Nil(t, res.Error) - // assert.Equal(t, len(typedChange), count) - - // // ----- - - // blockNumber = uint64(102) - // // create block - // if err := createBlock(model, blockNumber); err != nil { - // t.Fatal(err) - // } - - // log = &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, - // EventName: "AVSRewardsSubmissionCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "amount": 10000000000000000000000, "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}]}}`, - // } - - // err = model.SetupStateForBlock(blockNumber) - // assert.Nil(t, err) - - // isInteresting = model.IsInterestingLog(log) - // assert.True(t, isInteresting) - - // change, err = model.HandleStateChange(log) - // assert.Nil(t, err) - // assert.NotNil(t, change) - // typedChange = change.([]*OperatorDirectedRewardSubmission) - - // err = model.CommitFinalState(blockNumber) - // assert.Nil(t, err) - - // stateRoot, err = model.GenerateStateRoot(blockNumber) - // assert.Nil(t, err) - // assert.NotNil(t, stateRoot) - // assert.True(t, len(stateRoot) > 0) - - // query = `select count(*) from reward_submissions where block_number = ?` - // res = model.DB.Raw(query, blockNumber).Scan(&count) - - // assert.Nil(t, res.Error) - // assert.Equal(t, len(typedChange), count) - - // // ----- - - // blockNumber = uint64(103) - // // create block - // if err := createBlock(model, blockNumber); err != nil { - // t.Fatal(err) - // } - - // log = &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x66ae7d7c4d492e4e012b95977f14715b74498bc5", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 3, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0x99ebccb0f68eedbf3dff04c7773d6ff94fc439e0eebdd80918b3785ae8099f96", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, - // EventName: "RewardsSubmissionForAllCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rewardsSubmission": {"token": "0x554c393923c753d146aa34608523ad7946b61662", "amount": 10000000000000000000, "duration": 1814400, "startTimestamp": 1717632000, "strategiesAndMultipliers": [{"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 1000000000000000000}, {"strategy": "0xdccf401fd121d8c542e96bc1d0078884422afad2", "multiplier": 2000000000000000000}]}}`, - // } - - // err = model.SetupStateForBlock(blockNumber) - // assert.Nil(t, err) - - // isInteresting = model.IsInterestingLog(log) - // assert.True(t, isInteresting) - - // change, err = model.HandleStateChange(log) - // assert.Nil(t, err) - // assert.NotNil(t, change) - // typedChange = change.([]*OperatorDirectedRewardSubmission) - - // err = model.CommitFinalState(blockNumber) - // assert.Nil(t, err) - - // stateRoot, err = model.GenerateStateRoot(blockNumber) - // assert.Nil(t, err) - // assert.NotNil(t, stateRoot) - // assert.True(t, len(stateRoot) > 0) - - // query = `select count(*) from reward_submissions where block_number = ?` - // res = model.DB.Raw(query, blockNumber).Scan(&count) - - // assert.Nil(t, res.Error) - // assert.Equal(t, len(typedChange), count) - - // t.Cleanup(func() { - // teardown(model) - // }) - // }) - - // t.Run("single block, multiple events", func(t *testing.T) { - // esm := stateManager.NewEigenStateManager(l, grm) - - // model, err := NewOperatorDirectedRewardSubmissionsModel(esm, grm, l, cfg) - // assert.Nil(t, err) - - // submissionCounter := 0 - - // blockNumber := uint64(100) - // // create first block - // if err := createBlock(model, blockNumber); err != nil { - // t.Fatal(err) - // } - - // err = model.SetupStateForBlock(blockNumber) - // assert.Nil(t, err) - - // handleLog := func(log *storage.TransactionLog) { - // isInteresting := model.IsInterestingLog(log) - // assert.True(t, isInteresting) - - // change, err := model.HandleStateChange(log) - // assert.Nil(t, err) - // assert.NotNil(t, change) - // typedChange := change.([]*OperatorDirectedRewardSubmission) - - // submissionCounter += len(typedChange) - // } - - // // First RangePaymentCreated - // rangePaymentCreatedLog := &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000000"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x58959fBe6661daEA647E20dF7c6d2c7F0d2215fB"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, - // EventName: "RangePaymentCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rangePayment": {"token": "0x94373a4919b3240d86ea41593d5eba789fef3848", "amount": 50000000000000000000, "duration": 2419200, "startTimestamp": 1712188800, "strategiesAndMultipliers": [{"strategy": "0x3c28437e610fb099cc3d6de4d9c707dfacd308ae", "multiplier": 1000000000000000000}, {"strategy": "0x3cb1fd19cfb178c1098f2fc1e11090a0642b2314", "multiplier": 2000000000000000000}, {"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 3000000000000000000}, {"strategy": "0x6dc6ce589f852f96ac86cb160ab0b15b9f56dedd", "multiplier": 4500000000000000000}, {"strategy": "0x87f6c7d24b109919eb38295e3f8298425e6331d9", "multiplier": 500000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 8000000000000000000}, {"strategy": "0xdccf401fd121d8c542e96bc1d0078884422afad2", "multiplier": 5000000000000000000}]}}`, - // } - // handleLog(rangePaymentCreatedLog) - - // rangePaymentForAllLog := &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x00526A07855f743964F05CccAeCcf7a9E34847fF"}, {"Name": "paymentNonce", "Type": "uint256", "Value": "0x0000000000000000000000000000000000000001"}, {"Name": "rangePaymentHash", "Type": "bytes32", "Value": "0x69193C881C4BfA9015F1E9B2631e31238BedB93e"}, {"Name": "rangePayment", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": ""}]`, - // EventName: "RangePaymentForAllCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rangePayment": {"token": "0x3f1c547b21f65e10480de3ad8e19faac46c95034", "amount": 11000000000000000000, "duration": 2419200, "startTimestamp": 1713398400, "strategiesAndMultipliers": [{"strategy": "0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "multiplier": 1000000000000000000}, {"strategy": "0x7fa77c321bf66e42eabc9b10129304f7f90c5585", "multiplier": 2000000000000000000}, {"strategy": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", "multiplier": 3000000000000000000}, {"strategy": "0xd523267698c81a372191136e477fdebfa33d9fb4", "multiplier": 4500000000000000000}]}}`, - // } - // handleLog(rangePaymentForAllLog) - - // rewardSubmissionCreatedLog := &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "avs", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, - // EventName: "AVSRewardsSubmissionCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "amount": 10000000000000000000000, "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}]}}`, - // } - // handleLog(rewardSubmissionCreatedLog) - - // rewardsForAllLog := &storage.TransactionLog{ - // TransactionHash: "some hash", - // TransactionIndex: big.NewInt(100).Uint64(), - // BlockNumber: blockNumber, - // Address: cfg.GetContractsMapForChain().RewardsCoordinator, - // Arguments: `[{"Name": "submitter", "Type": "address", "Value": "0x002b273d4459b5636f971cc7be6443e95517d394", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": true}, {"Name": "rewardsSubmissionHash", "Type": "bytes32", "Value": "0xcb5e9dfd219cc5500e88a349d8f072b77241475b3266a0f2c6cf29b1e09d3211", "Indexed": true}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,uint256,uint32,uint32)", "Value": null, "Indexed": false}]`, - // EventName: "RewardsSubmissionForAllCreated", - // LogIndex: big.NewInt(12).Uint64(), - // OutputData: `{"rewardsSubmission": {"token": "0xdeeeee2b48c121e6728ed95c860e296177849932", "amount": 1000000000000000000000000000, "duration": 5443200, "startTimestamp": 1717027200, "strategiesAndMultipliers": [{"strategy": "0x05037a81bd7b4c9e0f7b430f1f2a22c31a2fd943", "multiplier": 1000000000000000000}, {"strategy": "0x31b6f59e1627cefc9fa174ad03859fc337666af7", "multiplier": 1000000000000000000}, {"strategy": "0x3a8fbdf9e77dfc25d09741f51d3e181b25d0c4e0", "multiplier": 1000000000000000000}, {"strategy": "0x46281e3b7fdcacdba44cadf069a94a588fd4c6ef", "multiplier": 1000000000000000000}, {"strategy": "0x70eb4d3c164a6b4a5f908d4fbb5a9caffb66bab6", "multiplier": 1000000000000000000}, {"strategy": "0x7673a47463f80c6a3553db9e54c8cdcd5313d0ac", "multiplier": 1000000000000000000}, {"strategy": "0x7d704507b76571a51d9cae8addabbfd0ba0e63d3", "multiplier": 1000000000000000000}, {"strategy": "0x80528d6e9a2babfc766965e0e26d5ab08d9cfaf9", "multiplier": 1000000000000000000}, {"strategy": "0x9281ff96637710cd9a5cacce9c6fad8c9f54631c", "multiplier": 1000000000000000000}, {"strategy": "0xaccc5a86732be85b5012e8614af237801636f8e5", "multiplier": 1000000000000000000}, {"strategy": "0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", "multiplier": 1000000000000000000}]}}`, - // } - // handleLog(rewardsForAllLog) - - // // check that we're starting with 0 rows - // query := `select count(*) from reward_submissions` - // var count int - // res := model.DB.Raw(query).Scan(&count) - // assert.Nil(t, res.Error) - // assert.Equal(t, 0, count) - - // // Commit the final state - // err = model.CommitFinalState(blockNumber) - // assert.Nil(t, err) - - // // Generate the stateroot - // stateRoot, err := model.GenerateStateRoot(blockNumber) - // assert.Nil(t, err) - // assert.NotNil(t, stateRoot) - // assert.True(t, len(stateRoot) > 0) - - // // Verify we have the right number of rows - // query = `select count(*) from reward_submissions where block_number = ?` - // res = model.DB.Raw(query, blockNumber).Scan(&count) - // assert.Nil(t, res.Error) - // assert.Equal(t, submissionCounter, count) - - // t.Cleanup(func() { - // teardown(model) - // }) - // }) - t.Cleanup(func() { // postgres.TeardownTestDatabase(dbName, cfg, grm, l) }) From 448ab6f8b75b9e7ac762f20b660ed52fbb38be5d Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 19 Nov 2024 15:43:59 -0500 Subject: [PATCH 13/21] operator avs split eigen model --- pkg/eigenState/eigenState.go | 5 + .../operatorAVSSplits/operatorAVSSplits.go | 279 ++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go diff --git a/pkg/eigenState/eigenState.go b/pkg/eigenState/eigenState.go index db33994..860617a 100644 --- a/pkg/eigenState/eigenState.go +++ b/pkg/eigenState/eigenState.go @@ -4,6 +4,7 @@ import ( "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/eigenState/avsOperators" "github.com/Layr-Labs/sidecar/pkg/eigenState/disabledDistributionRoots" + "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorAVSSplits" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorDirectedRewardSubmissions" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorShares" "github.com/Layr-Labs/sidecar/pkg/eigenState/rewardSubmissions" @@ -53,5 +54,9 @@ func LoadEigenStateModels( l.Sugar().Errorw("Failed to create OperatorDirectedRewardSubmissionsModel", zap.Error(err)) return err } + if _, err := operatorAVSSplits.NewOperatorAVSSplitModel(sm, grm, l, cfg); err != nil { + l.Sugar().Errorw("Failed to create OperatorAVSSplitModel", zap.Error(err)) + return err + } return nil } diff --git a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go new file mode 100644 index 0000000..8115d2f --- /dev/null +++ b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go @@ -0,0 +1,279 @@ +package operatorAVSSplits + +import ( + "encoding/json" + "fmt" + "slices" + "sort" + "strings" + "time" + + "github.com/Layr-Labs/sidecar/pkg/storage" + "github.com/Layr-Labs/sidecar/pkg/utils" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/pkg/eigenState/base" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/sidecar/pkg/eigenState/types" + "go.uber.org/zap" + "golang.org/x/xerrors" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type OperatorAVSSplit struct { + Operator string + Avs string + ActivatedAt *time.Time + OldOperatorAVSSplitBips uint64 + NewOperatorAVSSplitBips uint64 + BlockNumber uint64 + TransactionHash string + LogIndex uint64 +} + +type OperatorAVSSplitModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[[]*OperatorAVSSplit] + DB *gorm.DB + Network config.Network + Environment config.Environment + logger *zap.Logger + globalConfig *config.Config + + // Accumulates state changes for SlotIds, grouped by block number + stateAccumulator map[uint64]map[types.SlotID]*OperatorAVSSplit +} + +func NewOperatorAVSSplitModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) (*OperatorAVSSplitModel, error) { + model := &OperatorAVSSplitModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + DB: grm, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorAVSSplit), + } + + esm.RegisterState(model, 8) + return model, nil +} + +func (oas *OperatorAVSSplitModel) GetModelName() string { + return "OperatorAVSSplitModel" +} + +type operatorAVSSplitOutputData struct { + ActivatedAt uint64 `json:"activatedAt"` + OldOperatorAVSSplitBips uint64 `json:"oldOperatorAVSSplitBips"` + NewOperatorAVSSplitBips uint64 `json:"newOperatorAVSSplitBips"` +} + +func parseOperatorAVSSplitOutputData(outputDataStr string) (*operatorAVSSplitOutputData, error) { + outputData := &operatorAVSSplitOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + + return outputData, err +} + +func (oas *OperatorAVSSplitModel) handleOperatorAVSSplitBipsSetEvent(log *storage.TransactionLog) (*OperatorAVSSplit, error) { + arguments, err := oas.ParseLogArguments(log) + if err != nil { + return nil, err + } + + outputData, err := parseOperatorAVSSplitOutputData(log.OutputData) + if err != nil { + return nil, err + } + + activatedAt := time.Unix(int64(outputData.ActivatedAt), 0) + + split := &OperatorAVSSplit{ + Operator: strings.ToLower(arguments[1].Value.(string)), + Avs: strings.ToLower(arguments[2].Value.(string)), + ActivatedAt: &activatedAt, + OldOperatorAVSSplitBips: outputData.OldOperatorAVSSplitBips, + NewOperatorAVSSplitBips: outputData.NewOperatorAVSSplitBips, + BlockNumber: log.BlockNumber, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, + } + + return split, nil +} + +func (oas *OperatorAVSSplitModel) GetStateTransitions() (types.StateTransitions[*OperatorAVSSplit], []uint64) { + stateChanges := make(types.StateTransitions[*OperatorAVSSplit]) + + stateChanges[0] = func(log *storage.TransactionLog) (*OperatorAVSSplit, error) { + operatorAVSSplit, err := oas.handleOperatorAVSSplitBipsSetEvent(log) + if err != nil { + return nil, err + } + + slotId := base.NewSlotID(operatorAVSSplit.TransactionHash, operatorAVSSplit.LogIndex) + + _, ok := oas.stateAccumulator[log.BlockNumber][slotId] + if ok { + fmt.Printf("Submissions: %+v\n", oas.stateAccumulator[log.BlockNumber]) + err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) + oas.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) + return nil, err + } + + oas.stateAccumulator[log.BlockNumber][slotId] = operatorAVSSplit + + return operatorAVSSplit, nil + } + + // Create an ordered list of block numbers + blockNumbers := make([]uint64, 0) + for blockNumber := range stateChanges { + blockNumbers = append(blockNumbers, blockNumber) + } + sort.Slice(blockNumbers, func(i, j int) bool { + return blockNumbers[i] < blockNumbers[j] + }) + slices.Reverse(blockNumbers) + + return stateChanges, blockNumbers +} + +func (oas *OperatorAVSSplitModel) getContractAddressesForEnvironment() map[string][]string { + contracts := oas.globalConfig.GetContractsMapForChain() + return map[string][]string{ + contracts.RewardsCoordinator: { + "OperatorAVSSplitBipsSet", + }, + } +} + +func (oas *OperatorAVSSplitModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := oas.getContractAddressesForEnvironment() + return oas.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (oas *OperatorAVSSplitModel) SetupStateForBlock(blockNumber uint64) error { + oas.stateAccumulator[blockNumber] = make(map[types.SlotID]*OperatorAVSSplit) + return nil +} + +func (oas *OperatorAVSSplitModel) CleanupProcessedStateForBlock(blockNumber uint64) error { + delete(oas.stateAccumulator, blockNumber) + return nil +} + +func (oas *OperatorAVSSplitModel) HandleStateChange(log *storage.TransactionLog) (interface{}, error) { + stateChanges, sortedBlockNumbers := oas.GetStateTransitions() + + for _, blockNumber := range sortedBlockNumbers { + if log.BlockNumber >= blockNumber { + oas.logger.Sugar().Debugw("Handling state change", zap.Uint64("blockNumber", log.BlockNumber)) + + change, err := stateChanges[blockNumber](log) + if err != nil { + return nil, err + } + if change == nil { + return nil, nil + } + return change, nil + } + } + return nil, nil +} + +// prepareState prepares the state for commit by adding the new state to the existing state. +func (oas *OperatorAVSSplitModel) prepareState(blockNumber uint64) ([]*OperatorAVSSplit, error) { + accumulatedState, ok := oas.stateAccumulator[blockNumber] + if !ok { + err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) + oas.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, err + } + + recordsToInsert := make([]*OperatorAVSSplit, 0) + for _, split := range accumulatedState { + recordsToInsert = append(recordsToInsert, split) + } + return recordsToInsert, nil +} + +// CommitFinalState commits the final state for the given block number. +func (oas *OperatorAVSSplitModel) CommitFinalState(blockNumber uint64) error { + recordsToInsert, err := oas.prepareState(blockNumber) + if err != nil { + return err + } + + if len(recordsToInsert) > 0 { + for _, record := range recordsToInsert { + res := oas.DB.Model(&OperatorAVSSplit{}).Clauses(clause.Returning{}).Create(&record) + if res.Error != nil { + oas.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) + return res.Error + } + } + } + return nil +} + +// GenerateStateRoot generates the state root for the given block number using the results of the state changes. +func (oas *OperatorAVSSplitModel) GenerateStateRoot(blockNumber uint64) (types.StateRoot, error) { + inserts, err := oas.prepareState(blockNumber) + if err != nil { + return "", err + } + + inputs := oas.sortValuesForMerkleTree(inserts) + + if len(inputs) == 0 { + return "", nil + } + + fullTree, err := oas.MerkleizeState(blockNumber, inputs) + if err != nil { + oas.logger.Sugar().Errorw("Failed to create merkle tree", + zap.Error(err), + zap.Uint64("blockNumber", blockNumber), + zap.Any("inputs", inputs), + ) + return "", err + } + return types.StateRoot(utils.ConvertBytesToString(fullTree.Root())), 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 := "added" + inputs = append(inputs, &base.MerkleTreeInput{ + SlotID: slotID, + Value: []byte(value), + }) + } + + slices.SortFunc(inputs, func(i, j *base.MerkleTreeInput) int { + return strings.Compare(string(i.SlotID), string(j.SlotID)) + }) + + return inputs +} + +func (oas *OperatorAVSSplitModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return oas.BaseEigenState.DeleteState("operator_avs_splits", startBlockNumber, endBlockNumber, oas.DB) +} From 7757f068f7c0e4fc231e868520ee157b7afc5017 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 19 Nov 2024 15:57:48 -0500 Subject: [PATCH 14/21] migration --- .../202411191550_operatorAVSSplits/up.go | 34 +++++++++++++++++++ pkg/postgres/migrations/migrator.go | 2 ++ 2 files changed, 36 insertions(+) create mode 100644 pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go diff --git a/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go b/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go new file mode 100644 index 0000000..850dd89 --- /dev/null +++ b/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go @@ -0,0 +1,34 @@ +package _202411191550_operatorAVSSplits + +import ( + "database/sql" + + "gorm.io/gorm" +) + +type Migration struct { +} + +func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { + query := ` + create table if not exists operator_avs_splits ( + operator varchar not null, + avs varchar not null, + activated_at timestamp(6) not null, + old_operator_avs_split_bips integer not null, + new_operator_avs_split_bips integer not null, + block_number bigint not null, + transaction_hash varchar not null, + log_index bigint not null, + unique(transaction_hash, log_index, block_number) + ); + ` + if err := grm.Exec(query).Error; err != nil { + return err + } + return nil +} + +func (m *Migration) GetName() string { + return "202411191550_operatorAVSSplits" +} diff --git a/pkg/postgres/migrations/migrator.go b/pkg/postgres/migrations/migrator.go index 140486d..46ed2db 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -35,6 +35,7 @@ import ( _202411130953_addHashColumns "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411130953_addHashColumns" _202411131200_eigenStateModelConstraints "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411131200_eigenStateModelConstraints" _202411151931_operatorDirectedRewardSubmissions "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions" + _202411191550_operatorAVSSplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191550_operatorAVSSplits" _202411191947_cleanupUnusedTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191947_cleanupUnusedTables" "go.uber.org/zap" @@ -107,6 +108,7 @@ func (m *Migrator) MigrateAll() error { &_202411130953_addHashColumns.Migration{}, &_202411131200_eigenStateModelConstraints.Migration{}, &_202411151931_operatorDirectedRewardSubmissions.Migration{}, + &_202411191550_operatorAVSSplits.Migration{}, &_202411191947_cleanupUnusedTables.Migration{}, } From fdd30c22785f5a310b5dfdf816655d26892014e7 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 19 Nov 2024 16:57:37 -0500 Subject: [PATCH 15/21] unit tests --- .../operatorAVSSplits_test.go | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go diff --git a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go new file mode 100644 index 0000000..ae0d0d7 --- /dev/null +++ b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go @@ -0,0 +1,143 @@ +package operatorAVSSplits + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/storage" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/internal/logger" + "github.com/Layr-Labs/sidecar/internal/tests" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func setup() ( + string, + *gorm.DB, + *zap.Logger, + *config.Config, + error, +) { + cfg := config.NewConfig() + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: true}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, grm, l, cfg, nil +} + +func teardown(model *OperatorAVSSplitModel) { + queries := []string{ + `truncate table operator_avs_splits`, + `truncate table blocks cascade`, + } + for _, query := range queries { + res := model.DB.Exec(query) + if res.Error != nil { + fmt.Printf("Failed to run query: %v\n", res.Error) + } + } +} + +func createBlock(model *OperatorAVSSplitModel, blockNumber uint64) error { + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * time.Duration(blockNumber)), + } + res := model.DB.Model(&storage.Block{}).Create(block) + if res.Error != nil { + return res.Error + } + return nil +} + +func Test_OperatorAVSSplit(t *testing.T) { + _, grm, l, cfg, err := setup() + + if err != nil { + t.Fatal(err) + } + + t.Run("Test each event type", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + + model, err := NewOperatorAVSSplitModel(esm, grm, l, cfg) + + t.Run("Handle an operator avs split", func(t *testing.T) { + blockNumber := uint64(102) + + if err := createBlock(model, blockNumber); err != nil { + t.Fatal(err) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForChain().RewardsCoordinator, + Arguments: `[{"Name": "caller", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "operator", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "avs", "Type": "address", "Value": "0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "Indexed": true}, {"Name": "activatedAt", "Type": "uint32", "Value": 1725494400, "Indexed": false}, {"Name": "oldOperatorAVSSplitBips", "Type": "uint16", "Value": 1000, "Indexed": false}, {"Name": "newOperatorAVSSplitBips", "Type": "uint16", "Value": 2000, "Indexed": false}]`, + EventName: "OperatorAVSSplitBipsSet", + LogIndex: big.NewInt(12).Uint64(), + OutputData: `{"activatedAt": 1725494400, "oldOperatorAVSSplitBips": 1000, "newOperatorAVSSplitBips": 2000}`, + } + + err = model.SetupStateForBlock(blockNumber) + assert.Nil(t, err) + + isInteresting := model.IsInterestingLog(log) + assert.True(t, isInteresting) + + change, err := model.HandleStateChange(log) + assert.Nil(t, err) + assert.NotNil(t, change) + + split := change.(*OperatorAVSSplit) + + assert.Equal(t, strings.ToLower("0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101"), strings.ToLower(split.Operator)) + assert.Equal(t, strings.ToLower("0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1"), strings.ToLower(split.Avs)) + assert.Equal(t, int64(1725494400), split.ActivatedAt.Unix()) + assert.Equal(t, uint64(1000), split.OldOperatorAVSSplitBips) + assert.Equal(t, uint64(2000), split.NewOperatorAVSSplitBips) + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + splits := make([]*OperatorAVSSplit, 0) + query := `select * from operator_avs_splits where block_number = ?` + res := model.DB.Raw(query, blockNumber).Scan(&splits) + assert.Nil(t, res.Error) + assert.Equal(t, 1, len(splits)) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + // postgres.TeardownTestDatabase(dbName, cfg, grm, l) + }) +} From 1d9c4992102c12a6bc8ebec2a86dd6de3d62549b Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 19 Nov 2024 17:17:57 -0500 Subject: [PATCH 16/21] migration --- .../202411191708_operatorPISplits/up.go | 33 +++++++++++++++++++ .../up.go | 32 ++++++++++++++++++ pkg/postgres/migrations/migrator.go | 4 +++ 3 files changed, 69 insertions(+) create mode 100644 pkg/postgres/migrations/202411191708_operatorPISplits/up.go create mode 100644 pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go diff --git a/pkg/postgres/migrations/202411191708_operatorPISplits/up.go b/pkg/postgres/migrations/202411191708_operatorPISplits/up.go new file mode 100644 index 0000000..cbeb04b --- /dev/null +++ b/pkg/postgres/migrations/202411191708_operatorPISplits/up.go @@ -0,0 +1,33 @@ +package _202411191708_operatorPISplits + +import ( + "database/sql" + + "gorm.io/gorm" +) + +type Migration struct { +} + +func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { + query := ` + create table if not exists operator_pi_splits ( + operator varchar not null, + activated_at timestamp(6) not null, + old_operator_avs_split_bips integer not null, + new_operator_avs_split_bips integer not null, + block_number bigint not null, + transaction_hash varchar not null, + log_index bigint not null, + unique(transaction_hash, log_index, block_number) + ); + ` + if err := grm.Exec(query).Error; err != nil { + return err + } + return nil +} + +func (m *Migration) GetName() string { + return "202411191708_operatorPISplits" +} diff --git a/pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go b/pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go new file mode 100644 index 0000000..ccc9233 --- /dev/null +++ b/pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go @@ -0,0 +1,32 @@ +package _202411191710_blockNumberFkConstraint + +import ( + "database/sql" + "fmt" + + "gorm.io/gorm" +) + +type Migration struct { +} + +func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { + queries := []string{ + `alter table operator_directed_reward_submissions add constraint operator_directed_reward_submissions_block_number_fkey foreign key (block_number) references blocks (number) on delete cascade`, + `alter table operator_avs_splits add constraint operator_avs_splits_block_number_fkey foreign key (block_number) references blocks (number) on delete cascade`, + `alter table operator_pi_splits add constraint operator_pi_splits_block_number_fkey foreign key (block_number) references blocks (number) on delete cascade`, + } + + for _, query := range queries { + _, err := db.Exec(query) + if err != nil { + fmt.Printf("Failed to run migration query: %s - %+v\n", query, err) + return err + } + } + return nil +} + +func (m *Migration) GetName() string { + return "202411191710_blockNumberFkConstraint" +} diff --git a/pkg/postgres/migrations/migrator.go b/pkg/postgres/migrations/migrator.go index 46ed2db..05445ce 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -36,6 +36,8 @@ import ( _202411131200_eigenStateModelConstraints "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411131200_eigenStateModelConstraints" _202411151931_operatorDirectedRewardSubmissions "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions" _202411191550_operatorAVSSplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191550_operatorAVSSplits" + _202411191708_operatorPISplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191708_operatorPISplits" + _202411191710_blockNumberFkConstraint "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191710_blockNumberFkConstraint" _202411191947_cleanupUnusedTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191947_cleanupUnusedTables" "go.uber.org/zap" @@ -109,6 +111,8 @@ func (m *Migrator) MigrateAll() error { &_202411131200_eigenStateModelConstraints.Migration{}, &_202411151931_operatorDirectedRewardSubmissions.Migration{}, &_202411191550_operatorAVSSplits.Migration{}, + &_202411191708_operatorPISplits.Migration{}, + &_202411191710_blockNumberFkConstraint.Migration{}, &_202411191947_cleanupUnusedTables.Migration{}, } From 518c446f96335a7454469acca69655fa647c3292 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 19 Nov 2024 17:46:16 -0500 Subject: [PATCH 17/21] operator pi split state model --- pkg/eigenState/eigenState.go | 5 + .../operatorPISplits/operatorPISplits.go | 277 ++++++++++++++++++ .../operatorPISplits/operatorPISplits_test.go | 142 +++++++++ 3 files changed, 424 insertions(+) create mode 100644 pkg/eigenState/operatorPISplits/operatorPISplits.go create mode 100644 pkg/eigenState/operatorPISplits/operatorPISplits_test.go diff --git a/pkg/eigenState/eigenState.go b/pkg/eigenState/eigenState.go index 860617a..ecdac8c 100644 --- a/pkg/eigenState/eigenState.go +++ b/pkg/eigenState/eigenState.go @@ -6,6 +6,7 @@ import ( "github.com/Layr-Labs/sidecar/pkg/eigenState/disabledDistributionRoots" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorAVSSplits" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorDirectedRewardSubmissions" + "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorPISplits" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorShares" "github.com/Layr-Labs/sidecar/pkg/eigenState/rewardSubmissions" "github.com/Layr-Labs/sidecar/pkg/eigenState/stakerDelegations" @@ -58,5 +59,9 @@ func LoadEigenStateModels( l.Sugar().Errorw("Failed to create OperatorAVSSplitModel", zap.Error(err)) return err } + if _, err := operatorPISplits.NewOperatorPISplitModel(sm, grm, l, cfg); err != nil { + l.Sugar().Errorw("Failed to create OperatorPISplitModel", zap.Error(err)) + return err + } return nil } diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits.go b/pkg/eigenState/operatorPISplits/operatorPISplits.go new file mode 100644 index 0000000..adb78a0 --- /dev/null +++ b/pkg/eigenState/operatorPISplits/operatorPISplits.go @@ -0,0 +1,277 @@ +package operatorPISplits + +import ( + "encoding/json" + "fmt" + "slices" + "sort" + "strings" + "time" + + "github.com/Layr-Labs/sidecar/pkg/storage" + "github.com/Layr-Labs/sidecar/pkg/utils" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/pkg/eigenState/base" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/sidecar/pkg/eigenState/types" + "go.uber.org/zap" + "golang.org/x/xerrors" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type OperatorPISplit struct { + Operator string + ActivatedAt *time.Time + OldOperatorAVSSplitBips uint64 + NewOperatorAVSSplitBips uint64 + BlockNumber uint64 + TransactionHash string + LogIndex uint64 +} + +type OperatorPISplitModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[[]*OperatorPISplit] + DB *gorm.DB + Network config.Network + Environment config.Environment + logger *zap.Logger + globalConfig *config.Config + + // Accumulates state changes for SlotIds, grouped by block number + stateAccumulator map[uint64]map[types.SlotID]*OperatorPISplit +} + +func NewOperatorPISplitModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) (*OperatorPISplitModel, error) { + model := &OperatorPISplitModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + DB: grm, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorPISplit), + } + + esm.RegisterState(model, 9) + return model, nil +} + +func (ops *OperatorPISplitModel) GetModelName() string { + return "OperatorPISplitModel" +} + +type operatorPISplitOutputData struct { + ActivatedAt uint64 `json:"activatedAt"` + OldOperatorAVSSplitBips uint64 `json:"oldOperatorAVSSplitBips"` + NewOperatorAVSSplitBips uint64 `json:"newOperatorAVSSplitBips"` +} + +func parseOperatorPISplitOutputData(outputDataStr string) (*operatorPISplitOutputData, error) { + outputData := &operatorPISplitOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + + return outputData, err +} + +func (ops *OperatorPISplitModel) handleOperatorPISplitBipsSetEvent(log *storage.TransactionLog) (*OperatorPISplit, error) { + arguments, err := ops.ParseLogArguments(log) + if err != nil { + return nil, err + } + + outputData, err := parseOperatorPISplitOutputData(log.OutputData) + if err != nil { + return nil, err + } + + activatedAt := time.Unix(int64(outputData.ActivatedAt), 0) + + split := &OperatorPISplit{ + Operator: strings.ToLower(arguments[1].Value.(string)), + ActivatedAt: &activatedAt, + OldOperatorAVSSplitBips: outputData.OldOperatorAVSSplitBips, + NewOperatorAVSSplitBips: outputData.NewOperatorAVSSplitBips, + BlockNumber: log.BlockNumber, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, + } + + return split, nil +} + +func (ops *OperatorPISplitModel) GetStateTransitions() (types.StateTransitions[*OperatorPISplit], []uint64) { + stateChanges := make(types.StateTransitions[*OperatorPISplit]) + + stateChanges[0] = func(log *storage.TransactionLog) (*OperatorPISplit, error) { + operatorPISplit, err := ops.handleOperatorPISplitBipsSetEvent(log) + if err != nil { + return nil, err + } + + slotId := base.NewSlotID(operatorPISplit.TransactionHash, operatorPISplit.LogIndex) + + _, ok := ops.stateAccumulator[log.BlockNumber][slotId] + if ok { + fmt.Printf("Submissions: %+v\n", ops.stateAccumulator[log.BlockNumber]) + err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) + ops.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) + return nil, err + } + + ops.stateAccumulator[log.BlockNumber][slotId] = operatorPISplit + + return operatorPISplit, nil + } + + // Create an ordered list of block numbers + blockNumbers := make([]uint64, 0) + for blockNumber := range stateChanges { + blockNumbers = append(blockNumbers, blockNumber) + } + sort.Slice(blockNumbers, func(i, j int) bool { + return blockNumbers[i] < blockNumbers[j] + }) + slices.Reverse(blockNumbers) + + return stateChanges, blockNumbers +} + +func (ops *OperatorPISplitModel) getContractAddressesForEnvironment() map[string][]string { + contracts := ops.globalConfig.GetContractsMapForChain() + return map[string][]string{ + contracts.RewardsCoordinator: { + "OperatorPISplitBipsSet", + }, + } +} + +func (ops *OperatorPISplitModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := ops.getContractAddressesForEnvironment() + return ops.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (ops *OperatorPISplitModel) SetupStateForBlock(blockNumber uint64) error { + ops.stateAccumulator[blockNumber] = make(map[types.SlotID]*OperatorPISplit) + return nil +} + +func (ops *OperatorPISplitModel) CleanupProcessedStateForBlock(blockNumber uint64) error { + delete(ops.stateAccumulator, blockNumber) + return nil +} + +func (ops *OperatorPISplitModel) HandleStateChange(log *storage.TransactionLog) (interface{}, error) { + stateChanges, sortedBlockNumbers := ops.GetStateTransitions() + + for _, blockNumber := range sortedBlockNumbers { + if log.BlockNumber >= blockNumber { + ops.logger.Sugar().Debugw("Handling state change", zap.Uint64("blockNumber", log.BlockNumber)) + + change, err := stateChanges[blockNumber](log) + if err != nil { + return nil, err + } + if change == nil { + return nil, nil + } + return change, nil + } + } + return nil, nil +} + +// prepareState prepares the state for commit by adding the new state to the existing state. +func (ops *OperatorPISplitModel) prepareState(blockNumber uint64) ([]*OperatorPISplit, error) { + accumulatedState, ok := ops.stateAccumulator[blockNumber] + if !ok { + err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) + ops.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, err + } + + recordsToInsert := make([]*OperatorPISplit, 0) + for _, split := range accumulatedState { + recordsToInsert = append(recordsToInsert, split) + } + return recordsToInsert, nil +} + +// CommitFinalState commits the final state for the given block number. +func (ops *OperatorPISplitModel) CommitFinalState(blockNumber uint64) error { + recordsToInsert, err := ops.prepareState(blockNumber) + if err != nil { + return err + } + + if len(recordsToInsert) > 0 { + for _, record := range recordsToInsert { + res := ops.DB.Model(&OperatorPISplit{}).Clauses(clause.Returning{}).Create(&record) + if res.Error != nil { + ops.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) + return res.Error + } + } + } + return nil +} + +// GenerateStateRoot generates the state root for the given block number using the results of the state changes. +func (ops *OperatorPISplitModel) GenerateStateRoot(blockNumber uint64) (types.StateRoot, error) { + inserts, err := ops.prepareState(blockNumber) + if err != nil { + return "", err + } + + inputs := ops.sortValuesForMerkleTree(inserts) + + if len(inputs) == 0 { + return "", nil + } + + fullTree, err := ops.MerkleizeState(blockNumber, inputs) + if err != nil { + ops.logger.Sugar().Errorw("Failed to create merkle tree", + zap.Error(err), + zap.Uint64("blockNumber", blockNumber), + zap.Any("inputs", inputs), + ) + return "", err + } + return types.StateRoot(utils.ConvertBytesToString(fullTree.Root())), 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 := "added" + inputs = append(inputs, &base.MerkleTreeInput{ + SlotID: slotID, + Value: []byte(value), + }) + } + + slices.SortFunc(inputs, func(i, j *base.MerkleTreeInput) int { + return strings.Compare(string(i.SlotID), string(j.SlotID)) + }) + + return inputs +} + +func (ops *OperatorPISplitModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return ops.BaseEigenState.DeleteState("operator_pi_splits", startBlockNumber, endBlockNumber, ops.DB) +} diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits_test.go b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go new file mode 100644 index 0000000..d9e6b33 --- /dev/null +++ b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go @@ -0,0 +1,142 @@ +package operatorPISplits + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/storage" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/internal/logger" + "github.com/Layr-Labs/sidecar/internal/tests" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func setup() ( + string, + *gorm.DB, + *zap.Logger, + *config.Config, + error, +) { + cfg := config.NewConfig() + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: true}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, grm, l, cfg, nil +} + +func teardown(model *OperatorPISplitModel) { + queries := []string{ + `truncate table operator_pi_splits`, + `truncate table blocks cascade`, + } + for _, query := range queries { + res := model.DB.Exec(query) + if res.Error != nil { + fmt.Printf("Failed to run query: %v\n", res.Error) + } + } +} + +func createBlock(model *OperatorPISplitModel, blockNumber uint64) error { + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * time.Duration(blockNumber)), + } + res := model.DB.Model(&storage.Block{}).Create(block) + if res.Error != nil { + return res.Error + } + return nil +} + +func Test_OperatorPISplit(t *testing.T) { + _, grm, l, cfg, err := setup() + + if err != nil { + t.Fatal(err) + } + + t.Run("Test each event type", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + + model, err := NewOperatorPISplitModel(esm, grm, l, cfg) + + t.Run("Handle an operator pi split", func(t *testing.T) { + blockNumber := uint64(102) + + if err := createBlock(model, blockNumber); err != nil { + t.Fatal(err) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForChain().RewardsCoordinator, + Arguments: `[{"Name": "caller", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "operator", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "activatedAt", "Type": "uint32", "Value": 1725494400, "Indexed": false}, {"Name": "oldOperatorAVSSplitBips", "Type": "uint16", "Value": 1000, "Indexed": false}, {"Name": "newOperatorAVSSplitBips", "Type": "uint16", "Value": 2000, "Indexed": false}]`, + EventName: "OperatorPISplitBipsSet", + LogIndex: big.NewInt(12).Uint64(), + OutputData: `{"activatedAt": 1725494400, "oldOperatorAVSSplitBips": 1000, "newOperatorAVSSplitBips": 2000}`, + } + + err = model.SetupStateForBlock(blockNumber) + assert.Nil(t, err) + + isInteresting := model.IsInterestingLog(log) + assert.True(t, isInteresting) + + change, err := model.HandleStateChange(log) + assert.Nil(t, err) + assert.NotNil(t, change) + + split := change.(*OperatorPISplit) + + assert.Equal(t, strings.ToLower("0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101"), strings.ToLower(split.Operator)) + assert.Equal(t, int64(1725494400), split.ActivatedAt.Unix()) + assert.Equal(t, uint64(1000), split.OldOperatorAVSSplitBips) + assert.Equal(t, uint64(2000), split.NewOperatorAVSSplitBips) + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + splits := make([]*OperatorPISplit, 0) + query := `select * from operator_pi_splits where block_number = ?` + res := model.DB.Raw(query, blockNumber).Scan(&splits) + assert.Nil(t, res.Error) + assert.Equal(t, 1, len(splits)) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + // postgres.TeardownTestDatabase(dbName, cfg, grm, l) + }) +} From 4aa0730ee010893c722ae440425312224547dcdc Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Wed, 20 Nov 2024 11:04:51 -0500 Subject: [PATCH 18/21] removed unnecessary printf --- pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go | 6 ++---- .../operatorDirectedRewardSubmissions.go | 5 ++--- pkg/eigenState/operatorPISplits/operatorPISplits.go | 6 ++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go index 8115d2f..91cf9c4 100644 --- a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go +++ b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go @@ -2,7 +2,6 @@ package operatorAVSSplits import ( "encoding/json" - "fmt" "slices" "sort" "strings" @@ -128,9 +127,8 @@ func (oas *OperatorAVSSplitModel) GetStateTransitions() (types.StateTransitions[ _, ok := oas.stateAccumulator[log.BlockNumber][slotId] if ok { - fmt.Printf("Submissions: %+v\n", oas.stateAccumulator[log.BlockNumber]) - err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) - oas.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) + err := xerrors.Errorf("Duplicate operator AVS split submitted for slot %s at block %d", slotId, log.BlockNumber) + oas.logger.Sugar().Errorw("Duplicate operator AVS split submitted", zap.Error(err)) return nil, err } diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index cd8eaf4..26ed291 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -182,9 +182,8 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types _, ok := odrs.stateAccumulator[log.BlockNumber][slotId] if ok { - fmt.Printf("Submissions: %+v\n", odrs.stateAccumulator[log.BlockNumber]) - err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) - odrs.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) + err := xerrors.Errorf("Duplicate operator directed reward submission submitted for slot %s at block %d", slotId, log.BlockNumber) + odrs.logger.Sugar().Errorw("Duplicate operator directed reward submission submitted", zap.Error(err)) return nil, err } diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits.go b/pkg/eigenState/operatorPISplits/operatorPISplits.go index adb78a0..b862e06 100644 --- a/pkg/eigenState/operatorPISplits/operatorPISplits.go +++ b/pkg/eigenState/operatorPISplits/operatorPISplits.go @@ -2,7 +2,6 @@ package operatorPISplits import ( "encoding/json" - "fmt" "slices" "sort" "strings" @@ -126,9 +125,8 @@ func (ops *OperatorPISplitModel) GetStateTransitions() (types.StateTransitions[* _, ok := ops.stateAccumulator[log.BlockNumber][slotId] if ok { - fmt.Printf("Submissions: %+v\n", ops.stateAccumulator[log.BlockNumber]) - err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) - ops.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) + err := xerrors.Errorf("Duplicate operator PI split submitted for slot %s at block %d", slotId, log.BlockNumber) + ops.logger.Sugar().Errorw("Duplicate operator PI split submitted", zap.Error(err)) return nil, err } From 881466f3616a932e30e46195a683afeefe0a9912 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Wed, 20 Nov 2024 11:10:38 -0500 Subject: [PATCH 19/21] test database teardown --- pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go | 4 ++-- .../operatorDirectedRewardSubmissions_test.go | 4 ++-- pkg/eigenState/operatorPISplits/operatorPISplits_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go index ae0d0d7..f7dd2fe 100644 --- a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go +++ b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go @@ -66,7 +66,7 @@ func createBlock(model *OperatorAVSSplitModel, blockNumber uint64) error { } func Test_OperatorAVSSplit(t *testing.T) { - _, grm, l, cfg, err := setup() + dbName, grm, l, cfg, err := setup() if err != nil { t.Fatal(err) @@ -138,6 +138,6 @@ func Test_OperatorAVSSplit(t *testing.T) { }) t.Cleanup(func() { - // postgres.TeardownTestDatabase(dbName, cfg, grm, l) + postgres.TeardownTestDatabase(dbName, cfg, grm, l) }) } diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go index 8021e6b..6081e2a 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go @@ -66,7 +66,7 @@ func createBlock(model *OperatorDirectedRewardSubmissionsModel, blockNumber uint } func Test_OperatorDirectedRewardSubmissions(t *testing.T) { - _, grm, l, cfg, err := setup() + dbName, grm, l, cfg, err := setup() if err != nil { t.Fatal(err) @@ -168,6 +168,6 @@ func Test_OperatorDirectedRewardSubmissions(t *testing.T) { }) t.Cleanup(func() { - // postgres.TeardownTestDatabase(dbName, cfg, grm, l) + postgres.TeardownTestDatabase(dbName, cfg, grm, l) }) } diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits_test.go b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go index d9e6b33..97672bb 100644 --- a/pkg/eigenState/operatorPISplits/operatorPISplits_test.go +++ b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go @@ -66,7 +66,7 @@ func createBlock(model *OperatorPISplitModel, blockNumber uint64) error { } func Test_OperatorPISplit(t *testing.T) { - _, grm, l, cfg, err := setup() + dbName, grm, l, cfg, err := setup() if err != nil { t.Fatal(err) @@ -137,6 +137,6 @@ func Test_OperatorPISplit(t *testing.T) { }) t.Cleanup(func() { - // postgres.TeardownTestDatabase(dbName, cfg, grm, l) + postgres.TeardownTestDatabase(dbName, cfg, grm, l) }) } From bec2ccb4dd430e426f16fbd98a8af08b68d4311e Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Wed, 20 Nov 2024 11:45:40 -0500 Subject: [PATCH 20/21] updated merkle leaf value --- pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go | 3 ++- .../operatorDirectedRewardSubmissions.go | 2 +- pkg/eigenState/operatorPISplits/operatorPISplits.go | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go index 91cf9c4..ddaa802 100644 --- a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go +++ b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go @@ -2,6 +2,7 @@ package operatorAVSSplits import ( "encoding/json" + "fmt" "slices" "sort" "strings" @@ -258,7 +259,7 @@ func (oas *OperatorAVSSplitModel) sortValuesForMerkleTree(splits []*OperatorAVSS inputs := make([]*base.MerkleTreeInput, 0) for _, split := range splits { slotID := base.NewSlotID(split.TransactionHash, split.LogIndex) - value := "added" + value := fmt.Sprintf("%s_%s_%d_%d_%d", split.Operator, split.Avs, split.ActivatedAt.Unix(), split.OldOperatorAVSSplitBips, split.NewOperatorAVSSplitBips) inputs = append(inputs, &base.MerkleTreeInput{ SlotID: slotID, Value: []byte(value), diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go index 26ed291..9b8757f 100644 --- a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -314,7 +314,7 @@ func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(subm inputs := make([]*base.MerkleTreeInput, 0) for _, submission := range submissions { slotID := NewSlotID(submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex, submission.OperatorIndex) - value := "added" + value := fmt.Sprintf("%s_%s_%s_%s_%s", submission.RewardHash, submission.Strategy, submission.Multiplier, submission.Operator, submission.Amount) inputs = append(inputs, &base.MerkleTreeInput{ SlotID: slotID, Value: []byte(value), diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits.go b/pkg/eigenState/operatorPISplits/operatorPISplits.go index b862e06..baea18b 100644 --- a/pkg/eigenState/operatorPISplits/operatorPISplits.go +++ b/pkg/eigenState/operatorPISplits/operatorPISplits.go @@ -2,6 +2,7 @@ package operatorPISplits import ( "encoding/json" + "fmt" "slices" "sort" "strings" @@ -256,7 +257,7 @@ func (ops *OperatorPISplitModel) sortValuesForMerkleTree(splits []*OperatorPISpl inputs := make([]*base.MerkleTreeInput, 0) for _, split := range splits { slotID := base.NewSlotID(split.TransactionHash, split.LogIndex) - value := "added" + value := fmt.Sprintf("%s_%d_%d_%d", split.Operator, split.ActivatedAt.Unix(), split.OldOperatorAVSSplitBips, split.NewOperatorAVSSplitBips) inputs = append(inputs, &base.MerkleTreeInput{ SlotID: slotID, Value: []byte(value), From 873aeebecdaaaf8326e403957bf128a10f8c5617 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Wed, 20 Nov 2024 12:17:03 -0500 Subject: [PATCH 21/21] cleanup migrations --- .../up.go | 3 +- .../202411191550_operatorAVSSplits/up.go | 3 +- .../202411191708_operatorPISplits/up.go | 3 +- .../up.go | 32 ------------------- pkg/postgres/migrations/migrator.go | 2 -- 5 files changed, 6 insertions(+), 37 deletions(-) delete mode 100644 pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go diff --git a/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go b/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go index 1aea82d..48022de 100644 --- a/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go +++ b/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go @@ -27,7 +27,8 @@ func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { block_number bigint not null, transaction_hash varchar not null, log_index bigint not null, - unique(transaction_hash, log_index, block_number, reward_hash, strategy_index, operator_index) + unique(transaction_hash, log_index, block_number, reward_hash, strategy_index, operator_index), + CONSTRAINT operator_directed_reward_submissions_block_number_fkey FOREIGN KEY (block_number) REFERENCES blocks(number) ON DELETE CASCADE ); ` if err := grm.Exec(query).Error; err != nil { diff --git a/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go b/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go index 850dd89..15261d8 100644 --- a/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go +++ b/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go @@ -20,7 +20,8 @@ func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { block_number bigint not null, transaction_hash varchar not null, log_index bigint not null, - unique(transaction_hash, log_index, block_number) + unique(transaction_hash, log_index, block_number), + CONSTRAINT operator_avs_splits_block_number_fkey FOREIGN KEY (block_number) REFERENCES blocks(number) ON DELETE CASCADE ); ` if err := grm.Exec(query).Error; err != nil { diff --git a/pkg/postgres/migrations/202411191708_operatorPISplits/up.go b/pkg/postgres/migrations/202411191708_operatorPISplits/up.go index cbeb04b..a03d414 100644 --- a/pkg/postgres/migrations/202411191708_operatorPISplits/up.go +++ b/pkg/postgres/migrations/202411191708_operatorPISplits/up.go @@ -19,7 +19,8 @@ func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { block_number bigint not null, transaction_hash varchar not null, log_index bigint not null, - unique(transaction_hash, log_index, block_number) + unique(transaction_hash, log_index, block_number), + CONSTRAINT operator_pi_splits_block_number_fkey FOREIGN KEY (block_number) REFERENCES blocks(number) ON DELETE CASCADE ); ` if err := grm.Exec(query).Error; err != nil { diff --git a/pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go b/pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go deleted file mode 100644 index ccc9233..0000000 --- a/pkg/postgres/migrations/202411191710_blockNumberFkConstraint/up.go +++ /dev/null @@ -1,32 +0,0 @@ -package _202411191710_blockNumberFkConstraint - -import ( - "database/sql" - "fmt" - - "gorm.io/gorm" -) - -type Migration struct { -} - -func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error { - queries := []string{ - `alter table operator_directed_reward_submissions add constraint operator_directed_reward_submissions_block_number_fkey foreign key (block_number) references blocks (number) on delete cascade`, - `alter table operator_avs_splits add constraint operator_avs_splits_block_number_fkey foreign key (block_number) references blocks (number) on delete cascade`, - `alter table operator_pi_splits add constraint operator_pi_splits_block_number_fkey foreign key (block_number) references blocks (number) on delete cascade`, - } - - for _, query := range queries { - _, err := db.Exec(query) - if err != nil { - fmt.Printf("Failed to run migration query: %s - %+v\n", query, err) - return err - } - } - return nil -} - -func (m *Migration) GetName() string { - return "202411191710_blockNumberFkConstraint" -} diff --git a/pkg/postgres/migrations/migrator.go b/pkg/postgres/migrations/migrator.go index 05445ce..ab7a197 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -37,7 +37,6 @@ import ( _202411151931_operatorDirectedRewardSubmissions "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions" _202411191550_operatorAVSSplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191550_operatorAVSSplits" _202411191708_operatorPISplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191708_operatorPISplits" - _202411191710_blockNumberFkConstraint "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191710_blockNumberFkConstraint" _202411191947_cleanupUnusedTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191947_cleanupUnusedTables" "go.uber.org/zap" @@ -112,7 +111,6 @@ func (m *Migrator) MigrateAll() error { &_202411151931_operatorDirectedRewardSubmissions.Migration{}, &_202411191550_operatorAVSSplits.Migration{}, &_202411191708_operatorPISplits.Migration{}, - &_202411191710_blockNumberFkConstraint.Migration{}, &_202411191947_cleanupUnusedTables.Migration{}, }