diff --git a/lib/jormungandr/cardano-wallet-jormungandr.cabal b/lib/jormungandr/cardano-wallet-jormungandr.cabal index c67d86f2d70..0d258529a46 100644 --- a/lib/jormungandr/cardano-wallet-jormungandr.cabal +++ b/lib/jormungandr/cardano-wallet-jormungandr.cabal @@ -34,11 +34,9 @@ library build-depends: base , cardano-wallet-core --- , binary --- , bytestring --- , cardano-crypto --- , cryptonite --- , digest + , binary + , bytestring + , memory , text , text-class hs-source-dirs: diff --git a/lib/jormungandr/src/Cardano/Wallet/Binary/Jormungandr.hs b/lib/jormungandr/src/Cardano/Wallet/Binary/Jormungandr.hs index b41fec73eb7..abec909742b 100644 --- a/lib/jormungandr/src/Cardano/Wallet/Binary/Jormungandr.hs +++ b/lib/jormungandr/src/Cardano/Wallet/Binary/Jormungandr.hs @@ -1,5 +1,8 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeApplications #-} -- | -- Copyright: © 2018-2019 IOHK @@ -8,5 +11,173 @@ -- The format is for the Shelley era as implemented by the Jörmungandr node. module Cardano.Wallet.Binary.Jormungandr - ( + ( getBlockHeader + , Message (..) + , BlockHeader (..) + + -- * Re-export + , runGet + + -- * For dev + , genesisBlock + , dev ) where + +import Prelude + +import Cardano.Wallet.Primitive.Types + ( SlotId (..) ) +import Data.Binary.Get + ( Get + , getByteString + , getWord16be + , getWord32be + , getWord8 + , isEmpty + , isolate + , runGet + , skip + ) +import Data.ByteArray.Encoding + ( Base (Base16), convertFromBase ) +import Data.ByteString + ( ByteString ) +import Data.Word + ( Word16, Word32 ) + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BL + + +-- | Messages is what the block body consists of. +-- +-- Every message is prefixed with a message header. +-- +-- Following, as closely as possible: +-- https://github.com/input-output-hk/rust-cardano/blob/e0616f13bebd6b908320bddb1c1502dea0d3305a/chain-impl-mockchain/src/message/mod.rs#L22-L29 +data Message + = Initial [ConfigParam] + | OldUtxoDeclaration TODO + | Transaction TODO + | Certificate TODO + | UpdateProposal SignedUpdateProposal + | UpdateVote SignedVote + | UnimplementedMessage Int -- For development. Remove later. + deriving Show + + +data BlockHeader = BlockHeader + { version :: Word16 + , contentSize :: Word32 + , slot :: SlotId + , chainLength :: Word32 + , contentHash :: ByteString + , parentHeaderHash :: Maybe ByteString + } deriving (Show, Eq) + +data Block = Block BlockHeader [Message] + deriving Show + + +data SignedUpdateProposal = SignedUpdateProposal + deriving Show +data TODO = TODO + deriving Show +data SignedVote = SignedVote + deriving Show +data ConfigParam = ConfigParam + deriving Show + +{-# ANN getBlockHeader ("HLint: ignore Use <$>" :: String) #-} +getBlockHeader :: Get BlockHeader +getBlockHeader = (fromIntegral <$> getWord16be) >>= \s -> isolate s $ do + version <- getWord16be + sizeOfContent <- getWord32be + + slotEpoch <- fromIntegral <$> getWord32be + slotId <- fromIntegral <$> getWord32be + + chainLength <- getWord32be + + contentHash <- getByteString 32 -- or 256 bits + + parentHeaderHash <- getParentHeaderHash + + -- TODO: Handle special case for BFT + -- TODO: Handle special case for Praos/Genesis + + return $ + BlockHeader + version + sizeOfContent + (SlotId slotId slotEpoch) + chainLength + contentHash + parentHeaderHash + +getBlock :: Get Block +getBlock = do + header <- getBlockHeader + msgs <- isolate (fromIntegral $ contentSize header) + $ whileM (not <$> isEmpty) getBlockContent + return $ Block header msgs + +getBlockContent :: Get Message +getBlockContent = do + size <- getWord16be + contentType <- fromIntegral <$> getWord8 + + let msgSize = fromIntegral size - 1 + + let unimpl = skip msgSize >> return (UnimplementedMessage contentType) + + isolate msgSize $ case contentType of + 0 -> unimpl + 1 -> unimpl + 2 -> unimpl + 3 -> unimpl + 4 -> unimpl + 5 -> unimpl + other -> fail $ "Unexpected content type tag " ++ show other + +getParentHeaderHash :: Get (Maybe ByteString) +getParentHeaderHash = getByteString 32 >>= \case + a | a == BS.pack (replicate 32 0) + -> return Nothing + | otherwise + -> return $ Just a + + +{------------------------------------------------------------------------------- + For development +-------------------------------------------------------------------------------} + +dev :: IO () +dev = print $ runGet getBlock genesisBlock + +genesisBlock :: BL.ByteString +genesisBlock = either error BL.fromStrict $ convertFromBase @ByteString Base16 + "005200000000009f000000000000000000000000ffadebfecd59d9eaa12e903a\ + \d58100f7c1e35899739c3d05d022835c069d2b4f000000000000000000000000\ + \00000000000000000000000000000000000000000047000048000000005cc1c2\ + \4900810200c200010108000000000000087001410f01840000000a01e030a694\ + \b80dbba2d1b8a4b55652b03d96315c8414b054fa737445ac2d2a865c76002604\ + \0001000000ff0005000006000000000000000000000000000000000000000000\ + \0000000000002c020001833324c37869c122689a35917df53a4f2294a3a52f68\ + \5e05f5f8e53b87e7ea452f000000000000000e" + + +{------------------------------------------------------------------------------- + Helpers +-------------------------------------------------------------------------------} + +whileM :: Monad m => m Bool -> m a -> m [a] +whileM p f = go + where + go = do + x <- p + if x then do + x' <- f + xs <- go + return (x' : xs) + else return [] diff --git a/lib/jormungandr/test/unit/Cardano/Wallet/Binary/JormungandrSpec.hs b/lib/jormungandr/test/unit/Cardano/Wallet/Binary/JormungandrSpec.hs index 2a5eb1fc889..6501738f321 100644 --- a/lib/jormungandr/test/unit/Cardano/Wallet/Binary/JormungandrSpec.hs +++ b/lib/jormungandr/test/unit/Cardano/Wallet/Binary/JormungandrSpec.hs @@ -2,39 +2,41 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeApplications #-} -module Cardano.Wallet.Binary.JormungandrSpec - ( spec - ) where +module Cardano.Wallet.Binary.JormungandrSpec (spec) where import Prelude import Cardano.Wallet.Binary.Jormungandr - () -import Data.ByteString - ( ByteString ) - + ( BlockHeader (..), getBlockHeader, runGet ) import Cardano.Wallet.Primitive.Types - ( BlockHeader (..), Hash (..), SlotId (..) ) - + ( SlotId (..) ) import Data.ByteArray.Encoding ( Base (Base16), convertFromBase ) +import Data.ByteString + ( ByteString ) import Test.Hspec - ( Spec, describe, shouldBe, xit ) + ( Spec, describe, it, shouldBe ) + +import qualified Data.ByteString.Lazy as BL {-# ANN spec ("HLint: ignore Use head" :: String) #-} spec :: Spec spec = do describe "Decoding blocks" $ do - xit "should decode a genesis block" $ do - unsafeDeserialiseFromBytes decodeGenesisBlock genesisBlock + it "should decode a genesis block header" $ do + runGet getBlockHeader genesisBlock `shouldBe` - BlockHeader (SlotId 0 0) (Hash "?") - where - unsafeDeserialiseFromBytes = undefined - decodeGenesisBlock = error "TODO: import from Binary.Jormungandr" + (BlockHeader + { version = 0 + , contentSize = 159 + , slot = SlotId {epochNumber = 0 , slotNumber = 0} + , chainLength = 0 + , contentHash = "\255\173\235\254\205Y\217\234\161.\144:\213\129\NUL\247\193\227X\153s\156=\ENQ\208\"\131\\\ACK\157+O" + , parentHeaderHash = Nothing + }) -genesisBlock :: ByteString -genesisBlock = either error id $ convertFromBase @ByteString Base16 +genesisBlock :: BL.ByteString +genesisBlock = either error BL.fromStrict $ convertFromBase @ByteString Base16 "005200000000009f000000000000000000000000ffadebfecd59d9eaa12e903a\ \d58100f7c1e35899739c3d05d022835c069d2b4f000000000000000000000000\ \00000000000000000000000000000000000000000047000048000000005cc1c2\