Merge pull request #963 from cosmos/cwgoes/gas-guzzling
Gas management, estimation, limitation
This commit is contained in:
commit
e6d21c64cc
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -13,6 +13,13 @@ FEATURES
|
||||||
* [x/bank] Tx tags with sender/recipient for indexing & later retrieval
|
* [x/bank] Tx tags with sender/recipient for indexing & later retrieval
|
||||||
* [x/stake] Tx tags with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy
|
* [x/stake] Tx tags with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy
|
||||||
* [x/auth] Added ability to change pubkey to auth module
|
* [x/auth] Added ability to change pubkey to auth module
|
||||||
|
* [baseapp] baseapp now has settable functions for filtering peers by address/port & public key
|
||||||
|
* [sdk] Gas consumption is now measured as transactions are executed
|
||||||
|
* Transactions which run out of gas stop execution and revert state changes
|
||||||
|
* A "simulate" query has been added to determine how much gas a transaction will need
|
||||||
|
* Modules can include their own gas costs for execution of particular message types
|
||||||
|
* [x/bank] Bank module now tags transactions with sender/recipient for indexing & later retrieval
|
||||||
|
* [x/stake] Stake module now tags transactions with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy
|
||||||
|
|
||||||
IMPROVEMENTS
|
IMPROVEMENTS
|
||||||
|
|
||||||
|
@ -43,6 +50,7 @@ BREAKING CHANGES
|
||||||
* gaiad init now requires use of `--name` flag
|
* gaiad init now requires use of `--name` flag
|
||||||
* Removed Get from Msg interface
|
* Removed Get from Msg interface
|
||||||
* types/rational now extends big.Rat
|
* types/rational now extends big.Rat
|
||||||
|
* Queries against the store must be prefixed with the path "/store"
|
||||||
|
|
||||||
FEATURES:
|
FEATURES:
|
||||||
|
|
||||||
|
@ -55,7 +63,8 @@ FEATURES:
|
||||||
* New genesis account keys are automatically added to the client keybase (introduce `--client-home` flag)
|
* New genesis account keys are automatically added to the client keybase (introduce `--client-home` flag)
|
||||||
* Initialize with genesis txs using `--gen-txs` flag
|
* Initialize with genesis txs using `--gen-txs` flag
|
||||||
* Context now has access to the application-configured logger
|
* Context now has access to the application-configured logger
|
||||||
|
* Add (non-proof) subspace query helper functions
|
||||||
|
* Add more staking query functions: candidates, delegator-bonds
|
||||||
|
|
||||||
BUG FIXES
|
BUG FIXES
|
||||||
* Gaia now uses stake, ported from github.com/cosmos/gaia
|
* Gaia now uses stake, ported from github.com/cosmos/gaia
|
||||||
|
|
|
@ -3,6 +3,7 @@ package baseapp
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
@ -22,11 +23,24 @@ import (
|
||||||
// and to avoid affecting the Merkle root.
|
// and to avoid affecting the Merkle root.
|
||||||
var dbHeaderKey = []byte("header")
|
var dbHeaderKey = []byte("header")
|
||||||
|
|
||||||
|
// Enum mode for app.runTx
|
||||||
|
type runTxMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Check a transaction
|
||||||
|
runTxModeCheck runTxMode = iota
|
||||||
|
// Simulate a transaction
|
||||||
|
runTxModeSimulate runTxMode = iota
|
||||||
|
// Deliver a transaction
|
||||||
|
runTxModeDeliver runTxMode = iota
|
||||||
|
)
|
||||||
|
|
||||||
// The ABCI application
|
// The ABCI application
|
||||||
type BaseApp struct {
|
type BaseApp struct {
|
||||||
// initialized on creation
|
// initialized on creation
|
||||||
Logger log.Logger
|
Logger log.Logger
|
||||||
name string // application name from abci.Info
|
name string // application name from abci.Info
|
||||||
|
cdc *wire.Codec // Amino codec
|
||||||
db dbm.DB // common DB backend
|
db dbm.DB // common DB backend
|
||||||
cms sdk.CommitMultiStore // Main (uncached) state
|
cms sdk.CommitMultiStore // Main (uncached) state
|
||||||
router Router // handle any kind of message
|
router Router // handle any kind of message
|
||||||
|
@ -37,9 +51,11 @@ type BaseApp struct {
|
||||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||||
|
|
||||||
// may be nil
|
// may be nil
|
||||||
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
|
||||||
|
addrPeerFilter sdk.PeerFilter // filter peers by address and port
|
||||||
|
pubkeyPeerFilter sdk.PeerFilter // filter peers by public key
|
||||||
|
|
||||||
//--------------------
|
//--------------------
|
||||||
// Volatile
|
// Volatile
|
||||||
|
@ -61,6 +77,7 @@ func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB) *Bas
|
||||||
app := &BaseApp{
|
app := &BaseApp{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
name: name,
|
name: name,
|
||||||
|
cdc: cdc,
|
||||||
db: db,
|
db: db,
|
||||||
cms: store.NewCommitMultiStore(db),
|
cms: store.NewCommitMultiStore(db),
|
||||||
router: NewRouter(),
|
router: NewRouter(),
|
||||||
|
@ -137,6 +154,12 @@ func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) {
|
||||||
func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
|
func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
|
||||||
app.anteHandler = ah
|
app.anteHandler = ah
|
||||||
}
|
}
|
||||||
|
func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) {
|
||||||
|
app.addrPeerFilter = pf
|
||||||
|
}
|
||||||
|
func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) {
|
||||||
|
app.pubkeyPeerFilter = pf
|
||||||
|
}
|
||||||
func (app *BaseApp) Router() Router { return app.router }
|
func (app *BaseApp) Router() Router { return app.router }
|
||||||
|
|
||||||
// load latest application version
|
// load latest application version
|
||||||
|
@ -277,15 +300,74 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter peers by address / port
|
||||||
|
func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery {
|
||||||
|
if app.addrPeerFilter != nil {
|
||||||
|
return app.addrPeerFilter(info)
|
||||||
|
}
|
||||||
|
return abci.ResponseQuery{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter peers by public key
|
||||||
|
func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery {
|
||||||
|
if app.pubkeyPeerFilter != nil {
|
||||||
|
return app.pubkeyPeerFilter(info)
|
||||||
|
}
|
||||||
|
return abci.ResponseQuery{}
|
||||||
|
}
|
||||||
|
|
||||||
// Implements ABCI.
|
// Implements ABCI.
|
||||||
// Delegates to CommitMultiStore if it implements Queryable
|
// Delegates to CommitMultiStore if it implements Queryable
|
||||||
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||||
queryable, ok := app.cms.(sdk.Queryable)
|
path := strings.Split(req.Path, "/")
|
||||||
if !ok {
|
// first element is empty string
|
||||||
msg := "application doesn't support queries"
|
if len(path) > 0 && path[0] == "" {
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
path = path[1:]
|
||||||
}
|
}
|
||||||
return queryable.Query(req)
|
// "/app" prefix for special application queries
|
||||||
|
if len(path) >= 2 && path[0] == "app" {
|
||||||
|
var result sdk.Result
|
||||||
|
switch path[1] {
|
||||||
|
case "simulate":
|
||||||
|
txBytes := req.Data
|
||||||
|
tx, err := app.txDecoder(txBytes)
|
||||||
|
if err != nil {
|
||||||
|
result = err.Result()
|
||||||
|
} else {
|
||||||
|
result = app.Simulate(tx)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result()
|
||||||
|
}
|
||||||
|
value := app.cdc.MustMarshalBinary(result)
|
||||||
|
return abci.ResponseQuery{
|
||||||
|
Code: uint32(sdk.ABCICodeOK),
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// "/store" prefix for store queries
|
||||||
|
if len(path) >= 1 && path[0] == "store" {
|
||||||
|
queryable, ok := app.cms.(sdk.Queryable)
|
||||||
|
if !ok {
|
||||||
|
msg := "multistore doesn't support queries"
|
||||||
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
|
}
|
||||||
|
req.Path = "/" + strings.Join(path[1:], "/")
|
||||||
|
return queryable.Query(req)
|
||||||
|
}
|
||||||
|
// "/p2p" prefix for p2p queries
|
||||||
|
if len(path) >= 4 && path[0] == "p2p" {
|
||||||
|
if path[1] == "filter" {
|
||||||
|
if path[2] == "addr" {
|
||||||
|
return app.FilterPeerByAddrPort(path[3])
|
||||||
|
}
|
||||||
|
if path[2] == "pubkey" {
|
||||||
|
return app.FilterPeerByPubKey(path[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg := "unknown query path"
|
||||||
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements ABCI
|
// Implements ABCI
|
||||||
|
@ -312,7 +394,7 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result = err.Result()
|
result = err.Result()
|
||||||
} else {
|
} else {
|
||||||
result = app.runTx(true, txBytes, tx)
|
result = app.runTx(runTxModeCheck, txBytes, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return abci.ResponseCheckTx{
|
return abci.ResponseCheckTx{
|
||||||
|
@ -320,6 +402,7 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||||
Data: result.Data,
|
Data: result.Data,
|
||||||
Log: result.Log,
|
Log: result.Log,
|
||||||
GasWanted: result.GasWanted,
|
GasWanted: result.GasWanted,
|
||||||
|
GasUsed: result.GasUsed,
|
||||||
Fee: cmn.KI64Pair{
|
Fee: cmn.KI64Pair{
|
||||||
[]byte(result.FeeDenom),
|
[]byte(result.FeeDenom),
|
||||||
result.FeeAmount,
|
result.FeeAmount,
|
||||||
|
@ -336,7 +419,7 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result = err.Result()
|
result = err.Result()
|
||||||
} else {
|
} else {
|
||||||
result = app.runTx(false, txBytes, tx)
|
result = app.runTx(runTxModeDeliver, txBytes, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// After-handler hooks.
|
// After-handler hooks.
|
||||||
|
@ -358,22 +441,35 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint- Mostly for testing
|
// nolint - Mostly for testing
|
||||||
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
|
func (app *BaseApp) Check(tx sdk.Tx) (result sdk.Result) {
|
||||||
return app.runTx(true, nil, tx)
|
return app.runTx(runTxModeCheck, nil, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint - full tx execution
|
||||||
|
func (app *BaseApp) Simulate(tx sdk.Tx) (result sdk.Result) {
|
||||||
|
return app.runTx(runTxModeSimulate, nil, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint
|
||||||
func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
|
func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
|
||||||
return app.runTx(false, nil, tx)
|
return app.runTx(runTxModeDeliver, nil, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// txBytes may be nil in some cases, eg. in tests.
|
// txBytes may be nil in some cases, eg. in tests.
|
||||||
// Also, in the future we may support "internal" transactions.
|
// Also, in the future we may support "internal" transactions.
|
||||||
func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
|
func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
|
||||||
// Handle any panics.
|
// Handle any panics.
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
log := fmt.Sprintf("Recovered: %v\nstack:\n%v", r, string(debug.Stack()))
|
switch r.(type) {
|
||||||
result = sdk.ErrInternal(log).Result()
|
case sdk.ErrorOutOfGas:
|
||||||
|
log := fmt.Sprintf("Out of gas in location: %v", r.(sdk.ErrorOutOfGas).Descriptor)
|
||||||
|
result = sdk.ErrOutOfGas(log).Result()
|
||||||
|
default:
|
||||||
|
log := fmt.Sprintf("Recovered: %v\nstack:\n%v", r, string(debug.Stack()))
|
||||||
|
result = sdk.ErrInternal(log).Result()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -392,12 +488,17 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
|
|
||||||
// Get the context
|
// Get the context
|
||||||
var ctx sdk.Context
|
var ctx sdk.Context
|
||||||
if isCheckTx {
|
if mode == runTxModeCheck || mode == runTxModeSimulate {
|
||||||
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
ctx = app.checkState.ctx.WithTxBytes(txBytes)
|
||||||
} else {
|
} else {
|
||||||
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
|
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simulate a DeliverTx for gas calculation
|
||||||
|
if mode == runTxModeSimulate {
|
||||||
|
ctx = ctx.WithIsCheckTx(false)
|
||||||
|
}
|
||||||
|
|
||||||
// Run the ante handler.
|
// Run the ante handler.
|
||||||
if app.anteHandler != nil {
|
if app.anteHandler != nil {
|
||||||
newCtx, result, abort := app.anteHandler(ctx, tx)
|
newCtx, result, abort := app.anteHandler(ctx, tx)
|
||||||
|
@ -418,7 +519,7 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
|
|
||||||
// Get the correct cache
|
// Get the correct cache
|
||||||
var msCache sdk.CacheMultiStore
|
var msCache sdk.CacheMultiStore
|
||||||
if isCheckTx == true {
|
if mode == runTxModeCheck || mode == runTxModeSimulate {
|
||||||
// CacheWrap app.checkState.ms in case it fails.
|
// CacheWrap app.checkState.ms in case it fails.
|
||||||
msCache = app.checkState.CacheMultiStore()
|
msCache = app.checkState.CacheMultiStore()
|
||||||
ctx = ctx.WithMultiStore(msCache)
|
ctx = ctx.WithMultiStore(msCache)
|
||||||
|
@ -426,13 +527,15 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
|
||||||
// CacheWrap app.deliverState.ms in case it fails.
|
// CacheWrap app.deliverState.ms in case it fails.
|
||||||
msCache = app.deliverState.CacheMultiStore()
|
msCache = app.deliverState.CacheMultiStore()
|
||||||
ctx = ctx.WithMultiStore(msCache)
|
ctx = ctx.WithMultiStore(msCache)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result = handler(ctx, msg)
|
result = handler(ctx, msg)
|
||||||
|
|
||||||
// If result was successful, write to app.checkState.ms or app.deliverState.ms
|
// Set gas utilized
|
||||||
if result.IsOK() {
|
result.GasUsed = ctx.GasMeter().GasConsumed()
|
||||||
|
|
||||||
|
// If not a simulated run and result was successful, write to app.checkState.ms or app.deliverState.ms
|
||||||
|
if mode != runTxModeSimulate && result.IsOK() {
|
||||||
msCache.Write()
|
msCache.Write()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
"github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultLogger() log.Logger {
|
func defaultLogger() log.Logger {
|
||||||
|
@ -25,7 +27,9 @@ func defaultLogger() log.Logger {
|
||||||
func newBaseApp(name string) *BaseApp {
|
func newBaseApp(name string) *BaseApp {
|
||||||
logger := defaultLogger()
|
logger := defaultLogger()
|
||||||
db := dbm.NewMemDB()
|
db := dbm.NewMemDB()
|
||||||
return NewBaseApp(name, nil, logger, db)
|
codec := wire.NewCodec()
|
||||||
|
wire.RegisterCrypto(codec)
|
||||||
|
return NewBaseApp(name, codec, logger, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMountStores(t *testing.T) {
|
func TestMountStores(t *testing.T) {
|
||||||
|
@ -167,7 +171,7 @@ func TestInitChainer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
query := abci.RequestQuery{
|
query := abci.RequestQuery{
|
||||||
Path: "/main/key",
|
Path: "/store/main/key",
|
||||||
Data: key,
|
Data: key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,6 +264,97 @@ func TestDeliverTx(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSimulateTx(t *testing.T) {
|
||||||
|
app := newBaseApp(t.Name())
|
||||||
|
|
||||||
|
// make a cap key and mount the store
|
||||||
|
capKey := sdk.NewKVStoreKey("main")
|
||||||
|
app.MountStoresIAVL(capKey)
|
||||||
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
||||||
|
app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||||
|
ctx.GasMeter().ConsumeGas(10, "test")
|
||||||
|
store := ctx.KVStore(capKey)
|
||||||
|
// ensure store is never written
|
||||||
|
require.Nil(t, store.Get([]byte("key")))
|
||||||
|
store.Set([]byte("key"), []byte("value"))
|
||||||
|
// check we can see the current header
|
||||||
|
thisHeader := ctx.BlockHeader()
|
||||||
|
height := int64(counter)
|
||||||
|
assert.Equal(t, height, thisHeader.Height)
|
||||||
|
counter++
|
||||||
|
return sdk.Result{}
|
||||||
|
})
|
||||||
|
|
||||||
|
tx := testUpdatePowerTx{} // doesn't matter
|
||||||
|
header := abci.Header{AppHash: []byte("apphash")}
|
||||||
|
|
||||||
|
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||||
|
var ttx testUpdatePowerTx
|
||||||
|
fromJSON(txBytes, &ttx)
|
||||||
|
return ttx, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
nBlocks := 3
|
||||||
|
for blockN := 0; blockN < nBlocks; blockN++ {
|
||||||
|
// block1
|
||||||
|
header.Height = int64(blockN + 1)
|
||||||
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||||
|
result := app.Simulate(tx)
|
||||||
|
require.Equal(t, result.Code, sdk.ABCICodeOK)
|
||||||
|
require.Equal(t, int64(80), result.GasUsed)
|
||||||
|
counter--
|
||||||
|
encoded, err := json.Marshal(tx)
|
||||||
|
require.Nil(t, err)
|
||||||
|
query := abci.RequestQuery{
|
||||||
|
Path: "/app/simulate",
|
||||||
|
Data: encoded,
|
||||||
|
}
|
||||||
|
queryResult := app.Query(query)
|
||||||
|
require.Equal(t, queryResult.Code, uint32(sdk.ABCICodeOK))
|
||||||
|
var res sdk.Result
|
||||||
|
app.cdc.MustUnmarshalBinary(queryResult.Value, &res)
|
||||||
|
require.Equal(t, sdk.ABCICodeOK, res.Code)
|
||||||
|
require.Equal(t, int64(160), res.GasUsed)
|
||||||
|
app.EndBlock(abci.RequestEndBlock{})
|
||||||
|
app.Commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that transactions exceeding gas limits fail
|
||||||
|
func TestTxGasLimits(t *testing.T) {
|
||||||
|
logger := defaultLogger()
|
||||||
|
db := dbm.NewMemDB()
|
||||||
|
app := NewBaseApp(t.Name(), nil, logger, db)
|
||||||
|
|
||||||
|
// make a cap key and mount the store
|
||||||
|
capKey := sdk.NewKVStoreKey("main")
|
||||||
|
app.MountStoresIAVL(capKey)
|
||||||
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
app.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(0))
|
||||||
|
return
|
||||||
|
})
|
||||||
|
app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||||
|
ctx.GasMeter().ConsumeGas(10, "counter")
|
||||||
|
return sdk.Result{}
|
||||||
|
})
|
||||||
|
|
||||||
|
tx := testUpdatePowerTx{} // doesn't matter
|
||||||
|
header := abci.Header{AppHash: []byte("apphash")}
|
||||||
|
|
||||||
|
app.BeginBlock(abci.RequestBeginBlock{Header: header})
|
||||||
|
res := app.Deliver(tx)
|
||||||
|
assert.Equal(t, res.Code, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOutOfGas), "Expected transaction to run out of gas")
|
||||||
|
app.EndBlock(abci.RequestEndBlock{})
|
||||||
|
app.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
// Test that we can only query from the latest committed state.
|
// Test that we can only query from the latest committed state.
|
||||||
func TestQuery(t *testing.T) {
|
func TestQuery(t *testing.T) {
|
||||||
app := newBaseApp(t.Name())
|
app := newBaseApp(t.Name())
|
||||||
|
@ -280,7 +375,7 @@ func TestQuery(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
query := abci.RequestQuery{
|
query := abci.RequestQuery{
|
||||||
Path: "/main/key",
|
Path: "/store/main/key",
|
||||||
Data: key,
|
Data: key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +402,39 @@ func TestQuery(t *testing.T) {
|
||||||
assert.Equal(t, value, res.Value)
|
assert.Equal(t, value, res.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test p2p filter queries
|
||||||
|
func TestP2PQuery(t *testing.T) {
|
||||||
|
app := newBaseApp(t.Name())
|
||||||
|
|
||||||
|
// make a cap key and mount the store
|
||||||
|
capKey := sdk.NewKVStoreKey("main")
|
||||||
|
app.MountStoresIAVL(capKey)
|
||||||
|
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
app.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery {
|
||||||
|
require.Equal(t, "1.1.1.1:8000", addrport)
|
||||||
|
return abci.ResponseQuery{Code: uint32(3)}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery {
|
||||||
|
require.Equal(t, "testpubkey", pubkey)
|
||||||
|
return abci.ResponseQuery{Code: uint32(4)}
|
||||||
|
})
|
||||||
|
|
||||||
|
addrQuery := abci.RequestQuery{
|
||||||
|
Path: "/p2p/filter/addr/1.1.1.1:8000",
|
||||||
|
}
|
||||||
|
res := app.Query(addrQuery)
|
||||||
|
require.Equal(t, uint32(3), res.Code)
|
||||||
|
|
||||||
|
pubkeyQuery := abci.RequestQuery{
|
||||||
|
Path: "/p2p/filter/pubkey/testpubkey",
|
||||||
|
}
|
||||||
|
res = app.Query(pubkeyQuery)
|
||||||
|
require.Equal(t, uint32(4), res.Code)
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------
|
//----------------------
|
||||||
// TODO: clean this up
|
// TODO: clean this up
|
||||||
|
|
||||||
|
|
|
@ -58,8 +58,7 @@ func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName
|
||||||
|
|
||||||
// Query from Tendermint with the provided storename and path
|
// Query from Tendermint with the provided storename and path
|
||||||
func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) {
|
func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) {
|
||||||
|
path := fmt.Sprintf("/store/%s/key", storeName)
|
||||||
path := fmt.Sprintf("/%s/%s", storeName, endPath)
|
|
||||||
node, err := ctx.GetNode()
|
node, err := ctx.GetNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
|
@ -114,6 +113,7 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w
|
||||||
ChainID: chainID,
|
ChainID: chainID,
|
||||||
Sequences: []int64{sequence},
|
Sequences: []int64{sequence},
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
|
Fee: sdk.NewStdFee(10000, sdk.Coin{}), // TODO run simulate to estimate gas?
|
||||||
}
|
}
|
||||||
|
|
||||||
keybase, err := keys.GetKeyBase()
|
keybase, err := keys.GetKeyBase()
|
||||||
|
|
|
@ -40,7 +40,7 @@ var (
|
||||||
manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}}
|
manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}}
|
||||||
fee = sdk.StdFee{
|
fee = sdk.StdFee{
|
||||||
sdk.Coins{{"foocoin", 0}},
|
sdk.Coins{{"foocoin", 0}},
|
||||||
0,
|
100000,
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMsg1 = bank.MsgSend{
|
sendMsg1 = bank.MsgSend{
|
||||||
|
|
|
@ -39,7 +39,7 @@ var (
|
||||||
manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}}
|
manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}}
|
||||||
fee = sdk.StdFee{
|
fee = sdk.StdFee{
|
||||||
sdk.Coins{{"foocoin", 0}},
|
sdk.Coins{{"foocoin", 0}},
|
||||||
0,
|
100000,
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMsg1 = bank.MsgSend{
|
sendMsg1 = bank.MsgSend{
|
||||||
|
|
|
@ -33,7 +33,7 @@ var (
|
||||||
coins = sdk.Coins{{"foocoin", 10}}
|
coins = sdk.Coins{{"foocoin", 10}}
|
||||||
fee = sdk.StdFee{
|
fee = sdk.StdFee{
|
||||||
sdk.Coins{{"foocoin", 0}},
|
sdk.Coins{{"foocoin", 0}},
|
||||||
0,
|
1000000,
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMsg = bank.MsgSend{
|
sendMsg = bank.MsgSend{
|
||||||
|
|
|
@ -31,10 +31,9 @@ func TestInitApp(t *testing.T) {
|
||||||
app.InitChain(req)
|
app.InitChain(req)
|
||||||
app.Commit()
|
app.Commit()
|
||||||
|
|
||||||
// XXX test failing
|
|
||||||
// make sure we can query these values
|
// make sure we can query these values
|
||||||
query := abci.RequestQuery{
|
query := abci.RequestQuery{
|
||||||
Path: "/main/key",
|
Path: "/store/main/key",
|
||||||
Data: []byte("foo"),
|
Data: []byte("foo"),
|
||||||
}
|
}
|
||||||
qres := app.Query(query)
|
qres := app.Query(query)
|
||||||
|
@ -70,7 +69,7 @@ func TestDeliverTx(t *testing.T) {
|
||||||
|
|
||||||
// make sure we can query these values
|
// make sure we can query these values
|
||||||
query := abci.RequestQuery{
|
query := abci.RequestQuery{
|
||||||
Path: "/main/key",
|
Path: "/store/main/key",
|
||||||
Data: []byte(key),
|
Data: []byte(key),
|
||||||
}
|
}
|
||||||
qres := app.Query(query)
|
qres := app.Query(query)
|
||||||
|
|
|
@ -50,6 +50,10 @@ func (ms multiStore) GetKVStore(key sdk.StoreKey) sdk.KVStore {
|
||||||
return ms.kv[key]
|
return ms.kv[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ms multiStore) GetKVStoreWithGas(meter sdk.GasMeter, key sdk.StoreKey) sdk.KVStore {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (ms multiStore) GetStore(key sdk.StoreKey) sdk.Store {
|
func (ms multiStore) GetStore(key sdk.StoreKey) sdk.Store {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,3 +72,8 @@ func (cms cacheMultiStore) GetStore(key StoreKey) Store {
|
||||||
func (cms cacheMultiStore) GetKVStore(key StoreKey) KVStore {
|
func (cms cacheMultiStore) GetKVStore(key StoreKey) KVStore {
|
||||||
return cms.stores[key].(KVStore)
|
return cms.stores[key].(KVStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements MultiStore.
|
||||||
|
func (cms cacheMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, key StoreKey) KVStore {
|
||||||
|
return NewGasKVStore(meter, cms.GetKVStore(key))
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint
|
||||||
|
const (
|
||||||
|
HasCost = 10
|
||||||
|
ReadCostFlat = 10
|
||||||
|
ReadCostPerByte = 1
|
||||||
|
WriteCostFlat = 10
|
||||||
|
WriteCostPerByte = 10
|
||||||
|
KeyCostFlat = 5
|
||||||
|
ValueCostFlat = 10
|
||||||
|
ValueCostPerByte = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// gasKVStore applies gas tracking to an underlying kvstore
|
||||||
|
type gasKVStore struct {
|
||||||
|
gasMeter sdk.GasMeter
|
||||||
|
parent sdk.KVStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint
|
||||||
|
func NewGasKVStore(gasMeter sdk.GasMeter, parent sdk.KVStore) *gasKVStore {
|
||||||
|
kvs := &gasKVStore{
|
||||||
|
gasMeter: gasMeter,
|
||||||
|
parent: parent,
|
||||||
|
}
|
||||||
|
return kvs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Store.
|
||||||
|
func (gi *gasKVStore) GetStoreType() sdk.StoreType {
|
||||||
|
return gi.parent.GetStoreType()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) Get(key []byte) (value []byte) {
|
||||||
|
gi.gasMeter.ConsumeGas(ReadCostFlat, "GetFlat")
|
||||||
|
value = gi.parent.Get(key)
|
||||||
|
// TODO overflow-safe math?
|
||||||
|
gi.gasMeter.ConsumeGas(ReadCostPerByte*sdk.Gas(len(value)), "ReadPerByte")
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) Set(key []byte, value []byte) {
|
||||||
|
gi.gasMeter.ConsumeGas(WriteCostFlat, "SetFlat")
|
||||||
|
// TODO overflow-safe math?
|
||||||
|
gi.gasMeter.ConsumeGas(WriteCostPerByte*sdk.Gas(len(value)), "SetPerByte")
|
||||||
|
gi.parent.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) Has(key []byte) bool {
|
||||||
|
gi.gasMeter.ConsumeGas(HasCost, "Has")
|
||||||
|
return gi.parent.Has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) Delete(key []byte) {
|
||||||
|
// No gas costs for deletion
|
||||||
|
gi.parent.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) Iterator(start, end []byte) sdk.Iterator {
|
||||||
|
return gi.iterator(start, end, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) ReverseIterator(start, end []byte) sdk.Iterator {
|
||||||
|
return gi.iterator(start, end, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) SubspaceIterator(prefix []byte) sdk.Iterator {
|
||||||
|
return gi.iterator(prefix, sdk.PrefixEndBytes(prefix), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) ReverseSubspaceIterator(prefix []byte) sdk.Iterator {
|
||||||
|
return gi.iterator(prefix, sdk.PrefixEndBytes(prefix), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements KVStore.
|
||||||
|
func (gi *gasKVStore) CacheWrap() sdk.CacheWrap {
|
||||||
|
panic("you cannot CacheWrap a GasKVStore")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator {
|
||||||
|
var parent sdk.Iterator
|
||||||
|
if ascending {
|
||||||
|
parent = gi.parent.Iterator(start, end)
|
||||||
|
} else {
|
||||||
|
parent = gi.parent.ReverseIterator(start, end)
|
||||||
|
}
|
||||||
|
return newGasIterator(gi.gasMeter, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
type gasIterator struct {
|
||||||
|
gasMeter sdk.GasMeter
|
||||||
|
parent sdk.Iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGasIterator(gasMeter sdk.GasMeter, parent sdk.Iterator) sdk.Iterator {
|
||||||
|
return &gasIterator{
|
||||||
|
gasMeter: gasMeter,
|
||||||
|
parent: parent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Iterator.
|
||||||
|
func (g *gasIterator) Domain() (start []byte, end []byte) {
|
||||||
|
return g.parent.Domain()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Iterator.
|
||||||
|
func (g *gasIterator) Valid() bool {
|
||||||
|
return g.parent.Valid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Iterator.
|
||||||
|
func (g *gasIterator) Next() {
|
||||||
|
g.parent.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Iterator.
|
||||||
|
func (g *gasIterator) Key() (key []byte) {
|
||||||
|
g.gasMeter.ConsumeGas(KeyCostFlat, "KeyFlat")
|
||||||
|
key = g.parent.Key()
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Iterator.
|
||||||
|
func (g *gasIterator) Value() (value []byte) {
|
||||||
|
value = g.parent.Value()
|
||||||
|
g.gasMeter.ConsumeGas(ValueCostFlat, "ValueFlat")
|
||||||
|
g.gasMeter.ConsumeGas(ValueCostPerByte*sdk.Gas(len(value)), "ValuePerByte")
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Iterator.
|
||||||
|
func (g *gasIterator) Close() {
|
||||||
|
g.parent.Close()
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
dbm "github.com/tendermint/tmlibs/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newGasKVStore() KVStore {
|
||||||
|
meter := sdk.NewGasMeter(1000)
|
||||||
|
mem := dbStoreAdapter{dbm.NewMemDB()}
|
||||||
|
return NewGasKVStore(meter, mem)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGasKVStoreBasic(t *testing.T) {
|
||||||
|
mem := dbStoreAdapter{dbm.NewMemDB()}
|
||||||
|
meter := sdk.NewGasMeter(1000)
|
||||||
|
st := NewGasKVStore(meter, mem)
|
||||||
|
require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty")
|
||||||
|
st.Set(keyFmt(1), valFmt(1))
|
||||||
|
require.Equal(t, valFmt(1), st.Get(keyFmt(1)))
|
||||||
|
st.Delete(keyFmt(1))
|
||||||
|
require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty")
|
||||||
|
require.Equal(t, meter.GasConsumed(), sdk.Gas(183))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGasKVStoreIterator(t *testing.T) {
|
||||||
|
mem := dbStoreAdapter{dbm.NewMemDB()}
|
||||||
|
meter := sdk.NewGasMeter(1000)
|
||||||
|
st := NewGasKVStore(meter, mem)
|
||||||
|
require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty")
|
||||||
|
require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty")
|
||||||
|
st.Set(keyFmt(1), valFmt(1))
|
||||||
|
st.Set(keyFmt(2), valFmt(2))
|
||||||
|
iterator := st.Iterator(nil, nil)
|
||||||
|
ka := iterator.Key()
|
||||||
|
require.Equal(t, ka, keyFmt(1))
|
||||||
|
va := iterator.Value()
|
||||||
|
require.Equal(t, va, valFmt(1))
|
||||||
|
iterator.Next()
|
||||||
|
kb := iterator.Key()
|
||||||
|
require.Equal(t, kb, keyFmt(2))
|
||||||
|
vb := iterator.Value()
|
||||||
|
require.Equal(t, vb, valFmt(2))
|
||||||
|
iterator.Next()
|
||||||
|
require.False(t, iterator.Valid())
|
||||||
|
require.Panics(t, iterator.Next)
|
||||||
|
require.Equal(t, meter.GasConsumed(), sdk.Gas(356))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGasKVStoreOutOfGasSet(t *testing.T) {
|
||||||
|
mem := dbStoreAdapter{dbm.NewMemDB()}
|
||||||
|
meter := sdk.NewGasMeter(0)
|
||||||
|
st := NewGasKVStore(meter, mem)
|
||||||
|
require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGasKVStoreOutOfGasIterator(t *testing.T) {
|
||||||
|
mem := dbStoreAdapter{dbm.NewMemDB()}
|
||||||
|
meter := sdk.NewGasMeter(200)
|
||||||
|
st := NewGasKVStore(meter, mem)
|
||||||
|
st.Set(keyFmt(1), valFmt(1))
|
||||||
|
iterator := st.Iterator(nil, nil)
|
||||||
|
iterator.Next()
|
||||||
|
require.Panics(t, func() { iterator.Value() }, "Expected out-of-gas")
|
||||||
|
}
|
|
@ -183,6 +183,11 @@ func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore {
|
||||||
return rs.stores[key].(KVStore)
|
return rs.stores[key].(KVStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements MultiStore.
|
||||||
|
func (rs *rootMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, key StoreKey) KVStore {
|
||||||
|
return NewGasKVStore(meter, rs.GetKVStore(key))
|
||||||
|
}
|
||||||
|
|
||||||
// getStoreByName will first convert the original name to
|
// getStoreByName will first convert the original name to
|
||||||
// a special key, before looking up the CommitStore.
|
// a special key, before looking up the CommitStore.
|
||||||
// This is not exposed to the extensions (which will need the
|
// This is not exposed to the extensions (which will need the
|
||||||
|
|
|
@ -10,3 +10,6 @@ type BeginBlocker func(ctx Context, req abci.RequestBeginBlock) abci.ResponseBeg
|
||||||
|
|
||||||
// run code after the transactions in a block and return updates to the validator set
|
// run code after the transactions in a block and return updates to the validator set
|
||||||
type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBlock
|
type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBlock
|
||||||
|
|
||||||
|
// respond to p2p filtering queries from Tendermint
|
||||||
|
type PeerFilter func(info string) abci.ResponseQuery
|
||||||
|
|
|
@ -43,6 +43,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byt
|
||||||
c = c.WithIsCheckTx(isCheckTx)
|
c = c.WithIsCheckTx(isCheckTx)
|
||||||
c = c.WithTxBytes(txBytes)
|
c = c.WithTxBytes(txBytes)
|
||||||
c = c.WithLogger(logger)
|
c = c.WithLogger(logger)
|
||||||
|
c = c.WithGasMeter(NewInfiniteGasMeter())
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ func (c Context) Value(key interface{}) interface{} {
|
||||||
|
|
||||||
// KVStore fetches a KVStore from the MultiStore.
|
// KVStore fetches a KVStore from the MultiStore.
|
||||||
func (c Context) KVStore(key StoreKey) KVStore {
|
func (c Context) KVStore(key StoreKey) KVStore {
|
||||||
return c.multiStore().GetKVStore(key)
|
return c.multiStore().GetKVStoreWithGas(c.GasMeter(), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
|
@ -127,6 +128,7 @@ const (
|
||||||
contextKeyIsCheckTx
|
contextKeyIsCheckTx
|
||||||
contextKeyTxBytes
|
contextKeyTxBytes
|
||||||
contextKeyLogger
|
contextKeyLogger
|
||||||
|
contextKeyGasMeter
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: Do not expose MultiStore.
|
// NOTE: Do not expose MultiStore.
|
||||||
|
@ -155,6 +157,9 @@ func (c Context) TxBytes() []byte {
|
||||||
func (c Context) Logger() log.Logger {
|
func (c Context) Logger() log.Logger {
|
||||||
return c.Value(contextKeyLogger).(log.Logger)
|
return c.Value(contextKeyLogger).(log.Logger)
|
||||||
}
|
}
|
||||||
|
func (c Context) GasMeter() GasMeter {
|
||||||
|
return c.Value(contextKeyGasMeter).(GasMeter)
|
||||||
|
}
|
||||||
func (c Context) WithMultiStore(ms MultiStore) Context {
|
func (c Context) WithMultiStore(ms MultiStore) Context {
|
||||||
return c.withValue(contextKeyMultiStore, ms)
|
return c.withValue(contextKeyMultiStore, ms)
|
||||||
}
|
}
|
||||||
|
@ -177,6 +182,9 @@ func (c Context) WithTxBytes(txBytes []byte) Context {
|
||||||
func (c Context) WithLogger(logger log.Logger) Context {
|
func (c Context) WithLogger(logger log.Logger) Context {
|
||||||
return c.withValue(contextKeyLogger, logger)
|
return c.withValue(contextKeyLogger, logger)
|
||||||
}
|
}
|
||||||
|
func (c Context) WithGasMeter(meter GasMeter) Context {
|
||||||
|
return c.withValue(contextKeyGasMeter, meter)
|
||||||
|
}
|
||||||
|
|
||||||
// Cache the multistore and return a new cached context. The cached context is
|
// Cache the multistore and return a new cached context. The cached context is
|
||||||
// written to the context when writeCache is called.
|
// written to the context when writeCache is called.
|
||||||
|
|
|
@ -52,6 +52,7 @@ const (
|
||||||
CodeUnknownAddress CodeType = 9
|
CodeUnknownAddress CodeType = 9
|
||||||
CodeInsufficientCoins CodeType = 10
|
CodeInsufficientCoins CodeType = 10
|
||||||
CodeInvalidCoins CodeType = 11
|
CodeInvalidCoins CodeType = 11
|
||||||
|
CodeOutOfGas CodeType = 12
|
||||||
|
|
||||||
// CodespaceRoot is a codespace for error codes in this file only.
|
// CodespaceRoot is a codespace for error codes in this file only.
|
||||||
// Notice that 0 is an "unset" codespace, which can be overridden with
|
// Notice that 0 is an "unset" codespace, which can be overridden with
|
||||||
|
@ -88,6 +89,8 @@ func CodeToDefaultMsg(code CodeType) string {
|
||||||
return "Insufficient coins"
|
return "Insufficient coins"
|
||||||
case CodeInvalidCoins:
|
case CodeInvalidCoins:
|
||||||
return "Invalid coins"
|
return "Invalid coins"
|
||||||
|
case CodeOutOfGas:
|
||||||
|
return "Out of gas"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("Unknown code %d", code)
|
return fmt.Sprintf("Unknown code %d", code)
|
||||||
}
|
}
|
||||||
|
@ -131,6 +134,9 @@ func ErrInsufficientCoins(msg string) Error {
|
||||||
func ErrInvalidCoins(msg string) Error {
|
func ErrInvalidCoins(msg string) Error {
|
||||||
return newErrorWithRootCodespace(CodeInvalidCoins, msg)
|
return newErrorWithRootCodespace(CodeInvalidCoins, msg)
|
||||||
}
|
}
|
||||||
|
func ErrOutOfGas(msg string) Error {
|
||||||
|
return newErrorWithRootCodespace(CodeOutOfGas, msg)
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
// Error & sdkError
|
// Error & sdkError
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import ()
|
||||||
|
|
||||||
|
// Gas measured by the SDK
|
||||||
|
type Gas = int64
|
||||||
|
|
||||||
|
// Error thrown when out of gas
|
||||||
|
type ErrorOutOfGas struct {
|
||||||
|
Descriptor string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GasMeter interface to track gas consumption
|
||||||
|
type GasMeter interface {
|
||||||
|
GasConsumed() Gas
|
||||||
|
ConsumeGas(amount Gas, descriptor string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type basicGasMeter struct {
|
||||||
|
limit Gas
|
||||||
|
consumed Gas
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGasMeter(limit Gas) GasMeter {
|
||||||
|
return &basicGasMeter{
|
||||||
|
limit: limit,
|
||||||
|
consumed: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *basicGasMeter) GasConsumed() Gas {
|
||||||
|
return g.consumed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
||||||
|
g.consumed += amount
|
||||||
|
if g.consumed > g.limit {
|
||||||
|
panic(ErrorOutOfGas{descriptor})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type infiniteGasMeter struct {
|
||||||
|
consumed Gas
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInfiniteGasMeter() GasMeter {
|
||||||
|
return &infiniteGasMeter{
|
||||||
|
consumed: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *infiniteGasMeter) GasConsumed() Gas {
|
||||||
|
return g.consumed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) {
|
||||||
|
g.consumed += amount
|
||||||
|
}
|
|
@ -49,6 +49,7 @@ type MultiStore interface { //nolint
|
||||||
// Convenience for fetching substores.
|
// Convenience for fetching substores.
|
||||||
GetStore(StoreKey) Store
|
GetStore(StoreKey) Store
|
||||||
GetKVStore(StoreKey) KVStore
|
GetKVStore(StoreKey) KVStore
|
||||||
|
GetKVStoreWithGas(GasMeter, StoreKey) KVStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// From MultiStore.CacheMultiStore()....
|
// From MultiStore.CacheMultiStore()....
|
||||||
|
|
|
@ -8,6 +8,10 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
verifyCost = 100
|
||||||
|
)
|
||||||
|
|
||||||
// NewAnteHandler returns an AnteHandler that checks
|
// NewAnteHandler returns an AnteHandler that checks
|
||||||
// and increments sequence numbers, checks signatures,
|
// and increments sequence numbers, checks signatures,
|
||||||
// and deducts fees from the first signer.
|
// and deducts fees from the first signer.
|
||||||
|
@ -88,6 +92,9 @@ func NewAnteHandler(am sdk.AccountMapper, feeHandler sdk.FeeHandler) sdk.AnteHan
|
||||||
// cache the signer accounts in the context
|
// cache the signer accounts in the context
|
||||||
ctx = WithSigners(ctx, signerAccs)
|
ctx = WithSigners(ctx, signerAccs)
|
||||||
|
|
||||||
|
// set the gas meter
|
||||||
|
ctx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas))
|
||||||
|
|
||||||
// TODO: tx tags (?)
|
// TODO: tx tags (?)
|
||||||
|
|
||||||
return ctx, sdk.Result{}, false // continue...
|
return ctx, sdk.Result{}, false // continue...
|
||||||
|
@ -134,6 +141,7 @@ func processSig(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check sig.
|
// Check sig.
|
||||||
|
ctx.GasMeter().ConsumeGas(verifyCost, "ante verify")
|
||||||
if !pubKey.VerifyBytes(signBytes, sig.Signature) {
|
if !pubKey.VerifyBytes(signBytes, sig.Signature) {
|
||||||
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
|
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,14 @@ import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
costGetCoins sdk.Gas = 10
|
||||||
|
costHasCoins sdk.Gas = 10
|
||||||
|
costSetCoins sdk.Gas = 100
|
||||||
|
costSubtractCoins sdk.Gas = 10
|
||||||
|
costAddCoins sdk.Gas = 10
|
||||||
|
)
|
||||||
|
|
||||||
// Keeper manages transfers between accounts
|
// Keeper manages transfers between accounts
|
||||||
type Keeper struct {
|
type Keeper struct {
|
||||||
am sdk.AccountMapper
|
am sdk.AccountMapper
|
||||||
|
@ -108,6 +116,7 @@ func (keeper ViewKeeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coi
|
||||||
//______________________________________________________________________________________________
|
//______________________________________________________________________________________________
|
||||||
|
|
||||||
func getCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address) sdk.Coins {
|
func getCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address) sdk.Coins {
|
||||||
|
ctx.GasMeter().ConsumeGas(costGetCoins, "getCoins")
|
||||||
acc := am.GetAccount(ctx, addr)
|
acc := am.GetAccount(ctx, addr)
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
return sdk.Coins{}
|
return sdk.Coins{}
|
||||||
|
@ -116,6 +125,7 @@ func getCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address) sdk.Coins
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) sdk.Error {
|
func setCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) sdk.Error {
|
||||||
|
ctx.GasMeter().ConsumeGas(costSetCoins, "setCoins")
|
||||||
acc := am.GetAccount(ctx, addr)
|
acc := am.GetAccount(ctx, addr)
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
acc = am.NewAccountWithAddress(ctx, addr)
|
acc = am.NewAccountWithAddress(ctx, addr)
|
||||||
|
@ -127,11 +137,13 @@ func setCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.C
|
||||||
|
|
||||||
// HasCoins returns whether or not an account has at least amt coins.
|
// HasCoins returns whether or not an account has at least amt coins.
|
||||||
func hasCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) bool {
|
func hasCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) bool {
|
||||||
|
ctx.GasMeter().ConsumeGas(costHasCoins, "hasCoins")
|
||||||
return getCoins(ctx, am, addr).IsGTE(amt)
|
return getCoins(ctx, am, addr).IsGTE(amt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubtractCoins subtracts amt from the coins at the addr.
|
// SubtractCoins subtracts amt from the coins at the addr.
|
||||||
func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
||||||
|
ctx.GasMeter().ConsumeGas(costSubtractCoins, "subtractCoins")
|
||||||
oldCoins := getCoins(ctx, am, addr)
|
oldCoins := getCoins(ctx, am, addr)
|
||||||
newCoins := oldCoins.Minus(amt)
|
newCoins := oldCoins.Minus(amt)
|
||||||
if !newCoins.IsNotNegative() {
|
if !newCoins.IsNotNegative() {
|
||||||
|
@ -144,6 +156,7 @@ func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt
|
||||||
|
|
||||||
// AddCoins adds amt to the coins at the addr.
|
// AddCoins adds amt to the coins at the addr.
|
||||||
func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
||||||
|
ctx.GasMeter().ConsumeGas(costAddCoins, "addCoins")
|
||||||
oldCoins := getCoins(ctx, am, addr)
|
oldCoins := getCoins(ctx, am, addr)
|
||||||
newCoins := oldCoins.Plus(amt)
|
newCoins := oldCoins.Plus(amt)
|
||||||
if !newCoins.IsNotNegative() {
|
if !newCoins.IsNotNegative() {
|
||||||
|
|
|
@ -65,8 +65,7 @@ func TestKeeper(t *testing.T) {
|
||||||
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})
|
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})
|
||||||
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
|
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
|
||||||
|
|
||||||
_, _, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}})
|
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}})
|
||||||
assert.Implements(t, (*sdk.Error)(nil), err)
|
|
||||||
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
|
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
|
||||||
|
|
||||||
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 10}})
|
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 10}})
|
||||||
|
|
Loading…
Reference in New Issue