From c70d293c99dcf3ed4f23308075289d5d4236bb41 Mon Sep 17 00:00:00 2001 From: paolino Date: Sat, 18 Nov 2023 10:36:18 +0000 Subject: [PATCH 1/6] Copy PoolId type to primitive lib --- lib/primitive/cardano-wallet-primitive.cabal | 1 + .../Cardano/Wallet/Primitive/Types/PoolId.hs | 111 ++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 lib/primitive/lib/Cardano/Wallet/Primitive/Types/PoolId.hs diff --git a/lib/primitive/cardano-wallet-primitive.cabal b/lib/primitive/cardano-wallet-primitive.cabal index fa34cefebfd..bbfd8b0f3fc 100644 --- a/lib/primitive/cardano-wallet-primitive.cabal +++ b/lib/primitive/cardano-wallet-primitive.cabal @@ -105,6 +105,7 @@ library Cardano.Wallet.Primitive.Types.ExecutionUnitPrices Cardano.Wallet.Primitive.Types.FeePolicy Cardano.Wallet.Primitive.Types.Hash + Cardano.Wallet.Primitive.Types.PoolId Cardano.Wallet.Primitive.Types.ProtocolMagic Cardano.Wallet.Primitive.Types.ProtocolParameters Cardano.Wallet.Primitive.Types.RewardAccount diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/PoolId.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/PoolId.hs new file mode 100644 index 00000000000..fbe6f19cf02 --- /dev/null +++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/PoolId.hs @@ -0,0 +1,111 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Wallet.Primitive.Types.PoolId + ( PoolId (..) + , poolIdBytesLength + , decodePoolIdBech32 + , encodePoolIdBech32 + ) +where + +import Prelude + +import Control.DeepSeq + ( NFData + ) +import Data.ByteArray.Encoding + ( Base (Base16) + , convertFromBase + , convertToBase + ) +import Data.ByteString + ( ByteString + ) +import Data.List + ( intercalate + ) +import Data.Text.Class + ( FromText (..) + , TextDecodingError (TextDecodingError) + , ToText (..) + ) +import Data.Text.Encoding + ( decodeUtf8 + , encodeUtf8 + ) +import Fmt + ( Buildable (..) + , prefixF + ) +import GHC.Generics + ( Generic + ) + +import qualified Codec.Binary.Bech32 as Bech32 +import qualified Codec.Binary.Bech32.TH as Bech32 +import qualified Data.ByteString as BS +import qualified Data.Text as T + +-- | Identifies a stake pool. +-- For Jörmungandr a 'PoolId' is the blake2b-256 hash of the stake pool +-- registration certificate. +newtype PoolId = PoolId { getPoolId :: ByteString } + deriving (Generic, Eq, Ord) + +instance Show PoolId where + show p = "(PoolId " <> show (encodePoolIdBech32 p) <> ")" + +poolIdBytesLength :: [Int] +poolIdBytesLength = [28, 32] + +instance NFData PoolId + +instance Buildable PoolId where + build poolId = mempty + <> prefixF 8 poolIdF + where + poolIdF = build (toText poolId) + +instance ToText PoolId where + toText = decodeUtf8 + . convertToBase Base16 + . getPoolId + +instance FromText PoolId where + fromText t = case convertFromBase Base16 $ encodeUtf8 t of + Left _ -> + textDecodingError + Right bytes | BS.length bytes `elem` poolIdBytesLength -> + Right $ PoolId bytes + Right _ -> + textDecodingError + where + textDecodingError = Left $ TextDecodingError $ unwords + [ "Invalid stake pool id: expecting a hex-encoded value that is" + , intercalate " or " (show <$> poolIdBytesLength) + , "bytes in length." + ] + +-- | Encode 'PoolId' as Bech32 with "pool" hrp. +encodePoolIdBech32 :: PoolId -> T.Text +encodePoolIdBech32 = + Bech32.encodeLenient hrp + . Bech32.dataPartFromBytes + . getPoolId + where + hrp = [Bech32.humanReadablePart|pool|] + +-- | Decode a Bech32 encoded 'PoolId'. +decodePoolIdBech32 :: T.Text -> Either TextDecodingError PoolId +decodePoolIdBech32 t = + case fmap Bech32.dataPartToBytes <$> Bech32.decodeLenient t of + Left _ -> Left textDecodingError + Right (_, Just bytes) -> + Right $ PoolId bytes + Right _ -> Left textDecodingError + where + textDecodingError = TextDecodingError $ unwords + [ "Invalid stake pool id: expecting a Bech32 encoded value" + , "with human readable part of 'pool'." + ] From e568853d12bfc837ad143a8cafb3d89947885aa1 Mon Sep 17 00:00:00 2001 From: paolino Date: Sat, 18 Nov 2023 10:37:30 +0000 Subject: [PATCH 2/6] Copy DelegationCertificate to primitive lib --- lib/primitive/cardano-wallet-primitive.cabal | 1 + .../Primitive/Types/DelegationCertificate.hs | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 lib/primitive/lib/Cardano/Wallet/Primitive/Types/DelegationCertificate.hs diff --git a/lib/primitive/cardano-wallet-primitive.cabal b/lib/primitive/cardano-wallet-primitive.cabal index bbfd8b0f3fc..e5a84f6b59d 100644 --- a/lib/primitive/cardano-wallet-primitive.cabal +++ b/lib/primitive/cardano-wallet-primitive.cabal @@ -100,6 +100,7 @@ library Cardano.Wallet.Primitive.Types.Coin Cardano.Wallet.Primitive.Types.Coin.Gen Cardano.Wallet.Primitive.Types.DecentralizationLevel + Cardano.Wallet.Primitive.Types.DelegationCertificate Cardano.Wallet.Primitive.Types.EpochNo Cardano.Wallet.Primitive.Types.EraInfo Cardano.Wallet.Primitive.Types.ExecutionUnitPrices diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/DelegationCertificate.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/DelegationCertificate.hs new file mode 100644 index 00000000000..8ab71ce4b4b --- /dev/null +++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/DelegationCertificate.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE LambdaCase #-} + +module Cardano.Wallet.Primitive.Types.DelegationCertificate + ( DelegationCertificate (..) + , dlgCertAccount + , dlgCertPoolId + ) +where + +import Prelude + +import Cardano.Wallet.Primitive.Types.PoolId + ( PoolId + ) +import Cardano.Wallet.Primitive.Types.RewardAccount + ( RewardAccount + ) +import Control.DeepSeq + ( NFData + ) +import GHC.Generics + ( Generic + ) + +data DelegationCertificate + = CertDelegateNone RewardAccount + | CertDelegateFull RewardAccount PoolId + | CertRegisterKey RewardAccount + deriving (Generic, Show, Eq, Ord) + +instance NFData DelegationCertificate + +dlgCertAccount :: DelegationCertificate -> RewardAccount +dlgCertAccount = \case + CertDelegateNone acc -> acc + CertDelegateFull acc _ -> acc + CertRegisterKey acc -> acc + +dlgCertPoolId :: DelegationCertificate -> Maybe PoolId +dlgCertPoolId = \case + CertDelegateNone{} -> Nothing + CertDelegateFull _ poolId -> Just poolId + CertRegisterKey _ -> Nothing From 7546ff325e0e4687466026985c61820d57ae3e01 Mon Sep 17 00:00:00 2001 From: paolino Date: Sat, 18 Nov 2023 10:38:19 +0000 Subject: [PATCH 3/6] Copy Block type and deps to primitive lib --- lib/primitive/cardano-wallet-primitive.cabal | 1 + .../Cardano/Wallet/Primitive/Types/Block.hs | 178 ++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs diff --git a/lib/primitive/cardano-wallet-primitive.cabal b/lib/primitive/cardano-wallet-primitive.cabal index e5a84f6b59d..5e53d17b7dd 100644 --- a/lib/primitive/cardano-wallet-primitive.cabal +++ b/lib/primitive/cardano-wallet-primitive.cabal @@ -97,6 +97,7 @@ library Cardano.Wallet.Primitive.NetworkId Cardano.Wallet.Primitive.Types.Address Cardano.Wallet.Primitive.Types.Address.Gen + Cardano.Wallet.Primitive.Types.Block Cardano.Wallet.Primitive.Types.Coin Cardano.Wallet.Primitive.Types.Coin.Gen Cardano.Wallet.Primitive.Types.DecentralizationLevel diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs new file mode 100644 index 00000000000..d1addffabab --- /dev/null +++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs @@ -0,0 +1,178 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE RecordWildCards #-} +-- {-# LANGUAGE TypeSynonymInstances #-} + +module Cardano.Wallet.Primitive.Types.Block + ( Block (..) + , BlockHeader (..) + , ChainPoint (..) + , Slot + , isGenesisBlockHeader + , compareSlot + , chainPointFromBlockHeader + ) + +where + +import Prelude + +import Cardano.Slotting.Slot + ( SlotNo + , WithOrigin (..) + ) +import Cardano.Wallet.Primitive.Types.DelegationCertificate + ( DelegationCertificate + ) +import Cardano.Wallet.Primitive.Types.Hash + ( Hash (getHash) + ) +import Cardano.Wallet.Primitive.Types.Tx.Tx + ( Tx + ) +import Control.DeepSeq + ( NFData + ) +import Data.Quantity + ( Quantity (getQuantity) + ) +import Data.Word + ( Word32 + ) +import Fmt + ( Buildable (..) + , blockListF + , indentF + , prefixF + , pretty + ) +import GHC.Generics + ( Generic + ) +import NoThunks.Class + ( NoThunks + ) + +import Control.Lens + ( view + ) +import Data.ByteArray.Encoding + ( Base (Base16) + , convertToBase + ) +import Data.Maybe + ( isNothing + ) +import qualified Data.Text.Encoding as T + +data Block = Block + { header + :: !BlockHeader + , transactions + :: ![Tx] + , delegations + :: ![DelegationCertificate] + } deriving (Show, Eq, Ord, Generic) + +instance NFData Block + +instance Buildable (Block) where + build (Block h txs _) = mempty + <> build h + <> if null txs then " ∅" else "\n" <> indentF 4 (blockListF txs) + +data BlockHeader = BlockHeader + { slotNo + :: SlotNo + , blockHeight + :: Quantity "block" Word32 + , headerHash + :: !(Hash "BlockHeader") + , parentHeaderHash + :: !(Maybe (Hash "BlockHeader")) + } deriving (Show, Eq, Ord, Generic) + +-- | Check whether a block with a given 'BlockHeader' is the genesis block. +isGenesisBlockHeader :: BlockHeader -> Bool +isGenesisBlockHeader = isNothing . view #parentHeaderHash + +instance NFData BlockHeader + +instance Buildable BlockHeader where + build BlockHeader{..} = + previous + <> "[" + <> current + <> "-" + <> build slotNo + <> "#" <> (build . show . getQuantity) blockHeight + <> "]" + where + toHex = T.decodeUtf8 . convertToBase Base16 + current = prefixF 8 $ build $ toHex $ getHash headerHash + previous = case parentHeaderHash of + Nothing -> "" + Just h -> prefixF 8 (build $ toHex $ getHash h) <> "<-" + +-- | A point on the blockchain +-- is either the genesis block, or a block with a hash that was +-- created at a particular 'SlotNo'. +-- +-- TODO: +-- +-- * This type is essentially a copy of the 'Cardano.Api.Block.ChainPoint' +-- type. We want to import it from there when overhauling our types. +-- * That said, using 'WithOrigin' would not be bad. +-- * 'BlockHeader' is also a good type for rerencing points on the chain, +-- but it's less compatible with the types in ouroboros-network. +data ChainPoint + = ChainPointAtGenesis + | ChainPoint !SlotNo !(Hash "BlockHeader") + deriving (Eq, Ord, Show, Generic) + +-- | Compare the slot numbers of two 'ChainPoint's, +-- but where the 'ChainPointAtGenesis' comes before all other slot numbers. +compareSlot :: ChainPoint -> ChainPoint -> Ordering +compareSlot pt1 pt2 = compare (toSlot pt1) (toSlot pt2) + +-- | Convert a 'BlockHeader' into a 'ChainPoint'. +chainPointFromBlockHeader :: BlockHeader -> ChainPoint +chainPointFromBlockHeader header@(BlockHeader sl _ hash _) + | isGenesisBlockHeader header = ChainPointAtGenesis + | otherwise = ChainPoint sl hash + +instance NFData ChainPoint + +instance NoThunks ChainPoint + +instance Buildable ChainPoint where + build ChainPointAtGenesis = "[point genesis]" + build (ChainPoint slot hash) = + "[point " <> hashF <> " at slot " <> pretty slot <> "]" + where + hashF = prefixF 8 $ T.decodeUtf8 $ convertToBase Base16 $ getHash hash + +-- | A point in (slot) time, which is either genesis ('Origin') +-- or has a slot number ('At'). +-- +-- In contrast to 'ChainPoint', the type 'Slot' does not refer +-- to a point on an actual chain with valid block hashes, +-- but merely to a timeslot which can hold a single block. +-- This implies: +-- +-- * 'Slot' has a linear ordering implemented in the 'Ord' class +-- (where @Origin < At slot@). +-- * Using 'Slot' in QuickCheck testing requires less context +-- (such as an actual simulated chain.) +type Slot = WithOrigin SlotNo + +-- | Retrieve the slot of a 'ChainPoint'. +toSlot :: ChainPoint -> Slot +toSlot ChainPointAtGenesis = Origin +toSlot (ChainPoint slot _) = At slot + +-- instance Buildable Slot where +-- build Origin = "[genesis]" +-- build (At slot) = "[at slot " <> pretty slot <> "]" From b7173744229b7b6d921e56353b7b4b8cabee031a Mon Sep 17 00:00:00 2001 From: paolino Date: Sat, 18 Nov 2023 10:38:56 +0000 Subject: [PATCH 4/6] Use Block type from primitive lib in wallet --- .../Cardano/Wallet/Primitive/Types/Block.hs | 10 +- lib/wallet/src/Cardano/Pool/Types.hs | 83 +--------- .../src/Cardano/Wallet/Primitive/Types.hs | 151 +----------------- 3 files changed, 18 insertions(+), 226 deletions(-) diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs index d1addffabab..cbbd695d30f 100644 --- a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs +++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs @@ -3,7 +3,8 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE RecordWildCards #-} --- {-# LANGUAGE TypeSynonymInstances #-} + +{-# OPTIONS_GHC -Wno-orphans #-} module Cardano.Wallet.Primitive.Types.Block ( Block (..) @@ -13,6 +14,7 @@ module Cardano.Wallet.Primitive.Types.Block , isGenesisBlockHeader , compareSlot , chainPointFromBlockHeader + , toSlot ) where @@ -173,6 +175,6 @@ toSlot :: ChainPoint -> Slot toSlot ChainPointAtGenesis = Origin toSlot (ChainPoint slot _) = At slot --- instance Buildable Slot where --- build Origin = "[genesis]" --- build (At slot) = "[at slot " <> pretty slot <> "]" +instance Buildable Slot where + build Origin = "[genesis]" + build (At slot) = "[at slot " <> pretty slot <> "]" diff --git a/lib/wallet/src/Cardano/Pool/Types.hs b/lib/wallet/src/Cardano/Pool/Types.hs index e47bc5e1187..7c5974e2e7e 100644 --- a/lib/wallet/src/Cardano/Pool/Types.hs +++ b/lib/wallet/src/Cardano/Pool/Types.hs @@ -21,6 +21,12 @@ import Prelude import Cardano.Wallet.Primitive.Types.Coin ( Coin (..) ) +import Cardano.Wallet.Primitive.Types.PoolId + ( PoolId (..) + , decodePoolIdBech32 + , encodePoolIdBech32 + , poolIdBytesLength + ) import Cardano.Wallet.Util ( ShowFmt (..) ) @@ -34,17 +40,9 @@ import Data.Aeson ( FromJSON (parseJSON) , ToJSON (toJSON) ) -import Data.ByteArray.Encoding - ( Base (Base16) - , convertFromBase - , convertToBase - ) import Data.ByteString ( ByteString ) -import Data.List - ( intercalate - ) import Data.Map ( Map ) @@ -62,10 +60,6 @@ import Data.Text.Class , TextDecodingError (TextDecodingError) , ToText (..) ) -import Data.Text.Encoding - ( decodeUtf8 - , encodeUtf8 - ) import Database.Persist.Class.PersistField ( PersistField (..) ) @@ -79,7 +73,6 @@ import Fmt ( Buildable (..) , listF' , mapF - , prefixF , pretty ) import GHC.Generics @@ -88,7 +81,6 @@ import GHC.Generics import qualified Codec.Binary.Bech32 as Bech32 import qualified Codec.Binary.Bech32.TH as Bech32 -import qualified Data.ByteString as BS import qualified Data.Map as Map import qualified Data.Text as T @@ -119,69 +111,6 @@ instance PersistField StakePoolTicker where instance PersistFieldSql StakePoolTicker where sqlType _ = sqlType (Proxy @Text) --- | Identifies a stake pool. --- For Jörmungandr a 'PoolId' is the blake2b-256 hash of the stake pool --- registration certificate. -newtype PoolId = PoolId { getPoolId :: ByteString } - deriving (Generic, Eq, Ord) - -instance Show PoolId where - show p = "(PoolId " <> show (encodePoolIdBech32 p) <> ")" - -poolIdBytesLength :: [Int] -poolIdBytesLength = [28, 32] - -instance NFData PoolId - -instance Buildable PoolId where - build poolId = mempty - <> prefixF 8 poolIdF - where - poolIdF = build (toText poolId) - -instance ToText PoolId where - toText = decodeUtf8 - . convertToBase Base16 - . getPoolId - -instance FromText PoolId where - fromText t = case convertFromBase Base16 $ encodeUtf8 t of - Left _ -> - textDecodingError - Right bytes | BS.length bytes `elem` poolIdBytesLength -> - Right $ PoolId bytes - Right _ -> - textDecodingError - where - textDecodingError = Left $ TextDecodingError $ unwords - [ "Invalid stake pool id: expecting a hex-encoded value that is" - , intercalate " or " (show <$> poolIdBytesLength) - , "bytes in length." - ] - --- | Encode 'PoolId' as Bech32 with "pool" hrp. -encodePoolIdBech32 :: PoolId -> T.Text -encodePoolIdBech32 = - Bech32.encodeLenient hrp - . Bech32.dataPartFromBytes - . getPoolId - where - hrp = [Bech32.humanReadablePart|pool|] - --- | Decode a Bech32 encoded 'PoolId'. -decodePoolIdBech32 :: T.Text -> Either TextDecodingError PoolId -decodePoolIdBech32 t = - case fmap Bech32.dataPartToBytes <$> Bech32.decodeLenient t of - Left _ -> Left textDecodingError - Right (_, Just bytes) -> - Right $ PoolId bytes - Right _ -> Left textDecodingError - where - textDecodingError = TextDecodingError $ unwords - [ "Invalid stake pool id: expecting a Bech32 encoded value" - , "with human readable part of 'pool'." - ] - -- | A stake pool owner, which is a public key encoded in bech32 with prefix -- ed25519_pk. newtype PoolOwner = PoolOwner { getPoolOwner :: ByteString } diff --git a/lib/wallet/src/Cardano/Wallet/Primitive/Types.hs b/lib/wallet/src/Cardano/Wallet/Primitive/Types.hs index 3d6a9b4542c..b2f6de431b0 100644 --- a/lib/wallet/src/Cardano/Wallet/Primitive/Types.hs +++ b/lib/wallet/src/Cardano/Wallet/Primitive/Types.hs @@ -12,7 +12,6 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} @@ -194,9 +193,6 @@ import Cardano.Wallet.Primitive.Types.Hash import Cardano.Wallet.Primitive.Types.ProtocolParameters ( ProtocolParameters (..) ) -import Cardano.Wallet.Primitive.Types.RewardAccount - ( RewardAccount (..) - ) import Cardano.Wallet.Primitive.Types.SlottingParameters ( ActiveSlotCoefficient (..) , EpochLength (..) @@ -208,9 +204,6 @@ import Cardano.Wallet.Primitive.Types.SlottingParameters import Cardano.Wallet.Primitive.Types.TokenBundleMaxSize ( TokenBundleMaxSize (..) ) -import Cardano.Wallet.Primitive.Types.Tx.Tx - ( Tx (..) - ) import Cardano.Wallet.Primitive.Types.TxParameters ( ExecutionUnits (..) , TxParameters (..) @@ -263,11 +256,9 @@ import Data.Kind ) import Data.Maybe ( isJust - , isNothing ) import Data.Quantity ( Percentage (..) - , Quantity (..) ) import Data.String ( fromString @@ -308,9 +299,7 @@ import Database.Persist.Sql ) import Fmt ( Buildable (..) - , blockListF , blockListF' - , indentF , prefixF , pretty , suffixF @@ -322,10 +311,13 @@ import Network.URI ( URI (..) , uriToString ) -import NoThunks.Class - ( NoThunks - ) +import Cardano.Wallet.Primitive.Types.Block +import Cardano.Wallet.Primitive.Types.DelegationCertificate + ( DelegationCertificate (..) + , dlgCertAccount + , dlgCertPoolId + ) import qualified Data.Text as T import qualified Data.Text.Encoding as T {------------------------------------------------------------------------------- @@ -591,116 +583,6 @@ isSubrangeOf r1 r2 = {------------------------------------------------------------------------------- Block -------------------------------------------------------------------------------} --- | A block on the chain, as the wallet sees it. -data Block = Block - { header - :: !BlockHeader - , transactions - :: ![Tx] - , delegations - :: ![DelegationCertificate] - } deriving (Show, Eq, Ord, Generic) - -instance NFData Block - -instance Buildable (Block) where - build (Block h txs _) = mempty - <> build h - <> if null txs then " ∅" else "\n" <> indentF 4 (blockListF txs) - -data BlockHeader = BlockHeader - { slotNo - :: SlotNo - , blockHeight - :: Quantity "block" Word32 - , headerHash - :: !(Hash "BlockHeader") - , parentHeaderHash - :: !(Maybe (Hash "BlockHeader")) - } deriving (Show, Eq, Ord, Generic) - --- | Check whether a block with a given 'BlockHeader' is the genesis block. -isGenesisBlockHeader :: BlockHeader -> Bool -isGenesisBlockHeader = isNothing . view #parentHeaderHash - -instance NFData BlockHeader - -instance Buildable BlockHeader where - build BlockHeader{..} = - previous - <> "[" - <> current - <> "-" - <> build slotNo - <> "#" <> (build . show . getQuantity) blockHeight - <> "]" - where - toHex = T.decodeUtf8 . convertToBase Base16 - current = prefixF 8 $ build $ toHex $ getHash headerHash - previous = case parentHeaderHash of - Nothing -> "" - Just h -> prefixF 8 (build $ toHex $ getHash h) <> "<-" - --- | A point on the blockchain --- is either the genesis block, or a block with a hash that was --- created at a particular 'SlotNo'. --- --- TODO: --- --- * This type is essentially a copy of the 'Cardano.Api.Block.ChainPoint' --- type. We want to import it from there when overhauling our types. --- * That said, using 'WithOrigin' would not be bad. --- * 'BlockHeader' is also a good type for rerencing points on the chain, --- but it's less compatible with the types in ouroboros-network. -data ChainPoint - = ChainPointAtGenesis - | ChainPoint !SlotNo !(Hash "BlockHeader") - deriving (Eq, Ord, Show, Generic) - --- | Compare the slot numbers of two 'ChainPoint's, --- but where the 'ChainPointAtGenesis' comes before all other slot numbers. -compareSlot :: ChainPoint -> ChainPoint -> Ordering -compareSlot pt1 pt2 = compare (toSlot pt1) (toSlot pt2) - --- | Convert a 'BlockHeader' into a 'ChainPoint'. -chainPointFromBlockHeader :: BlockHeader -> ChainPoint -chainPointFromBlockHeader header@(BlockHeader sl _ hash _) - | isGenesisBlockHeader header = ChainPointAtGenesis - | otherwise = ChainPoint sl hash - -instance NFData ChainPoint - -instance NoThunks ChainPoint - -instance Buildable ChainPoint where - build ChainPointAtGenesis = "[point genesis]" - build (ChainPoint slot hash) = - "[point " <> hashF <> " at slot " <> pretty slot <> "]" - where - hashF = prefixF 8 $ T.decodeUtf8 $ convertToBase Base16 $ getHash hash - --- | A point in (slot) time, which is either genesis ('Origin') --- or has a slot number ('At'). --- --- In contrast to 'ChainPoint', the type 'Slot' does not refer --- to a point on an actual chain with valid block hashes, --- but merely to a timeslot which can hold a single block. --- This implies: --- --- * 'Slot' has a linear ordering implemented in the 'Ord' class --- (where @Origin < At slot@). --- * Using 'Slot' in QuickCheck testing requires less context --- (such as an actual simulated chain.) -type Slot = WithOrigin SlotNo - --- | Retrieve the slot of a 'ChainPoint'. -toSlot :: ChainPoint -> Slot -toSlot ChainPointAtGenesis = Origin -toSlot (ChainPoint slot _) = At slot - -instance Buildable Slot where - build Origin = "[genesis]" - build (At slot) = "[at slot " <> pretty slot <> "]" -- | A thin wrapper around derivation indexes. This can be used to represent -- derivation path as homogeneous lists of 'DerivationIndex'. This is slightly @@ -806,15 +688,6 @@ instance NFData StartTime Stake Pool Delegation and Registration Certificates -------------------------------------------------------------------------------} --- | Represent a delegation certificate. -data DelegationCertificate - = CertDelegateNone RewardAccount - | CertDelegateFull RewardAccount PoolId - | CertRegisterKey RewardAccount - deriving (Generic, Show, Eq, Ord) - -instance NFData DelegationCertificate - data StakeKeyCertificate = StakeKeyRegistration | StakeKeyDeregistration @@ -829,18 +702,6 @@ instance PersistField StakeKeyCertificate where instance PersistFieldSql StakeKeyCertificate where sqlType _ = sqlType (Proxy @Text) -dlgCertAccount :: DelegationCertificate -> RewardAccount -dlgCertAccount = \case - CertDelegateNone acc -> acc - CertDelegateFull acc _ -> acc - CertRegisterKey acc -> acc - -dlgCertPoolId :: DelegationCertificate -> Maybe PoolId -dlgCertPoolId = \case - CertDelegateNone{} -> Nothing - CertDelegateFull _ poolId -> Just poolId - CertRegisterKey _ -> Nothing - -- | Sum-type of pool registration- and retirement- certificates. Mirrors the -- @PoolCert@ type in cardano-ledger-specs. data PoolCertificate From a318e1a7739bf5b79ba7172e72af2d79c386d383 Mon Sep 17 00:00:00 2001 From: paolino Date: Mon, 20 Nov 2023 10:40:25 +0000 Subject: [PATCH 5/6] Move encodePoolIdBech32 specs to primitive lib tests --- lib/primitive/cardano-wallet-primitive.cabal | 1 + .../Wallet/Primitive/Types/PoolIdSpec.hs | 35 +++++++++++++++++++ .../Cardano/Wallet/Primitive/TypesSpec.hs | 6 ---- 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 lib/primitive/test/spec/Cardano/Wallet/Primitive/Types/PoolIdSpec.hs diff --git a/lib/primitive/cardano-wallet-primitive.cabal b/lib/primitive/cardano-wallet-primitive.cabal index 5e53d17b7dd..f181ef21245 100644 --- a/lib/primitive/cardano-wallet-primitive.cabal +++ b/lib/primitive/cardano-wallet-primitive.cabal @@ -187,6 +187,7 @@ test-suite test Cardano.Wallet.Primitive.Types.AddressSpec Cardano.Wallet.Primitive.Types.CoinSpec Cardano.Wallet.Primitive.Types.HashSpec + Cardano.Wallet.Primitive.Types.PoolIdSpec Cardano.Wallet.Primitive.Types.TokenBundleSpec Cardano.Wallet.Primitive.Types.TokenMapSpec Cardano.Wallet.Primitive.Types.TokenPolicySpec diff --git a/lib/primitive/test/spec/Cardano/Wallet/Primitive/Types/PoolIdSpec.hs b/lib/primitive/test/spec/Cardano/Wallet/Primitive/Types/PoolIdSpec.hs new file mode 100644 index 00000000000..a9fc4fe9837 --- /dev/null +++ b/lib/primitive/test/spec/Cardano/Wallet/Primitive/Types/PoolIdSpec.hs @@ -0,0 +1,35 @@ +{-# LANGUAGE ScopedTypeVariables #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module Cardano.Wallet.Primitive.Types.PoolIdSpec + ( spec + ) where + +import Prelude + +import Cardano.Wallet.Primitive.Types.PoolId + ( PoolId (..) + , decodePoolIdBech32 + , encodePoolIdBech32 + ) +import Test.Hspec + ( Spec + , describe + , it + ) +import Test.QuickCheck + ( forAll + , vector + , withMaxSuccess + , (===) + ) + +import qualified Data.ByteString as BS + +spec :: Spec +spec = describe "Cardano.Wallet.Primitive.Types.PoolId" $ do + it "Can roundtrip {decode,encode}PoolIdBech32" + $ withMaxSuccess 1000 + $ forAll (PoolId . BS.pack <$> vector 32) + $ \pid -> + decodePoolIdBech32 (encodePoolIdBech32 pid) === Right pid diff --git a/lib/wallet/test/unit/Cardano/Wallet/Primitive/TypesSpec.hs b/lib/wallet/test/unit/Cardano/Wallet/Primitive/TypesSpec.hs index 721bd385a70..2c24d655c88 100644 --- a/lib/wallet/test/unit/Cardano/Wallet/Primitive/TypesSpec.hs +++ b/lib/wallet/test/unit/Cardano/Wallet/Primitive/TypesSpec.hs @@ -23,8 +23,6 @@ import Cardano.Address.Derivation import Cardano.Pool.Types ( PoolId (..) , PoolOwner (..) - , decodePoolIdBech32 - , encodePoolIdBech32 ) import Cardano.Wallet.Address.Derivation ( Depth (..) @@ -324,10 +322,6 @@ spec = describe "Cardano.Wallet.Primitive.Types" $ do toText <$> fromText @(Hash "Account") text `shouldBe` Right text - it "Can roundtrip {decode,encode}PoolIdBech32" $ - withMaxSuccess 1000 $ property $ \(pid :: PoolId) -> - decodePoolIdBech32 (encodePoolIdBech32 pid) === Right pid - describe "Buildable" $ do it "WalletId" $ do let mw = someDummyMnemonic (Proxy @12) From e5bab287bcb4a2bb2313588f134721147f628067 Mon Sep 17 00:00:00 2001 From: paolino Date: Mon, 20 Nov 2023 08:55:41 +0000 Subject: [PATCH 6/6] Copy StakePoolSummary type to primitive lib --- lib/primitive/cardano-wallet-primitive.cabal | 1 + .../Primitive/Types/StakePoolSummary.hs | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 lib/primitive/lib/Cardano/Wallet/Primitive/Types/StakePoolSummary.hs diff --git a/lib/primitive/cardano-wallet-primitive.cabal b/lib/primitive/cardano-wallet-primitive.cabal index f181ef21245..853fdb79b80 100644 --- a/lib/primitive/cardano-wallet-primitive.cabal +++ b/lib/primitive/cardano-wallet-primitive.cabal @@ -113,6 +113,7 @@ library Cardano.Wallet.Primitive.Types.RewardAccount Cardano.Wallet.Primitive.Types.RewardAccount.Gen Cardano.Wallet.Primitive.Types.SlottingParameters + Cardano.Wallet.Primitive.Types.StakePoolSummary Cardano.Wallet.Primitive.Types.TokenBundle Cardano.Wallet.Primitive.Types.TokenBundle.Gen Cardano.Wallet.Primitive.Types.TokenBundleMaxSize diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Types/StakePoolSummary.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/StakePoolSummary.hs new file mode 100644 index 00000000000..b4221a3e4ae --- /dev/null +++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Types/StakePoolSummary.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE NamedFieldPuns #-} + +module Cardano.Wallet.Primitive.Types.StakePoolSummary + ( StakePoolsSummary (..) + ) where + +import Prelude + +import Cardano.Wallet.Primitive.Types.Coin + ( Coin (..) + ) +import Cardano.Wallet.Primitive.Types.PoolId + ( PoolId (..) + ) +import Data.Map + ( Map + ) +import Data.Quantity + ( Percentage + ) +import Fmt + ( Buildable (..) + , listF' + , mapF + , pretty + ) + +import qualified Data.Map as Map + +data StakePoolsSummary = StakePoolsSummary + { nOpt :: Int + , rewards :: Map PoolId Coin + , stake :: Map PoolId Percentage + } + deriving (Show, Eq) + +instance Buildable StakePoolsSummary where + build StakePoolsSummary{nOpt, rewards, stake} = + listF' + id + [ "Stake: " <> mapF (Map.toList stake) + , "Non-myopic member rewards: " <> mapF (Map.toList rewards) + , "Optimum number of pools: " <> pretty nOpt + ]