Merge branch 'develop' of https://github.com/cosmos/cosmos-sdk into develop

This commit is contained in:
David Kajpust 2018-05-18 22:05:07 -03:00
commit c0c31629c0
38 changed files with 954 additions and 127 deletions

View File

@ -1,6 +1,48 @@
# Changelog # Changelog
## 0.16.0 (TBD) ## 0.18.0 (TBD)
FEATURES
* [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
## 0.17.1 (May 17, 2018)
Update to Tendermint v0.19.4 (fixes a consensus bug and improves logging)
## 0.17.0 (May 15, 2018)
BREAKING CHANGES
* [stake] MarshalJSON -> MarshalBinary
FEATURES
* [gaiacli] Support queries for candidates, delegator-bonds
* [gaiad] Added `gaiad export` command to export current state to JSON
* [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
IMPROVEMENTS
* [gaiad] Update for Tendermint v0.19.3 (improve `/dump_consensus_state` and add
`/consensus_state`)
* [spec/ibc] Added spec!
* [spec/stake] Cleanup structure, include details about slashing and
auto-unbonding
* [spec/governance] Fixup some names and pseudocode
* NOTE: specs are still a work-in-progress ...
BUG FIXES
* Auto-sequencing now works correctly
## 0.16.0 (May 14th, 2018)
BREAKING CHANGES BREAKING CHANGES
@ -15,10 +57,10 @@ 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:
* Added `gaiad export` command, which exports genesis information & current state
* Gaia stake commands include, DeclareCandidacy, EditCandidacy, Delegate, Unbond * Gaia stake commands include, DeclareCandidacy, EditCandidacy, Delegate, Unbond
* MountStoreWithDB without providing a custom store works. * MountStoreWithDB without providing a custom store works.
* Repo is now lint compliant / GoMetaLinter with tendermint-lint integrated into CI * Repo is now lint compliant / GoMetaLinter with tendermint-lint integrated into CI
@ -30,12 +72,9 @@ FEATURES:
* 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 (non-proof) subspace query helper functions
* Add more staking query functions: candidates, delegator-bonds * Add more staking query functions: candidates, delegator-bonds
* Bank module now tags transactions with sender/recipient for indexing & later retrieval
* Stake module now tags transactions with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy
BUG FIXES BUG FIXES
* Gaia now uses stake, ported from github.com/cosmos/gaia * Gaia now uses stake, ported from github.com/cosmos/gaia
* Auto-sequencing now works correctly
## 0.15.1 (April 29, 2018) ## 0.15.1 (April 29, 2018)

35
Gopkg.lock generated
View File

@ -87,14 +87,14 @@
[[projects]] [[projects]]
name = "github.com/gorilla/context" name = "github.com/gorilla/context"
packages = ["."] packages = ["."]
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1" version = "v1.1.1"
[[projects]] [[projects]]
name = "github.com/gorilla/mux" name = "github.com/gorilla/mux"
packages = ["."] packages = ["."]
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996" revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.1" version = "v1.6.2"
[[projects]] [[projects]]
name = "github.com/gorilla/websocket" name = "github.com/gorilla/websocket"
@ -159,7 +159,7 @@
branch = "master" branch = "master"
name = "github.com/mitchellh/mapstructure" name = "github.com/mitchellh/mapstructure"
packages = ["."] packages = ["."]
revision = "00c29f56e2386353d58c599509e8dc3801b0d716" revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b"
[[projects]] [[projects]]
name = "github.com/pelletier/go-toml" name = "github.com/pelletier/go-toml"
@ -183,7 +183,7 @@
branch = "master" branch = "master"
name = "github.com/rcrowley/go-metrics" name = "github.com/rcrowley/go-metrics"
packages = ["."] packages = ["."]
revision = "d932a24a8ccb8fcadc993e5c6c58f93dac168294" revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
[[projects]] [[projects]]
name = "github.com/spf13/afero" name = "github.com/spf13/afero"
@ -250,7 +250,7 @@
"leveldb/table", "leveldb/table",
"leveldb/util" "leveldb/util"
] ]
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" revision = "9637fa0b2f0db13c99d899b91007edb7df4610b7"
[[projects]] [[projects]]
name = "github.com/tendermint/abci" name = "github.com/tendermint/abci"
@ -323,7 +323,6 @@
"p2p", "p2p",
"p2p/conn", "p2p/conn",
"p2p/pex", "p2p/pex",
"p2p/trust",
"p2p/upnp", "p2p/upnp",
"proxy", "proxy",
"rpc/client", "rpc/client",
@ -342,8 +341,8 @@
"types/priv_validator", "types/priv_validator",
"version" "version"
] ]
revision = "26f633ed48441f72895b710f0e87b7b6c6791066" revision = "19ccd1842fe5efffcc2ff32e6cfc127ca0cd1f9b"
version = "v0.19.1" version = "v0.19.4-rc0"
[[projects]] [[projects]]
name = "github.com/tendermint/tmlibs" name = "github.com/tendermint/tmlibs"
@ -360,8 +359,8 @@
"pubsub", "pubsub",
"pubsub/query" "pubsub/query"
] ]
revision = "d94e312673e16a11ea55d742cefb3e331228f898" revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd"
version = "v0.8.2" version = "v0.8.3-rc0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -377,7 +376,7 @@
"ripemd160", "ripemd160",
"salsa20/salsa" "salsa20/salsa"
] ]
revision = "b49d69b5da943f7ef3c9cf91c8777c1f78a0cc3c" revision = "2fc4c88bf43f0ea5ea305eae2b7af24b2cc93287"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -389,16 +388,15 @@
"http2/hpack", "http2/hpack",
"idna", "idna",
"internal/timeseries", "internal/timeseries",
"lex/httplex",
"trace" "trace"
] ]
revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23" revision = "2491c5de3490fced2f6cff376127c667efeed857"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = ["unix"] packages = ["unix"]
revision = "cbbc999da32df943dac6cd71eb3ee39e1d7838b9" revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b"
[[projects]] [[projects]]
name = "golang.org/x/text" name = "golang.org/x/text"
@ -422,10 +420,9 @@
version = "v0.3.0" version = "v0.3.0"
[[projects]] [[projects]]
branch = "master"
name = "google.golang.org/genproto" name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"] packages = ["googleapis/rpc/status"]
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2" revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
[[projects]] [[projects]]
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
@ -460,6 +457,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "fad966346d3b6042faf2bf793168b6ce9a8ff59ae207b7ad57008ead0f3ff7d4" inputs-digest = "8c37fee3e6d3034b865c68185f4796223fe53b1b0ee4a305adbee5f53c50bfce"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -70,11 +70,16 @@
[[constraint]] [[constraint]]
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
version = "0.19.1" version = "0.19.4-rc0"
[[override]] [[override]]
name = "github.com/tendermint/tmlibs" name = "github.com/tendermint/tmlibs"
version = "~0.8.2-rc1" version = "~0.8.3-rc0"
# this got updated and broke, so locked to an old working commit ...
[[override]]
name = "google.golang.org/genproto"
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
[prune] [prune]
go-tests = true go-tests = true

View File

@ -16,7 +16,7 @@ master | [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master.s
**WARNING**: the libraries are still undergoing breaking changes as we get better ideas and start building out the Apps. **WARNING**: the libraries are still undergoing breaking changes as we get better ideas and start building out the Apps.
**Note**: Requires [Go 1.9+](https://golang.org/dl/) **Note**: Requires [Go 1.10+](https://golang.org/dl/)
## Overview ## Overview

View File

@ -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
@ -40,6 +54,8 @@ type BaseApp struct {
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,16 +300,75 @@ 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) {
path := strings.Split(req.Path, "/")
// first element is empty string
if len(path) > 0 && path[0] == "" {
path = path[1:]
}
// "/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) queryable, ok := app.cms.(sdk.Queryable)
if !ok { if !ok {
msg := "application doesn't support queries" msg := "multistore doesn't support queries"
return sdk.ErrUnknownRequest(msg).QueryResult() return sdk.ErrUnknownRequest(msg).QueryResult()
} }
req.Path = "/" + strings.Join(path[1:], "/")
return queryable.Query(req) 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
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
@ -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.
@ -360,21 +443,34 @@ 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 {
switch r.(type) {
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())) log := fmt.Sprintf("Recovered: %v\nstack:\n%v", r, string(debug.Stack()))
result = sdk.ErrInternal(log).Result() result = sdk.ErrInternal(log).Result()
} }
}
}() }()
// Get the Msg. // Get the Msg.
@ -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()
} }

View File

@ -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

View File

@ -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()

View File

@ -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{

View File

@ -66,8 +66,9 @@ func GaiaAppInit() server.AppInit {
fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError) fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError)
fsAppGenTx.String(flagName, "", "validator moniker, if left blank, do not add validator") fsAppGenTx.String(flagName, "", "validator moniker, if left blank, do not add validator")
fsAppGenTx.String(flagClientHome, DefaultCLIHome, "home directory for the client, used for key generation") fsAppGenTx.String(flagClientHome, DefaultCLIHome,
fsAppGenTx.Bool(flagOWK, false, "overwrite the for the accounts created") "home directory for the client, used for key generation")
fsAppGenTx.Bool(flagOWK, false, "overwrite the accounts created")
return server.AppInit{ return server.AppInit{
FlagsAppGenState: fsAppGenState, FlagsAppGenState: fsAppGenState,

View File

@ -2,8 +2,8 @@ openapi: 3.0.0
servers: servers:
- url: 'http://localhost:8998' - url: 'http://localhost:8998'
info: info:
version: "1.0.0-oas3" version: "1.1.0"
title: Light client daemon to interface with Cosmos baseserver via REST title: Light client daemon to interface with full Gaia node via REST
description: Specification for the LCD provided by `gaia rest-server` description: Specification for the LCD provided by `gaia rest-server`
paths: paths:
@ -13,7 +13,7 @@ paths:
description: Get the version of the LCD running locally to compare against expected description: Get the version of the LCD running locally to compare against expected
responses: responses:
200: 200:
description: Plaintext version i.e. "v0.5.0" description: Plaintext version i.e. "0.16.0-dev-26440095"
/node_info: /node_info:
description: Only the node info. Block information can be queried via /block/latest description: Only the node info. Block information can be queried via /block/latest
get: get:
@ -26,25 +26,31 @@ paths:
schema: schema:
type: object type: object
properties: properties:
id:
description: ???
type: string
listen_addr:
type: string
example: 192.168.56.1:46656
network:
type: string
example: gaia-5000
version:
description: Tendermint version
type: string
example: 0.19.1
channels:
description: ???
type: string
pub_key: pub_key:
$ref: '#/components/schemas/PubKey' $ref: '#/components/schemas/PubKey'
moniker: moniker:
type: string type: string
example: 159.89.198.221 example: 159.89.198.221
network:
type: string
example: gaia-2
remote_addr: remote_addr:
type: string type: string
listen_addr:
type: string
example: 192.168.56.1:46656
version:
description: Tendermint version
type: string
example: 0.15.0
other: other:
description: more information on versions description: more information on versions and options for the node
type: array type: array
/syncing: /syncing:
get: get:
@ -204,6 +210,10 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type:
description: "???"
type: string
value:
$ref: "#/components/schemas/Balance" $ref: "#/components/schemas/Balance"
204: 204:
description: There is no data for the requested account. This is not a 404 as the account might exist, just does not hold data. description: There is no data for the requested account. This is not a 404 as the account might exist, just does not hold data.
@ -226,6 +236,7 @@ paths:
type: object type: object
properties: properties:
name: name:
description: Name of locally stored key
type: string type: string
password: password:
type: string type: string
@ -234,6 +245,9 @@ paths:
items: items:
$ref: "#/components/schemas/Coins" $ref: "#/components/schemas/Coins"
chain_id: chain_id:
description: Target chain
type: string
src_chain_id:
type: string type: string
squence: squence:
type: number type: number
@ -242,19 +256,6 @@ paths:
description: Tx was send and will probably be added to the next block description: Tx was send and will probably be added to the next block
400: 400:
description: The Tx was malformated description: The Tx was malformated
/accounts/{address}/nonce:
parameters:
- in: path
name: address
description: Account address
required: true
schema:
$ref: "#/components/schemas/Address"
get:
summary: Get the nonce for a certain account
responses:
200:
description: Plaintext nonce i.e. "4" defaults to "0"
/blocks/latest: /blocks/latest:
get: get:
summary: Get the latest block summary: Get the latest block
@ -667,15 +668,16 @@ components:
Balance: Balance:
type: object type: object
properties: properties:
height: address:
type: number type: string
example: 123456
coins: coins:
type: array type: array
items: items:
$ref: "#/components/schemas/Coins" $ref: "#/components/schemas/Coins"
credit: public_key:
type: array $ref: "#/components/schemas/PubKey"
sequence:
type: number
BlockID: BlockID:
type: object type: object
properties: properties:

View File

@ -70,6 +70,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// register message routes // register message routes
app.Router(). app.Router().
AddRoute("auth", auth.NewHandler(app.accountMapper.(auth.AccountMapper))).
AddRoute("bank", bank.NewHandler(app.coinKeeper)). AddRoute("bank", bank.NewHandler(app.coinKeeper)).
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)). AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)).
AddRoute("stake", stake.NewHandler(app.stakeKeeper)) AddRoute("stake", stake.NewHandler(app.stakeKeeper))

View File

@ -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{
@ -208,6 +208,61 @@ func TestGenesis(t *testing.T) {
assert.Equal(t, acc, res1) assert.Equal(t, acc, res1)
} }
func TestMsgChangePubKey(t *testing.T) {
bapp := newBasecoinApp()
// Construct some genesis bytes to reflect basecoin/types/AppAccount
// Give 77 foocoin to the first key
coins, err := sdk.ParseCoins("77foocoin")
require.Nil(t, err)
baseAcc := auth.BaseAccount{
Address: addr1,
Coins: coins,
}
// Construct genesis state
err = setGenesisAccounts(bapp, baseAcc)
assert.Nil(t, err)
// A checkTx context (true)
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
assert.Equal(t, baseAcc, res1.(*types.AppAccount).BaseAccount)
// Run a CheckDeliver
SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, true, priv1)
// Check balances
CheckBalance(t, bapp, addr1, "67foocoin")
CheckBalance(t, bapp, addr2, "10foocoin")
changePubKeyMsg := auth.MsgChangeKey{
Address: addr1,
NewPubKey: priv2.PubKey(),
}
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
acc := bapp.accountMapper.GetAccount(ctxDeliver, addr1)
// send a MsgChangePubKey
SignCheckDeliver(t, bapp, changePubKeyMsg, []int64{1}, true, priv1)
acc = bapp.accountMapper.GetAccount(ctxDeliver, addr1)
assert.True(t, priv2.PubKey().Equals(acc.GetPubKey()))
// signing a SendMsg with the old privKey should be an auth error
tx := genTx(sendMsg1, []int64{2}, priv1)
res := bapp.Deliver(tx)
assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log)
// resigning the tx with the new correct priv key should work
SignCheckDeliver(t, bapp, sendMsg1, []int64{2}, true, priv2)
// Check balances
CheckBalance(t, bapp, addr1, "57foocoin")
CheckBalance(t, bapp, addr2, "20foocoin")
}
func TestMsgSendWithAccounts(t *testing.T) { func TestMsgSendWithAccounts(t *testing.T) {
bapp := newBasecoinApp() bapp := newBasecoinApp()

View File

@ -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{

View File

@ -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)

View File

@ -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")
} }

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"sort"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -210,16 +211,18 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) ( func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) (
validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) { validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) {
// XXX sort the files by contents just incase people renamed their files
var fos []os.FileInfo var fos []os.FileInfo
fos, err = ioutil.ReadDir(genTxsDir) fos, err = ioutil.ReadDir(genTxsDir)
if err != nil { if err != nil {
return return
} }
genTxs := make(map[string]GenesisTx)
var nodeIDs []string
for _, fo := range fos { for _, fo := range fos {
filename := path.Join(genTxsDir, fo.Name()) filename := path.Join(genTxsDir, fo.Name())
if !fo.IsDir() && (path.Ext(filename) != ".json") { if !fo.IsDir() && (path.Ext(filename) != ".json") {
return continue
} }
// get the genTx // get the genTx
@ -234,6 +237,15 @@ func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) (
return return
} }
genTxs[genTx.NodeID] = genTx
nodeIDs = append(nodeIDs, genTx.NodeID)
}
sort.Strings(nodeIDs)
for _, nodeID := range nodeIDs {
genTx := genTxs[nodeID]
// combine some stuff // combine some stuff
validators = append(validators, genTx.Validator) validators = append(validators, genTx.Validator)
appGenTxs = append(appGenTxs, genTx.AppGenTx) appGenTxs = append(appGenTxs, genTx.AppGenTx)

View File

@ -79,7 +79,6 @@ func AddCommands(
ShowNodeIDCmd(ctx), ShowNodeIDCmd(ctx),
ShowValidatorCmd(ctx), ShowValidatorCmd(ctx),
ExportCmd(ctx, cdc, appExport), ExportCmd(ctx, cdc, appExport),
UnsafeResetAllCmd(ctx),
version.VersionCmd, version.VersionCmd,
) )
} }

View File

@ -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))
}

148
store/gaskvstore.go Normal file
View File

@ -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()
}

68
store/gaskvstore_test.go Normal file
View File

@ -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")
}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

58
types/gas.go Normal file
View File

@ -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
}

View File

@ -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()....

View File

@ -77,8 +77,8 @@ func FeePayer(tx Tx) Address {
// gas to be used by the transaction. The ratio yields an effective "gasprice", // gas to be used by the transaction. The ratio yields an effective "gasprice",
// which must be above some miminum to be accepted into the mempool. // which must be above some miminum to be accepted into the mempool.
type StdFee struct { type StdFee struct {
Amount Coins `json"amount"` Amount Coins `json:"amount"`
Gas int64 `json"gas"` Gas int64 `json:"gas"`
} }
func NewStdFee(gas int64, amount ...Coin) StdFee { func NewStdFee(gas int64, amount ...Coin) StdFee {

View File

@ -6,10 +6,10 @@ package version
// TODO improve // TODO improve
const Maj = "0" const Maj = "0"
const Min = "16" const Min = "18"
const Fix = "0" const Fix = "0"
const Version = "0.16.0-dev" const Version = "0.18.0-dev"
// GitCommit set by build flags // GitCommit set by build flags
var GitCommit = "" var GitCommit = ""

View File

@ -8,10 +8,14 @@ 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.
func NewAnteHandler(accountMapper sdk.AccountMapper, feeHandler sdk.FeeHandler) sdk.AnteHandler { func NewAnteHandler(am sdk.AccountMapper, feeHandler sdk.FeeHandler) sdk.AnteHandler {
return func( return func(
ctx sdk.Context, tx sdk.Tx, ctx sdk.Context, tx sdk.Tx,
) (_ sdk.Context, _ sdk.Result, abort bool) { ) (_ sdk.Context, _ sdk.Result, abort bool) {
@ -24,7 +28,6 @@ func NewAnteHandler(accountMapper sdk.AccountMapper, feeHandler sdk.FeeHandler)
true true
} }
// TODO: can tx just implement message?
msg := tx.GetMsg() msg := tx.GetMsg()
// TODO: will this always be a stdtx? should that be used in the function signature? // TODO: will this always be a stdtx? should that be used in the function signature?
@ -62,7 +65,7 @@ func NewAnteHandler(accountMapper sdk.AccountMapper, feeHandler sdk.FeeHandler)
// check signature, return account with incremented nonce // check signature, return account with incremented nonce
signerAcc, res := processSig( signerAcc, res := processSig(
ctx, accountMapper, ctx, am,
signerAddr, sig, signBytes, signerAddr, sig, signBytes,
) )
if !res.IsOK() { if !res.IsOK() {
@ -82,13 +85,16 @@ func NewAnteHandler(accountMapper sdk.AccountMapper, feeHandler sdk.FeeHandler)
} }
// Save the account. // Save the account.
accountMapper.SetAccount(ctx, signerAcc) am.SetAccount(ctx, signerAcc)
signerAccs[i] = signerAcc signerAccs[i] = signerAcc
} }
// 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...
@ -135,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()
} }

View File

@ -51,9 +51,6 @@ func (acc BaseAccount) GetPubKey() crypto.PubKey {
// Implements sdk.Account. // Implements sdk.Account.
func (acc *BaseAccount) SetPubKey(pubKey crypto.PubKey) error { func (acc *BaseAccount) SetPubKey(pubKey crypto.PubKey) error {
if acc.PubKey != nil {
return errors.New("cannot override BaseAccount pubkey")
}
acc.PubKey = pubKey acc.PubKey = pubKey
return nil return nil
} }

View File

@ -37,10 +37,10 @@ func TestBaseAccountAddressPubKey(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, pub1, acc.GetPubKey()) assert.Equal(t, pub1, acc.GetPubKey())
// can't override pubkey // can override pubkey
err = acc.SetPubKey(pub2) err = acc.SetPubKey(pub2)
assert.NotNil(t, err) assert.Nil(t, err)
assert.Equal(t, pub1, acc.GetPubKey()) assert.Equal(t, pub2, acc.GetPubKey())
//------------------------------------ //------------------------------------

34
x/auth/handler.go Normal file
View File

@ -0,0 +1,34 @@
package auth
import (
"reflect"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// NewHandler returns a handler for "auth" type messages.
func NewHandler(am AccountMapper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgChangeKey:
return handleMsgChangeKey(ctx, am, msg)
default:
errMsg := "Unrecognized auth Msg type: " + reflect.TypeOf(msg).Name()
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// Handle MsgChangeKey
// Should be very expensive, because once this happens, an account is un-prunable
func handleMsgChangeKey(ctx sdk.Context, am AccountMapper, msg MsgChangeKey) sdk.Result {
err := am.setPubKey(ctx, msg.Address, msg.NewPubKey)
if err != nil {
return err.Result()
}
return sdk.Result{
Tags: sdk.NewTags("action", []byte("changePubkey"), "address", msg.Address.Bytes(), "pubkey", msg.NewPubKey.Bytes()),
}
}

View File

@ -6,14 +6,15 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire" wire "github.com/cosmos/cosmos-sdk/wire"
crypto "github.com/tendermint/go-crypto"
) )
var _ sdk.AccountMapper = (*accountMapper)(nil) var _ sdk.AccountMapper = (*AccountMapper)(nil)
// Implements sdk.AccountMapper. // Implements sdk.AccountMapper.
// This AccountMapper encodes/decodes accounts using the // This AccountMapper encodes/decodes accounts using the
// go-amino (binary) encoding/decoding library. // go-amino (binary) encoding/decoding library.
type accountMapper struct { type AccountMapper struct {
// The (unexposed) key used to access the store from the Context. // The (unexposed) key used to access the store from the Context.
key sdk.StoreKey key sdk.StoreKey
@ -28,23 +29,23 @@ type accountMapper struct {
// NewAccountMapper returns a new sdk.AccountMapper that // NewAccountMapper returns a new sdk.AccountMapper that
// uses go-amino to (binary) encode and decode concrete sdk.Accounts. // uses go-amino to (binary) encode and decode concrete sdk.Accounts.
// nolint // nolint
func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto sdk.Account) accountMapper { func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto sdk.Account) AccountMapper {
return accountMapper{ return AccountMapper{
key: key, key: key,
proto: proto, proto: proto,
cdc: cdc, cdc: cdc,
} }
} }
// Implements sdk.AccountMapper. // Implaements sdk.AccountMapper.
func (am accountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.Address) sdk.Account { func (am AccountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.Address) sdk.Account {
acc := am.clonePrototype() acc := am.clonePrototype()
acc.SetAddress(addr) acc.SetAddress(addr)
return acc return acc
} }
// Implements sdk.AccountMapper. // Implements sdk.AccountMapper.
func (am accountMapper) GetAccount(ctx sdk.Context, addr sdk.Address) sdk.Account { func (am AccountMapper) GetAccount(ctx sdk.Context, addr sdk.Address) sdk.Account {
store := ctx.KVStore(am.key) store := ctx.KVStore(am.key)
bz := store.Get(addr) bz := store.Get(addr)
if bz == nil { if bz == nil {
@ -55,7 +56,7 @@ func (am accountMapper) GetAccount(ctx sdk.Context, addr sdk.Address) sdk.Accoun
} }
// Implements sdk.AccountMapper. // Implements sdk.AccountMapper.
func (am accountMapper) SetAccount(ctx sdk.Context, acc sdk.Account) { func (am AccountMapper) SetAccount(ctx sdk.Context, acc sdk.Account) {
addr := acc.GetAddress() addr := acc.GetAddress()
store := ctx.KVStore(am.key) store := ctx.KVStore(am.key)
bz := am.encodeAccount(acc) bz := am.encodeAccount(acc)
@ -63,7 +64,7 @@ func (am accountMapper) SetAccount(ctx sdk.Context, acc sdk.Account) {
} }
// Implements sdk.AccountMapper. // Implements sdk.AccountMapper.
func (am accountMapper) IterateAccounts(ctx sdk.Context, process func(sdk.Account) (stop bool)) { func (am AccountMapper) IterateAccounts(ctx sdk.Context, process func(sdk.Account) (stop bool)) {
store := ctx.KVStore(am.key) store := ctx.KVStore(am.key)
iter := store.Iterator(nil, nil) iter := store.Iterator(nil, nil)
for { for {
@ -79,11 +80,49 @@ func (am accountMapper) IterateAccounts(ctx sdk.Context, process func(sdk.Accoun
} }
} }
// Returns the PubKey of the account at address
func (am AccountMapper) GetPubKey(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, sdk.Error) {
acc := am.GetAccount(ctx, addr)
if acc == nil {
return nil, sdk.ErrUnknownAddress(addr.String())
}
return acc.GetPubKey(), nil
}
func (am AccountMapper) setPubKey(ctx sdk.Context, addr sdk.Address, newPubKey crypto.PubKey) sdk.Error {
acc := am.GetAccount(ctx, addr)
if acc == nil {
return sdk.ErrUnknownAddress(addr.String())
}
acc.SetPubKey(newPubKey)
am.SetAccount(ctx, acc)
return nil
}
// Returns the Sequence of the account at address
func (am AccountMapper) GetSequence(ctx sdk.Context, addr sdk.Address) (int64, sdk.Error) {
acc := am.GetAccount(ctx, addr)
if acc == nil {
return 0, sdk.ErrUnknownAddress(addr.String())
}
return acc.GetSequence(), nil
}
func (am AccountMapper) setSequence(ctx sdk.Context, addr sdk.Address, newSequence int64) sdk.Error {
acc := am.GetAccount(ctx, addr)
if acc == nil {
return sdk.ErrUnknownAddress(addr.String())
}
acc.SetSequence(newSequence)
am.SetAccount(ctx, acc)
return nil
}
//---------------------------------------- //----------------------------------------
// misc. // misc.
// Creates a new struct (or pointer to struct) from am.proto. // Creates a new struct (or pointer to struct) from am.proto.
func (am accountMapper) clonePrototype() sdk.Account { func (am AccountMapper) clonePrototype() sdk.Account {
protoRt := reflect.TypeOf(am.proto) protoRt := reflect.TypeOf(am.proto)
if protoRt.Kind() == reflect.Ptr { if protoRt.Kind() == reflect.Ptr {
protoCrt := protoRt.Elem() protoCrt := protoRt.Elem()
@ -106,7 +145,7 @@ func (am accountMapper) clonePrototype() sdk.Account {
return clone return clone
} }
func (am accountMapper) encodeAccount(acc sdk.Account) []byte { func (am AccountMapper) encodeAccount(acc sdk.Account) []byte {
bz, err := am.cdc.MarshalBinaryBare(acc) bz, err := am.cdc.MarshalBinaryBare(acc)
if err != nil { if err != nil {
panic(err) panic(err)
@ -114,7 +153,7 @@ func (am accountMapper) encodeAccount(acc sdk.Account) []byte {
return bz return bz
} }
func (am accountMapper) decodeAccount(bz []byte) (acc sdk.Account) { func (am AccountMapper) decodeAccount(bz []byte) (acc sdk.Account) {
err := am.cdc.UnmarshalBinaryBare(bz, &acc) err := am.cdc.UnmarshalBinaryBare(bz, &acc)
if err != nil { if err != nil {
panic(err) panic(err)

44
x/auth/msgs.go Normal file
View File

@ -0,0 +1,44 @@
package auth
import (
"encoding/json"
"github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// MsgChangeKey - high level transaction of the auth module
type MsgChangeKey struct {
Address sdk.Address `json:"address"`
NewPubKey crypto.PubKey `json:"public_key"`
}
var _ sdk.Msg = MsgChangeKey{}
// NewMsgChangeKey - msg to claim an account and set the PubKey
func NewMsgChangeKey(addr sdk.Address, pubkey crypto.PubKey) MsgChangeKey {
return MsgChangeKey{Address: addr, NewPubKey: pubkey}
}
// Implements Msg.
func (msg MsgChangeKey) Type() string { return "auth" }
// Implements Msg.
func (msg MsgChangeKey) ValidateBasic() sdk.Error {
return nil
}
// Implements Msg.
func (msg MsgChangeKey) GetSignBytes() []byte {
b, err := json.Marshal(msg) // XXX: ensure some canonical form
if err != nil {
panic(err)
}
return b
}
// Implements Msg.
func (msg MsgChangeKey) GetSigners() []sdk.Address {
return []sdk.Address{msg.Address}
}

47
x/auth/msgs_test.go Normal file
View File

@ -0,0 +1,47 @@
package auth
import (
"testing"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestNewMsgChangeKey(t *testing.T) {}
func TestMsgChangeKeyType(t *testing.T) {
addr1 := sdk.Address([]byte("input"))
newPubKey := crypto.GenPrivKeyEd25519().PubKey()
var msg = MsgChangeKey{
Address: addr1,
NewPubKey: newPubKey,
}
assert.Equal(t, msg.Type(), "auth")
}
func TestMsgChangeKeyValidation(t *testing.T) {
addr1 := sdk.Address([]byte("input"))
// emptyPubKey := crypto.PubKeyEd25519{}
// var msg = MsgChangeKey{
// Address: addr1,
// NewPubKey: emptyPubKey,
// }
// // fmt.Println(msg.NewPubKey.Empty())
// fmt.Println(msg.NewPubKey.Bytes())
// assert.NotNil(t, msg.ValidateBasic())
newPubKey := crypto.GenPrivKeyEd25519().PubKey()
msg := MsgChangeKey{
Address: addr1,
NewPubKey: newPubKey,
}
assert.Nil(t, msg.ValidateBasic())
}

View File

@ -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() {

View File

@ -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}})

View File

@ -119,7 +119,7 @@ func (msg MsgIssue) GetSigners() []sdk.Address {
//---------------------------------------- //----------------------------------------
// Input // Input
// Transaction Output // Transaction Input
type Input struct { type Input struct {
Address sdk.Address `json:"address"` Address sdk.Address `json:"address"`
Coins sdk.Coins `json:"coins"` Coins sdk.Coins `json:"coins"`