Message Handlers

A message is a statement of intention, and wrapped in a transaction, while provides authorization to this intention. Once this message ends up in the ABCI application and is to be processed, we send it to a Handler, which we have registered for this application.

Check vs Deliver

If you look at the definiton of a Handler, you will see it is responsible for Check and Deliver. These are similar logic, but there is an important distinction. Check is performed when a client proposes the transaction to the mempool, before it is added to a block. It is meant as a quick filter to weed out garbage transactions before writing them to the blockchain. The state it provides is a scratch buffer around the last committed state and will be discarded next block, so any writes here are never writen to disk.

Deliver is performed after the transaction was writen to the block. Upon consensus, every node will process the block by calling BeginBlock, Deliver for every transaction in the block, and finally EndBlock and Commit. Deliver will be called in the same order on every node and must make the exact same changes on every node, both now and in the future when the blocks are replayed. Even the slightest deviation will cause the merkle root of the store at the end of the block to differ with other nodes, and thus kick the deviating nodes out of consensus. (Note that Check may actually vary between nodes without breaking consensus rules, although we generally keep this deterministic as well).

This is a very powerful concept and means that when modifying a given state, users must not worry about any concurrential access or writing collision since by definition, any write access is garanteed to occur sequentially and in the same order on each node.

Writing a Handler

We usually can write a separate handler for each message type, although you can register multiple messages with the same handler if you reuse most of the code. Let’s focus on the simplest cases, and the handlers for creating a Blog and adding a Post to an existing blog.

Note that we can generally assume that Handlers are wrapped by a Savepoint Decorator, and that if Deliver returns an error after updating some objects, those update will be discarded. This means you can treat Handlers as atomic actions, all or none, and not worry too much about cleaning up partially finished state changes if a later portion fails.

Validation

Remember that we have to fulfill both Check and Deliver methods, and they share most of the same validation logic. A typical approach is to define a validate method that parses the proper message out of the transaction, verify all authorization preconditions are fulfilled by the transaction, and possibly check the current state of the blockchain to see if the action is allowed. If the validate method doesn’t return an error, then Check will return the expected cost of the transaction, while Deliver will actually peform the action and update the blockchain state accordingly.

Blog

Let us take a look at a first validation example when creating a blog :

	}

	msg, err := tx.GetMsg()
	if err != nil {
		return nil, err
	}

	createBlogMsg, ok := msg.(*CreateBlogMsg)
	if !ok {
		return nil, errors.WithType(errors.ErrMsg, msg)
	}

	err = createBlogMsg.Validate()
	if err != nil {
		return nil, err
	}

	// Check the blog does not already exist
	obj, err := h.bucket.Get(db, []byte(createBlogMsg.Slug))
	if err != nil || (obj != nil && obj.Value() != nil) {
		return nil, errors.Wrap(errors.ErrDuplicate, "blog")
	}

	return createBlogMsg, nil
}

type CreatePostMsgHandler struct {
	// error occurs during parsing the object found so thats also a ErrBlogExistError
	auth  x.Authenticator
	posts PostBucket

Before anything, we want to make sure that the transaction is allowed and in the case of Blog creation, we choose to consider the main Tx signer as the blog author. This is easily achieved using existing util functions :

	msg, err := tx.GetMsg()
	if err != nil {
		return nil, err
	}

Next comes the model validation as described in the Data Model section, and finally we want to make sure that the blog is unique. The example below shows how to do that by querying the BlogBucket :

	return createBlogMsg, nil
}

type CreatePostMsgHandler struct {

Post

In the case of adding a post, we must first validate that the transaction hold the proper message, the message passes all internal validation checks, the blog named in the message exists in our state, and the author both signed this transaction and belongs to authorized authors for this blog… What a mouthful. Since validate must load the relevant blog for authorization, which we may want to use elsewhere in the Handler as well, we return it from the validate call as well to avoid loading it twice.

	createPostMsg, ok := msg.(*CreatePostMsg)
	if !ok {
		return nil, nil, errors.WithType(errors.ErrMsg, msg)
	}

	// Check the author is one of the Tx signer
	if !h.auth.HasAddress(ctx, createPostMsg.Author) {
		return nil, nil, errors.Wrapf(errors.ErrUnauthorized, unauthorisedPostAuthorFmt, weave.Address(createPostMsg.Author))
	}

	err = createPostMsg.Validate()
	if err != nil {
		return nil, nil, err
	}

	// Check that the parent blog exists
	obj, err := h.blogs.Get(db, []byte(createPostMsg.Blog))
	if err != nil {
		return nil, nil, err
	}
	if obj == nil || (obj != nil && obj.Value() == nil) {
		return nil, nil, errors.Wrap(errors.ErrNotFound, "blog")
	}

	blog := obj.Value().(*Blog)
	return createPostMsg, blog, nil
}

func newPostCompositeKey(slug string, idx int64) []byte {
	key1 := []byte(slug)
	key2 := []byte(fmt.Sprintf("%08x", idx))
	return bytes.Join([][]byte{key1, key2}, nil)

Note how we ensure that the post author is one of the Tx signers :

	err = createPostMsg.Validate()
	if err != nil {
		return nil, nil, err
	}

Check

Once validate is implemented, Check must ensure it is valid and then return a rough cost of the message, which may be based on the storage cost of the text of the post. This return value is similar to the concept of gas in ethereum, although it doesn’t count to the fees yet, but rather is used by tendermint to prioritize the transactions to fit in a block.

Blog

A blog costs one gas to create :

func (h CreateBlogMsgHandler) Check(ctx weave.Context, db weave.KVStore, tx weave.Tx) (*weave.CheckResult, error) {
	if _, err := h.validate(ctx, db, tx); err != nil {
		return nil, err
	}
	return &weave.CheckResult{GasAllocated: newBlogCost}, nil
}

func (h CreateBlogMsgHandler) Deliver(ctx weave.Context, db weave.KVStore, tx weave.Tx) (*weave.DeliverResult, error) {
	msg, err := h.validate(ctx, db, tx)
	if err != nil {

Post

In the case of a Post creation, we decided to charge the author 1 gas per mile characters with the first 1000 characters offered :

	// First 1000 chars for free then 1 gas per mile chars
	res := &weave.CheckResult{
		GasAllocated: int64(len(msg.Text)) * newPostCost / postCostUnit,
	}
	return res, nil
}

func (h CreatePostMsgHandler) Deliver(ctx weave.Context, db weave.KVStore, tx weave.Tx) (*weave.DeliverResult, error) {
	msg, blog, err := h.validate(ctx, db, tx)
	if err != nil {

Deliver

Similarly to Check, Deliver also makes use of validate to perform the original checks.

Blog

Before saving the blog into the blog bucket, Deliver checks if the main signer of the Tx is part of the authorized authors for this blog and will add it if not.

	}

	blog := &Blog{
		// add main signer for this Tx to the authors of this blog if that's not already the case
		Authors: withSender(msg.Authors, x.MainSigner(ctx, h.auth).Address()),
		Title:   msg.Title,
	}

	obj := orm.NewSimpleObj([]byte(msg.Slug), blog)
	err = h.bucket.Save(db, obj)
	if err != nil {
		return nil, err
	}

	return &weave.DeliverResult{}, nil
}

// validate does all common pre-processing between Check and Deliver
func (h CreateBlogMsgHandler) validate(ctx weave.Context, db weave.KVStore, tx weave.Tx) (*CreateBlogMsg, error) {
	// Retrieve tx main signer in this context
	sender := x.MainSigner(ctx, h.auth)

Post

Deliver increments the article count on the Blog, and calculates the key of the Post based on the Blog slug and the count of this article. It then saves both the Post and the updated Blog. Note how the Handler has access to the height of the current block being processes (which is deterministic in contrast to a timestamp), and can attach that to the Post to allow a client to get a timestamp from the relevant header. (Actually the Handler has access to the full header, which contains a timestamp, which may or may not be reliable.)

	}

	height, _ := weave.GetHeight(ctx)
	post := &Post{
		Title:         msg.Title,
		Author:        msg.Author,
		Text:          msg.Text,
		CreationBlock: height,
	}

	blog.NumArticles++
	postKey := newPostCompositeKey(msg.Blog, blog.NumArticles)
	obj := orm.NewSimpleObj(postKey, post)
	err = h.posts.Save(db, obj)
	if err != nil {
		return nil, err
	}

	objParent := orm.NewSimpleObj([]byte(msg.Blog), blog)
	err = h.blogs.Save(db, objParent)
	if err != nil {
		return nil, err
	}

	return &weave.DeliverResult{}, nil
}

// validate does all common pre-processing between Check and Deliver
func (h CreatePostMsgHandler) validate(ctx weave.Context, db weave.KVStore, tx weave.Tx) (*CreatePostMsg, *Blog, error) {
	msg, err := tx.GetMsg()
	if err != nil {

Let us recall that when incrementing the article count on the parent blog, we don’t have to worry about concurrential access, nor use any synchronisation mechanism : We are garanteed that each Check and Deliver method will be executed sequentially and in the same order on each node.

Finally, note how we generate the composite key for the post by concatenating the blog slug and the blog count :

type RenameBlogMsgHandler struct {
	auth   x.Authenticator
	bucket BlogBucket
}

Routing Messages to Handler

After defining all the Messages, along with Handlers for them all, we need to make sure the application knows about them. When we instantiate an application, we define a Router and then register all handlers we are interested in. This allows the application to explicitly state, not only which messages it supports (in the Tx struct), but also which business logic will process each message.

	cash.RegisterRoutes(r, authFn, CashControl())
	validators.RegisterRoutes(r, authFn)
	return r
}

// QueryRouter returns a default query router,
// allowing access to "/wallets", "/auth", and "/"
func QueryRouter() weave.QueryRouter {

In order to make it easy for applications to register our extension as one piece and not worry about attaching every Handler we provide, it is common practice for an extension to provide a RegisterRoutes function that will take a Router (or the more permissive Registry interface), and any information it needs to construct instances of all the handlers. This RegisterRoutes function is responsible for instantiating all the Handlers with the desired configuration and attaching them to the Router to process the matching Message type (identified by it’s Path):

func RegisterRoutes(r weave.Registry, auth x.Authenticator) {
	blogs := NewBlogBucket()
	r.Handle(PathCreateBlogMsg, CreateBlogMsgHandler{auth, blogs})
	r.Handle(PathCreatePostMsg, CreatePostMsgHandler{auth, NewPostBucket(), blogs})
	r.Handle(PathRenameBlogMsg, RenameBlogMsgHandler{auth, blogs})
	r.Handle(PathChangeBlogAuthorsMsg, ChangeBlogAuthorsMsgHandler{auth, blogs})
	r.Handle(PathSetProfileMsg, SetProfileMsgHandler{auth, NewProfileBucket()})
}

Testing Handlers

In order to test a handler, we need four things :
  • A storage
  • A weave context
  • An Authenticator associated with our context
  • A Tx object to processs (eg. to check or to deliver)

There is a ready to use in memory storage available in the store package. There are also util functions available that we can use to create a weave context with a list of signers (eg. authorized addresses) via an Authenticator. The function below shows how to use them :

	auth := &weavetest.CtxAuth{Key: "authKey"}
	// Create a new context and add addr to the list of signers
	return auth.SetConditions(ctx, perms...), auth
}

// getDeliveredObject looks for key in all the buckets associated with handler
// returns the first matching object or nil if none
func getDeliveredObject(handler weave.Handler, db weave.KVStore, key []byte) (orm.Object, error) {
	switch t := handler.(type) {

Last but not least, there is a helper function allowing to create a Tx object from a message :

Now that we have all the pieces, let us put them together and write tests.

First we start by defining a pattern that we will follow in all our tests to make easier for the reader to navigate through them. A function to test a handler Check method would look like this :

func Test[HandlerName]Check(t *testing.T) {

    1 - generate keys to use in the test

    k1 := weavetest.NewCondition()
    // ...
    kN := weavetest.NewCondition()

    2 - call testHandlerCheck withs testcases as below

    testHandlerCheck(
        t,
        []testcase{
            // testcase1
            // testcase2
            // ...
            // testcaseN
        })
}

And for the Deliver method, like that :

func Test[HandlerName]Deliver(t *testing.T) {

    1 - generate keys to use in the test

    k1 := weavetest.NewCondition()
    // ...
    kN := weavetest.NewCondition()

    2 - call testHandlerDeliver withs testcases as below

    testHandlerDeliver(
        t,
        []testcase{
            // testcase1
            // testcase2
            // ...
            // testcaseN
        })
}

Our test functions rely on small utilities defined at the top of the test file, mainly, a testcase struct to hold the data required for a test :

type testcase struct {
	Name    string
	Err     *errors.Error
	Handler HandlerCreator
	Perms   []weave.Condition
	Deps    []testdep
	Msg     weave.Msg
	Res     *weave.CheckResult
	Obj     []*orm.SimpleObj
}

type HandlerCreator func(auth x.Authenticator) weave.Handler

func createBlogMsgHandlerFn(auth x.Authenticator) weave.Handler {
	return CreateBlogMsgHandler{
		auth:   auth,
		bucket: NewBlogBucket(),

A generic test runner for the Check method of a handler :

		db := store.MemStore()
		ctx, auth := newContextWithAuth(test.Perms)

		// add dependencies
		for _, dep := range test.Deps {
			depHandler := dep.Handler(auth)
			_, err := depHandler.Deliver(ctx, db, &weavetest.Tx{Msg: dep.Msg})
			require.NoError(t, err, test.Name, fmt.Sprintf("failed to deliver dep %s\n", dep.Name))
		}

		//run test
		handler := test.Handler(auth)
		res, err := handler.Check(ctx, db, &weavetest.Tx{Msg: test.Msg})
		if test.Err == nil {
			require.NoError(t, err, test.Name)
			require.EqualValues(t, test.Res, res, test.Name)
		} else {
			require.Error(t, err, test.Name) // to avoid seg fault at the next line
			require.True(t, test.Err.Is(err), "%s: %+v", test.Name, err)
		}
	}
}

// testHandlerCheck delivers test dependencies
// then calls Deliver on target handler
// and finally asserts errors or saved state(s)
func testHandlerDeliver(t *testing.T, testcases []testcase) {

And one for the Deliver method of a handler :

		db := store.MemStore()
		ctx, auth := newContextWithAuth(test.Perms)

		// add dependencies
		for _, dep := range test.Deps {
			depHandler := dep.Handler(auth)
			_, err := depHandler.Deliver(ctx, db, &weavetest.Tx{Msg: dep.Msg})
			require.NoError(t, err, test.Name, fmt.Sprintf("failed to deliver dep %s\n", dep.Name))
		}

		//run test
		handler := test.Handler(auth)
		_, err := handler.Deliver(ctx, db, &weavetest.Tx{Msg: test.Msg})
		if test.Err == nil {
			require.NoError(t, err, test.Name)
			for _, obj := range test.Obj {
				actual, err := getDeliveredObject(handler, db, obj.Key())
				require.NoError(t, err, test.Name)
				require.NotNil(t, actual, test.Name)
				require.EqualValues(t, obj.Value(), actual.Value(), test.Name)
			}
		} else {
			require.Error(t, err, test.Name) // to avoid seg fault at the next line
			require.EqualError(t, err, test.Err.Error(), test.Name)
		}
	}
}
func TestCreateBlogMsgHandlerCheck(t *testing.T) {
	signer := weavetest.NewCondition()
	testHandlerCheck(
		t,
		[]testcase{

The generic test runners help reducing boilerplates in tests by taking care of saving dependencies prior to running a test, and making asserts on the data returned upon completion. For example when creating a new Post, we need to save the corresponding Blog first, and upon completion, we need to retrieve both the Post and the Blog we saved to ensure they’re inline with our expectations.

Here is how a test would look like for the Check method of the CreateBlogMsg handler :

			{
				Name:    "valid blog",
				Handler: createBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},
				Msg: &CreateBlogMsg{
					Slug:    "this_is_a_blog",
					Title:   "this is a blog title",
					Authors: [][]byte{signer.Address()},
				},
				Res: &weave.CheckResult{
					GasAllocated: newBlogCost,
				},
			},
			{
				Name:    "no authors",
				Err:     errors.ErrState,
				Handler: createBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},
				Msg: &CreateBlogMsg{
					Slug:  "this_is_a_blog",
					Title: "this is a blog title",
				},
			},
			{
				Name:    "no slug",
				Err:     errors.ErrInput,
				Handler: createBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},
				Msg: &CreateBlogMsg{
					Title: "this is a blog title",
				},
			},
			{
				Name:    "no title",
				Err:     errors.ErrInput,
				Handler: createBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},
				Msg: &CreateBlogMsg{
					Slug: "this_is_a_blog",
				},
			},
			{
				Name:    "no signer",
				Err:     errors.ErrUnauthorized,
				Handler: createBlogMsgHandlerFn,
				Msg: &CreateBlogMsg{
					Slug:    "this_is_a_blog",
					Title:   "this is a blog title",
					Authors: [][]byte{signer.Address()},
				},
			},
			{
				Name:    "creating twice",
				Err:     errors.ErrDuplicate,
				Handler: createBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},
				Msg: &CreateBlogMsg{
					Slug:    "this_is_a_blog",
					Title:   "this is a blog title",
					Authors: [][]byte{signer.Address()},
				},
				Deps: []testdep{
					testdep{
						Name:    "blog duplicate",
						Handler: createBlogMsgHandlerFn,
						Msg: &CreateBlogMsg{
							Slug:    "this_is_a_blog",
							Title:   "this is a blog title",
							Authors: [][]byte{signer.Address()},
						},
					},
				},
			},
			{
				Name:    "wrong msg type",
				Err:     errors.ErrMsg,
				Handler: createBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},
				Msg: &CreatePostMsg{
					Blog:   "this_is_a_blog",
					Title:  "this is a post title",
					Text:   longText,
					Author: signer.Address(),
				},
			},
		},
	)
}
func TestCreateBlogMsgHandlerDeliver(t *testing.T) {
	signer := weavetest.NewCondition()
	author := weavetest.NewCondition()
	testHandlerDeliver(
		t,
		[]testcase{
			{
				Name:    "valid blog",
				Handler: createBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},

As stated above, the test implementation consists in defining the keys and test cases to be used. Util functions take care of the remaining.

Let’s take a look at another example with the test for the Deliver method of the CreateBlogMsgHandler struct :

				},
				Deps: []testdep{
					testdep{
						Name:    "Blog",
						Handler: createBlogMsgHandlerFn,
						Msg: &CreateBlogMsg{
							Slug:    "this_is_a_blog",
							Title:   "this is a blog title",
							Authors: [][]byte{signer.Address()},
						},
					},
				},
				Obj: []*orm.SimpleObj{
					orm.NewSimpleObj(
						newPostCompositeKey("this_is_a_blog", 1),
						&Post{
							Title:         "this is a post title",
							Text:          longText,
							Author:        signer.Address(),
							CreationBlock: 100,
						},
					),
					orm.NewSimpleObj(
						[]byte("this_is_a_blog"),
						&Blog{
							Title:       "this is a blog title",
							NumArticles: 1,
							Authors:     [][]byte{signer.Address()},
						},
					),
				},
			},
		},
	)
}
func TestRenameBlogMsgHandlerCheck(t *testing.T) {
	signer := weavetest.NewCondition()
	testHandlerCheck(
		t,
		[]testcase{
			{
				Name:    "valid rename",
				Handler: renameBlogMsgHandlerFn,
				Perms:   []weave.Condition{signer},
				Msg: &RenameBlogMsg{
					Slug:  "this_is_a_blog",
					Title: "this is a blog title which has been renamed",
				},
				Deps: []testdep{

It is very similar to what we saw before. One thing to notice here is that we specify the dependencies required, in this case, a Blog object. We also specify the objects we expect this test to deliver so we can assert wether or not they have been delivered correctly.