Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: Enforce block maximum gas limit in DeliverTx #2795

Merged
merged 33 commits into from
Nov 26, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2f73cf4
block gas meter working
rigelrozanski Nov 13, 2018
956d351
basic structure in place
rigelrozanski Nov 13, 2018
ebaa394
modified app provider to pass genesis
rigelrozanski Nov 13, 2018
3bf67b6
compiling
rigelrozanski Nov 13, 2018
8069b2b
default infinite block gas meter
rigelrozanski Nov 13, 2018
2446830
examples installing
rigelrozanski Nov 13, 2018
eead278
initial test case
rigelrozanski Nov 13, 2018
7e6fcc0
passing test
rigelrozanski Nov 13, 2018
68e3b9a
only use block gas for deliver
rigelrozanski Nov 14, 2018
0d4dd87
fix baseapp tests
rigelrozanski Nov 14, 2018
5249064
add init chain block gas for gen-txs (all unit tests fixed)
rigelrozanski Nov 14, 2018
7be5179
Merge remote-tracking branch 'origin/develop' into rigel/deliver-max-gas
rigelrozanski Nov 14, 2018
90217e2
docs and pending
rigelrozanski Nov 14, 2018
2a594fe
basic cwgoes comments
rigelrozanski Nov 15, 2018
4818e67
Merge remote-tracking branch 'origin/develop' into rigel/deliver-max-gas
rigelrozanski Nov 16, 2018
56dc236
Merge remote-tracking branch 'origin/develop' into rigel/deliver-max-gas
rigelrozanski Nov 20, 2018
d911565
Fix compile
jaekwon Nov 20, 2018
10bdf8f
Store ConsensusParams to main store
jaekwon Nov 21, 2018
4afd53d
Consume block gas to tx gas limit even upon overconsumption
jaekwon Nov 21, 2018
bd982b1
Merge reference/baseapp and spec/baseapp/WIP_abci_application.md
jaekwon Nov 21, 2018
70e60c2
Merge remote-tracking branch 'origin/develop' into rigel/deliver-max-gas
rigelrozanski Nov 21, 2018
6fd3132
lint fix, merge fix
rigelrozanski Nov 21, 2018
972377c
lint
rigelrozanski Nov 22, 2018
b4b61b8
address some comments while reviewing Jaes work
rigelrozanski Nov 22, 2018
abed373
extra max block gas test at limit
rigelrozanski Nov 22, 2018
2d3e1af
Add demonstrative failing testcase
cwgoes Nov 22, 2018
56fa7dc
fix BlockGasRecovery
rigelrozanski Nov 22, 2018
0861112
Merge remote-tracking branch 'origin/develop' into rigel/deliver-max-gas
rigelrozanski Nov 22, 2018
ce10ef2
replaced proto with codec in baseapp
rigelrozanski Nov 22, 2018
5792e1d
Apply suggestions from code review
alexanderbez Nov 25, 2018
819af35
Final fixes from review
jaekwon Nov 25, 2018
0da2472
Merge remote-tracking branch 'origin/develop' into rigel/deliver-max-gas
rigelrozanski Nov 26, 2018
f12ac43
dep
rigelrozanski Nov 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ FEATURES
for getting governance parameters.

* SDK
* [simulator] \#2682 MsgEditValidator now looks at the validator's max rate, thus it now succeeds a significant portion of the time
* [simulator] \#2682 MsgEditValidator now looks at the validator's max rate, thus it now succeeds a significant portion of the time
* [core] \#2775 Add deliverTx maximum block gas limit

* Tendermint

Expand Down
41 changes: 37 additions & 4 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ type BaseApp struct {
deliverState *state // for DeliverTx
voteInfos []abci.VoteInfo // absent validators from begin block

// minimum fees for spam prevention
minimumFees sdk.Coins
// spam prevention
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably good to indicate that its local spam prevention, not consensus

minimumFees sdk.Coins
maximumBlockGas int64

// flag for sealing
sealed bool
Expand Down Expand Up @@ -194,6 +195,9 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
// SetMinimumFees sets the minimum fees.
func (app *BaseApp) SetMinimumFees(fees sdk.Coins) { app.minimumFees = fees }

// SetMaximumBlockGas sets the maximum gas allowable per block.
func (app *BaseApp) SetMaximumBlockGas(gas int64) { app.maximumBlockGas = gas }

// NewContext returns a new Context with the correct store, the given header, and nil txBytes.
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
if isCheckTx {
Expand Down Expand Up @@ -258,6 +262,11 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
if app.initChainer == nil {
return
}

// add block gas meter for any genesis transactions (allow infinite gas)
app.deliverState.ctx = app.deliverState.ctx.
WithBlockGasMeter(sdk.NewInfiniteGasMeter())

res = app.initChainer(app.deliverState.ctx, req)

// NOTE: we don't commit, but BeginBlock for block 1
Expand Down Expand Up @@ -427,7 +436,18 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
} else {
// In the first block, app.deliverState.ctx will already be initialized
// by InitChain. Context is now updated with Header information.
app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header).WithBlockHeight(req.Header.Height)
app.deliverState.ctx = app.deliverState.ctx.
WithBlockHeader(req.Header).
WithBlockHeight(req.Header.Height)
}

// add block gas meter
if app.maximumBlockGas > 0 {
app.deliverState.ctx = app.deliverState.ctx.
WithBlockGasMeter(sdk.NewGasMeter(app.maximumBlockGas))
} else {
app.deliverState.ctx = app.deliverState.ctx.
WithBlockGasMeter(sdk.NewInfiniteGasMeter())
}

if app.beginBlocker != nil {
Expand Down Expand Up @@ -467,9 +487,10 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {

// Implements ABCI
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {

// Decode the Tx.
var result sdk.Result
var tx, err = app.txDecoder(txBytes)
var result sdk.Result
if err != nil {
result = err.Result()
} else {
Expand Down Expand Up @@ -602,6 +623,12 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
ctx := app.getContextForAnte(mode, txBytes)
ctx = app.initializeContext(ctx, mode)

// only run the tx if there is block gas remaining
if mode == runTxModeDeliver && ctx.BlockGasMeter().PastLimit() {
result = sdk.ErrOutOfGas("no block gas left to run tx").Result()
return
}

defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
Expand Down Expand Up @@ -655,6 +682,12 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
result = app.runMsgs(ctx, msgs, mode)
result.GasWanted = gasWanted

// consume block gas
if mode == runTxModeDeliver {
ctx.BlockGasMeter().ConsumeGas(
ctx.GasMeter().GasConsumed(), "block gas meter")
}

// only update state if all messages pass
if result.IsOK() {
msCache.Write()
Expand Down
97 changes: 97 additions & 0 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ func TestDeliverTx(t *testing.T) {
}

app := setupBaseApp(t, anteOpt, routerOpt)
app.InitChain(abci.RequestInitChain{})

// Create same codec used in txDecoder
codec := codec.New()
Expand Down Expand Up @@ -823,3 +824,99 @@ func TestTxGasLimits(t *testing.T) {
}
}
}

// Test that transactions exceeding gas limits fail
func TestMaxBlockGasLimits(t *testing.T) {
gasGranted := int64(10)
anteOpt := func(bapp *BaseApp) {
bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(gasGranted))

// NOTE/TODO/XXX:
// AnteHandlers must have their own defer/recover in order
// for the BaseApp to know how much gas was used 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 = gasGranted
res.GasUsed = newCtx.GasMeter().GasConsumed()
default:
panic(r)
}
}
}()

count := tx.(*txTest).Counter
newCtx.GasMeter().ConsumeGas(count, "counter-ante")
res = sdk.Result{
GasWanted: gasGranted,
}
return
})

}

routerOpt := func(bapp *BaseApp) {
bapp.Router().AddRoute(routeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
count := msg.(msgCounter).Counter
ctx.GasMeter().ConsumeGas(count, "counter-handler")
return sdk.Result{}
})
}

app := setupBaseApp(t, anteOpt, routerOpt)
app.SetMaximumBlockGas(100)

testCases := []struct {
tx *txTest
numDelivers int
gasUsedPerDeliver int64
fail bool
failAfterDeliver int
}{
{newTxCounter(0, 0), 0, 0, false, 0},
{newTxCounter(9, 1), 2, 10, false, 0},
{newTxCounter(10, 0), 3, 10, false, 0},
{newTxCounter(10, 0), 10, 10, false, 0},
{newTxCounter(2, 7), 11, 9, false, 0},

{newTxCounter(10, 0), 11, 10, true, 10},
{newTxCounter(10, 0), 15, 10, true, 10},
}

for i, tc := range testCases {
tx := tc.tx

// reset the block gas
app.BeginBlock(abci.RequestBeginBlock{})

// execute the transaction multiple times
for j := 0; j < tc.numDelivers; j++ {
res := app.Deliver(tx)

ctx := app.getContextForAnte(runTxModeDeliver, nil)
ctx = app.initializeContext(ctx, runTxModeDeliver)
blockGasUsed := ctx.BlockGasMeter().GasConsumed()

// check for failed transactions
if tc.fail && (j+1) > tc.failAfterDeliver {
require.Equal(t, res.Code, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOutOfGas), fmt.Sprintf("%d: %v, %v", i, tc, res))
require.True(t, ctx.BlockGasMeter().PastLimit())
} else {

// check gas used and wanted
expBlockGasUsed := tc.gasUsedPerDeliver * int64(j+1)
require.Equal(t, expBlockGasUsed, blockGasUsed,
fmt.Sprintf("%d,%d: %v, %v, %v, %v", i, j, tc, expBlockGasUsed, blockGasUsed, res))

require.True(t, res.IsOK(), fmt.Sprintf("%d,%d: %v, %v", i, j, tc, res))
require.False(t, ctx.BlockGasMeter().PastLimit())
}
}
}
}
5 changes: 5 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func SetMinimumFees(minFees string) func(*BaseApp) {
return func(bap *BaseApp) { bap.SetMinimumFees(fees) }
}

// SetMinimumFees returns an option that sets the minimum fees on the app.
func SetMaximumBlockGas(gas int64) func(*BaseApp) {
return func(bap *BaseApp) { bap.SetMaximumBlockGas(gas) }
}

func (app *BaseApp) SetName(name string) {
if app.sealed {
panic("SetName() on sealed BaseApp")
Expand Down
8 changes: 5 additions & 3 deletions cmd/gaia/app/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ type GenesisState struct {
GenTxs []json.RawMessage `json:"gentxs"`
}

func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, stakeData stake.GenesisState, mintData mint.GenesisState,
distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState {
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
stakeData stake.GenesisState, mintData mint.GenesisState,
distrData distr.GenesisState, govData gov.GenesisState,
slashingData slashing.GenesisState) GenesisState {

return GenesisState{
Accounts: accounts,
Expand Down Expand Up @@ -287,7 +289,7 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm

func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount {
accAuth := auth.NewBaseAccountWithAddress(addr)
coins :=sdk.Coins{
coins := sdk.Coins{
{"fooToken", sdk.NewInt(1000)},
{bondDenom, freeFermionsAcc},
}
Expand Down
18 changes: 15 additions & 3 deletions cmd/gaia/cmd/gaiad/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/tendermint/tendermint/libs/cli"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/node"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
Expand Down Expand Up @@ -56,16 +57,27 @@ func main() {
}
}

func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
func newApp(logger log.Logger, db dbm.DB,
traceStore io.Writer, genDocProvider node.GenesisDocProvider) abci.Application {

// get the maximum gas from tendermint genesis parameters
genDoc, err := genDocProvider()
if err != nil {
panic(err)
}
maxBlockGas := genDoc.ConsensusParams.BlockSize.MaxGas

return app.NewGaiaApp(logger, db, traceStore,
baseapp.SetPruning(viper.GetString("pruning")),
baseapp.SetMinimumFees(viper.GetString("minimum_fees")),
baseapp.SetMaximumBlockGas(maxBlockGas),
)
}

func exportAppStateAndTMValidators(
logger log.Logger, db dbm.DB, traceStore io.Writer,
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
logger log.Logger, db dbm.DB, traceStore io.Writer) (
json.RawMessage, []tmtypes.GenesisValidator, error) {

gApp := app.NewGaiaApp(logger, db, traceStore)
return gApp.ExportAppStateAndValidators()
}
3 changes: 2 additions & 1 deletion docs/examples/basecoin/cmd/basecoind/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"os"

"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/p2p"

"github.com/cosmos/cosmos-sdk/baseapp"
Expand Down Expand Up @@ -122,7 +123,7 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob
return cmd
}

func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer) abci.Application {
func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer, _ node.GenesisDocProvider) abci.Application {
return app.NewBasecoinApp(logger, db, baseapp.SetPruning(viper.GetString("pruning")))
}

Expand Down
5 changes: 4 additions & 1 deletion docs/examples/democoin/cmd/democoind/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/p2p"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -129,7 +130,9 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob
return cmd
}

func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application {
func newApp(logger log.Logger, db dbm.DB, _ io.Writer,
_ node.GenesisDocProvider) abci.Application {

return app.NewDemocoinApp(logger, db)
}

Expand Down
46 changes: 46 additions & 0 deletions docs/spec/baseapp/WIP_abci_application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ABCI application

The `BaseApp` struct fulfills the tendermint-abci `Application` interface.

## Info

## SetOption

## Query

## CheckTx

## InitChain
TODO pseudo code

During chain initialization InitChain runs the initialization logic directly on
the CommitMultiStore and commits it. The deliver and check states are
initialized with the ChainID. Additionally the Block gas meter is initialized
with an infinite amount of gas to run any genesis transactions.

Note that we do not `Commit` during `InitChain` however BeginBlock for block 1
starts from this deliverState.


## BeginBlock
TODO complete description & pseudo code

The block gas meter is reset within BeginBlock for the deliver state.
If no maximum block gas is set within baseapp then an infinite
gas meter is set, otherwise a gas meter with the baseapp `maximumBlockGas`
is initialized

## DeliverTx
TODO complete description & pseudo code

Before transaction logic is run, the `BlockGasMeter` is first checked for
remaining gas. If no gas remains, then `DeliverTx` immediately returns an error.

After the transaction has been processed the used gas is deducted from the
BlockGasMeter. If the remaining gas exceeds the meter's limits, then DeliverTx
returns an error and the transaction is not committed.

## EndBlock

## Commit

4 changes: 3 additions & 1 deletion server/constructors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/node"
tmtypes "github.com/tendermint/tendermint/types"
)

type (
// AppCreator is a function that allows us to lazily initialize an
// application using various configurations.
AppCreator func(log.Logger, dbm.DB, io.Writer) abci.Application
AppCreator func(log.Logger, dbm.DB,
io.Writer, node.GenesisDocProvider) abci.Application

// AppExporter is a function that dumps all app state to
// JSON-serializable structure and returns the current validator set.
Expand Down
Loading