Merge PR #2795: Enforce block maximum gas limit in DeliverTx
R4R: Enforce block maximum gas limit in DeliverTx
This commit is contained in:
commit
8af2eb2b5f
|
@ -165,13 +165,12 @@
|
||||||
version = "v1.2.0"
|
version = "v1.2.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
|
digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8"
|
||||||
name = "github.com/hashicorp/hcl"
|
name = "github.com/hashicorp/hcl"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
"hcl/ast",
|
"hcl/ast",
|
||||||
"hcl/parser",
|
"hcl/parser",
|
||||||
"hcl/printer",
|
|
||||||
"hcl/scanner",
|
"hcl/scanner",
|
||||||
"hcl/strconv",
|
"hcl/strconv",
|
||||||
"hcl/token",
|
"hcl/token",
|
||||||
|
@ -644,6 +643,7 @@
|
||||||
"github.com/bgentry/speakeasy",
|
"github.com/bgentry/speakeasy",
|
||||||
"github.com/btcsuite/btcd/btcec",
|
"github.com/btcsuite/btcd/btcec",
|
||||||
"github.com/cosmos/go-bip39",
|
"github.com/cosmos/go-bip39",
|
||||||
|
"github.com/gogo/protobuf/proto",
|
||||||
"github.com/golang/protobuf/proto",
|
"github.com/golang/protobuf/proto",
|
||||||
"github.com/gorilla/mux",
|
"github.com/gorilla/mux",
|
||||||
"github.com/mattn/go-isatty",
|
"github.com/mattn/go-isatty",
|
||||||
|
|
|
@ -44,6 +44,7 @@ FEATURES
|
||||||
|
|
||||||
* SDK
|
* 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
|
* Tendermint
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
@ -19,11 +20,8 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/version"
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Key to store the header in the DB itself.
|
// Key to store the consensus params in the main store.
|
||||||
// Use the db directly instead of a store to avoid
|
var mainConsensusParamsKey = []byte("consensus_params")
|
||||||
// conflicts with handlers writing to the store
|
|
||||||
// and to avoid affecting the Merkle root.
|
|
||||||
var dbHeaderKey = []byte("header")
|
|
||||||
|
|
||||||
// Enum mode for app.runTx
|
// Enum mode for app.runTx
|
||||||
type runTxMode uint8
|
type runTxMode uint8
|
||||||
|
@ -48,9 +46,11 @@ type BaseApp struct {
|
||||||
queryRouter QueryRouter // router for redirecting query calls
|
queryRouter QueryRouter // router for redirecting query calls
|
||||||
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
|
||||||
|
|
||||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
// set upon LoadVersion or LoadLatestVersion.
|
||||||
|
mainKey *sdk.KVStoreKey // Main KVStore in cms
|
||||||
|
|
||||||
// may be nil
|
// may be nil
|
||||||
|
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||||
initChainer sdk.InitChainer // initialize state with validators and state blob
|
initChainer sdk.InitChainer // initialize state with validators and state blob
|
||||||
beginBlocker sdk.BeginBlocker // logic to run before any txs
|
beginBlocker sdk.BeginBlocker // logic to run before any txs
|
||||||
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
|
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
|
||||||
|
@ -66,7 +66,11 @@ type BaseApp struct {
|
||||||
deliverState *state // for DeliverTx
|
deliverState *state // for DeliverTx
|
||||||
voteInfos []abci.VoteInfo // absent validators from begin block
|
voteInfos []abci.VoteInfo // absent validators from begin block
|
||||||
|
|
||||||
// minimum fees for spam prevention
|
// consensus params
|
||||||
|
// TODO move this in the future to baseapp param store on main store.
|
||||||
|
consensusParams *abci.ConsensusParams
|
||||||
|
|
||||||
|
// spam prevention
|
||||||
minimumFees sdk.Coins
|
minimumFees sdk.Coins
|
||||||
|
|
||||||
// flag for sealing
|
// flag for sealing
|
||||||
|
@ -77,10 +81,6 @@ var _ abci.Application = (*BaseApp)(nil)
|
||||||
|
|
||||||
// NewBaseApp returns a reference to an initialized BaseApp.
|
// NewBaseApp returns a reference to an initialized BaseApp.
|
||||||
//
|
//
|
||||||
// TODO: Determine how to use a flexible and robust configuration paradigm that
|
|
||||||
// allows for sensible defaults while being highly configurable
|
|
||||||
// (e.g. functional options).
|
|
||||||
//
|
|
||||||
// NOTE: The db is used to store the version number for now.
|
// NOTE: The db is used to store the version number for now.
|
||||||
// Accepts a user-defined txDecoder
|
// Accepts a user-defined txDecoder
|
||||||
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
|
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
|
||||||
|
@ -94,7 +94,6 @@ func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecod
|
||||||
queryRouter: NewQueryRouter(),
|
queryRouter: NewQueryRouter(),
|
||||||
txDecoder: txDecoder,
|
txDecoder: txDecoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(app)
|
option(app)
|
||||||
}
|
}
|
||||||
|
@ -137,21 +136,23 @@ func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// load latest application version
|
// load latest application version
|
||||||
func (app *BaseApp) LoadLatestVersion(mainKey sdk.StoreKey) error {
|
// panics if called more than once on a running baseapp
|
||||||
|
func (app *BaseApp) LoadLatestVersion(mainKey *sdk.KVStoreKey) error {
|
||||||
err := app.cms.LoadLatestVersion()
|
err := app.cms.LoadLatestVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return app.initFromStore(mainKey)
|
return app.initFromMainStore(mainKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// load application version
|
// load application version
|
||||||
func (app *BaseApp) LoadVersion(version int64, mainKey sdk.StoreKey) error {
|
// panics if called more than once on a running baseapp
|
||||||
|
func (app *BaseApp) LoadVersion(version int64, mainKey *sdk.KVStoreKey) error {
|
||||||
err := app.cms.LoadVersion(version)
|
err := app.cms.LoadVersion(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return app.initFromStore(mainKey)
|
return app.initFromMainStore(mainKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the last CommitID of the multistore
|
// the last CommitID of the multistore
|
||||||
|
@ -165,13 +166,34 @@ func (app *BaseApp) LastBlockHeight() int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializes the remaining logic from app.cms
|
// initializes the remaining logic from app.cms
|
||||||
func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
|
func (app *BaseApp) initFromMainStore(mainKey *sdk.KVStoreKey) error {
|
||||||
|
|
||||||
// main store should exist.
|
// main store should exist.
|
||||||
// TODO: we don't actually need the main store here
|
mainStore := app.cms.GetKVStore(mainKey)
|
||||||
main := app.cms.GetKVStore(mainKey)
|
if mainStore == nil {
|
||||||
if main == nil {
|
|
||||||
return errors.New("baseapp expects MultiStore with 'main' KVStore")
|
return errors.New("baseapp expects MultiStore with 'main' KVStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// memoize mainKey
|
||||||
|
if app.mainKey != nil {
|
||||||
|
panic("app.mainKey expected to be nil; duplicate init?")
|
||||||
|
}
|
||||||
|
app.mainKey = mainKey
|
||||||
|
|
||||||
|
// load consensus params from the main store
|
||||||
|
consensusParamsBz := mainStore.Get(mainConsensusParamsKey)
|
||||||
|
if consensusParamsBz != nil {
|
||||||
|
var consensusParams = &abci.ConsensusParams{}
|
||||||
|
err := proto.Unmarshal(consensusParamsBz, consensusParams)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
app.setConsensusParams(consensusParams)
|
||||||
|
} else {
|
||||||
|
// It will get saved later during InitChain.
|
||||||
|
// TODO assert that InitChain hasn't yet been called.
|
||||||
|
}
|
||||||
|
|
||||||
// Needed for `gaiad export`, which inits from store but never calls initchain
|
// Needed for `gaiad export`, which inits from store but never calls initchain
|
||||||
app.setCheckState(abci.Header{})
|
app.setCheckState(abci.Header{})
|
||||||
|
|
||||||
|
@ -220,6 +242,29 @@ func (app *BaseApp) setDeliverState(header abci.Header) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setConsensusParams memoizes the consensus params.
|
||||||
|
func (app *BaseApp) setConsensusParams(consensusParams *abci.ConsensusParams) {
|
||||||
|
app.consensusParams = consensusParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// setConsensusParams stores the consensus params to the main store.
|
||||||
|
func (app *BaseApp) storeConsensusParams(consensusParams *abci.ConsensusParams) {
|
||||||
|
consensusParamsBz, err := proto.Marshal(consensusParams)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mainStore := app.cms.GetKVStore(app.mainKey)
|
||||||
|
mainStore.Set(mainConsensusParamsKey, consensusParamsBz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMaximumBlockGas gets the maximum gas from the consensus params.
|
||||||
|
func (app *BaseApp) getMaximumBlockGas() (maxGas uint64) {
|
||||||
|
if app.consensusParams == nil || app.consensusParams.BlockSize == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return uint64(app.consensusParams.BlockSize.MaxGas)
|
||||||
|
}
|
||||||
|
|
||||||
//______________________________________________________________________________
|
//______________________________________________________________________________
|
||||||
|
|
||||||
// ABCI
|
// ABCI
|
||||||
|
@ -242,8 +287,15 @@ func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements ABCI
|
// Implements ABCI
|
||||||
// InitChain runs the initialization logic directly on the CommitMultiStore and commits it.
|
// InitChain runs the initialization logic directly on the CommitMultiStore.
|
||||||
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
|
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
|
||||||
|
|
||||||
|
// Stash the consensus params in the cms main store and memoize.
|
||||||
|
if req.ConsensusParams != nil {
|
||||||
|
app.setConsensusParams(req.ConsensusParams)
|
||||||
|
app.storeConsensusParams(req.ConsensusParams)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the deliver state and check state with ChainID and run initChain
|
// Initialize the deliver state and check state with ChainID and run initChain
|
||||||
app.setDeliverState(abci.Header{ChainID: req.ChainId})
|
app.setDeliverState(abci.Header{ChainID: req.ChainId})
|
||||||
app.setCheckState(abci.Header{ChainID: req.ChainId})
|
app.setCheckState(abci.Header{ChainID: req.ChainId})
|
||||||
|
@ -251,6 +303,11 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
|
||||||
if app.initChainer == nil {
|
if app.initChainer == nil {
|
||||||
return
|
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)
|
res = app.initChainer(app.deliverState.ctx, req)
|
||||||
|
|
||||||
// NOTE: we don't commit, but BeginBlock for block 1
|
// NOTE: we don't commit, but BeginBlock for block 1
|
||||||
|
@ -424,9 +481,20 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
|
||||||
} else {
|
} else {
|
||||||
// In the first block, app.deliverState.ctx will already be initialized
|
// In the first block, app.deliverState.ctx will already be initialized
|
||||||
// by InitChain. Context is now updated with Header information.
|
// 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
|
||||||
|
var gasMeter sdk.GasMeter
|
||||||
|
if maxGas := app.getMaximumBlockGas(); maxGas > 0 {
|
||||||
|
gasMeter = sdk.NewGasMeter(maxGas)
|
||||||
|
} else {
|
||||||
|
gasMeter = sdk.NewInfiniteGasMeter()
|
||||||
|
}
|
||||||
|
app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(gasMeter)
|
||||||
|
|
||||||
if app.beginBlocker != nil {
|
if app.beginBlocker != nil {
|
||||||
res = app.beginBlocker(app.deliverState.ctx, req)
|
res = app.beginBlocker(app.deliverState.ctx, req)
|
||||||
}
|
}
|
||||||
|
@ -464,9 +532,10 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||||
|
|
||||||
// Implements ABCI
|
// Implements ABCI
|
||||||
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
||||||
|
|
||||||
// Decode the Tx.
|
// Decode the Tx.
|
||||||
var result sdk.Result
|
|
||||||
var tx, err = app.txDecoder(txBytes)
|
var tx, err = app.txDecoder(txBytes)
|
||||||
|
var result sdk.Result
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result = err.Result()
|
result = err.Result()
|
||||||
} else {
|
} else {
|
||||||
|
@ -617,6 +686,12 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
ctx := app.getContextForTx(mode, txBytes)
|
ctx := app.getContextForTx(mode, txBytes)
|
||||||
ms := ctx.MultiStore()
|
ms := ctx.MultiStore()
|
||||||
|
|
||||||
|
// only run the tx if there is block gas remaining
|
||||||
|
if mode == runTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() {
|
||||||
|
result = sdk.ErrOutOfGas("no block gas left to run tx").Result()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
switch rType := r.(type) {
|
switch rType := r.(type) {
|
||||||
|
@ -633,6 +708,18 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
result.GasUsed = ctx.GasMeter().GasConsumed()
|
result.GasUsed = ctx.GasMeter().GasConsumed()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// If BlockGasMeter() panics it will be caught by the above recover and
|
||||||
|
// return an error - in any case BlockGasMeter will consume gas past
|
||||||
|
// the limit.
|
||||||
|
// NOTE: this must exist in a separate defer function for the
|
||||||
|
// above recovery to recover from this one
|
||||||
|
defer func() {
|
||||||
|
if mode == runTxModeDeliver {
|
||||||
|
ctx.BlockGasMeter().ConsumeGas(
|
||||||
|
ctx.GasMeter().GasConsumedToLimit(), "block gas meter")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var msgs = tx.GetMsgs()
|
var msgs = tx.GetMsgs()
|
||||||
if err := validateBasicTxMsgs(msgs); err != nil {
|
if err := validateBasicTxMsgs(msgs); err != nil {
|
||||||
return err.Result()
|
return err.Result()
|
||||||
|
@ -704,14 +791,6 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
|
||||||
// Implements ABCI
|
// Implements ABCI
|
||||||
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||||
header := app.deliverState.ctx.BlockHeader()
|
header := app.deliverState.ctx.BlockHeader()
|
||||||
/*
|
|
||||||
// Write the latest Header to the store
|
|
||||||
headerBytes, err := proto.Marshal(&header)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
app.db.SetSync(dbHeaderKey, headerBytes)
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Write the Deliver state and commit the MultiStore
|
// Write the Deliver state and commit the MultiStore
|
||||||
app.deliverState.ms.Write()
|
app.deliverState.ms.Write()
|
||||||
|
|
|
@ -56,7 +56,9 @@ func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp {
|
||||||
require.Equal(t, t.Name(), app.Name())
|
require.Equal(t, t.Name(), app.Name())
|
||||||
|
|
||||||
// no stores are mounted
|
// no stores are mounted
|
||||||
require.Panics(t, func() { app.LoadLatestVersion(capKey1) })
|
require.Panics(t, func() {
|
||||||
|
app.LoadLatestVersion(capKey1)
|
||||||
|
})
|
||||||
|
|
||||||
app.MountStoresIAVL(capKey1, capKey2)
|
app.MountStoresIAVL(capKey1, capKey2)
|
||||||
|
|
||||||
|
@ -514,6 +516,7 @@ func TestDeliverTx(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||||
|
app.InitChain(abci.RequestInitChain{})
|
||||||
|
|
||||||
// Create same codec used in txDecoder
|
// Create same codec used in txDecoder
|
||||||
codec := codec.New()
|
codec := codec.New()
|
||||||
|
@ -853,6 +856,110 @@ func TestTxGasLimits(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that transactions exceeding gas limits fail
|
||||||
|
func TestMaxBlockGasLimits(t *testing.T) {
|
||||||
|
gasGranted := uint64(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(uint64(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(uint64(count), "counter-handler")
|
||||||
|
return sdk.Result{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||||
|
app.InitChain(abci.RequestInitChain{
|
||||||
|
ConsensusParams: &abci.ConsensusParams{
|
||||||
|
BlockSize: &abci.BlockSizeParams{
|
||||||
|
MaxGas: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
tx *txTest
|
||||||
|
numDelivers int
|
||||||
|
gasUsedPerDeliver uint64
|
||||||
|
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), 10, 10, false, 0}, // hit the limit but pass
|
||||||
|
|
||||||
|
{newTxCounter(10, 0), 11, 10, true, 10},
|
||||||
|
{newTxCounter(10, 0), 15, 10, true, 10},
|
||||||
|
{newTxCounter(9, 0), 12, 9, true, 11}, // fly past the limit
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
fmt.Printf("debug i: %v\n", i)
|
||||||
|
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.getState(runTxModeDeliver).ctx
|
||||||
|
blockGasUsed := ctx.BlockGasMeter().GasConsumed()
|
||||||
|
|
||||||
|
// check for failed transactions
|
||||||
|
if tc.fail && (j+1) > tc.failAfterDeliver {
|
||||||
|
require.Equal(t, res.Code, sdk.CodeOutOfGas, fmt.Sprintf("%d: %v, %v", i, tc, res))
|
||||||
|
require.Equal(t, res.Codespace, sdk.CodespaceRoot, fmt.Sprintf("%d: %v, %v", i, tc, res))
|
||||||
|
require.True(t, ctx.BlockGasMeter().IsOutOfGas())
|
||||||
|
} else {
|
||||||
|
// check gas used and wanted
|
||||||
|
expBlockGasUsed := tc.gasUsedPerDeliver * uint64(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().IsPastLimit())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBaseAppAnteHandler(t *testing.T) {
|
func TestBaseAppAnteHandler(t *testing.T) {
|
||||||
anteKey := []byte("ante-key")
|
anteKey := []byte("ante-key")
|
||||||
anteOpt := func(bapp *BaseApp) {
|
anteOpt := func(bapp *BaseApp) {
|
||||||
|
|
|
@ -41,8 +41,10 @@ type GenesisState struct {
|
||||||
GenTxs []json.RawMessage `json:"gentxs"`
|
GenTxs []json.RawMessage `json:"gentxs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, stakeData stake.GenesisState, mintData mint.GenesisState,
|
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
|
||||||
distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState {
|
stakeData stake.GenesisState, mintData mint.GenesisState,
|
||||||
|
distrData distr.GenesisState, govData gov.GenesisState,
|
||||||
|
slashingData slashing.GenesisState) GenesisState {
|
||||||
|
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Accounts: accounts,
|
Accounts: accounts,
|
||||||
|
@ -260,7 +262,7 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm
|
||||||
"account %v not in genesis.json: %+v", addr, addrMap)
|
"account %v not in genesis.json: %+v", addr, addrMap)
|
||||||
}
|
}
|
||||||
if acc.Coins.AmountOf(msg.Delegation.Denom).LT(msg.Delegation.Amount) {
|
if acc.Coins.AmountOf(msg.Delegation.Denom).LT(msg.Delegation.Amount) {
|
||||||
err = fmt.Errorf("insufficient fund for the delegation: %s < %s",
|
err = fmt.Errorf("insufficient fund for the delegation: %v < %v",
|
||||||
acc.Coins.AmountOf(msg.Delegation.Denom), msg.Delegation.Amount)
|
acc.Coins.AmountOf(msg.Delegation.Denom), msg.Delegation.Amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,9 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportAppStateAndTMValidators(
|
func exportAppStateAndTMValidators(
|
||||||
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64,
|
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64) (
|
||||||
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
|
json.RawMessage, []tmtypes.GenesisValidator, error) {
|
||||||
|
|
||||||
gApp := app.NewGaiaApp(logger, db, traceStore)
|
gApp := app.NewGaiaApp(logger, db, traceStore)
|
||||||
if height != -1 {
|
if height != -1 {
|
||||||
err := gApp.LoadHeight(height)
|
err := gApp.LoadHeight(height)
|
||||||
|
|
|
@ -61,3 +61,64 @@ persisted even when the following Handler processing logic fails.
|
||||||
It is possible that a malicious proposer may include a transaction in a block
|
It is possible that a malicious proposer may include a transaction in a block
|
||||||
that fails the AnteHandler. In this case, all state transitions for the
|
that fails the AnteHandler. In this case, all state transitions for the
|
||||||
offending transaction are discarded.
|
offending transaction are discarded.
|
||||||
|
|
||||||
|
|
||||||
|
## Other ABCI Messages
|
||||||
|
|
||||||
|
Besides `CheckTx` and `DeliverTx`, BaseApp handles the following ABCI messages.
|
||||||
|
|
||||||
|
### Info
|
||||||
|
TODO complete description
|
||||||
|
|
||||||
|
### SetOption
|
||||||
|
TODO complete description
|
||||||
|
|
||||||
|
### Query
|
||||||
|
TODO complete description
|
||||||
|
|
||||||
|
### InitChain
|
||||||
|
TODO complete description
|
||||||
|
|
||||||
|
During chain initialization InitChain runs the initialization logic directly on
|
||||||
|
the CommitMultiStore. The deliver and check states are initialized with the
|
||||||
|
ChainID.
|
||||||
|
|
||||||
|
Note that we do not commit after InitChain, so BeginBlock for block 1 starts
|
||||||
|
from the deliver state as initialized by InitChain.
|
||||||
|
|
||||||
|
### BeginBlock
|
||||||
|
TODO complete description
|
||||||
|
|
||||||
|
### EndBlock
|
||||||
|
TODO complete description
|
||||||
|
|
||||||
|
### Commit
|
||||||
|
TODO complete description
|
||||||
|
|
||||||
|
|
||||||
|
## Gas Management
|
||||||
|
|
||||||
|
### Gas: InitChain
|
||||||
|
|
||||||
|
During InitChain, the block gas meter is initialized with an infinite amount of
|
||||||
|
gas to run any genesis transactions.
|
||||||
|
|
||||||
|
Additionally, the InitChain request message includes ConsensusParams as
|
||||||
|
declared in the genesis.json file.
|
||||||
|
|
||||||
|
### Gas: BeginBlock
|
||||||
|
|
||||||
|
The block gas meter is reset during 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 `ConsensusParam.BlockSize.MaxGas` is initialized.
|
||||||
|
|
||||||
|
### Gas: DeliverTx
|
||||||
|
|
||||||
|
Before the transaction logic is run, the `BlockGasMeter` is first checked to
|
||||||
|
see if any gas remains. If no gas remains, then `DeliverTx` immediately returns
|
||||||
|
an error.
|
||||||
|
|
||||||
|
After the transaction has been processed, the used gas (up to the transaction
|
||||||
|
gas limit) 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.
|
||||||
|
|
|
@ -230,13 +230,19 @@ func (rs *rootMultiStore) CacheMultiStore() CacheMultiStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements MultiStore.
|
// Implements MultiStore.
|
||||||
|
// If the store does not exist, panics.
|
||||||
func (rs *rootMultiStore) GetStore(key StoreKey) Store {
|
func (rs *rootMultiStore) GetStore(key StoreKey) Store {
|
||||||
return rs.stores[key]
|
store := rs.stores[key]
|
||||||
|
if store == nil {
|
||||||
|
panic("Could not load store " + key.String())
|
||||||
|
}
|
||||||
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKVStore implements the MultiStore interface. If tracing is enabled on the
|
// GetKVStore implements the MultiStore interface. If tracing is enabled on the
|
||||||
// rootMultiStore, a wrapped TraceKVStore will be returned with the given
|
// rootMultiStore, a wrapped TraceKVStore will be returned with the given
|
||||||
// tracer, otherwise, the original KVStore will be returned.
|
// tracer, otherwise, the original KVStore will be returned.
|
||||||
|
// If the store does not exist, panics.
|
||||||
func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore {
|
func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore {
|
||||||
store := rs.stores[key].(KVStore)
|
store := rs.stores[key].(KVStore)
|
||||||
|
|
||||||
|
|
|
@ -133,13 +133,13 @@ const (
|
||||||
contextKeyMultiStore contextKey = iota
|
contextKeyMultiStore contextKey = iota
|
||||||
contextKeyBlockHeader
|
contextKeyBlockHeader
|
||||||
contextKeyBlockHeight
|
contextKeyBlockHeight
|
||||||
contextKeyConsensusParams
|
|
||||||
contextKeyChainID
|
contextKeyChainID
|
||||||
contextKeyIsCheckTx
|
contextKeyIsCheckTx
|
||||||
contextKeyTxBytes
|
contextKeyTxBytes
|
||||||
contextKeyLogger
|
contextKeyLogger
|
||||||
contextKeyVoteInfos
|
contextKeyVoteInfos
|
||||||
contextKeyGasMeter
|
contextKeyGasMeter
|
||||||
|
contextKeyBlockGasMeter
|
||||||
contextKeyMinimumFees
|
contextKeyMinimumFees
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -151,10 +151,6 @@ func (c Context) BlockHeader() abci.Header { return c.Value(contextKeyBlockHeade
|
||||||
|
|
||||||
func (c Context) BlockHeight() int64 { return c.Value(contextKeyBlockHeight).(int64) }
|
func (c Context) BlockHeight() int64 { return c.Value(contextKeyBlockHeight).(int64) }
|
||||||
|
|
||||||
func (c Context) ConsensusParams() abci.ConsensusParams {
|
|
||||||
return c.Value(contextKeyConsensusParams).(abci.ConsensusParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Context) ChainID() string { return c.Value(contextKeyChainID).(string) }
|
func (c Context) ChainID() string { return c.Value(contextKeyChainID).(string) }
|
||||||
|
|
||||||
func (c Context) TxBytes() []byte { return c.Value(contextKeyTxBytes).([]byte) }
|
func (c Context) TxBytes() []byte { return c.Value(contextKeyTxBytes).([]byte) }
|
||||||
|
@ -167,6 +163,8 @@ func (c Context) VoteInfos() []abci.VoteInfo {
|
||||||
|
|
||||||
func (c Context) GasMeter() GasMeter { return c.Value(contextKeyGasMeter).(GasMeter) }
|
func (c Context) GasMeter() GasMeter { return c.Value(contextKeyGasMeter).(GasMeter) }
|
||||||
|
|
||||||
|
func (c Context) BlockGasMeter() GasMeter { return c.Value(contextKeyBlockGasMeter).(GasMeter) }
|
||||||
|
|
||||||
func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) }
|
func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) }
|
||||||
|
|
||||||
func (c Context) MinimumFees() Coins { return c.Value(contextKeyMinimumFees).(Coins) }
|
func (c Context) MinimumFees() Coins { return c.Value(contextKeyMinimumFees).(Coins) }
|
||||||
|
@ -198,16 +196,6 @@ func (c Context) WithBlockHeight(height int64) Context {
|
||||||
return c.withValue(contextKeyBlockHeight, height).withValue(contextKeyBlockHeader, newHeader)
|
return c.withValue(contextKeyBlockHeight, height).withValue(contextKeyBlockHeader, newHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context {
|
|
||||||
if params == nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Do we need to handle invalid MaxGas values?
|
|
||||||
return c.withValue(contextKeyConsensusParams, params).
|
|
||||||
WithGasMeter(NewGasMeter(uint64(params.BlockSize.MaxGas)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Context) WithChainID(chainID string) Context { return c.withValue(contextKeyChainID, chainID) }
|
func (c Context) WithChainID(chainID string) Context { return c.withValue(contextKeyChainID, chainID) }
|
||||||
|
|
||||||
func (c Context) WithTxBytes(txBytes []byte) Context { return c.withValue(contextKeyTxBytes, txBytes) }
|
func (c Context) WithTxBytes(txBytes []byte) Context { return c.withValue(contextKeyTxBytes, txBytes) }
|
||||||
|
@ -220,6 +208,10 @@ func (c Context) WithVoteInfos(VoteInfos []abci.VoteInfo) Context {
|
||||||
|
|
||||||
func (c Context) WithGasMeter(meter GasMeter) Context { return c.withValue(contextKeyGasMeter, meter) }
|
func (c Context) WithGasMeter(meter GasMeter) Context { return c.withValue(contextKeyGasMeter, meter) }
|
||||||
|
|
||||||
|
func (c Context) WithBlockGasMeter(meter GasMeter) Context {
|
||||||
|
return c.withValue(contextKeyBlockGasMeter, meter)
|
||||||
|
}
|
||||||
|
|
||||||
func (c Context) WithIsCheckTx(isCheckTx bool) Context {
|
func (c Context) WithIsCheckTx(isCheckTx bool) Context {
|
||||||
return c.withValue(contextKeyIsCheckTx, isCheckTx)
|
return c.withValue(contextKeyIsCheckTx, isCheckTx)
|
||||||
}
|
}
|
||||||
|
|
39
types/gas.go
39
types/gas.go
|
@ -34,7 +34,11 @@ type ErrorGasOverflow struct {
|
||||||
// GasMeter interface to track gas consumption
|
// GasMeter interface to track gas consumption
|
||||||
type GasMeter interface {
|
type GasMeter interface {
|
||||||
GasConsumed() Gas
|
GasConsumed() Gas
|
||||||
|
GasConsumedToLimit() Gas
|
||||||
|
Limit() Gas
|
||||||
ConsumeGas(amount Gas, descriptor string)
|
ConsumeGas(amount Gas, descriptor string)
|
||||||
|
IsPastLimit() bool
|
||||||
|
IsOutOfGas() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type basicGasMeter struct {
|
type basicGasMeter struct {
|
||||||
|
@ -54,6 +58,17 @@ func (g *basicGasMeter) GasConsumed() Gas {
|
||||||
return g.consumed
|
return g.consumed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *basicGasMeter) Limit() Gas {
|
||||||
|
return g.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *basicGasMeter) GasConsumedToLimit() Gas {
|
||||||
|
if g.IsPastLimit() {
|
||||||
|
return g.limit
|
||||||
|
}
|
||||||
|
return g.consumed
|
||||||
|
}
|
||||||
|
|
||||||
func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
||||||
var overflow bool
|
var overflow bool
|
||||||
|
|
||||||
|
@ -68,6 +83,14 @@ func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *basicGasMeter) IsPastLimit() bool {
|
||||||
|
return g.consumed > g.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *basicGasMeter) IsOutOfGas() bool {
|
||||||
|
return g.consumed >= g.limit
|
||||||
|
}
|
||||||
|
|
||||||
type infiniteGasMeter struct {
|
type infiniteGasMeter struct {
|
||||||
consumed Gas
|
consumed Gas
|
||||||
}
|
}
|
||||||
|
@ -83,6 +106,14 @@ func (g *infiniteGasMeter) GasConsumed() Gas {
|
||||||
return g.consumed
|
return g.consumed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *infiniteGasMeter) GasConsumedToLimit() Gas {
|
||||||
|
return g.consumed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *infiniteGasMeter) Limit() Gas {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
||||||
var overflow bool
|
var overflow bool
|
||||||
|
|
||||||
|
@ -93,6 +124,14 @@ func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *infiniteGasMeter) IsPastLimit() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *infiniteGasMeter) IsOutOfGas() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// GasConfig defines gas cost for each operation on KVStores
|
// GasConfig defines gas cost for each operation on KVStores
|
||||||
type GasConfig struct {
|
type GasConfig struct {
|
||||||
HasCost Gas
|
HasCost Gas
|
||||||
|
|
|
@ -27,9 +27,18 @@ func TestGasMeter(t *testing.T) {
|
||||||
used += usage
|
used += usage
|
||||||
require.NotPanics(t, func() { meter.ConsumeGas(usage, "") }, "Not exceeded limit but panicked. tc #%d, usage #%d", tcnum, unum)
|
require.NotPanics(t, func() { meter.ConsumeGas(usage, "") }, "Not exceeded limit but panicked. tc #%d, usage #%d", tcnum, unum)
|
||||||
require.Equal(t, used, meter.GasConsumed(), "Gas consumption not match. tc #%d, usage #%d", tcnum, unum)
|
require.Equal(t, used, meter.GasConsumed(), "Gas consumption not match. tc #%d, usage #%d", tcnum, unum)
|
||||||
|
require.Equal(t, used, meter.GasConsumedToLimit(), "Gas consumption (to limit) not match. tc #%d, usage #%d", tcnum, unum)
|
||||||
|
require.False(t, meter.IsPastLimit(), "Not exceeded limit but got IsPastLimit() true")
|
||||||
|
if unum < len(tc.usage)-1 {
|
||||||
|
require.False(t, meter.IsOutOfGas(), "Not yet at limit but got IsOutOfGas() true")
|
||||||
|
} else {
|
||||||
|
require.True(t, meter.IsOutOfGas(), "At limit but got IsOutOfGas() false")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Panics(t, func() { meter.ConsumeGas(1, "") }, "Exceeded but not panicked. tc #%d", tcnum)
|
require.Panics(t, func() { meter.ConsumeGas(1, "") }, "Exceeded but not panicked. tc #%d", tcnum)
|
||||||
|
require.Equal(t, meter.GasConsumedToLimit(), meter.Limit(), "Gas consumption (to limit) not match limit")
|
||||||
|
require.Equal(t, meter.GasConsumed(), meter.Limit()+1, "Gas consumption not match limit+1")
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ type MultiStore interface { //nolint
|
||||||
CacheMultiStore() CacheMultiStore
|
CacheMultiStore() CacheMultiStore
|
||||||
|
|
||||||
// Convenience for fetching substores.
|
// Convenience for fetching substores.
|
||||||
|
// If the store does not exist, panics.
|
||||||
GetStore(StoreKey) Store
|
GetStore(StoreKey) Store
|
||||||
GetKVStore(StoreKey) KVStore
|
GetKVStore(StoreKey) KVStore
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue