Merge remote-tracking branch 'origin/develop' into rigel/fee-distribution

This commit is contained in:
rigelrozanski 2018-09-19 13:25:23 -04:00
commit 97f7dbc5e6
30 changed files with 528 additions and 88 deletions

View File

@ -154,6 +154,7 @@ jobs:
command: |
export PATH="$GOBIN:$PATH"
make install
export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')"
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do
id=$(basename "$pkg")
GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"

View File

@ -1,8 +1,8 @@
PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation')
PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation')
COMMIT_HASH := $(shell git rev-parse --short HEAD)
VERSION := $(shell git describe --tags --long | sed 's/v\(.*\)/\1/')
BUILD_TAGS = netgo ledger
BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}"
BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.Version=${VERSION}"
GCC := $(shell command -v gcc 2> /dev/null)
LEDGER_ENABLED ?= true
UNAME_S := $(shell uname -s)
@ -142,10 +142,10 @@ test_examples:
@go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/examples/democoin/cli_test` -tags=cli_test
test_unit:
@go test $(PACKAGES_NOSIMULATION)
@VERSION=$(VERSION) go test $(PACKAGES_NOSIMULATION)
test_race:
@go test -race $(PACKAGES_NOSIMULATION)
@VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION)
test_sim_modules:
@echo "Running individual module simulations..."
@ -175,7 +175,7 @@ test_sim_gaia_profile:
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
test_cover:
@bash tests/test_cover.sh
@export VERSION=$(VERSION); bash tests/test_cover.sh
test_lint:
gometalinter.v2 --config=tools/gometalinter.json ./...

View File

@ -44,6 +44,7 @@ BREAKING CHANGES
* [simulation] Remove log and testing.TB from Operation and Invariants, in favor of using errors \#2282
* [tools] Removed gocyclo [#2211](https://github.com/cosmos/cosmos-sdk/issues/2211)
* [baseapp] Remove `SetTxDecoder` in favor of requiring the decoder be set in baseapp initialization. [#1441](https://github.com/cosmos/cosmos-sdk/issues/1441)
* [baseapp] [\#1921](https://github.com/cosmos/cosmos-sdk/issues/1921) Add minimumFees field to BaseApp.
* [store] Change storeInfo within the root multistore to use tmhash instead of ripemd160 \#2308
* [codec] \#2324 All referrences to wire have been renamed to codec. Additionally, wire.NewCodec is now codec.New().
* [types] \#2343 Make sdk.Msg have a names field, to facilitate automatic tagging.
@ -75,11 +76,15 @@ FEATURES
* Gaia
* [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address`
* [cli] [\#1921] (https://github.com/cosmos/cosmos-sdk/issues/1921)
* New configuration file `gaiad.toml` is now created to host Gaia-specific configuration.
* New --minimum_fees/minimum_fees flag/config option to set a minimum fee.
* SDK
* [querier] added custom querier functionality, so ABCI query requests can be handled by keepers
* [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations
* [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile"
* [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator
* Tendermint
@ -102,6 +107,7 @@ IMPROVEMENTS
* [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200)
* [genesis] \#2229 Ensure that there are no duplicate accounts or validators in the genesis state.
* Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) \#1571
* \#1941(https://github.com/cosmos/cosmos-sdk/issues/1941) Version is now inferred via `git describe --tags`.
* SDK
* [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present.

View File

@ -68,6 +68,9 @@ type BaseApp struct {
deliverState *state // for DeliverTx
signedValidators []abci.SigningValidator // absent validators from begin block
// minimum fees for spam prevention
minimumFees sdk.Coins
// flag for sealing
sealed bool
}
@ -188,10 +191,13 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
return nil
}
// SetMinimumFees sets the minimum fees.
func (app *BaseApp) SetMinimumFees(fees sdk.Coins) { app.minimumFees = fees }
// NewContext returns a new Context with the correct store, the given header, and nil txBytes.
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
if isCheckTx {
return sdk.NewContext(app.checkState.ms, header, true, app.Logger)
return sdk.NewContext(app.checkState.ms, header, true, app.Logger).WithMinimumFees(app.minimumFees)
}
return sdk.NewContext(app.deliverState.ms, header, false, app.Logger)
}
@ -209,7 +215,7 @@ func (app *BaseApp) setCheckState(header abci.Header) {
ms := app.cms.CacheMultiStore()
app.checkState = &state{
ms: ms,
ctx: sdk.NewContext(ms, header, true, app.Logger),
ctx: sdk.NewContext(ms, header, true, app.Logger).WithMinimumFees(app.minimumFees),
}
}
@ -386,7 +392,8 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult()
}
ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger)
ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger).
WithMinimumFees(app.minimumFees)
// Passes the rest of the path as an argument to the querier.
// For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path
resBytes, err := querier(ctx, path[2:], req)

View File

@ -20,9 +20,18 @@ func SetPruning(pruning string) func(*BaseApp) {
case "syncable":
pruningEnum = sdk.PruneSyncable
default:
panic(fmt.Sprintf("Invalid pruning strategy: %s", pruning))
panic(fmt.Sprintf("invalid pruning strategy: %s", pruning))
}
return func(bap *BaseApp) {
bap.cms.SetPruning(pruningEnum)
}
}
// SetMinimumFees returns an option that sets the minimum fees on the app.
func SetMinimumFees(minFees string) func(*BaseApp) {
fees, err := sdk.ParseCoins(minFees)
if err != nil {
panic(fmt.Sprintf("invalid minimum fees: %v", err))
}
return func(bap *BaseApp) { bap.SetMinimumFees(fees) }
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
"regexp"
"testing"
"time"
@ -25,6 +26,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
tests "github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
version "github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth"
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
"github.com/cosmos/cosmos-sdk/x/gov"
@ -34,6 +36,7 @@ import (
func init() {
cryptoKeys.BcryptSecurityParameter = 1
version.Version = os.Getenv("VERSION")
}
func TestKeys(t *testing.T) {
@ -124,16 +127,16 @@ func TestVersion(t *testing.T) {
res, body := Request(t, port, "GET", "/version", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`)
reg, err := regexp.Compile(`\d+\.\d+\.\d+.*`)
require.Nil(t, err)
match := reg.MatchString(body)
require.True(t, match, body)
require.True(t, match, body, fmt.Sprintf("%s", body))
// node info
res, body = Request(t, port, "GET", "/node_version", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
reg, err = regexp.Compile(`\d+\.\d+\.\d+(-dev)?`)
reg, err = regexp.Compile(`\d+\.\d+\.\d+.*`)
require.Nil(t, err)
match = reg.MatchString(body)
require.True(t, match, body)

View File

@ -36,6 +36,72 @@ func init() {
gaiadHome, gaiacliHome = getTestingHomeDirs()
}
func TestGaiaCLIMinimumFees(t *testing.T) {
chainID, servAddr, port := initializeFixtures(t)
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
// start gaiad server with minimum fees
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --minimum_fees=2feeToken", gaiadHome, servAddr))
defer proc.Stop(false)
tests.WaitForTMStart(port)
tests.WaitForNextNBlocksTM(2, port)
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
success := executeWrite(t, fmt.Sprintf(
"gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
require.False(t, success)
tests.WaitForNextNBlocksTM(2, port)
}
func TestGaiaCLIFeesDeduction(t *testing.T) {
chainID, servAddr, port := initializeFixtures(t)
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)
// start gaiad server with minimum fees
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --minimum_fees=1fooToken", gaiadHome, servAddr))
defer proc.Stop(false)
tests.WaitForTMStart(port)
tests.WaitForNextNBlocksTM(2, port)
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome))
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64())
// test simulation
success := executeWrite(t, fmt.Sprintf(
"gaiacli send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken --dry-run", flags, barAddr), app.DefaultKeyPass)
require.True(t, success)
tests.WaitForNextNBlocksTM(2, port)
// ensure state didn't change
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64())
// insufficient funds (coins + fees)
success = executeWrite(t, fmt.Sprintf(
"gaiacli send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken", flags, barAddr), app.DefaultKeyPass)
require.False(t, success)
tests.WaitForNextNBlocksTM(2, port)
// ensure state didn't change
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64())
// test success (transfer = coins + fees)
success = executeWrite(t, fmt.Sprintf(
"gaiacli send %v --fee=300fooToken --amount=500fooToken --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
require.True(t, success)
tests.WaitForNextNBlocksTM(2, port)
}
func TestGaiaCLISend(t *testing.T) {
chainID, servAddr, port := initializeFixtures(t)
flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID)

View File

@ -43,7 +43,10 @@ func main() {
}
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewGaiaApp(logger, db, traceStore, baseapp.SetPruning(viper.GetString("pruning")))
return app.NewGaiaApp(logger, db, traceStore,
baseapp.SetPruning(viper.GetString("pruning")),
baseapp.SetMinimumFees(viper.GetString("minimum_fees")),
)
}
func exportAppStateAndTMValidators(

View File

@ -1,16 +1,69 @@
# Documentation Maintenance Overview
# Docs Build Workflow
The documentation found in this directory is hosted at:
The documentation for the Cosmos SDK is hosted at:
- https://cosmos.network/docs/
- https://cosmos.network/docs/ and
- https://cosmos-staging.interblock.io/docs/
and built using [VuePress](https://vuepress.vuejs.org/) from the Cosmos website repo:
built from the files in this (`/docs`) directory for
[master](https://github.com/cosmos/cosmos-sdk/tree/master/docs)
and [develop](https://github.com/cosmos/cosmos-sdk/tree/develop/docs),
respectively.
- https://github.com/cosmos/cosmos.network
## How It Works
Under the hood, Jenkins listens for changes (on develop or master) in ./docs then rebuilds
either the staging or production site depending on which branch the changes were made.
There is a Jenkins job listening for changes in the `/docs` directory, on both
the `master` and `develop` branches. Any updates to files in this directory
on those branches will automatically trigger a website deployment. Under the hood,
a private website repository has make targets consumed by a standard Jenkins task.
To update the Table of Contents (layout of the documentation sidebar), edit the
`config.js` in this directory, while the `README.md` is the landing page for the
website documentation.
## README
The [README.md](./README.md) is also the landing page for the documentation
on the website.
## Config.js
The [config.js](./config.js) generates the sidebar and Table of Contents
on the website docs. Note the use of relative links and the omission of
file extensions. Additional features are available to improve the look
of the sidebar.
## Links
**NOTE:** Strongly consider the existing links - both within this directory
and to the website docs - when moving or deleting files.
Relative links should be used nearly everywhere, having discovered and weighed the following:
### Relative
Where is the other file, relative to the current one?
- works both on GitHub and for the VuePress build
- confusing / annoying to have things like: `../../../../myfile.md`
- requires more updates when files are re-shuffled
### Absolute
Where is the other file, given the root of the repo?
- works on GitHub, doesn't work for the VuePress build
- this is much nicer: `/docs/hereitis/myfile.md`
- if you move that file around, the links inside it are preserved (but not to it, of course)
### Full
The full GitHub URL to a file or directory. Used occasionally when it makes sense
to send users to the GitHub.
## Building Locally
Not currently possible but coming soon! Doing so requires
assets held in the (private) website repo, installing
[VuePress](https://vuepress.vuejs.org/), and modifying the `config.js`.
## Consistency
Because the build processes are identical (as is the information contained herein), this file should be kept in sync as
much as possible with its [counterpart in the Tendermint Core repo](https://github.com/tendermint/tendermint/blob/develop/docs/DOCS_README.md).

View File

@ -7,3 +7,8 @@ Cosmos is a decentralized network of independent parallel blockchains, each powe
The first blockchain in the Cosmos Network is the Cosmos Hub, whose native token is the Atom. Cosmos is a permission-less network, meaning that anybody can build a blockchain on it.
Cosmos can interoperate with multiple other applications and cryptocurrencies. By creating a new zone, you can plug any blockchain system into the Cosmos hub and pass tokens back and forth between those zones, without the need for an intermediary.
## Edit the Documentation
See [this file](./DOCS_README.md) for details of the build process and
considerations when making changes.

View File

@ -29,6 +29,19 @@ You can edit this `name` later, in the `~/.gaiad/config/config.toml` file:
moniker = "<your_custom_name>"
```
You can edit the `~/.gaiad/config/gaiad.toml` file in order to enable the anti spam mechanism and reject incoming transactions with less than a minimum fee:
```
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base config options #####
# Validators reject any tx from the mempool with less than the minimum fee per gas.
minimum_fees = ""
```
Your full node has been initialized! Please skip to [Genesis & Seeds](#genesis-seeds).
## Upgrading From Previous Testnet

View File

@ -1,5 +1,41 @@
package config
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
defaultMinimumFees = ""
)
// BaseConfig defines the server's basic configuration
type BaseConfig struct {
// Tx minimum fee
MinFees string `mapstructure:"minimum_fees"`
}
// Config defines the server's top level configuration
type Config struct {
BaseConfig `mapstructure:",squash"`
}
// SetMinimumFee sets the minimum fee.
func (c *Config) SetMinimumFees(fees sdk.Coins) { c.MinFees = fees.String() }
// SetMinimumFee sets the minimum fee.
func (c *Config) MinimumFees() sdk.Coins {
fees, err := sdk.ParseCoins(c.MinFees)
if err != nil {
panic(fmt.Sprintf("invalid minimum fees: %v", err))
}
return fees
}
// DefaultConfig returns server's default configuration.
func DefaultConfig() *Config { return &Config{BaseConfig{MinFees: defaultMinimumFees}} }
//_____________________________________________________________________
// Configuration structure for command functions that share configuration.

View File

@ -0,0 +1,19 @@
package config
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
func TestDefaultConfig(t *testing.T) {
cfg := DefaultConfig()
require.True(t, cfg.MinimumFees().IsZero())
}
func TestSetMinimumFees(t *testing.T) {
cfg := DefaultConfig()
cfg.SetMinimumFees(sdk.Coins{sdk.NewCoin("foo", sdk.NewInt(100))})
require.Equal(t, "100foo", cfg.MinFees)
}

46
server/config/toml.go Normal file
View File

@ -0,0 +1,46 @@
package config
import (
"bytes"
"text/template"
"github.com/spf13/viper"
cmn "github.com/tendermint/tendermint/libs/common"
)
const defaultConfigTemplate = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base config options #####
# Validators reject any tx from the mempool with less than the minimum fee per gas.
minimum_fees = "{{ .BaseConfig.MinFees }}"
`
var configTemplate *template.Template
func init() {
var err error
tmpl := template.New("cosmosConfigFileTemplate")
if configTemplate, err = tmpl.Parse(defaultConfigTemplate); err != nil {
panic(err)
}
}
// ParseConfig retrieves the default environment configuration for Cosmos.
func ParseConfig() (*Config, error) {
conf := DefaultConfig()
err := viper.Unmarshal(conf)
return conf, err
}
// WriteConfigFile renders config using the template and writes it to configFilePath.
func WriteConfigFile(configFilePath string, config *Config) {
var buffer bytes.Buffer
if err := configTemplate.Execute(&buffer, config); err != nil {
panic(err)
}
cmn.MustWriteFile(configFilePath, buffer.Bytes(), 0644)
}

View File

@ -19,6 +19,7 @@ const (
flagAddress = "address"
flagTraceStore = "trace-store"
flagPruning = "pruning"
flagMinimumFees = "minimum_fees"
)
// StartCmd runs the service passed in, either stand-alone or in-process with
@ -45,6 +46,7 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command {
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address")
cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
cmd.Flags().String(flagPruning, "syncable", "Pruning strategy: syncable, nothing, everything")
cmd.Flags().String(flagMinimumFees, "", "Minimum fees validator will accept for transactions")
// add support for all Tendermint-specific command line options
tcmd.AddNodeFlags(cmd)

View File

@ -12,6 +12,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server/config"
"github.com/cosmos/cosmos-sdk/version"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
cfg "github.com/tendermint/tendermint/config"
@ -97,6 +98,20 @@ func interceptLoadConfig() (conf *cfg.Config, err error) {
if conf == nil {
conf, err = tcmd.ParseConfig()
}
cosmosConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml")
viper.SetConfigName("cosmos")
_ = viper.MergeInConfig()
var cosmosConf *config.Config
if _, err := os.Stat(cosmosConfigFilePath); os.IsNotExist(err) {
cosmosConf, _ := config.ParseConfig()
config.WriteConfigFile(cosmosConfigFilePath, cosmosConf)
}
if cosmosConf == nil {
_, err = config.ParseConfig()
}
return
}

View File

@ -493,3 +493,25 @@ func (krc *keyRangeCounter) key() int {
//--------------------------------------------------------
func bz(s string) []byte { return []byte(s) }
func BenchmarkCacheKVStoreGetNoKeyFound(b *testing.B) {
st := newCacheKVStore()
b.ResetTimer()
// assumes b.N < 2**24
for i := 0; i < b.N; i++ {
st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)})
}
}
func BenchmarkCacheKVStoreGetKeyFound(b *testing.B) {
st := newCacheKVStore()
for i := 0; i < b.N; i++ {
arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}
st.Set(arr, arr)
}
b.ResetTimer()
// assumes b.N < 2**24
for i := 0; i < b.N; i++ {
st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)})
}
}

View File

@ -46,6 +46,12 @@ func (coin Coin) IsGTE(other Coin) bool {
return coin.SameDenomAs(other) && (!coin.Amount.LT(other.Amount))
}
// IsLT returns true if they are the same type and the receiver is
// a smaller value
func (coin Coin) IsLT(other Coin) bool {
return !coin.IsGTE(other)
}
// IsEqual returns true if the two sets of Coins have the same value
func (coin Coin) IsEqual(other Coin) bool {
return coin.SameDenomAs(other) && (coin.Amount.Equal(other.Amount))
@ -181,6 +187,12 @@ func (coins Coins) IsGTE(coinsB Coins) bool {
return diff.IsNotNegative()
}
// IsLT returns True iff every currency in coins, the currency is
// present at a smaller amount in coins
func (coins Coins) IsLT(coinsB Coins) bool {
return !coins.IsGTE(coinsB)
}
// IsZero returns true if there are no coins
// or all coins are zero.
func (coins Coins) IsZero() bool {

View File

@ -76,6 +76,24 @@ func TestIsGTECoin(t *testing.T) {
}
}
func TestIsLTCoin(t *testing.T) {
cases := []struct {
inputOne Coin
inputTwo Coin
expected bool
}{
{NewInt64Coin("A", 1), NewInt64Coin("A", 1), false},
{NewInt64Coin("A", 2), NewInt64Coin("A", 1), false},
{NewInt64Coin("A", -1), NewInt64Coin("A", 5), true},
{NewInt64Coin("a", 0), NewInt64Coin("b", 1), true},
}
for tcIndex, tc := range cases {
res := tc.inputOne.IsLT(tc.inputTwo)
require.Equal(t, tc.expected, res, "coin LT relation is incorrect, tc #%d", tcIndex)
}
}
func TestIsEqualCoin(t *testing.T) {
cases := []struct {
inputOne Coin
@ -227,6 +245,8 @@ func TestCoins(t *testing.T) {
assert.True(t, good.IsPositive(), "Expected coins to be positive: %v", good)
assert.False(t, null.IsPositive(), "Expected coins to not be positive: %v", null)
assert.True(t, good.IsGTE(empty), "Expected %v to be >= %v", good, empty)
assert.False(t, good.IsLT(empty), "Expected %v to be < %v", good, empty)
assert.True(t, empty.IsLT(good), "Expected %v to be < %v", empty, good)
assert.False(t, neg.IsPositive(), "Expected neg coins to not be positive: %v", neg)
assert.Zero(t, len(sum), "Expected 0 coins")
assert.False(t, badSort1.IsValid(), "Coins are not sorted")

View File

@ -1,3 +1,4 @@
// nolint
package types
import (
@ -41,10 +42,12 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Lo
c = c.WithBlockHeader(header)
c = c.WithBlockHeight(header.Height)
c = c.WithChainID(header.ChainID)
c = c.WithIsCheckTx(isCheckTx)
c = c.WithTxBytes(nil)
c = c.WithLogger(logger)
c = c.WithSigningValidators(nil)
c = c.WithGasMeter(NewInfiniteGasMeter())
c = c.WithMinimumFees(Coins{})
return c
}
@ -132,10 +135,12 @@ const (
contextKeyBlockHeight
contextKeyConsensusParams
contextKeyChainID
contextKeyIsCheckTx
contextKeyTxBytes
contextKeyLogger
contextKeySigningValidators
contextKeyGasMeter
contextKeyMinimumFees
)
// NOTE: Do not expose MultiStore.
@ -145,41 +150,41 @@ func (c Context) multiStore() MultiStore {
return c.Value(contextKeyMultiStore).(MultiStore)
}
// nolint
func (c Context) BlockHeader() abci.Header {
return c.Value(contextKeyBlockHeader).(abci.Header)
}
func (c Context) BlockHeight() int64 {
return c.Value(contextKeyBlockHeight).(int64)
}
func (c Context) BlockHeader() abci.Header { return c.Value(contextKeyBlockHeader).(abci.Header) }
func (c Context) BlockHeight() int64 { return c.Value(contextKeyBlockHeight).(int64) }
func (c Context) ConsensusParams() abci.ConsensusParams {
return c.Value(contextKeyConsensusParams).(abci.ConsensusParams)
}
func (c Context) ChainID() string {
return c.Value(contextKeyChainID).(string)
}
func (c Context) TxBytes() []byte {
return c.Value(contextKeyTxBytes).([]byte)
}
func (c Context) Logger() log.Logger {
return c.Value(contextKeyLogger).(log.Logger)
}
func (c Context) ChainID() string { return c.Value(contextKeyChainID).(string) }
func (c Context) TxBytes() []byte { return c.Value(contextKeyTxBytes).([]byte) }
func (c Context) Logger() log.Logger { return c.Value(contextKeyLogger).(log.Logger) }
func (c Context) SigningValidators() []abci.SigningValidator {
return c.Value(contextKeySigningValidators).([]abci.SigningValidator)
}
func (c Context) GasMeter() GasMeter {
return c.Value(contextKeyGasMeter).(GasMeter)
}
func (c Context) WithMultiStore(ms MultiStore) Context {
return c.withValue(contextKeyMultiStore, ms)
}
func (c Context) GasMeter() GasMeter { return c.Value(contextKeyGasMeter).(GasMeter) }
func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) }
func (c Context) MinimumFees() Coins { return c.Value(contextKeyMinimumFees).(Coins) }
func (c Context) WithMultiStore(ms MultiStore) Context { return c.withValue(contextKeyMultiStore, ms) }
func (c Context) WithBlockHeader(header abci.Header) Context {
var _ proto.Message = &header // for cloning.
return c.withValue(contextKeyBlockHeader, header)
}
func (c Context) WithBlockHeight(height int64) Context {
return c.withValue(contextKeyBlockHeight, height)
}
func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context {
if params == nil {
return c
@ -187,20 +192,25 @@ func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context {
return c.withValue(contextKeyConsensusParams, params).
WithGasMeter(NewGasMeter(params.TxSize.MaxGas))
}
func (c Context) WithChainID(chainID string) Context {
return c.withValue(contextKeyChainID, chainID)
}
func (c Context) WithTxBytes(txBytes []byte) Context {
return c.withValue(contextKeyTxBytes, txBytes)
}
func (c Context) WithLogger(logger log.Logger) Context {
return c.withValue(contextKeyLogger, logger)
}
func (c Context) WithChainID(chainID string) Context { return c.withValue(contextKeyChainID, chainID) }
func (c Context) WithTxBytes(txBytes []byte) Context { return c.withValue(contextKeyTxBytes, txBytes) }
func (c Context) WithLogger(logger log.Logger) Context { return c.withValue(contextKeyLogger, logger) }
func (c Context) WithSigningValidators(SigningValidators []abci.SigningValidator) Context {
return c.withValue(contextKeySigningValidators, SigningValidators)
}
func (c Context) WithGasMeter(meter GasMeter) Context {
return c.withValue(contextKeyGasMeter, meter)
func (c Context) WithGasMeter(meter GasMeter) Context { return c.withValue(contextKeyGasMeter, meter) }
func (c Context) WithIsCheckTx(isCheckTx bool) Context {
return c.withValue(contextKeyIsCheckTx, isCheckTx)
}
func (c Context) WithMinimumFees(minFees Coins) Context {
return c.withValue(contextKeyMinimumFees, minFees)
}
// Cache the multistore and return a new cached context. The cached context is

View File

@ -162,20 +162,23 @@ func TestContextWithCustom(t *testing.T) {
logger := NewMockLogger()
signvals := []abci.SigningValidator{{}}
meter := types.NewGasMeter(10000)
minFees := types.Coins{types.NewInt64Coin("feeCoin", 1)}
ctx = types.NewContext(nil, header, ischeck, logger).
WithBlockHeight(height).
WithChainID(chainid).
WithTxBytes(txbytes).
WithSigningValidators(signvals).
WithGasMeter(meter)
WithGasMeter(meter).
WithMinimumFees(minFees)
require.Equal(t, header, ctx.BlockHeader())
require.Equal(t, height, ctx.BlockHeight())
require.Equal(t, chainid, ctx.ChainID())
require.Equal(t, ischeck, ctx.IsCheckTx())
require.Equal(t, txbytes, ctx.TxBytes())
require.Equal(t, logger, ctx.Logger())
require.Equal(t, signvals, ctx.SigningValidators())
require.Equal(t, meter, ctx.GasMeter())
require.Equal(t, minFees, types.Coins{types.NewInt64Coin("feeCoin", 1)})
}

View File

@ -56,6 +56,7 @@ const (
CodeInvalidCoins CodeType = 11
CodeOutOfGas CodeType = 12
CodeMemoTooLarge CodeType = 13
CodeInsufficientFee CodeType = 14
// CodespaceRoot is a codespace for error codes in this file only.
// Notice that 0 is an "unset" codespace, which can be overridden with
@ -101,6 +102,8 @@ func CodeToDefaultMsg(code CodeType) string {
return "out of gas"
case CodeMemoTooLarge:
return "memo too large"
case CodeInsufficientFee:
return "insufficient fee"
default:
return unknownCodeMsg(code)
}
@ -150,6 +153,9 @@ func ErrOutOfGas(msg string) Error {
func ErrMemoTooLarge(msg string) Error {
return newErrorWithRootCodespace(CodeMemoTooLarge, msg)
}
func ErrInsufficientFee(msg string) Error {
return newErrorWithRootCodespace(CodeInsufficientFee, msg)
}
//----------------------------------------
// Error & sdkError

View File

@ -17,15 +17,10 @@ var (
// return version of CLI/node and commit hash
func GetVersion() string {
v := Version
if GitCommit != "" {
v = v + "-" + GitCommit
}
return v
return Version
}
// CMD
func printVersion(cmd *cobra.Command, args []string) {
v := GetVersion()
fmt.Println(v)
fmt.Println(GetVersion())
}

View File

@ -1,11 +1,5 @@
//nolint
package version
const Maj = "0"
const Min = "24"
const Fix = "2"
const Version = "0.24.2"
// GitCommit set by build flags
var GitCommit = ""
var Version = ""

View File

@ -12,11 +12,12 @@ import (
)
const (
deductFeesCost sdk.Gas = 10
memoCostPerByte sdk.Gas = 1
ed25519VerifyCost = 59
secp256k1VerifyCost = 100
maxMemoCharacters = 100
deductFeesCost sdk.Gas = 10
memoCostPerByte sdk.Gas = 1
ed25519VerifyCost = 59
secp256k1VerifyCost = 100
maxMemoCharacters = 100
feeDeductionGasFactor = 0.001
)
// NewAnteHandler returns an AnteHandler that checks
@ -94,8 +95,15 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
return newCtx, res, true
}
requiredFees := adjustFeesByGas(ctx.MinimumFees(), fee.Gas)
// fees must be greater than the minimum set by the validator adjusted by gas
if ctx.IsCheckTx() && !simulate && !ctx.MinimumFees().IsZero() && fee.Amount.IsLT(requiredFees) {
// validators reject any tx from the mempool with less than the minimum fee per gas * gas factor
return newCtx, sdk.ErrInsufficientFee(fmt.Sprintf(
"insufficient fee, got: %q required: %q", fee.Amount, requiredFees)).Result(), true
}
// first sig pays the fees
// TODO: Add min fees
// Can this function be moved outside of the loop?
if i == 0 && !fee.Amount.IsZero() {
newCtx.GasMeter().ConsumeGas(deductFeesCost, "deductFees")
@ -236,6 +244,15 @@ func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) {
}
}
func adjustFeesByGas(fees sdk.Coins, gas int64) sdk.Coins {
gasCost := int64(float64(gas) * feeDeductionGasFactor)
gasFees := make(sdk.Coins, len(fees))
for i := 0; i < len(fees); i++ {
gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, gasCost)
}
return fees.Plus(gasFees)
}
// Deduct the fee from the account.
// We could use the CoinKeeper (in addition to the AccountMapper,
// because the CoinKeeper doesn't give us accounts), but it seems easier to do this.

View File

@ -627,3 +627,24 @@ func TestConsumeSignatureVerificationGas(t *testing.T) {
})
}
}
func TestAdjustFeesByGas(t *testing.T) {
type args struct {
fee sdk.Coins
gas int64
}
tests := []struct {
name string
args args
want sdk.Coins
}{
{"nil coins", args{sdk.Coins{}, 10000}, sdk.Coins{}},
{"nil coins", args{sdk.Coins{sdk.NewInt64Coin("A", 10), sdk.NewInt64Coin("B", 0)}, 10000}, sdk.Coins{sdk.NewInt64Coin("A", 20), sdk.NewInt64Coin("B", 10)}},
{"negative coins", args{sdk.Coins{sdk.NewInt64Coin("A", -10), sdk.NewInt64Coin("B", 10)}, 10000}, sdk.Coins{sdk.NewInt64Coin("B", 20)}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.True(t, tt.want.IsEqual(adjustFeesByGas(tt.args.fee, tt.args.gas)))
})
}
}

View File

@ -60,3 +60,26 @@ func TestAccountMapperGetSet(t *testing.T) {
require.NotNil(t, acc)
require.Equal(t, newSequence, acc.GetSequence())
}
func BenchmarkAccountMapperGetAccountFound(b *testing.B) {
ms, capKey, _ := setupMultiStore()
cdc := codec.New()
RegisterBaseAccount(cdc)
// make context and mapper
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger())
mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount)
// assumes b.N < 2**24
for i := 0; i < b.N; i++ {
arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}
addr := sdk.AccAddress(arr)
acc := mapper.NewAccountWithAddress(ctx, addr)
mapper.SetAccount(ctx, acc)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}
mapper.GetAccount(ctx, sdk.AccAddress(arr))
}
}

View File

@ -69,7 +69,7 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe
fops := make([]simulation.FutureOperation, numVotes+1)
for i := 0; i < numVotes; i++ {
whenVote := ctx.BlockHeight() + r.Int63n(votingPeriod)
fops[i] = simulation.FutureOperation{int(whenVote), operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)}
fops[i] = simulation.FutureOperation{BlockHeight: int(whenVote), Op: operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)}
}
// 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant)
// TODO: Find a way to check if a validator was slashed other than just checking their balance a block

View File

@ -98,13 +98,14 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
request := RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header)
// These are operations which have been queued by previous operations
operationQueue := make(map[int][]Operation)
timeOperationQueue := []FutureOperation{}
var blockLogBuilders []*strings.Builder
if testingMode {
blockLogBuilders = make([]*strings.Builder, numBlocks)
}
displayLogs := logPrinter(testingMode, blockLogBuilders)
blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, numBlocks, displayLogs)
blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, timeOperationQueue, numBlocks, displayLogs)
if !testingMode {
b.ResetTimer()
} else {
@ -139,9 +140,10 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
// Run queued operations. Ignores blocksize if blocksize is too small
numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, logWriter, displayLogs, event)
thisBlockSize -= numQueuedOpsRan
numQueuedTimeOpsRan := runQueuedTimeOperations(timeOperationQueue, header.Time, tb, r, app, ctx, keys, logWriter, displayLogs, event)
thisBlockSize = thisBlockSize - numQueuedOpsRan - numQueuedTimeOpsRan
operations := blockSimulator(thisBlockSize, r, app, ctx, keys, header, logWriter)
opCount += operations + numQueuedOpsRan
opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan
res := app.EndBlock(abci.RequestEndBlock{})
header.Height++
@ -173,7 +175,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
// Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize
// memory overhead
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []WeightedOperation, operationQueue map[int][]Operation, totalNumBlocks int, displayLogs func()) func(
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []WeightedOperation, operationQueue map[int][]Operation, timeOperationQueue []FutureOperation, totalNumBlocks int, displayLogs func()) func(
blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, header abci.Header, logWriter func(string)) (opCount int) {
totalOpWeight := 0
for i := 0; i < len(ops); i++ {
@ -200,7 +202,7 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event f
}
logWriter(logUpdate)
queueOperations(operationQueue, futureOps)
queueOperations(operationQueue, timeOperationQueue, futureOps)
if testingMode {
if onOperation {
assertAllInvariants(t, app, invariants, displayLogs)
@ -239,15 +241,23 @@ func getBlockSize(r *rand.Rand, blockSize int) int {
}
// adds all future operations into the operation queue.
func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) {
func queueOperations(queuedOperations map[int][]Operation, queuedTimeOperations []FutureOperation, futureOperations []FutureOperation) {
if futureOperations == nil {
return
}
for _, futureOp := range futureOperations {
if val, ok := queuedOperations[futureOp.BlockHeight]; ok {
queuedOperations[futureOp.BlockHeight] = append(val, futureOp.Op)
if futureOp.BlockHeight != 0 {
if val, ok := queuedOperations[futureOp.BlockHeight]; ok {
queuedOperations[futureOp.BlockHeight] = append(val, futureOp.Op)
} else {
queuedOperations[futureOp.BlockHeight] = []Operation{futureOp.Op}
}
} else {
queuedOperations[futureOp.BlockHeight] = []Operation{futureOp.Op}
// TODO: Replace with proper sorted data structure, so don't have the copy entire slice
index := sort.Search(len(queuedTimeOperations), func(i int) bool { return queuedTimeOperations[i].BlockTime.After(futureOp.BlockTime) })
queuedTimeOperations = append(queuedTimeOperations, FutureOperation{})
copy(queuedTimeOperations[index+1:], queuedTimeOperations[index:])
queuedTimeOperations[index] = futureOp
}
}
}
@ -274,6 +284,26 @@ func runQueuedOperations(queueOperations map[int][]Operation, height int, tb tes
return 0
}
func runQueuedTimeOperations(queueOperations []FutureOperation, currentTime time.Time, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
privKeys []crypto.PrivKey, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) {
numOpsRan = 0
for len(queueOperations) > 0 && currentTime.After(queueOperations[0].BlockTime) {
// For now, queued operations cannot queue more operations.
// If a need arises for us to support queued messages to queue more messages, this can
// be changed.
logUpdate, _, err := queueOperations[0].Op(r, app, ctx, privKeys, event)
logWriter(logUpdate)
if err != nil {
displayLogs()
tb.FailNow()
}
queueOperations = queueOperations[1:]
numOpsRan++
}
return numOpsRan
}
func getKeys(validators map[string]mockValidator) []string {
keys := make([]string, len(validators))
i := 0

View File

@ -2,6 +2,7 @@ package simulation
import (
"math/rand"
"time"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -41,10 +42,12 @@ type (
// FutureOperation is an operation which will be ran at the
// beginning of the provided BlockHeight.
// If both a BlockHeight and BlockTime are specified, it will use the BlockHeight.
// In the (likely) event that multiple operations are queued at the same
// block height, they will execute in a FIFO pattern.
FutureOperation struct {
BlockHeight int
BlockTime time.Time
Op Operation
}