packages feed

ethereum-client-haskell-0.0.1: src/Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main (
  main
  ) where

import Control.Monad.IO.Class
import Control.Monad.State
import Control.Monad.Trans.Resource
import Data.Bits
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
--import Data.Functor
import Data.Time.Clock
import Data.Word
import Network.Haskoin.Crypto hiding (Address)
import Network.Socket (socketToHandle)
--import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))
import System.Entropy
import System.Environment
import System.IO

import Network.Simple.TCP

import Blockchain.Data.RLP

import Blockchain.BlockChain
import Blockchain.BlockSynchronizer
import Blockchain.Communication
import Blockchain.Constants
import Blockchain.Context
import Blockchain.Data.Address
--import Data.AddressState
import Blockchain.Data.Block
--import Data.SignedTransaction
--import Data.Transaction
import Blockchain.Data.Wire
import Blockchain.Database.MerklePatricia
import Blockchain.Display
import Blockchain.DB.ModifyStateDB
import Blockchain.PeerUrls
--import SampleTransactions
import Blockchain.SHA
import Blockchain.Util

--import Debug.Trace


prvKey::PrvKey
Just prvKey = makePrvKey 0xac3e8ce2ef31c3f45d5da860bcd9aee4b37a05c5a3ddee40dd061620c3dab380
--Just prvKey = makePrvKey 0xd69bceff85f3bc2d0a13bcc43b7caf6bd54a21ad0c1997ae623739216710ca19 --cpp client prvKey
--6ccf6b5c33ae2017a6c76b8791ca61276a69ab8e --cpp coinbase



getNextBlock::Block->UTCTime->ContextM Block
getNextBlock b ts = do
  let theCoinbase = prvKey2Address prvKey
  setStateRoot $ bStateRoot bd
  addToBalance theCoinbase (1500*finney)

  ctx <- get

  return Block{
               blockData=testGetNextBlockData $ stateRoot $ stateDB ctx,
               receiptTransactions=[],
               blockUncles=[]
             }
  where
    testGetNextBlockData::SHAPtr->BlockData
    testGetNextBlockData sr =
      BlockData {
        parentHash=blockHash b,
        unclesHash=hash $ B.pack [0xc0],
        coinbase=prvKey2Address prvKey,
        bStateRoot = sr,
        transactionsRoot = emptyTriePtr,
        receiptsRoot = emptyTriePtr,
        logBloom = B.pack [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],         
        difficulty = nextDifficulty (difficulty bd) (timestamp bd) ts,
        number = number bd + 1,
        gasLimit = max 125000 ((gasLimit bd * 1023 + gasUsed bd *6 `quot` 5) `quot` 1024),
        gasUsed = 0,
        timestamp = ts,  
        extraData = 0,
        nonce = SHA 5
        }
    bd = blockData b


submitNextBlock::Socket->Integer->Block->ContextM ()
submitNextBlock socket baseDifficulty b = do
        ts <- liftIO getCurrentTime
        newBlock <- getNextBlock b ts
        n <- liftIO $ fastFindNonce newBlock

        --let theBytes = headerHashWithoutNonce newBlock `B.append` B.pack (integer2Bytes n)
        let theNewBlock = addNonceToBlock newBlock n
        sendMessage socket $ NewBlockPacket theNewBlock (baseDifficulty + difficulty (blockData theNewBlock))
        addBlocks [theNewBlock]

ifBlockInDBSubmitNextBlock::Socket->Integer->Block->ContextM ()
ifBlockInDBSubmitNextBlock socket baseDifficulty b = do
  maybeBlock <- getBlock (blockHash b)
  case maybeBlock of
    Nothing -> return ()
    _ -> submitNextBlock socket baseDifficulty b



handlePayload::Socket->B.ByteString->ContextM ()
handlePayload socket payload = do
  --liftIO $ print $ payload
  --liftIO $ putStrLn $ show $ pretty $ rlpDeserialize payload
  let rlpObject = rlpDeserialize payload
  let msg = obj2WireMessage rlpObject
  displayMessage False msg
  case msg of
    Hello{} -> do
             bestBlock <- getBestBlock
             genesisBlockHash <- getGenesisBlockHash
             sendMessage socket Status{protocolVersion=fromIntegral ethVersion, networkID="", totalDifficulty=0, latestHash=blockHash bestBlock, genesisHash=genesisBlockHash}
    Ping -> do
      addPingCount
      sendMessage socket Pong
    GetPeers -> do
      sendMessage socket $ Peers []
      sendMessage socket GetPeers
    (Peers thePeers) -> do
      setPeers thePeers
    BlockHashes blockHashes -> handleNewBlockHashes socket blockHashes
    Blocks blocks -> do
      handleNewBlocks socket blocks
    NewBlockPacket block baseDifficulty -> do
      addBlocks [block]
      ifBlockInDBSubmitNextBlock socket baseDifficulty block

    Status{latestHash=lh} ->
        handleNewBlockHashes socket [lh]
    GetTransactions -> do
      sendMessage socket $ Transactions []
      --liftIO $ sendMessage socket GetTransactions
      return ()
      
    _-> return ()

getPayloads::[Word8]->[[Word8]]
getPayloads [] = []
getPayloads (0x22:0x40:0x08:0x91:s1:s2:s3:s4:remainder) =
  take payloadLength remainder:getPayloads (drop payloadLength remainder)
  where
    payloadLength = shift (fromIntegral s1) 24 + shift (fromIntegral s2) 16 + shift (fromIntegral s3) 8 + fromIntegral s4
getPayloads _ = error "Malformed data sent to getPayloads"

readAndOutput::Socket->ContextM ()
readAndOutput socket = do
  h <- liftIO $ socketToHandle socket ReadWriteMode
  payloads <- liftIO $ BL.hGetContents h
  handleAllPayloads $ getPayloads $ BL.unpack payloads
  where
    handleAllPayloads [] = error "Server has closed the connection"
    handleAllPayloads (pl:rest) = do
      handlePayload socket $ B.pack pl
      handleAllPayloads rest

mkHello::IO Message
mkHello = do
  peerId <- getEntropy 64
  return Hello {
               version = fromIntegral shhVersion,
               clientId = "Ethereum(G)/v0.6.4//linux/Haskell",
               capability = [ETH ethVersion, SHH shhVersion],
               port = 30303,
               nodeId = fromIntegral $ byteString2Integer peerId
             }

{-
createTransaction::Transaction->ContextM SignedTransaction
createTransaction t = do
    userNonce <- addressStateNonce <$> getAddressState (prvKey2Address prvKey)
    liftIO $ withSource devURandom $ signTransaction prvKey t{tNonce=userNonce}

createTransactions::[Transaction]->ContextM [SignedTransaction]
createTransactions transactions = do
    userNonce <- addressStateNonce <$> getAddressState (prvKey2Address prvKey)
    forM (zip transactions [userNonce..]) $ \(t, n) -> do
      liftIO $ withSource devURandom $ signTransaction prvKey t{tNonce=n}
-}

doit::Socket->ContextM ()
doit socket = do
  sendMessage socket =<< (liftIO mkHello)
  (setStateRoot . bStateRoot . blockData) =<< getBestBlock
  --signedTx <- createTransaction simpleTX
  --signedTx <- createTransaction outOfGasTX
  --signedTx <- createTransaction simpleStorageTX
  --signedTx <- createTransaction createContractTX
  --signedTx <- createTransaction sendMessageTX

  --signedTx <- createTransaction createContractTX
  --signedTx <- createTransaction paymentContract
  --signedTx <- createTransaction sendCoinTX
  --signedTx <- createTransaction keyValuePublisher
  --signedTx <- createTransaction sendKeyVal

  --liftIO $ print $ whoSignedThisTransaction signedTx

                
  --liftIO $ sendMessage socket $ Transactions [signedTx]


  --signedTxs <- createTransactions [createMysteryContract]
  --liftIO $ sendMessage socket $ Transactions signedTxs


  readAndOutput socket

main::IO ()    
main = do

  args <- getArgs

  let ipNum =
          case args of
            (arg:_) -> arg
            [] -> "119" --Just default to the one I know works right now....  Yup, this is kind of dumb.

  let (ipAddress, port') = ipAddresses !! read ipNum

  connect ipAddress port' $ \(socket, _) -> do
    putStrLn "Connected"

    runResourceT $ do
      cxt <- openDBs "h"
      _ <- liftIO $ runStateT (doit socket) cxt
      return ()