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

Finalize filtering in listTransactions by converting Range UTCTime to Range SlotId #595

Merged
merged 8 commits into from
Aug 2, 2019
1 change: 1 addition & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ test-suite unit
, memory
, network
, QuickCheck
, quickcheck-instances
, quickcheck-state-machine >= 0.6.0
, random
, servant
Expand Down
49 changes: 29 additions & 20 deletions lib/core/src/Cardano/Wallet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import Cardano.Wallet.Primitive.Types
, Direction (..)
, EpochLength (..)
, Hash (..)
, Range (..)
, SlotId (..)
, SlotId (..)
, SlotLength (..)
Expand All @@ -134,6 +135,7 @@ import Cardano.Wallet.Primitive.Types
, WalletState (..)
, computeUtxoStatistics
, log10
, slotAt
, slotDifference
, slotRatio
, slotStartTime
Expand All @@ -152,7 +154,7 @@ import Control.Concurrent.MVar
import Control.DeepSeq
( NFData )
import Control.Monad
( forM, unless, when )
( forM, unless )
import Control.Monad.IO.Class
( MonadIO, liftIO )
import Control.Monad.Trans.Class
Expand Down Expand Up @@ -376,8 +378,8 @@ data ErrListTransactions
-- | Indicates that the specified start time is later than the specified end
-- time.
data ErrStartTimeLaterThanEndTime = ErrStartTimeLaterThanEndTime
{ startTime :: UTCTime
, endTime :: UTCTime
{ errStartTime :: UTCTime
, errEndTime :: UTCTime
Copy link
Member

Choose a reason for hiding this comment

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

renamed to avoid name clash.

} deriving (Show, Eq)

{-------------------------------------------------------------------------------
Expand Down Expand Up @@ -460,10 +462,10 @@ newWalletLayer tracer bp db nw tl = do
where
BlockchainParameters
block0
block0Date
startTime
feePolicy
(SlotLength slotLength)
slotsPerEpoch
slotLength
epochLength
Copy link
Member

Choose a reason for hiding this comment

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

renamed for consistency in naming with the Primitive.Types module.

txMaxSize = bp

logDebugT :: MonadIO m => Text -> m ()
Expand Down Expand Up @@ -616,7 +618,8 @@ newWalletLayer tracer bp db nw tl = do
restoreSleep t wid slot = do
-- NOTE: Conversion functions will treat 'NominalDiffTime' as
-- picoseconds
let halfSlotLengthDelay = fromEnum slotLength `div` 2000000
let (SlotLength s) = slotLength
let halfSlotLengthDelay = fromEnum s `div` 2000000
threadDelay halfSlotLengthDelay
runExceptT (networkTip nw) >>= \case
Left e -> do
Expand Down Expand Up @@ -656,7 +659,7 @@ newWalletLayer tracer bp db nw tl = do
splitAt (length blocks - 1) blocks
liftIO $ logDebug t $ pretty (h ++ q)
let (txs, cp') = applyBlocks @s @t (h ++ q) cp
let progress = slotRatio slotsPerEpoch sup tip
let progress = slotRatio epochLength sup tip
let status' = if progress == maxBound
then Ready
else Restoring progress
Expand Down Expand Up @@ -753,17 +756,26 @@ newWalletLayer tracer bp db nw tl = do
-> SortOrder
-> ExceptT ErrListTransactions IO [TransactionInfo]
_listTransactions wid mStart mEnd order = do
guardRange (mStart, mEnd)
Copy link
Member

Choose a reason for hiding this comment

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

factored out for readability.

(w, _) <- withExceptT ErrListTransactionsNoSuchWallet $ _readWallet wid
case (mStart, mEnd) of
(Just start, Just end) -> when (start > end) $ throwE $
ErrListTransactionsStartTimeLaterThanEndTime $
ErrStartTimeLaterThanEndTime start end
_ -> pure ()
let tip = currentTip w ^. #slotId
let range = Range
{ rStart = slotAt epochLength slotLength startTime <$> mStart
, rEnd = slotAt epochLength slotLength startTime <$> mEnd
}
liftIO $ assemble tip
<$> DB.readTxHistory db (PrimaryKey wid)
order wholeRange
<$> DB.readTxHistory db (PrimaryKey wid) order range
where
guardRange
:: (Maybe UTCTime, Maybe UTCTime)
-> ExceptT ErrListTransactions IO ()
guardRange = \case
(Just start, Just end) | start > end -> do
let err = ErrStartTimeLaterThanEndTime start end
throwE (ErrListTransactionsStartTimeLaterThanEndTime err)
_ ->
pure ()

-- This relies on DB.readTxHistory returning all necessary transactions
-- to assemble coin selection information for outgoing payments.
-- To reliably provide this information, it should be looked up when
Expand All @@ -781,7 +793,7 @@ newWalletLayer tracer bp db nw tl = do
, txInfoOutputs = W.outputs @t tx
, txInfoMeta = meta
, txInfoDepth =
slotDifference slotsPerEpoch tip (meta ^. #slotId)
slotDifference epochLength tip (meta ^. #slotId)
, txInfoTime = txTime (meta ^. #slotId)
}
txOuts = Map.fromList
Expand All @@ -796,10 +808,7 @@ newWalletLayer tracer bp db nw tl = do
-- assume that the transaction "happens" at the start of the
-- slot. This is purely arbitrary and in practice, any time between
-- the start of a slot and the end could be a validate candidate.
txTime = slotStartTime
slotsPerEpoch
(SlotLength slotLength)
block0Date
txTime = slotStartTime epochLength slotLength startTime

_signTx
:: (Show s, NFData s, IsOwned s, GenChange s)
Expand Down
4 changes: 2 additions & 2 deletions lib/core/src/Cardano/Wallet/Api/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -691,9 +691,9 @@ instance LiftHandler ErrListTransactions where
instance LiftHandler ErrStartTimeLaterThanEndTime where
handler err = apiError err400 StartTimeLaterThanEndTime $ mconcat
[ "The specified start time '"
, toText $ Iso8601Time $ startTime err
, toText $ Iso8601Time $ errStartTime err
, "' is later than the specified end time '"
, toText $ Iso8601Time $ endTime err
, toText $ Iso8601Time $ errEndTime err
, "'."
]

Expand Down
32 changes: 27 additions & 5 deletions lib/core/src/Cardano/Wallet/Primitive/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
Expand Down Expand Up @@ -79,6 +80,7 @@ module Cardano.Wallet.Primitive.Types
, flatSlot
, fromFlatSlot
, slotStartTime
, slotAt
, slotDifference

-- * Wallet Metadata
Expand Down Expand Up @@ -147,7 +149,7 @@ import Data.Text.Class
, toTextFromBoundedEnum
)
import Data.Time.Clock
( NominalDiffTime, UTCTime, addUTCTime )
( NominalDiffTime, UTCTime, addUTCTime, diffUTCTime )
import Data.Word
( Word16, Word32, Word64 )
import Fmt
Expand Down Expand Up @@ -850,14 +852,14 @@ slotRatio epochLength a b =
-- | Convert a 'SlotId' to the number of slots since genesis.
flatSlot :: EpochLength -> SlotId -> Word64
flatSlot (EpochLength epochLength) (SlotId e s) =
epochLength * e + fromIntegral s
fromIntegral epochLength * e + fromIntegral s

-- | Convert a 'flatSlot' index to 'SlotId'.
fromFlatSlot :: EpochLength -> Word64 -> SlotId
fromFlatSlot (EpochLength epochLength) n = SlotId e (fromIntegral s)
where
e = n `div` epochLength
s = n `mod` epochLength
e = n `div` fromIntegral epochLength
s = n `mod` fromIntegral epochLength

-- | @slotDifference a b@ is how many slots @a@ is after @b@. The result is
-- non-negative, and if @b > a@ then this function returns zero.
Expand All @@ -876,12 +878,32 @@ slotStartTime epochLength (SlotLength slotLength) (StartTime start) sl =
where
offset = slotLength * fromIntegral (flatSlot epochLength sl)

-- | The SlotId at a given time.
slotAt :: EpochLength -> SlotLength -> StartTime -> UTCTime -> SlotId
slotAt (EpochLength nSlots) (SlotLength slotLength) (StartTime start) time =
SlotId
{ epochNumber = ep
, slotNumber = sl
}
where
diff :: NominalDiffTime
diff = time `diffUTCTime` start

epochLength :: NominalDiffTime
epochLength = fromIntegral nSlots * slotLength

ep :: Word64
ep = floor (diff / epochLength)

sl :: Word16
sl = floor ((diff - (fromIntegral ep) * epochLength) / slotLength)

-- | Duration of a single slot.
newtype SlotLength = SlotLength NominalDiffTime
deriving (Show, Eq)

-- | Number of slots in a single epoch
newtype EpochLength = EpochLength Word64
newtype EpochLength = EpochLength Word16
deriving (Show, Eq)

-- | Blockchain start time
Expand Down
57 changes: 57 additions & 0 deletions lib/core/test/unit/Cardano/Wallet/Primitive/TypesSpec.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
Expand Down Expand Up @@ -31,6 +32,8 @@ import Cardano.Wallet.Primitive.Types
, HistogramBar (..)
, ShowFmt (..)
, SlotId (..)
, SlotLength (..)
, StartTime (..)
, TxIn (..)
, TxMeta (TxMeta)
, TxOut (..)
Expand All @@ -48,7 +51,9 @@ import Cardano.Wallet.Primitive.Types
, isValidCoin
, restrictedBy
, restrictedTo
, slotAt
, slotRatio
, slotStartTime
, walletNameMaxLength
, walletNameMinLength
)
Expand Down Expand Up @@ -78,6 +83,7 @@ import Test.Hspec
( Spec, describe, it )
import Test.QuickCheck
( Arbitrary (..)
, NonZero (..)
, Property
, arbitraryBoundedEnum
, arbitraryPrintableChar
Expand All @@ -89,13 +95,18 @@ import Test.QuickCheck
, property
, scale
, vectorOf
, withMaxSuccess
, (=/=)
, (===)
)
import Test.QuickCheck.Arbitrary.Generic
( genericArbitrary, genericShrink )
import Test.QuickCheck.Instances.Time
()
import Test.Text.Roundtrip
( textRoundtrip )
import Test.Utils.Time
( genUniformTime )

import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS
Expand Down Expand Up @@ -146,6 +157,22 @@ spec = do
it "fromFlatSlot . flatSlot == id" $ property $ \n ->
flatSlot slotsPerEpoch (fromFlatSlot slotsPerEpoch n) === n

describe "SlotId <-> UTCTime conversions" $ do
it "slotAt . slotStartTime == id" $ withMaxSuccess 1000 $ property $
\slotLength startTime (epochLength, sl) -> do
let slotAt' = slotAt
epochLength
slotLength
startTime

let slotStartTime' = slotStartTime
epochLength
slotLength
startTime

counterexample (show $ slotStartTime' sl) $
slotAt' (slotStartTime' sl) === sl

describe "Negative cases for types decoding" $ do
it "fail fromText @AddressState \"unusedused\"" $ do
let err = "Unable to decode the given value: \"unusedused\".\
Expand Down Expand Up @@ -499,3 +526,33 @@ instance Arbitrary WalletName where
instance Arbitrary BoundType where
shrink = genericShrink
arbitrary = genericArbitrary

instance Arbitrary SlotLength where
shrink (SlotLength t) =
map (SlotLength . fromIntegral)
$ filter (> 0)
$ shrink (floor t :: Int)
arbitrary =
SlotLength . fromIntegral <$> choose (1 :: Int, 100)

instance Arbitrary StartTime where
arbitrary = StartTime <$> genUniformTime

instance Arbitrary EpochLength where
arbitrary = EpochLength . getNonZero <$> arbitrary

-- | Note, for functions which works with both an epoch length and a slot id,
-- we need to make sure that the 'slotNumber' doesn't exceed the epoch length,
-- otherwise, all computations get mixed up.
instance {-# OVERLAPS #-} Arbitrary (EpochLength, SlotId) where
shrink (a,b) =
filter validSlotConfig $ zip (shrink a) (shrink b)
where
validSlotConfig (EpochLength ep, SlotId _ sl) = sl < ep

arbitrary = do
(EpochLength epochLength) <- arbitrary
ep <- choose (0, 1000)
sl <- choose (0, fromIntegral epochLength - 1)
return (EpochLength epochLength, SlotId ep sl)

1 change: 1 addition & 0 deletions nix/.stack.nix/cardano-wallet-core.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.