diff --git a/.gitignore b/.gitignore index 8c3485a..d9a076b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /.purs* /.psa* /abis/**/*.json +./plasma.toml /purs/src/Plasma/Contracts *.swp chanterelle.js diff --git a/.travis.yml b/.travis.yml index fe3ffb3..5442ddc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,8 @@ install: # install plasma - plasmad init - - make prepare-plasma + - make write-plasma-toml + - cp plasma.toml $HOME/.plasmad/config/plasma.toml - mkdir -p $HOME/.plasmacli - ln -s $HOME/.plasmad/config/plasma.toml $HOME/.plasmacli/plasma.toml - plasmad start & diff --git a/Makefile b/Makefile index 65a6f95..6bba91b 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,12 @@ export FINALIZED_PERIOD ?= 18 NODE_URL ?= http://localhost:8545 +# plasma config vars, need to supply operator private key +PLASMA_CONFIG_DESTINATION ?= ./plasma.toml +IS_OPERATOR ?= false +COMMITMENT_RATE ?= 2 +PLASMA_ARTIFACT ?= ./abis/PlasmaMVP.json + help: ## Ask for help! @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -24,8 +30,8 @@ compile-contracts: build-purs ## Compile all contracts from dapp/contracts and w generate-genesis: ## Generate a cliquebait.json file chanterelle genesis --input ./cliquebait.json --output cliquebait-generated.json -prepare-plasma: - sed -i "/ethereum_plasma_contract_address = /c\ethereum_plasma_contract_address = `cat abis/PlasmaMVP.json | jq \".networks[].address\"`" "$(HOME)/.plasmad/config/plasma.toml" +write-plasma-toml: ## write the plasma config to the plasma.toml file + pulp run --jobs 8 --src-path purs/src -m Plasma.Config.TOMLMain test-plasma: ## Run the plasma e2e NODE_URL=$(NODE_URL) pulp test --src-path purs/src --test-path purs/test -m Spec.Main diff --git a/README.md b/README.md index 5260684..d53e3da 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,11 @@ We now provide a super simple way to run the test-suite. It only depends on dock ``` > make install -> docker-compose up -d +> docker-compose up -d cliquebait +> make deploy-contracts +> make write-plasma-toml > sleep 5 +> docker-compose up -d plasma > make deploy-and-test > docker-compose down ``` @@ -108,6 +111,9 @@ I[2046-04-02|14:05:54.388] binding to contract address 0xe545eaf693277ead76f5d9b ``` +## Generating a plasma.toml config file +You can generate a plasma.toml config file by running `make write-plasma-toml`. If you would like to create a config for an `Operator`, set `IS_OPERATOR=true OPERATOR_PRIVATE_KEY= make write-plasma-toml`. + ## WARNINGS We are still trying to figure out how to automate config/setup so that it's the same everywhere (test config vs $HOME/.plasmad/config/plasma.toml etc). Until that's done, you should check this warnings list/ update it with new warnings when you find them. 1. When running `make plasma-test` you need to supply the root chain plasma contract with environment variable `PLASMA_ADDRESS`. diff --git a/bower.json b/bower.json index fdebede..9a4d201 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,8 @@ "purescript-web3": "^1.0.0", "purescript-web3-generator": "^1.0.0", "purescript-chanterelle": "f-o-a-m/chanterelle#v2.0.2", - "purescript-servant": "f-o-a-m/purescript-servant#master" + "purescript-servant": "f-o-a-m/purescript-servant#master", + "purescript-heterogeneous": "0.2.0" }, "devDependencies": { "purescript-psci-support": "^4.0.0", diff --git a/docker-compose.yml b/docker-compose.yml index 73c2a15..322acaf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,3 +8,5 @@ services: image: docker.kube-system.svc.cluster.local/foam/plasma-mvp-sidechain:latest ports: - "1317:1317" + volumes: + - ./plasma.toml:/root/.plasmad/config/plasma.toml diff --git a/purs/src/Plasma/Config/TOML.purs b/purs/src/Plasma/Config/TOML.purs new file mode 100644 index 0000000..14086e5 --- /dev/null +++ b/purs/src/Plasma/Config/TOML.purs @@ -0,0 +1,219 @@ +module Plasma.Config.TOML + ( PlasmaConfig + , TimeInterval + , writePlasmaConfig + , makeConfigFromEnvironment + ) where + +import Prelude + +import Chanterelle.Internal.Deploy (readDeployAddress) +import Chanterelle.Internal.Logging (logDeployError) +import Control.Error.Util (note) +import Control.Monad.Except (runExceptT) +import Data.Array ((:)) +import Data.Bifunctor (lmap) +import Data.Either (Either(..)) +import Data.Int (fromString) +import Data.Maybe (Maybe(..), maybe) +import Data.String (joinWith) +import Data.Symbol (class IsSymbol, SProxy, reflectSymbol) +import Data.Tuple (Tuple(..)) +import Effect.Aff (Aff) +import Effect.Class (class MonadEffect, liftEffect) +import Effect.Class.Console as C +import Effect.Exception (throw) +import Heterogeneous.Folding (class FoldingWithIndex, hfoldlWithIndex) +import Network.Ethereum.Core.BigNumber (parseBigNumber, decimal) +import Network.Ethereum.Core.HexString (unHex) +import Network.Ethereum.Core.Signatures (PrivateKey, unPrivateKey, mkPrivateKey) +import Network.Ethereum.Web3 (Address, Provider, runWeb3, httpProvider, mkHexString, mkAddress) +import Network.Ethereum.Web3.Api (net_version) +import Node.Encoding (Encoding(UTF8)) +import Node.FS.Aff (writeTextFile) +import Node.Process as NP +import Partial.Unsafe (unsafeCrashWith) + +{- + +EXAMPLE + +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### ethereum config options ##### +# Boolean specifying if this node is the operator of the plasma contract +is_operator = "true" + +# Hex encoded private key +# Used to sign eth transactions interacting with the contract +ethereum_operator_privatekey = "ac4d28b45202507d3475761c1e9277d3efc5a8ae8cfd690d979083ce241dd6d8" + +# Ethereum plasma contract address +ethereum_plasma_contract_address = "0x05df3e846693afefce3dd9b6701787ad1d6cd9b1" + +# Plasma block commitment rate. i.e 1m30s, 1m, 1h, etc. +plasma_block_commitment_rate = "2s" + +# Node URL for eth client +ethereum_nodeurl = "http://localhost:8545" + +# Number of Ethereum blocks until a submitted block header is considered final +ethereum_finality = "2" + + +-} + +withQuotes :: String -> String +withQuotes s = "\""<> s <> "\"" + +newtype TimeInterval = TimeInterval Int + +class TomlValue a where + toTomlValue :: a -> String + +instance tomlValueInt :: TomlValue Int where + toTomlValue = show >>> withQuotes + +instance tomlValueString :: TomlValue String where + toTomlValue = show + +instance tomlValueTimeInterval :: TomlValue TimeInterval where + toTomlValue (TimeInterval n) = withQuotes $ show n <> "s" + +instance tomlValuePrivateKey :: TomlValue PrivateKey where + toTomlValue = unPrivateKey >>> unHex >>> withQuotes + +instance tomlValueAddress :: TomlValue Address where + toTomlValue = show >>> withQuotes + +instance tomlValueBool :: TomlValue Boolean where + toTomlValue = show >>> withQuotes + +instance tomlValueMaybe :: TomlValue a => TomlValue (Maybe a) where + toTomlValue = maybe (toTomlValue "") toTomlValue + +type PlasmaConfig = + { is_operator :: Boolean + , ethereum_operator_privatekey :: Maybe PrivateKey + , ethereum_plasma_contract_address :: Address + , plasma_block_commitment_rate :: TimeInterval + , ethereum_nodeurl :: String + , ethereum_finality :: Int + } + +-------------------------------------------------------------------------------- +-- Generate toml file +-------------------------------------------------------------------------------- + +type TomlFile = Array (Tuple String String) + +data TomlEntry = TomlEntry + +instance tomlEntry :: (TomlValue a, IsSymbol sym) => FoldingWithIndex TomlEntry (SProxy sym) (Array (Tuple String String)) a (Array (Tuple String String)) where + foldingWithIndex TomlEntry prop acc a = Tuple (reflectSymbol prop) (toTomlValue a) : acc + +-- | Using the toTomlValue instances, fold over the PlasmaConfig record to build the toml file as a string +templateTomlFile :: PlasmaConfig -> String +templateTomlFile = joinWith "\n" <<< map (\(Tuple k v) -> k <> " = " <> v) <<< toTomlFile + where + toTomlFile :: PlasmaConfig -> TomlFile + toTomlFile = hfoldlWithIndex TomlEntry ([] :: TomlFile) + +-- | read a bunch env vars and dynamically try to figure out where the plasma contract address is coming from. +makeConfigFromEnvironment :: Aff PlasmaConfig +makeConfigFromEnvironment = do + isOperator <- requireEnvVar "IS_OPERATOR" asBoolean + operatorKey <- if isOperator + then Just <$> requireEnvVar "OPERATOR_PRIVATE_KEY" asPrivateKey + else pure Nothing + commitmentRate <- requireEnvVar "COMMITMENT_RATE" asInt + nodeURL <- requireEnvVar "NODE_URL" asString + finality <- requireEnvVar "FINALIZED_PERIOD" asInt + plasmaAddress <- discoverPlasmaContractAddress + pure { is_operator: isOperator + , ethereum_operator_privatekey: operatorKey + , ethereum_plasma_contract_address: plasmaAddress + , plasma_block_commitment_rate: TimeInterval commitmentRate + , ethereum_nodeurl: nodeURL + , ethereum_finality: finality + } + +-- | write the plasma config to a file. +writePlasmaConfig :: PlasmaConfig -> Aff Unit +writePlasmaConfig cfg = do + configDest <- requireEnvVar "PLASMA_CONFIG_DESTINATION" asString + let content = templateTomlFile cfg + C.log ("Writing plasma config to " <> configDest) + writeTextFile UTF8 configDest content + +-------------------------------------------------------------------------------- +-- | ConfigUtils +-------------------------------------------------------------------------------- + +data AddressSource = + FromChanterelleArtifactFile Provider String + | AddressLiteral Address + + +discoverPlasmaContractAddress :: Aff Address +discoverPlasmaContractAddress = do + mAddr <- liftEffect (NP.lookupEnv "PLASMA_ADDRESS") + case mAddr of + Nothing -> do + C.log "PLASMA_ADDRESS env var not found, trying to read from file..." + fp <- requireEnvVar "PLASMA_ARTIFACT" asString + provider <- requireEnvVar "NODE_URL" asString >>= liftEffect <<< httpProvider + getPlasmaContractAddress (FromChanterelleArtifactFile provider fp) + Just addr -> case mkHexString addr >>= mkAddress of + Nothing -> liftEffect $ throw ("Error parsing PLASMA_ADDRESS: " <> show addr) + Just addr' -> getPlasmaContractAddress (AddressLiteral addr') + +getPlasmaContractAddress :: AddressSource -> Aff Address +getPlasmaContractAddress (AddressLiteral addr) = do + C.log "Plasma Contract Address given as literal" + pure addr +getPlasmaContractAddress (FromChanterelleArtifactFile provider filepath) = do + C.log $ "Taking Plasma Contract Address from file: " <> filepath + enId <- runWeb3 provider $ net_version + case lmap show enId >>= \nid -> note ("Couldn't parse network version: " <> nid) $ parseBigNumber decimal nid of + Left e -> liftEffect $ throw e + Right nId -> do + eRes <- runExceptT $ readDeployAddress filepath nId + case eRes of + Left e -> do + logDeployError e + liftEffect $ throw "error" + Right res -> pure res + + +type FromStringReader a = String -> Either String a + +requireEnvVar :: forall m a. MonadEffect m => String -> FromStringReader a -> m a +requireEnvVar var parse = liftEffect $ do + mval <- NP.lookupEnv var + case mval of + Nothing -> unsafeCrashWith $ "Must specify " <> show var <> " env var" + Just val -> case parse val of + Left err -> unsafeCrashWith + $ "Failed to parse env var: " <> show var + <> ", containing: " <> show val + <> ", with error: " <> err + Right a -> pure a + +asBoolean :: FromStringReader Boolean +asBoolean "true" = Right true +asBoolean "false" = Right false +asBoolean _ = Left "Invalid Boolean" + +asInt :: FromStringReader Int +asInt = fromString >>> note "invalid Int" + +asString :: FromStringReader String +asString = pure + +asPrivateKey :: FromStringReader PrivateKey +asPrivateKey = (mkHexString >=> mkPrivateKey) >>> note "invalid PrivateKey" + +asAddress :: FromStringReader Address +asAddress = (mkHexString >=> mkAddress) >>> note "invalid Address" diff --git a/purs/src/Plasma/Config/TOMLMain.purs b/purs/src/Plasma/Config/TOMLMain.purs new file mode 100644 index 0000000..c2afe60 --- /dev/null +++ b/purs/src/Plasma/Config/TOMLMain.purs @@ -0,0 +1,11 @@ +module Plasma.Config.TOMLMain (main) where + +import Prelude +import Plasma.Config.TOML (makeConfigFromEnvironment, writePlasmaConfig) +import Effect (Effect) +import Effect.Aff (launchAff_) + +main :: Effect Unit +main = launchAff_ do + cfg <- makeConfigFromEnvironment + writePlasmaConfig cfg