-
Notifications
You must be signed in to change notification settings - Fork 217
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Another primitive migration - [x] Move BlockHeader, Block, PoolId, DelegationCertificate to primitive lib
- Loading branch information
Showing
9 changed files
with
431 additions
and
228 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
180 changes: 180 additions & 0 deletions
180
lib/primitive/lib/Cardano/Wallet/Primitive/Types/Block.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
{-# LANGUAGE DataKinds #-} | ||
{-# LANGUAGE DeriveGeneric #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE OverloadedLabels #-} | ||
{-# LANGUAGE RecordWildCards #-} | ||
|
||
{-# OPTIONS_GHC -Wno-orphans #-} | ||
|
||
module Cardano.Wallet.Primitive.Types.Block | ||
( Block (..) | ||
, BlockHeader (..) | ||
, ChainPoint (..) | ||
, Slot | ||
, isGenesisBlockHeader | ||
, compareSlot | ||
, chainPointFromBlockHeader | ||
, toSlot | ||
) | ||
|
||
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 <> "]" |
44 changes: 44 additions & 0 deletions
44
lib/primitive/lib/Cardano/Wallet/Primitive/Types/DelegationCertificate.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
111 changes: 111 additions & 0 deletions
111
lib/primitive/lib/Cardano/Wallet/Primitive/Types/PoolId.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'." | ||
] |
Oops, something went wrong.