From 82281aa3bb675659973d131e1bd0ddc8f2e22067 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 29 Jun 2017 17:03:43 +0200 Subject: [PATCH] Add logger and chain middleware, default stack --- context.go | 6 ++- errors/common.go | 17 ++++++- stack/chain.go | 51 +++++++++++++++++++++ stack/chain_test.go | 63 ++++++++++++++++++++++++++ stack/context.go | 30 ++++++++---- stack/helpers_test.go | 9 ++-- stack/logger.go | 54 ++++++++++++++++++++++ stack/middleware.go | 14 ++++++ stack/recovery_test.go | 3 +- handlers/sigs.go => stack/signature.go | 5 +- txs/base.go | 2 +- 11 files changed, 234 insertions(+), 20 deletions(-) create mode 100644 stack/chain.go create mode 100644 stack/chain_test.go create mode 100644 stack/logger.go rename handlers/sigs.go => stack/signature.go (94%) diff --git a/context.go b/context.go index 704ac49ccb..2ba181c56c 100644 --- a/context.go +++ b/context.go @@ -1,6 +1,9 @@ package basecoin -import "github.com/tendermint/go-wire/data" +import ( + "github.com/tendermint/go-wire/data" + "github.com/tendermint/tmlibs/log" +) // Actor abstracts any address that can authorize actions, hold funds, // or initiate any sort of transaction. @@ -21,6 +24,7 @@ func NewActor(app string, addr []byte) Actor { // rely on private fields to control the actions type Context interface { // context.Context + log.Logger WithPermissions(perms ...Actor) Context HasPermission(perm Actor) bool IsParent(ctx Context) bool diff --git a/errors/common.go b/errors/common.go index addc06d2cd..8c152c4493 100644 --- a/errors/common.go +++ b/errors/common.go @@ -4,7 +4,11 @@ package errors * Copyright (C) 2017 Ethan Frey **/ -import abci "github.com/tendermint/abci/types" +import ( + "fmt" + + abci "github.com/tendermint/abci/types" +) const ( msgDecoding = "Error decoding input" @@ -20,6 +24,8 @@ const ( msgTooLarge = "Input size too large" msgMissingSignature = "Signature missing" msgTooManySignatures = "Too many signatures" + msgNoChain = "No chain id provided" + msgWrongChain = "Tx belongs to different chain - %s" ) func InternalError(msg string) TMError { @@ -46,6 +52,15 @@ func InvalidSignature() TMError { return New(msgInvalidSignature, abci.CodeType_Unauthorized) } +func NoChain() TMError { + return New(msgNoChain, abci.CodeType_Unauthorized) +} + +func WrongChain(chain string) TMError { + msg := fmt.Sprintf(msgWrongChain, chain) + return New(msg, abci.CodeType_Unauthorized) +} + func InvalidAddress() TMError { return New(msgInvalidAddress, abci.CodeType_BaseInvalidInput) } diff --git a/stack/chain.go b/stack/chain.go new file mode 100644 index 0000000000..f351c24864 --- /dev/null +++ b/stack/chain.go @@ -0,0 +1,51 @@ +package stack + +import ( + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/errors" + "github.com/tendermint/basecoin/txs" + "github.com/tendermint/basecoin/types" +) + +const ( + NameChain = "chan" +) + +// Chain enforces that this tx was bound to the named chain +type Chain struct { + ChainID string +} + +func (_ Chain) Name() string { + return NameRecovery +} + +var _ Middleware = Chain{} + +func (c Chain) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) { + stx, err := c.checkChain(tx) + if err != nil { + return res, err + } + return next.CheckTx(ctx, store, stx) +} + +func (c Chain) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) { + stx, err := c.checkChain(tx) + if err != nil { + return res, err + } + return next.DeliverTx(ctx, store, stx) +} + +// checkChain makes sure the tx is a txs.Chain and +func (c Chain) checkChain(tx basecoin.Tx) (basecoin.Tx, error) { + ctx, ok := tx.Unwrap().(*txs.Chain) + if !ok { + return tx, errors.NoChain() + } + if ctx.ChainID != c.ChainID { + return tx, errors.WrongChain(ctx.ChainID) + } + return ctx.Tx, nil +} diff --git a/stack/chain_test.go b/stack/chain_test.go new file mode 100644 index 0000000000..faa37accad --- /dev/null +++ b/stack/chain_test.go @@ -0,0 +1,63 @@ +package stack + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/txs" + "github.com/tendermint/basecoin/types" + "github.com/tendermint/tmlibs/log" +) + +func TestChain(t *testing.T) { + assert := assert.New(t) + msg := "got it" + chainID := "my-chain" + + raw := txs.NewRaw([]byte{1, 2, 3, 4}).Wrap() + cases := []struct { + tx basecoin.Tx + valid bool + errorMsg string + }{ + {txs.NewChain(chainID, raw).Wrap(), true, ""}, + {txs.NewChain("someone-else", raw).Wrap(), false, "Tx belongs to different chain - someone-else"}, + {raw, false, "No chain id provided"}, + } + + // generic args here... + ctx := NewContext(log.NewNopLogger()) + store := types.NewMemKVStore() + + // build the stack + ok := OKHandler{msg} + app := New(Chain{chainID}).Use(ok) + + for idx, tc := range cases { + i := strconv.Itoa(idx) + + // make sure check returns error, not a panic crash + res, err := app.CheckTx(ctx, store, tc.tx) + if tc.valid { + assert.Nil(err, "%d: %+v", idx, err) + assert.Equal(msg, res.Log, i) + } else { + if assert.NotNil(err, i) { + assert.Equal(tc.errorMsg, err.Error(), i) + } + } + + // make sure deliver returns error, not a panic crash + res, err = app.DeliverTx(ctx, store, tc.tx) + if tc.valid { + assert.Nil(err, "%d: %+v", idx, err) + assert.Equal(msg, res.Log, i) + } else { + if assert.NotNil(err, i) { + assert.Equal(tc.errorMsg, err.Error(), i) + } + } + } +} diff --git a/stack/context.go b/stack/context.go index 2ff81b50a6..60b13b81c4 100644 --- a/stack/context.go +++ b/stack/context.go @@ -5,6 +5,9 @@ import ( "math/rand" "github.com/pkg/errors" + + "github.com/tendermint/tmlibs/log" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/types" ) @@ -16,11 +19,13 @@ type secureContext struct { id nonce app string perms []basecoin.Actor + log.Logger } -func NewContext() basecoin.Context { +func NewContext(logger log.Logger) basecoin.Context { return secureContext{ - id: nonce(rand.Int63()), + id: nonce(rand.Int63()), + Logger: logger, } } @@ -37,8 +42,10 @@ func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context } return secureContext{ - id: c.id, - perms: append(c.perms, perms...), + id: c.id, + app: c.app, + perms: append(c.perms, perms...), + Logger: c.Logger, } } @@ -60,12 +67,14 @@ func (c secureContext) IsParent(other basecoin.Context) bool { return c.id == so.id } -// Reset should give a fresh context, +// Reset should clear out all permissions, // but carry on knowledge that this is a child func (c secureContext) Reset() basecoin.Context { return secureContext{ - id: c.id, - app: c.app, + id: c.id, + app: c.app, + perms: nil, + Logger: c.Logger, } } @@ -77,9 +86,10 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context { return ctx } return secureContext{ - id: sc.id, - app: app, - perms: sc.perms, + id: sc.id, + app: app, + perms: sc.perms, + Logger: sc.Logger, } } diff --git a/stack/helpers_test.go b/stack/helpers_test.go index 1beb5c0faf..d792cf9535 100644 --- a/stack/helpers_test.go +++ b/stack/helpers_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/tendermint/tmlibs/log" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/types" ) @@ -12,7 +15,7 @@ import ( func TestOK(t *testing.T) { assert := assert.New(t) - ctx := NewContext() + ctx := NewContext(log.NewNopLogger()) store := types.NewMemKVStore() data := "this looks okay" tx := basecoin.Tx{} @@ -30,7 +33,7 @@ func TestOK(t *testing.T) { func TestFail(t *testing.T) { assert := assert.New(t) - ctx := NewContext() + ctx := NewContext(log.NewNopLogger()) store := types.NewMemKVStore() msg := "big problem" tx := basecoin.Tx{} @@ -50,7 +53,7 @@ func TestFail(t *testing.T) { func TestPanic(t *testing.T) { assert := assert.New(t) - ctx := NewContext() + ctx := NewContext(log.NewNopLogger()) store := types.NewMemKVStore() msg := "system crash!" tx := basecoin.Tx{} diff --git a/stack/logger.go b/stack/logger.go new file mode 100644 index 0000000000..93cc8e5e50 --- /dev/null +++ b/stack/logger.go @@ -0,0 +1,54 @@ +package stack + +import ( + "time" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/types" +) + +const ( + NameLogger = "lggr" +) + +// Logger catches any panics and returns them as errors instead +type Logger struct{} + +func (_ Logger) Name() string { + return NameLogger +} + +var _ Middleware = Logger{} + +func (_ Logger) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) { + start := time.Now() + res, err = next.CheckTx(ctx, store, tx) + delta := time.Now().Sub(start) + // TODO: log some info on the tx itself? + l := ctx.With("duration", micros(delta)) + if err == nil { + l.Debug("CheckTx", "log", res.Log) + } else { + l.Info("CheckTx", "err", err) + } + return +} + +func (_ Logger) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) { + start := time.Now() + res, err = next.DeliverTx(ctx, store, tx) + delta := time.Now().Sub(start) + // TODO: log some info on the tx itself? + l := ctx.With("duration", micros(delta)) + if err == nil { + l.Info("DeliverTx", "log", res.Log) + } else { + l.Error("DeliverTx", "err", err) + } + return +} + +// micros returns how many microseconds passed in a call +func micros(d time.Duration) int { + return int(d.Seconds() * 1000000) +} diff --git a/stack/middleware.go b/stack/middleware.go index 509a577608..3bd2cb7385 100644 --- a/stack/middleware.go +++ b/stack/middleware.go @@ -54,6 +54,20 @@ func New(middlewares ...Middleware) *Stack { } } +// NewDefault sets up the common middlewares before your custom stack. +// +// This is logger, recovery, signature, and chain +func NewDefault(chainID string, middlewares ...Middleware) *Stack { + mids := []Middleware{ + Logger{}, + Recovery{}, + SignedHandler{true}, + Chain{chainID}, + } + mids = append(mids, middlewares...) + return New(mids...) +} + // Use sets the final handler for the stack and prepares it for use func (s *Stack) Use(handler basecoin.Handler) *Stack { if handler == nil { diff --git a/stack/recovery_test.go b/stack/recovery_test.go index c4d359dd13..6d646c19bb 100644 --- a/stack/recovery_test.go +++ b/stack/recovery_test.go @@ -8,13 +8,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/types" + "github.com/tendermint/tmlibs/log" ) func TestRecovery(t *testing.T) { assert := assert.New(t) // generic args here... - ctx := NewContext() + ctx := NewContext(log.NewNopLogger()) store := types.NewMemKVStore() tx := basecoin.Tx{} diff --git a/handlers/sigs.go b/stack/signature.go similarity index 94% rename from handlers/sigs.go rename to stack/signature.go index 2ebbf6b5c9..fa59c210a8 100644 --- a/handlers/sigs.go +++ b/stack/signature.go @@ -1,11 +1,10 @@ -package handlers +package stack import ( crypto "github.com/tendermint/go-crypto" "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/errors" - "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/types" ) @@ -22,7 +21,7 @@ func (_ SignedHandler) Name() string { return NameSigs } -var _ stack.Middleware = SignedHandler{} +var _ Middleware = SignedHandler{} func SigPerm(addr []byte) basecoin.Actor { return basecoin.NewActor(NameSigs, addr) diff --git a/txs/base.go b/txs/base.go index dfd7648322..e02d3c7826 100644 --- a/txs/base.go +++ b/txs/base.go @@ -121,7 +121,7 @@ type Chain struct { ChainID string `json:"chain_id"` } -func NewChain(tx basecoin.Tx, chainID string) *Chain { +func NewChain(chainID string, tx basecoin.Tx) *Chain { return &Chain{Tx: tx, ChainID: chainID} }