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

feat: add oracle upgrade endblocker #578

Merged
merged 18 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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
3 changes: 3 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/cosmos/cosmos-sdk/x/authz"
"github.com/medibloc/panacea-core/v2/x/oracle"
oracleclient "github.com/medibloc/panacea-core/v2/x/oracle/client"
oraclekeeper "github.com/medibloc/panacea-core/v2/x/oracle/keeper"
oracletypes "github.com/medibloc/panacea-core/v2/x/oracle/types"

Expand Down Expand Up @@ -162,6 +163,7 @@ func getGovProposalHandlers() []govclient.ProposalHandler {
upgradeclient.ProposalHandler,
upgradeclient.CancelProposalHandler,
// this line is used by starport scaffolding # stargate/app/govProposalHandler
oracleclient.ProposalHandler,
)
govProposalHandlers = append(govProposalHandlers, wasmclient.ProposalHandlers...)

Expand Down Expand Up @@ -440,6 +442,7 @@ func New(
keys[oracletypes.MemStoreKey],
app.GetSubspace(oracletypes.ModuleName),
)
govRouter.AddRoute(oracletypes.RouterKey, oracle.NewOracleProposalHandler(app.oracleKeeper))

app.datadealKeeper = *datadealkeeper.NewKeeper(
appCodec,
Expand Down
9 changes: 9 additions & 0 deletions proto/panacea/oracle/v2/oracle.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ message OracleRegistration {
string oracle_commission_max_change_rate = 10 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
bytes encrypted_oracle_priv_key = 11;
}

// OracleUpgradeInfo defines the info of oracle upgrade, which includes the target height of upgrade and unique ID of the new version of oracle
message OracleUpgradeInfo {
option (gogoproto.equal) = true;

string unique_id = 1;

int64 height = 2;
}
25 changes: 25 additions & 0 deletions proto/panacea/oracle/v2/proposal.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto3";
package panacea.oracle.v2;

option go_package = "github.com/medibloc/panacea-core/x/oracle/types";
option (gogoproto.goproto_getters_all) = false;

import "gogoproto/gogo.proto";

// Plan defines upgrade plan information.
message Plan {
option (gogoproto.equal) = true;

string unique_id = 1;

int64 height = 2;
}

// OracleUpgradeProposal defines the information required for a proposal.
message OracleUpgradeProposal {
option (gogoproto.equal) = true;

string title = 1;
string description = 2;
Plan plan = 3 [(gogoproto.nullable) = false];
}
13 changes: 13 additions & 0 deletions proto/panacea/oracle/v2/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ service Query {
option (google.api.http).get = "/panacea/oracle/v2/oracle-registrations/{unique_id}/{oracle_address}";
}

// OracleUpgradeInfo returns OracleUpgradeInfo of oracle module.
rpc OracleUpgradeInfo(QueryOracleUpgradeInfoRequest) returns (QueryOracleUpgradeInfoResponse) {
option (google.api.http).get = "/panacea/oracle/v2alpha2/oracle-upgrade-info";
}

// Params returns params of oracle module.
rpc Params(QueryOracleParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/panacea/oracle/v2/params";
Expand Down Expand Up @@ -82,6 +87,14 @@ message QueryOracleRegistrationResponse {
OracleRegistration oracle_registration = 1;
}

// QueryOracleUpgradeInfoRequest is the request type for the Query/OracleUpgradeInfo RPC method.
message QueryOracleUpgradeInfoRequest {}

// QueryOracleUpgradeInfoRequest is the response type for the Query/OracleUpgradeInfo RPC method.
message QueryOracleUpgradeInfoResponse {
OracleUpgradeInfo oracle_upgrade_info = 1;
}

// QueryOracleParamsRequest is the request type for the Query/OracleParams RPC method.
message QueryOracleParamsRequest {}

Expand Down
31 changes: 31 additions & 0 deletions x/oracle/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package oracle

import (
"errors"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/medibloc/panacea-core/v2/x/oracle/keeper"
"github.com/medibloc/panacea-core/v2/x/oracle/types"
)

func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) {
handlerOracleUpgrade(ctx, keeper)
}

func handlerOracleUpgrade(ctx sdk.Context, keeper keeper.Keeper) {
upgradeInfo, err := keeper.GetOracleUpgradeInfo(ctx)
if err != nil {
if errors.Is(err, types.ErrOracleUpgradeInfoNotFound) {
return
} else {
panic(err)
}
}

if upgradeInfo.ShouldExecute(ctx) {
if err := keeper.ApplyUpgrade(ctx, upgradeInfo); err != nil {
panic(err)
}
keeper.RemoveOracleUpgradeInfo(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about not deleting the upgrade info?
When a new upgrade proposal passes, it will be overwritten anyway.

The reason I'm talking about this is because of the following case:

If the oracles didn't upgrade until the target height, they had to re-request register-oracle transaction again to be shared the oracle private key in the new binary(upgrade version).
However, at this time, the oracles will be able to enter the commission rate, which overwrites the existing oracle commission rate.
In other words, this case should also be done through upgrade-oracle, and to do so, it seems that upgrade info should be left. upgrade info will mean upcoming or recent upgrade information.

--
FYI) Plus, in register-oracle, the check that if the oracle has already been set or not should be added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea.
Then, how about OracleKeyMigration rather than a name OracleUpgrade?
OracleKeyMigration works only when it has the same value as uniqueID of OracleUpgradeInfo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, please forget the name change. I'll think about it more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I understand the problem.
If the oracles didn't upgrade until the target height, oracle operator have to use upgrade-oracle because the oracle is already in Oracle store.
For now, I think @H4NLee 's suggestion is the best way.

Copy link
Contributor

@gyuguen gyuguen Jan 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you reflect this flow, please leave a comment :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed it in 8e1a3d8.

}
}
83 changes: 83 additions & 0 deletions x/oracle/abci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package oracle_test

import (
"encoding/base64"
"testing"

"github.com/btcsuite/btcd/btcec"
"github.com/medibloc/panacea-core/v2/types/testsuite"
"github.com/medibloc/panacea-core/v2/x/oracle"
"github.com/medibloc/panacea-core/v2/x/oracle/types"
"github.com/stretchr/testify/suite"
)

type abciTestSuite struct {
testsuite.TestSuite

uniqueID string
}

func TestAbciTestSuite(t *testing.T) {
suite.Run(t, new(abciTestSuite))
}

func (suite *abciTestSuite) BeforeTest(_, _ string) {
ctx := suite.Ctx
suite.uniqueID = "uniqueID"

oraclePrivKey, err := btcec.NewPrivateKey(btcec.S256())
suite.Require().NoError(err)

suite.OracleKeeper.SetParams(ctx, types.Params{
OraclePublicKey: base64.StdEncoding.EncodeToString(oraclePrivKey.PubKey().SerializeCompressed()),
OraclePubKeyRemoteReport: base64.StdEncoding.EncodeToString([]byte("oraclePubKeyRemoteReport")),
UniqueId: suite.uniqueID,
})
}

func (suite *abciTestSuite) TestOracleUpgradeSuccess() {
ctx := suite.Ctx
ctx = ctx.WithBlockHeight(1)

suite.Require().Equal(suite.uniqueID, suite.OracleKeeper.GetParams(ctx).UniqueId)

upgradeUniqueID := "upgradeUniqueID"

upgradeInfo := &types.OracleUpgradeInfo{
UniqueId: upgradeUniqueID,
Height: 10,
}

suite.Require().NoError(suite.OracleKeeper.SetOracleUpgradeInfo(ctx, upgradeInfo))

ctx = ctx.WithBlockHeight(10)

oracle.EndBlocker(ctx, suite.OracleKeeper)

suite.Require().Equal(upgradeUniqueID, suite.OracleKeeper.GetParams(ctx).UniqueId)

_, err := suite.OracleKeeper.GetOracleUpgradeInfo(ctx)
suite.Require().Error(err, types.ErrOracleUpgradeInfoNotFound)
}

func (suite *abciTestSuite) TestOracleUpgradeFailedBeforeReachUpgradeHeight() {
ctx := suite.Ctx
ctx = ctx.WithBlockHeight(1)

suite.Require().Equal(suite.uniqueID, suite.OracleKeeper.GetParams(ctx).UniqueId)

upgradeUniqueID := "upgradeUniqueID"

upgradeInfo := &types.OracleUpgradeInfo{
UniqueId: upgradeUniqueID,
Height: 10,
}

suite.Require().NoError(suite.OracleKeeper.SetOracleUpgradeInfo(ctx, upgradeInfo))

ctx = ctx.WithBlockHeight(9)

oracle.EndBlocker(ctx, suite.OracleKeeper)

suite.Require().Equal(suite.uniqueID, suite.OracleKeeper.GetParams(ctx).UniqueId)
}
1 change: 1 addition & 0 deletions x/oracle/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command {
cmd.AddCommand(CmdGetOracle())
cmd.AddCommand(CmdGetOracleRegistrations())
cmd.AddCommand(CmdGetOracleRegistration())
cmd.AddCommand(CmdGetOracleUpgradeInfo())
cmd.AddCommand(CmdGetParams())

return cmd
Expand Down
35 changes: 35 additions & 0 deletions x/oracle/client/cli/queryOracleUpgradeInfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cli

import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/medibloc/panacea-core/v2/x/oracle/types"
"github.com/spf13/cobra"
)

func CmdGetOracleUpgradeInfo() *cobra.Command {
cmd := &cobra.Command{
Use: "oracle-upgrade-info",
Short: "Query an oracle upgrade information",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.OracleUpgradeInfo(cmd.Context(), &types.QueryOracleUpgradeInfoRequest{})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
100 changes: 100 additions & 0 deletions x/oracle/client/cli/txUpgradeOracleProposal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package cli

import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/medibloc/panacea-core/v2/x/oracle/types"
"github.com/spf13/cobra"
)

const (
FlagUpgradeUniqueID = "upgrade-unique-id"
FlagUpgradeHeight = "upgrade-height"
)

func CmdUpgradeOracleProposal() *cobra.Command {
cmd := &cobra.Command{
Use: "oracle-upgrade (--upgrade-unique-id [uniqueID]) (--upgrade-height [height]) [flags]",
Args: cobra.ExactArgs(0),
Short: "Submit an oracle upgrade proposal",
Long: "Submit an oracle upgrade proposal along with an initial deposit.\n + " +
"You must enter the uniqueID of a new version of oracle and target block height for upgrade.",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
from := clientCtx.GetFromAddress()

depositStr, err := cmd.Flags().GetString(govcli.FlagDeposit)
if err != nil {
return err
}

deposit, err := sdk.ParseCoinsNormalized(depositStr)
if err != nil {
return err
}

content, err := makeProposalContent(cmd)
if err != nil {
return err
}

msg, err := govtypes.NewMsgSubmitProposal(content, deposit, from)
if err != nil {
return err
}
if err := msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
cmd.Flags().String(govcli.FlagTitle, "", "title of proposal")
cmd.Flags().String(govcli.FlagDescription, "", "description of proposal")
cmd.Flags().String(govcli.FlagDeposit, "", "deposit of proposal")
cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen")
cmd.Flags().String(FlagUpgradeUniqueID, "", "Oracle's uniqueID to be upgraded")

if err := cmd.MarkFlagRequired(FlagUpgradeHeight); err != nil {
panic(err)
}
if err := cmd.MarkFlagRequired(FlagUpgradeUniqueID); err != nil {
panic(err)
}
return cmd
}

func makeProposalContent(cmd *cobra.Command) (govtypes.Content, error) {
title, err := cmd.Flags().GetString(govcli.FlagTitle)
if err != nil {
return nil, err
}

description, err := cmd.Flags().GetString(govcli.FlagDescription)
if err != nil {
return nil, err
}

height, err := cmd.Flags().GetInt64(FlagUpgradeHeight)
if err != nil {
return nil, err
}

uniqueID, err := cmd.Flags().GetString(FlagUpgradeUniqueID)
if err != nil {
return nil, err
}

plan := types.Plan{UniqueId: uniqueID, Height: height}
if err := plan.ValidateBasic(); err != nil {
return nil, err
}
content := types.NewOracleUpgradeProposal(title, description, plan)
return content, nil
}
10 changes: 10 additions & 0 deletions x/oracle/client/proposal_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package client

import (
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
"github.com/medibloc/panacea-core/v2/x/oracle/client/rest"

"github.com/medibloc/panacea-core/v2/x/oracle/client/cli"
)

var ProposalHandler = govclient.NewProposalHandler(cli.CmdUpgradeOracleProposal, rest.ProposalRESTHandler)
Loading