Add logger and chain middleware, default stack

This commit is contained in:
Ethan Frey 2017-06-29 17:03:43 +02:00
parent 1df0c9fe5b
commit 82281aa3bb
11 changed files with 234 additions and 20 deletions

View File

@ -1,6 +1,9 @@
package basecoin 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, // Actor abstracts any address that can authorize actions, hold funds,
// or initiate any sort of transaction. // 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 // rely on private fields to control the actions
type Context interface { type Context interface {
// context.Context // context.Context
log.Logger
WithPermissions(perms ...Actor) Context WithPermissions(perms ...Actor) Context
HasPermission(perm Actor) bool HasPermission(perm Actor) bool
IsParent(ctx Context) bool IsParent(ctx Context) bool

View File

@ -4,7 +4,11 @@ package errors
* Copyright (C) 2017 Ethan Frey * Copyright (C) 2017 Ethan Frey
**/ **/
import abci "github.com/tendermint/abci/types" import (
"fmt"
abci "github.com/tendermint/abci/types"
)
const ( const (
msgDecoding = "Error decoding input" msgDecoding = "Error decoding input"
@ -20,6 +24,8 @@ const (
msgTooLarge = "Input size too large" msgTooLarge = "Input size too large"
msgMissingSignature = "Signature missing" msgMissingSignature = "Signature missing"
msgTooManySignatures = "Too many signatures" msgTooManySignatures = "Too many signatures"
msgNoChain = "No chain id provided"
msgWrongChain = "Tx belongs to different chain - %s"
) )
func InternalError(msg string) TMError { func InternalError(msg string) TMError {
@ -46,6 +52,15 @@ func InvalidSignature() TMError {
return New(msgInvalidSignature, abci.CodeType_Unauthorized) 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 { func InvalidAddress() TMError {
return New(msgInvalidAddress, abci.CodeType_BaseInvalidInput) return New(msgInvalidAddress, abci.CodeType_BaseInvalidInput)
} }

51
stack/chain.go Normal file
View File

@ -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
}

63
stack/chain_test.go Normal file
View File

@ -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)
}
}
}
}

View File

@ -5,6 +5,9 @@ import (
"math/rand" "math/rand"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin" "github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
) )
@ -16,11 +19,13 @@ type secureContext struct {
id nonce id nonce
app string app string
perms []basecoin.Actor perms []basecoin.Actor
log.Logger
} }
func NewContext() basecoin.Context { func NewContext(logger log.Logger) basecoin.Context {
return secureContext{ 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{ return secureContext{
id: c.id, id: c.id,
perms: append(c.perms, perms...), 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 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 // but carry on knowledge that this is a child
func (c secureContext) Reset() basecoin.Context { func (c secureContext) Reset() basecoin.Context {
return secureContext{ return secureContext{
id: c.id, id: c.id,
app: c.app, app: c.app,
perms: nil,
Logger: c.Logger,
} }
} }
@ -77,9 +86,10 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context {
return ctx return ctx
} }
return secureContext{ return secureContext{
id: sc.id, id: sc.id,
app: app, app: app,
perms: sc.perms, perms: sc.perms,
Logger: sc.Logger,
} }
} }

View File

@ -5,6 +5,9 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin" "github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
) )
@ -12,7 +15,7 @@ import (
func TestOK(t *testing.T) { func TestOK(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
ctx := NewContext() ctx := NewContext(log.NewNopLogger())
store := types.NewMemKVStore() store := types.NewMemKVStore()
data := "this looks okay" data := "this looks okay"
tx := basecoin.Tx{} tx := basecoin.Tx{}
@ -30,7 +33,7 @@ func TestOK(t *testing.T) {
func TestFail(t *testing.T) { func TestFail(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
ctx := NewContext() ctx := NewContext(log.NewNopLogger())
store := types.NewMemKVStore() store := types.NewMemKVStore()
msg := "big problem" msg := "big problem"
tx := basecoin.Tx{} tx := basecoin.Tx{}
@ -50,7 +53,7 @@ func TestFail(t *testing.T) {
func TestPanic(t *testing.T) { func TestPanic(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
ctx := NewContext() ctx := NewContext(log.NewNopLogger())
store := types.NewMemKVStore() store := types.NewMemKVStore()
msg := "system crash!" msg := "system crash!"
tx := basecoin.Tx{} tx := basecoin.Tx{}

54
stack/logger.go Normal file
View File

@ -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)
}

View File

@ -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 // Use sets the final handler for the stack and prepares it for use
func (s *Stack) Use(handler basecoin.Handler) *Stack { func (s *Stack) Use(handler basecoin.Handler) *Stack {
if handler == nil { if handler == nil {

View File

@ -8,13 +8,14 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tendermint/basecoin" "github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
"github.com/tendermint/tmlibs/log"
) )
func TestRecovery(t *testing.T) { func TestRecovery(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
// generic args here... // generic args here...
ctx := NewContext() ctx := NewContext(log.NewNopLogger())
store := types.NewMemKVStore() store := types.NewMemKVStore()
tx := basecoin.Tx{} tx := basecoin.Tx{}

View File

@ -1,11 +1,10 @@
package handlers package stack
import ( import (
crypto "github.com/tendermint/go-crypto" crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/basecoin" "github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
) )
@ -22,7 +21,7 @@ func (_ SignedHandler) Name() string {
return NameSigs return NameSigs
} }
var _ stack.Middleware = SignedHandler{} var _ Middleware = SignedHandler{}
func SigPerm(addr []byte) basecoin.Actor { func SigPerm(addr []byte) basecoin.Actor {
return basecoin.NewActor(NameSigs, addr) return basecoin.NewActor(NameSigs, addr)

View File

@ -121,7 +121,7 @@ type Chain struct {
ChainID string `json:"chain_id"` 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} return &Chain{Tx: tx, ChainID: chainID}
} }