diff --git a/PENDING.md b/PENDING.md index 6f82b55e62..8e7f45d86c 100644 --- a/PENDING.md +++ b/PENDING.md @@ -4,6 +4,8 @@ BREAKING CHANGES * [baseapp] Msgs are no longer run on CheckTx, removed `ctx.IsCheckTx()` * [x/gov] CLI flag changed from `proposalID` to `proposal-id` * [x/stake] Fixed the period check for the inflation calculation +* [baseapp] NewBaseApp constructor now takes sdk.TxDecoder as argument instead of wire.Codec +* [x/auth] Default TxDecoder can be found in `x/auth` rather than baseapp * \#1606 The following CLI commands have been switched to use `--from` * `gaiacli stake create-validator --address-validator` * `gaiacli stake edit-validator --address-validator` @@ -32,6 +34,7 @@ IMPROVEMENTS * [baseapp] Allow any alphanumeric character in route * [cli] Improve error messages for all txs when the account doesn't exist * [tools] Remove `rm -rf vendor/` from `make get_vendor_deps` +* [x/auth] Recover ErrorOutOfGas panic in order to set sdk.Result attributes correctly * [x/stake] Add revoked to human-readable validator * [x/gov] Votes on a proposal can now be queried * [x/bank] Unit tests are now table-driven diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index ca5e8fd97b..0487237555 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -18,7 +18,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" ) // Key to store the header in the DB itself. @@ -44,14 +43,12 @@ type BaseApp struct { // initialized on creation Logger log.Logger name string // application name from abci.Info - cdc *wire.Codec // Amino codec db dbm.DB // common DB backend cms sdk.CommitMultiStore // Main (uncached) state router Router // handle any kind of message codespacer *sdk.Codespacer // handle module codespacing + txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - // must be set - txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx anteHandler sdk.AnteHandler // ante handler for fee and auth // may be nil @@ -80,17 +77,17 @@ var _ abci.Application = (*BaseApp)(nil) // (e.g. functional options). // // NOTE: The db is used to store the version number for now. +// Accepts a user-defined txDecoder // Accepts variable number of option functions, which act on the BaseApp to set configuration choices -func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB, options ...func(*BaseApp)) *BaseApp { +func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp)) *BaseApp { app := &BaseApp{ Logger: logger, name: name, - cdc: cdc, db: db, cms: store.NewCommitMultiStore(db), router: NewRouter(), codespacer: sdk.NewCodespacer(), - txDecoder: defaultTxDecoder(cdc), + txDecoder: txDecoder, } // Register the undefined & root codespaces, which should not be used by @@ -135,35 +132,6 @@ func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) { app.cms.MountStoreWithDB(key, typ, nil) } -// Set the txDecoder function -func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) { - app.txDecoder = txDecoder -} - -// default custom logic for transaction decoding -// TODO: remove auth and wire dependencies from baseapp -// - move this to auth.DefaultTxDecoder -// - set the default here to JSON decode like docs/examples/app1 (it will fail -// for multiple messages ;)) -// - pass a TxDecoder into NewBaseApp, instead of a codec. -func defaultTxDecoder(cdc *wire.Codec) sdk.TxDecoder { - return func(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx = auth.StdTx{} - - if len(txBytes) == 0 { - return nil, sdk.ErrTxDecode("txBytes are empty") - } - - // StdTx.Msg is an interface. The concrete types - // are registered by MakeTxCodec - err := cdc.UnmarshalBinary(txBytes, &tx) - if err != nil { - return nil, sdk.ErrTxDecode("").TraceSDK(err.Error()) - } - return tx, nil - } -} - // nolint - Set functions func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) { app.initChainer = initChainer @@ -364,7 +332,9 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc default: result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result() } - value := app.cdc.MustMarshalBinary(result) + + // Encode with json + value := wire.Cdc.MustMarshalBinary(result) return abci.ResponseQuery{ Code: uint32(sdk.ABCICodeOK), Value: value, diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index b2e0760eab..26e398b4ed 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -30,7 +30,7 @@ func newBaseApp(name string) *BaseApp { db := dbm.NewMemDB() codec := wire.NewCodec() registerTestCodec(codec) - return NewBaseApp(name, codec, logger, db) + return NewBaseApp(name, logger, db, testTxDecoder(codec)) } func registerTestCodec(cdc *wire.Codec) { @@ -49,8 +49,6 @@ func setupBaseApp(t *testing.T) (*BaseApp, *sdk.KVStoreKey, *sdk.KVStoreKey) { app := newBaseApp(t.Name()) require.Equal(t, t.Name(), app.Name()) - app.SetTxDecoder(testTxDecoder(app.cdc)) - // make some cap keys capKey1 := sdk.NewKVStoreKey("key1") capKey2 := sdk.NewKVStoreKey("key2") @@ -85,7 +83,7 @@ func TestLoadVersion(t *testing.T) { logger := defaultLogger() db := dbm.NewMemDB() name := t.Name() - app := NewBaseApp(name, nil, logger, db) + app := NewBaseApp(name, logger, db, nil) // make a cap key and mount the store capKey := sdk.NewKVStoreKey("main") @@ -114,7 +112,7 @@ func TestLoadVersion(t *testing.T) { commitID2 := sdk.CommitID{2, res.Data} // reload with LoadLatestVersion - app = NewBaseApp(name, nil, logger, db) + app = NewBaseApp(name, logger, db, nil) app.MountStoresIAVL(capKey) err = app.LoadLatestVersion(capKey) require.Nil(t, err) @@ -122,7 +120,7 @@ func TestLoadVersion(t *testing.T) { // reload with LoadVersion, see if you can commit the same block and get // the same result - app = NewBaseApp(name, nil, logger, db) + app = NewBaseApp(name, logger, db, nil) app.MountStoresIAVL(capKey) err = app.LoadVersion(1, capKey) require.Nil(t, err) @@ -142,9 +140,7 @@ func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, exp func TestOptionFunction(t *testing.T) { logger := defaultLogger() db := dbm.NewMemDB() - codec := wire.NewCodec() - registerTestCodec(codec) - bap := NewBaseApp("starting name", codec, logger, db, testChangeNameHelper("new name")) + bap := NewBaseApp("starting name", logger, db, nil, testChangeNameHelper("new name")) require.Equal(t, bap.name, "new name", "BaseApp should have had name changed via option function") } @@ -216,7 +212,7 @@ func TestInitChainer(t *testing.T) { // we can reload the same app later db := dbm.NewMemDB() logger := defaultLogger() - app := NewBaseApp(name, nil, logger, db) + app := NewBaseApp(name, logger, db, nil) capKey := sdk.NewKVStoreKey("main") capKey2 := sdk.NewKVStoreKey("key2") app.MountStoresIAVL(capKey, capKey2) @@ -257,7 +253,7 @@ func TestInitChainer(t *testing.T) { require.Equal(t, value, res.Value) // reload app - app = NewBaseApp(name, nil, logger, db) + app = NewBaseApp(name, logger, db, nil) app.MountStoresIAVL(capKey, capKey2) err = app.LoadLatestVersion(capKey) // needed to make stores non-nil require.Nil(t, err) @@ -444,9 +440,13 @@ func TestCheckTx(t *testing.T) { app.InitChain(abci.RequestInitChain{}) + // Create same codec used in txDecoder + codec := wire.NewCodec() + registerTestCodec(codec) + for i := int64(0); i < nTxs; i++ { tx := newTxCounter(i, 0) - txBytes, err := app.cdc.MarshalBinary(tx) + txBytes, err := codec.MarshalBinary(tx) require.NoError(t, err) r := app.CheckTx(txBytes) assert.True(t, r.IsOK(), fmt.Sprintf("%v", r)) @@ -481,6 +481,10 @@ func TestDeliverTx(t *testing.T) { deliverKey := []byte("deliver-key") app.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey, deliverKey)) + // Create same codec used in txDecoder + codec := wire.NewCodec() + registerTestCodec(codec) + nBlocks := 3 txPerHeight := 5 for blockN := 0; blockN < nBlocks; blockN++ { @@ -488,7 +492,7 @@ func TestDeliverTx(t *testing.T) { for i := 0; i < txPerHeight; i++ { counter := int64(blockN*txPerHeight + i) tx := newTxCounter(counter, counter) - txBytes, err := app.cdc.MarshalBinary(tx) + txBytes, err := codec.MarshalBinary(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -518,12 +522,16 @@ func TestMultiMsgDeliverTx(t *testing.T) { app.Router().AddRoute(typeMsgCounter, handlerMsgCounter(t, capKey, deliverKey)) app.Router().AddRoute(typeMsgCounter2, handlerMsgCounter(t, capKey, deliverKey2)) + // Create same codec used in txDecoder + codec := wire.NewCodec() + registerTestCodec(codec) + // run a multi-msg tx // with all msgs the same type { app.BeginBlock(abci.RequestBeginBlock{}) tx := newTxCounter(0, 0, 1, 2) - txBytes, err := app.cdc.MarshalBinary(tx) + txBytes, err := codec.MarshalBinary(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -544,7 +552,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { tx := newTxCounter(1, 3) tx.Msgs = append(tx.Msgs, msgCounter2{0}) tx.Msgs = append(tx.Msgs, msgCounter2{1}) - txBytes, err := app.cdc.MarshalBinary(tx) + txBytes, err := codec.MarshalBinary(tx) require.NoError(t, err) res := app.DeliverTx(txBytes) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) @@ -589,6 +597,10 @@ func TestSimulateTx(t *testing.T) { }) app.InitChain(abci.RequestInitChain{}) + // Create same codec used in txDecoder + codec := wire.NewCodec() + registerTestCodec(codec) + nBlocks := 3 for blockN := 0; blockN < nBlocks; blockN++ { count := int64(blockN + 1) @@ -607,7 +619,7 @@ func TestSimulateTx(t *testing.T) { require.Equal(t, int64(gasConsumed), result.GasUsed) // simulate by calling Query with encoded tx - txBytes, err := app.cdc.MarshalBinary(tx) + txBytes, err := codec.MarshalBinary(tx) require.Nil(t, err) query := abci.RequestQuery{ Path: "/app/simulate", @@ -617,7 +629,8 @@ func TestSimulateTx(t *testing.T) { require.True(t, queryResult.IsOK(), queryResult.Log) var res sdk.Result - app.cdc.MustUnmarshalBinary(queryResult.Value, &res) + wire.Cdc.MustUnmarshalBinary(queryResult.Value, &res) + require.Nil(t, err, "Result unmarshalling failed") require.True(t, res.IsOK(), res.Log) require.Equal(t, gasConsumed, res.GasUsed, res.Log) app.EndBlock(abci.RequestEndBlock{}) diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index ab8a27e6c5..82d1123ea6 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -63,7 +63,7 @@ type GaiaApp struct { func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptions ...func(*bam.BaseApp)) *GaiaApp { cdc := MakeCodec() - bApp := bam.NewBaseApp(appName, cdc, logger, db, baseAppOptions...) + bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) var app = &GaiaApp{ diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index aa3bc939eb..48878ebe62 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -149,7 +149,7 @@ type GaiaApp struct { func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseApp)) *GaiaApp { cdc := MakeCodec() - bApp := bam.NewBaseApp(appName, cdc, logger, db, baseAppOptions...) + bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...) bApp.SetCommitMultiStoreTracer(os.Stdout) // create your application object diff --git a/docs/sdk/core/app1.md b/docs/sdk/core/app1.md index 6977d2ddc3..ddf2a533e5 100644 --- a/docs/sdk/core/app1.md +++ b/docs/sdk/core/app1.md @@ -421,17 +421,13 @@ Here is the complete setup for App1: ```go func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { - cdc := wire.NewCodec() // Create the base application object. - app := bapp.NewBaseApp(app1Name, cdc, logger, db) + app := bapp.NewBaseApp(app1Name, logger, db, tx1Decoder) // Create a capability key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") - // Determine how transactions are decoded. - app.SetTxDecoder(txDecoder) - // Register message routes. // Note the handler receives the keyAccount and thus // gets access to the account store. @@ -458,7 +454,7 @@ Here, we have only a single Msg type, `bank`, a single store for accounts, and a The handler is granted access to the store by giving it the capability key. In future apps, we'll have multiple stores and handlers, and not every handler will get access to every store. -After setting the transaction decoder and the message handling routes, the final +After setting the message handling routes, the final step is to mount the stores and load the latest version. Since we only have one store, we only mount one. diff --git a/docs/sdk/core/app2.md b/docs/sdk/core/app2.md index 0158976cd6..1f9e81a318 100644 --- a/docs/sdk/core/app2.md +++ b/docs/sdk/core/app2.md @@ -169,10 +169,19 @@ type app2Tx struct { func (tx app2Tx) GetMsgs() []sdk.Msg { return []sdk.Msg{tx.Msg} } -``` -We don't need a custom TxDecoder function anymore, since we're just using the -Amino codec! +// Amino decode app2Tx. Capable of decoding both MsgSend and MsgIssue +func tx2Decoder(cdc *wire.Codec) sdk.TxDecoder { + return func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx app2Tx + err := cdc.UnmarshalBinary(txBytes, &tx) + if err != nil { + return nil, sdk.ErrTxDecode(err.Error()) + } + return tx, nil + } +} +``` ## AnteHandler @@ -249,7 +258,7 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { cdc := NewCodec() // Create the base application object. - app := bapp.NewBaseApp(app2Name, cdc, logger, db) + app := bapp.NewBaseApp(app2Name, logger, db, txDecoder(cdc)) // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") @@ -280,9 +289,8 @@ key for a second store that is *only* passed to a second handler, the `handleMsgIssue`. The first `handleMsgSend` has no access to this second store and cannot read or write to it, ensuring a strong separation of concerns. -Note also that we do not need to use `SetTxDecoder` here - now that we're using -Amino, we simply create a codec, register our types on the codec, and pass the -codec into `NewBaseApp`. The SDK takes care of the rest for us! +Note now that we're using Amino, we create a codec, register our types on the codec, and pass the +codec into our TxDecoder constructor, `tx2Decoder`. The SDK takes care of the rest for us! ## Conclusion diff --git a/docs/sdk/core/app3.md b/docs/sdk/core/app3.md index 459f48c838..66bb05521b 100644 --- a/docs/sdk/core/app3.md +++ b/docs/sdk/core/app3.md @@ -328,7 +328,7 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { cdc := NewCodec() // Create the base application object. - app := bapp.NewBaseApp(app3Name, cdc, logger, db) + app := bapp.NewBaseApp(app3Name, logger, db, auth.DefaultTxDecoder(cdc)) // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") @@ -361,6 +361,9 @@ and receives only the `bank.Keeper`. See the [x/bank API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) for more details. +We also use the default txDecoder in `x/auth`, which decodes amino-encoded +`auth.StdTx` transactions. + ## Conclusion Armed with native modules for authentication and coin transfer, diff --git a/docs/sdk/core/examples/app1.go b/docs/sdk/core/examples/app1.go index b208f75cf7..ba33c61201 100644 --- a/docs/sdk/core/examples/app1.go +++ b/docs/sdk/core/examples/app1.go @@ -9,7 +9,6 @@ import ( bapp "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" ) const ( @@ -18,17 +17,12 @@ const ( func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { - cdc := wire.NewCodec() - // Create the base application object. - app := bapp.NewBaseApp(app1Name, cdc, logger, db) + app := bapp.NewBaseApp(app1Name, logger, db, tx1Decoder) // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") - // Determine how transactions are decoded. - app.SetTxDecoder(txDecoder) - // Register message routes. // Note the handler gets access to the account store. app.Router(). @@ -225,7 +219,7 @@ func (tx app1Tx) GetMsgs() []sdk.Msg { } // JSON decode MsgSend. -func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) { +func tx1Decoder(txBytes []byte) (sdk.Tx, sdk.Error) { var tx app1Tx err := json.Unmarshal(txBytes, &tx) if err != nil { diff --git a/docs/sdk/core/examples/app2.go b/docs/sdk/core/examples/app2.go index 4c20c17c3a..1e1910b628 100644 --- a/docs/sdk/core/examples/app2.go +++ b/docs/sdk/core/examples/app2.go @@ -37,7 +37,7 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { cdc := NewCodec() // Create the base application object. - app := bapp.NewBaseApp(app2Name, cdc, logger, db) + app := bapp.NewBaseApp(app2Name, logger, db, tx2Decoder(cdc)) // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") @@ -191,6 +191,18 @@ func (tx app2Tx) GetSignatures() []auth.StdSignature { return tx.Signatures } +// Amino decode app2Tx. Capable of decoding both MsgSend and MsgIssue +func tx2Decoder(cdc *wire.Codec) sdk.TxDecoder { + return func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx app2Tx + err := cdc.UnmarshalBinary(txBytes, &tx) + if err != nil { + return nil, sdk.ErrTxDecode(err.Error()) + } + return tx, nil + } +} + //------------------------------------------------------------------ // Simple anteHandler that ensures msg signers have signed. diff --git a/docs/sdk/core/examples/app3.go b/docs/sdk/core/examples/app3.go index 853ad687e1..15c8ea7584 100644 --- a/docs/sdk/core/examples/app3.go +++ b/docs/sdk/core/examples/app3.go @@ -21,7 +21,7 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { cdc := NewCodec() // Create the base application object. - app := bapp.NewBaseApp(app3Name, cdc, logger, db) + app := bapp.NewBaseApp(app3Name, logger, db, auth.DefaultTxDecoder(cdc)) // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") diff --git a/docs/sdk/core/examples/app4.go b/docs/sdk/core/examples/app4.go index a8ef37cee5..f8175fe41e 100644 --- a/docs/sdk/core/examples/app4.go +++ b/docs/sdk/core/examples/app4.go @@ -22,7 +22,7 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { cdc := NewCodec() // Create the base application object. - app := bapp.NewBaseApp(app3Name, cdc, logger, db) + app := bapp.NewBaseApp(app4Name, logger, db, auth.DefaultTxDecoder(cdc)) // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 14d4550d32..aa3ea25909 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -53,7 +53,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.Ba // create your application type var app = &BasecoinApp{ cdc: cdc, - BaseApp: bam.NewBaseApp(appName, cdc, logger, db, baseAppOptions...), + BaseApp: bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...), keyMain: sdk.NewKVStoreKey("main"), keyAccount: sdk.NewKVStoreKey("acc"), keyIBC: sdk.NewKVStoreKey("ibc"), diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 51d10002a7..4d4afb92d7 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -58,7 +58,7 @@ func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { // Create your application object. var app = &DemocoinApp{ - BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + BaseApp: bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc)), cdc: cdc, capKeyMainStore: sdk.NewKVStoreKey("main"), capKeyAccountStore: sdk.NewKVStoreKey("acc"), diff --git a/examples/kvstore/main.go b/examples/kvstore/main.go index 47416da057..125c7cd47e 100644 --- a/examples/kvstore/main.go +++ b/examples/kvstore/main.go @@ -32,14 +32,11 @@ func main() { var capKeyMainStore = sdk.NewKVStoreKey("main") // Create BaseApp. - var baseApp = bam.NewBaseApp("kvstore", nil, logger, db) + var baseApp = bam.NewBaseApp("kvstore", logger, db, decodeTx) // Set mounts for BaseApp's MultiStore. baseApp.MountStoresIAVL(capKeyMainStore) - // Set Tx decoder - baseApp.SetTxDecoder(decodeTx) - // Set a handler Route. baseApp.Router().AddRoute("kvstore", Handler(capKeyMainStore)) diff --git a/server/mock/app.go b/server/mock/app.go index 5229da41ee..7b22328b06 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -30,14 +30,11 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { capKeyMainStore := sdk.NewKVStoreKey("main") // Create BaseApp. - baseApp := bam.NewBaseApp("kvstore", nil, logger, db) + baseApp := bam.NewBaseApp("kvstore", logger, db, decodeTx) // Set mounts for BaseApp's MultiStore. baseApp.MountStoresIAVL(capKeyMainStore) - // Set Tx decoder - baseApp.SetTxDecoder(decodeTx) - baseApp.SetInitChainer(InitChainer(capKeyMainStore)) // Set a handler Route. diff --git a/x/auth/ante.go b/x/auth/ante.go index 9652b37de2..60aea8fc78 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -17,11 +17,12 @@ const ( // NewAnteHandler returns an AnteHandler that checks // and increments sequence numbers, checks signatures & account numbers, // and deducts fees from the first signer. +// nolint: gocyclo func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return func( ctx sdk.Context, tx sdk.Tx, - ) (_ sdk.Context, _ sdk.Result, abort bool) { + ) (newCtx sdk.Context, res sdk.Result, abort bool) { // This AnteHandler requires Txs to be StdTxs stdTx, ok := tx.(StdTx) @@ -29,20 +30,39 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return ctx, sdk.ErrInternal("tx must be StdTx").Result(), true } + // set the gas meter + newCtx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) + + // AnteHandlers must have their own defer/recover in order + // for the BaseApp to know how much gas was used! + // This is because the GasMeter is created in the AnteHandler, + // but if it panics the context won't be set properly in runTx's recover ... + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case sdk.ErrorOutOfGas: + log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) + res = sdk.ErrOutOfGas(log).Result() + res.GasWanted = stdTx.Fee.Gas + res.GasUsed = newCtx.GasMeter().GasConsumed() + abort = true + default: + panic(r) + } + } + }() + err := validateBasic(stdTx) if err != nil { - return ctx, err.Result(), true + return newCtx, err.Result(), true } sigs := stdTx.GetSignatures() signerAddrs := stdTx.GetSigners() msgs := tx.GetMsgs() - // set the gas meter - ctx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) - // charge gas for the memo - ctx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") + newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") // Get the sign bytes (requires all account & sequence numbers and the fee) sequences := make([]int64, len(sigs)) @@ -59,38 +79,38 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { signerAddr, sig := signerAddrs[i], sigs[i] // check signature, return account with incremented nonce - signBytes := StdSignBytes(ctx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) + signBytes := StdSignBytes(newCtx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) signerAcc, res := processSig( - ctx, am, + newCtx, am, signerAddr, sig, signBytes, ) if !res.IsOK() { - return ctx, res, true + return newCtx, res, true } // first sig pays the fees // TODO: Add min fees // Can this function be moved outside of the loop? if i == 0 && !fee.Amount.IsZero() { - ctx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") + newCtx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") signerAcc, res = deductFees(signerAcc, fee) if !res.IsOK() { - return ctx, res, true + return newCtx, res, true } - fck.addCollectedFees(ctx, fee.Amount) + fck.addCollectedFees(newCtx, fee.Amount) } // Save the account. - am.SetAccount(ctx, signerAcc) + am.SetAccount(newCtx, signerAcc) signerAccs[i] = signerAcc } // cache the signer accounts in the context - ctx = WithSigners(ctx, signerAccs) + newCtx = WithSigners(newCtx, signerAccs) // TODO: tx tags (?) - return ctx, sdk.Result{}, false // continue... + return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false // continue... } } diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index c30013d32f..907de17492 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -47,21 +47,20 @@ func checkValidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx // run the tx through the anteHandler and ensure it fails with the given code func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, code sdk.CodeType) { - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case sdk.ErrorOutOfGas: - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, code), sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOutOfGas), - fmt.Sprintf("Expected ErrorOutOfGas, got %v", r)) - default: - panic(r) - } - } - }() - _, result, abort := anteHandler(ctx, tx) + newCtx, result, abort := anteHandler(ctx, tx) require.True(t, abort) require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, code), result.Code, fmt.Sprintf("Expected %v, got %v", sdk.ToABCICode(sdk.CodespaceRoot, code), result)) + + if code == sdk.CodeOutOfGas { + stdTx, ok := tx.(StdTx) + require.True(t, ok, "tx must be in form auth.StdTx") + // GasWanted set correctly + require.Equal(t, stdTx.Fee.Gas, result.GasWanted, "Gas wanted not set correctly") + require.True(t, result.GasUsed > result.GasWanted, "GasUsed not greated than GasWanted") + // Check that context is set correctly + require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly") + } } func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee) sdk.Tx { diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index 316ff5e95a..bebd246234 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -4,6 +4,7 @@ import ( "encoding/json" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" "github.com/tendermint/tendermint/crypto" ) @@ -162,3 +163,22 @@ type StdSignature struct { AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` } + +// logic for standard transaction decoding +func DefaultTxDecoder(cdc *wire.Codec) sdk.TxDecoder { + return func(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx = StdTx{} + + if len(txBytes) == 0 { + return nil, sdk.ErrTxDecode("txBytes are empty") + } + + // StdTx.Msg is an interface. The concrete types + // are registered by MakeTxCodec + err := cdc.UnmarshalBinary(txBytes, &tx) + if err != nil { + return nil, sdk.ErrTxDecode("").TraceSDK(err.Error()) + } + return tx, nil + } +} diff --git a/x/mock/app.go b/x/mock/app.go index 175222fa88..a18383f2b1 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -47,7 +47,7 @@ func NewApp() *App { // Create your application object app := &App{ - BaseApp: bam.NewBaseApp("mock", cdc, logger, db), + BaseApp: bam.NewBaseApp("mock", logger, db, auth.DefaultTxDecoder(cdc)), Cdc: cdc, KeyMain: sdk.NewKVStoreKey("main"), KeyAccount: sdk.NewKVStoreKey("acc"),