-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Applicaton] Provable past delegations & delayed undelegations #497
Changes from all commits
9e0b6e6
210c2ba
f4bf348
6db9bf5
d94384e
264f51b
a3f8a2e
87acb41
400a207
47584b9
1828042
0a7873c
9a662b7
378bca8
63b76b4
b6b81ef
2d50588
9f6c037
2c5d592
e17ae43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,61 @@ | ||||||
Feature: Delegate Namespaces | ||||||
|
||||||
Background: | ||||||
# Setup Tx and NewBlock event query clients for each scenario | ||||||
Given the poktroll chain is reachable | ||||||
Comment on lines
+4
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally we wouldn't need the comments and the step would be self explanatory How about |
||||||
|
||||||
Scenario: User can delegate Application to Gateway | ||||||
Given the user has the pocketd binary installed | ||||||
And the actor type "application" with account "app1" is staked with enough uPOKT | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked at the implementation of I don't fully understand the intention - yet - but there's an opportunity to make it clearer. |
||||||
And the actor type "gateway" with account "gateway1" is staked with enough uPOKT | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||||||
# Ensure that the application does not have any delegation | ||||||
# This is to avoid any conflicts with previous delegations added by | ||||||
# genesis transactions or previous tests. | ||||||
And the application "app1" does not have any delegation | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
When the user delegates application "app1" to gateway "gateway1" | ||||||
Then application "app1" is delegated to gateway "gateway1" | ||||||
|
||||||
Scenario: User can undelegate Application from Gateway | ||||||
Given the user has the pocketd binary installed | ||||||
And the actor type "application" with account "app1" is staked with enough uPOKT | ||||||
And the actor type "gateway" with account "gateway1" is staked with enough uPOKT | ||||||
And the application "app1" does not have any delegation | ||||||
When the user delegates application "app1" to gateway "gateway1" | ||||||
# Ensure that the undelegation does not happen in the last block of the | ||||||
# sesssion which could prevent observing that is only effective in the next session. | ||||||
And the user undelegates application "app1" from gateway "gateway1" before the session end block | ||||||
# Undelegation is not effective yet. | ||||||
Then application "app1" is delegated to gateway "gateway1" | ||||||
When the user waits until the start of the next session | ||||||
# The undelegation becomes effective. | ||||||
Then application "app1" is not delegated to gateway "gateway1" | ||||||
And application "app1" has gateway "gateway1" address in the archived delegations | ||||||
|
||||||
Scenario: Application can override undelegation from a Gateway | ||||||
Given the user has the pocketd binary installed | ||||||
And the actor type "application" with account "app1" is staked with enough uPOKT | ||||||
And the actor type "gateway" with account "gateway1" is staked with enough uPOKT | ||||||
And the application "app1" does not have any delegation | ||||||
When the user delegates application "app1" to gateway "gateway1" | ||||||
# Undelegate before the current session's end block so the next step can override it. | ||||||
And the user undelegates application "app1" from gateway "gateway1" before the session end block | ||||||
# The user redelegates to the same gateway before the next session. | ||||||
# This should override the previous undelegation. | ||||||
And the user delegates application "app1" to gateway "gateway1" | ||||||
# Wait until the next session to see that the undelegation did not take effect. | ||||||
And the user waits until the start of the next session | ||||||
Then application "app1" is delegated to gateway "gateway1" | ||||||
|
||||||
Scenario: Application gets its archived delegations pruned | ||||||
Given the user has the pocketd binary installed | ||||||
And the actor type "application" with account "app1" is staked with enough uPOKT | ||||||
And the actor type "gateway" with account "gateway1" is staked with enough uPOKT | ||||||
And the application "app1" does not have any delegation | ||||||
When the user delegates application "app1" to gateway "gateway1" | ||||||
And the user undelegates application "app1" from gateway "gateway1" | ||||||
And the user waits until the start of the next session | ||||||
Then application "app1" is not delegated to gateway "gateway1" | ||||||
And application "app1" has gateway "gateway1" address in the archived delegations | ||||||
When the user waits until archived delegations are pruned | ||||||
Then application "app1" is not delegated to gateway "gateway1" | ||||||
And application "app1" does not have gateway "gateway1" address in the archived delegations |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
//go:build e2e | ||
|
||
package e2e | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
"sync" | ||
|
||
"cosmossdk.io/depinject" | ||
"github.com/cometbft/cometbft/libs/json" | ||
"github.com/stretchr/testify/require" | ||
"golang.org/x/exp/slices" | ||
|
||
"github.com/pokt-network/poktroll/pkg/client" | ||
"github.com/pokt-network/poktroll/pkg/client/block" | ||
"github.com/pokt-network/poktroll/pkg/client/events" | ||
"github.com/pokt-network/poktroll/pkg/client/tx" | ||
"github.com/pokt-network/poktroll/testutil/testclient" | ||
appkeeper "github.com/pokt-network/poktroll/x/application/keeper" | ||
"github.com/pokt-network/poktroll/x/application/types" | ||
sessionkeeper "github.com/pokt-network/poktroll/x/session/keeper" | ||
) | ||
|
||
const ( | ||
// serviceId to stake for by applications. | ||
serviceId = "anvil" | ||
// defaultStakeAmount is the default amount to stake for applications and gateways. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: Can we decouple app & gateway stakes? It'll be more "scalable" from a dec perspective. |
||
defaultStakeAmount = 1000000 | ||
) | ||
|
||
func (s *suite) TheActorTypeWithAccountIsStakedWithEnoughUpokt(accType, accName string) { | ||
// Get the staked amount for account, if it exists. | ||
// This is used to determine how much to stake for the actor and whether | ||
// it is already staked or not. | ||
stakedAmount, ok := s.getStakedAmount(accType, accName) | ||
if !ok { | ||
stakedAmount = defaultStakeAmount | ||
} | ||
|
||
// Fund the actor with enough upokt to stake for the service. | ||
s.TheUserSendsUpoktFromAccountToAccount(int64(stakedAmount+1), "pnf", accName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
s.waitForTxResultEvent( | ||
"transfer", | ||
"recipient", | ||
accNameToAddrMap[accName], | ||
) | ||
|
||
// Stake for the service with the | ||
s.TheUserStakesAWithUpoktForServiceFromTheAccount( | ||
accType, | ||
int64(stakedAmount+1), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
serviceId, | ||
accName, | ||
) | ||
s.TheUserShouldWaitForTheModuleMessageToBeSubmitted( | ||
accType, | ||
fmt.Sprintf("Stake%s", strings.Title(accType)), | ||
) | ||
} | ||
|
||
func (s *suite) TheUserDelegatesApplicationToGateway(appName, gatewayName string) { | ||
args := []string{ | ||
"tx", | ||
"application", | ||
"delegate-to-gateway", | ||
accNameToAddrMap[gatewayName], | ||
"--from", | ||
appName, | ||
keyRingFlag, | ||
chainIdFlag, | ||
"-y", | ||
} | ||
_, err := s.pocketd.RunCommandOnHost("", args...) | ||
require.NoError(s, err) | ||
|
||
s.TheUserShouldWaitForTheModuleMessageToBeSubmitted( | ||
"application", | ||
"DelegateToGateway", | ||
) | ||
} | ||
|
||
func (s *suite) TheApplicationDoesNotHaveAnyDelegation(appName string) { | ||
application := s.showApplication(appName) | ||
undelegationWaitGroup := sync.WaitGroup{} | ||
undelegationWaitGroup.Add(len(application.DelegateeGatewayAddresses)) | ||
|
||
// Concurrently undelegate the application from all gateways and wait for the | ||
// transactions to be committed. | ||
for _, gatewayAddress := range application.DelegateeGatewayAddresses { | ||
go func(gatewayAddress string) { | ||
s.TheUserUndelegatesApplicationFromGateway(appName, accAddrToNameMap[gatewayAddress]) | ||
s.TheUserWaitsUntilTheStartOfTheNextSession() | ||
undelegationWaitGroup.Done() | ||
}(gatewayAddress) | ||
} | ||
undelegationWaitGroup.Wait() | ||
} | ||
|
||
func (s *suite) ApplicationIsDelegatedToGateway(appName, gatewayName string) { | ||
application := s.showApplication(appName) | ||
require.Containsf(s, | ||
application.DelegateeGatewayAddresses, | ||
accNameToAddrMap[gatewayName], | ||
"app %q is not delegated to gateway %q", | ||
appName, gatewayName, | ||
) | ||
} | ||
|
||
func (s *suite) ApplicationIsNotDelegatedToGateway(appName, gatewayName string) { | ||
application := s.showApplication(appName) | ||
require.NotContainsf(s, | ||
application.DelegateeGatewayAddresses, | ||
accNameToAddrMap[gatewayName], | ||
"app %q is delegated to gateway %q", | ||
appName, gatewayName, | ||
) | ||
} | ||
|
||
func (s *suite) TheUserUndelegatesApplicationFromGatewayBeforeTheSessionEndBlock(appName, gatewayName string) { | ||
s.TheUserWaitsUntilTheStartOfTheNextSession() | ||
s.TheUserUndelegatesApplicationFromGateway(appName, gatewayName) | ||
} | ||
|
||
func (s *suite) TheUserUndelegatesApplicationFromGateway(appName, gatewayName string) { | ||
args := []string{ | ||
"tx", | ||
"application", | ||
"undelegate-from-gateway", | ||
accNameToAddrMap[gatewayName], | ||
"--from", | ||
appName, | ||
keyRingFlag, | ||
chainIdFlag, | ||
"-y", | ||
} | ||
_, err := s.pocketd.RunCommandOnHost("", args...) | ||
require.NoError(s, err) | ||
|
||
s.TheUserShouldWaitForTheModuleMessageToBeSubmitted( | ||
"application", | ||
"UndelegateFromGateway", | ||
) | ||
} | ||
|
||
func (s *suite) ApplicationHasGatewayAddressInTheArchivedDelegations(appName, gatewayName string) { | ||
application := s.showApplication(appName) | ||
require.Truef(s, | ||
slices.ContainsFunc(application.ArchivedDelegations, | ||
func(archivedDelegations types.ArchivedDelegations) bool { | ||
return slices.Contains(archivedDelegations.GatewayAddresses, accNameToAddrMap[gatewayName]) | ||
}), | ||
"app %q does not have gateway %q in its archived delegations", | ||
appName, gatewayName, | ||
) | ||
} | ||
|
||
func (s *suite) ApplicationDoesNotHaveGatewayAddressInTheArchivedDelegations(appName, gatewayName string) { | ||
application := s.showApplication(appName) | ||
require.Falsef(s, | ||
slices.ContainsFunc(application.ArchivedDelegations, | ||
func(archivedDelegations types.ArchivedDelegations) bool { | ||
return slices.Contains(archivedDelegations.GatewayAddresses, accNameToAddrMap[gatewayName]) | ||
}), | ||
"app %q has gateway %q in its archived delegations", | ||
appName, gatewayName, | ||
) | ||
} | ||
|
||
func (s *suite) ThePoktrollChainIsReachable() { | ||
ctx := context.Background() | ||
|
||
// Construct an events query client to listen for tx events from the supplier. | ||
deps := depinject.Supply(events.NewEventsQueryClient(testclient.CometLocalWebsocketURL)) | ||
txEventsReplayClient, err := events.NewEventsReplayClient( | ||
ctx, | ||
deps, | ||
"tm.event='Tx'", | ||
tx.UnmarshalTxResult, | ||
eventsReplayClientBufferSize, | ||
) | ||
require.NoError(s, err) | ||
s.scenarioState[txResultEventsReplayClientKey] = txEventsReplayClient | ||
|
||
// Construct an events query client to listen for claim settlement or expiration events on-chain. | ||
blockEventsReplayClient, err := events.NewEventsReplayClient( | ||
ctx, | ||
deps, | ||
newBlockEventSubscriptionQuery, | ||
block.UnmarshalNewBlockEvent, | ||
eventsReplayClientBufferSize, | ||
) | ||
require.NoError(s, err) | ||
s.scenarioState[newBlockEventReplayClientKey] = blockEventsReplayClient | ||
} | ||
|
||
func (s *suite) TheUserWaitsUntilTheStartOfTheNextSession() { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
blockReplayClient := s.scenarioState[newBlockEventReplayClientKey].(client.EventsReplayClient[*block.CometNewBlockEvent]) | ||
block := blockReplayClient.LastNEvents(ctx, 1)[0] | ||
nextSessionStartHeight := sessionkeeper.GetSessionEndBlockHeight(block.Height()) + 1 | ||
|
||
blockObs := blockReplayClient.EventsSequence(ctx).Subscribe(ctx) | ||
for newBlock := range blockObs.Ch() { | ||
if newBlock.Height() >= nextSessionStartHeight { | ||
break | ||
} | ||
} | ||
} | ||
|
||
func (s *suite) TheUserWaitsUntilArchivedDelegationsArePruned() { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
blockReplayClient := s.scenarioState[newBlockEventReplayClientKey].(client.EventsReplayClient[*block.CometNewBlockEvent]) | ||
block := blockReplayClient.LastNEvents(ctx, 1)[0] | ||
delegationPruningBlockHeight := block.Height() + appkeeper.ArchivedDelegationsRetentionBlocks | ||
|
||
blockObs := blockReplayClient.EventsSequence(ctx).Subscribe(ctx) | ||
for newBlock := range blockObs.Ch() { | ||
if newBlock.Height() >= delegationPruningBlockHeight { | ||
break | ||
} | ||
} | ||
} | ||
|
||
// showApplication issues a application show-application query with json output | ||
// parses its content and return the application. | ||
func (s *suite) showApplication(appName string) types.Application { | ||
args := []string{ | ||
"q", | ||
"application", | ||
"show-application", | ||
accNameToAddrMap[appName], | ||
chainIdFlag, | ||
"--output", | ||
"json", | ||
} | ||
res, err := s.pocketd.RunCommandOnHost("", args...) | ||
require.NoError(s, err) | ||
var queryGetApplicationResponse types.QueryGetApplicationResponse | ||
err = json.Unmarshal([]byte(res.Stdout), &queryGetApplicationResponse) | ||
require.NoError(s, err) | ||
|
||
return queryGetApplicationResponse.Application | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,7 +57,7 @@ const ( | |
) | ||
|
||
func (s *suite) TheUserShouldWaitForTheModuleMessageToBeSubmitted(module, message string) { | ||
s.waitForTxResultEvent(fmt.Sprintf("/poktroll.%s.Msg%s", module, message)) | ||
s.waitForTxResultEvent("message", "action", fmt.Sprintf("/poktroll.%s.Msg%s", module, message)) | ||
} | ||
|
||
func (s *suite) TheUserShouldWaitForTheModuleEventToBeBroadcast(module, message string) { | ||
|
@@ -200,7 +200,7 @@ func (s *suite) sendRelaysForSession( | |
} | ||
|
||
// waitForTxResultEvent waits for an event to be observed which has the given message action. | ||
func (s *suite) waitForTxResultEvent(targetAction string) { | ||
func (s *suite) waitForTxResultEvent(eventType, targetKey, targetValue string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
ctx, done := context.WithCancel(context.Background()) | ||
|
||
txResultEventsReplayClientState, ok := s.scenarioState[txResultEventsReplayClientKey] | ||
|
@@ -221,9 +221,12 @@ func (s *suite) waitForTxResultEvent(targetAction string) { | |
// Range over each event's attributes to find the "action" attribute | ||
// and compare its value to that of the action provided. | ||
for _, event := range txResult.Result.Events { | ||
if event.Type != eventType { | ||
continue | ||
} | ||
for _, attribute := range event.Attributes { | ||
if attribute.Key == "action" { | ||
if attribute.Value == targetAction { | ||
if attribute.Key == targetKey { | ||
if attribute.Value == targetValue { | ||
done() | ||
return | ||
} | ||
|
@@ -235,7 +238,7 @@ func (s *suite) waitForTxResultEvent(targetAction string) { | |
|
||
select { | ||
case <-time.After(eventTimeout): | ||
s.Fatalf("timed out waiting for message with action %q", targetAction) | ||
s.Fatalf("timed out waiting for message with %q %q", targetKey, targetValue) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add the type here as well |
||
case <-ctx.Done(): | ||
s.Log("Success; message detected before timeout.") | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL! Going to start using this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended up going through [1] to learn more but not going to change anything now.
[1] https://cucumber.io/docs/gherkin/reference/#rule