Run Chainhook as a service with a Stacks node

Learn how to run Chainhook as a service with a Stacks node to evaluate Stacks blocks against your predicates.

You can run Chainhook as a service to evaluate Stacks blocks against your predicates. You can also dynamically register new predicates by enabling the predicates registration API.

In this guide, you will learn how to:

  1. Configure your Stacks node to work with Chainhook.
  2. Generate a Chainhook predicate to evaluate your events.
  3. Scan the Stacks blockchain for transactions that match your predicates.
  4. Initiate a Chainhook service to watch for matching transactions.
  5. Dynamically register your predicates with Chainhook.

Prerequisites

  1. Chainhook installed (Refer to the Chainhook installation guide if you have not installed Chainhook yet.)
  2. A fully synced Bitcoin node is required to run Chainhook as a service. Check out our guide on How to sync a Bitcoin node before proceeding.
  3. A fully synced Stacks node is required to run Chainhook as a service. Check out our guide on How to sync a Stacks node before proceeding.

Configure Your Stacks Node

If you followed along with the previous guide on syncing a Stacks node, you should generate a Stacks.toml file within your Stacks node repository. Below is a sample Stacks.toml file:

Stacks.toml
[node]
working_dir = "/stacks-blockchain"
rpc_bind = "0.0.0.0:20443"          # Make a note of this port to use in the `Chainhook.toml`
p2p_bind = "0.0.0.0:20444"
bootstrap_node = "02da7a464ac770ae8337a343670778b93410f2f3fef6bea98dd1c3e9224459d36b@seed-0.mainnet.stacks.co:20444,02afeae522aab5f8c99a00ddf75fbcb4a641e052dd48836408d9cf437344b63516@seed-1.mainnet.stacks.co:20444,03652212ea76be0ed4cd83a25c06e57819993029a7b9999f7d63c36340b34a4e62@seed-2.mainnet.stacks.co:20444"

[burnchain]
chain = "bitcoin"
mode = "mainnet"
peer_host = "localhost"
username = "bitcoind_username"       # Must match the rpcuser in the bitcoin.conf
password = "bitcoind_password"       # Must match the rpcpassword in the bitcoin.conf
rpc_port = 8332                      # Must match the rpcport in the bitcoin.conf
peer_port = 8333

[[events_observer]]
endpoint = "localhost:20455"
retry_count = 255
events_keys = ["*"]

Ensure that the username, password, and rpc_port values in the Stacks.toml file match the values in the bitcoin.conf file. Also, note the rpc_bind port to use in the Chainhook.toml configuration in the next section.

Configure Chainhook

In this section, you will configure Chainhook to communicate with the network. Run the following command in your terminal to generate the Chainhook.toml file:

Terminal
chainhook config generate --mainnet

Several network parameters in the generated Chainhook.toml configuration file need to match those in the bitcoin.conf file created earlier in the Setting up a Bitcoin Node section. Update the following parameters accordingly:

  1. Update bitcoind_rpc_username with the username set for rpcuser in bitcoin.conf.
  2. Update bitcoind_rpc_password with the password set for rpcpassword in bitcoin.conf.
  3. Update bitcoind_rpc_url with the same host and port used for rpcport in bitcoin.conf.
  4. Ensure stacks_node_rpc_url matches the rpc_bind in the Stacks.toml file.

The generated Chainhook.toml file should look like this:

Chainhook.toml
[storage]
working_dir = "cache"

# The http API allows you to register / deregister predicates dynamically.
# This is disabled by default.

# [http_api]
# http_port = 20456
# database_uri = "redis://localhost:6379/"

[network]
mode = "mainnet"
bitcoind_rpc_url = "http://localhost:8332"
bitcoind_rpc_username = "devnet"
bitcoind_rpc_password = "devnet"
# Bitcoin block events can be received by Chainhook
# either through a Bitcoin node's ZeroMQ interface,
# or through the Stacks node. The Stacks node is
# used by default:
stacks_node_rpc_url = "http://localhost:20443"
stacks_events_ingestion_port = 20455
# but zmq can be used instead:
# bitcoind_zmq_url = "tcp://0.0.0.0:18543"

[limits]
max_number_of_bitcoin_predicates = 100
max_number_of_concurrent_bitcoin_scans = 100
max_number_of_stacks_predicates = 10
max_number_of_concurrent_stacks_scans = 10
max_number_of_processing_threads = 16
max_number_of_networking_threads = 16
max_caching_memory_size_mb = 32000

[[event_source]]
tsv_file_url = "https://archive.hiro.so/mainnet/stacks-blockchain-api/mainnet-stacks-blockchain-api-latest"

Ensure the following configurations are matched to allow Chainhook to communicate with both Stacks and Bitcoin:

bitcoin.confStacks.tomlChainhook.toml
rpcuserusernamebitcoind_rpc_username
rpcpasswordpasswordbitcoind_rpc_password
rpcportrpc_portbitcoind_rpc_url
zmqpubhashblockbitcoind_zmq_url
rpc_bindstacks_node_rpc_url
endpointstacks_events_ingestion_port

The bitcoind_zmq_url is optional when running Chainhook as a service using Stacks because Stacks will pull the blocks from Stacks and the Bitcoin chain.

Scan the blockchain based on predicates

Now that the Stacks and Chainhook configurations are done, you can scan your blocks by defining your predicates.

Appending events to a file

Terminal
chainhook predicates new print-event.json --stacks

This will generate a sample JSON file print-event.json. Update the generated print-event.json file with the following content:

print-event.json
{
  "chain": "stacks",
  "uuid": "6ad27176-2b83-4381-b51c-50baede11e3f",
  "name": "Hello world",
  "version": 1,
  "networks": {
    "testnet": {
      "start_block": 34239,
      "end_block": 50000,
      "if_this": {
        "scope": "print_event",
        "contract_identifier": "ST1SVA0SST0EDT4MFYGWGP6GNSXMMQJDVP1G8QTTC.arkadiko-freddie-v1-1",
        "contains": "vault"
      },
      "then_that": {
        "file_append": {
          "path": "arkadiko.txt"
        }
      }
    },
    "mainnet": {
      "start_block": 34239,
      "end_block": 50000,
      "if_this": {
        "scope": "print_event",
        "contract_identifier": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-freddie-v1-1",
        "contains": "vault"
      },
      "then_that": {
        "file_append": {
          "path": "arkadiko.txt"
        }
      }
    }
  }
}

Now, use the following command to scan the blocks based on the predicates defined in the print-event.json file:

Terminal
chainhook predicates scan print-event.json --mainnet

The output of the above command will be a file arkadiko.txt generated based on the predicate definition.

Sending events to an API endpoint

Terminal
chainhook predicates new print-event-post.json --stacks

This will generate a sample JSON file print-event-post.json. Update the generated print-event-post.json file with the following content:

print-event-post.json
{
  "chain": "stacks",
  "uuid": "e5fa09b2-ec3e-4b6a-9a4a-0ebb454f6e19",
  "name": "Hello world",
  "version": 1,
  "networks": {
    "testnet": {
      "if_this": {
        "scope": "print_event",
        "contract_identifier": "ST1SVA0SST0EDT4MFYGWGP6GNSXMMQJDVP1G8QTTC.arkadiko-freddie-v1-1",
        "contains": "vault"
      },
      "then_that": {
        "http_post": {
          "url": "http://localhost:3000/events",
          "authorization_header": "Bearer cn389ncoiwuencr"
        }
      },
      "start_block": 10200,
      "expire_after_occurrence": 5
    },
    "mainnet": {
      "if_this": {
        "scope": "print_event",
        "contract_identifier": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-freddie-v1-1",
        "contains": "vault"
      },
      "then_that": {
        "http_post": {
          "url": "http://localhost:3000/events",
          "authorization_header": "Bearer cn389ncoiwuencr"
        }
      },
      "start_block": 10200,
      "expire_after_occurrence": 5
    }
  }
}

The start_block is a required field to use the http_post then_that predicate.

Now, use the following command to scan the blocks based on the predicates defined in the print-event-post.json file:

Terminal
chainhook predicates scan print-event-post.json --mainnet

The above command posts events to the URL http://localhost:3000/events mentioned in the Chainhook.toml file.

Initiate Chainhook as a service

Terminal
chainhook service start --predicate-path=print-event.json --config-path=Chainhook.toml

This command registers the predicate and starts the Chainhook service.

Dynamically register predicates

You can also dynamically register new predicates with your Chainhook service.

This section requires that you have Redis running locally. To install, refer to the Redis documentation.

First, ensure that the following lines in the Chainhook.toml file are uncommented to enable the predicate registration server:

Chainhook.toml
[http_api]
http_port = 20456
database_uri = "redis://localhost:6379/"
Terminal
chainhook service start --predicate-path=print-event.json --config-path=Chainhook.toml

Start the Chainhook service with the following command:

Terminal
chainhook service start --config-path=Chainhook.toml

To dynamically register a new predicate, send a POST request to the running predicate registration server at localhost:20456/v1/chainhooks.

Use the following curl command as an example:

Terminal
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "stacks",
    "uuid": "42",
    "name": "Arkadiko",
    "version": 1,
    "networks": {
      "mainnet": {
        "start_block": 777534,
        "if_this": {
          "scope": "print_event",
          "contract_identifier": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-freddie-v1-1",
          "contains": "vault"
        },
        "then_that": {
          "http_post": {
            "url": "http://localhost:3000/events",
            "authorization_header": "Bearer cn389ncoiwuencr"
          }
        }
      }
    }
  }' \
  http://localhost:20456/v1/chainhooks

The response should look something like this:

{"result":"42","status":200}
You can also run the Chainhook service by passing multiple predicates: chainhook service start --predicate-path=predicate_1.json --predicate-path=predicate_2.json --config-path=Chainhook.toml

Next steps