From 006eb19dd86e9962bca8b893edc85dac1fb77f43 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Tue, 24 Oct 2023 02:26:16 +0200 Subject: [PATCH 01/19] feat: add general proxy logic and server builder --- pkg/relayer/proxy/errors.go | 8 ++++ pkg/relayer/proxy/interface.go | 12 ++++++ pkg/relayer/proxy/jsonrpc.go | 62 +++++++++++++++++++++++++++++ pkg/relayer/proxy/proxy.go | 57 +++++++++++++++----------- pkg/relayer/proxy/server_builder.go | 57 ++++++++++++++++++++++++++ proto/pocket/shared/supplier.proto | 5 +-- 6 files changed, 174 insertions(+), 27 deletions(-) create mode 100644 pkg/relayer/proxy/errors.go create mode 100644 pkg/relayer/proxy/jsonrpc.go create mode 100644 pkg/relayer/proxy/server_builder.go diff --git a/pkg/relayer/proxy/errors.go b/pkg/relayer/proxy/errors.go new file mode 100644 index 000000000..ae0186af0 --- /dev/null +++ b/pkg/relayer/proxy/errors.go @@ -0,0 +1,8 @@ +package proxy + +import errorsmod "cosmossdk.io/errors" + +var ( + ErrUnsupportedRPCType = errorsmod.Register(codespace, 1, "unsupported rpc type") + codespace = "relayer/proxy" +) diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index 016b27057..e56b56956 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -24,3 +24,15 @@ type RelayerProxy interface { // and its RelayResponse has been signed and successfully sent to the client. ServedRelays() observable.Observable[*types.Relay] } + +// RelayServer is the interface of the proxy servers provided by the RelayerProxy. +type RelayServer interface { + // Start starts the service server and returns an error if it fails. + Start(ctx context.Context) error + + // Stop terminates the service server and returns an error if it fails. + Stop(ctx context.Context) error + + // Name returns the name of the service. + Name() string +} diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go new file mode 100644 index 000000000..093ad3abf --- /dev/null +++ b/pkg/relayer/proxy/jsonrpc.go @@ -0,0 +1,62 @@ +package proxy + +import ( + "context" + "net/http" +) + +var _ RelayServer = &jsonRPCServer{} + +type jsonRPCServer struct { + // serviceId is the id of the service that the server is responsible for. + serviceId string + + // endpointUrl is the url that the server listens to for incoming relay requests. + endpointUrl string + + // server is the http server that listens for incoming relay requests. + server *http.Server + + // relayerProxy is the main relayer proxy that the server uses to perform its operations. + relayerProxy RelayerProxy +} + +// NewHTTPServer creates a new HTTP server that listens for incoming relay requests +// and proxies them to the supported native service. +// It takes the serviceId, endpointUrl, and the main RelayerProxy as arguments and returns +// a RelayServer that listens to incoming RelayRequests +func NewJSONRPCServer(serviceId string, endpointUrl string, proxy RelayerProxy) RelayServer { + return &jsonRPCServer{ + serviceId: serviceId, + endpointUrl: endpointUrl, + server: &http.Server{Addr: endpointUrl}, + relayerProxy: proxy, + } +} + +// Start starts the service server and returns an error if it fails. +// It also waits for the passed in context to be done to shut down. +// This method is blocking and should be called in a goroutine. +func (j *jsonRPCServer) Start(ctx context.Context) error { + go func() { + <-ctx.Done() + j.server.Shutdown(ctx) + }() + + return j.server.ListenAndServe() +} + +// Stop terminates the service server and returns an error if it fails. +func (j *jsonRPCServer) Stop(ctx context.Context) error { + return j.server.Shutdown(ctx) +} + +// Name returns the name of the service. +func (j *jsonRPCServer) Name() string { + return j.serviceId +} + +// ServeHTTP is the http handler that listens for incoming relay requests. +func (j *jsonRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + panic("TODO: implement httpServer.ServeHTTP") +} diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index f626f184f..f3cc9eefd 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -6,6 +6,7 @@ import ( sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keyring" accounttypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "golang.org/x/sync/errgroup" // TODO_INCOMPLETE(@red-0ne): Import the appropriate block client interface once available. // blocktypes "pocket/pkg/client" @@ -44,7 +45,7 @@ type relayerProxy struct { // providedServices is a map of the services provided by the relayer proxy. Each provided service // has the necessary information to start the server that listens for incoming relay requests and // the client that proxies the request to the supported native service. - providedServices map[string][]*ProvidedService + providedServices map[string][]RelayServer // servedRelays is an observable that notifies the miner about the relays that have been served. servedRelays observable.Observable[*types.Relay] @@ -66,18 +67,16 @@ func NewRelayerProxy( accountQuerier := accounttypes.NewQueryClient(clientCtx) supplierQuerier := suppliertypes.NewQueryClient(clientCtx) sessionQuerier := sessiontypes.NewQueryClient(clientCtx) - providedServices := buildProvidedServices(ctx, supplierQuerier) servedRelays, servedRelaysProducer := channel.NewObservable[*types.Relay]() return &relayerProxy{ // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. - // blockClient: blockClient, + // blockClient: blockClient, keyName: keyName, keyring: keyring, accountsQuerier: accountQuerier, supplierQuerier: supplierQuerier, sessionQuerier: sessionQuerier, - providedServices: providedServices, servedRelays: servedRelays, servedRelaysProducer: servedRelaysProducer, } @@ -85,34 +84,44 @@ func NewRelayerProxy( // Start starts all supported proxies and returns an error if any of them fail to start. func (rp *relayerProxy) Start(ctx context.Context) error { - panic("TODO: implement relayerProxy.Start") + // The provided services map is built from the supplier's on-chain advertised information, + // which is a runtime parameter that can be changed by the supplier. + // Build the provided services map at Start instead of NewRelayerProxy to avoid having to + // return an error from the constructor. + err := rp.BuildProvidedServices(ctx) + if err != nil { + return err + } + + eg, gctx := errgroup.WithContext(ctx) + + for _, providedService := range rp.providedServices { + for _, svr := range providedService { + server := svr // create a new variable scoped to the anonymous function + eg.Go(func() error { return server.Start(gctx) }) + } + } + + return eg.Wait() } // Stop stops all supported proxies and returns an error if any of them fail. func (rp *relayerProxy) Stop(ctx context.Context) error { - panic("TODO: implement relayerProxy.Stop") + eg, gctx := errgroup.WithContext(ctx) + + for _, providedService := range rp.providedServices { + for _, svr := range providedService { + server := svr // create a new variable scoped to the anonymous function + eg.Go(func() error { return server.Stop(gctx) }) + } + } + + return eg.Wait() } // ServedRelays returns an observable that notifies the miner about the relays that have been served. // A served relay is one whose RelayRequest's signature and session have been verified, // and its RelayResponse has been signed and successfully sent to the client. func (rp *relayerProxy) ServedRelays() observable.Observable[*types.Relay] { - panic("TODO: implement relayerProxy.ServedRelays") -} - -// buildProvidedServices builds the provided services map from the supplier's advertised information. -// It loops over the retrieved `SupplierServiceConfig` and, for each `SupplierEndpoint`, it creates the necessary -// server and client to populate the corresponding `ProvidedService` struct in the map. -func buildProvidedServices( - ctx context.Context, - supplierQuerier suppliertypes.QueryClient, -) map[string][]*ProvidedService { - panic("TODO: implement buildProvidedServices") -} - -// TODO_INCOMPLETE(@red-0ne): Add the appropriate server and client interfaces to be implemented by each RPC type. -type ProvidedService struct { - serviceId string - server struct{} - client struct{} + return rp.servedRelays } diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go new file mode 100644 index 000000000..3b8ce1bd9 --- /dev/null +++ b/pkg/relayer/proxy/server_builder.go @@ -0,0 +1,57 @@ +package proxy + +import ( + "context" + + sharedtypes "pocket/x/shared/types" + suppliertypes "pocket/x/supplier/types" +) + +type RelayServersMap map[string][]RelayServer + +// BuildProvidedServices builds the provided services from the supplier's on-chain advertised services. +// It populates the relayerProxy's `providedServices` map of servers for each service, where each server +// is responsible for listening for incoming relay requests and poxying them to the supported native service. +func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { + // Get the supplier address from the keyring + supplierAddress, err := rp.keyring.Key(rp.keyName) + if err != nil { + return err + } + + // Get the supplier's advertised information from the blockchain + supplierQuery := &suppliertypes.QueryGetSupplierRequest{Address: supplierAddress.String()} + supplierQueryResponse, err := rp.supplierQuerier.Supplier(ctx, supplierQuery) + if err != nil { + return err + } + + services := supplierQueryResponse.Supplier.Services + + // Build the provided services map. For each service's endpoint, create the appropriate server. + providedServices := make(RelayServersMap) + for _, serviceConfig := range services { + serviceId := serviceConfig.Id.Id + serviceEndpoints := make([]RelayServer, len(serviceConfig.Endpoints)) + + for _, endpoint := range serviceConfig.Endpoints { + var server RelayServer + + // Switch to the RPC type to create the appropriate server + switch endpoint.RpcType { + case sharedtypes.RPCType_JSON_RPC: + server = NewJSONRPCServer(serviceId, endpoint.Url, rp) + default: + return ErrUnsupportedRPCType + } + + serviceEndpoints = append(serviceEndpoints, server) + } + + providedServices[serviceId] = serviceEndpoints + } + + rp.providedServices = providedServices + + return nil +} diff --git a/proto/pocket/shared/supplier.proto b/proto/pocket/shared/supplier.proto index 9e1826522..4cc315ad6 100644 --- a/proto/pocket/shared/supplier.proto +++ b/proto/pocket/shared/supplier.proto @@ -8,12 +8,11 @@ option go_package = "pocket/x/shared/types"; import "cosmos_proto/cosmos.proto"; import "cosmos/base/v1beta1/coin.proto"; +import "pocket/shared/service.proto"; // Supplier is the type defining the actor in Pocket Network that provides RPC services. message Supplier { string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the supplier using cosmos' ScalarDescriptor to ensure deterministic deterministic encoding using cosmos' ScalarDescriptor to ensure deterministic deterministic encoding cosmos.base.v1beta1.Coin stake = 2; // The total amount of uPOKT the supplier has staked - // TODO(@Olshansk): Uncomment the line below once the `ServiceId` proto is defined - // repeated service.SupplierServiceConfig services = 3; // The service configs this supplier can support + repeated SupplierServiceConfig services = 3; // The service configs this supplier can support } - From 63f9467263e29f6b8d65d4a82f8d670ba6e492e0 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 25 Oct 2023 11:16:26 +0200 Subject: [PATCH 02/19] fix: make var names and comments more consistent --- pkg/relayer/proxy/interface.go | 12 +++++----- pkg/relayer/proxy/jsonrpc.go | 14 +++++++----- pkg/relayer/proxy/proxy.go | 34 +++++++++++++++++------------ pkg/relayer/proxy/server_builder.go | 14 +++++------- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index e56b56956..ef8bf63b1 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -8,15 +8,15 @@ import ( ) // RelayerProxy is the interface for the proxy that serves relays to the application. -// It is responsible for starting and stopping all supported proxies. +// It is responsible for starting and stopping all supported RelayServers. // While handling requests and responding in a closed loop, it also notifies // the miner about the relays that have been served. type RelayerProxy interface { - // Start starts all supported proxies and returns an error if any of them fail to start. + // Start starts all advertised relay servers and returns an error if any of them fail to start. Start(ctx context.Context) error - // Stop stops all supported proxies and returns an error if any of them fail. + // Stop stops all advertised relay servers and returns an error if any of them fail. Stop(ctx context.Context) error // ServedRelays returns an observable that notifies the miner about the relays that have been served. @@ -25,7 +25,7 @@ type RelayerProxy interface { ServedRelays() observable.Observable[*types.Relay] } -// RelayServer is the interface of the proxy servers provided by the RelayerProxy. +// RelayServer is the interface of the advertised relay servers provided by the RelayerProxy. type RelayServer interface { // Start starts the service server and returns an error if it fails. Start(ctx context.Context) error @@ -33,6 +33,6 @@ type RelayServer interface { // Stop terminates the service server and returns an error if it fails. Stop(ctx context.Context) error - // Name returns the name of the service. - Name() string + // ServiceId returns the serviceId of the service. + ServiceId() string } diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 093ad3abf..a03fd53e8 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -21,7 +21,7 @@ type jsonRPCServer struct { relayerProxy RelayerProxy } -// NewHTTPServer creates a new HTTP server that listens for incoming relay requests +// NewJSONRPCServer creates a new HTTP server that listens for incoming relay requests // and proxies them to the supported native service. // It takes the serviceId, endpointUrl, and the main RelayerProxy as arguments and returns // a RelayServer that listens to incoming RelayRequests @@ -51,12 +51,14 @@ func (j *jsonRPCServer) Stop(ctx context.Context) error { return j.server.Shutdown(ctx) } -// Name returns the name of the service. -func (j *jsonRPCServer) Name() string { +// ServiceId returns the serviceId of the service. +func (j *jsonRPCServer) ServiceId() string { return j.serviceId } -// ServeHTTP is the http handler that listens for incoming relay requests. -func (j *jsonRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - panic("TODO: implement httpServer.ServeHTTP") +// ServeHTTP listens for incoming relay requests. It implements the respective +// method of the http.Handler interface. It is called by http.ListenAndServe() +// when jsonRPCServer is used as an http.Handler with an http.Server. +// (see https://pkg.go.dev/net/http#Handler) +func (j *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { } diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index f3cc9eefd..d7a932a2e 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -19,6 +19,11 @@ import ( var _ RelayerProxy = (*relayerProxy)(nil) +type ( + serviceId = string + RelayServersMap = map[serviceId][]RelayServer +) + type relayerProxy struct { // keyName is the supplier's key name in the Cosmos's keybase. It is used along with the keyring to // get the supplier address and sign the relay responses. @@ -42,10 +47,11 @@ type relayerProxy struct { // which is needed to check if the relay proxy should be serving an incoming relay request. sessionQuerier sessiontypes.QueryClient - // providedServices is a map of the services provided by the relayer proxy. Each provided service + // advertisedRelayServers is a map of the services provided by the relayer proxy. Each provided service // has the necessary information to start the server that listens for incoming relay requests and - // the client that proxies the request to the supported native service. - providedServices map[string][]RelayServer + // the client that relays the request to the supported native service. + advertisedRelayServers RelayServersMap + // servedRelays is an observable that notifies the miner about the relays that have been served. servedRelays observable.Observable[*types.Relay] @@ -82,7 +88,7 @@ func NewRelayerProxy( } } -// Start starts all supported proxies and returns an error if any of them fail to start. +// Start concurrently starts all advertised relay servers and returns an error if any of them fails to start. func (rp *relayerProxy) Start(ctx context.Context) error { // The provided services map is built from the supplier's on-chain advertised information, // which is a runtime parameter that can be changed by the supplier. @@ -93,30 +99,30 @@ func (rp *relayerProxy) Start(ctx context.Context) error { return err } - eg, gctx := errgroup.WithContext(ctx) + startGroup, gctx := errgroup.WithContext(ctx) - for _, providedService := range rp.providedServices { - for _, svr := range providedService { + for _, relayServer := range rp.advertisedRelayServers { + for _, svr := range relayServer { server := svr // create a new variable scoped to the anonymous function - eg.Go(func() error { return server.Start(gctx) }) + startGroup.Go(func() error { return server.Start(gctx) }) } } - return eg.Wait() + return startGroup.Wait() } -// Stop stops all supported proxies and returns an error if any of them fail. +// Stop concurrently stops all advertised relay servers and returns an error if any of them fails. func (rp *relayerProxy) Stop(ctx context.Context) error { - eg, gctx := errgroup.WithContext(ctx) + stopGroup, gctx := errgroup.WithContext(ctx) - for _, providedService := range rp.providedServices { + for _, providedService := range rp.advertisedRelayServers { for _, svr := range providedService { server := svr // create a new variable scoped to the anonymous function - eg.Go(func() error { return server.Stop(gctx) }) + stopGroup.Go(func() error { return server.Stop(gctx) }) } } - return eg.Wait() + return stopGroup.Wait() } // ServedRelays returns an observable that notifies the miner about the relays that have been served. diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go index 3b8ce1bd9..d77341948 100644 --- a/pkg/relayer/proxy/server_builder.go +++ b/pkg/relayer/proxy/server_builder.go @@ -7,11 +7,9 @@ import ( suppliertypes "pocket/x/supplier/types" ) -type RelayServersMap map[string][]RelayServer - -// BuildProvidedServices builds the provided services from the supplier's on-chain advertised services. -// It populates the relayerProxy's `providedServices` map of servers for each service, where each server -// is responsible for listening for incoming relay requests and poxying them to the supported native service. +// BuildProvidedServices builds the advertised relay servers from the supplier's on-chain advertised services. +// It populates the relayerProxy's `advertisedRelayServers` map of servers for each service, where each server +// is responsible for listening for incoming relay requests and relaying them to the supported native service. func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { // Get the supplier address from the keyring supplierAddress, err := rp.keyring.Key(rp.keyName) @@ -28,7 +26,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { services := supplierQueryResponse.Supplier.Services - // Build the provided services map. For each service's endpoint, create the appropriate server. + // Build the advertised relay servers map. For each service's endpoint, create the appropriate RelayServer. providedServices := make(RelayServersMap) for _, serviceConfig := range services { serviceId := serviceConfig.Id.Id @@ -37,7 +35,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { for _, endpoint := range serviceConfig.Endpoints { var server RelayServer - // Switch to the RPC type to create the appropriate server + // Switch to the RPC type to create the appropriate RelayServer switch endpoint.RpcType { case sharedtypes.RPCType_JSON_RPC: server = NewJSONRPCServer(serviceId, endpoint.Url, rp) @@ -51,7 +49,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { providedServices[serviceId] = serviceEndpoints } - rp.providedServices = providedServices + rp.advertisedRelayServers = providedServices return nil } From 8f899fb91deee4fd7523c6dcda354a5e32c80ee7 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 25 Oct 2023 12:29:46 +0200 Subject: [PATCH 03/19] feat: add relay verification and signature --- pkg/relayer/proxy/interface.go | 7 +++++++ pkg/relayer/proxy/jsonrpc.go | 27 +++++++++++++++++++++----- pkg/relayer/proxy/proxy.go | 30 +++++++++++++++++++++-------- pkg/relayer/proxy/server_builder.go | 9 ++++++++- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index ef8bf63b1..c130f91ec 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -23,6 +23,13 @@ type RelayerProxy interface { // A served relay is one whose RelayRequest's signature and session have been verified, // and its RelayResponse has been signed and successfully sent to the client. ServedRelays() observable.Observable[*types.Relay] + + // VerifyRelayRequest is a shared method used by RelayServers to check the + // relay request signature and session validity. + VerifyRelayRequest(relayRequest *types.RelayRequest) (isValid bool, err error) + + // SignRelayResponse is a shared method used by RelayServers to sign the relay response. + SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) } // RelayServer is the interface of the advertised relay servers provided by the RelayerProxy. diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index a03fd53e8..11bdbe6d3 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -3,6 +3,8 @@ package proxy import ( "context" "net/http" + + "pocket/x/service/types" ) var _ RelayServer = &jsonRPCServer{} @@ -14,23 +16,38 @@ type jsonRPCServer struct { // endpointUrl is the url that the server listens to for incoming relay requests. endpointUrl string + // nativeServiceListenAddress is the address of the native service that the server relays requests to. + nativeServiceListenAddress string + // server is the http server that listens for incoming relay requests. server *http.Server // relayerProxy is the main relayer proxy that the server uses to perform its operations. relayerProxy RelayerProxy + + // servedRelaysProducer is a channel that emits the relays that have been served so that the + // servedRelays observable can fan out the notifications to its subscribers. + servedRelaysProducer chan<- *types.Relay } // NewJSONRPCServer creates a new HTTP server that listens for incoming relay requests // and proxies them to the supported native service. // It takes the serviceId, endpointUrl, and the main RelayerProxy as arguments and returns // a RelayServer that listens to incoming RelayRequests -func NewJSONRPCServer(serviceId string, endpointUrl string, proxy RelayerProxy) RelayServer { +func NewJSONRPCServer( + serviceId string, + endpointUrl string, + nativeServiceListenAddress string, + servedRelaysProducer chan<- *types.Relay, + proxy RelayerProxy, +) RelayServer { return &jsonRPCServer{ - serviceId: serviceId, - endpointUrl: endpointUrl, - server: &http.Server{Addr: endpointUrl}, - relayerProxy: proxy, + serviceId: serviceId, + endpointUrl: endpointUrl, + server: &http.Server{Addr: endpointUrl}, + relayerProxy: proxy, + nativeServiceListenAddress: nativeServiceListenAddress, + servedRelaysProducer: servedRelaysProducer, } } diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index d7a932a2e..1671e0d73 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -52,6 +52,9 @@ type relayerProxy struct { // the client that relays the request to the supported native service. advertisedRelayServers RelayServersMap + // nativeServiceListenAddresses is a map of the native services server's listen addresses + // that are supported by the relayer proxy. + nativeServicesListenAddress map[serviceId]string // servedRelays is an observable that notifies the miner about the relays that have been served. servedRelays observable.Observable[*types.Relay] @@ -66,7 +69,7 @@ func NewRelayerProxy( clientCtx sdkclient.Context, keyName string, keyring keyring.Keyring, - + nativeServicesListenAddress map[serviceId]string, // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. // blockClient blocktypes.BlockClient, ) RelayerProxy { @@ -78,13 +81,14 @@ func NewRelayerProxy( return &relayerProxy{ // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. // blockClient: blockClient, - keyName: keyName, - keyring: keyring, - accountsQuerier: accountQuerier, - supplierQuerier: supplierQuerier, - sessionQuerier: sessionQuerier, - servedRelays: servedRelays, - servedRelaysProducer: servedRelaysProducer, + keyName: keyName, + keyring: keyring, + accountsQuerier: accountQuerier, + supplierQuerier: supplierQuerier, + sessionQuerier: sessionQuerier, + nativeServicesListenAddress: nativeServicesListenAddress, + servedRelays: servedRelays, + servedRelaysProducer: servedRelaysProducer, } } @@ -131,3 +135,13 @@ func (rp *relayerProxy) Stop(ctx context.Context) error { func (rp *relayerProxy) ServedRelays() observable.Observable[*types.Relay] { return rp.servedRelays } + +// VerifyRelayRequest is a shared method used by RelayServers to check the relay request signature and session validity. +func (rp *relayerProxy) VerifyRelayRequest(relayRequest *types.RelayRequest) (isValid bool, err error) { + panic("TODO: implement relayerProxy.VerifyRelayRequest") +} + +// SignRelayResponse is a shared method used by RelayServers to sign the relay response. +func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) { + panic("TODO: implement relayerProxy.SignRelayResponse") +} diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go index d77341948..60e491048 100644 --- a/pkg/relayer/proxy/server_builder.go +++ b/pkg/relayer/proxy/server_builder.go @@ -30,6 +30,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { providedServices := make(RelayServersMap) for _, serviceConfig := range services { serviceId := serviceConfig.Id.Id + nativeServiceListenAddress := rp.nativeServicesListenAddress[serviceId] serviceEndpoints := make([]RelayServer, len(serviceConfig.Endpoints)) for _, endpoint := range serviceConfig.Endpoints { @@ -38,7 +39,13 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { // Switch to the RPC type to create the appropriate RelayServer switch endpoint.RpcType { case sharedtypes.RPCType_JSON_RPC: - server = NewJSONRPCServer(serviceId, endpoint.Url, rp) + server = NewJSONRPCServer( + serviceId, + endpoint.Url, + nativeServiceListenAddress, + rp.servedRelaysProducer, + rp, + ) default: return ErrUnsupportedRPCType } From 69698c971d320168437feffcd4aa9f9e8ee771ce Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 25 Oct 2023 12:33:24 +0200 Subject: [PATCH 04/19] fix: interface assignment --- pkg/relayer/proxy/jsonrpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 11bdbe6d3..88fe48359 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -7,7 +7,7 @@ import ( "pocket/x/service/types" ) -var _ RelayServer = &jsonRPCServer{} +var _ RelayServer = (*jsonRPCServer)(nil) type jsonRPCServer struct { // serviceId is the id of the service that the server is responsible for. From c81c3f6be75335dda04ae6964b4b2234478c85cb Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Thu, 26 Oct 2023 00:13:14 +0200 Subject: [PATCH 05/19] feat: implement relayer proxy full workflow --- pkg/relayer/proxy/error_reply.go | 41 ++++++++++++++ pkg/relayer/proxy/errors.go | 3 + pkg/relayer/proxy/interface.go | 2 +- pkg/relayer/proxy/jsonrpc.go | 86 +++++++++++++++++++++++++++++ pkg/relayer/proxy/proxy.go | 29 ++++------ pkg/relayer/proxy/relay_builders.go | 59 ++++++++++++++++++++ pkg/relayer/proxy/relay_signer.go | 21 +++++++ pkg/relayer/proxy/relay_verifier.go | 66 ++++++++++++++++++++++ pkg/relayer/proxy/server_builder.go | 1 + 9 files changed, 289 insertions(+), 19 deletions(-) create mode 100644 pkg/relayer/proxy/error_reply.go create mode 100644 pkg/relayer/proxy/relay_builders.go create mode 100644 pkg/relayer/proxy/relay_signer.go create mode 100644 pkg/relayer/proxy/relay_verifier.go diff --git a/pkg/relayer/proxy/error_reply.go b/pkg/relayer/proxy/error_reply.go new file mode 100644 index 000000000..20850188a --- /dev/null +++ b/pkg/relayer/proxy/error_reply.go @@ -0,0 +1,41 @@ +package proxy + +import ( + "log" + "net/http" + + "pocket/x/service/types" +) + +// replyWithError builds a JSONRPCResponseError from the passed in error and writes it to the writer. +// TODO_IMPROVE: This method should be aware of the request id and use it in the response by having +// the caller pass it along with the error if available. +// TODO_IMPROVE: This method should be aware of the nature of the error to use the appropriate JSONRPC +// Code, Message and Data. Possibly by augmenting the passed in error with the adequate information. +func (j *jsonRPCServer) replyWithError(writer http.ResponseWriter, err error) { + relayResponse := &types.RelayResponse{ + Payload: &types.RelayResponse_JsonRpcPayload{ + JsonRpcPayload: &types.JSONRPCResponsePayload{ + Id: make([]byte, 0), + Jsonrpc: "2.0", + Error: &types.JSONRPCResponseError{ + Code: -32000, + Message: err.Error(), + Data: nil, + }, + }, + }, + } + + relayResponseBz, err := relayResponse.Marshal() + if err != nil { + log.Printf("ERROR: failed marshaling relay response: %s", err) + return + } + + _, err = writer.Write(relayResponseBz) + if err != nil { + log.Printf("ERROR: failed writing relay response: %s", err) + return + } +} diff --git a/pkg/relayer/proxy/errors.go b/pkg/relayer/proxy/errors.go index ae0186af0..652832494 100644 --- a/pkg/relayer/proxy/errors.go +++ b/pkg/relayer/proxy/errors.go @@ -4,5 +4,8 @@ import errorsmod "cosmossdk.io/errors" var ( ErrUnsupportedRPCType = errorsmod.Register(codespace, 1, "unsupported rpc type") + ErrInvalidSignature = errorsmod.Register(codespace, 2, "invalid signature") + ErrInvalidSession = errorsmod.Register(codespace, 3, "invalid session") + ErrInvalidSupplier = errorsmod.Register(codespace, 4, "invalid supplier") codespace = "relayer/proxy" ) diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index c130f91ec..f1e067e3d 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -26,7 +26,7 @@ type RelayerProxy interface { // VerifyRelayRequest is a shared method used by RelayServers to check the // relay request signature and session validity. - VerifyRelayRequest(relayRequest *types.RelayRequest) (isValid bool, err error) + VerifyRelayRequest(ctx context.Context, relayRequest *types.RelayRequest, serviceId string) error // SignRelayResponse is a shared method used by RelayServers to sign the relay response. SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 88fe48359..97bc22b05 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -1,8 +1,11 @@ package proxy import ( + "bytes" "context" + "io" "net/http" + "net/url" "pocket/x/service/types" ) @@ -78,4 +81,87 @@ func (j *jsonRPCServer) ServiceId() string { // when jsonRPCServer is used as an http.Handler with an http.Server. // (see https://pkg.go.dev/net/http#Handler) func (j *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + ctx := request.Context() + // relay the request to the native service and build the response to be sent back to the client. + relay, err := j.serveHTTP(ctx, request) + if err != nil { + // Reply with an error if relay response could not be built. + j.replyWithError(writer, err) + return + } + + // Send the relay response to the client. + if err := j.sendRelayResponse(relay.Res, writer); err != nil { + j.replyWithError(writer, err) + return + } + + // Emit the relay to the servedRelays observable. + j.servedRelaysProducer <- relay +} + +// serveHTTP holds the underlying logic of ServeHTTP. +func (j *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (*types.Relay, error) { + // Extract the relay request from the request body + relayRequest, err := j.newRelayRequest(request) + if err != nil { + return nil, err + } + + // Verify the relay request signature and session + if err := j.relayerProxy.VerifyRelayRequest(ctx, relayRequest, j.serviceId); err != nil { + return nil, err + } + + // Get the relayRequest payload reader + var payloadBz []byte + if _, err = relayRequest.Payload.MarshalTo(payloadBz); err != nil { + return nil, err + } + requestBodyReader := io.NopCloser(bytes.NewBuffer(payloadBz)) + + // Build the request to be sent to the native service + destinationURL, err := url.Parse(request.URL.String()) + if err != nil { + return nil, err + } + destinationURL.Host = j.nativeServiceListenAddress + + relayHTTPRequest := &http.Request{ + Method: request.Method, + Header: request.Header, + URL: destinationURL, + Host: destinationURL.Host, + Body: requestBodyReader, + } + + // Send the relay request to the native service + httpResponse, err := http.DefaultClient.Do(relayHTTPRequest) + if err != nil { + return nil, err + } + + // Build the relay response from the native service response + // Use relayRequest.Meta.SessionHeader on the relayResponse session header since it was verified to be valid. + relayResponse, err := j.newRelayResponse(httpResponse, relayRequest.Meta.SessionHeader) + if err != nil { + return nil, err + } + + return &types.Relay{Req: relayRequest, Res: relayResponse}, nil +} + +// sendRelayResponse marshals the relay response and sends it to the client. +func (j *jsonRPCServer) sendRelayResponse(relayResponse *types.RelayResponse, writer http.ResponseWriter) error { + relayResposeBz, err := relayResponse.Marshal() + if err != nil { + return err + } + + _, err = writer.Write(relayResposeBz) + if err != nil { + return err + } + + return nil } diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index 1671e0d73..ab80ab574 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -8,8 +8,7 @@ import ( accounttypes "github.com/cosmos/cosmos-sdk/x/auth/types" "golang.org/x/sync/errgroup" - // TODO_INCOMPLETE(@red-0ne): Import the appropriate block client interface once available. - // blocktypes "pocket/pkg/client" + blocktypes "pocket/pkg/client" "pocket/pkg/observable" "pocket/pkg/observable/channel" "pocket/x/service/types" @@ -32,8 +31,7 @@ type relayerProxy struct { // blocksClient is the client used to get the block at the latest height from the blockchain // and be notified of new incoming blocks. It is used to update the current session data. - // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. - // blockClient blocktypes.BlockClient + blockClient blocktypes.BlockClient // accountsQuerier is the querier used to get account data (e.g. app publicKey) from the blockchain, // which, in the context of the RelayerProxy, is used to verify the relay request signatures. @@ -62,6 +60,12 @@ type relayerProxy struct { // servedRelaysProducer is a channel that emits the relays that have been served so that the // servedRelays observable can fan out the notifications to its subscribers. servedRelaysProducer chan<- *types.Relay + + // clientCtx is the Cosmos' client context used to unmarshal query replies. + clientCtx sdkclient.Context + + // supplierAddress is the address of the supplier that the relayer proxy running for. + supplierAddress string } func NewRelayerProxy( @@ -70,8 +74,7 @@ func NewRelayerProxy( keyName string, keyring keyring.Keyring, nativeServicesListenAddress map[serviceId]string, - // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. - // blockClient blocktypes.BlockClient, + blockClient blocktypes.BlockClient, ) RelayerProxy { accountQuerier := accounttypes.NewQueryClient(clientCtx) supplierQuerier := suppliertypes.NewQueryClient(clientCtx) @@ -79,8 +82,7 @@ func NewRelayerProxy( servedRelays, servedRelaysProducer := channel.NewObservable[*types.Relay]() return &relayerProxy{ - // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. - // blockClient: blockClient, + blockClient: blockClient, keyName: keyName, keyring: keyring, accountsQuerier: accountQuerier, @@ -89,6 +91,7 @@ func NewRelayerProxy( nativeServicesListenAddress: nativeServicesListenAddress, servedRelays: servedRelays, servedRelaysProducer: servedRelaysProducer, + clientCtx: clientCtx, } } @@ -135,13 +138,3 @@ func (rp *relayerProxy) Stop(ctx context.Context) error { func (rp *relayerProxy) ServedRelays() observable.Observable[*types.Relay] { return rp.servedRelays } - -// VerifyRelayRequest is a shared method used by RelayServers to check the relay request signature and session validity. -func (rp *relayerProxy) VerifyRelayRequest(relayRequest *types.RelayRequest) (isValid bool, err error) { - panic("TODO: implement relayerProxy.VerifyRelayRequest") -} - -// SignRelayResponse is a shared method used by RelayServers to sign the relay response. -func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) { - panic("TODO: implement relayerProxy.SignRelayResponse") -} diff --git a/pkg/relayer/proxy/relay_builders.go b/pkg/relayer/proxy/relay_builders.go new file mode 100644 index 000000000..7ba09eb6e --- /dev/null +++ b/pkg/relayer/proxy/relay_builders.go @@ -0,0 +1,59 @@ +package proxy + +import ( + "io" + "net/http" + + "pocket/x/service/types" + sessiontypes "pocket/x/session/types" +) + +// newRelayRequest builds a RelayRequest from an http.Request. +func (j *jsonRPCServer) newRelayRequest(request *http.Request) (*types.RelayRequest, error) { + requestBz, err := io.ReadAll(request.Body) + if err != nil { + return nil, err + } + + var relayRequest types.RelayRequest + if err := relayRequest.Unmarshal(requestBz); err != nil { + return nil, err + } + + return &relayRequest, nil +} + +// newRelayResponse builds a RelayResponse from an http.Response and a SessionHeader. +// It also signs the RelayResponse and assigns it to RelayResponse.Meta.SupplierSignature. +// If the response has a non-nil body, it will be parsed as a JSONRPCResponsePayload. +func (j *jsonRPCServer) newRelayResponse( + response *http.Response, + sessionHeader *sessiontypes.SessionHeader, +) (*types.RelayResponse, error) { + relayResponse := &types.RelayResponse{ + Meta: &types.RelayResponseMetadata{SessionHeader: sessionHeader}, + } + + if response.Body != nil { + responseBz, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + jsonRPCResponse := &types.JSONRPCResponsePayload{} + if err := jsonRPCResponse.Unmarshal(responseBz); err != nil { + return nil, err + } + + relayResponse.Payload = &types.RelayResponse_JsonRpcPayload{JsonRpcPayload: jsonRPCResponse} + } + + // Sign the relay response and add the signature to the relay response metadata + signature, err := j.relayerProxy.SignRelayResponse(relayResponse) + if err != nil { + return nil, err + } + relayResponse.Meta.SupplierSignature = signature + + return relayResponse, nil +} diff --git a/pkg/relayer/proxy/relay_signer.go b/pkg/relayer/proxy/relay_signer.go new file mode 100644 index 000000000..0b4f5308d --- /dev/null +++ b/pkg/relayer/proxy/relay_signer.go @@ -0,0 +1,21 @@ +package proxy + +import ( + "github.com/cometbft/cometbft/crypto" + + "pocket/x/service/types" +) + +// SignRelayResponse is a shared method used by RelayServers to sign the relay response. +func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) { + var payloadBz []byte + _, err := relayResponse.Payload.MarshalTo(payloadBz) + if err != nil { + return nil, err + } + + hash := crypto.Sha256(payloadBz) + signature, _, err := rp.keyring.Sign(rp.keyName, hash) + + return signature, err +} diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go new file mode 100644 index 000000000..015c0665c --- /dev/null +++ b/pkg/relayer/proxy/relay_verifier.go @@ -0,0 +1,66 @@ +package proxy + +import ( + "github.com/cometbft/cometbft/crypto" + accounttypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "golang.org/x/exp/slices" + + "context" + "pocket/x/service/types" + sessiontypes "pocket/x/session/types" +) + +// VerifyRelayRequest is a shared method used by RelayServers to check the relay request signature and session validity. +func (rp *relayerProxy) VerifyRelayRequest( + ctx context.Context, + relayRequest *types.RelayRequest, + serviceId serviceId, +) error { + // Query for the application account to get the application's public key to verify the relay request signature. + applicationAddress := relayRequest.Meta.SessionHeader.ApplicationAddress + accountQuery := &accounttypes.QueryAccountRequest{Address: applicationAddress} + accountResponse, err := rp.accountsQuerier.Account(ctx, accountQuery) + if err != nil { + return err + } + + var payloadBz []byte + _, err = relayRequest.Payload.MarshalTo(payloadBz) + if err != nil { + return err + } + hash := crypto.Sha256(payloadBz) + + // accountResponse.Account.Value is a protobuf Any type that should be unmarshaled into an AccountI interface. + // TODO_DISCUSS: Make sure this is the correct way to unmarshal an AccountI from an Any type. + var account accounttypes.AccountI + if err := rp.clientCtx.Codec.UnmarshalJSON(accountResponse.Account.Value, account); err != nil { + return err + } + + if !account.GetPubKey().VerifySignature(hash, relayRequest.Meta.Signature) { + return ErrInvalidSignature + } + + // Query for the current session to check if relayRequest sessionId matches the current session. + currentBlock := rp.blockClient.LatestBlock(ctx) + requestSessionId := relayRequest.Meta.SessionHeader.SessionId + sessionQuery := &sessiontypes.QueryGetSessionRequest{ + ApplicationAddress: applicationAddress, + ServiceId: &sessiontypes.ServiceId{Id: serviceId}, + BlockHeight: currentBlock.Height(), + } + sessionResponse, err := rp.sessionQuerier.GetSession(ctx, sessionQuery) + session := sessionResponse.Session + + if session.SessionId != requestSessionId { + return ErrInvalidSession + } + + // Check if the relayRequest is allowed to be served by the relayer proxy. + if !slices.Contains(session.Suppliers, rp.supplierAddress) { + return ErrInvalidSupplier + } + + return nil +} diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go index 60e491048..ab59cd642 100644 --- a/pkg/relayer/proxy/server_builder.go +++ b/pkg/relayer/proxy/server_builder.go @@ -57,6 +57,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { } rp.advertisedRelayServers = providedServices + rp.supplierAddress = supplierQueryResponse.Supplier.Address return nil } From edbd628e9146e232ef58c71cfa8f4be2135cdb50 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Thu, 26 Oct 2023 00:32:02 +0200 Subject: [PATCH 06/19] chore: improve code comments --- pkg/relayer/proxy/jsonrpc.go | 35 ++++++++++++++++------------- pkg/relayer/proxy/proxy.go | 4 ++-- pkg/relayer/proxy/relay_signer.go | 3 ++- pkg/relayer/proxy/relay_verifier.go | 9 ++++++-- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 97bc22b05..3b08a87fa 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -13,30 +13,30 @@ import ( var _ RelayServer = (*jsonRPCServer)(nil) type jsonRPCServer struct { - // serviceId is the id of the service that the server is responsible for. + // serviceId is the identifier of the service that the server is responsible for. serviceId string - // endpointUrl is the url that the server listens to for incoming relay requests. + // endpointUrl is the URL that the server listens to for incoming relay requests. endpointUrl string - // nativeServiceListenAddress is the address of the native service that the server relays requests to. + // nativeServiceListenAddress is the address of the native service to which the server relays requests. nativeServiceListenAddress string - // server is the http server that listens for incoming relay requests. + // server is the HTTP server that listens for incoming relay requests. server *http.Server // relayerProxy is the main relayer proxy that the server uses to perform its operations. relayerProxy RelayerProxy - // servedRelaysProducer is a channel that emits the relays that have been served so that the - // servedRelays observable can fan out the notifications to its subscribers. + // servedRelaysProducer is a channel that emits the relays that have been served, allowing + // the servedRelays observable to fan-out notifications to its subscribers. servedRelaysProducer chan<- *types.Relay } // NewJSONRPCServer creates a new HTTP server that listens for incoming relay requests -// and proxies them to the supported native service. +// and relays them to the supported native service. // It takes the serviceId, endpointUrl, and the main RelayerProxy as arguments and returns -// a RelayServer that listens to incoming RelayRequests +// a RelayServer that listens to incoming RelayRequests. func NewJSONRPCServer( serviceId string, endpointUrl string, @@ -55,7 +55,7 @@ func NewJSONRPCServer( } // Start starts the service server and returns an error if it fails. -// It also waits for the passed in context to be done to shut down. +// It also waits for the passed-in context to be done in order to shut down. // This method is blocking and should be called in a goroutine. func (j *jsonRPCServer) Start(ctx context.Context) error { go func() { @@ -82,7 +82,7 @@ func (j *jsonRPCServer) ServiceId() string { // (see https://pkg.go.dev/net/http#Handler) func (j *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { ctx := request.Context() - // relay the request to the native service and build the response to be sent back to the client. + // Relay the request to the native service and build the response that will be sent back to the client. relay, err := j.serveHTTP(ctx, request) if err != nil { // Reply with an error if relay response could not be built. @@ -102,25 +102,27 @@ func (j *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.Requ // serveHTTP holds the underlying logic of ServeHTTP. func (j *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (*types.Relay, error) { - // Extract the relay request from the request body + // Extract the relay request from the request body. relayRequest, err := j.newRelayRequest(request) if err != nil { return nil, err } - // Verify the relay request signature and session + // Verify the relay request signature and session. if err := j.relayerProxy.VerifyRelayRequest(ctx, relayRequest, j.serviceId); err != nil { return nil, err } - // Get the relayRequest payload reader + // Get the relayRequest payload's ReadCloser to add it to the http.Request + // that will be sent to the native service. var payloadBz []byte if _, err = relayRequest.Payload.MarshalTo(payloadBz); err != nil { return nil, err } requestBodyReader := io.NopCloser(bytes.NewBuffer(payloadBz)) - // Build the request to be sent to the native service + // Build the request to be sent to the native service by substituting + // the destination URL's host with the native service's listen address. destinationURL, err := url.Parse(request.URL.String()) if err != nil { return nil, err @@ -135,14 +137,15 @@ func (j *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (* Body: requestBodyReader, } - // Send the relay request to the native service + // Send the relay request to the native service. httpResponse, err := http.DefaultClient.Do(relayHTTPRequest) if err != nil { return nil, err } // Build the relay response from the native service response - // Use relayRequest.Meta.SessionHeader on the relayResponse session header since it was verified to be valid. + // Use relayRequest.Meta.SessionHeader on the relayResponse session header since it was verified to be valid + // and has to be the same as the relayResponse session header. relayResponse, err := j.newRelayResponse(httpResponse, relayRequest.Meta.SessionHeader) if err != nil { return nil, err diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index ab80ab574..c160c18ad 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -61,10 +61,10 @@ type relayerProxy struct { // servedRelays observable can fan out the notifications to its subscribers. servedRelaysProducer chan<- *types.Relay - // clientCtx is the Cosmos' client context used to unmarshal query replies. + // clientCtx is the Cosmos' client context used to build the needed query clients and unmarshal their replies. clientCtx sdkclient.Context - // supplierAddress is the address of the supplier that the relayer proxy running for. + // supplierAddress is the address of the supplier that the relayer proxy is running for. supplierAddress string } diff --git a/pkg/relayer/proxy/relay_signer.go b/pkg/relayer/proxy/relay_signer.go index 0b4f5308d..26ff9097c 100644 --- a/pkg/relayer/proxy/relay_signer.go +++ b/pkg/relayer/proxy/relay_signer.go @@ -6,7 +6,8 @@ import ( "pocket/x/service/types" ) -// SignRelayResponse is a shared method used by RelayServers to sign the relay response. +// SignRelayResponse is a shared method used by the RelayServers to sign a RelayResponse.Payload. +// It uses the keyring and keyName to sign the payload and returns the signature. func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) { var payloadBz []byte _, err := relayResponse.Payload.MarshalTo(payloadBz) diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index 015c0665c..412636cac 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -44,7 +44,6 @@ func (rp *relayerProxy) VerifyRelayRequest( // Query for the current session to check if relayRequest sessionId matches the current session. currentBlock := rp.blockClient.LatestBlock(ctx) - requestSessionId := relayRequest.Meta.SessionHeader.SessionId sessionQuery := &sessiontypes.QueryGetSessionRequest{ ApplicationAddress: applicationAddress, ServiceId: &sessiontypes.ServiceId{Id: serviceId}, @@ -53,7 +52,13 @@ func (rp *relayerProxy) VerifyRelayRequest( sessionResponse, err := rp.sessionQuerier.GetSession(ctx, sessionQuery) session := sessionResponse.Session - if session.SessionId != requestSessionId { + // Since the retrieved sessionId was in terms of: + // - the current block height (which is not provided by the relayRequest) + // - serviceId (which is not provided by the relayRequest) + // - applicationAddress (which is used to to verify the relayRequest signature) + // we can reduce the session validity check to checking if the retrieved session's sessionId + // matches the relayRequest sessionId. + if session.SessionId != relayRequest.Meta.SessionHeader.SessionId { return ErrInvalidSession } From 08e1915b3156a1d45f2f2d45cdc174261cc8a872 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Thu, 26 Oct 2023 15:38:45 +0200 Subject: [PATCH 07/19] chore: change native terms to proxied --- pkg/relayer/proxy/errors.go | 4 +-- pkg/relayer/proxy/interface.go | 3 +- pkg/relayer/proxy/jsonrpc.go | 40 +++++++++++++----------- pkg/relayer/proxy/proxy.go | 48 +++++++++++++++-------------- pkg/relayer/proxy/server_builder.go | 14 ++++----- proto/pocket/shared/supplier.proto | 1 + 6 files changed, 59 insertions(+), 51 deletions(-) diff --git a/pkg/relayer/proxy/errors.go b/pkg/relayer/proxy/errors.go index ae0186af0..1aa42ab7e 100644 --- a/pkg/relayer/proxy/errors.go +++ b/pkg/relayer/proxy/errors.go @@ -1,8 +1,8 @@ package proxy -import errorsmod "cosmossdk.io/errors" +import sdkerrors "cosmossdk.io/errors" var ( - ErrUnsupportedRPCType = errorsmod.Register(codespace, 1, "unsupported rpc type") codespace = "relayer/proxy" + ErrUnsupportedRPCType = sdkerrors.Register(codespace, 1, "unsupported rpc type") ) diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index c130f91ec..3987c757c 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -5,6 +5,7 @@ import ( "pocket/pkg/observable" "pocket/x/service/types" + sharedtypes "pocket/x/shared/types" ) // RelayerProxy is the interface for the proxy that serves relays to the application. @@ -41,5 +42,5 @@ type RelayServer interface { Stop(ctx context.Context) error // ServiceId returns the serviceId of the service. - ServiceId() string + ServiceId() *sharedtypes.ServiceId } diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 88fe48359..3c105b429 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -3,21 +3,24 @@ package proxy import ( "context" "net/http" + "net/url" "pocket/x/service/types" + sharedtypes "pocket/x/shared/types" ) var _ RelayServer = (*jsonRPCServer)(nil) type jsonRPCServer struct { // serviceId is the id of the service that the server is responsible for. - serviceId string + serviceId *sharedtypes.ServiceId - // endpointUrl is the url that the server listens to for incoming relay requests. - endpointUrl string + // serverEndpoint is the advertised endpoint configuration that the server uses to + // listen for incoming relay requests. + serverEndpoint *sharedtypes.SupplierEndpoint - // nativeServiceListenAddress is the address of the native service that the server relays requests to. - nativeServiceListenAddress string + // proxiedServiceEndpoint is the address of the proxied service that the server relays requests to. + proxiedServiceEndpoint url.URL // server is the http server that listens for incoming relay requests. server *http.Server @@ -31,28 +34,28 @@ type jsonRPCServer struct { } // NewJSONRPCServer creates a new HTTP server that listens for incoming relay requests -// and proxies them to the supported native service. +// and forwards them to the supported proxied service endpoint. // It takes the serviceId, endpointUrl, and the main RelayerProxy as arguments and returns // a RelayServer that listens to incoming RelayRequests func NewJSONRPCServer( - serviceId string, - endpointUrl string, - nativeServiceListenAddress string, + serviceId *sharedtypes.ServiceId, + supplierEndpoint *sharedtypes.SupplierEndpoint, + proxiedServiceEndpoint url.URL, servedRelaysProducer chan<- *types.Relay, proxy RelayerProxy, ) RelayServer { return &jsonRPCServer{ - serviceId: serviceId, - endpointUrl: endpointUrl, - server: &http.Server{Addr: endpointUrl}, - relayerProxy: proxy, - nativeServiceListenAddress: nativeServiceListenAddress, - servedRelaysProducer: servedRelaysProducer, + serviceId: serviceId, + serverEndpoint: supplierEndpoint, + server: &http.Server{Addr: supplierEndpoint.Url}, + relayerProxy: proxy, + proxiedServiceEndpoint: proxiedServiceEndpoint, + servedRelaysProducer: servedRelaysProducer, } } // Start starts the service server and returns an error if it fails. -// It also waits for the passed in context to be done to shut down. +// It also waits for the passed in context to end before shutting down. // This method is blocking and should be called in a goroutine. func (j *jsonRPCServer) Start(ctx context.Context) error { go func() { @@ -68,8 +71,8 @@ func (j *jsonRPCServer) Stop(ctx context.Context) error { return j.server.Shutdown(ctx) } -// ServiceId returns the serviceId of the service. -func (j *jsonRPCServer) ServiceId() string { +// ServiceId returns the serviceId of the JSON-RPC service. +func (j *jsonRPCServer) ServiceId() *sharedtypes.ServiceId { return j.serviceId } @@ -78,4 +81,5 @@ func (j *jsonRPCServer) ServiceId() string { // when jsonRPCServer is used as an http.Handler with an http.Server. // (see https://pkg.go.dev/net/http#Handler) func (j *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + panic("TODO: implement jsonRPCServer.ServeHTTP") } diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index 1671e0d73..2e14459a3 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -2,6 +2,7 @@ package proxy import ( "context" + "net/url" sdkclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -20,8 +21,9 @@ import ( var _ RelayerProxy = (*relayerProxy)(nil) type ( - serviceId = string - RelayServersMap = map[serviceId][]RelayServer + serviceId = string + relayServersMap = map[serviceId][]RelayServer + servicesEndpointsMap = map[serviceId]url.URL ) type relayerProxy struct { @@ -49,12 +51,11 @@ type relayerProxy struct { // advertisedRelayServers is a map of the services provided by the relayer proxy. Each provided service // has the necessary information to start the server that listens for incoming relay requests and - // the client that relays the request to the supported native service. - advertisedRelayServers RelayServersMap + // the client that relays the request to the supported proxied service. + advertisedRelayServers relayServersMap - // nativeServiceListenAddresses is a map of the native services server's listen addresses - // that are supported by the relayer proxy. - nativeServicesListenAddress map[serviceId]string + // proxiedServicesEndpoints is a map of the proxied services endpoints that the relayer proxy supports. + proxiedServicesEndpoints servicesEndpointsMap // servedRelays is an observable that notifies the miner about the relays that have been served. servedRelays observable.Observable[*types.Relay] @@ -69,7 +70,7 @@ func NewRelayerProxy( clientCtx sdkclient.Context, keyName string, keyring keyring.Keyring, - nativeServicesListenAddress map[serviceId]string, + proxiedServicesEndpoints servicesEndpointsMap, // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. // blockClient blocktypes.BlockClient, ) RelayerProxy { @@ -81,34 +82,34 @@ func NewRelayerProxy( return &relayerProxy{ // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. // blockClient: blockClient, - keyName: keyName, - keyring: keyring, - accountsQuerier: accountQuerier, - supplierQuerier: supplierQuerier, - sessionQuerier: sessionQuerier, - nativeServicesListenAddress: nativeServicesListenAddress, - servedRelays: servedRelays, - servedRelaysProducer: servedRelaysProducer, + keyName: keyName, + keyring: keyring, + accountsQuerier: accountQuerier, + supplierQuerier: supplierQuerier, + sessionQuerier: sessionQuerier, + proxiedServicesEndpoints: proxiedServicesEndpoints, + servedRelays: servedRelays, + servedRelaysProducer: servedRelaysProducer, } } // Start concurrently starts all advertised relay servers and returns an error if any of them fails to start. +// This method is blocking until all RelayServers are started. func (rp *relayerProxy) Start(ctx context.Context) error { // The provided services map is built from the supplier's on-chain advertised information, // which is a runtime parameter that can be changed by the supplier. - // Build the provided services map at Start instead of NewRelayerProxy to avoid having to + // NOTE: We build the provided services map at Start instead of NewRelayerProxy to avoid having to // return an error from the constructor. - err := rp.BuildProvidedServices(ctx) - if err != nil { + if err := rp.BuildProvidedServices(ctx); err != nil { return err } - startGroup, gctx := errgroup.WithContext(ctx) + startGroup, ctx := errgroup.WithContext(ctx) for _, relayServer := range rp.advertisedRelayServers { for _, svr := range relayServer { server := svr // create a new variable scoped to the anonymous function - startGroup.Go(func() error { return server.Start(gctx) }) + startGroup.Go(func() error { return server.Start(ctx) }) } } @@ -116,13 +117,14 @@ func (rp *relayerProxy) Start(ctx context.Context) error { } // Stop concurrently stops all advertised relay servers and returns an error if any of them fails. +// This method is blocking until all RelayServers are stopped. func (rp *relayerProxy) Stop(ctx context.Context) error { - stopGroup, gctx := errgroup.WithContext(ctx) + stopGroup, ctx := errgroup.WithContext(ctx) for _, providedService := range rp.advertisedRelayServers { for _, svr := range providedService { server := svr // create a new variable scoped to the anonymous function - stopGroup.Go(func() error { return server.Stop(gctx) }) + stopGroup.Go(func() error { return server.Stop(ctx) }) } } diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go index 60e491048..66b508989 100644 --- a/pkg/relayer/proxy/server_builder.go +++ b/pkg/relayer/proxy/server_builder.go @@ -9,7 +9,7 @@ import ( // BuildProvidedServices builds the advertised relay servers from the supplier's on-chain advertised services. // It populates the relayerProxy's `advertisedRelayServers` map of servers for each service, where each server -// is responsible for listening for incoming relay requests and relaying them to the supported native service. +// is responsible for listening for incoming relay requests and relaying them to the supported proxied service. func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { // Get the supplier address from the keyring supplierAddress, err := rp.keyring.Key(rp.keyName) @@ -27,10 +27,10 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { services := supplierQueryResponse.Supplier.Services // Build the advertised relay servers map. For each service's endpoint, create the appropriate RelayServer. - providedServices := make(RelayServersMap) + providedServices := make(relayServersMap) for _, serviceConfig := range services { - serviceId := serviceConfig.Id.Id - nativeServiceListenAddress := rp.nativeServicesListenAddress[serviceId] + serviceId := serviceConfig.Id + proxiedServicesEndpoints := rp.proxiedServicesEndpoints[serviceId.Id] serviceEndpoints := make([]RelayServer, len(serviceConfig.Endpoints)) for _, endpoint := range serviceConfig.Endpoints { @@ -41,8 +41,8 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { case sharedtypes.RPCType_JSON_RPC: server = NewJSONRPCServer( serviceId, - endpoint.Url, - nativeServiceListenAddress, + endpoint, + proxiedServicesEndpoints, rp.servedRelaysProducer, rp, ) @@ -53,7 +53,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { serviceEndpoints = append(serviceEndpoints, server) } - providedServices[serviceId] = serviceEndpoints + providedServices[serviceId.Id] = serviceEndpoints } rp.advertisedRelayServers = providedServices diff --git a/proto/pocket/shared/supplier.proto b/proto/pocket/shared/supplier.proto index 4cc315ad6..d965fc9df 100644 --- a/proto/pocket/shared/supplier.proto +++ b/proto/pocket/shared/supplier.proto @@ -14,5 +14,6 @@ import "pocket/shared/service.proto"; message Supplier { string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the supplier using cosmos' ScalarDescriptor to ensure deterministic deterministic encoding using cosmos' ScalarDescriptor to ensure deterministic deterministic encoding cosmos.base.v1beta1.Coin stake = 2; // The total amount of uPOKT the supplier has staked + // TODO(@olshansk): This is uncommented for integration purposes but business logic is not yet implemented. repeated SupplierServiceConfig services = 3; // The service configs this supplier can support } From 1b835aade5f893b96c79522ccb67ac84f18b3411 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Thu, 26 Oct 2023 20:27:33 +0200 Subject: [PATCH 08/19] chore: address change requests --- pkg/relayer/proxy/error_reply.go | 7 ++-- pkg/relayer/proxy/errors.go | 10 +++--- pkg/relayer/proxy/interface.go | 2 +- pkg/relayer/proxy/jsonrpc.go | 51 ++++++++++++++--------------- pkg/relayer/proxy/proxy.go | 6 ++-- pkg/relayer/proxy/relay_builders.go | 22 ++++++------- pkg/relayer/proxy/relay_verifier.go | 12 ++++--- 7 files changed, 52 insertions(+), 58 deletions(-) diff --git a/pkg/relayer/proxy/error_reply.go b/pkg/relayer/proxy/error_reply.go index 20850188a..1b4b98942 100644 --- a/pkg/relayer/proxy/error_reply.go +++ b/pkg/relayer/proxy/error_reply.go @@ -8,9 +8,9 @@ import ( ) // replyWithError builds a JSONRPCResponseError from the passed in error and writes it to the writer. -// TODO_IMPROVE: This method should be aware of the request id and use it in the response by having +// TODO_TECHDEBT: This method should be aware of the request id and use it in the response by having // the caller pass it along with the error if available. -// TODO_IMPROVE: This method should be aware of the nature of the error to use the appropriate JSONRPC +// TODO_TECHDEBT: This method should be aware of the nature of the error to use the appropriate JSONRPC // Code, Message and Data. Possibly by augmenting the passed in error with the adequate information. func (j *jsonRPCServer) replyWithError(writer http.ResponseWriter, err error) { relayResponse := &types.RelayResponse{ @@ -33,8 +33,7 @@ func (j *jsonRPCServer) replyWithError(writer http.ResponseWriter, err error) { return } - _, err = writer.Write(relayResponseBz) - if err != nil { + if _, err = writer.Write(relayResponseBz); err != nil { log.Printf("ERROR: failed writing relay response: %s", err) return } diff --git a/pkg/relayer/proxy/errors.go b/pkg/relayer/proxy/errors.go index 7677da69c..4d2b56241 100644 --- a/pkg/relayer/proxy/errors.go +++ b/pkg/relayer/proxy/errors.go @@ -3,9 +3,9 @@ package proxy import sdkerrors "cosmossdk.io/errors" var ( - codespace = "relayer/proxy" - ErrUnsupportedRPCType = sdkerrors.Register(codespace, 1, "unsupported rpc type") - ErrInvalidSignature = sdkerrors.Register(codespace, 2, "invalid signature") - ErrInvalidSession = sdkerrors.Register(codespace, 3, "invalid session") - ErrInvalidSupplier = sdkerrors.Register(codespace, 4, "invalid supplier") + codespace = "relayer/proxy" + ErrUnsupportedRPCType = sdkerrors.Register(codespace, 1, "unsupported rpc type") + ErrInvalidRelayRequestSignature = sdkerrors.Register(codespace, 2, "invalid relay request signature") + ErrInvalidSession = sdkerrors.Register(codespace, 3, "invalid session") + ErrInvalidSupplier = sdkerrors.Register(codespace, 4, "invalid supplier") ) diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/proxy/interface.go index 83b133d9f..c3f0a5bd4 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/proxy/interface.go @@ -27,7 +27,7 @@ type RelayerProxy interface { // VerifyRelayRequest is a shared method used by RelayServers to check the // relay request signature and session validity. - VerifyRelayRequest(ctx context.Context, relayRequest *types.RelayRequest, serviceId string) error + VerifyRelayRequest(ctx context.Context, relayRequest *types.RelayRequest, serviceId *sharedtypes.ServiceId) error // SignRelayResponse is a shared method used by RelayServers to sign the relay response. SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index c278d2f9c..b62113a84 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -59,64 +59,65 @@ func NewJSONRPCServer( // Start starts the service server and returns an error if it fails. // It also waits for the passed in context to end before shutting down. // This method is blocking and should be called in a goroutine. -func (j *jsonRPCServer) Start(ctx context.Context) error { +func (jsrv *jsonRPCServer) Start(ctx context.Context) error { go func() { <-ctx.Done() - j.server.Shutdown(ctx) + jsrv.server.Shutdown(ctx) }() - return j.server.ListenAndServe() + return jsrv.server.ListenAndServe() } // Stop terminates the service server and returns an error if it fails. -func (j *jsonRPCServer) Stop(ctx context.Context) error { - return j.server.Shutdown(ctx) +func (jsrv *jsonRPCServer) Stop(ctx context.Context) error { + return jsrv.server.Shutdown(ctx) } // ServiceId returns the serviceId of the JSON-RPC service. -func (j *jsonRPCServer) ServiceId() *sharedtypes.ServiceId { - return j.serviceId +func (jsrv *jsonRPCServer) ServiceId() *sharedtypes.ServiceId { + return jsrv.serviceId } // ServeHTTP listens for incoming relay requests. It implements the respective // method of the http.Handler interface. It is called by http.ListenAndServe() // when jsonRPCServer is used as an http.Handler with an http.Server. // (see https://pkg.go.dev/net/http#Handler) -func (j *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { +func (jsrv *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { ctx := request.Context() - // Relay the request to the native service and build the response that will be sent back to the client. - relay, err := j.serveHTTP(ctx, request) + // Relay the request to the proxied service and build the response that will be sent back to the client. + relay, err := jsrv.serveHTTP(ctx, request) if err != nil { // Reply with an error if relay response could not be built. - j.replyWithError(writer, err) + jsrv.replyWithError(writer, err) return } // Send the relay response to the client. - if err := j.sendRelayResponse(relay.Res, writer); err != nil { - j.replyWithError(writer, err) + if err := jsrv.sendRelayResponse(relay.Res, writer); err != nil { + jsrv.replyWithError(writer, err) return } // Emit the relay to the servedRelays observable. - j.servedRelaysProducer <- relay + jsrv.servedRelaysProducer <- relay } // serveHTTP holds the underlying logic of ServeHTTP. -func (j *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (*types.Relay, error) { +func (jsrv *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (*types.Relay, error) { // Extract the relay request from the request body. - relayRequest, err := j.newRelayRequest(request) + relayRequest, err := jsrv.newRelayRequest(request) if err != nil { return nil, err } // Verify the relay request signature and session. - if err := j.relayerProxy.VerifyRelayRequest(ctx, relayRequest, j.serviceId); err != nil { + if err := jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.serviceId); err != nil { return nil, err } - // Get the relayRequest payload's ReadCloser to add it to the http.Request + // Get the relayRequest payload's `io.ReadCloser` to add it to the http.Request // that will be sent to the native service. + // (see https://pkg.go.dev/net/http#Request) Body field type. var payloadBz []byte if _, err = relayRequest.Payload.MarshalTo(payloadBz); err != nil { return nil, err @@ -129,7 +130,7 @@ func (j *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (* if err != nil { return nil, err } - destinationURL.Host = j.nativeServiceListenAddress + destinationURL.Host = jsrv.proxiedServiceEndpoint.Host relayHTTPRequest := &http.Request{ Method: request.Method, @@ -148,7 +149,7 @@ func (j *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (* // Build the relay response from the native service response // Use relayRequest.Meta.SessionHeader on the relayResponse session header since it was verified to be valid // and has to be the same as the relayResponse session header. - relayResponse, err := j.newRelayResponse(httpResponse, relayRequest.Meta.SessionHeader) + relayResponse, err := jsrv.newRelayResponse(httpResponse, relayRequest.Meta.SessionHeader) if err != nil { return nil, err } @@ -158,15 +159,11 @@ func (j *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) (* // sendRelayResponse marshals the relay response and sends it to the client. func (j *jsonRPCServer) sendRelayResponse(relayResponse *types.RelayResponse, writer http.ResponseWriter) error { - relayResposeBz, err := relayResponse.Marshal() + relayResponseBz, err := relayResponse.Marshal() if err != nil { return err } - _, err = writer.Write(relayResposeBz) - if err != nil { - return err - } - - return nil + _, err = writer.Write(relayResponseBz) + return err } diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index 90a680e96..743a9ad16 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -75,8 +75,7 @@ func NewRelayerProxy( keyName string, keyring keyring.Keyring, proxiedServicesEndpoints servicesEndpointsMap, - // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. - // blockClient blocktypes.BlockClient, + blockClient blocktypes.BlockClient, ) RelayerProxy { accountQuerier := accounttypes.NewQueryClient(clientCtx) supplierQuerier := suppliertypes.NewQueryClient(clientCtx) @@ -84,8 +83,7 @@ func NewRelayerProxy( servedRelays, servedRelaysProducer := channel.NewObservable[*types.Relay]() return &relayerProxy{ - // TODO_INCOMPLETE(@red-0ne): Uncomment once the BlockClient interface is available. - // blockClient: blockClient, + blockClient: blockClient, keyName: keyName, keyring: keyring, accountsQuerier: accountQuerier, diff --git a/pkg/relayer/proxy/relay_builders.go b/pkg/relayer/proxy/relay_builders.go index 7ba09eb6e..2d31b6e9f 100644 --- a/pkg/relayer/proxy/relay_builders.go +++ b/pkg/relayer/proxy/relay_builders.go @@ -34,20 +34,18 @@ func (j *jsonRPCServer) newRelayResponse( Meta: &types.RelayResponseMetadata{SessionHeader: sessionHeader}, } - if response.Body != nil { - responseBz, err := io.ReadAll(response.Body) - if err != nil { - return nil, err - } - - jsonRPCResponse := &types.JSONRPCResponsePayload{} - if err := jsonRPCResponse.Unmarshal(responseBz); err != nil { - return nil, err - } - - relayResponse.Payload = &types.RelayResponse_JsonRpcPayload{JsonRpcPayload: jsonRPCResponse} + responseBz, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + jsonRPCResponse := &types.JSONRPCResponsePayload{} + if err := jsonRPCResponse.Unmarshal(responseBz); err != nil { + return nil, err } + relayResponse.Payload = &types.RelayResponse_JsonRpcPayload{JsonRpcPayload: jsonRPCResponse} + // Sign the relay response and add the signature to the relay response metadata signature, err := j.relayerProxy.SignRelayResponse(relayResponse) if err != nil { diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index 412636cac..3cef0bb95 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -8,18 +8,19 @@ import ( "context" "pocket/x/service/types" sessiontypes "pocket/x/session/types" + sharedtypes "pocket/x/shared/types" ) // VerifyRelayRequest is a shared method used by RelayServers to check the relay request signature and session validity. func (rp *relayerProxy) VerifyRelayRequest( ctx context.Context, relayRequest *types.RelayRequest, - serviceId serviceId, + serviceId *sharedtypes.ServiceId, ) error { // Query for the application account to get the application's public key to verify the relay request signature. applicationAddress := relayRequest.Meta.SessionHeader.ApplicationAddress - accountQuery := &accounttypes.QueryAccountRequest{Address: applicationAddress} - accountResponse, err := rp.accountsQuerier.Account(ctx, accountQuery) + accQueryReq := &accounttypes.QueryAccountRequest{Address: applicationAddress} + accQueryRes, err := rp.accountsQuerier.Account(ctx, accQueryReq) if err != nil { return err } @@ -34,12 +35,12 @@ func (rp *relayerProxy) VerifyRelayRequest( // accountResponse.Account.Value is a protobuf Any type that should be unmarshaled into an AccountI interface. // TODO_DISCUSS: Make sure this is the correct way to unmarshal an AccountI from an Any type. var account accounttypes.AccountI - if err := rp.clientCtx.Codec.UnmarshalJSON(accountResponse.Account.Value, account); err != nil { + if err := rp.clientCtx.Codec.UnmarshalJSON(accQueryRes.Account.Value, account); err != nil { return err } if !account.GetPubKey().VerifySignature(hash, relayRequest.Meta.Signature) { - return ErrInvalidSignature + return ErrInvalidRelayRequestSignature } // Query for the current session to check if relayRequest sessionId matches the current session. @@ -58,6 +59,7 @@ func (rp *relayerProxy) VerifyRelayRequest( // - applicationAddress (which is used to to verify the relayRequest signature) // we can reduce the session validity check to checking if the retrieved session's sessionId // matches the relayRequest sessionId. + // TODO_INVESTIGATE: Revisit the assumptions above at some point in the future, but good enough for now. if session.SessionId != relayRequest.Meta.SessionHeader.SessionId { return ErrInvalidSession } From 776e78cd3ad0aa4bd12095be19ebb47b81efb3eb Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Thu, 26 Oct 2023 20:32:44 +0200 Subject: [PATCH 09/19] chore: explain -32000 error code --- pkg/relayer/proxy/error_reply.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/relayer/proxy/error_reply.go b/pkg/relayer/proxy/error_reply.go index 1b4b98942..a13e856fd 100644 --- a/pkg/relayer/proxy/error_reply.go +++ b/pkg/relayer/proxy/error_reply.go @@ -19,6 +19,7 @@ func (j *jsonRPCServer) replyWithError(writer http.ResponseWriter, err error) { Id: make([]byte, 0), Jsonrpc: "2.0", Error: &types.JSONRPCResponseError{ + // Using conventional error code indicating internal server error. Code: -32000, Message: err.Error(), Data: nil, From fbba10626df79f6bf6e2218513dfdeb40a629790 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Thu, 26 Oct 2023 20:38:12 +0200 Subject: [PATCH 10/19] chore: add log message on relaying success/failure --- pkg/relayer/proxy/jsonrpc.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index b62113a84..137d6d78e 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "io" + "log" "net/http" "net/url" @@ -89,15 +90,19 @@ func (jsrv *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.R if err != nil { // Reply with an error if relay response could not be built. jsrv.replyWithError(writer, err) + log.Printf("WARN: failed serving relay request: %s", err) return } // Send the relay response to the client. if err := jsrv.sendRelayResponse(relay.Res, writer); err != nil { jsrv.replyWithError(writer, err) + log.Print("WARN: failed sending relay response: %s", err) return } + log.Print("INFO: relay request served successfully") + // Emit the relay to the servedRelays observable. jsrv.servedRelaysProducer <- relay } From 2e41656a782e35666d42a7ae0dc46c10adcb9a8e Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Fri, 27 Oct 2023 00:00:04 +0200 Subject: [PATCH 11/19] chore: Update AccountI/any unmarshaling comment Co-authored-by: Daniel Olshansky --- pkg/relayer/proxy/relay_verifier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index 3cef0bb95..27d04e866 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -33,7 +33,7 @@ func (rp *relayerProxy) VerifyRelayRequest( hash := crypto.Sha256(payloadBz) // accountResponse.Account.Value is a protobuf Any type that should be unmarshaled into an AccountI interface. - // TODO_DISCUSS: Make sure this is the correct way to unmarshal an AccountI from an Any type. + // TODO_TECHDEBT: Make sure our `AccountI`/`any` unmarshalling is correct. See https://github.com/pokt-network/poktroll/pull/101/files/edbd628e9146e232ef58c71cfa8f4be2135cdb50..fbba10626df79f6bf6e2218513dfdeb40a629790#r1372464439 var account accounttypes.AccountI if err := rp.clientCtx.Codec.UnmarshalJSON(accQueryRes.Account.Value, account); err != nil { return err From 46066a1ed1928ee7a71b372b83204f452d233a5a Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Tue, 7 Nov 2023 21:32:27 +0100 Subject: [PATCH 12/19] chore: update import paths to use GitHub organization name --- go.mod | 6 +++--- pkg/relayer/{proxy => }/interface.go | 2 +- pkg/relayer/proxy/error_reply.go | 2 +- pkg/relayer/proxy/jsonrpc.go | 9 +++++---- pkg/relayer/proxy/proxy.go | 7 ++++--- pkg/relayer/proxy/relay_builders.go | 4 ++-- pkg/relayer/proxy/relay_signer.go | 2 +- pkg/relayer/proxy/relay_verifier.go | 9 +++++---- pkg/relayer/proxy/server_builder.go | 5 +++-- proto/pocket/service/relay.proto | 9 +++------ 10 files changed, 28 insertions(+), 27 deletions(-) rename pkg/relayer/{proxy => }/interface.go (99%) diff --git a/go.mod b/go.mod index a8ef04265..c0d512a0f 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( cosmossdk.io/math v1.0.1 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 + github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 @@ -26,7 +27,9 @@ require ( github.com/stretchr/testify v1.8.4 go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df golang.org/x/sync v0.3.0 + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -70,7 +73,6 @@ require ( github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v0.20.0 // indirect @@ -254,7 +256,6 @@ require ( go.uber.org/dig v1.16.1 // indirect go.uber.org/fx v1.19.2 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect @@ -266,7 +267,6 @@ require ( gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/relayer/proxy/interface.go b/pkg/relayer/interface.go similarity index 99% rename from pkg/relayer/proxy/interface.go rename to pkg/relayer/interface.go index cc8ccc788..d8e50014d 100644 --- a/pkg/relayer/proxy/interface.go +++ b/pkg/relayer/interface.go @@ -1,4 +1,4 @@ -package proxy +package relayer import ( "context" diff --git a/pkg/relayer/proxy/error_reply.go b/pkg/relayer/proxy/error_reply.go index a13e856fd..d881057a0 100644 --- a/pkg/relayer/proxy/error_reply.go +++ b/pkg/relayer/proxy/error_reply.go @@ -4,7 +4,7 @@ import ( "log" "net/http" - "pocket/x/service/types" + "github.com/pokt-network/poktroll/x/service/types" ) // replyWithError builds a JSONRPCResponseError from the passed in error and writes it to the writer. diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 7cd4254a3..e8f666164 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -8,11 +8,12 @@ import ( "net/http" "net/url" + "github.com/pokt-network/poktroll/pkg/relayer" "github.com/pokt-network/poktroll/x/service/types" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) -var _ RelayServer = (*jsonRPCServer)(nil) +var _ relayer.RelayServer = (*jsonRPCServer)(nil) type jsonRPCServer struct { // serviceId is the id of the service that the server is responsible for. @@ -29,7 +30,7 @@ type jsonRPCServer struct { server *http.Server // relayerProxy is the main relayer proxy that the server uses to perform its operations. - relayerProxy RelayerProxy + relayerProxy relayer.RelayerProxy // servedRelaysProducer is a channel that emits the relays that have been served, allowing // the servedRelays observable to fan-out notifications to its subscribers. @@ -45,8 +46,8 @@ func NewJSONRPCServer( supplierEndpoint *sharedtypes.SupplierEndpoint, proxiedServiceEndpoint url.URL, servedRelaysProducer chan<- *types.Relay, - proxy RelayerProxy, -) RelayServer { + proxy relayer.RelayerProxy, +) relayer.RelayServer { return &jsonRPCServer{ serviceId: serviceId, serverEndpoint: supplierEndpoint, diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index c3514f46e..16edcf16f 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -12,16 +12,17 @@ import ( blocktypes "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/observable" "github.com/pokt-network/poktroll/pkg/observable/channel" + "github.com/pokt-network/poktroll/pkg/relayer" "github.com/pokt-network/poktroll/x/service/types" sessiontypes "github.com/pokt-network/poktroll/x/session/types" suppliertypes "github.com/pokt-network/poktroll/x/supplier/types" ) -var _ RelayerProxy = (*relayerProxy)(nil) +var _ relayer.RelayerProxy = (*relayerProxy)(nil) type ( serviceId = string - relayServersMap = map[serviceId][]RelayServer + relayServersMap = map[serviceId][]relayer.RelayServer servicesEndpointsMap = map[serviceId]url.URL ) @@ -76,7 +77,7 @@ func NewRelayerProxy( keyring keyring.Keyring, proxiedServicesEndpoints servicesEndpointsMap, blockClient blocktypes.BlockClient, -) RelayerProxy { +) relayer.RelayerProxy { accountQuerier := accounttypes.NewQueryClient(clientCtx) supplierQuerier := suppliertypes.NewQueryClient(clientCtx) sessionQuerier := sessiontypes.NewQueryClient(clientCtx) diff --git a/pkg/relayer/proxy/relay_builders.go b/pkg/relayer/proxy/relay_builders.go index 2d31b6e9f..97bfeaab7 100644 --- a/pkg/relayer/proxy/relay_builders.go +++ b/pkg/relayer/proxy/relay_builders.go @@ -4,8 +4,8 @@ import ( "io" "net/http" - "pocket/x/service/types" - sessiontypes "pocket/x/session/types" + "github.com/pokt-network/poktroll/x/service/types" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" ) // newRelayRequest builds a RelayRequest from an http.Request. diff --git a/pkg/relayer/proxy/relay_signer.go b/pkg/relayer/proxy/relay_signer.go index 26ff9097c..5a4d296b4 100644 --- a/pkg/relayer/proxy/relay_signer.go +++ b/pkg/relayer/proxy/relay_signer.go @@ -3,7 +3,7 @@ package proxy import ( "github.com/cometbft/cometbft/crypto" - "pocket/x/service/types" + "github.com/pokt-network/poktroll/x/service/types" ) // SignRelayResponse is a shared method used by the RelayServers to sign a RelayResponse.Payload. diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index 27d04e866..821927726 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -1,14 +1,15 @@ package proxy import ( + "context" + "github.com/cometbft/cometbft/crypto" accounttypes "github.com/cosmos/cosmos-sdk/x/auth/types" "golang.org/x/exp/slices" - "context" - "pocket/x/service/types" - sessiontypes "pocket/x/session/types" - sharedtypes "pocket/x/shared/types" + "github.com/pokt-network/poktroll/x/service/types" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) // VerifyRelayRequest is a shared method used by RelayServers to check the relay request signature and session validity. diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go index e54867c18..d0b46b381 100644 --- a/pkg/relayer/proxy/server_builder.go +++ b/pkg/relayer/proxy/server_builder.go @@ -3,6 +3,7 @@ package proxy import ( "context" + "github.com/pokt-network/poktroll/pkg/relayer" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" suppliertypes "github.com/pokt-network/poktroll/x/supplier/types" ) @@ -31,10 +32,10 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { for _, serviceConfig := range services { serviceId := serviceConfig.ServiceId proxiedServicesEndpoints := rp.proxiedServicesEndpoints[serviceId.Id] - serviceEndpoints := make([]RelayServer, len(serviceConfig.Endpoints)) + serviceEndpoints := make([]relayer.RelayServer, len(serviceConfig.Endpoints)) for _, endpoint := range serviceConfig.Endpoints { - var server RelayServer + var server relayer.RelayServer // Switch to the RPC type to create the appropriate RelayServer switch endpoint.RpcType { diff --git a/proto/pocket/service/relay.proto b/proto/pocket/service/relay.proto index 3450a1e40..7c9e888e4 100644 --- a/proto/pocket/service/relay.proto +++ b/proto/pocket/service/relay.proto @@ -4,9 +4,8 @@ package pocket.service; option go_package = "github.com/pokt-network/poktroll/x/service/types"; import "cosmos_proto/cosmos.proto"; -// TODO(@Olshansk): Uncomment the line below once the `service.proto` is added. -// import "pocket/service/service.proto"; import "pocket/application/application.proto"; +import "pocket/session/session.proto"; // Relay contains both the RelayRequest (signed by the Application) and the RelayResponse (signed by the Supplier). // The serialized tuple is inserted into the SMST leaves as values in the Claim/Proof lifecycle. @@ -17,8 +16,7 @@ message Relay { // RelayRequestMetadata contains the metadata for a RelayRequest. message RelayRequestMetadata { - // TODO(@Olshansk): Uncomment the line below once the `service.proto` is added. - // session.SessionHeader session_header = 1; // Session header associated with the relay. + session.SessionHeader session_header = 1; // Session header associated with the relay. // TODO_COMMENT(@h5law): Add link or more details to how this is related to ring signatures once implemented. bytes signature = 2; // The request signature. This may be the application signature, or any gateway it delegated to. } @@ -77,8 +75,7 @@ message RelayResponse { // RelayResponseMetadata contains the metadata for a RelayResponse. message RelayResponseMetadata { - // TODO(@Olshansk): Uncomment the line below once the `service.proto` is added. - // session.SessionHeader session_header = 1; // Session header associated with the relay. + session.SessionHeader session_header = 1; // Session header associated with the relay. bytes supplier_signature = 2; // Signature of the supplier on the response. } From 5041f43ef3ca6b661105ab9042d2a06cd2918dcc Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 00:37:50 +0100 Subject: [PATCH 13/19] chore: address change requests --- pkg/relayer/proxy/jsonrpc.go | 7 ++++++- pkg/relayer/proxy/proxy.go | 6 +++++- pkg/relayer/proxy/proxy_test.go | 1 + pkg/relayer/proxy/relay_verifier.go | 21 ++++++++++++--------- 4 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 pkg/relayer/proxy/proxy_test.go diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index e8f666164..715d6018b 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -98,7 +98,7 @@ func (jsrv *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.R // Send the relay response to the client. if err := jsrv.sendRelayResponse(relay.Res, writer); err != nil { jsrv.replyWithError(writer, err) - log.Print("WARN: failed sending relay response: %s", err) + log.Printf("WARN: failed sending relay response: %s", err) return } @@ -117,6 +117,11 @@ func (jsrv *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) } // Verify the relay request signature and session. + // TODO_TECHDEBT: Currently, the relayer proxy is responsible for verifying + // the relay request signature. This responsibility should be shifted to the relayer itself. + // Consider using a middleware pattern to handle non-proxy specific logic, such as + // request signature verification, session verification, and response signature. + // This would help in separating concerns and improving code maintainability. if err := jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.serviceId); err != nil { return nil, err } diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index 16edcf16f..e273ea0c7 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -26,6 +26,11 @@ type ( servicesEndpointsMap = map[serviceId]url.URL ) +// relayerProxy is the main relayer proxy that takes takes relay requests of supported services from the client +// and proxies them to the supported proxied services. +// It is responsible for notifying the miner about the relays that have been served so they can be counted +// when the miner enters the claim/proof phase. +// TODO_TEST: Have tests for the relayer proxy. type relayerProxy struct { // keyName is the supplier's key name in the Cosmos's keybase. It is used along with the keyring to // get the supplier address and sign the relay responses. @@ -71,7 +76,6 @@ type relayerProxy struct { } func NewRelayerProxy( - ctx context.Context, clientCtx sdkclient.Context, keyName string, keyring keyring.Keyring, diff --git a/pkg/relayer/proxy/proxy_test.go b/pkg/relayer/proxy/proxy_test.go new file mode 100644 index 000000000..7ecefa5be --- /dev/null +++ b/pkg/relayer/proxy/proxy_test.go @@ -0,0 +1 @@ +package proxy_test diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index 821927726..aedb862bc 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -5,7 +5,6 @@ import ( "github.com/cometbft/cometbft/crypto" accounttypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "golang.org/x/exp/slices" "github.com/pokt-network/poktroll/x/service/types" sessiontypes "github.com/pokt-network/poktroll/x/session/types" @@ -33,10 +32,8 @@ func (rp *relayerProxy) VerifyRelayRequest( } hash := crypto.Sha256(payloadBz) - // accountResponse.Account.Value is a protobuf Any type that should be unmarshaled into an AccountI interface. - // TODO_TECHDEBT: Make sure our `AccountI`/`any` unmarshalling is correct. See https://github.com/pokt-network/poktroll/pull/101/files/edbd628e9146e232ef58c71cfa8f4be2135cdb50..fbba10626df79f6bf6e2218513dfdeb40a629790#r1372464439 - var account accounttypes.AccountI - if err := rp.clientCtx.Codec.UnmarshalJSON(accQueryRes.Account.Value, account); err != nil { + account := new(accounttypes.BaseAccount) + if err := account.Unmarshal(accQueryRes.Account.Value); err != nil { return err } @@ -48,10 +45,14 @@ func (rp *relayerProxy) VerifyRelayRequest( currentBlock := rp.blockClient.LatestBlock(ctx) sessionQuery := &sessiontypes.QueryGetSessionRequest{ ApplicationAddress: applicationAddress, - ServiceId: &sessiontypes.ServiceId{Id: serviceId}, + ServiceId: serviceId, BlockHeight: currentBlock.Height(), } sessionResponse, err := rp.sessionQuerier.GetSession(ctx, sessionQuery) + if err != nil { + return err + } + session := sessionResponse.Session // Since the retrieved sessionId was in terms of: @@ -66,9 +67,11 @@ func (rp *relayerProxy) VerifyRelayRequest( } // Check if the relayRequest is allowed to be served by the relayer proxy. - if !slices.Contains(session.Suppliers, rp.supplierAddress) { - return ErrInvalidSupplier + for _, supplier := range session.Suppliers { + if supplier.Address == rp.supplierAddress { + return nil + } } - return nil + return ErrInvalidSupplier } From 6ce148232d854c675fb22193e858f5b9cbe3d9e4 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 01:04:32 +0100 Subject: [PATCH 14/19] chore: add TODO for test implementation --- pkg/relayer/proxy/proxy_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/relayer/proxy/proxy_test.go b/pkg/relayer/proxy/proxy_test.go index 7ecefa5be..1fd0237f5 100644 --- a/pkg/relayer/proxy/proxy_test.go +++ b/pkg/relayer/proxy/proxy_test.go @@ -1 +1,3 @@ package proxy_test + +// TODO: Add tests the the relayerProxy and its jsonRPC component. From cae8969b3b74f373d5d3c22828caf16a0cf581e3 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 19:35:37 +0100 Subject: [PATCH 15/19] chore: Refactor relay request & response methods in RelayerProxy --- pkg/relayer/interface.go | 19 +++++++++++++++---- pkg/relayer/proxy/jsonrpc.go | 6 ++++-- pkg/relayer/proxy/relay_builders.go | 3 +-- pkg/relayer/proxy/relay_signer.go | 14 ++++++++------ pkg/relayer/proxy/relay_verifier.go | 18 +++++++++--------- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/pkg/relayer/interface.go b/pkg/relayer/interface.go index d8e50014d..262fe77f6 100644 --- a/pkg/relayer/interface.go +++ b/pkg/relayer/interface.go @@ -27,10 +27,21 @@ type RelayerProxy interface { // VerifyRelayRequest is a shared method used by RelayServers to check the // relay request signature and session validity. - VerifyRelayRequest(ctx context.Context, relayRequest *types.RelayRequest, serviceId *sharedtypes.ServiceId) error - - // SignRelayResponse is a shared method used by RelayServers to sign the relay response. - SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) + // This method may mutate the relay request in the future, so the returned + // relay request should be used instead of the passed in one. + // TODO_TECHDEBT(@red-0ne): This method should be moved out of the RelayerProxy interface + // that should not be responsible for verifying relay requests. + VerifyRelayRequest( + ctx context.Context, + relayRequest *types.RelayRequest, + serviceId *sharedtypes.ServiceId, + ) (*types.RelayRequest, error) + + // SignRelayResponse is a shared method used by RelayServers to sign + // and return a signed RelayResponse the relay response. + // TODO_TECHDEBT(@red-0ne): This method should be moved out of the RelayerProxy interface + // that should not be responsible for signing relay responses. + SignRelayResponse(relayResponse *types.RelayResponse) (*types.RelayResponse, error) } // RelayServer is the interface of the advertised relay servers provided by the RelayerProxy. diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 715d6018b..e497d4658 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -117,12 +117,14 @@ func (jsrv *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) } // Verify the relay request signature and session. - // TODO_TECHDEBT: Currently, the relayer proxy is responsible for verifying + // TODO_TECHDEBT(red-0ne): Currently, the relayer proxy is responsible for verifying // the relay request signature. This responsibility should be shifted to the relayer itself. // Consider using a middleware pattern to handle non-proxy specific logic, such as // request signature verification, session verification, and response signature. // This would help in separating concerns and improving code maintainability. - if err := jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.serviceId); err != nil { + // See https://github.com/pokt-network/poktroll/issues/160 + relayRequest, err = jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.serviceId) + if err != nil { return nil, err } diff --git a/pkg/relayer/proxy/relay_builders.go b/pkg/relayer/proxy/relay_builders.go index 97bfeaab7..d01b95d6c 100644 --- a/pkg/relayer/proxy/relay_builders.go +++ b/pkg/relayer/proxy/relay_builders.go @@ -47,11 +47,10 @@ func (j *jsonRPCServer) newRelayResponse( relayResponse.Payload = &types.RelayResponse_JsonRpcPayload{JsonRpcPayload: jsonRPCResponse} // Sign the relay response and add the signature to the relay response metadata - signature, err := j.relayerProxy.SignRelayResponse(relayResponse) + relayResponse, err = j.relayerProxy.SignRelayResponse(relayResponse) if err != nil { return nil, err } - relayResponse.Meta.SupplierSignature = signature return relayResponse, nil } diff --git a/pkg/relayer/proxy/relay_signer.go b/pkg/relayer/proxy/relay_signer.go index 5a4d296b4..f50ab71ba 100644 --- a/pkg/relayer/proxy/relay_signer.go +++ b/pkg/relayer/proxy/relay_signer.go @@ -6,17 +6,19 @@ import ( "github.com/pokt-network/poktroll/x/service/types" ) -// SignRelayResponse is a shared method used by the RelayServers to sign a RelayResponse.Payload. +// SignRelayResponse is a shared method used by the RelayServers to sign the hash of the RelayResponse.. // It uses the keyring and keyName to sign the payload and returns the signature. -func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) ([]byte, error) { - var payloadBz []byte - _, err := relayResponse.Payload.MarshalTo(payloadBz) +func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) (*types.RelayResponse, error) { + var responseBz []byte + _, err := relayResponse.MarshalTo(responseBz) if err != nil { return nil, err } - hash := crypto.Sha256(payloadBz) + hash := crypto.Sha256(responseBz) signature, _, err := rp.keyring.Sign(rp.keyName, hash) - return signature, err + relayResponse.Meta.SupplierSignature = signature + + return relayResponse, err } diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index aedb862bc..15b3b2aff 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -16,29 +16,29 @@ func (rp *relayerProxy) VerifyRelayRequest( ctx context.Context, relayRequest *types.RelayRequest, serviceId *sharedtypes.ServiceId, -) error { +) (*types.RelayRequest, error) { // Query for the application account to get the application's public key to verify the relay request signature. applicationAddress := relayRequest.Meta.SessionHeader.ApplicationAddress accQueryReq := &accounttypes.QueryAccountRequest{Address: applicationAddress} accQueryRes, err := rp.accountsQuerier.Account(ctx, accQueryReq) if err != nil { - return err + return nil, err } var payloadBz []byte _, err = relayRequest.Payload.MarshalTo(payloadBz) if err != nil { - return err + return nil, err } hash := crypto.Sha256(payloadBz) account := new(accounttypes.BaseAccount) if err := account.Unmarshal(accQueryRes.Account.Value); err != nil { - return err + return nil, err } if !account.GetPubKey().VerifySignature(hash, relayRequest.Meta.Signature) { - return ErrInvalidRelayRequestSignature + return nil, ErrInvalidRelayRequestSignature } // Query for the current session to check if relayRequest sessionId matches the current session. @@ -50,7 +50,7 @@ func (rp *relayerProxy) VerifyRelayRequest( } sessionResponse, err := rp.sessionQuerier.GetSession(ctx, sessionQuery) if err != nil { - return err + return nil, err } session := sessionResponse.Session @@ -63,15 +63,15 @@ func (rp *relayerProxy) VerifyRelayRequest( // matches the relayRequest sessionId. // TODO_INVESTIGATE: Revisit the assumptions above at some point in the future, but good enough for now. if session.SessionId != relayRequest.Meta.SessionHeader.SessionId { - return ErrInvalidSession + return nil, ErrInvalidSession } // Check if the relayRequest is allowed to be served by the relayer proxy. for _, supplier := range session.Suppliers { if supplier.Address == rp.supplierAddress { - return nil + return relayRequest, nil } } - return ErrInvalidSupplier + return nil, ErrInvalidSupplier } From 75e88c729efabf71a8c80cae7ef238d18ff07d38 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 19:35:54 +0100 Subject: [PATCH 16/19] chore: Address request changes --- pkg/relayer/proxy/jsonrpc.go | 12 +++++++++--- pkg/relayer/proxy/proxy.go | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index e497d4658..4f4e465f2 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -89,7 +89,7 @@ func (jsrv *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.R // Relay the request to the proxied service and build the response that will be sent back to the client. relay, err := jsrv.serveHTTP(ctx, request) if err != nil { - // Reply with an error if relay response could not be built. + // Reply with an error if the relay could not be served. jsrv.replyWithError(writer, err) log.Printf("WARN: failed serving relay request: %s", err) return @@ -102,7 +102,13 @@ func (jsrv *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.R return } - log.Print("INFO: relay request served successfully") + log.Printf( + "INFO: relay request served successfully for application %s, service %s, session start block height %d, proxied service %s", + relay.Res.Meta.SessionHeader.ApplicationAddress, + relay.Res.Meta.SessionHeader.ServiceId.Id, + relay.Res.Meta.SessionHeader.SessionStartBlockHeight, + jsrv.serverEndpoint.Url, + ) // Emit the relay to the servedRelays observable. jsrv.servedRelaysProducer <- relay @@ -129,7 +135,7 @@ func (jsrv *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) } // Get the relayRequest payload's `io.ReadCloser` to add it to the http.Request - // that will be sent to the native service. + // that will be sent to the proxied (i.e. staked for) service. // (see https://pkg.go.dev/net/http#Request) Body field type. var payloadBz []byte if _, err = relayRequest.Payload.MarshalTo(payloadBz); err != nil { diff --git a/pkg/relayer/proxy/proxy.go b/pkg/relayer/proxy/proxy.go index e273ea0c7..6c64516cd 100644 --- a/pkg/relayer/proxy/proxy.go +++ b/pkg/relayer/proxy/proxy.go @@ -26,7 +26,7 @@ type ( servicesEndpointsMap = map[serviceId]url.URL ) -// relayerProxy is the main relayer proxy that takes takes relay requests of supported services from the client +// relayerProxy is the main relayer proxy that takes relay requests of supported services from the client // and proxies them to the supported proxied services. // It is responsible for notifying the miner about the relays that have been served so they can be counted // when the miner enters the claim/proof phase. From 67273dd8a45955e1ba8693ebc840b182633fb263 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 19:39:32 +0100 Subject: [PATCH 17/19] chore: comment about current proxy signing approach --- pkg/relayer/proxy/relay_signer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/relayer/proxy/relay_signer.go b/pkg/relayer/proxy/relay_signer.go index f50ab71ba..9fea22ecd 100644 --- a/pkg/relayer/proxy/relay_signer.go +++ b/pkg/relayer/proxy/relay_signer.go @@ -8,6 +8,9 @@ import ( // SignRelayResponse is a shared method used by the RelayServers to sign the hash of the RelayResponse.. // It uses the keyring and keyName to sign the payload and returns the signature. +// TODO_TECHDEBT(@red-0ne): This method should be moved out of the RelayerProxy interface +// that should not be responsible for signing relay responses. +// See https://github.com/pokt-network/poktroll/issues/160 for a better design. func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) (*types.RelayResponse, error) { var responseBz []byte _, err := relayResponse.MarshalTo(responseBz) From 3c94180174205527a257bcbc11a5a9ec2e111e18 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 22:59:38 +0100 Subject: [PATCH 18/19] Merge remote-tracking branch 'origin/main' into feat/relayer-proxy --- .github/label-actions.yml | 2 + .gitignore | 13 +- .tool-versions | 4 + Makefile | 100 +++- Tiltfile | 28 +- app/app.go | 22 +- docs/static/openapi.yml | 512 +++++++++++++----- e2e/tests/node.go | 36 +- go.mod | 5 +- go.sum | 5 + .../{pocketd => poktrolld}/config/app.toml | 0 .../{pocketd => poktrolld}/config/client.toml | 0 .../{pocketd => poktrolld}/config/config.toml | 0 pkg/client/gomock_reflect_3526400147/prog.go | 66 +++ pkg/client/services.go | 2 +- pkg/relayer/interface.go | 54 +- pkg/relayer/proxy/jsonrpc.go | 18 +- pkg/relayer/proxy/relay_verifier.go | 4 +- pkg/relayer/proxy/server_builder.go | 8 +- pkg/relayer/session/errors.go | 11 + pkg/relayer/session/session.go | 128 +++++ pkg/relayer/session/session_test.go | 3 + pkg/relayer/session/sessiontree.go | 210 +++++++ pkg/relayer/session/sessiontree_test.go | 3 + proto/pocket/application/application.proto | 2 +- proto/pocket/application/query.proto | 3 - proto/pocket/gateway/query.proto | 5 +- proto/pocket/session/query.proto | 4 +- proto/pocket/session/session.proto | 3 +- proto/pocket/shared/service.proto | 12 +- proto/pocket/supplier/claim.proto | 14 + proto/pocket/supplier/genesis.proto | 5 +- proto/pocket/supplier/query.proto | 31 +- testutil/keeper/session.go | 41 +- testutil/network/network.go | 7 +- x/application/client/cli/query_application.go | 2 +- .../client/cli/tx_delegate_to_gateway.go | 4 +- .../client/cli/tx_stake_application.go | 4 +- .../client/cli/tx_undelegate_from_gateway.go | 2 +- .../client/cli/tx_unstake_application.go | 4 +- x/application/genesis_test.go | 4 +- x/application/keeper/application_test.go | 2 +- .../msg_server_delegate_to_gateway_test.go | 8 +- .../msg_server_stake_application_test.go | 24 +- ...msg_server_undelegate_from_gateway_test.go | 6 +- .../msg_server_unstake_application_test.go | 2 +- x/application/types/genesis_test.go | 10 +- .../types/message_stake_application.go | 2 +- .../types/message_stake_application_test.go | 24 +- x/gateway/client/cli/helpers_test.go | 14 +- x/gateway/client/cli/query_gateway.go | 2 +- x/gateway/client/cli/tx_stake_gateway.go | 8 +- x/gateway/client/cli/tx_unstake_gateway.go | 4 +- x/session/client/cli/helpers_test.go | 44 ++ x/session/client/cli/query_get_session.go | 36 +- .../client/cli/query_get_session_test.go | 197 +++++++ x/session/keeper/query_get_session.go | 17 +- x/session/keeper/query_get_session_test.go | 59 +- x/session/keeper/session_hydrator.go | 44 +- x/session/keeper/session_hydrator_test.go | 143 +++-- x/session/types/errors.go | 10 +- x/session/types/query_get_session_request.go | 40 ++ x/shared/helpers/service.go | 10 + x/shared/helpers/service_configs.go | 29 +- x/shared/helpers/service_test.go | 231 +++++++- x/supplier/client/cli/query.go | 2 + x/supplier/client/cli/query_claim.go | 84 +++ x/supplier/client/cli/query_claim_test.go | 157 ++++++ x/supplier/client/cli/query_supplier.go | 2 +- x/supplier/client/cli/tx_create_claim.go | 5 +- x/supplier/client/cli/tx_stake_supplier.go | 6 +- x/supplier/client/cli/tx_submit_proof.go | 9 +- x/supplier/client/cli/tx_unstake_supplier.go | 2 +- x/supplier/genesis.go | 6 + x/supplier/genesis_test.go | 13 +- x/supplier/keeper/claim.go | 64 +++ x/supplier/keeper/claim_test.go | 64 +++ .../keeper/msg_server_stake_supplier_test.go | 22 +- .../msg_server_unstake_supplier_test.go | 2 +- x/supplier/keeper/query_claim.go | 58 ++ x/supplier/keeper/query_claim_test.go | 124 +++++ x/supplier/keeper/supplier.go | 2 +- x/supplier/keeper/supplier_test.go | 2 +- x/supplier/types/codec.go | 2 + x/supplier/types/errors.go | 14 +- x/supplier/types/genesis.go | 12 +- x/supplier/types/genesis_test.go | 30 +- x/supplier/types/key_claim.go | 23 + x/supplier/types/message_create_claim.go | 21 + x/supplier/types/message_create_claim_test.go | 89 ++- .../types/message_stake_supplier_test.go | 18 +- 91 files changed, 2662 insertions(+), 518 deletions(-) create mode 100644 .tool-versions rename localnet/{pocketd => poktrolld}/config/app.toml (100%) rename localnet/{pocketd => poktrolld}/config/client.toml (100%) rename localnet/{pocketd => poktrolld}/config/config.toml (100%) create mode 100644 pkg/client/gomock_reflect_3526400147/prog.go create mode 100644 pkg/relayer/session/errors.go create mode 100644 pkg/relayer/session/session.go create mode 100644 pkg/relayer/session/session_test.go create mode 100644 pkg/relayer/session/sessiontree.go create mode 100644 pkg/relayer/session/sessiontree_test.go create mode 100644 proto/pocket/supplier/claim.proto create mode 100644 x/session/client/cli/helpers_test.go create mode 100644 x/session/client/cli/query_get_session_test.go create mode 100644 x/session/types/query_get_session_request.go create mode 100644 x/supplier/client/cli/query_claim.go create mode 100644 x/supplier/client/cli/query_claim_test.go create mode 100644 x/supplier/keeper/claim.go create mode 100644 x/supplier/keeper/claim_test.go create mode 100644 x/supplier/keeper/query_claim.go create mode 100644 x/supplier/keeper/query_claim_test.go create mode 100644 x/supplier/types/key_claim.go diff --git a/.github/label-actions.yml b/.github/label-actions.yml index b4dc1814d..0ab29008d 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -4,6 +4,7 @@ devnet-e2e-test: comment: The CI will now also run the e2e tests on devnet, which increases the time it takes to complete all CI checks. label: - devnet + - push-image # When `devnet-e2e-test` is removed, also delete `devnet` from the PR. -devnet-e2e-test: @@ -33,3 +34,4 @@ push-image: prs: unlabel: - devnet + - devnet-e2e-test diff --git a/.gitignore b/.gitignore index 5dc239e58..c893568d5 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,13 @@ bin # Before we provision the localnet, `ignite` creates the accounts, genesis, etc. for us # As many of the files are dynamic, we only preserve the config files in git history. -localnet/pocketd/* -localnet/*/config/ -!localnet/*/config/{app.toml,client.toml,config.toml} +localnet/poktrolld/* +localnet/*/config/*.json +!localnet/poktrolld/config/ +!localnet/poktrolld/config/app.toml +!localnet/poktrolld/config/client.toml +!localnet/poktrolld/config/config.toml + # Macos .DS_Store @@ -45,9 +49,6 @@ localnet/*/config/ # Frontend utils ts-client/ -# asdf -.tool-versions - # Proto artifacts **/*.pb.go **/*.pb.gw.go diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..44c16157f --- /dev/null +++ b/.tool-versions @@ -0,0 +1,4 @@ +# Run `asdf plugin add golang` and `asdf install` to install the dependencies, +# and `asdf current` to switch to the versions of dependencies listed below +golang 1.20.10 +go 1.20.10 diff --git a/Makefile b/Makefile index 19c8fe59e..1860b6156 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .SILENT: -POCKETD_HOME := ./localnet/pocketd +POKTROLLD_HOME := ./localnet/poktrolld POCKET_NODE = tcp://127.0.0.1:36657 # The pocket rollup node (full node and sequencer in the localnet context) POCKET_ADDR_PREFIX = pokt @@ -113,9 +113,10 @@ localnet_down: ## Delete resources created by localnet localnet_regenesis: ## Regenerate the localnet genesis file # NOTE: intentionally not using --home flag to avoid overwriting the test keyring ignite chain init - cp -r ${HOME}/.pocket/keyring-test $(POCKETD_HOME) - cp ${HOME}/.pocket/config/*_key.json $(POCKETD_HOME)/config/ - cp ${HOME}/.pocket/config/genesis.json $(POCKETD_HOME)/config/ + mkdir -p $(POKTROLLD_HOME)/config/ + cp -r ${HOME}/.pocket/keyring-test $(POKTROLLD_HOME) + cp ${HOME}/.pocket/config/*_key.json $(POKTROLLD_HOME)/config/ + cp ${HOME}/.pocket/config/genesis.json $(POKTROLLD_HOME)/config/ ############### ### Linting ### @@ -134,7 +135,7 @@ go_imports: check_go_version ## Run goimports on all go files .PHONY: test_e2e test_e2e: ## Run all E2E tests - export POCKET_NODE=$(POCKET_NODE) POCKETD_HOME=../../$(POCKETD_HOME) && go test -v ./e2e/tests/... -tags=e2e + export POCKET_NODE=$(POCKET_NODE) POKTROLLD_HOME=../../$(POKTROLLD_HOME) && go test -v ./e2e/tests/... -tags=e2e .PHONY: go_test go_test: check_go_version ## Run all go tests @@ -231,11 +232,11 @@ todo_this_commit: ## List all the TODOs needed to be done in this commit .PHONY: gateway_list gateway_list: ## List all the staked gateways - poktrolld --home=$(POCKETD_HOME) q gateway list-gateway --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q gateway list-gateway --node $(POCKET_NODE) .PHONY: gateway_stake gateway_stake: ## Stake tokens for the gateway specified (must specify the gateway env var) - poktrolld --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) .PHONY: gateway1_stake gateway1_stake: ## Stake gateway1 @@ -251,7 +252,7 @@ gateway3_stake: ## Stake gateway3 .PHONY: gateway_unstake gateway_unstake: ## Unstake an gateway (must specify the GATEWAY env var) - poktrolld --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE) .PHONY: gateway1_unstake gateway1_unstake: ## Unstake gateway1 @@ -271,11 +272,11 @@ gateway3_unstake: ## Unstake gateway3 .PHONY: app_list app_list: ## List all the staked applications - poktrolld --home=$(POCKETD_HOME) q application list-application --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q application list-application --node $(POCKET_NODE) .PHONY: app_stake app_stake: ## Stake tokens for the application specified (must specify the APP and SERVICES env vars) - poktrolld --home=$(POCKETD_HOME) tx application stake-application 1000upokt $(SERVICES) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application stake-application 1000upokt $(SERVICES) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_stake app1_stake: ## Stake app1 @@ -291,7 +292,7 @@ app3_stake: ## Stake app3 .PHONY: app_unstake app_unstake: ## Unstake an application (must specify the APP env var) - poktrolld --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_unstake app1_unstake: ## Unstake app1 @@ -307,35 +308,41 @@ app3_unstake: ## Unstake app3 .PHONY: app_delegate app_delegate: ## Delegate trust to a gateway (must specify the APP and GATEWAY_ADDR env vars). Requires the app to be staked - poktrolld --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_delegate_gateway1 app1_delegate_gateway1: ## Delegate trust to gateway1 - APP=app1 GATEWAY_ADDR=pokt15vzxjqklzjtlz7lahe8z2dfe9nm5vxwwmscne4 make app_delegate + GATEWAY1=$$(make poktrolld_addr ACC_NAME=gateway1) && \ + APP=app1 GATEWAY_ADDR=$$GATEWAY1 make app_delegate .PHONY: app2_delegate_gateway2 app2_delegate_gateway2: ## Delegate trust to gateway2 - APP=app2 GATEWAY_ADDR=pokt15w3fhfyc0lttv7r585e2ncpf6t2kl9uh8rsnyz make app_delegate + GATEWAY2=$$(make poktrolld_addr ACC_NAME=gateway2) && \ + APP=app2 GATEWAY_ADDR=$$GATEWAY2 make app_delegate .PHONY: app3_delegate_gateway3 app3_delegate_gateway3: ## Delegate trust to gateway3 - APP=app3 GATEWAY_ADDR=pokt1zhmkkd0rh788mc9prfq0m2h88t9ge0j83gnxya make app_delegate + GATEWAY3=$$(make poktrolld_addr ACC_NAME=gateway3) && \ + APP=app3 GATEWAY_ADDR=$$GATEWAY3 make app_delegate .PHONY: app_undelegate app_undelegate: ## Undelegate trust to a gateway (must specify the APP and GATEWAY_ADDR env vars). Requires the app to be staked - poktrolld --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE) .PHONY: app1_undelegate_gateway1 app1_undelegate_gateway1: ## Undelegate trust to gateway1 - APP=app1 GATEWAY_ADDR=pokt15vzxjqklzjtlz7lahe8z2dfe9nm5vxwwmscne4 make app_undelegate + GATEWAY1=$$(make poktrolld_addr ACC_NAME=gateway1) && \ + APP=app1 GATEWAY_ADDR=$$GATEWAY1 make app_undelegate .PHONY: app2_undelegate_gateway2 app2_undelegate_gateway2: ## Undelegate trust to gateway2 - APP=app2 GATEWAY_ADDR=pokt15w3fhfyc0lttv7r585e2ncpf6t2kl9uh8rsnyz make app_undelegate + GATEWAY2=$$(make poktrolld_addr ACC_NAME=gateway2) && \ + APP=app2 GATEWAY_ADDR=$$GATEWAY2 make app_undelegate .PHONY: app3_undelegate_gateway3 app3_undelegate_gateway3: ## Undelegate trust to gateway3 - APP=app3 GATEWAY_ADDR=pokt1zhmkkd0rh788mc9prfq0m2h88t9ge0j83gnxya make app_undelegate + GATEWAY3=$$(make poktrolld_addr ACC_NAME=gateway3) && \ + APP=app3 GATEWAY_ADDR=$$GATEWAY3 make app_undelegate ################# ### Suppliers ### @@ -343,13 +350,13 @@ app3_undelegate_gateway3: ## Undelegate trust to gateway3 .PHONY: supplier_list supplier_list: ## List all the staked supplier - poktrolld --home=$(POCKETD_HOME) q supplier list-supplier --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q supplier list-supplier --node $(POCKET_NODE) # TODO(@Olshansk, @okdas): Add more services (in addition to anvil) for apps and suppliers to stake for. # TODO_TECHDEBT: svc1, svc2 and svc3 below are only in place to make GetSession testable .PHONY: supplier_stake supplier_stake: ## Stake tokens for the supplier specified (must specify the APP env var) - poktrolld --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt "$(SERVICES)" --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier 1000upokt "$(SERVICES)" --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) .PHONY: supplier1_stake supplier1_stake: ## Stake supplier1 @@ -365,7 +372,7 @@ supplier3_stake: ## Stake supplier3 .PHONY: supplier_unstake supplier_unstake: ## Unstake an supplier (must specify the SUPPLIER env var) - poktrolld --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) .PHONY: supplier1_unstake supplier1_unstake: ## Unstake supplier1 @@ -379,6 +386,29 @@ supplier2_unstake: ## Unstake supplier2 supplier3_unstake: ## Unstake supplier3 SUPPLIER=supplier3 make supplier_unstake +############### +### Session ### +############### + +.PHONY: get_session +get_session: ## Retrieve the session given the following env vars: (APP_ADDR, SVC, HEIGHT) + pocketd --home=$(POCKETD_HOME) q session get-session $(APP) $(SVC) $(HEIGHT) --node $(POCKET_NODE) + +.PHONY: get_session_app1_anvil +get_session_app1_anvil: ## Retrieve the session for (app1, anvil, latest_height) + APP1=$$(make poktrolld_addr ACC_NAME=app1) && \ + APP=$$APP1 SVC=anvil HEIGHT=0 make get_session + +.PHONY: get_session_app2_anvil +get_session_app2_anvil: ## Retrieve the session for (app2, anvil, latest_height) + APP2=$$(make poktrolld_addr ACC_NAME=app2) && \ + APP=$$APP2 SVC=anvil HEIGHT=0 make get_session + +.PHONY: get_session_app3_anvil +get_session_app3_anvil: ## Retrieve the session for (app3, anvil, latest_height) + APP3=$$(make poktrolld_addr ACC_NAME=app3) && \ + APP=$$APP3 SVC=anvil HEIGHT=0 make get_session + ################ ### Accounts ### ################ @@ -386,10 +416,10 @@ supplier3_unstake: ## Unstake supplier3 .PHONY: acc_balance_query acc_balance_query: ## Query the balance of the account specified (make acc_balance_query ACC=pokt...) @echo "~~~ Balances ~~~" - poktrolld --home=$(POCKETD_HOME) q bank balances $(ACC) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q bank balances $(ACC) --node $(POCKET_NODE) @echo "~~~ Spendable Balances ~~~" @echo "Querying spendable balance for $(ACC)" - poktrolld --home=$(POCKETD_HOME) q bank spendable-balances $(ACC) --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q bank spendable-balances $(ACC) --node $(POCKET_NODE) .PHONY: acc_balance_query_module_app acc_balance_query_module_app: ## Query the balance of the network level "application" module @@ -397,15 +427,17 @@ acc_balance_query_module_app: ## Query the balance of the network level "applica .PHONY: acc_balance_query_module_supplier acc_balance_query_module_supplier: ## Query the balance of the network level "supplier" module - make acc_balance_query ACC=pokt1j40dzzmn6cn9kxku7a5tjnud6hv37vesr5ccaa + SUPPLIER1=$(make poktrolld_addr ACC_NAME=supplier1) + make acc_balance_query ACC=SUPPLIER1 .PHONY: acc_balance_query_app1 acc_balance_query_app1: ## Query the balance of app1 - make acc_balance_query ACC=pokt1mrqt5f7qh8uxs27cjm9t7v9e74a9vvdnq5jva4 + APP1=$$(make poktrolld_addr ACC_NAME=app1) && \ + make acc_balance_query ACC=$$APP1 .PHONY: acc_balance_total_supply acc_balance_total_supply: ## Query the total supply of the network - poktrolld --home=$(POCKETD_HOME) q bank total --node $(POCKET_NODE) + poktrolld --home=$(POKTROLLD_HOME) q bank total --node $(POCKET_NODE) ###################### ### Ignite Helpers ### @@ -413,7 +445,7 @@ acc_balance_total_supply: ## Query the total supply of the network .PHONY: ignite_acc_list ignite_acc_list: ## List all the accounts in LocalNet - ignite account list --keyring-dir=$(POCKETD_HOME) --keyring-backend test --address-prefix $(POCKET_ADDR_PREFIX) + ignite account list --keyring-dir=$(POKTROLLD_HOME) --keyring-backend test --address-prefix $(POCKET_ADDR_PREFIX) ################## ### CI Helpers ### @@ -427,8 +459,20 @@ trigger_ci: ## Trigger the CI pipeline by submitting an empty commit; See https: ##################### ### Documentation ### ##################### + .PHONY: go_docs go_docs: check_godoc ## Generate documentation for the project echo "Visit http://localhost:6060/pkg/pocket/" godoc -http=:6060 +.PHONY: openapi_gen +openapi_gen: ## Generate the OpenAPI spec for the Ignite API + ignite generate openapi --yes + +###################### +### Ignite Helpers ### +###################### + +.PHONY: poktrolld_addr +poktrolld_addr: ## Retrieve the address for an account by ACC_NAME + @echo $(shell poktrolld keys show -a $(ACC_NAME)) diff --git a/Tiltfile b/Tiltfile index 17521b14b..1333a406e 100644 --- a/Tiltfile +++ b/Tiltfile @@ -60,15 +60,15 @@ def generate_config_map_yaml(name, data): # Import keyring/keybase files into Kubernetes ConfigMap k8s_yaml( generate_config_map_yaml( - "pocketd-keys", read_files_from_directory("localnet/pocketd/keyring-test/") + "poktrolld-keys", read_files_from_directory("localnet/poktrolld/keyring-test/") ) -) # pocketd/keys +) # poktrolld/keys # Import configuration files into Kubernetes ConfigMap k8s_yaml( generate_config_map_yaml( - "pocketd-configs", read_files_from_directory("localnet/pocketd/config/") + "poktrolld-configs", read_files_from_directory("localnet/poktrolld/config/") ) -) # pocketd/configs +) # poktrolld/configs # Hot reload protobuf changes local_resource( @@ -77,36 +77,36 @@ local_resource( deps=["proto"], labels=["hot-reloading"], ) -# Hot reload the pocketd binary used by the k8s cluster +# Hot reload the poktrolld binary used by the k8s cluster local_resource( - "hot-reload: pocketd", + "hot-reload: poktrolld", "GOOS=linux ignite chain build --skip-proto --output=./bin --debug -v", deps=hot_reload_dirs, labels=["hot-reloading"], resource_deps=["hot-reload: generate protobufs"], ) -# Hot reload the local pocketd binary used by the CLI +# Hot reload the local poktrolld binary used by the CLI local_resource( - "hot-reload: pocketd - local cli", + "hot-reload: poktrolld - local cli", "ignite chain build --skip-proto --debug -v -o $(go env GOPATH)/bin", deps=hot_reload_dirs, labels=["hot-reloading"], resource_deps=["hot-reload: generate protobufs"], ) -# Build an image with a pocketd binary +# Build an image with a poktrolld binary docker_build_with_restart( - "pocketd", + "poktrolld", ".", dockerfile_contents="""FROM golang:1.20.8 RUN apt-get -q update && apt-get install -qyy curl jq RUN go install github.com/go-delve/delve/cmd/dlv@latest -COPY bin/poktrolld /usr/local/bin/pocketd +COPY bin/poktrolld /usr/local/bin/poktrolld WORKDIR / """, only=["./bin/poktrolld"], entrypoint=["/bin/sh", "/scripts/pocket.sh"], - live_update=[sync("bin/poktrolld", "/usr/local/bin/pocketd")], + live_update=[sync("bin/poktrolld", "/usr/local/bin/poktrolld")], ) # Run celestia and anvil nodes @@ -119,7 +119,7 @@ helm_resource( "sequencer", sequencer_chart, flags=["--values=./localnet/kubernetes/values-common.yaml"], - image_deps=["pocketd"], + image_deps=["poktrolld"], image_keys=[("image.repository", "image.tag")], ) helm_resource( @@ -129,7 +129,7 @@ helm_resource( "--values=./localnet/kubernetes/values-common.yaml", "--set=replicaCount=" + str(localnet_config["relayers"]["count"]), ], - image_deps=["pocketd"], + image_deps=["poktrolld"], image_keys=[("image.repository", "image.tag")], ) diff --git a/app/app.go b/app/app.go index c6a48ee69..b34d4db68 100644 --- a/app/app.go +++ b/app/app.go @@ -575,17 +575,6 @@ func New( ) serviceModule := servicemodule.NewAppModule(appCodec, app.ServiceKeeper, app.AccountKeeper, app.BankKeeper) - app.SessionKeeper = *sessionmodulekeeper.NewKeeper( - appCodec, - keys[sessionmoduletypes.StoreKey], - keys[sessionmoduletypes.MemStoreKey], - app.GetSubspace(sessionmoduletypes.ModuleName), - - app.ApplicationKeeper, - app.SupplierKeeper, - ) - sessionModule := sessionmodule.NewAppModule(appCodec, app.SessionKeeper, app.AccountKeeper, app.BankKeeper) - app.SupplierKeeper = *suppliermodulekeeper.NewKeeper( appCodec, keys[suppliermoduletypes.StoreKey], @@ -618,6 +607,17 @@ func New( ) applicationModule := applicationmodule.NewAppModule(appCodec, app.ApplicationKeeper, app.AccountKeeper, app.BankKeeper) + app.SessionKeeper = *sessionmodulekeeper.NewKeeper( + appCodec, + keys[sessionmoduletypes.StoreKey], + keys[sessionmoduletypes.MemStoreKey], + app.GetSubspace(sessionmoduletypes.ModuleName), + + app.ApplicationKeeper, + app.SupplierKeeper, + ) + sessionModule := sessionmodule.NewAppModule(appCodec, app.SessionKeeper, app.AccountKeeper, app.BankKeeper) + // this line is used by starport scaffolding # stargate/app/keeperDefinition /**** IBC Routing ****/ diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index 687106365..f55a851f2 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -46477,18 +46477,16 @@ paths: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: >- + The Service for which the application is + configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but - was desigtned created to enable more complex - service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -46504,7 +46502,9 @@ paths: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to + request service for delegatee_gateway_addresses: type: array items: @@ -46659,18 +46659,16 @@ paths: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: >- + The Service for which the application is configured + for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -46686,7 +46684,9 @@ paths: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to + request service for delegatee_gateway_addresses: type: array items: @@ -47090,18 +47090,14 @@ paths: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -47125,6 +47121,12 @@ paths: NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience + session_end_block_height: + type: string + format: int64 + title: >- + The height at which this session ended, this is the + last block of the session description: >- SessionHeader is a lightweight header for a session that can be passed around. @@ -47173,18 +47175,16 @@ paths: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: >- + The Service for which the application is + configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but - was desigtned created to enable more complex - service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -47200,7 +47200,9 @@ paths: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured + to request service for delegatee_gateway_addresses: type: array items: @@ -47240,18 +47242,16 @@ paths: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: >- + The Service for which the supplier is + configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant - but was desigtned created to enable more - complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -47363,11 +47363,8 @@ paths: in: query required: false type: string - - name: service_id.id + - name: service.id description: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned created to - enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -47376,7 +47373,7 @@ paths: in: query required: false type: string - - name: service_id.name + - name: service.name description: >- TODO_TECHDEBT: Name is currently unused but acts as a reminder than an optional onchain representation of the service is necessary @@ -47430,6 +47427,189 @@ paths: additionalProperties: {} tags: - Query + /pocket/supplier/claim: + get: + operationId: PocketSupplierAllClaims + responses: + '200': + description: A successful response. + schema: + type: object + properties: + claim: + type: array + items: + type: object + properties: + index: + type: string + supplier_address: + type: string + session_id: + type: string + root_hash: + type: string + description: >- + TODO_UPNEXT(@Olshansk): The structure below is the default + (untouched) scaffolded type. Update + + and productionize it for our use case. + pagination: + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + default: + description: An unexpected error response. + schema: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + '@type': + type: string + additionalProperties: {} + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /pocket/supplier/claim/{index}: + get: + summary: Queries a list of Claim items. + operationId: PocketSupplierClaim + responses: + '200': + description: A successful response. + schema: + type: object + properties: + claim: + type: object + properties: + index: + type: string + supplier_address: + type: string + session_id: + type: string + root_hash: + type: string + description: >- + TODO_UPNEXT(@Olshansk): The structure below is the default + (untouched) scaffolded type. Update + + and productionize it for our use case. + default: + description: An unexpected error response. + schema: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + '@type': + type: string + additionalProperties: {} + parameters: + - name: index + in: path + required: true + type: string + tags: + - Query /pocket/supplier/params: get: summary: Parameters queries the parameters of the module. @@ -47506,18 +47686,14 @@ paths: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but - was desigtned created to enable more complex - service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -47738,18 +47914,14 @@ paths: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -76616,17 +76788,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the application is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned - created to enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -76640,7 +76809,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -76701,18 +76872,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the application is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -76728,7 +76895,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -76797,18 +76966,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the application is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -76822,7 +76987,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -76850,17 +77017,14 @@ definitions: pocket.shared.ApplicationServiceConfig: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the application is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned created - to enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? name: @@ -76873,16 +77037,13 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - pocket.shared.ServiceId: + pocket.shared.Service: type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned created to - enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? name: @@ -76892,7 +77053,7 @@ definitions: TODO_TECHDEBT: Name is currently unused but acts as a reminder than an optional onchain representation of the service is necessary title: >- - ServiceId message to encapsulate unique and semantic identifiers for a + Service message to encapsulate unique and semantic identifiers for a service on the network pocket.gateway.Gateway: type: object @@ -77043,17 +77204,14 @@ definitions: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned - created to enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -77074,6 +77232,12 @@ definitions: title: >- NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience + session_end_block_height: + type: string + format: int64 + title: >- + The height at which this session ended, this is the last block + of the session description: >- SessionHeader is a lightweight header for a session that can be passed around. @@ -77121,18 +77285,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the application is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -77148,7 +77308,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to + request service for delegatee_gateway_addresses: type: array items: @@ -77187,18 +77349,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -77300,17 +77458,14 @@ definitions: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned - created to enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -77331,6 +77486,12 @@ definitions: title: >- NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience + session_end_block_height: + type: string + format: int64 + title: >- + The height at which this session ended, this is the last block of + the session description: >- SessionHeader is a lightweight header for a session that can be passed around. @@ -77378,18 +77539,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the application is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -77403,7 +77560,9 @@ definitions: title: >- ApplicationServiceConfig holds the service configuration the application stakes for - title: The ID of the service this session is servicing + title: >- + The list of services this appliccation is configured to request + service for delegatee_gateway_addresses: type: array items: @@ -77442,18 +77601,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -77542,17 +77697,14 @@ definitions: title: >- The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - service_id: - title: The ID of the service this session is servicing + service: + title: The service this session is for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned created - to enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? name: @@ -77572,6 +77724,12 @@ definitions: title: >- NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience + session_end_block_height: + type: string + format: int64 + title: >- + The height at which this session ended, this is the last block of the + session description: >- SessionHeader is a lightweight header for a session that can be passed around. @@ -77657,17 +77815,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned - created to enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -77789,17 +77944,14 @@ definitions: pocket.shared.SupplierServiceConfig: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured for type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was desigtned created - to enable more complex service identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? name: @@ -77864,6 +78016,22 @@ definitions: title: >- SupplierServiceConfig holds the service configuration the supplier stakes for + pocket.supplier.Claim: + type: object + properties: + index: + type: string + supplier_address: + type: string + session_id: + type: string + root_hash: + type: string + description: >- + TODO_UPNEXT(@Olshansk): The structure below is the default (untouched) + scaffolded type. Update + + and productionize it for our use case. pocket.supplier.MsgCreateClaimResponse: type: object pocket.supplier.MsgStakeSupplierResponse: @@ -77875,6 +78043,53 @@ definitions: pocket.supplier.Params: type: object description: Params defines the parameters for the module. + pocket.supplier.QueryAllClaimsResponse: + type: object + properties: + claim: + type: array + items: + type: object + properties: + index: + type: string + supplier_address: + type: string + session_id: + type: string + root_hash: + type: string + description: >- + TODO_UPNEXT(@Olshansk): The structure below is the default + (untouched) scaffolded type. Update + + and productionize it for our use case. + pagination: + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } pocket.supplier.QueryAllSupplierResponse: type: object properties: @@ -77909,18 +78124,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? @@ -78021,6 +78232,25 @@ definitions: repeated Bar results = 1; PageResponse page = 2; } + pocket.supplier.QueryGetClaimResponse: + type: object + properties: + claim: + type: object + properties: + index: + type: string + supplier_address: + type: string + session_id: + type: string + root_hash: + type: string + description: >- + TODO_UPNEXT(@Olshansk): The structure below is the default (untouched) + scaffolded type. Update + + and productionize it for our use case. pocket.supplier.QueryGetSupplierResponse: type: object properties: @@ -78053,18 +78283,14 @@ definitions: items: type: object properties: - service_id: - title: Unique and semantic identifier for the service + service: + title: The Service for which the supplier is configured type: object properties: id: type: string description: Unique identifier for the service title: >- - NOTE: `ServiceId.Id` may seem redundant but was - desigtned created to enable more complex service - identification - For example, what if we want to request a session for a certain service but with some additional configs that identify it? diff --git a/e2e/tests/node.go b/e2e/tests/node.go index e46ad2889..2a8c81a73 100644 --- a/e2e/tests/node.go +++ b/e2e/tests/node.go @@ -3,9 +3,11 @@ package e2e import ( + "bytes" "fmt" "os" "os/exec" + "strings" ) var ( @@ -16,7 +18,7 @@ var ( // defaultRPCHost is the default RPC host that pocketd listens on defaultRPCHost = "127.0.0.1" // defaultHome is the default home directory for pocketd - defaultHome = os.Getenv("POCKETD_HOME") + defaultHome = os.Getenv("POKTROLLD_HOME") ) func init() { @@ -30,9 +32,10 @@ func init() { // commandResult combines the stdout, stderr, and err of an operation type commandResult struct { - Stdout string - Stderr string - Err error + Command string // the command that was executed + Stdout string // standard output + Stderr string // standard error + Err error // execution error, if any } // PocketClient is a single function interface for interacting with a node @@ -67,13 +70,26 @@ func (p *pocketdBin) RunCommandOnHost(rpcUrl string, args ...string) (*commandRe func (p *pocketdBin) runCmd(args ...string) (*commandResult, error) { base := []string{"--home", defaultHome} args = append(base, args...) + commandStr := "poktrolld " + strings.Join(args, " ") // Create a string representation of the command cmd := exec.Command("poktrolld", args...) - r := &commandResult{} - out, err := cmd.Output() - if err != nil { - return nil, err + + var stdoutBuf, stderrBuf bytes.Buffer + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + + err := cmd.Run() + r := &commandResult{ + Command: commandStr, // Set the command string + Stdout: stdoutBuf.String(), + Stderr: stderrBuf.String(), + Err: err, } - r.Stdout = string(out) p.result = r - return r, nil + + if err != nil { + // Include the command executed in the error message for context + err = fmt.Errorf("error running command [%s]: %v, stderr: %s", commandStr, err, stderrBuf.String()) + } + + return r, err } diff --git a/go.mod b/go.mod index c0d512a0f..51aa32938 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/pokt-network/poktroll -go 1.19 +go 1.20 require ( cosmossdk.io/api v0.3.1 @@ -13,6 +13,7 @@ require ( github.com/cosmos/cosmos-sdk v0.47.3 github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.1.0 + github.com/gogo/status v1.1.1 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 github.com/gorilla/mux v1.8.0 @@ -27,7 +28,6 @@ require ( github.com/stretchr/testify v1.8.4 go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df golang.org/x/sync v0.3.0 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 @@ -256,6 +256,7 @@ require ( go.uber.org/dig v1.16.1 // indirect go.uber.org/fx v1.19.2 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect diff --git a/go.sum b/go.sum index 64f7566f0..b554afc3b 100644 --- a/go.sum +++ b/go.sum @@ -688,10 +688,13 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= +github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= @@ -2610,6 +2613,7 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -2735,6 +2739,7 @@ google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnp google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= diff --git a/localnet/pocketd/config/app.toml b/localnet/poktrolld/config/app.toml similarity index 100% rename from localnet/pocketd/config/app.toml rename to localnet/poktrolld/config/app.toml diff --git a/localnet/pocketd/config/client.toml b/localnet/poktrolld/config/client.toml similarity index 100% rename from localnet/pocketd/config/client.toml rename to localnet/poktrolld/config/client.toml diff --git a/localnet/pocketd/config/config.toml b/localnet/poktrolld/config/config.toml similarity index 100% rename from localnet/pocketd/config/config.toml rename to localnet/poktrolld/config/config.toml diff --git a/pkg/client/gomock_reflect_3526400147/prog.go b/pkg/client/gomock_reflect_3526400147/prog.go new file mode 100644 index 000000000..6003ba81a --- /dev/null +++ b/pkg/client/gomock_reflect_3526400147/prog.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/gob" + "flag" + "fmt" + "os" + "path" + "reflect" + + "github.com/golang/mock/mockgen/model" + + pkg_ "github.com/pokt-network/poktroll/pkg/client" +) + +var output = flag.String("output", "", "The output file name, or empty to use stdout.") + +func main() { + flag.Parse() + + its := []struct { + sym string + typ reflect.Type + }{ + + {"TxContext", reflect.TypeOf((*pkg_.TxContext)(nil)).Elem()}, + + {"TxClient", reflect.TypeOf((*pkg_.TxClient)(nil)).Elem()}, + } + pkg := &model.Package{ + // NOTE: This behaves contrary to documented behaviour if the + // package name is not the final component of the import path. + // The reflect package doesn't expose the package name, though. + Name: path.Base("github.com/pokt-network/poktroll/pkg/client"), + } + + for _, it := range its { + intf, err := model.InterfaceFromInterfaceType(it.typ) + if err != nil { + fmt.Fprintf(os.Stderr, "Reflection: %v\n", err) + os.Exit(1) + } + intf.Name = it.sym + pkg.Interfaces = append(pkg.Interfaces, intf) + } + + outfile := os.Stdout + if len(*output) != 0 { + var err error + outfile, err = os.Create(*output) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open output file %q", *output) + } + defer func() { + if err := outfile.Close(); err != nil { + fmt.Fprintf(os.Stderr, "failed to close output file %q", *output) + os.Exit(1) + } + }() + } + + if err := gob.NewEncoder(outfile).Encode(pkg); err != nil { + fmt.Fprintf(os.Stderr, "gob encode: %v\n", err) + os.Exit(1) + } +} diff --git a/pkg/client/services.go b/pkg/client/services.go index 0d2ca060d..1e2667cf9 100644 --- a/pkg/client/services.go +++ b/pkg/client/services.go @@ -12,7 +12,7 @@ func NewTestApplicationServiceConfig(prefix string, count int) []*sharedtypes.Ap for i, _ := range appSvcCfg { serviceId := fmt.Sprintf("%s%d", prefix, i) appSvcCfg[i] = &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{Id: serviceId}, + Service: &sharedtypes.Service{Id: serviceId}, } } return appSvcCfg diff --git a/pkg/relayer/interface.go b/pkg/relayer/interface.go index 262fe77f6..bb7536b30 100644 --- a/pkg/relayer/interface.go +++ b/pkg/relayer/interface.go @@ -3,8 +3,11 @@ package relayer import ( "context" + "github.com/pokt-network/smt" + "github.com/pokt-network/poktroll/pkg/observable" "github.com/pokt-network/poktroll/x/service/types" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) @@ -34,7 +37,7 @@ type RelayerProxy interface { VerifyRelayRequest( ctx context.Context, relayRequest *types.RelayRequest, - serviceId *sharedtypes.ServiceId, + service *sharedtypes.Service, ) (*types.RelayRequest, error) // SignRelayResponse is a shared method used by RelayServers to sign @@ -52,6 +55,51 @@ type RelayServer interface { // Stop terminates the service server and returns an error if it fails. Stop(ctx context.Context) error - // ServiceId returns the serviceId of the service. - ServiceId() *sharedtypes.ServiceId + // Service returns the service to which the RelayServer relays. + Service() *sharedtypes.Service +} + +// RelayerSessionsManager is an interface for managing the relayer's sessions and Sparse +// Merkle Sum Trees (SMSTs). It provides notifications about closing sessions that are +// ready to be claimed, and handles the creation and retrieval of SMSTs for a given session. +// It also handles the creation and retrieval of SMSTs for a given session. +type RelayerSessionsManager interface { + // SessionsToClaim returns an observable that notifies of sessions ready to be claimed. + SessionsToClaim() observable.Observable[SessionTree] + + // EnsureSessionTree returns the SMST (Sparse Merkle State Tree) for a given session. + // It is used to retrieve the SMST and update it when a Relay has been successfully served. + // If the session is seen for the first time, it creates a new SMST for it before returning it. + // An error is returned if the corresponding KVStore for SMST fails to be created. + EnsureSessionTree(session *sessiontypes.Session) (SessionTree, error) +} + +// SessionTree is an interface that wraps an SMST (Sparse Merkle State Tree) and its corresponding session. +type SessionTree interface { + // GetSession returns the session corresponding to the SMST. + GetSession() *sessiontypes.Session + + // Update is a wrapper for the SMST's Update function. It updates the SMST with + // the given key, value, and weight. + // This function should be called when a Relay has been successfully served. + Update(key, value []byte, weight uint64) error + + // ProveClosest is a wrapper for the SMST's ProveClosest function. It returns the + // proof for the given path. + // This function should be called several blocks after a session has been claimed and needs to be proven. + ProveClosest(path []byte) (proof *smt.SparseMerkleClosestProof, err error) + + // Flush gets the root hash of the SMST needed for submitting the claim; + // then commits the entire tree to disk and stops the KVStore. + // It should be called before submitting the claim on-chain. This function frees up + // the in-memory resources used by the SMST that are no longer needed while waiting + // for the proof submission window to open. + Flush() (SMSTRoot []byte, err error) + + // TODO_DISCUSS: This function should not be part of the interface as it is an optimization + // aiming to free up KVStore resources after the proof is no longer needed. + // Delete deletes the SMST from the KVStore. + // WARNING: This function should be called only after the proof has been successfully + // submitted on-chain and the servicer has confirmed that it has been rewarded. + Delete() error } diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 4f4e465f2..1389573f0 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -16,8 +16,8 @@ import ( var _ relayer.RelayServer = (*jsonRPCServer)(nil) type jsonRPCServer struct { - // serviceId is the id of the service that the server is responsible for. - serviceId *sharedtypes.ServiceId + // service is the service that the server is responsible for. + service *sharedtypes.Service // serverEndpoint is the advertised endpoint configuration that the server uses to // listen for incoming relay requests. @@ -42,14 +42,14 @@ type jsonRPCServer struct { // It takes the serviceId, endpointUrl, and the main RelayerProxy as arguments and returns // a RelayServer that listens to incoming RelayRequests. func NewJSONRPCServer( - serviceId *sharedtypes.ServiceId, + service *sharedtypes.Service, supplierEndpoint *sharedtypes.SupplierEndpoint, proxiedServiceEndpoint url.URL, servedRelaysProducer chan<- *types.Relay, proxy relayer.RelayerProxy, ) relayer.RelayServer { return &jsonRPCServer{ - serviceId: serviceId, + service: service, serverEndpoint: supplierEndpoint, server: &http.Server{Addr: supplierEndpoint.Url}, relayerProxy: proxy, @@ -75,9 +75,9 @@ func (jsrv *jsonRPCServer) Stop(ctx context.Context) error { return jsrv.server.Shutdown(ctx) } -// ServiceId returns the serviceId of the JSON-RPC service. -func (jsrv *jsonRPCServer) ServiceId() *sharedtypes.ServiceId { - return jsrv.serviceId +// Service returns the JSON-RPC service. +func (j *jsonRPCServer) Service() *sharedtypes.Service { + return j.service } // ServeHTTP listens for incoming relay requests. It implements the respective @@ -105,7 +105,7 @@ func (jsrv *jsonRPCServer) ServeHTTP(writer http.ResponseWriter, request *http.R log.Printf( "INFO: relay request served successfully for application %s, service %s, session start block height %d, proxied service %s", relay.Res.Meta.SessionHeader.ApplicationAddress, - relay.Res.Meta.SessionHeader.ServiceId.Id, + relay.Res.Meta.SessionHeader.Service.Id, relay.Res.Meta.SessionHeader.SessionStartBlockHeight, jsrv.serverEndpoint.Url, ) @@ -129,7 +129,7 @@ func (jsrv *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) // request signature verification, session verification, and response signature. // This would help in separating concerns and improving code maintainability. // See https://github.com/pokt-network/poktroll/issues/160 - relayRequest, err = jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.serviceId) + relayRequest, err = jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.service) if err != nil { return nil, err } diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index 15b3b2aff..94c2db5f5 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -15,7 +15,7 @@ import ( func (rp *relayerProxy) VerifyRelayRequest( ctx context.Context, relayRequest *types.RelayRequest, - serviceId *sharedtypes.ServiceId, + service *sharedtypes.Service, ) (*types.RelayRequest, error) { // Query for the application account to get the application's public key to verify the relay request signature. applicationAddress := relayRequest.Meta.SessionHeader.ApplicationAddress @@ -45,7 +45,7 @@ func (rp *relayerProxy) VerifyRelayRequest( currentBlock := rp.blockClient.LatestBlock(ctx) sessionQuery := &sessiontypes.QueryGetSessionRequest{ ApplicationAddress: applicationAddress, - ServiceId: serviceId, + Service: service, BlockHeight: currentBlock.Height(), } sessionResponse, err := rp.sessionQuerier.GetSession(ctx, sessionQuery) diff --git a/pkg/relayer/proxy/server_builder.go b/pkg/relayer/proxy/server_builder.go index d0b46b381..a821b5752 100644 --- a/pkg/relayer/proxy/server_builder.go +++ b/pkg/relayer/proxy/server_builder.go @@ -30,8 +30,8 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { // Build the advertised relay servers map. For each service's endpoint, create the appropriate RelayServer. providedServices := make(relayServersMap) for _, serviceConfig := range services { - serviceId := serviceConfig.ServiceId - proxiedServicesEndpoints := rp.proxiedServicesEndpoints[serviceId.Id] + service := serviceConfig.Service + proxiedServicesEndpoints := rp.proxiedServicesEndpoints[service.Id] serviceEndpoints := make([]relayer.RelayServer, len(serviceConfig.Endpoints)) for _, endpoint := range serviceConfig.Endpoints { @@ -41,7 +41,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { switch endpoint.RpcType { case sharedtypes.RPCType_JSON_RPC: server = NewJSONRPCServer( - serviceId, + service, endpoint, proxiedServicesEndpoints, rp.servedRelaysProducer, @@ -54,7 +54,7 @@ func (rp *relayerProxy) BuildProvidedServices(ctx context.Context) error { serviceEndpoints = append(serviceEndpoints, server) } - providedServices[serviceId.Id] = serviceEndpoints + providedServices[service.Id] = serviceEndpoints } rp.advertisedRelayServers = providedServices diff --git a/pkg/relayer/session/errors.go b/pkg/relayer/session/errors.go new file mode 100644 index 000000000..adf5a403b --- /dev/null +++ b/pkg/relayer/session/errors.go @@ -0,0 +1,11 @@ +package session + +import sdkerrors "cosmossdk.io/errors" + +var ( + codespace = "relayer/session" + ErrSessionTreeClosed = sdkerrors.Register(codespace, 1, "session tree already closed") + ErrSessionTreeNotClosed = sdkerrors.Register(codespace, 2, "session tree not closed") + ErrSessionStorePathExists = sdkerrors.Register(codespace, 3, "session store path already exists") + ErrSessionTreeProofPathMismatch = sdkerrors.Register(codespace, 4, "session tree proof path mismatch") +) diff --git a/pkg/relayer/session/session.go b/pkg/relayer/session/session.go new file mode 100644 index 000000000..17b1282c0 --- /dev/null +++ b/pkg/relayer/session/session.go @@ -0,0 +1,128 @@ +package session + +import ( + "context" + "log" + "sync" + + blockclient "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/observable" + "github.com/pokt-network/poktroll/pkg/observable/channel" + "github.com/pokt-network/poktroll/pkg/relayer" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +var _ relayer.RelayerSessionsManager = (*relayerSessionsManager)(nil) + +type sessionsTreesMap = map[int64]map[string]relayer.SessionTree + +// relayerSessionsManager is an implementation of the RelayerSessions interface. +// TODO_TEST: Add tests to the relayerSessionsManager. +type relayerSessionsManager struct { + // sessionsToClaim notifies about sessions that are ready to be claimed. + sessionsToClaim observable.Observable[relayer.SessionTree] + + // sessionsToClaimPublisher is the channel used to publish sessions to claim. + sessionsToClaimPublisher chan<- relayer.SessionTree + + // sessionTrees is a map of block heights pointing to a map of SessionTrees + // indexed by their sessionId. + // The block height index is used to know when the sessions contained in the entry should be closed, + // this helps to avoid iterating over all sessionsTrees to check if they are ready to be closed. + sessionsTrees sessionsTreesMap + sessionsTreesMu *sync.Mutex + + // blockClient is used to get the notifications of committed blocks. + blockClient blockclient.BlockClient + + // storesDirectory points to a path on disk where KVStore data files are created. + storesDirectory string +} + +// NewRelayerSessions creates a new relayerSessions. +func NewRelayerSessions( + ctx context.Context, + storesDirectory string, + blockClient blockclient.BlockClient, +) relayer.RelayerSessionsManager { + rs := &relayerSessionsManager{ + sessionsTrees: make(sessionsTreesMap), + storesDirectory: storesDirectory, + blockClient: blockClient, + } + rs.sessionsToClaim, rs.sessionsToClaimPublisher = channel.NewObservable[relayer.SessionTree]() + + go rs.goListenToCommittedBlocks(ctx) + + return rs +} + +// SessionsToClaim returns an observable that notifies when sessions are ready to be claimed. +func (rs *relayerSessionsManager) SessionsToClaim() observable.Observable[relayer.SessionTree] { + return rs.sessionsToClaim +} + +// EnsureSessionTree returns the SessionTree for a given session. +// If no tree for the session exists, a new SessionTree is created before returning. +func (rs *relayerSessionsManager) EnsureSessionTree(session *sessiontypes.Session) (relayer.SessionTree, error) { + rs.sessionsTreesMu.Lock() + defer rs.sessionsTreesMu.Unlock() + + // Calculate the session end height based on the session start block height + // and the number of blocks per session. + sessionEndHeight := session.Header.SessionStartBlockHeight + session.NumBlocksPerSession + sessionsTrees, ok := rs.sessionsTrees[sessionEndHeight] + + // If there is no map for sessions at the sessionEndHeight, create one. + if !ok { + sessionsTrees = make(map[string]relayer.SessionTree) + rs.sessionsTrees[sessionEndHeight] = sessionsTrees + } + + // Get the sessionTree for the given session. + sessionTree, ok := sessionsTrees[session.SessionId] + + // If the sessionTree does not exist, create it. + if !ok { + sessionTree, err := NewSessionTree(session, rs.storesDirectory, rs.removeFromRelayerSessions) + if err != nil { + return nil, err + } + + sessionsTrees[session.SessionId] = sessionTree + } + + return sessionTree, nil +} + +// goListenToCommittedBlocks listens to committed blocks so that rs.sessionsToClaimPublisher +// can notify when sessions are ready to be claimed. +// It is intended to be called as a background goroutine. +func (rs *relayerSessionsManager) goListenToCommittedBlocks(ctx context.Context) { + committedBlocks := rs.blockClient.CommittedBlocksSequence(ctx).Subscribe(ctx).Ch() + + for block := range committedBlocks { + // Check if there are sessions to be closed at this block height. + if sessionsTrees, ok := rs.sessionsTrees[block.Height()]; ok { + // Iterate over the sessionsTrees that end at this block height and publish them. + for _, sessionTree := range sessionsTrees { + rs.sessionsToClaimPublisher <- sessionTree + } + } + } +} + +// removeFromRelayerSessions removes the session from the relayerSessions. +func (rs *relayerSessionsManager) removeFromRelayerSessions(session *sessiontypes.Session) { + rs.sessionsTreesMu.Lock() + defer rs.sessionsTreesMu.Unlock() + + sessionEndHeight := session.Header.SessionStartBlockHeight + session.NumBlocksPerSession + sessionsTrees, ok := rs.sessionsTrees[sessionEndHeight] + if !ok { + log.Print("session not found in relayerSessionsManager") + return + } + + delete(sessionsTrees, session.SessionId) +} diff --git a/pkg/relayer/session/session_test.go b/pkg/relayer/session/session_test.go new file mode 100644 index 000000000..9efb49710 --- /dev/null +++ b/pkg/relayer/session/session_test.go @@ -0,0 +1,3 @@ +package session_test + +// TODO: Add tests to the relayerSessionsManager logic diff --git a/pkg/relayer/session/sessiontree.go b/pkg/relayer/session/sessiontree.go new file mode 100644 index 000000000..b97711451 --- /dev/null +++ b/pkg/relayer/session/sessiontree.go @@ -0,0 +1,210 @@ +package session + +import ( + "bytes" + "crypto/sha256" + "os" + "path/filepath" + "sync" + + "github.com/pokt-network/smt" + + "github.com/pokt-network/poktroll/pkg/relayer" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +var _ relayer.SessionTree = (*sessionTree)(nil) + +// sessionTree is an implementation of the SessionTree interface. +// TODO_TEST: Add tests to the sessionTree. +type sessionTree struct { + // sessionMu is a mutex used to protect sessionTree operations from concurrent access. + sessionMu *sync.Mutex + + // session is the Session corresponding to the SMST (Sparse Merkle State Tree). + session *sessiontypes.Session + + // tree is the SMST (Sparse Merkle State Tree) corresponding the session. + tree *smt.SMST + + // claimedRoot is the root hash of the SMST needed for submitting the claim. + // If it holds a non-nil value, it means that the SMST has been flushed, + // committed to disk and no more updates can be made to it. A non-nil value also + // indicates that a proof could be generated using ProveClosest function. + claimedRoot []byte + + // proofPath is the path for which the proof was generated. + proofPath []byte + + // proof is the generated proof for the session given a proofPath. + proof *smt.SparseMerkleClosestProof + + // treeStore is the KVStore used to store the SMST. + treeStore smt.KVStore + + // storePath is the path to the KVStore used to store the SMST. + // It is created from the storePrefix and the session.sessionId. + // We keep track of it so we can use it at the end of the claim/proof lifecycle + // to delete the KVStore when it is no longer needed. + storePath string + + // removeFromRelayerSessions is a function that removes the sessionTree from + // the RelayerSessionsManager. + // Since the sessionTree has no knowledge of the RelayerSessionsManager, + // we pass this callback from the session manager to the sessionTree so + // it can remove itself from the RelayerSessionsManager when it is no longer needed. + removeFromRelayerSessions func(session *sessiontypes.Session) +} + +// NewSessionTree creates a new sessionTree from a Session and a storePrefix. It also takes a function +// removeFromRelayerSessions that removes the sessionTree from the RelayerSessionsManager. +// It returns an error if the KVStore fails to be created. +func NewSessionTree( + session *sessiontypes.Session, + storesDirectory string, + removeFromRelayerSessions func(session *sessiontypes.Session), +) (relayer.SessionTree, error) { + // Join the storePrefix and the session.sessionId to create a unique storePath + storePath := filepath.Join(storesDirectory, session.SessionId) + + // Make sure storePath does not exist when creating a new SessionTree + if _, err := os.Stat(storePath); !os.IsNotExist(err) { + return nil, ErrSessionStorePathExists + } + + treeStore, err := smt.NewKVStore(storePath) + if err != nil { + return nil, err + } + + // Create the SMST from the KVStore and a nil value hasher so the proof would + // contain a non-hashed Relay that could be used to validate the proof on-chain. + tree := smt.NewSparseMerkleSumTree(treeStore, sha256.New(), smt.WithValueHasher(nil)) + + sessionTree := &sessionTree{ + session: session, + storePath: storePath, + treeStore: treeStore, + tree: tree, + + removeFromRelayerSessions: removeFromRelayerSessions, + } + + return sessionTree, nil +} + +// GetSession returns the session corresponding to the SMST. +func (st *sessionTree) GetSession() *sessiontypes.Session { + return st.session +} + +// Update is a wrapper for the SMST's Update function. It updates the SMST with +// the given key, value, and weight. +// This function should be called by the Miner when a Relay has been successfully served. +// It returns an error if the SMST has been flushed to disk which indicates +// that updates are no longer allowed. +func (st *sessionTree) Update(key, value []byte, weight uint64) error { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + if st.claimedRoot != nil { + return ErrSessionTreeClosed + } + + return st.tree.Update(key, value, weight) +} + +// ProveClosest is a wrapper for the SMST's ProveClosest function. It returns a proof for the given path. +// This function is intended to be called after a session has been claimed and needs to be proven. +// If the proof has already been generated, it returns the cached proof. +// It returns an error if the SMST has not been flushed yet (the claim has not been generated) +func (st *sessionTree) ProveClosest(path []byte) (proof *smt.SparseMerkleClosestProof, err error) { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + // A claim need to be generated before a proof can be generated. + if st.claimedRoot == nil { + return nil, ErrSessionTreeNotClosed + } + + // If the proof has already been generated, return the cached proof. + if st.proof != nil { + // Make sure the path is the same as the one for which the proof was generated. + if !bytes.Equal(path, st.proofPath) { + return nil, ErrSessionTreeProofPathMismatch + } + + return st.proof, nil + } + + // Restore the KVStore from disk since it has been closed after the claim has been generated. + st.treeStore, err = smt.NewKVStore(st.storePath) + if err != nil { + return nil, err + } + + st.tree = smt.ImportSparseMerkleSumTree(st.treeStore, sha256.New(), st.claimedRoot, smt.WithValueHasher(nil)) + + // Generate the proof and cache it along with the path for which it was generated. + st.proof, err = st.tree.ProveClosest(path) + st.proofPath = path + + return st.proof, err +} + +// Flush gets the root hash of the SMST needed for submitting the claim; +// then commits the entire tree to disk and stops the KVStore. +// It should be called before submitting the claim on-chain. This function frees up the KVStore resources. +// If the SMST has already been flushed to disk, it returns the cached root hash. +func (st *sessionTree) Flush() (SMSTRoot []byte, err error) { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + // We already have the root hash, return it. + if st.claimedRoot != nil { + return st.claimedRoot, nil + } + + st.claimedRoot = st.tree.Root() + + // Commit the tree to disk + if err := st.tree.Commit(); err != nil { + return nil, err + } + + // Stop the KVStore + if err := st.treeStore.Stop(); err != nil { + return nil, err + } + + st.treeStore = nil + st.tree = nil + + return st.claimedRoot, nil +} + +// Delete deletes the SMST from the KVStore and removes the sessionTree from the RelayerSessionsManager. +// WARNING: This function deletes the KVStore associated to the session and should be +// called only after the proof has been successfully submitted on-chain and the servicer +// has confirmed that it has been rewarded. +func (st *sessionTree) Delete() error { + st.sessionMu.Lock() + defer st.sessionMu.Unlock() + + st.removeFromRelayerSessions(st.session) + + if err := st.treeStore.ClearAll(); err != nil { + return err + } + + if err := st.treeStore.Stop(); err != nil { + return err + } + + // Delete the KVStore from disk + if err := os.RemoveAll(st.storePath); err != nil { + return err + } + + return nil +} diff --git a/pkg/relayer/session/sessiontree_test.go b/pkg/relayer/session/sessiontree_test.go new file mode 100644 index 000000000..4e199dcfe --- /dev/null +++ b/pkg/relayer/session/sessiontree_test.go @@ -0,0 +1,3 @@ +package session_test + +// TODO: Add tests to the sessionTree logic diff --git a/proto/pocket/application/application.proto b/proto/pocket/application/application.proto index e5763d697..f4d3610ca 100644 --- a/proto/pocket/application/application.proto +++ b/proto/pocket/application/application.proto @@ -13,6 +13,6 @@ import "pocket/shared/service.proto"; message Application { string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding cosmos.base.v1beta1.Coin stake = 2; // The total amount of uPOKT the application has staked - repeated shared.ApplicationServiceConfig service_configs = 3; // The ID of the service this session is servicing + repeated shared.ApplicationServiceConfig service_configs = 3; // The list of services this appliccation is configured to request service for repeated string delegatee_gateway_addresses = 4 [(cosmos_proto.scalar) = "cosmos.AddressString", (gogoproto.nullable) = false]; // The Bech32 encoded addresses for all delegatee Gateways, in a non-nullable slice } diff --git a/proto/pocket/application/query.proto b/proto/pocket/application/query.proto index 28a48fb99..949ed3a07 100644 --- a/proto/pocket/application/query.proto +++ b/proto/pocket/application/query.proto @@ -16,17 +16,14 @@ service Query { // Parameters queries the parameters of the module. rpc Params (QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/pocket/application/params"; - } // Queries a list of Application items. rpc Application (QueryGetApplicationRequest) returns (QueryGetApplicationResponse) { option (google.api.http).get = "/pocket/application/application/{address}"; - } rpc ApplicationAll (QueryAllApplicationRequest) returns (QueryAllApplicationResponse) { option (google.api.http).get = "/pocket/application/application"; - } } // QueryParamsRequest is request type for the Query/Params RPC method. diff --git a/proto/pocket/gateway/query.proto b/proto/pocket/gateway/query.proto index 48bd62f98..91080554d 100644 --- a/proto/pocket/gateway/query.proto +++ b/proto/pocket/gateway/query.proto @@ -16,17 +16,14 @@ service Query { // Parameters queries the parameters of the module. rpc Params (QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/pocket/gateway/params"; - } // Queries a list of Gateway items. - rpc Gateway (QueryGetGatewayRequest) returns (QueryGetGatewayResponse) { + rpc Gateway (QueryGetGatewayRequest) returns (QueryGetGatewayResponse) { option (google.api.http).get = "/pocket/gateway/gateway/{address}"; - } rpc GatewayAll (QueryAllGatewayRequest) returns (QueryAllGatewayResponse) { option (google.api.http).get = "/pocket/gateway/gateway"; - } } // QueryParamsRequest is request type for the Query/Params RPC method. diff --git a/proto/pocket/session/query.proto b/proto/pocket/session/query.proto index f8b1c7187..44538be5d 100644 --- a/proto/pocket/session/query.proto +++ b/proto/pocket/session/query.proto @@ -17,13 +17,11 @@ service Query { // Parameters queries the parameters of the module. rpc Params (QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/pocket/session/params"; - } // Queries a list of GetSession items. rpc GetSession (QueryGetSessionRequest) returns (QueryGetSessionResponse) { option (google.api.http).get = "/pocket/session/get_session"; - } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -38,7 +36,7 @@ message QueryParamsResponse { message QueryGetSessionRequest { string application_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - shared.ServiceId service_id = 2; // The service id to query the session for + shared.Service service = 2; // The service id to query the session for int64 block_height = 3; // The block height to query the session for } diff --git a/proto/pocket/session/session.proto b/proto/pocket/session/session.proto index e8f14b35e..e92a5b5ac 100644 --- a/proto/pocket/session/session.proto +++ b/proto/pocket/session/session.proto @@ -14,10 +14,11 @@ import "pocket/shared/supplier.proto"; // It is the minimal amount of data required to hydrate & retrieve all data relevant to the session. message SessionHeader { string application_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the application using cosmos' ScalarDescriptor to ensure deterministic encoding - shared.ServiceId service_id = 2; // The ID of the service this session is servicing + shared.Service service = 2; // The service this session is for int64 session_start_block_height = 3; // The height at which this session started // NOTE: session_id can be derived from the above values using on-chain but is included in the header for convenience string session_id = 4; // A unique pseudoranom ID for this session + int64 session_end_block_height = 5; // The height at which this session ended, this is the last block of the session } // Session is a fully hydrated session object that contains all the information for the Session diff --git a/proto/pocket/shared/service.proto b/proto/pocket/shared/service.proto index c911bad1f..4700035ce 100644 --- a/proto/pocket/shared/service.proto +++ b/proto/pocket/shared/service.proto @@ -8,22 +8,18 @@ option go_package = "github.com/pokt-network/poktroll/x/shared/types"; // TODO_CLEANUP(@Olshansk): Add native optional identifiers once its supported; https://github.com/ignite/cli/issues/3698 -// ServiceId message to encapsulate unique and semantic identifiers for a service on the network -message ServiceId { - // NOTE: `ServiceId.Id` may seem redundant but was desigtned created to enable more complex service identification +// Service message to encapsulate unique and semantic identifiers for a service on the network +message Service { // For example, what if we want to request a session for a certain service but with some additional configs that identify it? string id = 1; // Unique identifier for the service // TODO_TECHDEBT: Name is currently unused but acts as a reminder than an optional onchain representation of the service is necessary string name = 2; // (Optional) Semantic human readable name for the service - - // NOTE: `ServiceId.Id` may seem redundant but was designed to enable more complex service identification. - // For example, what if we want to request a session for a certain service but with some additional configs that identify it? } // ApplicationServiceConfig holds the service configuration the application stakes for message ApplicationServiceConfig { - ServiceId service_id = 1; // Unique and semantic identifier for the service + Service service = 1; // The Service for which the application is configured // TODO_RESEARCH: There is an opportunity for applications to advertise the max // they're willing to pay for a certain configuration/price, but this is outside of scope. @@ -32,7 +28,7 @@ message ApplicationServiceConfig { // SupplierServiceConfig holds the service configuration the supplier stakes for message SupplierServiceConfig { - ServiceId service_id = 1; // Unique and semantic identifier for the service + Service service = 1; // The Service for which the supplier is configured repeated SupplierEndpoint endpoints = 2; // List of endpoints for the service // TODO_RESEARCH: There is an opportunity for supplier to advertise the min // they're willing to earn for a certain configuration/price, but this is outside of scope. diff --git a/proto/pocket/supplier/claim.proto b/proto/pocket/supplier/claim.proto new file mode 100644 index 000000000..c0c293083 --- /dev/null +++ b/proto/pocket/supplier/claim.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package pocket.supplier; + +option go_package = "github.com/pokt-network/poktroll/x/supplier/types"; + +// TODO_UPNEXT(@Olshansk): The structure below is the default (untouched) scaffolded type. Update +// and productionize it for our use case. +message Claim { + string index = 1; + string supplier_address = 2; + string session_id = 3; + string root_hash = 4; +} + diff --git a/proto/pocket/supplier/genesis.proto b/proto/pocket/supplier/genesis.proto index 81d3550d0..b8f184ef9 100644 --- a/proto/pocket/supplier/genesis.proto +++ b/proto/pocket/supplier/genesis.proto @@ -5,12 +5,15 @@ package pocket.supplier; import "gogoproto/gogo.proto"; import "pocket/supplier/params.proto"; import "pocket/shared/supplier.proto"; +import "pocket/supplier/claim.proto"; option go_package = "github.com/pokt-network/poktroll/x/supplier/types"; // GenesisState defines the supplier module's genesis state. message GenesisState { - Params params = 1 [(gogoproto.nullable) = false]; + Params params = 1 [(gogoproto.nullable) = false]; repeated pocket.shared.Supplier supplierList = 2 [(gogoproto.nullable) = false]; + // TODO_UPNEXT(@Olshansk): Delete `claimList` from the genesis state. + repeated Claim claimList = 3 [(gogoproto.nullable) = false]; } diff --git a/proto/pocket/supplier/query.proto b/proto/pocket/supplier/query.proto index 6ed7eb194..8e15e041a 100644 --- a/proto/pocket/supplier/query.proto +++ b/proto/pocket/supplier/query.proto @@ -7,6 +7,7 @@ import "google/api/annotations.proto"; import "cosmos/base/query/v1beta1/pagination.proto"; import "pocket/supplier/params.proto"; import "pocket/shared/supplier.proto"; +import "pocket/supplier/claim.proto"; option go_package = "github.com/pokt-network/poktroll/x/supplier/types"; @@ -16,17 +17,22 @@ service Query { // Parameters queries the parameters of the module. rpc Params (QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/pocket/supplier/params"; - } // Queries a list of Supplier items. - rpc Supplier (QueryGetSupplierRequest) returns (QueryGetSupplierResponse) { + rpc Supplier (QueryGetSupplierRequest) returns (QueryGetSupplierResponse) { option (google.api.http).get = "/pocket/supplier/supplier/{address}"; - } rpc SupplierAll (QueryAllSupplierRequest) returns (QueryAllSupplierResponse) { option (google.api.http).get = "/pocket/supplier/supplier"; + } + // Queries a list of Claim items. + rpc Claim (QueryGetClaimRequest) returns (QueryGetClaimResponse) { + option (google.api.http).get = "/pocket/supplier/claim/{index}"; + } + rpc AllClaims (QueryAllClaimsRequest) returns (QueryAllClaimsResponse) { + option (google.api.http).get = "/pocket/supplier/claim"; } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -34,7 +40,6 @@ message QueryParamsRequest {} // QueryParamsResponse is response type for the Query/Params RPC method. message QueryParamsResponse { - // params holds all the parameters of this module. Params params = 1 [(gogoproto.nullable) = false]; } @@ -52,7 +57,23 @@ message QueryAllSupplierRequest { } message QueryAllSupplierResponse { - repeated pocket.shared.Supplier supplier = 1 [(gogoproto.nullable) = false]; + repeated pocket.shared.Supplier supplier = 1 [(gogoproto.nullable) = false]; cosmos.base.query.v1beta1.PageResponse pagination = 2; } +message QueryGetClaimRequest { + string index = 1; +} + +message QueryGetClaimResponse { + Claim claim = 1 [(gogoproto.nullable) = false]; +} + +message QueryAllClaimsRequest { + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +message QueryAllClaimsResponse { + repeated Claim claim = 1 [(gogoproto.nullable) = false]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} \ No newline at end of file diff --git a/testutil/keeper/session.go b/testutil/keeper/session.go index e4be2537f..8b57ec0c4 100644 --- a/testutil/keeper/session.go +++ b/testutil/keeper/session.go @@ -27,33 +27,44 @@ import ( type option[V any] func(k *keeper.Keeper) var ( - TestServiceId1 = "svc1" - TestServiceId2 = "svc2" + TestServiceId1 = "svc1" // staked for by app1 & supplier1 + TestServiceId11 = "svc11" // staked for by app1 - TestApp1Address = "pokt106grzmkmep67pdfrm6ccl9snynryjqus6l3vct" // Generated via sample.AccAddress() + TestServiceId2 = "svc2" // staked for by app2 & supplier1 + TestServiceId22 = "svc22" // staked for by app2 + + TestServiceId12 = "svc12" // staked for by app1, app2 & supplier1 + + TestApp1Address = "pokt1mdccn4u38eyjdxkk4h0jaddw4n3c72u82m5m9e" // Generated via sample.AccAddress() TestApp1 = apptypes.Application{ Address: TestApp1Address, Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId1}, + Service: &sharedtypes.Service{Id: TestServiceId1}, + }, + { + Service: &sharedtypes.Service{Id: TestServiceId11}, }, { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId2}, + Service: &sharedtypes.Service{Id: TestServiceId12}, }, }, } - TestApp2Address = "pokt1dm7tr0a99ja232gzt5rjtrl7hj6z6h40669fwh" // Generated via sample.AccAddress() + TestApp2Address = "pokt133amv5suh75zwkxxcq896azvmmwszg99grvk9f" // Generated via sample.AccAddress() TestApp2 = apptypes.Application{ Address: TestApp1Address, Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId1}, + Service: &sharedtypes.Service{Id: TestServiceId2}, }, { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId2}, + Service: &sharedtypes.Service{Id: TestServiceId22}, + }, + { + Service: &sharedtypes.Service{Id: TestServiceId12}, }, }, } @@ -65,7 +76,7 @@ var ( Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId1}, + Service: &sharedtypes.Service{Id: TestServiceId1}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: TestSupplierUrl, @@ -75,7 +86,17 @@ var ( }, }, { - ServiceId: &sharedtypes.ServiceId{Id: TestServiceId2}, + Service: &sharedtypes.Service{Id: TestServiceId2}, + Endpoints: []*sharedtypes.SupplierEndpoint{ + { + Url: TestSupplierUrl, + RpcType: sharedtypes.RPCType_GRPC, + Configs: make([]*sharedtypes.ConfigOption, 0), + }, + }, + }, + { + Service: &sharedtypes.Service{Id: TestServiceId12}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: TestSupplierUrl, diff --git a/testutil/network/network.go b/testutil/network/network.go index c8ab3efcb..28f4eaa2f 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -111,7 +111,10 @@ func DefaultApplicationModuleGenesisState(t *testing.T, n int) *apptypes.Genesis Stake: &stake, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, + }, + { + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d%d", i, i)}, }, }, } @@ -152,7 +155,7 @@ func DefaultSupplierModuleGenesisState(t *testing.T, n int) *suppliertypes.Genes Stake: &stake, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: fmt.Sprintf("http://localhost:%d", i), diff --git a/x/application/client/cli/query_application.go b/x/application/client/cli/query_application.go index 61a5eb35b..4fbf32228 100644 --- a/x/application/client/cli/query_application.go +++ b/x/application/client/cli/query_application.go @@ -46,7 +46,7 @@ func CmdListApplication() *cobra.Command { func CmdShowApplication() *cobra.Command { cmd := &cobra.Command{ - Use: "show-application [address]", + Use: "show-application ", Short: "shows a application", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/application/client/cli/tx_delegate_to_gateway.go b/x/application/client/cli/tx_delegate_to_gateway.go index ea251e6cd..f1363e6db 100644 --- a/x/application/client/cli/tx_delegate_to_gateway.go +++ b/x/application/client/cli/tx_delegate_to_gateway.go @@ -15,14 +15,14 @@ var _ = strconv.Itoa(0) func CmdDelegateToGateway() *cobra.Command { cmd := &cobra.Command{ - Use: "delegate-to-gateway [gateway address]", + Use: "delegate-to-gateway ", Short: "Delegate an application to a gateway", Long: `Delegate an application to the gateway with the provided address. This is a broadcast operation that delegates authority to the gateway specified to sign relays requests for the application, allowing the gateway act on the behalf of the application during a session. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application delegate-to-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { gatewayAddress := args[0] diff --git a/x/application/client/cli/tx_stake_application.go b/x/application/client/cli/tx_stake_application.go index 510cfd648..2909eaf8e 100644 --- a/x/application/client/cli/tx_stake_application.go +++ b/x/application/client/cli/tx_stake_application.go @@ -21,13 +21,13 @@ func CmdStakeApplication() *cobra.Command { // TODO_HACK: For now we are only specifying the service IDs as a list of of strings separated by commas. // This needs to be expand to specify the full ApplicationServiceConfig. Furthermore, providing a flag to // a file where ApplicationServiceConfig specifying full service configurations in the CLI by providing a flag that accepts a JSON string - Use: "stake-application [amount] [svcId1,svcId2,...,svcIdN]", + Use: "stake-application ", Short: "Stake an application", Long: `Stake an application with the provided parameters. This is a broadcast operation that will stake the tokens and serviceIds and associate them with the application specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application stake-application 1000upokt svc1,svc2,svc3 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application stake-application 1000upokt svc1,svc2,svc3 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { stakeString := args[0] diff --git a/x/application/client/cli/tx_undelegate_from_gateway.go b/x/application/client/cli/tx_undelegate_from_gateway.go index 308a5d8a0..50a755d8f 100644 --- a/x/application/client/cli/tx_undelegate_from_gateway.go +++ b/x/application/client/cli/tx_undelegate_from_gateway.go @@ -22,7 +22,7 @@ that removes the authority from the gateway specified to sign relays requests fo act on the behalf of the application during a session. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application undelegate-from-gateway $(GATEWAY_ADDR) --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { gatewayAddress := args[0] diff --git a/x/application/client/cli/tx_unstake_application.go b/x/application/client/cli/tx_unstake_application.go index bfbf10e32..c602def16 100644 --- a/x/application/client/cli/tx_unstake_application.go +++ b/x/application/client/cli/tx_unstake_application.go @@ -16,13 +16,13 @@ var _ = strconv.Itoa(0) func CmdUnstakeApplication() *cobra.Command { // fromAddress & signature is retrieved via `flags.FlagFrom` in the `clientCtx` cmd := &cobra.Command{ - Use: "unstake-application [amount]", + Use: "unstake-application", Short: "Unstake an application", Long: `Unstake an application. This is a broadcast operation that will unstake the application specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx application unstake-application --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/application/genesis_test.go b/x/application/genesis_test.go index 51d1f7ec9..74ac91f37 100644 --- a/x/application/genesis_test.go +++ b/x/application/genesis_test.go @@ -24,7 +24,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, }, @@ -33,7 +33,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc2"}, + Service: &sharedtypes.Service{Id: "svc2"}, }, }, }, diff --git a/x/application/keeper/application_test.go b/x/application/keeper/application_test.go index 0fd7d7ea1..6af5e3d26 100644 --- a/x/application/keeper/application_test.go +++ b/x/application/keeper/application_test.go @@ -33,7 +33,7 @@ func createNApplication(keeper *keeper.Keeper, ctx sdk.Context, n int) []types.A app.Stake = &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(int64(i))} app.ServiceConfigs = []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, }, } keeper.SetApplication(ctx, *app) diff --git a/x/application/keeper/msg_server_delegate_to_gateway_test.go b/x/application/keeper/msg_server_delegate_to_gateway_test.go index 2e48edf5f..80e0b586a 100644 --- a/x/application/keeper/msg_server_delegate_to_gateway_test.go +++ b/x/application/keeper/msg_server_delegate_to_gateway_test.go @@ -36,7 +36,7 @@ func TestMsgServer_DelegateToGateway_SuccessfullyDelegate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -100,7 +100,7 @@ func TestMsgServer_DelegateToGateway_FailDuplicate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -158,7 +158,7 @@ func TestMsgServer_DelegateToGateway_FailGatewayNotStaked(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -203,7 +203,7 @@ func TestMsgServer_DelegateToGateway_FailMaxReached(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/keeper/msg_server_stake_application_test.go b/x/application/keeper/msg_server_stake_application_test.go index 3b67cd20c..d3e858987 100644 --- a/x/application/keeper/msg_server_stake_application_test.go +++ b/x/application/keeper/msg_server_stake_application_test.go @@ -31,7 +31,7 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -46,7 +46,7 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { require.Equal(t, addr, appFound.Address) require.Equal(t, int64(100), appFound.Stake.Amount.Int64()) require.Len(t, appFound.ServiceConfigs, 1) - require.Equal(t, "svc1", appFound.ServiceConfigs[0].ServiceId.Id) + require.Equal(t, "svc1", appFound.ServiceConfigs[0].Service.Id) // Prepare an updated application with a higher stake and another service updateStakeMsg := &types.MsgStakeApplication{ @@ -54,10 +54,10 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(200)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, { - ServiceId: &sharedtypes.ServiceId{Id: "svc2"}, + Service: &sharedtypes.Service{Id: "svc2"}, }, }, } @@ -69,8 +69,8 @@ func TestMsgServer_StakeApplication_SuccessfulCreateAndUpdate(t *testing.T) { require.True(t, isAppFound) require.Equal(t, int64(200), appFound.Stake.Amount.Int64()) require.Len(t, appFound.ServiceConfigs, 2) - require.Equal(t, "svc1", appFound.ServiceConfigs[0].ServiceId.Id) - require.Equal(t, "svc2", appFound.ServiceConfigs[1].ServiceId.Id) + require.Equal(t, "svc1", appFound.ServiceConfigs[0].Service.Id) + require.Equal(t, "svc2", appFound.ServiceConfigs[1].Service.Id) } func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing.T) { @@ -86,7 +86,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -111,7 +111,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing require.True(t, isAppFound) require.Equal(t, appAddr, app.Address) require.Len(t, app.ServiceConfigs, 1) - require.Equal(t, "svc1", app.ServiceConfigs[0].ServiceId.Id) + require.Equal(t, "svc1", app.ServiceConfigs[0].Service.Id) // Prepare the application stake message with an invalid service ID updateStakeMsg = &types.MsgStakeApplication{ @@ -119,7 +119,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1 INVALID ! & *"}, + Service: &sharedtypes.Service{Id: "svc1 INVALID ! & *"}, }, }, } @@ -133,7 +133,7 @@ func TestMsgServer_StakeApplication_FailRestakingDueToInvalidServices(t *testing require.True(t, isAppFound) require.Equal(t, appAddr, app.Address) require.Len(t, app.ServiceConfigs, 1) - require.Equal(t, "svc1", app.ServiceConfigs[0].ServiceId.Id) + require.Equal(t, "svc1", app.ServiceConfigs[0].Service.Id) } func TestMsgServer_StakeApplication_FailLoweringStake(t *testing.T) { @@ -148,7 +148,7 @@ func TestMsgServer_StakeApplication_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -165,7 +165,7 @@ func TestMsgServer_StakeApplication_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(50)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/keeper/msg_server_undelegate_from_gateway_test.go b/x/application/keeper/msg_server_undelegate_from_gateway_test.go index 30824a6d2..7a3b8283d 100644 --- a/x/application/keeper/msg_server_undelegate_from_gateway_test.go +++ b/x/application/keeper/msg_server_undelegate_from_gateway_test.go @@ -39,7 +39,7 @@ func TestMsgServer_UndelegateFromGateway_SuccessfullyUndelegate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -113,7 +113,7 @@ func TestMsgServer_UndelegateFromGateway_FailNotDelegated(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } @@ -174,7 +174,7 @@ func TestMsgServer_UndelegateFromGateway_SuccessfullyUndelegateFromUnstakedGatew Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/keeper/msg_server_unstake_application_test.go b/x/application/keeper/msg_server_unstake_application_test.go index fece27bd0..4d23ff0a0 100644 --- a/x/application/keeper/msg_server_unstake_application_test.go +++ b/x/application/keeper/msg_server_unstake_application_test.go @@ -32,7 +32,7 @@ func TestMsgServer_UnstakeApplication_Success(t *testing.T) { Stake: &initialStake, Services: []*sharedtypes.ApplicationServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, }, }, } diff --git a/x/application/types/genesis_test.go b/x/application/types/genesis_test.go index 01dd9c174..3e7ce0361 100644 --- a/x/application/types/genesis_test.go +++ b/x/application/types/genesis_test.go @@ -15,13 +15,13 @@ func TestGenesisState_Validate(t *testing.T) { addr1 := sample.AccAddress() stake1 := sdk.NewCoin("upokt", sdk.NewInt(100)) svc1AppConfig := &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + Service: &sharedtypes.Service{Id: "svc1"}, } addr2 := sample.AccAddress() stake2 := sdk.NewCoin("upokt", sdk.NewInt(100)) svc2AppConfig := &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{Id: "svc2"}, + Service: &sharedtypes.Service{Id: "svc2"}, } emptyDelegatees := make([]string, 0) @@ -314,7 +314,7 @@ func TestGenesisState_Validate(t *testing.T) { Address: addr1, Stake: &stake1, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "12345678901"}}, + {Service: &sharedtypes.Service{Id: "12345678901"}}, }, DelegateeGatewayAddresses: emptyDelegatees, }, @@ -333,7 +333,7 @@ func TestGenesisState_Validate(t *testing.T) { Address: addr1, Stake: &stake1, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{ + {Service: &sharedtypes.Service{ Id: "123", Name: "abcdefghijklmnopqrstuvwxyzab-abcdefghijklmnopqrstuvwxyzab", }}, @@ -355,7 +355,7 @@ func TestGenesisState_Validate(t *testing.T) { Address: addr1, Stake: &stake1, ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "12 45 !"}}, + {Service: &sharedtypes.Service{Id: "12 45 !"}}, }, DelegateeGatewayAddresses: emptyDelegatees, }, diff --git a/x/application/types/message_stake_application.go b/x/application/types/message_stake_application.go index 9dba9eebf..a70a54815 100644 --- a/x/application/types/message_stake_application.go +++ b/x/application/types/message_stake_application.go @@ -23,7 +23,7 @@ func NewMsgStakeApplication( appServiceConfigs := make([]*sharedtypes.ApplicationServiceConfig, len(serviceIds)) for idx, serviceId := range serviceIds { appServiceConfigs[idx] = &sharedtypes.ApplicationServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: serviceId, }, } diff --git a/x/application/types/message_stake_application_test.go b/x/application/types/message_stake_application_test.go index c14119898..0d155f50c 100644 --- a/x/application/types/message_stake_application_test.go +++ b/x/application/types/message_stake_application_test.go @@ -23,7 +23,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: "invalid_address", // Stake explicitly nil Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidAddress, @@ -36,7 +36,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), // Stake explicitly nil Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -46,7 +46,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, }, { @@ -55,7 +55,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(0)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -65,7 +65,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(-100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -75,7 +75,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "invalid", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -85,7 +85,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, }, }, err: ErrAppInvalidStake, @@ -98,8 +98,8 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "svc1"}}, - {ServiceId: &sharedtypes.ServiceId{Id: "svc2"}}, + {Service: &sharedtypes.Service{Id: "svc1"}}, + {Service: &sharedtypes.Service{Id: "svc2"}}, }, }, }, @@ -127,7 +127,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "123456790"}}, + {Service: &sharedtypes.Service{Id: "123456790"}}, }, }, err: ErrAppInvalidServiceConfigs, @@ -138,7 +138,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{ + {Service: &sharedtypes.Service{ Id: "123", Name: "abcdefghijklmnopqrstuvwxyzab-abcdefghijklmnopqrstuvwxyzab", }}, @@ -152,7 +152,7 @@ func TestMsgStakeApplication_ValidateBasic(t *testing.T) { Address: sample.AccAddress(), Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.ApplicationServiceConfig{ - {ServiceId: &sharedtypes.ServiceId{Id: "12 45 !"}}, + {Service: &sharedtypes.Service{Id: "12 45 !"}}, }, }, err: ErrAppInvalidServiceConfigs, diff --git a/x/gateway/client/cli/helpers_test.go b/x/gateway/client/cli/helpers_test.go index 927212a46..cf2ccf899 100644 --- a/x/gateway/client/cli/helpers_test.go +++ b/x/gateway/client/cli/helpers_test.go @@ -1,14 +1,24 @@ package cli_test import ( + "strconv" "testing" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/cmd/pocketd/cmd" "github.com/pokt-network/poktroll/testutil/network" "github.com/pokt-network/poktroll/x/gateway/types" - - "github.com/stretchr/testify/require" ) +// Dummy variable to avoid unused import error. +var _ = strconv.IntSize + +// init initializes the SDK configuration. +func init() { + cmd.InitSDKConfig() +} + // networkWithGatewayObjects creates a network with a populated gateway state of n gateway objects func networkWithGatewayObjects(t *testing.T, n int) (*network.Network, []types.Gateway) { t.Helper() diff --git a/x/gateway/client/cli/query_gateway.go b/x/gateway/client/cli/query_gateway.go index 30076ed22..124c515c8 100644 --- a/x/gateway/client/cli/query_gateway.go +++ b/x/gateway/client/cli/query_gateway.go @@ -46,7 +46,7 @@ func CmdListGateway() *cobra.Command { func CmdShowGateway() *cobra.Command { cmd := &cobra.Command{ - Use: "show-gateway [address]", + Use: "show-gateway ", Short: "shows a gateway", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/gateway/client/cli/tx_stake_gateway.go b/x/gateway/client/cli/tx_stake_gateway.go index 2c363b43b..3c4d29a30 100644 --- a/x/gateway/client/cli/tx_stake_gateway.go +++ b/x/gateway/client/cli/tx_stake_gateway.go @@ -3,25 +3,25 @@ package cli import ( "strconv" - "github.com/pokt-network/poktroll/x/gateway/types" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" + + "github.com/pokt-network/poktroll/x/gateway/types" ) var _ = strconv.Itoa(0) func CmdStakeGateway() *cobra.Command { cmd := &cobra.Command{ - Use: "stake-gateway [amount]", + Use: "stake-gateway ", Short: "Stake a gateway", Long: `Stake a gateway with the provided parameters. This is a broadcast operation that will stake the tokens and associate them with the gateway specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx gateway stake-gateway 1000upokt --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/gateway/client/cli/tx_unstake_gateway.go b/x/gateway/client/cli/tx_unstake_gateway.go index e417b7540..e97406495 100644 --- a/x/gateway/client/cli/tx_unstake_gateway.go +++ b/x/gateway/client/cli/tx_unstake_gateway.go @@ -16,12 +16,12 @@ var _ = strconv.Itoa(0) func CmdUnstakeGateway() *cobra.Command { // fromAddress & signature is retrieved via `flags.FlagFrom` in the `clientCtx` cmd := &cobra.Command{ - Use: "unstake-gateway [amount]", + Use: "unstake-gateway ", Short: "Unstake a gateway", Long: `Unstake a gateway. This is a broadcast operation that will unstake the gateway specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx gateway unstake-gateway --keyring-backend test --from $(GATEWAY) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, _ []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/session/client/cli/helpers_test.go b/x/session/client/cli/helpers_test.go new file mode 100644 index 000000000..484bca4db --- /dev/null +++ b/x/session/client/cli/helpers_test.go @@ -0,0 +1,44 @@ +// Package cli_test provides unit tests for the CLI functionality. +package cli_test + +import ( + "strconv" + "testing" + + "github.com/pokt-network/poktroll/cmd/pocketd/cmd" + "github.com/pokt-network/poktroll/testutil/network" + apptypes "github.com/pokt-network/poktroll/x/application/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" + suppliertypes "github.com/pokt-network/poktroll/x/supplier/types" + "github.com/stretchr/testify/require" +) + +// Dummy variable to avoid unused import error. +var _ = strconv.IntSize + +// init initializes the SDK configuration. +func init() { + cmd.InitSDKConfig() +} + +// networkWithApplicationsAndSupplier creates a new network with a given number of supplier & application objects. +// It returns the network and a slice of the created supplier & application objects. +func networkWithApplicationsAndSupplier(t *testing.T, n int) (*network.Network, []sharedtypes.Supplier, []apptypes.Application) { + t.Helper() + cfg := network.DefaultConfig() + + // Prepare the application genesis state + applicationGenesisState := network.DefaultApplicationModuleGenesisState(t, n) + buf, err := cfg.Codec.MarshalJSON(applicationGenesisState) + require.NoError(t, err) + cfg.GenesisState[apptypes.ModuleName] = buf + + // Prepare the supplier genesis state + supplierGenesisState := network.DefaultSupplierModuleGenesisState(t, n) + buf, err = cfg.Codec.MarshalJSON(supplierGenesisState) + require.NoError(t, err) + cfg.GenesisState[suppliertypes.ModuleName] = buf + + // Start the network + return network.New(t, cfg), supplierGenesisState.SupplierList, applicationGenesisState.ApplicationList +} diff --git a/x/session/client/cli/query_get_session.go b/x/session/client/cli/query_get_session.go index 1c21aa881..41b441de7 100644 --- a/x/session/client/cli/query_get_session.go +++ b/x/session/client/cli/query_get_session.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "strconv" "github.com/cosmos/cosmos-sdk/client" @@ -12,26 +13,49 @@ import ( var _ = strconv.Itoa(0) -// TODO(@Olshansk): Implement the CLI component of `GetSession`. func CmdGetSession() *cobra.Command { cmd := &cobra.Command{ - Use: "get-session", + Use: "get-session [block_height]", Short: "Query get-session", - Args: cobra.ExactArgs(0), + Long: `Query the session data for a specific (app, service, height) tuple. + +[block_height] is optional. If unspecified, or set to 0, it defaults to the latest height of the node being queried. + +This is a query operation that will not result in a state transition but simply gives a view into the chain state. + +Example: +$ pocketd --home=$(POCKETD_HOME) q session get-session pokt1mrqt5f7qh8uxs27cjm9t7v9e74a9vvdnq5jva4 svc1 42 --node $(POCKET_NODE)`, + Args: cobra.RangeArgs(2, 3), RunE: func(cmd *cobra.Command, args []string) (err error) { + appAddressString := args[0] + serviceIdString := args[1] + blockHeightString := "0" // 0 will default to latest height + if len(args) == 3 { + blockHeightString = args[2] + } + + blockHeight, err := strconv.ParseInt(blockHeightString, 10, 64) + if err != nil { + return fmt.Errorf("couldn't convert block height to int: %s; (%v)", blockHeightString, err) + } + + getSessionReq := types.NewQueryGetSessionRequest(appAddressString, serviceIdString, blockHeight) + if err := getSessionReq.ValidateBasic(); err != nil { + return err + } + clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { return err } queryClient := types.NewQueryClient(clientCtx) - req := &types.QueryGetSessionRequest{} - res, err := queryClient.GetSession(cmd.Context(), req) + getSessionRes, err := queryClient.GetSession(cmd.Context(), getSessionReq) if err != nil { return err } - return clientCtx.PrintProto(res) + return clientCtx.PrintProto(getSessionRes) }, } diff --git a/x/session/client/cli/query_get_session_test.go b/x/session/client/cli/query_get_session_test.go new file mode 100644 index 000000000..480bafa48 --- /dev/null +++ b/x/session/client/cli/query_get_session_test.go @@ -0,0 +1,197 @@ +package cli_test + +import ( + "fmt" + "testing" + + sdkerrors "cosmossdk.io/errors" + tmcli "github.com/cometbft/cometbft/libs/cli" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/gogo/status" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/x/session/client/cli" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" +) + +func TestCLI_GetSession(t *testing.T) { + // Prepare the network + net, suppliers, applications := networkWithApplicationsAndSupplier(t, 2) + _, err := net.WaitForHeight(10) // Wait for a sufficiently high block height to ensure the staking transactions have been processed + require.NoError(t, err) + val := net.Validators[0] + ctx := val.ClientCtx + + // Sanity check the application configs are what we expect them to be + appSvc0 := applications[0] + appSvc1 := applications[1] + + require.Len(t, appSvc0.ServiceConfigs, 2) + require.Len(t, appSvc1.ServiceConfigs, 2) + + require.Equal(t, appSvc0.ServiceConfigs[0].Service.Id, "svc0") // svc0 has a supplier + require.Equal(t, appSvc0.ServiceConfigs[1].Service.Id, "svc00") // svc00 doesn't have a supplier + require.Equal(t, appSvc1.ServiceConfigs[0].Service.Id, "svc1") // svc1 has a supplier + require.Equal(t, appSvc1.ServiceConfigs[1].Service.Id, "svc11") // svc11 doesn't have a supplier + + // Sanity check the supplier configs are what we expect them to be + supplierSvc0 := suppliers[0] // supplier for svc0 + supplierSvc1 := suppliers[1] // supplier for svc1 + + require.Len(t, supplierSvc0.Services, 1) + require.Len(t, supplierSvc1.Services, 1) + + require.Equal(t, supplierSvc0.Services[0].Service.Id, "svc0") + require.Equal(t, supplierSvc1.Services[0].Service.Id, "svc1") + + // Prepare the test cases + tests := []struct { + desc string + + appAddress string + serviceId string + blockHeight int64 + + expectedErr *sdkerrors.Error + expectedNumSuppliers int + }{ + // Valid requests + { + desc: "valid - block height specified and is zero", + + appAddress: appSvc0.Address, + serviceId: "svc0", + blockHeight: 0, + + expectedErr: nil, + expectedNumSuppliers: 1, + }, + { + desc: "valid - block height specified and is greater than zero", + + appAddress: appSvc1.Address, + serviceId: "svc1", + blockHeight: 10, + + expectedErr: nil, + expectedNumSuppliers: 1, + }, + { + desc: "valid - block height unspecified and defaults to 0", + + appAddress: appSvc0.Address, + serviceId: "svc0", + // blockHeight: intentionally omitted, + + expectedErr: nil, + expectedNumSuppliers: 1, + }, + + // Invalid requests - incompatible state + { + desc: "invalid - app not staked for service", + + appAddress: appSvc0.Address, + serviceId: "svc9001", // appSvc0 is only staked for svc0 (has supplier) and svc00 (doesn't have supplier) and is not staked for service over 9000 + blockHeight: 0, + + expectedErr: sessiontypes.ErrSessionAppNotStakedForService, + }, + { + desc: "invalid - no suppliers staked for service", + + appAddress: appSvc0.Address, // dynamically getting address from applications + serviceId: "svc00", // appSvc0 is only staked for svc0 (has supplier) and svc00 (doesn't have supplier) + blockHeight: 0, + + expectedErr: sessiontypes.ErrSessionSuppliersNotFound, + }, + { + desc: "invalid - block height is in the future", + + appAddress: appSvc0.Address, // dynamically getting address from applications + serviceId: "svc0", + blockHeight: 9001, // block height over 9000 is greater than the context height of 10 + + expectedErr: sessiontypes.ErrSessionInvalidBlockHeight, + }, + + // Invalid requests - bad app address input + { + desc: "invalid - invalid appAddress", + + appAddress: "invalidAddress", // providing a deliberately invalid address + serviceId: "svc0", + blockHeight: 0, + + expectedErr: sessiontypes.ErrSessionInvalidAppAddress, + }, + { + desc: "invalid - missing appAddress", + // appAddress: intentionally omitted + serviceId: "svc0", + blockHeight: 0, + + expectedErr: sessiontypes.ErrSessionInvalidAppAddress, + }, + + // Invalid requests - bad serviceID input + { + desc: "invalid - invalid service ID", + appAddress: appSvc0.Address, // dynamically getting address from applications + serviceId: "invalidServiceId", + blockHeight: 0, + + expectedErr: sessiontypes.ErrSessionInvalidService, + }, + { + desc: "invalid - missing service ID", + appAddress: appSvc0.Address, // dynamically getting address from applications + // serviceId: intentionally omitted + blockHeight: 0, + + expectedErr: sessiontypes.ErrSessionInvalidService, + }, + } + + // We want to use the `--output=json` flag for all tests so it's easy to unmarshal below + common := []string{ + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + + // Run the tests + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + // Prepare the arguments for the CLI command + args := []string{ + tt.appAddress, + tt.serviceId, + fmt.Sprintf("%d", tt.blockHeight), + } + args = append(args, common...) + + // Execute the command + getSessionOut, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdGetSession(), args) + if tt.expectedErr != nil { + stat, ok := status.FromError(tt.expectedErr) + require.True(t, ok) + require.Contains(t, stat.Message(), tt.expectedErr.Error()) + return + } + require.NoError(t, err) + + var getSessionRes sessiontypes.QueryGetSessionResponse + err = net.Config.Codec.UnmarshalJSON(getSessionOut.Bytes(), &getSessionRes) + require.NoError(t, err) + require.NotNil(t, getSessionRes) + + session := getSessionRes.Session + require.NotNil(t, session) + + // Verify some data about the session + require.Equal(t, tt.appAddress, session.Application.Address) + require.Equal(t, tt.serviceId, session.Header.Service.Id) + require.Len(t, session.Suppliers, tt.expectedNumSuppliers) + }) + } +} diff --git a/x/session/keeper/query_get_session.go b/x/session/keeper/query_get_session.go index d9fd3deaf..e8931b343 100644 --- a/x/session/keeper/query_get_session.go +++ b/x/session/keeper/query_get_session.go @@ -15,9 +15,24 @@ func (k Keeper) GetSession(goCtx context.Context, req *types.QueryGetSessionRequ return nil, status.Error(codes.InvalidArgument, "invalid request") } + if err := req.ValidateBasic(); err != nil { + return nil, err + } + ctx := sdk.UnwrapSDKContext(goCtx) - sessionHydrator := NewSessionHydrator(req.ApplicationAddress, req.ServiceId.Id, req.BlockHeight) + // If block height is not specified, use the current (context's latest) block height + // Note that `GetSession` is called via the `Query` service rather than the `Msg` server. + // The former is stateful but does not lead to state transitions, while the latter one + // does. The request height depends on how much the node has synched and only acts as a read, + // while the `Msg` server handles the code flow of the validator/sequencer when a new block + // is being proposed. + blockHeight := req.BlockHeight + if blockHeight == 0 { + blockHeight = ctx.BlockHeight() + } + + sessionHydrator := NewSessionHydrator(req.ApplicationAddress, req.Service.Id, req.BlockHeight) session, err := k.HydrateSession(ctx, sessionHydrator) if err != nil { return nil, err diff --git a/x/session/keeper/query_get_session_test.go b/x/session/keeper/query_get_session_test.go index 5f15a94e9..a8b8ecedb 100644 --- a/x/session/keeper/query_get_session_test.go +++ b/x/session/keeper/query_get_session_test.go @@ -8,6 +8,7 @@ import ( "github.com/pokt-network/poktroll/cmd/pocketd/cmd" keepertest "github.com/pokt-network/poktroll/testutil/keeper" + "github.com/pokt-network/poktroll/testutil/sample" "github.com/pokt-network/poktroll/x/session/types" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) @@ -22,6 +23,7 @@ func init() { func TestSession_GetSession_Success(t *testing.T) { keeper, ctx := keepertest.SessionKeeper(t) + ctx = ctx.WithBlockHeight(100) // provide a sufficiently large block height to avoid errors wctx := sdk.WrapSDKContext(ctx) type test struct { @@ -45,7 +47,7 @@ func TestSession_GetSession_Success(t *testing.T) { blockHeight: 1, // Intentionally only checking a subset of the session metadata returned - expectedSessionId: "e1e51d087e447525d7beb648711eb3deaf016a8089938a158e6a0f600979370c", + expectedSessionId: "cf5bbdce56ee5a7c46c5d5482303907685a7e5dbb22703cd4a85df521b9ab6e9", expectedSessionNumber: 0, expectedNumSuppliers: 1, }, @@ -53,10 +55,9 @@ func TestSession_GetSession_Success(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := &types.QueryGetSessionRequest{ ApplicationAddress: tt.appAddr, - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: tt.serviceId, }, BlockHeight: 1, @@ -75,6 +76,7 @@ func TestSession_GetSession_Success(t *testing.T) { func TestSession_GetSession_Failure(t *testing.T) { keeper, ctx := keepertest.SessionKeeper(t) + ctx = ctx.WithBlockHeight(100) // provide a sufficiently large block height to avoid errors wctx := sdk.WrapSDKContext(ctx) type test struct { @@ -91,20 +93,56 @@ func TestSession_GetSession_Failure(t *testing.T) { { name: "application address does not reflected a staked application", - appAddr: "some string that is not a valid app address", + appAddr: sample.AccAddress(), // a random (valid) app address that's not staked serviceId: keepertest.TestServiceId1, blockHeight: 1, - expectedErrContains: types.ErrAppNotFound.Error(), + expectedErrContains: types.ErrSessionAppNotFound.Error(), + }, + { + name: "application staked for service that has no available suppliers", + + appAddr: keepertest.TestApp1Address, + serviceId: keepertest.TestServiceId11, + blockHeight: 1, + + expectedErrContains: types.ErrSessionSuppliersNotFound.Error(), }, { - name: "service ID does not reflect one with staked suppliers", + name: "application is valid but not staked for the specified service", appAddr: keepertest.TestApp1Address, - serviceId: "some string that is not a valid service Id", + serviceId: "svc9001", // App1 is not staked for service over 9000 blockHeight: 1, - expectedErrContains: types.ErrSuppliersNotFound.Error(), + expectedErrContains: types.ErrSessionAppNotStakedForService.Error(), + }, + { + name: "application address is invalid format", + + appAddr: "invalid_app_address", + serviceId: keepertest.TestServiceId1, + blockHeight: 1, + + expectedErrContains: types.ErrSessionInvalidAppAddress.Error(), + }, + { + name: "service ID is invalid", + + appAddr: keepertest.TestApp1Address, + serviceId: "service_id_is_too_long_to_be_valid", + blockHeight: 1, + + expectedErrContains: "invalid service in session", + }, + { + name: "negative block height", + + appAddr: keepertest.TestApp1Address, + serviceId: keepertest.TestServiceId1, + blockHeight: -1, + + expectedErrContains: "invalid block height for session being retrieved", }, } @@ -112,13 +150,12 @@ func TestSession_GetSession_Failure(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := &types.QueryGetSessionRequest{ ApplicationAddress: tt.appAddr, - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: tt.serviceId, }, - BlockHeight: 1, + BlockHeight: tt.blockHeight, } res, err := keeper.GetSession(wctx, req) diff --git a/x/session/keeper/session_hydrator.go b/x/session/keeper/session_hydrator.go index b6e78e2c8..38f9d529e 100644 --- a/x/session/keeper/session_hydrator.go +++ b/x/session/keeper/session_hydrator.go @@ -12,6 +12,7 @@ import ( _ "golang.org/x/crypto/sha3" "github.com/pokt-network/poktroll/x/session/types" + sharedhelpers "github.com/pokt-network/poktroll/x/shared/helpers" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) @@ -45,7 +46,7 @@ func NewSessionHydrator( ) *sessionHydrator { sessionHeader := &types.SessionHeader{ ApplicationAddress: appAddress, - ServiceId: &sharedtypes.ServiceId{Id: serviceId}, + Service: &sharedtypes.Service{Id: serviceId}, } return &sessionHydrator{ sessionHeader: sessionHeader, @@ -61,22 +62,22 @@ func (k Keeper) HydrateSession(ctx sdk.Context, sh *sessionHydrator) (*types.Ses logger := k.Logger(ctx).With("method", "hydrateSession") if err := k.hydrateSessionMetadata(ctx, sh); err != nil { - return nil, sdkerrors.Wrapf(types.ErrHydratingSession, "failed to hydrate the session metadata: %v", err) + return nil, sdkerrors.Wrapf(types.ErrSessionHydration, "failed to hydrate the session metadata: %v", err) } logger.Debug("Finished hydrating session metadata") if err := k.hydrateSessionID(ctx, sh); err != nil { - return nil, sdkerrors.Wrapf(types.ErrHydratingSession, "failed to hydrate the session ID: %v", err) + return nil, sdkerrors.Wrapf(types.ErrSessionHydration, "failed to hydrate the session ID: %v", err) } logger.Info("Finished hydrating session ID: %s", sh.sessionHeader.SessionId) if err := k.hydrateSessionApplication(ctx, sh); err != nil { - return nil, sdkerrors.Wrapf(types.ErrHydratingSession, "failed to hydrate application for session: %v", err) + return nil, sdkerrors.Wrapf(types.ErrSessionHydration, "failed to hydrate application for session: %v", err) } logger.Debug("Finished hydrating session application: %+v", sh.session.Application) if err := k.hydrateSessionSuppliers(ctx, sh); err != nil { - return nil, sdkerrors.Wrapf(types.ErrHydratingSession, "failed to hydrate suppliers for session: %v", err) + return nil, sdkerrors.Wrapf(types.ErrSessionHydration, "failed to hydrate suppliers for session: %v", err) } logger.Debug("Finished hydrating session suppliers: %+v") @@ -90,9 +91,14 @@ func (k Keeper) HydrateSession(ctx sdk.Context, sh *sessionHydrator) (*types.Ses func (k Keeper) hydrateSessionMetadata(ctx sdk.Context, sh *sessionHydrator) error { // TODO_TECHDEBT: Add a test if `blockHeight` is ahead of the current chain or what this node is aware of + if sh.blockHeight > ctx.BlockHeight() { + return sdkerrors.Wrapf(types.ErrSessionHydration, "block height %d is ahead of the current block height %d", sh.blockHeight, ctx.BlockHeight()) + } + sh.session.NumBlocksPerSession = NumBlocksPerSession sh.session.SessionNumber = int64(sh.blockHeight / NumBlocksPerSession) sh.sessionHeader.SessionStartBlockHeight = sh.blockHeight - (sh.blockHeight % NumBlocksPerSession) + sh.sessionHeader.SessionEndBlockHeight = sh.sessionHeader.SessionStartBlockHeight + NumBlocksPerSession return nil } @@ -105,10 +111,13 @@ func (k Keeper) hydrateSessionID(ctx sdk.Context, sh *sessionHydrator) error { prevHashBz := []byte("TODO_BLOCKER: See the comment above") appPubKeyBz := []byte(sh.sessionHeader.ApplicationAddress) - // TODO_TECHDEBT: In the future, we will need to valid that the ServiceId is a valid service depending on whether - // or not its permissioned or permissionless - // TODO(@Olshansk): Add a check to make sure `IsValidServiceName(ServiceId.Id)` returns True - serviceIdBz := []byte(sh.sessionHeader.ServiceId.Id) + // TODO_TECHDEBT: In the future, we will need to valid that the Service is a valid service depending on whether + // or not its permissioned or permissionless + + if !sharedhelpers.IsValidService(sh.sessionHeader.Service) { + return sdkerrors.Wrapf(types.ErrSessionHydration, "invalid service: %v", sh.sessionHeader.Service) + } + serviceIdBz := []byte(sh.sessionHeader.Service.Id) sessionHeightBz := make([]byte, 8) binary.LittleEndian.PutUint64(sessionHeightBz, uint64(sh.sessionHeader.SessionStartBlockHeight)) @@ -123,10 +132,17 @@ func (k Keeper) hydrateSessionID(ctx sdk.Context, sh *sessionHydrator) error { func (k Keeper) hydrateSessionApplication(ctx sdk.Context, sh *sessionHydrator) error { app, appIsFound := k.appKeeper.GetApplication(ctx, sh.sessionHeader.ApplicationAddress) if !appIsFound { - return sdkerrors.Wrapf(types.ErrAppNotFound, "could not find app with address: %s at height %d", sh.sessionHeader.ApplicationAddress, sh.sessionHeader.SessionStartBlockHeight) + return sdkerrors.Wrapf(types.ErrSessionAppNotFound, "could not find app with address: %s at height %d", sh.sessionHeader.ApplicationAddress, sh.sessionHeader.SessionStartBlockHeight) } - sh.session.Application = &app - return nil + + for _, appServiceConfig := range app.ServiceConfigs { + if appServiceConfig.Service.Id == sh.sessionHeader.Service.Id { + sh.session.Application = &app + return nil + } + } + + return sdkerrors.Wrapf(types.ErrSessionAppNotStakedForService, "application %s not staked for service %s", sh.sessionHeader.ApplicationAddress, sh.sessionHeader.Service.Id) } // hydrateSessionSuppliers finds the suppliers that are staked at the session height and populates the session with them @@ -144,7 +160,7 @@ func (k Keeper) hydrateSessionSuppliers(ctx sdk.Context, sh *sessionHydrator) er for _, supplier := range suppliers { // TODO_OPTIMIZE: If `supplier.Services` was a map[string]struct{}, we could eliminate `slices.Contains()`'s loop for _, supplierServiceConfig := range supplier.Services { - if supplierServiceConfig.ServiceId.Id == sh.sessionHeader.ServiceId.Id { + if supplierServiceConfig.Service.Id == sh.sessionHeader.Service.Id { candidateSuppliers = append(candidateSuppliers, &supplier) break } @@ -153,7 +169,7 @@ func (k Keeper) hydrateSessionSuppliers(ctx sdk.Context, sh *sessionHydrator) er if len(candidateSuppliers) == 0 { logger.Error("[ERROR] no suppliers found for session") - return sdkerrors.Wrapf(types.ErrSuppliersNotFound, "could not find suppliers for service %s at height %d", sh.sessionHeader.ServiceId, sh.sessionHeader.SessionStartBlockHeight) + return sdkerrors.Wrapf(types.ErrSessionSuppliersNotFound, "could not find suppliers for service %s at height %d", sh.sessionHeader.Service, sh.sessionHeader.SessionStartBlockHeight) } if len(candidateSuppliers) < NumSupplierPerSession { diff --git a/x/session/keeper/session_hydrator_test.go b/x/session/keeper/session_hydrator_test.go index c50d653ab..897dd8539 100644 --- a/x/session/keeper/session_hydrator_test.go +++ b/x/session/keeper/session_hydrator_test.go @@ -13,6 +13,7 @@ import ( func TestSession_HydrateSession_Success_BaseCase(t *testing.T) { sessionKeeper, ctx := keepertest.SessionKeeper(t) + ctx = ctx.WithBlockHeight(100) // provide a sufficiently large block height to avoid errors blockHeight := int64(10) sessionHydrator := keeper.NewSessionHydrator(keepertest.TestApp1Address, keepertest.TestServiceId1, blockHeight) @@ -22,96 +23,120 @@ func TestSession_HydrateSession_Success_BaseCase(t *testing.T) { // Check the header sessionHeader := session.Header require.Equal(t, keepertest.TestApp1Address, sessionHeader.ApplicationAddress) - require.Equal(t, keepertest.TestServiceId1, sessionHeader.ServiceId.Id) - require.Equal(t, "", sessionHeader.ServiceId.Name) + require.Equal(t, keepertest.TestServiceId1, sessionHeader.Service.Id) + require.Equal(t, "", sessionHeader.Service.Name) require.Equal(t, int64(8), sessionHeader.SessionStartBlockHeight) - require.Equal(t, "23f037a10f9d51d020d27763c42dd391d7e71765016d95d0d61f36c4a122efd0", sessionHeader.SessionId) + require.Equal(t, int64(12), sessionHeader.SessionEndBlockHeight) + require.Equal(t, "5481d5ca2ddb15dc5edb792b8e20ba9c7d516a74475fc5feba6b6aeb95a26f58", sessionHeader.SessionId) // Check the session require.Equal(t, int64(4), session.NumBlocksPerSession) - require.Equal(t, "23f037a10f9d51d020d27763c42dd391d7e71765016d95d0d61f36c4a122efd0", session.SessionId) + require.Equal(t, "5481d5ca2ddb15dc5edb792b8e20ba9c7d516a74475fc5feba6b6aeb95a26f58", session.SessionId) require.Equal(t, int64(2), session.SessionNumber) // Check the application app := session.Application require.Equal(t, keepertest.TestApp1Address, app.Address) - require.Len(t, app.ServiceConfigs, 2) + require.Len(t, app.ServiceConfigs, 3) // Check the suppliers suppliers := session.Suppliers require.Len(t, suppliers, 1) supplier := suppliers[0] require.Equal(t, keepertest.TestSupplierAddress, supplier.Address) - require.Len(t, supplier.Services, 2) + require.Len(t, supplier.Services, 3) } func TestSession_HydrateSession_Metadata(t *testing.T) { type test struct { - name string + desc string blockHeight int64 expectedNumBlocksPerSession int64 expectedSessionNumber int64 expectedSessionStartBlock int64 + expectedSessionEndBlock int64 + errExpected error } // TODO_TECHDEBT: Extend these tests once `NumBlocksPerSession` is configurable. // Currently assumes NumBlocksPerSession=4 tests := []test{ { - name: "blockHeight = 0", + desc: "blockHeight = 0", blockHeight: 0, expectedNumBlocksPerSession: 4, expectedSessionNumber: 0, expectedSessionStartBlock: 0, + expectedSessionEndBlock: 4, + errExpected: nil, }, { - name: "blockHeight = 1", + desc: "blockHeight = 1", blockHeight: 1, expectedNumBlocksPerSession: 4, expectedSessionNumber: 0, expectedSessionStartBlock: 0, + expectedSessionEndBlock: 4, + errExpected: nil, }, { - name: "blockHeight = sessionHeight", + desc: "blockHeight = sessionHeight", blockHeight: 4, expectedNumBlocksPerSession: 4, expectedSessionNumber: 1, expectedSessionStartBlock: 4, + expectedSessionEndBlock: 8, + errExpected: nil, }, { - name: "blockHeight != sessionHeight", + desc: "blockHeight != sessionHeight", blockHeight: 5, expectedNumBlocksPerSession: 4, expectedSessionNumber: 1, expectedSessionStartBlock: 4, + expectedSessionEndBlock: 8, + errExpected: nil, + }, + { + desc: "blockHeight > contextHeight", + blockHeight: 9001, // block height over 9000 is too height given that the context height is 100 + + errExpected: types.ErrSessionHydration, }, } appAddr := keepertest.TestApp1Address serviceId := keepertest.TestServiceId1 sessionKeeper, ctx := keepertest.SessionKeeper(t) + ctx = ctx.WithBlockHeight(100) // provide a sufficiently large block height to avoid errors for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { sessionHydrator := keeper.NewSessionHydrator(appAddr, serviceId, tt.blockHeight) session, err := sessionKeeper.HydrateSession(ctx, sessionHydrator) + + if tt.errExpected != nil { + require.ErrorIs(t, tt.errExpected, err) + return + } require.NoError(t, err) require.Equal(t, tt.expectedNumBlocksPerSession, session.NumBlocksPerSession) require.Equal(t, tt.expectedSessionNumber, session.SessionNumber) require.Equal(t, tt.expectedSessionStartBlock, session.Header.SessionStartBlockHeight) + require.Equal(t, tt.expectedSessionEndBlock, session.Header.SessionEndBlockHeight) }) } } func TestSession_HydrateSession_SessionId(t *testing.T) { type test struct { - name string + desc string blockHeight1 int64 blockHeight2 int64 @@ -130,7 +155,7 @@ func TestSession_HydrateSession_SessionId(t *testing.T) { // Currently assumes NumBlocksPerSession=4 tests := []test{ { - name: "(app1, svc1): sessionId at first session block != sessionId at next session block", + desc: "(app1, svc1): sessionId at first session block != sessionId at next session block", blockHeight1: 4, blockHeight2: 8, @@ -141,11 +166,11 @@ func TestSession_HydrateSession_SessionId(t *testing.T) { serviceId1: keepertest.TestServiceId1, // svc1 serviceId2: keepertest.TestServiceId1, // svc1 - expectedSessionId1: "aabaa25668538f80395170be95ce1d1536d9228353ced71cc3b763171316fe39", - expectedSessionId2: "23f037a10f9d51d020d27763c42dd391d7e71765016d95d0d61f36c4a122efd0", + expectedSessionId1: "251665c7cf286a30fbd98acd983c63e9a34efc16496511373405e24eb02a8fb9", + expectedSessionId2: "5481d5ca2ddb15dc5edb792b8e20ba9c7d516a74475fc5feba6b6aeb95a26f58", }, { - name: "app1: sessionId for svc1 != sessionId for svc2", + desc: "app1: sessionId for svc1 != sessionId for svc12", blockHeight1: 4, blockHeight2: 4, @@ -153,14 +178,14 @@ func TestSession_HydrateSession_SessionId(t *testing.T) { appAddr1: keepertest.TestApp1Address, // app1 appAddr2: keepertest.TestApp1Address, // app1 - serviceId1: keepertest.TestServiceId1, // svc1 - serviceId2: keepertest.TestServiceId2, // svc2 + serviceId1: keepertest.TestServiceId1, // svc1 + serviceId2: keepertest.TestServiceId12, // svc12 - expectedSessionId1: "aabaa25668538f80395170be95ce1d1536d9228353ced71cc3b763171316fe39", - expectedSessionId2: "478d005769e5edf38d9bf2d8828a56d78b17348bb2c4796dd6d85b5d736a908a", + expectedSessionId1: "251665c7cf286a30fbd98acd983c63e9a34efc16496511373405e24eb02a8fb9", + expectedSessionId2: "44fce80205bece269429a5dc8b55f9d96e5bf7acdb9838f2ac9aa7216905a1cf", }, { - name: "svc1: sessionId for app1 != sessionId for app2", + desc: "svc12: sessionId for app1 != sessionId for app2", blockHeight1: 4, blockHeight2: 4, @@ -168,18 +193,19 @@ func TestSession_HydrateSession_SessionId(t *testing.T) { appAddr1: keepertest.TestApp1Address, // app1 appAddr2: keepertest.TestApp2Address, // app2 - serviceId1: keepertest.TestServiceId1, // svc1 - serviceId2: keepertest.TestServiceId1, // svc1 + serviceId1: keepertest.TestServiceId12, // svc12 + serviceId2: keepertest.TestServiceId12, // svc12 - expectedSessionId1: "aabaa25668538f80395170be95ce1d1536d9228353ced71cc3b763171316fe39", - expectedSessionId2: "b4b0d8747b1cf67050a7bfefd7e93ebbad80c534fa14fb3c69339886f2ed7061", + expectedSessionId1: "44fce80205bece269429a5dc8b55f9d96e5bf7acdb9838f2ac9aa7216905a1cf", + expectedSessionId2: "22328e12562532047c9d4200beaedc9be694cd99b38938ba64cf4cdca0a8ecba", }, } sessionKeeper, ctx := keepertest.SessionKeeper(t) + ctx = ctx.WithBlockHeight(100) // provide a sufficiently large block height to avoid errors for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { sessionHydrator1 := keeper.NewSessionHydrator(tt.appAddr1, tt.serviceId1, tt.blockHeight1) session1, err := sessionKeeper.HydrateSession(ctx, sessionHydrator1) require.NoError(t, err) @@ -198,30 +224,48 @@ func TestSession_HydrateSession_SessionId(t *testing.T) { // TODO_TECHDEBT: Expand these tests to account for application joining/leaving the network at different heights as well changing the services they support func TestSession_HydrateSession_Application(t *testing.T) { type test struct { - name string - appAddr string + // Description + desc string + // Inputs + appAddr string + serviceId string + // Outputs expectedErr error } tests := []test{ { - name: "app is found", - appAddr: keepertest.TestApp1Address, + desc: "app is found", + + appAddr: keepertest.TestApp1Address, + serviceId: keepertest.TestServiceId1, expectedErr: nil, }, { - name: "app is not found", - appAddr: sample.AccAddress(), // Generating a random address on the fly + desc: "app is not found", - expectedErr: types.ErrHydratingSession, + appAddr: sample.AccAddress(), // Generating a random address on the fly + serviceId: keepertest.TestServiceId1, + + expectedErr: types.ErrSessionHydration, }, { - name: "invalid app address", - appAddr: "invalid", + desc: "invalid app address", + + appAddr: "invalid", + serviceId: keepertest.TestServiceId1, - expectedErr: types.ErrHydratingSession, + expectedErr: types.ErrSessionHydration, + }, + { + desc: "invalid - app not staked for service", + + appAddr: keepertest.TestApp1Address, // app1 + serviceId: "svc9001", // app1 is only stake for svc1 and svc11 + + expectedErr: types.ErrSessionHydration, }, // TODO_TECHDEBT: Add tests for when: // - Application join/leaves (stakes/unstakes) altogether @@ -229,13 +273,13 @@ func TestSession_HydrateSession_Application(t *testing.T) { // - Application increases stakes mid-session } - serviceId := keepertest.TestServiceId1 blockHeight := int64(10) sessionKeeper, ctx := keepertest.SessionKeeper(t) + ctx = ctx.WithBlockHeight(100) // provide a sufficiently large block height to avoid errors for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sessionHydrator := keeper.NewSessionHydrator(tt.appAddr, serviceId, blockHeight) + t.Run(tt.desc, func(t *testing.T) { + sessionHydrator := keeper.NewSessionHydrator(tt.appAddr, tt.serviceId, blockHeight) _, err := sessionKeeper.HydrateSession(ctx, sessionHydrator) if tt.expectedErr != nil { require.Error(t, err) @@ -249,10 +293,14 @@ func TestSession_HydrateSession_Application(t *testing.T) { // TODO_TECHDEBT: Expand these tests to account for supplier joining/leaving the network at different heights as well changing the services they support func TestSession_HydrateSession_Suppliers(t *testing.T) { type test struct { - name string + // Description + desc string + + // Inputs appAddr string serviceId string + // Outputs numExpectedSuppliers int expectedErr error } @@ -261,15 +309,17 @@ func TestSession_HydrateSession_Suppliers(t *testing.T) { // Currently assumes NumSupplierPerSession=15 tests := []test{ { - name: "num_suppliers_available = 0", + desc: "num_suppliers_available = 0", + appAddr: keepertest.TestApp1Address, // app1 - serviceId: "svc_unknown", + serviceId: keepertest.TestServiceId11, numExpectedSuppliers: 0, - expectedErr: types.ErrSuppliersNotFound, + expectedErr: types.ErrSessionSuppliersNotFound, }, { - name: "num_suppliers_available < num_suppliers_per_session_param", + desc: "num_suppliers_available < num_suppliers_per_session_param", + appAddr: keepertest.TestApp1Address, // app1 serviceId: keepertest.TestServiceId1, // svc1 @@ -288,9 +338,10 @@ func TestSession_HydrateSession_Suppliers(t *testing.T) { blockHeight := int64(10) sessionKeeper, ctx := keepertest.SessionKeeper(t) + ctx = ctx.WithBlockHeight(100) // provide a sufficiently large block height to avoid errors for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) {}) + t.Run(tt.desc, func(t *testing.T) {}) sessionHydrator := keeper.NewSessionHydrator(tt.appAddr, tt.serviceId, blockHeight) session, err := sessionKeeper.HydrateSession(ctx, sessionHydrator) diff --git a/x/session/types/errors.go b/x/session/types/errors.go index 3bf90eae7..dde73c1e6 100644 --- a/x/session/types/errors.go +++ b/x/session/types/errors.go @@ -8,7 +8,11 @@ import ( // x/session module sentinel errors var ( - ErrHydratingSession = sdkerrors.Register(ModuleName, 1, "error during session hydration") - ErrAppNotFound = sdkerrors.Register(ModuleName, 2, "application not found") - ErrSuppliersNotFound = sdkerrors.Register(ModuleName, 3, "suppliers not found") + ErrSessionHydration = sdkerrors.Register(ModuleName, 1, "error during session hydration") + ErrSessionAppNotFound = sdkerrors.Register(ModuleName, 2, "application for session not found not found ") + ErrSessionAppNotStakedForService = sdkerrors.Register(ModuleName, 3, "application in session not staked for requested service") + ErrSessionSuppliersNotFound = sdkerrors.Register(ModuleName, 4, "no suppliers not found for session") + ErrSessionInvalidAppAddress = sdkerrors.Register(ModuleName, 5, "invalid application address for session") + ErrSessionInvalidService = sdkerrors.Register(ModuleName, 6, "invalid service in session") + ErrSessionInvalidBlockHeight = sdkerrors.Register(ModuleName, 7, "invalid block height for session") ) diff --git a/x/session/types/query_get_session_request.go b/x/session/types/query_get_session_request.go new file mode 100644 index 000000000..31b705f8e --- /dev/null +++ b/x/session/types/query_get_session_request.go @@ -0,0 +1,40 @@ +package types + +import ( + sdkerrors "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + sharedhelpers "github.com/pokt-network/poktroll/x/shared/helpers" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" +) + +// NOTE: Please note that `QueryGetSessionRequest` is not a `sdk.Msg`, and is therefore not a message/request +// that will be signable or invoke a state transition. However, following a similar `ValidateBasic` pattern +// allows us to localize & reuse validation logic. +func NewQueryGetSessionRequest(appAddress, serviceId string, blockHeight int64) *QueryGetSessionRequest { + return &QueryGetSessionRequest{ + ApplicationAddress: appAddress, + Service: &sharedtypes.Service{ + Id: serviceId, + }, + BlockHeight: blockHeight, + } +} + +func (query *QueryGetSessionRequest) ValidateBasic() error { + // Validate the application address + if _, err := sdk.AccAddressFromBech32(query.ApplicationAddress); err != nil { + return sdkerrors.Wrapf(ErrSessionInvalidAppAddress, "invalid app address for session being retrieved %s; (%v)", query.ApplicationAddress, err) + } + + // Validate the Service ID + if !sharedhelpers.IsValidService(query.Service) { + return sdkerrors.Wrapf(ErrSessionInvalidService, "invalid service for session being retrieved %s;", query.Service) + } + + // Validate the height for which a session is being retrieved + if query.BlockHeight < 0 { // Note that `0` defaults to the latest height rather than genesis + return sdkerrors.Wrapf(ErrSessionInvalidBlockHeight, "invalid block height for session being retrieved %d;", query.BlockHeight) + } + return nil +} diff --git a/x/shared/helpers/service.go b/x/shared/helpers/service.go index 3a4e56e5f..9825f26d4 100644 --- a/x/shared/helpers/service.go +++ b/x/shared/helpers/service.go @@ -3,6 +3,8 @@ package helpers import ( "net/url" "regexp" + + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) const ( @@ -25,6 +27,14 @@ func init() { } +// IsValidService checks if the provided ServiceId struct has valid fields +func IsValidService(service *sharedtypes.Service) bool { + // Check if service Id and Name are valid using the provided helper functions + return service != nil && + IsValidServiceId(service.Id) && + IsValidServiceName(service.Name) +} + // IsValidServiceId checks if the input string is a valid serviceId func IsValidServiceId(serviceId string) bool { // ServiceId CANNOT be empty diff --git a/x/shared/helpers/service_configs.go b/x/shared/helpers/service_configs.go index 6884da7ae..95335d058 100644 --- a/x/shared/helpers/service_configs.go +++ b/x/shared/helpers/service_configs.go @@ -15,17 +15,9 @@ func ValidateAppServiceConfigs(services []*sharedtypes.ApplicationServiceConfig) if serviceConfig == nil { return fmt.Errorf("serviceConfig cannot be nil: %v", services) } - if serviceConfig.ServiceId == nil { - return fmt.Errorf("serviceId cannot be nil: %v", serviceConfig) - } - if serviceConfig.ServiceId.Id == "" { - return fmt.Errorf("serviceId.Id cannot be empty: %v", serviceConfig) - } - if !IsValidServiceId(serviceConfig.ServiceId.Id) { - return fmt.Errorf("invalid serviceId.Id: %v", serviceConfig) - } - if !IsValidServiceName(serviceConfig.ServiceId.Name) { - return fmt.Errorf("invalid serviceId.Name: %v", serviceConfig) + // Check the Service + if !IsValidService(serviceConfig.Service) { + return fmt.Errorf("invalid service: %v", serviceConfig.Service) } } return nil @@ -41,18 +33,9 @@ func ValidateSupplierServiceConfigs(services []*sharedtypes.SupplierServiceConfi return fmt.Errorf("serviceConfig cannot be nil: %v", services) } - // Check the ServiceId - if serviceConfig.ServiceId == nil { - return fmt.Errorf("serviceId cannot be nil: %v", serviceConfig) - } - if serviceConfig.ServiceId.Id == "" { - return fmt.Errorf("serviceId.Id cannot be empty: %v", serviceConfig) - } - if !IsValidServiceId(serviceConfig.ServiceId.Id) { - return fmt.Errorf("invalid serviceId.Id: %v", serviceConfig) - } - if !IsValidServiceName(serviceConfig.ServiceId.Name) { - return fmt.Errorf("invalid serviceId.Name: %v", serviceConfig) + // Check the Service + if !IsValidService(serviceConfig.Service) { + return fmt.Errorf("invalid service: %v", serviceConfig.Service) } // Check the Endpoints diff --git a/x/shared/helpers/service_test.go b/x/shared/helpers/service_test.go index 335cbcd51..d74b77afa 100644 --- a/x/shared/helpers/service_test.go +++ b/x/shared/helpers/service_test.go @@ -1,82 +1,261 @@ package helpers -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/require" + + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" +) + +func TestIsValidService(t *testing.T) { + tests := []struct { + desc string + + serviceId string + serviceName string + + expectedIsValid bool + }{ + { + desc: "Valid ID and Name", + + serviceId: "Service1", + serviceName: "Valid Service Name", + + expectedIsValid: true, + }, + { + desc: "Valid ID and empty Name", + + serviceId: "Srv", + serviceName: "", // Valid because the service name can be empty + + expectedIsValid: true, + }, + { + desc: "ID exceeds max length", + + serviceId: "TooLongId123", // Exceeds maxServiceIdLength + serviceName: "Valid Name", + + expectedIsValid: false, + }, + { + desc: "Name exceeds max length", + + serviceId: "ValidID", + serviceName: "This service name is way too long to be considered valid since it exceeds the max length", + + expectedIsValid: false, + }, + { + desc: "Empty ID is invalid", + + serviceId: "", // Invalid because the service ID cannot be empty + serviceName: "Valid Name", + + expectedIsValid: false, + }, + { + desc: "Invalid characters in ID", + + serviceId: "ID@Invalid", // Invalid character '@' + serviceName: "Valid Name", + + expectedIsValid: false, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + service := &sharedtypes.Service{ + Id: test.serviceId, + Name: test.serviceName, + } + result := IsValidService(service) + require.Equal(t, test.expectedIsValid, result) + }) + } +} + +func TestIsValidServiceName(t *testing.T) { + tests := []struct { + desc string + input string + expected bool + }{ + { + desc: "Valid with hyphen and number", + input: "ValidName-1", + expected: true, + }, + { + desc: "Valid with space and underscore", + input: "Valid Name_1", + expected: true, + }, + { + desc: "Valid name with spaces", + input: "valid name with spaces", + expected: true, + }, + { + desc: "Invalid character '@'", + input: "invalid@name", + expected: false, + }, + { + desc: "Invalid character '.'", + input: "Valid.Name", + expected: false, + }, + { + desc: "Empty string", + input: "", + expected: true, + }, + { + desc: "Exceeds maximum length", + input: "validnamebuttoolongvalidnamebuttoolongvalidnamebuttoolong", + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + result := IsValidServiceName(test.input) + require.Equal(t, test.expected, result) + }) + } +} func TestIsValidServiceId(t *testing.T) { tests := []struct { + desc string + input string expected bool }{ - {"Hello-1", true}, - {"Hello_2", true}, - {"hello-world", false}, // exceeds maxServiceIdLength - {"Hello@", false}, // contains invalid character '@' - {"HELLO", true}, - {"12345678", true}, // exactly maxServiceIdLength - {"123456789", false}, // exceeds maxServiceIdLength - {"Hello.World", false}, // contains invalid character '.' - {"", false}, // empty string + { + desc: "Valid alphanumeric with hyphen", + + input: "Hello-1", + expected: true, + }, + { + desc: "Valid alphanumeric with underscore", + + input: "Hello_2", + expected: true, + }, + { + desc: "Exceeds maximum length", + + input: "hello-world", + expected: false, // exceeds maxServiceIdLength + }, + { + desc: "Contains invalid character '@'", + + input: "Hello@", + expected: false, // contains invalid character '@' + }, + { + desc: "All uppercase", + + input: "HELLO", + expected: true, + }, + { + desc: "Maximum length boundary", + + input: "12345678", + expected: true, // exactly maxServiceIdLength + }, + { + desc: "Above maximum length boundary", + + input: "123456789", + expected: false, // exceeds maxServiceIdLength + }, + { + desc: "Contains invalid character '.'", + + input: "Hello.World", + expected: false, // contains invalid character '.' + }, + { + desc: "Empty string", + + input: "", + expected: false, // empty string + }, } for _, test := range tests { - t.Run(test.input, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { result := IsValidServiceId(test.input) - if result != test.expected { - t.Errorf("For input %s, expected %v but got %v", test.input, test.expected, result) - } + require.Equal(t, test.expected, result) }) } } func TestIsValidEndpointUrl(t *testing.T) { tests := []struct { - name string + desc string + input string expected bool }{ { - name: "valid http URL", + desc: "valid http URL", + input: "http://example.com", expected: true, }, { - name: "valid https URL", + desc: "valid https URL", + input: "https://example.com/path?query=value#fragment", expected: true, }, { - name: "valid localhost URL with scheme", + desc: "valid localhost URL with scheme", + input: "https://localhost:8081", expected: true, }, { - name: "valid loopback URL with scheme", + desc: "valid loopback URL with scheme", + input: "http://127.0.0.1:8081", expected: true, }, { - name: "invalid scheme", + desc: "invalid scheme", + input: "ftp://example.com", expected: false, }, { - name: "missing scheme", + desc: "missing scheme", + input: "example.com", expected: false, }, { - name: "invalid URL", + desc: "invalid URL", + input: "not-a-valid-url", expected: false, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { got := IsValidEndpointUrl(tt.input) - if got != tt.expected { - t.Errorf("IsValidEndpointUrl(%q) = %v, want %v", tt.input, got, tt.expected) - } + require.Equal(t, tt.expected, got) }) } } diff --git a/x/supplier/client/cli/query.go b/x/supplier/client/cli/query.go index da1e3de17..f70e996ba 100644 --- a/x/supplier/client/cli/query.go +++ b/x/supplier/client/cli/query.go @@ -27,6 +27,8 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdQueryParams()) cmd.AddCommand(CmdListSupplier()) cmd.AddCommand(CmdShowSupplier()) + cmd.AddCommand(CmdListClaim()) + cmd.AddCommand(CmdShowClaim()) // this line is used by starport scaffolding # 1 return cmd diff --git a/x/supplier/client/cli/query_claim.go b/x/supplier/client/cli/query_claim.go new file mode 100644 index 000000000..a71bdd246 --- /dev/null +++ b/x/supplier/client/cli/query_claim.go @@ -0,0 +1,84 @@ +package cli + +import ( + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "github.com/pokt-network/poktroll/x/supplier/types" +) + +// Prevent strconv unused error +var _ = strconv.IntSize + +func CmdListClaim() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-claims", + Short: "list all claims", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + params := &types.QueryAllClaimsRequest{ + Pagination: pageReq, + } + + res, err := queryClient.AllClaims(cmd.Context(), params) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddPaginationFlagsToCmd(cmd, cmd.Use) + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdShowClaim() *cobra.Command { + cmd := &cobra.Command{ + Use: "show-claim ", + Short: "shows a claim", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + argIndex := args[0] + + params := &types.QueryGetClaimRequest{ + Index: argIndex, + } + + res, err := queryClient.Claim(cmd.Context(), params) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/supplier/client/cli/query_claim_test.go b/x/supplier/client/cli/query_claim_test.go new file mode 100644 index 000000000..3c42d23de --- /dev/null +++ b/x/supplier/client/cli/query_claim_test.go @@ -0,0 +1,157 @@ +package cli_test + +import ( + "fmt" + "strconv" + "testing" + + tmcli "github.com/cometbft/cometbft/libs/cli" + "github.com/cosmos/cosmos-sdk/client/flags" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/pokt-network/poktroll/testutil/network" + "github.com/pokt-network/poktroll/testutil/nullify" + "github.com/pokt-network/poktroll/x/supplier/client/cli" + "github.com/pokt-network/poktroll/x/supplier/types" +) + +func networkWithClaimObjects(t *testing.T, n int) (*network.Network, []types.Claim) { + t.Helper() + cfg := network.DefaultConfig() + state := types.GenesisState{} + for i := 0; i < n; i++ { + claim := types.Claim{ + Index: strconv.Itoa(i), + } + nullify.Fill(&claim) + state.ClaimList = append(state.ClaimList, claim) + } + buf, err := cfg.Codec.MarshalJSON(&state) + require.NoError(t, err) + cfg.GenesisState[types.ModuleName] = buf + return network.New(t, cfg), state.ClaimList +} + +func TestShowClaim(t *testing.T) { + net, objs := networkWithClaimObjects(t, 2) + + ctx := net.Validators[0].ClientCtx + common := []string{ + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + tests := []struct { + desc string + idIndex string + + args []string + err error + obj types.Claim + }{ + { + desc: "found", + idIndex: objs[0].Index, + + args: common, + obj: objs[0], + }, + { + desc: "not found", + idIndex: strconv.Itoa(100000), + + args: common, + err: status.Error(codes.NotFound, "not found"), + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + args := []string{ + tc.idIndex, + } + args = append(args, tc.args...) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowClaim(), args) + if tc.err != nil { + stat, ok := status.FromError(tc.err) + require.True(t, ok) + require.ErrorIs(t, stat.Err(), tc.err) + } else { + require.NoError(t, err) + var resp types.QueryGetClaimResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.NotNil(t, resp.Claim) + require.Equal(t, + nullify.Fill(&tc.obj), + nullify.Fill(&resp.Claim), + ) + } + }) + } +} + +func TestListClaim(t *testing.T) { + net, objs := networkWithClaimObjects(t, 5) + + ctx := net.Validators[0].ClientCtx + request := func(next []byte, offset, limit uint64, total bool) []string { + args := []string{ + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + if next == nil { + args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) + } else { + args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) + } + args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) + if total { + args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) + } + return args + } + t.Run("ByOffset", func(t *testing.T) { + step := 2 + for i := 0; i < len(objs); i += step { + args := request(nil, uint64(i), uint64(step), false) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListClaim(), args) + require.NoError(t, err) + var resp types.QueryAllClaimsResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.LessOrEqual(t, len(resp.Claim), step) + require.Subset(t, + nullify.Fill(objs), + nullify.Fill(resp.Claim), + ) + } + }) + t.Run("ByKey", func(t *testing.T) { + step := 2 + var next []byte + for i := 0; i < len(objs); i += step { + args := request(next, 0, uint64(step), false) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListClaim(), args) + require.NoError(t, err) + var resp types.QueryAllClaimsResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.LessOrEqual(t, len(resp.Claim), step) + require.Subset(t, + nullify.Fill(objs), + nullify.Fill(resp.Claim), + ) + next = resp.Pagination.NextKey + } + }) + t.Run("Total", func(t *testing.T) { + args := request(nil, 0, uint64(len(objs)), true) + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListClaim(), args) + require.NoError(t, err) + var resp types.QueryAllClaimsResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.NoError(t, err) + require.Equal(t, len(objs), int(resp.Pagination.Total)) + require.ElementsMatch(t, + nullify.Fill(objs), + nullify.Fill(resp.Claim), + ) + }) +} diff --git a/x/supplier/client/cli/query_supplier.go b/x/supplier/client/cli/query_supplier.go index cfbc6b4ec..604c1182f 100644 --- a/x/supplier/client/cli/query_supplier.go +++ b/x/supplier/client/cli/query_supplier.go @@ -46,7 +46,7 @@ func CmdListSupplier() *cobra.Command { func CmdShowSupplier() *cobra.Command { cmd := &cobra.Command{ - Use: "show-supplier [address]", + Use: "show-supplier ", Short: "shows a supplier", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/supplier/client/cli/tx_create_claim.go b/x/supplier/client/cli/tx_create_claim.go index db951891f..2869ce95d 100644 --- a/x/supplier/client/cli/tx_create_claim.go +++ b/x/supplier/client/cli/tx_create_claim.go @@ -2,9 +2,8 @@ package cli import ( "encoding/base64" - "strconv" - "encoding/json" + "strconv" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -21,7 +20,7 @@ var _ = strconv.Itoa(0) func CmdCreateClaim() *cobra.Command { cmd := &cobra.Command{ - Use: "create-claim [session-header] [root-hash-base64]", + Use: "create-claim ", Short: "Broadcast message create-claim", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/supplier/client/cli/tx_stake_supplier.go b/x/supplier/client/cli/tx_stake_supplier.go index eac4b4044..83a1413e2 100644 --- a/x/supplier/client/cli/tx_stake_supplier.go +++ b/x/supplier/client/cli/tx_stake_supplier.go @@ -23,7 +23,7 @@ func CmdStakeSupplier() *cobra.Command { // TODO_HACK: For now we are only specifying the service IDs as a list of of strings separated by commas. // This needs to be expand to specify the full SupplierServiceConfig. Furthermore, providing a flag to // a file where SupplierServiceConfig specifying full service configurations in the CLI by providing a flag that accepts a JSON string - Use: "stake-supplier [amount] [svcId1;url1,svcId2;url2,...,svcIdN;urlN]", + Use: "stake-supplier ", Short: "Stake a supplier", Long: `Stake an supplier with the provided parameters. This is a broadcast operation that will stake the tokens and associate them with the supplier specified by the 'from' address. @@ -32,7 +32,7 @@ of comma separated values of the form 'service;url' where 'service' is the servi For example, an application that stakes for 'anvil' could be matched with a supplier staking for 'anvil;http://anvil:8547'. Example: -$ poktrolld --home=$(POCKETD_HOME) tx supplier stake-supplier 1000upokt anvil;http://anvil:8547 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx supplier stake-supplier 1000upokt anvil;http://anvil:8547 --keyring-backend test --from $(APP) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { stakeString := args[0] @@ -84,7 +84,7 @@ func hackStringToServices(servicesArg string) ([]*sharedtypes.SupplierServiceCon return nil, fmt.Errorf("invalid service string: %s. Expected it to be of the form 'service;url'", serviceString) } service := &sharedtypes.SupplierServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: serviceParts[0], }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/client/cli/tx_submit_proof.go b/x/supplier/client/cli/tx_submit_proof.go index 64c498026..0c3ba758f 100644 --- a/x/supplier/client/cli/tx_submit_proof.go +++ b/x/supplier/client/cli/tx_submit_proof.go @@ -2,9 +2,8 @@ package cli import ( "encoding/base64" - "strconv" - "encoding/json" + "strconv" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -15,13 +14,13 @@ import ( "github.com/pokt-network/poktroll/x/supplier/types" ) -var _ = strconv.Itoa(0) - // TODO(@bryanchriswhite): Add unit tests for the CLI command when implementing the business logic. +var _ = strconv.Itoa(0) + func CmdSubmitProof() *cobra.Command { cmd := &cobra.Command{ - Use: "submit-proof [session-header] [proof-base64]", + Use: "submit-proof ", Short: "Broadcast message submit-proof", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/supplier/client/cli/tx_unstake_supplier.go b/x/supplier/client/cli/tx_unstake_supplier.go index 2daf7c00a..434eceb24 100644 --- a/x/supplier/client/cli/tx_unstake_supplier.go +++ b/x/supplier/client/cli/tx_unstake_supplier.go @@ -17,7 +17,7 @@ func CmdUnstakeSupplier() *cobra.Command { Long: `Unstake an supplier with the provided parameters. This is a broadcast operation that will unstake the supplier specified by the 'from' address. Example: -$ poktrolld --home=$(POCKETD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE)`, +$ poktrolld --home=$(POKTROLLD_HOME) tx supplier unstake-supplier --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE)`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { diff --git a/x/supplier/genesis.go b/x/supplier/genesis.go index fb7d59806..bda688fb9 100644 --- a/x/supplier/genesis.go +++ b/x/supplier/genesis.go @@ -7,12 +7,17 @@ import ( "github.com/pokt-network/poktroll/x/supplier/types" ) +// TODO_TECHDEBT(@Olshansk): Remove existing claims from genesis. // InitGenesis initializes the module's state from a provided genesis state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { // Set all the supplier for _, supplier := range genState.SupplierList { k.SetSupplier(ctx, supplier) } + // Set all the claim + for _, elem := range genState.ClaimList { + k.SetClaim(ctx, elem) + } // this line is used by starport scaffolding # genesis/module/init k.SetParams(ctx, genState.Params) } @@ -23,6 +28,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { genesis.Params = k.GetParams(ctx) genesis.SupplierList = k.GetAllSupplier(ctx) + genesis.ClaimList = k.GetAllClaims(ctx) // this line is used by starport scaffolding # genesis/module/export return genesis diff --git a/x/supplier/genesis_test.go b/x/supplier/genesis_test.go index b6af0545c..3afdd0a8a 100644 --- a/x/supplier/genesis_test.go +++ b/x/supplier/genesis_test.go @@ -24,7 +24,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -42,7 +42,7 @@ func TestGenesis(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -56,6 +56,14 @@ func TestGenesis(t *testing.T) { }, }, }, + ClaimList: []types.Claim{ + { + Index: "0", + }, + { + Index: "1", + }, + }, // this line is used by starport scaffolding # genesis/test/state } @@ -68,5 +76,6 @@ func TestGenesis(t *testing.T) { nullify.Fill(got) require.ElementsMatch(t, genesisState.SupplierList, got.SupplierList) + require.ElementsMatch(t, genesisState.ClaimList, got.ClaimList) // this line is used by starport scaffolding # genesis/test/assert } diff --git a/x/supplier/keeper/claim.go b/x/supplier/keeper/claim.go new file mode 100644 index 000000000..db6f627b4 --- /dev/null +++ b/x/supplier/keeper/claim.go @@ -0,0 +1,64 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/pokt-network/poktroll/x/supplier/types" +) + +// SetClaim set a specific claim in the store from its index +func (k Keeper) SetClaim(ctx sdk.Context, claim types.Claim) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ClaimKeyPrefix)) + b := k.cdc.MustMarshal(&claim) + store.Set(types.ClaimKey( + claim.Index, + ), b) +} + +// GetClaim returns a claim from its index +func (k Keeper) GetClaim( + ctx sdk.Context, + index string, + +) (val types.Claim, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ClaimKeyPrefix)) + + b := store.Get(types.ClaimKey( + index, + )) + if b == nil { + return val, false + } + + k.cdc.MustUnmarshal(b, &val) + return val, true +} + +// RemoveClaim removes a claim from the store +func (k Keeper) RemoveClaim( + ctx sdk.Context, + index string, + +) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ClaimKeyPrefix)) + store.Delete(types.ClaimKey( + index, + )) +} + +// GetAllClaims returns all claim +func (k Keeper) GetAllClaims(ctx sdk.Context) (list []types.Claim) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.ClaimKeyPrefix)) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var val types.Claim + k.cdc.MustUnmarshal(iterator.Value(), &val) + list = append(list, val) + } + + return +} diff --git a/x/supplier/keeper/claim_test.go b/x/supplier/keeper/claim_test.go new file mode 100644 index 000000000..dc80a9938 --- /dev/null +++ b/x/supplier/keeper/claim_test.go @@ -0,0 +1,64 @@ +package keeper_test + +import ( + "strconv" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + keepertest "github.com/pokt-network/poktroll/testutil/keeper" + "github.com/pokt-network/poktroll/testutil/nullify" + "github.com/pokt-network/poktroll/x/supplier/keeper" + "github.com/pokt-network/poktroll/x/supplier/types" +) + +// Prevent strconv unused error +var _ = strconv.IntSize + +func createNClaims(keeper *keeper.Keeper, ctx sdk.Context, n int) []types.Claim { + items := make([]types.Claim, n) + for i := range items { + items[i].Index = strconv.Itoa(i) + + keeper.SetClaim(ctx, items[i]) + } + return items +} + +func TestClaimGet(t *testing.T) { + keeper, ctx := keepertest.SupplierKeeper(t) + items := createNClaims(keeper, ctx, 10) + for _, item := range items { + rst, found := keeper.GetClaim(ctx, + item.Index, + ) + require.True(t, found) + require.Equal(t, + nullify.Fill(&item), + nullify.Fill(&rst), + ) + } +} +func TestClaimRemove(t *testing.T) { + keeper, ctx := keepertest.SupplierKeeper(t) + items := createNClaims(keeper, ctx, 10) + for _, item := range items { + keeper.RemoveClaim(ctx, + item.Index, + ) + _, found := keeper.GetClaim(ctx, + item.Index, + ) + require.False(t, found) + } +} + +func TestGetAllClaims(t *testing.T) { + keeper, ctx := keepertest.SupplierKeeper(t) + items := createNClaims(keeper, ctx, 10) + require.ElementsMatch(t, + nullify.Fill(items), + nullify.Fill(keeper.GetAllClaims(ctx)), + ) +} diff --git a/x/supplier/keeper/msg_server_stake_supplier_test.go b/x/supplier/keeper/msg_server_stake_supplier_test.go index 7ac0ee031..cd6158a81 100644 --- a/x/supplier/keeper/msg_server_stake_supplier_test.go +++ b/x/supplier/keeper/msg_server_stake_supplier_test.go @@ -31,7 +31,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -55,7 +55,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { require.Equal(t, addr, supplierFound.Address) require.Equal(t, int64(100), supplierFound.Stake.Amount.Int64()) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8080", supplierFound.Services[0].Endpoints[0].Url) @@ -65,7 +65,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(200)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -86,7 +86,7 @@ func TestMsgServer_StakeSupplier_SuccessfulCreateAndUpdate(t *testing.T) { require.True(t, isSupplierFound) require.Equal(t, int64(200), supplierFound.Stake.Amount.Int64()) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId2", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId2", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8082", supplierFound.Services[0].Endpoints[0].Url) } @@ -104,7 +104,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -128,7 +128,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svcId"}, + Service: &sharedtypes.Service{Id: "svcId"}, Endpoints: []*sharedtypes.SupplierEndpoint{}, }, }, @@ -143,7 +143,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) require.True(t, isSupplierFound) require.Equal(t, supplierAddr, supplierFound.Address) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8080", supplierFound.Services[0].Endpoints[0].Url) @@ -153,7 +153,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: "svc1 INVALID ! & *"}, + Service: &sharedtypes.Service{Id: "svc1 INVALID ! & *"}, }, }, } @@ -167,7 +167,7 @@ func TestMsgServer_StakeSupplier_FailRestakingDueToInvalidServices(t *testing.T) require.True(t, isSupplierFound) require.Equal(t, supplierAddr, supplierFound.Address) require.Len(t, supplierFound.Services, 1) - require.Equal(t, "svcId", supplierFound.Services[0].ServiceId.Id) + require.Equal(t, "svcId", supplierFound.Services[0].Service.Id) require.Len(t, supplierFound.Services[0].Endpoints, 1) require.Equal(t, "http://localhost:8080", supplierFound.Services[0].Endpoints[0].Url) } @@ -184,7 +184,7 @@ func TestMsgServer_StakeSupplier_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -210,7 +210,7 @@ func TestMsgServer_StakeSupplier_FailLoweringStake(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(50)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/keeper/msg_server_unstake_supplier_test.go b/x/supplier/keeper/msg_server_unstake_supplier_test.go index 163cbe5ae..993201971 100644 --- a/x/supplier/keeper/msg_server_unstake_supplier_test.go +++ b/x/supplier/keeper/msg_server_unstake_supplier_test.go @@ -32,7 +32,7 @@ func TestMsgServer_UnstakeSupplier_Success(t *testing.T) { Stake: &initialStake, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", }, Endpoints: []*sharedtypes.SupplierEndpoint{ diff --git a/x/supplier/keeper/query_claim.go b/x/supplier/keeper/query_claim.go new file mode 100644 index 000000000..6f2021f14 --- /dev/null +++ b/x/supplier/keeper/query_claim.go @@ -0,0 +1,58 @@ +package keeper + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/pokt-network/poktroll/x/supplier/types" +) + +func (k Keeper) AllClaims(goCtx context.Context, req *types.QueryAllClaimsRequest) (*types.QueryAllClaimsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + var claims []types.Claim + ctx := sdk.UnwrapSDKContext(goCtx) + + store := ctx.KVStore(k.storeKey) + claimStore := prefix.NewStore(store, types.KeyPrefix(types.ClaimKeyPrefix)) + + pageRes, err := query.Paginate(claimStore, req.Pagination, func(key []byte, value []byte) error { + var claim types.Claim + if err := k.cdc.Unmarshal(value, &claim); err != nil { + return err + } + + claims = append(claims, claim) + return nil + }) + + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryAllClaimsResponse{Claim: claims, Pagination: pageRes}, nil +} + +func (k Keeper) Claim(goCtx context.Context, req *types.QueryGetClaimRequest) (*types.QueryGetClaimResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + val, found := k.GetClaim( + ctx, + req.Index, + ) + if !found { + return nil, status.Error(codes.NotFound, "not found") + } + + return &types.QueryGetClaimResponse{Claim: val}, nil +} diff --git a/x/supplier/keeper/query_claim_test.go b/x/supplier/keeper/query_claim_test.go new file mode 100644 index 000000000..0d403a104 --- /dev/null +++ b/x/supplier/keeper/query_claim_test.go @@ -0,0 +1,124 @@ +package keeper_test + +import ( + "strconv" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + keepertest "github.com/pokt-network/poktroll/testutil/keeper" + "github.com/pokt-network/poktroll/testutil/nullify" + "github.com/pokt-network/poktroll/x/supplier/types" +) + +func TestClaimQuerySingle(t *testing.T) { + keeper, ctx := keepertest.SupplierKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + msgs := createNClaims(keeper, ctx, 2) + tests := []struct { + desc string + request *types.QueryGetClaimRequest + response *types.QueryGetClaimResponse + err error + }{ + { + desc: "First", + request: &types.QueryGetClaimRequest{ + Index: msgs[0].Index, + }, + response: &types.QueryGetClaimResponse{Claim: msgs[0]}, + }, + { + desc: "Second", + request: &types.QueryGetClaimRequest{ + Index: msgs[1].Index, + }, + response: &types.QueryGetClaimResponse{Claim: msgs[1]}, + }, + { + desc: "KeyNotFound", + request: &types.QueryGetClaimRequest{ + Index: strconv.Itoa(100000), + }, + err: status.Error(codes.NotFound, "not found"), + }, + { + desc: "InvalidRequest", + err: status.Error(codes.InvalidArgument, "invalid request"), + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + response, err := keeper.Claim(wctx, tc.request) + if tc.err != nil { + require.ErrorIs(t, err, tc.err) + } else { + require.NoError(t, err) + require.Equal(t, + nullify.Fill(tc.response), + nullify.Fill(response), + ) + } + }) + } +} + +func TestClaimQueryPaginated(t *testing.T) { + keeper, ctx := keepertest.SupplierKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + msgs := createNClaims(keeper, ctx, 5) + + request := func(next []byte, offset, limit uint64, total bool) *types.QueryAllClaimsRequest { + return &types.QueryAllClaimsRequest{ + Pagination: &query.PageRequest{ + Key: next, + Offset: offset, + Limit: limit, + CountTotal: total, + }, + } + } + t.Run("ByOffset", func(t *testing.T) { + step := 2 + for i := 0; i < len(msgs); i += step { + resp, err := keeper.AllClaims(wctx, request(nil, uint64(i), uint64(step), false)) + require.NoError(t, err) + require.LessOrEqual(t, len(resp.Claim), step) + require.Subset(t, + nullify.Fill(msgs), + nullify.Fill(resp.Claim), + ) + } + }) + t.Run("ByKey", func(t *testing.T) { + step := 2 + var next []byte + for i := 0; i < len(msgs); i += step { + resp, err := keeper.AllClaims(wctx, request(next, 0, uint64(step), false)) + require.NoError(t, err) + require.LessOrEqual(t, len(resp.Claim), step) + require.Subset(t, + nullify.Fill(msgs), + nullify.Fill(resp.Claim), + ) + next = resp.Pagination.NextKey + } + }) + t.Run("Total", func(t *testing.T) { + resp, err := keeper.AllClaims(wctx, request(nil, 0, 0, true)) + require.NoError(t, err) + require.Equal(t, len(msgs), int(resp.Pagination.Total)) + require.ElementsMatch(t, + nullify.Fill(msgs), + nullify.Fill(resp.Claim), + ) + }) + t.Run("InvalidRequest", func(t *testing.T) { + _, err := keeper.AllClaims(wctx, nil) + require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "invalid request")) + }) +} diff --git a/x/supplier/keeper/supplier.go b/x/supplier/keeper/supplier.go index 0c31db2be..559ca4a97 100644 --- a/x/supplier/keeper/supplier.go +++ b/x/supplier/keeper/supplier.go @@ -64,5 +64,5 @@ func (k Keeper) GetAllSupplier(ctx sdk.Context) (suppliers []sharedtypes.Supplie return } -// TODO_OPTIMIZE: Index suppliers by serviceId so we can easily query `k.GetAllSupplier(ctx, ServiceId)` +// TODO_OPTIMIZE: Index suppliers by service so we can easily query `k.GetAllSupplier(ctx, Service)` // func (k Keeper) GetAllSupplier(ctx, sdkContext, serviceId string) (suppliers []sharedtypes.Supplier) {} diff --git a/x/supplier/keeper/supplier_test.go b/x/supplier/keeper/supplier_test.go index c02c5758e..e7b901489 100644 --- a/x/supplier/keeper/supplier_test.go +++ b/x/supplier/keeper/supplier_test.go @@ -33,7 +33,7 @@ func createNSupplier(keeper *keeper.Keeper, ctx sdk.Context, n int) []sharedtype supplier.Stake = &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(int64(i))} supplier.Services = []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{Id: fmt.Sprintf("svc%d", i)}, + Service: &sharedtypes.Service{Id: fmt.Sprintf("svc%d", i)}, Endpoints: []*sharedtypes.SupplierEndpoint{ { Url: fmt.Sprintf("http://localhost:%d", i), diff --git a/x/supplier/types/codec.go b/x/supplier/types/codec.go index 3bb7fcd12..364fde198 100644 --- a/x/supplier/types/codec.go +++ b/x/supplier/types/codec.go @@ -29,6 +29,8 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { // this line is used by starport scaffolding # 3 msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } var ( diff --git a/x/supplier/types/errors.go b/x/supplier/types/errors.go index 02a76a573..bc5a32be9 100644 --- a/x/supplier/types/errors.go +++ b/x/supplier/types/errors.go @@ -8,9 +8,13 @@ import ( // x/supplier module sentinel errors var ( - ErrSupplierInvalidStake = sdkerrors.Register(ModuleName, 1, "invalid supplier stake") - ErrSupplierInvalidAddress = sdkerrors.Register(ModuleName, 2, "invalid supplier address") - ErrSupplierUnauthorized = sdkerrors.Register(ModuleName, 3, "unauthorized supplier signer") - ErrSupplierNotFound = sdkerrors.Register(ModuleName, 4, "supplier not found") - ErrSupplierInvalidServiceConfig = sdkerrors.Register(ModuleName, 5, "invalid service config") + ErrSupplierInvalidStake = sdkerrors.Register(ModuleName, 1, "invalid supplier stake") + ErrSupplierInvalidAddress = sdkerrors.Register(ModuleName, 2, "invalid supplier address") + ErrSupplierUnauthorized = sdkerrors.Register(ModuleName, 3, "unauthorized supplier signer") + ErrSupplierNotFound = sdkerrors.Register(ModuleName, 4, "supplier not found") + ErrSupplierInvalidServiceConfig = sdkerrors.Register(ModuleName, 5, "invalid service config") + ErrSupplierInvalidSessionStartHeight = sdkerrors.Register(ModuleName, 6, "invalid session start height") + ErrSupplierInvalidSessionId = sdkerrors.Register(ModuleName, 7, "invalid session ID") + ErrSupplierInvalidService = sdkerrors.Register(ModuleName, 8, "invalid service in supplier") + ErrSupplierInvalidClaimRootHash = sdkerrors.Register(ModuleName, 9, "invalid root hash") ) diff --git a/x/supplier/types/genesis.go b/x/supplier/types/genesis.go index 2f4ff1872..521bfdc0d 100644 --- a/x/supplier/types/genesis.go +++ b/x/supplier/types/genesis.go @@ -17,6 +17,7 @@ const DefaultIndex uint64 = 1 func DefaultGenesis() *GenesisState { return &GenesisState{ SupplierList: []sharedtypes.Supplier{}, + ClaimList: []Claim{}, // this line is used by starport scaffolding # genesis/types/default Params: DefaultParams(), } @@ -56,13 +57,22 @@ func (gs GenesisState) Validate() error { return sdkerrors.Wrapf(ErrSupplierInvalidStake, "invalid stake amount denom for supplier %v", supplier.Stake) } - // Valid the application service configs // Validate the application service configs if err := servicehelpers.ValidateSupplierServiceConfigs(supplier.Services); err != nil { return sdkerrors.Wrapf(ErrSupplierInvalidServiceConfig, err.Error()) } } + // Check for duplicated index in claim + claimIndexMap := make(map[string]struct{}) + + for _, elem := range gs.ClaimList { + index := string(ClaimKey(elem.Index)) + if _, ok := claimIndexMap[index]; ok { + return fmt.Errorf("duplicated index for claim") + } + claimIndexMap[index] = struct{}{} + } // this line is used by starport scaffolding # genesis/types/validate return gs.Params.Validate() diff --git a/x/supplier/types/genesis_test.go b/x/supplier/types/genesis_test.go index ba806c98b..6a0c11190 100644 --- a/x/supplier/types/genesis_test.go +++ b/x/supplier/types/genesis_test.go @@ -15,7 +15,7 @@ func TestGenesisState_Validate(t *testing.T) { addr1 := sample.AccAddress() stake1 := sdk.NewCoin("upokt", sdk.NewInt(100)) serviceConfig1 := &sharedtypes.SupplierServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -31,7 +31,7 @@ func TestGenesisState_Validate(t *testing.T) { addr2 := sample.AccAddress() stake2 := sdk.NewCoin("upokt", sdk.NewInt(100)) serviceConfig2 := &sharedtypes.SupplierServiceConfig{ - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -70,6 +70,14 @@ func TestGenesisState_Validate(t *testing.T) { Services: serviceList2, }, }, + ClaimList: []types.Claim{ + { + Index: "0", + }, + { + Index: "1", + }, + }, // this line is used by starport scaffolding # types/genesis/validField }, valid: true, @@ -250,7 +258,7 @@ func TestGenesisState_Validate(t *testing.T) { Stake: &stake2, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -281,7 +289,7 @@ func TestGenesisState_Validate(t *testing.T) { Stake: &stake2, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -298,6 +306,20 @@ func TestGenesisState_Validate(t *testing.T) { }, valid: false, }, + { + desc: "duplicated claim", + genState: &types.GenesisState{ + ClaimList: []types.Claim{ + { + Index: "0", + }, + { + Index: "0", + }, + }, + }, + valid: false, + }, // this line is used by starport scaffolding # types/genesis/testcase } for _, tc := range tests { diff --git a/x/supplier/types/key_claim.go b/x/supplier/types/key_claim.go new file mode 100644 index 000000000..e57164b57 --- /dev/null +++ b/x/supplier/types/key_claim.go @@ -0,0 +1,23 @@ +package types + +import "encoding/binary" + +var _ binary.ByteOrder + +const ( + // ClaimKeyPrefix is the prefix to retrieve all Claim + ClaimKeyPrefix = "Claim/value/" +) + +// ClaimKey returns the store key to retrieve a Claim from the index fields +func ClaimKey( + index string, +) []byte { + var key []byte + + indexBytes := []byte(index) + key = append(key, indexBytes...) + key = append(key, []byte("/")...) + + return key +} diff --git a/x/supplier/types/message_create_claim.go b/x/supplier/types/message_create_claim.go index 7d68c6a94..0e702c956 100644 --- a/x/supplier/types/message_create_claim.go +++ b/x/supplier/types/message_create_claim.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedhelpers "github.com/pokt-network/poktroll/x/shared/helpers" ) const TypeMsgCreateClaim = "create_claim" @@ -41,9 +42,29 @@ func (msg *MsgCreateClaim) GetSignBytes() []byte { } func (msg *MsgCreateClaim) ValidateBasic() error { + // Validate the supplier address _, err := sdk.AccAddressFromBech32(msg.SupplierAddress) if err != nil { return sdkerrors.Wrapf(ErrSupplierInvalidAddress, "invalid supplierAddress address (%s)", err) } + + // Validate the session header + sessionHeader := msg.SessionHeader + if sessionHeader.SessionStartBlockHeight < 1 { + return sdkerrors.Wrapf(ErrSupplierInvalidSessionStartHeight, "invalid session start block height (%d)", sessionHeader.SessionStartBlockHeight) + } + if len(sessionHeader.SessionId) == 0 { + return sdkerrors.Wrapf(ErrSupplierInvalidSessionId, "invalid session ID (%v)", sessionHeader.SessionId) + } + if !sharedhelpers.IsValidService(sessionHeader.Service) { + return sdkerrors.Wrapf(ErrSupplierInvalidService, "invalid service (%v)", sessionHeader.Service) + } + + // Validate the root hash + // TODO_IMPROVE: Only checking to make sure a non-nil hash was provided for now, but we can validate the length as well. + if len(msg.RootHash) == 0 { + return sdkerrors.Wrapf(ErrSupplierInvalidClaimRootHash, "invalid root hash (%v)", msg.RootHash) + } + return nil } diff --git a/x/supplier/types/message_create_claim_test.go b/x/supplier/types/message_create_claim_test.go index 8401c0d3d..c46647ebe 100644 --- a/x/supplier/types/message_create_claim_test.go +++ b/x/supplier/types/message_create_claim_test.go @@ -6,37 +6,104 @@ import ( "github.com/stretchr/testify/require" "github.com/pokt-network/poktroll/testutil/sample" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) -// TODO(@bryanchriswhite): Add unit tests for message validation when adding the business logic. - func TestMsgCreateClaim_ValidateBasic(t *testing.T) { tests := []struct { - name string - msg MsgCreateClaim - err error + desc string + + msg MsgCreateClaim + err error }{ { - name: "invalid address", + desc: "invalid address", + msg: MsgCreateClaim{ SupplierAddress: "invalid_address", }, err: ErrSupplierInvalidAddress, - }, { - name: "valid address", + }, + { + desc: "valid address but invalid session start height", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 0, // Invalid start height + }, + }, + err: ErrSupplierInvalidSessionStartHeight, + }, + { + desc: "valid address and session start height but invalid session ID", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "", // Invalid session ID + }, + }, + err: ErrSupplierInvalidSessionId, + }, + { + desc: "valid address, session start height, session ID but invalid service", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "valid_session_id", + Service: &sharedtypes.Service{ + Id: "invalid_service_id", // Assuming this ID is invalid + }, // Should trigger error + }, + }, + err: ErrSupplierInvalidService, + }, + { + desc: "valid address, session start height, session ID, service but invalid root hash", + + msg: MsgCreateClaim{ + SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "valid_session_id", + Service: &sharedtypes.Service{ + Id: "svcId", // Assuming this ID is valid + }, + }, + RootHash: []byte(""), // Invalid root hash + }, + err: ErrSupplierInvalidClaimRootHash, + }, + { + desc: "all valid inputs", + msg: MsgCreateClaim{ SupplierAddress: sample.AccAddress(), + SessionHeader: &sessiontypes.SessionHeader{ + SessionStartBlockHeight: 100, + SessionId: "valid_session_id", + Service: &sharedtypes.Service{ + Id: "svcId", // Assuming this ID is valid + }, + }, + RootHash: []byte("valid_root_hash"), // Assuming this is valid }, + err: nil, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { err := tt.msg.ValidateBasic() if tt.err != nil { require.ErrorIs(t, err, tt.err) - return + } else { + require.NoError(t, err) } - require.NoError(t, err) }) } } diff --git a/x/supplier/types/message_stake_supplier_test.go b/x/supplier/types/message_stake_supplier_test.go index 57ce3e4fd..158fd8ff3 100644 --- a/x/supplier/types/message_stake_supplier_test.go +++ b/x/supplier/types/message_stake_supplier_test.go @@ -17,7 +17,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { defaultServicesList := []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -103,7 +103,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId1", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -115,7 +115,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { }, }, { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId2", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -154,7 +154,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "123456790", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -176,7 +176,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "123", Name: "abcdefghijklmnopqrstuvwxyzab-abcdefghijklmnopqrstuvwxyzab", }, @@ -199,7 +199,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "12 45 !", }, Endpoints: []*sharedtypes.SupplierEndpoint{ @@ -221,7 +221,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", Name: "name", }, @@ -244,7 +244,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", Name: "name", }, @@ -267,7 +267,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, Services: []*sharedtypes.SupplierServiceConfig{ { - ServiceId: &sharedtypes.ServiceId{ + Service: &sharedtypes.Service{ Id: "svcId", Name: "name", }, From 6e30e58e4894598954aaa7d37bce7f05a6ef47fe Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Wed, 8 Nov 2023 23:20:19 +0100 Subject: [PATCH 19/19] chore: Revert relay request & response signing and verification interface --- pkg/relayer/interface.go | 8 +++----- pkg/relayer/proxy/jsonrpc.go | 3 +-- pkg/relayer/proxy/relay_builders.go | 3 +-- pkg/relayer/proxy/relay_signer.go | 12 +++++------- pkg/relayer/proxy/relay_verifier.go | 21 ++++++++++----------- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/pkg/relayer/interface.go b/pkg/relayer/interface.go index bb7536b30..a6d4d293f 100644 --- a/pkg/relayer/interface.go +++ b/pkg/relayer/interface.go @@ -30,21 +30,19 @@ type RelayerProxy interface { // VerifyRelayRequest is a shared method used by RelayServers to check the // relay request signature and session validity. - // This method may mutate the relay request in the future, so the returned - // relay request should be used instead of the passed in one. // TODO_TECHDEBT(@red-0ne): This method should be moved out of the RelayerProxy interface // that should not be responsible for verifying relay requests. VerifyRelayRequest( ctx context.Context, relayRequest *types.RelayRequest, service *sharedtypes.Service, - ) (*types.RelayRequest, error) + ) error // SignRelayResponse is a shared method used by RelayServers to sign - // and return a signed RelayResponse the relay response. + // and append the signature to the RelayResponse. // TODO_TECHDEBT(@red-0ne): This method should be moved out of the RelayerProxy interface // that should not be responsible for signing relay responses. - SignRelayResponse(relayResponse *types.RelayResponse) (*types.RelayResponse, error) + SignRelayResponse(relayResponse *types.RelayResponse) error } // RelayServer is the interface of the advertised relay servers provided by the RelayerProxy. diff --git a/pkg/relayer/proxy/jsonrpc.go b/pkg/relayer/proxy/jsonrpc.go index 1389573f0..8ea953d6f 100644 --- a/pkg/relayer/proxy/jsonrpc.go +++ b/pkg/relayer/proxy/jsonrpc.go @@ -129,8 +129,7 @@ func (jsrv *jsonRPCServer) serveHTTP(ctx context.Context, request *http.Request) // request signature verification, session verification, and response signature. // This would help in separating concerns and improving code maintainability. // See https://github.com/pokt-network/poktroll/issues/160 - relayRequest, err = jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.service) - if err != nil { + if err = jsrv.relayerProxy.VerifyRelayRequest(ctx, relayRequest, jsrv.service); err != nil { return nil, err } diff --git a/pkg/relayer/proxy/relay_builders.go b/pkg/relayer/proxy/relay_builders.go index d01b95d6c..beb400cad 100644 --- a/pkg/relayer/proxy/relay_builders.go +++ b/pkg/relayer/proxy/relay_builders.go @@ -47,8 +47,7 @@ func (j *jsonRPCServer) newRelayResponse( relayResponse.Payload = &types.RelayResponse_JsonRpcPayload{JsonRpcPayload: jsonRPCResponse} // Sign the relay response and add the signature to the relay response metadata - relayResponse, err = j.relayerProxy.SignRelayResponse(relayResponse) - if err != nil { + if err = j.relayerProxy.SignRelayResponse(relayResponse); err != nil { return nil, err } diff --git a/pkg/relayer/proxy/relay_signer.go b/pkg/relayer/proxy/relay_signer.go index 9fea22ecd..bc172af00 100644 --- a/pkg/relayer/proxy/relay_signer.go +++ b/pkg/relayer/proxy/relay_signer.go @@ -6,22 +6,20 @@ import ( "github.com/pokt-network/poktroll/x/service/types" ) -// SignRelayResponse is a shared method used by the RelayServers to sign the hash of the RelayResponse.. +// SignRelayResponse is a shared method used by the RelayServers to sign the hash of the RelayResponse. // It uses the keyring and keyName to sign the payload and returns the signature. // TODO_TECHDEBT(@red-0ne): This method should be moved out of the RelayerProxy interface // that should not be responsible for signing relay responses. // See https://github.com/pokt-network/poktroll/issues/160 for a better design. -func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) (*types.RelayResponse, error) { +func (rp *relayerProxy) SignRelayResponse(relayResponse *types.RelayResponse) error { var responseBz []byte _, err := relayResponse.MarshalTo(responseBz) if err != nil { - return nil, err + return err } hash := crypto.Sha256(responseBz) - signature, _, err := rp.keyring.Sign(rp.keyName, hash) + relayResponse.Meta.SupplierSignature, _, err = rp.keyring.Sign(rp.keyName, hash) - relayResponse.Meta.SupplierSignature = signature - - return relayResponse, err + return err } diff --git a/pkg/relayer/proxy/relay_verifier.go b/pkg/relayer/proxy/relay_verifier.go index 94c2db5f5..54f1685a4 100644 --- a/pkg/relayer/proxy/relay_verifier.go +++ b/pkg/relayer/proxy/relay_verifier.go @@ -16,29 +16,28 @@ func (rp *relayerProxy) VerifyRelayRequest( ctx context.Context, relayRequest *types.RelayRequest, service *sharedtypes.Service, -) (*types.RelayRequest, error) { +) error { // Query for the application account to get the application's public key to verify the relay request signature. applicationAddress := relayRequest.Meta.SessionHeader.ApplicationAddress accQueryReq := &accounttypes.QueryAccountRequest{Address: applicationAddress} accQueryRes, err := rp.accountsQuerier.Account(ctx, accQueryReq) if err != nil { - return nil, err + return err } var payloadBz []byte - _, err = relayRequest.Payload.MarshalTo(payloadBz) - if err != nil { - return nil, err + if _, err = relayRequest.Payload.MarshalTo(payloadBz); err != nil { + return err } hash := crypto.Sha256(payloadBz) account := new(accounttypes.BaseAccount) if err := account.Unmarshal(accQueryRes.Account.Value); err != nil { - return nil, err + return err } if !account.GetPubKey().VerifySignature(hash, relayRequest.Meta.Signature) { - return nil, ErrInvalidRelayRequestSignature + return ErrInvalidRelayRequestSignature } // Query for the current session to check if relayRequest sessionId matches the current session. @@ -50,7 +49,7 @@ func (rp *relayerProxy) VerifyRelayRequest( } sessionResponse, err := rp.sessionQuerier.GetSession(ctx, sessionQuery) if err != nil { - return nil, err + return err } session := sessionResponse.Session @@ -63,15 +62,15 @@ func (rp *relayerProxy) VerifyRelayRequest( // matches the relayRequest sessionId. // TODO_INVESTIGATE: Revisit the assumptions above at some point in the future, but good enough for now. if session.SessionId != relayRequest.Meta.SessionHeader.SessionId { - return nil, ErrInvalidSession + return ErrInvalidSession } // Check if the relayRequest is allowed to be served by the relayer proxy. for _, supplier := range session.Suppliers { if supplier.Address == rp.supplierAddress { - return relayRequest, nil + return nil } } - return nil, ErrInvalidSupplier + return ErrInvalidSupplier }