Skip to content
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

[Application] feat: Add past delegation proofs #518

Merged
merged 17 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,9 @@ localnet_regenesis: check_yq acc_initialize_pubkeys_warn_message ## Regenerate t
cp -r ${HOME}/.poktroll/keyring-test $(POKTROLLD_HOME) ;\
cp -r ${HOME}/.poktroll/config $(POKTROLLD_HOME)/ ;\

.PHONY: send_relay_self_signing_app
send_relay_self_signing_app: # Send a relay through the AppGateServer as an application that's self-signing
## TODO_TECHDEBT: Rename self_signing parameter to `sovereign` in code, configs and documentation
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
.PHONY: send_relay_sovereign_app
send_relay_sovereign_app: # Send a relay through the AppGateServer as a sovereign application
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:42069/anvil
Expand Down
27 changes: 13 additions & 14 deletions pkg/crypto/rings/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ func (rc *ringClient) VerifyRelayRequestSignature(
return nil
}

// getRingPubKeysForAddress returns the public keys corresponding to a ring
// It is a slice consisting of the application's public key and the public keys
// of the gateways an application delegated the ability to sign relay requests
// on its behalf at the given block height.
// getRingPubKeysForAddress returns the public keys corresponding to a ring.
// It returns a slice consisting of the application's public key and the public
// keys of the gateways to which the application delegated the authority to sign
// relay requests on its behalf at the given block height.
func (rc *ringClient) getRingPubKeysForAddress(
ctx context.Context,
appAddress string,
Expand All @@ -177,8 +177,6 @@ func (rc *ringClient) getRingPubKeysForAddress(
ringAddresses := make([]string, 0)
ringAddresses = append(ringAddresses, appAddress) // app address is index 0

// If there is no delegateeGatewayAddresses, add app address a second time to
// make the ring size of minimum 2.
// TODO_IMPROVE: The appAddress is added twice because a ring signature
// requires AT LEAST two pubKeys. If the Application has not delegated
// to any gateways, the app's own address needs to be used twice to
Expand Down Expand Up @@ -220,9 +218,9 @@ func (rc *ringClient) addressesToPubKeys(
}

// GetRingAddressesAtBlock returns the active gateway addresses that need to be
// used to construct the ring to validate the app should pay for. It takes into
// account both active delegations and pending undelegations that should still
// be part of the ring.
// used to construct the ring in order to validate that the given app should pay for.
// It takes into account both active delegations and pending undelegations that
// should still be part of the ring at the given block height.
// The ring addresses slice is reconstructed by adding back the past delegated
// gateways that have been undelegated after the target session end height.
func GetRingAddressesAtBlock(app *apptypes.Application, blockHeight int64) []string {
Expand All @@ -233,14 +231,15 @@ func GetRingAddressesAtBlock(app *apptypes.Application, blockHeight int64) []str

// Use a map to keep track of the gateways addresses that have been added to
// the active delegations slice to avoid duplicates.
addedDelegations := make(map[string]bool)
addedDelegations := make(map[string]struct{})

// Iterate over the pending undelegations recorded at their respective block
// height and check whether to add them back as active delegations.
for pendingUndelegationHeight, undelegatedGateways := range app.PendingUndelegations {
// If the pending undelegation happened BEFORE the target session end height,
// skip it, as it became effective before the target session end height.
if targetSessionEndHeight > pendingUndelegationHeight {
// If the pending undelegation happened BEFORE the target session end height, skip it.
// The gateway is pending undelegation and simply has not been pruned yet.
// It will be pruned in the near future.
if pendingUndelegationHeight < targetSessionEndHeight {
continue
}
// Add back any gateway address that was undelegated after the target session
Expand All @@ -252,7 +251,7 @@ func GetRingAddressesAtBlock(app *apptypes.Application, blockHeight int64) []str

activeDelegationsAtHeight = append(activeDelegationsAtHeight, gatewayAddress)
// Mark the gateway address as added to avoid duplicates.
addedDelegations[gatewayAddress] = true
addedDelegations[gatewayAddress] = struct{}{}
}

}
Expand Down
3 changes: 2 additions & 1 deletion proto/poktroll/application/application.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ message Application {
repeated poktroll.shared.ApplicationServiceConfig service_configs = 3; // The list of services this appliccation is configured to request service for
repeated string delegatee_gateway_addresses = 4 [(cosmos_proto.scalar) = "cosmos.AddressString", (gogoproto.nullable) = false]; // The Bech32 encoded addresses for all delegatee Gateways, in a non-nullable slice
Olshansk marked this conversation as resolved.
Show resolved Hide resolved
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
// A map from sessionEndHeights to a list of Gateways.
// The key is height is the last block of the sessions during which the undelegation was triggered.
// The key is the height of the last block of the session during which the
// respective undelegation was committed.
// The value is a list of gateways being undelegated from.
// TODO_DOCUMENT(@red-0ne): Need to document the flow from this comment
// so its clear to everyone why this is necessary; https://github.com/pokt-network/poktroll/issues/476#issuecomment-2052639906.
Expand Down
2 changes: 1 addition & 1 deletion x/application/keeper/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func createNApplications(keeper keeper.Keeper, ctx context.Context, n int) []typ
apps := make([]types.Application, n)
for i := range apps {
apps[i].Address = strconv.Itoa(i)
// Setting pending undelegations since nullify. Fill does not seem to do it.
// Setting pending undelegations since nullify.Fill() does not seem to do it.
apps[i].PendingUndelegations = make(map[uint64]types.UndelegatingGatewayList)

keeper.SetApplication(ctx, apps[i])
Expand Down
15 changes: 4 additions & 11 deletions x/application/keeper/msg_server_undelegate_from_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,7 @@ func (k Keeper) recordPendingUndelegation(
currentBlockHeight int64,
) {
sessionEndHeight := uint64(sessionkeeper.GetSessionEndBlockHeight(currentBlockHeight))

// Create the session pending undelegations list entry for the given session
// end height if it doesn't already exist.
undelegatingGatewayListAtBlock, ok := app.PendingUndelegations[sessionEndHeight]
if !ok {
undelegatingGatewayListAtBlock = types.UndelegatingGatewayList{
GatewayAddresses: []string{},
}
}
undelegatingGatewayListAtBlock := app.PendingUndelegations[sessionEndHeight]

// Add the gateway address to the undelegated gateways list if it's not already there.
if !slices.Contains(undelegatingGatewayListAtBlock.GatewayAddresses, gatewayAddress) {
Expand All @@ -99,8 +91,9 @@ func (k Keeper) recordPendingUndelegation(
)
app.PendingUndelegations[sessionEndHeight] = undelegatingGatewayListAtBlock
} else {
k.logger.Warn(fmt.Sprintf(
"Application undelegating (again) from gateway it's already undelegating from with address [%s]",
k.logger.Info(fmt.Sprintf(
"Application with address [%s] undelegating (again) from a gateway it's already undelegating from with address [%s]",
app.Address,
gatewayAddress,
))
}
Expand Down
75 changes: 46 additions & 29 deletions x/application/keeper/msg_server_undelegate_from_gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,29 @@ func TestMsgServer_UndelegateFromGateway_SuccessfullyUndelegateFromUnstakedGatew
require.Equal(t, 0, len(foundApp.DelegateeGatewayAddresses))
}

// Test an undelegation at different stages of the undelegation lifecycle:
//
// - Create an application, stake it, delegate then undelegate it from a gateway.
//
// - Increment the block height without moving to the next session and check that
// the undelegated gateway is still part of the application's delegate gateways.
//
// - Increment the block height to the next session and check that the undelegated
// gateway is no longer part of the application's delegate gateways.
//
// - Increment the block height past the tested session's grace period and check:
//
// - The undelegated gateway is still not part of the application's delegate gateways
//
// - If queried for a past block height, corresponding to the session at which the
// undelegation occurred, the reconstructed delegate gateway list does include
// the undelegated gateway.
func TestMsgServer_UndelegateFromGateway_DelegationIsActiveUntilNextSession(t *testing.T) {
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
k, ctx := keepertest.ApplicationKeeper(t)
srv := keeper.NewMsgServerImpl(k)

undelegationHeight := int64(1)
sdkCtx, app, delegatedToAddr, pendingUndelegateToAddr :=
sdkCtx, app, delegateAddr, pendingUndelegateFromAddr :=
createAppStakeDelegateAndUndelegate(ctx, t, srv, k, undelegationHeight)

// Increment the block height without moving to the next session, then run the
Expand All @@ -295,22 +312,22 @@ func TestMsgServer_UndelegateFromGateway_DelegationIsActiveUntilNextSession(t *t
require.NotNil(t, app)

// Verify that the gateway was removed from the application's delegatee gateway addresses.
require.NotContains(t, app.DelegateeGatewayAddresses, pendingUndelegateToAddr)
require.NotContains(t, app.DelegateeGatewayAddresses, pendingUndelegateFromAddr)

// Verify that the gateway is added to the pending undelegation list with the
// right sessionEndHeight as the map key.
sessionEndHeight := uint64(sessionkeeper.GetSessionEndBlockHeight(undelegationHeight))
require.Contains(t,
app.PendingUndelegations[sessionEndHeight].GatewayAddresses,
pendingUndelegateToAddr,
pendingUndelegateFromAddr,
)

// Verify that the other gateways are still delegated to the application.
require.Contains(t, app.DelegateeGatewayAddresses, delegatedToAddr)
// Verify that the application is still delegating to other gateways.
require.Contains(t, app.DelegateeGatewayAddresses, delegateAddr)

// Verify that the reconstructed delegatee gateway list include the undelegated gateway.
// Verify that the reconstructed delegatee gateway list includes the undelegated gateway.
gatewayAddresses := rings.GetRingAddressesAtBlock(&app, sdkCtx.BlockHeight())
require.Contains(t, gatewayAddresses, pendingUndelegateToAddr)
require.Contains(t, gatewayAddresses, pendingUndelegateFromAddr)

// Increment the block height to the next session and run the pruning
// undelegations logic again.
Expand All @@ -326,7 +343,7 @@ func TestMsgServer_UndelegateFromGateway_DelegationIsActiveUntilNextSession(t *t
// Verify that when queried for the next session the reconstructed delegatee
// gateway list does not include the undelegated gateway.
nextSessionGatewayAddresses := rings.GetRingAddressesAtBlock(&app, nextSessionStartHeight)
require.NotContains(t, nextSessionGatewayAddresses, pendingUndelegateToAddr)
require.NotContains(t, nextSessionGatewayAddresses, pendingUndelegateFromAddr)

// Increment the block height past the tested session's grace period and run
// the pruning undelegations logic again.
Expand All @@ -338,21 +355,21 @@ func TestMsgServer_UndelegateFromGateway_DelegationIsActiveUntilNextSession(t *t
// Verify that when queried for a block height past the tested session's grace period,
// the reconstructed delegatee gateway list does not include the undelegated gateway.
pastGracePeriodGatewayAddresses := rings.GetRingAddressesAtBlock(&app, afterSessionGracePeriodHeight)
require.NotContains(t, pastGracePeriodGatewayAddresses, pendingUndelegateToAddr)
require.NotContains(t, pastGracePeriodGatewayAddresses, pendingUndelegateFromAddr)

// Verify that when queried for a block height corresponding to the past session
// that has its grace period elapsed, the reconstructed delegatee gateway list
// does include the undelegated gateway.
pastGracePeriodGatewayAddresses = rings.GetRingAddressesAtBlock(&app, int64(sessionEndHeight))
require.Contains(t, pastGracePeriodGatewayAddresses, pendingUndelegateToAddr)
// Ensure that when queried for the block height corresponding to the session
// at which the undelegation occurred, the reconstructed delegatee gateway list
// includes the undelegated gateway.
gatewayAddressesBeforeUndelegation := rings.GetRingAddressesAtBlock(&app, int64(sessionEndHeight))
require.Contains(t, gatewayAddressesBeforeUndelegation, pendingUndelegateFromAddr)
}

func TestMsgServer_UndelegateFromGateway_DelegationIsPrunedAfterRetentionPeriod(t *testing.T) {
k, ctx := keepertest.ApplicationKeeper(t)
srv := keeper.NewMsgServerImpl(k)

undelegationHeight := int64(1)
sdkCtx, app, delegatedToAddr, pendingUndelegateToAddr :=
sdkCtx, app, delegateAddr, pendingUndelegateFromAddr :=
createAppStakeDelegateAndUndelegate(ctx, t, srv, k, undelegationHeight)

// Increment the block height past the undelegation retention period then run
Expand All @@ -374,8 +391,8 @@ func TestMsgServer_UndelegateFromGateway_DelegationIsPrunedAfterRetentionPeriod(
// Verify that the reconstructed delegatee gateway list can no longer include
// the undelegated gateway since it has been pruned.
gatewayAddressesAfterPruning := rings.GetRingAddressesAtBlock(&app, undelegationHeight)
require.NotContains(t, gatewayAddressesAfterPruning, pendingUndelegateToAddr)
require.Contains(t, gatewayAddressesAfterPruning, delegatedToAddr)
require.NotContains(t, gatewayAddressesAfterPruning, pendingUndelegateFromAddr)
require.Contains(t, gatewayAddressesAfterPruning, delegateAddr)
}

func TestMsgServer_UndelegateFromGateway_RedelegationAfterUndelegationAtTheSameSessionNumber(t *testing.T) {
Expand Down Expand Up @@ -413,7 +430,7 @@ func TestMsgServer_UndelegateFromGateway_RedelegationAfterUndelegationAtTheSameS
gatewayAddrToRedelegate,
)

// Verify that the reconstructed delegatee gateway list include the redelegated gateway.
// Verify that the reconstructed delegatee gateway list includes the redelegated gateway.
gatewayAddresses := rings.GetRingAddressesAtBlock(&app, sdkCtx.BlockHeight())
require.Contains(t, gatewayAddresses, gatewayAddrToRedelegate)

Expand All @@ -435,7 +452,7 @@ func TestMsgServer_UndelegateFromGateway_RedelegationAfterUndelegationAtTheSameS
// sessionEndHeight key.
require.Empty(t, app.PendingUndelegations[sessionEndHeight])

// Verify that the reconstructed delegatee gateway list includes the reundelegated gateway
// Verify that the reconstructed delegatee gateway list includes the redelegated gateway
gatewayAddressesAfterPruning := rings.GetRingAddressesAtBlock(&app, sdkCtx.BlockHeight())
require.Contains(t, gatewayAddressesAfterPruning, gatewayAddrToRedelegate)
}
Olshansk marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -455,8 +472,8 @@ func createAppStakeDelegateAndUndelegate(
) (
sdkCtx sdk.Context,
app types.Application,
delegatedToAddr,
pendingUndelegateToAddr string,
delegateAddr,
pendingUndelegateFromAddr string,
) {
// Generate an application address and stake the application.
appAddr := sample.AccAddress()
Expand All @@ -474,22 +491,22 @@ func createAppStakeDelegateAndUndelegate(

// Generate gateway addresses, mock the gateways being staked then delegate the
// application to the gateways.
delegatedToAddr = sample.AccAddress()
keepertest.AddGatewayToStakedGatewayMap(t, delegatedToAddr)
delegateAddr = sample.AccAddress()
keepertest.AddGatewayToStakedGatewayMap(t, delegateAddr)

delegateMsg := &types.MsgDelegateToGateway{
AppAddress: appAddr,
GatewayAddress: delegatedToAddr,
GatewayAddress: delegateAddr,
}
_, err = srv.DelegateToGateway(ctx, delegateMsg)
require.NoError(t, err)

pendingUndelegateToAddr = sample.AccAddress()
keepertest.AddGatewayToStakedGatewayMap(t, pendingUndelegateToAddr)
pendingUndelegateFromAddr = sample.AccAddress()
keepertest.AddGatewayToStakedGatewayMap(t, pendingUndelegateFromAddr)

delegateMsg = &types.MsgDelegateToGateway{
AppAddress: appAddr,
GatewayAddress: pendingUndelegateToAddr,
GatewayAddress: pendingUndelegateFromAddr,
}
_, err = srv.DelegateToGateway(ctx, delegateMsg)
require.NoError(t, err)
Expand All @@ -500,7 +517,7 @@ func createAppStakeDelegateAndUndelegate(
// Undelegate from the first gateway.
undelegateMsg := &types.MsgUndelegateFromGateway{
AppAddress: appAddr,
GatewayAddress: pendingUndelegateToAddr,
GatewayAddress: pendingUndelegateFromAddr,
}
_, err = srv.UndelegateFromGateway(sdkCtx, undelegateMsg)
require.NoError(t, err)
Expand All @@ -511,7 +528,7 @@ func createAppStakeDelegateAndUndelegate(
require.True(t, isAppFound)
require.NotNil(t, foundApp)

return sdkCtx, foundApp, delegatedToAddr, pendingUndelegateToAddr
return sdkCtx, foundApp, delegateAddr, pendingUndelegateFromAddr
}

// getUndelegationPruningBlockHeight returns the block height at which undelegations
Expand Down
8 changes: 4 additions & 4 deletions x/application/keeper/prune_undelegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ func (k Keeper) EndBlockerPruneAppToGatewayPendingUndelegation(ctx sdk.Context)
if currentHeight <= numBlocksUndelegationRetention {
return nil
}
pruningBlockHeight := uint64(currentHeight - numBlocksUndelegationRetention)
earliestUnprunedUndelegationHeight := uint64(currentHeight - numBlocksUndelegationRetention)

// Iterate over all applications and prune undelegations that are older than
// the retention period.
for _, application := range k.GetAllApplications(ctx) {
for undelegationSessionEndHeight := range application.PendingUndelegations {
if undelegationSessionEndHeight < pruningBlockHeight {
if undelegationSessionEndHeight < earliestUnprunedUndelegationHeight {
// prune undelegations
delete(application.PendingUndelegations, undelegationSessionEndHeight)
}
Expand All @@ -42,8 +42,8 @@ func (k Keeper) EndBlockerPruneAppToGatewayPendingUndelegation(ctx sdk.Context)
return nil
}

// GetNumBlocksUndelegationRetention returns the number of blocks undelegations
// should be kept before being pruned.
// GetNumBlocksUndelegationRetention returns the number of blocks for which
// undelegations should be kept before being pruned.
func GetNumBlocksUndelegationRetention() int64 {
return sessionkeeper.GetSessionGracePeriodBlockCount() +
(sessionkeeper.NumBlocksPerSession * NumSessionsAppToGatewayUndelegationRetention)
Expand Down
Loading