diff --git a/handler.go b/handler.go index 97356c4ab..5acc175dd 100644 --- a/handler.go +++ b/handler.go @@ -77,3 +77,18 @@ type InitStater interface { InitState(logger log.Logger, store state.SimpleDB, module, key, value string) (string, error) } + +////////////////////////////////////////////////// +// Helper methods + +// Msg allows us to get the actual tx from a structure with lots of +// decorator information. This is usually what should be passed to Handlers. +type Msg interface { + GetTx() interface{} +} + +// MustGetTx forces the msg to the interface and extracts the tx +func MustGetTx(msg interface{}) interface{} { + m := msg.(Msg) + return m.GetTx() +} diff --git a/modules/util/chain.go b/util/chain.go similarity index 85% rename from modules/util/chain.go rename to util/chain.go index f2a5bee7f..d4ce6049b 100644 --- a/modules/util/chain.go +++ b/util/chain.go @@ -33,7 +33,9 @@ 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) { +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 @@ -43,7 +45,9 @@ func (c Chain) CheckTx(ctx sdk.Context, store state.SimpleDB, tx interface{}, ne // 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) { +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 diff --git a/modules/util/chain_test.go b/util/chain_test.go similarity index 65% rename from modules/util/chain_test.go rename to util/chain_test.go index f8037dc62..8ec96fb86 100644 --- a/modules/util/chain_test.go +++ b/util/chain_test.go @@ -6,42 +6,28 @@ import ( "github.com/stretchr/testify/assert" - "github.com/tendermint/tmlibs/log" - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/stack" "github.com/cosmos/cosmos-sdk/state" ) -func TestChainValidate(t *testing.T) { - assert := assert.New(t) - raw := stack.NewRawTx([]byte{1, 2, 3, 4}) - - cases := []struct { - name string - expires uint64 - valid bool - }{ - {"hello", 0, true}, - {"one-2-three", 123, true}, - {"super!@#$%@", 0, false}, - {"WISH_2_be", 14, true}, - {"öhhh", 54, false}, +func NewChainTx(name string, height uint64, data []byte) ChainedTx { + return chainTx{ + ChainData: ChainData{name, height}, + Data: data, } +} - for _, tc := range cases { - tx := NewChainTx(tc.name, tc.expires, raw) - err := tx.ValidateBasic() - if tc.valid { - assert.Nil(err, "%s: %+v", tc.name, err) - } else { - assert.NotNil(err, tc.name) - } - } +type chainTx struct { + ChainData + Data []byte +} - empty := NewChainTx("okay", 0, sdk.Tx{}) - err := empty.ValidateBasic() - assert.NotNil(err) +func (c chainTx) GetChain() ChainData { + return c.ChainData +} + +func (c chainTx) GetTx() interface{} { + return RawTx{c.Data} } func TestChain(t *testing.T) { @@ -50,9 +36,9 @@ func TestChain(t *testing.T) { chainID := "my-chain" height := uint64(100) - raw := stack.NewRawTx([]byte{1, 2, 3, 4}) + raw := []byte{1, 2, 3, 4} cases := []struct { - tx sdk.Tx + tx interface{} valid bool errorMsg string }{ @@ -71,12 +57,12 @@ func TestChain(t *testing.T) { } // generic args here... - ctx := stack.NewContext(chainID, height, log.NewNopLogger()) + ctx := MockContext(chainID, height) store := state.NewMemKVStore() // build the stack - ok := stack.OKHandler{Log: msg} - app := stack.New(Chain{}).Use(ok) + ok := OKHandler{Log: msg} + app := sdk.ChainDecorators(Chain{}).WithHandler(ok) for idx, tc := range cases { i := strconv.Itoa(idx) diff --git a/modules/util/commands/wrap.go b/util/commands/wrap.go similarity index 100% rename from modules/util/commands/wrap.go rename to util/commands/wrap.go diff --git a/modules/util/errors.go b/util/errors.go similarity index 100% rename from modules/util/errors.go rename to util/errors.go diff --git a/modules/util/errors_test.go b/util/errors_test.go similarity index 100% rename from modules/util/errors_test.go rename to util/errors_test.go diff --git a/util/helpers.go b/util/helpers.go new file mode 100644 index 000000000..ba2849a2b --- /dev/null +++ b/util/helpers.go @@ -0,0 +1,127 @@ +package util + +import ( + "github.com/tendermint/go-wire/data" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/errors" + "github.com/cosmos/cosmos-sdk/state" +) + +//nolint +const ( + // ByteRawTx = 0xF0 + // ByteCheckTx = 0xF1 + // ByteFailTx = 0xF2 + + // TypeRawTx = NameOK + "/raw" // this will just say a-ok to RawTx + // TypeCheckTx = NameCheck + "/tx" + // TypeFailTx = NameFail + "/tx" + + rawMaxSize = 2000 * 1000 +) + +func init() { + // sdk.TxMapper. + // RegisterImplementation(RawTx{}, TypeRawTx, ByteRawTx). + // RegisterImplementation(CheckTx{}, TypeCheckTx, ByteCheckTx). + // RegisterImplementation(FailTx{}, TypeFailTx, ByteFailTx) +} + +// RawTx just contains bytes that can be hex-ified +type RawTx struct { + Data data.Bytes +} + +// ValidateBasic can ensure a limited size of tx +func (r RawTx) ValidateBasic() error { + if len(r.Data) > rawMaxSize { + return errors.ErrTooLarge() + } + return nil +} + +// OKHandler just used to return okay to everything +type OKHandler struct { + Log string +} + +var _ sdk.Handler = OKHandler{} + +// CheckTx always returns an empty success tx +func (ok OKHandler) CheckTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}) (res sdk.CheckResult, err error) { + return sdk.CheckResult{Log: ok.Log}, nil +} + +// DeliverTx always returns an empty success tx +func (ok OKHandler) DeliverTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}) (res sdk.DeliverResult, err error) { + return sdk.DeliverResult{Log: ok.Log}, nil +} + +// EchoHandler returns success, echoing res.Data = tx bytes +type EchoHandler struct{} + +var _ sdk.Handler = EchoHandler{} + +// CheckTx returns input if RawTx comes in, otherwise panic +func (EchoHandler) CheckTx(ctx sdk.Context, store state.SimpleDB, + msg interface{}) (res sdk.CheckResult, err error) { + raw := sdk.MustGetTx(msg).(RawTx) + return sdk.CheckResult{Data: raw.Data}, nil +} + +// DeliverTx returns input if RawTx comes in, otherwise panic +func (EchoHandler) DeliverTx(ctx sdk.Context, store state.SimpleDB, + msg interface{}) (res sdk.DeliverResult, err error) { + raw := sdk.MustGetTx(msg).(RawTx) + return sdk.DeliverResult{Data: raw.Data}, nil +} + +// FailHandler always returns an error +type FailHandler struct { + Err error +} + +var _ sdk.Handler = FailHandler{} + +// CheckTx always returns the given error +func (f FailHandler) CheckTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}) (res sdk.CheckResult, err error) { + return res, errors.Wrap(f.Err) +} + +// DeliverTx always returns the given error +func (f FailHandler) DeliverTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}) (res sdk.DeliverResult, err error) { + return res, errors.Wrap(f.Err) +} + +// PanicHandler always panics, using the given error (first choice) or msg (fallback) +type PanicHandler struct { + Msg string + Err error +} + +var _ sdk.Handler = PanicHandler{} + +// CheckTx always panics +func (p PanicHandler) CheckTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}) (res sdk.CheckResult, err error) { + + if p.Err != nil { + panic(p.Err) + } + panic(p.Msg) +} + +// DeliverTx always panics +func (p PanicHandler) DeliverTx(ctx sdk.Context, store state.SimpleDB, + tx interface{}) (res sdk.DeliverResult, err error) { + + if p.Err != nil { + panic(p.Err) + } + panic(p.Msg) +} diff --git a/util/helpers_test.go b/util/helpers_test.go new file mode 100644 index 000000000..9e036483c --- /dev/null +++ b/util/helpers_test.go @@ -0,0 +1,62 @@ +package util + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/state" +) + +func TestOK(t *testing.T) { + assert := assert.New(t) + + ctx := MockContext("test-chain", 20) + store := state.NewMemKVStore() + data := "this looks okay" + tx := sdk.Tx{} + + ok := OKHandler{Log: data} + res, err := ok.CheckTx(ctx, store, tx) + assert.Nil(err, "%+v", err) + assert.Equal(data, res.Log) + + dres, err := ok.DeliverTx(ctx, store, tx) + assert.Nil(err, "%+v", err) + assert.Equal(data, dres.Log) +} + +func TestFail(t *testing.T) { + assert := assert.New(t) + + ctx := MockContext("test-chain", 20) + store := state.NewMemKVStore() + msg := "big problem" + tx := sdk.Tx{} + + fail := FailHandler{Err: errors.New(msg)} + _, err := fail.CheckTx(ctx, store, tx) + if assert.NotNil(err) { + assert.Equal(msg, err.Error()) + } + + _, err = fail.DeliverTx(ctx, store, tx) + if assert.NotNil(err) { + assert.Equal(msg, err.Error()) + } +} + +func TestPanic(t *testing.T) { + assert := assert.New(t) + + ctx := MockContext("test-chain", 20) + store := state.NewMemKVStore() + msg := "system crash!" + tx := sdk.Tx{} + + fail := PanicHandler{Msg: msg} + assert.Panics(func() { fail.CheckTx(ctx, store, tx) }) + assert.Panics(func() { fail.DeliverTx(ctx, store, tx) }) +} diff --git a/modules/util/logger.go b/util/logger.go similarity index 100% rename from modules/util/logger.go rename to util/logger.go diff --git a/util/mock.go b/util/mock.go new file mode 100644 index 000000000..b6f7975da --- /dev/null +++ b/util/mock.go @@ -0,0 +1,93 @@ +package util + +import ( + "math/rand" + + "github.com/tendermint/tmlibs/log" + + sdk "github.com/cosmos/cosmos-sdk" +) + +// store nonce as it's own type so no one can even try to fake it +type nonce int64 + +type naiveContext struct { + id nonce + chain string + height uint64 + perms []sdk.Actor + log.Logger +} + +// MockContext returns a simple, non-checking context for test cases. +// +// Always use NewContext() for production code to sandbox malicious code better +func MockContext(chain string, height uint64) sdk.Context { + return naiveContext{ + id: nonce(rand.Int63()), + chain: chain, + height: height, + Logger: log.NewNopLogger(), + } +} + +var _ sdk.Context = naiveContext{} + +func (c naiveContext) ChainID() string { + return c.chain +} + +func (c naiveContext) BlockHeight() uint64 { + return c.height +} + +// WithPermissions will panic if they try to set permission without the proper app +func (c naiveContext) WithPermissions(perms ...sdk.Actor) sdk.Context { + return naiveContext{ + id: c.id, + chain: c.chain, + height: c.height, + perms: append(c.perms, perms...), + Logger: c.Logger, + } +} + +func (c naiveContext) HasPermission(perm sdk.Actor) bool { + for _, p := range c.perms { + if p.Equals(perm) { + return true + } + } + return false +} + +func (c naiveContext) GetPermissions(chain, app string) (res []sdk.Actor) { + for _, p := range c.perms { + if chain == p.ChainID { + if app == "" || app == p.App { + res = append(res, p) + } + } + } + return res +} + +// IsParent ensures that this is derived from the given secureClient +func (c naiveContext) IsParent(other sdk.Context) bool { + nc, ok := other.(naiveContext) + if !ok { + return false + } + return c.id == nc.id +} + +// Reset should clear out all permissions, +// but carry on knowledge that this is a child +func (c naiveContext) Reset() sdk.Context { + return naiveContext{ + id: c.id, + chain: c.chain, + height: c.height, + Logger: c.Logger, + } +} diff --git a/modules/util/recovery.go b/util/recovery.go similarity index 100% rename from modules/util/recovery.go rename to util/recovery.go diff --git a/modules/util/recovery_test.go b/util/recovery_test.go similarity index 88% rename from modules/util/recovery_test.go rename to util/recovery_test.go index 3361904a7..4a3dc0c54 100644 --- a/modules/util/recovery_test.go +++ b/util/recovery_test.go @@ -7,8 +7,6 @@ import ( "github.com/stretchr/testify/assert" - "github.com/tendermint/tmlibs/log" - sdk "github.com/cosmos/cosmos-sdk" "github.com/cosmos/cosmos-sdk/state" ) @@ -17,9 +15,9 @@ func TestRecovery(t *testing.T) { assert := assert.New(t) // generic args here... - ctx := NewContext("test-chain", 20, log.NewNopLogger()) + ctx := MockContext("test-chain", 20) store := state.NewMemKVStore() - tx := sdk.Tx{} + tx := 0 // we ignore it, so it can be anything cases := []struct { msg string // what to send to panic @@ -35,7 +33,7 @@ func TestRecovery(t *testing.T) { i := strconv.Itoa(idx) fail := PanicHandler{Msg: tc.msg, Err: tc.err} rec := Recovery{} - app := New(rec).Use(fail) + app := sdk.ChainDecorators(rec).WithHandler(fail) // make sure check returns error, not a panic crash _, err := app.CheckTx(ctx, store, tx)