diff --git a/types/testsuite/suite.go b/types/testsuite/suite.go index 3bfe3872..f0f1c2cf 100644 --- a/types/testsuite/suite.go +++ b/types/testsuite/suite.go @@ -56,6 +56,7 @@ type TestSuite struct { suite.Suite Ctx sdk.Context + Cdc params.EncodingConfig AccountKeeper authkeeper.AccountKeeper StakingKeeper stakingkeeper.Keeper @@ -137,6 +138,7 @@ func (suite *TestSuite) SetupTest() { scopedIBCKeeper := suite.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) suite.Ctx = ctx + suite.Cdc = cdc suite.AccountKeeper = authkeeper.NewAccountKeeper( cdc.Marshaler, keyParams[authtypes.StoreKey], @@ -246,6 +248,7 @@ func newTestCodec() params.EncodingConfig { authtypes.RegisterInterfaces(interfaceRegistry) banktypes.RegisterInterfaces(interfaceRegistry) didtypes.RegisterInterfaces(interfaceRegistry) + oracletypes.RegisterInterfaces(interfaceRegistry) //TODO: When other Msgs are implemented, I'll remove this comment. //datadealtypes.RegisterInterfaces(interfaceRegistry) marshaler := codec.NewProtoCodec(interfaceRegistry) diff --git a/x/oracle/handler.go b/x/oracle/handler.go index 5ac37fb8..466436dc 100644 --- a/x/oracle/handler.go +++ b/x/oracle/handler.go @@ -20,9 +20,9 @@ func NewHandler(k keeper.Keeper) sdk.Handler { case *types.MsgRegisterOracle: res, err := msgServer.RegisterOracle(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) - //case *types.MsgApproveOracleRegistration: - // res, err := msgServer.ApproveOracleRegistration(sdk.WrapSDKContext(ctx), msg) - // return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgApproveOracleRegistration: + res, err := msgServer.ApproveOracleRegistration(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) //case *types.MsgUpdateOracleInfo: // res, err := msgServer.UpdateOracleInfo(sdk.WrapSDKContext(ctx), msg) // return sdk.WrapServiceResult(ctx, res, err) diff --git a/x/oracle/keeper/keeper.go b/x/oracle/keeper/keeper.go index b651643f..5565defc 100644 --- a/x/oracle/keeper/keeper.go +++ b/x/oracle/keeper/keeper.go @@ -1,6 +1,9 @@ package keeper import ( + "fmt" + + "github.com/btcsuite/btcd/btcec" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/medibloc/panacea-core/v2/x/oracle/types" @@ -35,3 +38,27 @@ func NewKeeper( paramSpace: paramSpace, } } + +func (k Keeper) VerifySignature(ctx sdk.Context, msg codec.ProtoMarshaler, sigBz []byte) error { + bz, err := k.cdc.Marshal(msg) + if err != nil { + return err + } + + oraclePubKeyBz := k.GetParams(ctx).MustDecodeOraclePublicKey() + oraclePubKey, err := btcec.ParsePubKey(oraclePubKeyBz, btcec.S256()) + if err != nil { + return err + } + + signature, err := btcec.ParseSignature(sigBz, btcec.S256()) + if err != nil { + return err + } + + if !signature.Verify(bz, oraclePubKey) { + return fmt.Errorf("failed to signature validation") + } + + return nil +} diff --git a/x/oracle/keeper/msg_server_oracle.go b/x/oracle/keeper/msg_server_oracle.go index 41082b80..e6a1102c 100644 --- a/x/oracle/keeper/msg_server_oracle.go +++ b/x/oracle/keeper/msg_server_oracle.go @@ -25,9 +25,21 @@ func (m msgServer) RegisterOracle(goCtx context.Context, msg *types.MsgRegisterO return &types.MsgRegisterOracleResponse{}, nil } -func (m msgServer) ApproveOracleRegistration(ctx context.Context, registration *types.MsgApproveOracleRegistration) (*types.MsgApproveOracleRegistrationResponse, error) { - //TODO implement me - panic("implement me") +func (m msgServer) ApproveOracleRegistration(goCtx context.Context, msg *types.MsgApproveOracleRegistration) (*types.MsgApproveOracleRegistrationResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := m.Keeper.ApproveOracleRegistration(ctx, msg); err != nil { + return nil, err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + ) + + return &types.MsgApproveOracleRegistrationResponse{}, nil } func (m msgServer) UpdateOracleInfo(ctx context.Context, info *types.MsgUpdateOracleInfo) (*types.MsgUpdateOracleInfoResponse, error) { diff --git a/x/oracle/keeper/oracle.go b/x/oracle/keeper/oracle.go index 4864895f..9cb3ae0d 100644 --- a/x/oracle/keeper/oracle.go +++ b/x/oracle/keeper/oracle.go @@ -2,6 +2,7 @@ package keeper import ( "errors" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -42,6 +43,68 @@ func (k Keeper) RegisterOracle(ctx sdk.Context, msg *types.MsgRegisterOracle) er return nil } +func (k Keeper) ApproveOracleRegistration(ctx sdk.Context, msg *types.MsgApproveOracleRegistration) error { + + if err := k.validateApproveOracleRegistration(ctx, msg); err != nil { + return sdkerrors.Wrapf(types.ErrApproveOracleRegistration, err.Error()) + } + + oracleRegistration, err := k.GetOracleRegistration(ctx, msg.ApproveOracleRegistration.UniqueId, msg.ApproveOracleRegistration.TargetOracleAddress) + if err != nil { + return sdkerrors.Wrapf(types.ErrApproveOracleRegistration, err.Error()) + } + + newOracle := types.NewOracle( + msg.ApproveOracleRegistration.TargetOracleAddress, + msg.ApproveOracleRegistration.UniqueId, + oracleRegistration.Endpoint, + oracleRegistration.OracleCommissionRate, + ) + + // append new oracle info + if err := k.SetOracle(ctx, newOracle); err != nil { + return sdkerrors.Wrapf(types.ErrApproveOracleRegistration, err.Error()) + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeApproveOracleRegistration, + sdk.NewAttribute(types.AttributeKeyOracleAddress, msg.ApproveOracleRegistration.TargetOracleAddress), + ), + ) + + return nil + +} + +// validateApproveOracleRegistration checks signature +func (k Keeper) validateApproveOracleRegistration(ctx sdk.Context, msg *types.MsgApproveOracleRegistration) error { + + params := k.GetParams(ctx) + targetOracleAddress := msg.ApproveOracleRegistration.TargetOracleAddress + + // check unique id + if msg.ApproveOracleRegistration.UniqueId != params.UniqueId { + return types.ErrInvalidUniqueID + } + + // verify signature + if err := k.VerifySignature(ctx, msg.ApproveOracleRegistration, msg.Signature); err != nil { + return err + } + + // check if the oracle has been already registered + hasOracle, err := k.HasOracle(ctx, targetOracleAddress) + if err != nil { + return err + } + if hasOracle { + return fmt.Errorf("already registered oracle. address(%s)", targetOracleAddress) + } + + return nil +} + func (k Keeper) SetOracleRegistration(ctx sdk.Context, regOracle *types.OracleRegistration) error { store := ctx.KVStore(k.storeKey) @@ -163,3 +226,13 @@ func (k Keeper) GetOracle(ctx sdk.Context, address string) (*types.Oracle, error return oracle, nil } + +func (k Keeper) HasOracle(ctx sdk.Context, address string) (bool, error) { + store := ctx.KVStore(k.storeKey) + accAddr, err := sdk.AccAddressFromBech32(address) + if err != nil { + return false, err + } + + return store.Has(types.GetOracleKey(accAddr)), nil +} diff --git a/x/oracle/keeper/oracle_test.go b/x/oracle/keeper/oracle_test.go index 25c5a929..9af88711 100644 --- a/x/oracle/keeper/oracle_test.go +++ b/x/oracle/keeper/oracle_test.go @@ -220,3 +220,177 @@ func (suite *oracleTestSuite) TestRegisterOracleNotSameUniqueID() { suite.Require().Error(err, types.ErrOracleRegistration) suite.Require().ErrorContains(err, "is not match the currently active uniqueID") } + +func (suite *oracleTestSuite) TestApproveOracleRegistrationSuccess() { + ctx := suite.Ctx + + msgRegisterOracle := &types.MsgRegisterOracle{ + UniqueId: suite.uniqueID, + OracleAddress: suite.oracleAccAddr.String(), + NodePubKey: suite.nodePubKey.SerializeCompressed(), + NodePubKeyRemoteReport: suite.nodePubKeyRemoteReport, + TrustedBlockHeight: suite.trustedBlockHeight, + TrustedBlockHash: suite.trustedBlockHash, + Endpoint: suite.endpoint, + OracleCommissionRate: suite.oracleCommissionRate, + } + + err := suite.OracleKeeper.RegisterOracle(ctx, msgRegisterOracle) + suite.Require().NoError(err) + + encryptedOraclePrivKey, err := btcec.Encrypt(suite.nodePubKey, suite.oraclePrivKey.Serialize()) + suite.Require().NoError(err) + + approveOracleRegistration := &types.ApproveOracleRegistration{ + UniqueId: suite.uniqueID, + ApproverOracleAddress: suite.oracleAccAddr.String(), + TargetOracleAddress: suite.oracleAccAddr.String(), + EncryptedOraclePrivKey: encryptedOraclePrivKey, + } + + approveOracleRegistrationBz, err := suite.Cdc.Marshaler.Marshal(approveOracleRegistration) + suite.Require().NoError(err) + signature, err := suite.oraclePrivKey.Sign(approveOracleRegistrationBz) + suite.Require().NoError(err) + + msgApproveOracleRegistration := types.NewMsgApproveOracleRegistration(approveOracleRegistration, signature.Serialize()) + + err = suite.OracleKeeper.ApproveOracleRegistration(ctx, msgApproveOracleRegistration) + suite.Require().NoError(err) + + events := suite.Ctx.EventManager().Events() + suite.Require().Equal(2, len(events)) + suite.Require().Equal(types.EventTypeApproveOracleRegistration, events[1].Type) + + eventVoteAttributes := events[1].Attributes + suite.Require().Equal(1, len(eventVoteAttributes)) + suite.Require().Equal(types.AttributeKeyOracleAddress, string(eventVoteAttributes[0].Key)) + suite.Require().Equal(suite.oracleAccAddr.String(), string(eventVoteAttributes[0].Value)) + + getOracle, err := suite.OracleKeeper.GetOracle(ctx, suite.oracleAccAddr.String()) + suite.Require().NoError(err) + suite.Require().Equal(suite.uniqueID, getOracle.UniqueId) + suite.Require().Equal(suite.oracleAccAddr.String(), getOracle.OracleAddress) + suite.Require().Equal(suite.endpoint, getOracle.Endpoint) + suite.Require().Equal(suite.oracleCommissionRate, getOracle.OracleCommissionRate) +} + +func (suite *oracleTestSuite) TestApproveOracleRegistrationFailedInvalidUniqueID() { + ctx := suite.Ctx + + msgRegisterOracle := &types.MsgRegisterOracle{ + UniqueId: suite.uniqueID, + OracleAddress: suite.oracleAccAddr.String(), + NodePubKey: suite.nodePubKey.SerializeCompressed(), + NodePubKeyRemoteReport: suite.nodePubKeyRemoteReport, + TrustedBlockHeight: suite.trustedBlockHeight, + TrustedBlockHash: suite.trustedBlockHash, + Endpoint: suite.endpoint, + OracleCommissionRate: suite.oracleCommissionRate, + } + + err := suite.OracleKeeper.RegisterOracle(ctx, msgRegisterOracle) + suite.Require().NoError(err) + + encryptedOraclePrivKey, err := btcec.Encrypt(suite.nodePubKey, suite.oraclePrivKey.Serialize()) + suite.Require().NoError(err) + + approveOracleRegistration := &types.ApproveOracleRegistration{ + UniqueId: "uniqueID2", + ApproverOracleAddress: suite.oracleAccAddr.String(), + TargetOracleAddress: suite.oracleAccAddr.String(), + EncryptedOraclePrivKey: encryptedOraclePrivKey, + } + approveOracleRegistrationBz, err := suite.Cdc.Marshaler.Marshal(approveOracleRegistration) + suite.Require().NoError(err) + signature, err := suite.oraclePrivKey.Sign(approveOracleRegistrationBz) + suite.Require().NoError(err) + + msgApproveOracleRegistration := types.NewMsgApproveOracleRegistration(approveOracleRegistration, signature.Serialize()) + + err = suite.OracleKeeper.ApproveOracleRegistration(ctx, msgApproveOracleRegistration) + suite.Require().Error(err, types.ErrInvalidUniqueID) +} + +func (suite *oracleTestSuite) TestApproveOracleRegistrationFailedInvalidSignature() { + ctx := suite.Ctx + + msgRegisterOracle := &types.MsgRegisterOracle{ + UniqueId: suite.uniqueID, + OracleAddress: suite.oracleAccAddr.String(), + NodePubKey: suite.nodePubKey.SerializeCompressed(), + NodePubKeyRemoteReport: suite.nodePubKeyRemoteReport, + TrustedBlockHeight: suite.trustedBlockHeight, + TrustedBlockHash: suite.trustedBlockHash, + Endpoint: suite.endpoint, + OracleCommissionRate: suite.oracleCommissionRate, + } + + err := suite.OracleKeeper.RegisterOracle(ctx, msgRegisterOracle) + suite.Require().NoError(err) + + encryptedOraclePrivKey, err := btcec.Encrypt(suite.nodePubKey, suite.oraclePrivKey.Serialize()) + suite.Require().NoError(err) + + approveOracleRegistration := &types.ApproveOracleRegistration{ + UniqueId: "uniqueID", + ApproverOracleAddress: suite.oracleAccAddr.String(), + TargetOracleAddress: suite.oracleAccAddr.String(), + EncryptedOraclePrivKey: encryptedOraclePrivKey, + } + approveOracleRegistrationBz, err := suite.Cdc.Marshaler.Marshal(approveOracleRegistration) + suite.Require().NoError(err) + newPrivateKey, err := btcec.NewPrivateKey(btcec.S256()) + suite.Require().NoError(err) + signature, err := newPrivateKey.Sign(approveOracleRegistrationBz) + suite.Require().NoError(err) + + msgApproveOracleRegistration := types.NewMsgApproveOracleRegistration(approveOracleRegistration, signature.Serialize()) + + err = suite.OracleKeeper.ApproveOracleRegistration(ctx, msgApproveOracleRegistration) + suite.Require().Error(err, "failed to signature validation") +} + +func (suite *oracleTestSuite) TestApproveOracleRegistrationFailedAlreadyExistOracle() { + ctx := suite.Ctx + + oracle := types.NewOracle(suite.oracleAccAddr.String(), suite.uniqueID, suite.endpoint, suite.oracleCommissionRate) + err := suite.OracleKeeper.SetOracle(ctx, oracle) + suite.Require().NoError(err) + + msgRegisterOracle := &types.MsgRegisterOracle{ + UniqueId: suite.uniqueID, + OracleAddress: suite.oracleAccAddr.String(), + NodePubKey: suite.nodePubKey.SerializeCompressed(), + NodePubKeyRemoteReport: suite.nodePubKeyRemoteReport, + TrustedBlockHeight: suite.trustedBlockHeight, + TrustedBlockHash: suite.trustedBlockHash, + Endpoint: suite.endpoint, + OracleCommissionRate: suite.oracleCommissionRate, + } + + oracleRegistration := types.NewOracleRegistration(msgRegisterOracle) + + err = suite.OracleKeeper.SetOracleRegistration(ctx, oracleRegistration) + suite.Require().NoError(err) + + encryptedOraclePrivKey, err := btcec.Encrypt(suite.nodePubKey, suite.oraclePrivKey.Serialize()) + suite.Require().NoError(err) + + approveOracleRegistration := &types.ApproveOracleRegistration{ + UniqueId: "uniqueID", + ApproverOracleAddress: suite.oracleAccAddr.String(), + TargetOracleAddress: suite.oracleAccAddr.String(), + EncryptedOraclePrivKey: encryptedOraclePrivKey, + } + approveOracleRegistrationBz, err := suite.Cdc.Marshaler.Marshal(approveOracleRegistration) + suite.Require().NoError(err) + signature, err := suite.oraclePrivKey.Sign(approveOracleRegistrationBz) + suite.Require().NoError(err) + + msgApproveOracleRegistration := types.NewMsgApproveOracleRegistration(approveOracleRegistration, signature.Serialize()) + + err = suite.OracleKeeper.ApproveOracleRegistration(ctx, msgApproveOracleRegistration) + suite.Require().Error(err, types.ErrOracleRegistration) + suite.Require().ErrorContains(err, fmt.Sprintf("already registered oracle. address(%s)", msgRegisterOracle.OracleAddress)) +} diff --git a/x/oracle/types/codec.go b/x/oracle/types/codec.go index efb17925..f14699a4 100644 --- a/x/oracle/types/codec.go +++ b/x/oracle/types/codec.go @@ -19,6 +19,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &MsgApproveOracleRegistration{}, &MsgUpdateOracleInfo{}, ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/oracle/types/errors.go b/x/oracle/types/errors.go index b9e92631..f58538aa 100644 --- a/x/oracle/types/errors.go +++ b/x/oracle/types/errors.go @@ -3,8 +3,10 @@ package types import sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" var ( - ErrOracleRegistration = sdkerrors.Register(ModuleName, 1, "error while registering a oracle") - ErrGetOracleRegistration = sdkerrors.Register(ModuleName, 2, "error while getting a oracle registration") - ErrGetOracle = sdkerrors.Register(ModuleName, 3, "error while getting a oracle") - ErrOracleNotFound = sdkerrors.Register(ModuleName, 4, "oracle not found") + ErrOracleRegistration = sdkerrors.Register(ModuleName, 1, "error while registering a oracle") + ErrGetOracleRegistration = sdkerrors.Register(ModuleName, 2, "error while getting a oracle registration") + ErrGetOracle = sdkerrors.Register(ModuleName, 3, "error while getting a oracle") + ErrOracleNotFound = sdkerrors.Register(ModuleName, 4, "oracle not found") + ErrInvalidUniqueID = sdkerrors.Register(ModuleName, 5, "invalid unique id") + ErrApproveOracleRegistration = sdkerrors.Register(ModuleName, 6, "error while approving an oracle registration") ) diff --git a/x/oracle/types/events.go b/x/oracle/types/events.go index e1950f2f..a2a8dbf9 100644 --- a/x/oracle/types/events.go +++ b/x/oracle/types/events.go @@ -1,7 +1,8 @@ package types const ( - EventTypeRegistration = "oracle_registration" + EventTypeRegistration = "oracle_registration" + EventTypeApproveOracleRegistration = "approve_oracle_registration" AttributeKeyUniqueID = "unique_id" AttributeKeyOracleAddress = "oracle_address" diff --git a/x/oracle/types/params.go b/x/oracle/types/params.go index c36c9548..b391fb8d 100644 --- a/x/oracle/types/params.go +++ b/x/oracle/types/params.go @@ -93,3 +93,17 @@ func validateUniqueID(i interface{}) error { return nil } + +// MustDecodeOraclePublicKey decodes a base64-encoded Params.OraclePublicKey. +// It panics if the decoding is failed, assuming that the Params was already validated by Params.Validate(). +func (p Params) MustDecodeOraclePublicKey() []byte { + return mustDecodeBase64Str(p.OraclePublicKey) +} + +func mustDecodeBase64Str(s string) []byte { + decoded, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic(err) + } + return decoded +}