diff --git a/app/app.go b/app/app.go index 3b802d372..940520285 100644 --- a/app/app.go +++ b/app/app.go @@ -1,23 +1,22 @@ package app import ( - "encoding/hex" - "encoding/json" "strings" abci "github.com/tendermint/abci/types" - wire "github.com/tendermint/go-wire" + "github.com/tendermint/basecoin" eyes "github.com/tendermint/merkleeyes/client" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + "github.com/tendermint/basecoin/errors" + "github.com/tendermint/basecoin/modules/coin" + "github.com/tendermint/basecoin/stack" sm "github.com/tendermint/basecoin/state" - "github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/version" ) const ( - maxTxSize = 10240 PluginNameBase = "base" ) @@ -25,23 +24,29 @@ type Basecoin struct { eyesCli *eyes.Client state *sm.State cacheState *sm.State - plugins *types.Plugins + handler basecoin.Handler logger log.Logger } -func NewBasecoin(eyesCli *eyes.Client, l log.Logger) *Basecoin { +func NewBasecoin(h basecoin.Handler, eyesCli *eyes.Client, l log.Logger) *Basecoin { state := sm.NewState(eyesCli, l.With("module", "state")) - plugins := types.NewPlugins() return &Basecoin{ + handler: h, eyesCli: eyesCli, state: state, cacheState: nil, - plugins: plugins, logger: l, } } +// placeholder to just handle sendtx +func DefaultHandler() basecoin.Handler { + // use the default stack + h := coin.NewHandler() + return stack.NewDefault().Use(h) +} + // XXX For testing, not thread safe! func (app *Basecoin) GetState() *sm.State { return app.state.CacheWrap() @@ -60,87 +65,85 @@ func (app *Basecoin) Info() abci.ResponseInfo { } } -func (app *Basecoin) RegisterPlugin(plugin types.Plugin) { - app.plugins.RegisterPlugin(plugin) -} - // ABCI::SetOption func (app *Basecoin) SetOption(key string, value string) string { - pluginName, key := splitKey(key) - if pluginName != PluginNameBase { - // Set option on plugin - plugin := app.plugins.GetByName(pluginName) - if plugin == nil { - return "Invalid plugin name: " + pluginName - } - app.logger.Info("SetOption on plugin", "plugin", pluginName, "key", key, "value", value) - return plugin.SetOption(app.state, key, value) - } else { - // Set option on basecoin - switch key { - case "chain_id": - app.state.SetChainID(value) - return "Success" - case "account": - var acc GenesisAccount - err := json.Unmarshal([]byte(value), &acc) - if err != nil { - return "Error decoding acc message: " + err.Error() - } - acc.Balance.Sort() - addr, err := acc.GetAddr() - if err != nil { - return "Invalid address: " + err.Error() - } - app.state.SetAccount(addr, acc.ToAccount()) - app.logger.Info("SetAccount", "addr", hex.EncodeToString(addr), "acc", acc) + // TODO + return "todo" + // pluginName, key := splitKey(key) + // if pluginName != PluginNameBase { + // // Set option on plugin + // plugin := app.plugins.GetByName(pluginName) + // if plugin == nil { + // return "Invalid plugin name: " + pluginName + // } + // app.logger.Info("SetOption on plugin", "plugin", pluginName, "key", key, "value", value) + // return plugin.SetOption(app.state, key, value) + // } else { + // // Set option on basecoin + // switch key { + // case "chain_id": + // app.state.SetChainID(value) + // return "Success" + // case "account": + // var acc GenesisAccount + // err := json.Unmarshal([]byte(value), &acc) + // if err != nil { + // return "Error decoding acc message: " + err.Error() + // } + // acc.Balance.Sort() + // addr, err := acc.GetAddr() + // if err != nil { + // return "Invalid address: " + err.Error() + // } + // app.state.SetAccount(addr, acc.ToAccount()) + // app.logger.Info("SetAccount", "addr", hex.EncodeToString(addr), "acc", acc) - return "Success" - } - return "Unrecognized option key " + key - } + // return "Success" + // } + // return "Unrecognized option key " + key + // } } // ABCI::DeliverTx -func (app *Basecoin) DeliverTx(txBytes []byte) (res abci.Result) { - if len(txBytes) > maxTxSize { - return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum") - } - - // Decode tx - var tx types.Tx - err := wire.ReadBinaryBytes(txBytes, &tx) +func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result { + tx, err := basecoin.LoadTx(txBytes) if err != nil { - return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + return errors.Result(err) } - // Validate and exec tx - res = sm.ExecTx(app.state, app.plugins, tx, false, nil) - if res.IsErr() { - return res.PrependLog("Error in DeliverTx") + // TODO: can we abstract this setup and commit logic?? + cache := app.state.CacheWrap() + ctx := stack.NewContext(app.state.GetChainID(), + app.logger.With("call", "delivertx")) + res, err := app.handler.DeliverTx(ctx, cache, tx) + + if err != nil { + // discard the cache... + return errors.Result(err) } - return res + // commit the cache and return result + cache.CacheSync() + return res.ToABCI() } // ABCI::CheckTx -func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) { - if len(txBytes) > maxTxSize { - return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum") - } - - // Decode tx - var tx types.Tx - err := wire.ReadBinaryBytes(txBytes, &tx) +func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { + tx, err := basecoin.LoadTx(txBytes) if err != nil { - return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + return errors.Result(err) } - // Validate tx - res = sm.ExecTx(app.cacheState, app.plugins, tx, true, nil) - if res.IsErr() { - return res.PrependLog("Error in CheckTx") + // TODO: can we abstract this setup and commit logic?? + ctx := stack.NewContext(app.state.GetChainID(), + app.logger.With("call", "checktx")) + // checktx generally shouldn't touch the state, but we don't care + // here on the framework level, since the cacheState is thrown away next block + res, err := app.handler.CheckTx(ctx, app.cacheState, tx) + + if err != nil { + return errors.Result(err) } - return abci.OK + return res.ToABCI() } // ABCI::Query @@ -151,12 +154,6 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu return } - // handle special path for account info - if reqQuery.Path == "/account" { - reqQuery.Path = "/key" - reqQuery.Data = types.AccountKey(reqQuery.Data) - } - resQuery, err := app.eyesCli.QuerySync(reqQuery) if err != nil { resQuery.Log = "Failed to query MerkleEyes: " + err.Error() @@ -183,24 +180,24 @@ func (app *Basecoin) Commit() (res abci.Result) { // ABCI::InitChain func (app *Basecoin) InitChain(validators []*abci.Validator) { - for _, plugin := range app.plugins.GetList() { - plugin.InitChain(app.state, validators) - } + // for _, plugin := range app.plugins.GetList() { + // plugin.InitChain(app.state, validators) + // } } // ABCI::BeginBlock func (app *Basecoin) BeginBlock(hash []byte, header *abci.Header) { - for _, plugin := range app.plugins.GetList() { - plugin.BeginBlock(app.state, hash, header) - } + // for _, plugin := range app.plugins.GetList() { + // plugin.BeginBlock(app.state, hash, header) + // } } // ABCI::EndBlock func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) { - for _, plugin := range app.plugins.GetList() { - pluginRes := plugin.EndBlock(app.state, height) - res.Diffs = append(res.Diffs, pluginRes.Diffs...) - } + // for _, plugin := range app.plugins.GetList() { + // pluginRes := plugin.EndBlock(app.state, height) + // res.Diffs = append(res.Diffs, pluginRes.Diffs...) + // } return } diff --git a/app/app_test.go b/app/app_test.go index e407b1e55..164585320 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -56,7 +56,8 @@ func (at *appTest) reset() { at.accOut = types.MakeAcc("output0") eyesCli := eyes.NewLocalClient("", 0) - at.app = NewBasecoin(eyesCli, log.TestingLogger().With("module", "app")) + at.app = NewBasecoin(DefaultHandler(), eyesCli, + log.TestingLogger().With("module", "app")) res := at.app.SetOption("base/chain_id", at.chainID) require.EqualValues(at.t, res, "Success") @@ -105,7 +106,8 @@ func TestSetOption(t *testing.T) { require := require.New(t) eyesCli := eyes.NewLocalClient("", 0) - app := NewBasecoin(eyesCli, log.TestingLogger().With("module", "app")) + app := NewBasecoin(DefaultHandler(), eyesCli, + log.TestingLogger().With("module", "app")) //testing ChainID chainID := "testChain" diff --git a/app/genesis_test.go b/app/genesis_test.go index 1fb84acd9..2575f3fa9 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -19,7 +19,7 @@ const genesisAcctFilepath = "./testdata/genesis2.json" func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { eyesCli := eyescli.NewLocalClient("", 0) - app := NewBasecoin(eyesCli, log.TestingLogger()) + app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger()) err := app.LoadGenesis("./testdata/genesis3.json") require.Nil(t, err, "%+v", err) } @@ -28,7 +28,7 @@ func TestLoadGenesis(t *testing.T) { assert, require := assert.New(t), require.New(t) eyesCli := eyescli.NewLocalClient("", 0) - app := NewBasecoin(eyesCli, log.TestingLogger()) + app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger()) err := app.LoadGenesis(genesisFilepath) require.Nil(err, "%+v", err) @@ -65,7 +65,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) { assert, require := assert.New(t), require.New(t) eyesCli := eyescli.NewLocalClient("", 0) - app := NewBasecoin(eyesCli, log.TestingLogger()) + app := NewBasecoin(DefaultHandler(), eyesCli, log.TestingLogger()) err := app.LoadGenesis(genesisAcctFilepath) require.Nil(err, "%+v", err) diff --git a/cmd/basecoin/commands/start.go b/cmd/basecoin/commands/start.go index d27ddd152..8f12efed3 100644 --- a/cmd/basecoin/commands/start.go +++ b/cmd/basecoin/commands/start.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/viper" "github.com/tendermint/abci/server" + "github.com/tendermint/basecoin" eyesApp "github.com/tendermint/merkleeyes/app" eyes "github.com/tendermint/merkleeyes/client" "github.com/tendermint/tmlibs/cli" @@ -21,6 +22,8 @@ import ( "github.com/tendermint/tendermint/types" "github.com/tendermint/basecoin/app" + "github.com/tendermint/basecoin/modules/coin" + "github.com/tendermint/basecoin/stack" ) var StartCmd = &cobra.Command{ @@ -48,6 +51,22 @@ func init() { tcmd.AddNodeFlags(StartCmd) } +// TODO: setup handler instead of Plugins +func getHandler() basecoin.Handler { + // use the default stack + h := coin.NewHandler() + app := stack.NewDefault("change-this").Use(h) + return app + + // register IBC plugn + // basecoinApp.RegisterPlugin(NewIBCPlugin()) + + // register all other plugins + // for _, p := range plugins { + // basecoinApp.RegisterPlugin(p.newPlugin()) + // } +} + func startCmd(cmd *cobra.Command, args []string) error { rootDir := viper.GetString(cli.HomeFlag) meyes := viper.GetString(FlagEyes) @@ -66,15 +85,8 @@ func startCmd(cmd *cobra.Command, args []string) error { } // Create Basecoin app - basecoinApp := app.NewBasecoin(eyesCli, logger.With("module", "app")) - - // register IBC plugn - // basecoinApp.RegisterPlugin(NewIBCPlugin()) - - // register all other plugins - for _, p := range plugins { - basecoinApp.RegisterPlugin(p.newPlugin()) - } + h := app.DefaultHandler() + basecoinApp := app.NewBasecoin(h, eyesCli, logger.With("module", "app")) // if chain_id has not been set yet, load the genesis. // else, assume it's been loaded diff --git a/context.go b/context.go index a7737d5f5..8532cc6c9 100644 --- a/context.go +++ b/context.go @@ -34,4 +34,5 @@ type Context interface { HasPermission(perm Actor) bool IsParent(ctx Context) bool Reset() Context + ChainID() string } diff --git a/modules/coin/handler_test.go b/modules/coin/handler_test.go index a2804f259..8027b489c 100644 --- a/modules/coin/handler_test.go +++ b/modules/coin/handler_test.go @@ -68,7 +68,7 @@ func TestHandlerValidation(t *testing.T) { } for i, tc := range cases { - ctx := stack.MockContext().WithPermissions(tc.perms...) + ctx := stack.MockContext("base-chain").WithPermissions(tc.perms...) _, err := checkTx(ctx, tc.tx) if tc.valid { assert.Nil(err, "%d: %+v", i, err) @@ -142,7 +142,7 @@ func TestDeliverTx(t *testing.T) { require.Nil(err, "%d: %+v", i, err) } - ctx := stack.MockContext().WithPermissions(tc.perms...) + ctx := stack.MockContext("base-chain").WithPermissions(tc.perms...) _, err := h.DeliverTx(ctx, store, tc.tx) if len(tc.final) > 0 { // valid assert.Nil(err, "%d: %+v", i, err) diff --git a/stack/chain.go b/stack/chain.go index ef174495b..70da742f6 100644 --- a/stack/chain.go +++ b/stack/chain.go @@ -12,9 +12,7 @@ const ( ) // Chain enforces that this tx was bound to the named chain -type Chain struct { - ChainID string -} +type Chain struct{} func (_ Chain) Name() string { return NameRecovery @@ -23,7 +21,7 @@ func (_ Chain) Name() string { 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) + stx, err := c.checkChain(ctx.ChainID(), tx) if err != nil { return res, err } @@ -31,7 +29,7 @@ func (c Chain) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx } 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) + stx, err := c.checkChain(ctx.ChainID(), tx) if err != nil { return res, err } @@ -39,12 +37,12 @@ func (c Chain) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin. } // checkChain makes sure the tx is a txs.Chain and -func (c Chain) checkChain(tx basecoin.Tx) (basecoin.Tx, error) { +func (c Chain) checkChain(chainID string, tx basecoin.Tx) (basecoin.Tx, error) { ctx, ok := tx.Unwrap().(*txs.Chain) if !ok { return tx, errors.ErrNoChain() } - if ctx.ChainID != c.ChainID { + if ctx.ChainID != chainID { return tx, errors.ErrWrongChain(ctx.ChainID) } return ctx.Tx, nil diff --git a/stack/chain_test.go b/stack/chain_test.go index 8d7833ae8..c9afaa700 100644 --- a/stack/chain_test.go +++ b/stack/chain_test.go @@ -30,12 +30,12 @@ func TestChain(t *testing.T) { } // generic args here... - ctx := NewContext(log.NewNopLogger()) + ctx := NewContext(chainID, log.NewNopLogger()) store := types.NewMemKVStore() // build the stack ok := OKHandler{msg} - app := New(Chain{chainID}).Use(ok) + app := New(Chain{}).Use(ok) for idx, tc := range cases { i := strconv.Itoa(idx) diff --git a/stack/context.go b/stack/context.go index 09703b944..6124a9678 100644 --- a/stack/context.go +++ b/stack/context.go @@ -17,20 +17,26 @@ type nonce int64 type secureContext struct { id nonce + chain string app string perms []basecoin.Actor log.Logger } -func NewContext(logger log.Logger) basecoin.Context { +func NewContext(chain string, logger log.Logger) basecoin.Context { return secureContext{ id: nonce(rand.Int63()), + chain: chain, Logger: logger, } } var _ basecoin.Context = secureContext{} +func (c secureContext) ChainID() string { + return c.chain +} + // WithPermissions will panic if they try to set permission without the proper app func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context { // the guard makes sure you only set permissions for the app you are inside @@ -44,6 +50,7 @@ func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context return secureContext{ id: c.id, + chain: c.chain, app: c.app, perms: append(c.perms, perms...), Logger: c.Logger, @@ -73,6 +80,7 @@ func (c secureContext) IsParent(other basecoin.Context) bool { func (c secureContext) Reset() basecoin.Context { return secureContext{ id: c.id, + chain: c.chain, app: c.app, perms: nil, Logger: c.Logger, @@ -88,6 +96,7 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context { } return secureContext{ id: sc.id, + chain: sc.chain, app: app, perms: sc.perms, Logger: sc.Logger, diff --git a/stack/helpers_test.go b/stack/helpers_test.go index d792cf953..10e365580 100644 --- a/stack/helpers_test.go +++ b/stack/helpers_test.go @@ -15,7 +15,7 @@ import ( func TestOK(t *testing.T) { assert := assert.New(t) - ctx := NewContext(log.NewNopLogger()) + ctx := NewContext("test-chain", log.NewNopLogger()) store := types.NewMemKVStore() data := "this looks okay" tx := basecoin.Tx{} @@ -33,7 +33,7 @@ func TestOK(t *testing.T) { func TestFail(t *testing.T) { assert := assert.New(t) - ctx := NewContext(log.NewNopLogger()) + ctx := NewContext("test-chain", log.NewNopLogger()) store := types.NewMemKVStore() msg := "big problem" tx := basecoin.Tx{} @@ -53,7 +53,7 @@ func TestFail(t *testing.T) { func TestPanic(t *testing.T) { assert := assert.New(t) - ctx := NewContext(log.NewNopLogger()) + ctx := NewContext("test-chain", log.NewNopLogger()) store := types.NewMemKVStore() msg := "system crash!" tx := basecoin.Tx{} diff --git a/stack/middleware.go b/stack/middleware.go index 7dfc5cf04..f61b233da 100644 --- a/stack/middleware.go +++ b/stack/middleware.go @@ -57,12 +57,12 @@ 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 { +func NewDefault(middlewares ...Middleware) *Stack { mids := []Middleware{ Logger{}, Recovery{}, Signatures{}, - Chain{chainID}, + Chain{}, } mids = append(mids, middlewares...) return New(mids...) diff --git a/stack/middleware_test.go b/stack/middleware_test.go index 74fa431c8..adbb97093 100644 --- a/stack/middleware_test.go +++ b/stack/middleware_test.go @@ -17,7 +17,7 @@ func TestPermissionSandbox(t *testing.T) { require := require.New(t) // generic args - ctx := NewContext(log.NewNopLogger()) + ctx := NewContext("test-chain", log.NewNopLogger()) store := types.NewMemKVStore() raw := txs.NewRaw([]byte{1, 2, 3, 4}) rawBytes, err := data.ToWire(raw) diff --git a/stack/mock.go b/stack/mock.go index dbf8c2df7..443f83dfd 100644 --- a/stack/mock.go +++ b/stack/mock.go @@ -10,17 +10,23 @@ import ( type mockContext struct { perms []basecoin.Actor + chain string log.Logger } -func MockContext() basecoin.Context { +func MockContext(chain string) basecoin.Context { return mockContext{ + chain: chain, Logger: log.NewNopLogger(), } } var _ basecoin.Context = mockContext{} +func (c mockContext) ChainID() string { + return c.chain +} + // WithPermissions will panic if they try to set permission without the proper app func (c mockContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context { return mockContext{ diff --git a/stack/recovery_test.go b/stack/recovery_test.go index 58a6ba66a..54fcbe693 100644 --- a/stack/recovery_test.go +++ b/stack/recovery_test.go @@ -15,7 +15,7 @@ func TestRecovery(t *testing.T) { assert := assert.New(t) // generic args here... - ctx := NewContext(log.NewNopLogger()) + ctx := NewContext("test-chain", log.NewNopLogger()) store := types.NewMemKVStore() tx := basecoin.Tx{} diff --git a/stack/signature_test.go b/stack/signature_test.go index 1ca618c4c..1e6f484ab 100644 --- a/stack/signature_test.go +++ b/stack/signature_test.go @@ -17,7 +17,7 @@ func TestSignatureChecks(t *testing.T) { assert := assert.New(t) // generic args - ctx := NewContext(log.NewNopLogger()) + ctx := NewContext("test-chain", log.NewNopLogger()) store := types.NewMemKVStore() raw := txs.NewRaw([]byte{1, 2, 3, 4}) diff --git a/tx.go b/tx.go index 800a41752..8336f02b6 100644 --- a/tx.go +++ b/tx.go @@ -1,5 +1,12 @@ package basecoin +import ( + "github.com/pkg/errors" + "github.com/tendermint/go-wire/data" +) + +const maxTxSize = 10240 + // TxInner is the interface all concrete transactions should implement. // // It adds bindings for clean un/marhsaling of the various implementations @@ -15,6 +22,20 @@ type TxInner interface { ValidateBasic() error } +// LoadTx parses a tx from data +// +// TODO: label both errors with abci.CodeType_EncodingError +// need to move errors to avoid import cycle +func LoadTx(bin []byte) (tx Tx, err error) { + if len(bin) > maxTxSize { + return tx, errors.New("Tx size exceeds maximum") + } + + // Decode tx + err = data.FromWire(bin, &tx) + return tx, err +} + // TODO: do we need this abstraction? TxLayer??? // please review again after implementing "middleware"