Skip to content

Commit a638544

Browse files
authored
Merge pull request #1592 from input-output-hk/KtorZ/ntp-fix
NTP fix: block and force check only when asked
2 parents 1d8c22b + 100be78 commit a638544

File tree

13 files changed

+117
-13
lines changed

13 files changed

+117
-13
lines changed

lib/cli/src/Cardano/CLI.hs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ import Options.Applicative
247247
, command
248248
, customExecParser
249249
, eitherReader
250+
, flag
250251
, flag'
251252
, footer
252253
, header
@@ -1422,8 +1423,9 @@ cmdNetworkParameters mkClient =
14221423
runClient wPort Aeson.encodePretty $ networkParameters mkClient epoch
14231424

14241425
-- | Arguments for 'network clock' command
1425-
newtype NetworkClockArgs = NetworkClockArgs
1426+
data NetworkClockArgs = NetworkClockArgs
14261427
{ _port :: Port "Wallet"
1428+
, _forceNtpCheck :: Bool
14271429
}
14281430

14291431
cmdNetworkClock
@@ -1433,9 +1435,11 @@ cmdNetworkClock mkClient =
14331435
command "clock" $ info (helper <*> cmd) $ mempty
14341436
<> progDesc "View NTP offset."
14351437
where
1436-
cmd = fmap exec $ NetworkClockArgs <$> portOption
1437-
exec (NetworkClockArgs wPort) = do
1438-
runClient wPort Aeson.encodePretty $ networkClock mkClient
1438+
cmd = fmap exec $ NetworkClockArgs
1439+
<$> portOption
1440+
<*> forceNtpCheckOption
1441+
exec (NetworkClockArgs wPort forceNtpCheck) = do
1442+
runClient wPort Aeson.encodePretty $ networkClock mkClient forceNtpCheck
14391443

14401444
{-------------------------------------------------------------------------------
14411445
Commands - 'launch'
@@ -1613,6 +1617,13 @@ sortOrderOption = optionT $ mempty
16131617
<> help "specifies a sort order, either 'ascending' or 'descending'."
16141618
<> showDefaultWith showT
16151619

1620+
-- | [--force-ntp-check]
1621+
forceNtpCheckOption :: Parser Bool
1622+
forceNtpCheckOption = flag False True $ mempty
1623+
<> long "force-ntp-check"
1624+
<> help "When set, will block and force an NTP check with the server. \
1625+
\Otherwise, uses an available cached result."
1626+
16161627
loggingSeverities :: [(String, Severity)]
16171628
loggingSeverities =
16181629
[ ("debug", Debug)

lib/cli/test/unit/Cardano/CLISpec.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,13 +518,16 @@ spec = do
518518
]
519519

520520
["network", "clock", "--help"] `shouldShowUsage`
521-
[ "Usage: network clock [--port INT]"
521+
[ "Usage: network clock [--port INT] [--force-ntp-check]"
522522
, " View NTP offset."
523523
, ""
524524
, "Available options:"
525525
, " -h,--help Show this help text"
526526
, " --port INT port used for serving the wallet"
527527
, " API. (default: 8090)"
528+
, " --force-ntp-check When set, will block and force an NTP"
529+
, " check with the server. Otherwise, uses"
530+
, " an available cached result."
528531
]
529532

530533
["key", "root", "--help"] `shouldShowUsage`

lib/core-integration/src/Test/Integration/Scenario/API/Network.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ spec = do
161161
expectResponseCode @IO HTTP.status200 r
162162
expectField (#ntpStatus . #status)
163163
(`shouldBe` NtpSyncingStatusAvailable) r
164+
165+
it "NETWORK_CLOCK - Can query network clock and force NTP check" $ \ctx -> do
166+
sandboxed <- inNixBuild
167+
when sandboxed $
168+
pendingWith "Internet NTP servers unavailable in build sandbox"
169+
eventually "ntp status = (un)available" $ do
170+
r <- request @ApiNetworkClock ctx
171+
(Link.getNetworkClock' True) Default Empty
172+
expectResponseCode @IO HTTP.status200 r
173+
expectField (#ntpStatus . #status)
174+
(`shouldBe` NtpSyncingStatusAvailable) r
164175
where
165176
verifyEpochNumWrong
166177
:: Context t

lib/core/cardano-wallet-core.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ library
8585
, servant-server
8686
, split
8787
, streaming-commons
88+
, stm
8889
, template-haskell
8990
, text
9091
, text-class

lib/core/src/Cardano/Wallet/Api.hs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,15 @@ import Data.Generics.Product.Typed
155155
import GHC.Generics
156156
( Generic )
157157
import Servant.API
158-
( (:<|>), (:>), Capture, JSON, OctetStream, QueryParam, ReqBody )
158+
( (:<|>)
159+
, (:>)
160+
, Capture
161+
, JSON
162+
, OctetStream
163+
, QueryFlag
164+
, QueryParam
165+
, ReqBody
166+
)
159167
import Servant.API.Verbs
160168
( DeleteAccepted
161169
, DeleteNoContent
@@ -533,6 +541,7 @@ type GetNetworkParameters = "network"
533541

534542
type GetNetworkClock = "network"
535543
:> "clock"
544+
:> QueryFlag "forceNtpCheck"
536545
:> Get '[JSON] ApiNetworkClock
537546

538547
{-------------------------------------------------------------------------------

lib/core/src/Cardano/Wallet/Api/Client.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ data NetworkClient = NetworkClient
195195
:: ApiEpochNumber
196196
-> ClientM ApiNetworkParameters
197197
, networkClock
198-
:: ClientM ApiNetworkClock
198+
:: Bool -- When 'True', block and force NTP check
199+
-> ClientM ApiNetworkClock
199200
}
200201

201202
-- | Produces a 'WalletClient' working against the /wallets API.

lib/core/src/Cardano/Wallet/Api/Link.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ module Cardano.Wallet.Api.Link
7575
, getNetworkInfo
7676
, getNetworkParams
7777
, getNetworkClock
78+
, getNetworkClock'
7879

7980
-- * Proxy
8081
, postExternalTransaction
@@ -119,6 +120,7 @@ import Servant.API
119120
, Header'
120121
, IsElem
121122
, NoContentVerb
123+
, QueryFlag
122124
, QueryParam
123125
, ReflectMethod (..)
124126
, ReqBody
@@ -454,7 +456,14 @@ getNetworkParams e =
454456
getNetworkClock
455457
:: (Method, Text)
456458
getNetworkClock =
457-
endpoint @Api.GetNetworkClock id
459+
endpoint @Api.GetNetworkClock (False &)
460+
461+
getNetworkClock'
462+
:: Bool -- ^ When 'True', block and force NTP check
463+
-> (Method, Text)
464+
getNetworkClock' forceNtpCheck =
465+
endpoint @Api.GetNetworkClock (forceNtpCheck &)
466+
458467

459468
--
460469
-- Proxy
@@ -551,5 +560,8 @@ instance HasVerb sub => HasVerb (ReqBody a b :> sub) where
551560
instance HasVerb sub => HasVerb (QueryParam a b :> sub) where
552561
method _ = method (Proxy @sub)
553562

563+
instance HasVerb sub => HasVerb (QueryFlag sym :> sub) where
564+
method _ = method (Proxy @sub)
565+
554566
instance HasVerb sub => HasVerb (Header' opts name ty :> sub) where
555567
method _ = method (Proxy @sub)

lib/core/src/Cardano/Wallet/Api/Server.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,8 +1716,8 @@ data ErrNoSuchEpoch = ErrNoSuchEpoch
17161716
, errCurrentEpoch :: W.EpochNo
17171717
} deriving (Eq, Show)
17181718

1719-
getNetworkClock :: NtpClient -> Handler ApiNetworkClock
1720-
getNetworkClock = liftIO . getNtpStatus
1719+
getNetworkClock :: NtpClient -> Bool -> Handler ApiNetworkClock
1720+
getNetworkClock client = liftIO . getNtpStatus client
17211721

17221722
{-------------------------------------------------------------------------------
17231723
Proxy

lib/core/src/Network/Ntp.hs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import Cardano.BM.Data.Tracer
2626
( HasPrivacyAnnotation (..), HasSeverityAnnotation (..) )
2727
import Cardano.Wallet.Api.Types
2828
( ApiNetworkClock (..), ApiNtpStatus (..), NtpSyncingStatus (..) )
29+
import Control.Concurrent.STM
30+
( atomically, check )
2931
import Control.Tracer
3032
( Tracer )
3133
import Data.Quantity
@@ -61,7 +63,7 @@ ntpSettings = NtpSettings
6163
{ ntpServers = [ "0.de.pool.ntp.org", "0.europe.pool.ntp.org"
6264
, "0.pool.ntp.org", "1.pool.ntp.org"
6365
, "2.pool.ntp.org", "3.pool.ntp.org" ]
64-
, ntpRequiredNumberOfResults = 2
66+
, ntpRequiredNumberOfResults = 3
6567
, ntpResponseTimeout = 1_000_000
6668
, ntpPollDelay = 300_000_000
6769
}
@@ -121,8 +123,24 @@ instance HasSeverityAnnotation NtpTrace where
121123
NtpTracePacketReceived _ _ -> Debug
122124
NtpTraceWaitingForRepliesTimeout _ -> Notice
123125

124-
getNtpStatus :: NtpClient -> IO ApiNetworkClock
125-
getNtpStatus = fmap (ApiNetworkClock . toStatus) . ntpQueryBlocking
126+
getNtpStatus
127+
:: NtpClient
128+
-> Bool
129+
-- ^ When 'True', will block and force a NTP check instead of using cached results
130+
-> IO ApiNetworkClock
131+
getNtpStatus client forceCheck = (ApiNetworkClock . toStatus) <$>
132+
if forceCheck
133+
-- Forces an NTP check / query on the central servers, use with care
134+
then do
135+
ntpQueryBlocking client
136+
137+
else atomically $ do
138+
-- Reads a cached NTP status from an STM.TVar so we don't get
139+
-- blacklisted by the central NTP "authorities" for sending too many NTP
140+
-- requests.
141+
s <- ntpGetStatus client
142+
check (s /= NtpSyncPending)
143+
pure s
126144
where
127145
toStatus = \case
128146
NtpSyncPending ->

lib/core/test/unit/Cardano/Wallet/Api/TypesSpec.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ import Servant
221221
, Header'
222222
, JSON
223223
, PostNoContent
224+
, QueryFlag
224225
, QueryParam
225226
, ReqBody
226227
, StdMethod (..)
@@ -1810,6 +1811,9 @@ instance HasPath sub => HasPath (ReqBody a b :> sub) where
18101811
instance HasPath sub => HasPath (QueryParam a b :> sub) where
18111812
getPath _ = getPath (Proxy @sub)
18121813

1814+
instance HasPath sub => HasPath (QueryFlag sym :> sub) where
1815+
getPath _ = getPath (Proxy @sub)
1816+
18131817
instance HasPath sub => HasPath (Header' opts name ty :> sub) where
18141818
getPath _ = getPath (Proxy @sub)
18151819

0 commit comments

Comments
 (0)