Repository: ovrclk/akash
Pull request #1085 (2021-02-12 18:32:00)

refactor(certs): return serial number when querying certificates
check certificate file exists before creating deployment

Signed-off-by: Artur Troian <troian.ap@gmail.com>


codecov[bot]: # [Codecov](https://codecov.io/gh/ovrclk/akash/pull/1085?src=pr&el=h1) Report
> Merging [#1085](https://codecov.io/gh/ovrclk/akash/pull/1085?src=pr&el=desc) (0ec0eeb) into [master](https://codecov.io/gh/ovrclk/akash/commit/811153cfc27a864e9dacc2ed0a63adcc01604acd?el=desc) (811153c) will **decrease** coverage by `1.31%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##           master    #1085      +/-   ##
==========================================
- Coverage   78.51%   77.20%   -1.32%     
==========================================
  Files           6        6              
  Lines         526      500      -26     
==========================================
- Hits          413      386      -27     
- Misses        109      110       +1     
  Partials        4        4              
```



Patch:
diff --git a/proto/akash/cert/v1beta1/query.proto b/proto/akash/cert/v1beta1/query.proto
index 11b71478..03ccb290 100644
--- a/proto/akash/cert/v1beta1/query.proto
+++ b/proto/akash/cert/v1beta1/query.proto
@@ -16,6 +16,19 @@ service Query {
   }
 }
 
+message CertificateResponse {
+  Certificate certificate = 1 [
+    (gogoproto.nullable) = false,
+    (gogoproto.jsontag) = "certificate",
+    (gogoproto.moretags) = "yaml:\"certificate\""
+  ];
+
+  string serial = 2 [
+    (gogoproto.jsontag) = "serial",
+    (gogoproto.moretags) = "yaml:\"serial\""
+  ];
+}
+
 // QueryDeploymentsRequest is request type for the Query/Deployments RPC method
 message QueryCertificatesRequest {
   CertificateFilter filter = 1 [
@@ -27,9 +40,9 @@ message QueryCertificatesRequest {
 
 // QueryCertificatesResponse is response type for the Query/Certificates RPC method
 message QueryCertificatesResponse {
-  repeated Certificate certificates = 1 [
+  repeated CertificateResponse certificates = 1 [
     (gogoproto.nullable) = false,
-    (gogoproto.castrepeated) = "Certificates"
+    (gogoproto.castrepeated) = "CertificatesResponse"
   ];
 
   cosmos.base.query.v1beta1.PageResponse pagination = 2;
diff --git a/provider/gateway/rest/client.go b/provider/gateway/rest/client.go
index 0e4aaa90..0d10a5fd 100644
--- a/provider/gateway/rest/client.go
+++ b/provider/gateway/rest/client.go
@@ -7,7 +7,6 @@ import (
 	"crypto/x509"
 	"encoding/json"
 	"fmt"
-	cutils "github.com/ovrclk/akash/x/cert/utils"
 	"io"
 	"net/http"
 	"net/url"
@@ -16,11 +15,14 @@ import (
 	"sync"
 	"time"
 
+	cutils "github.com/ovrclk/akash/x/cert/utils"
+
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/gorilla/websocket"
 	"github.com/pkg/errors"
 
 	cosmosclient "github.com/cosmos/cosmos-sdk/client"
+
 	akashclient "github.com/ovrclk/akash/client"
 	"github.com/ovrclk/akash/manifest"
 	"github.com/ovrclk/akash/provider"
@@ -209,7 +211,7 @@ func (c *client) verifyPeerCertificate(certificates [][]byte, _ [][]*x509.Certif
 	if err != nil {
 		return errors.Wrap(err, "tls: unable to fetch certificate from chain")
 	}
-	if (len(resp.Certificates) != 1) || (resp.Certificates[0].State != ctypes.CertificateValid) {
+	if (len(resp.Certificates) != 1) || !resp.Certificates[0].Certificate.IsState(ctypes.CertificateValid) {
 		return errors.New("tls: attempt to use non-existing or revoked certificate")
 	}
 
diff --git a/provider/gateway/utils/utils.go b/provider/gateway/utils/utils.go
index b1496083..b7cf2b02 100644
--- a/provider/gateway/utils/utils.go
+++ b/provider/gateway/utils/utils.go
@@ -62,7 +62,7 @@ func NewServerTLSConfig(ctx context.Context, certs []tls.Certificate, cquery cty
 				if err != nil {
 					return errors.Wrap(err, "tls: unable to fetch certificate from chain")
 				}
-				if (len(resp.Certificates) != 1) || (resp.Certificates[0].State != ctypes.CertificateValid) {
+				if (len(resp.Certificates) != 1) || !resp.Certificates[0].Certificate.IsState(ctypes.CertificateValid) {
 					return errors.New("tls: attempt to use non-existing or revoked certificate")
 				}
 
diff --git a/x/cert/client/cli/tx.go b/x/cert/client/cli/tx.go
index 79e2664b..c6c4dc9b 100644
--- a/x/cert/client/cli/tx.go
+++ b/x/cert/client/cli/tx.go
@@ -48,6 +48,7 @@ func GetTxCmd() *cobra.Command {
 		Use:                        types.ModuleName,
 		Short:                      "Certificates transaction subcommands",
 		SuggestionsMinimumDistance: 2,
+		DisableFlagParsing:         true,
 		RunE:                       sdkclient.ValidateCmd,
 	}
 
@@ -61,8 +62,11 @@ func GetTxCmd() *cobra.Command {
 
 func cmdCreate() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "create",
-		Short: "create/update api certificates",
+		Use:                        "create",
+		Short:                      "create/update api certificates",
+		SuggestionsMinimumDistance: 2,
+		DisableFlagParsing:         true,
+		RunE:                       sdkclient.ValidateCmd,
 	}
 
 	cmd.AddCommand(
@@ -260,7 +264,7 @@ func doCreateCmd(cmd *cobra.Command, domains []string) error {
 				}
 			}
 		} else {
-			if res.Certificates[0].State == types.CertificateValid {
+			if res.Certificates[0].Certificate.IsState(types.CertificateValid) {
 				err = doRevoke(cctx, cmd.Flags(), x509cert.SerialNumber.String())
 				if err == nil {
 					removeFile = true
diff --git a/x/cert/client/rest/rest.go b/x/cert/client/rest/rest.go
deleted file mode 100644
index fd2e0480..00000000
--- a/x/cert/client/rest/rest.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package rest
-
-import (
-	"fmt"
-	"math/big"
-	"net/http"
-
-	"github.com/cosmos/cosmos-sdk/client"
-	sdk "github.com/cosmos/cosmos-sdk/types"
-	"github.com/cosmos/cosmos-sdk/types/rest"
-	"github.com/gorilla/mux"
-
-	"github.com/ovrclk/akash/x/cert/query"
-	"github.com/ovrclk/akash/x/cert/types"
-)
-
-func RegisterRoutes(ctx client.Context, r *mux.Router, ns string) {
-	prefix := fmt.Sprintf("/%s/certificates", ns)
-
-	r.HandleFunc(fmt.Sprintf("/%s/list", prefix), listAll(ctx, ns)).Methods("GET")
-	r.HandleFunc(fmt.Sprintf("/%s/state/{state}/list", prefix), listAllState(ctx, ns)).Methods("GET")
-	r.HandleFunc(fmt.Sprintf("/%s/owner/{owner}/list", prefix), listOwner(ctx, ns)).Methods("GET")
-	r.HandleFunc(fmt.Sprintf("/%s/owner/{owner}/state/{state}/list", prefix), listOwnerState(ctx, ns)).Methods("GET")
-	r.HandleFunc(fmt.Sprintf("/%s/owner/{owner}/{serial}", prefix), getOwnerSerial(ctx, ns)).Methods("GET")
-}
-
-func listAll(ctx client.Context, ns string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		res, err := query.NewRawClient(ctx, ns).Certificates()
-		if err != nil {
-			rest.WriteErrorResponse(w, http.StatusNotFound, "Not Found")
-			return
-		}
-		rest.PostProcessResponse(w, ctx, res)
-	}
-}
-
-func listAllState(ctx client.Context, ns string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		res, err := query.NewRawClient(ctx, ns).CertificatesState(mux.Vars(r)["state"])
-		if err != nil {
-			rest.WriteErrorResponse(w, http.StatusNotFound, "Not Found")
-			return
-		}
-		rest.PostProcessResponse(w, ctx, res)
-	}
-}
-
-func listOwner(ctx client.Context, ns string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		owner, err := sdk.AccAddressFromBech32(mux.Vars(r)["owner"])
-		if err != nil {
-			rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid address")
-			return
-		}
-
-		var res []byte
-		if res, err = query.NewRawClient(ctx, ns).Owner(owner); err != nil {
-			rest.WriteErrorResponse(w, http.StatusNotFound, "Not Found")
-			return
-		}
-
-		rest.PostProcessResponse(w, ctx, res)
-	}
-}
-
-func listOwnerState(ctx client.Context, ns string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		owner, err := sdk.AccAddressFromBech32(mux.Vars(r)["owner"])
-		if err != nil {
-			rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid address")
-			return
-		}
-
-		var res []byte
-		if res, err = query.NewRawClient(ctx, ns).OwnerState(owner, mux.Vars(r)["state"]); err != nil {
-			rest.WriteErrorResponse(w, http.StatusNotFound, "Not Found")
-			return
-		}
-
-		rest.PostProcessResponse(w, ctx, res)
-	}
-}
-
-func getOwnerSerial(ctx client.Context, ns string) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		owner, err := sdk.AccAddressFromBech32(mux.Vars(r)["owner"])
-		if err != nil {
-			rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid address")
-			return
-		}
-
-		serial, valid := new(big.Int).SetString(mux.Vars(r)["serial"], 10)
-		if !valid {
-			rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid serial number")
-			return
-		}
-
-		var res []byte
-
-		res, err = query.NewRawClient(ctx, ns).Certificate(types.CertID{
-			Owner:  owner,
-			Serial: *serial,
-		})
-
-		if err != nil {
-			rest.WriteErrorResponse(w, http.StatusNotFound, "Not Found")
-			return
-		}
-
-		rest.PostProcessResponse(w, ctx, res)
-	}
-}
diff --git a/x/cert/keeper/grpc_query.go b/x/cert/keeper/grpc_query.go
index 07961d51..68258bf5 100644
--- a/x/cert/keeper/grpc_query.go
+++ b/x/cert/keeper/grpc_query.go
@@ -14,18 +14,18 @@ import (
 )
 
 // Querier is used as Keeper will have duplicate methods if used directly, and gRPC names take precedence over keeper
-type Querier struct {
-	Keeper
+type querier struct {
+	keeper
 }
 
-var _ types.QueryServer = Querier{}
+var _ types.QueryServer = &querier{}
 
-func (q Querier) Certificates(c context.Context, req *types.QueryCertificatesRequest) (*types.QueryCertificatesResponse, error) {
+func (q querier) Certificates(c context.Context, req *types.QueryCertificatesRequest) (*types.QueryCertificatesResponse, error) {
 	if req == nil {
 		return nil, status.Error(codes.InvalidArgument, "empty request")
 	}
 
-	var certificates types.Certificates
+	var certificates types.CertificatesResponse
 	var pageRes *sdkquery.PageResponse
 	var err error
 
@@ -55,13 +55,13 @@ func (q Querier) Certificates(c context.Context, req *types.QueryCertificatesReq
 				return nil, status.Error(codes.InvalidArgument, "invalid serial number")
 			}
 
-			cert, exists := q.GetCertificateByID(ctx, types.CertID{
+			item, exists := q.GetCertificateByID(ctx, types.CertID{
 				Owner:  owner,
 				Serial: *serial,
 			})
 
-			if exists && filterCertByState(state, cert.State) {
-				certificates = append(certificates, cert)
+			if exists && filterCertByState(state, item.Certificate.State) {
+				certificates = append(certificates, item)
 			}
 		} else {
 			page, limit, err := sdkquery.ParsePagination(req.Pagination)
@@ -71,25 +71,24 @@ func (q Querier) Certificates(c context.Context, req *types.QueryCertificatesReq
 
 			it := sdkstore.KVStorePrefixIteratorPaginated(store, certificatePrefix(owner), uint(page), uint(limit))
 			for ; it.Valid(); it.Next() {
-				var cert types.Certificate
-				if e := q.cdc.UnmarshalBinaryBare(it.Value(), &cert); e != nil {
+				item, err := q.unmarshalIterator(it.Key(), it.Value())
+				if err != nil {
 					return nil, err
 				}
-
-				if filterCertByState(state, cert.State) {
-					certificates = append(certificates, cert)
+				if filterCertByState(state, item.Certificate.State) {
+					certificates = append(certificates, item)
 				}
 			}
 		}
 	} else {
 		pageRes, err = sdkquery.Paginate(store, req.Pagination, func(key []byte, value []byte) error {
-			var cert types.Certificate
-			if e := q.cdc.UnmarshalBinaryBare(value, &cert); e != nil {
+			item, err := q.unmarshalIterator(key, value)
+			if err != nil {
 				return err
 			}
 
-			if filterCertByState(state, cert.State) {
-				certificates = append(certificates, cert)
+			if filterCertByState(state, item.Certificate.State) {
+				certificates = append(certificates, item)
 			}
 			return nil
 		})
diff --git a/x/cert/keeper/keeper.go b/x/cert/keeper/keeper.go
index 28fc7191..79472f4d 100644
--- a/x/cert/keeper/keeper.go
+++ b/x/cert/keeper/keeper.go
@@ -8,22 +8,41 @@ import (
 )
 
 // Keeper of the provider store
-type Keeper struct {
+type Keeper interface {
+	Querier() types.QueryServer
+	Codec() codec.BinaryMarshaler
+	CreateCertificate(sdk.Context, sdk.Address, []byte, []byte) error
+	RevokeCertificate(sdk.Context, types.CertID) error
+	GetCertificateByID(ctx sdk.Context, id types.CertID) (types.CertificateResponse, bool)
+	WithCertificates(ctx sdk.Context, fn func(certificate types.CertificateResponse) bool)
+	WithCertificatesState(ctx sdk.Context, state types.Certificate_State, fn func(certificate types.CertificateResponse) bool)
+	WithOwner(ctx sdk.Context, id sdk.Address, fn func(types.CertificateResponse) bool)
+	WithOwnerState(ctx sdk.Context, id sdk.Address, state types.Certificate_State, fn func(types.CertificateResponse) bool)
+}
+
+type keeper struct {
 	skey sdk.StoreKey
 	cdc  codec.BinaryMarshaler
 }
 
+var _ Keeper = (*keeper)(nil)
+
 // NewKeeper creates and returns an instance for Market keeper
 func NewKeeper(cdc codec.BinaryMarshaler, skey sdk.StoreKey) Keeper {
-	return Keeper{cdc: cdc, skey: skey}
+	return &keeper{cdc: cdc, skey: skey}
+}
+
+// Querier return gRPC query handler
+func (k keeper) Querier() types.QueryServer {
+	return &querier{keeper: k}
 }
 
 // Codec returns keeper codec
-func (k Keeper) Codec() codec.BinaryMarshaler {
+func (k keeper) Codec() codec.BinaryMarshaler {
 	return k.cdc
 }
 
-func (k Keeper) CreateCertificate(ctx sdk.Context, owner sdk.Address, crt []byte, pubkey []byte) error {
+func (k keeper) CreateCertificate(ctx sdk.Context, owner sdk.Address, crt []byte, pubkey []byte) error {
 	store := ctx.KVStore(k.skey)
 
 	cert, err := types.ParseAndValidateCertificate(owner, crt, pubkey)
@@ -56,7 +75,7 @@ func (k Keeper) CreateCertificate(ctx sdk.Context, owner sdk.Address, crt []byte
 	return nil
 }
 
-func (k Keeper) RevokeCertificate(ctx sdk.Context, id types.CertID) error {
+func (k keeper) RevokeCertificate(ctx sdk.Context, id types.CertID) error {
 	store := ctx.KVStore(k.skey)
 	key := certificateKey(id)
 
@@ -80,22 +99,25 @@ func (k Keeper) RevokeCertificate(ctx sdk.Context, id types.CertID) error {
 }
 
 // GetCertificateByID returns a provider with given auditor and owner id
-func (k Keeper) GetCertificateByID(ctx sdk.Context, id types.CertID) (types.Certificate, bool) {
+func (k keeper) GetCertificateByID(ctx sdk.Context, id types.CertID) (types.CertificateResponse, bool) {
 	store := ctx.KVStore(k.skey)
 
 	buf := store.Get(certificateKey(id))
 	if buf == nil {
-		return types.Certificate{}, false
+		return types.CertificateResponse{}, false
 	}
 
 	var val types.Certificate
 	k.cdc.MustUnmarshalBinaryBare(buf, &val)
 
-	return val, true
+	return types.CertificateResponse{
+		Certificate: val,
+		Serial:      id.Serial.String(),
+	}, true
 }
 
 // WithCertificates iterates all certificates
-func (k Keeper) WithCertificates(ctx sdk.Context, fn func(certificate types.Certificate) bool) {
+func (k keeper) WithCertificates(ctx sdk.Context, fn func(certificate types.CertificateResponse) bool) {
 	store := ctx.KVStore(k.skey)
 	iter := store.Iterator(nil, nil)
 
@@ -104,16 +126,15 @@ func (k Keeper) WithCertificates(ctx sdk.Context, fn func(certificate types.Cert
 	}()
 
 	for ; iter.Valid(); iter.Next() {
-		var val types.Certificate
-		k.cdc.MustUnmarshalBinaryBare(iter.Value(), &val)
-		if stop := fn(val); stop {
+		item := k.mustUnmarshal(iter.Key(), iter.Value())
+		if stop := fn(item); stop {
 			break
 		}
 	}
 }
 
-// WithCertificates iterates all certificates
-func (k Keeper) WithCertificatesState(ctx sdk.Context, state types.Certificate_State, fn func(certificate types.Certificate) bool) {
+// WithCertificatesState iterates all certificates in certain state
+func (k keeper) WithCertificatesState(ctx sdk.Context, state types.Certificate_State, fn func(certificate types.CertificateResponse) bool) {
 	store := ctx.KVStore(k.skey)
 	iter := store.Iterator(nil, nil)
 
@@ -122,10 +143,9 @@ func (k Keeper) WithCertificatesState(ctx sdk.Context, state types.Certificate_S
 	}()
 
 	for ; iter.Valid(); iter.Next() {
-		var val types.Certificate
-		k.cdc.MustUnmarshalBinaryBare(iter.Value(), &val)
-		if val.State == state {
-			if stop := fn(val); stop {
+		item := k.mustUnmarshal(iter.Key(), iter.Value())
+		if item.Certificate.State == state {
+			if stop := fn(item); stop {
 				break
 			}
 		}
@@ -133,7 +153,7 @@ func (k Keeper) WithCertificatesState(ctx sdk.Context, state types.Certificate_S
 }
 
 // WithOwner iterates all certificates by owner
-func (k Keeper) WithOwner(ctx sdk.Context, id sdk.Address, fn func(types.Certificate) bool) {
+func (k keeper) WithOwner(ctx sdk.Context, id sdk.Address, fn func(types.CertificateResponse) bool) {
 	store := ctx.KVStore(k.skey)
 	iter := sdk.KVStorePrefixIterator(store, certificatePrefix(id))
 	defer func() {
@@ -141,16 +161,15 @@ func (k Keeper) WithOwner(ctx sdk.Context, id sdk.Address, fn func(types.Certifi
 	}()
 
 	for ; iter.Valid(); iter.Next() {
-		var val types.Certificate
-		k.cdc.MustUnmarshalBinaryBare(iter.Value(), &val)
-		if stop := fn(val); stop {
+		item := k.mustUnmarshal(iter.Key(), iter.Value())
+		if stop := fn(item); stop {
 			break
 		}
 	}
 }
 
-// WithOwner iterates all certificates by owner
-func (k Keeper) WithOwnerState(ctx sdk.Context, id sdk.Address, state types.Certificate_State, fn func(types.Certificate) bool) {
+// WithOwnerState iterates all certificates by owner in certain state
+func (k keeper) WithOwnerState(ctx sdk.Context, id sdk.Address, state types.Certificate_State, fn func(types.CertificateResponse) bool) {
 	store := ctx.KVStore(k.skey)
 	iter := sdk.KVStorePrefixIterator(store, certificatePrefix(id))
 	defer func() {
@@ -158,12 +177,34 @@ func (k Keeper) WithOwnerState(ctx sdk.Context, id sdk.Address, state types.Cert
 	}()
 
 	for ; iter.Valid(); iter.Next() {
-		var val types.Certificate
-		k.cdc.MustUnmarshalBinaryBare(iter.Value(), &val)
-		if val.State == state {
-			if stop := fn(val); stop {
+		item := k.mustUnmarshal(iter.Key(), iter.Value())
+		if item.Certificate.State == state {
+			if stop := fn(item); stop {
 				break
 			}
 		}
 	}
 }
+
+func (k keeper) mustUnmarshal(key, val []byte) types.CertificateResponse {
+	serial := certificateSerialFromKey(key)
+	item := types.CertificateResponse{
+		Serial: serial.String(),
+	}
+	k.cdc.MustUnmarshalBinaryBare(val, &item.Certificate)
+
+	return item
+}
+
+func (k keeper) unmarshalIterator(key, val []byte) (types.CertificateResponse, error) {
+	serial := certificateSerialFromKey(key)
+	item := types.CertificateResponse{
+		Serial: serial.String(),
+	}
+
+	if err := k.cdc.UnmarshalBinaryBare(val, &item.Certificate); err != nil {
+		return types.CertificateResponse{}, err
+	}
+
+	return item, nil
+}
diff --git a/x/cert/keeper/key.go b/x/cert/keeper/key.go
index 06cfa4e9..6d80f01c 100644
--- a/x/cert/keeper/key.go
+++ b/x/cert/keeper/key.go
@@ -2,12 +2,17 @@ package keeper
 
 import (
 	"bytes"
+	"math/big"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	"github.com/ovrclk/akash/x/cert/types"
 )
 
+const (
+	keyAddrPrefixLen = 1 + sdk.AddrLen
+)
+
 var (
 	prefixCertificateID = []byte{0x01}
 )
@@ -33,3 +38,11 @@ func certificatePrefix(id sdk.Address) []byte {
 
 	return buf.Bytes()
 }
+
+func certificateSerialFromKey(key []byte) big.Int {
+	if len(key) < keyAddrPrefixLen+1 {
+		panic("invalid key size")
+	}
+
+	return *new(big.Int).SetBytes(key[keyAddrPrefixLen:])
+}
diff --git a/x/cert/module.go b/x/cert/module.go
index b618c5a5..5ceae67b 100644
--- a/x/cert/module.go
+++ b/x/cert/module.go
@@ -21,9 +21,9 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/ovrclk/akash/x/cert/client/cli"
-	"github.com/ovrclk/akash/x/cert/client/rest"
 	"github.com/ovrclk/akash/x/cert/handler"
 	"github.com/ovrclk/akash/x/cert/keeper"
+	"github.com/ovrclk/akash/x/cert/simulation"
 	"github.com/ovrclk/akash/x/cert/types"
 )
 
@@ -76,8 +76,8 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, _ client.TxEncodi
 }
 
 // RegisterRESTRoutes registers rest routes for this module
-func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
-	rest.RegisterRoutes(clientCtx, rtr, StoreKey)
+func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {
+
 }
 
 // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the provider module.
@@ -123,7 +123,7 @@ func (AppModule) Name() string {
 }
 
 // RegisterInvariants registers module invariants
-func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {}
+func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
 
 // Route returns the message routing key for the audit module.
 func (am AppModule) Route() sdk.Route {
@@ -140,11 +140,10 @@ func (am AppModule) LegacyQuerierHandler(_ *codec.LegacyAmino) sdk.Querier {
 	return nil
 }
 
-// RegisterServices registers the module's servicess
+// RegisterServices registers the module's services
 func (am AppModule) RegisterServices(cfg module.Configurator) {
 	types.RegisterMsgServer(cfg.MsgServer(), handler.NewMsgServerImpl(am.keeper))
-	querier := keeper.Querier{Keeper: am.keeper}
-	types.RegisterQueryServer(cfg.QueryServer(), querier)
+	types.RegisterQueryServer(cfg.QueryServer(), am.keeper.Querier())
 }
 
 // BeginBlock performs no-op
@@ -152,7 +151,7 @@ func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
 
 // EndBlock returns the end blocker for the audit module. It returns no auditor
 // updates.
-func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
+func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
 	return []abci.ValidatorUpdate{}
 }
 
@@ -188,7 +187,7 @@ func NewAppModuleSimulation(k keeper.Keeper) AppModuleSimulation {
 // AppModuleSimulation functions
 // GenerateGenesisState creates a randomized GenState of the staking module.
 func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) {
-	// simulation.RandomizedGenState(simState)
+	simulation.RandomizedGenState(simState)
 }
 
 // ProposalContents doesn't return any content functions for governance proposals.
@@ -197,16 +196,16 @@ func (AppModuleSimulation) ProposalContents(_ module.SimulationState) []sim.Weig
 }
 
 // RandomizedParams creates randomized staking param changes for the simulator.
-func (AppModuleSimulation) RandomizedParams(r *rand.Rand) []sim.ParamChange {
+func (AppModuleSimulation) RandomizedParams(_ *rand.Rand) []sim.ParamChange {
 	return nil
 }
 
 // RegisterStoreDecoder registers a decoder for staking module's types
-func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
+func (AppModuleSimulation) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {
 
 }
 
 // WeightedOperations returns the all the staking module operations with their respective weights.
-func (am AppModuleSimulation) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation {
+func (am AppModuleSimulation) WeightedOperations(_ module.SimulationState) []sim.WeightedOperation {
 	return nil
 }
diff --git a/x/cert/query/client.go b/x/cert/query/client.go
deleted file mode 100644
index 7e52af6e..00000000
--- a/x/cert/query/client.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package query
-
-// Client interface
-type Client interface {
-}
diff --git a/x/cert/query/rawclient.go b/x/cert/query/rawclient.go
deleted file mode 100644
index 0bf89633..00000000
--- a/x/cert/query/rawclient.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package query
-
-import (
-	"fmt"
-
-	sdkclient "github.com/cosmos/cosmos-sdk/client"
-	sdk "github.com/cosmos/cosmos-sdk/types"
-
-	"github.com/ovrclk/akash/x/cert/types"
-)
-
-// RawClient interface
-type RawClient interface {
-	Certificates() ([]byte, error)
-	CertificatesState(string) ([]byte, error)
-	Owner(sdk.Address) ([]byte, error)
-	OwnerState(sdk.Address, string) ([]byte, error)
-	Certificate(types.CertID) ([]byte, error)
-}
-
-type rawclient struct {
-	ctx sdkclient.Context
-	key string
-}
-
-// NewRawClient creates a client instance with provided context and key
-func NewRawClient(ctx sdkclient.Context, key string) RawClient {
-	return &rawclient{ctx: ctx, key: key}
-}
-
-func (r rawclient) Certificates() ([]byte, error) {
-	buf, _, err := r.ctx.QueryWithData(fmt.Sprintf("custom/%s/certificates/list", r.key), nil)
-	if err != nil {
-		return []byte{}, err
-	}
-
-	return buf, err
-}
-
-func (r rawclient) CertificatesState(state string) ([]byte, error) {
-	buf, _, err := r.ctx.QueryWithData(fmt.Sprintf("custom/%s/certificates/state/{%s}/list", r.key, state), nil)
-	if err != nil {
-		return []byte{}, err
-	}
-
-	return buf, err
-}
-
-func (r rawclient) Owner(address sdk.Address) ([]byte, error) {
-	buf, _, err := r.ctx.QueryWithData(fmt.Sprintf("custom/%s/certificates/owner/%s/list", r.key, address.String()), nil)
-	if err != nil {
-		return []byte{}, err
-	}
-	return buf, err
-}
-
-func (r rawclient) OwnerState(address sdk.Address, state string) ([]byte, error) {
-	buf, _, err := r.ctx.QueryWithData(fmt.Sprintf("custom/%s/certificates/owner/%s/state/%s/list", r.key, address.String(), state), nil)
-	if err != nil {
-		return []byte{}, err
-	}
-	return buf, err
-}
-
-func (r rawclient) Certificate(id types.CertID) ([]byte, error) {
-	buf, _, err := r.ctx.QueryWithData(fmt.Sprintf("custom/%s/certificates/owner/%s/%s", r.key, id.Owner.String(), id.Serial.String()), nil)
-	if err != nil {
-		return []byte{}, err
-	}
-	return buf, err
-}
diff --git a/x/cert/simulation/genesis.go b/x/cert/simulation/genesis.go
new file mode 100644
index 00000000..3cb66f54
--- /dev/null
+++ b/x/cert/simulation/genesis.go
@@ -0,0 +1,13 @@
+package simulation
+
+import (
+	"github.com/cosmos/cosmos-sdk/types/module"
+
+	"github.com/ovrclk/akash/x/cert/types"
+)
+
+func RandomizedGenState(simState *module.SimulationState) {
+	deploymentGenesis := &types.GenesisState{}
+
+	simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(deploymentGenesis)
+}
diff --git a/x/cert/types/cert.go b/x/cert/types/cert.go
index cd4a38d1..bb918632 100644
--- a/x/cert/types/cert.go
+++ b/x/cert/types/cert.go
@@ -65,3 +65,7 @@ func (m Certificate) Validate(owner sdk.Address) error {
 
 	return nil
 }
+
+func (m Certificate) IsState(state Certificate_State) bool {
+	return m.State == state
+}
diff --git a/x/cert/types/errors.go b/x/cert/types/errors.go
index 1183aab7..d642fc8f 100644
--- a/x/cert/types/errors.go
+++ b/x/cert/types/errors.go
@@ -13,6 +13,7 @@ const (
 	errInvalidCertificateValue
 	errInvalidPubkeyValue
 	errInvalidState
+	errInvalidKeySize
 )
 
 var (
@@ -39,4 +40,7 @@ var (
 
 	// ErrInvalidState invalid certificate state
 	ErrInvalidState = sdkerrors.Register(ModuleName, errInvalidState, "invalid state")
+
+	// ErrInvalidState invalid certificate state
+	ErrInvalidKeySize = sdkerrors.Register(ModuleName, errInvalidKeySize, "invalid key size")
 )
diff --git a/x/cert/types/query.pb.go b/x/cert/types/query.pb.go
index f674e3c3..0470d7c4 100644
--- a/x/cert/types/query.pb.go
+++ b/x/cert/types/query.pb.go
@@ -30,6 +30,58 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
+type CertificateResponse struct {
+	Certificate Certificate `protobuf:"bytes,1,opt,name=certificate,proto3" json:"certificate" yaml:"certificate"`
+	Serial      string      `protobuf:"bytes,2,opt,name=serial,proto3" json:"serial" yaml:"serial"`
+}
+
+func (m *CertificateResponse) Reset()         { *m = CertificateResponse{} }
+func (m *CertificateResponse) String() string { return proto.CompactTextString(m) }
+func (*CertificateResponse) ProtoMessage()    {}
+func (*CertificateResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_253641229681779f, []int{0}
+}
+func (m *CertificateResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *CertificateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_CertificateResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *CertificateResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CertificateResponse.Merge(m, src)
+}
+func (m *CertificateResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *CertificateResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CertificateResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CertificateResponse proto.InternalMessageInfo
+
+func (m *CertificateResponse) GetCertificate() Certificate {
+	if m != nil {
+		return m.Certificate
+	}
+	return Certificate{}
+}
+
+func (m *CertificateResponse) GetSerial() string {
+	if m != nil {
+		return m.Serial
+	}
+	return ""
+}
+
 // QueryDeploymentsRequest is request type for the Query/Deployments RPC method
 type QueryCertificatesRequest struct {
 	Filter     CertificateFilter  `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter"`
@@ -40,7 +92,7 @@ func (m *QueryCertificatesRequest) Reset()         { *m = QueryCertificatesReque
 func (m *QueryCertificatesRequest) String() string { return proto.CompactTextString(m) }
 func (*QueryCertificatesRequest) ProtoMessage()    {}
 func (*QueryCertificatesRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_253641229681779f, []int{0}
+	return fileDescriptor_253641229681779f, []int{1}
 }
 func (m *QueryCertificatesRequest) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -85,15 +137,15 @@ func (m *QueryCertificatesRequest) GetPagination() *query.PageRequest {
 
 // QueryCertificatesResponse is response type for the Query/Certificates RPC method
 type QueryCertificatesResponse struct {
-	Certificates Certificates        `protobuf:"bytes,1,rep,name=certificates,proto3,castrepeated=Certificates" json:"certificates"`
-	Pagination   *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
+	Certificates CertificatesResponse `protobuf:"bytes,1,rep,name=certificates,proto3,castrepeated=CertificatesResponse" json:"certificates"`
+	Pagination   *query.PageResponse  `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
 }
 
 func (m *QueryCertificatesResponse) Reset()         { *m = QueryCertificatesResponse{} }
 func (m *QueryCertificatesResponse) String() string { return proto.CompactTextString(m) }
 func (*QueryCertificatesResponse) ProtoMessage()    {}
 func (*QueryCertificatesResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_253641229681779f, []int{1}
+	return fileDescriptor_253641229681779f, []int{2}
 }
 func (m *QueryCertificatesResponse) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -122,7 +174,7 @@ func (m *QueryCertificatesResponse) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_QueryCertificatesResponse proto.InternalMessageInfo
 
-func (m *QueryCertificatesResponse) GetCertificates() Certificates {
+func (m *QueryCertificatesResponse) GetCertificates() CertificatesResponse {
 	if m != nil {
 		return m.Certificates
 	}
@@ -137,6 +189,7 @@ func (m *QueryCertificatesResponse) GetPagination() *query.PageResponse {
 }
 
 func init() {
+	proto.RegisterType((*CertificateResponse)(nil), "akash.cert.v1beta1.CertificateResponse")
 	proto.RegisterType((*QueryCertificatesRequest)(nil), "akash.cert.v1beta1.QueryCertificatesRequest")
 	proto.RegisterType((*QueryCertificatesResponse)(nil), "akash.cert.v1beta1.QueryCertificatesResponse")
 }
@@ -144,32 +197,37 @@ func init() {
 func init() { proto.RegisterFile("akash/cert/v1beta1/query.proto", fileDescriptor_253641229681779f) }
 
 var fileDescriptor_253641229681779f = []byte{
-	// 396 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x31, 0x6f, 0xda, 0x40,
-	0x1c, 0xc5, 0x7d, 0xb4, 0x65, 0x38, 0x98, 0x4e, 0x0c, 0x2e, 0x6a, 0x0d, 0x42, 0xa5, 0xa0, 0xaa,
-	0xdc, 0x09, 0xba, 0x77, 0x00, 0x89, 0xae, 0x0d, 0x5b, 0xb2, 0x9d, 0xad, 0xc3, 0x9c, 0x30, 0x3e,
-	0xe3, 0x3b, 0x50, 0x58, 0xf3, 0x09, 0x22, 0x65, 0xcb, 0x1c, 0x29, 0x52, 0xbe, 0x44, 0x56, 0x46,
-	0xa4, 0x2c, 0x99, 0x92, 0x08, 0xf2, 0x41, 0x22, 0x9f, 0x4d, 0x62, 0x14, 0x23, 0xb2, 0x59, 0x7e,
-	0xef, 0xff, 0xfe, 0xbf, 0x67, 0xff, 0xa1, 0x45, 0xc7, 0x54, 0x8e, 0x88, 0xc3, 0x42, 0x45, 0xe6,
-	0x6d, 0x9b, 0x29, 0xda, 0x26, 0xd3, 0x19, 0x0b, 0x17, 0x38, 0x08, 0x85, 0x12, 0x08, 0x69, 0x1d,
-	0x47, 0x3a, 0x4e, 0xf4, 0x72, 0xc9, 0x15, 0xae, 0xd0, 0x32, 0x89, 0x9e, 0x62, 0x67, 0xf9, 0x9b,
-	0x2b, 0x84, 0xeb, 0x31, 0x42, 0x03, 0x4e, 0xa8, 0xef, 0x0b, 0x45, 0x15, 0x17, 0xbe, 0x4c, 0xd4,
-	0x5f, 0x8e, 0x90, 0x13, 0x21, 0x89, 0x4d, 0x25, 0x8b, 0x17, 0xbc, 0xae, 0x0b, 0xa8, 0xcb, 0x7d,
-	0x6d, 0x4e, 0xbc, 0xdf, 0x33, 0x98, 0x34, 0x80, 0x96, 0x6b, 0xd7, 0x00, 0x9a, 0x47, 0x51, 0x42,
-	0x8f, 0x85, 0x8a, 0x0f, 0xb9, 0x43, 0x15, 0x93, 0x03, 0x36, 0x9d, 0x31, 0xa9, 0x50, 0x0f, 0xe6,
-	0x87, 0xdc, 0x53, 0x2c, 0x34, 0x41, 0x15, 0x34, 0x0b, 0x9d, 0x3a, 0x7e, 0x5f, 0x00, 0xa7, 0x06,
-	0xfb, 0xda, 0xdc, 0xfd, 0xbc, 0x7c, 0xa8, 0x18, 0x83, 0x64, 0x14, 0xf5, 0x21, 0x7c, 0x83, 0x32,
-	0x73, 0x3a, 0xe8, 0x27, 0x8e, 0x1b, 0xe0, 0xa8, 0x01, 0x8e, 0x3f, 0xd1, 0x36, 0xef, 0x3f, 0x75,
-	0x59, 0x02, 0x30, 0x48, 0x4d, 0xd6, 0x6e, 0x01, 0xfc, 0x9a, 0x41, 0x2a, 0x03, 0xe1, 0x4b, 0x86,
-	0x8e, 0x61, 0xd1, 0x49, 0xbd, 0x37, 0x41, 0xf5, 0x53, 0xb3, 0xd0, 0xa9, 0x1c, 0x00, 0xee, 0x96,
-	0x22, 0xd4, 0x9b, 0xc7, 0x4a, 0x71, 0x27, 0x74, 0x27, 0x0a, 0xfd, 0xcb, 0x28, 0xd0, 0x38, 0x58,
-	0x20, 0xe6, 0x4a, 0x37, 0xe8, 0x5c, 0x01, 0xf8, 0x45, 0x37, 0x40, 0x97, 0x00, 0xee, 0x6c, 0x44,
-	0xbf, 0xb3, 0x40, 0xf7, 0xfd, 0x97, 0x72, 0xeb, 0x83, 0xee, 0x98, 0xa1, 0xd6, 0x3a, 0xbb, 0x7b,
-	0xbe, 0xc8, 0x35, 0x50, 0x9d, 0xec, 0xb9, 0x85, 0xed, 0x04, 0xf1, 0xb8, 0x54, 0xdd, 0xbf, 0xcb,
-	0xb5, 0x05, 0x56, 0x6b, 0x0b, 0x3c, 0xad, 0x2d, 0x70, 0xbe, 0xb1, 0x8c, 0xd5, 0xc6, 0x32, 0xee,
-	0x37, 0x96, 0x71, 0xf2, 0xc3, 0xe5, 0x6a, 0x34, 0xb3, 0xb1, 0x23, 0x26, 0x44, 0xcc, 0x43, 0xc7,
-	0x1b, 0x27, 0x89, 0xa7, 0x71, 0xa6, 0x5a, 0x04, 0x4c, 0xda, 0x79, 0x7d, 0x59, 0x7f, 0x5e, 0x02,
-	0x00, 0x00, 0xff, 0xff, 0xa1, 0xde, 0x5b, 0x79, 0x0e, 0x03, 0x00, 0x00,
+	// 477 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0xb1, 0x6e, 0xd4, 0x30,
+	0x18, 0xc7, 0xcf, 0x05, 0x4e, 0xc2, 0x57, 0x16, 0xd3, 0xe1, 0x38, 0x4a, 0x72, 0x8a, 0x28, 0x77,
+	0x20, 0x6a, 0xab, 0xe9, 0xc6, 0xc0, 0x90, 0x4a, 0x65, 0x85, 0x8c, 0x6c, 0x4e, 0xe4, 0xa6, 0x56,
+	0x73, 0x71, 0x1a, 0xfb, 0x2a, 0x6e, 0xe5, 0x09, 0x90, 0xd8, 0x98, 0x91, 0x90, 0x78, 0x02, 0x1e,
+	0xa1, 0x1b, 0x95, 0x58, 0x98, 0x02, 0xba, 0x63, 0xea, 0xd8, 0x27, 0x40, 0xb1, 0x7d, 0xd4, 0x88,
+	0x9c, 0x8e, 0x2d, 0xf1, 0xff, 0xfb, 0x7f, 0xdf, 0xef, 0xff, 0x39, 0x81, 0x1e, 0x3d, 0xa1, 0xf2,
+	0x98, 0xa4, 0xac, 0x52, 0xe4, 0x6c, 0x2f, 0x61, 0x8a, 0xee, 0x91, 0xd3, 0x29, 0xab, 0x66, 0xb8,
+	0xac, 0x84, 0x12, 0x08, 0x69, 0x1d, 0x37, 0x3a, 0xb6, 0xfa, 0x60, 0x2b, 0x13, 0x99, 0xd0, 0x32,
+	0x69, 0x9e, 0x4c, 0xe5, 0x60, 0x3b, 0x13, 0x22, 0xcb, 0x19, 0xa1, 0x25, 0x27, 0xb4, 0x28, 0x84,
+	0xa2, 0x8a, 0x8b, 0x42, 0x5a, 0xf5, 0x49, 0x2a, 0xe4, 0x44, 0x48, 0x92, 0x50, 0xc9, 0xcc, 0x80,
+	0x3f, 0xe3, 0x4a, 0x9a, 0xf1, 0x42, 0x17, 0xdb, 0xda, 0x07, 0x2d, 0x4c, 0x1a, 0x40, 0xcb, 0xc1,
+	0x17, 0x00, 0xef, 0x1e, 0xb0, 0x4a, 0xf1, 0x23, 0x9e, 0x52, 0xc5, 0x62, 0x26, 0x4b, 0x51, 0x48,
+	0x86, 0x72, 0xd8, 0x4b, 0xaf, 0x8f, 0xfb, 0x60, 0x08, 0xc6, 0xbd, 0xd0, 0xc7, 0xff, 0x06, 0xc0,
+	0x8e, 0x3b, 0x7a, 0x7c, 0x5e, 0xfb, 0x9d, 0xcb, 0xda, 0x77, 0xbd, 0x57, 0xb5, 0x8f, 0x66, 0x74,
+	0x92, 0x3f, 0x0b, 0x9c, 0xc3, 0x20, 0x76, 0x4b, 0xd0, 0x3e, 0xec, 0x4a, 0x56, 0x71, 0x9a, 0xf7,
+	0x37, 0x86, 0x60, 0x7c, 0x3b, 0xba, 0x7f, 0x59, 0xfb, 0xf6, 0xe4, 0xaa, 0xf6, 0xef, 0x18, 0xbb,
+	0x79, 0x0f, 0x62, 0x2b, 0x04, 0x9f, 0x00, 0xec, 0xbf, 0x6a, 0xc2, 0x3b, 0x04, 0x32, 0x66, 0xa7,
+	0x53, 0x26, 0x15, 0x3a, 0x80, 0xdd, 0x23, 0x9e, 0x2b, 0x56, 0x59, 0xf4, 0x9d, 0x35, 0xe8, 0x87,
+	0xba, 0x38, 0xba, 0xd9, 0x04, 0x88, 0xad, 0x15, 0x1d, 0x42, 0x78, 0xbd, 0x4f, 0x8d, 0xd6, 0x0b,
+	0x1f, 0x61, 0xb3, 0x7c, 0xdc, 0x2c, 0x1f, 0x9b, 0xdb, 0x5d, 0xf6, 0x7b, 0x49, 0x33, 0x66, 0x01,
+	0x62, 0xc7, 0x19, 0x7c, 0x05, 0xf0, 0x5e, 0x0b, 0xa9, 0x5d, 0x35, 0x87, 0x9b, 0xce, 0x2e, 0x64,
+	0x1f, 0x0c, 0x6f, 0x8c, 0x7b, 0xe1, 0x68, 0x0d, 0xf0, 0xd2, 0x1e, 0x6d, 0x37, 0xc8, 0x9f, 0x7f,
+	0xf8, 0x5b, 0x6d, 0xcd, 0xe3, 0xbf, 0x5a, 0xa3, 0x17, 0x2d, 0x81, 0x46, 0x6b, 0x03, 0xd9, 0x56,
+	0x8e, 0x35, 0xfc, 0x08, 0xe0, 0x2d, 0x9d, 0x08, 0x7d, 0x00, 0x70, 0xd3, 0x9d, 0x8c, 0x9e, 0xb6,
+	0x81, 0xaf, 0xba, 0xa7, 0xc1, 0xee, 0x7f, 0x56, 0x1b, 0x86, 0x60, 0xf7, 0xed, 0xb7, 0x5f, 0xef,
+	0x37, 0x46, 0x68, 0x87, 0xac, 0xf8, 0xac, 0x97, 0x0e, 0x92, 0x73, 0xa9, 0xa2, 0xe7, 0xe7, 0x73,
+	0x0f, 0x5c, 0xcc, 0x3d, 0xf0, 0x73, 0xee, 0x81, 0x77, 0x0b, 0xaf, 0x73, 0xb1, 0xf0, 0x3a, 0xdf,
+	0x17, 0x5e, 0xe7, 0xf5, 0xc3, 0x8c, 0xab, 0xe3, 0x69, 0x82, 0x53, 0x31, 0x21, 0xe2, 0xac, 0x4a,
+	0xf3, 0x13, 0xdb, 0xf1, 0x8d, 0xe9, 0xa9, 0x66, 0x25, 0x93, 0x49, 0x57, 0xff, 0x24, 0xfb, 0xbf,
+	0x03, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x8c, 0x50, 0xe9, 0xd9, 0x03, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -254,6 +312,46 @@ var _Query_serviceDesc = grpc.ServiceDesc{
 	Metadata: "akash/cert/v1beta1/query.proto",
 }
 
+func (m *CertificateResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *CertificateResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *CertificateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Serial) > 0 {
+		i -= len(m.Serial)
+		copy(dAtA[i:], m.Serial)
+		i = encodeVarintQuery(dAtA, i, uint64(len(m.Serial)))
+		i--
+		dAtA[i] = 0x12
+	}
+	{
+		size, err := m.Certificate.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintQuery(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
 func (m *QueryCertificatesRequest) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -359,6 +457,21 @@ func encodeVarintQuery(dAtA []byte, offset int, v uint64) int {
 	dAtA[offset] = uint8(v)
 	return base
 }
+func (m *CertificateResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Certificate.Size()
+	n += 1 + l + sovQuery(uint64(l))
+	l = len(m.Serial)
+	if l > 0 {
+		n += 1 + l + sovQuery(uint64(l))
+	}
+	return n
+}
+
 func (m *QueryCertificatesRequest) Size() (n int) {
 	if m == nil {
 		return 0
@@ -399,6 +512,124 @@ func sovQuery(x uint64) (n int) {
 func sozQuery(x uint64) (n int) {
 	return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63))))
 }
+func (m *CertificateResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQuery
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: CertificateResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: CertificateResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Certificate", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Certificate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Serial", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Serial = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQuery(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *QueryCertificatesRequest) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -579,7 +810,7 @@ func (m *QueryCertificatesResponse) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.Certificates = append(m.Certificates, Certificate{})
+			m.Certificates = append(m.Certificates, CertificateResponse{})
 			if err := m.Certificates[len(m.Certificates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
 				return err
 			}
diff --git a/x/cert/types/types.go b/x/cert/types/types.go
index 9f507652..66520034 100644
--- a/x/cert/types/types.go
+++ b/x/cert/types/types.go
@@ -38,6 +38,8 @@ func ToCertID(id CertificateID) (CertID, error) {
 // Certificates is the collection of Certificate
 type Certificates []Certificate
 
+type CertificatesResponse []CertificateResponse
+
 // String implements the Stringer interface for a Certificates object.
 func (obj Certificates) String() string {
 	var buf bytes.Buffer
diff --git a/x/deployment/client/cli/tx.go b/x/deployment/client/cli/tx.go
index f018897a..41a17a4f 100644
--- a/x/deployment/client/cli/tx.go
+++ b/x/deployment/client/cli/tx.go
@@ -2,13 +2,16 @@ package cli
 
 import (
 	"fmt"
+	"os"
 
 	"github.com/cosmos/cosmos-sdk/client"
 	"github.com/cosmos/cosmos-sdk/client/flags"
 	"github.com/cosmos/cosmos-sdk/client/tx"
+	"github.com/pkg/errors"
 
 	"github.com/ovrclk/akash/cmd/common"
 	"github.com/ovrclk/akash/sdl"
+	cutils "github.com/ovrclk/akash/x/cert/utils"
 	"github.com/ovrclk/akash/x/deployment/types"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
@@ -45,6 +48,16 @@ func cmdCreate(key string) *cobra.Command {
 				return err
 			}
 
+			// first lets validate certificate exists for given account
+			if _, err = cutils.LoadCertificateForAccount(clientCtx, clientCtx.Keyring); err != nil {
+				if os.IsNotExist(err) {
+					err = errors.Errorf("no certificate file found for account %q.\n"+
+						"consider creating it as certificate required to submit manifest", clientCtx.FromAddress.String())
+				}
+
+				return err
+			}
+
 			sdlManifest, err := sdl.ReadFile(args[0])
 			if err != nil {
 				return err


Test patch:
diff --git a/testutil/cert.go b/testutil/cert.go
index bd279a2c..ce37abab 100644
--- a/testutil/cert.go
+++ b/testutil/cert.go
@@ -16,6 +16,7 @@ import (
 	"time"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/stretchr/testify/require"
 
 	"github.com/ovrclk/akash/client/mocks"
 	"github.com/ovrclk/akash/x/cert/types"
@@ -188,14 +189,25 @@ func Certificate(t testing.TB, addr sdk.Address, opts ...CertificateOption) Test
 				},
 			}).
 			Return(&types.QueryCertificatesResponse{
-				Certificates: []types.Certificate{
-					{
-						State:  types.CertificateValid,
-						Cert:   res.PEM.Cert,
-						Pubkey: res.PEM.Pub,
+				Certificates: types.CertificatesResponse{
+					types.CertificateResponse{
+						Certificate: types.Certificate{
+							State:  types.CertificateValid,
+							Cert:   res.PEM.Cert,
+							Pubkey: res.PEM.Pub,
+						},
+						Serial: res.Serial.String(),
 					},
 				},
 			}, nil)
 	}
 	return res
 }
+
+func CertificateRequireEqualResponse(t *testing.T, cert TestCertificate, resp types.CertificateResponse, state types.Certificate_State) {
+	t.Helper()
+
+	require.Equal(t, state, resp.Certificate.State)
+	require.Equal(t, cert.PEM.Cert, resp.Certificate.Cert)
+	require.Equal(t, cert.PEM.Pub, resp.Certificate.Pubkey)
+}
diff --git a/x/cert/client/cli/test_helpers.go b/x/cert/client/cli/test_helpers.go
index d44718cd..edee06b3 100644
--- a/x/cert/client/cli/test_helpers.go
+++ b/x/cert/client/cli/test_helpers.go
@@ -19,3 +19,14 @@ func TxCreateServerExec(clientCtx client.Context, from fmt.Stringer, host string
 
 	return clitestutil.ExecTestCLICmd(clientCtx, cmdCreateServer(), args)
 }
+
+// TxCreateClientExec is used for testing create client certificate tx
+func TxCreateClientExec(clientCtx client.Context, from fmt.Stringer, extraArgs ...string) (sdktest.BufferWriter, error) {
+	args := []string{
+		fmt.Sprintf("--from=%s", from.String()),
+	}
+
+	args = append(args, extraArgs...)
+
+	return clitestutil.ExecTestCLICmd(clientCtx, cmdCreateClient(), args)
+}
diff --git a/x/cert/handler/handler_test.go b/x/cert/handler/handler_test.go
index 8503e294..ac8ada22 100644
--- a/x/cert/handler/handler_test.go
+++ b/x/cert/handler/handler_test.go
@@ -79,9 +79,7 @@ func TestCertHandlerCreate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 }
 
 func TestCertHandlerCreateOwnerMismatch(t *testing.T) {
@@ -135,9 +133,7 @@ func TestCertHandlerDuplicate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	res, err = suite.handler(suite.ctx, msg)
 	require.Nil(t, res)
@@ -159,18 +155,14 @@ func TestCertHandlerDuplicate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	resp, exists = suite.keeper.GetCertificateByID(suite.ctx, types.CertID{
 		Owner:  owner,
 		Serial: cert1.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert1.PEM.Cert, resp.Cert)
-	require.Equal(t, cert1.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert1, resp, types.CertificateValid)
 }
 
 func TestCertHandlerRevoke(t *testing.T) {
@@ -195,9 +187,7 @@ func TestCertHandlerRevoke(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	msgRevoke := &types.MsgRevokeCertificate{
 		ID: types.CertificateID{
@@ -215,9 +205,7 @@ func TestCertHandlerRevoke(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateRevoked, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateRevoked)
 
 	res, err = suite.handler(suite.ctx, msgRevoke)
 	require.Nil(t, res)
@@ -246,9 +234,7 @@ func TestCertHandlerRevokeCreateRevoked(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	msgRevoke := &types.MsgRevokeCertificate{
 		ID: types.CertificateID{
@@ -266,9 +252,7 @@ func TestCertHandlerRevokeCreateRevoked(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateRevoked, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateRevoked)
 
 	res, err = suite.handler(suite.ctx, msgCreate)
 	require.Nil(t, res)
@@ -295,9 +279,7 @@ func TestCertHandlerRevokeCreate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	msgRevoke := &types.MsgRevokeCertificate{
 		ID: types.CertificateID{
@@ -315,9 +297,7 @@ func TestCertHandlerRevokeCreate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateRevoked, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateRevoked)
 
 	cert1 := testutil.Certificate(t, owner)
 
diff --git a/x/cert/keeper/grpc_query_test.go b/x/cert/keeper/grpc_query_test.go
index 4a7a3833..544dc589 100644
--- a/x/cert/keeper/grpc_query_test.go
+++ b/x/cert/keeper/grpc_query_test.go
@@ -30,7 +30,7 @@ func setupTest(t *testing.T) *grpcTestSuite {
 
 	suite.app = app.Setup(false)
 	suite.ctx, suite.keeper = setupKeeper(t)
-	querier := keeper.Querier{Keeper: suite.keeper}
+	querier := suite.keeper.Querier()
 
 	queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
 	types.RegisterQueryServer(queryHelper, querier)
@@ -157,8 +157,14 @@ func TestCertGRPCQueryCertificates(t *testing.T) {
 				require.NotNil(t, res)
 				if expCertificates != nil {
 					sortCerts(expCertificates)
-					sortCerts(res.Certificates)
-					require.Equal(t, expCertificates, res.Certificates)
+
+					respCerts := make(types.Certificates, len(res.Certificates))
+					for i, cert := range res.Certificates {
+						respCerts[i] = cert.Certificate
+					}
+
+					sortCerts(respCerts)
+					require.Equal(t, expCertificates, respCerts)
 				}
 			} else {
 				require.NotNil(t, res)
diff --git a/x/cert/keeper/keeper_test.go b/x/cert/keeper/keeper_test.go
index 8292777e..206007b0 100644
--- a/x/cert/keeper/keeper_test.go
+++ b/x/cert/keeper/keeper_test.go
@@ -29,9 +29,7 @@ func TestCertKeeperCreate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 }
 
 func TestCertKeeperCreateOwnerMismatch(t *testing.T) {
@@ -67,9 +65,7 @@ func TestCertKeeperMultipleActive(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	err = keeper.CreateCertificate(ctx, owner, cert.PEM.Cert, cert.PEM.Pub)
 	require.Error(t, err, types.ErrCertificateExists.Error())
@@ -83,18 +79,14 @@ func TestCertKeeperMultipleActive(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	resp, exists = keeper.GetCertificateByID(ctx, types.CertID{
 		Owner:  owner,
 		Serial: cert1.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert1.PEM.Cert, resp.Cert)
-	require.Equal(t, cert1.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert1, resp, types.CertificateValid)
 }
 
 func TestCertKeeperRevoke(t *testing.T) {
@@ -110,9 +102,7 @@ func TestCertKeeperRevoke(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 
 	err = keeper.RevokeCertificate(ctx, types.CertID{
 		Owner:  owner,
@@ -125,9 +115,7 @@ func TestCertKeeperRevoke(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateRevoked, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateRevoked)
 
 	err = keeper.RevokeCertificate(ctx, types.CertID{
 		Owner:  owner,
@@ -149,10 +137,7 @@ func TestCertKeeperRevokeCreateRevoked(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
-
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 	err = keeper.RevokeCertificate(ctx, types.CertID{
 		Owner:  owner,
 		Serial: cert.Serial,
@@ -164,9 +149,7 @@ func TestCertKeeperRevokeCreateRevoked(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateRevoked, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateRevoked)
 
 	err = keeper.CreateCertificate(ctx, owner, cert.PEM.Cert, cert.PEM.Pub)
 	require.Error(t, err, types.ErrCertificateExists.Error())
@@ -185,10 +168,7 @@ func TestCertKeeperRevokeCreate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateValid, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
-
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateValid)
 	err = keeper.RevokeCertificate(ctx, types.CertID{
 		Owner:  owner,
 		Serial: cert.Serial,
@@ -200,9 +180,7 @@ func TestCertKeeperRevokeCreate(t *testing.T) {
 		Serial: cert.Serial,
 	})
 	require.True(t, exists)
-	require.Equal(t, types.CertificateRevoked, resp.State)
-	require.Equal(t, cert.PEM.Cert, resp.Cert)
-	require.Equal(t, cert.PEM.Pub, resp.Pubkey)
+	testutil.CertificateRequireEqualResponse(t, cert, resp, types.CertificateRevoked)
 
 	cert1 := testutil.Certificate(t, owner)
 	err = keeper.CreateCertificate(ctx, owner, cert1.PEM.Cert, cert1.PEM.Pub)
diff --git a/x/deployment/client/cli/cli_test.go b/x/deployment/client/cli/cli_test.go
index 9eef0fce..68a578a3 100644
--- a/x/deployment/client/cli/cli_test.go
+++ b/x/deployment/client/cli/cli_test.go
@@ -11,6 +11,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/testutil/network"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/ovrclk/akash/testutil"
+	ccli "github.com/ovrclk/akash/x/cert/client/cli"
 	"github.com/ovrclk/akash/x/deployment/client/cli"
 	"github.com/ovrclk/akash/x/deployment/types"
 )
@@ -34,6 +35,20 @@ func (s *IntegrationTestSuite) SetupSuite() {
 
 	_, err := s.network.WaitForHeight(1)
 	s.Require().NoError(err)
+
+	val := s.network.Validators[0]
+
+	// Create client certificate
+	_, err = ccli.TxCreateClientExec(
+		val.ClientCtx,
+		val.Address,
+		fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
+		fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
+		fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
+		fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
+	)
+	s.Require().NoError(err)
+	s.Require().NoError(s.network.WaitForNextBlock())
 }
 
 func (s *IntegrationTestSuite) TearDownSuite() {
@@ -182,7 +197,6 @@ func (s *IntegrationTestSuite) TestGroup() {
 		fmt.Sprintf("--deposit=%s", cli.DefaultDeposit),
 	)
 	s.Require().NoError(err)
-
 	s.Require().NoError(s.network.WaitForNextBlock())
 
 	// test query deployments
diff --git a/x/deployment/client/cli/grpc_rest_test.go b/x/deployment/client/cli/grpc_rest_test.go
index 76156dd9..a5a84ba1 100644
--- a/x/deployment/client/cli/grpc_rest_test.go
+++ b/x/deployment/client/cli/grpc_rest_test.go
@@ -12,6 +12,7 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkrest "github.com/cosmos/cosmos-sdk/types/rest"
 	"github.com/ovrclk/akash/testutil"
+	ccli "github.com/ovrclk/akash/x/cert/client/cli"
 	"github.com/ovrclk/akash/x/deployment/client/cli"
 	"github.com/ovrclk/akash/x/deployment/types"
 )
@@ -41,6 +42,18 @@ func (s *GRPCRestTestSuite) SetupSuite() {
 	deploymentPath, err := filepath.Abs("../../testdata/deployment.yaml")
 	s.Require().NoError(err)
 
+	// Create client certificate
+	_, err = ccli.TxCreateClientExec(
+		val.ClientCtx,
+		val.Address,
+		fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
+		fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
+		fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
+		fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
+	)
+	s.Require().NoError(err)
+	s.Require().NoError(s.network.WaitForNextBlock())
+
 	// create deployment
 	_, err = cli.TxCreateDeploymentExec(
 		val.ClientCtx,
diff --git a/x/market/client/cli/cli_test.go b/x/market/client/cli/cli_test.go
index 46bada34..2374305d 100644
--- a/x/market/client/cli/cli_test.go
+++ b/x/market/client/cli/cli_test.go
@@ -14,7 +14,9 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
 	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+
 	"github.com/ovrclk/akash/testutil"
+	ccli "github.com/ovrclk/akash/x/cert/client/cli"
 	dcli "github.com/ovrclk/akash/x/deployment/client/cli"
 	dtypes "github.com/ovrclk/akash/x/deployment/types"
 	"github.com/ovrclk/akash/x/market/client/cli"
@@ -46,6 +48,20 @@ func (s *IntegrationTestSuite) SetupSuite() {
 
 	_, err = s.network.WaitForHeight(1)
 	s.Require().NoError(err)
+
+	val := s.network.Validators[0]
+
+	// Create client certificate
+	_, err = ccli.TxCreateClientExec(
+		val.ClientCtx,
+		val.Address,
+		fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
+		fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
+		fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
+		fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
+	)
+	s.Require().NoError(err)
+	s.Require().NoError(s.network.WaitForNextBlock())
 }
 
 func (s *IntegrationTestSuite) TearDownSuite() {
diff --git a/x/market/client/cli/grpc_rest_test.go b/x/market/client/cli/grpc_rest_test.go
index 0973029f..690e6b26 100644
--- a/x/market/client/cli/grpc_rest_test.go
+++ b/x/market/client/cli/grpc_rest_test.go
@@ -14,7 +14,9 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkrest "github.com/cosmos/cosmos-sdk/types/rest"
 	bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
+
 	"github.com/ovrclk/akash/testutil"
+	ccli "github.com/ovrclk/akash/x/cert/client/cli"
 	dcli "github.com/ovrclk/akash/x/deployment/client/cli"
 	"github.com/ovrclk/akash/x/market/client/cli"
 	"github.com/ovrclk/akash/x/market/types"
@@ -49,6 +51,18 @@ func (s *GRPCRestTestSuite) SetupSuite() {
 
 	val := s.network.Validators[0]
 
+	// Create client certificate
+	_, err = ccli.TxCreateClientExec(
+		val.ClientCtx,
+		val.Address,
+		fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
+		fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
+		fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
+		fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
+	)
+	s.Require().NoError(err)
+	s.Require().NoError(s.network.WaitForNextBlock())
+
 	deploymentPath, err := filepath.Abs("./../../../deployment/testdata/deployment.yaml")
 	s.Require().NoError(err)