Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AppGate] Implement the MVP AppGateServer #108

Merged
merged 42 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cec3ef4
feat: implement app client
red-0ne Oct 27, 2023
d312423
Merge remote-tracking branch 'origin/main' into feat/app-client
red-0ne Nov 6, 2023
c4fe2da
chore: address review comments
red-0ne Nov 7, 2023
a6edf61
Merge remote-tracking branch 'origin/main' into feat/app-client
red-0ne Nov 7, 2023
aab9e17
fix: remove signature field before signing
red-0ne Nov 7, 2023
0f2a53f
chore: go.mod
h5law Nov 7, 2023
65c524f
Merge branch 'main' into feat/app-client
h5law Nov 7, 2023
6e265c2
feat: add ring signatures
h5law Nov 7, 2023
a966b66
Merge branch 'main' into feat/app-client
h5law Nov 7, 2023
80af5fb
Merge branch 'main' into feat/app-client
h5law Nov 9, 2023
c1f6115
chore: remove mock files
h5law Nov 9, 2023
c681484
chore: fix spelling errors
h5law Nov 9, 2023
c787d80
fixup: spelling mistake
h5law Nov 9, 2023
65e9bee
feat: add command to start the appgateserver
h5law Nov 9, 2023
c22dc9c
chore: add debug lines
h5law Nov 9, 2023
c14a8b6
Merge branch 'main' into feat/app-client
h5law Nov 9, 2023
ec57a8e
chore: debugging
h5law Nov 9, 2023
ba3bd8f
Merge branch 'main' into feat/app-client
h5law Nov 9, 2023
781ee13
chore: go.mod
h5law Nov 9, 2023
078be29
chore: close websocket connections
h5law Nov 9, 2023
00211a9
Merge branch 'main' into feat/app-client
h5law Nov 9, 2023
12d97d6
chore: add ws todo
h5law Nov 9, 2023
2591e19
Merge remote-tracking branch 'origin/main' into feat/app-client
red-0ne Nov 10, 2023
3f2cb90
chore: Use depinject for AppGateServer
red-0ne Nov 10, 2023
a857e5f
fix: Get appAddress from url query when appAddress is empty
red-0ne Nov 10, 2023
bc2efb3
feat: address comments
h5law Nov 10, 2023
76a31c0
chore: fix signing key field
h5law Nov 10, 2023
09c843c
chore: defer cancelling ctx
h5law Nov 10, 2023
151313e
chore: cleanup log lines
h5law Nov 10, 2023
2cd04e7
chore: address comments
h5law Nov 10, 2023
5daed4a
chore: address comments
h5law Nov 10, 2023
d8c668a
Merge branch 'main' into feat/app-client
h5law Nov 10, 2023
118d26d
chore: update comments and naming
h5law Nov 10, 2023
ce91371
chore: fix missing if
h5law Nov 10, 2023
969bf0f
chore: add signed relay received debug log
h5law Nov 10, 2023
211df7f
chore: cleanup comments
h5law Nov 10, 2023
89786c3
chore: comments comments comments
h5law Nov 10, 2023
b6f7ef5
chore: comments comments comments
h5law Nov 10, 2023
1d9c233
chore: update ring comments
h5law Nov 10, 2023
89688ab
feat: refactor appgateserver creation with depinject supplier functio…
h5law Nov 10, 2023
683b285
chore: re-add missing signing information check
h5law Nov 10, 2023
35936ac
Merge branch 'main' into feat/app-client
h5law Nov 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/pocketd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (

"github.com/pokt-network/poktroll/app"
appparams "github.com/pokt-network/poktroll/app/params"
appgateservercmd "github.com/pokt-network/poktroll/pkg/appgateserver/cmd"
)

// NewRootCmd creates a new root command for a Cosmos SDK application
Expand Down Expand Up @@ -148,6 +149,11 @@ func initRootCmd(
txCommand(),
keys.Commands(app.DefaultNodeHome),
)

// add the appgate server command
rootCmd.AddCommand(
appgateservercmd.AppGateServerCmd(),
)
}

// queryCommand returns the sub-command to send queries to the app
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
cosmossdk.io/depinject v1.0.0-alpha.3
cosmossdk.io/errors v1.0.0-beta.7
cosmossdk.io/math v1.0.1
github.com/athanorlabs/go-dleq v0.1.0
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
Expand All @@ -20,6 +21,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
github.com/noot/ring-go v0.0.0-20231019173746-6c4b33bcf03f
github.com/pokt-network/smt v0.7.1
github.com/regen-network/gocuke v0.6.2
github.com/spf13/cast v1.5.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI=
github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI=
github.com/athanorlabs/go-dleq v0.1.0 h1:0/llWZG8fz2uintMBKOiBC502zCsDA8nt8vxI73W9Qc=
github.com/athanorlabs/go-dleq v0.1.0/go.mod h1:DWry6jSD7A13MKmeZA0AX3/xBeQCXDoygX99VPwL3yU=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
Expand Down Expand Up @@ -1481,6 +1483,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nishanths/exhaustive v0.8.1/go.mod h1:qj+zJJUgJ76tR92+25+03oYUhzF4R7/2Wk7fGTfCHmg=
github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ=
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
github.com/noot/ring-go v0.0.0-20231019173746-6c4b33bcf03f h1:1+NP/H13eFAqBYrGpRkbJUWVWIO2Zr2eP7a/q0UtZVQ=
github.com/noot/ring-go v0.0.0-20231019173746-6c4b33bcf03f/go.mod h1:0t3gzoSfW2bkTce1E/Jis3MQpjiKGhAgqieFK+nkQsI=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Expand Down
186 changes: 186 additions & 0 deletions pkg/appgateserver/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package cmd

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/signal"

"cosmossdk.io/depinject"
ring_secp256k1 "github.com/athanorlabs/go-dleq/secp256k1"
ringtypes "github.com/athanorlabs/go-dleq/types"
cosmosclient "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/spf13/cobra"

"github.com/pokt-network/poktroll/pkg/appgateserver"
blockclient "github.com/pokt-network/poktroll/pkg/client/block"
eventsquery "github.com/pokt-network/poktroll/pkg/client/events_query"
)

var (
flagSigningKey string
flagSelfSigning bool
flagListeningEndpoint string
flagCometWebsocketUrl string
)

func AppGateServerCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "appgate-server",
Short: "Starts the AppGate server",
Long: `Starts the AppGate server that listens for incoming relay requests and handles
the necessary on-chain interactions (sessions, suppliers, etc) to receive the
respective relay response.

-- App Mode (Flag)- -
If the server is started with a defined '--self-signing' flag, it will behave
as an Application. Any incoming requests will be signed by using the private
key and ring associated with the '--signing-key' flag.

-- Gateway Mode (Flag)--
If the '--self-signing' flag is not provided, the server will behave as a Gateway.
It will sign relays on behalf of any Application sending it relays, provided
that the address associated with '--signing-key' has been delegated to. This is
necessary for the application<->gateway ring signature to function.

-- App Mode (HTTP) --
If an application doesn't provide the '--self-signing' flag, it can still send
relays to the AppGate server and function as an Application, provided that:
1. Each request contains the '?senderAddress=[address]' query parameter
2. The key associated with the '--signing-key' flag belongs to the address
provided in the request, otherwise the ring signature will not be valid.`,
Args: cobra.NoArgs,
RunE: runAppGateServer,
}

cmd.Flags().StringVar(&flagSigningKey, "signing-key", "", "The name of the key that will be used to sign relays")
cmd.Flags().StringVar(&flagListeningEndpoint, "listening-endpoint", "http://localhost:42069", "The host and port that the appgate server will listen on")
cmd.Flags().StringVar(&flagCometWebsocketUrl, "comet-websocket-url", "ws://localhost:36657/websocket", "The URL of the comet websocket endpoint to communicate with the pocket blockchain")
cmd.Flags().BoolVar(&flagSelfSigning, "self-signing", false, "Whether the server should sign all incoming requests with its own ring (for applications)")

cmd.Flags().String(flags.FlagKeyringBackend, "", "Select keyring's backend (os|file|kwallet|pass|test)")
cmd.Flags().String(flags.FlagNode, "tcp://localhost:36657", "The URL of the comet tcp endpoint to communicate with the pocket blockchain")

return cmd
}

func runAppGateServer(cmd *cobra.Command, _ []string) error {
// Create a context that is canceled when the command is interrupted
ctx, cancelCtx := context.WithCancel(cmd.Context())
defer cancelCtx()

// Retrieve the client context for the chain interactions.
clientCtx := cosmosclient.GetClientContextFromCmd(cmd)

// Parse the listening endpoint.
listeningUrl, err := url.Parse(flagListeningEndpoint)
if err != nil {
return fmt.Errorf("failed to parse listening endpoint: %w", err)
}

log.Printf("INFO: Creating block client, using comet websocket URL: %s...", flagCometWebsocketUrl)

// Create the block client with its dependency on the events client.
eventsQueryClient := eventsquery.NewEventsQueryClient(flagCometWebsocketUrl)
h5law marked this conversation as resolved.
Show resolved Hide resolved
deps := depinject.Supply(eventsQueryClient)
blockClient, err := blockclient.NewBlockClient(ctx, deps, flagCometWebsocketUrl)
if err != nil {
return fmt.Errorf("failed to create block client: %w", err)
}

log.Println("INFO: Creating AppGate server...")

keyRecord, err := clientCtx.Keyring.Key(flagSigningKey)
if err != nil {
return fmt.Errorf("failed to get key from keyring: %w", err)
}

appAddress, err := keyRecord.GetAddress()
if err != nil {
return fmt.Errorf("failed to get address from key: %w", err)
}
signingAddress := ""
if flagSelfSigning {
signingAddress = appAddress.String()
}

// Convert the key record to a private key and return the scalar
// point on the secp256k1 curve that it corresponds to.
// If the key is not a secp256k1 key, this will return an error.
signingKey, err := recordLocalToScalar(keyRecord.GetLocal())
if err != nil {
return fmt.Errorf("failed to convert private key to scalar: %w", err)
}
signingInfo := appgateserver.SigningInformation{
h5law marked this conversation as resolved.
Show resolved Hide resolved
SigningKey: signingKey,
AppAddress: signingAddress,
}

// Create the AppGate server.
appGateServerDeps := depinject.Supply(
clientCtx,
blockClient,
)

appGateServer, err := appgateserver.NewAppGateServer(
appGateServerDeps,
appgateserver.WithSigningInformation(&signingInfo),
appgateserver.WithListeningUrl(listeningUrl),
)
if err != nil {
return fmt.Errorf("failed to create AppGate server: %w", err)
}

// Handle interrupts in a goroutine.
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)

// Block until we receive an interrupt or kill signal (OS-agnostic)
<-sigCh
log.Println("INFO: Interrupt signal received, shutting down...")

// Signal goroutines to stop
cancelCtx()
}()

log.Printf("INFO: Starting AppGate server, listening on %s...", listeningUrl.String())

// Start the AppGate server.
if err := appGateServer.Start(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("failed to start app gate server: %w", err)
} else if errors.Is(err, http.ErrServerClosed) {
log.Println("INFO: AppGate server stopped")
}

return nil
}

// recordLocalToScalar converts the private key obtained from a
// key record to a scalar point on the secp256k1 curve
func recordLocalToScalar(local *keyring.Record_Local) (ringtypes.Scalar, error) {
if local == nil {
return nil, fmt.Errorf("cannot extract private key from key record: nil")
}
priv, ok := local.PrivKey.GetCachedValue().(cryptotypes.PrivKey)
if !ok {
return nil, fmt.Errorf("cannot extract private key from key record: %T", local.PrivKey.GetCachedValue())
}
if _, ok := priv.(*secp256k1.PrivKey); !ok {
return nil, fmt.Errorf("unexpected private key type: %T, want %T", priv, &secp256k1.PrivKey{})
}
crv := ring_secp256k1.NewCurve()
privKey, err := crv.DecodeToScalar(priv.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to decode private key: %w", err)
}
return privKey, nil
}
45 changes: 45 additions & 0 deletions pkg/appgateserver/endpoint_selector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package appgateserver

import (
"context"
"log"
"net/url"

sessiontypes "github.com/pokt-network/poktroll/x/session/types"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
)

// TODO_IMPROVE: This implements a naive greedy approach that defaults to the
// first available supplier. Future optimizations (e.g. Quality-of-Service) can be introduced here.
// TODO(@h5law): Look into different endpoint selection depending on their suitability.
// getRelayerUrl gets the URL of the relayer for the given service.
func (app *appGateServer) getRelayerUrl(
h5law marked this conversation as resolved.
Show resolved Hide resolved
ctx context.Context,
serviceId string,
rpcType sharedtypes.RPCType,
session *sessiontypes.Session,
) (supplierUrl *url.URL, supplierAddress string, err error) {
for _, supplier := range session.Suppliers {
for _, service := range supplier.Services {
// Skip services that don't match the requested serviceId.
if service.Service.Id != serviceId {
continue
}

for _, endpoint := range service.Endpoints {
// Return the first endpoint url that matches the JSON RPC RpcType.
if endpoint.RpcType == rpcType {
supplierUrl, err := url.Parse(endpoint.Url)
if err != nil {
log.Printf("error parsing url: %s", err)
continue
}
return supplierUrl, supplier.Address, nil
}
}
}
}

// Return an error if no relayer endpoints were found.
return nil, "", ErrAppGateNoRelayEndpoints
}
13 changes: 13 additions & 0 deletions pkg/appgateserver/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package appgateserver

import sdkerrors "cosmossdk.io/errors"

var (
codespace = "appgateserver"
ErrAppGateInvalidRelayResponseSignature = sdkerrors.Register(codespace, 1, "invalid relay response signature")
ErrAppGateNoRelayEndpoints = sdkerrors.Register(codespace, 2, "no relay endpoints found")
ErrAppGateInvalidRequestURL = sdkerrors.Register(codespace, 3, "invalid request URL")
ErrAppGateMissingAppAddress = sdkerrors.Register(codespace, 4, "missing application address")
ErrAppGateMissingSigningInformation = sdkerrors.Register(codespace, 5, "missing app client signing information")
ErrAppGateMissingListeningEndpoint = sdkerrors.Register(codespace, 6, "missing app client listening endpoint")
)
Loading