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

This commit is contained in:
elvin 2018-05-21 15:57:23 +08:00
commit c1e15d0cbf
32 changed files with 884 additions and 96 deletions

View File

@ -1,5 +1,18 @@
# Changelog
## 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.2
*May 20, 2018*
@ -52,6 +65,7 @@ BREAKING CHANGES
* gaiad init now requires use of `--name` flag
* Removed Get from Msg interface
* types/rational now extends big.Rat
* Queries against the store must be prefixed with the path "/store"
FEATURES:
@ -64,7 +78,8 @@ FEATURES:
* New genesis account keys are automatically added to the client keybase (introduce `--client-home` flag)
* Initialize with genesis txs using `--gen-txs` flag
* 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
* Gaia now uses stake, ported from github.com/cosmos/gaia

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.
**Note**: Requires [Go 1.9+](https://golang.org/dl/)
**Note**: Requires [Go 1.10+](https://golang.org/dl/)
## Overview

View File

@ -3,6 +3,7 @@ package baseapp
import (
"fmt"
"runtime/debug"
"strings"
"github.com/pkg/errors"
@ -22,11 +23,24 @@ import (
// and to avoid affecting the Merkle root.
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
type BaseApp struct {
// initialized on creation
Logger log.Logger
name string // application name from abci.Info
cdc *wire.Codec // Amino codec
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
router Router // handle any kind of message
@ -37,9 +51,11 @@ type BaseApp struct {
anteHandler sdk.AnteHandler // ante handler for fee and auth
// may be nil
initChainer sdk.InitChainer // initialize state with validators and state blob
beginBlocker sdk.BeginBlocker // logic to run before any txs
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
initChainer sdk.InitChainer // initialize state with validators and state blob
beginBlocker sdk.BeginBlocker // logic to run before any txs
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
addrPeerFilter sdk.PeerFilter // filter peers by address and port
pubkeyPeerFilter sdk.PeerFilter // filter peers by public key
//--------------------
// Volatile
@ -61,6 +77,7 @@ func NewBaseApp(name string, cdc *wire.Codec, logger log.Logger, db dbm.DB) *Bas
app := &BaseApp{
Logger: logger,
name: name,
cdc: cdc,
db: db,
cms: store.NewCommitMultiStore(db),
router: NewRouter(),
@ -137,6 +154,12 @@ func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) {
func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
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 }
// load latest application version
@ -277,15 +300,74 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
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.
// Delegates to CommitMultiStore if it implements Queryable
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
queryable, ok := app.cms.(sdk.Queryable)
if !ok {
msg := "application doesn't support queries"
return sdk.ErrUnknownRequest(msg).QueryResult()
path := strings.Split(req.Path, "/")
// first element is empty string
if len(path) > 0 && path[0] == "" {
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
@ -312,7 +394,7 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
if err != nil {
result = err.Result()
} else {
result = app.runTx(true, txBytes, tx)
result = app.runTx(runTxModeCheck, txBytes, tx)
}
return abci.ResponseCheckTx{
@ -320,6 +402,7 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
Data: result.Data,
Log: result.Log,
GasWanted: result.GasWanted,
GasUsed: result.GasUsed,
Fee: cmn.KI64Pair{
[]byte(result.FeeDenom),
result.FeeAmount,
@ -336,7 +419,7 @@ func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
if err != nil {
result = err.Result()
} else {
result = app.runTx(false, txBytes, tx)
result = app.runTx(runTxModeDeliver, txBytes, tx)
}
// 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) {
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) {
return app.runTx(false, nil, tx)
return app.runTx(runTxModeDeliver, nil, tx)
}
// txBytes may be nil in some cases, eg. in tests.
// 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.
defer func() {
if r := recover(); r != nil {
log := fmt.Sprintf("Recovered: %v\nstack:\n%v", r, string(debug.Stack()))
result = sdk.ErrInternal(log).Result()
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()))
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
var ctx sdk.Context
if isCheckTx {
if mode == runTxModeCheck || mode == runTxModeSimulate {
ctx = app.checkState.ctx.WithTxBytes(txBytes)
} else {
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
}
// Simulate a DeliverTx for gas calculation
if mode == runTxModeSimulate {
ctx = ctx.WithIsCheckTx(false)
}
// Run the ante handler.
if app.anteHandler != nil {
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
var msCache sdk.CacheMultiStore
if isCheckTx == true {
if mode == runTxModeCheck || mode == runTxModeSimulate {
// CacheWrap app.checkState.ms in case it fails.
msCache = app.checkState.CacheMultiStore()
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.
msCache = app.deliverState.CacheMultiStore()
ctx = ctx.WithMultiStore(msCache)
}
result = handler(ctx, msg)
// If result was successful, write to app.checkState.ms or app.deliverState.ms
if result.IsOK() {
// Set gas utilized
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()
}

View File

@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-crypto"
@ -16,6 +17,7 @@ import (
"github.com/tendermint/tmlibs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
func defaultLogger() log.Logger {
@ -25,7 +27,9 @@ func defaultLogger() log.Logger {
func newBaseApp(name string) *BaseApp {
logger := defaultLogger()
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) {
@ -167,7 +171,7 @@ func TestInitChainer(t *testing.T) {
}
query := abci.RequestQuery{
Path: "/main/key",
Path: "/store/main/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.
func TestQuery(t *testing.T) {
app := newBaseApp(t.Name())
@ -280,7 +375,7 @@ func TestQuery(t *testing.T) {
})
query := abci.RequestQuery{
Path: "/main/key",
Path: "/store/main/key",
Data: key,
}
@ -307,6 +402,39 @@ func TestQuery(t *testing.T) {
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

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
func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) {
path := fmt.Sprintf("/%s/%s", storeName, endPath)
path := fmt.Sprintf("/store/%s/key", storeName)
node, err := ctx.GetNode()
if err != nil {
return res, err
@ -114,6 +113,7 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w
ChainID: chainID,
Sequences: []int64{sequence},
Msg: msg,
Fee: sdk.NewStdFee(10000, sdk.Coin{}), // TODO run simulate to estimate gas?
}
keybase, err := keys.GetKeyBase()

View File

@ -40,7 +40,7 @@ var (
manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}}
fee = sdk.StdFee{
sdk.Coins{{"foocoin", 0}},
0,
100000,
}
sendMsg1 = bank.MsgSend{

View File

@ -2,8 +2,8 @@ openapi: 3.0.0
servers:
- url: 'http://localhost:8998'
info:
version: "1.0.0-oas3"
title: Light client daemon to interface with Cosmos baseserver via REST
version: "1.1.0"
title: Light client daemon to interface with full Gaia node via REST
description: Specification for the LCD provided by `gaia rest-server`
paths:
@ -13,7 +13,7 @@ paths:
description: Get the version of the LCD running locally to compare against expected
responses:
200:
description: Plaintext version i.e. "v0.5.0"
description: Plaintext version i.e. "0.16.0-dev-26440095"
/node_info:
description: Only the node info. Block information can be queried via /block/latest
get:
@ -26,25 +26,31 @@ paths:
schema:
type: object
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:
$ref: '#/components/schemas/PubKey'
moniker:
type: string
example: 159.89.198.221
network:
type: string
example: gaia-2
remote_addr:
type: string
listen_addr:
type: string
example: 192.168.56.1:46656
version:
description: Tendermint version
type: string
example: 0.15.0
other:
description: more information on versions
description: more information on versions and options for the node
type: array
/syncing:
get:
@ -204,7 +210,11 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/Balance"
type:
description: "???"
type: string
value:
$ref: "#/components/schemas/Balance"
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.
/accounts/{address}/send:
@ -226,6 +236,7 @@ paths:
type: object
properties:
name:
description: Name of locally stored key
type: string
password:
type: string
@ -234,6 +245,9 @@ paths:
items:
$ref: "#/components/schemas/Coins"
chain_id:
description: Target chain
type: string
src_chain_id:
type: string
squence:
type: number
@ -242,19 +256,6 @@ paths:
description: Tx was send and will probably be added to the next block
400:
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:
get:
summary: Get the latest block
@ -667,15 +668,16 @@ components:
Balance:
type: object
properties:
height:
type: number
example: 123456
address:
type: string
coins:
type: array
items:
$ref: "#/components/schemas/Coins"
credit:
type: array
public_key:
$ref: "#/components/schemas/PubKey"
sequence:
type: number
BlockID:
type: object
properties:

View File

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

View File

@ -39,7 +39,7 @@ var (
manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}}
fee = sdk.StdFee{
sdk.Coins{{"foocoin", 0}},
0,
100000,
}
sendMsg1 = bank.MsgSend{
@ -208,6 +208,61 @@ func TestGenesis(t *testing.T) {
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) {
bapp := newBasecoinApp()

View File

@ -33,7 +33,7 @@ var (
coins = sdk.Coins{{"foocoin", 10}}
fee = sdk.StdFee{
sdk.Coins{{"foocoin", 0}},
0,
1000000,
}
sendMsg = bank.MsgSend{

View File

@ -31,10 +31,9 @@ func TestInitApp(t *testing.T) {
app.InitChain(req)
app.Commit()
// XXX test failing
// make sure we can query these values
query := abci.RequestQuery{
Path: "/main/key",
Path: "/store/main/key",
Data: []byte("foo"),
}
qres := app.Query(query)
@ -70,7 +69,7 @@ func TestDeliverTx(t *testing.T) {
// make sure we can query these values
query := abci.RequestQuery{
Path: "/main/key",
Path: "/store/main/key",
Data: []byte(key),
}
qres := app.Query(query)

View File

@ -50,6 +50,10 @@ func (ms multiStore) GetKVStore(key sdk.StoreKey) sdk.KVStore {
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 {
panic("not implemented")
}

View File

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

View File

@ -72,3 +72,8 @@ func (cms cacheMultiStore) GetStore(key StoreKey) Store {
func (cms cacheMultiStore) GetKVStore(key StoreKey) 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)
}
// 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
// a special key, before looking up the CommitStore.
// 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
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.WithTxBytes(txBytes)
c = c.WithLogger(logger)
c = c.WithGasMeter(NewInfiniteGasMeter())
return c
}
@ -68,7 +69,7 @@ func (c Context) Value(key interface{}) interface{} {
// KVStore fetches a KVStore from the MultiStore.
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
contextKeyTxBytes
contextKeyLogger
contextKeyGasMeter
)
// NOTE: Do not expose MultiStore.
@ -155,6 +157,9 @@ func (c Context) TxBytes() []byte {
func (c Context) Logger() 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 {
return c.withValue(contextKeyMultiStore, ms)
}
@ -177,6 +182,9 @@ func (c Context) WithTxBytes(txBytes []byte) Context {
func (c Context) WithLogger(logger log.Logger) Context {
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
// written to the context when writeCache is called.

View File

@ -52,6 +52,7 @@ const (
CodeUnknownAddress CodeType = 9
CodeInsufficientCoins CodeType = 10
CodeInvalidCoins CodeType = 11
CodeOutOfGas CodeType = 12
// CodespaceRoot is a codespace for error codes in this file only.
// Notice that 0 is an "unset" codespace, which can be overridden with
@ -88,6 +89,8 @@ func CodeToDefaultMsg(code CodeType) string {
return "Insufficient coins"
case CodeInvalidCoins:
return "Invalid coins"
case CodeOutOfGas:
return "Out of gas"
default:
return fmt.Sprintf("Unknown code %d", code)
}
@ -131,6 +134,9 @@ func ErrInsufficientCoins(msg string) Error {
func ErrInvalidCoins(msg string) Error {
return newErrorWithRootCodespace(CodeInvalidCoins, msg)
}
func ErrOutOfGas(msg string) Error {
return newErrorWithRootCodespace(CodeOutOfGas, msg)
}
//----------------------------------------
// 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.
GetStore(StoreKey) Store
GetKVStore(StoreKey) KVStore
GetKVStoreWithGas(GasMeter, StoreKey) KVStore
}
// From MultiStore.CacheMultiStore()....

View File

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

View File

@ -8,10 +8,14 @@ import (
"github.com/spf13/viper"
)
const (
verifyCost = 100
)
// NewAnteHandler returns an AnteHandler that checks
// and increments sequence numbers, checks signatures,
// 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(
ctx sdk.Context, tx sdk.Tx,
) (_ sdk.Context, _ sdk.Result, abort bool) {
@ -24,7 +28,6 @@ func NewAnteHandler(accountMapper sdk.AccountMapper, feeHandler sdk.FeeHandler)
true
}
// TODO: can tx just implement message?
msg := tx.GetMsg()
// 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
signerAcc, res := processSig(
ctx, accountMapper,
ctx, am,
signerAddr, sig, signBytes,
)
if !res.IsOK() {
@ -82,13 +85,16 @@ func NewAnteHandler(accountMapper sdk.AccountMapper, feeHandler sdk.FeeHandler)
}
// Save the account.
accountMapper.SetAccount(ctx, signerAcc)
am.SetAccount(ctx, signerAcc)
signerAccs[i] = signerAcc
}
// cache the signer accounts in the context
ctx = WithSigners(ctx, signerAccs)
// set the gas meter
ctx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas))
// TODO: tx tags (?)
return ctx, sdk.Result{}, false // continue...
@ -135,6 +141,7 @@ func processSig(
}
// Check sig.
ctx.GasMeter().ConsumeGas(verifyCost, "ante verify")
if !pubKey.VerifyBytes(signBytes, sig.Signature) {
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
}

View File

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

View File

@ -37,10 +37,10 @@ func TestBaseAccountAddressPubKey(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, pub1, acc.GetPubKey())
// can't override pubkey
// can override pubkey
err = acc.SetPubKey(pub2)
assert.NotNil(t, err)
assert.Equal(t, pub1, acc.GetPubKey())
assert.Nil(t, err)
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"
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.
// This AccountMapper encodes/decodes accounts using the
// go-amino (binary) encoding/decoding library.
type accountMapper struct {
type AccountMapper struct {
// The (unexposed) key used to access the store from the Context.
key sdk.StoreKey
@ -28,23 +29,23 @@ type accountMapper struct {
// NewAccountMapper returns a new sdk.AccountMapper that
// uses go-amino to (binary) encode and decode concrete sdk.Accounts.
// nolint
func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto sdk.Account) accountMapper {
return accountMapper{
func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto sdk.Account) AccountMapper {
return AccountMapper{
key: key,
proto: proto,
cdc: cdc,
}
}
// Implements sdk.AccountMapper.
func (am accountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.Address) sdk.Account {
// Implaements sdk.AccountMapper.
func (am AccountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.Address) sdk.Account {
acc := am.clonePrototype()
acc.SetAddress(addr)
return acc
}
// 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)
bz := store.Get(addr)
if bz == nil {
@ -55,7 +56,7 @@ func (am accountMapper) GetAccount(ctx sdk.Context, addr sdk.Address) sdk.Accoun
}
// 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()
store := ctx.KVStore(am.key)
bz := am.encodeAccount(acc)
@ -63,7 +64,7 @@ func (am accountMapper) SetAccount(ctx sdk.Context, acc sdk.Account) {
}
// 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)
iter := store.Iterator(nil, nil)
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.
// 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)
if protoRt.Kind() == reflect.Ptr {
protoCrt := protoRt.Elem()
@ -106,7 +145,7 @@ func (am accountMapper) clonePrototype() sdk.Account {
return clone
}
func (am accountMapper) encodeAccount(acc sdk.Account) []byte {
func (am AccountMapper) encodeAccount(acc sdk.Account) []byte {
bz, err := am.cdc.MarshalBinaryBare(acc)
if err != nil {
panic(err)
@ -114,7 +153,7 @@ func (am accountMapper) encodeAccount(acc sdk.Account) []byte {
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)
if err != nil {
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"
)
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
type Keeper struct {
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 {
ctx.GasMeter().ConsumeGas(costGetCoins, "getCoins")
acc := am.GetAccount(ctx, addr)
if acc == nil {
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 {
ctx.GasMeter().ConsumeGas(costSetCoins, "setCoins")
acc := am.GetAccount(ctx, addr)
if acc == nil {
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.
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)
}
// 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) {
ctx.GasMeter().ConsumeGas(costSubtractCoins, "subtractCoins")
oldCoins := getCoins(ctx, am, addr)
newCoins := oldCoins.Minus(amt)
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.
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)
newCoins := oldCoins.Plus(amt)
if !newCoins.IsNotNegative() {

View File

@ -65,8 +65,7 @@ func TestKeeper(t *testing.T) {
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
_, _, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}})
assert.Implements(t, (*sdk.Error)(nil), err)
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}})
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 10}})