Skip to content

Commit

Permalink
cleanup now redundant API types regarding derivation path
Browse files Browse the repository at this point in the history
  Turns out that it is much easier and less error-prone to use a single
  opaque type 'DerivationIndex' to represent derivation path as homogeneous
  lists. Having 2 level of indirections in the API now makes thing more
  verbose and quite hard to grasp for a non-educated reader.
  • Loading branch information
KtorZ committed Oct 9, 2020
1 parent 71ca8fc commit 648cab2
Show file tree
Hide file tree
Showing 7 changed files with 3,511 additions and 1,645 deletions.
26 changes: 1 addition & 25 deletions lib/core/src/Cardano/Wallet/Api/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@ import Cardano.Wallet.Api.Types
, ApiByronWalletBalance (..)
, ApiCoinSelection (..)
, ApiCoinSelectionInput (..)
, ApiDerivationPath (..)
, ApiDerivationSegment (..)
, ApiEpochInfo (ApiEpochInfo)
, ApiErrorCode (..)
, ApiFee (..)
Expand All @@ -177,7 +175,6 @@ import Cardano.Wallet.Api.Types
, ApiPoolId (..)
, ApiPostRandomAddressData (..)
, ApiPutAddressesData (..)
, ApiRelativeDerivationIndex (..)
, ApiSelectCoinsData (..)
, ApiT (..)
, ApiTimeReference (..)
Expand Down Expand Up @@ -1799,30 +1796,9 @@ mkApiCoinSelection (UnsignedTx inputs outputs) =
, index = index
, address = (ApiT addr, Proxy @n)
, amount = Quantity $ fromIntegral c
, derivationPath = ApiDerivationPath $ mkApiDerivationSegment <$> path
, derivationPath = ApiT <$> path
}

mkApiDerivationSegment
:: DerivationIndex
-> ApiDerivationSegment
mkApiDerivationSegment (DerivationIndex ix)
| ix >= hardenedThreshold =
ApiDerivationSegment
{ derivationIndex =
ApiRelativeDerivationIndex $ fromIntegral (ix - hardenedThreshold)
, derivationType =
Api.Hardened
}
| otherwise =
ApiDerivationSegment
{ derivationIndex =
ApiRelativeDerivationIndex $ fromIntegral ix
, derivationType =
Api.Soft
}
where
hardenedThreshold = getIndex @'Hardened minBound

mkApiTransaction
:: forall n m. Monad m
=> TimeInterpreter m
Expand Down
112 changes: 26 additions & 86 deletions lib/core/src/Cardano/Wallet/Api/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ module Cardano.Wallet.Api.Types

-- * API Types
, ApiAddress (..)
, ApiDerivationPath (..)
, ApiDerivationSegment (..)
, ApiDerivationType (..)
, ApiRelativeDerivationIndex (..)
, ApiEpochInfo (..)
, ApiSelectCoinsData (..)
, ApiCoinSelection (..)
Expand Down Expand Up @@ -150,6 +146,7 @@ import Cardano.Mnemonic
)
import Cardano.Wallet.Primitive.AddressDerivation
( Depth (..)
, DerivationType (..)
, Index (..)
, NetworkDiscriminant (..)
, Passphrase (..)
Expand All @@ -173,6 +170,7 @@ import Cardano.Wallet.Primitive.Types
, ChimericAccount (..)
, Coin (..)
, DecentralizationLevel (..)
, DerivationIndex (..)
, Direction (..)
, EpochLength (..)
, EpochNo (..)
Expand Down Expand Up @@ -205,7 +203,7 @@ import Control.Applicative
import Control.Arrow
( left )
import Control.Monad
( guard, (<=<), (>=>) )
( guard, (>=>) )
import Data.Aeson
( FromJSON (..)
, SumEncoding (..)
Expand Down Expand Up @@ -233,8 +231,6 @@ import Data.ByteString
( ByteString )
import Data.Either.Extra
( maybeToEither )
import Data.Foldable
( asum )
import Data.Function
( (&) )
import Data.Generics.Internal.VL.Lens
Expand Down Expand Up @@ -375,33 +371,6 @@ data ApiAddress (n :: NetworkDiscriminant) = ApiAddress
, state :: !(ApiT AddressState)
} deriving (Eq, Generic, Show)

newtype ApiDerivationPath = ApiDerivationPath
{ unApiDerivationPath :: NonEmpty ApiDerivationSegment
} deriving (Eq, Generic, Show)

data ApiDerivationSegment = ApiDerivationSegment
{ derivationIndex :: !ApiRelativeDerivationIndex
, derivationType :: !ApiDerivationType
} deriving (Eq, Generic, Show)

-- | Represents a type of address derivation.
--
-- Note that the values of this type are a strict subset of those provided
-- by 'DerivationType' from 'Cardano.Wallet.Primitive.AddressDerivation'.
--
data ApiDerivationType
= Hardened
| Soft
deriving (Bounded, Enum, Eq, Generic, Show)

-- | Represents a relative address index.
--
-- The range of this type is exactly half that of a 'Word32'.
--
newtype ApiRelativeDerivationIndex = ApiRelativeDerivationIndex
{ unApiRelativeDerivationIndex :: Word31
} deriving (Bounded, Enum, Eq, Generic, Show)

data ApiEpochInfo = ApiEpochInfo
{ epochNumber :: !(ApiT EpochNo)
, epochStartTime :: !UTCTime
Expand All @@ -420,7 +389,7 @@ data ApiCoinSelectionInput (n :: NetworkDiscriminant) = ApiCoinSelectionInput
{ id :: !(ApiT (Hash "Tx"))
, index :: !Word32
, address :: !(ApiT Address, Proxy n)
, derivationPath :: ApiDerivationPath
, derivationPath :: NonEmpty (ApiT DerivationIndex)
, amount :: !(Quantity "lovelace" Natural)
} deriving (Eq, Generic, Show)

Expand Down Expand Up @@ -962,37 +931,37 @@ instance DecodeAddress n => FromJSON (ApiAddress n) where
instance EncodeAddress n => ToJSON (ApiAddress n) where
toJSON = genericToJSON defaultRecordTypeOptions

instance ToJSON ApiDerivationPath where
toJSON = toJSON . unApiDerivationPath
instance FromJSON ApiDerivationPath where
parseJSON = fmap ApiDerivationPath . parseJSON

instance ToJSON ApiDerivationSegment where
toJSON (ApiDerivationSegment (ApiRelativeDerivationIndex ix) typ)
| typ == Hardened = toJSON (show ix <> "H")
instance ToJSON (ApiT DerivationIndex) where
toJSON (ApiT (DerivationIndex ix))
| ix >= firstHardened = toJSON (show (ix - firstHardened) <> "H")
| otherwise = toJSON (show ix)
instance FromJSON ApiDerivationSegment where
parseJSON value = asum
[ parseJSON value >>= parseAsScientific
, parseJSON value >>= parseAsText
]
where
parseAsText :: Text -> Aeson.Parser ApiDerivationSegment
firstHardened = getIndex @'Hardened minBound

instance FromJSON (ApiT DerivationIndex) where
parseJSON value = ApiT <$> (parseJSON value >>= parseAsText)
where
firstHardened = getIndex @'Hardened minBound

parseAsText :: Text -> Aeson.Parser DerivationIndex
parseAsText txt =
if "H" `T.isSuffixOf` txt then do
path <- castNumber (T.init txt) >>= parseAsScientific
pure $ path { derivationType = Hardened }
DerivationIndex ix <- castNumber (T.init txt) >>= parseAsScientific
pure $ DerivationIndex $ ix + firstHardened
else
castNumber txt >>= parseAsScientific

parseAsScientific :: Scientific -> Aeson.Parser ApiDerivationSegment
parseAsScientific :: Scientific -> Aeson.Parser DerivationIndex
parseAsScientific x =
case toBoundedInteger x of
Nothing -> fail "expected an unsigned int31"
Just ix -> pure ApiDerivationSegment
{ derivationIndex = ApiRelativeDerivationIndex ix
, derivationType = Soft
}
Just ix | ix < firstHardened -> pure $ DerivationIndex ix
_ -> fail $ mconcat
[ "A derivation index must be a natural number between "
, show (getIndex @'Soft minBound)
, " and "
, show (getIndex @'Soft maxBound)
, "."
]

castNumber :: Text -> Aeson.Parser Scientific
castNumber txt =
Expand All @@ -1003,35 +972,6 @@ instance FromJSON ApiDerivationSegment where
Just s ->
pure s

instance ToJSON (ApiDerivationType) where
toJSON = genericToJSON defaultSumTypeOptions
instance FromJSON (ApiDerivationType) where
parseJSON = genericParseJSON defaultSumTypeOptions

instance ToJSON ApiRelativeDerivationIndex where
toJSON = toJSON . fromEnum
instance FromJSON ApiRelativeDerivationIndex where
parseJSON = eitherToParser . integerToIndex <=< parseJSON
where
integerToIndex :: Integer -> Either String ApiRelativeDerivationIndex
integerToIndex i
| i < minIntegerBound = Left errorMessage
| i > maxIntegerBound = Left errorMessage
| otherwise = Right
$ ApiRelativeDerivationIndex
$ fromIntegral i

minIntegerBound = toInteger $ unApiRelativeDerivationIndex minBound
maxIntegerBound = toInteger $ unApiRelativeDerivationIndex maxBound

errorMessage = mconcat
[ "A relative address index must be a natural number between "
, show minIntegerBound
, " and "
, show maxIntegerBound
, "."
]

instance FromJSON ApiEpochInfo where
parseJSON = genericParseJSON defaultRecordTypeOptions
instance ToJSON ApiEpochInfo where
Expand Down
Loading

0 comments on commit 648cab2

Please sign in to comment.