In previous posts, I described how I hosted a zcashd node and added a litewalletd service on top of that. In this post, I describe why and how I switched the node from zcashd to “Zebra”, or zebrad.

Why switch to zebra?

Zebra has much better performance than zcashd. Bootstrapping zebra from nothing took about 3 days. Compare this with zcashd, which took 7+ days just to reindex an already downloaded blockchain.

Zebra’s startup time is perhaps 30 seconds where zcashd required 2+ minutes as well in my unscientific testing.

Zebra’s lack of a private wallet is both a limitation and a security benefit. As a node that contributes to consensus and can act as a backing server for litewalletd, it contributes to the general resiliency of the network. Given it does this best when it has an open port exposed on the Internet, the lack of a private wallet can bring a sense of better security.

Zebra isn’t RTW quality, but as of this writing it’s at “RC 2”, so that’s pretty good. I haven’t noticed any quality issues, myself. It’s not a drop-in replacement for zcashd when you need a wallet with your node, but for other use cases it seems to work well.

Given zebra doesn’t include a wallet, if you have your own wallet, setting up a litewalletd service and using a lite wallet with it to create transactions is the recommended approach. The lite wallet will create and sign the transaction, send it to your litewallet server, which well forward it to your zebra node, which will relay it across the zcash node network for inclusion in the mempool and ultimately in the next mined block. You don’t need to host your own zcash node or litewallet server for this, of course, as public litewallet servers exist that are configured to use by default in your lite wallet apps already. But if you see value in hosting your own services, this blog post can help you set it up.

How to host zebra

To start, I created a new docker-compose.yml file to define both my zebra and litewallet services.

Create this docker-compose.yml file in a new zebra directory:

version: '3.1'
services:
  zcashd:
    build: ./zebrad
    restart: unless-stopped
    ports:
    - 8232:8232 # RPC (security sensitive)
    - 8233:8233 # Zcash network (public)
    volumes:
    - zebrad_cache:/var/cache/zebrad-cache
    - zebra_zebrad_conf:/etc/zebra
  litewalletd:
    image: electriccoinco/lightwalletd
    depends_on:
      zcashd:
        condition: service_healthy
    command: >
      --grpc-bind-addr 0.0.0.0:9067
      --tls-cert /srv/lightwalletd/conf/fullchain1.pem
      --tls-key /srv/lightwalletd/conf/privkey1.pem
      --zcash-conf-path /srv/lightwalletd/conf/zcash.conf
      --data-dir /srv/lightwalletd/db_volume
      --log-file /dev/stdout
    restart: unless-stopped
    ports:
    - 9067:9067
    volumes:
    - litewalletd:/srv/lightwalletd/db_volume
    - zcash_litewalletd_conf:/srv/lightwalletd/conf
volumes:
  zebra_zebrad_conf:
    external: true # zebrad.toml must be placed first
  zebrad_cache:
  litewalletd:
  zcash_litewalletd_conf:
    external: true # zcash.conf must be placed first

Much of the content of this file was presented in my prior two posts linked at the top of this post. I’ll just call out that I didn’t bother renaming the zcashd service to zebra, but you can do that so long as you rename it in other files that they appear as well.

The open ports are the same as those specified for zcashd and litewalletd in my prior posts, so these docker containers will not start while the older pair are running. But you may not need the older pair at all anymore.

Adding a health check to the zebra service

The zebra docker image doesn’t include a health check. The health check is useful for Docker’s status reporting, and it allows docker-compose to delay the litewalletd launch until zebra is ready to serve requests.

Create a zebrad/Dockerfile file that adds HEALTHCHECK to zebra’s stock docker image:

FROM zfnd/zebra:1.0.0-rc.2

RUN apt-get update \
 && apt-get -y install curl \
 && rm -rf /var/lib/apt/lists/*

HEALTHCHECK --interval=15s --start-period=3m CMD curl --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }' -H 'content-type: application/json' http://127.0.0.1:8232/ || exit 1

The interesting bits about this Dockerfile are first that we have to install curl in order to use it in the healthcheck. The base image does not include this tool. With zcashd, we could use the included zcash-cli tool, but that tool is not included in the zebra image. I suspect it would be incompatible anyway, which gets me to the next interesting point on this file.

The curl command originally came from the RPC docs as a sample, but it didn’t work out of the box. I had to change the Content-Type header from text/plain to application/json. I also had to remove the --user switch to avoid the interactive prompt for a password, and zebra doesn’t need it anyway.

Configure zebra to respond to RPC

Zebra comes with reasonable defaults, but we still need a configuration file to ensure that its RPC port binds to the 0.0.0.0 IP address so that our litewalletd container can connect to it.

We also need to specify where the zebrad service will write the downloaded blockchain and indexes. This location will coincide with where we mount another docker volume that will persist beyond the lifetime of any particular container.

Create a zebrad.toml file in the root of the zebra_zebrad_conf docker volume:

[network]
network = 'Mainnet'
[consensus]
checkpoint_sync = true
[state]
cache_dir = '/zebrad-cache'
[rpc]
listen_addr = '0.0.0.0:8232'
parallel_cpu_threads = 0
[metrics]
#endpoint_addr = '127.0.0.1:9999'
[tracing]
#endpoint_addr = '127.0.0.1:3000'

Configuring litewalletd to find our zebra RPC service

Litewallet needs to know the hostname of our zebra service. For this, we need a zcash.conf file that litewalletd will read.

Create the zcash.conf file in the root of the zcash_litewalletd_conf docker volume:

rpcbind=zcashd
rpcuser=nomatter
rpcpassword=nomatter

I believe the rpcuser and rpcpassword fields can be anything because zebra ignores it.

Note I used zcashd as the host name to match the service name as specified in my docker-compose.yml file. I didn’t bother renaming this to zebrad. If you choose to change it, be sure to change it consistently everywhere.