diff --git a/app/app.go b/app/app.go index 543ce2c9d..c9669c1f9 100644 --- a/app/app.go +++ b/app/app.go @@ -29,11 +29,11 @@ import ( mesgcodec "github.com/mesg-foundation/engine/codec" "github.com/mesg-foundation/engine/cosmos" executionsdk "github.com/mesg-foundation/engine/sdk/execution" - servicesdk "github.com/mesg-foundation/engine/sdk/service" "github.com/mesg-foundation/engine/x/instance" "github.com/mesg-foundation/engine/x/ownership" "github.com/mesg-foundation/engine/x/process" "github.com/mesg-foundation/engine/x/runner" + "github.com/mesg-foundation/engine/x/service" ) const appName = "engine" @@ -63,7 +63,7 @@ var ( instance.AppModuleBasic{}, process.AppModuleBasic{}, runner.AppModuleBasic{}, - cosmos.NewAppModuleBasic(servicesdk.ModuleName), + service.AppModuleBasic{}, cosmos.NewAppModuleBasic(executionsdk.ModuleName), ) @@ -119,7 +119,7 @@ type NewApp struct { instanceKeeper instance.Keeper processKeeper process.Keeper runnerKeeper runner.Keeper - serviceKeeper *servicesdk.Keeper + serviceKeeper service.Keeper executionKeeper *executionsdk.Keeper // Module Manager @@ -159,7 +159,7 @@ func NewInitApp( instance.ModuleName, process.ModuleName, runner.ModuleName, - servicesdk.ModuleName, + service.ModuleName, executionsdk.ModuleName, ) @@ -245,7 +245,7 @@ func NewInitApp( app.ownershipKeeper = ownership.NewKeeper(app.cdc, keys[ownership.StoreKey]) app.instanceKeeper = instance.NewKeeper(app.cdc, keys[instance.StoreKey]) app.processKeeper = process.NewKeeper(app.cdc, keys[process.StoreKey], app.instanceKeeper, app.ownershipKeeper) - app.serviceKeeper = servicesdk.NewKeeper(keys[servicesdk.ModuleName], app.ownershipKeeper) + app.serviceKeeper = service.NewKeeper(app.cdc, keys[service.StoreKey], app.ownershipKeeper) app.runnerKeeper = runner.NewKeeper(app.cdc, keys[runner.StoreKey], app.instanceKeeper) app.executionKeeper = executionsdk.NewKeeper(keys[executionsdk.ModuleName], app.serviceKeeper, app.instanceKeeper, app.runnerKeeper, app.processKeeper) @@ -264,7 +264,7 @@ func NewInitApp( instance.NewAppModule(app.instanceKeeper), process.NewAppModule(app.processKeeper), runner.NewAppModule(app.runnerKeeper, app.instanceKeeper), - servicesdk.NewModule(app.serviceKeeper), + service.NewAppModule(app.serviceKeeper), executionsdk.NewModule(app.executionKeeper), staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), @@ -291,7 +291,7 @@ func NewInitApp( instance.ModuleName, process.ModuleName, runner.ModuleName, - servicesdk.ModuleName, + service.ModuleName, executionsdk.ModuleName, supply.ModuleName, diff --git a/sdk/execution/keeper.go b/sdk/execution/keeper.go index 896756579..776d9014e 100644 --- a/sdk/execution/keeper.go +++ b/sdk/execution/keeper.go @@ -10,23 +10,23 @@ import ( "github.com/mesg-foundation/engine/hash" "github.com/mesg-foundation/engine/protobuf/api" "github.com/mesg-foundation/engine/protobuf/types" - servicesdk "github.com/mesg-foundation/engine/sdk/service" "github.com/mesg-foundation/engine/x/instance" "github.com/mesg-foundation/engine/x/process" "github.com/mesg-foundation/engine/x/runner" + "github.com/mesg-foundation/engine/x/service" ) // Keeper holds the logic to read and write data. type Keeper struct { storeKey *cosmostypes.KVStoreKey - serviceKeeper *servicesdk.Keeper + serviceKeeper service.Keeper instanceKeeper instance.Keeper runnerKeeper runner.Keeper processKeeper process.Keeper } // NewKeeper initialize a new keeper. -func NewKeeper(storeKey *cosmostypes.KVStoreKey, serviceKeeper *servicesdk.Keeper, instanceKeeper instance.Keeper, runnerKeeper runner.Keeper, processKeeper process.Keeper) *Keeper { +func NewKeeper(storeKey *cosmostypes.KVStoreKey, serviceKeeper service.Keeper, instanceKeeper instance.Keeper, runnerKeeper runner.Keeper, processKeeper process.Keeper) *Keeper { return &Keeper{ storeKey: storeKey, serviceKeeper: serviceKeeper, diff --git a/sdk/service/codec.go b/sdk/service/codec.go deleted file mode 100644 index 90ecf078c..000000000 --- a/sdk/service/codec.go +++ /dev/null @@ -1,9 +0,0 @@ -package servicesdk - -import ( - "github.com/mesg-foundation/engine/codec" -) - -func init() { - codec.RegisterConcrete(msgCreateService{}, "service/create", nil) -} diff --git a/sdk/service/keeper.go b/sdk/service/keeper.go deleted file mode 100644 index 90a356a07..000000000 --- a/sdk/service/keeper.go +++ /dev/null @@ -1,119 +0,0 @@ -package servicesdk - -import ( - "fmt" - - cosmostypes "github.com/cosmos/cosmos-sdk/types" - "github.com/mesg-foundation/engine/codec" - "github.com/mesg-foundation/engine/hash" - ownershippb "github.com/mesg-foundation/engine/ownership" - "github.com/mesg-foundation/engine/protobuf/api" - "github.com/mesg-foundation/engine/service" - "github.com/mesg-foundation/engine/service/validator" - "github.com/mesg-foundation/engine/x/ownership" -) - -// Keeper holds the logic to read and write data. -type Keeper struct { - storeKey *cosmostypes.KVStoreKey - ownershipKeeper ownership.Keeper -} - -// NewKeeper initialize a new keeper. -func NewKeeper(storeKey *cosmostypes.KVStoreKey, ownershipKeeper ownership.Keeper) *Keeper { - return &Keeper{ - storeKey: storeKey, - ownershipKeeper: ownershipKeeper, - } -} - -// Create creates a new service. -func (k *Keeper) Create(request cosmostypes.Request, msg *msgCreateService) (*service.Service, error) { - store := request.KVStore(k.storeKey) - // create service - srv := initializeService(msg.Request) - - // check if service already exists. - if store.Has(srv.Hash) { - return nil, fmt.Errorf("service %q already exists", srv.Hash) - } - - // TODO: the following test should be moved in New function - if srv.Sid == "" { - // make sure that sid doesn't have the same length with id. - srv.Sid = "_" + srv.Hash.String() - } - - if err := validator.ValidateService(srv); err != nil { - return nil, err - } - - if _, err := k.ownershipKeeper.Set(request, msg.Owner, srv.Hash, ownershippb.Ownership_Service); err != nil { - return nil, err - } - - value, err := codec.MarshalBinaryBare(srv) - if err != nil { - return nil, err - } - store.Set(srv.Hash, value) - return srv, nil -} - -// Get returns the service that matches given hash. -func (k *Keeper) Get(request cosmostypes.Request, hash hash.Hash) (*service.Service, error) { - var sv *service.Service - store := request.KVStore(k.storeKey) - if !store.Has(hash) { - return nil, fmt.Errorf("service %q not found", hash) - } - value := store.Get(hash) - return sv, codec.UnmarshalBinaryBare(value, &sv) -} - -// Exists returns true if a specific set of data exists in the database, false otherwise -func (k *Keeper) Exists(request cosmostypes.Request, hash hash.Hash) (bool, error) { - return request.KVStore(k.storeKey).Has(hash), nil -} - -// Hash returns the hash of a service request. -func (k *Keeper) Hash(serviceRequest *api.CreateServiceRequest) hash.Hash { - return initializeService(serviceRequest).Hash -} - -// List returns all services. -func (k *Keeper) List(request cosmostypes.Request) ([]*service.Service, error) { - var ( - services []*service.Service - iter = request.KVStore(k.storeKey).Iterator(nil, nil) - ) - for iter.Valid() { - var sv *service.Service - if err := codec.UnmarshalBinaryBare(iter.Value(), &sv); err != nil { - return nil, err - } - services = append(services, sv) - iter.Next() - } - iter.Close() - return services, nil -} - -func initializeService(req *api.CreateServiceRequest) *service.Service { - // create service - srv := &service.Service{ - Sid: req.Sid, - Name: req.Name, - Description: req.Description, - Configuration: req.Configuration, - Tasks: req.Tasks, - Events: req.Events, - Dependencies: req.Dependencies, - Repository: req.Repository, - Source: req.Source, - } - - // calculate and apply hash to service. - srv.Hash = hash.Dump(srv) - return srv -} diff --git a/sdk/service/module.go b/sdk/service/module.go deleted file mode 100644 index aac85aaf8..000000000 --- a/sdk/service/module.go +++ /dev/null @@ -1,67 +0,0 @@ -package servicesdk - -import ( - "fmt" - - cosmostypes "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/mesg-foundation/engine/codec" - "github.com/mesg-foundation/engine/cosmos" - "github.com/mesg-foundation/engine/hash" - "github.com/mesg-foundation/engine/protobuf/api" - abci "github.com/tendermint/tendermint/abci/types" -) - -// ModuleName is the name of this module. -const ModuleName = "service" - -// NewModule returns the module of this sdk. -func NewModule(k *Keeper) module.AppModule { - return cosmos.NewAppModule(cosmos.NewAppModuleBasic(ModuleName), handler(k), querier(k)) -} - -func handler(k *Keeper) cosmos.Handler { - return func(request cosmostypes.Request, msg cosmostypes.Msg) (hash.Hash, error) { - switch msg := msg.(type) { - case msgCreateService: - srv, err := k.Create(request, &msg) - if err != nil { - return nil, cosmos.NewMesgWrapError(cosmos.CodeInternal, err) - } - return srv.Hash, nil - default: - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "Unrecognized service Msg type: %v", msg.Type()) - } - } -} - -func querier(k *Keeper) cosmos.Querier { - return func(request cosmostypes.Request, path []string, req abci.RequestQuery) (res interface{}, err error) { - switch path[0] { - case "get": - hash, err := hash.Decode(path[1]) - if err != nil { - return nil, err - } - return k.Get(request, hash) - case "list": - return k.List(request) - case "hash": - var createServiceRequest api.CreateServiceRequest - if err := codec.UnmarshalBinaryBare(req.Data, &createServiceRequest); err != nil { - return nil, err - } - return k.Hash(&createServiceRequest), nil - case "exists": - hash, err := hash.Decode(path[1]) - if err != nil { - return nil, err - } - return k.Exists(request, hash) - - default: - return nil, fmt.Errorf("unknown service query endpoint %s", path[0]) - } - } -} diff --git a/sdk/service/msgs.go b/sdk/service/msgs.go deleted file mode 100644 index 6f1c903f2..000000000 --- a/sdk/service/msgs.go +++ /dev/null @@ -1,54 +0,0 @@ -package servicesdk - -import ( - cosmostypes "github.com/cosmos/cosmos-sdk/types" - cosmoserrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/mesg-foundation/engine/codec" - "github.com/mesg-foundation/engine/ext/xvalidator" - "github.com/mesg-foundation/engine/protobuf/api" -) - -// msgCreateService defines a state transition to create a service. -type msgCreateService struct { - Request *api.CreateServiceRequest `json:"request" validate:"required"` - Owner cosmostypes.AccAddress `json:"owner" validate:"required,accaddress"` -} - -// newMsgCreateService is a constructor function for msgCreateService. -func newMsgCreateService(req *api.CreateServiceRequest, owner cosmostypes.AccAddress) *msgCreateService { - return &msgCreateService{ - Request: req, - Owner: owner, - } -} - -// Route should return the name of the module. -func (msg msgCreateService) Route() string { - return ModuleName -} - -// Type returns the action. -func (msg msgCreateService) Type() string { - return "create_service" -} - -// ValidateBasic runs stateless checks on the message. -func (msg msgCreateService) ValidateBasic() error { - if err := xvalidator.Validate.Struct(msg); err != nil { - return err - } - if msg.Owner.Empty() { - return cosmoserrors.Wrap(cosmoserrors.ErrInvalidAddress, "owner is missing") - } - return nil -} - -// GetSignBytes encodes the message for signing. -func (msg msgCreateService) GetSignBytes() []byte { - return cosmostypes.MustSortJSON(codec.MustMarshalJSON(msg)) -} - -// GetSigners defines whose signature is required. -func (msg msgCreateService) GetSigners() []cosmostypes.AccAddress { - return []cosmostypes.AccAddress{msg.Owner} -} diff --git a/sdk/service/sdk.go b/sdk/service/sdk.go index 3ea7d308c..8e2ac18c7 100644 --- a/sdk/service/sdk.go +++ b/sdk/service/sdk.go @@ -1,10 +1,13 @@ package servicesdk import ( + "fmt" + "github.com/mesg-foundation/engine/cosmos" "github.com/mesg-foundation/engine/hash" "github.com/mesg-foundation/engine/protobuf/api" - "github.com/mesg-foundation/engine/service" + servicepb "github.com/mesg-foundation/engine/service" + "github.com/mesg-foundation/engine/x/service" ) // SDK is the service sdk. @@ -14,19 +17,16 @@ type SDK struct { // New returns the service sdk. func New(client *cosmos.Client) *SDK { - sdk := &SDK{ - client: client, - } - return sdk + return &SDK{client: client} } // Create creates a new service from definition. -func (s *SDK) Create(req *api.CreateServiceRequest) (*service.Service, error) { +func (s *SDK) Create(req *api.CreateServiceRequest) (*servicepb.Service, error) { acc, err := s.client.GetAccount() if err != nil { return nil, err } - msg := newMsgCreateService(req, acc.GetAddress()) + msg := service.NewMsgCreateService(acc.GetAddress(), req) tx, err := s.client.BuildAndBroadcastMsg(msg) if err != nil { return nil, err @@ -35,18 +35,18 @@ func (s *SDK) Create(req *api.CreateServiceRequest) (*service.Service, error) { } // Get returns the service that matches given hash. -func (s *SDK) Get(hash hash.Hash) (*service.Service, error) { - var service service.Service - if err := s.client.Query("custom/"+ModuleName+"/get/"+hash.String(), nil, &service); err != nil { +func (s *SDK) Get(hash hash.Hash) (*servicepb.Service, error) { + var se servicepb.Service + if err := s.client.QueryJSON(fmt.Sprintf("custom/%s/%s/%s", service.QuerierRoute, service.QueryGetService, hash), nil, &se); err != nil { return nil, err } - return &service, nil + return &se, nil } // List returns all services. -func (s *SDK) List() ([]*service.Service, error) { - var services []*service.Service - if err := s.client.Query("custom/"+ModuleName+"/list", nil, &services); err != nil { +func (s *SDK) List() ([]*servicepb.Service, error) { + var services []*servicepb.Service + if err := s.client.QueryJSON(fmt.Sprintf("custom/%s/%s", service.QuerierRoute, service.QueryListService), nil, &services); err != nil { return nil, err } return services, nil @@ -55,7 +55,7 @@ func (s *SDK) List() ([]*service.Service, error) { // Exists returns if a service already exists. func (s *SDK) Exists(hash hash.Hash) (bool, error) { var exists bool - if err := s.client.Query("custom/"+ModuleName+"/exists/"+hash.String(), nil, &exists); err != nil { + if err := s.client.QueryJSON(fmt.Sprintf("custom/%s/%s/%s", service.QuerierRoute, service.QueryExistService, hash), nil, &exists); err != nil { return false, err } return exists, nil @@ -64,7 +64,7 @@ func (s *SDK) Exists(hash hash.Hash) (bool, error) { // Hash returns the calculate hash of a service. func (s *SDK) Hash(req *api.CreateServiceRequest) (hash.Hash, error) { var h hash.Hash - if err := s.client.Query("custom/"+ModuleName+"/hash", req, &h); err != nil { + if err := s.client.QueryJSON(fmt.Sprintf("custom/%s/%s", service.QuerierRoute, service.QueryHashService), req, &h); err != nil { return nil, err } return h, nil diff --git a/x/service/abci.go b/x/service/abci.go new file mode 100644 index 000000000..f799d1db7 --- /dev/null +++ b/x/service/abci.go @@ -0,0 +1,13 @@ +package service + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// BeginBlocker check for infraction evidence or downtime of validators +// on every begin block +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {} + +// EndBlocker called every block, process inflation, update validator set. +func EndBlocker(ctx sdk.Context, k Keeper) {} diff --git a/x/service/alias.go b/x/service/alias.go new file mode 100644 index 000000000..24f54257f --- /dev/null +++ b/x/service/alias.go @@ -0,0 +1,43 @@ +package service + +import ( + "github.com/mesg-foundation/engine/x/service/internal/keeper" + "github.com/mesg-foundation/engine/x/service/internal/types" +) + +// const aliases +const ( + ModuleName = types.ModuleName + RouterKey = types.RouterKey + StoreKey = types.StoreKey + DefaultParamspace = types.DefaultParamspace + QuerierRoute = types.QuerierRoute +) + +// functions and variable aliases +var ( + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + RegisterCodec = types.RegisterCodec + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + ValidateGenesis = types.ValidateGenesis + + ModuleCdc = types.ModuleCdc + + QueryGetService = types.QueryGetService + QueryListService = types.QueryListService + QueryHashService = types.QueryHashService + QueryExistService = types.QueryExistService + + NewMsgCreateService = types.NewMsgCreateService +) + +// module types +type ( + Keeper = keeper.Keeper + GenesisState = types.GenesisState + Params = types.Params + + MsgCreateService = types.MsgCreateService +) diff --git a/x/service/client/cli/query.go b/x/service/client/cli/query.go new file mode 100644 index 000000000..bea3e9ac3 --- /dev/null +++ b/x/service/client/cli/query.go @@ -0,0 +1,116 @@ +package cli + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/service" + "github.com/mesg-foundation/engine/x/service/internal/types" + "github.com/spf13/cobra" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + // Group service queries under a subcommand + serviceQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + serviceQueryCmd.AddCommand( + flags.GetCommands( + GetCmdGetService(queryRoute, cdc), + GetCmdListService(queryRoute, cdc), + GetCmdHashService(queryRoute, cdc), + GetCmdExistService(queryRoute, cdc), + )..., + ) + + return serviceQueryCmd +} + +func GetCmdGetService(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "get [hash]", + Short: "get", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", queryRoute, types.QueryGetService, args[0]), nil) + if err != nil { + fmt.Printf("could not get service\n%s\n", err.Error()) + return nil + } + + var out *service.Service + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(out) + }, + } +} + +func GetCmdListService(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "list", + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryListService), nil) + if err != nil { + fmt.Printf("could not list services\n%s\n", err.Error()) + return nil + } + + var out []*service.Service + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(out) + }, + } +} + +func GetCmdHashService(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "hash [definition]", + Short: "hash", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryHashService), []byte(args[0])) + if err != nil { + fmt.Printf("could not check service\n%s\n", err.Error()) + return nil + } + + var out hash.Hash + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(out) + }, + } +} + +func GetCmdExistService(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "exist [hash]", + Short: "exist", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", queryRoute, types.QueryExistService, args[0]), nil) + if err != nil { + fmt.Printf("could not check service\n%s\n", err.Error()) + return nil + } + + var out bool + cdc.MustUnmarshalJSON(res, &out) + return cliCtx.PrintOutput(out) + }, + } +} diff --git a/x/service/client/cli/tx.go b/x/service/client/cli/tx.go new file mode 100644 index 000000000..dc3ce6ce7 --- /dev/null +++ b/x/service/client/cli/tx.go @@ -0,0 +1,26 @@ +package cli + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/mesg-foundation/engine/x/service/internal/types" + "github.com/spf13/cobra" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd(cdc *codec.Codec) *cobra.Command { + serviceTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + serviceTxCmd.AddCommand(flags.PostCommands()...) + + return serviceTxCmd +} diff --git a/x/service/client/rest/query.go b/x/service/client/rest/query.go new file mode 100644 index 000000000..48d36666c --- /dev/null +++ b/x/service/client/rest/query.go @@ -0,0 +1,122 @@ +package rest + +import ( + "fmt" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/mesg-foundation/engine/x/service/internal/types" +) + +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc( + "/service/get/{hash}", + queryGetHandlerFn(cliCtx), + ).Methods(http.MethodGet) + r.HandleFunc( + "/service/list", + queryListHandlerFn(cliCtx), + ).Methods(http.MethodGet) + r.HandleFunc( + "/service/hash", + queryHashHandlerFn(cliCtx), + ).Methods(http.MethodPost) + r.HandleFunc( + "/service/exist/{hash}", + queryExistHandlerFn(cliCtx), + ).Methods(http.MethodGet) +} + +func queryGetHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryGetService, vars["hash"]) + + res, height, err := cliCtx.QueryWithData(route, nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryListHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryListService) + + res, height, err := cliCtx.QueryWithData(route, nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryHashHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryHashService) + + res, height, err := cliCtx.QueryWithData(route, data) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryExistHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryExistService, vars["hash"]) + + res, height, err := cliCtx.QueryWithData(route, nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} diff --git a/x/service/client/rest/rest.go b/x/service/client/rest/rest.go new file mode 100644 index 000000000..400fdfb2c --- /dev/null +++ b/x/service/client/rest/rest.go @@ -0,0 +1,12 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/gorilla/mux" +) + +// RegisterRoutes registers service-related REST handlers to a router +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + registerQueryRoutes(cliCtx, r) + registerTxRoutes(cliCtx, r) +} diff --git a/x/service/client/rest/tx.go b/x/service/client/rest/tx.go new file mode 100644 index 000000000..048e7d9e9 --- /dev/null +++ b/x/service/client/rest/tx.go @@ -0,0 +1,10 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/gorilla/mux" +) + +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { + // TODO: +} diff --git a/x/service/genesis.go b/x/service/genesis.go new file mode 100644 index 000000000..87c204348 --- /dev/null +++ b/x/service/genesis.go @@ -0,0 +1,18 @@ +package service + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/mesg-foundation/engine/x/service/internal/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// InitGenesis initialize default parameters and the keeper's address to pubkey map. +func InitGenesis(ctx sdk.Context, k Keeper, data types.GenesisState) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// ExportGenesis writes the current store values // to a genesis file, +// which can be imported again with InitGenesis. +func ExportGenesis(ctx sdk.Context, k Keeper) types.GenesisState { + return types.NewGenesisState() +} diff --git a/x/service/handler.go b/x/service/handler.go new file mode 100644 index 000000000..7cd577b12 --- /dev/null +++ b/x/service/handler.go @@ -0,0 +1,46 @@ +package service + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/mesg-foundation/engine/x/service/internal/types" +) + +// NewHandler creates an sdk.Handler for all the service type messages +func NewHandler(k Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case MsgCreateService: + return handleMsgCreateService(ctx, k, &msg) + default: + errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) + return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) + } + } +} + +// handleMsgCreateService creates a new process. +func handleMsgCreateService(ctx sdk.Context, k Keeper, msg *MsgCreateService) (*sdk.Result, error) { + s, err := k.Create(ctx, msg) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypeCreateService), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Owner.String()), + sdk.NewAttribute(types.AttributeHash, s.Hash.String()), + ), + ) + + return &sdk.Result{ + Data: s.Hash, + Events: ctx.EventManager().Events(), + }, nil +} diff --git a/x/service/internal/keeper/keeper.go b/x/service/internal/keeper/keeper.go new file mode 100644 index 000000000..0e12d4f40 --- /dev/null +++ b/x/service/internal/keeper/keeper.go @@ -0,0 +1,127 @@ +package keeper + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/mesg-foundation/engine/hash" + ownershippb "github.com/mesg-foundation/engine/ownership" + "github.com/mesg-foundation/engine/protobuf/api" + servicepb "github.com/mesg-foundation/engine/service" + "github.com/mesg-foundation/engine/service/validator" + "github.com/mesg-foundation/engine/x/service/internal/types" + "github.com/tendermint/tendermint/libs/log" +) + +// Keeper of the service store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + ownershipKeeper types.OwnershipKeeper +} + +// NewKeeper creates a service keeper +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ownershipKeeper types.OwnershipKeeper) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + ownershipKeeper: ownershipKeeper, + } + return keeper +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + +// Create creates a new service. +func (k *Keeper) Create(ctx sdk.Context, msg *types.MsgCreateService) (*servicepb.Service, error) { + store := ctx.KVStore(k.storeKey) + // create service + srv := initializeService(msg.Request) + + // check if service already exists. + if store.Has(srv.Hash) { + return nil, fmt.Errorf("service %q already exists", srv.Hash) + } + + // TODO: the following test should be moved in New function + if srv.Sid == "" { + // make sure that sid doesn't have the same length with id. + srv.Sid = "_" + srv.Hash.String() + } + + if err := validator.ValidateService(srv); err != nil { + return nil, err + } + + if _, err := k.ownershipKeeper.Set(ctx, msg.Owner, srv.Hash, ownershippb.Ownership_Service); err != nil { + return nil, err + } + + value, err := k.cdc.MarshalBinaryLengthPrefixed(srv) + if err != nil { + return nil, err + } + store.Set(srv.Hash, value) + return srv, nil +} + +// Get returns the service that matches given hash. +func (k *Keeper) Get(ctx sdk.Context, hash hash.Hash) (*servicepb.Service, error) { + store := ctx.KVStore(k.storeKey) + if !store.Has(hash) { + return nil, fmt.Errorf("service %q not found", hash) + } + var sv *servicepb.Service + return sv, k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(hash), &sv) +} + +// Exists returns true if a specific set of data exists in the database, false otherwise +func (k *Keeper) Exists(ctx sdk.Context, hash hash.Hash) (bool, error) { + return ctx.KVStore(k.storeKey).Has(hash), nil +} + +// Hash returns the hash of a service request. +func (k *Keeper) Hash(_ sdk.Context, serviceRequest *api.CreateServiceRequest) hash.Hash { + return initializeService(serviceRequest).Hash +} + +// List returns all services. +func (k *Keeper) List(ctx sdk.Context) ([]*servicepb.Service, error) { + var ( + services []*servicepb.Service + iter = ctx.KVStore(k.storeKey).Iterator(nil, nil) + ) + for iter.Valid() { + var sv *servicepb.Service + if err := k.cdc.UnmarshalBinaryLengthPrefixed(iter.Value(), &sv); err != nil { + return nil, err + } + services = append(services, sv) + iter.Next() + } + iter.Close() + return services, nil +} + +func initializeService(req *api.CreateServiceRequest) *servicepb.Service { + // create service + srv := &servicepb.Service{ + Sid: req.Sid, + Name: req.Name, + Description: req.Description, + Configuration: req.Configuration, + Tasks: req.Tasks, + Events: req.Events, + Dependencies: req.Dependencies, + Repository: req.Repository, + Source: req.Source, + } + + // calculate and apply hash to service. + srv.Hash = hash.Dump(srv) + return srv +} diff --git a/x/service/internal/keeper/querier.go b/x/service/internal/keeper/querier.go new file mode 100644 index 000000000..efe59cf7e --- /dev/null +++ b/x/service/internal/keeper/querier.go @@ -0,0 +1,92 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/protobuf/api" + "github.com/mesg-foundation/engine/x/service/internal/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// NewQuerier creates a new querier for service clients. +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { + switch path[0] { + case types.QueryGetService: + return getService(ctx, k, path[1:]) + case types.QueryListService: + return listService(ctx, k) + case types.QueryHashService: + return hashService(ctx, k, req) + case types.QueryExistService: + return existService(ctx, k, path[1:]) + default: + return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown service query endpoint") + } + } +} + +func getService(ctx sdk.Context, k Keeper, path []string) ([]byte, error) { + hash, err := hash.Decode(path[0]) + if err != nil { + return nil, err + } + + servcie, err := k.Get(ctx, hash) + if err != nil { + return nil, err + } + + res, err := types.ModuleCdc.MarshalJSON(servcie) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return res, nil +} + +func hashService(ctx sdk.Context, k Keeper, req abci.RequestQuery) ([]byte, error) { + var createServiceRequest api.CreateServiceRequest + if err := k.cdc.UnmarshalJSON(req.Data, &createServiceRequest); err != nil { + return nil, err + } + + hash := k.Hash(ctx, &createServiceRequest) + + res, err := types.ModuleCdc.MarshalJSON(hash) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return res, nil +} + +func listService(ctx sdk.Context, k Keeper) ([]byte, error) { + servcies, err := k.List(ctx) + if err != nil { + return nil, err + } + + res, err := types.ModuleCdc.MarshalJSON(servcies) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return res, nil +} + +func existService(ctx sdk.Context, k Keeper, path []string) ([]byte, error) { + hash, err := hash.Decode(path[0]) + if err != nil { + return nil, err + } + + servcie, err := k.Exists(ctx, hash) + if err != nil { + return nil, err + } + + res, err := types.ModuleCdc.MarshalJSON(servcie) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return res, nil +} diff --git a/x/service/internal/types/codec.go b/x/service/internal/types/codec.go new file mode 100644 index 000000000..f660358cb --- /dev/null +++ b/x/service/internal/types/codec.go @@ -0,0 +1,20 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// RegisterCodec registers concrete types on codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgCreateService{}, "service/CreateService", nil) +} + +// ModuleCdc defines the module codec +var ModuleCdc *codec.Codec + +func init() { + ModuleCdc = codec.New() + RegisterCodec(ModuleCdc) + codec.RegisterCrypto(ModuleCdc) + ModuleCdc.Seal() +} diff --git a/x/service/internal/types/events.go b/x/service/internal/types/events.go new file mode 100644 index 000000000..49046f01c --- /dev/null +++ b/x/service/internal/types/events.go @@ -0,0 +1,10 @@ +package types + +// service module event types +const ( + EventTypeCreateService = "CreateService" + + AttributeHash = "hash" + + AttributeValueCategory = ModuleName +) diff --git a/x/service/internal/types/expected_keepers.go b/x/service/internal/types/expected_keepers.go new file mode 100644 index 000000000..d3e4e4729 --- /dev/null +++ b/x/service/internal/types/expected_keepers.go @@ -0,0 +1,21 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/mesg-foundation/engine/hash" + ownershippb "github.com/mesg-foundation/engine/ownership" +) + +// ParamSubspace defines the expected Subspace interfacace +type ParamSubspace interface { + WithKeyTable(table params.KeyTable) params.Subspace + Get(ctx sdk.Context, key []byte, ptr interface{}) + GetParamSet(ctx sdk.Context, ps params.ParamSet) + SetParamSet(ctx sdk.Context, ps params.ParamSet) +} + +// OwnershipKeeper module interface. +type OwnershipKeeper interface { + Set(ctx sdk.Context, owner sdk.AccAddress, resourceHash hash.Hash, resource ownershippb.Ownership_Resource) (*ownershippb.Ownership, error) +} diff --git a/x/service/internal/types/genesis.go b/x/service/internal/types/genesis.go new file mode 100644 index 000000000..bebeec0dc --- /dev/null +++ b/x/service/internal/types/genesis.go @@ -0,0 +1,19 @@ +package types + +// GenesisState - all instance state that must be provided at genesis +type GenesisState struct{} + +// NewGenesisState creates a new GenesisState object +func NewGenesisState() GenesisState { + return GenesisState{} +} + +// DefaultGenesisState - default GenesisState used by Cosmos Hub +func DefaultGenesisState() GenesisState { + return GenesisState{} +} + +// ValidateGenesis validates the instance genesis parameters +func ValidateGenesis(data GenesisState) error { + return nil +} diff --git a/x/service/internal/types/key.go b/x/service/internal/types/key.go new file mode 100644 index 000000000..40bfea37f --- /dev/null +++ b/x/service/internal/types/key.go @@ -0,0 +1,15 @@ +package types + +const ( + // ModuleName is the name of the module + ModuleName = "service" + + // StoreKey to be used when creating the KVStore + StoreKey = ModuleName + + // RouterKey to be used for routing msgs + RouterKey = ModuleName + + // QuerierRoute to be used for routing + QuerierRoute = ModuleName +) diff --git a/x/service/internal/types/msg.go b/x/service/internal/types/msg.go new file mode 100644 index 000000000..4e89520d2 --- /dev/null +++ b/x/service/internal/types/msg.go @@ -0,0 +1,53 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/mesg-foundation/engine/ext/xvalidator" + "github.com/mesg-foundation/engine/protobuf/api" +) + +// MsgCreateService defines a state transition to create a service. +type MsgCreateService struct { + Owner sdk.AccAddress `json:"owner" validate:"required,accaddress"` + Request *api.CreateServiceRequest `json:"request" validate:"required"` +} + +// NewMsgCreateService is a constructor function for MsgCreateService. +func NewMsgCreateService(owner sdk.AccAddress, request *api.CreateServiceRequest) *MsgCreateService { + return &MsgCreateService{ + Owner: owner, + Request: request, + } +} + +// Route should return the name of the module route. +func (msg MsgCreateService) Route() string { + return RouterKey +} + +// Type returns the action. +func (msg MsgCreateService) Type() string { + return "CreateService" +} + +// ValidateBasic runs stateless checks on the message. +func (msg MsgCreateService) ValidateBasic() error { + if msg.Owner.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "owner is missing") + } + if err := xvalidator.Validate.Struct(msg); err != nil { + return err + } + return nil +} + +// GetSignBytes encodes the message for signing. +func (msg MsgCreateService) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners defines whose signature is required. +func (msg MsgCreateService) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Owner} +} diff --git a/x/service/internal/types/params.go b/x/service/internal/types/params.go new file mode 100644 index 000000000..4940992b5 --- /dev/null +++ b/x/service/internal/types/params.go @@ -0,0 +1,38 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/x/params" +) + +// Default parameter namespace +const ( + DefaultParamspace = ModuleName +) + +// ParamKeyTable for instance module +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable().RegisterParamSet(&Params{}) +} + +// Params - used for initializing default parameter for instance at genesis +type Params struct{} + +// NewParams creates a new Params object +func NewParams() Params { + return Params{} +} + +// String implements the stringer interface for Params +func (p Params) String() string { + return "" +} + +// ParamSetPairs - Implements params.ParamSet +func (p *Params) ParamSetPairs() params.ParamSetPairs { + return params.ParamSetPairs{} +} + +// DefaultParams defines the parameters for this module +func DefaultParams() Params { + return NewParams() +} diff --git a/x/service/internal/types/querier.go b/x/service/internal/types/querier.go new file mode 100644 index 000000000..474039158 --- /dev/null +++ b/x/service/internal/types/querier.go @@ -0,0 +1,9 @@ +package types + +// Query endpoints supported by the service querier +const ( + QueryGetService = "get" + QueryListService = "list" + QueryHashService = "hash" + QueryExistService = "exist" +) diff --git a/x/service/module.go b/x/service/module.go new file mode 100644 index 000000000..0504f2978 --- /dev/null +++ b/x/service/module.go @@ -0,0 +1,141 @@ +package service + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/gorilla/mux" + "github.com/mesg-foundation/engine/x/service/client/cli" + "github.com/mesg-foundation/engine/x/service/client/rest" + "github.com/mesg-foundation/engine/x/service/internal/types" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic defines the basic application module used by the service module. +type AppModuleBasic struct{} + +var _ module.AppModuleBasic = AppModuleBasic{} + +// Name returns the service module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterCodec registers the service module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the service +// module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the service module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &data) + if err != nil { + return err + } + return ValidateGenesis(data) +} + +// RegisterRESTRoutes registers the REST routes for the service module. +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + rest.RegisterRoutes(ctx, rtr) +} + +// GetTxCmd returns the root tx command for the service module. +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(cdc) +} + +// GetQueryCmd returns no root query command for the service module. +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetQueryCmd(StoreKey, cdc) +} + +//____________________________________________________________________________ + +// AppModule implements an application module for the service module. +type AppModule struct { + AppModuleBasic + + keeper Keeper + // TODO: Add keepers that your application depends on +} + +// NewAppModule creates a new AppModule object +func NewAppModule(k Keeper /*TODO: Add Keepers that your application depends on*/) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: k, + // TODO: Add keepers that your application depends on + } +} + +// Name returns the service module's name. +func (AppModule) Name() string { + return ModuleName +} + +// RegisterInvariants registers the service module invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route returns the message routing key for the service module. +func (AppModule) Route() string { + return RouterKey +} + +// NewHandler returns an sdk.Handler for the service module. +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} + +// QuerierRoute returns the service module's querier route name. +func (AppModule) QuerierRoute() string { + return QuerierRoute +} + +// NewQuerierHandler returns the service module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} + +// InitGenesis performs genesis initialization for the service module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the service +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock returns the begin blocker for the service module. +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, req, am.keeper) +} + +// EndBlock returns the end blocker for the service module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +}