Skip to content

Commit

Permalink
Return estimated unbonding time (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
asalzmann committed Jan 25, 2024
1 parent 43156c1 commit d599415
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 93 deletions.
14 changes: 12 additions & 2 deletions proto/stride/staketia/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ message QueryRedemptionRecordRequest {
string address = 2;
};
message QueryRedemptionRecordResponse {
RedemptionRecord redemption_record = 1;
RedemptionRecordResponse redemption_record_response = 1;
}

// All Redemption Records
Expand All @@ -96,7 +96,7 @@ message QueryRedemptionRecordsRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 3;
};
message QueryRedemptionRecordsResponse {
repeated RedemptionRecord redemption_records = 1
repeated RedemptionRecordResponse redemption_record_responses = 1
[ (gogoproto.nullable) = false ];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
Expand All @@ -106,3 +106,13 @@ message QuerySlashRecordsRequest {};
message QuerySlashRecordsResponse {
repeated SlashRecord slash_records = 1 [ (gogoproto.nullable) = false ];
}

// Data structure for frontend to consume
message RedemptionRecordResponse {
// Redemption record
RedemptionRecord redemption_record = 1;

// The Unix timestamp (in seconds) at which the unbonding for the UR
// associated with this RR completes
uint64 unbonding_completion_time_seconds = 2;
}
61 changes: 52 additions & 9 deletions x/staketia/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"time"

"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -72,7 +73,14 @@ func (k Keeper) RedemptionRecord(c context.Context, req *types.QueryRedemptionRe
"no redemption record found for unbonding ID %d and address %s", req.UnbondingRecordId, req.Address)
}

return &types.QueryRedemptionRecordResponse{RedemptionRecord: &redemptionRecord}, nil
// Get the unbonding time from the unbonding record
unbondingRecord, found := k.GetUnbondingRecord(ctx, req.UnbondingRecordId)
if !found {
return &types.QueryRedemptionRecordResponse{}, types.ErrUnbondingRecordNotFound
}

redemptionRecordResponse := types.NewRedemptionRecordResponse(redemptionRecord, unbondingRecord.UnbondingCompletionTimeSeconds)
return &types.QueryRedemptionRecordResponse{RedemptionRecordResponse: &redemptionRecordResponse}, nil
}

// Queries all redemption records with an optional filter by address
Expand All @@ -82,23 +90,55 @@ func (k Keeper) RedemptionRecords(c context.Context, req *types.QueryRedemptionR
}

ctx := sdk.UnwrapSDKContext(c)
redemptionRecords := []types.RedemptionRecord{}
redemptionRecordResponses := []types.RedemptionRecordResponse{}

// Create a map of estimated unbonding time by UnbondingRecord
unbondingTimeMap := map[uint64]uint64{}
unbondingRecords := k.GetAllActiveUnbondingRecords(ctx)
zone, err := k.GetHostZone(ctx)
if err != nil {
return &types.QueryRedemptionRecordsResponse{}, types.ErrHostZoneNotFound
}
fourDays := time.Duration(4) * time.Hour * 24
unbondingLength := time.Duration(zone.UnbondingPeriodSeconds) * time.Second // 21 days
estimatedUnbondingTime := uint64(ctx.BlockTime().Add(unbondingLength).Add(fourDays).Unix()) // 21 days from now + 4 day buffer
for _, unbondingRecord := range unbondingRecords {
// Edge case: a user has submitted a redemption, but the corresponding unbonding record has not been confirmed, meaning
// the unbonding completion time is 0. Give a rough estimate.
if unbondingRecord.UnbondingCompletionTimeSeconds == 0 {
unbondingTimeMap[unbondingRecord.Id] = estimatedUnbondingTime
continue
}
unbondingTimeMap[unbondingRecord.Id] = unbondingRecord.UnbondingCompletionTimeSeconds
}

// If they specify an address, search for that address and only return the matches
if req.Address != "" {
redemptionRecords := k.GetRedemptionRecordsFromAddress(ctx, req.Address)
// Iterate records and create response objects
redemptionRecordResponses := []types.RedemptionRecordResponse{}
for _, redemptionRecord := range redemptionRecords {
unbondingTime := unbondingTimeMap[redemptionRecord.UnbondingRecordId]
redemptionRecordResponses = append(redemptionRecordResponses, types.NewRedemptionRecordResponse(redemptionRecord, unbondingTime))
}
return &types.QueryRedemptionRecordsResponse{
RedemptionRecords: redemptionRecords,
Pagination: nil,
RedemptionRecordResponses: redemptionRecordResponses,
Pagination: nil,
}, nil
}

// If they specify an unbonding record ID, grab just the records for that ID
if req.UnbondingRecordId != 0 {
unbondingTime := unbondingTimeMap[req.UnbondingRecordId]
redemptionRecords := k.GetRedemptionRecordsFromUnbondingId(ctx, req.UnbondingRecordId)
redemptionRecordResponses := []types.RedemptionRecordResponse{}
// Iterate records and create response objects
for _, redemptionRecord := range redemptionRecords {
redemptionRecordResponses = append(redemptionRecordResponses, types.NewRedemptionRecordResponse(redemptionRecord, unbondingTime))
}
return &types.QueryRedemptionRecordsResponse{
RedemptionRecords: redemptionRecords,
Pagination: nil,
RedemptionRecordResponses: redemptionRecordResponses,
Pagination: nil,
}, nil
}

Expand All @@ -112,16 +152,19 @@ func (k Keeper) RedemptionRecords(c context.Context, req *types.QueryRedemptionR
return err
}

redemptionRecords = append(redemptionRecords, redemptionRecord)
unbondingTime := unbondingTimeMap[redemptionRecord.UnbondingRecordId]
redemptionRecordResponse := types.NewRedemptionRecordResponse(redemptionRecord, unbondingTime)

redemptionRecordResponses = append(redemptionRecordResponses, redemptionRecordResponse)
return nil
})
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

return &types.QueryRedemptionRecordsResponse{
RedemptionRecords: redemptionRecords,
Pagination: pageRes,
RedemptionRecordResponses: redemptionRecordResponses,
Pagination: pageRes,
}, nil
}

Expand Down
42 changes: 34 additions & 8 deletions x/staketia/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ func (s *KeeperTestSuite) TestQueryRedemptionRecord() {
queriedUnbondingRecordId := uint64(2)
queriedAddress := "address-B"

unbondingRecords := []types.UnbondingRecord{
{Id: 1, UnbondingCompletionTimeSeconds: 12345},
{Id: 2, UnbondingCompletionTimeSeconds: 12346},
{Id: 3, UnbondingCompletionTimeSeconds: 12347},
{Id: 4, UnbondingCompletionTimeSeconds: 12348},
}
for _, unbondingRecord := range unbondingRecords {
s.App.StaketiaKeeper.SetUnbondingRecord(s.Ctx, unbondingRecord)
}
// map unbonding record id to unbonding time
unbondingTimeMap := map[uint64]uint64{}
for _, unbondingRecord := range unbondingRecords {
unbondingTimeMap[unbondingRecord.Id] = unbondingRecord.UnbondingCompletionTimeSeconds
}

redemptionRecords := []types.RedemptionRecord{
{UnbondingRecordId: 1, Redeemer: "address-A"},
{UnbondingRecordId: 2, Redeemer: "address-B"},
Expand All @@ -122,13 +137,17 @@ func (s *KeeperTestSuite) TestQueryRedemptionRecord() {
resp, err := s.App.StaketiaKeeper.RedemptionRecord(sdk.WrapSDKContext(s.Ctx), req)
s.Require().NoError(err, "no error expected when querying redemption record")

s.Require().Equal(queriedUnbondingRecordId, resp.RedemptionRecord.UnbondingRecordId, "redemption record unbonding ID")
s.Require().Equal(queriedAddress, resp.RedemptionRecord.Redeemer, "redemption record address")
s.Require().Equal(queriedUnbondingRecordId, resp.RedemptionRecordResponse.RedemptionRecord.UnbondingRecordId, "redemption record unbonding ID")
s.Require().Equal(queriedAddress, resp.RedemptionRecordResponse.RedemptionRecord.Redeemer, "redemption record address")
s.Require().Equal(unbondingTimeMap[queriedUnbondingRecordId], resp.RedemptionRecordResponse.UnbondingCompletionTimeSeconds, "redemption record unbonding time")
}

func (s *KeeperTestSuite) TestQueryAllRedemptionRecords_Address() {
queriedAddress := "address-B"
expectedUnbondingRecordIds := []uint64{2, 4}
s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{
UnbondingPeriodSeconds: 10000,
})
allRedemptionRecords := []types.RedemptionRecord{
{UnbondingRecordId: 1, Redeemer: "address-A"},
{UnbondingRecordId: 2, Redeemer: "address-B"},
Expand All @@ -147,13 +166,16 @@ func (s *KeeperTestSuite) TestQueryAllRedemptionRecords_Address() {
s.Require().Nil(resp.Pagination, "pagination should be nil since it all fits on one page")

actualUnbondingRecordIds := []uint64{}
for _, record := range resp.RedemptionRecords {
actualUnbondingRecordIds = append(actualUnbondingRecordIds, record.UnbondingRecordId)
for _, resp := range resp.RedemptionRecordResponses {
actualUnbondingRecordIds = append(actualUnbondingRecordIds, resp.RedemptionRecord.UnbondingRecordId)
}
s.Require().ElementsMatch(expectedUnbondingRecordIds, actualUnbondingRecordIds)
}

func (s *KeeperTestSuite) TestQueryAllRedemptionRecords_UnbondingRecordId() {
s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{
UnbondingPeriodSeconds: 10000,
})
queriedUnbondingRecordId := uint64(2)
expectedAddresss := []string{"address-B", "address-D"}
allRedemptionRecords := []types.RedemptionRecord{
Expand All @@ -174,13 +196,17 @@ func (s *KeeperTestSuite) TestQueryAllRedemptionRecords_UnbondingRecordId() {
s.Require().Nil(resp.Pagination, "pagination should be nil since it all fits on one page")

actualAddresss := []string{}
for _, record := range resp.RedemptionRecords {
actualAddresss = append(actualAddresss, record.Redeemer)
for _, response := range resp.RedemptionRecordResponses {
actualAddresss = append(actualAddresss, response.RedemptionRecord.Redeemer)
}
s.Require().ElementsMatch(expectedAddresss, actualAddresss)
}

func (s *KeeperTestSuite) TestQueryAllRedemptionRecords_Pagination() {
s.App.StaketiaKeeper.SetHostZone(s.Ctx, types.HostZone{
UnbondingPeriodSeconds: 10000,
})

// Set more records than what will fit on one page
pageLimit := 50
numExcessRecords := 10
Expand All @@ -201,7 +227,7 @@ func (s *KeeperTestSuite) TestQueryAllRedemptionRecords_Pagination() {
s.Require().NoError(err, "no error expected when querying all redemption records")

// Confirm only the first page was returned
s.Require().Equal(pageLimit, len(resp.RedemptionRecords), "only the first page should be returned")
s.Require().Equal(pageLimit, len(resp.RedemptionRecordResponses), "only the first page should be returned")

// Attempt one more page, and it should get the remainder
req = &types.QueryRedemptionRecordsRequest{
Expand All @@ -211,7 +237,7 @@ func (s *KeeperTestSuite) TestQueryAllRedemptionRecords_Pagination() {
}
resp, err = s.App.StaketiaKeeper.RedemptionRecords(sdk.WrapSDKContext(s.Ctx), req)
s.Require().NoError(err, "no error expected when querying all redemption records on second page")
s.Require().Equal(numExcessRecords, len(resp.RedemptionRecords), "only the remainder should be returned")
s.Require().Equal(numExcessRecords, len(resp.RedemptionRecordResponses), "only the remainder should be returned")
}

func (s *KeeperTestSuite) TestQuerySlashRecords() {
Expand Down
Loading

0 comments on commit d599415

Please sign in to comment.