diff --git a/cmd/debugger/main.go b/cmd/debugger/main.go index 0c37812b..556ee807 100644 --- a/cmd/debugger/main.go +++ b/cmd/debugger/main.go @@ -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" @@ -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)) } diff --git a/cmd/run.go b/cmd/run.go index 6a360af9..c0b96f2d 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -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" @@ -116,6 +117,8 @@ 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) @@ -123,16 +126,16 @@ var runCmd = &cobra.Command{ // 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)) } diff --git a/examples/rewardsProofs/main.go b/examples/rewardsProofs/main.go new file mode 100644 index 00000000..39229289 --- /dev/null +++ b/examples/rewardsProofs/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + v1 "github.com/Layr-Labs/protocol-apis/gen/protos/eigenlayer/sidecar/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "log" + "strings" +) + +func NewSidecarClient(url string, insecureConn bool) (v1.RewardsClient, error) { + var creds grpc.DialOption + if strings.Contains(url, "localhost:") || strings.Contains(url, "127.0.0.1:") || insecureConn { + creds = grpc.WithTransportCredentials(insecure.NewCredentials()) + } else { + creds = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})) + } + + grpcClient, err := grpc.NewClient(url, creds) + if err != nil { + return nil, err + } + + return v1.NewRewardsClient(grpcClient), nil +} + +func main() { + earnerAddress := "0x111116fe4f8c2f83e3eb2318f090557b7cd0bf76" + tokens := []string{"0xdeeeeE2b48C121e6728ed95c860e296177849932"} + + client, err := NewSidecarClient("localhost:7100", true) + if err != nil { + log.Fatal(err) + } + + res, err := client.GenerateClaimProof(context.Background(), &v1.GenerateClaimProofRequest{ + EarnerAddress: earnerAddress, + Tokens: tokens, + }) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Proof: %+v\n", res.Proof) +} diff --git a/go.mod b/go.mod index 2bb78e82..3f7bad2b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/DataDog/datadog-go/v5 v5.5.0 github.com/Layr-Labs/eigenlayer-contracts v0.4.1-holesky-pepe.0.20240813143901-00fc4b95e9c1 github.com/Layr-Labs/eigenlayer-rewards-proofs v0.2.13 - github.com/Layr-Labs/protocol-apis v1.1.0 + github.com/Layr-Labs/protocol-apis v1.1.1-0.20250114181701-acb87ef4eeb5 github.com/ethereum/go-ethereum v1.14.9 github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 5c164efa..1e9ef4ec 100644 --- a/go.sum +++ b/go.sum @@ -41,12 +41,16 @@ 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/Layr-Labs/protocol-apis v1.1.1-0.20250110201222-e8670ac00c32 h1:nRHAH0dn5qkQXUjdrPlGThtKLt154UKAHfzCEyMqfr0= +github.com/Layr-Labs/protocol-apis v1.1.1-0.20250110201222-e8670ac00c32/go.mod h1:prNA2/mLO5vpMZ2q78Nsn0m97wm28uiRnwO+/yOxigk= +github.com/Layr-Labs/protocol-apis v1.1.1-0.20250110201843-c2f2cf37e910 h1:X3t1mr1kAOGDJ3paPS/lzHJchK4y+oWSJ+D/7+MgOmY= +github.com/Layr-Labs/protocol-apis v1.1.1-0.20250110201843-c2f2cf37e910/go.mod h1:prNA2/mLO5vpMZ2q78Nsn0m97wm28uiRnwO+/yOxigk= +github.com/Layr-Labs/protocol-apis v1.1.1-0.20250114180833-6f2487a7e08c h1:kcTwHJVRDQAGqVacRJ4h6r6LKIZP6nBkxaBEJvZ9A3Q= +github.com/Layr-Labs/protocol-apis v1.1.1-0.20250114180833-6f2487a7e08c/go.mod h1:prNA2/mLO5vpMZ2q78Nsn0m97wm28uiRnwO+/yOxigk= +github.com/Layr-Labs/protocol-apis v1.1.1-0.20250114181701-acb87ef4eeb5 h1:0PLxb8fpwdpWpfk24yhdZzETFCxVMN2yJjRDyBBf6wM= +github.com/Layr-Labs/protocol-apis v1.1.1-0.20250114181701-acb87ef4eeb5/go.mod h1:prNA2/mLO5vpMZ2q78Nsn0m97wm28uiRnwO+/yOxigk= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= diff --git a/pkg/pipeline/pipeline.go b/pkg/pipeline/pipeline.go index e8f30182..c5d32117 100644 --- a/pkg/pipeline/pipeline.go +++ b/pkg/pipeline/pipeline.go @@ -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), diff --git a/pkg/proofs/rewardsProofs.go b/pkg/proofs/rewardsProofs.go new file mode 100644 index 00000000..30c82016 --- /dev/null +++ b/pkg/proofs/rewardsProofs.go @@ -0,0 +1,121 @@ +package proofs + +import ( + "fmt" + 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, rootIndex int64) ( + []byte, + *rewardsCoordinator.IRewardsCoordinatorRewardsMerkleClaim, + error, +) { + distributionRoot, err := rps.rewardsCalculator.FindClaimableDistributionRoot(rootIndex) + if err != nil { + rps.logger.Sugar().Errorf("Failed to find claimable distribution root for root_index", + zap.Int64("rootIndex", rootIndex), + zap.Error(err), + ) + return nil, nil, err + } + if distributionRoot == nil { + return nil, nil, fmt.Errorf("No claimable distribution root found for root index %d", rootIndex) + } + 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) + + claim, err := claimgen.GetProofForEarner( + proofData.Distribution, + uint32(distributionRoot.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 +} diff --git a/pkg/rewards/rewards.go b/pkg/rewards/rewards.go index 029a3dcc..87a0e195 100644 --- a/pkg/rewards/rewards.go +++ b/pkg/rewards/rewards.go @@ -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" @@ -12,6 +13,7 @@ import ( "github.com/wealdtech/go-merkletree/v2" "gorm.io/gorm/clause" "slices" + "strconv" "strings" "sync/atomic" "time" @@ -185,10 +187,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() @@ -206,12 +213,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) { @@ -480,7 +487,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 @@ -668,3 +675,93 @@ func (rc *RewardsCalculator) generateAndInsertFromQuery( rc.logger, ) } + +func (rc *RewardsCalculator) FindClaimableDistributionRoot(rootIndex int64) (*types.SubmittedDistributionRoot, error) { + 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 .rootIndex "-1" }} + and activated_at <= now() + {{ else }} + and sdr.root_index = {{.rootIndex}} + {{ end }} + order by sdr.root_index desc + limit 1 + ` + renderedQuery, err := rewardsUtils.RenderQueryTemplate(query, map[string]string{ + "rootIndex": strconv.Itoa(int(rootIndex)), + }) + 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 by root_index", + zap.Int64("rootIndex", rootIndex), + 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' + and grs.snapshot_date::timestamp(6) >= '{{.snapshotDate}}'::timestamp(6) + order by grs.snapshot_date asc + 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 +} + +type DistributionRoot struct { + types.SubmittedDistributionRoot + Disabled bool +} + +func (rc *RewardsCalculator) ListDistributionRoots() ([]*DistributionRoot, error) { + query := ` + select + sdr.*, + case when ddr.root_index is not null then true else false end as disabled + from submitted_distribution_roots as sdr + left join disabled_distribution_roots as ddr on (sdr.root_index = ddr.root_index) + order by root_index desc + ` + var submittedDistributionRoots []*DistributionRoot + res := rc.grm.Raw(query).Scan(&submittedDistributionRoots) + if res.Error != nil { + rc.logger.Sugar().Errorw("Failed to list submitted distribution roots", "error", res.Error) + return nil, res.Error + } + return submittedDistributionRoots, nil +} diff --git a/pkg/rewards/rewards_test.go b/pkg/rewards/rewards_test.go index ecfbda6d..a42d7f7e 100644 --- a/pkg/rewards/rewards_test.go +++ b/pkg/rewards/rewards_test.go @@ -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()) diff --git a/pkg/rpcServer/proofsHandlers.go b/pkg/rpcServer/proofsHandlers.go new file mode 100644 index 00000000..be4ca7d3 --- /dev/null +++ b/pkg/rpcServer/proofsHandlers.go @@ -0,0 +1,58 @@ +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() + rootIndex := req.GetRootIndex() + + var rootIndexVal int64 + if rootIndex == nil { + rootIndexVal = -1 + } else { + rootIndexVal = rootIndex.GetValue() + } + + root, claim, err := rpc.rewardsProofs.GenerateRewardsClaimProof(earner, tokens, rootIndexVal) + 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 +} diff --git a/pkg/rpcServer/rewardsHandlers.go b/pkg/rpcServer/rewardsHandlers.go index 53064852..916a6766 100644 --- a/pkg/rpcServer/rewardsHandlers.go +++ b/pkg/rpcServer/rewardsHandlers.go @@ -10,6 +10,7 @@ import ( "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" ) func (rpc *RpcServer) GetRewardsRoot(ctx context.Context, req *sidecarV1.GetRewardsRootRequest) (*sidecarV1.GetRewardsRootResponse, error) { @@ -83,7 +84,7 @@ func (rpc *RpcServer) GenerateRewardsRoot(ctx context.Context, req *sidecarV1.Ge zap.String("rewardsCalcEndDate", rewardsCalcEndDate), ) - accountTree, _, err := rpc.rewardsCalculator.MerkelizeRewardsForSnapshot(rewardsCalcEndDate) + accountTree, _, _, err := rpc.rewardsCalculator.MerkelizeRewardsForSnapshot(rewardsCalcEndDate) if err != nil { rpc.Logger.Sugar().Errorw("failed to merkelize rewards for snapshot", zap.Error(err), @@ -180,10 +181,6 @@ func (rpc *RpcServer) GetAttributableRewardsForDistributionRoot(ctx context.Cont return nil, status.Error(codes.Unimplemented, "method GetAttributableRewardsForDistributionRoot not implemented") } -func (rpc *RpcServer) GenerateClaimProof(ctx context.Context, req *sidecarV1.GenerateClaimProofRequest) (*sidecarV1.GenerateClaimProofResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GenerateClaimProof not implemented") -} - func (rpc *RpcServer) GetAvailableRewards(ctx context.Context, req *sidecarV1.GetAvailableRewardsRequest) (*sidecarV1.GetAvailableRewardsResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetAvailableRewards not implemented") } @@ -203,3 +200,31 @@ func (rpc *RpcServer) GetSummarizedRewardsForEarner(ctx context.Context, req *si func (rpc *RpcServer) GetClaimedRewardsByBlock(ctx context.Context, req *sidecarV1.GetClaimedRewardsByBlockRequest) (*sidecarV1.GetClaimedRewardsByBlockResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetClaimedRewardsByBlock not implemented") } + +func (rpc *RpcServer) ListDistributionRoots(ctx context.Context, req *sidecarV1.ListDistributionRootsRequest) (*sidecarV1.ListDistributionRootsResponse, error) { + roots, err := rpc.rewardsCalculator.ListDistributionRoots() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + responseRoots := make([]*sidecarV1.DistributionRoot, 0, len(roots)) + for _, root := range roots { + responseRoots = append(responseRoots, &sidecarV1.DistributionRoot{ + Root: root.Root, + RootIndex: root.RootIndex, + RewardsCalculationEnd: timestamppb.New(root.RewardsCalculationEnd), + RewardsCalculationEndUnit: root.RewardsCalculationEndUnit, + ActivatedAt: timestamppb.New(root.ActivatedAt), + ActivatedAtUnit: root.ActivatedAtUnit, + CreatedAtBlockNumber: root.CreatedAtBlockNumber, + TransactionHash: root.TransactionHash, + BlockHeight: root.BlockNumber, + LogIndex: root.LogIndex, + Disabled: root.Disabled, + }) + } + + return &sidecarV1.ListDistributionRootsResponse{ + DistributionRoots: responseRoots, + }, nil +} diff --git a/pkg/rpcServer/server.go b/pkg/rpcServer/server.go index b89e8cef..d4c186f8 100644 --- a/pkg/rpcServer/server.go +++ b/pkg/rpcServer/server.go @@ -9,6 +9,7 @@ import ( "github.com/Layr-Labs/sidecar/internal/logger" "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" "github.com/Layr-Labs/sidecar/pkg/eventBus/eventBusTypes" + "github.com/Layr-Labs/sidecar/pkg/proofs" "github.com/Layr-Labs/sidecar/pkg/rewards" "github.com/Layr-Labs/sidecar/pkg/rewardsCalculatorQueue" "github.com/Layr-Labs/sidecar/pkg/storage" @@ -38,6 +39,7 @@ type RpcServer struct { rewardsCalculator *rewards.RewardsCalculator rewardsQueue *rewardsCalculatorQueue.RewardsCalculatorQueue eventBus eventBusTypes.IEventBus + rewardsProofs *proofs.RewardsProofsStore } func NewRpcServer( @@ -47,6 +49,7 @@ func NewRpcServer( rc *rewards.RewardsCalculator, rcq *rewardsCalculatorQueue.RewardsCalculatorQueue, eb eventBusTypes.IEventBus, + rp *proofs.RewardsProofsStore, l *zap.Logger, ) *RpcServer { server := &RpcServer{ @@ -56,6 +59,7 @@ func NewRpcServer( rewardsCalculator: rc, rewardsQueue: rcq, eventBus: eb, + rewardsProofs: rp, Logger: l, } diff --git a/pkg/sidecar/sidecar.go b/pkg/sidecar/sidecar.go index 2642c1f6..4c75cfbf 100644 --- a/pkg/sidecar/sidecar.go +++ b/pkg/sidecar/sidecar.go @@ -6,6 +6,7 @@ import ( "github.com/Layr-Labs/sidecar/pkg/clients/ethereum" "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" "github.com/Layr-Labs/sidecar/pkg/pipeline" + "github.com/Layr-Labs/sidecar/pkg/proofs" "github.com/Layr-Labs/sidecar/pkg/rewards" "github.com/Layr-Labs/sidecar/pkg/rewardsCalculatorQueue" "github.com/Layr-Labs/sidecar/pkg/storage" @@ -27,6 +28,7 @@ type Sidecar struct { StateManager *stateManager.EigenStateManager RewardsCalculator *rewards.RewardsCalculator RewardsCalculatorQueue *rewardsCalculatorQueue.RewardsCalculatorQueue + RewardProofs *proofs.RewardsProofsStore ShutdownChan chan bool shouldShutdown *atomic.Bool } @@ -39,6 +41,7 @@ func NewSidecar( em *stateManager.EigenStateManager, rc *rewards.RewardsCalculator, rcq *rewardsCalculatorQueue.RewardsCalculatorQueue, + rp *proofs.RewardsProofsStore, l *zap.Logger, ethClient *ethereum.Client, ) *Sidecar { @@ -53,6 +56,7 @@ func NewSidecar( EthereumClient: ethClient, RewardsCalculator: rc, RewardsCalculatorQueue: rcq, + RewardProofs: rp, StateManager: em, ShutdownChan: make(chan bool), shouldShutdown: shouldShutdown,