Skip to content

Commit

Permalink
feat: add rpc for generating rewards claim proofs
Browse files Browse the repository at this point in the history
add missing arg
  • Loading branch information
seanmcgary committed Jan 10, 2025
1 parent 16bf7db commit 309a553
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 25 deletions.
10 changes: 6 additions & 4 deletions cmd/debugger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/Layr-Labs/sidecar/pkg/indexer"
"github.com/Layr-Labs/sidecar/pkg/pipeline"
"github.com/Layr-Labs/sidecar/pkg/postgres"
"github.com/Layr-Labs/sidecar/pkg/proofs"
"github.com/Layr-Labs/sidecar/pkg/rewards"
"github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators"
"github.com/Layr-Labs/sidecar/pkg/rewardsCalculatorQueue"
Expand Down Expand Up @@ -100,21 +101,22 @@ func main() {
rcq := rewardsCalculatorQueue.NewRewardsCalculatorQueue(rc, l)

p := pipeline.NewPipeline(fetchr, idxr, mds, sm, rc, rcq, cfg, sdc, eb, l)
rps := proofs.NewRewardsProofsStore(rc, l)

// Create new sidecar instance
// Create new sidecar instance
_ = sidecar.NewSidecar(&sidecar.SidecarConfig{
GenesisBlockNumber: cfg.GetGenesisBlockNumber(),
}, cfg, mds, p, sm, rc, rcq, l, client)
}, cfg, mds, p, sm, rc, rcq, rps, l, client)

rpcServer := rpcServer.NewRpcServer(&rpcServer.RpcServerConfig{
rpc := rpcServer.NewRpcServer(&rpcServer.RpcServerConfig{
GrpcPort: cfg.RpcConfig.GrpcPort,
HttpPort: cfg.RpcConfig.HttpPort,
}, mds, sm, rc, rcq, eb, l)
}, mds, sm, rc, rcq, eb, rps, l)

// RPC channel to notify the RPC server to shutdown gracefully
rpcChannel := make(chan bool)
if err := rpcServer.Start(ctx, rpcChannel); err != nil {
if err := rpc.Start(ctx, rpcChannel); err != nil {
l.Sugar().Fatalw("Failed to start RPC server", zap.Error(err))
}

Expand Down
11 changes: 7 additions & 4 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/Layr-Labs/sidecar/pkg/indexer"
"github.com/Layr-Labs/sidecar/pkg/pipeline"
"github.com/Layr-Labs/sidecar/pkg/postgres"
"github.com/Layr-Labs/sidecar/pkg/proofs"
"github.com/Layr-Labs/sidecar/pkg/rewards"
"github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators"
"github.com/Layr-Labs/sidecar/pkg/rewardsCalculatorQueue"
Expand Down Expand Up @@ -116,23 +117,25 @@ var runCmd = &cobra.Command{

rcq := rewardsCalculatorQueue.NewRewardsCalculatorQueue(rc, l)

rps := proofs.NewRewardsProofsStore(rc, l)

go rcq.Process()

p := pipeline.NewPipeline(fetchr, idxr, mds, sm, rc, rcq, cfg, sdc, eb, l)

// Create new sidecar instance
sidecar := sidecar.NewSidecar(&sidecar.SidecarConfig{
GenesisBlockNumber: cfg.GetGenesisBlockNumber(),
}, cfg, mds, p, sm, rc, rcq, l, client)
}, cfg, mds, p, sm, rc, rcq, rps, l, client)

rpcServer := rpcServer.NewRpcServer(&rpcServer.RpcServerConfig{
rpc := rpcServer.NewRpcServer(&rpcServer.RpcServerConfig{
GrpcPort: cfg.RpcConfig.GrpcPort,
HttpPort: cfg.RpcConfig.HttpPort,
}, mds, sm, rc, rcq, eb, l)
}, mds, sm, rc, rcq, eb, rps, l)

// RPC channel to notify the RPC server to shutdown gracefully
rpcChannel := make(chan bool)
if err := rpcServer.Start(ctx, rpcChannel); err != nil {
if err := rpc.Start(ctx, rpcChannel); err != nil {
l.Sugar().Fatalw("Failed to start RPC server", zap.Error(err))
}

Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ github.com/Layr-Labs/eigenlayer-contracts v0.4.1-holesky-pepe.0.20240813143901-0
github.com/Layr-Labs/eigenlayer-contracts v0.4.1-holesky-pepe.0.20240813143901-00fc4b95e9c1/go.mod h1:Ie8YE3EQkTHqG6/tnUS0He7/UPMkXPo/3OFXwSy0iRo=
github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.13 h1:Blb4AE+jC/vddV71w4/MQAPooM+8EVqv9w2bL4OytgY=
github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.13/go.mod h1:PD/HoyzZjxDw1tAcZw3yD0yGddo+yhmwQAi+lk298r4=
github.com/Layr-Labs/protocol-apis v1.0.0-rc.1.0.20250109222723-376a40434d4e h1:IArMOWI0V+2KFexZbJwZvIxVFTJdaDZG8PMcih/GV4A=
github.com/Layr-Labs/protocol-apis v1.0.0-rc.1.0.20250109222723-376a40434d4e/go.mod h1:prNA2/mLO5vpMZ2q78Nsn0m97wm28uiRnwO+/yOxigk=
github.com/Layr-Labs/protocol-apis v1.0.0-rc.1.0.20250109230911-e6fec5ffbd4c h1:uKOEYFWJ6OcCGTMZK8M4rZJHIxdWMuTI7j+LvNe8D84=
github.com/Layr-Labs/protocol-apis v1.0.0-rc.1.0.20250109230911-e6fec5ffbd4c/go.mod h1:prNA2/mLO5vpMZ2q78Nsn0m97wm28uiRnwO+/yOxigk=
github.com/Layr-Labs/protocol-apis v1.1.0 h1:PO6x+Y9ORiac2dkaWJayRFqhyzcvMbvRQkDIpLTNtVc=
github.com/Layr-Labs/protocol-apis v1.1.0/go.mod h1:prNA2/mLO5vpMZ2q78Nsn0m97wm28uiRnwO+/yOxigk=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
Expand Down
2 changes: 1 addition & 1 deletion pkg/pipeline/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func (p *Pipeline) RunForFetchedBlock(ctx context.Context, block *fetcher.Fetche
zap.String("cutoffDate", cutoffDate),
zap.Uint64("blockNumber", blockNumber),
)
accountTree, _, err := p.rewardsCalculator.MerkelizeRewardsForSnapshot(rewardsCalculationEnd)
accountTree, _, _, err := p.rewardsCalculator.MerkelizeRewardsForSnapshot(rewardsCalculationEnd)
if err != nil {
p.Logger.Sugar().Errorw("Failed to merkelize rewards for snapshot date",
zap.String("cutoffDate", cutoffDate), zap.Error(err),
Expand Down
121 changes: 121 additions & 0 deletions pkg/proofs/rewardsProofs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package proofs

import (
rewardsCoordinator "github.com/Layr-Labs/eigenlayer-contracts/pkg/bindings/IRewardsCoordinator"
"github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/claimgen"
"github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/distribution"
"github.com/Layr-Labs/sidecar/pkg/rewards"
"github.com/Layr-Labs/sidecar/pkg/utils"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/wealdtech/go-merkletree/v2"
"go.uber.org/zap"
)

type RewardsProofsStore struct {
rewardsCalculator *rewards.RewardsCalculator
logger *zap.Logger
rewardsData map[string]*ProofData
}

type ProofData struct {
SnapshotDate string
AccountTree *merkletree.MerkleTree
TokenTree map[gethcommon.Address]*merkletree.MerkleTree
Distribution *distribution.Distribution
}

func NewRewardsProofsStore(
rc *rewards.RewardsCalculator,
l *zap.Logger,
) *RewardsProofsStore {
return &RewardsProofsStore{
rewardsCalculator: rc,
logger: l,
rewardsData: make(map[string]*ProofData),
}
}

func (rps *RewardsProofsStore) getRewardsDataForSnapshot(snapshot string) (*ProofData, error) {
data, ok := rps.rewardsData[snapshot]
if !ok {
accountTree, tokenTree, distro, err := rps.rewardsCalculator.MerkelizeRewardsForSnapshot(snapshot)
if err != nil {
rps.logger.Sugar().Errorw("Failed to fetch rewards for snapshot",
zap.String("snapshot", snapshot),
zap.Error(err),
)
return nil, err
}

data = &ProofData{
SnapshotDate: snapshot,
AccountTree: accountTree,
TokenTree: tokenTree,
Distribution: distro,
}
rps.rewardsData[snapshot] = data
}
return data, nil
}

func (rps *RewardsProofsStore) GenerateRewardsClaimProof(earnerAddress string, tokenAddresses []string, snapshotDate string) (
[]byte,
*rewardsCoordinator.IRewardsCoordinatorRewardsMerkleClaim,
error,
) {
if snapshotDate == "" {
snapshotDate = "latest"
}

distributionRoot, err := rps.rewardsCalculator.FindClaimableDistributionRoot(snapshotDate)
if err != nil {
rps.logger.Sugar().Errorf("Failed to find most claimable distribution root", zap.Error(err))
return nil, nil, err
}
if snapshotDate == "latest" {
snapshotDate = distributionRoot.GetSnapshotDate()
}

// Make sure rewards have been generated for this snapshot.
// Any snapshot that is >= the provided date is valid since we'll select only data up
// to the snapshot/cutoff date
generatedSnapshot, err := rps.rewardsCalculator.GetGeneratedRewardsForSnapshotDate(snapshotDate)
if err != nil {
rps.logger.Sugar().Errorf("Failed to get generated rewards for snapshot date", zap.Error(err))
return nil, nil, err
}
rps.logger.Sugar().Infow("Using snapshot for rewards proof",
zap.String("requestedSnapshot", snapshotDate),
zap.String("snapshot", generatedSnapshot.SnapshotDate),
)

proofData, err := rps.getRewardsDataForSnapshot(snapshotDate)
if err != nil {
rps.logger.Sugar().Error("Failed to get rewards data for snapshot",
zap.String("snapshot", snapshotDate),
zap.Error(err),
)
return nil, nil, err
}

tokens := utils.Map(tokenAddresses, func(addr string, i uint64) gethcommon.Address {
return gethcommon.HexToAddress(addr)
})
earner := gethcommon.HexToAddress(earnerAddress)
rootIndex := distributionRoot.RootIndex

claim, err := claimgen.GetProofForEarner(
proofData.Distribution,
uint32(rootIndex),
proofData.AccountTree,
proofData.TokenTree,
earner,
tokens,
)
if err != nil {
rps.logger.Sugar().Error("Failed to generate claim proof for earner", zap.Error(err))
return nil, nil, err
}

return proofData.AccountTree.Root(), claim, nil
}
87 changes: 81 additions & 6 deletions pkg/rewards/rewards.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/distribution"
"github.com/Layr-Labs/sidecar/pkg/eigenState/types"
"github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators"
"github.com/Layr-Labs/sidecar/pkg/rewardsUtils"
"github.com/Layr-Labs/sidecar/pkg/storage"
Expand Down Expand Up @@ -185,10 +186,15 @@ func (rc *RewardsCalculator) GetRewardSnapshotStatus(snapshotDate string) (*stor
return r, nil
}

func (rc *RewardsCalculator) MerkelizeRewardsForSnapshot(snapshotDate string) (*merkletree.MerkleTree, map[gethcommon.Address]*merkletree.MerkleTree, error) {
rewards, err := rc.fetchRewardsForSnapshot(snapshotDate)
func (rc *RewardsCalculator) MerkelizeRewardsForSnapshot(snapshotDate string) (
*merkletree.MerkleTree,
map[gethcommon.Address]*merkletree.MerkleTree,
*distribution.Distribution,
error,
) {
rewards, err := rc.FetchRewardsForSnapshot(snapshotDate)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

distro := distribution.NewDistribution()
Expand All @@ -206,12 +212,12 @@ func (rc *RewardsCalculator) MerkelizeRewardsForSnapshot(snapshotDate string) (*

if err := distro.LoadLines(earnerLines); err != nil {
rc.logger.Error("Failed to load lines", zap.Error(err))
return nil, nil, err
return nil, nil, nil, err
}

accountTree, tokenTree, err := distro.Merklize()

return accountTree, tokenTree, err
return accountTree, tokenTree, distro, err
}

func (rc *RewardsCalculator) GetMaxSnapshotDateForCutoffDate(cutoffDate string) (string, error) {
Expand Down Expand Up @@ -480,7 +486,7 @@ type Reward struct {
CumulativeAmount string
}

func (rc *RewardsCalculator) fetchRewardsForSnapshot(snapshotDate string) ([]*Reward, error) {
func (rc *RewardsCalculator) FetchRewardsForSnapshot(snapshotDate string) ([]*Reward, error) {
var goldRows []*Reward
query, err := rewardsUtils.RenderQueryTemplate(`
select
Expand Down Expand Up @@ -668,3 +674,72 @@ func (rc *RewardsCalculator) generateAndInsertFromQuery(
rc.logger,
)
}

func (rc *RewardsCalculator) FindClaimableDistributionRoot(snapshotDate string) (*types.SubmittedDistributionRoot, error) {
if snapshotDate == "" {
snapshotDate = "latest"
}
query := `
select
*
from submitted_distribution_roots as sdr
left join disabled_distribution_roots as ddr on (sdr.root_index = ddr.root_index)
where
ddr.root_index is null
{{ if eq .snapshotDate "latest" }}
and activated_at >= now()
{{ else }}
and activated_at >= '{{.snapshotDate}}'::timestamp(6)
{{ end }}
order by root_index desc
limit 1
`
renderedQuery, err := rewardsUtils.RenderQueryTemplate(query, map[string]string{"snapshotDate": snapshotDate})
if err != nil {
rc.logger.Sugar().Errorw("Failed to render query template", "error", err)
return nil, err
}

var submittedDistributionRoot *types.SubmittedDistributionRoot
res := rc.grm.Raw(renderedQuery).Scan(&submittedDistributionRoot)
if res.Error != nil {
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
rc.logger.Sugar().Errorw("No active distribution root found for snapshot",
zap.String("snapshotDate", snapshotDate),
zap.Error(res.Error),
)
return nil, res.Error
}
rc.logger.Sugar().Errorw("Failed to find most recent claimable distribution root", "error", res.Error)
return nil, res.Error
}

return submittedDistributionRoot, nil
}

func (rc *RewardsCalculator) GetGeneratedRewardsForSnapshotDate(snapshotDate string) (*storage.GeneratedRewardsSnapshots, error) {
query, err := rewardsUtils.RenderQueryTemplate(`
select
*
from generated_rewards_snapshots as grs
where
status = 'complete'
{{if ne .snapshotDate "latest"}}
and grs.snapshot_date::timestamp(6) >= '{{.snapshotDate}}'::timestamp(6)
{{end}}
limit 1
`, map[string]string{"snapshotDate": snapshotDate})

if err != nil {
rc.logger.Sugar().Errorw("Failed to render query template", "error", err)
return nil, err
}

var generatedRewardsSnapshot *storage.GeneratedRewardsSnapshots
res := rc.grm.Raw(query).Scan(&generatedRewardsSnapshot)
if res.Error != nil {
rc.logger.Sugar().Errorw("Failed to get generated rewards snapshots", "error", res.Error)
return nil, res.Error
}
return generatedRewardsSnapshot, nil
}
2 changes: 1 addition & 1 deletion pkg/rewards/rewards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ func Test_Rewards(t *testing.T) {
err = rc.sog.GenerateStakerOperatorsTable(snapshotDate)
assert.Nil(t, err)

accountTree, _, err := rc.MerkelizeRewardsForSnapshot(snapshotDate)
accountTree, _, _, err := rc.MerkelizeRewardsForSnapshot(snapshotDate)
assert.Nil(t, err)

root := utils.ConvertBytesToString(accountTree.Root())
Expand Down
51 changes: 51 additions & 0 deletions pkg/rpcServer/proofsHandlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package rpcServer

import (
"context"
"github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/claimgen"
sidecarV1 "github.com/Layr-Labs/protocol-apis/gen/protos/eigenlayer/sidecar/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func convertClaimProofToRPCResponse(solidityProof *claimgen.IRewardsCoordinatorRewardsMerkleClaimStrings) *sidecarV1.Proof {
tokenLeaves := make([]*sidecarV1.TokenLeaf, 0)

for _, l := range solidityProof.TokenLeaves {
tokenLeaves = append(tokenLeaves, &sidecarV1.TokenLeaf{
Token: l.Token.String(),
CumulativeEarnings: l.CumulativeEarnings,
})
}

return &sidecarV1.Proof{
Root: solidityProof.Root,
RootIndex: solidityProof.RootIndex,
EarnerIndex: solidityProof.EarnerIndex,
EarnerTreeProof: solidityProof.EarnerTreeProof,
EarnerLeaf: &sidecarV1.EarnerLeaf{
Earner: solidityProof.EarnerLeaf.Earner.String(),
EarnerTokenRoot: solidityProof.EarnerLeaf.EarnerTokenRoot,
},
LeafIndices: solidityProof.TokenIndices,
TokenTreeProofs: solidityProof.TokenTreeProofs,
TokenLeaves: tokenLeaves,
}
}

func (rpc *RpcServer) GenerateClaimProof(ctx context.Context, req *sidecarV1.GenerateClaimProofRequest) (*sidecarV1.GenerateClaimProofResponse, error) {
earner := req.GetEarnerAddress()
tokens := req.GetTokens()
snapshotDate := req.GetSnapshot()

root, claim, err := rpc.rewardsProofs.GenerateRewardsClaimProof(earner, tokens, snapshotDate)
if err != nil {
return nil, status.Errorf(codes.Internal, "Failed to generate claim proof %s", err.Error())
}

solidityClaim := claimgen.FormatProofForSolidity(root, claim)

return &sidecarV1.GenerateClaimProofResponse{
Proof: convertClaimProofToRPCResponse(solidityClaim),
}, nil
}
Loading

0 comments on commit 309a553

Please sign in to comment.