Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a0516f9
feat: phase 1 of major refactor - move fullnode functionality to SDK
Jun 11, 2025
eb145f2
comments
Jun 11, 2025
2f7029b
make validte relay request a method on the full node
Jun 11, 2025
ca3d365
feat: implement GatewayClients and move relevant code from PATH to Sh…
Jun 11, 2025
8cea41c
move FullNode interface to sdk package
Jun 11, 2025
8585235
make gateway client structs private
Jun 11, 2025
6d88a19
gateway mode method on gateway client
Jun 11, 2025
d6dd265
remove unused queryCodec
Jun 12, 2025
a94f720
Merge branch 'shannon-sdk-refactor' into shannon-sdk-refactor-pt2
Jun 12, 2025
c19c78a
update to sync comments with PATH PR #298
Jun 13, 2025
88ebd61
chore: merge conflicts
Jun 13, 2025
a549c5f
chore: merge conflicts
Jun 13, 2025
0a86437
updated to incorporate PR 298 changes
Jun 13, 2025
de5522f
move error handling for observations for delegated mode
Jun 13, 2025
71f0037
update comments in full node config
Jun 13, 2025
3489fb4
Merge branch 'shannon-sdk-refactor' into shannon-sdk-refactor-pt2
Jun 13, 2025
d81e4bd
chore: merge conflicts
Jun 16, 2025
9740238
fix: implement review comments
Jun 16, 2025
b37b272
fix: implement review comments
Jun 16, 2025
f4fdaac
make 'getAccountPubKey' private
Jun 17, 2025
544fd19
review comments and minor refactor
Jun 17, 2025
b2d5a08
fix observation errors
Jun 17, 2025
1106f83
fix: implement review comments
Jun 18, 2025
8b09728
fix: implement review comments
Jun 18, 2025
e715f0c
fix: implement review comments
Jun 18, 2025
c6e7b6b
improve logs and erros in gateway client
Jun 18, 2025
990759a
add comments make pub key fetch private
Jun 18, 2025
545fc0e
fix: implement review comments
Jun 18, 2025
a3a227f
reintroduce "lazy" fetching concept as GRPC client
Jun 19, 2025
6dc1456
fix comments
Jun 19, 2025
b556cff
fix comment
Jun 19, 2025
4f67b8a
fix nil logger
Jun 19, 2025
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
40 changes: 0 additions & 40 deletions account.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@ package sdk
import (
"context"

"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/types"
accounttypes "github.com/cosmos/cosmos-sdk/x/auth/types"
grpc "github.com/cosmos/gogoproto/grpc"
grpcoptions "google.golang.org/grpc"
)

var queryCodec *codec.ProtoCodec

// -----------------------------
// Interfaces and Structs
// -----------------------------
Expand All @@ -39,39 +32,6 @@ type AccountClient struct {
PoktNodeAccountFetcher
}

// -----------------------------
// Functions
// -----------------------------

// init initializes the codec for the account module.
func init() {
reg := cdctypes.NewInterfaceRegistry()
accounttypes.RegisterInterfaces(reg)
cryptocodec.RegisterInterfaces(reg)
queryCodec = codec.NewProtoCodec(reg)
}

// GetPubKeyFromAddress returns the public key of the account with the given address.
//
// - Queries the account module using the gRPC query client.
func (ac *AccountClient) GetPubKeyFromAddress(
ctx context.Context,
address string,
) (pubKey cryptotypes.PubKey, err error) {
req := &accounttypes.QueryAccountRequest{Address: address}
res, err := ac.PoktNodeAccountFetcher.Account(ctx, req)
if err != nil {
return nil, err
}

var fetchedAccount types.AccountI
if err = queryCodec.UnpackAny(res.Account, &fetchedAccount); err != nil {
return nil, err
}

return fetchedAccount.GetPubKey(), nil
}

// NewPoktNodeAccountFetcher returns the default implementation of the PoktNodeAccountFetcher interface.
//
// - Connects to a POKT full node through the account module's query client to get account data.
Expand Down
43 changes: 0 additions & 43 deletions application.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,14 @@ package sdk

import (
"context"
"errors"
"fmt"
"slices"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
query "github.com/cosmos/cosmos-sdk/types/query"
"github.com/pokt-network/poktroll/pkg/crypto/rings"
"github.com/pokt-network/poktroll/x/application/types"
"github.com/pokt-network/ring-go"
)

type ApplicationRing struct {
types.Application
PublicKeyFetcher
}

// ApplicationClient is the interface to interact with the on-chain application-module.
//
// - Used to get the list of applications and the details of a specific application
Expand Down Expand Up @@ -128,38 +120,3 @@ func (ac *ApplicationClient) GetApplicationsDelegatingToGateway(

return gatewayDelegatingApplications, nil
}

// GetRing returns the ring for the application until the current session end height.
//
// - Ring is created using the application's public key and the public keys of gateways currently delegated from the application
// - Returns error if PublicKeyFetcher is not set or any pubkey fetch fails
func (a ApplicationRing) GetRing(
ctx context.Context,
sessionEndHeight uint64,
) (addressRing *ring.Ring, err error) {
if a.PublicKeyFetcher == nil {
return nil, errors.New("GetRing: Public Key Fetcher not set")
}

currentGatewayAddresses := rings.GetRingAddressesAtSessionEndHeight(&a.Application, sessionEndHeight)

ringAddresses := make([]string, 0)
ringAddresses = append(ringAddresses, a.Application.Address)

if len(currentGatewayAddresses) == 0 {
ringAddresses = append(ringAddresses, a.Application.Address)
} else {
ringAddresses = append(ringAddresses, currentGatewayAddresses...)
}

ringPubKeys := make([]cryptotypes.PubKey, 0, len(ringAddresses))
for _, address := range ringAddresses {
pubKey, err := a.PublicKeyFetcher.GetPubKeyFromAddress(ctx, address)
if err != nil {
return nil, err
}
ringPubKeys = append(ringPubKeys, pubKey)
}

return rings.GetRingFromPubKeys(ringPubKeys)
}
115 changes: 115 additions & 0 deletions client/config_fullnode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package client

import (
"errors"
"net"
"net/url"
"time"
)

var (
errShannonInvalidNodeURL = errors.New("invalid node URL")
errShannonInvalidGrpcHostPort = errors.New("invalid grpc host port")
errShannonCacheConfigSetForLazyMode = errors.New("session TTL cannot be set when caching is disabled")
)

type (
// FullNodeConfig is the configuration for the full node used by the GatewayClient.
FullNodeConfig struct {
// RPC URL is used to make RPC calls to the full node.
RpcURL string `yaml:"rpc_url"`
// GRPCConfig is the configuration for the gRPC connection to the full node.
GRPCConfig GRPCConfig `yaml:"grpc_config"`
// CacheConfig configures the caching behavior of the full node, including whether caching is enabled.
CacheConfig CacheConfig `yaml:"cache_config"`
}

// GRPCConfig configures the gRPC connection to the full node.
GRPCConfig struct {
// HostPort is the host and port of the full node.
// eg: localhost:26657
HostPort string `yaml:"host_port"`
// UseInsecureGRPCConn determines if the gRPC connection to the full node should use TLS.
// This is useful for local development.
UseInsecureGRPCConn bool `yaml:"insecure"`
}

// CacheConfig configures the caching behavior of the full node, including whether caching is enabled.
CacheConfig struct {
// CachingEnabled determines if the full node should use caching.
// If set to `false`, the full node will not use caching, and will be returned directly.
// If set to `true`, the full node will be wrapped in a SturdyC-based cache.
CachingEnabled bool `yaml:"caching_enabled"`
// SessionTTL is the time to live for the session cache.
// Optional. If not set, the default session TTL will be used.
SessionTTL time.Duration `yaml:"session_ttl"`
}
)

func (c FullNodeConfig) Validate() error {
if !isValidURL(c.RpcURL) {
return errShannonInvalidNodeURL
}
if !isValidHostPort(c.GRPCConfig.HostPort) {
return errShannonInvalidGrpcHostPort
}
if err := c.CacheConfig.validate(); err != nil {
return err
}
return nil
}

// defaultSessionCacheTTL is the default time to live for the session cache.
// It should match the protocol's session length.
//
// TODO_NEXT(@commoddity): Session refresh handling should be significantly reworked as part
// of the next changes following PATH PR #297.
// The proposed change is to align session refreshes with actual session expiry time,
// using the session expiry block and the Shannon SDK's block client.
// When this is done, session cache TTL can be removed altogether.
const defaultSessionCacheTTL = 30 * time.Second

// validate validates the cache configuration for the full node.
func (c *CacheConfig) validate() error {
// Cannot set both lazy mode and cache configuration.
if !c.CachingEnabled && c.SessionTTL != 0 {
return errShannonCacheConfigSetForLazyMode
}
return nil
}

// hydrateDefaults hydrates the cache configuration with defaults for any fields that are not set.
func (c *CacheConfig) hydrateDefaults() {
if c.SessionTTL == 0 {
c.SessionTTL = defaultSessionCacheTTL
}
}

// isValidURL returns true if the supplied URL string can be parsed into a valid URL accepted by the Shannon SDK.
func isValidURL(urlStr string) bool {
u, err := url.Parse(urlStr)
if err != nil {
return false
}

if u.Scheme == "" || u.Host == "" {
return false
}

return true
}

// isValidHostPort returns true if the supplied string can be parsed into a host and port combination.
func isValidHostPort(hostPort string) bool {
host, port, err := net.SplitHostPort(hostPort)

if err != nil {
return false
}

if host == "" || port == "" {
return false
}

return true
}
144 changes: 144 additions & 0 deletions client/fullnode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package client

import (
"context"
"fmt"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/types"
accounttypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/pokt-network/poktroll/pkg/polylog"
apptypes "github.com/pokt-network/poktroll/x/application/types"
sessiontypes "github.com/pokt-network/poktroll/x/session/types"

sdk "github.com/pokt-network/shannon-sdk"
sdkTypes "github.com/pokt-network/shannon-sdk/types"
)

// TODO_IN_THIS_PR(@commoddity): start a "micro readme" (just with bullet points) that captures more details about full node implementations?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[linter-name (fail-on-found)] reported by reviewdog 🐶
// TODO_IN_THIS_PR(@commoddity): start a "micro readme" (just with bullet points) that captures more details about full node implementations?


// fullNode: default implementation of a full node for the Shannon.
//
// A fullNode queries the onchain data for every data item it needs to do an action (e.g. serve a relay request, etc).
//
// This is done to enable supporting short block times (a few seconds), by avoiding caching
// which can result in failures due to stale data in the cache.
//
// Key differences from a caching full node:
// - Intentionally avoids caching:
// - Enables support for short block times (e.g. LocalNet)
// - Use CachingFullNode struct if caching is desired for performance
//
// Implements the FullNode interface.
type fullNode struct {
logger polylog.Logger

appClient *sdk.ApplicationClient
sessionClient *sdk.SessionClient
blockClient *sdk.BlockClient
accountClient *sdk.AccountClient
}

// newFullNode builds and returns a fullNode using the provided configuration.
func newFullNode(logger polylog.Logger, rpcURL string, fullNodeConfig FullNodeConfig) (*fullNode, error) {
grpcConn, err := connectGRPC(
fullNodeConfig.GRPCConfig.HostPort,
fullNodeConfig.GRPCConfig.UseInsecureGRPCConn,
)
if err != nil {
return nil, fmt.Errorf("NewFullNode: error creating new GRPC connection at url %s: %w",
fullNodeConfig.GRPCConfig.HostPort, err)
}

blockClient, err := newBlockClient(rpcURL)
if err != nil {
return nil, fmt.Errorf("NewFullNode: error creating new Shannon block client at URL %s: %w", rpcURL, err)
}

fullNode := &fullNode{
logger: logger,
sessionClient: newSessionClient(grpcConn),
appClient: newAppClient(grpcConn),
accountClient: newAccClient(grpcConn),
blockClient: blockClient,
}

return fullNode, nil
}

// GetApp:
// - Returns the onchain application matching the supplied application address.
// - Required to fulfill the FullNode interface.
func (fn *fullNode) GetApp(ctx context.Context, appAddr string) (*apptypes.Application, error) {
app, err := fn.appClient.GetApplication(ctx, appAddr)
if err != nil {
return nil, fmt.Errorf("GetApp: error getting the application for address %s: %w", appAddr, err)
}

fn.logger.Debug().Msgf("GetApp: fetched application %s", app)

return &app, err
}

// GetSession:
// - Uses the session client to fetch a session for the (serviceID, appAddr) combination.
// - Required to fulfill the FullNode interface.
func (fn *fullNode) GetSession(
ctx context.Context,
serviceID sdk.ServiceID,
appAddr string,
) (sessiontypes.Session, error) {
session, err := fn.sessionClient.GetSession(
ctx,
appAddr,
string(serviceID),
0,
)

if err != nil {
return sessiontypes.Session{},
fmt.Errorf("GetSession: error getting the session for service %s app %s: %w",
serviceID, appAddr, err,
)
}

if session == nil {
return sessiontypes.Session{},
fmt.Errorf("GetSession: got nil session for service %s app %s: %w",
serviceID, appAddr, err,
)
}

fn.logger.Debug().Msgf("GetSession: fetched session %s", session)

return *session, nil
}

// IsHealthy:
// - Always returns true for a fullNode.
// - Required to fulfill the FullNode interface.
func (fn *fullNode) IsHealthy() bool {
return true
}

// GetAccountPubKey returns the public key of the account with the given address.
//
// - Queries the account module using the gRPC query client.
func (fn *fullNode) getAccountPubKey(
ctx context.Context,
address string,
) (pubKey cryptotypes.PubKey, err error) {
req := &accounttypes.QueryAccountRequest{Address: address}

res, err := fn.accountClient.Account(ctx, req)
if err != nil {
return nil, err
}

var fetchedAccount types.AccountI
if err = sdkTypes.QueryCodec.UnpackAny(res.Account, &fetchedAccount); err != nil {
return nil, err
}

return fetchedAccount.GetPubKey(), nil
}
Loading