Skip to content

Commit

Permalink
Allow an AppMsgStake tx to change the address of a staked app (#1585)
Browse files Browse the repository at this point in the history
The patch implements [PIP-35](https://forum.pokt.network/t/pip-35-introduce-a-secure-way-to-transfer-a-staked-app-to-a-new-account/4806), allowing the existing AppMsgStake transaction to change the address of a staked app. This enables us to *transfer* the existing app slot from one
to a new account without unstaking. To make this operation easier, this patch also introduces a new command `app transfer`.

## Description

<!-- reviewpad:summarize:start -->
### Summary generated by Reviewpad on 15 Dec 23 11:53 UTC
This pull request includes changes in multiple files. Here is a summary
of the changes:

1. The file `appStateChanges_test.go` has changes related to application
state changes and transfers. The changes include the addition of new
import statements, test functions, and helper functions.

2. The file `types.go` has changes related to the `MsgStake` type. The
changes involve adding a new method `IsValidTransfer` to check for a
special case in the `MsgStake` type where ownership transfer is being
done.

3. The file `codec.go` has changes related to the support of an "App
Transfer" feature. The changes include adding a constant and a new
function for checking if a certain upgrade has occurred.

4. The file `expectedKeepers.go` has changes related to the addition of
a new interface `AppKeeper`.

5. The file `keeper.go` has changes that involve adding a new field to
the `Keeper` struct.

6. The file `baseapp.go` has changes that include formatting changes in
the package comment and modifications in the `DeliverTx` function.

7. The file `app.go` has changes related to the assignment of a field in
the `app.accountKeeper` object.

8. The file `auth.go` has changes that include adding comments and
conditions for non-custodial and output address editor upgrades.

9. The file `common_test.go` has changes that involve importing
packages, renaming a package, and adding/modifying functions.

10. The file `appStateChanges.go` has changes related to the validation
and transfer functionality of applications.

11. The file `txUtil.go` has changes that add a new function for
transferring an application.

12. The file `app/cmd/cli/app.go` has changes related to the addition of
a new command for transferring the ownership of a staked app.

13. The file `keeper.go` has changes that add a new method for checking
if a message is for transferring ownership.

14. The file `handler.go` has changes that include import statements,
function parameter modifications, and logic for transferring application
ownership.

These are the summaries of the changes in each file. Let me know if you
have any specific questions or need further information regarding these
changes.
<!-- reviewpad:summarize:end -->
  • Loading branch information
msmania authored Dec 20, 2023
1 parent 02a4e3a commit 762dd5a
Show file tree
Hide file tree
Showing 14 changed files with 479 additions and 52 deletions.
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func NewPocketCoreApp(genState GenesisState, keybase keys.Keybase, tmClient clie
app.nodesKeeper.PocketKeeper = app.pocketKeeper
app.appsKeeper.PocketKeeper = app.pocketKeeper
app.accountKeeper.POSKeeper = app.nodesKeeper
app.accountKeeper.AppKeeper = app.appsKeeper
// setup module manager
app.mm = module.NewManager(
auth.NewAppModule(app.accountKeeper),
Expand Down
60 changes: 60 additions & 0 deletions app/cmd/cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func init() {
rootCmd.AddCommand(appCmd)
appCmd.AddCommand(appStakeCmd)
appCmd.AddCommand(appUnstakeCmd)
appCmd.AddCommand(appTransferCmd)
appCmd.AddCommand(createAATCmd)
}

Expand Down Expand Up @@ -116,6 +117,65 @@ Prompts the user for the <fromAddr> account passphrase.`,
},
}

var appTransferCmd = &cobra.Command{
Use: "transfer <fromAddr> <newAppPubKey> <networkID> <fee> [memo]",
Short: "Transfer the ownership of a staked app from one to another",
Long: `Submits a transaction to transfer the ownership of a staked app from
<fromAddr> to a new account specified as <newAppPubKey> without unstaking
any app. In other words, this edits the address of a staked app. To run this
command, you must have the private key of the current staked app <fromAddr>
`,
Args: cobra.MinimumNArgs(4),
Run: func(cmd *cobra.Command, args []string) {
app.InitConfig(datadir, tmNode, persistentPeers, seeds, remoteCLIURL)

currentAppAddr := args[0]
newAppPubKey := args[1]
networkId := args[2]
feeStr := args[3]
memo := ""
if len(args) >= 5 {
memo = args[4]
}

fee, err := strconv.ParseInt(feeStr, 10, 64)
if err != nil {
fmt.Println("Invalid fee:", err)
return
}

fmt.Printf("Enter passphrase to unlock %s: ", currentAppAddr)
passphrase := app.Credentials(pwd)

rawTx, err := TransferApp(
currentAppAddr,
newAppPubKey,
passphrase,
networkId,
fee,
memo,
)
if err != nil {
fmt.Println("Failed to build a transaction:", err)
return
}

rawTxBytes, err := json.Marshal(rawTx)
if err != nil {
fmt.Println(err)
return
}

resp, err := QueryRPC(SendRawTxPath, rawTxBytes)
if err != nil {
fmt.Println("Failed to submit a transaction:", err)
return
}

fmt.Println(resp)
},
}

var createAATCmd = &cobra.Command{
Use: "create-aat <appAddr> <clientPubKey>",
Short: "Creates an application authentication token",
Expand Down
46 changes: 46 additions & 0 deletions app/cmd/cli/txUtil.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,52 @@ func UnstakeApp(fromAddr, passphrase, chainID string, fees int64, legacyCodec bo
}, nil
}

func TransferApp(
currentAppAddrStr, newAppPubKeyStr, passphrase, networkId string,
fee int64,
memo string,
) (*rpc.SendRawTxParams, error) {
currentAppAddr, err := sdk.AddressFromHex(currentAppAddrStr)
if err != nil {
return nil, err
}

newAppPubKey, err := crypto.NewPublicKey(newAppPubKeyStr)
if err != nil {
return nil, err
}

keybase, err := app.GetKeybase()
if err != nil {
return nil, err
}

msg := appsType.MsgStake{PubKey: newAppPubKey}
if err = msg.ValidateBasic(); err != nil {
return nil, err
}

txBz, err := newTxBz(
app.Codec(),
&msg,
currentAppAddr,
networkId,
keybase,
passphrase,
fee,
memo,
false,
)
if err != nil {
return nil, err
}

return &rpc.SendRawTxParams{
Addr: currentAppAddrStr,
RawHexBytes: hex.EncodeToString(txBz),
}, nil
}

func DAOTx(fromAddr, toAddr, passphrase string, amount sdk.BigInt, action, chainID string, fees int64, legacyCodec bool) (*rpc.SendRawTxParams, error) {
fa, err := sdk.AddressFromHex(fromAddr)
if err != nil {
Expand Down
18 changes: 8 additions & 10 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
///*
//Package baseapp contains data structures that provide basic data storage
//functionality and act as a bridge between the ABCI interface and the SDK
//abstractions.
// /*
// Package baseapp contains data structures that provide basic data storage
// functionality and act as a bridge between the ABCI interface and the SDK
// abstractions.
//
//BaseApp has no state except the CommitMultiStore you provide upon init.
//*/
// BaseApp has no state except the CommitMultiStore you provide upon init.
// */
package baseapp

import (
"encoding/hex"
"fmt"
"github.com/pokt-network/pocket-core/codec/types"
"github.com/pokt-network/pocket-core/crypto"
types2 "github.com/pokt-network/pocket-core/x/apps/types"
"github.com/pokt-network/pocket-core/x/auth"
"github.com/tendermint/tendermint/evidence"
"github.com/tendermint/tendermint/node"
Expand Down Expand Up @@ -810,9 +809,8 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliv
msg := tx.GetMsg()
messageType = msg.Type()
recipient = msg.GetRecipient()
if signerPK == nil || messageType == types2.MsgAppStakeName {
signers := msg.GetSigners()
if len(signers) >= 1 {
if signerPK == nil {
if signers := msg.GetSigners(); len(signers) >= 1 {
signer = signers[0]
}
} else {
Expand Down
7 changes: 7 additions & 0 deletions codec/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const (
OutputAddressEditKey = "OEDIT"
ClearUnjailedValSessionKey = "CRVAL"
PerChainRTTM = "PerChainRTTM"
AppTransferKey = "AppTransfer"
)

func GetCodecUpgradeHeight() int64 {
Expand Down Expand Up @@ -287,6 +288,12 @@ func (cdc *Codec) IsAfterPerChainRTTMUpgrade(height int64) bool {
TestMode <= -3
}

func (cdc *Codec) IsAfterAppTransferUpgrade(height int64) bool {
return (UpgradeFeatureMap[AppTransferKey] != 0 &&
height >= UpgradeFeatureMap[AppTransferKey]) ||
TestMode <= -3
}

// IsOnNonCustodialUpgrade Note: includes the actual upgrade height
func (cdc *Codec) IsOnNonCustodialUpgrade(height int64) bool {
return (UpgradeFeatureMap[NonCustodialUpdateKey] != 0 && height == UpgradeFeatureMap[NonCustodialUpdateKey]) || TestMode <= -3
Expand Down
49 changes: 33 additions & 16 deletions x/apps/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ package pos

import (
"fmt"
"reflect"

"github.com/pokt-network/pocket-core/crypto"
sdk "github.com/pokt-network/pocket-core/types"
"github.com/pokt-network/pocket-core/x/apps/keeper"
"github.com/pokt-network/pocket-core/x/apps/types"
"reflect"
)

func NewHandler(k keeper.Keeper) sdk.Handler {
return func(ctx sdk.Ctx, msg sdk.Msg, _ crypto.PublicKey) sdk.Result {
return func(ctx sdk.Ctx, msg sdk.Msg, signer crypto.PublicKey) sdk.Result {
ctx = ctx.WithEventManager(sdk.NewEventManager())
// convert to value for switch consistency
if reflect.ValueOf(msg).Kind() == reflect.Ptr {
msg = reflect.Indirect(reflect.ValueOf(msg)).Interface().(sdk.Msg)
}
switch msg := msg.(type) {
case types.MsgStake:
return handleStake(ctx, msg, k)
return handleStake(ctx, msg, signer, k)
case types.MsgBeginUnstake:
return handleMsgBeginUnstake(ctx, msg, k)
case types.MsgUnjail:
Expand All @@ -30,25 +31,41 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
}
}

func handleStake(ctx sdk.Ctx, msg types.MsgStake, k keeper.Keeper) sdk.Result {
func handleStake(
ctx sdk.Ctx,
msg types.MsgStake,
signer crypto.PublicKey,
k keeper.Keeper,
) sdk.Result {
pk := msg.PubKey
addr := pk.Address()
ctx.Logger().Info("Begin Staking App Message received from " + sdk.Address(pk.Address()).String())
// create application object using the message fields
application := types.NewApplication(sdk.Address(addr), pk, msg.Chains, sdk.ZeroInt())
ctx.Logger().Info("Validate App Can Stake " + sdk.Address(addr).String())
// check if they can stake
if err := k.ValidateApplicationStaking(ctx, application, msg.Value); err != nil {
ctx.Logger().Error(fmt.Sprintf("Validate App Can Stake Error, at height: %d with address: %s", ctx.BlockHeight(), sdk.Address(addr).String()))
return err.Result()
}
ctx.Logger().Info("Change App state to Staked " + sdk.Address(addr).String())
// change the application state to staked
err := k.StakeApplication(ctx, application, msg.Value)
if err != nil {
return err.Result()
// check if the msg is to transfer an application first
if curApp, err := k.ValidateApplicationTransfer(ctx, signer, msg); err == nil {
ctx.Logger().Info(
"Transferring application",
"from", curApp.Address.String(),
"to", msg.PubKey.Address().String(),
)
k.TransferApplication(ctx, curApp, msg.PubKey)
} else {
// otherwise check if the message is to stake an application
if err := k.ValidateApplicationStaking(ctx, application, msg.Value); err != nil {
ctx.Logger().Error(fmt.Sprintf("Validate App Can Stake Error, at height: %d with address: %s", ctx.BlockHeight(), sdk.Address(addr).String()))
return err.Result()
}

ctx.Logger().Info("Change App state to Staked " + sdk.Address(addr).String())
// change the application state to staked
if err := k.StakeApplication(ctx, application, msg.Value); err != nil {
return err.Result()
}
}
// create the event
signerAddrStr := sdk.Address(signer.Address()).String()
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeCreateApplication,
Expand All @@ -57,13 +74,13 @@ func handleStake(ctx sdk.Ctx, msg types.MsgStake, k keeper.Keeper) sdk.Result {
sdk.NewEvent(
types.EventTypeStake,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sdk.Address(addr).String()),
sdk.NewAttribute(sdk.AttributeKeySender, signerAddrStr),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Value.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sdk.Address(addr).String()),
sdk.NewAttribute(sdk.AttributeKeySender, signerAddrStr),
),
})
return sdk.Result{Events: ctx.EventManager().Events()}
Expand Down
Loading

0 comments on commit 762dd5a

Please sign in to comment.