cowprotocol / watch-tower

Conducting the music of Composable CoWs 🎶🐮
GNU General Public License v3.0
7 stars 2 forks source link
cow-protocol cowswap erc1271 erc20 ethersjs

Watch-Tower for Programmatic Orders 🐄🤖

Overview

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.

Deployment

If running your own watch-tower instance, you will need the following:

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.

Docker

The preferred method of deployment is using docker. The watch-tower is available as a docker image on GitHub. The tags available are:

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.

DAppNode

For DAppNode, the watch-tower is available as a package. This package is held in a separate repository.

Running locally

Requirements

CLI

# Install dependencies
yarn
# Run watch-tower
yarn cli run --config-path ./config.json

Architecture

Events

The watch-tower monitors the following events:

When a new event is discovered, the watch-tower will:

  1. Fetch the conditional order(s) from the blockchain.
  2. Post the discrete order(s) to the CoW Protocol OrderBook API.

Storage (registry)

The watch-tower stores the following state:

As orders expire, or are cancelled, they are removed from the registry to conserve storage space.

Database

The chosen architecture for the storage is a NoSQL (key-value) store. The watch-tower uses the following:

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.

Schema

The following keys are used:

Logging

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

API Server

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:

You 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.

Developers

Requirements

Local development

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

Testing

To run the tests:

yarn test

Linting / Formatting

# To lint the code
yarn lint
# To fix linting errors
yarn lint:fix
# To format the code
yarn fmt

Building the docker image

To build the docker image:

docker build -t watch-tower .