From 5b8a499fd0c4cabc8b233f98333f6f81afbf1c3a Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 18 Sep 2018 19:47:54 -0700 Subject: [PATCH 1/5] R4R: Simulation Queued Time Operations (#2348) * closes #2349 * queue time operations simulation * removed FutureTimeOperation --- PENDING.md | 1 + x/gov/simulation/msgs.go | 2 +- x/mock/simulation/random_simulate_blocks.go | 48 +++++++++++++++++---- x/mock/simulation/types.go | 3 ++ 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/PENDING.md b/PENDING.md index 1344344118..75d0fe62ac 100644 --- a/PENDING.md +++ b/PENDING.md @@ -80,6 +80,7 @@ FEATURES * [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 diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 4ca1cdf445..168081b188 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -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 diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index de78ae0942..96dbff5725 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -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 diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 401899efe3..a32edea6d6 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -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 } From c6a3928d37530b61f6a5ba1e2f260af1042158c2 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Tue, 18 Sep 2018 20:00:49 -0700 Subject: [PATCH 2/5] Added a couple of benchmarks (#2353) These method were surprisingly slow from the profiling. (Mapper.Get accounted for 2.7% of time taken, GetKey also took an interestingly large portion of time) --- store/cachekvstore_test.go | 22 ++++++++++++++++++++++ x/auth/mapper_test.go | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/store/cachekvstore_test.go b/store/cachekvstore_test.go index e7958dfcdf..9d02d57d3a 100644 --- a/store/cachekvstore_test.go +++ b/store/cachekvstore_test.go @@ -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)}) + } +} diff --git a/x/auth/mapper_test.go b/x/auth/mapper_test.go index 96dc57b79f..9580d3133d 100644 --- a/x/auth/mapper_test.go +++ b/x/auth/mapper_test.go @@ -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)) + } +} From b74a6a90669d574e84e26adb19fc3d775a0bef71 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 19 Sep 2018 16:25:52 +0100 Subject: [PATCH 3/5] Merge PR #2328: Support min fees-based anti spam strategy --- PENDING.md | 4 ++ baseapp/baseapp.go | 13 ++++-- baseapp/options.go | 11 ++++- cmd/gaia/cli_test/cli_test.go | 66 +++++++++++++++++++++++++++ cmd/gaia/cmd/gaiad/main.go | 5 +- docs/getting-started/full-node.md | 13 ++++++ server/config/config.go | 36 +++++++++++++++ server/config/config_test.go | 19 ++++++++ server/config/toml.go | 46 +++++++++++++++++++ server/start.go | 2 + server/util.go | 15 ++++++ types/coin.go | 12 +++++ types/coin_test.go | 20 ++++++++ types/context.go | 76 +++++++++++++++++-------------- types/context_test.go | 7 ++- types/errors.go | 6 +++ x/auth/ante.go | 29 +++++++++--- x/auth/ante_test.go | 21 +++++++++ 18 files changed, 355 insertions(+), 46 deletions(-) create mode 100644 server/config/config_test.go create mode 100644 server/config/toml.go diff --git a/PENDING.md b/PENDING.md index 75d0fe62ac..cf1dcd8995 100644 --- a/PENDING.md +++ b/PENDING.md @@ -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,6 +76,9 @@ 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 diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 1f99440142..63d7b17e8d 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -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) diff --git a/baseapp/options.go b/baseapp/options.go index 0a404217ae..048a17d588 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -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) } +} diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 3d9f86789a..8ac8f55598 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -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) diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index aa5978407d..0b5f0e505e 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -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( diff --git a/docs/getting-started/full-node.md b/docs/getting-started/full-node.md index 87c28581b1..66ec97cad5 100644 --- a/docs/getting-started/full-node.md +++ b/docs/getting-started/full-node.md @@ -29,6 +29,19 @@ You can edit this `name` later, in the `~/.gaiad/config/config.toml` file: moniker = "" ``` +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 diff --git a/server/config/config.go b/server/config/config.go index e6fc6a4de9..bd0d966e35 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -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. diff --git a/server/config/config_test.go b/server/config/config_test.go new file mode 100644 index 0000000000..e4d552ad21 --- /dev/null +++ b/server/config/config_test.go @@ -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) +} diff --git a/server/config/toml.go b/server/config/toml.go new file mode 100644 index 0000000000..3c60fbdf93 --- /dev/null +++ b/server/config/toml.go @@ -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) +} diff --git a/server/start.go b/server/start.go index 829e393639..5d5b1b3eb0 100644 --- a/server/start.go +++ b/server/start.go @@ -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) diff --git a/server/util.go b/server/util.go index 22dbd78678..9508e04894 100644 --- a/server/util.go +++ b/server/util.go @@ -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 } diff --git a/types/coin.go b/types/coin.go index aa60295590..d7484a6699 100644 --- a/types/coin.go +++ b/types/coin.go @@ -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 { diff --git a/types/coin_test.go b/types/coin_test.go index 145c0c40a2..3ff0bffe57 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -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") diff --git a/types/context.go b/types/context.go index 85fb16a7ff..6a54f247de 100644 --- a/types/context.go +++ b/types/context.go @@ -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 diff --git a/types/context_test.go b/types/context_test.go index b11a774cd9..e08aca01ff 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -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)}) } diff --git a/types/errors.go b/types/errors.go index 1d0de5eb4b..1d4900d3c2 100644 --- a/types/errors.go +++ b/types/errors.go @@ -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 diff --git a/x/auth/ante.go b/x/auth/ante.go index 5b2cdb1559..5773e6b1fe 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -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. diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 75fd130436..2a289f317b 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -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))) + }) + } +} From 10b916eb28a8aed07df80d8c305a40b7f073d739 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 19 Sep 2018 17:03:04 +0100 Subject: [PATCH 4/5] Merge PR #2318: Simplify version handling, rely on git describe --- .circleci/config.yml | 1 + Makefile | 10 +++++----- PENDING.md | 1 + client/lcd/lcd_test.go | 9 ++++++--- version/command.go | 9 ++------- version/version.go | 8 +------- 6 files changed, 16 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 269d4ca94b..a62c61fd35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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" diff --git a/Makefile b/Makefile index a2c51b6df7..60c213871c 100644 --- a/Makefile +++ b/Makefile @@ -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 ./... diff --git a/PENDING.md b/PENDING.md index cf1dcd8995..cafe2025af 100644 --- a/PENDING.md +++ b/PENDING.md @@ -107,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. diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 7bd629cb18..263b112b27 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -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) diff --git a/version/command.go b/version/command.go index 2cff1bbe92..8f47dba8f5 100644 --- a/version/command.go +++ b/version/command.go @@ -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()) } diff --git a/version/version.go b/version/version.go index 8bfea95770..407797eeb0 100644 --- a/version/version.go +++ b/version/version.go @@ -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 = "" From 1167e07b988cc1268fc2fc2cbc3059d14f53e51c Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 19 Sep 2018 13:24:31 -0400 Subject: [PATCH 5/5] Merge PR #2341: Link to DOCS_README --- docs/DOCS_README.md | 73 ++++++++++++++++++++++++++++++++++++++------- docs/README.md | 5 ++++ 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index da11b2c01e..78451c0ef2 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -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). diff --git a/docs/README.md b/docs/README.md index 9921cd757d..e721ad1a1b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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.