diff --git a/.changelog/2740.feature.md b/.changelog/2740.feature.md new file mode 100644 index 00000000000..7cc16db6849 --- /dev/null +++ b/.changelog/2740.feature.md @@ -0,0 +1,4 @@ +go/staking: Add a Delegatons call, and expose over gRPC + +This adds a Delegations call in the spirt of DebondingDelegations that +returns a map of delegations for a given delegator. \ No newline at end of file diff --git a/go/consensus/tendermint/apps/staking/query.go b/go/consensus/tendermint/apps/staking/query.go index b4bb6379bdc..7dcd4f852c9 100644 --- a/go/consensus/tendermint/apps/staking/query.go +++ b/go/consensus/tendermint/apps/staking/query.go @@ -19,6 +19,7 @@ type Query interface { DebondingInterval(context.Context) (epochtime.EpochTime, error) Accounts(context.Context) ([]signature.PublicKey, error) AccountInfo(context.Context, signature.PublicKey) (*staking.Account, error) + Delegations(context.Context, signature.PublicKey) (map[signature.PublicKey]*staking.Delegation, error) DebondingDelegations(context.Context, signature.PublicKey) (map[signature.PublicKey][]*staking.DebondingDelegation, error) Genesis(context.Context) (*staking.Genesis, error) } @@ -91,6 +92,10 @@ func (sq *stakingQuerier) AccountInfo(ctx context.Context, id signature.PublicKe return sq.state.Account(id), nil } +func (sq *stakingQuerier) Delegations(ctx context.Context, id signature.PublicKey) (map[signature.PublicKey]*staking.Delegation, error) { + return sq.state.DelegationsFor(id) +} + func (sq *stakingQuerier) DebondingDelegations(ctx context.Context, id signature.PublicKey) (map[signature.PublicKey][]*staking.DebondingDelegation, error) { return sq.state.DebondingDelegationsFor(id) } diff --git a/go/consensus/tendermint/apps/staking/state/state.go b/go/consensus/tendermint/apps/staking/state/state.go index 6cda4ae1b5f..c860d636c99 100644 --- a/go/consensus/tendermint/apps/staking/state/state.go +++ b/go/consensus/tendermint/apps/staking/state/state.go @@ -237,6 +237,33 @@ func (s *ImmutableState) Delegation(delegatorID, escrowID signature.PublicKey) * return &del } +func (s *ImmutableState) DelegationsFor(delegatorID signature.PublicKey) (map[signature.PublicKey]*staking.Delegation, error) { + delegations := make(map[signature.PublicKey]*staking.Delegation) + s.Snapshot.IterateRange( + delegationKeyFmt.Encode(), + nil, + true, + func(key, value []byte) bool { + var escrowID signature.PublicKey + var decDelegatorID signature.PublicKey + if !delegationKeyFmt.Decode(key, &escrowID, &decDelegatorID) || !decDelegatorID.Equal(delegatorID) { + return true + } + + var del staking.Delegation + if err := cbor.Unmarshal(value, &del); err != nil { + panic("staking: corrupt delegation state: " + err.Error()) + } + + delegations[escrowID] = &del + + return false + }, + ) + + return delegations, nil +} + func (s *ImmutableState) DebondingDelegations() (map[signature.PublicKey]map[signature.PublicKey][]*staking.DebondingDelegation, error) { delegations := make(map[signature.PublicKey]map[signature.PublicKey][]*staking.DebondingDelegation) s.Snapshot.IterateRange( diff --git a/go/consensus/tendermint/staking/staking.go b/go/consensus/tendermint/staking/staking.go index 927cf7c06e7..856e18d6243 100644 --- a/go/consensus/tendermint/staking/staking.go +++ b/go/consensus/tendermint/staking/staking.go @@ -80,6 +80,15 @@ func (tb *tendermintBackend) AccountInfo(ctx context.Context, query *api.OwnerQu return q.AccountInfo(ctx, query.Owner) } +func (tb *tendermintBackend) Delegations(ctx context.Context, query *api.OwnerQuery) (map[signature.PublicKey]*api.Delegation, error) { + q, err := tb.querier.QueryAt(ctx, query.Height) + if err != nil { + return nil, err + } + + return q.Delegations(ctx, query.Owner) +} + func (tb *tendermintBackend) DebondingDelegations(ctx context.Context, query *api.OwnerQuery) (map[signature.PublicKey][]*api.DebondingDelegation, error) { q, err := tb.querier.QueryAt(ctx, query.Height) if err != nil { diff --git a/go/staking/api/api.go b/go/staking/api/api.go index 128376493ab..4e4e6930c4f 100644 --- a/go/staking/api/api.go +++ b/go/staking/api/api.go @@ -84,6 +84,10 @@ type Backend interface { // AccountInfo returns the account descriptor for the given account. AccountInfo(ctx context.Context, query *OwnerQuery) (*Account, error) + // Delegations returns the list of delegations for the given owner + // (delegator). + Delegations(ctx context.Context, query *OwnerQuery) (map[signature.PublicKey]*Delegation, error) + // DebondingDelegations returns the list of debonding delegations for // the given owner (delegator). DebondingDelegations(ctx context.Context, query *OwnerQuery) (map[signature.PublicKey][]*DebondingDelegation, error) diff --git a/go/staking/api/grpc.go b/go/staking/api/grpc.go index df1256fefc0..4133f025769 100644 --- a/go/staking/api/grpc.go +++ b/go/staking/api/grpc.go @@ -25,6 +25,8 @@ var ( methodAccounts = serviceName.NewMethod("Accounts", int64(0)) // methodAccountInfo is the AccountInfo method. methodAccountInfo = serviceName.NewMethod("AccountInfo", OwnerQuery{}) + // methodDelegations is the Delegations method. + methodDelegations = serviceName.NewMethod("Delegations", OwnerQuery{}) // methodDebondingDelegations is the DebondingDelegations method. methodDebondingDelegations = serviceName.NewMethod("DebondingDelegations", OwnerQuery{}) // methodStateToGenesis is the StateToGenesis method. @@ -62,6 +64,10 @@ var ( MethodName: methodAccountInfo.ShortName(), Handler: handlerAccountInfo, }, + { + MethodName: methodDelegations.ShortName(), + Handler: handlerDelegations, + }, { MethodName: methodDebondingDelegations.ShortName(), Handler: handlerDebondingDelegations, @@ -206,6 +212,29 @@ func handlerAccountInfo( // nolint: golint return interceptor(ctx, &query, info, handler) } +func handlerDelegations( // nolint: golint + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + var query OwnerQuery + if err := dec(&query); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(Backend).Delegations(ctx, &query) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodDelegations.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(Backend).Delegations(ctx, req.(*OwnerQuery)) + } + return interceptor(ctx, &query, info, handler) +} + func handlerDebondingDelegations( // nolint: golint srv interface{}, ctx context.Context, @@ -385,6 +414,14 @@ func (c *stakingClient) AccountInfo(ctx context.Context, query *OwnerQuery) (*Ac return &rsp, nil } +func (c *stakingClient) Delegations(ctx context.Context, query *OwnerQuery) (map[signature.PublicKey]*Delegation, error) { + var rsp map[signature.PublicKey]*Delegation + if err := c.conn.Invoke(ctx, methodDelegations.FullName(), query, &rsp); err != nil { + return nil, err + } + return rsp, nil +} + func (c *stakingClient) DebondingDelegations(ctx context.Context, query *OwnerQuery) (map[signature.PublicKey][]*DebondingDelegation, error) { var rsp map[signature.PublicKey][]*DebondingDelegation if err := c.conn.Invoke(ctx, methodDebondingDelegations.FullName(), query, &rsp); err != nil {