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"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
|
||||
digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
|
@ -644,6 +643,7 @@
|
|||
"github.com/bgentry/speakeasy",
|
||||
"github.com/btcsuite/btcd/btcec",
|
||||
"github.com/cosmos/go-bip39",
|
||||
"github.com/gogo/protobuf/proto",
|
||||
"github.com/golang/protobuf/proto",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/mattn/go-isatty",
|
||||
|
|
|
@ -44,6 +44,7 @@ FEATURES
|
|||
|
||||
* SDK
|
||||
* [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
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
@ -19,11 +20,8 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
// Key to store the header in the DB itself.
|
||||
// Use the db directly instead of a store to avoid
|
||||
// conflicts with handlers writing to the store
|
||||
// and to avoid affecting the Merkle root.
|
||||
var dbHeaderKey = []byte("header")
|
||||
// Key to store the consensus params in the main store.
|
||||
var mainConsensusParamsKey = []byte("consensus_params")
|
||||
|
||||
// Enum mode for app.runTx
|
||||
type runTxMode uint8
|
||||
|
@ -48,9 +46,11 @@ type BaseApp struct {
|
|||
queryRouter QueryRouter // router for redirecting query calls
|
||||
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
|
||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||
initChainer sdk.InitChainer // initialize state with validators and state blob
|
||||
beginBlocker sdk.BeginBlocker // logic to run before any txs
|
||||
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
|
||||
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
|
||||
|
||||
// flag for sealing
|
||||
|
@ -77,10 +81,6 @@ var _ abci.Application = (*BaseApp)(nil)
|
|||
|
||||
// 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.
|
||||
// Accepts a user-defined txDecoder
|
||||
// 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(),
|
||||
txDecoder: txDecoder,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(app)
|
||||
}
|
||||
|
@ -137,21 +136,23 @@ func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) {
|
|||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return app.initFromStore(mainKey)
|
||||
return app.initFromMainStore(mainKey)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return app.initFromStore(mainKey)
|
||||
return app.initFromMainStore(mainKey)
|
||||
}
|
||||
|
||||
// the last CommitID of the multistore
|
||||
|
@ -165,13 +166,34 @@ func (app *BaseApp) LastBlockHeight() int64 {
|
|||
}
|
||||
|
||||
// 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.
|
||||
// TODO: we don't actually need the main store here
|
||||
main := app.cms.GetKVStore(mainKey)
|
||||
if main == nil {
|
||||
mainStore := app.cms.GetKVStore(mainKey)
|
||||
if mainStore == nil {
|
||||
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
|
||||
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
|
||||
|
@ -242,8 +287,15 @@ func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOp
|
|||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
// 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
|
||||
app.setDeliverState(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 {
|
||||
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
|
||||
|
@ -424,9 +481,20 @@ 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
|
||||
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 {
|
||||
res = app.beginBlocker(app.deliverState.ctx, req)
|
||||
}
|
||||
|
@ -464,9 +532,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 {
|
||||
|
@ -617,6 +686,12 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
|
|||
ctx := app.getContextForTx(mode, txBytes)
|
||||
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() {
|
||||
if r := recover(); r != nil {
|
||||
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()
|
||||
}()
|
||||
|
||||
// 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()
|
||||
if err := validateBasicTxMsgs(msgs); err != nil {
|
||||
return err.Result()
|
||||
|
@ -704,14 +791,6 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
|
|||
// Implements ABCI
|
||||
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||
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
|
||||
app.deliverState.ms.Write()
|
||||
|
|
|
@ -56,7 +56,9 @@ func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp {
|
|||
require.Equal(t, t.Name(), app.Name())
|
||||
|
||||
// no stores are mounted
|
||||
require.Panics(t, func() { app.LoadLatestVersion(capKey1) })
|
||||
require.Panics(t, func() {
|
||||
app.LoadLatestVersion(capKey1)
|
||||
})
|
||||
|
||||
app.MountStoresIAVL(capKey1, capKey2)
|
||||
|
||||
|
@ -514,6 +516,7 @@ func TestDeliverTx(t *testing.T) {
|
|||
}
|
||||
|
||||
app := setupBaseApp(t, anteOpt, routerOpt)
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
|
||||
// Create same codec used in txDecoder
|
||||
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) {
|
||||
anteKey := []byte("ante-key")
|
||||
anteOpt := func(bapp *BaseApp) {
|
||||
|
|
|
@ -41,8 +41,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,
|
||||
|
@ -260,7 +262,7 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm
|
|||
"account %v not in genesis.json: %+v", addr, addrMap)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -62,8 +62,9 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application
|
|||
}
|
||||
|
||||
func exportAppStateAndTMValidators(
|
||||
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64,
|
||||
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
|
||||
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64) (
|
||||
json.RawMessage, []tmtypes.GenesisValidator, error) {
|
||||
|
||||
gApp := app.NewGaiaApp(logger, db, traceStore)
|
||||
if height != -1 {
|
||||
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
|
||||
that fails the AnteHandler. In this case, all state transitions for the
|
||||
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.
|
||||
// If the store does not exist, panics.
|
||||
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
|
||||
// rootMultiStore, a wrapped TraceKVStore will be returned with the given
|
||||
// tracer, otherwise, the original KVStore will be returned.
|
||||
// If the store does not exist, panics.
|
||||
func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore {
|
||||
store := rs.stores[key].(KVStore)
|
||||
|
||||
|
|
|
@ -133,13 +133,13 @@ const (
|
|||
contextKeyMultiStore contextKey = iota
|
||||
contextKeyBlockHeader
|
||||
contextKeyBlockHeight
|
||||
contextKeyConsensusParams
|
||||
contextKeyChainID
|
||||
contextKeyIsCheckTx
|
||||
contextKeyTxBytes
|
||||
contextKeyLogger
|
||||
contextKeyVoteInfos
|
||||
contextKeyGasMeter
|
||||
contextKeyBlockGasMeter
|
||||
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) ConsensusParams() abci.ConsensusParams {
|
||||
return c.Value(contextKeyConsensusParams).(abci.ConsensusParams)
|
||||
}
|
||||
|
||||
func (c Context) ChainID() string { return c.Value(contextKeyChainID).(string) }
|
||||
|
||||
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) BlockGasMeter() GasMeter { return c.Value(contextKeyBlockGasMeter).(GasMeter) }
|
||||
|
||||
func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) }
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) 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) WithBlockGasMeter(meter GasMeter) Context {
|
||||
return c.withValue(contextKeyBlockGasMeter, meter)
|
||||
}
|
||||
|
||||
func (c Context) WithIsCheckTx(isCheckTx bool) Context {
|
||||
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
|
||||
type GasMeter interface {
|
||||
GasConsumed() Gas
|
||||
GasConsumedToLimit() Gas
|
||||
Limit() Gas
|
||||
ConsumeGas(amount Gas, descriptor string)
|
||||
IsPastLimit() bool
|
||||
IsOutOfGas() bool
|
||||
}
|
||||
|
||||
type basicGasMeter struct {
|
||||
|
@ -54,6 +58,17 @@ func (g *basicGasMeter) GasConsumed() Gas {
|
|||
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) {
|
||||
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 {
|
||||
consumed Gas
|
||||
}
|
||||
|
@ -83,6 +106,14 @@ func (g *infiniteGasMeter) GasConsumed() Gas {
|
|||
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) {
|
||||
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
|
||||
type GasConfig struct {
|
||||
HasCost Gas
|
||||
|
|
|
@ -27,9 +27,18 @@ func TestGasMeter(t *testing.T) {
|
|||
used += usage
|
||||
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.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.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
|
||||
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ type MultiStore interface { //nolint
|
|||
CacheMultiStore() CacheMultiStore
|
||||
|
||||
// Convenience for fetching substores.
|
||||
// If the store does not exist, panics.
|
||||
GetStore(StoreKey) Store
|
||||
GetKVStore(StoreKey) KVStore
|
||||
|
||||
|
|
Loading…
Reference in New Issue