From 7caa2ed1486e73fea0abfdb99c8661b0692e8b9f Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Tue, 10 Sep 2024 15:22:14 -0500 Subject: [PATCH 1/3] Add support for all reward submission types --- .../rewardSubmissions/rewardSubmissions.go | 473 ++++++++++++++++++ .../rewardSubmissions_test.go | 1 + 2 files changed, 474 insertions(+) create mode 100644 internal/eigenState/rewardSubmissions/rewardSubmissions.go create mode 100644 internal/eigenState/rewardSubmissions/rewardSubmissions_test.go diff --git a/internal/eigenState/rewardSubmissions/rewardSubmissions.go b/internal/eigenState/rewardSubmissions/rewardSubmissions.go new file mode 100644 index 00000000..ba42ac3f --- /dev/null +++ b/internal/eigenState/rewardSubmissions/rewardSubmissions.go @@ -0,0 +1,473 @@ +package rewardSubmissions + +import ( + "database/sql" + "encoding/json" + "fmt" + "github.com/Layr-Labs/go-sidecar/internal/config" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/base" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/stateManager" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/types" + "github.com/Layr-Labs/go-sidecar/internal/storage" + "github.com/Layr-Labs/go-sidecar/internal/utils" + "github.com/wealdtech/go-merkletree/v2" + "github.com/wealdtech/go-merkletree/v2/keccak256" + orderedmap "github.com/wk8/go-ordered-map/v2" + "go.uber.org/zap" + "golang.org/x/xerrors" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "slices" + "sort" + "strings" +) + +type RewardSubmission struct { + Avs string + RewardHash string + Token string + Amount string + Strategy string + StrategyIndex uint64 + Multiplier string `gorm:"type:numeric"` + StartTimestamp uint64 `gorm:"type:DATETIME"` + EndTimestamp uint64 `gorm:"type:DATETIME"` + Duration uint64 + BlockNumber uint64 + IsForAll bool +} + +type RewardSubmissionDiff struct { + RewardSubmission *RewardSubmission + IsNew bool + IsNoLongerActive bool +} + +type RewardSubmissions struct { + Submissions []*RewardSubmission +} + +type SlotId string + +func NewSlotId(rewardHash string, strategy string) SlotId { + return SlotId(fmt.Sprintf("%s_%s", rewardHash, strategy)) +} + +type RewardSubmissionsModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[RewardSubmissions] + 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[SlotId]*RewardSubmission +} + +func NewRewardSubmissionsModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + Network config.Network, + Environment config.Environment, + logger *zap.Logger, + globalConfig *config.Config, +) (*RewardSubmissionsModel, error) { + model := &RewardSubmissionsModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + Db: grm, + Network: Network, + Environment: Environment, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[SlotId]*RewardSubmission), + } + + esm.RegisterState(model, 4) + return model, nil +} + +func (rs *RewardSubmissionsModel) GetModelName() string { + return "RewardSubmissionsModel" +} + +type genericRewardPaymentData struct { + Token string + Amount json.Number + StartTimestamp uint64 + Duration uint64 + StrategiesAndMultipliers []struct { + Strategy string + Multiplier json.Number + } `json:"strategiesAndMultipliers"` +} + +type rewardSubmissionOutputData struct { + RewardSubmission *genericRewardPaymentData `json:"rewardSubmission"` + 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 (rs *RewardSubmissionsModel) handleRewardSubmissionCreatedEvent(log *storage.TransactionLog) (*RewardSubmissions, error) { + arguments, err := rs.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.RewardSubmission + } + + rewardSubmissions := make([]*RewardSubmission, 0) + + for _, strategyAndMultiplier := range actualOuputData.StrategiesAndMultipliers { + rewardSubmission := &RewardSubmission{ + Avs: arguments[0].Value.(string), + RewardHash: arguments[2].Value.(string), + Token: actualOuputData.Token, + Amount: actualOuputData.Amount.String(), + Strategy: strategyAndMultiplier.Strategy, + Multiplier: strategyAndMultiplier.Multiplier.String(), + StartTimestamp: actualOuputData.StartTimestamp, + EndTimestamp: actualOuputData.StartTimestamp + actualOuputData.Duration, + Duration: actualOuputData.Duration, + BlockNumber: log.BlockNumber, + IsForAll: log.EventName == "RewardsSubmissionForAllCreated" || log.EventName == "RangePaymentForAllCreated", + } + rewardSubmissions = append(rewardSubmissions, rewardSubmission) + } + + return &RewardSubmissions{Submissions: rewardSubmissions}, nil +} + +func (rs *RewardSubmissionsModel) GetStateTransitions() (types.StateTransitions[RewardSubmissions], []uint64) { + stateChanges := make(types.StateTransitions[RewardSubmissions]) + + stateChanges[0] = func(log *storage.TransactionLog) (*RewardSubmissions, error) { + rewardSubmissions, err := rs.handleRewardSubmissionCreatedEvent(log) + if err != nil { + return nil, err + } + + for _, rewardSubmission := range rewardSubmissions.Submissions { + slotId := NewSlotId(rewardSubmission.RewardHash, rewardSubmission.Strategy) + + record, ok := rs.stateAccumulator[log.BlockNumber][slotId] + if ok { + err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) + rs.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) + return nil, err + } + + rs.stateAccumulator[log.BlockNumber][slotId] = record + } + + 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 (rs *RewardSubmissionsModel) getContractAddressesForEnvironment() map[string][]string { + contracts := rs.globalConfig.GetContractsMapForEnvAndNetwork() + return map[string][]string{ + contracts.RewardsCoordinator: []string{ + "RangePaymentForAllCreated", + "RewardsSubmissionForAllCreated", + "RangePaymentCreated", + "AVSRewardsSubmissionCreated", + }, + } +} + +func (rs *RewardSubmissionsModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := rs.getContractAddressesForEnvironment() + return rs.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (rs *RewardSubmissionsModel) InitBlockProcessing(blockNumber uint64) error { + rs.stateAccumulator[blockNumber] = make(map[SlotId]*RewardSubmission) + return nil +} + +func (rs *RewardSubmissionsModel) HandleStateChange(log *storage.TransactionLog) (interface{}, error) { + stateChanges, sortedBlockNumbers := rs.GetStateTransitions() + + for _, blockNumber := range sortedBlockNumbers { + if log.BlockNumber >= blockNumber { + rs.logger.Sugar().Debugw("Handling state change", zap.Uint64("blockNumber", blockNumber)) + + change, err := stateChanges[blockNumber](log) + if err != nil { + return nil, err + } + if change == nil { + return nil, nil + } + return change, nil + } + } + return nil, nil +} + +func (rs *RewardSubmissionsModel) clonePreviousBlocksToNewBlock(blockNumber uint64) error { + query := ` + insert into reward_submissions(avs, reward_hash, token, amount, strategy, strategy_index, multiplier, start_timestamp, end_timestamp, duration, block_number, is_for_all) + select + avs, + reward_hash, + token, + amount, + strategy, + strategy_index, + multiplier, + start_timestamp, + end_timestamp, + duration, + is_for_all, + @currentBlock as block_number, + from reward_submissions + where block_number = @previousBlock + ` + res := rs.Db.Exec(query, + sql.Named("currentBlock", blockNumber), + sql.Named("previousBlock", blockNumber-1), + ) + + if res.Error != nil { + rs.logger.Sugar().Errorw("Failed to clone previous block state to new block", zap.Error(res.Error)) + return res.Error + } + return nil +} + +// prepareState prepares the state for commit by adding the new state to the existing state +func (rs *RewardSubmissionsModel) prepareState(blockNumber uint64) ([]*RewardSubmissionDiff, []*RewardSubmissionDiff, error) { + accumulatedState, ok := rs.stateAccumulator[blockNumber] + if !ok { + err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) + rs.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, nil, err + } + + currentBlock := &storage.Block{} + err := rs.Db.Where("number = ?", blockNumber).First(currentBlock).Error + if err != nil { + rs.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{ + RewardSubmission: change, + IsNew: true, + }) + } + + // find all the records that are no longer active + noLongerActiveSubmissions := make([]*RewardSubmission, 0) + query := ` + select + * + from reward_submissions + where + block_number = @previousBlock + and end_timestamp <= @blockTime + ` + res := rs.Db. + Model(&RewardSubmission{}). + Raw(query, + sql.Named("previousBlock", blockNumber-1), + sql.Named("blockTime", currentBlock.BlockTime.Unix()), + ). + Find(&noLongerActiveSubmissions) + + if res.Error != nil { + rs.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{ + RewardSubmission: submission, + IsNoLongerActive: true, + }) + } + return inserts, deletes, nil +} + +// CommitFinalState commits the final state for the given block number +func (rs *RewardSubmissionsModel) CommitFinalState(blockNumber uint64) error { + err := rs.clonePreviousBlocksToNewBlock(blockNumber) + if err != nil { + return err + } + + recordsToInsert, recordsToDelete, err := rs.prepareState(blockNumber) + if err != nil { + return err + } + + for _, record := range recordsToDelete { + res := rs.Db.Delete(&RewardSubmission{}, "reward_hash = ? and strategy = ? and block_number = ?", record.RewardSubmission.RewardHash, record.RewardSubmission.Strategy, blockNumber) + if res.Error != nil { + rs.logger.Sugar().Errorw("Failed to delete record", + zap.Error(res.Error), + zap.String("rewardHash", record.RewardSubmission.RewardHash), + zap.String("strategy", record.RewardSubmission.Strategy), + zap.Uint64("blockNumber", blockNumber), + ) + return res.Error + } + } + if len(recordsToInsert) > 0 { + records := make([]RewardSubmission, 0) + for _, record := range recordsToInsert { + records = append(records, *record.RewardSubmission) + } + res := rs.Db.Model(&RewardSubmission{}).Clauses(clause.Returning{}).Create(&records) + if res.Error != nil { + rs.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) + return res.Error + } + } + return nil +} + +func (rs *RewardSubmissionsModel) ClearAccumulatedState(blockNumber uint64) error { + delete(rs.stateAccumulator, blockNumber) + return nil +} + +// GenerateStateRoot generates the state root for the given block number using the results of the state changes +func (rs *RewardSubmissionsModel) GenerateStateRoot(blockNumber uint64) (types.StateRoot, error) { + inserts, deletes, err := rs.prepareState(blockNumber) + if err != nil { + return "", err + } + + combinedResults := make([]*RewardSubmissionDiff, 0) + for _, record := range inserts { + combinedResults = append(combinedResults, record) + } + for _, record := range deletes { + combinedResults = append(combinedResults, record) + } + + fullTree, err := rs.merkelizeState(blockNumber, combinedResults) + if err != nil { + return "", err + } + return types.StateRoot(utils.ConvertBytesToString(fullTree.Root())), nil +} + +func (rs *RewardSubmissionsModel) merkelizeState(blockNumber uint64, rewardSubmissions []*RewardSubmissionDiff) (*merkletree.MerkleTree, error) { + // Avs -> slot_id -> string (added/removed) + om := orderedmap.New[string, *orderedmap.OrderedMap[SlotId, string]]() + + for _, result := range rewardSubmissions { + existingAvs, found := om.Get(result.RewardSubmission.Avs) + if !found { + existingAvs = orderedmap.New[SlotId, string]() + om.Set(result.RewardSubmission.Avs, existingAvs) + + prev := om.GetPair(result.RewardSubmission.Avs).Prev() + if prev != nil && strings.Compare(prev.Key, result.RewardSubmission.Avs) >= 0 { + om.Delete(result.RewardSubmission.Avs) + return nil, fmt.Errorf("avs not in order") + } + } + slotId := NewSlotId(result.RewardSubmission.RewardHash, result.RewardSubmission.Strategy) + var state string + if result.IsNew { + state = "added" + } else if result.IsNoLongerActive { + state = "removed" + } else { + return nil, fmt.Errorf("invalid state change") + } + existingAvs.Set(slotId, state) + + prev := existingAvs.GetPair(slotId).Prev() + if prev != nil && strings.Compare(string(prev.Key), string(slotId)) >= 0 { + existingAvs.Delete(slotId) + return nil, fmt.Errorf("operator not in order") + } + } + + avsLeaves := rs.InitializeMerkleTreeBaseStateWithBlock(blockNumber) + + for avs := om.Oldest(); avs != nil; avs = avs.Next() { + submissionLeafs := make([][]byte, 0) + for submission := avs.Value.Oldest(); submission != nil; submission = submission.Next() { + slotId := submission.Key + state := submission.Value + submissionLeafs = append(submissionLeafs, encodeSubmissionLeaf(slotId, state)) + } + + avsTree, err := merkletree.NewTree( + merkletree.WithData(submissionLeafs), + merkletree.WithHashType(keccak256.New()), + ) + if err != nil { + return nil, err + } + + avsLeaves = append(avsLeaves, encodeAvsLeaf(avs.Key, avsTree.Root())) + } + + return merkletree.NewTree( + merkletree.WithData(avsLeaves), + merkletree.WithHashType(keccak256.New()), + ) +} + +func encodeSubmissionLeaf(slotId SlotId, state string) []byte { + return []byte(fmt.Sprintf("%s:%s", slotId, state)) +} + +func encodeAvsLeaf(avs string, avsSubmissionRoot []byte) []byte { + return append([]byte(avs), avsSubmissionRoot[:]...) +} + +func (rs *RewardSubmissionsModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return rs.BaseEigenState.DeleteState("registered_avs_operators", startBlockNumber, endBlockNumber, rs.Db) +} diff --git a/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go b/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go new file mode 100644 index 00000000..b426ca14 --- /dev/null +++ b/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go @@ -0,0 +1 @@ +package rewardSubmissions From cc24f4f297ba0b4acfd340cb3b32c5cc75cd07e2 Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Tue, 10 Sep 2024 15:58:27 -0500 Subject: [PATCH 2/3] Add test for RangeSubmission --- .../rewardSubmissions/rewardSubmissions.go | 38 +++-- .../rewardSubmissions_test.go | 150 ++++++++++++++++++ .../submittedDistributionRoots_test.go | 2 - .../202409101540_rewardSubmissions/up.go | 36 +++++ internal/sqlite/migrations/migrator.go | 2 + 5 files changed, 215 insertions(+), 13 deletions(-) create mode 100644 internal/sqlite/migrations/202409101540_rewardSubmissions/up.go diff --git a/internal/eigenState/rewardSubmissions/rewardSubmissions.go b/internal/eigenState/rewardSubmissions/rewardSubmissions.go index ba42ac3f..2f563f75 100644 --- a/internal/eigenState/rewardSubmissions/rewardSubmissions.go +++ b/internal/eigenState/rewardSubmissions/rewardSubmissions.go @@ -20,6 +20,7 @@ import ( "slices" "sort" "strings" + "time" ) type RewardSubmission struct { @@ -29,9 +30,9 @@ type RewardSubmission struct { Amount string Strategy string StrategyIndex uint64 - Multiplier string `gorm:"type:numeric"` - StartTimestamp uint64 `gorm:"type:DATETIME"` - EndTimestamp uint64 `gorm:"type:DATETIME"` + Multiplier string `gorm:"type:numeric"` + StartTimestamp *time.Time `gorm:"type:DATETIME"` + EndTimestamp *time.Time `gorm:"type:DATETIME"` Duration uint64 BlockNumber uint64 IsForAll bool @@ -143,15 +144,19 @@ func (rs *RewardSubmissionsModel) handleRewardSubmissionCreatedEvent(log *storag rewardSubmissions := make([]*RewardSubmission, 0) for _, strategyAndMultiplier := range actualOuputData.StrategiesAndMultipliers { + + startTimestamp := time.Unix(int64(actualOuputData.StartTimestamp), 0) + endTimestamp := startTimestamp.Add(time.Duration(actualOuputData.Duration) * time.Second) + rewardSubmission := &RewardSubmission{ - Avs: arguments[0].Value.(string), - RewardHash: arguments[2].Value.(string), - Token: actualOuputData.Token, + Avs: strings.ToLower(arguments[0].Value.(string)), + RewardHash: strings.ToLower(arguments[2].Value.(string)), + Token: strings.ToLower(actualOuputData.Token), Amount: actualOuputData.Amount.String(), Strategy: strategyAndMultiplier.Strategy, Multiplier: strategyAndMultiplier.Multiplier.String(), - StartTimestamp: actualOuputData.StartTimestamp, - EndTimestamp: actualOuputData.StartTimestamp + actualOuputData.Duration, + StartTimestamp: &startTimestamp, + EndTimestamp: &endTimestamp, Duration: actualOuputData.Duration, BlockNumber: log.BlockNumber, IsForAll: log.EventName == "RewardsSubmissionForAllCreated" || log.EventName == "RangePaymentForAllCreated", @@ -174,14 +179,14 @@ func (rs *RewardSubmissionsModel) GetStateTransitions() (types.StateTransitions[ for _, rewardSubmission := range rewardSubmissions.Submissions { slotId := NewSlotId(rewardSubmission.RewardHash, rewardSubmission.Strategy) - record, ok := rs.stateAccumulator[log.BlockNumber][slotId] + _, ok := rs.stateAccumulator[log.BlockNumber][slotId] if ok { err := xerrors.Errorf("Duplicate distribution root submitted for slot %s at block %d", slotId, log.BlockNumber) rs.logger.Sugar().Errorw("Duplicate distribution root submitted", zap.Error(err)) return nil, err } - rs.stateAccumulator[log.BlockNumber][slotId] = record + rs.stateAccumulator[log.BlockNumber][slotId] = rewardSubmission } return rewardSubmissions, nil @@ -257,7 +262,7 @@ func (rs *RewardSubmissionsModel) clonePreviousBlocksToNewBlock(blockNumber uint end_timestamp, duration, is_for_all, - @currentBlock as block_number, + @currentBlock as block_number from reward_submissions where block_number = @previousBlock ` @@ -281,6 +286,7 @@ func (rs *RewardSubmissionsModel) prepareState(blockNumber uint64) ([]*RewardSub rs.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) return nil, nil, err } + fmt.Printf("Accumulated state: %v\n", accumulatedState) currentBlock := &storage.Block{} err := rs.Db.Where("number = ?", blockNumber).First(currentBlock).Error @@ -346,6 +352,9 @@ func (rs *RewardSubmissionsModel) CommitFinalState(blockNumber uint64) error { return err } + fmt.Printf("Records to insert: %v\n", recordsToInsert) + fmt.Printf("Records to delete: %v\n", recordsToDelete) + for _, record := range recordsToDelete { res := rs.Db.Delete(&RewardSubmission{}, "reward_hash = ? and strategy = ? and block_number = ?", record.RewardSubmission.RewardHash, record.RewardSubmission.Strategy, blockNumber) if res.Error != nil { @@ -403,6 +412,13 @@ func (rs *RewardSubmissionsModel) merkelizeState(blockNumber uint64, rewardSubmi // Avs -> slot_id -> string (added/removed) om := orderedmap.New[string, *orderedmap.OrderedMap[SlotId, string]]() + sort.Slice(rewardSubmissions, func(i, j int) bool { + if rewardSubmissions[i].RewardSubmission.Avs != rewardSubmissions[j].RewardSubmission.Avs { + return strings.Compare(rewardSubmissions[i].RewardSubmission.Avs, rewardSubmissions[j].RewardSubmission.Avs) < 0 + } + return strings.Compare(rewardSubmissions[i].RewardSubmission.Strategy, rewardSubmissions[j].RewardSubmission.Strategy) < 0 + }) + for _, result := range rewardSubmissions { existingAvs, found := om.Get(result.RewardSubmission.Avs) if !found { diff --git a/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go b/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go index b426ca14..2185f24d 100644 --- a/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go +++ b/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go @@ -1 +1,151 @@ package rewardSubmissions + +import ( + "github.com/Layr-Labs/go-sidecar/internal/config" + "github.com/Layr-Labs/go-sidecar/internal/eigenState/stateManager" + "github.com/Layr-Labs/go-sidecar/internal/logger" + "github.com/Layr-Labs/go-sidecar/internal/sqlite/migrations" + "github.com/Layr-Labs/go-sidecar/internal/storage" + "github.com/Layr-Labs/go-sidecar/internal/tests" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" + "math/big" + "strings" + "testing" + "time" +) + +func setup() ( + *config.Config, + *gorm.DB, + *zap.Logger, + error, +) { + cfg := tests.GetConfig() + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug}) + + db, err := tests.GetSqliteDatabaseConnection() + if err != nil { + panic(err) + } + sqliteMigrator := migrations.NewSqliteMigrator(db, l) + if err := sqliteMigrator.MigrateAll(); err != nil { + l.Sugar().Fatalw("Failed to migrate", "error", err) + } + + return cfg, db, l, err +} + +func teardown(model *RewardSubmissionsModel) { + queries := []string{ + `truncate table reward_submissions cascade`, + } + for _, query := range queries { + model.Db.Raw(query) + } +} + +func Test_RewardSubmissions(t *testing.T) { + cfg, grm, l, err := setup() + + if err != nil { + t.Fatal(err) + } + + esm := stateManager.NewEigenStateManager(l, grm) + + model, err := NewRewardSubmissionsModel(esm, grm, cfg.Network, cfg.Environment, l, cfg) + + t.Run("Handle a range payment submission", func(t *testing.T) { + blockNumber := uint64(100) + + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * 100), + } + res := model.Db.Model(&storage.Block{}).Create(block) + if res.Error != nil { + t.Fatal(res.Error) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().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.InitBlockProcessing(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 + }{ + {"0x3c28437e610fb099cc3d6de4d9c707dfacd308ae", "1000000000000000000"}, + {"0x3cb1fd19cfb178c1098f2fc1e11090a0642b2314", "2000000000000000000"}, + {"0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "3000000000000000000"}, + {"0x6dc6ce589f852f96ac86cb160ab0b15b9f56dedd", "4500000000000000000"}, + {"0x87f6c7d24b109919eb38295e3f8298425e6331d9", "500000000000000000"}, + {"0xd523267698c81a372191136e477fdebfa33d9fb4", "8000000000000000000"}, + {"0xdccf401fd121d8c542e96bc1d0078884422afad2", "5000000000000000000"}, + } + + typedChange := change.(*RewardSubmissions) + assert.Equal(t, len(strategiesAndMultipliers), len(typedChange.Submissions)) + + for i, submission := range typedChange.Submissions { + assert.Equal(t, strings.ToLower("0x00526A07855f743964F05CccAeCcf7a9E34847fF"), strings.ToLower(submission.Avs)) + assert.Equal(t, strings.ToLower("0x94373a4919b3240d86ea41593d5eba789fef3848"), strings.ToLower(submission.Token)) + assert.Equal(t, strings.ToLower("0x58959fBe6661daEA647E20dF7c6d2c7F0d2215fB"), strings.ToLower(submission.RewardHash)) + assert.Equal(t, "50000000000000000000", submission.Amount) + assert.Equal(t, uint64(2419200), submission.Duration) + assert.Equal(t, int64(1712188800), submission.StartTimestamp.Unix()) + assert.Equal(t, int64(2419200+1712188800), submission.EndTimestamp.Unix()) + + assert.Equal(t, strings.ToLower(strategiesAndMultipliers[i].Strategy), strings.ToLower(submission.Strategy)) + assert.Equal(t, strategiesAndMultipliers[i].Multiplier, submission.Multiplier) + } + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + query := `select count(*) from reward_submissions` + var count int + res = model.Db.Raw(query, blockNumber).Scan(&count) + assert.Nil(t, res.Error) + assert.Equal(t, len(strategiesAndMultipliers), count) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + teardown(model) + }) + + t.Run("Handle a range payment for all submission", func(t *testing.T) { + + }) + + t.Run("Handle a reward submission", func(t *testing.T) { + + }) + + t.Run("Handle a reward submission for all", func(t *testing.T) { + + }) +} diff --git a/internal/eigenState/submittedDistributionRoots/submittedDistributionRoots_test.go b/internal/eigenState/submittedDistributionRoots/submittedDistributionRoots_test.go index 3757a552..6d47f27b 100644 --- a/internal/eigenState/submittedDistributionRoots/submittedDistributionRoots_test.go +++ b/internal/eigenState/submittedDistributionRoots/submittedDistributionRoots_test.go @@ -74,8 +74,6 @@ func Test_SubmittedDistributionRoots(t *testing.T) { DeletedAt: time.Time{}, } - assert.Nil(t, err) - err = model.InitBlockProcessing(blockNumber) assert.Nil(t, err) diff --git a/internal/sqlite/migrations/202409101540_rewardSubmissions/up.go b/internal/sqlite/migrations/202409101540_rewardSubmissions/up.go new file mode 100644 index 00000000..1d9cefaa --- /dev/null +++ b/internal/sqlite/migrations/202409101540_rewardSubmissions/up.go @@ -0,0 +1,36 @@ +package _202409101540_rewardSubmissions + +import ( + "gorm.io/gorm" +) + +type SqliteMigration struct { +} + +func (m *SqliteMigration) Up(grm *gorm.DB) error { + query := ` + create table if not exists reward_submissions ( + avs TEXT NOT NULL, + reward_hash TEST NOT NULL, + token TEXT NOT NULL, + amount NUMERIC NOT NULL, + strategy TEXT NOT NULL, + strategy_index INTEGER NOT NULL, + multiplier NUMERIC NOT NULL, + start_timestamp DATETIME NOT NULL, + end_timestamp DATETIME NOT NULL, + duration INTEGER NOT NULL, + is_for_all INTEGER DEFAULT 0, + block_number INTEGER NOT NULL, + unique(reward_hash, strategy) + ); + ` + if err := grm.Exec(query).Error; err != nil { + return err + } + return nil +} + +func (m *SqliteMigration) GetName() string { + return "202409101540_rewardSubmissions" +} diff --git a/internal/sqlite/migrations/migrator.go b/internal/sqlite/migrations/migrator.go index ce536226..114f3c2c 100644 --- a/internal/sqlite/migrations/migrator.go +++ b/internal/sqlite/migrations/migrator.go @@ -10,6 +10,7 @@ import ( _202409080918_staterootTable "github.com/Layr-Labs/go-sidecar/internal/sqlite/migrations/202409080918_staterootTable" _202409082234_stakerShare "github.com/Layr-Labs/go-sidecar/internal/sqlite/migrations/202409082234_stakerShare" _202409101144_submittedDistributionRoot "github.com/Layr-Labs/go-sidecar/internal/sqlite/migrations/202409101144_submittedDistributionRoot" + _202409101540_rewardSubmissions "github.com/Layr-Labs/go-sidecar/internal/sqlite/migrations/202409101540_rewardSubmissions" "go.uber.org/zap" "gorm.io/gorm" "time" @@ -47,6 +48,7 @@ func (m *SqliteMigrator) MigrateAll() error { &_202409080918_staterootTable.SqliteMigration{}, &_202409082234_stakerShare.SqliteMigration{}, &_202409101144_submittedDistributionRoot.SqliteMigration{}, + &_202409101540_rewardSubmissions.SqliteMigration{}, } m.Logger.Sugar().Info("Running migrations") From c4b6f7eeb88929cbf841152172ac5364bc769d73 Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Wed, 11 Sep 2024 09:35:30 -0500 Subject: [PATCH 3/3] Add remaining tests for reward submissions --- .../rewardSubmissions/rewardSubmissions.go | 6 +- .../rewardSubmissions_test.go | 243 +++++++++++++++++- .../202409101540_rewardSubmissions/up.go | 2 +- 3 files changed, 240 insertions(+), 11 deletions(-) diff --git a/internal/eigenState/rewardSubmissions/rewardSubmissions.go b/internal/eigenState/rewardSubmissions/rewardSubmissions.go index 2f563f75..1819ab09 100644 --- a/internal/eigenState/rewardSubmissions/rewardSubmissions.go +++ b/internal/eigenState/rewardSubmissions/rewardSubmissions.go @@ -107,8 +107,8 @@ type genericRewardPaymentData struct { } type rewardSubmissionOutputData struct { - RewardSubmission *genericRewardPaymentData `json:"rewardSubmission"` - RangePayment *genericRewardPaymentData `json:"rangePayment"` + RewardsSubmission *genericRewardPaymentData `json:"rewardsSubmission"` + RangePayment *genericRewardPaymentData `json:"rangePayment"` } func parseRewardSubmissionOutputData(outputDataStr string) (*rewardSubmissionOutputData, error) { @@ -138,7 +138,7 @@ func (rs *RewardSubmissionsModel) handleRewardSubmissionCreatedEvent(log *storag if log.EventName == "RangePaymentCreated" || log.EventName == "RangePaymentForAllCreated" { actualOuputData = outputData.RangePayment } else { - actualOuputData = outputData.RewardSubmission + actualOuputData = outputData.RewardsSubmission } rewardSubmissions := make([]*RewardSubmission, 0) diff --git a/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go b/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go index 2185f24d..04580c52 100644 --- a/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go +++ b/internal/eigenState/rewardSubmissions/rewardSubmissions_test.go @@ -1,6 +1,7 @@ package rewardSubmissions import ( + "fmt" "github.com/Layr-Labs/go-sidecar/internal/config" "github.com/Layr-Labs/go-sidecar/internal/eigenState/stateManager" "github.com/Layr-Labs/go-sidecar/internal/logger" @@ -40,9 +41,13 @@ func setup() ( func teardown(model *RewardSubmissionsModel) { queries := []string{ `truncate table reward_submissions cascade`, + `truncate table blocks cascade`, } for _, query := range queries { - model.Db.Raw(query) + res := model.Db.Raw(query) + if res.Error != nil { + fmt.Printf("Failed to run query: %v\n", res.Error) + } } } @@ -57,6 +62,8 @@ func Test_RewardSubmissions(t *testing.T) { model, err := NewRewardSubmissionsModel(esm, grm, cfg.Network, cfg.Environment, l, cfg) + submissionCounter := 0 + t.Run("Handle a range payment submission", func(t *testing.T) { blockNumber := uint64(100) @@ -123,29 +130,251 @@ func Test_RewardSubmissions(t *testing.T) { err = model.CommitFinalState(blockNumber) assert.Nil(t, err) - query := `select count(*) from reward_submissions` - var count int - res = model.Db.Raw(query, blockNumber).Scan(&count) + rewards := make([]*RewardSubmission, 0) + query := `select * from reward_submissions where block_number = ?` + res = model.Db.Raw(query, blockNumber).Scan(&rewards) assert.Nil(t, res.Error) - assert.Equal(t, len(strategiesAndMultipliers), count) + assert.Equal(t, len(strategiesAndMultipliers), len(rewards)) + + submissionCounter += len(rewards) stateRoot, err := model.GenerateStateRoot(blockNumber) assert.Nil(t, err) assert.NotNil(t, stateRoot) assert.True(t, len(stateRoot) > 0) - - teardown(model) }) t.Run("Handle a range payment for all submission", func(t *testing.T) { + blockNumber := uint64(101) + + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * 100), + } + res := model.Db.Model(&storage.Block{}).Create(block) + if res.Error != nil { + t.Fatal(res.Error) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().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.InitBlockProcessing(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 + }{ + {"0x5c8b55722f421556a2aafb7a3ea63d4c3e514312", "1000000000000000000"}, + {"0x7fa77c321bf66e42eabc9b10129304f7f90c5585", "2000000000000000000"}, + {"0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0", "3000000000000000000"}, + {"0xd523267698c81a372191136e477fdebfa33d9fb4", "4500000000000000000"}, + } + + typedChange := change.(*RewardSubmissions) + assert.Equal(t, len(strategiesAndMultipliers), len(typedChange.Submissions)) + + for i, submission := range typedChange.Submissions { + assert.Equal(t, strings.ToLower("0x00526A07855f743964F05CccAeCcf7a9E34847fF"), strings.ToLower(submission.Avs)) + assert.Equal(t, strings.ToLower("0x3f1c547b21f65e10480de3ad8e19faac46c95034"), strings.ToLower(submission.Token)) + assert.Equal(t, strings.ToLower("0x69193C881C4BfA9015F1E9B2631e31238BedB93e"), strings.ToLower(submission.RewardHash)) + assert.Equal(t, "11000000000000000000", submission.Amount) + assert.Equal(t, uint64(2419200), submission.Duration) + assert.Equal(t, int64(1713398400), submission.StartTimestamp.Unix()) + assert.Equal(t, int64(2419200+1713398400), submission.EndTimestamp.Unix()) + + assert.Equal(t, strings.ToLower(strategiesAndMultipliers[i].Strategy), strings.ToLower(submission.Strategy)) + assert.Equal(t, strategiesAndMultipliers[i].Multiplier, submission.Multiplier) + + fmt.Printf("Submission: %+v\n", submission) + } + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + rewards := make([]*RewardSubmission, 0) + query := `select * from reward_submissions where block_number = ?` + res = model.Db.Raw(query, blockNumber).Scan(&rewards) + assert.Nil(t, res.Error) + assert.Equal(t, len(strategiesAndMultipliers), len(rewards)) + + submissionCounter += len(strategiesAndMultipliers) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + teardown(model) }) t.Run("Handle a reward submission", func(t *testing.T) { + blockNumber := uint64(102) + + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * 100), + } + res := model.Db.Model(&storage.Block{}).Create(block) + if res.Error != nil { + t.Fatal(res.Error) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().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.InitBlockProcessing(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"}, + } + + typedChange := change.(*RewardSubmissions) + assert.Equal(t, len(strategiesAndMultipliers), len(typedChange.Submissions)) + + for i, submission := range typedChange.Submissions { + 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, "10000000000000000000000", submission.Amount) + 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) + } + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + rewards := make([]*RewardSubmission, 0) + query := `select * from reward_submissions where block_number = ?` + res = model.Db.Raw(query, blockNumber).Scan(&rewards) + assert.Nil(t, res.Error) + assert.Equal(t, len(strategiesAndMultipliers), len(rewards)) + + submissionCounter += len(strategiesAndMultipliers) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + teardown(model) }) t.Run("Handle a reward submission for all", func(t *testing.T) { + blockNumber := uint64(103) + + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * 100), + } + res := model.Db.Model(&storage.Block{}).Create(block) + if res.Error != nil { + t.Fatal(res.Error) + } + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForEnvAndNetwork().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.InitBlockProcessing(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 + }{ + {"0xd523267698c81a372191136e477fdebfa33d9fb4", "1000000000000000000"}, + {"0xdccf401fd121d8c542e96bc1d0078884422afad2", "2000000000000000000"}, + } + + typedChange := change.(*RewardSubmissions) + assert.Equal(t, len(strategiesAndMultipliers), len(typedChange.Submissions)) + + for i, submission := range typedChange.Submissions { + assert.Equal(t, strings.ToLower("0x66ae7d7c4d492e4e012b95977f14715b74498bc5"), strings.ToLower(submission.Avs)) + assert.Equal(t, strings.ToLower("0x554c393923c753d146aa34608523ad7946b61662"), strings.ToLower(submission.Token)) + assert.Equal(t, strings.ToLower("0x99ebccb0f68eedbf3dff04c7773d6ff94fc439e0eebdd80918b3785ae8099f96"), strings.ToLower(submission.RewardHash)) + assert.Equal(t, "10000000000000000000", submission.Amount) + assert.Equal(t, uint64(1814400), submission.Duration) + assert.Equal(t, int64(1717632000), submission.StartTimestamp.Unix()) + assert.Equal(t, int64(1814400+1717632000), submission.EndTimestamp.Unix()) + + assert.Equal(t, strings.ToLower(strategiesAndMultipliers[i].Strategy), strings.ToLower(submission.Strategy)) + assert.Equal(t, strategiesAndMultipliers[i].Multiplier, submission.Multiplier) + } + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + rewards := make([]*RewardSubmission, 0) + query := `select * from reward_submissions where block_number = ?` + res = model.Db.Raw(query, blockNumber).Scan(&rewards) + assert.Nil(t, res.Error) + assert.Equal(t, len(strategiesAndMultipliers), len(rewards)) + + submissionCounter += len(strategiesAndMultipliers) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + teardown(model) }) } diff --git a/internal/sqlite/migrations/202409101540_rewardSubmissions/up.go b/internal/sqlite/migrations/202409101540_rewardSubmissions/up.go index 1d9cefaa..222c0ccb 100644 --- a/internal/sqlite/migrations/202409101540_rewardSubmissions/up.go +++ b/internal/sqlite/migrations/202409101540_rewardSubmissions/up.go @@ -22,7 +22,7 @@ func (m *SqliteMigration) Up(grm *gorm.DB) error { duration INTEGER NOT NULL, is_for_all INTEGER DEFAULT 0, block_number INTEGER NOT NULL, - unique(reward_hash, strategy) + unique(reward_hash, strategy, block_number) ); ` if err := grm.Exec(query).Error; err != nil {