Skip to content

Commit

Permalink
Hide event server behind config
Browse files Browse the repository at this point in the history
  • Loading branch information
arcz committed Jan 4, 2024
1 parent a8f1df8 commit 7a31c8f
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 18 deletions.
1 change: 1 addition & 0 deletions lib/Echidna/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ instance FromJSON EConfigWithUsage where
<*> v ..:? "mutConsts" ..!= defaultMutationConsts
<*> v ..:? "coverageFormats" ..!= [Txt,Html,Lcov]
<*> v ..:? "workers"
<*> v ..:? "server"

solConfParser = SolConf
<$> v ..:? "contractAddr" ..!= defaultContractAddr
Expand Down
17 changes: 8 additions & 9 deletions lib/Echidna/SSE.hs → lib/Echidna/Server.hs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
module Echidna.SSE where
module Echidna.Server where

import Control.Concurrent
import Control.Monad (when)
import Control.Monad (when, void)
import Data.Aeson
import Data.Binary.Builder (fromLazyByteString)
import Data.IORef
import Data.Time (LocalTime)
import Data.Word (Word16)
import Network.Wai.EventSource (ServerEvent(..), eventSourceAppIO)
import Network.Wai.Handler.Warp (run)

Expand All @@ -21,10 +22,9 @@ instance ToJSON SSE where
, "data" .= event
]

runSSEServer :: Env -> Int -> IO (MVar ())
runSSEServer env nworkers = do
runSSEServer :: MVar () -> Env -> Word16 -> Int -> IO ()
runSSEServer serverStopVar env port nworkers = do
aliveRef <- newIORef nworkers
sseFinished <- newEmptyMVar
sseChan <- dupChan env.eventQueue

let sseListener = do
Expand All @@ -42,14 +42,13 @@ runSSEServer env nworkers = do
case campaignEvent of
WorkerStopped _ -> do
aliveAfter <- atomicModifyIORef' aliveRef (\n -> (n-1, n-1))
when (aliveAfter == 0) $ putMVar sseFinished ()
when (aliveAfter == 0) $ putMVar serverStopVar ()
_ -> pure ()
pure $ ServerEvent
{ eventName = Just (eventName campaignEvent)
, eventId = Nothing
, eventData = [ fromLazyByteString $ encode (SSE event) ]
}

_serverTid <- forkIO $ do
run 3413 $ eventSourceAppIO sseListener
pure sseFinished
void . forkIO $ do
run (fromIntegral port) $ eventSourceAppIO sseListener
5 changes: 4 additions & 1 deletion lib/Echidna/Types/Campaign.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Data.Aeson
import Data.Map (Map)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Word (Word8)
import Data.Word (Word8, Word16)

import Echidna.ABI (GenDict, emptyDict, encodeSig)
import Echidna.Output.Source (CoverageFileType)
Expand Down Expand Up @@ -40,6 +40,9 @@ data CampaignConf = CampaignConf
, coverageFormats :: [CoverageFileType]
-- ^ List of file formats to save coverage reports
, workers :: Maybe Word8
-- ^ Number of fuzzing workers
, serverPort :: Maybe Word16
-- ^ Server-Sent Events HTTP port number, if missing server is not ran
}

data CampaignEvent
Expand Down
18 changes: 11 additions & 7 deletions lib/Echidna/UI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import Data.Binary.Builder
import Data.ByteString.Lazy qualified as BS
import Data.List.Split (chunksOf)
import Data.Map (Map)
import Data.Maybe (fromMaybe)
import Data.Maybe (fromMaybe, isJust)
import Data.Time
import UnliftIO
( MonadUnliftIO, newIORef, readIORef, hFlush, stdout , writeIORef, timeout)
Expand All @@ -36,7 +36,7 @@ import EVM.Types (Addr, Contract, VM, W256)
import Echidna.ABI
import Echidna.Campaign (runWorker)
import Echidna.Output.JSON qualified
import Echidna.SSE (runSSEServer)
import Echidna.Server (runSSEServer)
import Echidna.Types.Campaign
import Echidna.Types.Config
import Echidna.Types.Corpus (corpusSize)
Expand Down Expand Up @@ -158,10 +158,11 @@ ui vm world dict initialCorpus = do
#endif

NonInteractive outputFormat -> do
serverStopVar <- newEmptyMVar
#ifdef INTERACTIVE_UI
-- Handles ctrl-c, TODO: this doesn't work on Windows
liftIO $ forM_ [sigINT, sigTERM] $ \sig ->
installHandler sig (Catch $ stopWorkers workers) Nothing
installHandler sig (Catch $ stopWorkers workers >> putMVar serverStopVar ()) Nothing
#endif
let forwardEvent = putStrLn . ppLogLine
liftIO $ spawnListener env forwardEvent nworkers listenerStopVar
Expand All @@ -173,7 +174,9 @@ ui vm world dict initialCorpus = do
putStrLn $ time <> "[status] " <> line
hFlush stdout

sseFinished <- liftIO $ runSSEServer env nworkers
case conf.campaignConf.serverPort of
Just port -> liftIO $ runSSEServer serverStopVar env port nworkers
Nothing -> pure ()

ticker <- liftIO . forkIO . forever $ do
threadDelay 3_000_000 -- 3 seconds
Expand All @@ -187,9 +190,10 @@ ui vm world dict initialCorpus = do
-- print final status regardless the last scheduled update
liftIO printStatus

-- wait until we send all SSE events
liftIO $ putStrLn "Waiting until all SSE are received..."
readMVar sseFinished
when (isJust conf.campaignConf.serverPort) $ do
-- wait until we send all SSE events
liftIO $ putStrLn "Waiting until all SSE are received..."
readMVar serverStopVar

states <- liftIO $ workerStates workers

Expand Down
7 changes: 6 additions & 1 deletion src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import Data.Text (Text)
import Data.Time.Clock.System (getSystemTime, systemSeconds)
import Data.Vector qualified as Vector
import Data.Version (showVersion)
import Data.Word (Word8)
import Data.Word (Word8, Word16)
import Main.Utf8 (withUtf8)
import Options.Applicative
import Paths_echidna (version)
Expand Down Expand Up @@ -225,6 +225,7 @@ readFileIfExists path = do
data Options = Options
{ cliFilePath :: NE.NonEmpty FilePath
, cliWorkers :: Maybe Word8
, cliServerPort :: Maybe Word16
, cliSelectedContract :: Maybe Text
, cliConfigFilepath :: Maybe FilePath
, cliOutputFormat :: Maybe OutputFormat
Expand Down Expand Up @@ -255,6 +256,9 @@ options = Options
<*> optional (option auto $ long "workers"
<> metavar "N"
<> help "Number of workers to run")
<*> optional (option auto $ long "server"
<> metavar "PORT"
<> help "Run events server on the given port")
<*> optional (option str $ long "contract"
<> metavar "CONTRACT"
<> help "Contract to analyze")
Expand Down Expand Up @@ -339,6 +343,7 @@ overrideConfig config Options{..} = do
, seqLen = fromMaybe campaignConf.seqLen cliSeqLen
, seed = cliSeed <|> campaignConf.seed
, workers = cliWorkers <|> campaignConf.workers
, serverPort = cliServerPort <|> campaignConf.serverPort
}

overrideSolConf solConf = solConf
Expand Down
1 change: 1 addition & 0 deletions src/test/Tests/Seed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ seedTests =
, mutConsts = defaultMutationConsts
, coverageFormats = [Txt,Html,Lcov]
, workers = Nothing
, serverPort = Nothing
}
}
& overrideQuiet
Expand Down
2 changes: 2 additions & 0 deletions tests/solidity/basic/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,5 @@ rpcUrl: null
rpcBlock: null
# number of workers
workers: 1
# events server port
server: null

0 comments on commit 7a31c8f

Please sign in to comment.