This module provides tools to limit the resource usage of transactions and contract calls. Currently it just has a timeout function (and a particularly inefficient one at that, due to a limitation of GHC's runtime) but it could have, in addition, at least a memory limit as well.
GHC has an actual bug in its concurrency that non-allocating
5 | evaluations can never be pre-empted, so the stock timeout function
6 | from System.Timeout does not work on completely general values. This
7 | implementation is much, much slower because it forks a process rather
8 | than sparking a thread, but it does not have that particular
9 | limitation.
The timeout is in half-milliseconds because the fork takes that order of
10 | magnitude of time, and in fact, it is incurred twice because (in order
11 | to avoid having to communicate the value between the processes) the
12 | evaluation happens twice: once to check that it terminates, and once to
13 | get the actual value.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Ryan Reich (c) 2017
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above
12 | copyright notice, this list of conditions and the following
13 | disclaimer in the documentation and/or other materials provided
14 | with the distribution.
15 |
16 | * Neither the name of Ryan Reich nor the names of other
17 | contributors may be used to endorse or promote products derived
18 | from this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fae, a functional smart contract system
2 |
3 | Fae is a smart contract system and blockchain definition with equivalent power
4 | to Ethereum but with the following additional benefits:
5 |
6 | - Transaction execution is parallelizable and each network node need only
7 | execute transactions it wants.
8 |
9 | - There is no virtual machine, but rather a minimal execution environment that
10 | can run an existing, general-purpose programming language.
11 |
12 | - There is no native currency, only a simple token awarded to the participants
13 | that mine blocks.
14 |
15 | - Scarcity policy is left to the discretion of each smart contract.
16 |
17 | We provide an [implementation](src/) of this system in Haskell, for which we have included the [haddocks](https://consensys.github.io/Fae/). Prose documentation for various design aspects of Fae can be found in the [documentation](documentation) directory as HTML files. As of version 2.0.0.0, there are [docker images](https://cloud.docker.com/u/teamfae/repository/list) for the two executables `faeServer` and `postTX` that comprise a playground environment.
18 |
19 | For the curious, the name "Fae" is inspired by an element of the setting of Patrick Rothfuss' *The Kingkiller Chronicles*: an otherworldly realm that periodically verges on the natural world. This and other metaphorical parallels suggest the relationship between Fae and the physical world, or Fae and Ethereum. A more direct comparison lies in the acronym "Fae: functional alternative to Ethereum".
20 |
21 |
--------------------------------------------------------------------------------
/bin/FaeServer.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: FaeServer
3 | Description: Simple HTTP-based server for Fae transactions
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | This program, which rises to slightly above the level of a utility, is
10 | a complete Fae node, excepting the part of that job that involves the
11 | blockchain. It exposes its functionality via a regular HTTP
12 | query-parameter interface (it would be misleading to call it a REST
13 | protocol) and returns the results, mostly, as JSON, both of which are
14 | hopefully standard enough to be interoperable with other things.
15 | -}
16 |
17 | import Control.Concurrent.Lifted
18 | import Control.Concurrent.STM
19 |
20 | import Control.Monad
21 | import Control.Monad.Reader
22 |
23 | import Data.List
24 | import Data.Maybe
25 | import Data.Proxy
26 |
27 | import FaeServer.App
28 | import FaeServer.Args
29 | import FaeServer.Concurrency
30 | import FaeServer.Fae
31 | import FaeServer.Faeth
32 | import FaeServer.Git
33 |
34 | import System.Directory
35 | import System.Environment
36 | import System.Exit
37 | import System.FilePath
38 |
39 | main :: IO ()
40 | main = do
41 | userHome <- getHomeDirectory
42 | faeHome <- fromMaybe (userHome > "fae") <$> lookupEnv "FAE_HOME"
43 | createDirectoryIfMissing True faeHome
44 | setCurrentDirectory faeHome
45 | setEnv "GIT_DIR" "git"
46 |
47 | tID <- myThreadId
48 | txQueue <- atomically newTQueue
49 | args <- parseArgs <$> getArgs
50 |
51 | flip runReaderT txQueue $ case args of
52 | ArgsServer args@ServerArgs{..} -> do
53 | let portDir = "port-" ++ show faePort
54 | liftIO $ do
55 | createDirectoryIfMissing False portDir
56 | setCurrentDirectory portDir
57 | void $ fork $ runFae tID args
58 | void $ fork $ runServer importExportPort importExportApp queueTXExecData
59 | case serverMode of
60 | FaeMode -> runServer faePort (serverApp $ Proxy @String) queueTXExecData
61 | FaethMode -> runFaeth args tID
62 | (ArgsUsage xs) -> liftIO $ case xs of
63 | [] -> do
64 | usage
65 | exitSuccess
66 | xs -> do
67 | putStrLn $ "Unrecognized option(s): " ++ intercalate ", " xs
68 | usage
69 | exitFailure
70 |
71 | usage :: IO ()
72 | usage = do
73 | self <- getProgName
74 | putStrLn $ unlines
75 | [
76 | "Usage: (standalone) " ++ self ++ " options",
77 | " (with stack) stack exec " ++ self ++ " -- options",
78 | "",
79 | "where the available options are:",
80 | " --help Print this message",
81 | " --normal-mode Operate as standalone Fae",
82 | " --faeth-mode Synonym for --faeth",
83 | " --faeth Receive transactions via Ethereum from",
84 | " a Parity client",
85 | " --faeth-hostname=string Connect to Parity at a given hostname",
86 | " (default: 127.0.0.1)",
87 | " --faeth-port=number Connect to Parity at a given port",
88 | " (default: 8546)",
89 | " --fae-port=number Listen on a given port for normal-mode",
90 | " requests (default: 27182)",
91 | " --import-export-port=number, Listen on a given port for import/export",
92 | " requests (default: 27183)",
93 | " --new-session Deletes previous transaction history",
94 | " --resume-session Reloads previous transaction history",
95 | " --eval-timeout Maximum contract or transaction call",
96 | " duration, in milliseconds",
97 | "",
98 | "Later options shadow earlier ones when they have the same domain.",
99 | "The Fae server listens on port 'fae-port' and accepts import/export data",
100 | "on 'import-export-port'.",
101 | "",
102 | "Recognized environment variables:",
103 | " FAE_HOME Directory where transaction modules and history are stored",
104 | "",
105 | "In normal-mode, it receives requests having the following parameters:",
106 | " parent: the transaction ID that this one should immediately follow",
107 | " view: just recall an existing transaction result",
108 | " lazy: don't evaluate the transaction result, just print a generic message",
109 | " fake: don't save the transaction in the history, just run it once",
110 | " reward: take this to be a 'reward transaction' getting a Reward argument",
111 | "and expecting, except if 'view = True', the following file parameters",
112 | "each one expected to have the filename matching its module name:",
113 | " message: the binary serialization of the 'TXMessage'",
114 | " body: the module defining 'body :: a -> b -> ... -> FaeTX c'",
115 | " other: each time it appears, contains another module that is part of the transaction",
116 | "In Faeth mode, these are also accepted, but 'fake' is forced to",
117 | "'True'; live transactions are only accepted through the Ethereum",
118 | "blockchain.",
119 | "",
120 | "The import/export server accepts requests of the form:",
121 | " for exporting:",
122 | " parent: as for normal mode",
123 | " export: a pair (transaction ID, index in its input list) to export",
124 | " for importing:",
125 | " parent: as for normal mode",
126 | " import: a quadruple (cID, status, names, type) where:",
127 | " cID = the contract ID to import, with an explicit version",
128 | " status = the Status of the call to the cID",
129 | " names = list of module names required to express the contract's ContractName",
130 | " type = the ContractName itself",
131 | " valuePackage: a file containing the binary serialization of the",
132 | " value to be imported"
133 | ]
134 |
--------------------------------------------------------------------------------
/bin/FaeServer/Args.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: FaeServer.Args
3 | Description: Handles command-line arguments for faeServer
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | This very simple, ad-hoc command line parser is just explicit
10 | pattern-matching the flag strings; fortunately, we do not accept very
11 | complex flags, so this is appropriate and understandable.
12 | -}
13 |
14 | {-# LANGUAGE TemplateHaskell #-}
15 | module FaeServer.Args where
16 |
17 | import Common.Lens
18 | import Data.List
19 | import Data.Maybe
20 | import Text.Read
21 |
22 | -- | faeServer is either actually running a server of some kind, or
23 | -- printing the help text.
24 | data Args =
25 | ArgsServer ServerArgs |
26 | -- | The list contains unrecognized arguments.
27 | ArgsUsage [String]
28 |
29 | -- | This collection of data is passed around and various parts used all
30 | -- over, though not all in any one place. This kind of suggests that maybe
31 | -- just the one constructor is not appropriate, but it is not yet worth
32 | -- changing.
33 | data ServerArgs =
34 | ServerArgs
35 | {
36 | serverMode :: ServerMode,
37 | newSession :: Bool,
38 | evalTimeout :: Int, -- ^ microseconds, 0 is disabled
39 | faePort :: Int,
40 | importExportPort :: Int,
41 | faethHostname :: String,
42 | faethPort :: Int
43 | }
44 |
45 | -- | Either accepts transactions over HTTP, or from Parity via JSON-RPC.
46 | data ServerMode = FaeMode | FaethMode
47 |
48 | -- | This uses one of the several synonyms for @localhost@, chosen to
49 | -- prevent Parity from blocking the connection as unsafe.
50 | defaultFaethHost :: String
51 | defaultFaethHost = "127.0.0.1"
52 |
53 | -- | This is not our choice; Parity defaults to 8546.
54 | defaultFaethPort :: Int
55 | defaultFaethPort = 8546
56 |
57 | makeLenses ''Args
58 | makePrisms ''Args
59 | makeLenses ''ServerArgs
60 |
61 | -- | Fills in the structure by folding the argument list. Two comments on
62 | -- the defaults: first, the 'newSession' field defaults to 'False' now,
63 | -- whereas it used to be 'True', which will wipe out unsuspecting users'
64 | -- histories; and second, the default port of 27182 is chosen to be the
65 | -- initial decimal digits of the number e.
66 | parseArgs :: [String] -> Args
67 | parseArgs = foldl addArg $
68 | ArgsServer ServerArgs
69 | {
70 | serverMode = FaeMode,
71 | newSession = False,
72 | evalTimeout = 1000,
73 | faePort = 27182,
74 | importExportPort = 27183,
75 | faethHostname = defaultFaethHost,
76 | faethPort = defaultFaethPort
77 | }
78 |
79 | -- | Adds to 'ArgsServer' until a bad argument occurs, then starts
80 | -- accumulating those in 'ArgsUsage'.
81 | addArg :: Args -> String -> Args
82 | addArg args x = getArgAction x & case args of
83 | ArgsServer{} -> maybe (ArgsUsage [x]) ($ args)
84 | (ArgsUsage xs) -> maybe (ArgsUsage $ x : xs) (const args)
85 |
86 | -- | There aren't really any conflicts between flags, so this just sets the
87 | -- appropriate fields for each one.
88 | getArgAction :: String -> Maybe (Args -> Args)
89 | getArgAction = \case
90 | x | x == "--faeth" || x == "--faeth-mode" -> Just setFaeth
91 | | ("--faeth-hostname", '=' : hostnameArgument) <- break (== '=') x ->
92 | Just $
93 | (_ArgsServer . _faethHostname .~ hostnameArgument) .
94 | setFaeth
95 | | ("--faeth-port", '=' : portArgument) <- break (== '=') x ->
96 | let err = error $ "Could not read port argument: " ++ portArgument in
97 | Just $
98 | (_ArgsServer . _faethPort .~ readErr err portArgument) .
99 | setFaeth
100 | | ("--fae-port", '=' : faePortArg) <- break (== '=') x ->
101 | let err = error $ "Could not read port argument: " ++ faePortArg in
102 | Just $ _ArgsServer . _faePort .~ readErr err faePortArg
103 | | ("--import-export-port", '=' : importExportPortArg) <- break (== '=') x ->
104 | let err=error $ "Could not read port argument: " ++ importExportPortArg in
105 | Just $ _ArgsServer . _importExportPort .~ readErr err importExportPortArg
106 | | ("--eval-timeout", '=' : evalTimeoutArg) <- break (== '=') x ->
107 | let err = error $ "Could not read timeout argument: " ++ evalTimeoutArg in
108 | Just $ _ArgsServer . _evalTimeout .~ readErr err evalTimeoutArg
109 | "--normal-mode" -> Just $ _ArgsServer . _serverMode .~ FaeMode
110 | "--resume-session" -> Just $ _ArgsServer . _newSession .~ False
111 | "--new-session" -> Just $ _ArgsServer . _newSession .~ True
112 | "--help" -> Just $ const $ ArgsUsage []
113 | _ -> Nothing
114 |
115 | where
116 | readErr err = fromMaybe err . readMaybe
117 | setFaeth = _ArgsServer . _serverMode .~ FaethMode
118 |
--------------------------------------------------------------------------------
/bin/FaeServer/Concurrency.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: FaeServer.Concurrency
3 | Description: Concurrency-related constructs for faeServer
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | The concurrency aspects of @faeServer@ are all centered around the Fae
10 | interpreter, which runs in a dedicated thread that reads transactions (and
11 | other instructions) to execute from a queue defined here.
12 | -}
13 |
14 | module FaeServer.Concurrency where
15 |
16 | import Blockchain.Fae.FrontEnd
17 |
18 | import Common.ProtocolT
19 |
20 | import Control.Concurrent
21 | import Control.Concurrent.STM
22 |
23 | import Control.Monad.Cont
24 | import Control.Monad.Reader
25 | import Control.Monad.State
26 | import Control.Monad.Writer
27 |
28 | import Data.ByteString (ByteString)
29 | import qualified Data.ByteString.Char8 as C8
30 |
31 | import Data.Map (Map)
32 |
33 | import System.Exit
34 |
35 | -- | All the pieces of data that are required to execute a transaction in
36 | -- the dedicated interpreter thread.
37 | data TXExecData =
38 | TXExecData
39 | {
40 | mainFile :: Module,
41 | modules :: ModuleMap,
42 | parentM :: Maybe TransactionID,
43 | lazy :: Bool,
44 | fake :: Bool,
45 | tx :: TX,
46 | resultVar :: TMVar String,
47 | callerTID :: ThreadId
48 | } |
49 | View
50 | {
51 | viewTXID :: TransactionID,
52 | parentM :: Maybe TransactionID,
53 | resultVar :: TMVar String,
54 | callerTID :: ThreadId
55 | } |
56 | ExportValue
57 | {
58 | parentM :: Maybe TransactionID,
59 | calledInTX :: TransactionID,
60 | ixInTX :: Int,
61 | exportResultVar :: TMVar ExportData,
62 | callerTID :: ThreadId
63 | } |
64 | ImportValue
65 | {
66 | parentM :: Maybe TransactionID,
67 | exportData :: ExportData,
68 | signalVar :: TMVar (),
69 | callerTID :: ThreadId
70 | }
71 |
72 | -- | Communications channel with the interpreter thread
73 | type TXQueue = TQueue TXExecData
74 |
75 | -- | Context in which transactions can be placed for execution
76 | type TXQueueT = ReaderT TXQueue
77 |
78 | -- | An mtl-style class for things with a 'TXQueue'. Its instances are all
79 | -- trivially 'lift'ing in various monad transformers.
80 | class (MonadIO m) => TXQueueM m where
81 | liftTXQueueT :: TXQueueT IO a -> m a
82 |
83 | -- | -
84 | instance {-# OVERLAPPING #-} TXQueueM (TXQueueT IO) where
85 | liftTXQueueT = id
86 |
87 | -- | -
88 | instance (TXQueueM m) => TXQueueM (ReaderT r m) where
89 | liftTXQueueT = lift . liftTXQueueT
90 |
91 | -- | -
92 | instance (TXQueueM m, Monoid w) => TXQueueM (WriterT w m) where
93 | liftTXQueueT = lift . liftTXQueueT
94 |
95 | -- | -
96 | instance (TXQueueM m) => TXQueueM (StateT s m) where
97 | liftTXQueueT = lift . liftTXQueueT
98 |
99 | -- | -
100 | instance (TXQueueM m) => TXQueueM (ContT r m) where
101 | liftTXQueueT = lift . liftTXQueueT
102 |
103 | instance (TXQueueM m) => TXQueueM (FaeInterpretT m) where
104 | liftTXQueueT = lift . liftTXQueueT
105 |
106 | instance (TXQueueM m) => TXQueueM (ProtocolT m) where
107 | liftTXQueueT = ProtocolT . liftTXQueueT
108 |
109 | -- | Abstraction for "doing something with" a transaction
110 | type SendTXExecData m = TXExecData -> m ()
111 |
112 | -- | Blocks on the result variable after sending off the TX.
113 | waitRunTXExecData ::
114 | (TXQueueM m) =>
115 | SendTXExecData m -> (TXExecData -> TMVar a) -> TXExecData -> m a
116 | waitRunTXExecData sendOff tmVar txExecData = do
117 | sendOff txExecData
118 | ioAtomically $ takeTMVar $ tmVar txExecData
119 |
120 | -- | Sends the TX by simply placing it in the queue.
121 | queueTXExecData :: (TXQueueM m) => SendTXExecData m
122 | queueTXExecData txExecData = do
123 | txQueue <- liftTXQueueT ask
124 | ioAtomically $ writeTQueue txQueue txExecData
125 |
126 | -- | Checks the queue for a new item, blocking until it finds one.
127 | readTXExecData :: (TXQueueM m) => m TXExecData
128 | readTXExecData = do
129 | txQueue <- liftTXQueueT ask
130 | ioAtomically $ readTQueue txQueue
131 |
132 | -- | Handles exceptions in the mixed situation where /most/ of them have to
133 | -- go back to the calling thread (i.e. the one with the connection that
134 | -- created the present 'TXExecData' item), except the ones that would
135 | -- directly exit the process, which have to go to the main thread so that,
136 | -- e.g. Ctrl-C works on the terminal.
137 | reThrowExit :: (MonadIO m, MonadCatch m) => ThreadId -> ThreadId -> m () -> m ()
138 | reThrowExit mainTID callerTID =
139 | reThrow callerTID . handle (liftIO . throwTo @ExitCode mainTID)
140 |
141 | -- | Just bounces exceptions to a different thread. The interpreter thread
142 | -- must never terminate, and the victim is whichever Warp thread
143 | -- corresponds to the presently executing item.
144 | reThrow :: (MonadIO m, MonadCatch m) => ThreadId -> m () -> m ()
145 | reThrow tID = handleAll (liftIO . throwTo tID)
146 |
147 | -- | Though barely shorter than its definition, this does save having to
148 | -- reproduce the (trivial) particular logic each time.
149 | ioAtomically :: (MonadIO m) => STM a -> m a
150 | ioAtomically = liftIO . atomically
151 |
152 |
--------------------------------------------------------------------------------
/bin/FaeServer/Fae.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: FaeServer.Fae
3 | Description: The Fae interpreter in faeServer
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | This module is essentially the interpreter thread. It reads from the
10 | 'TXQueue' to which all the Warp threads write, updating an in-memory
11 | history with each new state or import, or reading from that history for
12 | viewing or export.
13 | -}
14 |
15 | module FaeServer.Fae where
16 |
17 | import Blockchain.Fae.FrontEnd
18 |
19 | import Common.JSON
20 |
21 | import Control.Concurrent
22 | import Control.Concurrent.STM
23 |
24 | import Control.Exception.Base
25 |
26 | import Control.Monad
27 | import Control.Monad.IO.Class
28 | import Control.Monad.State
29 | import Control.Monad.Trans.Cont
30 |
31 | import Data.ByteString (ByteString)
32 | import qualified Data.ByteString as B
33 | import qualified Data.Map as Map
34 | import Data.Maybe
35 | import Data.Serialize (Serialize)
36 | import qualified Data.Serialize as S
37 |
38 | import FaeServer.Args
39 | import FaeServer.Concurrency
40 | import FaeServer.Git
41 | import FaeServer.History
42 | import FaeServer.Modules
43 |
44 | import System.Directory
45 | import System.FilePath
46 | import System.IO
47 |
48 | -- | Not just an infinite event loop, this also handles initializing the
49 | -- storage from cached transactions when a new session was not requested,
50 | -- or clearing that cache when it was. It is guaranteed never to die from
51 | -- an exception (unless that exception is actually uncatchable), though any
52 | -- exception that is only caught here is bounced to the main thread and,
53 | -- thus, ends the program. Once in the event loop, only actual 'ExitCode'
54 | -- exceptions can terminate the program.
55 | runFae :: ThreadId -> ServerArgs -> TXQueueT IO ()
56 | runFae mainTID ServerArgs{..} = reThrow mainTID $ runFaeInterpretWithHistory $ do
57 | indexExists <- liftIO $ doesFileExist indexFileName
58 | if newSession || not indexExists
59 | then liftIO $ do
60 | removePathForcibly "Blockchain"
61 | removePathForcibly cacheDirName
62 | createDirectory cacheDirName
63 | gitInit
64 | -- Note that this case runs transactions but does not catch exceptions as
65 | -- the main loop does. This is because it runs them lazily, and so the
66 | -- exceptions are never thrown.
67 | else forM_TXCache $ \tx@TX{..} parentM -> do
68 | txCount <- innerRun tx parentM gitReset
69 | incrementHistory txID txCount
70 | liftIO $ putStrLn $
71 | "Replayed transaction " ++ show txID ++ " (#" ++ show txCount ++ ")"
72 | forever $ do
73 | txExecData <- readTXExecData
74 | reThrowExit mainTID (callerTID txExecData) $
75 | runTXExecData evalTimeout txExecData
76 |
77 | -- | Gives operational meaning to the various 'TXExecData' alternatives.
78 | runTXExecData ::
79 | (Typeable m, MonadIO m, MonadMask m) =>
80 | Int -> TXExecData -> FaeInterpretWithHistoryT m ()
81 | -- This, the normal case, surrounds actually running the transaction with
82 | -- a great deal of bookkeeping to build the longest chain and save and
83 | -- restore history from the correct point in time.
84 | runTXExecData evalTimeout TXExecData{tx=tx@TX{..}, ..} = do
85 | dup <- gets $ Map.member txID . txStorageAndCounts
86 | when dup $ throw $ ErrorCall $ "Duplicate transaction ID: " ++ show txID
87 |
88 | txCount <- innerRun tx parentM (writeModules mainFile modules)
89 | txResult <-
90 | if lazy
91 | then return $ "Transaction " ++ show txID ++ " (#" ++ show txCount ++ ")"
92 | else do
93 | txSummary <- lift . evalTimed evalTimeout $ collectTransaction txID
94 | -- Strict because I've seen a case where an exception is thrown so
95 | -- late that it isn't caught by the app and a response isn't even sent.
96 | return $! encodeJSON txSummary
97 | if fake
98 | then unless lazy $ liftIO gitClean
99 | else do
100 | incrementHistory txID txCount
101 | extendTXCache tx parentM
102 | liftIO $ gitCommit txID
103 | ioAtomically $ putTMVar resultVar txResult
104 |
105 | -- For a 'View', we just roll back the history and build the transaction
106 | -- summary.
107 | runTXExecData evalTimeout View{..} = do
108 | void $ recallHistory parentM
109 | txSummary <- lift . evalTimed evalTimeout $ collectTransaction viewTXID
110 | ioAtomically $ putTMVar resultVar (encodeJSON txSummary)
111 |
112 | -- 'ExportValue' is quite similar to 'View', except an 'ExportData' is
113 | -- built instead.
114 | runTXExecData _ ExportValue{..} = do
115 | void $ recallHistory parentM
116 | exportResult <- lift $ lift $ getExportedValue calledInTX ixInTX
117 | ioAtomically $ putTMVar exportResultVar exportResult
118 |
119 | -- 'ImportValue' is just the bookkeeping part of the 'TXExecData' case's
120 | -- operation, calling out to the interpreter to insert the imported value
121 | -- directly rather than running a new transaction.
122 | runTXExecData _ ImportValue{..} = do
123 | parentCount <- recallHistory parentM
124 | lift $ interpretImportedValue exportData
125 | updateHistory parentM parentCount
126 | ioAtomically $ putTMVar signalVar ()
127 |
128 | -- | The interpreter is actually run here; this appears both in the initial
129 | -- fast-forward from the transaction cache and in 'runTXExecData'.
130 | innerRun ::
131 | (Typeable m, MonadIO m, MonadMask m) =>
132 | TX -> Maybe TransactionID -> (TransactionID -> IO ()) ->
133 | FaeInterpretWithHistoryT m Integer
134 | innerRun tx@TX{..} parentM placeModules = do
135 | txCount <- recallHistory parentM
136 | liftIO $ placeModules txID
137 | lift $ interpretTX tx
138 | return txCount
139 |
140 | -- | Appends a new transaction to the cache. The cache is not very
141 | -- complicated: it has a file for each transaction object, and a line in
142 | -- a single file for each transaction ID, in order of appearance,
143 | -- regardless of how they depend on earlier ones.
144 | extendTXCache :: (MonadIO m) => TX -> Maybe TransactionID -> m ()
145 | extendTXCache tx@TX{..} parentM = liftIO $ do
146 | B.writeFile (makeTXFileName txID) $ S.encode (tx, parentM)
147 | B.appendFile indexFileName $ S.encode txID
148 |
149 | -- | Does...something...to each transaction in the cache. Though the
150 | -- definition is somewhat extended, most of it is just handling filesystem
151 | -- stuff.
152 | forM_TXCache :: (MonadIO m) => (TX -> Maybe TransactionID -> m ()) -> m ()
153 | forM_TXCache f = evalContT $ callCC $ \done -> do
154 | let
155 | act h = go where
156 | hGetS :: (Serialize a, MonadIO m) => Int -> m a
157 | hGetS = decodeFile (error "Bad TX cache") (liftIO . B.hGet h)
158 | go = do
159 | atEOF <- liftIO $ hIsEOF h
160 | when atEOF $ done ()
161 | txID <- hGetS txIDLength
162 | let txErr = error $ "Bad transaction file for TX" ++ show txID
163 | (tx, parentM) <- getTX txErr $ makeTXFileName txID
164 | lift $ f tx parentM
165 | go
166 | isCache <- liftIO $ doesFileExist indexFileName
167 | unless isCache $ done ()
168 | h <- liftIO $ openBinaryFile indexFileName ReadMode
169 | act h
170 | liftIO $ hClose h
171 |
172 | where
173 | decodeFile :: (Serialize a, Monad m) => a -> (b -> m ByteString) -> (b -> m a)
174 | decodeFile err getter = fmap (either (const err) id . S.decode) . getter
175 | getTX err = decodeFile err (liftIO . B.readFile)
176 |
177 | cacheDirName :: String
178 | cacheDirName = "txcache"
179 |
180 | -- | Establishes the location of the transaction cache's index file.
181 | indexFileName :: String
182 | indexFileName = cacheDirName > "index"
183 |
184 | -- | Establishes the location of the transaction cache files.
185 | makeTXFileName :: TransactionID -> String
186 | makeTXFileName txID = cacheDirName > show txID
187 |
188 | -- | A constant that, no doubt, is equal to 32 (bytes).
189 | txIDLength :: Int
190 | txIDLength = B.length $ S.encode nullID
191 |
192 |
--------------------------------------------------------------------------------
/bin/FaeServer/Git.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: FaeServer.Git
3 | Description: Git operations for keeping transaction modules
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | Fae transactions come along with their source code, which is a bunch of
10 | Haskell modules that may be visible to other, later transactions.
11 | Therefore the correct set of modules needs to be visible on disk (in the
12 | interpreter's search path) at each point in history. We achieve this by
13 | utilizing @git@ to record each post-transaction state as a commit tagged by
14 | the transaction ID, and to reset to each one when it is used as a parent
15 | transaction.
16 | -}
17 |
18 | module FaeServer.Git where
19 |
20 | import Blockchain.Fae.FrontEnd
21 |
22 | import Control.Monad
23 |
24 | import System.Directory
25 | import System.Environment
26 | import System.Exit
27 | import System.Process
28 |
29 | -- | More than just @git init@, this also ensures a truly fresh repo, sets
30 | -- up necessary user info, and creates the "genesis transaction" commit.
31 | gitInit :: IO ()
32 | gitInit = do
33 | removePathForcibly "git"
34 | runGitWithArgs "init" ["--quiet"]
35 | runGitWithArgs "config" ["user.name", "Fae"]
36 | runGitWithArgs "config" ["user.email", "fae"]
37 | runGitWithArgs "commit" ["-q", "--allow-empty", "-m", "Transaction " ++ show nullID]
38 | runGitWithArgs "tag" [mkTXIDName nullID]
39 |
40 | -- | Commits and also tags the @Blockchain@ hierarchical directory.
41 | gitCommit :: TransactionID -> IO ()
42 | gitCommit txID = do
43 | runGitWithArgs "add" ["Blockchain"]
44 | runGitWithArgs "commit" ["-q", "-m", "Transaction " ++ show txID]
45 | runGitWithArgs "tag" [mkTXIDName txID]
46 |
47 | -- | This is a /hard/ reset, because we really want to have exactly the
48 | -- correct set of modules on disk afterwards.
49 | gitReset :: TransactionID -> IO ()
50 | gitReset oldTXID = runGitWithArgs "reset" ["--hard", "-q", mkTXIDName oldTXID]
51 |
52 | -- | @git clean@ removes untracked files, e.g. modules produced by fake
53 | -- transactions. These would otherwise persist across hard resets.
54 | gitClean :: IO ()
55 | gitClean = runGitWithArgs "clean" ["-q", "-f", "Blockchain"]
56 |
57 | -- | Invokes @git --work-tree .@ since we intentionally use a nonstandardly
58 | -- named git directory; any output is helpfully reported.
59 | runGitWithArgs :: String -> [String] -> IO ()
60 | runGitWithArgs cmd args = do
61 | let fullArgs = "--work-tree" : "." : cmd : args
62 | (exitCode, out, err) <- readProcessWithExitCode "git" fullArgs ""
63 | case exitCode of
64 | ExitSuccess -> unless (null out) $ putStrLn $ unlines
65 | [
66 | "`git " ++ cmd ++ "` was successful with the following output:",
67 | out
68 | ]
69 | ExitFailure n -> do
70 | putStrLn $ unlines $
71 | ("`git " ++ cmd ++ "` returned code " ++ show n) :
72 | if null err then [] else
73 | [
74 | "Error message:",
75 | err
76 | ]
77 | exitFailure
78 |
79 |
--------------------------------------------------------------------------------
/bin/FaeServer/History.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: FaeServer.History
3 | Description: Stores and tracks successive Fae storage states
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | The entry point to the Fae core library is just `interpretTX`, i.e. running
10 | a single transaction with some preexisting storage state. This state is
11 | built up by @faeServer@'s event loop, but even that is not enough history,
12 | because we need to be able to roll back to previous parent transactions
13 | with the correct post-transaction state. This module manages
14 | a meta-history containing each such storage state for all transactions.
15 | (Due to sharing of immutable values in Haskell, successive entries in this
16 | history should only add an incremental amount of data, so the memory
17 | requirements will not be quadratic as they may seem.)
18 | -}
19 |
20 | module FaeServer.History where
21 |
22 | import Blockchain.Fae.FrontEnd
23 |
24 | import Common.Lens ((&))
25 |
26 | import Control.Monad.IO.Class
27 | import Control.Monad.State
28 | import Control.Monad.Trans.Class
29 |
30 | import Data.Map (Map)
31 | import qualified Data.Map as Map
32 |
33 | import Data.Maybe
34 |
35 | import FaeServer.Concurrency
36 | import FaeServer.Git
37 |
38 | -- | Tracks all post-transaction states for the purpose of rolling back.
39 | data TXHistory =
40 | TXHistory
41 | {
42 | txStorageAndCounts :: Map TransactionID (Storage, Integer),
43 | bestTXID :: TransactionID,
44 | bestTXCount :: Integer
45 | }
46 |
47 | -- | Monad for tracking history
48 | type FaeInterpretWithHistoryT m = StateT TXHistory (FaeInterpretT m)
49 |
50 | -- | Marshals the several parallel operations that need to occur when
51 | -- a state rollback occurs: reset git /and/ fetch the old Fae storage.
52 | recallHistory ::
53 | (MonadIO m) => Maybe TransactionID -> FaeInterpretWithHistoryT m Integer
54 | recallHistory parentM = do
55 | TXHistory{..} <- get
56 | let parent = fromMaybe bestTXID parentM
57 | err = error $ "No transaction in history with ID: " ++ show parent
58 | -- Weird construct forces this lookup before git runs
59 | (s, n) <- return $ Map.findWithDefault err parent txStorageAndCounts
60 | liftIO $ gitReset parent
61 | lift $ put s
62 | return n
63 |
64 | -- | Not quite complementary to 'recallHistory', this merely updates the
65 | -- 'TXHistory' but does not run git, which will operate differently
66 | -- depending on transaction-specific parameters (e.g. @fake@). It also
67 | -- recomputes the "best", i.e. longest, chain of transactions, which is
68 | -- used as the default parent for transactions that don't specify one.
69 | --
70 | -- The second, @Integer@ parameter gives the new transaction count; this is
71 | -- necessary because there is a fencepost problem in how this is calculated
72 | -- in the fast-forward from the transaction cache versus adding a new
73 | -- transaction.
74 | updateHistory ::
75 | (Monad m) => Maybe TransactionID -> Integer -> FaeInterpretWithHistoryT m ()
76 | updateHistory txIDM newCount = do
77 | TXHistory{..} <- get
78 | s <- lift get
79 | let txID = fromMaybe bestTXID txIDM
80 | txStorageAndCounts' = Map.insert txID (s, newCount) txStorageAndCounts
81 | (bestTXID', bestTXCount')
82 | | newCount > bestTXCount = (txID, newCount)
83 | | otherwise = (bestTXID, bestTXCount)
84 | put $ TXHistory txStorageAndCounts' bestTXID' bestTXCount'
85 |
86 | -- | Updates with incremented count.
87 | incrementHistory ::
88 | (Monad m) => TransactionID -> Integer -> FaeInterpretWithHistoryT m ()
89 | incrementHistory txID n = updateHistory (Just txID) (n + 1)
90 |
91 | -- | Runs the stack of monads with an empty history. The transaction count
92 | -- starts at 1, not 0.
93 | runFaeInterpretWithHistory ::
94 | (MonadMask m, MonadIO m) => FaeInterpretWithHistoryT m () -> m ()
95 | runFaeInterpretWithHistory = runFaeInterpret . flip evalStateT emptyTXHistory where
96 | emptyTXHistory =
97 | TXHistory
98 | {
99 | txStorageAndCounts =
100 | Map.singleton nullID (Storage Map.empty Map.empty, 1),
101 | bestTXID = nullID,
102 | bestTXCount = 1
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/bin/FaeServer/Modules.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: FaeServer.Modules
3 | Description: A few functions for getting a transaction's modules
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | In addition to organizing the modules attached to a transaction message,
10 | this module also contains the logic to alter their headers, and create
11 | re-export versions, to match the interpreter's expectations.
12 | -}
13 |
14 | module FaeServer.Modules where
15 |
16 | import Blockchain.Fae.FrontEnd
17 |
18 | import Control.DeepSeq
19 | import Control.Monad
20 |
21 | import qualified Data.ByteString.Lazy.Char8 as LC8
22 | import qualified Data.ByteString.Char8 as C8
23 |
24 | import Data.Map (Map)
25 | import qualified Data.Map as Map
26 |
27 | import Data.Char
28 | import Data.List
29 | import Data.Maybe
30 | import Data.Monoid
31 | import Data.Proxy
32 | import Data.Serialize
33 |
34 | import FaeServer.Git
35 |
36 | import Network.Wai.Parse
37 |
38 | import System.Directory
39 | import System.FilePath
40 |
41 | -- | How files in a request are presented.
42 | type RequestFiles = [(Module, FileInfo LC8.ByteString)]
43 |
44 | -- | Extracts the transaction object from the full message and arranges the
45 | -- files into a name-contents mapping.
46 | makeFilesMap ::
47 | (Serialize a) =>
48 | TXMessage a -> Module -> ModuleMap -> Bool -> Bool -> (TX, Module, ModuleMap)
49 | makeFilesMap txMessage mainFile0 modules0 reward isFake = (tx, mainFile, modules) where
50 | moduleNames = dropExtension <$> Map.keys modules0
51 | mainFile = fixImports txID moduleNames $ addHeader txID mainFile0
52 | modules = Map.mapWithKey (fixModule txID moduleNames . dropExtension) modules0
53 | tx@TX{..} =
54 | maybe (error "Invalid transaction message") force $
55 | txMessageToTX reward txMessage isFake
56 |
57 | -- | Looks up a file or throws an error.
58 | getFile :: RequestFiles -> String -> Module
59 | getFile files name = fromMaybe (error $ "Missing " ++ name) $
60 | getFileMaybe files name
61 |
62 | -- | Looks up a file, maybe. The one selected is the last one of that
63 | -- name.
64 | getFileMaybe :: RequestFiles -> String -> Maybe Module
65 | getFileMaybe files = last . (Nothing :) . map (Just . snd) . getFiles files
66 |
67 | -- | Gets all files with a given name, converting to regular data types.
68 | getFiles :: RequestFiles -> String -> [(String, Module)]
69 | getFiles files name =
70 | [
71 | (C8.unpack fileName, LC8.toStrict fileContent)
72 | | (name', FileInfo{..}) <- files, name' == C8.pack name
73 | ]
74 |
75 | -- | Places the main module and the others into the required directory
76 | -- structure, along with "private" variants.
77 | writeModules :: Module -> ModuleMap -> TransactionID -> IO ()
78 | writeModules mainFile modules txID = do
79 | let thisTXDir = foldr (>) "" $ mkTXPathParts txID
80 | writeModule fileName fileContents =
81 | C8.writeFile (thisTXDir > fileName) fileContents
82 | createDirectoryIfMissing True thisTXDir
83 | C8.writeFile (thisTXDir <.> "hs") mainFile
84 | sequence_ $ Map.mapWithKey writeModule modules
85 |
86 | -- | Adds a module header to the body module (like @Main@ it does not have
87 | -- a header, nor could it even write one, as the TXID is not known until
88 | -- after the module is written!)
89 | addHeader :: TransactionID -> Module -> Module
90 | addHeader txID = C8.append header where
91 | header = moduleHeader (mkTXModuleName txID) Nothing "Blockchain.Fae"
92 |
93 | -- | Adjust an "other" module to have correctly qualified module names in
94 | -- the header and imports.
95 | fixModule :: TransactionID -> [String] -> String -> Module -> Module
96 | fixModule txID moduleNames fileName =
97 | fixImports txID moduleNames .
98 | fixHeader txID fileName
99 |
100 | -- | For each import of one of the "other" modules, qualify it with the
101 | -- transaction ID path.
102 | fixImports :: TransactionID -> [String] -> Module -> Module
103 | fixImports txID moduleNames =
104 | C8.unlines . fmap (fixImport txID moduleNames) . C8.lines
105 |
106 | -- | Determine if a line imports an "other" module and, if so, qualify the
107 | -- module name.
108 | fixImport :: TransactionID -> [String] -> C8.ByteString -> C8.ByteString
109 | fixImport txID moduleNames line
110 | | ("import", rest0) <- C8.break isSpace line,
111 | let rest = C8.dropWhile isSpace rest0
112 | (isQualified, rest') =
113 | maybe (False, rest) ((True,) . C8.dropWhile isSpace) $
114 | C8.stripPrefix "qualified" rest
115 | (moduleNameBS, rest'') = C8.break isSpace rest'
116 | moduleName = C8.unpack moduleNameBS,
117 | moduleName `elem` moduleNames
118 | = C8.pack
119 | (
120 | "import " ++ if isQualified then "qualified " else "" ++
121 | qualify txID moduleName
122 | ) <>
123 | rest''
124 | | otherwise = line
125 |
126 | -- | Adjusts the name of one of the imported modules to live under the Fae
127 | -- hierarchy (the original name is available as the private variant).
128 | fixHeader :: TransactionID -> String -> Module -> Module
129 | fixHeader txID fileName = replaceModuleNameWith (qualify txID fileName)
130 |
131 | -- | Creates a module header with given name, exports, and import.
132 | moduleHeader :: String -> Maybe [String] -> String -> C8.ByteString
133 | moduleHeader moduleName exportsM importModule = C8.pack $
134 | "module " ++ moduleName ++
135 | maybe " " (\exports -> " (" ++ intercalate "," exports ++ ") ") exportsM ++
136 | "where\n\nimport " ++ importModule ++ "\n\n"
137 |
138 | -- | Actually does the module name adjustment. This does
139 | -- a...suspect...kind of parsing to figure out where the name should be:
140 | -- cut out the chunk of the file from the first "module" to the first
141 | -- subsequent "where". Assuming there are no weird comments above the
142 | -- module header, and that the exports do not include anything with "where"
143 | -- in the name, this should actually work. I wish there were an accessible
144 | -- Haskell parser I could use.
145 | replaceModuleNameWith :: String -> Module -> Module
146 | replaceModuleNameWith moduleName contents =
147 | pre `C8.append` C8.pack ("module " ++ moduleName ++ " ") `C8.append` post
148 | where
149 | (pre, post0) = C8.breakSubstring "module" contents
150 | (_, post) = C8.breakSubstring "where" post0
151 |
152 | -- | Adds "Blockchain.Fae.TX." in front of the module name.
153 | qualify :: TransactionID -> String -> String
154 | qualify txID moduleName = mkTXModuleName txID ++ "." ++ moduleName
155 |
156 |
--------------------------------------------------------------------------------
/bin/PostTX/Faeth.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: PostTX.Faeth
3 | Description: Handler for postTX's Faeth mode
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | The two 'submit' functions craft a JSON-RPC message for Parity describing
10 | the transaction to submit or to look up. All that logic, however, is in
11 | 'Common.ProtocolT'; we just fill in some blanks here.
12 | -}
13 | module PostTX.Faeth where
14 |
15 | import Blockchain.Fae.FrontEnd
16 |
17 | import Common.Lens
18 | import Common.ProtocolT
19 |
20 | import Control.Monad.Trans
21 |
22 | import Data.Aeson (ToJSON(..))
23 | import Data.Maybe
24 |
25 | import PostTX.Args
26 | import PostTX.TXSpec
27 |
28 | import System.Console.Haskeline
29 |
30 | import Text.Read
31 |
32 | -- | Since an 'EthTXID' is actually just a byte string, this gives it
33 | -- a distinct identity so that it can have the following instances.
34 | newtype GetFaethTX = GetFaethTX EthTXID
35 |
36 | -- | -
37 | instance ToJSON GetFaethTX where
38 | toJSON (GetFaethTX ethTXID) = toJSON [ethTXID]
39 |
40 | -- | -
41 | instance ToRequest GetFaethTX where
42 | requestMethod _ = "eth_getTransactionByHash"
43 |
44 | -- | Sends a new Fae transaction to Parity, wrapped in an Ethereum
45 | -- transaction. The details of this are in 'Common.ProtocolT' and
46 | -- 'FaeServer.Faeth'.
47 | submitFaeth :: String -> Maybe Integer -> Maybe EthAddress -> TXSpec Salt -> IO ()
48 | submitFaeth host valM faethTo TXSpec{specModules = LoadedModules{..}, ..} = do
49 | senderEthAccount <- inputAccount
50 | let (hostname, ':' : port) = break (== ':') host
51 | let portNum = fromMaybe (error $ "Bad port number: " ++ port) $ readMaybe port
52 | runProtocolT hostname portNum $ do
53 | ethTXID <- sendReceiveProtocolT
54 | FaethTXData
55 | {
56 | faeTX =
57 | EthArgFaeTX FaeTX
58 | {
59 | faeTXMessage = txMessage,
60 | faeMainModule = snd mainModule,
61 | faeOtherModules = otherModules
62 | },
63 | faethEthValue = HexInteger <$> valM,
64 | faethEthAddress = fromMaybe (address senderEthAccount) faethTo,
65 | ..
66 | }
67 | liftIO . putStrLn $
68 | "Ethereum transaction ID: " ++ ethTXID ++
69 | "\nFae transaction ID: " ++ show (getTXID txMessage)
70 |
71 | -- | Requests a previously entered Faeth transaction from Parity (or
72 | -- rather, requests an Ethereum transaction and tries to extract a Fae
73 | -- transaction from it), then changes various parameters of the Ethereum
74 | -- transaction to match the ones required in the Fae transaction's 'Salt'.
75 | resubmitFaeth :: String -> EthTXID -> FaethArgs -> IO ()
76 | resubmitFaeth host ethTXID FaethArgs{..} = do
77 | senderEthAccount <- inputAccount
78 | let (hostname, ':' : port) = break (== ':') host
79 | let portNum = fromMaybe (error $ "Bad port number: " ++ port) $ readMaybe port
80 | runProtocolT hostname portNum $ do
81 | faethTXData <- sendReceiveProtocolT $ GetFaethTX ethTXID
82 | newKeys <- liftIO $ mapM resolveKeyName newKeyNames
83 | let
84 | addSigners =
85 | foldr (.) id $
86 | zipWith addSigner newNames newKeys
87 | txID = getTXID $ faeTXMessage $ getEthArgFaeTX $ faeTX faethTXData
88 | ethTXID <- sendReceiveProtocolT $
89 | faethTXData
90 | & _faeTX . _getEthArgFaeTX . _faeTXMessage %~ addSigners
91 | & _senderEthAccount .~ senderEthAccount
92 | & _faethEthAddress .~ fromMaybe (address senderEthAccount) faethTo
93 | & _faethEthValue .~ (HexInteger <$> faethValue)
94 | liftIO . putStrLn $
95 | "New Ethereum transaction ID: " ++ ethTXID ++
96 | "\nfor Fae transaction: " ++ show txID
97 |
98 | where (newNames, newKeyNames) = unzip newSigners
99 |
100 | -- | Console routine to take Ethereum account information.
101 | inputAccount :: IO EthAccount
102 | inputAccount = runInputT defaultSettings $
103 | EthAccount <$> inputAddress <*> inputPassphrase
104 |
105 | -- | Accepts the account address, echoing the input.
106 | inputAddress :: InputT IO EthAddress
107 | inputAddress = do
108 | addressSM <- getInputLine "Ethereum address: "
109 | let addressM = addressSM >>= readMaybe
110 | maybe (error "Bad address") return addressM
111 |
112 | -- | Accepts the account password, /not/ echoing it.
113 | inputPassphrase :: InputT IO String
114 | inputPassphrase = do
115 | passphraseM <- getInputLine "Passphrase: "
116 | maybe (error "Bad passphrase") return passphraseM
117 |
--------------------------------------------------------------------------------
/bin/PostTX/ImportExport.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: PostTX.ImportExport
3 | Description: Handler for postTX's import/export mode
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | Import/export mode is a pipe between two Fae servers, with no processing in between.
10 | -}
11 | module PostTX.ImportExport where
12 |
13 | import Blockchain.Fae.FrontEnd
14 |
15 | import Data.ByteString (ByteString)
16 |
17 | import Data.Serialize (Serialize)
18 | import qualified Data.Serialize as S
19 |
20 | import Network.HTTP.Client
21 | import Network.HTTP.Client.MultipartFormData
22 |
23 | import PostTX.Network
24 |
25 | -- | Requests a contract return value, then forwards the response. TODO:
26 | -- actually do a little processing to figure out if there are errors,
27 | -- rather than just printing the same message.
28 | importExport :: TransactionID -> Int -> String -> String -> IO ()
29 | importExport exportTXID exportIx exportHost importHost = do
30 | exportRequest <- buildExportRequest (exportTXID, exportIx) exportHost
31 | exportResponse <- sendReceiveSerialize exportRequest
32 | importRequest <- buildImportRequest exportResponse importHost
33 | sendReceiveSerialize @() importRequest
34 | putStrLn $
35 | "Transferred return value of contract call #" ++ show exportIx ++
36 | " in transaction " ++ show exportTXID ++
37 | " from " ++ exportHost ++ " to " ++ importHost
38 |
39 | -- | Requests a transaction's input result by index.
40 | buildExportRequest :: (TransactionID, Int) -> String -> IO Request
41 | buildExportRequest exportData exportHost =
42 | flip formDataBody (requestURL exportHost) $
43 | modulePart "export" "export" (S.encode exportData) : []
44 |
45 | -- | Forwards the exported value, doing only enough processing to separate
46 | -- the binary portion from the metadata.
47 | buildImportRequest :: ExportData -> String -> IO Request
48 | buildImportRequest ExportData{..} importHost =
49 | flip formDataBody (requestURL importHost) $
50 | modulePart "import" "import"
51 | (S.encode (exportedCID, exportStatus, neededModules, exportValType)) :
52 | modulePart "valuePackage" "valuePackage" exportedValue : []
53 |
54 |
--------------------------------------------------------------------------------
/bin/PostTX/Keys.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: PostTX.Keys
3 | Description: Handler for postTX's show-keys mode
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | Implements the key-lookup logic.
10 | -}
11 | module PostTX.Keys where
12 |
13 | import Blockchain.Fae.FrontEnd (PrivateKey, PublicKey, public)
14 |
15 | import Control.Applicative
16 | import Control.Exception
17 | import Control.Monad
18 | import Control.Lens
19 |
20 | import qualified Data.ByteString as BS
21 | import qualified Data.ByteString.Char8 as C8
22 | import qualified Data.Serialize as S
23 | import Data.List
24 | import Data.Maybe
25 |
26 | import PostTX.Args
27 |
28 | import System.Directory
29 | import System.Environment
30 | import System.Exit
31 | import System.FilePath
32 |
33 |
34 | -- | Prints a set of the stored public keys inside of the FaeHome directory.
35 | -- The empty list in the first pattern match denotes that all stored keys
36 | -- are to be shown.
37 | showKeys :: FilePath -> [String] -> IO ()
38 | showKeys faeHome [] = do -- Empty list denotes that all keys should be shown
39 | storedKeys <- getHomeKeys faeHome
40 | if null storedKeys then print $ "No keys found at " ++ show faeHome else
41 | putStr . unlines $
42 | storedKeys <&> \(keyName, privKey) ->
43 | keyName ++ ": " ++
44 | maybe "Couldn't validate key" show (public privKey)
45 | showKeys faeHome keyNamesList =
46 | sequence_ $ showHomeKey faeHome <$> keyNamesList
47 |
48 | -- | Decodes and prints the contents of a given home key
49 | showHomeKey :: FilePath -> String -> IO ()
50 | showHomeKey faeHome keyName = do
51 | maybeFile <- findFile [faeHome] keyName
52 | case maybeFile of
53 | Nothing ->
54 | putStrLn $ keyName ++ " " ++ "not found in " ++ faeHome
55 | Just file -> do
56 | keyBytes <- BS.readFile file
57 | case S.decode keyBytes of
58 | Left err -> putStrLn $
59 | show faeHome ++ keyName ++ " could not be decoded" ++ " : " ++ show err
60 | Right key -> putStrLn $
61 | maybe "Couldn't validate key" showKey $ public key
62 | where showKey key = takeBaseName file ++ ": " ++ show key
63 |
64 | -- | Retrieves all valid key files from the FaeHome directory.
65 | getHomeKeys :: FilePath -> IO [(String, PrivateKey)]
66 | getHomeKeys path = do
67 | dirList <- getDirectoryContents path
68 | fileList <- filterM doesFileExist dirList
69 | sortOn (view _1) . mapMaybe (_2 (preview _Right)) <$>
70 | traverse sequenceA [(takeBaseName a, S.decode <$> BS.readFile a) | a <- fileList]
71 |
--------------------------------------------------------------------------------
/bin/PostTX/Network.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: PostTX.Network
3 | Description: Handler for postTX's Faeth mode
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | The foundational module for all network-using modes, abstracting away the
10 | process of connecting to the server and interpreting its response.
11 | -}
12 |
13 | module PostTX.Network where
14 |
15 | import Blockchain.Fae.FrontEnd
16 |
17 | import Common.JSON()
18 |
19 | import Data.Aeson (FromJSON)
20 | import qualified Data.Aeson as A
21 | import Data.Bifunctor
22 | import qualified Data.ByteString.Lazy.Char8 as LC8
23 | import Data.Maybe
24 | import Data.Proxy
25 | import Data.Serialize (Serialize)
26 |
27 | import qualified Data.ByteString.Lazy.Char8 as LC8
28 |
29 | import qualified Data.Map as Map
30 | import Data.Map (Map)
31 |
32 | import qualified Data.Serialize as S
33 |
34 | import qualified Data.Text as T
35 | import qualified Data.Text.Encoding as T
36 | import Data.Typeable
37 |
38 | import Network.HTTP.Client hiding (Proxy)
39 | import Network.HTTP.Client.MultipartFormData
40 |
41 | import PostTX.TXSpec
42 |
43 | import Text.PrettyPrint.HughesPJClass
44 |
45 | -- | Makes a request from an HTTP URL (here, just the hostname).
46 | requestURL :: String -> Request
47 | requestURL host = fromMaybe (error $ "Bad host string: " ++ host) $
48 | parseRequest $ "http://" ++ host
49 |
50 | -- | Performs the conversation with the server and handles the response as
51 | -- requested.
52 | sendReceive :: (LC8.ByteString -> a) -> Request -> IO a
53 | sendReceive decode request = do
54 | manager <- newManager defaultManagerSettings
55 | response <- httpLbs request manager
56 | return $ decode $ responseBody response
57 |
58 | -- | Specialization of 'sendReceive' that expects a 'String' response
59 | -- literally represented in UTF-8 format by the response bytestring.
60 | sendReceiveString :: Request -> IO String
61 | sendReceiveString = sendReceive LC8.unpack
62 |
63 | -- | Specialization of 'sendReceive' that expects a typed value encoded as
64 | -- the response bytestring.
65 | sendReceiveSerialize :: (Typeable a, Serialize a) => Request -> IO a
66 | sendReceiveSerialize =
67 | sendReceive $ \bs -> either (error $ LC8.unpack bs) id $ S.decodeLazy bs
68 |
69 | -- | Specialization of 'sendReceive' that expects a JSON value encoded as
70 | -- the response bytestring.
71 | sendReceiveJSON :: (Typeable a, FromJSON a) => Request -> IO (Either String a)
72 | sendReceiveJSON = sendReceive $ \bs ->
73 | bimap (\_ -> T.unpack $ T.decodeUtf8 $ LC8.toStrict bs) id $ A.eitherDecode bs
74 |
75 | -- | Combines the 'String' and 'JSON' variants, pretty-printing the latter
76 | -- if it is requested.
77 | sendReceiveJSONString :: Bool -> Request -> IO String
78 | sendReceiveJSONString isJson
79 | | isJson = sendReceiveString
80 | | otherwise = fmap (either id prettyShow) . sendReceiveJSON @TXSummary
81 |
82 | -- | Standard error format for this module.
83 | responseError :: forall a. (Typeable a) => String -> a
84 | responseError s =
85 | error $ "Couldn't parse response as type " ++ show tr ++ ": " ++ s
86 | where tr = typeRep $ Proxy @a
87 |
88 | -- | Creates a request part containing a module source code file.
89 | modulePart :: String -> String -> Module -> Part
90 | modulePart param name = partFileRequestBody (T.pack param) name . RequestBodyBS
91 |
92 | -- | Creates all parts from a module-name mapping.
93 | moduleParts :: String -> Modules -> [Part]
94 | moduleParts param = Map.foldrWithKey (\name -> (:) . modulePart param name) []
95 |
96 |
--------------------------------------------------------------------------------
/bin/PostTX/Submit.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: PostTX.Submit
3 | Description: Handler for postTX's normal or view modes
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | Converts a 'TXSpec' to a request, sends it to the server, and prints the
10 | response to the screen.
11 | -}
12 | module PostTX.Submit where
13 |
14 | import qualified Data.ByteString.Char8 as C8
15 | import Data.Serialize (Serialize)
16 | import qualified Data.Serialize as S
17 | import qualified Data.Text as T
18 |
19 | import Network.HTTP.Client
20 | import Network.HTTP.Client.MultipartFormData
21 |
22 | import PostTX.Network
23 | import PostTX.TXSpec
24 |
25 | -- | If JSON formatting is not enabled through postTX CLI flag
26 | -- pretty print a summary of TX output.
27 | submit ::
28 | (Serialize a) =>
29 | String -> Bool -> Bool -> Bool -> TXSpec a -> IO ()
30 | submit host fake lazy isJson txSpec =
31 | buildRequest host fake lazy txSpec >>= sendReceiveJSONString isJson >>= putStrLn
32 |
33 | -- | Constructs a request whose parts are the various Haskell modules that
34 | -- are part of the transaction, and whose query parameters are the other
35 | -- pieces of metadata in the transaction file or from the command line.
36 | buildRequest ::
37 | (Serialize a) =>
38 | String -> Bool -> Bool -> TXSpec a -> IO Request
39 | buildRequest host fake lazy TXSpec{specModules = LoadedModules{..}, ..} =
40 | flip formDataBody (requestURL host) $
41 | modulePart "message" txName (S.encode txMessage) :
42 | modulePart "body" txName mainModuleBS :
43 | moduleParts "other" otherModules ++
44 | fmap (uncurry partBS) (maybe id (:) parentArg [lazyArg, fakeArg, rewardArg])
45 |
46 | where
47 | (txName, mainModuleBS) = mainModule
48 | lazyArg = ("lazy", ) $ if lazy then "True" else "False"
49 | fakeArg = ("fake", ) $ if fake then "True" else "False"
50 | rewardArg = ("reward", ) $ if isReward then "True" else "False"
51 | parentArg = ("parent", ) . C8.pack . show <$> parentM
52 |
53 |
--------------------------------------------------------------------------------
/bin/PostTX/TXSpec.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: PostTX.TXSpec
3 | Description: Handler for postTX's Faeth mode
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | A 'TXSpec' contains a complete Fae transaction message, the associated
10 | modules (which are not part of the basic message), and the metadata of
11 | whether this is a reward transaction and what its parent is. If and when
12 | Fae is managed by a blockchain, both of the latter will be inferred from
13 | the blocks, and the modules will be sent in a separate communication from
14 | the message.
15 | -}
16 | {-# LANGUAGE TemplateHaskell #-}
17 | module PostTX.TXSpec
18 | (
19 | module PostTX.TXSpec,
20 | Input(..), Module, ModuleMap, Renames(..), TransactionID, getTXID
21 | ) where
22 |
23 | import Blockchain.Fae.FrontEnd
24 |
25 | import Common.Lens
26 | import Common.ProtocolT
27 |
28 | import Control.Monad.Reader
29 |
30 | import qualified Data.ByteString as BS
31 | import qualified Data.ByteString.Char8 as C8
32 |
33 | import qualified Data.Map as Map
34 | import Data.Map (Map)
35 |
36 | import qualified Data.Serialize as S
37 | import Data.Serialize (Serialize)
38 |
39 | import Data.Maybe
40 | import Data.Time.Clock
41 |
42 | import GHC.Generics
43 |
44 | import PostTX.Args
45 |
46 | import System.Directory
47 |
48 | import Text.Read
49 |
50 | -- * Spec types
51 |
52 | -- | The structure that 'PostTX.Parser' parses a transaction file into.
53 | data TXData =
54 | TXData
55 | {
56 | dataModules :: LoadedModules,
57 | fallback :: [String],
58 | materials :: InputMaterials,
59 | inputs :: [Input],
60 | keys :: [(String, String)],
61 | reward :: Bool,
62 | parent :: Maybe TransactionID
63 | }
64 |
65 | -- | All transaction information appropriately organized. Recall that the
66 | -- reason the modules are not part of the message is that it is designed to
67 | -- make it possible for a recipient to opt out of incurring significant
68 | -- data costs by discriminating based on file size. In one-off postTX
69 | -- operation, this dynamic is not present, so the modules are sent
70 | -- alongside the transaction.
71 | data TXSpec a =
72 | TXSpec
73 | {
74 | txMessage :: TXMessage a,
75 | specModules :: LoadedModules,
76 | isReward :: Bool,
77 | parentM :: Maybe TransactionID
78 | }
79 | deriving (Generic)
80 |
81 | -- | Modules having been read from disk
82 | data LoadedModules =
83 | LoadedModules
84 | {
85 | mainModule :: (FileName, Module),
86 | otherModules :: Modules
87 | }
88 | deriving (Generic)
89 |
90 | -- | Less generic.
91 | type FileName = String
92 | -- | Less generic.
93 | type Identifier = String
94 | -- | Convenient.
95 | type Modules = ModuleMap
96 | -- | Definitely convenient.
97 | type Keys = Map String (Either PublicKey PrivateKey)
98 |
99 | -- | This is a class in the sense of being actually an overloaded function
100 | -- more than an interface. Depending on the format of the transaction's
101 | -- "salt", constructing the message may require alternative steps.
102 | -- Presumably the rest of the message is constructed the same way.
103 | class (Monad m, Serialize a) => MakesTXSpec m a where
104 | txDataToTXSpec :: TXData -> m (TXSpec a)
105 |
106 | -- * Template Haskell
107 | makeLenses ''TXData
108 | makeLenses ''TXSpec
109 |
110 | -- | -
111 | instance Serialize LoadedModules
112 | -- | -
113 | instance (Serialize a) => Serialize (TXSpec a)
114 |
115 | -- | Just uses the time as the salt value.
116 | instance MakesTXSpec IO String where
117 | txDataToTXSpec = txSpecTimeSalt id
118 |
119 | -- | Makes a Faeth salt with Ethereum metadata.
120 | instance MakesTXSpec (ReaderT FaethArgs IO) Salt where
121 | txDataToTXSpec txData = do
122 | FaethArgs{..} <- ask
123 | let
124 | makeSalt faeSalt =
125 | Salt
126 | {
127 | ethArgument = maybe BS.empty getHex faethArgument,
128 | ethFee = HexInteger <$> faethFee,
129 | ethRecipient = faethRecipient,
130 | ..
131 | }
132 | liftIO $ txSpecTimeSalt makeSalt txData
133 |
134 | -- | Given a partially constructed salt, finishes it off by inserting the
135 | -- current time, then constructs the 'TXSpec' containing it.
136 | txSpecTimeSalt ::
137 | (Serialize a) => (String -> a) -> TXData -> IO (TXSpec a)
138 | txSpecTimeSalt makeSalt txData = do
139 | now <- getCurrentTime
140 | go <- getMakeTXSpec txData
141 | return $ go $ makeSalt $ show now
142 |
143 | -- | This higher-order function fills in the private keys requested in the
144 | -- 'TXData', then returns a partially constructed 'TXSpec' that expects
145 | -- only the salt, to be provided by 'txSpecTimeSalt'.
146 | getMakeTXSpec :: (Serialize a) => TXData -> IO (a -> TXSpec a)
147 | getMakeTXSpec TXData{..} = do
148 | let
149 | keys' = if null keys then [("self", "self")] else keys
150 | (signerNames, keyNames) = unzip keys'
151 | privKeys <- mapM resolveKeyName keyNames
152 | let keyMap = Map.fromList $ zip signerNames privKeys
153 | return $ makeTXSpec dataModules materials inputs keyMap fallback parent reward
154 |
155 | -- | Fills in the 'TXMessage' with the supplied parameters, and also
156 | -- extracts file previews for each module, then signs the whole thing with
157 | -- the provided keys.
158 | makeTXSpec ::
159 | (Serialize a) =>
160 | LoadedModules -> InputMaterials -> [Input] -> Keys -> [Identifier] ->
161 | Maybe TransactionID -> Bool -> a ->
162 | TXSpec a
163 | makeTXSpec specModules materialsCalls inputCalls keys
164 | fallbackFunctions parentM isReward salt =
165 | TXSpec
166 | {
167 | txMessage = addSignatures keys
168 | TXMessage
169 | {
170 | mainModulePreview = uncurry makePreview mainModule,
171 | otherModulePreviews = Map.mapWithKey makePreview otherModules,
172 | signatures = (,Nothing) . either id (fromMaybe keyErr . public) <$> keys,
173 | ..
174 | },
175 | ..
176 | }
177 |
178 | where
179 | LoadedModules{..} = specModules
180 | keyErr = error "Bad private key"
181 |
182 | -- | Just processes the module as a byte string
183 | makePreview :: FileName -> Module -> ModulePreview
184 | makePreview fName moduleBS =
185 | ModulePreview
186 | {
187 | moduleDigest = digest $ C8.pack fName `C8.append` moduleBS,
188 | moduleSize = toInteger $ BS.length moduleBS
189 | }
190 |
191 | addSignatures :: (Serialize a) => Keys -> TXMessage a -> TXMessage a
192 | addSignatures keys m = Map.foldrWithKey addSigner m keys
193 |
194 | -- | Signs a 'TXMessage' with a single key in a given role. This allows
195 | -- for the possibility that the role was /not/ provided with a key, thus
196 | -- producing an incomplete message. This will be, strictly speaking,
197 | -- invalid, but when sent with @--fake@ is admissible for testing purposes.
198 | addSigner ::
199 | (Serialize a) =>
200 | String -> Either PublicKey PrivateKey -> TXMessage a -> TXMessage a
201 | addSigner _ (Left _) = id
202 | addSigner name (Right privKey) =
203 | fromMaybe (error $ "Not a signer role in this transaction: " ++ name) .
204 | signTXMessage name privKey
205 |
206 | -- | If the key "name" parses as a public key, then that is the result and
207 | -- the message will not be signed (by this key). Otherwise, it is looked
208 | -- up in @faeHome@ as a file containing a private key.
209 | resolveKeyName :: String -> IO (Either PublicKey PrivateKey)
210 | resolveKeyName pubKeyS | Just pubKey <- readMaybe pubKeyS = return $ Left pubKey
211 | resolveKeyName name = do
212 | keyExists <- doesFileExist name
213 | if keyExists
214 | then bimap (error $ "Couldn't decode private key: " ++ name) id .
215 | S.decode <$> BS.readFile name
216 | else do
217 | privKey <- newPrivateKey
218 | BS.writeFile name $ S.encode privKey
219 | return $ Right privKey
220 |
--------------------------------------------------------------------------------
/bin/PostTX/View.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: PostTX.View
3 | Description: Handler for postTX's View mode
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | View mode is trivial: it sends a single transaction ID and prints the
10 | result that had already been obtained from running that transaction.
11 | -}
12 | module PostTX.View where
13 |
14 | import PostTX.Network
15 |
16 | import Blockchain.Fae.FrontEnd (TransactionID)
17 |
18 | import qualified Data.ByteString.Lazy.Char8 as LC8
19 |
20 | import Network.HTTP.Client.MultipartFormData
21 |
22 | -- | The view request just has a "view" query parameter containing the
23 | -- transaction ID.
24 | view :: TransactionID -> String -> Bool -> IO ()
25 | view txID host isJSON = do
26 | request <- flip formDataBody (requestURL host)
27 | [partLBS "view" $ LC8.pack $ show txID]
28 | sendReceiveJSONString isJSON request >>= putStrLn
29 |
--------------------------------------------------------------------------------
/common/Common/JSON.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: Common.JSON
3 | Description: JSON instances for encoding transaction summaries
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | This module exports (orphan) JSON instances and utilities for types that
10 | are generally useful both to the server (@faeServer@) and the client (@postTX@).
11 | -}
12 | {-# LANGUAGE OverloadedStrings #-}
13 | {-# LANGUAGE RecordWildCards #-}
14 | {-# LANGUAGE ScopedTypeVariables #-}
15 | {-# LANGUAGE TupleSections #-}
16 | module Common.JSON where
17 |
18 | import Blockchain.Fae.FrontEnd
19 |
20 | import Control.Applicative
21 | import Control.DeepSeq
22 |
23 | import Data.Aeson (FromJSON, ToJSON, Object, toJSON,
24 | parseJSON, object, Value(..), withText,
25 | withObject, (.=), (.:))
26 | import qualified Data.Aeson as A
27 | import Data.Aeson.Types
28 | import qualified Data.ByteString.Lazy as BS
29 | import Data.Maybe
30 | import qualified Data.Text as T
31 | import qualified Data.Text.Encoding as T
32 | import Data.Text (Text)
33 |
34 | import System.IO.Unsafe
35 |
36 | import Text.Read
37 |
38 | -- | -
39 | instance ToJSON TXInputSummary where
40 | toJSON TXInputSummary{..} =
41 | object [
42 | "txInputStatus" .= wrapExceptions txInputStatus,
43 | "txInputOutputs" .= wrapExceptions txInputOutputs,
44 | "txInputMaterialsSummaries" .= txInputMaterialsSummaries,
45 | "txInputVersion" .= wrapExceptions txInputVersion ]
46 |
47 | -- | -
48 | instance ToJSON TXSummary where
49 | toJSON TXSummary{..} = object [
50 | "transactionID" .= transactionID,
51 | "txResult" .= wrapExceptions txResult,
52 | "txOutputs" .= wrapExceptions txOutputs,
53 | "txInputSummaries" .= txInputSummaries,
54 | "txMaterialsSummaries" .= txMaterialsSummaries,
55 | "txSSigners" .= txSSigners ]
56 |
57 | -- | -
58 | instance FromJSON TXInputSummary where
59 | parseJSON = withObject "TXInputSummary" $ \o -> do
60 | TXInputSummary
61 | <$> readJSONField "txInputStatus" o
62 | <*> readJSONField "txInputOutputs" o
63 | <*> o .: "txInputMaterialsSummaries"
64 | <*> readJSONField "txInputVersion" o
65 |
66 | -- | -
67 | instance FromJSON TXSummary where
68 | parseJSON = withObject "TXSummary" $ \o ->
69 | TXSummary
70 | <$> o .: "transactionID"
71 | <*> readJSONField "txResult" o
72 | <*> readJSONField "txOutputs" o
73 | <*> o .: "txInputSummaries"
74 | <*> o .: "txMaterialsSummaries"
75 | <*> o .: "txSSigners"
76 |
77 | -- | -
78 | instance FromJSON PublicKey where
79 | parseJSON = readJSONText "PublicKey"
80 |
81 | -- | -
82 | instance ToJSON PublicKey where
83 | toJSON = toJSON . T.pack . show
84 |
85 | -- | -
86 | instance ToJSON ContractID where
87 | toJSON = toJSON . show
88 |
89 | -- | -
90 | instance FromJSON ContractID where
91 | parseJSON = readJSONText "ContractID"
92 |
93 | -- | -
94 | instance ToJSON Digest where
95 | toJSON = toJSON . show
96 |
97 | -- | -
98 | instance FromJSON Digest where
99 | parseJSON = readJSONText "Digest"
100 |
101 | -- | -
102 | instance ToJSON UnquotedString where
103 | toJSON = toJSON . show
104 |
105 | -- | -
106 | instance FromJSON UnquotedString where
107 | parseJSON = fmap UnquotedString . parseJSON
108 |
109 | -- | -
110 | instance ToJSON Status
111 | -- | -
112 | instance FromJSON Status
113 |
114 | -- | Chains a couple conversions to get a readable json string.
115 | encodeJSON :: (ToJSON a) => a -> String
116 | encodeJSON = T.unpack . T.decodeUtf8 . BS.toStrict . A.encode
117 |
118 | -- | If an exception is found then we tag the value as an exception.
119 | -- By forcing evaluation of exceptions we prevent uncaught exceptions being thrown
120 | -- and crashing faeServer.
121 | wrapExceptions :: forall a. (ToJSON a) => a -> Value
122 | wrapExceptions val =
123 | unsafePerformIO $ catchAll (evaluate $ force $ toJSON val)
124 | (return . object . pure . ("exception",) . A.String . T.pack . show)
125 |
126 | -- | If parsing fails then we look for the tagged exception.
127 | readJSONField :: forall a. (FromJSON a) => Text -> Object -> Parser a
128 | readJSONField fieldName obj =
129 | obj .: fieldName <|> (obj .: fieldName >>= exceptionValue)
130 |
131 | -- | Reads a value from a JSON text expression.
132 | readJSONText :: (Read a) => String -> Value -> Parser a
133 | readJSONText l = withText l $ \t -> return $
134 | let s = T.unpack t in
135 | fromMaybe (throw $ JSONException s) $ readMaybe s
136 |
137 | -- | Parses a tagged exception.
138 | exceptionValue :: Object -> Parser a
139 | exceptionValue x = throw . TXFieldException <$> x .: "exception"
140 |
141 |
--------------------------------------------------------------------------------
/common/Common/Lens.hs:
--------------------------------------------------------------------------------
1 | {- |
2 | Module: Common.Lens
3 | Description: Wrapper library for "Control.Lens"
4 | Copyright: (c) Ryan Reich, 2017-2018
5 | License: MIT
6 | Maintainer: ryan.reich@gmail.com
7 | Stability: experimental
8 |
9 | This module just re-exports "Control.Lens", replacing 'makeLenses' with one
10 | that works exactly oppositely to the default one regarding underscored
11 | names, and including a utility function.
12 | -}
13 | module Common.Lens
14 | (
15 | module Common.Lens,
16 | module Control.Lens
17 | ) where
18 |
19 | import Control.Lens hiding (makeLenses)
20 |
21 | import Data.Maybe
22 |
23 | import Language.Haskell.TH
24 |
25 | -- | Defined as
26 | --
27 | -- > makeLensesWith myLensRules
28 | --
29 | -- where 'myLensRules' /adds/ an underscore to names, rather than removes
30 | -- one that exists. Because the default behavior is backwards.
31 | makeLenses :: Name -> Q [Dec]
32 | makeLenses = makeLensesWith myLensRules
33 | where
34 | myLensRules = lensRules & lensField .~ myFieldNamer
35 | -- The inverse of the default field namer: ignores _-prefixed fields,
36 | -- otherwise prepends a _.
37 | myFieldNamer _ _ fName =
38 | case nameBase fName of
39 | ('_' : _) -> []
40 | x -> [TopName $ mkName $ '_' : x]
41 |
42 | -- | Useful when using 'at', say
43 | --
44 | -- > someMapLens . at index . defaultLens (error $ show index ++ " not found!")
45 | --
46 | -- I'm pretty sure this can be replaced with some 'Traversal'-related
47 | -- function, but I am not familiar enough with @lens@ yet to figure out
48 | -- how.
49 | defaultLens :: a -> Lens' (Maybe a) a
50 | defaultLens x = lens (fromMaybe x) (flip $ const . Just)
51 |
52 |
--------------------------------------------------------------------------------
/demos/.gitignore:
--------------------------------------------------------------------------------
1 | postTX.sh
2 | faeServer.sh
3 |
--------------------------------------------------------------------------------
/demos/Auction/Auction.hs:
--------------------------------------------------------------------------------
1 | module Auction where
2 |
3 | import Blockchain.Fae
4 | import Blockchain.Fae.Currency
5 |
6 | import Control.Monad.State
7 |
8 | import qualified Data.Map as Map
9 | import Data.Map (Map)
10 |
11 | import Data.Maybe
12 |
13 | data AuctionAction = Bid | Collect deriving (Read)
14 | data AuctionResult coin a = BidAccepted | Remit coin | Prize a deriving (Generic)
15 |
16 | data AuctionError =
17 | NoBids | Can'tBid | Can'tCollect | MustBeat Natural |
18 | UnauthorizedSeller PublicKey
19 | deriving (Show)
20 |
21 | instance Exception AuctionError
22 |
23 | data AuctionState coin =
24 | BidState
25 | {
26 | bids :: Container (Map PublicKey coin),
27 | highBid :: Valuation coin,
28 | seller :: PublicKey,
29 | bidsLeft :: Natural
30 | } |
31 | CollectState (Container (Map PublicKey coin))
32 | deriving (Generic)
33 |
34 | auction ::
35 | (ContractVal a, Currency coin, MonadTX m) =>
36 | a -> Valuation coin -> Natural -> m ()
37 | auction _ _ 0 = throw NoBids
38 | auction x bid0 maxBids = do
39 | seller <- signer "self"
40 | let state0 = BidState (Container Map.empty) bid0 seller maxBids
41 | newContract $ Auction state0 x
42 |
43 | data Auction coin a = Auction (AuctionState coin) a deriving (Generic)
44 |
45 | instance (ContractVal a, Currency coin) => ContractName (Auction coin a) where
46 | type ArgType (Auction coin a) = AuctionAction
47 | type ValType (Auction coin a) = AuctionResult coin a
48 | theContract (Auction state0 x) = usingState state0 $ feedback $ \act -> do
49 | aState <- get
50 | case (aState, act) of
51 | (CollectState{}, Bid) -> throw Can'tBid
52 | (BidState{}, Bid) -> bidStage x
53 | (_, Collect) -> collectStage
54 |
55 | bidStage ::
56 | (ContractVal a, Currency coin, MonadTX m) =>
57 | a -> StateT (AuctionState coin) m (AuctionResult coin a)
58 | bidStage x = do
59 | -- Because of this, we have to assure that 'BidState' is the only
60 | -- possible constructor.
61 | BidState{..} <- get
62 | -- The seller has to sign on to a bid, lest someone pass a malicious
63 | -- "coin"
64 | claimedSeller <- signer "seller"
65 | unless (claimedSeller == seller) $ throw (UnauthorizedSeller claimedSeller)
66 | -- We look for an amount supplied by the bidder to /raise/ their previous
67 | -- bid (starting from 0), because we hold on to the bids until the
68 | -- auction is finished.
69 | raise <- material "raise"
70 | -- Look up the bidder's previous bid, if any, and add the new coin amount
71 | -- to get the new bid
72 | bidder <- signer "self"
73 |
74 | let bidsMap = getContainer bids
75 | oldBidM = Map.lookup bidder bidsMap
76 | newBidCoin <- maybe (return raise) (add raise) oldBidM
77 | newBid <- value newBidCoin
78 | -- Make sure they are actually raising
79 | unless (newBid > highBid) $ throw (MustBeat $ fromIntegral highBid)
80 | let bidsLeft' = bidsLeft - 1
81 | if (bidsLeft' > 0)
82 | then do
83 | let bidsMap' = Map.insert bidder newBidCoin bidsMap
84 | put $ BidState (Container bidsMap') newBid seller bidsLeft'
85 | return BidAccepted
86 | else do
87 | put $ CollectState $ Container $
88 | -- The winning bid is earmarked for the seller
89 | Map.insert seller newBidCoin $
90 | -- The winning bidder doesn't get money back
91 | Map.delete bidder bidsMap
92 | return $ Prize x
93 |
94 | collectStage ::
95 | (Currency coin, HasEscrowIDs a, MonadTX m) =>
96 | StateT (AuctionState coin) m (AuctionResult coin a)
97 | collectStage = do
98 | sender <- signer "self"
99 | aState <- get
100 | Remit <$> case aState of
101 | BidState{bids = Container bidsMap,..} -> do
102 | let bid = Map.findWithDefault (throw Can'tCollect) sender bidsMap
103 | bidVal <- value bid
104 | when (bidVal == highBid) $ throw Can'tCollect
105 | let bidsMap' = Map.delete sender bidsMap
106 | put aState{bids = Container bidsMap'}
107 | return bid
108 | CollectState (Container bidsMap) -> do
109 | let bid = Map.findWithDefault (throw Can'tCollect) sender bidsMap
110 | bidsMap' = Map.delete sender bidsMap
111 | put $ CollectState $ Container bidsMap'
112 | return bid
113 |
--------------------------------------------------------------------------------
/demos/Auction/Bid:
--------------------------------------------------------------------------------
1 | body = Bid
2 | keys
3 | self = $key
4 | seller = seller
5 | inputs
6 | $aucTX/Body/0/Current : Bid
7 | self = self
8 | seller = seller
9 | raise = $coinTX/Body/0/Current : ()
10 | self = self
11 |
--------------------------------------------------------------------------------
/demos/Auction/Bid.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Contracts
2 | import Blockchain.Fae.Currency
3 |
4 | import Blockchain.Fae.Transactions.TX$aucTX.Auction
5 |
6 | body :: AuctionResult Coin String -> FaeTX String
7 | body BidAccepted = return "Bid accepted"
8 | body (Prize s) = return s
9 | body _ = error "unexpected response"
10 |
11 |
--------------------------------------------------------------------------------
/demos/Auction/Collect:
--------------------------------------------------------------------------------
1 | body = Collect
2 | keys
3 | self = $key
4 | inputs
5 | $aucTX/Body/0/Current : Collect
6 | self = self
7 |
--------------------------------------------------------------------------------
/demos/Auction/Collect.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Contracts
2 | import Blockchain.Fae.Currency
3 |
4 | import Blockchain.Fae.Transactions.TX$aucTX.Auction
5 |
6 | body :: AuctionResult Coin String -> FaeTX String
7 | body (Remit c) = do
8 | deposit c "self"
9 | return "Collected"
10 | body _ = error "unexpected result"
11 |
--------------------------------------------------------------------------------
/demos/Auction/Create:
--------------------------------------------------------------------------------
1 | body = Create
2 | keys
3 | self = seller
4 | others
5 | - Auction
6 |
--------------------------------------------------------------------------------
/demos/Auction/Create.hs:
--------------------------------------------------------------------------------
1 | import Auction
2 | import Blockchain.Fae.Currency
3 |
4 | body :: FaeTX ()
5 | body = do
6 | let price = 1 :: Valuation Coin
7 | let numBids = 3
8 | auction ("You won!" :: String) price numBids
9 |
10 |
--------------------------------------------------------------------------------
/demos/GetCoins/GetCoin:
--------------------------------------------------------------------------------
1 | body = GetCoin
2 | reward = True
3 | keys
4 | self = $self
5 |
--------------------------------------------------------------------------------
/demos/GetCoins/GetCoin.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Contracts
2 | import Blockchain.Fae.Currency
3 |
4 | body :: Reward -> FaeTX ()
5 | body rID = do
6 | coin <- reward rID
7 | deposit coin "self"
8 |
--------------------------------------------------------------------------------
/demos/GetCoins/GetMoreCoins:
--------------------------------------------------------------------------------
1 | body = GetMoreCoins
2 | reward = True
3 | keys
4 | self = $self
5 | inputs
6 | $coinTX / Body / 0 / $ver : ()
7 |
--------------------------------------------------------------------------------
/demos/GetCoins/GetMoreCoins.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Contracts
2 | import Blockchain.Fae.Currency
3 |
4 | body :: Reward -> Coin -> FaeTX ()
5 | body rID oldCoin = do
6 | coin <- reward rID
7 | newCoin <- add oldCoin coin
8 | deposit newCoin "self"
9 |
--------------------------------------------------------------------------------
/demos/HelloWorld1/HelloWorld1:
--------------------------------------------------------------------------------
1 | body = HelloWorld1
2 |
--------------------------------------------------------------------------------
/demos/HelloWorld1/HelloWorld1.hs:
--------------------------------------------------------------------------------
1 | body :: Transaction Void String
2 | body _ = return "Hello, world 1!"
3 |
--------------------------------------------------------------------------------
/demos/HelloWorld1/HelloWorld1.md:
--------------------------------------------------------------------------------
1 | # HelloWorld1
2 |
3 | ## Step1: Build and start the server
4 | ```
5 | stack build
6 | stack exec faeServer
7 | ```
8 |
9 | ## Step2: Execute HelloWorld1
10 | ```
11 | cd demos/HelloWorld1/
12 | stack exec POSTTX HelloWorld1
13 | ```
14 | The result should look something like this:
15 | ```
16 | Transaction f6df16bc11a23a66a2be5fadbd2470cf39abfb6de0d08f780223d48f8b3eb4f0:
17 | result: "Hello, world 1!"
18 | outputs:
19 | txSigners:
20 | self: 800725c2cbf8c17ef4244222ae12308cebd6d111cee08fd5a4300688ae833a52
21 | ```
22 |
--------------------------------------------------------------------------------
/demos/HelloWorld2/CallHelloWorld2:
--------------------------------------------------------------------------------
1 | body = CallHelloWorld2
2 | inputs
3 | TransactionOutput $txID 0 = ()
4 |
--------------------------------------------------------------------------------
/demos/HelloWorld2/CallHelloWorld2.hs:
--------------------------------------------------------------------------------
1 | body :: Transaction String String
2 | body = return
3 |
--------------------------------------------------------------------------------
/demos/HelloWorld2/HelloWorld2:
--------------------------------------------------------------------------------
1 | body = HelloWorld2
2 |
--------------------------------------------------------------------------------
/demos/HelloWorld2/HelloWorld2.hs:
--------------------------------------------------------------------------------
1 | body :: Transaction Void ()
2 | body _ = newContract [] c where
3 | c :: Contract () String
4 | c _ = spend "Hello, world!"
5 |
--------------------------------------------------------------------------------
/demos/HelloWorld2/HelloWorld2.md:
--------------------------------------------------------------------------------
1 | # HelloWorld2
2 |
3 | ## Step1: Build and start the server
4 | ```
5 | stack build
6 | stack exec faeServer
7 | ```
8 |
9 | ## Step2: Execute HelloWorld2
10 | ```
11 | cd demos/HelloWorld2/
12 | stack exec POSTTX HelloWorld2
13 | ```
14 |
15 | The result should look something like this:
16 | ```
17 | Transaction 1685a9d0c34639f47b152849b0b5984f8f3b37ee1203635154955869bdc8883d:
18 | result: ()
19 | outputs:
20 | 0: 00b628bdff52ad413d19b094646ee0685e22931037fbca595dd4e066f10bf431
21 | txSigners:
22 | self: 800725c2cbf8c17ef4244222ae12308cebd6d111cee08fd5a4300688ae833a52
23 | ```
24 |
25 | Now execute CallHelloWorld2 to print it's contents:
26 | ```
27 | stack exec txID=1685a9d0c34639f47b152849b0b5984f8f3b37ee1203635154955869bdc8883d CallHelloWorld2
28 | ```
29 |
--------------------------------------------------------------------------------
/demos/Swap/CreateSwap:
--------------------------------------------------------------------------------
1 | body = CreateSwap
2 | reward = True
3 |
4 | inputs
5 | TransactionOutput $bobCoinTX 0 = ()
6 |
7 | keys
8 | partyA = alice
9 | partyB = bob
10 | self = bob
11 |
--------------------------------------------------------------------------------
/demos/Swap/CreateSwap.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Contracts
2 | import Blockchain.Fae.Currency
3 |
4 | body :: Transaction (RewardEscrowID, Coin) ()
5 | body (rwd, coin) = twoPartySwap rwd coin
6 |
7 |
--------------------------------------------------------------------------------
/demos/Swap/GetOffering:
--------------------------------------------------------------------------------
1 | body = GetOffering
2 |
3 | inputs
4 | TransactionOutput $swapTX 0 = Nothing
5 |
6 | keys
7 | self = $self
8 |
--------------------------------------------------------------------------------
/demos/Swap/GetOffering.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Currency
2 |
3 | body ::
4 | Transaction (Maybe (Either (Versioned RewardEscrowID) (Versioned Coin))) String
5 | body Nothing = error "Swap is incomplete"
6 | body (Just (Left (Versioned !rwd))) = return "Got reward"
7 | body (Just (Right (Versioned !coin))) = return "Got coin"
8 |
--------------------------------------------------------------------------------
/demos/Swap/GiveOpinion:
--------------------------------------------------------------------------------
1 | body = GiveOpinion
2 |
3 | inputs
4 | TransactionOutput $swapTX 0 = Just $opinion
5 |
6 | keys
7 | self = $self
8 |
--------------------------------------------------------------------------------
/demos/Swap/GiveOpinion.hs:
--------------------------------------------------------------------------------
1 | body :: Transaction Void ()
2 | body _ = return ()
3 |
--------------------------------------------------------------------------------
/demos/talk-3/FaethService:
--------------------------------------------------------------------------------
1 | keys
2 | alice = $alice
3 | bob = $bob
4 |
--------------------------------------------------------------------------------
/demos/talk-3/FaethService.hs:
--------------------------------------------------------------------------------
1 | body :: Transaction Void String
2 | body _ = return "Hello from Fae, Ethereum!"
3 |
--------------------------------------------------------------------------------
/demos/talk-3/alice:
--------------------------------------------------------------------------------
1 | user-address
--------------------------------------------------------------------------------
/demos/talk-3/bob:
--------------------------------------------------------------------------------
1 | dev-address
--------------------------------------------------------------------------------
/demos/talk-3/bob-fae:
--------------------------------------------------------------------------------
1 | ca5e0c648f45086fb1847ab483a38b84f324748c684328e20f51532553f34a91
2 |
--------------------------------------------------------------------------------
/demos/talk-3/cmd1:
--------------------------------------------------------------------------------
1 | cmd="alice=alice bob=$(/dev/null
24 | }
25 | trap cleanup EXIT
26 |
27 | # Stack stanza
28 | $makedir bin/
29 | $stack build :$exeName :collectPackage
30 | $stack --local-bin-path $PWD/bin install :$exeName
31 | $stack exec collectPackage
32 |
33 | # GHC stanza
34 | ghcDir=$(stack ghc -- --print-libdir)
35 | $makedir lib/
36 | $cp $ghcDir/settings $ghcDir/platformConstants lib/
37 |
38 | # Dynamic loading stanza
39 | awkexp ()
40 | {
41 | field=$1
42 | echo "\$$field ~ /^\// {print \$$field;}"
43 | }
44 | exePath=bin/$exeName
45 | ldlinux=$(ldd $exePath | awk "$(awkexp 1)")
46 | libdeps=$(\
47 | LD_LIBRARY_PATH=$PWD/lib ldd $exePath \
48 | | awk "$(awkexp 3)" \
49 | | grep -v ^$PWD/lib
50 | )
51 | $makedir lib64/
52 | $cp $ldlinux lib64/
53 | for lib in $libdeps; do
54 | libname=$(basename $lib)
55 | newlibname=${libname/%.so.*/.so}
56 | $cp $lib lib
57 | if [[ $libname != $newlibname ]]
58 | then
59 | echoeval ln -sf $libname lib/$newlibname
60 | fi
61 | done
62 |
63 | # Git stanza
64 | git=$(which git)
65 | $cp $git bin/git
66 | gitdeps=$(\
67 | LD_LIBRARY_PATH=$PWD/lib ldd ./bin/git \
68 | | awk "$(awkexp 3)" \
69 | | grep -v ^$PWD/lib\
70 | )
71 | $cp -r $gitdeps lib/
72 |
73 | # Docker coda
74 | $docker build -t teamfae/faeserver .
75 |
--------------------------------------------------------------------------------
/docker/faeServer/etc/faeServer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | imgName=teamfae/faeserver
3 | if [[ -n $FAE_VERSION ]]
4 | then imgName=$imgName:$FAE_VERSION
5 | fi
6 |
7 | faeDir=${FAE_HOME:-~/fae}
8 | [[ -d $faeDir ]] || mkdir -p $faeDir
9 |
10 | FAE_UID=$(id -u)
11 | FAE_GID=$(id -g)
12 |
13 | docker run -it --rm \
14 | --user $FAE_UID:$FAE_GID \
15 | --network host \
16 | --mount type=bind,src=$faeDir,dst=/var/lib/fae/ \
17 | $imgName $@
18 |
--------------------------------------------------------------------------------
/docker/faeServer/tmp/anchor:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsenSysMesh/Fae/3ff023f70fa403e9cef80045907e415ccd88d7e8/docker/faeServer/tmp/anchor
--------------------------------------------------------------------------------
/docker/postTX/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | build
3 | **/*~
4 | **/.gitignore
5 | **/.dockerignore
6 |
--------------------------------------------------------------------------------
/docker/postTX/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM scratch
2 | ENV FAE_HOME=/fae\
3 | LD_LIBRARY_PATH=/lib:/lib64
4 | WORKDIR $FAE_HOME
5 | WORKDIR /txs
6 | ENTRYPOINT ["/bin/postTX"]
7 | ADD . /
8 |
--------------------------------------------------------------------------------
/docker/postTX/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | exeName=postTX
5 | repoName=${exeName,,}
6 | imgName=teamfae/$repoName
7 | cp="cp --preserve=all --dereference"
8 | gai_lib=$( \
9 | ldconfig -p | \
10 | grep libnss | \
11 | awk --field-separator '=>' '$2 ~ /x86_64/ {print $2;}' \
12 | )
13 | gai_etc="/etc/nsswitch.conf /etc/gai.conf"
14 |
15 | awkexp ()
16 | {
17 | field=$1
18 | echo "\$$field ~ /^\// {print \$$field;}"
19 | }
20 |
21 | mkdir -p bin/ lib/ lib64/
22 | stack --local-bin-path $PWD/bin install :$exeName
23 | exePath="./bin/$exeName"
24 | libdeps=$(ldd $exePath | awk "$(awkexp 3)")
25 | ldlinux=$(ldd $exePath | awk "$(awkexp 1)")
26 | $cp $gai_lib $libdeps lib/
27 | $cp $ldlinux lib64/
28 | $cp $gai_etc etc/
29 | { docker build -t $imgName . ; s=$?; } || true
30 | rm -r ./bin ./lib ./lib64/ ./etc/*.conf
31 | exit $s
32 |
--------------------------------------------------------------------------------
/docker/postTX/etc/.dockerignore:
--------------------------------------------------------------------------------
1 | postTX.cmd
2 |
--------------------------------------------------------------------------------
/docker/postTX/etc/postTX.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [[ $# -eq 0 ]]
3 | then echo >&2 "Must supply a transaction spec file name"; exit 1
4 | fi
5 |
6 | imgName=teamfae/posttx
7 | if [[ -n $FAE_VERSION ]]
8 | then imgName=$imgName:$FAE_VERSION
9 | fi
10 |
11 | faeDir=${FAE_HOME:-~/fae}
12 | [[ -d $faeDir ]] || mkdir -p $faeDir
13 |
14 | FAE_UID=$(id -u)
15 | FAE_GID=$(id -g)
16 |
17 | declare -a envlist
18 | n=0
19 | while true; do
20 | case $1 in
21 | -e|--env-list|--env-file)
22 | envlist[$n]=$1
23 | envlist[$n + 1]=$2
24 | let n+=2
25 | shift 2
26 | ;;
27 | -*)
28 | echo >&2 "Got regular option: " $1
29 | echo >&2 "Only environment variable assignments (-e var=val) allowed before the main argument"
30 | exit 1
31 | ;;
32 | *) break ;;
33 | esac
34 | done
35 |
36 | cd $(dirname $1)
37 | txfile=$(basename $1)
38 | shift
39 |
40 | docker run \
41 | --rm \
42 | --interactive --tty \
43 | --network host \
44 | --user $FAE_UID:$FAE_GID \
45 | --mount type=bind,src=$PWD,dst=/txs/,readonly \
46 | --mount type=bind,src=$faeDir,dst=/fae/ \
47 | "${envlist[@]}" $imgName $txfile $@
48 |
49 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/CallHelloWorld2:
--------------------------------------------------------------------------------
1 | body = CallHelloWorld2
2 | inputs
3 | $txID/Body/0/Current = ()
4 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/CallHelloWorld2.hs:
--------------------------------------------------------------------------------
1 | body :: String -> FaeTX String
2 | body = return
3 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/CallHelloWorld3:
--------------------------------------------------------------------------------
1 | body = CallHelloWorld3
2 | inputs
3 | $txID/Body/0/Current = ()
4 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/CallHelloWorld3.hs:
--------------------------------------------------------------------------------
1 | body :: String -> FaeTX String
2 | body = return
3 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/CallHelloWorld4:
--------------------------------------------------------------------------------
1 | body = CallHelloWorld4
2 | inputs
3 | $txID/Body/0/Current = ()
4 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/CallHelloWorld4.hs:
--------------------------------------------------------------------------------
1 | body :: String -> FaeTX String
2 | body = return
3 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld:
--------------------------------------------------------------------------------
1 | body = HelloWorld
2 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld.hs:
--------------------------------------------------------------------------------
1 | body :: FaeTX String
2 | body = return "Hello, world!"
3 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld2:
--------------------------------------------------------------------------------
1 | body = HelloWorld2
2 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld2.hs:
--------------------------------------------------------------------------------
1 | body :: FaeTX ()
2 | body = newContract c where
3 | c :: Contract () String
4 | -- The underscore here means "there is an argument, which is ignored"
5 | c _ = spend "Hello, world!"
6 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld2a:
--------------------------------------------------------------------------------
1 | body = HelloWorld2a
2 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld2a.hs:
--------------------------------------------------------------------------------
1 | body :: FaeTX ()
2 | body = newContract (Echo "Hello, world!")
3 |
4 | data Echo = Echo String deriving (Generic)
5 |
6 | instance ContractName Echo where
7 | type ArgType Echo = ()
8 | type ValType Echo = String
9 | theContract (Echo s) = \_ -> spend s
10 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld3:
--------------------------------------------------------------------------------
1 | body = HelloWorld3
2 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld3.hs:
--------------------------------------------------------------------------------
1 | body :: FaeTX ()
2 | body = newContract (Echo2 "Hello, world!" "Goodbye!")
3 |
4 | data Echo2 = Echo2 String String deriving (Generic)
5 |
6 | instance ContractName Echo2 where
7 | type ArgType Echo2 = ()
8 | type ValType Echo2 = String
9 | theContract (Echo2 s1 s2) = \_ -> do
10 | release s1
11 | spend s2
12 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld4:
--------------------------------------------------------------------------------
1 | body = HelloWorld4
2 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-1/HelloWorld4.hs:
--------------------------------------------------------------------------------
1 | import Control.Monad (forever)
2 |
3 | body :: FaeTX ()
4 | body = newContract (EchoForever "Hello, world!")
5 |
6 | data EchoForever = EchoForever String deriving (Generic)
7 |
8 | instance ContractName EchoForever where
9 | type ArgType EchoForever = ()
10 | type ValType EchoForever = String
11 | theContract (EchoForever s) = \_ -> forever (release s)
12 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/CallEscrow1:
--------------------------------------------------------------------------------
1 | body = CallEscrow1
2 | inputs
3 | $txID/Body/0/Current = "Ryan Reich"
4 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/CallEscrow1.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Transactions.TX$txID
2 |
3 | body :: EscrowID EType -> FaeTX String
4 | body eID = useEscrow [] eID ()
5 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/CallEscrow3:
--------------------------------------------------------------------------------
1 | body = CallEscrow3
2 | inputs
3 | $txID/Body/0/Current = "Ryan Reich"
4 | keys
5 | self = $self
6 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/CallEscrow3.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Transactions.TX$tx1ID
2 |
3 | body :: EscrowID EType -> FaeTX String
4 | body eID = useEscrow [] eID ()
5 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/CallRewardNametag:
--------------------------------------------------------------------------------
1 | body = CallRewardNametag
2 | inputs
3 | $txID/Body/0/Current = ()
4 | keys
5 | self = $self
6 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/CallRewardNametag.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Transactions.TX$txID.Nametag
2 |
3 | body :: Nametag -> FaeTX String
4 | body = checkNametag
5 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/Escrow1:
--------------------------------------------------------------------------------
1 | body = Escrow1
2 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/Escrow1.hs:
--------------------------------------------------------------------------------
1 | type EType = Contract () String
2 |
3 | body :: FaeTX ()
4 | body = newContract c where
5 | c :: Contract String (EscrowID EType)
6 | c name = do
7 | eID <- newEscrow e
8 | spend eID
9 |
10 | where
11 | e :: EType
12 | e _ = spend ("Property of: " ++ name)
13 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/Escrow2:
--------------------------------------------------------------------------------
1 | body = Escrow2
2 | inputs
3 | $txID/Body/0/Current = "Ryan Reich"
4 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/Escrow2.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Transactions.TX$txID
2 |
3 | body :: EscrowID EType -> FaeTX ()
4 | body eID = newContract (Spend eID)
5 |
6 | data Spend a = Spend a deriving (Generic)
7 |
8 | instance (ContractVal a) => ContractName (Spend a) where
9 | type ArgType (Spend a) = ()
10 | type ValType (Spend a) = a
11 | theContract (Spend x) = \_ -> spend x
12 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/Escrow3:
--------------------------------------------------------------------------------
1 | body = Escrow3
2 | inputs
3 | $txID/Body/0/Current = "Ryan Reich"
4 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/Escrow3.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Transactions.TX$txID
2 | import Control.Monad (unless)
3 |
4 | body :: EscrowID EType -> FaeTX ()
5 | body eID = do
6 | us <- signer "self"
7 | newContract (Restricted us eID)
8 |
9 | data Restricted a = Restricted PublicKey a deriving (Generic)
10 |
11 | instance (ContractVal a) => ContractName (Restricted a) where
12 | type ArgType (Restricted a) = ()
13 | type ValType (Restricted a) = a
14 | theContract (Restricted us x) = \_ -> do
15 | them <- signer "self"
16 | unless (us == them) (error "Wrong sender")
17 | spend x
18 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/Nametag.hs:
--------------------------------------------------------------------------------
1 | module Nametag (Nametag, getNametag, checkNametag) where
2 |
3 | import Blockchain.Fae
4 | import Control.Monad (forever)
5 |
6 | data NametagName = NametagName String deriving (Generic)
7 | newtype Nametag = Nametag (EscrowID NametagName) deriving (Generic)
8 |
9 | instance ContractName NametagName where
10 | type ArgType NametagName = ()
11 | type ValType NametagName = String
12 | theContract (NametagName name) = \_ -> forever $ release $ "Property of: " ++ name
13 |
14 | getNametag :: (MonadTX m) => Reward -> String -> m Nametag
15 | getNametag rwd name = do
16 | claimReward rwd
17 | Nametag <$> newEscrow (NametagName name)
18 |
19 | checkNametag :: (MonadTX m) => Nametag -> m String
20 | checkNametag (Nametag eID) = useEscrow [] eID ()
21 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/RewardNametag:
--------------------------------------------------------------------------------
1 | body = RewardNametag
2 | others
3 | - Nametag
4 | reward = True
5 | keys
6 | self = ryan
7 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-2/RewardNametag.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Contracts
2 | import Nametag
3 |
4 | body :: Reward -> FaeTX ()
5 | body rwd = do
6 | nt <- getNametag rwd "Ryan Reich"
7 | deposit nt "self"
8 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/EnterLottery:
--------------------------------------------------------------------------------
1 | body = EnterLottery
2 | keys
3 | self = $self
4 | inputs
5 | $lotteryID/Body/0/Current : Enter
6 | self = self
7 | nametag = $nametagID/Body/0/Current : ()
8 | self = self
9 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/EnterLottery.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Transactions.TX$lotteryID.Lottery
2 |
3 | body :: LotteryResult -> FaeTX (Natural, String)
4 | body EnterResult{..} = return (enterCount, message)
5 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/ExitLottery:
--------------------------------------------------------------------------------
1 | body = ExitLottery
2 | keys
3 | self = $self
4 | inputs
5 | $lotteryID/Body/0/Current : Exit
6 | self = self
7 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/ExitLottery.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Transactions.TX$lotteryID.Lottery
2 |
3 | body :: LotteryResult -> FaeTX String
4 | body WinResult{..} = return $ "Won with " ++ show others ++ " other" ++ plural where
5 | others = winnersCount - 1
6 | plural | others == 1 = ""
7 | | otherwise = "s"
8 | body ExitResult{..} = return $ "Did not win out of " ++ show lotteryLimit ++ " entries"
9 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/Lottery.hs:
--------------------------------------------------------------------------------
1 | module Lottery
2 | (
3 | Lottery(..), LotteryAction(..), LotteryResult(..), Nametags
4 | ) where
5 |
6 | import Blockchain.Fae
7 |
8 | import Control.Monad
9 | import Control.Monad.Trans.State
10 |
11 | import Data.Maybe
12 |
13 | import Nametag
14 |
15 | import Data.Map.Strict (Map)
16 | import qualified Data.Map.Strict as Map
17 |
18 | import Data.Sequence (Seq)
19 | import qualified Data.Sequence as Seq
20 |
21 | data Lottery =
22 | Lottery
23 | {
24 | limit :: Natural,
25 | owner :: PublicKey
26 | }
27 | deriving (Generic)
28 |
29 | data LotteryAction = Enter | Exit deriving (Generic, Read)
30 |
31 | data LotteryResult =
32 | EnterResult
33 | {
34 | enterCount :: !Natural,
35 | message :: !String
36 | } |
37 | ExitResult
38 | {
39 | lotteryLimit :: Natural,
40 | returned :: !Nametags
41 | } |
42 | WinResult
43 | {
44 | winnersCount :: Natural,
45 | returned :: !Nametags
46 | }
47 | deriving (Generic)
48 |
49 | data LotteryState =
50 | RunningState
51 | {
52 | nametags :: Entries,
53 | count :: Natural,
54 | winningCount :: Natural
55 | } |
56 | FinishedState
57 | {
58 | winners :: Entries,
59 | totalWinners :: Natural,
60 | nonWinners :: Entries
61 | }
62 |
63 | type Entries = Map PublicKey (Seq Nametag)
64 | type Nametags = Container (Seq Nametag)
65 |
66 | data LotteryError = NotAuthorized | Finished | NotFound | BothMaps
67 |
68 | instance Show LotteryError where
69 | show NotAuthorized = "This action was not authorized by the lottery owner"
70 | show Finished = "The lottery is finished; no new entries accepted"
71 | show NotFound = "Not an entrant or already exited"
72 | show BothMaps = "Internal error; entrant is in both maps"
73 |
74 | instance Exception LotteryError
75 |
76 | startingState :: LotteryState
77 | startingState = RunningState Map.empty 0 0
78 |
79 | addEntry :: Nametag -> PublicKey -> Entries -> (Natural, Entries)
80 | addEntry tag = Map.alterF $ finish . maybe (Seq.singleton tag) (tag Seq.<|)
81 | where finish set = (fromIntegral $ Seq.length set, Just set)
82 |
83 | takeEntry :: PublicKey -> Entries -> (Maybe Nametags, Entries)
84 | takeEntry = Map.alterF $ (,Nothing) . fmap Container
85 |
86 | splitEntries :: Natural -> Entries -> (Entries, Entries)
87 | splitEntries n = Map.partition ((== n) . fromIntegral . Seq.length)
88 |
89 | instance ContractName Lottery where
90 | type ArgType Lottery = LotteryAction
91 | type ValType Lottery = LotteryResult
92 | theContract Lottery{..} = usingState startingState $ feedback $ \case
93 | Enter -> newEntry limit owner
94 | Exit -> getEntry limit
95 |
96 | newEntry ::
97 | Natural -> PublicKey ->
98 | StateT LotteryState (Fae LotteryAction LotteryResult) LotteryResult
99 | newEntry limit owner = do
100 | signedBy <- signer "owner"
101 | unless (signedBy == owner) $ throw NotAuthorized
102 |
103 | lotteryState <- get
104 | case lotteryState of
105 | FinishedState{} -> throw Finished
106 | RunningState{..} -> do
107 | nametag <- material "nametag"
108 | message <- checkNametag nametag
109 | entrant <- signer "self"
110 | let (enterCount, nametags') = addEntry nametag entrant nametags
111 | count' = count + 1
112 | winningCount'
113 | | enterCount > winningCount = enterCount
114 | | otherwise = winningCount
115 | put $
116 | if count' == limit
117 | then let (winners, nonWinners) = splitEntries winningCount' nametags' in
118 | FinishedState{totalWinners = fromIntegral $ Map.size winners, ..}
119 | else RunningState
120 | {
121 | nametags = nametags',
122 | count = count',
123 | winningCount = winningCount'
124 | }
125 | return $! EnterResult{..}
126 |
127 | getEntry ::
128 | Natural -> StateT LotteryState (Fae LotteryAction LotteryResult) LotteryResult
129 | getEntry lotteryLimit = do
130 | entrant <- signer "self"
131 | lotteryState <- get
132 | (result, newState) <- case lotteryState of
133 | RunningState{..} -> do
134 | let (returnedM, nametags') = takeEntry entrant nametags
135 | returned = fromMaybe (throw NotFound) returnedM
136 | result = ExitResult{..}
137 | count' = count - 1
138 | winningCount'
139 | | count < winningCount = winningCount
140 | | otherwise = fromIntegral $ maximum $ fmap Seq.length nametags'
141 | newState =
142 | RunningState
143 | {
144 | nametags = nametags',
145 | count = count',
146 | winningCount = winningCount'
147 | }
148 | return (result, newState)
149 | FinishedState{..} -> do
150 | let (wSetM, winners') = takeEntry entrant winners
151 | (nwSetM, nonWinners') = takeEntry entrant nonWinners
152 | case (wSetM, nwSetM) of
153 | (Nothing, Nothing) -> throw NotFound
154 | (Just _, Just _) -> throw BothMaps
155 | (Just returned, _) -> do
156 | let result = WinResult{winnersCount = totalWinners, ..}
157 | newState = lotteryState{winners = winners'}
158 | return (result, newState)
159 | (_, Just returned) -> do
160 | let result = ExitResult{..}
161 | newState = lotteryState{nonWinners = nonWinners'}
162 | return (result, newState)
163 | put newState
164 | return $! result
165 |
166 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/Nametag.hs:
--------------------------------------------------------------------------------
1 | ../tutorial-2/Nametag.hs
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/NewLottery:
--------------------------------------------------------------------------------
1 | body = NewLottery
2 | others
3 | - Nametag
4 | - Lottery
5 | keys
6 | self = $self
7 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/NewLottery.hs:
--------------------------------------------------------------------------------
1 | import Lottery
2 |
3 | body :: FaeTX ()
4 | body = do
5 | owner <- signer "self"
6 | newContract $ Lottery 5 owner
7 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/RewardNametag:
--------------------------------------------------------------------------------
1 | body = RewardNametag
2 | reward = True
3 | keys
4 | self = $self
5 |
--------------------------------------------------------------------------------
/documentation/tutorial/tutorial-3/RewardNametag.hs:
--------------------------------------------------------------------------------
1 | import Blockchain.Fae.Contracts
2 | import Blockchain.Fae.Transactions.TX$nametagID.Nametag
3 |
4 | body :: Reward -> FaeTX ()
5 | body rwd = do
6 | nt <- getNametag rwd "Ryan Reich"
7 | deposit nt "self"
8 |
--------------------------------------------------------------------------------
/fae.cabal:
--------------------------------------------------------------------------------
1 | name: fae
2 | -- My take on semantic versioning:
3 | -- a.*.*.* -> (a + 1).0.0.0 : Breaking API changes
4 | -- *.b.*.* -> *.(b + 1).0.0 : Compatible but major new features
5 | -- *.*.c.* -> *.*.(c + 1).0 : Bugfixes that may change the (incorrect) behavior of existing code
6 | -- or minor, compatible new features
7 | -- *.*.*.d -> *.*.*.(d + 1) : Adding omitted exports, GHC extensions, cosmetic stuff, demos ...
8 | version: 2.0.0.0
9 | -- synopsis:
10 | -- description:
11 | homepage: https://github.com/Consensys/Fae
12 | license: BSD3
13 | license-file: LICENSE
14 | author: Ryan Reich
15 | maintainer: ryan.reich@consensys.net
16 | copyright: MIT
17 | category: none
18 | build-type: Simple
19 | extra-source-files: README.md
20 | cabal-version: >=1.10
21 |
22 | library
23 | hs-source-dirs: src, common
24 | exposed-modules:
25 | Blockchain.Fae
26 | Blockchain.Fae.FrontEnd
27 | Blockchain.Fae.Internal
28 | Blockchain.Fae.Contracts
29 | Blockchain.Fae.Currency
30 | other-modules:
31 | Blockchain.Fae.Internal.Contract
32 | Blockchain.Fae.Internal.Crypto
33 | Blockchain.Fae.Internal.Exceptions
34 | Blockchain.Fae.Internal.GenericInstances
35 | Blockchain.Fae.Internal.IDs
36 | Blockchain.Fae.Internal.IDs.Types
37 | Blockchain.Fae.Internal.Messages,
38 | Blockchain.Fae.Internal.Monitors,
39 | Blockchain.Fae.Internal.TXSummary
40 | Blockchain.Fae.Internal.Reward
41 | Blockchain.Fae.Internal.Serialization
42 | Blockchain.Fae.Internal.Storage
43 | Blockchain.Fae.Internal.Suspend
44 | Blockchain.Fae.Internal.Transaction
45 | Blockchain.Fae.Internal.TX
46 | Common.Lens
47 | build-depends:
48 | base >= 4.10 && < 5,
49 | base16-bytestring,
50 | bytestring,
51 | cereal,
52 | containers,
53 | cryptonite >= 0.24,
54 | deepseq,
55 | exceptions,
56 | filepath,
57 | hint,
58 | lens,
59 | memory,
60 | mtl,
61 | pretty,
62 | template-haskell,
63 | text,
64 | transformers,
65 | unix,
66 | vector
67 | default-language: Haskell2010
68 | default-extensions:
69 | BangPatterns
70 | ConstraintKinds
71 | DefaultSignatures
72 | DeriveGeneric
73 | ExistentialQuantification
74 | FlexibleContexts
75 | FlexibleInstances
76 | FunctionalDependencies
77 | GeneralizedNewtypeDeriving
78 | LambdaCase
79 | MultiParamTypeClasses
80 | MultiWayIf
81 | NamedFieldPuns
82 | OverloadedStrings
83 | PatternGuards
84 | RankNTypes
85 | RecordWildCards
86 | ScopedTypeVariables
87 | StandaloneDeriving
88 | TupleSections
89 | TypeApplications
90 | TypeFamilies
91 | TypeFamilyDependencies
92 | TypeOperators
93 |
94 | executable faeServer
95 | buildable: True
96 | hs-source-dirs: bin, common
97 | main-is: FaeServer.hs
98 | ghc-options: -dynamic -threaded -rtsopts -with-rtsopts=-N
99 | other-modules:
100 | Common.JSON
101 | Common.Lens
102 | Common.ProtocolT
103 | FaeServer.App
104 | FaeServer.Args
105 | FaeServer.Concurrency
106 | FaeServer.Fae
107 | FaeServer.Faeth
108 | FaeServer.Git
109 | FaeServer.History
110 | FaeServer.Modules
111 | build-depends:
112 | aeson,
113 | base,
114 | base16-bytestring,
115 | bytestring,
116 | cereal,
117 | containers,
118 | deepseq,
119 | directory,
120 | fae,
121 | filepath,
122 | http-types,
123 | lens,
124 | lifted-base,
125 | mtl,
126 | pretty,
127 | process,
128 | stm,
129 | template-haskell,
130 | text,
131 | transformers,
132 | unix,
133 | wai,
134 | wai-extra,
135 | warp,
136 | websockets,
137 | zlib
138 | default-language: Haskell2010
139 | default-extensions:
140 | DeriveGeneric
141 | FlexibleContexts
142 | FlexibleInstances
143 | GeneralizedNewtypeDeriving
144 | LambdaCase
145 | MultiParamTypeClasses
146 | NamedFieldPuns
147 | OverloadedStrings
148 | RankNTypes
149 | RecordWildCards
150 | ScopedTypeVariables
151 | TupleSections
152 | TypeApplications
153 |
154 | executable postTX
155 | buildable: True
156 | hs-source-dirs: bin, common
157 | main-is: PostTX.hs
158 | other-modules:
159 | Common.JSON
160 | Common.Lens
161 | Common.ProtocolT
162 | PostTX.Args
163 | PostTX.Faeth
164 | PostTX.ImportExport
165 | PostTX.Keys
166 | PostTX.Network
167 | PostTX.Parser
168 | PostTX.Submit
169 | PostTX.TXSpec
170 | PostTX.View
171 | build-depends:
172 | aeson,
173 | base,
174 | base16-bytestring,
175 | bytestring,
176 | cereal,
177 | containers,
178 | deepseq,
179 | directory,
180 | fae,
181 | filepath,
182 | haskeline,
183 | http-client,
184 | lens,
185 | megaparsec,
186 | mtl,
187 | pretty,
188 | process,
189 | template-haskell,
190 | text,
191 | time,
192 | websockets,
193 | zlib
194 | default-language: Haskell2010
195 | default-extensions:
196 | DeriveGeneric
197 | FlexibleContexts
198 | FlexibleInstances
199 | GeneralizedNewtypeDeriving
200 | RankNTypes
201 | RecordWildCards
202 | MultiParamTypeClasses
203 | NamedFieldPuns
204 | OverloadedStrings
205 | ScopedTypeVariables
206 | TupleSections
207 | TypeApplications
208 |
209 | executable collectPackage
210 | buildable: True
211 | hs-source-dirs: tools, common
212 | main-is: CollectPackage.hs
213 | other-modules:
214 | Common.Lens
215 | PackageInfo
216 | build-depends:
217 | base,
218 | bytestring,
219 | Cabal,
220 | containers,
221 | directory,
222 | filepath,
223 | ghc-boot,
224 | lens,
225 | mtl,
226 | process,
227 | template-haskell,
228 | unix
229 | default-language: Haskell2010
230 | default-extensions:
231 | FlexibleContexts
232 | FlexibleInstances
233 | MultiParamTypeClasses
234 | NamedFieldPuns
235 | RankNTypes
236 | RecordWildCards
237 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 | fae-2.0.0.0