packages feed

haskoin-core-1.6.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,
    maxPayloadSize,
  )
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

-- | Largest payload that a message of this command may have on a network.
-- On Bitcoin every message is limited to 4,000,000 bytes, as in Bitcoin Core.
-- On Bitcoin Cash a message is limited to 2 MiB, unless it carries block
-- content: @block@, @cmpctblock@ and @blocktxn@ may be twice the block size.
-- This is @CMessageHeader::IsOversized@ in Bitcoin Cash Node. A node should
-- check the length of a message header against this limit before it buffers
-- the payload.
maxPayloadSize :: Network -> MessageCommand -> Word32
maxPayloadSize net cmd
  | carriesBlock = net.maxMessageSize
  | otherwise = min net.maxMessageSize net.maxProtocolMessageSize
  where
    carriesBlock = cmd `elem` [MCBlock, MCCmpctBlock, MCBlockTxn]

-- | Read one message of a network. The checks come in this order: the magic
-- bytes, the command name, the length against 'maxPayloadSize' of the
-- command, then the payload is read and its checksum compared. The payload
-- parser sees the declared bytes only. Any failure fails the parse. Bitcoin
-- Core drops a message with a wrong checksum or an invalid command name and
-- keeps the connection, which a caller can do by reading the header itself.
--
-- Transactions, in @tx@, @block@, @cmpctblock@ and @blocktxn@ messages, are
-- read with 'getNetworkTx', and compact block positions are limited as the
-- network requires, see 'getCompactBlock' and 'getGetBlockTxn'.
getMessage :: (MonadGet m) => Network -> m Message
getMessage net = do
  -- The magic is checked before the rest of the header is looked at.
  mgc <- lookAhead getWord32be
  unless
    (mgc == net.magic)
    (fail $ "get: Invalid network magic bytes: " ++ show mgc)
  (MessageHeader _ cmd len chk) <- deserialize
  let limit = maxPayloadSize net cmd
  when (len > limit) $
    fail $
      "get: Message of "
        ++ show len
        ++ " bytes is larger than the limit of "
        ++ show limit
  payload <- getByteString (fromIntegral len)
  unless
    (checkSum32 payload == chk)
    (fail $ "get: Invalid message checksum: " ++ show chk)
  either fail return (runGetS (getPayload net cmd len) payload)

getPayload :: (MonadGet m) => Network -> MessageCommand -> Word32 -> m Message
getPayload net 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 <$> getNetworkTx net
    MCBlock -> MBlock <$> (Block <$> deserialize <*> getVarIntList (getNetworkTx net))
    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 <$> getCompactBlock net
    MCGetBlockTxn -> MGetBlockTxn <$> getGetBlockTxn net
    MCBlockTxn -> MBlockTxn <$> getBlockTxn net
    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