packages feed

ethereum-client-haskell-0.0.2: src/Blockchain/Data/Wire.hs

module Blockchain.Data.Wire (
  Message(..),
  Capability(..),
  obj2WireMessage,
  wireMessage2Obj
  ) where

import Data.Functor
import Data.List
import Network.Haskoin.Crypto
import Numeric
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))

import qualified Blockchain.Colors as CL
import Blockchain.Data.Block
import Blockchain.Data.Peer
import Blockchain.Data.RLP
import Blockchain.Data.SignedTransaction
import Blockchain.Format
import Blockchain.SHA
import Blockchain.Util

--import Debug.Trace

data Capability = ETH Integer | SHH Integer deriving (Show)

name2Cap::Integer->String->Capability
name2Cap qqqq "eth" = ETH qqqq
name2Cap qqqq "shh" = SHH qqqq
name2Cap _ x = error $ "Unknown capability string: " ++ x

{-capValue::Capability->String
capValue ETH = "eth"
capValue SHH = "shh"-}

instance RLPSerializable Capability where
    rlpEncode (ETH qqqq) = RLPArray [rlpEncode "eth", rlpEncode qqqq]
    rlpEncode (SHH qqqq) = RLPArray [rlpEncode "shh", rlpEncode qqqq]

    rlpDecode (RLPArray [name, qqqq]) = name2Cap (rlpDecode qqqq) $ rlpDecode name
    rlpDecode x = error $ "wrong format given to rlpDecode for Capability: " ++ show (pretty x)

data TerminationReason =
  DisconnectRequested
  | TCPSubSystemError
  | BreachOfProtocol
  | UselessPeer
  | TooManyPeers
  | AlreadyConnected
  | IncompatibleP2PProtocolVersion
  | NullNodeIdentityReceived
  | ClientQuitting
  | UnexpectedIdentity
  | ConnectedToSelf
  | PingTimeout
  | OtherSubprotocolReason deriving (Show)


numberToTerminationReason::Integer->TerminationReason
numberToTerminationReason 0x00 = DisconnectRequested
numberToTerminationReason 0x01 = TCPSubSystemError
numberToTerminationReason 0x02 = BreachOfProtocol
numberToTerminationReason 0x03 = UselessPeer
numberToTerminationReason 0x04 = TooManyPeers
numberToTerminationReason 0x05 = AlreadyConnected
numberToTerminationReason 0x06 = IncompatibleP2PProtocolVersion
numberToTerminationReason 0x07 = NullNodeIdentityReceived
numberToTerminationReason 0x08 = ClientQuitting
numberToTerminationReason 0x09 = UnexpectedIdentity
numberToTerminationReason 0x0a = ConnectedToSelf
numberToTerminationReason 0x0b = PingTimeout
numberToTerminationReason 0x0c = OtherSubprotocolReason
numberToTerminationReason _ = error "numberToTerminationReasion called with unsupported number"


terminationReasonToNumber::TerminationReason->Integer
terminationReasonToNumber DisconnectRequested = 0x00
terminationReasonToNumber TCPSubSystemError = 0x01
terminationReasonToNumber BreachOfProtocol = 0x02
terminationReasonToNumber UselessPeer = 0x03
terminationReasonToNumber TooManyPeers = 0x04
terminationReasonToNumber AlreadyConnected = 0x05
terminationReasonToNumber IncompatibleP2PProtocolVersion = 0x06
terminationReasonToNumber NullNodeIdentityReceived = 0x07
terminationReasonToNumber ClientQuitting = 0x08
terminationReasonToNumber UnexpectedIdentity = 0x09
terminationReasonToNumber ConnectedToSelf = 0x0a
terminationReasonToNumber PingTimeout = 0x0b
terminationReasonToNumber OtherSubprotocolReason = 0x0c
  


data Message =
  Hello { version::Int, clientId::String, capability::[Capability], port::Int, nodeId::Word512 } |
  Disconnect TerminationReason |
  Ping |
  Pong |
  GetPeers |
  Peers [Peer] |
  Status { protocolVersion::Int, networkID::String, totalDifficulty::Int, latestHash::SHA, genesisHash:: SHA } |
  QqqqStatus Int |
  Transactions [SignedTransaction] | 
  GetBlocks [SHA] |
  Blocks [Block] |
  BlockHashes [SHA] |
  GetBlockHashes { parentSHAs::[SHA], numChildItems::Integer } |
  GetTransactions |
  NewBlockPacket Block Integer |
  PacketCount Integer |
  QqqqPacket |
  WhisperProtocolVersion Int deriving (Show)

instance Format Message where
  format Hello{version=ver, clientId=c, capability=cap, port=p, nodeId=n} =
    CL.blue "Hello" ++
      "    version: " ++ show ver ++ "\n" ++
      "    cliendId: " ++ show c ++ "\n" ++
      "    capability: " ++ intercalate ", " (show <$> cap) ++ "\n" ++
      "    port: " ++ show p ++ "\n" ++
      "    nodeId: " ++ take 20 (padZeros 64 (showHex n "")) ++ "...."
  format (Disconnect reason) = CL.blue "Disconnect" ++ "(" ++ show reason ++ ")"
  format Ping = CL.blue "Ping"
  format Pong = CL.blue "Pong"
  format GetPeers = CL.blue "GetPeers"
  format (Peers peers) = CL.blue "Peers: " ++ intercalate ", " (format <$> peers)
  format Status{ protocolVersion=ver, networkID=nID, totalDifficulty=d, latestHash=lh, genesisHash=gh } =
    CL.blue "Status" ++
      "    protocolVersion: " ++ show ver ++ "\n" ++
      "    networkID: " ++ show nID ++ "\n" ++
      "    totalDifficulty: " ++ show d ++ "\n" ++
      "    latestHash: " ++ show (pretty lh) ++ "\n" ++
      "    genesisHash: " ++ show (pretty gh)
  format (QqqqStatus ver) =
    CL.blue "QqqqStatus " ++
      "    protocolVersion: " ++ show ver
  format (Transactions transactions) =
    CL.blue "Transactions:\n    " ++ tab (intercalate "\n    " (format <$> transactions))
    
--Short version
  format (BlockHashes shas) =
    CL.blue "BlockHashes " ++ "(" ++ show (length shas) ++ " new hashes)" 
--Long version
{-  format (BlockHashes shas) =
    CL.blue "BlockHashes:" ++  
   tab ("\n" ++ intercalate "\n    " (show . pretty <$> shas))-}

  format (GetBlocks shas) =
    CL.blue "GetBlocks:" ++ 
    tab ("\n" ++ intercalate "\n    " (show . pretty <$> shas))
  format (Blocks blocks) = CL.blue "Blocks:" ++ tab("\n" ++ intercalate "\n    " (format <$> blocks))
  format (GetBlockHashes pSHAs numChild) =
    CL.blue "GetBlockHashes" ++ " (max: " ++ show numChild ++ "):\n    " ++
    intercalate ",\n    " (show . pretty <$> pSHAs)
  format (NewBlockPacket block d) = CL.blue "NewBlockPacket" ++ " (" ++ show d ++ ")" ++ tab ("\n" ++ format block)
  format (PacketCount c) =
    CL.blue "PacketCount:" ++ show c
  format QqqqPacket = CL.blue "QqqqPacket"
  format GetTransactions = CL.blue "GetTransactions"
  format (WhisperProtocolVersion ver) = CL.blue "WhisperProtocolVersion " ++ show ver


obj2WireMessage::RLPObject->Message
obj2WireMessage (RLPArray [RLPString "", ver, cId, RLPArray cap, p, nId]) =
  Hello (fromInteger $ rlpDecode ver) (rlpDecode cId) (rlpDecode <$> cap) (fromInteger $ rlpDecode p) $ rlp2Word512 nId
obj2WireMessage (RLPArray [RLPScalar 0x01, reason]) =
  Disconnect (numberToTerminationReason $ rlpDecode reason)
obj2WireMessage (RLPArray [RLPScalar 0x02]) = Ping
obj2WireMessage (RLPArray [RLPScalar 0x03]) = Pong
obj2WireMessage (RLPArray [RLPScalar 0x04]) = GetPeers
obj2WireMessage (RLPArray (RLPScalar 0x05:peers)) = Peers $ rlpDecode <$> peers
obj2WireMessage (RLPArray [RLPScalar 0x10, ver, nID, d, lh, gh]) = 
    Status {
  protocolVersion=fromInteger $ rlpDecode ver,
  networkID = rlpDecode nID,
  totalDifficulty = fromInteger $ rlpDecode d,
  latestHash=rlpDecode lh,
  genesisHash=rlpDecode gh
}
obj2WireMessage (RLPArray [RLPScalar 0x10, ver]) = 
    QqqqStatus $ fromInteger $ rlpDecode ver

obj2WireMessage (RLPArray [RLPScalar 0x11]) = GetTransactions
obj2WireMessage (RLPArray (RLPScalar 0x12:transactions)) =
  Transactions $ rlpDecode <$> transactions


obj2WireMessage (RLPArray (RLPScalar 0x13:items)) =
  GetBlockHashes (rlpDecode <$> init items) $ rlpDecode $ last items
obj2WireMessage (RLPArray (RLPScalar 0x14:items)) =
  BlockHashes $ rlpDecode <$> items


obj2WireMessage (RLPArray (RLPScalar 0x15:items)) =
  GetBlocks $ rlpDecode <$> items
obj2WireMessage (RLPArray (RLPScalar 0x16:blocks)) =
  Blocks $ rlpDecode <$> blocks
obj2WireMessage (RLPArray [RLPScalar 0x17, block, td]) =
  NewBlockPacket (rlpDecode block) (rlpDecode td)
obj2WireMessage (RLPArray [RLPScalar 0x18, c]) =
  PacketCount $ rlpDecode c
obj2WireMessage (RLPArray [RLPScalar 0x19]) =
  QqqqPacket

obj2WireMessage (RLPArray [RLPScalar 0x20, ver]) =
  WhisperProtocolVersion $ fromInteger $ rlpDecode ver

obj2WireMessage x = error ("Missing case in obj2WireMessage: " ++ show (pretty x))






wireMessage2Obj::Message->RLPObject
wireMessage2Obj Hello { version = ver,
                        clientId = cId,
                        capability = cap,
                        port = p,
                        nodeId = nId } =
  RLPArray [
    RLPString [],
    rlpEncode $ toInteger ver,
    rlpEncode cId,
    RLPArray $ rlpEncode <$> cap,
    rlpEncode $ toInteger p,
    word5122RLP nId
    ]
wireMessage2Obj (Disconnect reason) =
  RLPArray [RLPScalar 0x1, rlpEncode $ terminationReasonToNumber reason]
wireMessage2Obj Ping = RLPArray [RLPScalar 0x2]
wireMessage2Obj Pong = RLPArray [RLPScalar 0x3]
wireMessage2Obj GetPeers = RLPArray [RLPScalar 0x4]
wireMessage2Obj (Peers peers) = RLPArray $ RLPScalar 0x5:(rlpEncode <$> peers)

wireMessage2Obj (Status ver nID d lh gh) =
    RLPArray [RLPScalar 0x10, rlpEncode $ toInteger ver, rlpEncode nID, rlpEncode $ toInteger d, rlpEncode lh, rlpEncode gh]
wireMessage2Obj (QqqqStatus ver) =
    RLPArray [RLPScalar 0x10, rlpEncode $ toInteger ver]
wireMessage2Obj GetTransactions = RLPArray [RLPScalar 0x11]
wireMessage2Obj (Transactions transactions) =
  RLPArray (RLPScalar 0x12:(rlpEncode <$> transactions))
wireMessage2Obj (GetBlockHashes pSHAs numChildren) = 
  RLPArray $ [RLPScalar 0x13] ++
  (rlpEncode <$> pSHAs) ++
  [rlpEncode numChildren]
wireMessage2Obj (BlockHashes shas) = 
  RLPArray $ RLPScalar 0x14:(rlpEncode <$> shas)
wireMessage2Obj (GetBlocks shas) = 
  RLPArray $ RLPScalar 0x15:(rlpEncode <$> shas)
wireMessage2Obj (Blocks blocks) =
  RLPArray (RLPScalar 0x16:(rlpEncode <$> blocks))
wireMessage2Obj (NewBlockPacket block d) =
  RLPArray [RLPScalar 0x17, rlpEncode block, rlpEncode d]
wireMessage2Obj (PacketCount c) =
  RLPArray [RLPScalar 0x18, rlpEncode c]
wireMessage2Obj QqqqPacket =
  RLPArray [RLPScalar 0x19]

wireMessage2Obj (WhisperProtocolVersion ver) = 
  RLPArray [RLPScalar 0x20, rlpEncode $ toInteger ver]