haskoin-core-1.5.0: src/Haskoin/Network/Message.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}
-- |
-- Module : Haskoin.Network.Message
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- Peer-to-peer network message serialization.
module Haskoin.Network.Message
( -- * Network Message
Message (..),
MessageHeader (..),
msgType,
putMessage,
getMessage,
)
where
import Control.DeepSeq
import Control.Monad (unless, when)
import Data.Binary (Binary (..))
import Data.ByteString (ByteString)
import Data.ByteString qualified as B
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Serialize (Serialize (..))
import Data.Word (Word32)
import GHC.Generics (Generic)
import Haskoin.Block.Common
import Haskoin.Block.Merkle
import Haskoin.Crypto.Hash
import Haskoin.Network.BlockFilter
import Haskoin.Network.Bloom
import Haskoin.Network.Common
import Haskoin.Network.CompactBlock
import Haskoin.Network.Data
import Haskoin.Transaction.Common
-- | Data type representing the header of a 'Message'. All messages sent between
-- nodes contain a message header.
data MessageHeader = MessageHeader
{ -- | magic bytes identify network
magic :: !Word32,
-- | message type
cmd :: !MessageCommand,
-- | length of payload
size :: !Word32,
-- | checksum of payload
checksum :: !CheckSum32
}
deriving (Eq, Show, Generic, NFData)
instance Serial MessageHeader where
deserialize =
MessageHeader
<$> getWord32be
<*> deserialize
<*> getWord32le
<*> deserialize
serialize (MessageHeader m c l chk) = do
putWord32be m
serialize c
putWord32le l
serialize chk
instance Binary MessageHeader where
put = serialize
get = deserialize
instance Serialize MessageHeader where
put = serialize
get = deserialize
-- | The 'Message' type is used to identify all the valid messages that can be
-- sent between bitcoin peers. Only values of type 'Message' will be accepted
-- by other bitcoin peers as bitcoin protocol messages need to be correctly
-- serialized with message headers. Serializing a 'Message' value will
-- include the 'MessageHeader' with the correct checksum value automatically.
-- No need to add the 'MessageHeader' separately.
data Message
= MVersion !Version
| MVerAck
| MAddr !Addr
| MAddrV2 !AddrV2
| MSendAddrV2
| MInv !Inv
| MGetData !GetData
| MNotFound !NotFound
| MGetBlocks !GetBlocks
| MGetHeaders !GetHeaders
| MTx !Tx
| MBlock !Block
| MMerkleBlock !MerkleBlock
| MHeaders !Headers
| MGetAddr
| MFilterLoad !FilterLoad
| MFilterAdd !FilterAdd
| MFilterClear
| MPing !Ping
| MPong !Pong
| MAlert !Alert
| MMempool
| MReject !Reject
| MSendHeaders
| MFeeFilter !FeeFilter
| MSendCompact !SendCompact
| MCompactBlock !CompactBlock
| MGetBlockTxn !GetBlockTxn
| MBlockTxn !BlockTxn
| MGetCFilters !GetCFilters
| MCFilter !CFilter
| MGetCFHeaders !GetCFHeaders
| MCFHeaders !CFHeaders
| MGetCFCheckpt !GetCFCheckpt
| MCFCheckpt !CFCheckpt
| MWtxidRelay
| MSendTxRcncl !SendTxRcncl
| -- | a message of a command this library does not know, with its payload
MOther !ByteString !ByteString
deriving (Eq, Show, Generic, NFData)
-- | Get 'MessageCommand' assocated with a message.
msgType :: Message -> MessageCommand
msgType (MVersion _) = MCVersion
msgType MVerAck = MCVerAck
msgType (MAddr _) = MCAddr
msgType (MAddrV2 _) = MCAddrV2
msgType MSendAddrV2 = MCSendAddrV2
msgType (MInv _) = MCInv
msgType (MGetData _) = MCGetData
msgType (MNotFound _) = MCNotFound
msgType (MGetBlocks _) = MCGetBlocks
msgType (MGetHeaders _) = MCGetHeaders
msgType (MTx _) = MCTx
msgType (MBlock _) = MCBlock
msgType (MMerkleBlock _) = MCMerkleBlock
msgType (MHeaders _) = MCHeaders
msgType MGetAddr = MCGetAddr
msgType (MFilterLoad _) = MCFilterLoad
msgType (MFilterAdd _) = MCFilterAdd
msgType MFilterClear = MCFilterClear
msgType (MPing _) = MCPing
msgType (MPong _) = MCPong
msgType (MAlert _) = MCAlert
msgType MMempool = MCMempool
msgType (MReject _) = MCReject
msgType MSendHeaders = MCSendHeaders
msgType (MFeeFilter _) = MCFeeFilter
msgType (MSendCompact _) = MCSendCmpct
msgType (MCompactBlock _) = MCCmpctBlock
msgType (MGetBlockTxn _) = MCGetBlockTxn
msgType (MBlockTxn _) = MCBlockTxn
msgType (MGetCFilters _) = MCGetCFilters
msgType (MCFilter _) = MCCFilter
msgType (MGetCFHeaders _) = MCGetCFHeaders
msgType (MCFHeaders _) = MCCFHeaders
msgType (MGetCFCheckpt _) = MCGetCFCheckpt
msgType (MCFCheckpt _) = MCCFCheckpt
msgType MWtxidRelay = MCWtxidRelay
msgType (MSendTxRcncl _) = MCSendTxRcncl
msgType (MOther c _) = MCOther c
-- | Deserializer for network messages. It reads one message and nothing
-- more, so messages can be read one after the other from a stream. The parse
-- fails for a header with the magic of another network, a payload larger
-- than the 'maxMessageSize' of the network, or a checksum that does not
-- match the payload. Only the declared payload is handed to the parser of the
-- message type. A message type that carries no payload is accepted with
-- whatever payload it comes with, as in Bitcoin Core.
getMessage :: (MonadGet m) => Network -> m Message
getMessage net = do
(MessageHeader mgc cmd len chk) <- deserialize
unless
(mgc == net.magic)
(fail $ "get: Invalid network magic bytes: " ++ show mgc)
when (len > net.maxMessageSize) $
fail $
"get: Message of "
++ show len
++ " bytes is larger than the limit of "
++ show net.maxMessageSize
payload <- getByteString (fromIntegral len)
unless
(checkSum32 payload == chk)
(fail $ "get: Invalid message checksum: " ++ show chk)
either fail return (runGetS (getPayload cmd len) payload)
-- | Parser for the payload of a message of the given command.
getPayload :: (MonadGet m) => MessageCommand -> Word32 -> m Message
getPayload cmd len =
case cmd of
MCVersion -> MVersion <$> deserialize
MCVerAck -> return MVerAck
MCAddr -> MAddr <$> deserialize
MCAddrV2 -> MAddrV2 <$> deserialize
MCSendAddrV2 -> return MSendAddrV2
MCInv -> MInv <$> deserialize
MCGetData -> MGetData <$> deserialize
MCNotFound -> MNotFound <$> deserialize
MCGetBlocks -> MGetBlocks <$> deserialize
MCGetHeaders -> MGetHeaders <$> deserialize
MCTx -> MTx <$> deserialize
MCBlock -> MBlock <$> deserialize
MCMerkleBlock -> MMerkleBlock <$> deserialize
MCHeaders -> MHeaders <$> deserialize
MCGetAddr -> return MGetAddr
MCFilterLoad -> MFilterLoad <$> deserialize
MCFilterAdd -> MFilterAdd <$> deserialize
MCFilterClear -> return MFilterClear
MCPing -> MPing <$> deserialize
MCPong -> MPong <$> deserialize
MCAlert -> MAlert <$> deserialize
MCMempool -> return MMempool
MCReject -> MReject <$> deserialize
MCSendHeaders -> return MSendHeaders
MCFeeFilter -> MFeeFilter <$> deserialize
MCSendCmpct -> MSendCompact <$> deserialize
MCCmpctBlock -> MCompactBlock <$> deserialize
MCGetBlockTxn -> MGetBlockTxn <$> deserialize
MCBlockTxn -> MBlockTxn <$> deserialize
MCGetCFilters -> MGetCFilters <$> deserialize
MCCFilter -> MCFilter <$> deserialize
MCGetCFHeaders -> MGetCFHeaders <$> deserialize
MCCFHeaders -> MCFHeaders <$> deserialize
MCGetCFCheckpt -> MGetCFCheckpt <$> deserialize
MCCFCheckpt -> MCFCheckpt <$> deserialize
MCWtxidRelay -> return MWtxidRelay
MCSendTxRcncl -> MSendTxRcncl <$> deserialize
MCOther c -> MOther c <$> getByteString (fromIntegral len)
-- | Serializer for network messages.
putMessage :: (MonadPut m) => Network -> Message -> m ()
putMessage net msg = do
let (cmd, payload) =
case msg of
MVersion m -> (MCVersion, runPutS $ serialize m)
MVerAck -> (MCVerAck, B.empty)
MAddr m -> (MCAddr, runPutS $ serialize m)
MAddrV2 m -> (MCAddrV2, runPutS $ serialize m)
MSendAddrV2 -> (MCSendAddrV2, B.empty)
MInv m -> (MCInv, runPutS $ serialize m)
MGetData m -> (MCGetData, runPutS $ serialize m)
MNotFound m -> (MCNotFound, runPutS $ serialize m)
MGetBlocks m -> (MCGetBlocks, runPutS $ serialize m)
MGetHeaders m -> (MCGetHeaders, runPutS $ serialize m)
MTx m -> (MCTx, runPutS $ serialize m)
MBlock m -> (MCBlock, runPutS $ serialize m)
MMerkleBlock m -> (MCMerkleBlock, runPutS $ serialize m)
MHeaders m -> (MCHeaders, runPutS $ serialize m)
MGetAddr -> (MCGetAddr, B.empty)
MFilterLoad m -> (MCFilterLoad, runPutS $ serialize m)
MFilterAdd m -> (MCFilterAdd, runPutS $ serialize m)
MFilterClear -> (MCFilterClear, B.empty)
MPing m -> (MCPing, runPutS $ serialize m)
MPong m -> (MCPong, runPutS $ serialize m)
MAlert m -> (MCAlert, runPutS $ serialize m)
MMempool -> (MCMempool, B.empty)
MReject m -> (MCReject, runPutS $ serialize m)
MSendHeaders -> (MCSendHeaders, B.empty)
MFeeFilter m -> (MCFeeFilter, runPutS $ serialize m)
MSendCompact m -> (MCSendCmpct, runPutS $ serialize m)
MCompactBlock m -> (MCCmpctBlock, runPutS $ serialize m)
MGetBlockTxn m -> (MCGetBlockTxn, runPutS $ serialize m)
MBlockTxn m -> (MCBlockTxn, runPutS $ serialize m)
MGetCFilters m -> (MCGetCFilters, runPutS $ serialize m)
MCFilter m -> (MCCFilter, runPutS $ serialize m)
MGetCFHeaders m -> (MCGetCFHeaders, runPutS $ serialize m)
MCFHeaders m -> (MCCFHeaders, runPutS $ serialize m)
MGetCFCheckpt m -> (MCGetCFCheckpt, runPutS $ serialize m)
MCFCheckpt m -> (MCCFCheckpt, runPutS $ serialize m)
MWtxidRelay -> (MCWtxidRelay, B.empty)
MSendTxRcncl m -> (MCSendTxRcncl, runPutS $ serialize m)
MOther c p -> (MCOther c, p)
chk = checkSum32 payload
len = fromIntegral $ B.length payload
header = MessageHeader net.magic cmd len chk
serialize header
putByteString payload