From ead85f86ee3b38f9533ff731e09fa17bd693335d Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Mon, 20 Feb 2023 20:09:23 +0545 Subject: [PATCH] Add endpoint for chaintip query --- .ci/Dockerfile | 2 + playground/package-lock.json | 100 ++++++++------------- playground/package.json | 2 +- server/app/Main.hs | 105 ++++++++++++++++++----- server/kuber-server.cabal | 3 +- server/src/Kuber/Server/Core.hs | 30 ++++++- server/src/Kuber/Server/Spec.hs | 6 +- src/Cardano/Kuber/Utility/QueryHelper.hs | 1 - 8 files changed, 155 insertions(+), 94 deletions(-) diff --git a/.ci/Dockerfile b/.ci/Dockerfile index f51af9c..08002bb 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -19,6 +19,7 @@ RUN bash -e /merge-root /layer1 \ FROM node:16 as ui-layer WORKDIR /app +ENV NODE_OPTIONS=--max_old_space_size=6144 COPY ./playground/package.json ./playground/package-lock.json ./ RUN npm ci COPY ./playground . @@ -30,4 +31,5 @@ COPY --from=layer1 / / WORKDIR /app COPY --from=ui-layer /app/dist/ . EXPOSE 8081 +HEALTHCHECK --interval=40s --timeout=10s --start-period=30s --retries=2 CMD [ "/bin/kuber" , "--healthcheck" ] ENTRYPOINT /bin/kuber diff --git a/playground/package-lock.json b/playground/package-lock.json index ed29198..5aba494 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -4567,7 +4567,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4609,7 +4608,6 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -4672,7 +4670,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", - "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" }, @@ -4756,7 +4753,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -5085,64 +5081,48 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tw-elements": { - "version": "1.0.0-alpha13", - "resolved": "https://registry.npmjs.org/tw-elements/-/tw-elements-1.0.0-alpha13.tgz", - "integrity": "sha512-lz1D583ZGDF4s8e89dmXkhfD8m2abgAlaK8/J6cAEm3DLxz7RtqKdunzja6xcKxDZO3bXEd6oGNdQ5QHpyCqrg==", + "version": "1.0.0-beta1", + "resolved": "https://registry.npmjs.org/tw-elements/-/tw-elements-1.0.0-beta1.tgz", + "integrity": "sha512-N7YBHpco5kOBGwPzCrnyxTbjFreb7SisEFw+paJpUHGgZUwdI6KUP2QJzM3YZSFrcigWIjRwR0jOb/PQcIuk5g==", "dependencies": { "@popperjs/core": "^2.6.0", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", "deepmerge": "^4.2.2", "detect-autofill": "^1.1.3", - "perfect-scrollbar": "^1.5.0", + "perfect-scrollbar": "^1.5.5", "popper.js": "^1.16.1", - "tailwindcss": "~3.0.7" - } - }, - "node_modules/tw-elements/node_modules/postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", - "dependencies": { - "postcss-selector-parser": "^6.0.6" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" + "tailwindcss": "3.2.4" } }, "node_modules/tw-elements/node_modules/tailwindcss": { - "version": "3.0.24", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.24.tgz", - "integrity": "sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz", + "integrity": "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==", "dependencies": { - "arg": "^5.0.1", + "arg": "^5.0.2", "chokidar": "^3.5.3", "color-name": "^1.1.4", - "detective": "^5.2.0", + "detective": "^5.2.1", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.11", + "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "lilconfig": "^2.0.5", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.12", + "postcss": "^8.4.18", + "postcss-import": "^14.1.0", "postcss-js": "^4.0.0", "postcss-load-config": "^3.1.4", - "postcss-nested": "5.0.6", + "postcss-nested": "6.0.0", "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", - "resolve": "^1.22.0" + "resolve": "^1.22.1" }, "bin": { "tailwind": "lib/cli.js", @@ -8593,8 +8573,7 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" }, "popper.js": { "version": "1.16.1", @@ -8615,7 +8594,6 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "dev": true, "requires": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -8643,7 +8621,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", - "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" } @@ -8691,7 +8668,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "requires": { "pify": "^2.3.0" } @@ -8897,54 +8873,48 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tw-elements": { - "version": "1.0.0-alpha13", - "resolved": "https://registry.npmjs.org/tw-elements/-/tw-elements-1.0.0-alpha13.tgz", - "integrity": "sha512-lz1D583ZGDF4s8e89dmXkhfD8m2abgAlaK8/J6cAEm3DLxz7RtqKdunzja6xcKxDZO3bXEd6oGNdQ5QHpyCqrg==", + "version": "1.0.0-beta1", + "resolved": "https://registry.npmjs.org/tw-elements/-/tw-elements-1.0.0-beta1.tgz", + "integrity": "sha512-N7YBHpco5kOBGwPzCrnyxTbjFreb7SisEFw+paJpUHGgZUwdI6KUP2QJzM3YZSFrcigWIjRwR0jOb/PQcIuk5g==", "requires": { "@popperjs/core": "^2.6.0", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", "deepmerge": "^4.2.2", "detect-autofill": "^1.1.3", - "perfect-scrollbar": "^1.5.0", + "perfect-scrollbar": "^1.5.5", "popper.js": "^1.16.1", - "tailwindcss": "~3.0.7" + "tailwindcss": "3.2.4" }, "dependencies": { - "postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", - "requires": { - "postcss-selector-parser": "^6.0.6" - } - }, "tailwindcss": { - "version": "3.0.24", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.24.tgz", - "integrity": "sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz", + "integrity": "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==", "requires": { - "arg": "^5.0.1", + "arg": "^5.0.2", "chokidar": "^3.5.3", "color-name": "^1.1.4", - "detective": "^5.2.0", + "detective": "^5.2.1", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.11", + "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "lilconfig": "^2.0.5", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.12", + "postcss": "^8.4.18", + "postcss-import": "^14.1.0", "postcss-js": "^4.0.0", "postcss-load-config": "^3.1.4", - "postcss-nested": "5.0.6", + "postcss-nested": "6.0.0", "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", - "resolve": "^1.22.0" + "resolve": "^1.22.1" } } } diff --git a/playground/package.json b/playground/package.json index 622a9f9..f4484ce 100644 --- a/playground/package.json +++ b/playground/package.json @@ -4,7 +4,7 @@ "scripts": { "start": "vite --host=0.0.0.0", "dev": "vite", - "build": "vue-tsc --noEmit && vite build", + "build": "vite build", "preview": "vite preview --port 5050", "typecheck": "vue-tsc --noEmit" }, diff --git a/server/app/Main.hs b/server/app/Main.hs index a6ec094..019154d 100644 --- a/server/app/Main.hs +++ b/server/app/Main.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# HLINT ignore "Use newtype instead of data" #-} +{-# LANGUAGE TypeApplications #-} module Main where @@ -12,31 +13,91 @@ import Cardano.Kuber.Util (timestampToSlot) import Data.Text (stripStart) import Data.Data (Data) import Data.Typeable (Typeable) -import System.Console.CmdArgs import Text.Read (readMaybe) import Data.String (IsString(..)) import System.IO +import qualified Data.ByteString.Lazy.Char8 as L8 +import Network.HTTP.Simple (httpLBS, HttpException (HttpExceptionRequest)) +import Network.HTTP.Client.Conduit (Response(responseStatus, responseBody), HttpException (HttpExceptionRequest, InvalidUrlException), Request (requestBody)) +import System.Exit (exitFailure) +import Network.HTTP.Types (status200) +import Control.Exception (try, catch) +import Data.Function ((&)) + +import Options.Applicative +import Data.Semigroup ((<>)) + +data KuberConfig = KuberConfig + { host :: Maybe String + , port :: Int + , healthCheckUrl :: String + , healthCheck :: Bool + } + +sample :: Parser KuberConfig +sample = KuberConfig + <$> option auto( + long "host" + <> short 'H' + <> metavar "IP-Address" + <> help "IP Address to bind to" + <> showDefaultWith (const "Listen on all available intefaces") + <> value Nothing + ) + <*> option auto + ( long "port" + <> short 'p' + <> help "Port to listen on" + <> showDefault + <> value 8081) + <*> option auto + ( long "url" + <> help "Url for health-check operation" + <> showDefaultWith (const "http://127.0.0.1:8081/api/v1/chaintip") + <> value "http://127.0.0.1:8081/api/v1/chaintip" + <> metavar "URL" ) + <*> switch ( + long "healthcheck" + <> help "Perform health-check request on kuber server" + ) + +opts = info (sample <**> helper) + ( fullDesc + <> progDesc "Kuber Server" + ) main :: IO () main = do - hSetBuffering stdout LineBuffering - dcinfo <- chainInfoFromEnv >>= withDetails - Modes port hostStr <- cmdArgs $ modes [ - Modes { - port = 8081 &= typ "Port", - host = "*" &=typ "Host" - } - ]&=program "kuber" - - let settings = setPort port defaultSettings - host = setHost (fromString hostStr) settings - putStrLn $ "Starting server on port " ++ show port ++"..." - runSettings host $ app dcinfo - run port $ app dcinfo - -data Modes = - Modes { - port:: Int, - host :: String - } - deriving (Show, Data, Typeable) + KuberConfig hostStr port healthCheckUrl doHealthCheck <- execParser opts + + if doHealthCheck + then + performRequest healthCheckUrl + + else do + dcinfo <- chainInfoFromEnv >>= withDetails + + let settings = setPort port defaultSettings + let settings2 = (case hostStr of + Nothing -> settings + Just s -> setHost (fromString s) settings ) + putStrLn $ "Starting server on port " ++ show port ++"..." + runSettings settings2 $ app dcinfo + run port $ app dcinfo + +performRequest :: String -> IO () +performRequest url = do + res <- catch (httpLBS (fromString url)) exceptionHandler + if responseStatus res /= status200 + then do + putStr $ "Response " ++ show (responseStatus res) ++" : " + L8.putStr $ responseBody res + exitFailure + else L8.putStr $ responseBody res + where + exceptionHandler :: HttpException -> IO a + exceptionHandler ex = do + case ex of + HttpExceptionRequest re hec -> putStr (url ++": " ++ show hec) + InvalidUrlException s str -> putStr $ str ++ ": " ++ s + exitFailure diff --git a/server/kuber-server.cabal b/server/kuber-server.cabal index db44b7c..bf8b867 100644 --- a/server/kuber-server.cabal +++ b/server/kuber-server.cabal @@ -97,8 +97,9 @@ executable kuber , cborg , http-types , http-media + , http-conduit , wai-cors , cardano-binary , cardano-ledger-core , kuber-server - , cmdargs >= 0.10.18 \ No newline at end of file + , optparse-applicative \ No newline at end of file diff --git a/server/src/Kuber/Server/Core.hs b/server/src/Kuber/Server/Core.hs index e01cabd..8802497 100644 --- a/server/src/Kuber/Server/Core.hs +++ b/server/src/Kuber/Server/Core.hs @@ -26,10 +26,10 @@ import Data.Text (Text) import Cardano.Kuber.Data.Models import qualified Data.ByteString.Char8 as BS8 import Data.Functor ((<&>)) -import Cardano.Kuber.Api (TxBuilder) import Cardano.Kuber.Data.Parsers (parseTxIn) import qualified Debug.Trace as Debug - +import Data.Word (Word64) +import qualified Data.Aeson.Key as A getKeyHash :: AddressModal -> IO KeyHashResponse getKeyHash aie = do @@ -37,7 +37,33 @@ getKeyHash aie = do Nothing -> throw $ FrameworkError ParserError "Couldn't derive key-hash from address " Just ha -> pure $ KeyHashResponse $ BS8.unpack $ serialiseToRawBytesHex ha +data QueryTipResponse = QueryTipResponse{ + blk:: String + , qtrSlotNo :: Word64 +} +instance ToJSON QueryTipResponse where + toJSON (QueryTipResponse blk slot) = A.object [ + A.fromString "slot" A..= slot, + A.fromString "block" A..= blk + ] + + +queryTip ::ChainInfo x => x -> IO QueryTipResponse +queryTip ctx = do + chainPoint<-doQuery (QueryChainPoint CardanoMode) + systemStart<-doQuery QuerySystemStart + tip <- doQuery (QueryChainPoint CardanoMode) + case chainPoint of + ChainPointAtGenesis -> pure $ QueryTipResponse "genesis" 0 + ChainPoint sn ha -> pure $ QueryTipResponse (toHexString $ serialiseToRawBytes ha) (unSlotNo sn) + where + doQuery q= do + a <-queryNodeLocalState conn Nothing q + case a of + Left af -> throw $ FrameworkError NodeQueryError (show af) + Right e -> pure e + conn= getConnectInfo ctx getBalance :: ChainInfo x => x -> String -> IO BalanceResponse getBalance ctx addrStr = do diff --git a/server/src/Kuber/Server/Spec.hs b/server/src/Kuber/Server/Spec.hs index c0624b3..43a234b 100644 --- a/server/src/Kuber/Server/Spec.hs +++ b/server/src/Kuber/Server/Spec.hs @@ -69,10 +69,10 @@ type TransactionAPI = "api" :> "v1" :> "tx" :> QueryParam "submit" Bool :> ReqBody '[JSON] TxBuilder :> Post '[JSON] (TxResponse ) :<|> "api" :> "v1" :> "tx" :> "submit" :> ReqBody '[JSON ,CBORBinary,CBORText ] (SubmitTxModal ) :> Post '[JSON] (TxResponse ) :<|> "api" :> "v1" :> "tx" :> "exUnits" :> ReqBody '[CBORText,CBORBinary,CBORText ] (Tx BabbageEra) :> Post '[JSON] ([Either String ExecutionUnits ]) - :<|> "api" :> "v1" :> "tx" :> "fee" :> ReqBody '[CBORText,CBORBinary,CBORText ] (Tx BabbageEra) :> Post '[JSON] Integer + :<|> "api" :> "v1" :> "tx" :> "fee" :> ReqBody '[CBORText,CBORBinary,CBORText ] (Tx BabbageEra) :> Post '[JSON] Integer :<|> "api" :> "v1" :> "scriptPolicy" :> ReqBody '[JSON] (Aeson.Value ) :> Post '[PlainText ] (Text) :<|> "api" :> "v1" :> "keyhash" :> ReqBody '[JSON] (AddressModal) :> Post '[JSON ] (KeyHashResponse) - + :<|> "api" :> "v1" :> "chaintip" :> Get '[JSON] QueryTipResponse ) server :: DetailedChainInfo -> Server TransactionAPI @@ -83,6 +83,8 @@ server dcInfo = :<|> errorGuard (evaluateFee dcInfo) :<|> errorGuard (\sc -> parseAnyScript (Aeson.encode sc)<&> (\(ScriptInAnyLang sl sc') ->serialiseToRawBytesHexText ( scriptPolicyId sc')) ) :<|> errorGuard (getKeyHash) + :<|> liftIO (errorHandler $ queryTip dcInfo) + where errorGuard f v = liftIO $ do diff --git a/src/Cardano/Kuber/Utility/QueryHelper.hs b/src/Cardano/Kuber/Utility/QueryHelper.hs index 47f89ff..eb3c7da 100644 --- a/src/Cardano/Kuber/Utility/QueryHelper.hs +++ b/src/Cardano/Kuber/Utility/QueryHelper.hs @@ -30,7 +30,6 @@ performQuery conn q= qFilter = QueryInEra BabbageEraInCardanoMode $ QueryInShelleyBasedEra ShelleyBasedEraBabbage q - queryUtxos :: LocalNodeConnectInfo CardanoMode-> Set AddressAny -> IO (Either FrameworkError (UTxO BabbageEra)) queryUtxos conn addr= performQuery conn (QueryUTxO (QueryUTxOByAddress addr))