Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dynamic default split #174

Merged
merged 4 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ const (
Chain_Holesky Chain = "holesky"
Chain_Preprod Chain = "preprod"

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

ENV_PREFIX = "SIDECAR"
)
Expand Down Expand Up @@ -252,24 +253,27 @@ func (c *Config) GetForkDates() (ForkMap, error) {
switch c.Chain {
case Chain_Preprod:
return ForkMap{
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on preprod as we backfilled
Fork_Nile: "2024-08-14", // Last calculation end timestamp was 8-13: https://holesky.etherscan.io/tx/0xb5a6855e88c79312b7c0e1c9f59ae9890b97f157ea27e69e4f0fadada4712b64#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-11",
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on preprod as we backfilled
Fork_Nile: "2024-08-14", // Last calculation end timestamp was 8-13: https://holesky.etherscan.io/tx/0xb5a6855e88c79312b7c0e1c9f59ae9890b97f157ea27e69e4f0fadada4712b64#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-11",
Fork_Trinity: "2025-01-09",
}, nil
case Chain_Holesky:
return ForkMap{
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on testnet as we backfilled
Fork_Nile: "2024-08-13", // Last calculation end timestamp was 8-12: https://holesky.etherscan.io/tx/0x5fc81b5ed2a78b017ef313c181d8627737a97fef87eee85acedbe39fc8708c56#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-13",
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on testnet as we backfilled
Fork_Nile: "2024-08-13", // Last calculation end timestamp was 8-12: https://holesky.etherscan.io/tx/0x5fc81b5ed2a78b017ef313c181d8627737a97fef87eee85acedbe39fc8708c56#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-13",
Fork_Trinity: "2025-01-09",
seanmcgary marked this conversation as resolved.
Show resolved Hide resolved
}, nil
case Chain_Mainnet:
return ForkMap{
Fork_Amazon: "2024-08-02", // Last calculation end timestamp was 8-01: https://etherscan.io/tx/0x2aff6f7b0132092c05c8f6f41a5e5eeeb208aa0d95ebcc9022d7823e343dd012#eventlog
Fork_Nile: "2024-08-12", // Last calculation end timestamp was 8-11: https://etherscan.io/tx/0x922d29d93c02d189fc2332041f01a80e0007cd7a625a5663ef9d30082f7ef66f#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2025-01-07",
Fork_Amazon: "2024-08-02", // Last calculation end timestamp was 8-01: https://etherscan.io/tx/0x2aff6f7b0132092c05c8f6f41a5e5eeeb208aa0d95ebcc9022d7823e343dd012#eventlog
Fork_Nile: "2024-08-12", // Last calculation end timestamp was 8-11: https://etherscan.io/tx/0x922d29d93c02d189fc2332041f01a80e0007cd7a625a5663ef9d30082f7ef66f#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2025-01-21",
Fork_Trinity: "2025-01-21",
}, nil
}
return nil, errors.New("unsupported chain")
Expand Down
260 changes: 260 additions & 0 deletions pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package defaultOperatorSplits

import (
"encoding/json"
"fmt"
"slices"
"sort"
"strings"

"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"
"github.com/Layr-Labs/sidecar/pkg/storage"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

type DefaultOperatorSplit struct {
OldDefaultOperatorSplitBips uint64
NewDefaultOperatorSplitBips uint64
BlockNumber uint64
TransactionHash string
LogIndex uint64
}

type DefaultOperatorSplitModel struct {
base.BaseEigenState
StateTransitions types.StateTransitions[[]*DefaultOperatorSplit]
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]*DefaultOperatorSplit
}

func NewDefaultOperatorSplitModel(
esm *stateManager.EigenStateManager,
grm *gorm.DB,
logger *zap.Logger,
globalConfig *config.Config,
) (*DefaultOperatorSplitModel, error) {
model := &DefaultOperatorSplitModel{
BaseEigenState: base.BaseEigenState{
Logger: logger,
},
DB: grm,
logger: logger,
globalConfig: globalConfig,
stateAccumulator: make(map[uint64]map[types.SlotID]*DefaultOperatorSplit),
}

esm.RegisterState(model, 10)
return model, nil
}

func (oas *DefaultOperatorSplitModel) GetModelName() string {
return "DefaultOperatorSplitModel"
}

type defaultOperatorSplitOutputData struct {
OldDefaultOperatorSplitBips uint64 `json:"oldDefaultOperatorSplitBips"`
NewDefaultOperatorSplitBips uint64 `json:"newDefaultOperatorSplitBips"`
}

func parseDefaultOperatorSplitOutputData(outputDataStr string) (*defaultOperatorSplitOutputData, error) {
outputData := &defaultOperatorSplitOutputData{}
decoder := json.NewDecoder(strings.NewReader(outputDataStr))
decoder.UseNumber()

err := decoder.Decode(&outputData)
if err != nil {
return nil, err
}

return outputData, err
}

func (oas *DefaultOperatorSplitModel) handleDefaultOperatorSplitBipsSetEvent(log *storage.TransactionLog) (*DefaultOperatorSplit, error) {
outputData, err := parseDefaultOperatorSplitOutputData(log.OutputData)
if err != nil {
return nil, err
}

split := &DefaultOperatorSplit{
OldDefaultOperatorSplitBips: outputData.OldDefaultOperatorSplitBips,
NewDefaultOperatorSplitBips: outputData.NewDefaultOperatorSplitBips,
BlockNumber: log.BlockNumber,
TransactionHash: log.TransactionHash,
LogIndex: log.LogIndex,
}

return split, nil
}

func (oas *DefaultOperatorSplitModel) GetStateTransitions() (types.StateTransitions[*DefaultOperatorSplit], []uint64) {
stateChanges := make(types.StateTransitions[*DefaultOperatorSplit])

stateChanges[0] = func(log *storage.TransactionLog) (*DefaultOperatorSplit, error) {
defaultOperatorSplit, err := oas.handleDefaultOperatorSplitBipsSetEvent(log)
if err != nil {
return nil, err
}

slotId := base.NewSlotID(defaultOperatorSplit.TransactionHash, defaultOperatorSplit.LogIndex)

_, ok := oas.stateAccumulator[log.BlockNumber][slotId]
if ok {
err := fmt.Errorf("Duplicate default operator split submitted for slot %s at block %d", slotId, log.BlockNumber)
oas.logger.Sugar().Errorw("Duplicate default operator split submitted", zap.Error(err))
return nil, err
}

oas.stateAccumulator[log.BlockNumber][slotId] = defaultOperatorSplit

return defaultOperatorSplit, 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 *DefaultOperatorSplitModel) getContractAddressesForEnvironment() map[string][]string {
contracts := oas.globalConfig.GetContractsMapForChain()
return map[string][]string{
contracts.RewardsCoordinator: {
"DefaultOperatorSplitBipsSet",
},
}
}

func (oas *DefaultOperatorSplitModel) IsInterestingLog(log *storage.TransactionLog) bool {
addresses := oas.getContractAddressesForEnvironment()
return oas.BaseEigenState.IsInterestingLog(addresses, log)
}

func (oas *DefaultOperatorSplitModel) SetupStateForBlock(blockNumber uint64) error {
oas.stateAccumulator[blockNumber] = make(map[types.SlotID]*DefaultOperatorSplit)
return nil
}

func (oas *DefaultOperatorSplitModel) CleanupProcessedStateForBlock(blockNumber uint64) error {
delete(oas.stateAccumulator, blockNumber)
return nil
}

func (oas *DefaultOperatorSplitModel) 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 *DefaultOperatorSplitModel) prepareState(blockNumber uint64) ([]*DefaultOperatorSplit, error) {
accumulatedState, ok := oas.stateAccumulator[blockNumber]
if !ok {
err := fmt.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([]*DefaultOperatorSplit, 0)
for _, split := range accumulatedState {
recordsToInsert = append(recordsToInsert, split)
}
return recordsToInsert, nil
}

// CommitFinalState commits the final state for the given block number.
func (oas *DefaultOperatorSplitModel) 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(&DefaultOperatorSplit{}).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 *DefaultOperatorSplitModel) GenerateStateRoot(blockNumber uint64) ([]byte, error) {
inserts, err := oas.prepareState(blockNumber)
if err != nil {
return nil, err
}

inputs := oas.sortValuesForMerkleTree(inserts)

if len(inputs) == 0 {
return nil, nil
}

fullTree, err := oas.MerkleizeEigenState(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 nil, err
}
return fullTree.Root(), nil
}

func (oas *DefaultOperatorSplitModel) sortValuesForMerkleTree(splits []*DefaultOperatorSplit) []*base.MerkleTreeInput {
inputs := make([]*base.MerkleTreeInput, 0)
for _, split := range splits {
slotID := base.NewSlotID(split.TransactionHash, split.LogIndex)
value := fmt.Sprintf("%016x_%016x", split.OldDefaultOperatorSplitBips, split.NewDefaultOperatorSplitBips)
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 *DefaultOperatorSplitModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error {
return oas.BaseEigenState.DeleteState("default_operator_splits", startBlockNumber, endBlockNumber, oas.DB)
}
Loading
Loading