diff --git a/modules/base/chain.go b/modules/base/chain.go deleted file mode 100644 index 61f919c0ef..0000000000 --- a/modules/base/chain.go +++ /dev/null @@ -1,68 +0,0 @@ -package base - -import ( - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/stack" - "github.com/cosmos/cosmos-sdk/state" -) - -//nolint -const ( - NameChain = "chain" -) - -// Chain enforces that this tx was bound to the named chain -type Chain struct { - stack.PassInitState - stack.PassInitValidate -} - -// Name of the module - fulfills Middleware interface -func (Chain) Name() string { - return NameChain -} - -var _ stack.Middleware = Chain{} - -// CheckTx makes sure we are on the proper chain - fulfills Middlware interface -func (c Chain) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Checker) (res sdk.CheckResult, err error) { - stx, err := c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx) - if err != nil { - return res, err - } - return next.CheckTx(ctx, store, stx) -} - -// DeliverTx makes sure we are on the proper chain - fulfills Middlware interface -func (c Chain) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Deliver) (res sdk.DeliverResult, err error) { - stx, err := c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx) - if err != nil { - return res, err - } - return next.DeliverTx(ctx, store, stx) -} - -// checkChainTx makes sure the tx is a Chain Tx, it is on the proper chain, -// and it has not expired. -func (c Chain) checkChainTx(chainID string, height uint64, tx sdk.Tx) (sdk.Tx, error) { - // make sure it is a chaintx - ctx, ok := tx.Unwrap().(ChainTx) - if !ok { - return tx, ErrNoChain() - } - - // basic validation - err := ctx.ValidateBasic() - if err != nil { - return tx, err - } - - // compare against state - if ctx.ChainID != chainID { - return tx, ErrWrongChain(ctx.ChainID) - } - if ctx.ExpiresAt != 0 && ctx.ExpiresAt <= height { - return tx, ErrExpired() - } - return ctx.Tx, nil -} diff --git a/modules/base/logger.go b/modules/base/logger.go deleted file mode 100644 index ab15c01f61..0000000000 --- a/modules/base/logger.go +++ /dev/null @@ -1,86 +0,0 @@ -package base - -import ( - "time" - - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/log" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/stack" - "github.com/cosmos/cosmos-sdk/state" -) - -// nolint -const ( - NameLogger = "lggr" -) - -// Logger catches any panics and returns them as errors instead -type Logger struct{} - -// Name of the module - fulfills Middleware interface -func (Logger) Name() string { - return NameLogger -} - -var _ stack.Middleware = Logger{} - -// CheckTx logs time and result - fulfills Middlware interface -func (Logger) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Checker) (res sdk.CheckResult, 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 -} - -// DeliverTx logs time and result - fulfills Middlware interface -func (Logger) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx, next sdk.Deliver) (res sdk.DeliverResult, 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 -} - -// InitState logs time and result - fulfills Middlware interface -func (Logger) InitState(l log.Logger, store state.SimpleDB, module, key, value string, next sdk.InitStater) (string, error) { - start := time.Now() - res, err := next.InitState(l, store, module, key, value) - delta := time.Now().Sub(start) - // TODO: log the value being set also? - l = l.With("duration", micros(delta)).With("mod", module).With("key", key) - if err == nil { - l.Info("InitState", "log", res) - } else { - l.Error("InitState", "err", err) - } - return res, err -} - -// InitValidate logs time and result - fulfills Middlware interface -func (Logger) InitValidate(l log.Logger, store state.SimpleDB, vals []*abci.Validator, next sdk.InitValidater) { - start := time.Now() - next.InitValidate(l, store, vals) - delta := time.Now().Sub(start) - l = l.With("duration", micros(delta)) - l.Info("InitValidate") -} - -// micros returns how many microseconds passed in a call -func micros(d time.Duration) int { - return int(d.Seconds() * 1000000) -} diff --git a/modules/base/tx.go b/modules/base/tx.go deleted file mode 100644 index edd57b6721..0000000000 --- a/modules/base/tx.go +++ /dev/null @@ -1,99 +0,0 @@ -package base - -import ( - "regexp" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/errors" -) - -// nolint -const ( - // for utils... - ByteMultiTx = 0x2 - ByteChainTx = 0x3 -) - -//nolint -const ( - TypeMultiTx = NameMultiplexer + "/tx" - TypeChainTx = NameChain + "/tx" -) - -func init() { - sdk.TxMapper. - RegisterImplementation(MultiTx{}, TypeMultiTx, ByteMultiTx). - RegisterImplementation(ChainTx{}, TypeChainTx, ByteChainTx) -} - -/**** MultiTx ******/ - -// MultiTx - a transaction containing multiple transactions -type MultiTx struct { - Txs []sdk.Tx `json:"txs"` -} - -var _ sdk.TxInner = &MultiTx{} - -//nolint - TxInner Functions -func NewMultiTx(txs ...sdk.Tx) sdk.Tx { - return (MultiTx{Txs: txs}).Wrap() -} -func (mt MultiTx) Wrap() sdk.Tx { - return sdk.Tx{mt} -} -func (mt MultiTx) ValidateBasic() error { - for _, t := range mt.Txs { - err := t.ValidateBasic() - if err != nil { - return err - } - } - return nil -} - -/*** ChainTx ****/ - -// ChainTx locks this tx to one chainTx, wrap with this before signing -type ChainTx struct { - // name of chain, must be [A-Za-z0-9_-]+ - ChainID string `json:"chain_id"` - // block height at which it is no longer valid, 0 means no expiration - ExpiresAt uint64 `json:"expires_at"` - Tx sdk.Tx `json:"tx"` -} - -var _ sdk.TxInner = &ChainTx{} - -var ( - chainPattern = regexp.MustCompile("^[A-Za-z0-9_-]+$") -) - -// NewChainTx wraps a particular tx with the ChainTx wrapper, -// to enforce chain and height -func NewChainTx(chainID string, expires uint64, tx sdk.Tx) sdk.Tx { - c := ChainTx{ - ChainID: chainID, - ExpiresAt: expires, - Tx: tx, - } - return c.Wrap() -} - -//nolint - TxInner Functions -func (c ChainTx) Wrap() sdk.Tx { - return sdk.Tx{c} -} -func (c ChainTx) ValidateBasic() error { - if c.ChainID == "" { - return ErrNoChain() - } - if !chainPattern.MatchString(c.ChainID) { - return ErrWrongChain(c.ChainID) - } - if c.Tx.Empty() { - return errors.ErrUnknownTxType(c.Tx) - } - // TODO: more checks? chainID? - return c.Tx.ValidateBasic() -} diff --git a/modules/base/tx_test.go b/modules/base/tx_test.go deleted file mode 100644 index 82f0ffad7f..0000000000 --- a/modules/base/tx_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package base - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/go-wire/data" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/stack" -) - -func TestEncoding(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - raw := stack.NewRawTx([]byte{0x34, 0xa7}) - // raw2 := stack.NewRawTx([]byte{0x73, 0x86, 0x22}) - - cases := []struct { - Tx sdk.Tx - }{ - {raw}, - // {NewMultiTx(raw, raw2)}, - {NewChainTx("foobar", 0, raw)}, - } - - for idx, tc := range cases { - i := strconv.Itoa(idx) - tx := tc.Tx - - // test json in and out - js, err := data.ToJSON(tx) - require.Nil(err, i) - var jtx sdk.Tx - err = data.FromJSON(js, &jtx) - require.Nil(err, i) - assert.Equal(tx, jtx, i) - - // test wire in and out - bin, err := data.ToWire(tx) - require.Nil(err, i) - var wtx sdk.Tx - err = data.FromWire(bin, &wtx) - require.Nil(err, i) - assert.Equal(tx, wtx, i) - } -} diff --git a/modules/bonus/doc.go b/modules/bonus/doc.go new file mode 100644 index 0000000000..2cb05260dc --- /dev/null +++ b/modules/bonus/doc.go @@ -0,0 +1,6 @@ +/** +Package bonus is a temporary home for various functionalities +that were removed for complexity, but may be added back in +later. +**/ +package bonus diff --git a/modules/base/helpers.go b/modules/bonus/helpers.go similarity index 99% rename from modules/base/helpers.go rename to modules/bonus/helpers.go index 4958a8bfbb..fdd3825f77 100644 --- a/modules/base/helpers.go +++ b/modules/bonus/helpers.go @@ -1,4 +1,4 @@ -package base +package bonus import ( abci "github.com/tendermint/abci/types" diff --git a/modules/base/multiplexer.go b/modules/bonus/multiplexer.go similarity index 99% rename from modules/base/multiplexer.go rename to modules/bonus/multiplexer.go index d899a76ebe..8f82abd1c7 100644 --- a/modules/base/multiplexer.go +++ b/modules/bonus/multiplexer.go @@ -1,4 +1,4 @@ -package base +package bonus import ( "strings" diff --git a/modules/base/multiplexer_test.go b/modules/bonus/multiplexer_test.go similarity index 99% rename from modules/base/multiplexer_test.go rename to modules/bonus/multiplexer_test.go index 97d5324ee6..bf448a492b 100644 --- a/modules/base/multiplexer_test.go +++ b/modules/bonus/multiplexer_test.go @@ -1,12 +1,12 @@ -package base +package bonus import ( "testing" - "github.com/stretchr/testify/assert" sdk "github.com/cosmos/cosmos-sdk" "github.com/cosmos/cosmos-sdk/stack" "github.com/cosmos/cosmos-sdk/state" + "github.com/stretchr/testify/assert" wire "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" "github.com/tendermint/tmlibs/log" diff --git a/modules/util/chain.go b/modules/util/chain.go new file mode 100644 index 0000000000..f2a5bee7f3 --- /dev/null +++ b/modules/util/chain.go @@ -0,0 +1,73 @@ +package util + +import ( + "regexp" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/state" +) + +var ( + // ChainPattern must match any valid chain_id + ChainPattern = regexp.MustCompile("^[A-Za-z0-9_-]+$") +) + +// ChainedTx interface should be implemented by any +// message to bind it to one chain, with an optional +// expiration height +type ChainedTx interface { + GetChain() ChainData +} + +// ChainData is info from the message to bind it to +// chain and time +type ChainData struct { + ChainID string `json:"chain_id"` + ExpiresAt uint64 `json:"expires_at"` +} + +// Chain enforces that this tx was bound to the named chain +type Chain struct{} + +var _ sdk.Decorator = Chain{} + +// CheckTx makes sure we are on the proper chain +// - fulfills Decorator interface +func (c Chain) CheckTx(ctx sdk.Context, store state.SimpleDB, tx interface{}, next sdk.Checker) (res sdk.CheckResult, err error) { + err = c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx) + if err != nil { + return res, err + } + return next.CheckTx(ctx, store, tx) +} + +// DeliverTx makes sure we are on the proper chain +// - fulfills Decorator interface +func (c Chain) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx interface{}, next sdk.Deliverer) (res sdk.DeliverResult, err error) { + err = c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx) + if err != nil { + return res, err + } + return next.DeliverTx(ctx, store, tx) +} + +// checkChainTx makes sure the tx is a ChainedTx, +// it is on the proper chain, and it has not expired. +func (c Chain) checkChainTx(chainID string, height uint64, tx interface{}) error { + // make sure it is a chaintx + ctx, ok := tx.(ChainedTx) + if !ok { + return ErrNoChain() + } + + data := ctx.GetChain() + + // compare against state + if data.ChainID != chainID { + return ErrWrongChain(data.ChainID) + } + if data.ExpiresAt != 0 && data.ExpiresAt <= height { + return ErrExpired() + } + return nil +} diff --git a/modules/base/chain_test.go b/modules/util/chain_test.go similarity index 99% rename from modules/base/chain_test.go rename to modules/util/chain_test.go index 4ce1a53a72..f8037dc624 100644 --- a/modules/base/chain_test.go +++ b/modules/util/chain_test.go @@ -1,4 +1,4 @@ -package base +package util import ( "strconv" diff --git a/modules/base/commands/wrap.go b/modules/util/commands/wrap.go similarity index 100% rename from modules/base/commands/wrap.go rename to modules/util/commands/wrap.go diff --git a/modules/base/errors.go b/modules/util/errors.go similarity index 98% rename from modules/base/errors.go rename to modules/util/errors.go index 4ebf626b8f..b3ef62a187 100644 --- a/modules/base/errors.go +++ b/modules/util/errors.go @@ -1,5 +1,5 @@ //nolint -package base +package util import ( "fmt" diff --git a/modules/base/errors_test.go b/modules/util/errors_test.go similarity index 98% rename from modules/base/errors_test.go rename to modules/util/errors_test.go index 0354cd8c8a..45cfa57cb1 100644 --- a/modules/base/errors_test.go +++ b/modules/util/errors_test.go @@ -1,4 +1,4 @@ -package base +package util import ( "testing" diff --git a/modules/util/logger.go b/modules/util/logger.go new file mode 100644 index 0000000000..7510bfa09f --- /dev/null +++ b/modules/util/logger.go @@ -0,0 +1,72 @@ +package util + +import ( + "time" + + abci "github.com/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/state" +) + +// Logger writes out log messages on every request +type Logger struct{} + +var _ sdk.Decorator = Logger{} + +// CheckTx logs time and result - fulfills Middlware interface +func (Logger) CheckTx(ctx sdk.Context, store state.SimpleDB, tx interface{}, next sdk.Checker) (res sdk.CheckResult, 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 +} + +// DeliverTx logs time and result - fulfills Middlware interface +func (Logger) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx interface{}, next sdk.Deliverer) (res sdk.DeliverResult, 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 +} + +// LogTicker wraps any ticker and records the time it takes. +// Pass in a name to be logged with this to separate out various +// tickers +func LogTicker(clock sdk.Ticker, name string) sdk.Ticker { + res := func(ctx sdk.Context, s state.SimpleDB) ([]*abci.Validator, error) { + start := time.Now() + vals, err := clock.Tick(ctx, s) + delta := time.Now().Sub(start) + l := ctx.With("duration", micros(delta)) + if name != "" { + l = l.With("name", name) + } + if err == nil { + l.Info("Tock") + } else { + l.Error("Tock", "err", err) + } + return vals, err + } + return sdk.TickerFunc(res) +} + +// micros returns how many microseconds passed in a call +func micros(d time.Duration) int { + return int(d.Seconds() * 1000000) +} diff --git a/modules/util/recovery.go b/modules/util/recovery.go new file mode 100644 index 0000000000..952b86a6e4 --- /dev/null +++ b/modules/util/recovery.go @@ -0,0 +1,47 @@ +package util + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/errors" + "github.com/cosmos/cosmos-sdk/state" +) + +// Recovery catches any panics and returns them as errors instead +type Recovery struct{} + +var _ sdk.Decorator = Recovery{} + +// CheckTx catches any panic and converts to error - fulfills Middlware interface +func (Recovery) CheckTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}, next sdk.Checker) (res sdk.CheckResult, err error) { + + defer func() { + if r := recover(); r != nil { + err = normalizePanic(r) + } + }() + return next.CheckTx(ctx, store, tx) +} + +// DeliverTx catches any panic and converts to error - fulfills Middlware interface +func (Recovery) DeliverTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}, next sdk.Deliverer) (res sdk.DeliverResult, err error) { + + defer func() { + if r := recover(); r != nil { + err = normalizePanic(r) + } + }() + return next.DeliverTx(ctx, store, tx) +} + +// normalizePanic makes sure we can get a nice TMError (with stack) out of it +func normalizePanic(p interface{}) error { + if err, isErr := p.(error); isErr { + return errors.Wrap(err) + } + msg := fmt.Sprintf("%v", p) + return errors.ErrInternal(msg) +} diff --git a/modules/util/recovery_test.go b/modules/util/recovery_test.go new file mode 100644 index 0000000000..3361904a7b --- /dev/null +++ b/modules/util/recovery_test.go @@ -0,0 +1,53 @@ +package util + +import ( + "errors" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/tendermint/tmlibs/log" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/state" +) + +func TestRecovery(t *testing.T) { + assert := assert.New(t) + + // generic args here... + ctx := NewContext("test-chain", 20, log.NewNopLogger()) + store := state.NewMemKVStore() + tx := sdk.Tx{} + + cases := []struct { + msg string // what to send to panic + err error // what to send to panic + expected string // expected text in panic + }{ + {"buzz", nil, "buzz"}, + {"", errors.New("some text"), "some text"}, + {"text", errors.New("error"), "error"}, + } + + for idx, tc := range cases { + i := strconv.Itoa(idx) + fail := PanicHandler{Msg: tc.msg, Err: tc.err} + rec := Recovery{} + app := New(rec).Use(fail) + + // make sure check returns error, not a panic crash + _, err := app.CheckTx(ctx, store, tx) + if assert.NotNil(err, i) { + assert.Equal(tc.expected, err.Error(), i) + } + + // make sure deliver returns error, not a panic crash + _, err = app.DeliverTx(ctx, store, tx) + if assert.NotNil(err, i) { + assert.Equal(tc.expected, err.Error(), i) + } + + } +}