Skip to content

Clojure host/server for Tendermint's ABCI protocol.

License

Notifications You must be signed in to change notification settings

datopia/abci-host

Repository files navigation

org.datopia/abci

Clojars Project

A Clojure library which acts as an application host for Tendermint's ABCI --- allowing the exposure of plain functions as replicable state machines.

org.datopia/abci uses org.datopia/stickler to provide pure-data representations of the protobuf-encoded messages received from the Tendermint node process --- maps in, maps out.

Documentation

Project -> Tendermint Version Mapping

org.datopia/abci Tendermint
0.1.0 0.34.12

There's a previous, deprecated artifact:

io.datopia/abci
0.1.* 0.26.0

Toy Example / Walkthrough

The simplest possible service looks something like:

(ns my.abci
  (:require [abci.host            :as host]
            [abci.host.middleware :as mw]))

(def service
  (-> (constantly ::mw/default)
      ;; Wrap handler invocations w/ (manifold.deferred/future).
      mw/wrap-synchronous
      ;; Substitute ::mw/default for a default success response,
      ;; appropriate to the incoming request.
      mw/wrap-default
      ;; Return a Closeable, per aleph.tcp/start-server.
      host/start))

While this isn't a particularly dynamic application, we can successfully point a Tendermint node process at it and indefinitely operate a no-op blockchain.

Requests & Responses

Let's transform our handler from (constantly ::mw/default) to something slightly different:

(fn [req]
  (pprint req)
  ::mw/default)

Once the Tendermint node connects, our first message:

{:stickler/msg          :abci/Request
 :info                  {:stickler/msg  :abci/RequestInfo
                         :version       "0.26.0-c086d0a3"
                         :block-version 7
                         :p2p-version   4}
 :stickler.one-of/value :info
 ;; The node initiates 3 distinct connections: :info, :query, :consensus
 :abci.host/conn        :info}

The minimal success response to the above:

{:stickler/msg :abci/Response
 :info         {:stickler/msg :abci/ResponseInfo
                :data         "NO_INFO"}}

Let's update our service to construct this response explictly, while using middleware to strip incoming :abci/Request envelopes - and wrap responses in :abci/Response envelopes. While this won't alter how our service functions, it may make it a little clearer.

(defn handler [{msg-type :stickler/msg :as req}]
  (pprint req)
  (case msg-type
    :abci/RequestInfo {:stickler/msg :abci/ResponseInfo
                       :data         "NO_INFO"}
    ::mw/default))

(def service
  (-> handler
      mw/wrap-synchronous
      mw/wrap-default
      ;; Combines mw/wrap-request-envelope and mw/wrap-response-envelope
      mw/wrap-envelope
      host/start))

Without the envelopes, our incoming request sequence:

{:stickler/msg   :abci/RequestInfo
 :version        "0.26.0-c086d0a3"
 :block-version  7
 :p2p-version    4
 :abci.host/conn :info}

{:stickler/msg   :abci/RequestFlush
 :abci.host/conn :info}

{:stickler/msg   :abci/RequestInitChain
 :time           {:stickler/msg :google.protobuf/Timestamp
                  :seconds      1544530118
                  :nanos        776243100}
 :chain-id       "test-chain-anZqUW"
 :validators
 [{:stickler/msg :abci/ValidatorUpdate
   :pub-key      {:stickler/msg :abci/PubKey
                  :type         "ed25519"
                  :data         <bytes>}
   :power        10}]
 ...
 :abci.host/conn :consensus}

...

The project's abci.edn resource describes the full complement of messages. As Tendermint uses Go-specific extensions in its protobuf files, abci.edn is generated by org.datopia/stickler from the types.proto maintained by jabci.

Contributors

  • Moe Aboulkheir

License

MIT