packages feed

solana-haskell-sdk-1.2.0.0: src/Network/Solana/RPC/HTTP/Chain.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : Network.Solana.RPC.HTTP.Chain
-- Description : Cluster and node-level RPC methods for Solana.
--
-- This module provides access to Solana JSON-RPC endpoints related to cluster-wide
-- and node-specific information.
--
-- These endpoints are especially useful for monitoring, diagnostics,
-- and understanding the state of the cluster from a validator or RPC node's perspective.
module Network.Solana.RPC.HTTP.Chain where

import Data.Aeson
import Data.Aeson.Types
import Data.List (sort)
import Data.Word
import GHC.Generics (Generic)
import Network.JsonRpc.TinyClient (JsonRpc (..))
import Network.Solana.Core.Crypto (SolanaPublicKey)
import Network.Solana.RPC.HTTP.Types

------------------------------------------------------------------------------------------------

-- * getClusterNodes

------------------------------------------------------------------------------------------------

-- | Returns information about all the nodes currently participating in the cluster.
getClusterNodes :: (JsonRpc m) => m [ClusterNodes]
getClusterNodes = do
  remote "getClusterNodes"
{-# INLINE getClusterNodes #-}

-- | Contains information about all the nodes participating in the cluster.
data ClusterNodes = ClusterNodes
  { -- | The node's public key.
    pubkey :: SolanaPublicKey,
    -- | Gossip network address for the node.
    gossip :: Maybe String,
    -- | TPU (Transaction Processing Unit) network address.
    tpu :: Maybe String,
    -- | JSON-RPC address, if the RPC service is enabled.
    rpc :: Maybe String,
    -- | Software version of the node, if available.
    version :: Maybe String,
    -- | Unique identifier of the node's feature set.
    featureSet :: Maybe Word32,
    -- | Shred version used by the node.
    shredVersion :: Maybe Word16
  }
  deriving (Show, Generic, FromJSON)

------------------------------------------------------------------------------------------------

-- * getHealth

------------------------------------------------------------------------------------------------

-- | Checks if the node is healthy.
--
-- A healthy node is within a certain number of slots (defined by @HEALTH_CHECK_SLOT_DISTANCE@, a validator-side constant)
-- of the latest confirmed cluster slot. Returns @"ok"@ if healthy; otherwise, an error is returned.
getHealth :: (JsonRpc m) => m String
getHealth = do
  remote "getHealth"
{-# INLINE getHealth #-}

------------------------------------------------------------------------------------------------

-- * getHighestSnapshotSlot

------------------------------------------------------------------------------------------------

-- | Returns the highest full and incremental snapshot slots that the node has generated.
--
-- This includes the highest full snapshot slot and, if available, the corresponding highest incremental snapshot slot.
getHighestSnapshotSlot :: (JsonRpc m) => m HighestSnapshotSlot
getHighestSnapshotSlot = do
  remote "getHighestSnapshotSlot"
{-# INLINE getHighestSnapshotSlot #-}

-- | Contains the highest slot information that the node has snapshots for.
data HighestSnapshotSlot = HighestSnapshotSlot
  { -- | The highest full snapshot slot.
    full :: Word64,
    -- | The highest incremental snapshot slot, if available.
    incremental :: Maybe Word64
  }
  deriving (Show, Generic, FromJSON)

------------------------------------------------------------------------------------------------

-- * getIdentity

------------------------------------------------------------------------------------------------

-- | Returns the full 'NodeIdentity' object for the current node.
getIdentity' :: (JsonRpc m) => m NodeIdentity
getIdentity' = do
  remote "getIdentity"
{-# INLINE getIdentity' #-}

-- | Returns the identity public key of the current node.
getIdentity :: (JsonRpc m) => m SolanaPublicKey
getIdentity = identity <$> getIdentity'
{-# INLINE getIdentity #-}

-- | Identity information for the current node.
newtype NodeIdentity = NodeIdentity
  { -- | The node's identity public key.
    identity :: SolanaPublicKey
  }
  deriving (Show, Generic)
  deriving anyclass (FromJSON)

------------------------------------------------------------------------------------------------

-- * getRecentPerformanceSamples

------------------------------------------------------------------------------------------------

-- | Returns a list of recent 'PerformanceSample' entries, in reverse slot order.
--
-- Performance samples are collected every 60 seconds and include metrics such as the number
-- of transactions, slots, and non-vote transactions within each sample window.
-- Optionally takes the number of samples to return (must be ≤ 720).
getRecentPerformanceSamples :: (JsonRpc m) => Maybe Int -> m [PerformanceSample]
getRecentPerformanceSamples = do
  remote "getRecentPerformanceSamples"
{-# INLINE getRecentPerformanceSamples #-}

-- | Performance metrics sampled periodically from the node.
data PerformanceSample = PerformanceSample
  { -- | Slot in which the sample was taken.
    slot :: Slot,
    -- | Number of transactions during the sample period.
    numTransactions :: Word64,
    -- | Number of slots completed during the sample.
    numSlots :: Word64,
    -- | Duration of the sampling window, in seconds.
    samplePeriodSecs :: Word16,
    -- | Number of non-vote transactions during the sample.
    numNonVoteTransactions :: Word64
  }
  deriving (Generic, Show, FromJSON)

------------------------------------------------------------------------------------------------

-- * getRecentPrioritizationFees

------------------------------------------------------------------------------------------------

-- | Returns recent prioritization fees observed in recent blocks.
--
-- Optionally takes a list of up to 128 account addresses. If provided, the response estimates
-- the prioritization fee for landing a transaction that locks all listed accounts as writable.
getRecentPrioritizationFees :: (JsonRpc m) => Maybe [SolanaPublicKey] -> m [PrioritizationFee]
getRecentPrioritizationFees = do
  remote "getRecentPrioritizationFees"
{-# INLINE getRecentPrioritizationFees #-}

-- | A prioritization fee sample from a recent block.
data PrioritizationFee = PrioritizationFee
  { -- | Slot in which the fee was observed.
    pfSlot :: Slot,
    -- | Per-compute-unit fee (in micro-lamports).
    prioritizationFee :: Word64
  }
  deriving (Generic, Show)

instance FromJSON PrioritizationFee where
  parseJSON :: Value -> Parser PrioritizationFee
  parseJSON = withObject "PrioritizationFee" $ \v ->
    PrioritizationFee
      <$> v .: "slot"
      <*> v .: "prioritizationFee"

-- | Nearest-rank percentile over the sorted, nonzero fee samples (e.g. from
-- 'getRecentPrioritizationFees'). Zero samples (slots with no priority fee)
-- are dropped before ranking, so the result reflects only slots that
-- actually paid a priority fee. @p@ is clamped into @[0, 1]@. An empty
-- sample list, or one containing only zeros, returns @0@.
percentilePriorityFee :: Double -> [Word64] -> Word64
percentilePriorityFee p samples =
  case sort (filter (/= 0) samples) of
    [] -> 0
    nonzero ->
      let n = length nonzero
          clampedP = max 0 (min 1 p)
          idx = max 0 (ceiling (clampedP * fromIntegral n) - 1)
       in nonzero !! idx

------------------------------------------------------------------------------------------------

-- * getVersion

------------------------------------------------------------------------------------------------

-- | Returns the Solana software version and feature set currently running on the node.
getVersion :: (JsonRpc m) => m SolanaVersion
getVersion = do
  remote "getVersion"

-- | Contains information about the software version and feature set identifier of the node.
data SolanaVersion = SolanaVersion
  { -- | Software version of 'solana-core'.
    solana_core :: String,
    -- | Unique identifier of the software's feature set.
    feature_set :: Word32
  }
  deriving (Generic, Show, Eq)

instance FromJSON SolanaVersion where
  parseJSON :: Value -> Parser SolanaVersion
  parseJSON = withObject "SolanaVersion" $ \v ->
    SolanaVersion
      <$> v .: "solana-core"
      <*> v .: "feature-set"