The programmatic order framework requires a watch-tower to monitor the blockchain for new orders, and to post them to the CoW Protocol OrderBook
API. The watch-tower is a standalone application that can be run locally as a script for development, or deployed as a docker container to a server, or dappnode.
If running your own watch-tower instance, you will need the following:
OrderBook
API.CAUTION: Conditional order types may consume considerable RPC calls.
NOTE: deployment-block
refers to the block number at which the ComposableCoW
contract was deployed to the respective chain. This is used to optimise the watch-tower by only fetching events from the blockchain after this block number. Refer to Deployed Contracts for the respective chains.
NOTE: The pageSize
option is used to specify the number of blocks to fetch from the blockchain when querying historical events (eth_getLogs
). The default is 5000
, which is the maximum number of blocks that can be fetched in a single request from Infura. If you are running the watch-tower against your own RPC, you may want to set this to 0
to fetch all blocks in one request, as opposed to paging requests.
The preferred method of deployment is using docker
. The watch-tower is available as a docker image on GitHub. The tags available are:
latest
- the latest version of the watch-tower.vX.Y.Z
- the version of the watch-tower.main
- the latest version of the watch-tower on the main
branch.pr-<PR_NUMBER>
- the latest version of the watch-tower on the PR.As an example, to run the latest version of the watch-tower via docker
:
docker run --rm -it \
-v "$(pwd)/config.json.example:/config.json" \
ghcr.io/cowprotocol/watch-tower:latest \
run \
--config-path /config.json
NOTE: See the example config.json.example
for an example configuration file.
For DAppNode, the watch-tower is available as a package. This package is held in a separate repository.
node
(>= v16.18.0
)yarn
# Install dependencies
yarn
# Run watch-tower
yarn cli run --config-path ./config.json
The watch-tower monitors the following events:
ConditionalOrderCreated
- emitted when a single new conditional order is created.MerkleRootSet
- emitted when a new merkle root (ie. n
conditional orders) is set for a safe.When a new event is discovered, the watch-tower will:
OrderBook
API.The watch-tower stores the following state:
As orders expire, or are cancelled, they are removed from the registry to conserve storage space.
The chosen architecture for the storage is a NoSQL (key-value) store. The watch-tower uses the following:
level
$PWD/database
LevelDB
is chosen it it provides ACID guarantees, and is a simple key-value store. The watch-tower uses the level
package to provide a simple interface to the database. All writes are batched, and if a write fails, the watch-tower will throw an error and exit. On restarting, the watch-tower will attempt to re-process from the last block that was successfully indexed, resulting in the database becoming eventually consistent with the blockchain.
The following keys are used:
LAST_PROCESSED_BLOCK
- the last block (number, timestamp, and hash) that was processed by the watch-tower.CONDITIONAL_ORDER_REGISTRY
- the registry of conditional orders by safe.CONDITIONAL_ORDER_REGISTRY_VERSION
- the version of the registry. This is used to migrate the registry when the schema changes.LAST_NOTIFIED_ERROR
- the last time an error was notified via Slack. This is used to prevent spamming the slack channel.To control logging level, you can set the LOG_LEVEL
environment variable with one of the following values: TRACE
, DEBUG
, INFO
, WARN
, ERROR
:
LOG_LEVEL=WARN
Additionally, you can enable module specific logging by specifying the log level for the module name:
# Enable logging for an specific module (chainContext in this case)
LOG_LEVEL=chainContext=INFO
# Of-course, you can provide the root log level, and the override at the same time
# - All loggers will have WARN level
# - Except the "chainContext" which will have INFO level
LOG_LEVEL=WARN,chainContext=INFO
You can specify more than one overrides
LOG_LEVEL=chainContext=INFO,_placeOrder=TRACE
The module definition is actually a regex pattern, so you can make more complex definitions:
# Match a logger using a pattern
# Matches: chainContext:processBlock:100:30212964
# Matches: chainContext:processBlock:1:30212964
# Matches: chainContext:processBlock:5:30212964
LOG_LEVEL=chainContext:processBlock:(\d{1,3}):(\d*)$=DEBUG
# Another example
# Matches: chainContext:processBlock:100:30212964
# Matches: chainContext:processBlock:1:30212964
# But not: chainContext:processBlock:5:30212964
LOG_LEVEL=chainContext:processBlock:(100|1):(\d*)$=DEBUG
Combine all of the above to control the log level of any modules:
LOG_LEVEL="WARN,commands=DEBUG,^checkForAndPlaceOrder=WARN,^chainContext=INFO,_checkForAndPlaceOrder:1:=INFO" yarn cli
Commands that run the watch-tower in a watching mode, will also start an API server. By default the API server will start on port 8080
. You can change the port using the --api-port <apiPort>
CLI option.
The server exposes automatically:
http://localhost:8080/api/dump/:chainId
e.g. http://localhost:8080/api/dump/1You can prevent the API server from starting by setting the --disable-api
flag for the run
command.
The /api/version
endpoint, exposes the information in the package.json
. This can be helpful to identify the version of the watch-tower. Additionally, for environments using docker
, the environment variable DOCKER_IMAGE_TAG
can be used to specify the Docker image tag used.
node
(>= v16.18.0
)yarn
npm
It is recommended to test against the Goerli testnet. To run the watch-tower:
# Install dependencies
yarn
# Run watch-tower
yarn cli run --config-path ./config.json
To run the tests:
yarn test
# To lint the code
yarn lint
# To fix linting errors
yarn lint:fix
# To format the code
yarn fmt
To build the docker image:
docker build -t watch-tower .