Skip to content

Commit

Permalink
[Application] Add missing (un)staking events (#867)
Browse files Browse the repository at this point in the history
## Summary

Adds:
- `EventApplicationStake`
- `EventApplicationUnbondingBegin`
- `EventApplicationUnbondingEnd`
- `EventApplicationUnbondingCanceled`

Also refactors application min. stake unbonding to re-use existing
unbonding period and end-blocker mechanics instead of unbonding
immediately.

## Issue

- #858

## Type of change

Select one or more from the following:

- [x] New feature, functionality or library
- [ ] Consensus breaking; add the `consensus-breaking` label if so. See
#791 for details
- [ ] Bug fix
- [x] Code health or cleanup
- [ ] Documentation
- [ ] Other (specify)

## Testing

- [ ] **Documentation**: `make docusaurus_start`; only needed if you
make doc changes
- [ ] **Unit Tests**: `make go_develop_and_test`
- [ ] **LocalNet E2E Tests**: `make test_e2e`
- [ ] **DevNet E2E Tests**: Add the `devnet-test-e2e` label to the PR.

## Sanity Checklist

- [x] I have tested my changes using the available tooling
- [x] I have commented my code
- [x] I have performed a self-review of my own code; both comments &
source code
- [ ] I create and reference any new tickets, if applicable
- [ ] I have left TODOs throughout the codebase, if applicable

---------

Co-authored-by: Redouane Lakrache <[email protected]>
Co-authored-by: red-0ne <[email protected]>
  • Loading branch information
3 people authored Oct 21, 2024
1 parent 15cc450 commit c4d8449
Show file tree
Hide file tree
Showing 45 changed files with 6,000 additions and 1,390 deletions.
4,531 changes: 3,686 additions & 845 deletions api/poktroll/application/event.pulsar.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion api/poktroll/supplier/tx.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
upgradetypes "cosmossdk.io/x/upgrade/types"

"github.com/pokt-network/poktroll/app/upgrades"
)

Expand Down
1 change: 1 addition & 0 deletions app/upgrades/historical.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/cosmos/cosmos-sdk/types/module"
consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types"

"github.com/pokt-network/poktroll/app/keepers"
)

Expand Down
1 change: 1 addition & 0 deletions app/upgrades/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
storetypes "cosmossdk.io/store/types"
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/cosmos/cosmos-sdk/types/module"

"github.com/pokt-network/poktroll/app/keepers"
)

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ require (
require (
cosmossdk.io/x/tx v0.13.4
github.com/jhump/protoreflect v1.16.0
go.uber.org/mock v0.4.0
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,8 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
Expand Down
9 changes: 5 additions & 4 deletions pkg/client/delegation/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/events"
apptypes "github.com/pokt-network/poktroll/x/application/types"
)

const (
Expand Down Expand Up @@ -54,13 +55,13 @@ func NewDelegationClient(
opt(dClient)
}

dClient.eventsReplayClient, err = events.NewEventsReplayClient[client.Redelegation](
dClient.eventsReplayClient, err = events.NewEventsReplayClient[*apptypes.EventRedelegation](
ctx,
deps,
delegationEventQuery,
newRedelegationEventFactoryFn(),
defaultRedelegationsReplayLimit,
events.WithConnRetryLimit[client.Redelegation](dClient.connRetryLimit),
events.WithConnRetryLimit[*apptypes.EventRedelegation](dClient.connRetryLimit),
)
if err != nil {
return nil, err
Expand All @@ -77,7 +78,7 @@ type delegationClient struct {
// and the RedelegationReplayObservable type as its generic types.
// These enable the EventsReplayClient to correctly map the raw event bytes
// to Redelegation objects and to correctly return a RedelegationReplayObservable
eventsReplayClient client.EventsReplayClient[client.Redelegation]
eventsReplayClient client.EventsReplayClient[*apptypes.EventRedelegation]

// connRetryLimit is the number of times the underlying replay client
// should retry in the event that it encounters an error or its connection is interrupted.
Expand All @@ -92,7 +93,7 @@ func (b *delegationClient) RedelegationsSequence(ctx context.Context) client.Red
}

// LastNRedelegations returns the latest n redelegation events from the DelegationClient.
func (b *delegationClient) LastNRedelegations(ctx context.Context, n int) []client.Redelegation {
func (b *delegationClient) LastNRedelegations(ctx context.Context, n int) []*apptypes.EventRedelegation {
return b.eventsReplayClient.LastNEvents(ctx, n)
}

Expand Down
14 changes: 8 additions & 6 deletions pkg/client/delegation/client_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/delegation"
"github.com/pokt-network/poktroll/pkg/client/events"
"github.com/pokt-network/poktroll/testutil/network"
Expand Down Expand Up @@ -59,17 +58,20 @@ func TestDelegationClient_RedelegationsObservables(t *testing.T) {
// and then undelegate app1 from gateway and then undelegate app2 from gateway
// We expect to receive 4 delegation changes where the address of the
// Redelegation event alternates between app1 and app2
var previousRedelegation client.Redelegation
var previousRedelegation *apptypes.EventRedelegation
for change := range delegationSub.Ch() {
t.Logf("received delegation change: %+v", change)
// Verify that the Redelegation event is valid and that the address
// of the Redelegation event alternates between app1 and app2
if previousRedelegation != nil {
require.NotEqual(t, previousRedelegation.GetAppAddress(), change.GetAppAddress())
if previousRedelegation.GetAppAddress() == appAddresses[0] {
require.Equal(t, appAddresses[1], change.GetAppAddress())
previousAppAddr := previousRedelegation.GetApplication().GetAddress()
changeAppAddr := change.GetApplication().GetAddress()

require.NotEqual(t, previousAppAddr, changeAppAddr)
if previousAppAddr == appAddresses[0] {
require.Equal(t, appAddresses[1], changeAppAddr)
} else {
require.Equal(t, appAddresses[0], change.GetAppAddress())
require.Equal(t, appAddresses[0], changeAppAddr)
}
}
previousRedelegation = change
Expand Down
24 changes: 12 additions & 12 deletions pkg/client/delegation/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
"cosmossdk.io/depinject"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/delegation"
"github.com/pokt-network/poktroll/testutil/sample"
"github.com/pokt-network/poktroll/testutil/testclient/testdelegation"
"github.com/pokt-network/poktroll/testutil/testclient/testeventsquery"
apptypes "github.com/pokt-network/poktroll/x/application/types"
)

const (
Expand Down Expand Up @@ -47,27 +47,27 @@ func TestDelegationClient(t *testing.T) {

tests := []struct {
name string
fn func() client.Redelegation
fn func() *apptypes.EventRedelegation
}{
{
name: "LastNRedelegations successfully returns latest redelegation",
fn: func() client.Redelegation {
fn: func() *apptypes.EventRedelegation {
lastRedelegation := delegationClient.LastNRedelegations(ctx, 1)[0]
require.Equal(t, expectedAppAddress, lastRedelegation.GetAppAddress())
require.Equal(t, expectedGatewayAddress, lastRedelegation.GetGatewayAddress())
require.Equal(t, expectedAppAddress, lastRedelegation.GetApplication().GetAddress())
require.Contains(t, lastRedelegation.GetApplication().GetDelegateeGatewayAddresses(), expectedGatewayAddress)
return lastRedelegation
},
},
{
name: "RedelegationsSequence successfully returns latest redelegation",
fn: func() client.Redelegation {
fn: func() *apptypes.EventRedelegation {
redelegationObs := delegationClient.RedelegationsSequence(ctx)
require.NotNil(t, redelegationObs)

// Ensure that the observable is replayable via Last.
lastRedelegation := redelegationObs.Last(ctx, 1)[0]
require.Equal(t, expectedAppAddress, lastRedelegation.GetAppAddress())
require.Equal(t, expectedGatewayAddress, lastRedelegation.GetGatewayAddress())
require.Equal(t, expectedAppAddress, lastRedelegation.GetApplication().GetAddress())
require.Contains(t, lastRedelegation.GetApplication().GetDelegateeGatewayAddresses(), expectedGatewayAddress)

return lastRedelegation
},
Expand All @@ -76,20 +76,20 @@ func TestDelegationClient(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actualRedelegationCh := make(chan client.Redelegation, 10)
actualRedelegationCh := make(chan *apptypes.EventRedelegation, 10)

// Run test functions asynchronously because they can block, leading
// to an unresponsive test. If any of the methods under test hang,
// the test will time out in the select statement that follows.
go func(fn func() client.Redelegation) {
go func(fn func() *apptypes.EventRedelegation) {
actualRedelegationCh <- fn()
close(actualRedelegationCh)
}(test.fn)

select {
case actualRedelegation := <-actualRedelegationCh:
require.Equal(t, expectedAppAddress, actualRedelegation.GetAppAddress())
require.Equal(t, expectedGatewayAddress, actualRedelegation.GetGatewayAddress())
require.Equal(t, expectedAppAddress, actualRedelegation.GetApplication().GetAddress())
require.Contains(t, actualRedelegation.GetApplication().GetDelegateeGatewayAddresses(), expectedGatewayAddress)
case <-time.After(testTimeoutDuration):
t.Fatal("timed out waiting for redelegation event")
}
Expand Down
68 changes: 18 additions & 50 deletions pkg/client/delegation/redelegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,26 @@ package delegation
// of listening to all events and doing a verbose filter.

import (
"strconv"
"strings"

cosmostypes "github.com/cosmos/cosmos-sdk/types"

"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/events"
"github.com/pokt-network/poktroll/pkg/client/tx"
apptypes "github.com/pokt-network/poktroll/x/application/types"
)

// redelegationEventType is the type of the EventRedelegation event emitted by
// both the MsgDelegateToGateway and MsgUndelegateFromGateway messages.
const redelegationEventType = "poktroll.application.EventRedelegation"

var _ client.Redelegation = (*redelegation)(nil)

// redelegation wraps the EventRedelegation event emitted by the application
// module, for use in the observable, it is one of the log entries embedded
// within the log field of the response struct from the app module's query.
type redelegation struct {
AppAddress string `json:"app_address"`
GatewayAddress string `json:"gateway_address"`
}

// GetAppAddress returns the application address of the redelegation event
func (d redelegation) GetAppAddress() string {
return d.AppAddress
}

// GetGatewayAddress returns the gateway address of the redelegation event
func (d redelegation) GetGatewayAddress() string {
return d.GatewayAddress
}
var redelegationEventType = cosmostypes.MsgTypeURL(&apptypes.EventRedelegation{})

// newRedelegationEventFactoryFn is a factory function that returns a
// function that attempts to deserialize the given bytes into a redelegation
// struct. If the delegate struct has an empty app address then an
// ErrUnmarshalRedelegation error is returned. Otherwise if deserialisation
// fails then the error is returned.
func newRedelegationEventFactoryFn() events.NewEventsFn[client.Redelegation] {
return func(eventBz []byte) (client.Redelegation, error) {
func newRedelegationEventFactoryFn() events.NewEventsFn[*apptypes.EventRedelegation] {
return func(eventBz []byte) (*apptypes.EventRedelegation, error) {
// Try to deserialize the provided bytes into an abci.TxResult.
txResult, err := tx.UnmarshalTxResult(eventBz)
if err != nil {
Expand All @@ -51,37 +33,23 @@ func newRedelegationEventFactoryFn() events.NewEventsFn[client.Redelegation] {

// Iterate through the log entries to find EventRedelegation
for _, event := range txResult.Result.Events {
if event.GetType() != redelegationEventType {
if strings.Trim(event.GetType(), "/") != strings.Trim(redelegationEventType, "/") {
continue
}
var redelegationEvent redelegation
for _, attr := range event.Attributes {
switch attr.Key {
case "app_address":
appAddr, err := unescape(attr.Value)
if err != nil {
return nil, events.ErrEventsUnmarshalEvent.Wrapf("cannot retrieve app address: %v", err)
}
redelegationEvent.AppAddress = appAddr
case "gateway_address":
gatewayAddr, err := unescape(attr.Value)
if err != nil {
return nil, events.ErrEventsUnmarshalEvent.Wrapf("cannot retrieve gateway address: %v", err)
}
redelegationEvent.GatewayAddress = gatewayAddr
}

typedEvent, err := cosmostypes.ParseTypedEvent(event)
if err != nil {
return nil, err
}
// Handle the redelegation event
if redelegationEvent.AppAddress == "" || redelegationEvent.GatewayAddress == "" {
return nil, events.ErrEventsUnmarshalEvent.
Wrapf("empty redelegation event: %s", string(eventBz))

redelegationEvent, ok := typedEvent.(*apptypes.EventRedelegation)
if !ok {
return nil, events.ErrEventsUnmarshalEvent.Wrapf("unexpected event type: %T", typedEvent)
}

// TODO_MAINNET(@bryanchriswhite): Refactor DelegationClient and/or ReplayClient to support multiple events per tx.
return redelegationEvent, nil
}
return nil, events.ErrEventsUnmarshalEvent.Wrap("no redelegation event found")
}
}

func unescape(s string) (string, error) {
return strconv.Unquote(s)
}
14 changes: 3 additions & 11 deletions pkg/client/interface.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//go:generate mockgen -destination=../../testutil/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient
//go:generate mockgen -destination=../../testutil/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient
//go:generate mockgen -destination=../../testutil/mockclient/delegation_client_mock.go -package=mockclient . Redelegation,DelegationClient
//go:generate mockgen -destination=../../testutil/mockclient/delegation_client_mock.go -package=mockclient . DelegationClient
//go:generate mockgen -destination=../../testutil/mockclient/tx_client_mock.go -package=mockclient . TxContext,TxClient
//go:generate mockgen -destination=../../testutil/mockclient/supplier_client_mock.go -package=mockclient . SupplierClient
//go:generate mockgen -destination=../../testutil/mockclient/account_query_client_mock.go -package=mockclient . AccountQueryClient
Expand Down Expand Up @@ -136,14 +136,6 @@ type Block interface {
Txs() []comettypes.Tx
}

// Redelegation is an interface which wraps the EventRedelegation event
// emitted by the application module.
// See: proto/poktroll/application/types/event.proto#EventRedelegation
type Redelegation interface {
GetAppAddress() string
GetGatewayAddress() string
}

// EventsObservable is a replay observable for events of some type T.
// NB: This cannot be an alias due to gomock's lack of support for generic types.
type EventsObservable[T any] observable.ReplayObservable[T]
Expand Down Expand Up @@ -180,7 +172,7 @@ type BlockClient interface {
// RedelegationReplayObservable is a defined type which is a replay observable
// of type Redelegation.
// NB: This cannot be an alias due to gomock's lack of support for generic types.
type RedelegationReplayObservable EventsObservable[Redelegation]
type RedelegationReplayObservable EventsObservable[*apptypes.EventRedelegation]

// DelegationClient is an interface that wraps the EventsReplayClient interface
// specific for the EventsReplayClient[Redelegation] implementation
Expand All @@ -190,7 +182,7 @@ type DelegationClient interface {
RedelegationsSequence(context.Context) RedelegationReplayObservable
// LastNRedelegations returns the latest N redelegation events that have
// occurred on chain.
LastNRedelegations(context.Context, int) []Redelegation
LastNRedelegations(context.Context, int) []*apptypes.EventRedelegation
// Close unsubscribes all observers of the committed block sequence
// observable and closes the events query client.
Close()
Expand Down
12 changes: 7 additions & 5 deletions pkg/crypto/rings/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/pokt-network/poktroll/pkg/crypto"
"github.com/pokt-network/poktroll/pkg/observable/channel"
"github.com/pokt-network/poktroll/pkg/polylog"
apptypes "github.com/pokt-network/poktroll/x/application/types"
"github.com/pokt-network/poktroll/x/service/types"
)

Expand Down Expand Up @@ -87,19 +88,20 @@ func (rc *ringCache) goInvalidateCache(ctx context.Context) {
redelegationObs := rc.delegationClient.RedelegationsSequence(ctx)
// For each redelegation event, check if the redelegation events'
// app address is in the cache. If it is, invalidate the cache entry.
channel.ForEach[client.Redelegation](
channel.ForEach[*apptypes.EventRedelegation](
ctx, redelegationObs,
func(ctx context.Context, redelegation client.Redelegation) {
func(ctx context.Context, redelegation *apptypes.EventRedelegation) {
// Lock ringsByAddr for writing.
rc.ringsByAddrMu.Lock()
defer rc.ringsByAddrMu.Unlock()
// Check if the redelegation event's app address is in the cache.
if _, ok := rc.ringsByAddr[redelegation.GetAppAddress()]; ok {
appAddr := redelegation.GetApplication().GetAddress()
if _, ok := rc.ringsByAddr[appAddr]; ok {
rc.logger.Debug().
Str("app_address", redelegation.GetAppAddress()).
Str("app_address", appAddr).
Msg("redelegation event received; invalidating ringsByAddr entry")
// Invalidate the ringsByAddr entry.
delete(rc.ringsByAddr, redelegation.GetAppAddress())
delete(rc.ringsByAddr, appAddr)
}
})
}
Expand Down
Loading

0 comments on commit c4d8449

Please sign in to comment.