diff --git a/.circleci/config.yml b/.circleci/config.yml index a8e718b109aa..37ad00181da4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 defaults: &linux_defaults working_directory: /go/src/github.com/cosmos/cosmos-sdk docker: - - image: circleci/golang:1.11.4 + - image: circleci/golang:1.11.5 environment: GOBIN: /tmp/workspace/bin @@ -17,7 +17,7 @@ macos_config: &macos_defaults xcode: "10.1.0" working_directory: /Users/distiller/project/src/github.com/cosmos/cosmos-sdk environment: - GO_VERSION: "1.11.4" + GO_VERSION: "1.11.5" set_macos_env: &macos_env run: @@ -82,6 +82,7 @@ jobs: name: Get metalinter command: | export PATH="$GOBIN:$PATH" + make devtools-clean make devtools - run: name: Lint source @@ -171,7 +172,7 @@ jobs: name: Test multi-seed Gaia simulation long command: | export PATH="$GOBIN:$PATH" - scripts/multisim.sh 800 50 TestFullGaiaSimulation + scripts/multisim.sh 500 50 TestFullGaiaSimulation test_sim_gaia_multi_seed: <<: *linux_defaults @@ -243,7 +244,7 @@ jobs: GOPATH: /home/circleci/.go_workspace/ GOOS: linux GOARCH: amd64 - GO_VERSION: "1.11.4" + GO_VERSION: "1.11.5" parallelism: 1 steps: - checkout diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dcdf7080b874..11f9e598b42c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,4 +4,6 @@ * @ebuchman @rigelrozanski @cwgoes # Precious documentation -/docs/ @zramsay @jolesbi +/docs/README.md @zramsay +/docs/DOCS_README.md @zramsay +/docs/.vuepress/ @zramsay diff --git a/Gopkg.lock b/Gopkg.lock index fa9eec6a673f..97fcf673472d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -522,12 +522,12 @@ revision = "v0.29.0" [[projects]] - digest = "1:a7485b2a69f996923f9d3406a9a853fd8eb31818515e985a830d71f88f6a925b" + digest = "1:29b886f00694ae7c18c4559a2901f2a057d5a62308ed5eb6cd52b9a31016fb14" name = "github.com/zondax/ledger-cosmos-go" packages = ["."] pruneopts = "UT" - revision = "d4aed6d929a703bb555a2d79fe9c470afe61f648" - version = "v0.9.2" + revision = "71aa0ab6e03d2d320c82bbe13678a36584a5b813" + version = "v0.9.3" [[projects]] digest = "1:6f6dc6060c4e9ba73cf28aa88f12a69a030d3d19d518ef8e931879eaa099628d" diff --git a/Gopkg.toml b/Gopkg.toml index 54a64d419a75..1bf115d61992 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -44,7 +44,7 @@ [[constraint]] name = "github.com/zondax/ledger-cosmos-go" - version = "=v0.9.2" + version = "=v0.9.3" ## deps without releases: diff --git a/Makefile b/Makefile index 2f8086fd7ae4..d583e0c763fd 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation') PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') -VERSION := $(subst v,,$(shell git describe --tags --long)) +VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') COMMIT := $(shell git log -1 --format='%H') BUILD_TAGS = netgo CAT := $(if $(filter $(OS),Windows_NT),type,cat) @@ -15,7 +15,7 @@ GOTOOLS = \ github.com/rakyll/statik GOBIN ?= $(GOPATH)/bin -all: devtools get_vendor_deps install test_lint test +all: devtools vendor-deps install test_lint test # The below include contains the tools target. include scripts/Makefile @@ -99,6 +99,7 @@ update_dev_tools: go get -u github.com/tendermint/lint/golint devtools: devtools-stamp +devtools-clean: tools-clean devtools-stamp: tools @echo "--> Downloading linters (this may take awhile)" $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) @@ -122,7 +123,10 @@ draw_deps: tools @goviz -i github.com/cosmos/cosmos-sdk/cmd/gaia/cmd/gaiad -d 2 | dot -Tpng -o dependency-graph.png clean: - rm -f devtools-stamp vendor-deps + rm -f devtools-stamp vendor-deps snapcraft-local.yaml + +distclean: clean + rm -rf vendor/ ######################################## ### Documentation @@ -150,9 +154,15 @@ test_sim_gaia_nondeterminism: @echo "Running nondeterminism test..." @go test ./cmd/gaia/app -run TestAppStateDeterminism -SimulationEnabled=true -v -timeout 10m +test_sim_gaia_custom_genesis_fast: + @echo "Running custom genesis simulation..." + @echo "By default, ${HOME}/.gaiad/config/genesis.json will be used." + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationGenesis=${HOME}/.gaiad/config/genesis.json \ + -SimulationEnabled=true -SimulationNumBlocks=100 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -SimulationPeriod=5 -v -timeout 24h + test_sim_gaia_fast: @echo "Running quick Gaia simulation. This may take several minutes..." - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -v -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=100 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -SimulationPeriod=5 -v -timeout 24h test_sim_gaia_import_export: @echo "Running Gaia import/export simulation. This may take several minutes..." @@ -162,6 +172,11 @@ test_sim_gaia_simulation_after_import: @echo "Running Gaia simulation-after-import. This may take several minutes..." @bash scripts/multisim.sh 50 5 TestGaiaSimulationAfterImport +test_sim_gaia_custom_genesis_multi_seed: + @echo "Running multi-seed custom genesis simulation..." + @echo "By default, ${HOME}/.gaiad/config/genesis.json will be used." + @bash scripts/multisim.sh 400 5 TestFullGaiaSimulation ${HOME}/.gaiad/config/genesis.json + test_sim_gaia_multi_seed: @echo "Running multi-seed Gaia simulation. This may take awhile!" @bash scripts/multisim.sh 400 5 TestFullGaiaSimulation @@ -171,11 +186,13 @@ SIM_BLOCK_SIZE ?= 200 SIM_COMMIT ?= true test_sim_gaia_benchmark: @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" - @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 + @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 test_sim_gaia_profile: @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" - @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 + @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: @export VERSION=$(VERSION); bash tests/test_cover.sh @@ -235,12 +252,21 @@ localnet-start: localnet-stop localnet-stop: docker-compose down + +######################################## +### Packaging + +snapcraft-local.yaml: snapcraft-local.yaml.in + sed "s/@VERSION@/${VERSION}/g" < $< > $@ + # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: build install install_debug dist \ +.PHONY: build install install_debug dist clean distclean \ check_tools check_dev_tools get_vendor_deps draw_deps test test_cli test_unit \ test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \ build-linux build-docker-gaiadnode localnet-start localnet-stop \ format check-ledger test_sim_gaia_nondeterminism test_sim_modules test_sim_gaia_fast \ -test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools +test_sim_gaia_custom_genesis_fast test_sim_gaia_custom_genesis_multi_seed \ +test_sim_gaia_multi_seed test_sim_gaia_import_export update_tools update_dev_tools \ +devtools-clean diff --git a/PENDING.md b/PENDING.md index 0a01c3eb6500..545d74380d2b 100644 --- a/PENDING.md +++ b/PENDING.md @@ -3,23 +3,37 @@ BREAKING CHANGES * Gaia REST API (`gaiacli advanced rest-server`) + * [\#3284](https://github.com/cosmos/cosmos-sdk/issues/3284) Rename the `name` + field to `from` in the `base_req` body. + * [\#3485](https://github.com/cosmos/cosmos-sdk/pull/3485) Error responses are now JSON objects. * Gaia CLI (`gaiacli`) + - [#3399](https://github.com/cosmos/cosmos-sdk/pull/3399) Add `gaiad validate-genesis` command to facilitate checking of genesis files + - [\#1894](https://github.com/cosmos/cosmos-sdk/issues/1894) `version` prints out short info by default. Add `--long` flag. Proper handling of `--format` flag introduced. + - [\#3465](https://github.com/cosmos/cosmos-sdk/issues/3465) `gaiacli rest-server` switched back to insecure mode by default: + - `--insecure` flag is removed. + - `--tls` is now used to enable secure layer. * Gaia * SDK + * [\#3487](https://github.com/cosmos/cosmos-sdk/pull/3487) Move HTTP/REST utilities out of client/utils into a new dedicated client/rest package. * Tendermint FEATURES -* Gaia REST API (`gaiacli advanced rest-server`) +* Gaia REST API * Gaia CLI (`gaiacli`) + * [\#3429](https://github.com/cosmos/cosmos-sdk/issues/3429) Support querying + for all delegator distribution rewards. + * \#3449 Proof verification now works with absence proofs * Gaia + - [\#3397](https://github.com/cosmos/cosmos-sdk/pull/3397) Implement genesis file sanitization to avoid failures at chain init. + * \#3428 Run the simulation from a particular genesis state loaded from a file * SDK * \#3270 [x/staking] limit number of ongoing unbonding delegations /redelegations per pair/trio @@ -29,24 +43,47 @@ FEATURES IMPROVEMENTS -* Gaia REST API (`gaiacli advanced rest-server`) +* Gaia REST API + * [\#3284](https://github.com/cosmos/cosmos-sdk/issues/3284) Update Gaia Lite + REST service to support the following: + * Automatic account number and sequence population when fields are omitted + * Generate only functionality no longer requires access to a local Keybase + * `from` field in the `base_req` body can be a Keybase name or account address + * [\#3423](https://github.com/cosmos/cosmos-sdk/issues/3423) Allow simulation + (auto gas) to work with generate only. * Gaia CLI (`gaiacli`) * Gaia + * [\#3418](https://github.com/cosmos/cosmos-sdk/issues/3418) Add vesting account + genesis validation checks to `GaiaValidateGenesisState`. + * [\#3420](https://github.com/cosmos/cosmos-sdk/issues/3420) Added maximum length to governance proposal descriptions and titles + * [\#3454](https://github.com/cosmos/cosmos-sdk/pull/3454) Add `--jail-whitelist` to `gaiad export` to enable testing of complex exports + * [\#3424](https://github.com/cosmos/cosmos-sdk/issues/3424) Allow generation of gentxs with empty memo field. * SDK + * [\#2986](https://github.com/cosmos/cosmos-sdk/pull/2986) Store Refactor + * \#3435 Test that store implementations do not allow nil values * Tendermint BUG FIXES -* Gaia REST API (`gaiacli advanced rest-server`) +* Gaia REST API * Gaia CLI (`gaiacli`) + - [\#3417](https://github.com/cosmos/cosmos-sdk/pull/3417) Fix `q slashing signing-info` panic by ensuring safety of user input and properly returning not found error + - [\#3345](https://github.com/cosmos/cosmos-sdk/issues/3345) Upgrade ledger-cosmos-go dependency to v0.9.3 to pull + https://github.com/ZondaX/ledger-cosmos-go/commit/ed9aa39ce8df31bad1448c72d3d226bf2cb1a8d1 in order to fix a derivation path issue that causes `gaiacli keys add --recover` + to malfunction. + - [\#3419](https://github.com/cosmos/cosmos-sdk/pull/3419) Fix `q distr slashes` panic + - [\#3453](https://github.com/cosmos/cosmos-sdk/pull/3453) The `rest-server` command didn't respect persistent flags such as `--chain-id` and `--trust-node` if they were + passed on the command line. * Gaia + * [\#3486](https://github.com/cosmos/cosmos-sdk/pull/3486) Use AmountOf in + vesting accounts instead of zipping/aligning denominations. * SDK diff --git a/README.md b/README.md index 2ab5cf4e2b36..d23a1edc8dee 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![version](https://img.shields.io/github/tag/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/releases/latest) [![CircleCI](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master.svg?style=shield)](https://circleci.com/gh/cosmos/cosmos-sdk/tree/master) +[![Snap Status](https://build.snapcraft.io/badge/cosmos/cosmos-sdk.svg)](https://build.snapcraft.io/user/cosmos/cosmos-sdk) [![codecov](https://codecov.io/gh/cosmos/cosmos-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/cosmos/cosmos-sdk) [![Go Report Card](https://goreportcard.com/badge/github.com/cosmos/cosmos-sdk)](https://goreportcard.com/report/github.com/cosmos/cosmos-sdk) [![license](https://img.shields.io/github/license/cosmos/cosmos-sdk.svg)](https://github.com/cosmos/cosmos-sdk/blob/master/LICENSE) @@ -17,7 +18,7 @@ It is being used to build `Gaia`, the first implementation of the Cosmos Hub. **WARNING**: The SDK has mostly stabilized, but we are still making some breaking changes. -**Note**: Requires [Go 1.11.4+](https://golang.org/dl/) +**Note**: Requires [Go 1.11.5+](https://golang.org/dl/) ## Cosmos Hub Public Testnet diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index cfeb97214eeb..ef5c692b9bb6 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -114,7 +114,7 @@ func (app *BaseApp) Name() string { // SetCommitMultiStoreTracer sets the store tracer on the BaseApp's underlying // CommitMultiStore. func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) { - app.cms.WithTracer(w) + app.cms.SetTracer(w) } // Mount IAVL or DB stores to the provided keys in the BaseApp multistore @@ -396,7 +396,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc return abci.ResponseQuery{ Code: uint32(sdk.CodeOK), Codespace: string(sdk.CodespaceRoot), - Value: []byte(version.GetVersion()), + Value: []byte(version.Version), } default: result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result() @@ -483,8 +483,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res // BeginBlock implements the ABCI application interface. func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { if app.cms.TracingEnabled() { - app.cms.ResetTraceContext() - app.cms.WithTracingContext(sdk.TraceContext( + app.cms.SetTracingContext(sdk.TraceContext( map[string]interface{}{"blockHeight": req.Header.Height}, )) } @@ -679,7 +678,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) ( // TODO: https://github.com/cosmos/cosmos-sdk/issues/2824 msCache := ms.CacheMultiStore() if msCache.TracingEnabled() { - msCache = msCache.WithTracingContext( + msCache = msCache.SetTracingContext( sdk.TraceContext( map[string]interface{}{ "txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)), @@ -813,7 +812,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk // EndBlock implements the ABCI application interface. func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { if app.deliverState.ms.TracingEnabled() { - app.deliverState.ms = app.deliverState.ms.ResetTraceContext().(sdk.CacheMultiStore) + app.deliverState.ms = app.deliverState.ms.SetTracingContext(nil).(sdk.CacheMultiStore) } if app.endBlocker != nil { diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index acccd1fbf6fe..82ee0bd9c3d7 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -7,7 +7,7 @@ import ( "os" "testing" - "github.com/cosmos/cosmos-sdk/store" + store "github.com/cosmos/cosmos-sdk/store/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/client/config.go b/client/config.go index 675a35296a62..ce57434a5717 100644 --- a/client/config.go +++ b/client/config.go @@ -9,8 +9,6 @@ import ( "github.com/tendermint/tendermint/libs/cli" - "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/pelletier/go-toml" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -32,7 +30,7 @@ func init() { // ConfigCmd returns a CLI command to interactively create a // Gaia CLI config file. -func ConfigCmd() *cobra.Command { +func ConfigCmd(defaultCLIHome string) *cobra.Command { cmd := &cobra.Command{ Use: "config [value]", Short: "Create or query a Gaia CLI configuration file", @@ -40,7 +38,7 @@ func ConfigCmd() *cobra.Command { Args: cobra.RangeArgs(0, 2), } - cmd.Flags().String(cli.HomeFlag, app.DefaultCLIHome, + cmd.Flags().String(cli.HomeFlag, defaultCLIHome, "set client's home directory for configuration") cmd.Flags().Bool(flagGet, false, "print configuration value or its default if unset") diff --git a/client/context/context.go b/client/context/context.go index 6950122c432e..a9744408d7d4 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -8,7 +8,9 @@ import ( "path/filepath" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" + cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/spf13/viper" @@ -19,13 +21,12 @@ import ( tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" - "github.com/cosmos/cosmos-sdk/client/keys" - cskeys "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) var ( - verifier tmlite.Verifier + verifier tmlite.Verifier + verifierHome string ) // CLIContext implements a typical CLI context created in SDK modules for @@ -45,10 +46,11 @@ type CLIContext struct { Async bool PrintResponse bool Verifier tmlite.Verifier + VerifierHome string Simulate bool GenerateOnly bool - fromAddress types.AccAddress - fromName string + FromAddress sdk.AccAddress + FromName string Indent bool } @@ -63,11 +65,16 @@ func NewCLIContext() CLIContext { } from := viper.GetString(client.FlagFrom) - fromAddress, fromName := fromFields(from) + fromAddress, fromName, err := GetFromFields(from) + if err != nil { + fmt.Printf("failed to get from fields: %v", err) + os.Exit(1) + } // We need to use a single verifier for all contexts - if verifier == nil { + if verifier == nil || verifierHome != viper.GetString(cli.HomeFlag) { verifier = createVerifier() + verifierHome = viper.GetString(cli.HomeFlag) } return CLIContext{ @@ -85,8 +92,8 @@ func NewCLIContext() CLIContext { Verifier: verifier, Simulate: viper.GetBool(client.FlagDryRun), GenerateOnly: viper.GetBool(client.FlagGenerateOnly), - fromAddress: fromAddress, - fromName: fromName, + FromAddress: fromAddress, + FromName: fromName, Indent: viper.GetBool(client.FlagIndentResponse), } } @@ -137,37 +144,6 @@ func createVerifier() tmlite.Verifier { return verifier } -func fromFields(from string) (fromAddr types.AccAddress, fromName string) { - if from == "" { - return nil, "" - } - - keybase, err := keys.GetKeyBase() - if err != nil { - fmt.Println("no keybase found") - os.Exit(1) - } - - var info cskeys.Info - if addr, err := types.AccAddressFromBech32(from); err == nil { - info, err = keybase.GetByAddress(addr) - if err != nil { - fmt.Printf("could not find key %s\n", from) - os.Exit(1) - } - } else { - info, err = keybase.Get(from) - if err != nil { - fmt.Printf("could not find key %s\n", from) - os.Exit(1) - } - } - - fromAddr = info.GetAddress() - fromName = info.GetName() - return -} - // WithCodec returns a copy of the context with an updated codec. func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext { ctx.Codec = cdc @@ -255,6 +231,19 @@ func (ctx CLIContext) WithSimulation(simulate bool) CLIContext { return ctx } +// WithFromName returns a copy of the context with an updated from account name. +func (ctx CLIContext) WithFromName(name string) CLIContext { + ctx.FromName = name + return ctx +} + +// WithFromAddress returns a copy of the context with an updated from account +// address. +func (ctx CLIContext) WithFromAddress(addr sdk.AccAddress) CLIContext { + ctx.FromAddress = addr + return ctx +} + // PrintOutput prints output while respecting output and indent flags // NOTE: pass in marshalled structs that have been unmarshaled // because this function will panic on marshaling errors @@ -279,3 +268,31 @@ func (ctx CLIContext) PrintOutput(toPrint fmt.Stringer) (err error) { fmt.Println(string(out)) return } + +// GetFromFields returns a from account address and Keybase name given either +// an address or key name. +func GetFromFields(from string) (sdk.AccAddress, string, error) { + if from == "" { + return nil, "", nil + } + + keybase, err := keys.GetKeyBase() + if err != nil { + return nil, "", err + } + + var info cryptokeys.Info + if addr, err := sdk.AccAddressFromBech32(from); err == nil { + info, err = keybase.GetByAddress(addr) + if err != nil { + return nil, "", err + } + } else { + info, err = keybase.Get(from) + if err != nil { + return nil, "", err + } + } + + return info.GetAddress(), info.GetName(), nil +} diff --git a/client/context/query.go b/client/context/query.go index aba8df19095e..36229ad593ad 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -10,6 +10,7 @@ import ( "strings" + "github.com/cosmos/cosmos-sdk/store/rootmulti" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" @@ -17,8 +18,6 @@ import ( tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/store" ) // GetNode returns an RPC client. If the context's client is not defined, an @@ -82,13 +81,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) { } // GetFromAddress returns the from address from the context's name. -func (ctx CLIContext) GetFromAddress() (sdk.AccAddress, error) { - return ctx.fromAddress, nil +func (ctx CLIContext) GetFromAddress() sdk.AccAddress { + return ctx.FromAddress } // GetFromName returns the key name for the current context. -func (ctx CLIContext) GetFromName() (string, error) { - return ctx.fromName, nil +func (ctx CLIContext) GetFromName() string { + return ctx.FromName } // GetAccountNumber returns the next account number for the given account @@ -116,11 +115,7 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (uint64, error) { // EnsureAccountExists ensures that an account exists for a given context. An // error is returned if it does not. func (ctx CLIContext) EnsureAccountExists() error { - addr, err := ctx.GetFromAddress() - if err != nil { - return err - } - + addr := ctx.GetFromAddress() accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore) if err != nil { return err @@ -211,7 +206,7 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err } // TODO: Instead of reconstructing, stash on CLIContext field? - prt := store.DefaultProofRuntime() + prt := rootmulti.DefaultProofRuntime() // TODO: Better convention for path? storeName, err := parseQueryStorePath(queryPath) @@ -223,6 +218,13 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) + if resp.Value == nil { + err = prt.VerifyAbsence(resp.Proof, commit.Header.AppHash, kp.String()) + if err != nil { + return errors.Wrap(err, "failed to prove merkle proof") + } + return nil + } err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value) if err != nil { return errors.Wrap(err, "failed to prove merkle proof") @@ -251,7 +253,7 @@ func isQueryStoreWithProof(path string) bool { return false case paths[0] != "store": return false - case store.RequireProof("/" + paths[2]): + case rootmulti.RequireProof("/" + paths[2]): return true } diff --git a/client/errors.go b/client/errors.go new file mode 100644 index 000000000000..d01d574292fd --- /dev/null +++ b/client/errors.go @@ -0,0 +1,9 @@ +package client + +import "errors" + +// common errors for CLI and REST clients +var ( + ErrInvalidGasAdjustment = errors.New("invalid gas adjustment") + ErrInvalidSigner = errors.New("tx intended signer does not match the given signer") +) diff --git a/client/flags.go b/client/flags.go index 9ee121c2096c..31feacf6cdc4 100644 --- a/client/flags.go +++ b/client/flags.go @@ -40,7 +40,7 @@ const ( FlagListenAddr = "laddr" FlagCORS = "cors" FlagMaxOpenConnections = "max-open" - FlagInsecure = "insecure" + FlagTLS = "tls" FlagSSLHosts = "ssl-hosts" FlagSSLCertFile = "ssl-certfile" FlagSSLKeyFile = "ssl-keyfile" @@ -103,21 +103,15 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { // RegisterRestServerFlags registers the flags required for rest server func RegisterRestServerFlags(cmd *cobra.Command) *cobra.Command { + cmd = GetCommands(cmd)[0] cmd.Flags().String(FlagListenAddr, "tcp://localhost:1317", "The address for the server to listen on") - cmd.Flags().Bool(FlagInsecure, false, "Do not set up SSL/TLS layer") + cmd.Flags().Bool(FlagTLS, false, "Enable SSL/TLS layer") cmd.Flags().String(FlagSSLHosts, "", "Comma-separated hostnames and IPs to generate a certificate for") cmd.Flags().String(FlagSSLCertFile, "", "Path to a SSL certificate file. If not supplied, a self-signed certificate will be generated.") cmd.Flags().String(FlagSSLKeyFile, "", "Path to a key file; ignored if a certificate file is not supplied.") cmd.Flags().String(FlagCORS, "", "Set the domains that can make CORS requests (* for all)") - cmd.Flags().String(FlagChainID, "", "Chain ID of Tendermint node") - cmd.Flags().String(FlagNode, "tcp://localhost:26657", "Address of the node to connect to") cmd.Flags().Int(FlagMaxOpenConnections, 1000, "The number of maximum open connections") - cmd.Flags().Bool(FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") - cmd.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response") - viper.BindPFlag(FlagTrustNode, cmd.Flags().Lookup(FlagTrustNode)) - viper.BindPFlag(FlagChainID, cmd.Flags().Lookup(FlagChainID)) - viper.BindPFlag(FlagNode, cmd.Flags().Lookup(FlagNode)) return cmd } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 0d680ada44ba..c531a00404ac 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -2,7 +2,6 @@ package lcd import ( "encoding/hex" - "encoding/json" "fmt" "net/http" "os" @@ -15,17 +14,18 @@ import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" - client "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/rest" "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" - tests "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" "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/bank" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" @@ -211,16 +211,17 @@ func TestCoinSend(t *testing.T) { // run simulation and test success with estimated gas res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, "10000", 1.0, true, false, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) - var responseBody struct { - GasEstimate int64 `json:"gas_estimate"` - } - require.Nil(t, json.Unmarshal([]byte(body), &responseBody)) + + var gasEstResp rest.GasEstimateResponse + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &gasEstResp)) + require.NotZero(t, gasEstResp.GasEstimate) acc = getAccount(t, port, addr) require.Equal(t, expectedBalance.Amount, acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom)) - res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, - fmt.Sprintf("%d", responseBody.GasEstimate), 1.0, false, false, fees) + // run successful tx + gas := fmt.Sprintf("%d", gasEstResp.GasEstimate) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, pw, addr, gas, 1.0, false, false, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultTx) @@ -235,15 +236,67 @@ func TestCoinSend(t *testing.T) { require.Equal(t, expectedBalance.Amount.SubRaw(1), acc.GetCoins().AmountOf(stakingTypes.DefaultBondDenom)) } +func TestCoinSendAccAuto(t *testing.T) { + addr, seed := CreateAddr(t, name1, pw, GetKeyBase(t)) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // send a transfer tx without specifying account number and sequence + res, body, _ := doTransferWithGasAccAuto(t, port, seed, name1, memo, pw, "200000", 1.0, false, false, fees) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + // query sender + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + expectedBalance := initialBalance[0].Minus(fees[0]) + + require.Equal(t, stakingTypes.DefaultBondDenom, coins[0].Denom) + require.Equal(t, expectedBalance.Amount.SubRaw(1), coins[0].Amount) +} + +func TestCoinSendGenerateOnly(t *testing.T) { + addr, seed := CreateAddr(t, name1, pw, GetKeyBase(t)) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // generate only + res, body, _ := doTransferWithGas(t, port, seed, "", memo, "", addr, "200000", 1, false, true, fees) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var stdTx auth.StdTx + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &stdTx)) + require.Equal(t, len(stdTx.Msgs), 1) + require.Equal(t, stdTx.GetMsgs()[0].Route(), "bank") + require.Equal(t, stdTx.GetMsgs()[0].GetSigners(), []sdk.AccAddress{addr}) + require.Equal(t, 0, len(stdTx.Signatures)) + require.Equal(t, memo, stdTx.Memo) + require.NotZero(t, stdTx.Fee.Gas) + require.IsType(t, stdTx.GetMsgs()[0], bank.MsgSend{}) + require.Equal(t, addr, stdTx.GetMsgs()[0].(bank.MsgSend).Inputs[0].Address) +} + func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { addr, seed := CreateAddr(t, name1, pw, GetKeyBase(t)) cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) defer cleanup() acc := getAccount(t, port, addr) - // generate TX - res, body, _ := doTransferWithGas(t, port, seed, name1, memo, "", addr, client.GasFlagAuto, 1, false, true, fees) + // simulate tx + res, body, _ := doTransferWithGas(t, port, seed, name1, memo, "", addr, client.GasFlagAuto, 1, true, false, fees) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var gasEstResp rest.GasEstimateResponse + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &gasEstResp)) + require.NotZero(t, gasEstResp.GasEstimate) + + // generate tx + gas := fmt.Sprintf("%d", gasEstResp.GasEstimate) + res, body, _ = doTransferWithGas(t, port, seed, name1, memo, "", addr, gas, 1, false, true, fees) require.Equal(t, http.StatusOK, res.StatusCode, body) + var msg auth.StdTx require.Nil(t, cdc.UnmarshalJSON([]byte(body), &msg)) require.Equal(t, len(msg.Msgs), 1) @@ -251,6 +304,7 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { require.Equal(t, msg.Msgs[0].GetSigners(), []sdk.AccAddress{addr}) require.Equal(t, 0, len(msg.Signatures)) require.Equal(t, memo, msg.Memo) + require.NotZero(t, msg.Fee.Gas) gasEstimate := int64(msg.Fee.Gas) accnum := acc.GetAccountNumber() @@ -261,13 +315,14 @@ func TestCoinSendGenerateSignAndBroadcast(t *testing.T) { payload := authrest.SignBody{ Tx: msg, - BaseReq: utils.NewBaseReq( + BaseReq: rest.NewBaseReq( name1, pw, "", viper.GetString(client.FlagChainID), "", "", accnum, sequence, nil, nil, false, false, ), } json, err := cdc.MarshalJSON(payload) require.Nil(t, err) + res, body = Request(t, port, "POST", "/tx/sign", json) require.Equal(t, http.StatusOK, res.StatusCode, body) require.Nil(t, cdc.UnmarshalJSON([]byte(body), &signedMsg)) diff --git a/client/lcd/root.go b/client/lcd/root.go index 07aade96b332..7021149f2e88 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -70,7 +70,7 @@ func (rs *RestServer) setKeybase(kb keybase.Keybase) { // Start starts the rest server func (rs *RestServer) Start(listenAddr string, sslHosts string, - certFile string, keyFile string, maxOpen int, insecure bool) (err error) { + certFile string, keyFile string, maxOpen int, secure bool) (err error) { server.TrapSignal(func() { err := rs.listener.Close() @@ -84,10 +84,11 @@ func (rs *RestServer) Start(listenAddr string, sslHosts string, if err != nil { return } - rs.log.Info("Starting Gaia Lite REST service...") + rs.log.Info(fmt.Sprintf("Starting Gaia Lite REST service (chain-id: %q)...", + viper.GetString(client.FlagChainID))) // launch rest-server in insecure mode - if insecure { + if !secure { return rpcserver.StartHTTPServer(rs.listener, rs.Mux, rs.log) } @@ -145,15 +146,13 @@ func ServeCommand(cdc *codec.Codec, registerRoutesFn func(*RestServer)) *cobra.C viper.GetString(client.FlagSSLCertFile), viper.GetString(client.FlagSSLKeyFile), viper.GetInt(client.FlagMaxOpenConnections), - viper.GetBool(client.FlagInsecure)) + viper.GetBool(client.FlagTLS)) return err }, } - client.RegisterRestServerFlags(cmd) - - return cmd + return client.RegisterRestServerFlags(cmd) } func (rs *RestServer) registerSwaggerUI() { diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index eed9d7e27691..363345917aac 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -1,28 +1,28 @@ --- -swagger: '2.0' +swagger: "2.0" info: version: "3.0" title: Gaia-Lite for Cosmos description: A REST interface for state queries, transaction generation, signing, and broadcast. tags: -- name: ICS0 - description: Tendermint APIs, such as query blocks, transactions and validatorset -- name: ICS1 - description: Key management APIs -- name: ICS20 - description: Create, sign and broadcast transactions -- name: ICS21 - description: Stake module APIs -- name: ICS22 - description: Governance module APIs -- name: ICS23 - description: Slashing module APIs -- name: ICS24 - description: WIP - Fee distribution module APIs -- name: version - description: Query app version + - name: ICS0 + description: Tendermint APIs, such as query blocks, transactions and validatorset + - name: ICS1 + description: Key management APIs + - name: ICS20 + description: Create, sign and broadcast transactions + - name: ICS21 + description: Stake module APIs + - name: ICS22 + description: Governance module APIs + - name: ICS23 + description: Slashing module APIs + - name: ICS24 + description: WIP - Fee distribution module APIs + - name: version + description: Query app version schemes: -- https + - https host: fabo.interblock.io:1317 securityDefinitions: kms: @@ -32,7 +32,7 @@ paths: get: summary: Version of Gaia-lite tags: - - version + - version description: Get the version of gaia-lite running locally to compare against expected responses: 200: @@ -41,7 +41,7 @@ paths: get: summary: Version of the connected node tags: - - version + - version description: Get the version of the SDK running on the connected node to compare against expected responses: 200: @@ -53,9 +53,9 @@ paths: description: Information about the connected node summary: The properties of the connected node tags: - - ICS0 + - ICS0 produces: - - application/json + - application/json responses: 200: description: Node status @@ -90,7 +90,7 @@ paths: get: summary: Syncing state of node tags: - - ICS0 + - ICS0 description: Get if the node is currently syning with other nodes responses: 200: @@ -101,9 +101,9 @@ paths: get: summary: Get the latest block tags: - - ICS0 + - ICS0 produces: - - application/json + - application/json responses: 200: description: The latest block @@ -115,15 +115,15 @@ paths: get: summary: Get a block at a certain height tags: - - ICS0 + - ICS0 produces: - - application/json + - application/json parameters: - - in: path - name: height - description: Block height - required: true - type: number + - in: path + name: height + description: Block height + required: true + type: number responses: 200: description: The block at a specific height @@ -139,9 +139,9 @@ paths: get: summary: Get the latest validator set tags: - - ICS0 + - ICS0 produces: - - application/json + - application/json responses: 200: description: The validator set at the latest block height @@ -160,15 +160,15 @@ paths: get: summary: Get a validator set a certain height tags: - - ICS0 + - ICS0 produces: - - application/json + - application/json parameters: - - in: path - name: height - description: Block height - required: true - type: number + - in: path + name: height + description: Block height + required: true + type: number responses: 200: description: The validator set at a specific block height @@ -191,15 +191,15 @@ paths: get: summary: Get a Tx by hash tags: - - ICS0 + - ICS0 produces: - - application/json + - application/json parameters: - - in: path - name: hash - description: Tx hash - required: true - type: string + - in: path + name: hash + description: Tx hash + required: true + type: string responses: 200: description: Tx with the provided hash @@ -210,25 +210,25 @@ paths: /txs: get: tags: - - ICS0 + - ICS0 summary: Search transactions description: Search transactions by tag(s). produces: - - application/json + - application/json parameters: - - in: query - name: tag - type: string - description: "transaction tags such as 'action=submit-proposal' and 'proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc' which results in the following endpoint: 'GET /txs?action=submit-proposal&proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc'" - required: true - - in: query - name: page - description: Pagination page - type: integer - - in: query - name: size - description: Pagination size - type: integer + - in: query + name: tag + type: string + description: "transaction tags such as 'action=submit-proposal' and 'proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc' which results in the following endpoint: 'GET /txs?action=submit-proposal&proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc'" + required: true + - in: query + name: page + description: Pagination page + type: integer + - in: query + name: size + description: Pagination size + type: integer responses: 200: description: All txs matching the provided tags @@ -242,26 +242,26 @@ paths: description: Internal Server Error post: tags: - - ICS0 + - ICS0 summary: broadcast Tx description: broadcast tx with tendermint rpc consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: body - name: txBroadcast - description: Build a StdTx transaction and serilize it to a byte array with amino, then the `"tx"` field in the post body will be the base64 encoding of the byte array. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away). - required: true - schema: - type: object - properties: - tx: - type: string - return: - type: string - example: block + - in: body + name: txBroadcast + description: Build a StdTx transaction and serilize it to a byte array with amino, then the `"tx"` field in the post body will be the base64 encoding of the byte array. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away). + required: true + schema: + type: object + properties: + tx: + type: string + return: + type: string + example: block responses: 200: description: Broadcast tx result @@ -272,28 +272,28 @@ paths: /tx/sign: post: tags: - - ICS20 + - ICS20 summary: Sign a Tx description: Sign a Tx providing locally stored account and according password consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: body - name: sendToken - description: sign tx - required: true - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - tx: - $ref: "#/definitions/StdTx" - append_sig: - type: boolean - example: true + - in: body + name: sendToken + description: sign tx + required: true + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + tx: + $ref: "#/definitions/StdTx" + append_sig: + type: boolean + example: true responses: 200: description: The signed Tx @@ -308,23 +308,23 @@ paths: /tx/broadcast: post: tags: - - ICS20 + - ICS20 summary: Send a signed Tx description: Send a signed Tx to a Gaiad full node consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: body - name: txBroadcast - description: broadcast tx - required: true - schema: - type: object - properties: - tx: - $ref: "#/definitions/StdTx" + - in: body + name: txBroadcast + description: broadcast tx + required: true + schema: + type: object + properties: + tx: + $ref: "#/definitions/StdTx" responses: 202: description: Tx was send and will probably be added to the next block @@ -338,15 +338,15 @@ paths: get: summary: Get the account balances tags: - - ICS20 + - ICS20 produces: - - application/json + - application/json parameters: - - in: path - name: address - description: Account address in bech32 format - required: true - type: string + - in: path + name: address + description: Account address in bech32 format + required: true + type: string responses: 200: description: Account balances @@ -363,30 +363,30 @@ paths: summary: Send coins (build -> sign -> send) description: Send coins (build -> sign -> send) tags: - - ICS20 + - ICS20 consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: path - name: address - description: Account address in bech32 format - required: true - type: string - - in: body - name: account - description: The password of the account to remove from the KMS - required: true - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - amount: - type: array - items: - $ref: "#/definitions/Coin" + - in: path + name: address + description: Account address in bech32 format + required: true + type: string + - in: body + name: account + description: The password of the account to remove from the KMS + required: true + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + amount: + type: array + items: + $ref: "#/definitions/Coin" responses: 202: description: Tx was send and will probably be added to the next block @@ -402,43 +402,43 @@ paths: get: summary: List of accounts stored locally tags: - - ICS1 + - ICS1 produces: - - application/json + - application/json responses: 200: description: Array of accounts schema: type: array items: - $ref: '#/definitions/KeyOutput' + $ref: "#/definitions/KeyOutput" 500: description: Server internal error post: summary: Create a new account locally tags: - - ICS1 + - ICS1 consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: body - name: account - description: The account to create - schema: - type: object - required: - - name - - password - - seed - properties: - name: - type: string - password: - type: string - seed: - type: string + - in: body + name: account + description: The account to create + schema: + type: object + required: + - name + - password + - seed + properties: + name: + type: string + password: + type: string + seed: + type: string responses: 200: description: Returns account information of the created key @@ -454,7 +454,7 @@ paths: get: summary: Create a new seed to create a new account with tags: - - ICS1 + - ICS1 responses: 200: description: 24 word Seed @@ -465,30 +465,30 @@ paths: post: summary: Recover a account from a seed tags: - - ICS1 + - ICS1 consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: path - name: name - description: Account name - required: true - type: string - - in: body - name: pwdAndSeed - description: Provide password and seed to recover a key - schema: - type: object - required: - - password - - seed - properties: - password: - type: string - seed: - type: string + - in: path + name: name + description: Account name + required: true + type: string + - in: body + name: pwdAndSeed + description: Provide password and seed to recover a key + schema: + type: object + required: + - password + - seed + properties: + password: + type: string + seed: + type: string responses: 200: description: Returns account information of the recovered key @@ -502,17 +502,17 @@ paths: description: Server internal error /keys/{name}: parameters: - - in: path - name: name - description: Account name - required: true - type: string + - in: path + name: name + description: Account name + required: true + type: string get: summary: Get a certain locally stored account tags: - - ICS1 + - ICS1 produces: - - application/json + - application/json responses: 200: description: Locally stored account @@ -523,23 +523,23 @@ paths: put: summary: Update the password for this account in the KMS tags: - - ICS1 + - ICS1 consumes: - - application/json + - application/json parameters: - - in: body - name: account - description: The new and old password - schema: - type: object - required: - - new_password - - old_password - properties: - new_password: - type: string - old_password: - type: string + - in: body + name: account + description: The new and old password + schema: + type: object + required: + - new_password + - old_password + properties: + new_password: + type: string + old_password: + type: string responses: 200: description: Updated password @@ -550,20 +550,20 @@ paths: delete: summary: Remove an account tags: - - ICS1 + - ICS1 consumes: - - application/json + - application/json parameters: - - in: body - name: account - description: The password of the account to remove from the KMS - schema: - type: object - required: - - password - properties: - password: - type: string + - in: body + name: account + description: The password of the account to remove from the KMS + schema: + type: object + required: + - password + properties: + password: + type: string responses: 200: description: Removed account @@ -575,15 +575,15 @@ paths: get: summary: Get the account information on blockchain tags: - - ICS1 + - ICS1 produces: - - application/json + - application/json parameters: - - in: path - name: address - description: Account address - required: true - type: string + - in: path + name: address + description: Account address + required: true + type: string responses: 200: description: Account information on the blockchain @@ -613,17 +613,17 @@ paths: description: Server internel error /staking/delegators/{delegatorAddr}/delegations: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string get: summary: Get all delegations from a delegator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -638,26 +638,26 @@ paths: post: summary: Submit delegation parameters: - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - delegator_addr: - $ref: "#/definitions/Address" - validator_addr: - $ref: "#/definitions/ValidatorAddress" - delegation: - $ref: "#/definitions/Coin" + - in: body + name: delegation + description: The password of the account to remove from the KMS + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + delegator_addr: + $ref: "#/definitions/Address" + validator_addr: + $ref: "#/definitions/ValidatorAddress" + delegation: + $ref: "#/definitions/Coin" tags: - - ICS21 + - ICS21 consumes: - - application/json + - application/json produces: - - application/json + - application/json responses: 200: description: OK @@ -671,22 +671,22 @@ paths: description: Internal Server Error /staking/delegators/{delegatorAddr}/delegations/{validatorAddr}: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string get: summary: Query the current delegation between a delegator and a validator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -698,17 +698,17 @@ paths: description: Internal Server Error /staking/delegators/{delegatorAddr}/unbonding_delegations: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string get: summary: Get all unbonding delegations from a delegator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -723,37 +723,27 @@ paths: post: summary: Submit an unbonding delegation parameters: - - in: query - name: simulate - description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it - required: false - type: boolean - - in: query - name: generate_only - description: if true, build an unsigned transaction and write it back - required: false - type: boolean - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - delegator_addr: - $ref: "#/definitions/Address" - validator_addr: - $ref: "#/definitions/ValidatorAddress" - shares: - type: string - example: "100" - tags: - - ICS21 + - in: body + name: delegation + description: The password of the account to remove from the KMS + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + delegator_addr: + $ref: "#/definitions/Address" + validator_addr: + $ref: "#/definitions/ValidatorAddress" + shares: + type: string + example: "100" + tags: + - ICS21 consumes: - - application/json + - application/json produces: - - application/json + - application/json responses: 200: description: OK @@ -767,22 +757,22 @@ paths: description: Internal Server Error /staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string get: summary: Query all unbonding delegations between a delegator and a validator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -796,27 +786,27 @@ paths: description: Internal Server Error /staking/redelegations: parameters: - - in: query - name: delegator - description: Bech32 AccAddress of Delegator - required: false - type: string - - in: query - name: validator_from - description: Bech32 ValAddress of SrcValidator - required: false - type: string - - in: query - name: validator_to - description: Bech32 ValAddress of DstValidator - required: false - type: string + - in: query + name: delegator + description: Bech32 AccAddress of Delegator + required: false + type: string + - in: query + name: validator_from + description: Bech32 ValAddress of SrcValidator + required: false + type: string + - in: query + name: validator_to + description: Bech32 ValAddress of DstValidator + required: false + type: string get: summary: Get all redelegations (filter by query params) tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -828,41 +818,31 @@ paths: description: Internal Server Error /staking/delegators/{delegatorAddr}/redelegations: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string post: summary: Submit a redelegation parameters: - - in: query - name: simulate - description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it - required: false - type: boolean - - in: query - name: generate_only - description: if true, build an unsigned transaction and write it back - required: false - type: boolean - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - delegator_addr: - $ref: "#/definitions/Address" - validator_src_addr: - $ref: "#/definitions/ValidatorAddress" - validator_dst_addr: - $ref: "#/definitions/ValidatorAddress" - shares: - type: string - example: "100" + - in: body + name: delegation + description: The password of the account to remove from the KMS + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + delegator_addr: + $ref: "#/definitions/Address" + validator_src_addr: + $ref: "#/definitions/ValidatorAddress" + validator_dst_addr: + $ref: "#/definitions/ValidatorAddress" + shares: + type: string + example: "100" tags: - ICS21 consumes: @@ -882,17 +862,17 @@ paths: description: Internal Server Error /staking/delegators/{delegatorAddr}/validators: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string get: summary: Query all validators that a delegator is bonded to tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -906,22 +886,22 @@ paths: description: Internal Server Error /staking/delegators/{delegatorAddr}/validators/{validatorAddr}: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - - in: path - name: validatorAddr - description: Bech32 ValAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 ValAddress of Delegator + required: true + type: string get: summary: Query a validator that a delegator is bonded to tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -933,17 +913,17 @@ paths: description: Internal Server Error /staking/delegators/{delegatorAddr}/txs: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string get: summary: Get all staking txs (i.e msgs) from a delegator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -961,9 +941,9 @@ paths: get: summary: Get all validator candidates tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -975,17 +955,17 @@ paths: description: Internal Server Error /staking/validators/{validatorAddr}: parameters: - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string get: summary: Query the information from a single validator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -997,17 +977,17 @@ paths: description: Internal Server Error /staking/validators/{validatorAddr}/delegations: parameters: - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string get: summary: Get all delegations from a validator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -1021,17 +1001,17 @@ paths: description: Internal Server Error /staking/validators/{validatorAddr}/unbonding_delegations: parameters: - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string get: summary: Get all unbonding delegations from a validator tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -1047,9 +1027,9 @@ paths: get: summary: Get the current state of the staking pool tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -1074,9 +1054,9 @@ paths: get: summary: Get the current staking parameter values tags: - - ICS21 + - ICS21 produces: - - application/json + - application/json responses: 200: description: OK @@ -1104,15 +1084,15 @@ paths: summary: Get sign info of given validator description: Get sign info of given validator produces: - - application/json + - application/json tags: - - ICS23 + - ICS23 parameters: - - type: string - description: Bech32 validator public key - name: validatorPubKey - required: true - in: path + - type: string + description: Bech32 validator public key + name: validatorPubKey + required: true + in: path responses: 200: description: OK @@ -1138,26 +1118,26 @@ paths: summary: Unjail a jailed validator description: Send transaction to unjail a jailed validator consumes: - - application/json + - application/json produces: - - application/json + - application/json tags: - - ICS23 + - ICS23 parameters: - - type: string - description: Bech32 validator address - name: validatorAddr - required: true - in: path - - description: '' - name: UnjailBody - in: body - required: true - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" + - type: string + description: Bech32 validator address + name: validatorAddr + required: true + in: path + - description: "" + name: UnjailBody + in: body + required: true + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" responses: 200: description: OK @@ -1173,9 +1153,9 @@ paths: get: summary: Get the current slashing parameters tags: - - ICS23 + - ICS23 produces: - - application/json + - application/json responses: 200: description: OK @@ -1203,34 +1183,34 @@ paths: summary: Submit a proposal description: Send transaction to submit a proposal consumes: - - application/json + - application/json produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - description: valid value of `"proposal_type"` can be `"text"`, `"parameter_change"`, `"software_upgrade"` - name: post_proposal_body - in: body - required: true - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - title: - type: string - description: - type: string - proposal_type: - type: string - example: "text" - proposer: - $ref: "#/definitions/Address" - initial_deposit: - type: array - items: - $ref: "#/definitions/Coin" + - description: valid value of `"proposal_type"` can be `"text"`, `"parameter_change"`, `"software_upgrade"` + name: post_proposal_body + in: body + required: true + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + title: + type: string + description: + type: string + proposal_type: + type: string + example: "text" + proposer: + $ref: "#/definitions/Address" + initial_deposit: + type: array + items: + $ref: "#/definitions/Coin" responses: 200: description: OK @@ -1246,25 +1226,25 @@ paths: summary: Query proposals description: Query proposals information with parameters produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - in: query - name: voter - description: voter address - required: false - type: string - - in: query - name: depositor - description: depositor address - required: false - type: string - - in: query - name: status - description: proposal status, valid values can be `"deposit_period"`, `"voting_period"`, `"passed"`, `"rejected"` - required: false - type: string + - in: query + name: voter + description: voter address + required: false + type: string + - in: query + name: depositor + description: depositor address + required: false + type: string + - in: query + name: status + description: proposal status, valid values can be `"deposit_period"`, `"voting_period"`, `"passed"`, `"rejected"` + required: false + type: string responses: 200: description: OK @@ -1281,14 +1261,14 @@ paths: summary: Query a proposal description: Query a proposal by id produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - name: proposalId - required: true - in: path + - type: string + name: proposalId + required: true + in: path responses: 200: description: OK @@ -1303,14 +1283,14 @@ paths: summary: Query proposer description: Query for the proposer for a proposal produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - name: proposalId - required: true - in: path + - type: string + name: proposalId + required: true + in: path responses: 200: description: OK @@ -1325,14 +1305,14 @@ paths: summary: Query deposits description: Query deposits by proposalId produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - name: proposalId - required: true - in: path + - type: string + name: proposalId + required: true + in: path responses: 200: description: OK @@ -1348,32 +1328,32 @@ paths: summary: Deposit tokens to a proposal description: Send transaction to deposit tokens to a proposal consumes: - - application/json + - application/json produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path - - description: '' - name: post_deposit_body - in: body - required: true - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - depositor: - $ref: "#/definitions/Address" - amount: - type: array - items: - $ref: "#/definitions/Coin" + - type: string + description: proposal id + name: proposalId + required: true + in: path + - description: "" + name: post_deposit_body + in: body + required: true + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + depositor: + $ref: "#/definitions/Address" + amount: + type: array + items: + $ref: "#/definitions/Coin" responses: 200: description: OK @@ -1390,20 +1370,20 @@ paths: summary: Query deposit description: Query deposit by proposalId and depositor address produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path - - type: string - description: Bech32 depositor address - name: depositor - required: true - in: path + - type: string + description: proposal id + name: proposalId + required: true + in: path + - type: string + description: Bech32 depositor address + name: depositor + required: true + in: path responses: 200: description: OK @@ -1420,15 +1400,15 @@ paths: summary: Query voters description: Query voters information by proposalId produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path + - type: string + description: proposal id + name: proposalId + required: true + in: path responses: 200: description: OK @@ -1444,31 +1424,31 @@ paths: summary: Vote a proposal description: Send transaction to vote a proposal consumes: - - application/json + - application/json produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path - - description: valid value of `"option"` field can be `"yes"`, `"no"`, `"no_with_veto"` and `"abstain"` - name: post_vote_body - in: body - required: true - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - voter: - $ref: "#/definitions/Address" - option: - type: string - example: "yes" + - type: string + description: proposal id + name: proposalId + required: true + in: path + - description: valid value of `"option"` field can be `"yes"`, `"no"`, `"no_with_veto"` and `"abstain"` + name: post_vote_body + in: body + required: true + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + voter: + $ref: "#/definitions/Address" + option: + type: string + example: "yes" responses: 200: description: OK @@ -1485,20 +1465,20 @@ paths: summary: Query vote description: Query vote information by proposal Id and voter address produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path - - type: string - description: Bech32 voter address - name: voter - required: true - in: path + - type: string + description: proposal id + name: proposalId + required: true + in: path + - type: string + description: Bech32 voter address + name: voter + required: true + in: path responses: 200: description: OK @@ -1515,15 +1495,15 @@ paths: summary: Get a proposal's tally result at the current time description: Gets a proposal's tally result at the current time. If the proposal is pending deposits (i.e status 'DepositPeriod') it returns an empty tally result. produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path + - type: string + description: proposal id + name: proposalId + required: true + in: path responses: 200: description: OK @@ -1538,9 +1518,9 @@ paths: summary: Query governance deposit parameters description: Query governance deposit parameters. The max_deposit_period units are in nanoseconds. produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 responses: 200: description: OK @@ -1565,9 +1545,9 @@ paths: summary: Query governance tally parameters description: Query governance tally parameters produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 responses: 200: description: OK @@ -1593,9 +1573,9 @@ paths: summary: Query governance voting parameters description: Query governance voting parameters. The voting_period units are in nanoseconds. produces: - - application/json + - application/json tags: - - ICS22 + - ICS22 responses: 200: description: OK @@ -1612,18 +1592,18 @@ paths: description: Internal Server Error /distribution/delegators/{delegatorAddr}/rewards: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string get: summary: Get the total rewards balance from all delegations description: Get the sum of all the rewards earned by delegations by a single delegator produces: - - application/json + - application/json tags: - - ICS24 + - ICS24 responses: 200: description: OK @@ -1639,28 +1619,18 @@ paths: summary: Withdraw all the delegator's delegation rewards description: Withdraw all the delegator's delegation rewards tags: - - ICS24 + - ICS24 consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: query - name: simulate - description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it - required: false - type: boolean - - in: query - name: generate_only - description: if true, build an unsigned transaction and write it back - required: false - type: boolean - - in: body - name: Withdraw request body - schema: - properties: - base_req: - $ref: "#/definitions/BaseReq" + - in: body + name: Withdraw request body + schema: + properties: + base_req: + $ref: "#/definitions/BaseReq" responses: 200: description: OK @@ -1674,23 +1644,23 @@ paths: description: Internal Server Error /distribution/delegators/{delegatorAddr}/rewards/{validatorAddr}: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string get: summary: Query a delegation reward description: Query a single delegation reward by a delegator tags: - - ICS24 + - ICS24 produces: - - application/json + - application/json responses: 200: description: OK @@ -1706,28 +1676,18 @@ paths: summary: Withdraw a delegation reward description: Withdraw a delegator's delegation reward from a single validator tags: - - ICS24 + - ICS24 consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: query - name: simulate - description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it - required: false - type: boolean - - in: query - name: generate_only - description: if true, build an unsigned transaction and write it back - required: false - type: boolean - - in: body - name: Withdraw request body - schema: - properties: - base_req: - $ref: "#/definitions/BaseReq" + - in: body + name: Withdraw request body + schema: + properties: + base_req: + $ref: "#/definitions/BaseReq" responses: 200: description: OK @@ -1741,18 +1701,18 @@ paths: description: Internal Server Error /distribution/delegators/{delegatorAddr}/withdraw_address: parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string get: summary: Get the rewards withdrawal address description: Get the delegations' rewards withdrawal address. This is the address in which the user will receive the reward funds tags: - - ICS24 + - ICS24 produces: - - application/json + - application/json responses: 200: description: OK @@ -1766,30 +1726,20 @@ paths: summary: Replace the rewards withdrawal address description: Replace the delegations' rewards withdrawal address for a new one. tags: - - ICS24 + - ICS24 consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: query - name: simulate - description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it - required: false - type: boolean - - in: query - name: generate_only - description: if true, build an unsigned transaction and write it back - required: false - type: boolean - - in: body - name: Withdraw request body - schema: - properties: - base_req: - $ref: "#/definitions/BaseReq" - withdraw_address: - $ref: "#/definitions/Address" + - in: body + name: Withdraw request body + schema: + properties: + base_req: + $ref: "#/definitions/BaseReq" + withdraw_address: + $ref: "#/definitions/Address" responses: 200: description: OK @@ -1812,9 +1762,9 @@ paths: summary: Validator distribution information description: Query the distribution information of a single validator tags: - - ICS24 + - ICS24 produces: - - application/json + - application/json responses: 200: description: OK @@ -1835,9 +1785,9 @@ paths: summary: Commission and self-delegation rewards of a single a validator description: Query the commission and self-delegation rewards of a validator. tags: - - ICS24 + - ICS24 produces: - - application/json + - application/json responses: 200: description: OK @@ -1853,28 +1803,18 @@ paths: summary: Withdraw the validator's rewards description: Withdraw the validator's self-delegation and commissions rewards tags: - - ICS24 + - ICS24 consumes: - - application/json + - application/json produces: - - application/json + - application/json parameters: - - in: query - name: simulate - description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it - required: false - type: boolean - - in: query - name: generate_only - description: if true, build an unsigned transaction and write it back - required: false - type: boolean - - in: body - name: Withdraw request body - schema: - properties: - base_req: - $ref: "#/definitions/BaseReq" + - in: body + name: Withdraw request body + schema: + properties: + base_req: + $ref: "#/definitions/BaseReq" responses: 200: description: OK @@ -1890,9 +1830,9 @@ paths: get: summary: Fee distribution parameters tags: - - ICS24 + - ICS24 produces: - - application/json + - application/json responses: 200: description: OK @@ -1910,9 +1850,9 @@ paths: get: summary: Fee distribution pool tags: - - ICS24 + - ICS24 produces: - - application/json + - application/json responses: 200: description: OK @@ -1948,8 +1888,8 @@ definitions: gas_wanted: 10000 info: info tags: - - '' - - '' + - "" + - "" DeliverTxResult: type: object properties: @@ -1977,8 +1917,8 @@ definitions: gas_wanted: 10000 info: info tags: - - '' - - '' + - "" + - "" BroadcastTxCommitResult: type: object properties: @@ -2123,7 +2063,7 @@ definitions: example: 1 time: type: string - example: '2017-12-30T05:53:09.287+01:00' + example: "2017-12-30T05:53:09.287+01:00" num_txs: type: number example: 0 @@ -2186,7 +2126,7 @@ definitions: example: "0" timestamp: type: string - example: '2017-12-30T05:53:09.287+01:00' + example: "2017-12-30T05:53:09.287+01:00" type: type: number example: 2 @@ -2194,7 +2134,7 @@ definitions: $ref: "#/definitions/BlockID" signature: type: string - example: '7uTC74QlknqYWEwg7Vn6M8Om7FuZ0EO4bjvuj6rwH1mTUJrRuMMZvAAqT9VjNgP0RA/TDp6u/92AqrZfXJSpBQ==' + example: "7uTC74QlknqYWEwg7Vn6M8Om7FuZ0EO4bjvuj6rwH1mTUJrRuMMZvAAqT9VjNgP0RA/TDp6u/92AqrZfXJSpBQ==" BlockQuery: type: object properties: @@ -2210,9 +2150,10 @@ definitions: BaseReq: type: object properties: - name: + from: type: string - example: "my_name" + example: "cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc" + description: Sender address or Keybase name to generate a transaction password: type: string example: "12345678" @@ -2238,21 +2179,19 @@ definitions: type: array items: $ref: "#/definitions/Coin" - gas_prices: - type: array - items: - $ref: "#/definitions/DecCoin" generate_only: type: boolean example: false + description: Create a JSON transaction that can be signed client side instead of actually signing and broadcasting simulate: type: boolean - example: true + example: false + description: Estimate gas for a transaction (cannot be used in conjunction with generate_only) TendermintValidator: type: object properties: address: - $ref: '#/definitions/ValidatorAddress' + $ref: "#/definitions/ValidatorAddress" pub_key: type: string example: cosmosvalconspub1zcjduepq7sjfglw7ra4mjxpw4ph7dtdhdheh7nz8dfgl6t8u2n5szuuql9mqsrwquu @@ -2276,7 +2215,7 @@ definitions: proposal_status: type: string final_tally_result: - $ref: "#/definitions/TallyResult" + $ref: "#/definitions/TallyResult" submit_time: type: string total_deposit: @@ -2331,7 +2270,7 @@ definitions: type: object properties: operator_address: - $ref: '#/definitions/ValidatorAddress' + $ref: "#/definitions/ValidatorAddress" consensus_pubkey: type: string example: cosmosvalconspub1zcjduepq7sjfglw7ra4mjxpw4ph7dtdhdheh7nz8dfgl6t8u2n5szuuql9mqsrwquu @@ -2356,31 +2295,31 @@ definitions: type: string bond_height: type: string - example: '0' + example: "0" bond_intra_tx_counter: type: integer example: 0 unbonding_height: type: string - example: '0' + example: "0" unbonding_time: type: string - example: '1970-01-01T00:00:00Z' + example: "1970-01-01T00:00:00Z" commission: type: object properties: rate: type: string - example: '0' + example: "0" max_rate: type: string - example: '0' + example: "0" max_change_rate: type: string - example: '0' + example: "0" update_time: type: string - example: '1970-01-01T00:00:00Z' + example: "1970-01-01T00:00:00Z" Delegation: type: object properties: @@ -2434,13 +2373,13 @@ definitions: community_pool: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" val_accum: - $ref: '#/definitions/TotalAccum' + $ref: "#/definitions/TotalAccum" val_pool: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" TotalAccum: type: object properties: @@ -2452,16 +2391,16 @@ definitions: type: object properties: operator_addr: - $ref: '#/definitions/ValidatorAddress' + $ref: "#/definitions/ValidatorAddress" fee_pool_withdrawal_height: type: integer del_accum: - $ref: '#/definitions/TotalAccum' + $ref: "#/definitions/TotalAccum" del_pool: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" val_commission: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index da41e196b276..b420215eb245 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -26,9 +26,9 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/rest" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/client/utils" gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" @@ -646,7 +646,7 @@ func doSign(t *testing.T, port, name, password, chainID string, accnum, sequence var signedMsg auth.StdTx payload := authrest.SignBody{ Tx: msg, - BaseReq: utils.NewBaseReq( + BaseReq: rest.NewBaseReq( name, password, "", chainID, "", "", accnum, sequence, nil, nil, false, false, ), } @@ -688,23 +688,32 @@ func doTransfer(t *testing.T, port, seed, name, memo, password string, addr sdk. return receiveAddr, resultTx } -func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, addr sdk.AccAddress, gas string, - gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins) ( - res *http.Response, body string, receiveAddr sdk.AccAddress) { +func doTransferWithGas( + t *testing.T, port, seed, from, memo, password string, addr sdk.AccAddress, + gas string, gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins, +) (res *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := client.MockKeyBase() - receiveInfo, _, err := kb.CreateMnemonic("receive_address", cryptoKeys.English, gapp.DefaultKeyPass, cryptoKeys.SigningAlgo("secp256k1")) + + receiveInfo, _, err := kb.CreateMnemonic( + "receive_address", cryptoKeys.English, gapp.DefaultKeyPass, cryptoKeys.SigningAlgo("secp256k1"), + ) require.Nil(t, err) - receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) + receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) acc := getAccount(t, port, addr) accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq( - name, password, memo, chainID, gas, + if generateOnly { + // generate only txs do not use a Keybase so the address must be used + from = addr.String() + } + + baseReq := rest.NewBaseReq( + from, password, memo, chainID, gas, fmt.Sprintf("%f", gasAdjustment), accnum, sequence, fees, nil, generateOnly, simulate, ) @@ -721,9 +730,42 @@ func doTransferWithGas(t *testing.T, port, seed, name, memo, password string, ad return } +func doTransferWithGasAccAuto( + t *testing.T, port, seed, from, memo, password string, gas string, + gasAdjustment float64, simulate, generateOnly bool, fees sdk.Coins, +) (res *http.Response, body string, receiveAddr sdk.AccAddress) { + + // create receive address + kb := client.MockKeyBase() + + receiveInfo, _, err := kb.CreateMnemonic( + "receive_address", cryptoKeys.English, gapp.DefaultKeyPass, cryptoKeys.SigningAlgo("secp256k1"), + ) + require.Nil(t, err) + + receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) + chainID := viper.GetString(client.FlagChainID) + + baseReq := rest.NewBaseReq( + from, password, memo, chainID, gas, + fmt.Sprintf("%f", gasAdjustment), 0, 0, fees, nil, generateOnly, simulate, + ) + + sr := sendReq{ + Amount: sdk.Coins{sdk.NewInt64Coin(stakingTypes.DefaultBondDenom, 1)}, + BaseReq: baseReq, + } + + req, err := cdc.MarshalJSON(sr) + require.NoError(t, err) + + res, body = Request(t, port, "POST", fmt.Sprintf("/bank/accounts/%s/transfers", receiveAddr), req) + return +} + type sendReq struct { - Amount sdk.Coins `json:"amount"` - BaseReq utils.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` + BaseReq rest.BaseReq `json:"base_req"` } // ---------------------------------------------------------------------- @@ -737,7 +779,7 @@ func doDelegate(t *testing.T, port, name, password string, accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) msg := msgDelegationsInput{ BaseReq: baseReq, DelegatorAddr: delAddr, @@ -757,7 +799,7 @@ func doDelegate(t *testing.T, port, name, password string, } type msgDelegationsInput struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 Delegation sdk.Coin `json:"delegation"` @@ -771,7 +813,7 @@ func doUndelegate(t *testing.T, port, name, password string, accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) msg := msgUndelegateInput{ BaseReq: baseReq, DelegatorAddr: delAddr, @@ -792,7 +834,7 @@ func doUndelegate(t *testing.T, port, name, password string, } type msgUndelegateInput struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 SharesAmount sdk.Dec `json:"shares"` @@ -807,7 +849,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string, sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) msg := msgBeginRedelegateInput{ BaseReq: baseReq, @@ -830,7 +872,7 @@ func doBeginRedelegation(t *testing.T, port, name, password string, } type msgBeginRedelegateInput struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32 ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32 @@ -1038,7 +1080,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) pr := postProposalReq{ Title: "Test", @@ -1064,7 +1106,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA } type postProposalReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} @@ -1134,7 +1176,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) dr := depositReq{ Depositor: proposerAddr, @@ -1156,7 +1198,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk } type depositReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } @@ -1188,7 +1230,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac accnum := acc.GetAccountNumber() sequence := acc.GetSequence() chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) vr := voteReq{ Voter: proposerAddr, @@ -1210,7 +1252,7 @@ func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Ac } type voteReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` Voter sdk.AccAddress `json:"voter"` // address of the voter Option string `json:"option"` // option from OptionSet chosen by the voter } @@ -1320,7 +1362,7 @@ func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing. func doUnjail(t *testing.T, port, seed, name, password string, valAddr sdk.ValAddress, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { chainID := viper.GetString(client.FlagChainID) - baseReq := utils.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", 1, 1, fees, nil, false, false) ur := unjailReq{ BaseReq: baseReq, @@ -1338,5 +1380,5 @@ func doUnjail(t *testing.T, port, seed, name, password string, } type unjailReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` } diff --git a/client/rest/rest.go b/client/rest/rest.go new file mode 100644 index 000000000000..0b87e084df87 --- /dev/null +++ b/client/rest/rest.go @@ -0,0 +1,255 @@ +package rest + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" +) + +//----------------------------------------------------------------------------- +// Basic HTTP utilities + +// ErrorResponse defines the attributes of a JSON error response. +type ErrorResponse struct { + Code int `json:"code,omitempty"` + Message string `json:"message"` +} + +// NewErrorResponse creates a new ErrorResponse instance. +func NewErrorResponse(code int, msg string) ErrorResponse { + return ErrorResponse{Code: code, Message: msg} +} + +// WriteErrorResponse prepares and writes a HTTP error +// given a status code and an error message. +func WriteErrorResponse(w http.ResponseWriter, status int, err string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + w.Write(codec.Cdc.MustMarshalJSON(NewErrorResponse(0, err))) +} + +// WriteSimulationResponse prepares and writes an HTTP +// response for transactions simulations. +func WriteSimulationResponse(w http.ResponseWriter, cdc *codec.Codec, gas uint64) { + gasEst := GasEstimateResponse{GasEstimate: gas} + resp, err := cdc.MarshalJSON(gasEst) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(resp) +} + +// ParseInt64OrReturnBadRequest converts s to a int64 value. +func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) { + var err error + + n, err = strconv.ParseInt(s, 10, 64) + if err != nil { + err := fmt.Errorf("'%s' is not a valid int64", s) + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return n, false + } + + return n, true +} + +// ParseUint64OrReturnBadRequest converts s to a uint64 value. +func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) { + var err error + + n, err = strconv.ParseUint(s, 10, 64) + if err != nil { + err := fmt.Errorf("'%s' is not a valid uint64", s) + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return n, false + } + + return n, true +} + +// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a +// default value, defaultIfEmpty, if the string is empty. +func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { + if len(s) == 0 { + return defaultIfEmpty, true + } + + n, err := strconv.ParseFloat(s, 64) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return n, false + } + + return n, true +} + +//----------------------------------------------------------------------------- +// Building / Sending utilities + +// CompleteAndBroadcastTxREST implements a utility function that facilitates +// sending a series of messages in a signed tx. In addition, it will handle +// tx gas simulation and estimation. +// +// NOTE: Also see CompleteAndBroadcastTxCLI. +func CompleteAndBroadcastTxREST( + w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, + baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec, +) { + + gasAdj, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + + simAndExec, gas, err := client.ParseGas(baseReq.Gas) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + // derive the from account address and name from the Keybase + fromAddress, fromName, err := context.GetFromFields(baseReq.From) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) + txBldr := authtxb.NewTxBuilder( + utils.GetTxEncoder(cdc), baseReq.AccountNumber, + baseReq.Sequence, gas, gasAdj, baseReq.Simulate, + baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices, + ) + + txBldr, err = utils.PrepareTxBuilder(txBldr, cliCtx) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + if baseReq.Simulate || simAndExec { + if gasAdj < 0 { + WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error()) + return + } + + txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + if baseReq.Simulate { + WriteSimulationResponse(w, cdc, txBldr.GetGas()) + return + } + } + + txBytes, err := txBldr.BuildAndSign(cliCtx.GetFromName(), baseReq.Password, msgs) + if keyerror.IsErrKeyNotFound(err) { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } else if keyerror.IsErrWrongPassword(err) { + WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + return + } else if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + res, err := cliCtx.BroadcastTx(txBytes) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + PostProcessResponse(w, cdc, res, cliCtx.Indent) +} + +// PostProcessResponse performs post processing for a REST response. +func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response interface{}, indent bool) { + var output []byte + + switch response.(type) { + default: + var err error + if indent { + output, err = cdc.MarshalJSONIndent(response, "", " ") + } else { + output, err = cdc.MarshalJSON(response) + } + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + case []byte: + output = response.([]byte) + } + + w.Header().Set("Content-Type", "application/json") + w.Write(output) +} + +// WriteGenerateStdTxResponse writes response for the generate only mode. +func WriteGenerateStdTxResponse( + w http.ResponseWriter, cdc *codec.Codec, cliCtx context.CLIContext, br BaseReq, msgs []sdk.Msg, +) { + + gasAdj, ok := ParseFloat64OrReturnBadRequest(w, br.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + + simAndExec, gas, err := client.ParseGas(br.Gas) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + txBldr := authtxb.NewTxBuilder( + utils.GetTxEncoder(cdc), br.AccountNumber, br.Sequence, gas, gasAdj, + br.Simulate, br.ChainID, br.Memo, br.Fees, br.GasPrices, + ) + + if simAndExec { + if gasAdj < 0 { + WriteErrorResponse(w, http.StatusBadRequest, client.ErrInvalidGasAdjustment.Error()) + return + } + + txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + } + + stdMsg, err := txBldr.Build(msgs) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + output, err := cdc.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo)) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(output) + return +} diff --git a/client/rest/types.go b/client/rest/types.go new file mode 100644 index 000000000000..113dc0650d02 --- /dev/null +++ b/client/rest/types.go @@ -0,0 +1,126 @@ +package rest + +import ( + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GasEstimateResponse defines a response definition for tx gas estimation. +type GasEstimateResponse struct { + GasEstimate uint64 `json:"gas_estimate"` +} + +// BaseReq defines a structure that can be embedded in other request structures +// that all share common "base" fields. +type BaseReq struct { + From string `json:"from"` + Password string `json:"password"` + Memo string `json:"memo"` + ChainID string `json:"chain_id"` + AccountNumber uint64 `json:"account_number"` + Sequence uint64 `json:"sequence"` + Fees sdk.Coins `json:"fees"` + GasPrices sdk.DecCoins `json:"gas_prices"` + Gas string `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` + GenerateOnly bool `json:"generate_only"` + Simulate bool `json:"simulate"` +} + +// NewBaseReq creates a new basic request instance and sanitizes its values +func NewBaseReq( + from, password, memo, chainID string, gas, gasAdjustment string, + accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool, +) BaseReq { + + return BaseReq{ + From: strings.TrimSpace(from), + Password: password, + Memo: strings.TrimSpace(memo), + ChainID: strings.TrimSpace(chainID), + Fees: fees, + GasPrices: gasPrices, + Gas: strings.TrimSpace(gas), + GasAdjustment: strings.TrimSpace(gasAdjustment), + AccountNumber: accNumber, + Sequence: seq, + GenerateOnly: genOnly, + Simulate: simulate, + } +} + +// Sanitize performs basic sanitization on a BaseReq object. +func (br BaseReq) Sanitize() BaseReq { + return NewBaseReq( + br.From, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment, + br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate, + ) +} + +// ValidateBasic performs basic validation of a BaseReq. If custom validation +// logic is needed, the implementing request handler should perform those +// checks manually. +func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { + if !br.GenerateOnly && !br.Simulate { + switch { + case len(br.Password) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") + return false + + case len(br.ChainID) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified") + return false + + case !br.Fees.IsZero() && !br.GasPrices.IsZero(): + // both fees and gas prices were provided + WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices") + return false + + case !br.Fees.IsValid() && !br.GasPrices.IsValid(): + // neither fees or gas prices were provided + WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided") + return false + } + } + + if len(br.From) == 0 { + WriteErrorResponse(w, http.StatusUnauthorized, "name or address required but not specified") + return false + } + + return true +} + +/* +ReadRESTReq is a simple convenience wrapper that reads the body and +unmarshals to the req interface. + + Usage: + type SomeReq struct { + BaseReq `json:"base_req"` + CustomField string `json:"custom_field"` + } + + req := new(SomeReq) + err := ReadRESTReq(w, r, cdc, req) +*/ +func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return err + } + + err = cdc.UnmarshalJSON(body, req) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err)) + return err + } + + return nil +} diff --git a/client/rpc/block.go b/client/rpc/block.go index 95f5acb991dd..d5aaecc1f38b 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -5,6 +5,8 @@ import ( "net/http" "strconv" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" @@ -12,8 +14,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" - - "github.com/cosmos/cosmos-sdk/client/utils" ) //BlockCommand returns the verified block data for a given heights @@ -131,7 +131,7 @@ func BlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { w.Write([]byte(err.Error())) return } - utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, output, cliCtx.Indent) } } @@ -150,6 +150,6 @@ func LatestBlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { w.Write([]byte(err.Error())) return } - utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, output, cliCtx.Indent) } } diff --git a/client/rpc/root.go b/client/rpc/root.go index cb25b47f31e8..6ea831cac8c7 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -4,10 +4,11 @@ import ( "fmt" "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/gorilla/mux" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/version" ) @@ -26,7 +27,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { // cli version REST handler endpoint func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.Write([]byte(fmt.Sprintf("{\"version\": \"%s\"}", version.GetVersion()))) + w.Write([]byte(fmt.Sprintf("{\"version\": \"%s\"}", version.Version))) } // connected node version REST handler endpoint @@ -34,7 +35,7 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { version, err := cliCtx.Query("/app/version", nil) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/client/rpc/status.go b/client/rpc/status.go index 86bb1ef31f68..438346f16549 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -5,6 +5,8 @@ import ( "net/http" "strconv" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/spf13/cobra" "github.com/spf13/viper" @@ -12,7 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" ) // StatusCommand returns the status of the network @@ -77,7 +78,7 @@ func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } nodeInfo := status.NodeInfo - utils.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, nodeInfo, cliCtx.Indent) } } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 0992f8504bc7..379b3bc6fac6 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -6,6 +6,8 @@ import ( "net/http" "strconv" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -14,7 +16,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -157,7 +158,7 @@ func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { w.Write([]byte(err.Error())) return } - utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, output, cliCtx.Indent) } } @@ -177,6 +178,6 @@ func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerF w.Write([]byte(err.Error())) return } - utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, output, cliCtx.Indent) } } diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 4080b68ec33e..9dd7b3abd6f4 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -3,10 +3,11 @@ package tx import ( "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "io/ioutil" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" ) @@ -32,12 +33,12 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle var m BroadcastBody body, err := ioutil.ReadAll(r.Body) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } err = cdc.UnmarshalJSON(body, &m) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } var res interface{} @@ -49,13 +50,13 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle case flagAsync: res, err = cliCtx.BroadcastTxAsync(m.TxBytes) default: - utils.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async") + rest.WriteErrorResponse(w, http.StatusInternalServerError, "unsupported return type. supported types: block, sync, async") return } if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/client/tx/query.go b/client/tx/query.go index e169286e37e1..754b5ba96f97 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/tendermint/tendermint/libs/common" "github.com/gorilla/mux" @@ -16,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -142,9 +143,9 @@ func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.H output, err := queryTx(cdc, cliCtx, hashHexStr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, output, cliCtx.Indent) } } diff --git a/client/tx/search.go b/client/tx/search.go index 29537e00bd11..97beee07524d 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -8,9 +8,10 @@ import ( "strconv" "strings" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -175,28 +176,28 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. var txs []Info err := r.ParseForm() if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error())) + rest.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error())) return } if len(r.Form) == 0 { - utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent) return } tags, page, limit, err = parseHTTPArgs(r) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } txs, err = SearchTxs(cliCtx, cdc, tags, page, limit) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent) } } diff --git a/client/utils/rest.go b/client/utils/rest.go deleted file mode 100644 index f6bd18a4f583..000000000000 --- a/client/utils/rest.go +++ /dev/null @@ -1,305 +0,0 @@ -package utils - -import ( - "fmt" - "io/ioutil" - "net/http" - "strconv" - "strings" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" -) - -//---------------------------------------- -// Basic HTTP utilities - -// WriteErrorResponse prepares and writes a HTTP error -// given a status code and an error message. -func WriteErrorResponse(w http.ResponseWriter, status int, err string) { - w.WriteHeader(status) - w.Write([]byte(err)) -} - -// WriteSimulationResponse prepares and writes an HTTP -// response for transactions simulations. -func WriteSimulationResponse(w http.ResponseWriter, gas uint64) { - w.WriteHeader(http.StatusOK) - w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas))) -} - -// ParseInt64OrReturnBadRequest converts s to a int64 value. -func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) { - var err error - - n, err = strconv.ParseInt(s, 10, 64) - if err != nil { - err := fmt.Errorf("'%s' is not a valid int64", s) - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return n, false - } - - return n, true -} - -// ParseUint64OrReturnBadRequest converts s to a uint64 value. -func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) { - var err error - - n, err = strconv.ParseUint(s, 10, 64) - if err != nil { - err := fmt.Errorf("'%s' is not a valid uint64", s) - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return n, false - } - - return n, true -} - -// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a -// default value, defaultIfEmpty, if the string is empty. -func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { - if len(s) == 0 { - return defaultIfEmpty, true - } - - n, err := strconv.ParseFloat(s, 64) - if err != nil { - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return n, false - } - - return n, true -} - -// WriteGenerateStdTxResponse writes response for the generate_only mode. -func WriteGenerateStdTxResponse(w http.ResponseWriter, cdc *codec.Codec, txBldr authtxb.TxBuilder, msgs []sdk.Msg) { - stdMsg, err := txBldr.Build(msgs) - if err != nil { - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - output, err := cdc.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo)) - if err != nil { - WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) - return -} - -//---------------------------------------- -// Building / Sending utilities - -// BaseReq defines a structure that can be embedded in other request structures -// that all share common "base" fields. -type BaseReq struct { - Name string `json:"name"` - Password string `json:"password"` - Memo string `json:"memo"` - ChainID string `json:"chain_id"` - AccountNumber uint64 `json:"account_number"` - Sequence uint64 `json:"sequence"` - Fees sdk.Coins `json:"fees"` - GasPrices sdk.DecCoins `json:"gas_prices"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - GenerateOnly bool `json:"generate_only"` - Simulate bool `json:"simulate"` -} - -// NewBaseReq creates a new basic request instance and sanitizes its values -func NewBaseReq( - name, password, memo, chainID string, gas, gasAdjustment string, - accNumber, seq uint64, fees sdk.Coins, gasPrices sdk.DecCoins, genOnly, simulate bool, -) BaseReq { - - return BaseReq{ - Name: strings.TrimSpace(name), - Password: password, - Memo: strings.TrimSpace(memo), - ChainID: strings.TrimSpace(chainID), - Fees: fees, - GasPrices: gasPrices, - Gas: strings.TrimSpace(gas), - GasAdjustment: strings.TrimSpace(gasAdjustment), - AccountNumber: accNumber, - Sequence: seq, - GenerateOnly: genOnly, - Simulate: simulate, - } -} - -// Sanitize performs basic sanitization on a BaseReq object. -func (br BaseReq) Sanitize() BaseReq { - return NewBaseReq( - br.Name, br.Password, br.Memo, br.ChainID, br.Gas, br.GasAdjustment, - br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.GenerateOnly, br.Simulate, - ) -} - -// ValidateBasic performs basic validation of a BaseReq. If custom validation -// logic is needed, the implementing request handler should perform those -// checks manually. -func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { - if !br.GenerateOnly && !br.Simulate { - switch { - case len(br.Password) == 0: - WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") - return false - - case len(br.ChainID) == 0: - WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified") - return false - - case !br.Fees.IsZero() && !br.GasPrices.IsZero(): - // both fees and gas prices were provided - WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices") - return false - - case !br.Fees.IsValid() && !br.GasPrices.IsValid(): - // neither fees or gas prices were provided - WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided") - return false - } - } - - if len(br.Name) == 0 { - WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified") - return false - } - - return true -} - -/* -ReadRESTReq is a simple convenience wrapper that reads the body and -unmarshals to the req interface. - - Usage: - type SomeReq struct { - BaseReq `json:"base_req"` - CustomField string `json:"custom_field"` - } - - req := new(SomeReq) - err := ReadRESTReq(w, r, cdc, req) -*/ -func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err - } - - err = cdc.UnmarshalJSON(body, req) - if err != nil { - WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err)) - return err - } - - return nil -} - -// CompleteAndBroadcastTxREST implements a utility function that facilitates -// sending a series of messages in a signed transaction given a TxBuilder and a -// QueryContext. It ensures that the account exists, has a proper number and -// sequence set. In addition, it builds and signs a transaction with the -// supplied messages. Finally, it broadcasts the signed transaction to a node. -// -// NOTE: Also see CompleteAndBroadcastTxCli. -// NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn. -func CompleteAndBroadcastTxREST( - w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, - baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec, -) { - - gasAdjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - - simulateAndExecute, gas, err := client.ParseGas(baseReq.Gas) - if err != nil { - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - txBldr := authtxb.NewTxBuilder( - GetTxEncoder(cdc), baseReq.AccountNumber, - baseReq.Sequence, gas, gasAdjustment, baseReq.Simulate, - baseReq.ChainID, baseReq.Memo, baseReq.Fees, baseReq.GasPrices, - ) - - if baseReq.Simulate || simulateAndExecute { - if gasAdjustment < 0 { - WriteErrorResponse(w, http.StatusBadRequest, "gas adjustment must be a positive float") - return - } - - txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs) - if err != nil { - WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - if baseReq.Simulate { - WriteSimulationResponse(w, txBldr.GetGas()) - return - } - } - - if baseReq.GenerateOnly { - WriteGenerateStdTxResponse(w, cdc, txBldr, msgs) - return - } - - txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, msgs) - if keyerror.IsErrKeyNotFound(err) { - WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } else if keyerror.IsErrWrongPassword(err) { - WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } else if err != nil { - WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - PostProcessResponse(w, cdc, res, cliCtx.Indent) -} - -// PostProcessResponse performs post process for rest response -func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response interface{}, indent bool) { - var output []byte - switch response.(type) { - default: - var err error - if indent { - output, err = cdc.MarshalJSONIndent(response, "", " ") - } else { - output, err = cdc.MarshalJSON(response) - } - if err != nil { - WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - case []byte: - output = response.([]byte) - } - w.Header().Set("Content-Type", "application/json") - w.Write(output) -} diff --git a/client/utils/utils.go b/client/utils/utils.go index 92ce82b9937f..daad43156ac3 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -6,6 +6,7 @@ import ( "io" "os" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/tendermint/go-amino" @@ -18,26 +19,23 @@ import ( authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" ) -// CompleteAndBroadcastTxCli implements a utility function that facilitates +// CompleteAndBroadcastTxCLI implements a utility function that facilitates // sending a series of messages in a signed transaction given a TxBuilder and a // QueryContext. It ensures that the account exists, has a proper number and // sequence set. In addition, it builds and signs a transaction with the // supplied messages. Finally, it broadcasts the signed transaction to a node. // // NOTE: Also see CompleteAndBroadcastTxREST. -func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { - txBldr, err := prepareTxBuilder(txBldr, cliCtx) +func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { + txBldr, err := PrepareTxBuilder(txBldr, cliCtx) if err != nil { return err } - name, err := cliCtx.GetFromName() - if err != nil { - return err - } + fromName := cliCtx.GetFromName() if txBldr.GetSimulateAndExecute() || cliCtx.Simulate { - txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) + txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) if err != nil { return err } @@ -47,13 +45,13 @@ func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIConte return nil } - passphrase, err := keys.GetPassphrase(name) + passphrase, err := keys.GetPassphrase(fromName) if err != nil { return err } // build and sign the transaction - txBytes, err := txBldr.BuildAndSign(name, passphrase, msgs) + txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs) if err != nil { return err } @@ -63,10 +61,10 @@ func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIConte return err } -// EnrichCtxWithGas calculates the gas estimate that would be consumed by the +// EnrichWithGas calculates the gas estimate that would be consumed by the // transaction and set the transaction's respective value accordingly. -func EnrichCtxWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authtxb.TxBuilder, error) { - _, adjusted, err := simulateMsgs(txBldr, cliCtx, name, msgs) +func EnrichWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtxb.TxBuilder, error) { + _, adjusted, err := simulateMsgs(txBldr, cliCtx, msgs) if err != nil { return txBldr, err } @@ -129,8 +127,7 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, // check whether the address is a signer if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { - return signedStdTx, fmt.Errorf( - "The generated transaction's intended signer does not match the given signer: %q", name) + return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name) } if !offline { @@ -158,8 +155,7 @@ func SignStdTxWithSignerAddress(txBldr authtxb.TxBuilder, cliCtx context.CLICont // check whether the address is a signer if !isTxSigner(addr, stdTx.GetSigners()) { - return signedStdTx, fmt.Errorf( - "The generated transaction's intended signer does not match the given signer: %q", name) + return signedStdTx, fmt.Errorf("%s: %s", client.ErrInvalidSigner, name) } if !offline { @@ -210,8 +206,8 @@ func GetTxEncoder(cdc *codec.Codec) (encoder sdk.TxEncoder) { // nolint // SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. -func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (estimated, adjusted uint64, err error) { - txBytes, err := txBldr.BuildWithPubKey(name, msgs) +func simulateMsgs(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (estimated, adjusted uint64, err error) { + txBytes, err := txBldr.BuildTxForSim(msgs) if err != nil { return } @@ -231,15 +227,13 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (uint64, error) { return simulationResult.GasUsed, nil } -func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { +// PrepareTxBuilder populates a TxBuilder in preparation for the build of a Tx. +func PrepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { if err := cliCtx.EnsureAccountExists(); err != nil { return txBldr, err } - from, err := cliCtx.GetFromAddress() - if err != nil { - return txBldr, err - } + from := cliCtx.GetFromAddress() // TODO: (ref #1903) Allow for user supplied account number without // automatically doing a manual lookup. @@ -266,7 +260,7 @@ func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth // buildUnsignedStdTx builds a StdTx as per the parameters passed in the // contexts. Gas is automatically estimated if gas wanted is set to 0. func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { - txBldr, err = prepareTxBuilder(txBldr, cliCtx) + txBldr, err = PrepareTxBuilder(txBldr, cliCtx) if err != nil { return } @@ -275,22 +269,19 @@ func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { if txBldr.GetSimulateAndExecute() { - var name string - name, err = cliCtx.GetFromName() + txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) if err != nil { return } - txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) - if err != nil { - return - } fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.GetGas()) } + stdSignMsg, err := txBldr.Build(msgs) if err != nil { return } + return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil } @@ -300,5 +291,6 @@ func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool { return true } } + return false } diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 8b5ce4e73380..2d66bd9342df 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -102,7 +102,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b ) // add handlers - app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) + app.bankKeeper = bank.NewBaseKeeper( + app.accountKeeper, + app.paramsKeeper.Subspace(bank.DefaultParamspace), + bank.DefaultCodespace, + ) app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( app.cdc, app.keyFeeCollection, @@ -228,10 +232,7 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R // initialize store from a genesis state func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate { - // sort by account number to maintain consistency - sort.Slice(genesisState.Accounts, func(i, j int) bool { - return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber - }) + genesisState.Sanitize() // load the accounts for _, gacc := range genesisState.Accounts { @@ -251,13 +252,13 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt // initialize module-specific stores auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData) + bank.InitGenesis(ctx, app.bankKeeper, genesisState.BankData) slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData) gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) // validate genesis state - err = GaiaValidateGenesisState(genesisState) - if err != nil { + if err := GaiaValidateGenesisState(genesisState); err != nil { panic(err) // TODO find a way to do this w/o panics } diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 5f2b5b968772..0ee1596ea878 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -4,6 +4,8 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -28,6 +30,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { genesisState := NewGenesisState( genaccs, auth.DefaultGenesisState(), + bank.DefaultGenesisState(), staking.DefaultGenesisState(), mint.DefaultGenesisState(), distr.DefaultGenesisState(), @@ -55,6 +58,6 @@ func TestGaiadExport(t *testing.T) { // Making a new app object with the db, so that initchain hasn't been called newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true) - _, _, err := newGapp.ExportAppStateAndValidators(false) + _, _, err := newGapp.ExportAppStateAndValidators(false, []string{}) require.NoError(t, err, "ExportAppStateAndValidators should not have an error") } diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index f535d1373355..efc9c2051451 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -2,6 +2,7 @@ package app import ( "encoding/json" + "log" abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" @@ -9,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" @@ -17,14 +19,14 @@ import ( ) // export the state of gaia for a genesis file -func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( +func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string) ( appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { // as if they could withdraw from the start of the next block ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) if forZeroHeight { - app.prepForZeroHeightGenesis(ctx) + app.prepForZeroHeightGenesis(ctx, jailWhiteList) } // iterate to get the accounts @@ -39,6 +41,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( genState := NewGenesisState( accounts, auth.ExportGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper), + bank.ExportGenesis(ctx, app.bankKeeper), staking.ExportGenesis(ctx, app.stakingKeeper), mint.ExportGenesis(ctx, app.mintKeeper), distr.ExportGenesis(ctx, app.distrKeeper), @@ -54,7 +57,23 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( } // prepare for fresh start at zero height -func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) { +func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) { + applyWhiteList := false + + //Check if there is a whitelist + if len(jailWhiteList) > 0 { + applyWhiteList = true + } + + whiteListMap := make(map[string]bool) + + for _, addr := range jailWhiteList { + _, err := sdk.ValAddressFromBech32(addr) + if err != nil { + log.Fatal(err) + } + whiteListMap[addr] = true + } /* Just to be safe, assert the invariants on current state. */ app.assertRuntimeInvariantsOnContext(ctx) @@ -134,6 +153,9 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) { validator.BondHeight = 0 validator.UnbondingHeight = 0 valConsAddrs = append(valConsAddrs, validator.ConsAddress()) + if applyWhiteList && !whiteListMap[addr.String()] { + validator.Jailed = true + } app.stakingKeeper.SetValidator(ctx, validator) counter++ @@ -141,6 +163,8 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) { iter.Close() + _ = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + /* Handle slashing state. */ // reset start height on signing infos diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index cfda1c6c488d..3eae8af87508 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -9,6 +9,9 @@ import ( "path/filepath" "sort" "strings" + "time" + + "github.com/cosmos/cosmos-sdk/x/bank" tmtypes "github.com/tendermint/tendermint/types" @@ -34,6 +37,7 @@ var ( type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` AuthData auth.GenesisState `json:"auth"` + BankData bank.GenesisState `json:"bank"` StakingData staking.GenesisState `json:"staking"` MintData mint.GenesisState `json:"mint"` DistrData distr.GenesisState `json:"distr"` @@ -43,6 +47,7 @@ type GenesisState struct { } func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, + bankData bank.GenesisState, stakingData staking.GenesisState, mintData mint.GenesisState, distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState { @@ -50,6 +55,7 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, return GenesisState{ Accounts: accounts, AuthData: authData, + BankData: bankData, StakingData: stakingData, MintData: mintData, DistrData: distrData, @@ -58,6 +64,17 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, } } +// Sanitize sorts accounts and coin sets. +func (gs GenesisState) Sanitize() { + sort.Slice(gs.Accounts, func(i, j int) bool { + return gs.Accounts[i].AccountNumber < gs.Accounts[j].AccountNumber + }) + + for _, acc := range gs.Accounts { + acc.Coins = acc.Coins.Sort() + } +} + // GenesisAccount defines an account initialized at genesis. type GenesisAccount struct { Address sdk.AccAddress `json:"address"` @@ -69,8 +86,8 @@ type GenesisAccount struct { OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation - StartTime int64 `json:"start_time"` // vesting start time - EndTime int64 `json:"end_time"` // vesting end time + StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time) + EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time) } func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { @@ -190,6 +207,7 @@ func NewDefaultGenesisState() GenesisState { return GenesisState{ Accounts: nil, AuthData: auth.DefaultGenesisState(), + BankData: bank.DefaultGenesisState(), StakingData: staking.DefaultGenesisState(), MintData: mint.DefaultGenesisState(), DistrData: distr.DefaultGenesisState(), @@ -216,6 +234,9 @@ func GaiaValidateGenesisState(genesisState GenesisState) error { if err := auth.ValidateGenesis(genesisState.AuthData); err != nil { return err } + if err := bank.ValidateGenesis(genesisState.BankData); err != nil { + return err + } if err := staking.ValidateGenesis(genesisState.StakingData); err != nil { return err } @@ -232,17 +253,38 @@ func GaiaValidateGenesisState(genesisState GenesisState) error { return slashing.ValidateGenesis(genesisState.SlashingData) } -// Ensures that there are no duplicate accounts in the genesis state, +// validateGenesisStateAccounts performs validation of genesis accounts. It +// ensures that there are no duplicate accounts in the genesis state and any +// provided vesting accounts are valid. func validateGenesisStateAccounts(accs []GenesisAccount) error { addrMap := make(map[string]bool, len(accs)) - for i := 0; i < len(accs); i++ { - acc := accs[i] - strAddr := string(acc.Address) - if _, ok := addrMap[strAddr]; ok { - return fmt.Errorf("Duplicate account in genesis state: Address %v", acc.Address) + for _, acc := range accs { + addrStr := acc.Address.String() + + // disallow any duplicate accounts + if _, ok := addrMap[addrStr]; ok { + return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr) } - addrMap[strAddr] = true + + // validate any vesting fields + if !acc.OriginalVesting.IsZero() { + if acc.EndTime == 0 { + return fmt.Errorf("missing end time for vesting account; address: %s", addrStr) + } + + if acc.StartTime >= acc.EndTime { + return fmt.Errorf( + "vesting start time must before end time; address: %s, start: %s, end: %s", + addrStr, + time.Unix(acc.StartTime, 0).UTC().Format(time.RFC3339), + time.Unix(acc.EndTime, 0).UTC().Format(time.RFC3339), + ) + } + } + + addrMap[addrStr] = true } + return nil } diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 1383794a3b8a..82829b699499 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" - "github.com/tendermint/tendermint/crypto/secp256k1" - tmtypes "github.com/tendermint/tendermint/types" - "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -111,29 +111,41 @@ func makeMsg(name string, pk crypto.PubKey) auth.StdTx { } func TestGaiaGenesisValidation(t *testing.T) { - genTxs := make([]auth.StdTx, 2) - // Test duplicate accounts fails - genTxs[0] = makeMsg("test-0", pk1) - genTxs[1] = makeMsg("test-1", pk1) - genesisState := makeGenesisState(t, genTxs) + genTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk2)} + dupGenTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk1)} + + // require duplicate accounts fails validation + genesisState := makeGenesisState(t, dupGenTxs) err := GaiaValidateGenesisState(genesisState) - require.NotNil(t, err) - // Test bonded + jailed validator fails + require.Error(t, err) + + // require invalid vesting account fails validation (invalid end time) + genesisState = makeGenesisState(t, genTxs) + genesisState.Accounts[0].OriginalVesting = genesisState.Accounts[0].Coins + err = GaiaValidateGenesisState(genesisState) + require.Error(t, err) + genesisState.Accounts[0].StartTime = 1548888000 + genesisState.Accounts[0].EndTime = 1548775410 + err = GaiaValidateGenesisState(genesisState) + require.Error(t, err) + + // require bonded + jailed validator fails validation genesisState = makeGenesisState(t, genTxs) val1 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #2"}) val1.Jailed = true val1.Status = sdk.Bonded genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) err = GaiaValidateGenesisState(genesisState) - require.NotNil(t, err) - // Test duplicate validator fails + require.Error(t, err) + + // require duplicate validator fails validation val1.Jailed = false genesisState = makeGenesisState(t, genTxs) val2 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #3"}) genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2) err = GaiaValidateGenesisState(genesisState) - require.NotNil(t, err) + require.Error(t, err) } func TestNewDefaultGenesisAccount(t *testing.T) { @@ -142,3 +154,36 @@ func TestNewDefaultGenesisAccount(t *testing.T) { require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("footoken")) require.Equal(t, sdk.NewInt(150), acc.Coins.AmountOf(bondDenom)) } + +func TestGenesisStateSanitize(t *testing.T) { + genesisState := makeGenesisState(t, nil) + require.Nil(t, GaiaValidateGenesisState(genesisState)) + + addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + authAcc1 := auth.NewBaseAccountWithAddress(addr1) + authAcc1.SetCoins(sdk.Coins{ + sdk.NewInt64Coin("bcoin", 150), + sdk.NewInt64Coin("acoin", 150), + }) + authAcc1.SetAccountNumber(1) + genAcc1 := NewGenesisAccount(&authAcc1) + + addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + authAcc2 := auth.NewBaseAccountWithAddress(addr2) + authAcc2.SetCoins(sdk.Coins{ + sdk.NewInt64Coin("acoin", 150), + sdk.NewInt64Coin("bcoin", 150), + }) + genAcc2 := NewGenesisAccount(&authAcc2) + + genesisState.Accounts = []GenesisAccount{genAcc1, genAcc2} + require.True(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber) + require.Equal(t, genesisState.Accounts[0].Coins[0].Denom, "bcoin") + require.Equal(t, genesisState.Accounts[0].Coins[1].Denom, "acoin") + require.Equal(t, genesisState.Accounts[1].Address, addr2) + genesisState.Sanitize() + require.False(t, genesisState.Accounts[0].AccountNumber > genesisState.Accounts[1].AccountNumber) + require.Equal(t, genesisState.Accounts[1].Address, addr1) + require.Equal(t, genesisState.Accounts[1].Coins[0].Denom, "acoin") + require.Equal(t, genesisState.Accounts[1].Coins[1].Denom, "bcoin") +} diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index b88e809eba6e..dae525342a46 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -13,13 +13,16 @@ import ( "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation" + "github.com/cosmos/cosmos-sdk/x/bank" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" distr "github.com/cosmos/cosmos-sdk/x/distribution" distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" @@ -35,16 +38,18 @@ import ( ) var ( - seed int64 - numBlocks int - blockSize int - enabled bool - verbose bool - commit bool - period int + genesisFile string + seed int64 + numBlocks int + blockSize int + enabled bool + verbose bool + commit bool + period int ) func init() { + flag.StringVar(&genesisFile, "SimulationGenesis", "", "Custom simulation genesis file") flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed") flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks") flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block") @@ -54,7 +59,31 @@ func init() { flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions") } -func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) json.RawMessage { +func appStateFromGenesisFileFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) { + var genesis tmtypes.GenesisDoc + cdc := MakeCodec() + bytes, err := ioutil.ReadFile(genesisFile) + if err != nil { + panic(err) + } + cdc.MustUnmarshalJSON(bytes, &genesis) + var appState GenesisState + cdc.MustUnmarshalJSON(genesis.AppState, &appState) + var newAccs []simulation.Account + for _, acc := range appState.Accounts { + // Pick a random private key, since we don't know the actual key + // This should be fine as it's only used for mock Tendermint validators + // and these keys are never actually used to sign by mock Tendermint. + privkeySeed := make([]byte, 15) + r.Read(privkeySeed) + privKey := secp256k1.GenPrivKeySecp256k1(privkeySeed) + newAccs = append(newAccs, simulation.Account{privKey, privKey.PubKey(), acc.Address}) + } + return json.RawMessage(genesis.AppState), newAccs, genesis.ChainID +} + +func appStateRandomizedFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) { + var genesisAccounts []GenesisAccount amount := int64(r.Intn(1e6)) @@ -118,6 +147,9 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T } fmt.Printf("Selected randomly generated auth parameters:\n\t%+v\n", authGenesis) + bankGenesis := bank.NewGenesisState(r.Int63n(2) == 0) + fmt.Printf("Selected randomly generated bank parameters:\n\t%+v\n", bankGenesis) + // Random genesis states vp := time.Duration(r.Intn(2*172800)) * time.Second govGenesis := gov.GenesisState{ @@ -203,6 +235,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T genesis := GenesisState{ Accounts: genesisAccounts, AuthData: authGenesis, + BankData: bankGenesis, StakingData: stakingGenesis, MintData: mintGenesis, DistrData: distrGenesis, @@ -216,7 +249,14 @@ func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.T panic(err) } - return appState + return appState, accs, "simulation" +} + +func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) (json.RawMessage, []simulation.Account, string) { + if genesisFile != "" { + return appStateFromGenesisFileFn(r, accs, genesisTimestamp) + } + return appStateRandomizedFn(r, accs, genesisTimestamp) } func randIntBetween(r *rand.Rand, min, max int) int { @@ -376,7 +416,7 @@ func TestGaiaImportExport(t *testing.T) { fmt.Printf("Exporting genesis...\n") - appState, _, err := app.ExportAppStateAndValidators(false) + appState, _, err := app.ExportAppStateAndValidators(false, []string{}) if err != nil { panic(err) } @@ -480,7 +520,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) { fmt.Printf("Exporting genesis...\n") - appState, _, err := app.ExportAppStateAndValidators(true) + appState, _, err := app.ExportAppStateAndValidators(true, []string{}) if err != nil { panic(err) } diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 2d6864ec99e2..6e2e60536c47 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -45,6 +45,14 @@ func TestGaiaCLIKeysAddMultisig(t *testing.T) { require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address) } +func TestGaiaCLIKeysAddRecover(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + f.KeysAddRecover("test-recover", "dentist task convince chimney quality leave banana trade firm crawl eternal easily") + require.Equal(t, f.KeyAddress("test-recover").String(), "cosmos1qcfdf69js922qrdr4yaww3ax7gjml6pdds46f4") +} + func TestGaiaCLIMinimumFees(t *testing.T) { t.Parallel() f := InitFixtures(t) @@ -909,10 +917,7 @@ func TestGaiadCollectGentxs(t *testing.T) { f.UnsafeResetAll() // Initialize keys - f.KeysDelete(keyFoo) - f.KeysDelete(keyBar) f.KeysAdd(keyFoo) - f.KeysAdd(keyBar) // Configure json output f.CLIConfig("output", "json") @@ -932,6 +937,42 @@ func TestGaiadCollectGentxs(t *testing.T) { f.Cleanup(gentxDir) } +func TestGaiadAddGenesisAccount(t *testing.T) { + t.Parallel() + f := NewFixtures(t) + + // Reset testing path + f.UnsafeResetAll() + + // Initialize keys + f.KeysDelete(keyFoo) + f.KeysDelete(keyBar) + f.KeysDelete(keyBaz) + f.KeysAdd(keyFoo) + f.KeysAdd(keyBar) + f.KeysAdd(keyBaz) + + // Configure json output + f.CLIConfig("output", "json") + + // Run init + f.GDInit(keyFoo) + + // Add account to genesis.json + bazCoins := sdk.Coins{ + sdk.NewInt64Coin("acoin", 1000000), + sdk.NewInt64Coin("bcoin", 1000000), + } + + f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + f.AddGenesisAccount(f.KeyAddress(keyBar), bazCoins) + genesisState := f.GenesisState() + require.Equal(t, genesisState.Accounts[0].Address, f.KeyAddress(keyFoo)) + require.Equal(t, genesisState.Accounts[1].Address, f.KeyAddress(keyBar)) + require.True(t, genesisState.Accounts[0].Coins.IsEqual(startCoins)) + require.True(t, genesisState.Accounts[1].Coins.IsEqual(bazCoins)) +} + func TestSlashingGetParams(t *testing.T) { t.Parallel() f := InitFixtures(t) @@ -944,4 +985,19 @@ func TestSlashingGetParams(t *testing.T) { require.Equal(t, time.Duration(120000000000), params.MaxEvidenceAge) require.Equal(t, int64(100), params.SignedBlocksWindow) require.Equal(t, sdk.NewDecWithPrec(5, 1), params.MinSignedPerWindow) + + sinfo := f.QuerySigningInfo(f.GDTendermint("show-validator")) + require.Equal(t, int64(0), sinfo.StartHeight) + require.False(t, sinfo.Tombstoned) +} + +func TestValidateGenesis(t *testing.T) { + t.Parallel() + f := InitFixtures(t) + + // start gaiad server + proc := f.GDStart() + defer proc.Stop(false) + + f.ValidateGenesis() } diff --git a/cmd/gaia/cli_test/test_helpers.go b/cmd/gaia/cli_test/test_helpers.go index 7571985062a1..b2288382f02c 100644 --- a/cmd/gaia/cli_test/test_helpers.go +++ b/cmd/gaia/cli_test/test_helpers.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + appInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" @@ -76,6 +77,22 @@ func NewFixtures(t *testing.T) *Fixtures { } } +// GenesisFile returns the path of the genesis file +func (f Fixtures) GenesisFile() string { + return filepath.Join(f.GDHome, "config", "genesis.json") +} + +// GenesisFile returns the application's genesis state +func (f Fixtures) GenesisState() app.GenesisState { + cdc := codec.New() + genDoc, err := appInit.LoadGenesisDoc(cdc, f.GenesisFile()) + require.NoError(f.T, err) + + var appState app.GenesisState + require.NoError(f.T, cdc.UnmarshalJSON(genDoc.AppState, &appState)) + return appState +} + // InitFixtures is called at the beginning of a test // and initializes a chain with 1 validator func InitFixtures(t *testing.T) (f *Fixtures) { @@ -179,6 +196,21 @@ func (f *Fixtures) GDStart(flags ...string) *tests.Process { return proc } +// GDTendermint returns the results of gaiad tendermint [query] +func (f *Fixtures) GDTendermint(query string) string { + cmd := fmt.Sprintf("gaiad tendermint %s --home=%s", query, f.GDHome) + success, stdout, stderr := executeWriteRetStdStreams(f.T, cmd) + require.Empty(f.T, stderr) + require.True(f.T, success) + return strings.TrimSpace(stdout) +} + +// ValidateGenesis runs gaiad validate-genesis +func (f *Fixtures) ValidateGenesis() { + cmd := fmt.Sprintf("gaiad validate-genesis --home=%s", f.GDHome) + executeWriteCheckErr(f.T, cmd) +} + //___________________________________________________________________________________ // gaiacli keys @@ -194,6 +226,12 @@ func (f *Fixtures) KeysAdd(name string, flags ...string) { executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } +// KeysAdd is gaiacli keys add --recover +func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) { + cmd := fmt.Sprintf("gaiacli keys add --home=%s --recover %s", f.GCLIHome, name) + executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass, mnemonic) +} + // KeysShow is gaiacli keys show func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput { cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name) @@ -498,6 +536,18 @@ func (f *Fixtures) QueryGovDeposits(propsalID int, flags ...string) []gov.Deposi //___________________________________________________________________________________ // query slashing +// QuerySigningInfo returns the signing info for a validator +func (f *Fixtures) QuerySigningInfo(val string) slashing.ValidatorSigningInfo { + cmd := fmt.Sprintf("gaiacli query slashing signing-info %s %s", val, f.Flags()) + res, errStr := tests.ExecuteT(f.T, cmd, "") + require.Empty(f.T, errStr) + cdc := app.MakeCodec() + var sinfo slashing.ValidatorSigningInfo + err := cdc.UnmarshalJSON([]byte(res), &sinfo) + require.NoError(f.T, err) + return sinfo +} + // QuerySlashingParams is gaiacli query slashing params func (f *Fixtures) QuerySlashingParams() slashing.Params { cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags()) diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index f390324aa383..3042c35d60eb 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -84,7 +84,7 @@ func main() { // Construct Root Command rootCmd.AddCommand( rpc.StatusCommand(), - client.ConfigCmd(), + client.ConfigCmd(app.DefaultCLIHome), queryCmd(cdc, mc), txCmd(cdc, mc), client.LineBreak, diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index 4014bef7330e..d6d88e0dfa30 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -43,6 +43,7 @@ func main() { rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc)) rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc)) rootCmd.AddCommand(gaiaInit.AddGenesisAccountCmd(ctx, cdc)) + rootCmd.AddCommand(gaiaInit.ValidateGenesisCmd(ctx, cdc)) rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true)) server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) @@ -59,13 +60,13 @@ func main() { func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application { return app.NewGaiaApp( logger, db, traceStore, true, - baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning"))), + baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))), baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)), ) } func exportAppStateAndTMValidators( - logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, ) (json.RawMessage, []tmtypes.GenesisValidator, error) { if height != -1 { gApp := app.NewGaiaApp(logger, db, traceStore, false) @@ -73,8 +74,8 @@ func exportAppStateAndTMValidators( if err != nil { return nil, nil, err } - return gApp.ExportAppStateAndValidators(forZeroHeight) + return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) } gApp := app.NewGaiaApp(logger, db, traceStore, true) - return gApp.ExportAppStateAndValidators(forZeroHeight) + return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) } diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go index 022192861e37..eb4c8749bfca 100644 --- a/cmd/gaia/cmd/gaiadebug/hack.go +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -50,7 +50,7 @@ func runHackCmd(cmd *cobra.Command, args []string) error { fmt.Println(err) os.Exit(1) } - app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptions(viper.GetString("pruning")))) + app := NewGaiaApp(logger, db, baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning")))) // print some info id := app.LastCommitID() @@ -178,7 +178,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp ) // add handlers - app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper) + app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, app.paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) app.stakingKeeper = staking.NewKeeper(app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), slashing.DefaultCodespace) diff --git a/cmd/gaia/init/collect.go b/cmd/gaia/init/collect.go index e630d494cc7d..d0842df2cbe0 100644 --- a/cmd/gaia/init/collect.go +++ b/cmd/gaia/init/collect.go @@ -44,7 +44,7 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { return err } - genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile()) if err != nil { return err } diff --git a/cmd/gaia/init/genesis_accts.go b/cmd/gaia/init/genesis_accts.go index bb9c4af0c8ce..8b580584f3b5 100644 --- a/cmd/gaia/init/genesis_accts.go +++ b/cmd/gaia/init/genesis_accts.go @@ -1,7 +1,6 @@ package init import ( - "encoding/json" "fmt" "github.com/spf13/cobra" @@ -49,7 +48,7 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command if !common.FileExists(genFile) { return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile) } - genDoc, err := loadGenesisDoc(cdc, genFile) + genDoc, err := LoadGenesisDoc(cdc, genFile) if err != nil { return err } @@ -59,7 +58,12 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command return err } - appStateJSON, err := addGenesisAccount(cdc, appState, addr, coins) + appState, err = addGenesisAccount(cdc, appState, addr, coins) + if err != nil { + return err + } + + appStateJSON, err := cdc.MarshalJSON(appState) if err != nil { return err } @@ -73,15 +77,15 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command return cmd } -func addGenesisAccount(cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress, coins sdk.Coins) (json.RawMessage, error) { +func addGenesisAccount(cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress, coins sdk.Coins) (app.GenesisState, error) { for _, stateAcc := range appState.Accounts { if stateAcc.Address.Equals(addr) { - return nil, fmt.Errorf("the application state already contains account %v", addr) + return appState, fmt.Errorf("the application state already contains account %v", addr) } } acc := auth.NewBaseAccountWithAddress(addr) acc.Coins = coins appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc)) - return cdc.MarshalJSON(appState) + return appState, nil } diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go index 25de93aab498..ec3ae3573946 100644 --- a/cmd/gaia/init/gentx.go +++ b/cmd/gaia/init/gentx.go @@ -61,12 +61,14 @@ following delegation and commission default parameters: if err != nil { return err } + ip, err := server.ExternalIP() if err != nil { - return err + fmt.Fprintf(os.Stderr, "couldn't retrieve an external IP, "+ + "consequently the tx's memo field will be unset: %s", err) } - genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile()) if err != nil { return err } diff --git a/cmd/gaia/init/testnet.go b/cmd/gaia/init/testnet.go index 111a58032697..c69384a2d659 100644 --- a/cmd/gaia/init/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -295,7 +295,7 @@ func collectGenFiles( nodeID, valPubKey := nodeIDs[i], valPubKeys[i] initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey) - genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) + genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile()) if err != nil { return err } diff --git a/cmd/gaia/init/utils.go b/cmd/gaia/init/utils.go index 149b4d10e1bb..4e2f2f26f27d 100644 --- a/cmd/gaia/init/utils.go +++ b/cmd/gaia/init/utils.go @@ -88,7 +88,8 @@ func InitializeNodeValidatorFiles( return nodeID, valPubKey, nil } -func loadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { +// LoadGenesisDoc reads and unmarshals GenesisDoc from the given file. +func LoadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { genContents, err := ioutil.ReadFile(genFile) if err != nil { return genDoc, err diff --git a/cmd/gaia/init/validate_genesis.go b/cmd/gaia/init/validate_genesis.go new file mode 100644 index 000000000000..5f5aae7099a3 --- /dev/null +++ b/cmd/gaia/init/validate_genesis.go @@ -0,0 +1,52 @@ +package init + +import ( + "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/types" +) + +// Validate genesis command takes +func ValidateGenesisCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "validate-genesis [file]", + Args: cobra.RangeArgs(0, 1), + Short: "validates the genesis file at the default location or at the location passed as an arg", + RunE: func(cmd *cobra.Command, args []string) (err error) { + + // Load default if passed no args, otherwise load passed file + var genesis string + if len(args) == 0 { + genesis = ctx.Config.GenesisFile() + } else { + genesis = args[0] + } + + //nolint + fmt.Fprintf(os.Stderr, "validating genesis file at %s\n", genesis) + + var genDoc types.GenesisDoc + if genDoc, err = LoadGenesisDoc(cdc, genesis); err != nil { + return errors.Errorf("Error loading genesis doc from %s: %s", genesis, err.Error()) + } + + var genstate app.GenesisState + if err = cdc.UnmarshalJSON(genDoc.AppState, &genstate); err != nil { + return errors.Errorf("Error unmarshaling genesis doc %s: %s", genesis, err.Error()) + } + + if err = app.GaiaValidateGenesisState(genstate); err != nil { + return errors.Errorf("Error validating genesis file %s: %s", genesis, err.Error()) + } + + fmt.Printf("File at %s is a valid genesis file for gaiad\n", genesis) + return nil + }, + } +} diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 94aaf953c0c4..cb5f8cd43c95 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -21,7 +21,7 @@ module.exports = { }, nav: [ { text: "Back to Cosmos", link: "https://cosmos.network" }, - { text: "RPC", link: "../rpc/" } + { text: "RPC", link: "https://cosmos.network/rpc/" } ], sidebar: [ { @@ -37,7 +37,7 @@ module.exports = { title: "Gaia", collapsable: false, children: [ - "/gaia/what-is-gaia" + "/gaia/what-is-gaia", "/gaia/installation", "/gaia/join-testnet", "/gaia/validators/validator-setup", diff --git a/docs/clients/lite/getting_started.md b/docs/clients/lite/getting_started.md index faa3ea91b849..47ee16f9e0e9 100644 --- a/docs/clients/lite/getting_started.md +++ b/docs/clients/lite/getting_started.md @@ -19,17 +19,18 @@ gaiacli rest-server --chain-id=test \ --trust-node=false ``` -The server listens on HTTPS by default. You can set the SSL certificate to be used by the server with these additional flags: +The server listens on HTTP by default. You can enable the secure layer by adding the `--tls` flag. +By default a self-signed certificate will be generated and its fingerprint printed out. You can +configure the server to use a SSL certificate by passing the certificate and key files via the +`--ssl-certfile` and `--ssl-keyfile` flags: ```bash gaiacli rest-server --chain-id=test \ --laddr=tcp://localhost:1317 \ --node tcp://localhost:26657 \ --trust-node=false \ + --tls \ --ssl-certfile=mycert.pem --ssl-keyfile=mykey.key ``` -If no certificate/keyfile pair is supplied, a self-signed certificate will be generated and its fingerprint printed out. -Append `--insecure` to the command line if you want to disable the secure layer and listen on an insecure HTTP port. - For more information about the Gaia-Lite RPC, see the [swagger documentation](https://cosmos.network/rpc/) diff --git a/docs/clients/service-providers.md b/docs/clients/service-providers.md index 358031b966f4..68024d64132a 100644 --- a/docs/clients/service-providers.md +++ b/docs/clients/service-providers.md @@ -105,10 +105,19 @@ The recommended way to listen for incoming transaction is to periodically query ## Rest API -The Rest API documents all the available endpoints that you can use to interract with your full node. It can be found [here](https://cosmos.network/rpc/). - -The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://cosmos.network/rpc/#/ICS20/) describes the API to interact with tokens. - -To give more flexibility to implementers, we have included the ability to generate unsigned transactions, [sign](https://cosmos.network/rpc/#/ICS20/post_tx_sign) and [broadcast](https://cosmos.network/rpc/#/ICS20/post_tx_broadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance. - -In order to generate an unsigned transaction (example with [coin transfer](https://cosmos.network/rpc/#/ICS20/post_bank_accounts__address__transfers)), you need to use the flag `?generate_only`. +The Rest API documents all the available endpoints that you can use to interact +with your full node. It can be found [here](https://cosmos.network/rpc/). + +The API is divided into ICS standards for each category of endpoints. For +example, the [ICS20](https://cosmos.network/rpc/#/ICS20/) describes the API to +interact with tokens. + +To give more flexibility to implementers, we have included the ability to +generate unsigned transactions, [sign](https://cosmos.network/rpc/#/ICS20/post_tx_sign) +and [broadcast](https://cosmos.network/rpc/#/ICS20/post_tx_broadcast) them with +different API endpoints. This allows service providers to use their own signing +mechanism for instance. + +In order to generate an unsigned transaction (example with +[coin transfer](https://cosmos.network/rpc/#/ICS20/post_bank_accounts__address__transfers)), +you need to use the field `generate_only` in the body of `base_req`. diff --git a/docs/gaia/gaiacli.md b/docs/gaia/gaiacli.md index 5982c30d4eda..a3ad6dc36952 100644 --- a/docs/gaia/gaiacli.md +++ b/docs/gaia/gaiacli.md @@ -209,7 +209,8 @@ You can also check your balance at a given block by using the `--block` flag: gaiacli query account --block= ``` -You can simulate a transaction without actually broadcasting it by appending the `--dry-run` flag to the command line: +You can simulate a transaction without actually broadcasting it by appending the +`--dry-run` flag to the command line: ```bash gaiacli tx send \ @@ -220,7 +221,8 @@ gaiacli tx send \ --dry-run ``` -Furthermore, you can build a transaction and print its JSON format to STDOUT by appending `--generate-only` to the list of the command line arguments: +Furthermore, you can build a transaction and print its JSON format to STDOUT by +appending `--generate-only` to the list of the command line arguments: ```bash gaiacli tx send \ @@ -231,8 +233,6 @@ gaiacli tx send \ --generate-only > unsignedSendTx.json ``` -You can now sign the transaction file generated through the `--generate-only` flag by providing your key to the following command: - ```bash gaiacli tx sign \ --chain-id= \ @@ -676,6 +676,14 @@ To check current rewards for a delegation (were they to be withdrawn), run: gaiacli query distr rewards ``` +#### Query all delegator rewards + +To check all current rewards for a delegation (were they to be withdrawn), run: + +```bash +gaiacli query distr rewards +``` + ### Multisig transactions Multisig transactions require signatures of multiple private keys. Thus, generating and signing diff --git a/docs/gaia/genesis.md b/docs/gaia/genesis.md new file mode 100644 index 000000000000..727944fbc971 --- /dev/null +++ b/docs/gaia/genesis.md @@ -0,0 +1,60 @@ +# Gaia Genesis State + +Gaia genesis state, `GenesisState`, is composed of accounts, various module +states and metadata such as genesis transactions. Each module may specify its +own `GenesisState`. In addition, each module may specify its own genesis state +validation, import and export functionality. + +The Gaia genesis state is defined as follows: + +```go +type GenesisState struct { + Accounts []GenesisAccount `json:"accounts"` + AuthData auth.GenesisState `json:"auth"` + BankData bank.GenesisState `json:"bank"` + StakingData staking.GenesisState `json:"staking"` + MintData mint.GenesisState `json:"mint"` + DistrData distr.GenesisState `json:"distr"` + GovData gov.GenesisState `json:"gov"` + SlashingData slashing.GenesisState `json:"slashing"` + GenTxs []json.RawMessage `json:"gentxs"` +} +``` + +In the ABCI `initChainer` definition of Gaia the `initFromGenesisState` is called +which internally calls each module's `InitGenesis` providing its own respective +`GenesisState` as a parameter. + +## Accounts + +Genesis accounts defined in the `GenesisState` are defined as follows: + +```go +type GenesisAccount struct { + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Sequence uint64 `json:"sequence_number"` + AccountNumber uint64 `json:"account_number"` + + // vesting account fields + OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization + DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation + DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation + StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time) + EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time) +} +``` + +Each account must have a valid and unique account number in addition to a +sequence number (nonce) and address. + +Accounts may also be vesting, in which case they must provide the necessary vesting +information. Vesting accounts must provide at a minimum `OriginalVesting` and +`EndTime`. If `StartTime` is also provided, the account will be treated as a +"continuous" vesting account in which it vests coins at a predefined schedule. +Providing a `StartTime` must be less than `EndTime` but may be in the future. +In other words, it does not have to be equal to the genesis time. In a new chain +starting from a fresh state (not exported), `OriginalVesting` must be less than +or equal to `Coins.` + + diff --git a/docs/gaia/installation.md b/docs/gaia/installation.md index 59d6d35a7c26..84b4f92697bd 100644 --- a/docs/gaia/installation.md +++ b/docs/gaia/installation.md @@ -14,7 +14,7 @@ echo "export PATH=$PATH:$GOBIN" >> ~/.bash_profile ``` ::: tip -**Go 1.11.4+** is required for the Cosmos SDK. +**Go 1.11.5+** is required for the Cosmos SDK. ::: ### Install the binaries @@ -28,7 +28,7 @@ mkdir -p $GOPATH/src/github.com/cosmos cd $GOPATH/src/github.com/cosmos git clone https://github.com/cosmos/cosmos-sdk cd cosmos-sdk && git checkout master -make tools && make get_vendor_deps && make install +make tools install ``` > *NOTE*: If you have issues at this step, please check that you have the latest stable version of GO installed. diff --git a/docs/gaia/join-testnet.md b/docs/gaia/join-testnet.md index 2cb2fedf87c6..91b3afef613f 100644 --- a/docs/gaia/join-testnet.md +++ b/docs/gaia/join-testnet.md @@ -76,7 +76,7 @@ Now it is time to upgrade the software: ```bash cd $GOPATH/src/github.com/cosmos/cosmos-sdk git fetch --all && git checkout master -make update_tools && make get_vendor_deps && make install +make update_tools install ``` ::: tip diff --git a/docs/gaia/ledger.md b/docs/gaia/ledger.md index ce03c88b2e89..71c41603e5f0 100644 --- a/docs/gaia/ledger.md +++ b/docs/gaia/ledger.md @@ -15,15 +15,15 @@ Once you have the Cosmos app installed on your Ledger, and the Ledger is accessible from the machine you are using `gaiacli` from you can create a new account key using the Ledger: ```bash -$ gaiacli keys add {{ .Key.Name }} --ledger +$ gaiacli keys add { .Key.Name } --ledger NAME: TYPE: ADDRESS: PUBKEY: -{{ .Key.Name }} ledger cosmos1aw64xxr80lwqqdk8u2xhlrkxqaxamkr3e2g943 cosmospub1addwnpepqvhs678gh9aqrjc2tg2vezw86csnvgzqq530ujkunt5tkuc7lhjkz5mj629 +{ .Key.Name } ledger cosmos1aw64xxr80lwqqdk8u2xhlrkxqaxamkr3e2g943 cosmospub1addwnpepqvhs678gh9aqrjc2tg2vezw86csnvgzqq530ujkunt5tkuc7lhjkz5mj629 ``` This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following: ```bash -$ gaiacli tx send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 +$ gaiacli tx send --from { .Key.Name } --to { .Destination.AccAddr } --chain-id=gaia-7000 ``` You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake! diff --git a/docs/spec/slashing/tombstone.md b/docs/spec/slashing/tombstone.md new file mode 100644 index 000000000000..d6ba47d5f62f --- /dev/null +++ b/docs/spec/slashing/tombstone.md @@ -0,0 +1,121 @@ +# Staking Tombstone + +## Abstract + +In the current implementation of the `slashing` module, when the consensus engine +informs the state machine of a validator's consensus fault, the validator is +partially slashed, and put into a "jail period", a period of time in which they +are not allowed to rejoin the validator set. However, because of the nature of +consensus faults and ABCI, there can be a delay between an infraction occurring, +and evidence of the infraction reaching the state machine (this is one of the +primary reasons for the existence of the unbonding period). + +> Note: The tombstone concept, only applies to faults that have a delay between +the infraction occurring and evidence reaching the state machine. For example, +evidence of a validator double signing may take a while to reach the state machine +due to unpredictable evidence gossip layer delays and the ability of validators to +selectively reveal double-signatures (e.g. to infrequently-online light clients). +Liveness slashing, on the other hand, is detected immediately as soon as the +infraction occurs, and therefore no slashing period is needed. A validator is +immediately put into jail period, and they cannot commit another liveness fault +until they unjail. In the future, there may be other types of byzantine faults +that have delays (for example, submitting evidence of an invalid proposal as a transaction). +When implemented, it will have to be decided whether these future types of +byzantine faults will result in a tombstoning (and if not, the slash amounts +will not be capped by a slashing period). + +In the current system design, once a validator is put in the jail for a consensus +fault, after the `JailPeriod` they are allowed to send a transaction to `unjail` +themselves, and thus rejoin the validator set. + +One of the "design desires" of the `slashing` module is that if multiple +infractions occur before evidence is executed (and a validator is put in jail), +they should only be punished for single worst infraction, but not cumulatively. +For example, if the sequence of events is: + +1. Validator A commits Infraction 1 (worth 30% slash) +2. Validator A commits Infraction 2 (worth 40% slash) +3. Validator A commits Infraction 3 (worth 35% slash) +4. Evidence for Infraction 1 reaches state machine (and validator is put in jail) +5. Evidence for Infraction 2 reaches state machine +6. Evidence for Infraction 3 reaches state machine + +Only Infraction 2 should have its slash take effect, as it is the highest. This +is done, so that in the case of the compromise of a validator's consensus key, +they will only be punished once, even if the hacker double-signs many blocks. +Because, the unjailing has to be done with the validator's operator key, they +have a chance to re-secure their consensus key, and then signal that they are +ready using their operator key. We call this period during which we track only +the max infraction, the "slashing period". + +Once, a validator rejoins by unjailing themselves, we begin a new slashing period; +if they commit a new infraction after unjailing, it gets slashed cumulatively on +top of the worst infraction from the previous slashing period. + +However, while infractions are grouped based off of the slashing periods, because +evidence can be submitted up to an `unbondingPeriod` after the infraction, we +still have to allow for evidence to be submitted for previous slashing periods. +For example, if the sequence of events is: + +1. Validator A commits Infraction 1 (worth 30% slash) +2. Validator A commits Infraction 2 (worth 40% slash) +3. Evidence for Infraction 1 reaches state machine (and Validator A is put in jail) +4. Validator A unjails + +We are now in a new slashing period, however we still have to keep the door open +for the previous infraction, as the evidence for Infraction 2 may still come in. +As the number of slashing periods increase, it creates more complexity as we have +to keep track of the highest infraction amount for every single slashing period. + +> Note: Currently, according to the `slashing` module spec, a new slashing period +is created every time a validator is unbonded then rebonded. This should probably +be changed to jailed/unjailed. See issue [#3205](https://github.com/cosmos/cosmos-sdk/issues/3205) +for further details. For the remainder of this, I will assume that we only start +a new slashing period when a validator gets unjailed. + +The maximum number of slashing periods is the `len(UnbondingPeriod) / len(JailPeriod)`. +The current defaults in Gaia for the `UnbondingPeriod` and `JailPeriod` are 3 weeks +and 2 days, respectively. This means there could potentially be up to 11 slashing +periods concurrently being tracked per validator. If we set the `JailPeriod >= UnbondingPeriod`, +we only have to track 1 slashing period (i.e not have to track slashing periods). + +Currently, in the jail period implementation, once a validator unjails, all of +their delegators who are delegated to them (haven't unbonded / redelegated away), +stay with them. Given that consensus safety faults are so egregious +(way more so than liveness faults), it is probably prudent to have delegators not +"auto-rebond" to the validator. Thus, we propose setting the "jail time" for a +validator who commits a consensus safety fault, to `infinite` (i.e. a tombstone state). +This essentially kicks the validator out of the validator set and does not allow +them to re-enter the validator set. All of their delegators (including the operator themselves) +have to either unbond or redelegate away. The validator operator can create a new +validator if they would like, with a new operator key and consensus key, but they +have to "re-earn" their delegations back. To put the validator in the tombstone +state, we set `DoubleSignJailEndTime` to `time.Unix(253402300800)`, the maximum +time supported by Amino. + +Implementing the tombstone system and getting rid of the slashing period tracking +will make the `slashing` module way simpler, especially because we can remove all +of the hooks defined in the `slashing` module consumed by the `staking` module +(the `slashing` module still consumes hooks defined in `staking`). + +### Single slashing amount + +Another optimization that can be made is that if we assume that all ABCI faults +for Tendermint consensus are slashed at the same level, we don't have to keep +track of "max slash". Once an ABCI fault happens, we don't have to worry about +comparing potential future ones to find the max. + +Currently the only Tendermint ABCI fault is: + +- Unjustified precommits (double signs) + +It is currently planned to include the following fault in the near future: + +- Signing a precommit when you're in unbonding phase (needed to make light client bisection safe) + +Given that these faults are both attributable byzantine faults, we will likely +want to slash them equally, and thus we can enact the above change. + +> Note: This change may make sense for current Tendermint consensus, but maybe +not for a different consensus algorithm or future versions of Tendermint that +may want to punish at different levels (for example, partial slashing). diff --git a/scripts/Makefile b/scripts/Makefile index 910c6f68db22..4a0a7d157b5f 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -45,7 +45,7 @@ $(GOPATH)/bin/dep: #v2.0.11 $(GOPATH)/bin/gometalinter: - $(call go_install,alecthomas,gometalinter,17a7ffa42374937bfecabfb8d2efbd4db0c26741) + $(call go_install,alecthomas,gometalinter,v3.0.0) $(GOPATH)/bin/statik: $(call go_install,rakyll,statik,v0.1.5) @@ -53,4 +53,7 @@ $(GOPATH)/bin/statik: $(GOPATH)/bin/goimports: go get golang.org/x/tools/cmd/goimports -.PHONY: all +tools-clean: + cd $(GOPATH)/bin && rm -f dep gometalinter statik goimports + +.PHONY: all clean-tools diff --git a/scripts/install/install_sdk_arm.sh b/scripts/install/install_sdk_arm.sh index 951e2830c2a3..f7aa0ec86c66 100644 --- a/scripts/install/install_sdk_arm.sh +++ b/scripts/install/install_sdk_arm.sh @@ -4,7 +4,7 @@ BRANCH=master REPO=github.com/cosmos/cosmos-sdk -GO_VERSION=1.11.4 +GO_VERSION=1.11.5 sudo apt-get update -y sudo apt-get upgrade -y diff --git a/scripts/install/install_sdk_bsd.sh b/scripts/install/install_sdk_bsd.sh index fda0eb1e1dc2..684274e9963c 100644 --- a/scripts/install/install_sdk_bsd.sh +++ b/scripts/install/install_sdk_bsd.sh @@ -16,7 +16,7 @@ set BRANCH=master set REPO=github.com/cosmos/cosmos-sdk -set GO_VERSION=1.11.4 +set GO_VERSION=1.11.5 sudo pkg update diff --git a/scripts/install/install_sdk_ubuntu.sh b/scripts/install/install_sdk_ubuntu.sh index 3ac23ccaeebd..9d235f41c0fe 100644 --- a/scripts/install/install_sdk_ubuntu.sh +++ b/scripts/install/install_sdk_ubuntu.sh @@ -7,7 +7,7 @@ BRANCH=master REPO=github.com/cosmos/cosmos-sdk -GO_VERSION=1.11.4 +GO_VERSION=1.11.5 sudo apt-get update -y sudo apt-get upgrade -y diff --git a/scripts/multisim.sh b/scripts/multisim.sh index e27035983dde..e8df463dcf8a 100755 --- a/scripts/multisim.sh +++ b/scripts/multisim.sh @@ -5,10 +5,12 @@ seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823 blocks=$1 period=$2 testname=$3 +genesis=$4 echo "Running multi-seed simulation with seeds ${seeds[@]}" echo "Running $blocks blocks per seed" echo "Running test $testname" +echo "Using genesis file $genesis" echo "Edit scripts/multisim.sh to add new seeds. Keeping parameters in the file makes failures easy to reproduce." echo "This script will kill all sub-simulations on SIGINT/SIGTERM (i.e. Ctrl-C)." @@ -22,7 +24,7 @@ sim() { echo "Running Gaia simulation with seed $seed. This may take awhile!" file="$tmpdir/gaia-simulation-seed-$seed-date-$(date -u +"%Y-%m-%dT%H:%M:%S+00:00").stdout" echo "Writing stdout to $file..." - go test ./cmd/gaia/app -run $testname -SimulationEnabled=true -SimulationNumBlocks=$blocks \ + go test ./cmd/gaia/app -run $testname -SimulationEnabled=true -SimulationNumBlocks=$blocks -SimulationGenesis=$genesis \ -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -SimulationPeriod=$period -v -timeout 24h > $file } diff --git a/server/constructors.go b/server/constructors.go index 909bda8e2535..5a51937a9a95 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -19,7 +19,7 @@ type ( // AppExporter is a function that dumps all app state to // JSON-serializable structure and returns the current validator set. - AppExporter func(log.Logger, dbm.DB, io.Writer, int64, bool) (json.RawMessage, []tmtypes.GenesisValidator, error) + AppExporter func(log.Logger, dbm.DB, io.Writer, int64, bool, []string) (json.RawMessage, []tmtypes.GenesisValidator, error) ) func openDB(rootDir string) (dbm.DB, error) { diff --git a/server/export.go b/server/export.go index 16187fa664e5..1637b1dd2e81 100644 --- a/server/export.go +++ b/server/export.go @@ -18,6 +18,7 @@ import ( const ( flagHeight = "height" flagForZeroHeight = "for-zero-height" + flagJailWhitelist = "jail-whitelist" ) // ExportCmd dumps app state to JSON. @@ -54,7 +55,8 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C } height := viper.GetInt64(flagHeight) forZeroHeight := viper.GetBool(flagForZeroHeight) - appState, validators, err := appExporter(ctx.Logger, db, traceWriter, height, forZeroHeight) + jailWhiteList := viper.GetStringSlice(flagJailWhitelist) + appState, validators, err := appExporter(ctx.Logger, db, traceWriter, height, forZeroHeight, jailWhiteList) if err != nil { return errors.Errorf("error exporting state: %v\n", err) } @@ -78,6 +80,7 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C } cmd.Flags().Int64(flagHeight, -1, "Export state from a particular height (-1 means latest height)") cmd.Flags().Bool(flagForZeroHeight, false, "Export state to start at height zero (perform preproccessing)") + cmd.Flags().StringSlice(flagJailWhitelist, []string{}, "List of validators to not jail state export") return cmd } diff --git a/server/mock/store.go b/server/mock/store.go index 3aecc1734407..2d18159aed01 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -26,19 +26,15 @@ func (ms multiStore) CacheWrapWithTrace(_ io.Writer, _ sdk.TraceContext) sdk.Cac panic("not implemented") } -func (ms multiStore) ResetTraceContext() sdk.MultiStore { - panic("not implemented") -} - func (ms multiStore) TracingEnabled() bool { panic("not implemented") } -func (ms multiStore) WithTracingContext(tc sdk.TraceContext) sdk.MultiStore { +func (ms multiStore) SetTracingContext(tc sdk.TraceContext) sdk.MultiStore { panic("not implemented") } -func (ms multiStore) WithTracer(w io.Writer) sdk.MultiStore { +func (ms multiStore) SetTracer(w io.Writer) sdk.MultiStore { panic("not implemented") } diff --git a/snapcraft.yaml b/snapcraft.yaml new file mode 100644 index 000000000000..0c24dafe0a40 --- /dev/null +++ b/snapcraft.yaml @@ -0,0 +1,45 @@ +name: gaia +version: git +summary: Gaia Daemon # 79 char long summary +description: | + This snap provides the Gaia daemon gaiad and the command line + tool gaiacli. +grade: devel # must be 'stable' to release into candidate/stable channels +confinement: strict + +apps: + gaiad: + command: bin/gaiad + plugs: [home,network,network-bind] + gaiacli: + command: bin/gaiacli + plugs: [home,network,network-bind] + +parts: + gaia: + plugin: dump + source: ./ + override-pull: | + rootdir=$(pwd) + gitroot=$(git rev-parse --show-toplevel) + cd ${gitroot} && git archive \ + -o ${rootdir}/gaia-git.tar.gz \ + --format tar.gz -9 --prefix gaia-git/ HEAD + cd ${rootdir} + tar xf gaia-git.tar.gz ; rm -f gaia-git.tar.gz + mkdir -p go/src/github.com/cosmos bin + mv gaia-git/ go/src/github.com/cosmos/cosmos-sdk/ + + build-snaps: [go] + override-build: | + base=`pwd` + export GOPATH=`pwd`/go + export GOBIN=$GOPATH/bin + export PATH=$GOBIN:$PATH + cd $GOPATH/src/github.com/cosmos/cosmos-sdk + make tools + make vendor-deps + make install + mkdir $SNAPCRAFT_PART_INSTALL/bin + cp $GOPATH/bin/gaiad $SNAPCRAFT_PART_INSTALL/bin + cp $GOPATH/bin/gaiacli $SNAPCRAFT_PART_INSTALL/bin diff --git a/snapcraft.yaml.in b/snapcraft.yaml.in new file mode 100644 index 000000000000..8bb2ad73d72d --- /dev/null +++ b/snapcraft.yaml.in @@ -0,0 +1,35 @@ +name: gaia # you probably want to 'snapcraft register ' +# base: core18 # the base snap is the execution environment for this snap +version: '@VERSION@' # just for humans, typically '1.2+git' or '1.3.2' +summary: Gaia Daemon # 79 char long summary +description: | + This snap provides the Gaia daemon gaiad and the command line + tool gaiacli. +grade: devel # must be 'stable' to release into candidate/stable channels +confinement: strict # use 'strict' once you have the right plugs and slots + +apps: + gaiad: + command: bin/gaiad + plugs: [home,network,network-bind] + gaiacli: + command: bin/gaiacli + plugs: [home,network,network-bind] + +parts: + gaia: + plugin: dump + source: ./ + override-pull: | + echo "Installing files from $GOBIN ..." + + # Use the following instructions to build a package from a release. + # wget https://github.com/cosmos/cosmos-sdk/archive/v@VERSION@.tar.gz + # tar xvf v@VERSION@.tar.gz + # rm v@VERSION@.tar.gz + + build-snaps: [go] + override-build: | + mkdir -p $SNAPCRAFT_PART_INSTALL/bin + cp $GOBIN/gaiad $SNAPCRAFT_PART_INSTALL/bin + cp $GOBIN/gaiacli $SNAPCRAFT_PART_INSTALL/bin diff --git a/store/README.md b/store/README.md new file mode 100644 index 000000000000..54994374bcd6 --- /dev/null +++ b/store/README.md @@ -0,0 +1,130 @@ +# Store + +## CacheKV + +`cachekv.Store` is a wrapper `KVStore` which provides buffered writing / cached reading functionalities over the underlying `KVStore`. + +```go +type Store struct { + cache map[string]cValue + parent types.KVStore +} +``` + +### Get + +`Store.Get()` checks `Store.cache` first in order to find if there is any cached value associated with the key. If the value exists, the function returns it. If not, the function calls `Store.parent.Get()`, sets the key-value pair to the `Store.cache`, and returns it. + +### Set + +`Store.Set()` sets the key-value pair to the `Store.cache`. `cValue` has the field `dirty bool` which indicates whether the cached value is different from the underlying value. When `Store.Set()` cache new pair, the `cValue.dirty` is set true so when `Store.Write()` is called it can be written to the underlying store. + +### Iterator + +`Store.Iterator()` have to traverse on both caches items and the original items. In `Store.iterator()`, two iterators are generated for each of them, and merged. `memIterator` is essentially a slice of the `KVPair`s, used for cached items. `mergeIterator` is a combination of two iterators, where traverse happens ordered on both iterators. + +## CacheMulti + +`cachemulti.Store` is a wrapper `MultiStore` which provides buffered writing / cached reading functionalities over the underlying `MutliStore` + +```go +type Store struct { + db types.CacheKVStore + stores map[types.StoreKey] types.CacheWrap +} +``` + +`cachemulti.Store` cache wraps all substores in its constructor and hold them in `Store.stores`. `Store.GetKVStore()` returns the store from `Store.stores`, and `Store.Write()` recursively calls `CacheWrap.Write()` on the substores. + +## DBAdapter + +`dbadapter.Store` is a adapter for `dbm.DB` making it fulfilling the `KVStore` interface. + +```go +type Store struct { + dbm.DB +} +``` + +`dbadapter.Store` embeds `dbm.DB`, so most of the `KVStore` interface functions are implemented. The other functions(mostly miscellaneous) are manually implemented. + +## IAVL + +`iavl.Store` is a base-layer self-balancing merkle tree. It is guaranteed that + +1. Get & set operations are `O(log n)`, where `n` is the number of elements in the tree +2. Iteration efficiently returns the sorted elements within the range +3. Each tree version is immutable and can be retrieved even after a commit(depending on the pruning settings) + +Specification and implementation of IAVL tree can be found in [https://github.com/tendermint/iavl]. + +## GasKV + +`gaskv.Store` is a wrapper `KVStore` which provides gas consuming functionalities over the underlying `KVStore`. + +```go +type Store struct { + gasMeter types.GasMeter + gasConfig types.GasConfig + parent types.KVStore +} +``` + +When each `KVStore` methods are called, `gaskv.Store` automatically consumes appropriate amount of gas depending on the `Store.gasConfig`. + + +## Prefix + +`prefix.Store` is a wrapper `KVStore` which provides automatic key-prefixing functionalities over the underlying `KVStore`. + +```go +type Store struct { + parent types.KVStore + prefix []byte +} +``` + +When `Store.{Get, Set}()` is called, the store forwards the call to its parent, with the key prefixed with the `Store.prefix`. + +When `Store.Iterator()` is called, it does not simply prefix the `Store.prefix`, since it does not work as intended. In that case, some of the elements are traversed even they are not starting with the prefix. + +## RootMulti + +`rootmulti.Store` is a base-layer `MultiStore` where multiple `KVStore` can be mounted on it and retrieved via object-capability keys. The keys are memory addresses, so it is impossible to forge the key unless an object is a valid owner(or a receiver) of the key, according to the object capability principles. + +## TraceKV + +`tracekv.Store` is a wrapper `KVStore` which provides operation tracing functionalities over the underlying `KVStore`. + +```go +type Store struct { + parent types.KVStore + writer io.Writer + context types.TraceContext +} +``` + +When each `KVStore` methods are called, `tracekv.Store` automatically logs `traceOperation` to the `Store.writer`. + +```go +type traceOperation struct { + Operation operation + Key string + Value string + Metadata map[string]interface{} +} +``` + +`traceOperation.Metadata` is filled with `Store.context` when it is not nil. `TraceContext` is a `map[string]interface{}`. + +## Transient + +`transient.Store` is a base-layer `KVStore` which is automatically discarded at the end of the block. + +```go +type Store struct { + dbadapter.Store +} +``` + +`Store.Store` is a `dbadapter.Store` with a `dbm.NewMemDB()`. All `KVStore` methods are reused. When `Store.Commit()` is called, new `dbadapter.Store` is assigned, discarding previous reference and making it garbage collected. diff --git a/store/memiterator.go b/store/cachekv/memiterator.go similarity index 98% rename from store/memiterator.go rename to store/cachekv/memiterator.go index c9a026cb5ff1..972280c42a0a 100644 --- a/store/memiterator.go +++ b/store/cachekv/memiterator.go @@ -1,4 +1,4 @@ -package store +package cachekv import ( "bytes" diff --git a/store/cachemergeiterator.go b/store/cachekv/mergeiterator.go similarity index 95% rename from store/cachemergeiterator.go rename to store/cachekv/mergeiterator.go index cd739500de76..d45303e074e0 100644 --- a/store/cachemergeiterator.go +++ b/store/cachekv/mergeiterator.go @@ -1,7 +1,9 @@ -package store +package cachekv import ( "bytes" + + "github.com/cosmos/cosmos-sdk/store/types" ) // cacheMergeIterator merges a parent Iterator and a cache Iterator. @@ -12,14 +14,14 @@ import ( // // TODO: Optimize by memoizing. type cacheMergeIterator struct { - parent Iterator - cache Iterator + parent types.Iterator + cache types.Iterator ascending bool } -var _ Iterator = (*cacheMergeIterator)(nil) +var _ types.Iterator = (*cacheMergeIterator)(nil) -func newCacheMergeIterator(parent, cache Iterator, ascending bool) *cacheMergeIterator { +func newCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheMergeIterator { iter := &cacheMergeIterator{ parent: parent, cache: cache, diff --git a/store/cachekv/store.go b/store/cachekv/store.go new file mode 100644 index 000000000000..673a101efa61 --- /dev/null +++ b/store/cachekv/store.go @@ -0,0 +1,196 @@ +package cachekv + +import ( + "bytes" + "io" + "sort" + "sync" + + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/types" + + "github.com/cosmos/cosmos-sdk/store/tracekv" +) + +// If value is nil but deleted is false, it means the parent doesn't have the +// key. (No need to delete upon Write()) +type cValue struct { + value []byte + deleted bool + dirty bool +} + +// Store wraps an in-memory cache around an underlying types.KVStore. +type Store struct { + mtx sync.Mutex + cache map[string]cValue + parent types.KVStore +} + +var _ types.CacheKVStore = (*Store)(nil) + +// nolint +func NewStore(parent types.KVStore) *Store { + return &Store{ + cache: make(map[string]cValue), + parent: parent, + } +} + +// Implements Store. +func (store *Store) GetStoreType() types.StoreType { + return store.parent.GetStoreType() +} + +// Implements types.KVStore. +func (store *Store) Get(key []byte) (value []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + types.AssertValidKey(key) + + cacheValue, ok := store.cache[string(key)] + if !ok { + value = store.parent.Get(key) + store.setCacheValue(key, value, false, false) + } else { + value = cacheValue.value + } + + return value +} + +// Implements types.KVStore. +func (store *Store) Set(key []byte, value []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + types.AssertValidKey(key) + types.AssertValidValue(value) + + store.setCacheValue(key, value, false, true) +} + +// Implements types.KVStore. +func (store *Store) Has(key []byte) bool { + value := store.Get(key) + return value != nil +} + +// Implements types.KVStore. +func (store *Store) Delete(key []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + types.AssertValidKey(key) + + store.setCacheValue(key, nil, true, true) +} + +// Implements Cachetypes.KVStore. +func (store *Store) Write() { + store.mtx.Lock() + defer store.mtx.Unlock() + + // We need a copy of all of the keys. + // Not the best, but probably not a bottleneck depending. + keys := make([]string, 0, len(store.cache)) + for key, dbValue := range store.cache { + if dbValue.dirty { + keys = append(keys, key) + } + } + + sort.Strings(keys) + + // TODO: Consider allowing usage of Batch, which would allow the write to + // at least happen atomically. + for _, key := range keys { + cacheValue := store.cache[key] + if cacheValue.deleted { + store.parent.Delete([]byte(key)) + } else if cacheValue.value == nil { + // Skip, it already doesn't exist in parent. + } else { + store.parent.Set([]byte(key), cacheValue.value) + } + } + + // Clear the cache + store.cache = make(map[string]cValue) +} + +//---------------------------------------- +// To cache-wrap this Store further. + +// Implements CacheWrapper. +func (store *Store) CacheWrap() types.CacheWrap { + return NewStore(store) +} + +// CacheWrapWithTrace implements the CacheWrapper interface. +func (store *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap { + return NewStore(tracekv.NewStore(store, w, tc)) +} + +//---------------------------------------- +// Iteration + +// Implements types.KVStore. +func (store *Store) Iterator(start, end []byte) types.Iterator { + return store.iterator(start, end, true) +} + +// Implements types.KVStore. +func (store *Store) ReverseIterator(start, end []byte) types.Iterator { + return store.iterator(start, end, false) +} + +func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { + var parent, cache types.Iterator + + if ascending { + parent = store.parent.Iterator(start, end) + } else { + parent = store.parent.ReverseIterator(start, end) + } + + items := store.dirtyItems(start, end, ascending) + cache = newMemIterator(start, end, items) + + return newCacheMergeIterator(parent, cache, ascending) +} + +// Constructs a slice of dirty items, to use w/ memIterator. +func (store *Store) dirtyItems(start, end []byte, ascending bool) []cmn.KVPair { + items := make([]cmn.KVPair, 0) + + for key, cacheValue := range store.cache { + if !cacheValue.dirty { + continue + } + if dbm.IsKeyInDomain([]byte(key), start, end) { + items = append(items, cmn.KVPair{Key: []byte(key), Value: cacheValue.value}) + } + } + + sort.Slice(items, func(i, j int) bool { + if ascending { + return bytes.Compare(items[i].Key, items[j].Key) < 0 + } + return bytes.Compare(items[i].Key, items[j].Key) > 0 + }) + + return items +} + +//---------------------------------------- +// etc + +// Only entrypoint to mutate store.cache. +func (store *Store) setCacheValue(key, value []byte, deleted bool, dirty bool) { + store.cache[string(key)] = cValue{ + value: value, + deleted: deleted, + dirty: dirty, + } +} diff --git a/store/cachekvstore_test.go b/store/cachekv/store_test.go similarity index 89% rename from store/cachekvstore_test.go rename to store/cachekv/store_test.go index 37e0364fb5aa..30c8d148ac1d 100644 --- a/store/cachekvstore_test.go +++ b/store/cachekv/store_test.go @@ -1,4 +1,4 @@ -package store +package cachekv_test import ( "fmt" @@ -7,19 +7,23 @@ import ( "github.com/stretchr/testify/require" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/types" ) -func newCacheKVStore() CacheKVStore { - mem := dbStoreAdapter{dbm.NewMemDB()} - return NewCacheKVStore(mem) +func newCacheKVStore() types.CacheKVStore { + mem := dbadapter.Store{dbm.NewMemDB()} + return cachekv.NewStore(mem) } func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } func TestCacheKVStore(t *testing.T) { - mem := dbStoreAdapter{dbm.NewMemDB()} - st := NewCacheKVStore(mem) + mem := dbadapter.Store{dbm.NewMemDB()} + st := cachekv.NewStore(mem) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") @@ -45,11 +49,11 @@ func TestCacheKVStore(t *testing.T) { require.Equal(t, valFmt(2), st.Get(keyFmt(1))) // make a new one, check it - st = NewCacheKVStore(mem) + st = cachekv.NewStore(mem) require.Equal(t, valFmt(2), st.Get(keyFmt(1))) // make a new one and delete - should not be removed from mem - st = NewCacheKVStore(mem) + st = cachekv.NewStore(mem) st.Delete(keyFmt(1)) require.Empty(t, st.Get(keyFmt(1))) require.Equal(t, mem.Get(keyFmt(1)), valFmt(2)) @@ -60,9 +64,15 @@ func TestCacheKVStore(t *testing.T) { require.Empty(t, mem.Get(keyFmt(1)), "Expected `key1` to be empty") } +func TestCacheKVStoreNoNilSet(t *testing.T) { + mem := dbadapter.Store{dbm.NewMemDB()} + st := cachekv.NewStore(mem) + require.Panics(t, func() { st.Set([]byte("key"), nil) }, "setting a nil value should panic") +} + func TestCacheKVStoreNested(t *testing.T) { - mem := dbStoreAdapter{dbm.NewMemDB()} - st := NewCacheKVStore(mem) + mem := dbadapter.Store{dbm.NewMemDB()} + st := cachekv.NewStore(mem) // set. check its there on st and not on mem. st.Set(keyFmt(1), valFmt(1)) @@ -70,7 +80,7 @@ func TestCacheKVStoreNested(t *testing.T) { require.Equal(t, valFmt(1), st.Get(keyFmt(1))) // make a new from st and check - st2 := NewCacheKVStore(st) + st2 := cachekv.NewStore(st) require.Equal(t, valFmt(1), st2.Get(keyFmt(1))) // update the value on st2, check it only effects st2 @@ -312,7 +322,7 @@ func randInt(n int) int { } // useful for replaying a error case if we find one -func doOp(st CacheKVStore, truth dbm.DB, op int, args ...int) { +func doOp(st types.CacheKVStore, truth dbm.DB, op int, args ...int) { switch op { case opSet: k := args[0] @@ -335,7 +345,7 @@ func doOp(st CacheKVStore, truth dbm.DB, op int, args ...int) { } } -func doRandomOp(st CacheKVStore, truth dbm.DB, maxKey int) { +func doRandomOp(st types.CacheKVStore, truth dbm.DB, maxKey int) { r := randInt(totalOps) switch r { case opSet: @@ -362,7 +372,7 @@ func doRandomOp(st CacheKVStore, truth dbm.DB, maxKey int) { //------------------------------------------------------------------------------------------- // iterate over whole domain -func assertIterateDomain(t *testing.T, st KVStore, expectedN int) { +func assertIterateDomain(t *testing.T, st types.KVStore, expectedN int) { itr := st.Iterator(nil, nil) var i = 0 for ; itr.Valid(); itr.Next() { @@ -374,7 +384,7 @@ func assertIterateDomain(t *testing.T, st KVStore, expectedN int) { require.Equal(t, expectedN, i) } -func assertIterateDomainCheck(t *testing.T, st KVStore, mem dbm.DB, r []keyRange) { +func assertIterateDomainCheck(t *testing.T, st types.KVStore, mem dbm.DB, r []keyRange) { // iterate over each and check they match the other itr := st.Iterator(nil, nil) itr2 := mem.Iterator(nil, nil) // ground truth @@ -404,7 +414,7 @@ func assertIterateDomainCheck(t *testing.T, st KVStore, mem dbm.DB, r []keyRange require.False(t, itr2.Valid()) } -func assertIterateDomainCompare(t *testing.T, st KVStore, mem dbm.DB) { +func assertIterateDomainCompare(t *testing.T, st types.KVStore, mem dbm.DB) { // iterate over each and check they match the other itr := st.Iterator(nil, nil) itr2 := mem.Iterator(nil, nil) // ground truth @@ -412,7 +422,7 @@ func assertIterateDomainCompare(t *testing.T, st KVStore, mem dbm.DB) { checkIterators(t, itr2, itr) } -func checkIterators(t *testing.T, itr, itr2 Iterator) { +func checkIterators(t *testing.T, itr, itr2 types.Iterator) { for ; itr.Valid(); itr.Next() { require.True(t, itr2.Valid()) k, v := itr.Key(), itr.Value() @@ -427,14 +437,14 @@ func checkIterators(t *testing.T, itr, itr2 Iterator) { //-------------------------------------------------------- -func setRange(st KVStore, mem dbm.DB, start, end int) { +func setRange(st types.KVStore, mem dbm.DB, start, end int) { for i := start; i < end; i++ { st.Set(keyFmt(i), valFmt(i)) mem.Set(keyFmt(i), valFmt(i)) } } -func deleteRange(st KVStore, mem dbm.DB, start, end int) { +func deleteRange(st types.KVStore, mem dbm.DB, start, end int) { for i := start; i < end; i++ { st.Delete(keyFmt(i)) mem.Delete(keyFmt(i)) diff --git a/store/cachekvstore.go b/store/cachekvstore.go deleted file mode 100644 index 4860566c4b6d..000000000000 --- a/store/cachekvstore.go +++ /dev/null @@ -1,214 +0,0 @@ -package store - -import ( - "bytes" - "io" - "sort" - "sync" - - cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" -) - -// If value is nil but deleted is false, it means the parent doesn't have the -// key. (No need to delete upon Write()) -type cValue struct { - value []byte - deleted bool - dirty bool -} - -// cacheKVStore wraps an in-memory cache around an underlying KVStore. -type cacheKVStore struct { - mtx sync.Mutex - cache map[string]cValue - parent KVStore -} - -var _ CacheKVStore = (*cacheKVStore)(nil) - -// nolint -func NewCacheKVStore(parent KVStore) *cacheKVStore { - return &cacheKVStore{ - cache: make(map[string]cValue), - parent: parent, - } -} - -// Implements Store. -func (ci *cacheKVStore) GetStoreType() StoreType { - return ci.parent.GetStoreType() -} - -// Implements KVStore. -func (ci *cacheKVStore) Get(key []byte) (value []byte) { - ci.mtx.Lock() - defer ci.mtx.Unlock() - ci.assertValidKey(key) - - cacheValue, ok := ci.cache[string(key)] - if !ok { - value = ci.parent.Get(key) - ci.setCacheValue(key, value, false, false) - } else { - value = cacheValue.value - } - - return value -} - -// Implements KVStore. -func (ci *cacheKVStore) Set(key []byte, value []byte) { - ci.mtx.Lock() - defer ci.mtx.Unlock() - ci.assertValidKey(key) - ci.assertValidValue(value) - - ci.setCacheValue(key, value, false, true) -} - -// Implements KVStore. -func (ci *cacheKVStore) Has(key []byte) bool { - value := ci.Get(key) - return value != nil -} - -// Implements KVStore. -func (ci *cacheKVStore) Delete(key []byte) { - ci.mtx.Lock() - defer ci.mtx.Unlock() - ci.assertValidKey(key) - - ci.setCacheValue(key, nil, true, true) -} - -// Implements KVStore -func (ci *cacheKVStore) Prefix(prefix []byte) KVStore { - return prefixStore{ci, prefix} -} - -// Implements KVStore -func (ci *cacheKVStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, ci) -} - -// Implements CacheKVStore. -func (ci *cacheKVStore) Write() { - ci.mtx.Lock() - defer ci.mtx.Unlock() - - // We need a copy of all of the keys. - // Not the best, but probably not a bottleneck depending. - keys := make([]string, 0, len(ci.cache)) - for key, dbValue := range ci.cache { - if dbValue.dirty { - keys = append(keys, key) - } - } - - sort.Strings(keys) - - // TODO: Consider allowing usage of Batch, which would allow the write to - // at least happen atomically. - for _, key := range keys { - cacheValue := ci.cache[key] - if cacheValue.deleted { - ci.parent.Delete([]byte(key)) - } else if cacheValue.value == nil { - // Skip, it already doesn't exist in parent. - } else { - ci.parent.Set([]byte(key), cacheValue.value) - } - } - - // Clear the cache - ci.cache = make(map[string]cValue) -} - -//---------------------------------------- -// To cache-wrap this cacheKVStore further. - -// Implements CacheWrapper. -func (ci *cacheKVStore) CacheWrap() CacheWrap { - return NewCacheKVStore(ci) -} - -// CacheWrapWithTrace implements the CacheWrapper interface. -func (ci *cacheKVStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap { - return NewCacheKVStore(NewTraceKVStore(ci, w, tc)) -} - -//---------------------------------------- -// Iteration - -// Implements KVStore. -func (ci *cacheKVStore) Iterator(start, end []byte) Iterator { - return ci.iterator(start, end, true) -} - -// Implements KVStore. -func (ci *cacheKVStore) ReverseIterator(start, end []byte) Iterator { - return ci.iterator(start, end, false) -} - -func (ci *cacheKVStore) iterator(start, end []byte, ascending bool) Iterator { - var parent, cache Iterator - - if ascending { - parent = ci.parent.Iterator(start, end) - } else { - parent = ci.parent.ReverseIterator(start, end) - } - - items := ci.dirtyItems(start, end, ascending) - cache = newMemIterator(start, end, items) - - return newCacheMergeIterator(parent, cache, ascending) -} - -// Constructs a slice of dirty items, to use w/ memIterator. -func (ci *cacheKVStore) dirtyItems(start, end []byte, ascending bool) []cmn.KVPair { - items := make([]cmn.KVPair, 0) - - for key, cacheValue := range ci.cache { - if !cacheValue.dirty { - continue - } - if dbm.IsKeyInDomain([]byte(key), start, end) { - items = append(items, cmn.KVPair{Key: []byte(key), Value: cacheValue.value}) - } - } - - sort.Slice(items, func(i, j int) bool { - if ascending { - return bytes.Compare(items[i].Key, items[j].Key) < 0 - } - return bytes.Compare(items[i].Key, items[j].Key) > 0 - }) - - return items -} - -//---------------------------------------- -// etc - -func (ci *cacheKVStore) assertValidKey(key []byte) { - if key == nil { - panic("key is nil") - } -} - -func (ci *cacheKVStore) assertValidValue(value []byte) { - if value == nil { - panic("value is nil") - } -} - -// Only entrypoint to mutate ci.cache. -func (ci *cacheKVStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { - ci.cache[string(key)] = cValue{ - value: value, - deleted: deleted, - dirty: dirty, - } -} diff --git a/store/cachemulti/store.go b/store/cachemulti/store.go new file mode 100644 index 000000000000..e1257a3385b2 --- /dev/null +++ b/store/cachemulti/store.go @@ -0,0 +1,135 @@ +package cachemulti + +import ( + "io" + + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/types" +) + +//---------------------------------------- +// Store + +// Store holds many cache-wrapped stores. +// Implements MultiStore. +// NOTE: a Store (and MultiStores in general) should never expose the +// keys for the substores. +type Store struct { + db types.CacheKVStore + stores map[types.StoreKey]types.CacheWrap + keys map[string]types.StoreKey + + traceWriter io.Writer + traceContext types.TraceContext +} + +var _ types.CacheMultiStore = Store{} + +func NewFromKVStore( + store types.KVStore, + stores map[types.StoreKey]types.CacheWrapper, keys map[string]types.StoreKey, + traceWriter io.Writer, traceContext types.TraceContext, +) Store { + cms := Store{ + db: cachekv.NewStore(store), + stores: make(map[types.StoreKey]types.CacheWrap, len(stores)), + keys: keys, + traceWriter: traceWriter, + traceContext: traceContext, + } + + for key, store := range stores { + if cms.TracingEnabled() { + cms.stores[key] = store.CacheWrapWithTrace(cms.traceWriter, cms.traceContext) + } else { + cms.stores[key] = store.CacheWrap() + } + } + + return cms +} + +func NewStore( + db dbm.DB, + stores map[types.StoreKey]types.CacheWrapper, keys map[string]types.StoreKey, + traceWriter io.Writer, traceContext types.TraceContext, +) Store { + return NewFromKVStore(dbadapter.Store{db}, stores, keys, traceWriter, traceContext) +} + +func newCacheMultiStoreFromCMS(cms Store) Store { + stores := make(map[types.StoreKey]types.CacheWrapper) + for k, v := range cms.stores { + stores[k] = v + } + return NewFromKVStore(cms.db, stores, nil, cms.traceWriter, cms.traceContext) +} + +// SetTracer sets the tracer for the MultiStore that the underlying +// stores will utilize to trace operations. A MultiStore is returned. +func (cms Store) SetTracer(w io.Writer) types.MultiStore { + cms.traceWriter = w + return cms +} + +// SetTracingContext updates the tracing context for the MultiStore by merging +// the given context with the existing context by key. Any existing keys will +// be overwritten. It is implied that the caller should update the context when +// necessary between tracing operations. It returns a modified MultiStore. +func (cms Store) SetTracingContext(tc types.TraceContext) types.MultiStore { + if cms.traceContext != nil { + for k, v := range tc { + cms.traceContext[k] = v + } + } else { + cms.traceContext = tc + } + + return cms +} + +// TracingEnabled returns if tracing is enabled for the MultiStore. +func (cms Store) TracingEnabled() bool { + return cms.traceWriter != nil +} + +// GetStoreType returns the type of the store. +func (cms Store) GetStoreType() types.StoreType { + return types.StoreTypeMulti +} + +// Write calls Write on each underlying store. +func (cms Store) Write() { + cms.db.Write() + for _, store := range cms.stores { + store.Write() + } +} + +// Implements CacheWrapper. +func (cms Store) CacheWrap() types.CacheWrap { + return cms.CacheMultiStore().(types.CacheWrap) +} + +// CacheWrapWithTrace implements the CacheWrapper interface. +func (cms Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap { + return cms.CacheWrap() +} + +// Implements MultiStore. +func (cms Store) CacheMultiStore() types.CacheMultiStore { + return newCacheMultiStoreFromCMS(cms) +} + +// GetStore returns an underlying Store by key. +func (cms Store) GetStore(key types.StoreKey) types.Store { + return cms.stores[key].(types.Store) +} + +// GetKVStore returns an underlying KVStore by key. +func (cms Store) GetKVStore(key types.StoreKey) types.KVStore { + return cms.stores[key].(types.KVStore) +} diff --git a/store/cachemultistore.go b/store/cachemultistore.go deleted file mode 100644 index ee19778559a0..000000000000 --- a/store/cachemultistore.go +++ /dev/null @@ -1,141 +0,0 @@ -package store - -import ( - "io" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -//---------------------------------------- -// cacheMultiStore - -// cacheMultiStore holds many cache-wrapped stores. -// Implements MultiStore. -// NOTE: a cacheMultiStore (and MultiStores in general) should never expose the -// keys for the substores. -type cacheMultiStore struct { - db CacheKVStore - stores map[StoreKey]CacheWrap - keysByName map[string]StoreKey - - traceWriter io.Writer - traceContext TraceContext -} - -var _ CacheMultiStore = cacheMultiStore{} - -func newCacheMultiStoreFromRMS(rms *rootMultiStore) cacheMultiStore { - cms := cacheMultiStore{ - db: NewCacheKVStore(dbStoreAdapter{rms.db}), - stores: make(map[StoreKey]CacheWrap, len(rms.stores)), - keysByName: rms.keysByName, - traceWriter: rms.traceWriter, - traceContext: rms.traceContext, - } - - for key, store := range rms.stores { - if cms.TracingEnabled() { - cms.stores[key] = store.CacheWrapWithTrace(cms.traceWriter, cms.traceContext) - } else { - cms.stores[key] = store.CacheWrap() - } - } - - return cms -} - -func newCacheMultiStoreFromCMS(cms cacheMultiStore) cacheMultiStore { - cms2 := cacheMultiStore{ - db: NewCacheKVStore(cms.db), - stores: make(map[StoreKey]CacheWrap, len(cms.stores)), - traceWriter: cms.traceWriter, - traceContext: cms.traceContext, - } - - for key, store := range cms.stores { - if cms2.TracingEnabled() { - cms2.stores[key] = store.CacheWrapWithTrace(cms2.traceWriter, cms2.traceContext) - } else { - cms2.stores[key] = store.CacheWrap() - } - } - - return cms2 -} - -// WithTracer sets the tracer for the MultiStore that the underlying -// stores will utilize to trace operations. A MultiStore is returned. -func (cms cacheMultiStore) WithTracer(w io.Writer) MultiStore { - cms.traceWriter = w - return cms -} - -// WithTracingContext updates the tracing context for the MultiStore by merging -// the given context with the existing context by key. Any existing keys will -// be overwritten. It is implied that the caller should update the context when -// necessary between tracing operations. It returns a modified MultiStore. -func (cms cacheMultiStore) WithTracingContext(tc TraceContext) MultiStore { - if cms.traceContext != nil { - for k, v := range tc { - cms.traceContext[k] = v - } - } else { - cms.traceContext = tc - } - - return cms -} - -// TracingEnabled returns if tracing is enabled for the MultiStore. -func (cms cacheMultiStore) TracingEnabled() bool { - return cms.traceWriter != nil -} - -// ResetTraceContext resets the current tracing context. -func (cms cacheMultiStore) ResetTraceContext() MultiStore { - cms.traceContext = nil - return cms -} - -// Implements Store. -func (cms cacheMultiStore) GetStoreType() StoreType { - return sdk.StoreTypeMulti -} - -// Implements CacheMultiStore. -func (cms cacheMultiStore) Write() { - cms.db.Write() - for _, store := range cms.stores { - store.Write() - } -} - -// Implements CacheWrapper. -func (cms cacheMultiStore) CacheWrap() CacheWrap { - return cms.CacheMultiStore().(CacheWrap) -} - -// CacheWrapWithTrace implements the CacheWrapper interface. -func (cms cacheMultiStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { - return cms.CacheWrap() -} - -// Implements MultiStore. -func (cms cacheMultiStore) CacheMultiStore() CacheMultiStore { - return newCacheMultiStoreFromCMS(cms) -} - -// Implements MultiStore. -func (cms cacheMultiStore) GetStore(key StoreKey) Store { - return cms.stores[key].(Store) -} - -// Implements MultiStore. -func (cms cacheMultiStore) GetKVStore(key StoreKey) KVStore { - return cms.stores[key].(KVStore) -} - -// Implements MultiStore. -func (cms cacheMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, config sdk.GasConfig, key StoreKey) KVStore { - return NewGasKVStore(meter, config, cms.GetKVStore(key)) -} diff --git a/store/dbadapter/store.go b/store/dbadapter/store.go new file mode 100644 index 000000000000..b96c6f5ccc08 --- /dev/null +++ b/store/dbadapter/store.go @@ -0,0 +1,34 @@ +package dbadapter + +import ( + "io" + + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" +) + +// Wrapper type for dbm.Db with implementation of KVStore +type Store struct { + dbm.DB +} + +// GetStoreType returns the type of the store. +func (Store) GetStoreType() types.StoreType { + return types.StoreTypeDB +} + +// CacheWrap cache wraps the underlying store. +func (dsa Store) CacheWrap() types.CacheWrap { + return cachekv.NewStore(dsa) +} + +// CacheWrapWithTrace implements KVStore. +func (dsa Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap { + return cachekv.NewStore(tracekv.NewStore(dsa, w, tc)) +} + +// dbm.DB implements KVStore so we can CacheKVStore it. +var _ types.KVStore = Store{} diff --git a/store/dbstoreadapter.go b/store/dbstoreadapter.go deleted file mode 100644 index b662bcf45ddd..000000000000 --- a/store/dbstoreadapter.go +++ /dev/null @@ -1,67 +0,0 @@ -package store - -import ( - "io" - - dbm "github.com/tendermint/tendermint/libs/db" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Wrapper type for dbm.Db with implementation of KVStore -type dbStoreAdapter struct { - dbm.DB -} - -// Implements Store. -func (dbStoreAdapter) GetStoreType() StoreType { - return sdk.StoreTypeDB -} - -// Implements KVStore. -func (dsa dbStoreAdapter) CacheWrap() CacheWrap { - return NewCacheKVStore(dsa) -} - -// CacheWrapWithTrace implements the KVStore interface. -func (dsa dbStoreAdapter) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap { - return NewCacheKVStore(NewTraceKVStore(dsa, w, tc)) -} - -// Implements KVStore -func (dsa dbStoreAdapter) Prefix(prefix []byte) KVStore { - return prefixStore{dsa, prefix} -} - -// Implements KVStore -func (dsa dbStoreAdapter) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, dsa) -} - -// dbm.DB implements KVStore so we can CacheKVStore it. -var _ KVStore = dbStoreAdapter{} - -//---------------------------------------- -// commitDBStoreWrapper should only be used for simulation/debugging, -// as it doesn't compute any commit hash, and it cannot load older state. - -// Wrapper type for dbm.Db with implementation of KVStore -type commitDBStoreAdapter struct { - dbStoreAdapter -} - -func (cdsa commitDBStoreAdapter) Commit() CommitID { - return CommitID{ - Version: -1, - Hash: []byte("FAKE_HASH"), - } -} - -func (cdsa commitDBStoreAdapter) LastCommitID() CommitID { - return CommitID{ - Version: -1, - Hash: []byte("FAKE_HASH"), - } -} - -func (cdsa commitDBStoreAdapter) SetPruning(_ PruningOptions) {} diff --git a/store/firstlast.go b/store/firstlast.go index a47f1396d158..708a7d0d3dce 100644 --- a/store/firstlast.go +++ b/store/firstlast.go @@ -4,6 +4,8 @@ import ( "bytes" cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/store/types" ) // Gets the first item. @@ -22,7 +24,7 @@ func Last(st KVStore, start, end []byte) (kv cmn.KVPair, ok bool) { iter := st.ReverseIterator(end, start) if !iter.Valid() { if v := st.Get(start); v != nil { - return cmn.KVPair{Key: cp(start), Value: cp(v)}, true + return cmn.KVPair{Key: types.Cp(start), Value: types.Cp(v)}, true } return kv, false } diff --git a/store/gaskvstore.go b/store/gaskv/store.go similarity index 55% rename from store/gaskvstore.go rename to store/gaskv/store.go index 1100f8433979..e6bdbdc21ad5 100644 --- a/store/gaskvstore.go +++ b/store/gaskv/store.go @@ -1,25 +1,25 @@ -package store +package gaskv import ( "io" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store/types" ) -var _ KVStore = &gasKVStore{} +var _ types.KVStore = &Store{} -// gasKVStore applies gas tracking to an underlying KVStore. It implements the +// Store applies gas tracking to an underlying KVStore. It implements the // KVStore interface. -type gasKVStore struct { - gasMeter sdk.GasMeter - gasConfig sdk.GasConfig - parent sdk.KVStore +type Store struct { + gasMeter types.GasMeter + gasConfig types.GasConfig + parent types.KVStore } -// NewGasKVStore returns a reference to a new GasKVStore. +// NewStore returns a reference to a new GasKVStore. // nolint -func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KVStore) *gasKVStore { - kvs := &gasKVStore{ +func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store { + kvs := &Store{ gasMeter: gasMeter, gasConfig: gasConfig, parent: parent, @@ -28,61 +28,47 @@ func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KV } // Implements Store. -func (gs *gasKVStore) GetStoreType() sdk.StoreType { +func (gs *Store) GetStoreType() types.StoreType { return gs.parent.GetStoreType() } // Implements KVStore. -func (gs *gasKVStore) Get(key []byte) (value []byte) { - gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, sdk.GasReadCostFlatDesc) +func (gs *Store) Get(key []byte) (value []byte) { + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc) value = gs.parent.Get(key) // TODO overflow-safe math? - gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), sdk.GasReadPerByteDesc) + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc) return value } // Implements KVStore. -func (gs *gasKVStore) Set(key []byte, value []byte) { - gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, sdk.GasWriteCostFlatDesc) +func (gs *Store) Set(key []byte, value []byte) { + types.AssertValidValue(value) + gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc) // TODO overflow-safe math? - gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*sdk.Gas(len(value)), sdk.GasWritePerByteDesc) + gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(value)), types.GasWritePerByteDesc) gs.parent.Set(key, value) } // Implements KVStore. -func (gs *gasKVStore) Has(key []byte) bool { - gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, sdk.GasHasDesc) +func (gs *Store) Has(key []byte) bool { + gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc) return gs.parent.Has(key) } // Implements KVStore. -func (gs *gasKVStore) Delete(key []byte) { +func (gs *Store) Delete(key []byte) { // charge gas to prevent certain attack vectors even though space is being freed - gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, sdk.GasDeleteDesc) + gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, types.GasDeleteDesc) gs.parent.Delete(key) } -// Implements KVStore -func (gs *gasKVStore) Prefix(prefix []byte) KVStore { - // Keep gasstore layer at the top - return &gasKVStore{ - gasMeter: gs.gasMeter, - gasConfig: gs.gasConfig, - parent: prefixStore{gs.parent, prefix}, - } -} - -// Implements KVStore -func (gs *gasKVStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, gs) -} - // Iterator implements the KVStore interface. It returns an iterator which // incurs a flat gas cost for seeking to the first key/value pair and a variable // gas cost based on the current value's length if the iterator is valid. -func (gs *gasKVStore) Iterator(start, end []byte) sdk.Iterator { +func (gs *Store) Iterator(start, end []byte) types.Iterator { return gs.iterator(start, end, true) } @@ -90,22 +76,22 @@ func (gs *gasKVStore) Iterator(start, end []byte) sdk.Iterator { // iterator which incurs a flat gas cost for seeking to the first key/value pair // and a variable gas cost based on the current value's length if the iterator // is valid. -func (gs *gasKVStore) ReverseIterator(start, end []byte) sdk.Iterator { +func (gs *Store) ReverseIterator(start, end []byte) types.Iterator { return gs.iterator(start, end, false) } // Implements KVStore. -func (gs *gasKVStore) CacheWrap() sdk.CacheWrap { +func (gs *Store) CacheWrap() types.CacheWrap { panic("cannot CacheWrap a GasKVStore") } // CacheWrapWithTrace implements the KVStore interface. -func (gs *gasKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { +func (gs *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap { panic("cannot CacheWrapWithTrace a GasKVStore") } -func (gs *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { - var parent sdk.Iterator +func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator { + var parent types.Iterator if ascending { parent = gs.parent.Iterator(start, end) } else { @@ -121,12 +107,12 @@ func (gs *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { } type gasIterator struct { - gasMeter sdk.GasMeter - gasConfig sdk.GasConfig - parent sdk.Iterator + gasMeter types.GasMeter + gasConfig types.GasConfig + parent types.Iterator } -func newGasIterator(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.Iterator) sdk.Iterator { +func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator) types.Iterator { return &gasIterator{ gasMeter: gasMeter, gasConfig: gasConfig, @@ -179,6 +165,7 @@ func (gi *gasIterator) Close() { func (gi *gasIterator) consumeSeekGas() { value := gi.Value() - gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), sdk.GasValuePerByteDesc) - gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, sdk.GasIterNextCostFlatDesc) + gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasValuePerByteDesc) + gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, types.GasIterNextCostFlatDesc) + } diff --git a/store/gaskv/store_test.go b/store/gaskv/store_test.go new file mode 100644 index 000000000000..8d6acd86ef8a --- /dev/null +++ b/store/gaskv/store_test.go @@ -0,0 +1,78 @@ +package gaskv_test + +import ( + "fmt" + "testing" + + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/gaskv" + stypes "github.com/cosmos/cosmos-sdk/store/types" + + "github.com/stretchr/testify/require" +) + +func newGasKVStore() stypes.KVStore { + meter := stypes.NewGasMeter(10000) + mem := dbadapter.Store{dbm.NewMemDB()} + return gaskv.NewStore(mem, meter, stypes.KVGasConfig()) +} + +func bz(s string) []byte { return []byte(s) } + +func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } +func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } + +func TestGasKVStoreBasic(t *testing.T) { + mem := dbadapter.Store{dbm.NewMemDB()} + meter := stypes.NewGasMeter(10000) + st := gaskv.NewStore(mem, meter, stypes.KVGasConfig()) + 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(), stypes.Gas(6429)) +} + +func TestGasKVStoreIterator(t *testing.T) { + mem := dbadapter.Store{dbm.NewMemDB()} + meter := stypes.NewGasMeter(10000) + st := gaskv.NewStore(mem, meter, stypes.KVGasConfig()) + 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(), stypes.Gas(6987)) +} + +func TestGasKVStoreOutOfGasSet(t *testing.T) { + mem := dbadapter.Store{dbm.NewMemDB()} + meter := stypes.NewGasMeter(0) + st := gaskv.NewStore(mem, meter, stypes.KVGasConfig()) + require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas") +} + +func TestGasKVStoreOutOfGasIterator(t *testing.T) { + mem := dbadapter.Store{dbm.NewMemDB()} + meter := stypes.NewGasMeter(20000) + st := gaskv.NewStore(mem, meter, stypes.KVGasConfig()) + st.Set(keyFmt(1), valFmt(1)) + iterator := st.Iterator(nil, nil) + iterator.Next() + require.Panics(t, func() { iterator.Value() }, "Expected out-of-gas") +} diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go deleted file mode 100644 index 2426cf334727..000000000000 --- a/store/gaskvstore_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package store - -import ( - "testing" - - "github.com/tendermint/iavl" - dbm "github.com/tendermint/tendermint/libs/db" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/stretchr/testify/require" -) - -func newGasKVStore() KVStore { - meter := sdk.NewGasMeter(10000) - mem := dbStoreAdapter{dbm.NewMemDB()} - return NewGasKVStore(meter, sdk.KVGasConfig(), mem) -} - -func TestGasKVStoreBasic(t *testing.T) { - mem := dbStoreAdapter{dbm.NewMemDB()} - meter := sdk.NewGasMeter(10000) - st := NewGasKVStore(meter, sdk.KVGasConfig(), 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(6429)) -} - -func TestGasKVStoreIterator(t *testing.T) { - mem := dbStoreAdapter{dbm.NewMemDB()} - meter := sdk.NewGasMeter(10000) - st := NewGasKVStore(meter, sdk.KVGasConfig(), 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(6987)) -} - -func TestGasKVStoreOutOfGasSet(t *testing.T) { - mem := dbStoreAdapter{dbm.NewMemDB()} - meter := sdk.NewGasMeter(0) - st := NewGasKVStore(meter, sdk.KVGasConfig(), 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(20000) - st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) - st.Set(keyFmt(1), valFmt(1)) - iterator := st.Iterator(nil, nil) - iterator.Next() - require.Panics(t, func() { iterator.Value() }, "Expected out-of-gas") -} - -func testGasKVStoreWrap(t *testing.T, store KVStore) { - meter := sdk.NewGasMeter(100000) - - store = store.Gas(meter, sdk.GasConfig{HasCost: 10}) - require.Equal(t, uint64(0), meter.GasConsumed()) - - store.Has([]byte("key")) - require.Equal(t, uint64(10), meter.GasConsumed()) - - store = store.Gas(meter, sdk.GasConfig{HasCost: 20}) - - store.Has([]byte("key")) - require.Equal(t, uint64(40), meter.GasConsumed()) -} - -func TestGasKVStoreWrap(t *testing.T) { - db := dbm.NewMemDB() - tree := iavl.NewMutableTree(db, cacheSize) - iavl := newIAVLStore(tree, numRecent, storeEvery) - testGasKVStoreWrap(t, iavl) - - st := NewCacheKVStore(iavl) - testGasKVStoreWrap(t, st) - - pref := st.Prefix([]byte("prefix")) - testGasKVStoreWrap(t, pref) - - dsa := dbStoreAdapter{dbm.NewMemDB()} - testGasKVStoreWrap(t, dsa) - - ts := newTransientStore() - testGasKVStoreWrap(t, ts) - -} diff --git a/store/iavlstore.go b/store/iavl/store.go similarity index 76% rename from store/iavlstore.go rename to store/iavl/store.go index 26c739da3bf3..29e375b55f65 100644 --- a/store/iavlstore.go +++ b/store/iavl/store.go @@ -1,4 +1,4 @@ -package store +package iavl import ( "fmt" @@ -11,7 +11,11 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" - sdk "github.com/cosmos/cosmos-sdk/types" + stypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/tracekv" ) const ( @@ -19,25 +23,25 @@ const ( ) // load the iavl store -func LoadIAVLStore(db dbm.DB, id CommitID, pruning sdk.PruningOptions) (CommitStore, error) { +func LoadStore(db dbm.DB, id types.CommitID, pruning types.PruningOptions) (types.CommitStore, error) { tree := iavl.NewMutableTree(db, defaultIAVLCacheSize) _, err := tree.LoadVersion(id.Version) if err != nil { return nil, err } - iavl := newIAVLStore(tree, int64(0), int64(0)) + iavl := UnsafeNewStore(tree, int64(0), int64(0)) iavl.SetPruning(pruning) return iavl, nil } //---------------------------------------- -var _ KVStore = (*iavlStore)(nil) -var _ CommitStore = (*iavlStore)(nil) -var _ Queryable = (*iavlStore)(nil) +var _ types.KVStore = (*Store)(nil) +var _ types.CommitStore = (*Store)(nil) +var _ types.Queryable = (*Store)(nil) -// iavlStore Implements KVStore and CommitStore. -type iavlStore struct { +// Store Implements types.KVStore and CommitStore. +type Store struct { // The underlying tree. tree *iavl.MutableTree @@ -56,8 +60,8 @@ type iavlStore struct { // CONTRACT: tree should be fully loaded. // nolint: unparam -func newIAVLStore(tree *iavl.MutableTree, numRecent int64, storeEvery int64) *iavlStore { - st := &iavlStore{ +func UnsafeNewStore(tree *iavl.MutableTree, numRecent int64, storeEvery int64) *Store { + st := &Store{ tree: tree, numRecent: numRecent, storeEvery: storeEvery, @@ -66,7 +70,7 @@ func newIAVLStore(tree *iavl.MutableTree, numRecent int64, storeEvery int64) *ia } // Implements Committer. -func (st *iavlStore) Commit() CommitID { +func (st *Store) Commit() types.CommitID { // Save a new version. hash, version, err := st.tree.SaveVersion() if err != nil { @@ -86,84 +90,75 @@ func (st *iavlStore) Commit() CommitID { } } - return CommitID{ + return types.CommitID{ Version: version, Hash: hash, } } // Implements Committer. -func (st *iavlStore) LastCommitID() CommitID { - return CommitID{ +func (st *Store) LastCommitID() types.CommitID { + return types.CommitID{ Version: st.tree.Version(), Hash: st.tree.Hash(), } } // Implements Committer. -func (st *iavlStore) SetPruning(opt sdk.PruningOptions) { +func (st *Store) SetPruning(opt types.PruningOptions) { st.numRecent = opt.KeepRecent() st.storeEvery = opt.KeepEvery() } // VersionExists returns whether or not a given version is stored. -func (st *iavlStore) VersionExists(version int64) bool { +func (st *Store) VersionExists(version int64) bool { return st.tree.VersionExists(version) } // Implements Store. -func (st *iavlStore) GetStoreType() StoreType { - return sdk.StoreTypeIAVL +func (st *Store) GetStoreType() types.StoreType { + return types.StoreTypeIAVL } // Implements Store. -func (st *iavlStore) CacheWrap() CacheWrap { - return NewCacheKVStore(st) +func (st *Store) CacheWrap() types.CacheWrap { + return cachekv.NewStore(st) } // CacheWrapWithTrace implements the Store interface. -func (st *iavlStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap { - return NewCacheKVStore(NewTraceKVStore(st, w, tc)) +func (st *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap { + return cachekv.NewStore(tracekv.NewStore(st, w, tc)) } -// Implements KVStore. -func (st *iavlStore) Set(key, value []byte) { +// Implements types.KVStore. +func (st *Store) Set(key, value []byte) { + stypes.AssertValidValue(value) st.tree.Set(key, value) } -// Implements KVStore. -func (st *iavlStore) Get(key []byte) (value []byte) { +// Implements types.KVStore. +func (st *Store) Get(key []byte) (value []byte) { _, v := st.tree.Get(key) return v } -// Implements KVStore. -func (st *iavlStore) Has(key []byte) (exists bool) { +// Implements types.KVStore. +func (st *Store) Has(key []byte) (exists bool) { return st.tree.Has(key) } -// Implements KVStore. -func (st *iavlStore) Delete(key []byte) { +// Implements types.KVStore. +func (st *Store) Delete(key []byte) { st.tree.Remove(key) } -// Implements KVStore -func (st *iavlStore) Prefix(prefix []byte) KVStore { - return prefixStore{st, prefix} -} - -// Implements KVStore -func (st *iavlStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, st) -} - -// Implements KVStore. -func (st *iavlStore) Iterator(start, end []byte) Iterator { +// Implements types.KVStore. +func (st *Store) Iterator(start, end []byte) types.Iterator { return newIAVLIterator(st.tree.ImmutableTree, start, end, true) } -// Implements KVStore. -func (st *iavlStore) ReverseIterator(start, end []byte) Iterator { +// Implements types.KVStore. +func (st *Store) ReverseIterator(start, end []byte) types.Iterator { return newIAVLIterator(st.tree.ImmutableTree, start, end, false) } @@ -188,10 +183,10 @@ func getHeight(tree *iavl.MutableTree, req abci.RequestQuery) int64 { // If latest-1 is not present, use latest (which must be present) // if you care to have the latest data to see a tx results, you must // explicitly set the height you want to see -func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { +func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { if len(req.Data) == 0 { msg := "Query cannot be zero length" - return sdk.ErrTxDecode(msg).QueryResult() + return types.ErrTxDecode(msg).QueryResult() } tree := st.tree @@ -236,14 +231,14 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { } case "/subspace": - var KVs []KVPair + var KVs []types.KVPair subspace := req.Data res.Key = subspace - iterator := sdk.KVStorePrefixIterator(st, subspace) + iterator := stypes.KVStorePrefixIterator(st, subspace) for ; iterator.Valid(); iterator.Next() { - KVs = append(KVs, KVPair{Key: iterator.Key(), Value: iterator.Value()}) + KVs = append(KVs, types.KVPair{Key: iterator.Key(), Value: iterator.Value()}) } iterator.Close() @@ -251,7 +246,7 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { default: msg := fmt.Sprintf("Unexpected Query path: %v", req.Path) - return sdk.ErrUnknownRequest(msg).QueryResult() + return types.ErrUnknownRequest(msg).QueryResult() } return @@ -259,7 +254,7 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { //---------------------------------------- -// Implements Iterator. +// Implements types.Iterator. type iavlIterator struct { // Underlying store tree *iavl.ImmutableTree @@ -288,7 +283,7 @@ type iavlIterator struct { value []byte // The current value } -var _ Iterator = (*iavlIterator)(nil) +var _ types.Iterator = (*iavlIterator)(nil) // newIAVLIterator will create a new iavlIterator. // CONTRACT: Caller must release the iavlIterator, as each one creates a new @@ -296,8 +291,8 @@ var _ Iterator = (*iavlIterator)(nil) func newIAVLIterator(tree *iavl.ImmutableTree, start, end []byte, ascending bool) *iavlIterator { iter := &iavlIterator{ tree: tree, - start: cp(start), - end: cp(end), + start: stypes.Cp(start), + end: stypes.Cp(end), ascending: ascending, iterCh: make(chan cmn.KVPair, 0), // Set capacity > 0? quitCh: make(chan struct{}), @@ -330,12 +325,12 @@ func (iter *iavlIterator) initRoutine() { close(iter.initCh) } -// Implements Iterator. +// Implements types.Iterator. func (iter *iavlIterator) Domain() (start, end []byte) { return iter.start, iter.end } -// Implements Iterator. +// Implements types.Iterator. func (iter *iavlIterator) Valid() bool { iter.waitInit() iter.mtx.Lock() @@ -345,7 +340,7 @@ func (iter *iavlIterator) Valid() bool { return validity } -// Implements Iterator. +// Implements types.Iterator. func (iter *iavlIterator) Next() { iter.waitInit() iter.mtx.Lock() @@ -355,7 +350,7 @@ func (iter *iavlIterator) Next() { iter.mtx.Unlock() } -// Implements Iterator. +// Implements types.Iterator. func (iter *iavlIterator) Key() []byte { iter.waitInit() iter.mtx.Lock() @@ -366,7 +361,7 @@ func (iter *iavlIterator) Key() []byte { return key } -// Implements Iterator. +// Implements types.Iterator. func (iter *iavlIterator) Value() []byte { iter.waitInit() iter.mtx.Lock() @@ -377,7 +372,7 @@ func (iter *iavlIterator) Value() []byte { return val } -// Implements Iterator. +// Implements types.Iterator. func (iter *iavlIterator) Close() { close(iter.quitCh) } @@ -421,14 +416,3 @@ func (iter *iavlIterator) assertIsValid(unlockMutex bool) { panic("invalid iterator") } } - -//---------------------------------------- - -func cp(bz []byte) (ret []byte) { - if bz == nil { - return nil - } - ret = make([]byte, len(bz)) - copy(ret, bz) - return ret -} diff --git a/store/iavlstore_test.go b/store/iavl/store_test.go similarity index 87% rename from store/iavlstore_test.go rename to store/iavl/store_test.go index 4d8605e4e198..360fa712cd93 100644 --- a/store/iavlstore_test.go +++ b/store/iavl/store_test.go @@ -1,4 +1,4 @@ -package store +package iavl import ( "fmt" @@ -11,7 +11,7 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types" ) var ( @@ -29,7 +29,7 @@ var ( ) // make a tree with data from above and save it -func newAlohaTree(t *testing.T, db dbm.DB) (*iavl.MutableTree, CommitID) { +func newAlohaTree(t *testing.T, db dbm.DB) (*iavl.MutableTree, types.CommitID) { tree := iavl.NewMutableTree(db, cacheSize) for k, v := range treeData { tree.Set([]byte(k), []byte(v)) @@ -41,13 +41,13 @@ func newAlohaTree(t *testing.T, db dbm.DB) (*iavl.MutableTree, CommitID) { } hash, ver, err := tree.SaveVersion() require.Nil(t, err) - return tree, CommitID{ver, hash} + return tree, types.CommitID{ver, hash} } func TestIAVLStoreGetSetHasDelete(t *testing.T) { db := dbm.NewMemDB() tree, _ := newAlohaTree(t, db) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) key := "hello" @@ -69,10 +69,17 @@ func TestIAVLStoreGetSetHasDelete(t *testing.T) { require.False(t, exists) } +func TestIAVLStoreNoNilSet(t *testing.T) { + db := dbm.NewMemDB() + tree, _ := newAlohaTree(t, db) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) + require.Panics(t, func() { iavlStore.Set([]byte("key"), nil) }, "setting a nil value should panic") +} + func TestIAVLIterator(t *testing.T) { db := dbm.NewMemDB() tree, _ := newAlohaTree(t, db) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) iter := iavlStore.Iterator([]byte("aloha"), []byte("hellz")) expected := []string{"aloha", "hello"} var i int @@ -145,7 +152,7 @@ func TestIAVLIterator(t *testing.T) { func TestIAVLReverseIterator(t *testing.T) { db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) iavlStore.Set([]byte{0x00}, []byte("0")) iavlStore.Set([]byte{0x00, 0x00}, []byte("0 0")) @@ -176,7 +183,7 @@ func TestIAVLReverseIterator(t *testing.T) { func TestIAVLPrefixIterator(t *testing.T) { db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) iavlStore.Set([]byte("test1"), []byte("test1")) iavlStore.Set([]byte("test2"), []byte("test2")) @@ -190,7 +197,7 @@ func TestIAVLPrefixIterator(t *testing.T) { var i int - iter := sdk.KVStorePrefixIterator(iavlStore, []byte("test")) + iter := types.KVStorePrefixIterator(iavlStore, []byte("test")) expected := []string{"test1", "test2", "test3"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] @@ -202,7 +209,7 @@ func TestIAVLPrefixIterator(t *testing.T) { iter.Close() require.Equal(t, len(expected), i) - iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) + iter = types.KVStorePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) expected2 := [][]byte{ {byte(55), byte(255), byte(255), byte(0)}, {byte(55), byte(255), byte(255), byte(1)}, @@ -218,7 +225,7 @@ func TestIAVLPrefixIterator(t *testing.T) { iter.Close() require.Equal(t, len(expected), i) - iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) + iter = types.KVStorePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) expected2 = [][]byte{ {byte(255), byte(255), byte(0)}, {byte(255), byte(255), byte(1)}, @@ -238,7 +245,7 @@ func TestIAVLPrefixIterator(t *testing.T) { func TestIAVLReversePrefixIterator(t *testing.T) { db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) iavlStore.Set([]byte("test1"), []byte("test1")) iavlStore.Set([]byte("test2"), []byte("test2")) @@ -252,7 +259,7 @@ func TestIAVLReversePrefixIterator(t *testing.T) { var i int - iter := sdk.KVStoreReversePrefixIterator(iavlStore, []byte("test")) + iter := types.KVStoreReversePrefixIterator(iavlStore, []byte("test")) expected := []string{"test3", "test2", "test1"} for i = 0; iter.Valid(); iter.Next() { expectedKey := expected[i] @@ -263,7 +270,7 @@ func TestIAVLReversePrefixIterator(t *testing.T) { } require.Equal(t, len(expected), i) - iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) + iter = types.KVStoreReversePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) expected2 := [][]byte{ {byte(55), byte(255), byte(255), byte(255)}, {byte(55), byte(255), byte(255), byte(1)}, @@ -278,7 +285,7 @@ func TestIAVLReversePrefixIterator(t *testing.T) { } require.Equal(t, len(expected), i) - iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) + iter = types.KVStoreReversePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) expected2 = [][]byte{ {byte(255), byte(255), byte(255)}, {byte(255), byte(255), byte(1)}, @@ -294,7 +301,7 @@ func TestIAVLReversePrefixIterator(t *testing.T) { require.Equal(t, len(expected), i) } -func nextVersion(iavl *iavlStore) { +func nextVersion(iavl *Store) { key := []byte(fmt.Sprintf("Key for tree: %d", iavl.LastCommitID().Version)) value := []byte(fmt.Sprintf("Value for tree: %d", iavl.LastCommitID().Version)) iavl.Set(key, value) @@ -357,7 +364,7 @@ type pruneState struct { func testPruning(t *testing.T, numRecent int64, storeEvery int64, states []pruneState) { db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) for step, state := range states { for _, ver := range state.stored { require.True(t, iavlStore.VersionExists(ver), @@ -376,7 +383,7 @@ func testPruning(t *testing.T, numRecent int64, storeEvery int64, states []prune func TestIAVLNoPrune(t *testing.T) { db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numRecent, int64(1)) + iavlStore := UnsafeNewStore(tree, numRecent, int64(1)) nextVersion(iavlStore) for i := 1; i < 100; i++ { for j := 1; j <= i; j++ { @@ -391,7 +398,7 @@ func TestIAVLNoPrune(t *testing.T) { func TestIAVLPruneEverything(t *testing.T) { db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, int64(0), int64(0)) + iavlStore := UnsafeNewStore(tree, int64(0), int64(0)) nextVersion(iavlStore) for i := 1; i < 100; i++ { for j := 1; j < i; j++ { @@ -409,19 +416,19 @@ func TestIAVLPruneEverything(t *testing.T) { func TestIAVLStoreQuery(t *testing.T) { db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) k1, v1 := []byte("key1"), []byte("val1") k2, v2 := []byte("key2"), []byte("val2") v3 := []byte("val3") ksub := []byte("key") - KVs0 := []KVPair{} - KVs1 := []KVPair{ + KVs0 := []types.KVPair{} + KVs1 := []types.KVPair{ {Key: k1, Value: v1}, {Key: k2, Value: v2}, } - KVs2 := []KVPair{ + KVs2 := []types.KVPair{ {Key: k1, Value: v3}, {Key: k2, Value: v2}, } @@ -436,7 +443,7 @@ func TestIAVLStoreQuery(t *testing.T) { // query subspace before anything set qres := iavlStore.Query(querySub) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, valExpSubEmpty, qres.Value) // set data @@ -445,24 +452,24 @@ func TestIAVLStoreQuery(t *testing.T) { // set data without commit, doesn't show up qres = iavlStore.Query(query) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Nil(t, qres.Value) // commit it, but still don't see on old version cid = iavlStore.Commit() qres = iavlStore.Query(query) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Nil(t, qres.Value) // but yes on the new version query.Height = cid.Version qres = iavlStore.Query(query) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, v1, qres.Value) // and for the subspace qres = iavlStore.Query(querySub) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, valExpSub1, qres.Value) // modify @@ -471,28 +478,28 @@ func TestIAVLStoreQuery(t *testing.T) { // query will return old values, as height is fixed qres = iavlStore.Query(query) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, v1, qres.Value) // update to latest in the query and we are happy query.Height = cid.Version qres = iavlStore.Query(query) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, v3, qres.Value) query2 := abci.RequestQuery{Path: "/key", Data: k2, Height: cid.Version} qres = iavlStore.Query(query2) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, v2, qres.Value) // and for the subspace qres = iavlStore.Query(querySub) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, valExpSub2, qres.Value) // default (height 0) will show latest -1 query0 := abci.RequestQuery{Path: "/key", Data: k1} qres = iavlStore.Query(query0) - require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, uint32(types.CodeOK), qres.Code) require.Equal(t, v1, qres.Value) } @@ -505,8 +512,8 @@ func BenchmarkIAVLIteratorNext(b *testing.B) { value := cmn.RandBytes(50) tree.Set(key, value) } - iavlStore := newIAVLStore(tree, numRecent, storeEvery) - iterators := make([]Iterator, b.N/treeSize) + iavlStore := UnsafeNewStore(tree, numRecent, storeEvery) + iterators := make([]types.Iterator, b.N/treeSize) for i := 0; i < len(iterators); i++ { iterators[i] = iavlStore.Iterator([]byte{0}, []byte{255, 255, 255, 255, 255}) } diff --git a/store/iavl/wire.go b/store/iavl/wire.go new file mode 100644 index 000000000000..43173f3e7895 --- /dev/null +++ b/store/iavl/wire.go @@ -0,0 +1,7 @@ +package iavl + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +var cdc = codec.New() diff --git a/store/list.go b/store/list/list.go similarity index 92% rename from store/list.go rename to store/list/list.go index b38f11b803ee..934dfaa0f1ce 100644 --- a/store/list.go +++ b/store/list/list.go @@ -1,11 +1,12 @@ -package store +package list import ( "fmt" "strconv" "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/store/types" ) // Key for the length of the list @@ -22,11 +23,11 @@ func ElemKey(index uint64) []byte { // It panics when the element type cannot be (un/)marshalled by the codec type List struct { cdc *codec.Codec - store sdk.KVStore + store types.KVStore } // NewList constructs new List -func NewList(cdc *codec.Codec, store sdk.KVStore) List { +func NewList(cdc *codec.Codec, store types.KVStore) List { return List{ cdc: cdc, store: store, @@ -83,7 +84,8 @@ func (m List) Push(value interface{}) { // CONTRACT: No writes may happen within a domain while iterating over it. func (m List) Iterate(ptr interface{}, fn func(uint64) bool) { - iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) + iter := types.KVStorePrefixIterator(m.store, []byte{0x01}) + defer iter.Close() for ; iter.Valid(); iter.Next() { v := iter.Value() m.cdc.MustUnmarshalBinaryLengthPrefixed(v, ptr) @@ -100,8 +102,6 @@ func (m List) Iterate(ptr interface{}, fn func(uint64) bool) { break } } - - iter.Close() } func subspace(prefix []byte) (start, end []byte) { diff --git a/store/list_test.go b/store/list/list_test.go similarity index 61% rename from store/list_test.go rename to store/list/list_test.go index 6767457cd163..970c836bc3eb 100644 --- a/store/list_test.go +++ b/store/list/list_test.go @@ -1,34 +1,54 @@ -package store +package list import ( "math/rand" "testing" - "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/rootmulti" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" ) +type TestStruct struct { + I uint64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { + db := dbm.NewMemDB() + cms := rootmulti.NewStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) + cdc := codec.New() + return ctx, cdc +} func TestList(t *testing.T) { key := sdk.NewKVStoreKey("test") ctx, cdc := defaultComponents(key) store := ctx.KVStore(key) lm := NewList(cdc, store) - val := S{1, true} - var res S + val := TestStruct{1, true} + var res TestStruct lm.Push(val) require.Equal(t, uint64(1), lm.Len()) lm.Get(uint64(0), &res) require.Equal(t, val, res) - val = S{2, false} + val = TestStruct{2, false} lm.Set(uint64(0), val) lm.Get(uint64(0), &res) require.Equal(t, val, res) - val = S{100, false} + val = TestStruct{100, false} lm.Push(val) require.Equal(t, uint64(2), lm.Len()) lm.Get(uint64(1), &res) @@ -38,7 +58,7 @@ func TestList(t *testing.T) { require.Equal(t, uint64(2), lm.Len()) lm.Iterate(&res, func(index uint64) (brk bool) { - var temp S + var temp TestStruct lm.Get(index, &temp) require.Equal(t, temp, res) @@ -47,12 +67,12 @@ func TestList(t *testing.T) { }) lm.Iterate(&res, func(index uint64) (brk bool) { - lm.Set(index, S{res.I + 1, !res.B}) + lm.Set(index, TestStruct{res.I + 1, !res.B}) return }) lm.Get(uint64(0), &res) - require.Equal(t, S{3, true}, res) + require.Equal(t, TestStruct{3, true}, res) } func TestListRandom(t *testing.T) { diff --git a/store/prefixstore.go b/store/prefix/store.go similarity index 71% rename from store/prefixstore.go rename to store/prefix/store.go index 1f52a99fec48..a04995273baf 100644 --- a/store/prefixstore.go +++ b/store/prefix/store.go @@ -1,23 +1,31 @@ -package store +package prefix import ( "bytes" "io" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" ) -var _ KVStore = prefixStore{} +var _ types.KVStore = Store{} -// prefixStore is similar with tendermint/tendermint/libs/db/prefix_db +// Store is similar with tendermint/tendermint/libs/db/prefix_db // both gives access only to the limited subset of the store // for convinience or safety - -type prefixStore struct { - parent KVStore +type Store struct { + parent types.KVStore prefix []byte } +func NewStore(parent types.KVStore, prefix []byte) Store { + return Store{ + parent: parent, + prefix: prefix, + } +} + func cloneAppend(bz []byte, tail []byte) (res []byte) { res = make([]byte, len(bz)+len(tail)) copy(res, bz) @@ -25,63 +33,55 @@ func cloneAppend(bz []byte, tail []byte) (res []byte) { return } -func (s prefixStore) key(key []byte) (res []byte) { +func (s Store) key(key []byte) (res []byte) { if key == nil { - panic("nil key on prefixStore") + panic("nil key on Store") } res = cloneAppend(s.prefix, key) return } // Implements Store -func (s prefixStore) GetStoreType() StoreType { +func (s Store) GetStoreType() types.StoreType { return s.parent.GetStoreType() } // Implements CacheWrap -func (s prefixStore) CacheWrap() CacheWrap { - return NewCacheKVStore(s) +func (s Store) CacheWrap() types.CacheWrap { + return cachekv.NewStore(s) } // CacheWrapWithTrace implements the KVStore interface. -func (s prefixStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap { - return NewCacheKVStore(NewTraceKVStore(s, w, tc)) +func (s Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap { + return cachekv.NewStore(tracekv.NewStore(s, w, tc)) } // Implements KVStore -func (s prefixStore) Get(key []byte) []byte { +func (s Store) Get(key []byte) []byte { res := s.parent.Get(s.key(key)) return res } // Implements KVStore -func (s prefixStore) Has(key []byte) bool { +func (s Store) Has(key []byte) bool { return s.parent.Has(s.key(key)) } // Implements KVStore -func (s prefixStore) Set(key, value []byte) { +func (s Store) Set(key, value []byte) { + types.AssertValidKey(key) + types.AssertValidValue(value) s.parent.Set(s.key(key), value) } // Implements KVStore -func (s prefixStore) Delete(key []byte) { +func (s Store) Delete(key []byte) { s.parent.Delete(s.key(key)) } -// Implements KVStore -func (s prefixStore) Prefix(prefix []byte) KVStore { - return prefixStore{s, prefix} -} - -// Implements KVStore -func (s prefixStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, s) -} - // Implements KVStore // Check https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db.go#L106 -func (s prefixStore) Iterator(start, end []byte) Iterator { +func (s Store) Iterator(start, end []byte) types.Iterator { newstart := cloneAppend(s.prefix, start) var newend []byte @@ -98,7 +98,7 @@ func (s prefixStore) Iterator(start, end []byte) Iterator { // Implements KVStore // Check https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db.go#L129 -func (s prefixStore) ReverseIterator(start, end []byte) Iterator { +func (s Store) ReverseIterator(start, end []byte) types.Iterator { newstart := cloneAppend(s.prefix, start) var newend []byte @@ -113,16 +113,16 @@ func (s prefixStore) ReverseIterator(start, end []byte) Iterator { return newPrefixIterator(s.prefix, start, end, iter) } -var _ sdk.Iterator = (*prefixIterator)(nil) +var _ types.Iterator = (*prefixIterator)(nil) type prefixIterator struct { prefix []byte start, end []byte - iter Iterator + iter types.Iterator valid bool } -func newPrefixIterator(prefix, start, end []byte, parent Iterator) *prefixIterator { +func newPrefixIterator(prefix, start, end []byte, parent types.Iterator) *prefixIterator { return &prefixIterator{ prefix: prefix, start: start, @@ -184,9 +184,9 @@ func stripPrefix(key []byte, prefix []byte) []byte { return key[len(prefix):] } -// wrapping sdk.PrefixEndBytes +// wrapping types.PrefixEndBytes func cpIncr(bz []byte) []byte { - return sdk.PrefixEndBytes(bz) + return types.PrefixEndBytes(bz) } // copied from github.com/tendermint/tendermint/libs/db/util.go @@ -209,7 +209,7 @@ func cpDecr(bz []byte) (ret []byte) { return nil } -func skipOne(iter Iterator, skipKey []byte) { +func skipOne(iter types.Iterator, skipKey []byte) { if iter.Valid() { if bytes.Equal(iter.Key(), skipKey) { iter.Next() diff --git a/store/prefixstore_test.go b/store/prefix/store_test.go similarity index 78% rename from store/prefixstore_test.go rename to store/prefix/store_test.go index 48e6cd17f7b9..04019ead30b0 100644 --- a/store/prefixstore_test.go +++ b/store/prefix/store_test.go @@ -1,4 +1,4 @@ -package store +package prefix import ( "math/rand" @@ -6,12 +6,24 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/iavl" + tiavl "github.com/tendermint/iavl" dbm "github.com/tendermint/tendermint/libs/db" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/gaskv" + "github.com/cosmos/cosmos-sdk/store/iavl" + "github.com/cosmos/cosmos-sdk/store/types" ) +// copied from iavl/store_test.go +var ( + cacheSize = 100 + numRecent int64 = 5 + storeEvery int64 = 3 +) + +func bz(s string) []byte { return []byte(s) } + type kvpair struct { key []byte value []byte @@ -30,7 +42,7 @@ func genRandomKVPairs(t *testing.T) []kvpair { return kvps } -func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { +func setRandomKVPairs(t *testing.T, store types.KVStore) []kvpair { kvps := genRandomKVPairs(t) for _, kvp := range kvps { store.Set(kvp.key, kvp.value) @@ -38,9 +50,9 @@ func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { return kvps } -func testPrefixStore(t *testing.T, baseStore KVStore, prefix []byte) { - prefixStore := baseStore.Prefix(prefix) - prefixPrefixStore := prefixStore.Prefix([]byte("prefix")) +func testPrefixStore(t *testing.T, baseStore types.KVStore, prefix []byte) { + prefixStore := NewStore(baseStore, prefix) + prefixPrefixStore := NewStore(prefixStore, []byte("prefix")) require.Panics(t, func() { prefixStore.Get(nil) }) require.Panics(t, func() { prefixStore.Set(nil, []byte{}) }) @@ -75,36 +87,29 @@ func testPrefixStore(t *testing.T, baseStore KVStore, prefix []byte) { func TestIAVLStorePrefix(t *testing.T) { db := dbm.NewMemDB() - tree := iavl.NewMutableTree(db, cacheSize) - iavlStore := newIAVLStore(tree, numRecent, storeEvery) + tree := tiavl.NewMutableTree(db, cacheSize) + iavlStore := iavl.UnsafeNewStore(tree, numRecent, storeEvery) testPrefixStore(t, iavlStore, []byte("test")) } -func TestCacheKVStorePrefix(t *testing.T) { - cacheStore := newCacheKVStore() - - testPrefixStore(t, cacheStore, []byte("test")) -} - -func TestGasKVStorePrefix(t *testing.T) { - meter := sdk.NewGasMeter(100000000) - mem := dbStoreAdapter{dbm.NewMemDB()} - gasStore := NewGasKVStore(meter, sdk.KVGasConfig(), mem) - - testPrefixStore(t, gasStore, []byte("test")) +func TestPrefixKVStoreNoNilSet(t *testing.T) { + meter := types.NewGasMeter(100000000) + mem := dbadapter.Store{dbm.NewMemDB()} + gasStore := gaskv.NewStore(mem, meter, types.KVGasConfig()) + require.Panics(t, func() { gasStore.Set([]byte("key"), nil) }, "setting a nil value should panic") } func TestPrefixStoreIterate(t *testing.T) { db := dbm.NewMemDB() - baseStore := dbStoreAdapter{db} + baseStore := dbadapter.Store{db} prefix := []byte("test") - prefixStore := baseStore.Prefix(prefix) + prefixStore := NewStore(baseStore, prefix) setRandomKVPairs(t, prefixStore) - bIter := sdk.KVStorePrefixIterator(baseStore, prefix) - pIter := sdk.KVStorePrefixIterator(prefixStore, nil) + bIter := types.KVStorePrefixIterator(baseStore, prefix) + pIter := types.KVStorePrefixIterator(prefixStore, nil) for bIter.Valid() && pIter.Valid() { require.Equal(t, bIter.Key(), append(prefix, pIter.Key()...)) @@ -143,11 +148,11 @@ func TestCloneAppend(t *testing.T) { func TestPrefixStoreIteratorEdgeCase(t *testing.T) { db := dbm.NewMemDB() - baseStore := dbStoreAdapter{db} + baseStore := dbadapter.Store{db} // overflow in cpIncr prefix := []byte{0xAA, 0xFF, 0xFF} - prefixStore := baseStore.Prefix(prefix) + prefixStore := NewStore(baseStore, prefix) // ascending order baseStore.Set([]byte{0xAA, 0xFF, 0xFE}, []byte{}) @@ -173,11 +178,11 @@ func TestPrefixStoreIteratorEdgeCase(t *testing.T) { func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { db := dbm.NewMemDB() - baseStore := dbStoreAdapter{db} + baseStore := dbadapter.Store{db} // overflow in cpIncr prefix := []byte{0xAA, 0xFF, 0xFF} - prefixStore := baseStore.Prefix(prefix) + prefixStore := NewStore(baseStore, prefix) // descending order baseStore.Set([]byte{0xAB, 0x00, 0x00}, []byte{}) @@ -201,11 +206,11 @@ func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { iter.Close() db = dbm.NewMemDB() - baseStore = dbStoreAdapter{db} + baseStore = dbadapter.Store{db} // underflow in cpDecr prefix = []byte{0xAA, 0x00, 0x00} - prefixStore = baseStore.Prefix(prefix) + prefixStore = NewStore(baseStore, prefix) baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00, 0x00}, []byte{}) baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00}, []byte{}) @@ -230,9 +235,9 @@ func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { // Tests below are ported from https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db_test.go -func mockStoreWithStuff() sdk.KVStore { +func mockStoreWithStuff() types.KVStore { db := dbm.NewMemDB() - store := dbStoreAdapter{db} + store := dbadapter.Store{db} // Under "key" prefix store.Set(bz("key"), bz("value")) store.Set(bz("key1"), bz("value1")) @@ -246,55 +251,55 @@ func mockStoreWithStuff() sdk.KVStore { return store } -func checkValue(t *testing.T, store sdk.KVStore, key []byte, expected []byte) { +func checkValue(t *testing.T, store types.KVStore, key []byte, expected []byte) { bz := store.Get(key) require.Equal(t, expected, bz) } -func checkValid(t *testing.T, itr sdk.Iterator, expected bool) { +func checkValid(t *testing.T, itr types.Iterator, expected bool) { valid := itr.Valid() require.Equal(t, expected, valid) } -func checkNext(t *testing.T, itr sdk.Iterator, expected bool) { +func checkNext(t *testing.T, itr types.Iterator, expected bool) { itr.Next() valid := itr.Valid() require.Equal(t, expected, valid) } -func checkDomain(t *testing.T, itr sdk.Iterator, start, end []byte) { +func checkDomain(t *testing.T, itr types.Iterator, start, end []byte) { ds, de := itr.Domain() require.Equal(t, start, ds) require.Equal(t, end, de) } -func checkItem(t *testing.T, itr sdk.Iterator, key, value []byte) { +func checkItem(t *testing.T, itr types.Iterator, key, value []byte) { require.Exactly(t, key, itr.Key()) require.Exactly(t, value, itr.Value()) } -func checkInvalid(t *testing.T, itr sdk.Iterator) { +func checkInvalid(t *testing.T, itr types.Iterator) { checkValid(t, itr, false) checkKeyPanics(t, itr) checkValuePanics(t, itr) checkNextPanics(t, itr) } -func checkKeyPanics(t *testing.T, itr sdk.Iterator) { +func checkKeyPanics(t *testing.T, itr types.Iterator) { require.Panics(t, func() { itr.Key() }) } -func checkValuePanics(t *testing.T, itr sdk.Iterator) { +func checkValuePanics(t *testing.T, itr types.Iterator) { require.Panics(t, func() { itr.Value() }) } -func checkNextPanics(t *testing.T, itr sdk.Iterator) { +func checkNextPanics(t *testing.T, itr types.Iterator) { require.Panics(t, func() { itr.Next() }) } func TestPrefixDBSimple(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) checkValue(t, pstore, bz("key"), nil) checkValue(t, pstore, bz(""), bz("value")) @@ -312,7 +317,7 @@ func TestPrefixDBSimple(t *testing.T) { func TestPrefixDBIterator1(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.Iterator(nil, nil) checkDomain(t, itr, nil, nil) @@ -330,7 +335,7 @@ func TestPrefixDBIterator1(t *testing.T) { func TestPrefixDBIterator2(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.Iterator(nil, bz("")) checkDomain(t, itr, nil, bz("")) @@ -340,7 +345,7 @@ func TestPrefixDBIterator2(t *testing.T) { func TestPrefixDBIterator3(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.Iterator(bz(""), nil) checkDomain(t, itr, bz(""), nil) @@ -358,7 +363,7 @@ func TestPrefixDBIterator3(t *testing.T) { func TestPrefixDBIterator4(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.Iterator(bz(""), bz("")) checkDomain(t, itr, bz(""), bz("")) @@ -368,7 +373,7 @@ func TestPrefixDBIterator4(t *testing.T) { func TestPrefixDBReverseIterator1(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.ReverseIterator(nil, nil) checkDomain(t, itr, nil, nil) @@ -386,7 +391,7 @@ func TestPrefixDBReverseIterator1(t *testing.T) { func TestPrefixDBReverseIterator2(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.ReverseIterator(bz(""), nil) checkDomain(t, itr, bz(""), nil) @@ -404,7 +409,7 @@ func TestPrefixDBReverseIterator2(t *testing.T) { func TestPrefixDBReverseIterator3(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.ReverseIterator(nil, bz("")) checkDomain(t, itr, nil, bz("")) @@ -414,7 +419,7 @@ func TestPrefixDBReverseIterator3(t *testing.T) { func TestPrefixDBReverseIterator4(t *testing.T) { store := mockStoreWithStuff() - pstore := store.Prefix(bz("key")) + pstore := NewStore(store, bz("key")) itr := pstore.ReverseIterator(bz(""), bz("")) checkInvalid(t, itr) diff --git a/store/pruning.go b/store/pruning.go deleted file mode 100644 index 9a7aeb4d29e6..000000000000 --- a/store/pruning.go +++ /dev/null @@ -1,29 +0,0 @@ -package store - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// default pruning strategies -var ( - // PruneEverything means all saved states will be deleted, storing only the current state - PruneEverything = sdk.NewPruningOptions(0, 0) - // PruneNothing means all historic states will be saved, nothing will be deleted - PruneNothing = sdk.NewPruningOptions(0, 1) - // PruneSyncable means only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th) - PruneSyncable = sdk.NewPruningOptions(100, 10000) -) - -func NewPruningOptions(strategy string) (opt PruningOptions) { - switch strategy { - case "nothing": - opt = PruneNothing - case "everything": - opt = PruneEverything - case "syncable": - opt = PruneSyncable - default: - opt = PruneSyncable - } - return -} diff --git a/store/queue.go b/store/queue/queue.go similarity index 95% rename from store/queue.go rename to store/queue/queue.go index f41ecb7d7a19..0f65d0375ba3 100644 --- a/store/queue.go +++ b/store/queue/queue.go @@ -1,8 +1,12 @@ package store +// TODO: make it independent from list +/* import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/store/list" ) // Key for the top element position in the queue @@ -13,7 +17,7 @@ func TopKey() []byte { // Queue is a List wrapper that provides queue-like functions // It panics when the element type cannot be (un/)marshalled by the codec type Queue struct { - List List + List list.List } // NewQueue constructs new Queue @@ -86,3 +90,4 @@ func (m Queue) Flush(ptr interface{}, fn func() bool) { } m.setTop(i) } +*/ diff --git a/store/queue_test.go b/store/queue_test.go deleted file mode 100644 index 58e96f56a19e..000000000000 --- a/store/queue_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package store - -import ( - "testing" - - "github.com/stretchr/testify/require" - - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - abci "github.com/tendermint/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type S struct { - I uint64 - B bool -} - -func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { - db := dbm.NewMemDB() - cms := NewCommitMultiStore(db) - cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) - cms.LoadLatestVersion() - ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) - cdc := codec.New() - return ctx, cdc -} - -func TestQueue(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx, cdc := defaultComponents(key) - store := ctx.KVStore(key) - - qm := NewQueue(cdc, store) - - val := S{1, true} - var res S - - qm.Push(val) - qm.Peek(&res) - require.Equal(t, val, res) - - qm.Pop() - empty := qm.IsEmpty() - - require.True(t, empty) - require.NotNil(t, qm.Peek(&res)) - - qm.Push(S{1, true}) - qm.Push(S{2, true}) - qm.Push(S{3, true}) - qm.Flush(&res, func() (brk bool) { - if res.I == 3 { - brk = true - } - return - }) - - require.False(t, qm.IsEmpty()) - - qm.Pop() - require.True(t, qm.IsEmpty()) -} - -func TestKeys(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx, cdc := defaultComponents(key) - store := ctx.KVStore(key) - queue := NewQueue(cdc, store) - - for i := 0; i < 10; i++ { - queue.Push(i) - } - - var len uint64 - var top uint64 - var expected int - var actual int - - // Checking keys.LengthKey - err := cdc.UnmarshalBinaryLengthPrefixed(store.Get(LengthKey()), &len) - require.Nil(t, err) - require.Equal(t, len, queue.List.Len()) - - // Checking keys.ElemKey - for i := 0; i < 10; i++ { - queue.List.Get(uint64(i), &expected) - bz := store.Get(ElemKey(uint64(i))) - err = cdc.UnmarshalBinaryLengthPrefixed(bz, &actual) - require.Nil(t, err) - require.Equal(t, expected, actual) - } - - queue.Pop() - - err = cdc.UnmarshalBinaryLengthPrefixed(store.Get(TopKey()), &top) - require.Nil(t, err) - require.Equal(t, top, queue.getTop()) -} diff --git a/store/codec.go b/store/reexport.go similarity index 72% rename from store/codec.go rename to store/reexport.go index 181f12e511b7..913a8c55a8b3 100644 --- a/store/codec.go +++ b/store/reexport.go @@ -1,7 +1,8 @@ package store import ( - "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store/types" + stypes "github.com/cosmos/cosmos-sdk/store/types" ) // Import cosmos-sdk/types/store.go for convenience. @@ -26,7 +27,14 @@ type ( StoreType = types.StoreType Queryable = types.Queryable TraceContext = types.TraceContext - Gas = types.Gas + Gas = stypes.Gas GasMeter = types.GasMeter - GasConfig = types.GasConfig + GasConfig = stypes.GasConfig +) + +// nolint - reexport +var ( + PruneNothing = types.PruneNothing + PruneEverything = types.PruneEverything + PruneSyncable = types.PruneSyncable ) diff --git a/store/rootmulti/dbadapter.go b/store/rootmulti/dbadapter.go new file mode 100644 index 000000000000..cda6e62ba721 --- /dev/null +++ b/store/rootmulti/dbadapter.go @@ -0,0 +1,33 @@ +package rootmulti + +import ( + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/types" +) + +var commithash = []byte("FAKE_HASH") + +//---------------------------------------- +// commitDBStoreWrapper should only be used for simulation/debugging, +// as it doesn't compute any commit hash, and it cannot load older state. + +// Wrapper type for dbm.Db with implementation of KVStore +type commitDBStoreAdapter struct { + dbadapter.Store +} + +func (cdsa commitDBStoreAdapter) Commit() types.CommitID { + return types.CommitID{ + Version: -1, + Hash: commithash, + } +} + +func (cdsa commitDBStoreAdapter) LastCommitID() types.CommitID { + return types.CommitID{ + Version: -1, + Hash: commithash, + } +} + +func (cdsa commitDBStoreAdapter) SetPruning(_ types.PruningOptions) {} diff --git a/store/multistoreproof.go b/store/rootmulti/proof.go similarity index 99% rename from store/multistoreproof.go rename to store/rootmulti/proof.go index 96f0a48373ea..6657a6007a66 100644 --- a/store/multistoreproof.go +++ b/store/rootmulti/proof.go @@ -1,4 +1,4 @@ -package store +package rootmulti import ( "bytes" diff --git a/store/multistoreproof_test.go b/store/rootmulti/proof_test.go similarity index 84% rename from store/multistoreproof_test.go rename to store/rootmulti/proof_test.go index 3d70451e7273..885d0ddb4eb4 100644 --- a/store/multistoreproof_test.go +++ b/store/rootmulti/proof_test.go @@ -1,20 +1,22 @@ -package store +package rootmulti import ( "testing" + "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store/iavl" + stypes "github.com/cosmos/cosmos-sdk/store/types" ) func TestVerifyIAVLStoreQueryProof(t *testing.T) { // Create main tree for testing. db := dbm.NewMemDB() - iStore, err := LoadIAVLStore(db, CommitID{}, PruneNothing) - store := iStore.(*iavlStore) + iStore, err := iavl.LoadStore(db, types.CommitID{}, stypes.PruneNothing) + store := iStore.(*iavl.Store) require.Nil(t, err) store.Set([]byte("MYKEY"), []byte("MYVALUE")) cid := store.Commit() @@ -56,13 +58,13 @@ func TestVerifyIAVLStoreQueryProof(t *testing.T) { func TestVerifyMultiStoreQueryProof(t *testing.T) { // Create main tree for testing. db := dbm.NewMemDB() - store := NewCommitMultiStore(db) - iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + store := NewStore(db) + iavlStoreKey := types.NewKVStoreKey("iavlStoreKey") - store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.MountStoreWithDB(iavlStoreKey, types.StoreTypeIAVL, nil) store.LoadVersion(0) - iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavl.Store) iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) cid := store.Commit() @@ -111,10 +113,10 @@ func TestVerifyMultiStoreQueryProof(t *testing.T) { func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { // Create main tree for testing. db := dbm.NewMemDB() - store := NewCommitMultiStore(db) - iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + store := NewStore(db) + iavlStoreKey := types.NewKVStoreKey("iavlStoreKey") - store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.MountStoreWithDB(iavlStoreKey, types.StoreTypeIAVL, nil) store.LoadVersion(0) cid := store.Commit() // Commit with empty iavl store. @@ -140,13 +142,13 @@ func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { // Create main tree for testing. db := dbm.NewMemDB() - store := NewCommitMultiStore(db) - iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + store := NewStore(db) + iavlStoreKey := types.NewKVStoreKey("iavlStoreKey") - store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.MountStoreWithDB(iavlStoreKey, types.StoreTypeIAVL, nil) store.LoadVersion(0) - iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavl.Store) iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) cid := store.Commit() // Commit with empty iavl store. diff --git a/store/rootmultistore.go b/store/rootmulti/store.go similarity index 67% rename from store/rootmultistore.go rename to store/rootmulti/store.go index fa576d3dcc6a..8be98a1d8c52 100644 --- a/store/rootmultistore.go +++ b/store/rootmulti/store.go @@ -1,4 +1,4 @@ -package store +package rootmulti import ( "fmt" @@ -10,7 +10,12 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" dbm "github.com/tendermint/tendermint/libs/db" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store/cachemulti" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/iavl" + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/transient" + "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -18,36 +23,36 @@ const ( commitInfoKeyFmt = "s/%d" // s/ ) -// rootMultiStore is composed of many CommitStores. Name contrasts with +// Store is composed of many CommitStores. Name contrasts with // cacheMultiStore which is for cache-wrapping other MultiStores. It implements // the CommitMultiStore interface. -type rootMultiStore struct { +type Store struct { db dbm.DB - lastCommitID CommitID - pruningOpts sdk.PruningOptions - storesParams map[StoreKey]storeParams - stores map[StoreKey]CommitStore - keysByName map[string]StoreKey + lastCommitID types.CommitID + pruningOpts types.PruningOptions + storesParams map[types.StoreKey]storeParams + stores map[types.StoreKey]types.CommitStore + keysByName map[string]types.StoreKey traceWriter io.Writer - traceContext TraceContext + traceContext types.TraceContext } -var _ CommitMultiStore = (*rootMultiStore)(nil) -var _ Queryable = (*rootMultiStore)(nil) +var _ types.CommitMultiStore = (*Store)(nil) +var _ types.Queryable = (*Store)(nil) // nolint -func NewCommitMultiStore(db dbm.DB) *rootMultiStore { - return &rootMultiStore{ +func NewStore(db dbm.DB) *Store { + return &Store{ db: db, - storesParams: make(map[StoreKey]storeParams), - stores: make(map[StoreKey]CommitStore), - keysByName: make(map[string]StoreKey), + storesParams: make(map[types.StoreKey]storeParams), + stores: make(map[types.StoreKey]types.CommitStore), + keysByName: make(map[string]types.StoreKey), } } // Implements CommitMultiStore -func (rs *rootMultiStore) SetPruning(pruningOpts sdk.PruningOptions) { +func (rs *Store) SetPruning(pruningOpts types.PruningOptions) { rs.pruningOpts = pruningOpts for _, substore := range rs.stores { substore.SetPruning(pruningOpts) @@ -55,20 +60,20 @@ func (rs *rootMultiStore) SetPruning(pruningOpts sdk.PruningOptions) { } // Implements Store. -func (rs *rootMultiStore) GetStoreType() StoreType { - return sdk.StoreTypeMulti +func (rs *Store) GetStoreType() types.StoreType { + return types.StoreTypeMulti } // Implements CommitMultiStore. -func (rs *rootMultiStore) MountStoreWithDB(key StoreKey, typ StoreType, db dbm.DB) { +func (rs *Store) MountStoreWithDB(key types.StoreKey, typ types.StoreType, db dbm.DB) { if key == nil { panic("MountIAVLStore() key cannot be nil") } if _, ok := rs.storesParams[key]; ok { - panic(fmt.Sprintf("rootMultiStore duplicate store key %v", key)) + panic(fmt.Sprintf("Store duplicate store key %v", key)) } if _, ok := rs.keysByName[key.Name()]; ok { - panic(fmt.Sprintf("rootMultiStore duplicate store key name %v", key)) + panic(fmt.Sprintf("Store duplicate store key name %v", key)) } rs.storesParams[key] = storeParams{ key: key, @@ -79,36 +84,36 @@ func (rs *rootMultiStore) MountStoreWithDB(key StoreKey, typ StoreType, db dbm.D } // Implements CommitMultiStore. -func (rs *rootMultiStore) GetCommitStore(key StoreKey) CommitStore { +func (rs *Store) GetCommitStore(key types.StoreKey) types.CommitStore { return rs.stores[key] } // Implements CommitMultiStore. -func (rs *rootMultiStore) GetCommitKVStore(key StoreKey) CommitKVStore { - return rs.stores[key].(CommitKVStore) +func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore { + return rs.stores[key].(types.CommitKVStore) } // Implements CommitMultiStore. -func (rs *rootMultiStore) LoadLatestVersion() error { +func (rs *Store) LoadLatestVersion() error { ver := getLatestVersion(rs.db) return rs.LoadVersion(ver) } // Implements CommitMultiStore. -func (rs *rootMultiStore) LoadVersion(ver int64) error { +func (rs *Store) LoadVersion(ver int64) error { // Special logic for version 0 if ver == 0 { for key, storeParams := range rs.storesParams { - id := CommitID{} + id := types.CommitID{} store, err := rs.loadCommitStoreFromParams(key, id, storeParams) if err != nil { - return fmt.Errorf("failed to load rootMultiStore: %v", err) + return fmt.Errorf("failed to load Store: %v", err) } rs.stores[key] = store } - rs.lastCommitID = CommitID{} + rs.lastCommitID = types.CommitID{} return nil } // Otherwise, version is 1 or greater @@ -120,15 +125,15 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { } // Convert StoreInfos slice to map - infos := make(map[StoreKey]storeInfo) + infos := make(map[types.StoreKey]storeInfo) for _, storeInfo := range cInfo.StoreInfos { infos[rs.nameToKey(storeInfo.Name)] = storeInfo } // Load each Store - var newStores = make(map[StoreKey]CommitStore) + var newStores = make(map[types.StoreKey]types.CommitStore) for key, storeParams := range rs.storesParams { - var id CommitID + var id types.CommitID info, ok := infos[key] if ok { id = info.Core.CommitID @@ -136,7 +141,7 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { store, err := rs.loadCommitStoreFromParams(key, id, storeParams) if err != nil { - return fmt.Errorf("failed to load rootMultiStore: %v", err) + return fmt.Errorf("failed to load Store: %v", err) } newStores[key] = store } @@ -147,18 +152,18 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { return nil } -// WithTracer sets the tracer for the MultiStore that the underlying +// SetTracer sets the tracer for the MultiStore that the underlying // stores will utilize to trace operations. A MultiStore is returned. -func (rs *rootMultiStore) WithTracer(w io.Writer) MultiStore { +func (rs *Store) SetTracer(w io.Writer) types.MultiStore { rs.traceWriter = w return rs } -// WithTracingContext updates the tracing context for the MultiStore by merging +// SetTracingContext updates the tracing context for the MultiStore by merging // the given context with the existing context by key. Any existing keys will // be overwritten. It is implied that the caller should update the context when // necessary between tracing operations. It returns a modified MultiStore. -func (rs *rootMultiStore) WithTracingContext(tc TraceContext) MultiStore { +func (rs *Store) SetTracingContext(tc types.TraceContext) types.MultiStore { if rs.traceContext != nil { for k, v := range tc { rs.traceContext[k] = v @@ -171,26 +176,20 @@ func (rs *rootMultiStore) WithTracingContext(tc TraceContext) MultiStore { } // TracingEnabled returns if tracing is enabled for the MultiStore. -func (rs *rootMultiStore) TracingEnabled() bool { +func (rs *Store) TracingEnabled() bool { return rs.traceWriter != nil } -// ResetTraceContext resets the current tracing context. -func (rs *rootMultiStore) ResetTraceContext() MultiStore { - rs.traceContext = nil - return rs -} - //---------------------------------------- // +CommitStore // Implements Committer/CommitStore. -func (rs *rootMultiStore) LastCommitID() CommitID { +func (rs *Store) LastCommitID() types.CommitID { return rs.lastCommitID } // Implements Committer/CommitStore. -func (rs *rootMultiStore) Commit() CommitID { +func (rs *Store) Commit() types.CommitID { // Commit stores. version := rs.lastCommitID.Version + 1 @@ -203,7 +202,7 @@ func (rs *rootMultiStore) Commit() CommitID { batch.Write() // Prepare for next version. - commitID := CommitID{ + commitID := types.CommitID{ Version: version, Hash: commitInfo.Hash(), } @@ -212,12 +211,12 @@ func (rs *rootMultiStore) Commit() CommitID { } // Implements CacheWrapper/Store/CommitStore. -func (rs *rootMultiStore) CacheWrap() CacheWrap { - return rs.CacheMultiStore().(CacheWrap) +func (rs *Store) CacheWrap() types.CacheWrap { + return rs.CacheMultiStore().(types.CacheWrap) } // CacheWrapWithTrace implements the CacheWrapper interface. -func (rs *rootMultiStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { +func (rs *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap { return rs.CacheWrap() } @@ -225,13 +224,17 @@ func (rs *rootMultiStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheW // +MultiStore // Implements MultiStore. -func (rs *rootMultiStore) CacheMultiStore() CacheMultiStore { - return newCacheMultiStoreFromRMS(rs) +func (rs *Store) CacheMultiStore() types.CacheMultiStore { + stores := make(map[types.StoreKey]types.CacheWrapper) + for k, v := range rs.stores { + stores[k] = v + } + return cachemulti.NewStore(rs.db, stores, rs.keysByName, rs.traceWriter, rs.traceContext) } // Implements MultiStore. // If the store does not exist, panics. -func (rs *rootMultiStore) GetStore(key StoreKey) Store { +func (rs *Store) GetStore(key types.StoreKey) types.Store { store := rs.stores[key] if store == nil { panic("Could not load store " + key.String()) @@ -240,14 +243,14 @@ func (rs *rootMultiStore) GetStore(key StoreKey) Store { } // GetKVStore implements the MultiStore interface. If tracing is enabled on the -// rootMultiStore, a wrapped TraceKVStore will be returned with the given +// Store, a wrapped TraceKVStore will be returned with the given // tracer, otherwise, the original KVStore will be returned. // If the store does not exist, panics. -func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore { - store := rs.stores[key].(KVStore) +func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore { + store := rs.stores[key].(types.KVStore) if rs.TracingEnabled() { - store = NewTraceKVStore(store, rs.traceWriter, rs.traceContext) + store = tracekv.NewStore(store, rs.traceWriter, rs.traceContext) } return store @@ -260,7 +263,7 @@ func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore { // This is not exposed to the extensions (which will need the // StoreKey), but is useful in main, and particularly app.Query, // in order to convert human strings into CommitStores. -func (rs *rootMultiStore) getStoreByName(name string) Store { +func (rs *Store) getStoreByName(name string) types.Store { key := rs.keysByName[name] if key == nil { return nil @@ -274,7 +277,7 @@ func (rs *rootMultiStore) getStoreByName(name string) Store { // modified to remove the substore prefix. // Ie. `req.Path` here is `//`, and trimmed to `/` for the substore. // TODO: add proof for `multistore -> substore`. -func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { +func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { // Query just routes this to a substore. path := req.Path storeName, subpath, err := parsePath(path) @@ -285,12 +288,12 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { store := rs.getStoreByName(storeName) if store == nil { msg := fmt.Sprintf("no such store: %s", storeName) - return sdk.ErrUnknownRequest(msg).QueryResult() + return types.ErrUnknownRequest(msg).QueryResult() } - queryable, ok := store.(Queryable) + queryable, ok := store.(types.Queryable) if !ok { msg := fmt.Sprintf("store %s doesn't support queries", storeName) - return sdk.ErrUnknownRequest(msg).QueryResult() + return types.ErrUnknownRequest(msg).QueryResult() } // trim the path and make the query @@ -302,12 +305,12 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { } if res.Proof == nil || len(res.Proof.Ops) == 0 { - return sdk.ErrInternal("substore proof was nil/empty when it should never be").QueryResult() + return types.ErrInternal("substore proof was nil/empty when it should never be").QueryResult() } commitInfo, errMsg := getCommitInfo(rs.db, res.Height) if errMsg != nil { - return sdk.ErrInternal(errMsg.Error()).QueryResult() + return types.ErrInternal(errMsg.Error()).QueryResult() } // Restore origin path and append proof op. @@ -324,9 +327,9 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { // parsePath expects a format like /[/] // Must start with /, subpath may be empty // Returns error if it doesn't start with / -func parsePath(path string) (storeName string, subpath string, err sdk.Error) { +func parsePath(path string) (storeName string, subpath string, err types.Error) { if !strings.HasPrefix(path, "/") { - err = sdk.ErrUnknownRequest(fmt.Sprintf("invalid path: %s", path)) + err = types.ErrUnknownRequest(fmt.Sprintf("invalid path: %s", path)) return } @@ -342,7 +345,7 @@ func parsePath(path string) (storeName string, subpath string, err sdk.Error) { //---------------------------------------- -func (rs *rootMultiStore) loadCommitStoreFromParams(key sdk.StoreKey, id CommitID, params storeParams) (store CommitStore, err error) { +func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (store types.CommitStore, err error) { var db dbm.DB if params.db != nil { db = dbm.NewPrefixDB(params.db, []byte("s/_/")) @@ -350,30 +353,30 @@ func (rs *rootMultiStore) loadCommitStoreFromParams(key sdk.StoreKey, id CommitI db = dbm.NewPrefixDB(rs.db, []byte("s/k:"+params.key.Name()+"/")) } switch params.typ { - case sdk.StoreTypeMulti: + case types.StoreTypeMulti: panic("recursive MultiStores not yet supported") // TODO: id? // return NewCommitMultiStore(db, id) - case sdk.StoreTypeIAVL: - store, err = LoadIAVLStore(db, id, rs.pruningOpts) + case types.StoreTypeIAVL: + store, err = iavl.LoadStore(db, id, rs.pruningOpts) return - case sdk.StoreTypeDB: - store = commitDBStoreAdapter{dbStoreAdapter{db}} + case types.StoreTypeDB: + store = commitDBStoreAdapter{dbadapter.Store{db}} return - case sdk.StoreTypeTransient: - _, ok := key.(*sdk.TransientStoreKey) + case types.StoreTypeTransient: + _, ok := key.(*types.TransientStoreKey) if !ok { err = fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String()) return } - store = newTransientStore() + store = transient.NewStore() return default: panic(fmt.Sprintf("unrecognized store type %v", params.typ)) } } -func (rs *rootMultiStore) nameToKey(name string) StoreKey { +func (rs *Store) nameToKey(name string) types.StoreKey { for key := range rs.storesParams { if key.Name() == name { return key @@ -386,9 +389,9 @@ func (rs *rootMultiStore) nameToKey(name string) StoreKey { // storeParams type storeParams struct { - key StoreKey + key types.StoreKey db dbm.DB - typ StoreType + typ types.StoreType } //---------------------------------------- @@ -415,8 +418,8 @@ func (ci commitInfo) Hash() []byte { return merkle.SimpleHashFromMap(m) } -func (ci commitInfo) CommitID() CommitID { - return CommitID{ +func (ci commitInfo) CommitID() types.CommitID { + return types.CommitID{ Version: ci.Version, Hash: ci.Hash(), } @@ -426,7 +429,7 @@ func (ci commitInfo) CommitID() CommitID { // storeInfo // storeInfo contains the name and core reference for an -// underlying store. It is the leaf of the rootMultiStores top +// underlying store. It is the leaf of the Stores top // level simple merkle tree. type storeInfo struct { Name string @@ -435,7 +438,7 @@ type storeInfo struct { type storeCore struct { // StoreType StoreType - CommitID CommitID + CommitID types.CommitID // ... maybe add more state } @@ -480,14 +483,14 @@ func setLatestVersion(batch dbm.Batch, version int64) { } // Commits each store and returns a new commitInfo. -func commitStores(version int64, storeMap map[StoreKey]CommitStore) commitInfo { +func commitStores(version int64, storeMap map[types.StoreKey]types.CommitStore) commitInfo { storeInfos := make([]storeInfo, 0, len(storeMap)) for key, store := range storeMap { // Commit commitID := store.Commit() - if store.GetStoreType() == sdk.StoreTypeTransient { + if store.GetStoreType() == types.StoreTypeTransient { continue } @@ -513,14 +516,14 @@ func getCommitInfo(db dbm.DB, ver int64) (commitInfo, error) { cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver) cInfoBytes := db.Get([]byte(cInfoKey)) if cInfoBytes == nil { - return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: no data") + return commitInfo{}, fmt.Errorf("failed to get Store: no data") } var cInfo commitInfo err := cdc.UnmarshalBinaryLengthPrefixed(cInfoBytes, &cInfo) if err != nil { - return commitInfo{}, fmt.Errorf("failed to get rootMultiStore: %v", err) + return commitInfo{}, fmt.Errorf("failed to get Store: %v", err) } return cInfo, nil diff --git a/store/rootmultistore_test.go b/store/rootmulti/store_test.go similarity index 71% rename from store/rootmultistore_test.go rename to store/rootmulti/store_test.go index 10f0956562ba..75aed91b6c4b 100644 --- a/store/rootmultistore_test.go +++ b/store/rootmulti/store_test.go @@ -1,4 +1,4 @@ -package store +package rootmulti import ( "testing" @@ -8,32 +8,33 @@ import ( "github.com/tendermint/tendermint/crypto/merkle" dbm "github.com/tendermint/tendermint/libs/db" - sdk "github.com/cosmos/cosmos-sdk/types" + stypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/types" ) const useDebugDB = false func TestStoreType(t *testing.T) { db := dbm.NewMemDB() - store := NewCommitMultiStore(db) + store := NewStore(db) store.MountStoreWithDB( - sdk.NewKVStoreKey("store1"), sdk.StoreTypeIAVL, db) + types.NewKVStoreKey("store1"), types.StoreTypeIAVL, db) } func TestStoreMount(t *testing.T) { db := dbm.NewMemDB() - store := NewCommitMultiStore(db) + store := NewStore(db) - key1 := sdk.NewKVStoreKey("store1") - key2 := sdk.NewKVStoreKey("store2") - dup1 := sdk.NewKVStoreKey("store1") + key1 := types.NewKVStoreKey("store1") + key2 := types.NewKVStoreKey("store2") + dup1 := types.NewKVStoreKey("store1") - require.NotPanics(t, func() { store.MountStoreWithDB(key1, sdk.StoreTypeIAVL, db) }) - require.NotPanics(t, func() { store.MountStoreWithDB(key2, sdk.StoreTypeIAVL, db) }) + require.NotPanics(t, func() { store.MountStoreWithDB(key1, types.StoreTypeIAVL, db) }) + require.NotPanics(t, func() { store.MountStoreWithDB(key2, types.StoreTypeIAVL, db) }) - require.Panics(t, func() { store.MountStoreWithDB(key1, sdk.StoreTypeIAVL, db) }) - require.Panics(t, func() { store.MountStoreWithDB(dup1, sdk.StoreTypeIAVL, db) }) + require.Panics(t, func() { store.MountStoreWithDB(key1, types.StoreTypeIAVL, db) }) + require.Panics(t, func() { store.MountStoreWithDB(dup1, types.StoreTypeIAVL, db) }) } func TestMultistoreCommitLoad(t *testing.T) { @@ -46,7 +47,7 @@ func TestMultistoreCommitLoad(t *testing.T) { require.Nil(t, err) // New store has empty last commit. - commitID := CommitID{} + commitID := types.CommitID{} checkStore(t, store, commitID, commitID) // Make sure we can get stores by name. @@ -137,11 +138,11 @@ func TestMultiStoreQuery(t *testing.T) { require.Nil(t, garbage) // Set and commit data in one store. - store1 := multi.getStoreByName("store1").(KVStore) + store1 := multi.getStoreByName("store1").(types.KVStore) store1.Set(k, v) // ... and another. - store2 := multi.getStoreByName("store2").(KVStore) + store2 := multi.getStoreByName("store2").(types.KVStore) store2.Set(k2, v2) // Commit the multistore. @@ -156,69 +157,69 @@ func TestMultiStoreQuery(t *testing.T) { // Test bad path. query := abci.RequestQuery{Path: "/key", Data: k, Height: ver} qres := multi.Query(query) - require.EqualValues(t, sdk.CodeUnknownRequest, qres.Code) - require.EqualValues(t, sdk.CodespaceRoot, qres.Codespace) + require.EqualValues(t, types.CodeUnknownRequest, qres.Code) + require.EqualValues(t, types.CodespaceRoot, qres.Codespace) query.Path = "h897fy32890rf63296r92" qres = multi.Query(query) - require.EqualValues(t, sdk.CodeUnknownRequest, qres.Code) - require.EqualValues(t, sdk.CodespaceRoot, qres.Codespace) + require.EqualValues(t, types.CodeUnknownRequest, qres.Code) + require.EqualValues(t, types.CodespaceRoot, qres.Codespace) // Test invalid store name. query.Path = "/garbage/key" qres = multi.Query(query) - require.EqualValues(t, sdk.CodeUnknownRequest, qres.Code) - require.EqualValues(t, sdk.CodespaceRoot, qres.Codespace) + require.EqualValues(t, types.CodeUnknownRequest, qres.Code) + require.EqualValues(t, types.CodespaceRoot, qres.Codespace) // Test valid query with data. query.Path = "/store1/key" qres = multi.Query(query) - require.EqualValues(t, sdk.CodeOK, qres.Code) + require.EqualValues(t, types.CodeOK, qres.Code) require.Equal(t, v, qres.Value) // Test valid but empty query. query.Path = "/store2/key" query.Prove = true qres = multi.Query(query) - require.EqualValues(t, sdk.CodeOK, qres.Code) + require.EqualValues(t, types.CodeOK, qres.Code) require.Nil(t, qres.Value) // Test store2 data. query.Data = k2 qres = multi.Query(query) - require.EqualValues(t, sdk.CodeOK, qres.Code) + require.EqualValues(t, types.CodeOK, qres.Code) require.Equal(t, v2, qres.Value) } //----------------------------------------------------------------------- // utils -func newMultiStoreWithMounts(db dbm.DB) *rootMultiStore { - store := NewCommitMultiStore(db) - store.pruningOpts = PruneSyncable +func newMultiStoreWithMounts(db dbm.DB) *Store { + store := NewStore(db) + store.pruningOpts = stypes.PruneSyncable store.MountStoreWithDB( - sdk.NewKVStoreKey("store1"), sdk.StoreTypeIAVL, nil) + types.NewKVStoreKey("store1"), types.StoreTypeIAVL, nil) store.MountStoreWithDB( - sdk.NewKVStoreKey("store2"), sdk.StoreTypeIAVL, nil) + types.NewKVStoreKey("store2"), types.StoreTypeIAVL, nil) store.MountStoreWithDB( - sdk.NewKVStoreKey("store3"), sdk.StoreTypeIAVL, nil) + types.NewKVStoreKey("store3"), types.StoreTypeIAVL, nil) return store } -func checkStore(t *testing.T, store *rootMultiStore, expect, got CommitID) { +func checkStore(t *testing.T, store *Store, expect, got types.CommitID) { require.Equal(t, expect, got) require.Equal(t, expect, store.LastCommitID()) } -func getExpectedCommitID(store *rootMultiStore, ver int64) CommitID { - return CommitID{ +func getExpectedCommitID(store *Store, ver int64) types.CommitID { + return types.CommitID{ Version: ver, Hash: hashStores(store.stores), } } -func hashStores(stores map[StoreKey]CommitStore) []byte { +func hashStores(stores map[types.StoreKey]types.CommitStore) []byte { m := make(map[string][]byte, len(stores)) for key, store := range stores { name := key.Name() diff --git a/store/rootmulti/wire.go b/store/rootmulti/wire.go new file mode 100644 index 000000000000..8d6d936160ff --- /dev/null +++ b/store/rootmulti/wire.go @@ -0,0 +1,7 @@ +package rootmulti + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +var cdc = codec.New() diff --git a/store/store.go b/store/store.go new file mode 100644 index 000000000000..cb7a0ada8515 --- /dev/null +++ b/store/store.go @@ -0,0 +1,26 @@ +package store + +import ( + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/rootmulti" + "github.com/cosmos/cosmos-sdk/store/types" +) + +func NewCommitMultiStore(db dbm.DB) types.CommitMultiStore { + return rootmulti.NewStore(db) +} + +func NewPruningOptionsFromString(strategy string) (opt PruningOptions) { + switch strategy { + case "nothing": + opt = PruneNothing + case "everything": + opt = PruneEverything + case "syncable": + opt = PruneSyncable + default: + opt = PruneSyncable + } + return +} diff --git a/store/tracekvstore.go b/store/tracekv/store.go similarity index 68% rename from store/tracekvstore.go rename to store/tracekv/store.go index d8c34c354542..ee9558ecbe5a 100644 --- a/store/tracekvstore.go +++ b/store/tracekv/store.go @@ -1,4 +1,4 @@ -package store +package tracekv import ( "encoding/base64" @@ -6,7 +6,7 @@ import ( "fmt" "io" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store/types" ) const ( @@ -18,16 +18,16 @@ const ( ) type ( - // TraceKVStore implements the KVStore interface with tracing enabled. + // Store implements the KVStore interface with tracing enabled. // Operations are traced on each core KVStore call and written to the // underlying io.writer. // // TODO: Should we use a buffered writer and implement Commit on - // TraceKVStore? - TraceKVStore struct { - parent sdk.KVStore + // Store? + Store struct { + parent types.KVStore writer io.Writer - context TraceContext + context types.TraceContext } // operation represents an IO operation @@ -42,15 +42,15 @@ type ( } ) -// NewTraceKVStore returns a reference to a new traceKVStore given a parent +// NewStore returns a reference to a new traceKVStore given a parent // KVStore implementation and a buffered writer. -func NewTraceKVStore(parent sdk.KVStore, writer io.Writer, tc TraceContext) *TraceKVStore { - return &TraceKVStore{parent: parent, writer: writer, context: tc} +func NewStore(parent types.KVStore, writer io.Writer, tc types.TraceContext) *Store { + return &Store{parent: parent, writer: writer, context: tc} } // Get implements the KVStore interface. It traces a read operation and // delegates a Get call to the parent KVStore. -func (tkv *TraceKVStore) Get(key []byte) []byte { +func (tkv *Store) Get(key []byte) []byte { value := tkv.parent.Get(key) writeOperation(tkv.writer, readOp, tkv.context, key, value) @@ -59,50 +59,40 @@ func (tkv *TraceKVStore) Get(key []byte) []byte { // Set implements the KVStore interface. It traces a write operation and // delegates the Set call to the parent KVStore. -func (tkv *TraceKVStore) Set(key []byte, value []byte) { +func (tkv *Store) Set(key []byte, value []byte) { writeOperation(tkv.writer, writeOp, tkv.context, key, value) tkv.parent.Set(key, value) } // Delete implements the KVStore interface. It traces a write operation and // delegates the Delete call to the parent KVStore. -func (tkv *TraceKVStore) Delete(key []byte) { +func (tkv *Store) Delete(key []byte) { writeOperation(tkv.writer, deleteOp, tkv.context, key, nil) tkv.parent.Delete(key) } // Has implements the KVStore interface. It delegates the Has call to the // parent KVStore. -func (tkv *TraceKVStore) Has(key []byte) bool { +func (tkv *Store) Has(key []byte) bool { return tkv.parent.Has(key) } -// Prefix implements the KVStore interface. -func (tkv *TraceKVStore) Prefix(prefix []byte) KVStore { - return prefixStore{tkv, prefix} -} - -// Gas implements the KVStore interface. -func (tkv *TraceKVStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, tkv.parent) -} - // Iterator implements the KVStore interface. It delegates the Iterator call // the to the parent KVStore. -func (tkv *TraceKVStore) Iterator(start, end []byte) sdk.Iterator { +func (tkv *Store) Iterator(start, end []byte) types.Iterator { return tkv.iterator(start, end, true) } // ReverseIterator implements the KVStore interface. It delegates the // ReverseIterator call the to the parent KVStore. -func (tkv *TraceKVStore) ReverseIterator(start, end []byte) sdk.Iterator { +func (tkv *Store) ReverseIterator(start, end []byte) types.Iterator { return tkv.iterator(start, end, false) } // iterator facilitates iteration over a KVStore. It delegates the necessary // calls to it's parent KVStore. -func (tkv *TraceKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { - var parent sdk.Iterator +func (tkv *Store) iterator(start, end []byte, ascending bool) types.Iterator { + var parent types.Iterator if ascending { parent = tkv.parent.Iterator(start, end) @@ -114,12 +104,12 @@ func (tkv *TraceKVStore) iterator(start, end []byte, ascending bool) sdk.Iterato } type traceIterator struct { - parent sdk.Iterator + parent types.Iterator writer io.Writer - context TraceContext + context types.TraceContext } -func newTraceIterator(w io.Writer, parent sdk.Iterator, tc TraceContext) sdk.Iterator { +func newTraceIterator(w io.Writer, parent types.Iterator, tc types.TraceContext) types.Iterator { return &traceIterator{writer: w, parent: parent, context: tc} } @@ -161,26 +151,26 @@ func (ti *traceIterator) Close() { // GetStoreType implements the KVStore interface. It returns the underlying // KVStore type. -func (tkv *TraceKVStore) GetStoreType() sdk.StoreType { +func (tkv *Store) GetStoreType() types.StoreType { return tkv.parent.GetStoreType() } -// CacheWrap implements the KVStore interface. It panics as a TraceKVStore +// CacheWrap implements the KVStore interface. It panics as a Store // cannot be cache wrapped. -func (tkv *TraceKVStore) CacheWrap() sdk.CacheWrap { - panic("cannot CacheWrap a TraceKVStore") +func (tkv *Store) CacheWrap() types.CacheWrap { + panic("cannot CacheWrap a Store") } // CacheWrapWithTrace implements the KVStore interface. It panics as a -// TraceKVStore cannot be cache wrapped. -func (tkv *TraceKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { - panic("cannot CacheWrapWithTrace a TraceKVStore") +// Store cannot be cache wrapped. +func (tkv *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap { + panic("cannot CacheWrapWithTrace a Store") } // writeOperation writes a KVStore operation to the underlying io.Writer as // JSON-encoded data where the key/value pair is base64 encoded. // nolint: errcheck -func writeOperation(w io.Writer, op operation, tc TraceContext, key, value []byte) { +func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value []byte) { traceOp := traceOperation{ Operation: op, Key: base64.StdEncoding.EncodeToString(key), diff --git a/store/tracekvstore_test.go b/store/tracekv/store_test.go similarity index 89% rename from store/tracekvstore_test.go rename to store/tracekv/store_test.go index 887fcf96e5c9..36d101583d05 100644 --- a/store/tracekvstore_test.go +++ b/store/tracekv/store_test.go @@ -1,22 +1,33 @@ -package store +package tracekv_test import ( "bytes" + "fmt" "io" "testing" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" ) -var kvPairs = []KVPair{ +func bz(s string) []byte { return []byte(s) } + +func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } +func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } + +var kvPairs = []types.KVPair{ {Key: keyFmt(1), Value: valFmt(1)}, {Key: keyFmt(2), Value: valFmt(2)}, {Key: keyFmt(3), Value: valFmt(3)}, } -func newTraceKVStore(w io.Writer) *TraceKVStore { +func newTraceKVStore(w io.Writer) *tracekv.Store { store := newEmptyTraceKVStore(w) for _, kvPair := range kvPairs { @@ -26,11 +37,11 @@ func newTraceKVStore(w io.Writer) *TraceKVStore { return store } -func newEmptyTraceKVStore(w io.Writer) *TraceKVStore { - memDB := dbStoreAdapter{dbm.NewMemDB()} - tc := TraceContext(map[string]interface{}{"blockHeight": 64}) +func newEmptyTraceKVStore(w io.Writer) *tracekv.Store { + memDB := dbadapter.Store{dbm.NewMemDB()} + tc := types.TraceContext(map[string]interface{}{"blockHeight": 64}) - return NewTraceKVStore(memDB, w, tc) + return tracekv.NewStore(memDB, w, tc) } func TestTraceKVStoreGet(t *testing.T) { @@ -263,12 +274,12 @@ func TestTestTraceKVStoreReverseIterator(t *testing.T) { func TestTraceKVStorePrefix(t *testing.T) { store := newEmptyTraceKVStore(nil) - pStore := store.Prefix([]byte("trace_prefix")) - require.IsType(t, prefixStore{}, pStore) + pStore := prefix.NewStore(store, []byte("trace_prefix")) + require.IsType(t, prefix.Store{}, pStore) } func TestTraceKVStoreGetStoreType(t *testing.T) { - memDB := dbStoreAdapter{dbm.NewMemDB()} + memDB := dbadapter.Store{dbm.NewMemDB()} store := newEmptyTraceKVStore(nil) require.Equal(t, memDB.GetStoreType(), store.GetStoreType()) } diff --git a/store/transient/store.go b/store/transient/store.go new file mode 100644 index 000000000000..0a7e945e3e58 --- /dev/null +++ b/store/transient/store.go @@ -0,0 +1,42 @@ +package transient + +import ( + "github.com/cosmos/cosmos-sdk/store/types" + dbm "github.com/tendermint/tendermint/libs/db" + + "github.com/cosmos/cosmos-sdk/store/dbadapter" +) + +var _ types.Committer = (*Store)(nil) +var _ types.KVStore = (*Store)(nil) + +// Store is a wrapper for a MemDB with Commiter implementation +type Store struct { + dbadapter.Store +} + +// Constructs new MemDB adapter +func NewStore() *Store { + return &Store{dbadapter.Store{dbm.NewMemDB()}} +} + +// Implements CommitStore +// Commit cleans up Store. +func (ts *Store) Commit() (id types.CommitID) { + ts.Store = dbadapter.Store{dbm.NewMemDB()} + return +} + +// Implements CommitStore +func (ts *Store) SetPruning(pruning types.PruningOptions) { +} + +// Implements CommitStore +func (ts *Store) LastCommitID() (id types.CommitID) { + return +} + +// Implements Store. +func (ts *Store) GetStoreType() types.StoreType { + return types.StoreTypeTransient +} diff --git a/store/transientstore_test.go b/store/transient/store_test.go similarity index 86% rename from store/transientstore_test.go rename to store/transient/store_test.go index 1c9e98cfaaa1..846c8a3a43ca 100644 --- a/store/transientstore_test.go +++ b/store/transient/store_test.go @@ -1,4 +1,4 @@ -package store +package transient import ( "testing" @@ -9,7 +9,7 @@ import ( var k, v = []byte("hello"), []byte("world") func TestTransientStore(t *testing.T) { - tstore := newTransientStore() + tstore := NewStore() require.Nil(t, tstore.Get(k)) diff --git a/store/transientstore.go b/store/transientstore.go deleted file mode 100644 index 2de1197b977d..000000000000 --- a/store/transientstore.go +++ /dev/null @@ -1,50 +0,0 @@ -package store - -import ( - dbm "github.com/tendermint/tendermint/libs/db" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -var _ KVStore = (*transientStore)(nil) - -// transientStore is a wrapper for a MemDB with Commiter implementation -type transientStore struct { - dbStoreAdapter -} - -// Constructs new MemDB adapter -func newTransientStore() *transientStore { - return &transientStore{dbStoreAdapter{dbm.NewMemDB()}} -} - -// Implements CommitStore -// Commit cleans up transientStore. -func (ts *transientStore) Commit() (id CommitID) { - ts.dbStoreAdapter = dbStoreAdapter{dbm.NewMemDB()} - return -} - -// Implements CommitStore -func (ts *transientStore) SetPruning(opts PruningOptions) { -} - -// Implements CommitStore -func (ts *transientStore) LastCommitID() (id CommitID) { - return -} - -// Implements KVStore -func (ts *transientStore) Prefix(prefix []byte) KVStore { - return prefixStore{ts, prefix} -} - -// Implements KVStore -func (ts *transientStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, ts) -} - -// Implements Store. -func (ts *transientStore) GetStoreType() StoreType { - return sdk.StoreTypeTransient -} diff --git a/types/gas.go b/store/types/gas.go similarity index 89% rename from types/gas.go rename to store/types/gas.go index 2daecdeb18dd..0acbcd7e2293 100644 --- a/types/gas.go +++ b/store/types/gas.go @@ -1,5 +1,7 @@ package types +import "math" + // Gas consumption descriptors. const ( GasIterNextCostFlatDesc = "IterNextFlat" @@ -69,11 +71,20 @@ func (g *basicGasMeter) GasConsumedToLimit() Gas { return g.consumed } +// addUint64Overflow performs the addition operation on two uint64 integers and +// returns a boolean on whether or not the result overflows. +func addUint64Overflow(a, b uint64) (uint64, bool) { + if math.MaxUint64-a < b { + return 0, true + } + + return a + b, false +} + func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) { var overflow bool - // TODO: Should we set the consumed field after overflow checking? - g.consumed, overflow = AddUint64Overflow(g.consumed, amount) + g.consumed, overflow = addUint64Overflow(g.consumed, amount) if overflow { panic(ErrorGasOverflow{descriptor}) } @@ -81,6 +92,7 @@ func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) { if g.consumed > g.limit { panic(ErrorOutOfGas{descriptor}) } + } func (g *basicGasMeter) IsPastLimit() bool { @@ -116,9 +128,8 @@ func (g *infiniteGasMeter) Limit() Gas { func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { var overflow bool - // TODO: Should we set the consumed field after overflow checking? - g.consumed, overflow = AddUint64Overflow(g.consumed, amount) + g.consumed, overflow = addUint64Overflow(g.consumed, amount) if overflow { panic(ErrorGasOverflow{descriptor}) } diff --git a/types/gas_test.go b/store/types/gas_test.go similarity index 70% rename from types/gas_test.go rename to store/types/gas_test.go index 5f862dccdb67..81bb0c901747 100644 --- a/types/gas_test.go +++ b/store/types/gas_test.go @@ -1,6 +1,7 @@ package types import ( + "math" "testing" "github.com/stretchr/testify/require" @@ -43,3 +44,28 @@ func TestGasMeter(t *testing.T) { } } + +func TestAddUint64Overflow(t *testing.T) { + testCases := []struct { + a, b uint64 + result uint64 + overflow bool + }{ + {0, 0, 0, false}, + {100, 100, 200, false}, + {math.MaxUint64 / 2, math.MaxUint64/2 + 1, math.MaxUint64, false}, + {math.MaxUint64 / 2, math.MaxUint64/2 + 2, 0, true}, + } + + for i, tc := range testCases { + res, overflow := addUint64Overflow(tc.a, tc.b) + require.Equal( + t, tc.overflow, overflow, + "invalid overflow result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, + ) + require.Equal( + t, tc.result, res, + "invalid uint64 result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, + ) + } +} diff --git a/store/types/pruning.go b/store/types/pruning.go new file mode 100644 index 000000000000..f82023a2c998 --- /dev/null +++ b/store/types/pruning.go @@ -0,0 +1,35 @@ +package types + +// PruningStrategy specifies how old states will be deleted over time where +// keepRecent can be used with keepEvery to create a pruning "strategy". +type PruningOptions struct { + keepRecent int64 + keepEvery int64 +} + +func NewPruningOptions(keepRecent, keepEvery int64) PruningOptions { + return PruningOptions{ + keepRecent: keepRecent, + keepEvery: keepEvery, + } +} + +// How much recent state will be kept. Older state will be deleted. +func (po PruningOptions) KeepRecent() int64 { + return po.keepRecent +} + +// Keeps every N stated, deleting others. +func (po PruningOptions) KeepEvery() int64 { + return po.keepEvery +} + +// default pruning strategies +var ( + // PruneEverything means all saved states will be deleted, storing only the current state + PruneEverything = NewPruningOptions(0, 0) + // PruneNothing means all historic states will be saved, nothing will be deleted + PruneNothing = NewPruningOptions(0, 1) + // PruneSyncable means only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th) + PruneSyncable = NewPruningOptions(100, 10000) +) diff --git a/store/types/store.go b/store/types/store.go new file mode 100644 index 000000000000..db696f688787 --- /dev/null +++ b/store/types/store.go @@ -0,0 +1,274 @@ +package types + +import ( + "fmt" + "io" + + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" +) + +type Store interface { //nolint + GetStoreType() StoreType + CacheWrapper +} + +// something that can persist to disk +type Committer interface { + Commit() CommitID + LastCommitID() CommitID + SetPruning(PruningOptions) +} + +// Stores of MultiStore must implement CommitStore. +type CommitStore interface { + Committer + Store +} + +// Queryable allows a Store to expose internal state to the abci.Query +// interface. Multistore can route requests to the proper Store. +// +// This is an optional, but useful extension to any CommitStore +type Queryable interface { + Query(abci.RequestQuery) abci.ResponseQuery +} + +//---------------------------------------- +// MultiStore + +type MultiStore interface { //nolint + Store + + // Cache wrap MultiStore. + // NOTE: Caller should probably not call .Write() on each, but + // call CacheMultiStore.Write(). + CacheMultiStore() CacheMultiStore + + // Convenience for fetching substores. + // If the store does not exist, panics. + GetStore(StoreKey) Store + GetKVStore(StoreKey) KVStore + + // TracingEnabled returns if tracing is enabled for the MultiStore. + TracingEnabled() bool + + // SetTracer sets the tracer for the MultiStore that the underlying + // stores will utilize to trace operations. The modified MultiStore is + // returned. + SetTracer(w io.Writer) MultiStore + + // SetTracingContext sets the tracing context for a MultiStore. It is + // implied that the caller should update the context when necessary between + // tracing operations. The modified MultiStore is returned. + SetTracingContext(TraceContext) MultiStore +} + +// From MultiStore.CacheMultiStore().... +type CacheMultiStore interface { + MultiStore + Write() // Writes operations to underlying KVStore +} + +// A non-cache MultiStore. +type CommitMultiStore interface { + Committer + MultiStore + + // Mount a store of type using the given db. + // If db == nil, the new store will use the CommitMultiStore db. + MountStoreWithDB(key StoreKey, typ StoreType, db dbm.DB) + + // Panics on a nil key. + GetCommitStore(key StoreKey) CommitStore + + // Panics on a nil key. + GetCommitKVStore(key StoreKey) CommitKVStore + + // Load the latest persisted version. Called once after all + // calls to Mount*Store() are complete. + LoadLatestVersion() error + + // Load a specific persisted version. When you load an old + // version, or when the last commit attempt didn't complete, + // the next commit after loading must be idempotent (return the + // same commit id). Otherwise the behavior is undefined. + LoadVersion(ver int64) error +} + +//---------subsp------------------------------- +// KVStore + +// KVStore is a simple interface to get/set data +type KVStore interface { + Store + + // Get returns nil iff key doesn't exist. Panics on nil key. + Get(key []byte) []byte + + // Has checks if a key exists. Panics on nil key. + Has(key []byte) bool + + // Set sets the key. Panics on nil key or value. + Set(key, value []byte) + + // Delete deletes the key. Panics on nil key. + Delete(key []byte) + + // Iterator over a domain of keys in ascending order. End is exclusive. + // Start must be less than end, or the Iterator is invalid. + // Iterator must be closed by caller. + // To iterate over entire domain, use store.Iterator(nil, nil) + // CONTRACT: No writes may happen within a domain while an iterator exists over it. + // Exceptionally allowed for cachekv.Store, safe to write in the modules. + Iterator(start, end []byte) Iterator + + // Iterator over a domain of keys in descending order. End is exclusive. + // Start must be less than end, or the Iterator is invalid. + // Iterator must be closed by caller. + // CONTRACT: No writes may happen within a domain while an iterator exists over it. + // Exceptionally allowed for cachekv.Store, safe to write in the modules. + ReverseIterator(start, end []byte) Iterator +} + +// Alias iterator to db's Iterator for convenience. +type Iterator = dbm.Iterator + +// CacheKVStore cache-wraps a KVStore. After calling .Write() on +// the CacheKVStore, all previously created CacheKVStores on the +// object expire. +type CacheKVStore interface { + KVStore + + // Writes operations to underlying KVStore + Write() +} + +// Stores of MultiStore must implement CommitStore. +type CommitKVStore interface { + Committer + KVStore +} + +//---------------------------------------- +// CacheWrap + +// CacheWrap makes the most appropriate cache-wrap. For example, +// IAVLStore.CacheWrap() returns a CacheKVStore. CacheWrap should not return +// a Committer, since Commit cache-wraps make no sense. It can return KVStore, +// HeapStore, SpaceStore, etc. +type CacheWrap interface { + // Write syncs with the underlying store. + Write() + + // CacheWrap recursively wraps again. + CacheWrap() CacheWrap + + // CacheWrapWithTrace recursively wraps again with tracing enabled. + CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap +} + +type CacheWrapper interface { //nolint + // CacheWrap cache wraps. + CacheWrap() CacheWrap + + // CacheWrapWithTrace cache wraps with tracing enabled. + CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap +} + +//---------------------------------------- +// CommitID + +// CommitID contains the tree version number and its merkle root. +type CommitID struct { + Version int64 + Hash []byte +} + +func (cid CommitID) IsZero() bool { //nolint + return cid.Version == 0 && len(cid.Hash) == 0 +} + +func (cid CommitID) String() string { + return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version) +} + +//---------------------------------------- +// Store types + +// kind of store +type StoreType int + +const ( + //nolint + StoreTypeMulti StoreType = iota + StoreTypeDB + StoreTypeIAVL + StoreTypeTransient +) + +//---------------------------------------- +// Keys for accessing substores + +// StoreKey is a key used to index stores in a MultiStore. +type StoreKey interface { + Name() string + String() string +} + +// KVStoreKey is used for accessing substores. +// Only the pointer value should ever be used - it functions as a capabilities key. +type KVStoreKey struct { + name string +} + +// NewKVStoreKey returns a new pointer to a KVStoreKey. +// Use a pointer so keys don't collide. +func NewKVStoreKey(name string) *KVStoreKey { + return &KVStoreKey{ + name: name, + } +} + +func (key *KVStoreKey) Name() string { + return key.name +} + +func (key *KVStoreKey) String() string { + return fmt.Sprintf("KVStoreKey{%p, %s}", key, key.name) +} + +// TransientStoreKey is used for indexing transient stores in a MultiStore +type TransientStoreKey struct { + name string +} + +// Constructs new TransientStoreKey +// Must return a pointer according to the ocap principle +func NewTransientStoreKey(name string) *TransientStoreKey { + return &TransientStoreKey{ + name: name, + } +} + +// Implements StoreKey +func (key *TransientStoreKey) Name() string { + return key.name +} + +// Implements StoreKey +func (key *TransientStoreKey) String() string { + return fmt.Sprintf("TransientStoreKey{%p, %s}", key, key.name) +} + +//---------------------------------------- + +// key-value result for iterator queries +type KVPair cmn.KVPair + +//---------------------------------------- + +// TraceContext contains TraceKVStore context data. It will be written with +// every trace operation. +type TraceContext map[string]interface{} diff --git a/store/types/utils.go b/store/types/utils.go new file mode 100644 index 000000000000..a86efdb8c82f --- /dev/null +++ b/store/types/utils.go @@ -0,0 +1,98 @@ +package types + +import ( + "bytes" + + cmn "github.com/tendermint/tendermint/libs/common" +) + +// Iterator over all the keys with a certain prefix in ascending order +func KVStorePrefixIterator(kvs KVStore, prefix []byte) Iterator { + return kvs.Iterator(prefix, PrefixEndBytes(prefix)) +} + +// Iterator over all the keys with a certain prefix in descending order. +func KVStoreReversePrefixIterator(kvs KVStore, prefix []byte) Iterator { + return kvs.ReverseIterator(prefix, PrefixEndBytes(prefix)) +} + +// Compare two KVstores, return either the first key/value pair +// at which they differ and whether or not they are equal, skipping +// value comparison for a set of provided prefixes +func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvA cmn.KVPair, kvB cmn.KVPair, count int64, equal bool) { + iterA := a.Iterator(nil, nil) + iterB := b.Iterator(nil, nil) + count = int64(0) + for { + if !iterA.Valid() && !iterB.Valid() { + break + } + var kvA, kvB cmn.KVPair + if iterA.Valid() { + kvA = cmn.KVPair{Key: iterA.Key(), Value: iterA.Value()} + iterA.Next() + } + if iterB.Valid() { + kvB = cmn.KVPair{Key: iterB.Key(), Value: iterB.Value()} + iterB.Next() + } + if !bytes.Equal(kvA.Key, kvB.Key) { + return kvA, kvB, count, false + } + compareValue := true + for _, prefix := range prefixesToSkip { + // Skip value comparison if we matched a prefix + if bytes.Equal(kvA.Key[:len(prefix)], prefix) { + compareValue = false + } + } + if compareValue && !bytes.Equal(kvA.Value, kvB.Value) { + return kvA, kvB, count, false + } + count++ + } + return cmn.KVPair{}, cmn.KVPair{}, count, true +} + +// PrefixEndBytes returns the []byte that would end a +// range query for all []byte with a certain prefix +// Deals with last byte of prefix being FF without overflowing +func PrefixEndBytes(prefix []byte) []byte { + if prefix == nil { + return nil + } + + end := make([]byte, len(prefix)) + copy(end, prefix) + + for { + if end[len(end)-1] != byte(255) { + end[len(end)-1]++ + break + } else { + end = end[:len(end)-1] + if len(end) == 0 { + end = nil + break + } + } + } + return end +} + +// InclusiveEndBytes returns the []byte that would end a +// range query such that the input would be included +func InclusiveEndBytes(inclusiveBytes []byte) (exclusiveBytes []byte) { + exclusiveBytes = append(inclusiveBytes, byte(0x00)) + return exclusiveBytes +} + +//---------------------------------------- +func Cp(bz []byte) (ret []byte) { + if bz == nil { + return nil + } + ret = make([]byte, len(bz)) + copy(ret, bz) + return ret +} diff --git a/store/types/validity.go b/store/types/validity.go new file mode 100644 index 000000000000..59dbd0c032bd --- /dev/null +++ b/store/types/validity.go @@ -0,0 +1,15 @@ +package types + +// Check if the key is valid(key is not nil) +func AssertValidKey(key []byte) { + if key == nil { + panic("key is nil") + } +} + +// Check if the value is valid(value is not nil) +func AssertValidValue(value []byte) { + if value == nil { + panic("value is nil") + } +} diff --git a/types/context.go b/types/context.go index 3b815ee20319..1663a94e65ac 100644 --- a/types/context.go +++ b/types/context.go @@ -10,6 +10,9 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/store/gaskv" + stypes "github.com/cosmos/cosmos-sdk/store/types" ) /* @@ -46,7 +49,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Lo c = c.WithTxBytes(nil) c = c.WithLogger(logger) c = c.WithVoteInfos(nil) - c = c.WithGasMeter(NewInfiniteGasMeter()) + c = c.WithGasMeter(stypes.NewInfiniteGasMeter()) c = c.WithMinGasPrices(DecCoins{}) c = c.WithConsensusParams(nil) return c @@ -74,12 +77,12 @@ 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).Gas(c.GasMeter(), cachedKVGasConfig) + return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), stypes.KVGasConfig()) } // TransientStore fetches a TransientStore from the MultiStore. func (c Context) TransientStore(key StoreKey) KVStore { - return c.MultiStore().GetKVStore(key).Gas(c.GasMeter(), cachedTransientGasConfig) + return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), stypes.TransientGasConfig()) } //---------------------------------------- diff --git a/types/int.go b/types/int.go index dec7e81c8278..5c4ff42450a3 100644 --- a/types/int.go +++ b/types/int.go @@ -2,7 +2,6 @@ package types import ( "encoding/json" - "math" "testing" "math/big" @@ -575,16 +574,6 @@ func UintOverflow(x Uint) bool { return x.i.Sign() == -1 || x.i.Sign() == 1 && x.i.BitLen() > 256 } -// AddUint64Overflow performs the addition operation on two uint64 integers and -// returns a boolean on whether or not the result overflows. -func AddUint64Overflow(a, b uint64) (uint64, bool) { - if math.MaxUint64-a < b { - return 0, true - } - - return a + b, false -} - // intended to be used with require/assert: require.True(IntEq(...)) func IntEq(t *testing.T, exp, got Int) (*testing.T, bool, string, string, string) { return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() diff --git a/types/int_test.go b/types/int_test.go index 18d968e903e8..a6c3c900232c 100644 --- a/types/int_test.go +++ b/types/int_test.go @@ -631,28 +631,3 @@ func TestSafeSub(t *testing.T) { ) } } - -func TestAddUint64Overflow(t *testing.T) { - testCases := []struct { - a, b uint64 - result uint64 - overflow bool - }{ - {0, 0, 0, false}, - {100, 100, 200, false}, - {math.MaxUint64 / 2, math.MaxUint64/2 + 1, math.MaxUint64, false}, - {math.MaxUint64 / 2, math.MaxUint64/2 + 2, 0, true}, - } - - for i, tc := range testCases { - res, overflow := AddUint64Overflow(tc.a, tc.b) - require.Equal( - t, tc.overflow, overflow, - "invalid overflow result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, - ) - require.Equal( - t, tc.result, res, - "invalid uint64 result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, - ) - } -} diff --git a/types/store.go b/types/store.go index 3118fba8f166..1b5b20213230 100644 --- a/types/store.go +++ b/types/store.go @@ -1,398 +1,130 @@ package types import ( - "bytes" - "fmt" - "io" - - abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" -) - -// NOTE: These are implemented in cosmos-sdk/store. - -// PruningStrategy specifies how old states will be deleted over time where -// keepRecent can be used with keepEvery to create a pruning "strategy". -type PruningOptions struct { - keepRecent int64 - keepEvery int64 -} - -func NewPruningOptions(keepRecent, keepEvery int64) PruningOptions { - return PruningOptions{ - keepRecent: keepRecent, - keepEvery: keepEvery, - } -} - -// How much recent state will be kept. Older state will be deleted. -func (po PruningOptions) KeepRecent() int64 { - return po.keepRecent -} - -// Keeps every N stated, deleting others. -func (po PruningOptions) KeepEvery() int64 { - return po.keepEvery -} - -type Store interface { //nolint - GetStoreType() StoreType - CacheWrapper -} - -// something that can persist to disk -type Committer interface { - Commit() CommitID - LastCommitID() CommitID - SetPruning(PruningOptions) -} - -// Stores of MultiStore must implement CommitStore. -type CommitStore interface { - Committer - Store -} - -// Queryable allows a Store to expose internal state to the abci.Query -// interface. Multistore can route requests to the proper Store. -// -// This is an optional, but useful extension to any CommitStore -type Queryable interface { - Query(abci.RequestQuery) abci.ResponseQuery -} - -//---------------------------------------- -// MultiStore -type MultiStore interface { //nolint - Store - - // Cache wrap MultiStore. - // NOTE: Caller should probably not call .Write() on each, but - // call CacheMultiStore.Write(). - CacheMultiStore() CacheMultiStore - - // Convenience for fetching substores. - // If the store does not exist, panics. - GetStore(StoreKey) Store - GetKVStore(StoreKey) KVStore - - // TracingEnabled returns if tracing is enabled for the MultiStore. - TracingEnabled() bool - - // WithTracer sets the tracer for the MultiStore that the underlying - // stores will utilize to trace operations. A MultiStore is returned. - WithTracer(w io.Writer) MultiStore - - // WithTracingContext sets the tracing context for a MultiStore. It is - // implied that the caller should update the context when necessary between - // tracing operations. A MultiStore is returned. - WithTracingContext(TraceContext) MultiStore - - // ResetTraceContext resets the current tracing context. - ResetTraceContext() MultiStore -} - -// From MultiStore.CacheMultiStore().... -type CacheMultiStore interface { - MultiStore - Write() // Writes operations to underlying KVStore -} - -// A non-cache MultiStore. -type CommitMultiStore interface { - Committer - MultiStore - - // Mount a store of type using the given db. - // If db == nil, the new store will use the CommitMultiStore db. - MountStoreWithDB(key StoreKey, typ StoreType, db dbm.DB) - - // Panics on a nil key. - GetCommitStore(key StoreKey) CommitStore - - // Panics on a nil key. - GetCommitKVStore(key StoreKey) CommitKVStore - - // Load the latest persisted version. Called once after all - // calls to Mount*Store() are complete. - LoadLatestVersion() error - - // Load a specific persisted version. When you load an old - // version, or when the last commit attempt didn't complete, - // the next commit after loading must be idempotent (return the - // same commit id). Otherwise the behavior is undefined. - LoadVersion(ver int64) error -} - -//---------subsp------------------------------- -// KVStore - -// KVStore is a simple interface to get/set data -type KVStore interface { - Store - - // Get returns nil iff key doesn't exist. Panics on nil key. - Get(key []byte) []byte - - // Has checks if a key exists. Panics on nil key. - Has(key []byte) bool - - // Set sets the key. Panics on nil key or value. - Set(key, value []byte) - - // Delete deletes the key. Panics on nil key. - Delete(key []byte) - - // Iterator over a domain of keys in ascending order. End is exclusive. - // Start must be less than end, or the Iterator is invalid. - // Iterator must be closed by caller. - // To iterate over entire domain, use store.Iterator(nil, nil) - // CONTRACT: No writes may happen within a domain while an iterator exists over it. - Iterator(start, end []byte) Iterator - - // Iterator over a domain of keys in descending order. End is exclusive. - // Start must be less than end, or the Iterator is invalid. - // Iterator must be closed by caller. - // CONTRACT: No writes may happen within a domain while an iterator exists over it. - ReverseIterator(start, end []byte) Iterator - - // TODO Not yet implemented. - // CreateSubKVStore(key *storeKey) (KVStore, error) - - // TODO Not yet implemented. - // GetSubKVStore(key *storeKey) KVStore - - // Prefix applied keys with the argument - // CONTRACT: when Prefix is called on a KVStore more than once, - // the concatanation of the prefixes is applied - Prefix(prefix []byte) KVStore + "github.com/cosmos/cosmos-sdk/store/types" +) - // Gas consuming store - // CONTRACT: when Gas is called on a KVStore more than once, - // the concatanation of the meters/configs is applied - Gas(GasMeter, GasConfig) KVStore -} +// nolint - reexport +type ( + PruningOptions = types.PruningOptions +) -// Alias iterator to db's Iterator for convenience. -type Iterator = dbm.Iterator +// nolint - reexport +type ( + Store = types.Store + Committer = types.Committer + CommitStore = types.CommitStore + Queryable = types.Queryable + MultiStore = types.MultiStore + CacheMultiStore = types.CacheMultiStore + CommitMultiStore = types.CommitMultiStore + KVStore = types.KVStore + Iterator = types.Iterator +) // Iterator over all the keys with a certain prefix in ascending order func KVStorePrefixIterator(kvs KVStore, prefix []byte) Iterator { - return kvs.Iterator(prefix, PrefixEndBytes(prefix)) + return types.KVStorePrefixIterator(kvs, prefix) } // Iterator over all the keys with a certain prefix in descending order. func KVStoreReversePrefixIterator(kvs KVStore, prefix []byte) Iterator { - return kvs.ReverseIterator(prefix, PrefixEndBytes(prefix)) + return types.KVStoreReversePrefixIterator(kvs, prefix) } // Compare two KVstores, return either the first key/value pair // at which they differ and whether or not they are equal, skipping // value comparison for a set of provided prefixes func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvA cmn.KVPair, kvB cmn.KVPair, count int64, equal bool) { - iterA := a.Iterator(nil, nil) - iterB := b.Iterator(nil, nil) - count = int64(0) - for { - if !iterA.Valid() && !iterB.Valid() { - break - } - var kvA, kvB cmn.KVPair - if iterA.Valid() { - kvA = cmn.KVPair{Key: iterA.Key(), Value: iterA.Value()} - iterA.Next() - } - if iterB.Valid() { - kvB = cmn.KVPair{Key: iterB.Key(), Value: iterB.Value()} - iterB.Next() - } - if !bytes.Equal(kvA.Key, kvB.Key) { - return kvA, kvB, count, false - } - compareValue := true - for _, prefix := range prefixesToSkip { - // Skip value comparison if we matched a prefix - if bytes.Equal(kvA.Key[:len(prefix)], prefix) { - compareValue = false - } - } - if compareValue && !bytes.Equal(kvA.Value, kvB.Value) { - return kvA, kvB, count, false - } - count++ - } - return cmn.KVPair{}, cmn.KVPair{}, count, true -} - -// CacheKVStore cache-wraps a KVStore. After calling .Write() on -// the CacheKVStore, all previously created CacheKVStores on the -// object expire. -type CacheKVStore interface { - KVStore - - // Writes operations to underlying KVStore - Write() + return types.DiffKVStores(a, b, prefixesToSkip) } -// Stores of MultiStore must implement CommitStore. -type CommitKVStore interface { - Committer - KVStore -} - -//---------------------------------------- -// CacheWrap - -// CacheWrap makes the most appropriate cache-wrap. For example, -// IAVLStore.CacheWrap() returns a CacheKVStore. CacheWrap should not return -// a Committer, since Commit cache-wraps make no sense. It can return KVStore, -// HeapStore, SpaceStore, etc. -type CacheWrap interface { - // Write syncs with the underlying store. - Write() - - // CacheWrap recursively wraps again. - CacheWrap() CacheWrap - - // CacheWrapWithTrace recursively wraps again with tracing enabled. - CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap -} - -type CacheWrapper interface { //nolint - // CacheWrap cache wraps. - CacheWrap() CacheWrap - - // CacheWrapWithTrace cache wraps with tracing enabled. - CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap -} - -//---------------------------------------- -// CommitID - -// CommitID contains the tree version number and its merkle root. -type CommitID struct { - Version int64 - Hash []byte -} - -func (cid CommitID) IsZero() bool { //nolint - return cid.Version == 0 && len(cid.Hash) == 0 -} - -func (cid CommitID) String() string { - return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version) -} - -//---------------------------------------- -// Store types +// nolint - reexport +type ( + CacheKVStore = types.CacheKVStore + CommitKVStore = types.CommitKVStore + CacheWrap = types.CacheWrap + CacheWrapper = types.CacheWrapper + CommitID = types.CommitID +) -// kind of store -type StoreType int +// nolint - reexport +type StoreType = types.StoreType +// nolint - reexport const ( - //nolint - StoreTypeMulti StoreType = iota - StoreTypeDB - StoreTypeIAVL - StoreTypeTransient + StoreTypeMulti = types.StoreTypeMulti + StoreTypeDB = types.StoreTypeDB + StoreTypeIAVL = types.StoreTypeIAVL + StoreTypeTransient = types.StoreTypeTransient ) -//---------------------------------------- -// Keys for accessing substores - -// StoreKey is a key used to index stores in a MultiStore. -type StoreKey interface { - Name() string - String() string -} - -// KVStoreKey is used for accessing substores. -// Only the pointer value should ever be used - it functions as a capabilities key. -type KVStoreKey struct { - name string -} +// nolint - reexport +type ( + StoreKey = types.StoreKey + KVStoreKey = types.KVStoreKey + TransientStoreKey = types.TransientStoreKey +) // NewKVStoreKey returns a new pointer to a KVStoreKey. // Use a pointer so keys don't collide. func NewKVStoreKey(name string) *KVStoreKey { - return &KVStoreKey{ - name: name, - } + return types.NewKVStoreKey(name) } -func (key *KVStoreKey) Name() string { - return key.name -} - -func (key *KVStoreKey) String() string { - return fmt.Sprintf("KVStoreKey{%p, %s}", key, key.name) +// Constructs new TransientStoreKey +// Must return a pointer according to the ocap principle +func NewTransientStoreKey(name string) *TransientStoreKey { + return types.NewTransientStoreKey(name) } // PrefixEndBytes returns the []byte that would end a // range query for all []byte with a certain prefix // Deals with last byte of prefix being FF without overflowing func PrefixEndBytes(prefix []byte) []byte { - if prefix == nil { - return nil - } - - end := make([]byte, len(prefix)) - copy(end, prefix) - - for { - if end[len(end)-1] != byte(255) { - end[len(end)-1]++ - break - } else { - end = end[:len(end)-1] - if len(end) == 0 { - end = nil - break - } - } - } - return end + return types.PrefixEndBytes(prefix) } // InclusiveEndBytes returns the []byte that would end a // range query such that the input would be included func InclusiveEndBytes(inclusiveBytes []byte) (exclusiveBytes []byte) { - exclusiveBytes = append(inclusiveBytes, byte(0x00)) - return exclusiveBytes -} - -// TransientStoreKey is used for indexing transient stores in a MultiStore -type TransientStoreKey struct { - name string -} - -// Constructs new TransientStoreKey -// Must return a pointer according to the ocap principle -func NewTransientStoreKey(name string) *TransientStoreKey { - return &TransientStoreKey{ - name: name, - } -} - -// Implements StoreKey -func (key *TransientStoreKey) Name() string { - return key.name -} - -// Implements StoreKey -func (key *TransientStoreKey) String() string { - return fmt.Sprintf("TransientStoreKey{%p, %s}", key, key.name) + return types.InclusiveEndBytes(inclusiveBytes) } //---------------------------------------- // key-value result for iterator queries -type KVPair cmn.KVPair +type KVPair = types.KVPair //---------------------------------------- // TraceContext contains TraceKVStore context data. It will be written with // every trace operation. -type TraceContext map[string]interface{} +type TraceContext = types.TraceContext + +// -------------------------------------- + +// nolint - reexport +type ( + Gas = types.Gas + GasMeter = types.GasMeter + GasConfig = types.GasConfig +) + +// nolint - reexport +func NewGasMeter(limit Gas) GasMeter { + return types.NewGasMeter(limit) +} + +// nolint - reexport +type ( + ErrorOutOfGas = types.ErrorOutOfGas + ErrorGasOverflow = types.ErrorGasOverflow +) + +// nolint - reexport +func NewInfiniteGasMeter() GasMeter { + return types.NewInfiniteGasMeter() +} diff --git a/version/command.go b/version/command.go index 49cf1e021d2c..c3ff71df10e9 100644 --- a/version/command.go +++ b/version/command.go @@ -1,31 +1,48 @@ package version import ( + "encoding/json" "fmt" - "runtime" "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/tendermint/libs/cli" +) + +const ( + flagLong = "long" ) var ( + // VersionCmd prints out the current sdk version VersionCmd = &cobra.Command{ Use: "version", Short: "Print the app version", - Run: printVersion, + RunE: func(_ *cobra.Command, _ []string) error { + verInfo := newVersionInfo() + + if !viper.GetBool(flagLong) { + fmt.Println(verInfo.CosmosSDK) + return nil + } + + if viper.GetString(cli.OutputFlag) != "json" { + fmt.Print(verInfo) + return nil + } + + bz, err := json.Marshal(verInfo) + if err != nil { + return err + } + fmt.Println(string(bz)) + return nil + }, } ) -// return version of CLI/node and commit hash -func GetVersion() string { - return Version -} - -// CMD -func printVersion(cmd *cobra.Command, args []string) { - fmt.Println("cosmos-sdk:", GetVersion()) - fmt.Println("git commit:", Commit) - fmt.Println("vendor hash:", VendorDirHash) - fmt.Printf("go version %s %s/%s\n", - runtime.Version(), runtime.GOOS, runtime.GOARCH) +func init() { + VersionCmd.Flags().Bool(flagLong, false, "Print long version information") } diff --git a/version/version.go b/version/version.go index 031976ecd367..6affcfbd4978 100644 --- a/version/version.go +++ b/version/version.go @@ -1,9 +1,34 @@ //nolint package version +import ( + "fmt" + "runtime" +) + // Variables set by build flags var ( Commit = "" Version = "" VendorDirHash = "" ) + +type versionInfo struct { + CosmosSDK string `json:"cosmos_sdk"` + GitCommit string `json:"commit"` + VendorDirHash string `json:"vendor_hash"` + GoVersion string `json:"go"` +} + +func (v versionInfo) String() string { + return fmt.Sprintf(`cosmos-sdk: %s +git commit: %s +vendor hash: %s +%s`, v.CosmosSDK, v.GitCommit, v.VendorDirHash, v.GoVersion) +} + +func newVersionInfo() versionInfo { + return versionInfo{ + Version, Commit, VendorDirHash, fmt.Sprintf("go version %s %s/%s\n", + runtime.Version(), runtime.GOOS, runtime.GOARCH)} +} diff --git a/x/auth/account.go b/x/auth/account.go index e67e081aea6c..bb4e98684128 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -83,12 +83,20 @@ type BaseAccount struct { // String implements fmt.Stringer func (acc BaseAccount) String() string { - return fmt.Sprintf(`Account %s: + var pubkey string + + if acc.PubKey != nil { + pubkey = sdk.MustBech32ifyAccPub(acc.PubKey) + } + + return fmt.Sprintf(`Account: + Address: %s + Pubkey: %s Coins: %s - PubKey: %s AccountNumber: %d - Sequence: %d`, acc.Address, acc.Coins, - acc.PubKey.Address(), acc.AccountNumber, acc.Sequence) + Sequence: %d`, + acc.Address, pubkey, acc.Coins, acc.AccountNumber, acc.Sequence, + ) } // Prototype function for BaseAccount @@ -183,8 +191,15 @@ type BaseVestingAccount struct { // String implements fmt.Stringer func (bva BaseVestingAccount) String() string { + var pubkey string + + if bva.PubKey != nil { + pubkey = sdk.MustBech32ifyAccPub(bva.PubKey) + } + return fmt.Sprintf(`Vesting Account: - Address: %s + Address: %s + Pubkey: %s Coins: %s AccountNumber: %d Sequence: %d @@ -192,10 +207,8 @@ func (bva BaseVestingAccount) String() string { DelegatedFree: %s DelegatedVesting: %s EndTime: %d `, - bva.Address, bva.Coins, - bva.AccountNumber, bva.Sequence, - bva.OriginalVesting, bva.DelegatedFree, - bva.DelegatedVesting, bva.EndTime, + bva.Address, pubkey, bva.Coins, bva.AccountNumber, bva.Sequence, + bva.OriginalVesting, bva.DelegatedFree, bva.DelegatedVesting, bva.EndTime, ) } @@ -208,27 +221,11 @@ func (bva BaseVestingAccount) spendableCoins(vestingCoins sdk.Coins) sdk.Coins { var spendableCoins sdk.Coins bc := bva.GetCoins() - j, k := 0, 0 for _, coin := range bc { // zip/lineup all coins by their denomination to provide O(n) time - for j < len(vestingCoins) && vestingCoins[j].Denom != coin.Denom { - j++ - } - for k < len(bva.DelegatedVesting) && bva.DelegatedVesting[k].Denom != coin.Denom { - k++ - } - baseAmt := coin.Amount - - vestingAmt := sdk.ZeroInt() - if len(vestingCoins) > 0 { - vestingAmt = vestingCoins[j].Amount - } - - delVestingAmt := sdk.ZeroInt() - if len(bva.DelegatedVesting) > 0 { - delVestingAmt = bva.DelegatedVesting[k].Amount - } + vestingAmt := vestingCoins.AmountOf(coin.Denom) + delVestingAmt := bva.DelegatedVesting.AmountOf(coin.Denom) // compute min((BC + DV) - V, BC) per the specification min := sdk.MinInt(baseAmt.Add(delVestingAmt).Sub(vestingAmt), baseAmt) @@ -251,33 +248,12 @@ func (bva BaseVestingAccount) spendableCoins(vestingCoins sdk.Coins) sdk.Coins { func (bva *BaseVestingAccount) trackDelegation(vestingCoins, amount sdk.Coins) { bc := bva.GetCoins() - i, j, k := 0, 0, 0 for _, coin := range amount { // zip/lineup all coins by their denomination to provide O(n) time - for i < len(bc) && bc[i].Denom != coin.Denom { - i++ - } - for j < len(vestingCoins) && vestingCoins[j].Denom != coin.Denom { - j++ - } - for k < len(bva.DelegatedVesting) && bva.DelegatedVesting[k].Denom != coin.Denom { - k++ - } - - baseAmt := sdk.ZeroInt() - if len(bc) > 0 { - baseAmt = bc[i].Amount - } - vestingAmt := sdk.ZeroInt() - if len(vestingCoins) > 0 { - vestingAmt = vestingCoins[j].Amount - } - - delVestingAmt := sdk.ZeroInt() - if len(bva.DelegatedVesting) > 0 { - delVestingAmt = bva.DelegatedVesting[k].Amount - } + baseAmt := bc.AmountOf(coin.Denom) + vestingAmt := vestingCoins.AmountOf(coin.Denom) + delVestingAmt := bva.DelegatedVesting.AmountOf(coin.Denom) // Panic if the delegation amount is zero or if the base coins does not // exceed the desired delegation amount. @@ -312,21 +288,12 @@ func (bva *BaseVestingAccount) trackDelegation(vestingCoins, amount sdk.Coins) { // // CONTRACT: The account's coins and undelegation coins must be sorted. func (bva *BaseVestingAccount) TrackUndelegation(amount sdk.Coins) { - i := 0 for _, coin := range amount { // panic if the undelegation amount is zero if coin.Amount.IsZero() { panic("undelegation attempt with zero coins") } - - for i < len(bva.DelegatedFree) && bva.DelegatedFree[i].Denom != coin.Denom { - i++ - } - - delegatedFree := sdk.ZeroInt() - if len(bva.DelegatedFree) > 0 { - delegatedFree = bva.DelegatedFree[i].Amount - } + delegatedFree := bva.DelegatedFree.AmountOf(coin.Denom) // compute x and y per the specification, where: // X := min(DF, D) @@ -395,8 +362,15 @@ func NewContinuousVestingAccount( } func (cva ContinuousVestingAccount) String() string { + var pubkey string + + if cva.PubKey != nil { + pubkey = sdk.MustBech32ifyAccPub(cva.PubKey) + } + return fmt.Sprintf(`Continuous Vesting Account: - Address: %s + Address: %s + Pubkey: %s Coins: %s AccountNumber: %d Sequence: %d @@ -405,10 +379,9 @@ func (cva ContinuousVestingAccount) String() string { DelegatedVesting: %s StartTime: %d EndTime: %d `, - cva.Address, cva.Coins, - cva.AccountNumber, cva.Sequence, - cva.OriginalVesting, cva.DelegatedFree, - cva.DelegatedVesting, cva.StartTime, cva.EndTime, + cva.Address, pubkey, cva.Coins, cva.AccountNumber, cva.Sequence, + cva.OriginalVesting, cva.DelegatedFree, cva.DelegatedVesting, + cva.StartTime, cva.EndTime, ) } diff --git a/x/auth/account_test.go b/x/auth/account_test.go index 6a7753a59ba1..7d4fb53461af 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -12,7 +12,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -var testDenom = "testdenom" +var ( + stakeDenom = "stake" + feeDenom = "fee" +) func TestBaseAddressPubKey(t *testing.T) { _, pub1, addr1 := keyPubAddr() @@ -107,7 +110,7 @@ func TestGetVestedCoinsContVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) @@ -122,7 +125,7 @@ func TestGetVestedCoinsContVestingAcc(t *testing.T) { // require 50% of coins vested vestedCoins = cva.GetVestedCoins(now.Add(12 * time.Hour)) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, vestedCoins) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins) // require 100% of coins vested vestedCoins = cva.GetVestedCoins(now.Add(48 * time.Hour)) @@ -134,7 +137,7 @@ func TestGetVestingCoinsContVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) @@ -149,7 +152,7 @@ func TestGetVestingCoinsContVestingAcc(t *testing.T) { // require 50% of coins vesting vestingCoins = cva.GetVestingCoins(now.Add(12 * time.Hour)) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, vestingCoins) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestingCoins) } func TestSpendableCoinsContVestingAcc(t *testing.T) { @@ -157,7 +160,7 @@ func TestSpendableCoinsContVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) @@ -174,15 +177,15 @@ func TestSpendableCoinsContVestingAcc(t *testing.T) { // require that all vested coins (50%) are spendable spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour)) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, spendableCoins) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, spendableCoins) // receive some coins - recvAmt := sdk.Coins{sdk.NewInt64Coin(testDenom, 50)} + recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)} cva.SetCoins(cva.GetCoins().Plus(recvAmt)) // require that all vested coins (50%) are spendable plus any received spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour)) - require.Equal(t, origCoins, spendableCoins) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 100)}, spendableCoins) // spend all spendable coins cva.SetCoins(cva.GetCoins().Minus(spendableCoins)) @@ -197,7 +200,7 @@ func TestTrackDelegationContVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) @@ -219,20 +222,20 @@ func TestTrackDelegationContVestingAcc(t *testing.T) { // require the ability to delegate all vesting coins (50%) and all vested coins (50%) bacc.SetCoins(origCoins) cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) - cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting) + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, cva.DelegatedVesting) require.Nil(t, cva.DelegatedFree) - cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedFree) - require.Nil(t, cva.GetCoins()) + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, cva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, cva.DelegatedFree) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000)}, cva.GetCoins()) // require no modifications when delegation amount is zero or not enough funds bacc.SetCoins(origCoins) cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) require.Panics(t, func() { - cva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)}) + cva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 1000000)}) }) require.Nil(t, cva.DelegatedVesting) require.Nil(t, cva.DelegatedFree) @@ -244,7 +247,7 @@ func TestTrackUndelegationContVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) @@ -271,7 +274,7 @@ func TestTrackUndelegationContVestingAcc(t *testing.T) { cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) require.Panics(t, func() { - cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)}) + cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 0)}) }) require.Nil(t, cva.DelegatedFree) require.Nil(t, cva.DelegatedVesting) @@ -279,20 +282,20 @@ func TestTrackUndelegationContVestingAcc(t *testing.T) { // vest 50% and delegate to two validators cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) - cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) - cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) + cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) // undelegate from one validator that got slashed 50% - cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.DelegatedFree) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.GetCoins()) + cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}, cva.DelegatedFree) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, cva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 25)}, cva.GetCoins()) // undelegate from the other validator that did not get slashed - cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) require.Nil(t, cva.DelegatedFree) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.DelegatedVesting) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, cva.GetCoins()) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}, cva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 75)}, cva.GetCoins()) } func TestGetVestedCoinsDelVestingAcc(t *testing.T) { @@ -300,7 +303,7 @@ func TestGetVestedCoinsDelVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) @@ -319,7 +322,7 @@ func TestGetVestingCoinsDelVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) @@ -338,7 +341,7 @@ func TestSpendableCoinsDelVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) @@ -358,7 +361,7 @@ func TestSpendableCoinsDelVestingAcc(t *testing.T) { require.Nil(t, spendableCoins) // receive some coins - recvAmt := sdk.Coins{sdk.NewInt64Coin(testDenom, 50)} + recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)} dva.SetCoins(dva.GetCoins().Plus(recvAmt)) // require that only received coins are spendable since the account is still @@ -379,7 +382,7 @@ func TestTrackDelegationDelVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) @@ -413,7 +416,7 @@ func TestTrackDelegationDelVestingAcc(t *testing.T) { dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) require.Panics(t, func() { - dva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)}) + dva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 1000000)}) }) require.Nil(t, dva.DelegatedVesting) require.Nil(t, dva.DelegatedFree) @@ -425,7 +428,7 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { endTime := now.Add(24 * time.Hour) _, _, addr := keyPubAddr() - origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := NewBaseAccountWithAddress(addr) bacc.SetCoins(origCoins) @@ -452,7 +455,7 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) require.Panics(t, func() { - dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)}) + dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 0)}) }) require.Nil(t, dva.DelegatedFree) require.Nil(t, dva.DelegatedVesting) @@ -461,19 +464,19 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { // vest 50% and delegate to two validators bacc.SetCoins(origCoins) dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) - dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) - dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) + dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) // undelegate from one validator that got slashed 50% - dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}) + dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}) require.Nil(t, dva.DelegatedFree) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, dva.DelegatedVesting) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, dva.GetCoins()) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 75)}, dva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 25)}, dva.GetCoins()) // undelegate from the other validator that did not get slashed - dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) + dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}) require.Nil(t, dva.DelegatedFree) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, dva.DelegatedVesting) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, dva.GetCoins()) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}, dva.DelegatedVesting) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 75)}, dva.GetCoins()) } diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index e60e0fdc1d85..50ae9e23418d 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/client/rest" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -39,13 +39,13 @@ func QueryAccountRequestHandlerFn( addr, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -58,11 +58,11 @@ func QueryAccountRequestHandlerFn( // decode the value account, err := decoder(res) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, account, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, account, cliCtx.Indent) } } @@ -78,13 +78,13 @@ func QueryBalancesRequestHandlerFn( addr, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -97,10 +97,10 @@ func QueryBalancesRequestHandlerFn( // decode the value account, err := decoder(res) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, account.GetCoins(), cliCtx.Indent) + rest.PostProcessResponse(w, cdc, account.GetCoins(), cliCtx.Indent) } } diff --git a/x/auth/client/rest/sign.go b/x/auth/client/rest/sign.go index 7f7787ea4298..50ec660552fa 100644 --- a/x/auth/client/rest/sign.go +++ b/x/auth/client/rest/sign.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/rest" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" @@ -14,9 +15,9 @@ import ( // SignBody defines the properties of a sign request's body. type SignBody struct { - Tx auth.StdTx `json:"tx"` - AppendSig bool `json:"append_sig"` - BaseReq utils.BaseReq `json:"base_req"` + Tx auth.StdTx `json:"tx"` + AppendSig bool `json:"append_sig"` + BaseReq rest.BaseReq `json:"base_req"` } // nolint: unparam @@ -25,8 +26,8 @@ func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha return func(w http.ResponseWriter, r *http.Request) { var m SignBody - if err := utils.ReadRESTReq(w, r, cdc, &m); err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + if err := rest.ReadRESTReq(w, r, cdc, &m); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -37,10 +38,19 @@ func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha // validate tx // discard error if it's CodeNoSignatures as the tx comes with no signatures if err := m.Tx.ValidateBasic(); err != nil && err.Code() != sdk.CodeNoSignatures { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } + // derive the from account address and name from the Keybase + fromAddress, fromName, err := context.GetFromFields(m.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) + txBldr := authtxb.NewTxBuilder( utils.GetTxEncoder(cdc), m.BaseReq.AccountNumber, @@ -54,18 +64,18 @@ func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha nil, ) - signedTx, err := txBldr.SignStdTx(m.BaseReq.Name, m.BaseReq.Password, m.Tx, m.AppendSig) + signedTx, err := txBldr.SignStdTx(cliCtx.GetFromName(), m.BaseReq.Password, m.Tx, m.AppendSig) if keyerror.IsErrKeyNotFound(err) { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } else if keyerror.IsErrWrongPassword(err) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + rest.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } else if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, signedTx, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, signedTx, cliCtx.Indent) } } diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index ed5f4339d360..331d811ba959 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -156,6 +156,8 @@ func (bldr TxBuilder) WithAccountNumber(accnum uint64) TxBuilder { // Build builds a single message to be signed from a TxBuilder given a set of // messages. It returns an error if a fee is supplied but cannot be parsed. +// +// TODO: Should consider renaming to BuildSignMsg. func (bldr TxBuilder) Build(msgs []sdk.Msg) (StdSignMsg, error) { chainID := bldr.chainID if chainID == "" { @@ -212,31 +214,17 @@ func (bldr TxBuilder) BuildAndSign(name, passphrase string, msgs []sdk.Msg) ([]b return bldr.Sign(name, passphrase, msg) } -// BuildWithPubKey builds a single message to be signed from a TxBuilder given a set of -// messages and attach the public key associated to the given name. -// It returns an error if a fee is supplied but cannot be parsed or the key cannot be -// retrieved. -func (bldr TxBuilder) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, error) { - msg, err := bldr.Build(msgs) - if err != nil { - return nil, err - } - - keybase, err := keys.GetKeyBase() +// BuildTxForSim creates a StdSignMsg and encodes a transaction with the +// StdSignMsg with a single empty StdSignature for tx simulation. +func (bldr TxBuilder) BuildTxForSim(msgs []sdk.Msg) ([]byte, error) { + signMsg, err := bldr.Build(msgs) if err != nil { return nil, err } - info, err := keybase.Get(name) - if err != nil { - return nil, err - } - - sigs := []auth.StdSignature{{ - PubKey: info.GetPubKey(), - }} - - return bldr.txEncoder(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) + // the ante handler will populate with a sentinel pubkey + sigs := []auth.StdSignature{{}} + return bldr.txEncoder(auth.NewStdTx(signMsg.Msgs, signMsg.Fee, sigs, signMsg.Memo)) } // SignStdTx appends a signature to a StdTx and returns a copy of a it. If append diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 36e084b73a86..a6e9d6dfefea 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -91,6 +91,17 @@ func getMockApp(t *testing.T) *mock.App { return mapp } +// overwrite the mock init chainer +func getInitChainer(mapp *mock.App, keeper BaseKeeper) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + mapp.InitChainer(ctx, req) + bankGenesis := DefaultGenesisState() + InitGenesis(ctx, keeper, bankGenesis) + + return abci.ResponseInitChain{} + } +} + func TestMsgSendWithAccounts(t *testing.T) { mapp := getMockApp(t) acc := &auth.BaseAccount{ diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index a3f69dcdd4d8..5eb70271315e 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -16,8 +16,13 @@ func getBenchmarkMockApp() (*mock.App, error) { mapp := mock.NewApp() RegisterCodec(mapp.Cdc) - bankKeeper := NewBaseKeeper(mapp.AccountKeeper) + bankKeeper := NewBaseKeeper( + mapp.AccountKeeper, + mapp.ParamsKeeper.Subspace(DefaultParamspace), + DefaultCodespace, + ) mapp.Router().AddRoute("bank", NewHandler(bankKeeper)) + mapp.SetInitChainer(getInitChainer(mapp, bankKeeper)) err := mapp.CompleteSetup() return mapp, err diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 161261276ad8..9da232789a50 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -50,11 +50,7 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { return err } - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + from := cliCtx.GetFromAddress() account, err := cliCtx.GetAccount(from) if err != nil { return err @@ -71,7 +67,7 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/bank/client/rest/broadcast.go b/x/bank/client/rest/broadcast.go index 71696f12b3c3..1d40c2820e23 100644 --- a/x/bank/client/rest/broadcast.go +++ b/x/bank/client/rest/broadcast.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/client/rest" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" ) @@ -24,29 +24,29 @@ func BroadcastTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) ht txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(m.Tx) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } res, err := cliCtx.BroadcastTx(txBytes) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } func unmarshalBodyOrReturnBadRequest(cliCtx context.CLIContext, w http.ResponseWriter, r *http.Request, m *broadcastBody) bool { body, err := ioutil.ReadAll(r.Body) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return false } err = cliCtx.Codec.UnmarshalJSON(body, m) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return false } return true diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index d73d7e61e3cb..70aae05fccc0 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -3,13 +3,14 @@ package rest import ( "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/bank/client" + bankclient "github.com/cosmos/cosmos-sdk/x/bank/client" "github.com/gorilla/mux" ) @@ -21,8 +22,8 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, } type sendReq struct { - BaseReq utils.BaseReq `json:"base_req"` - Amount sdk.Coins `json:"amount"` + BaseReq rest.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } var msgCdc = codec.New() @@ -37,14 +38,14 @@ func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIC vars := mux.Vars(r) bech32Addr := vars["address"] - to, err := sdk.AccAddressFromBech32(bech32Addr) + toAddr, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } var req sendReq - err = utils.ReadRESTReq(w, r, cdc, &req) + err = rest.ReadRESTReq(w, r, cdc, &req) if err != nil { return } @@ -54,13 +55,30 @@ func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIC return } - info, err := kb.Get(req.BaseReq.Name) + if req.BaseReq.GenerateOnly { + // When generate only is supplied, the from field must be a valid Bech32 + // address. + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + msg := bankclient.CreateMsg(fromAddr, toAddr, req.Amount) + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) + return + } + + // derive the from account address and name from the Keybase + fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - msg := client.CreateMsg(sdk.AccAddress(info.GetPubKey().Address()), to, req.Amount) - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) + msg := bankclient.CreateMsg(cliCtx.GetFromAddress(), toAddr, req.Amount) + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/bank/errors.go b/x/bank/errors.go index 5df5cfdce501..fd4211899521 100644 --- a/x/bank/errors.go +++ b/x/bank/errors.go @@ -11,6 +11,7 @@ const ( CodeInvalidInput sdk.CodeType = 101 CodeInvalidOutput sdk.CodeType = 102 + CodeSendDisabled sdk.CodeType = 103 ) // NOTE: Don't stringer this, we'll put better messages in later. @@ -44,6 +45,10 @@ func ErrNoOutputs(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidOutput, "") } +func ErrSendDisabled(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeSendDisabled, "") +} + //---------------------------------------- func msgOrDefaultMsg(msg string, code sdk.CodeType) string { diff --git a/x/bank/genesis.go b/x/bank/genesis.go new file mode 100644 index 000000000000..b85ba21937c3 --- /dev/null +++ b/x/bank/genesis.go @@ -0,0 +1,32 @@ +package bank + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState is the bank state that must be provided at genesis. +type GenesisState struct { + SendEnabled bool `json:"send_enabled"` +} + +// NewGenesisState creates a new genesis state. +func NewGenesisState(sendEnabled bool) GenesisState { + return GenesisState{SendEnabled: sendEnabled} +} + +// Return a default genesis state +func DefaultGenesisState() GenesisState { return NewGenesisState(true) } + +// InitGenesis sets distribution information for genesis. +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + keeper.SetSendEnabled(ctx, data.SendEnabled) +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + return NewGenesisState(keeper.GetSendEnabled(ctx)) +} + +// ValidateGenesis performs basic validation of bank genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { return nil } diff --git a/x/bank/handler.go b/x/bank/handler.go index 9b4095700050..2d1abf822697 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -20,6 +20,9 @@ func NewHandler(k Keeper) sdk.Handler { // Handle MsgSend. func handleMsgSend(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result { // NOTE: totalIn == totalOut should already have been checked + if !k.GetSendEnabled(ctx) { + return ErrSendDisabled(k.Codespace()).Result() + } tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) if err != nil { return err.Result() diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 499b43e59499..3055d8df4c90 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/params" ) //----------------------------------------------------------------------------- @@ -32,14 +33,20 @@ type Keeper interface { type BaseKeeper struct { BaseSendKeeper - ak auth.AccountKeeper + ak auth.AccountKeeper + paramSpace params.Subspace } // NewBaseKeeper returns a new BaseKeeper -func NewBaseKeeper(ak auth.AccountKeeper) BaseKeeper { +func NewBaseKeeper(ak auth.AccountKeeper, + paramSpace params.Subspace, + codespace sdk.CodespaceType) BaseKeeper { + + ps := paramSpace.WithTypeTable(ParamTypeTable()) return BaseKeeper{ - BaseSendKeeper: NewBaseSendKeeper(ak), + BaseSendKeeper: NewBaseSendKeeper(ak, ps, codespace), ak: ak, + paramSpace: ps, } } @@ -95,6 +102,9 @@ type SendKeeper interface { ViewKeeper SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) + + GetSendEnabled(ctx sdk.Context) bool + SetSendEnabled(ctx sdk.Context, enabled bool) } var _ SendKeeper = (*BaseSendKeeper)(nil) @@ -104,14 +114,18 @@ var _ SendKeeper = (*BaseSendKeeper)(nil) type BaseSendKeeper struct { BaseViewKeeper - ak auth.AccountKeeper + ak auth.AccountKeeper + paramSpace params.Subspace } // NewBaseSendKeeper returns a new BaseSendKeeper. -func NewBaseSendKeeper(ak auth.AccountKeeper) BaseSendKeeper { +func NewBaseSendKeeper(ak auth.AccountKeeper, + paramSpace params.Subspace, codespace sdk.CodespaceType) BaseSendKeeper { + return BaseSendKeeper{ - BaseViewKeeper: NewBaseViewKeeper(ak), + BaseViewKeeper: NewBaseViewKeeper(ak, codespace), ak: ak, + paramSpace: paramSpace, } } @@ -119,10 +133,22 @@ func NewBaseSendKeeper(ak auth.AccountKeeper) BaseSendKeeper { func (keeper BaseSendKeeper) SendCoins( ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins, ) (sdk.Tags, sdk.Error) { - return sendCoins(ctx, keeper.ak, fromAddr, toAddr, amt) } +// GetSendEnabled returns the current SendEnabled +// nolint: errcheck +func (keeper BaseSendKeeper) GetSendEnabled(ctx sdk.Context) bool { + var enabled bool + keeper.paramSpace.Get(ctx, ParamStoreKeySendEnabled, &enabled) + return enabled +} + +// nolint: errcheck +func (keeper BaseSendKeeper) SetSendEnabled(ctx sdk.Context, enabled bool) { + keeper.paramSpace.Set(ctx, ParamStoreKeySendEnabled, &enabled) +} + //----------------------------------------------------------------------------- // View Keeper @@ -133,18 +159,19 @@ var _ ViewKeeper = (*BaseViewKeeper)(nil) type ViewKeeper interface { GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool + + Codespace() sdk.CodespaceType } // BaseViewKeeper implements a read only keeper implementation of ViewKeeper. type BaseViewKeeper struct { - ak auth.AccountKeeper + ak auth.AccountKeeper + codespace sdk.CodespaceType } // NewBaseViewKeeper returns a new BaseViewKeeper. -func NewBaseViewKeeper(ak auth.AccountKeeper) BaseViewKeeper { - return BaseViewKeeper{ - ak: ak, - } +func NewBaseViewKeeper(ak auth.AccountKeeper, codespace sdk.CodespaceType) BaseViewKeeper { + return BaseViewKeeper{ak: ak, codespace: codespace} } // GetCoins returns the coins at the addr. @@ -157,6 +184,11 @@ func (keeper BaseViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt return hasCoins(ctx, keeper.ak, addr, amt) } +// Codespace returns the keeper's codespace. +func (keeper BaseViewKeeper) Codespace() sdk.CodespaceType { + return keeper.codespace +} + //----------------------------------------------------------------------------- // Auxiliary diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 8a3d4dc83882..23e6321abcdc 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -21,6 +21,7 @@ type testInput struct { cdc *codec.Codec ctx sdk.Context ak auth.AccountKeeper + pk params.Keeper } func setupTestInput() testInput { @@ -49,13 +50,14 @@ func setupTestInput() testInput { ak.SetParams(ctx, auth.DefaultParams()) - return testInput{cdc: cdc, ctx: ctx, ak: ak} + return testInput{cdc: cdc, ctx: ctx, ak: ak, pk: pk} } func TestKeeper(t *testing.T) { input := setupTestInput() ctx := input.ctx - bankKeeper := NewBaseKeeper(input.ak) + bankKeeper := NewBaseKeeper(input.ak, input.pk.Subspace(DefaultParamspace), DefaultCodespace) + bankKeeper.SetSendEnabled(ctx, true) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -134,8 +136,10 @@ func TestKeeper(t *testing.T) { func TestSendKeeper(t *testing.T) { input := setupTestInput() ctx := input.ctx - bankKeeper := NewBaseKeeper(input.ak) - sendKeeper := NewBaseSendKeeper(input.ak) + paramSpace := input.pk.Subspace(DefaultParamspace) + bankKeeper := NewBaseKeeper(input.ak, paramSpace, DefaultCodespace) + sendKeeper := NewBaseSendKeeper(input.ak, paramSpace, DefaultCodespace) + bankKeeper.SetSendEnabled(ctx, true) addr := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -181,8 +185,10 @@ func TestSendKeeper(t *testing.T) { func TestViewKeeper(t *testing.T) { input := setupTestInput() ctx := input.ctx - bankKeeper := NewBaseKeeper(input.ak) - viewKeeper := NewBaseViewKeeper(input.ak) + paramSpace := input.pk.Subspace(DefaultParamspace) + bankKeeper := NewBaseKeeper(input.ak, paramSpace, DefaultCodespace) + bankKeeper.SetSendEnabled(ctx, true) + viewKeeper := NewBaseViewKeeper(input.ak, DefaultCodespace) addr := sdk.AccAddress([]byte("addr1")) acc := input.ak.NewAccountWithAddress(ctx, addr) @@ -209,7 +215,8 @@ func TestVestingAccountSend(t *testing.T) { origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} sendCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} - bankKeeper := NewBaseKeeper(input.ak) + bankKeeper := NewBaseKeeper(input.ak, input.pk.Subspace(DefaultParamspace), DefaultCodespace) + bankKeeper.SetSendEnabled(ctx, true) addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -242,7 +249,8 @@ func TestVestingAccountReceive(t *testing.T) { origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} sendCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} - bankKeeper := NewBaseKeeper(input.ak) + bankKeeper := NewBaseKeeper(input.ak, input.pk.Subspace(DefaultParamspace), DefaultCodespace) + bankKeeper.SetSendEnabled(ctx, true) addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -275,7 +283,8 @@ func TestDelegateCoins(t *testing.T) { origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} delCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} - bankKeeper := NewBaseKeeper(input.ak) + bankKeeper := NewBaseKeeper(input.ak, input.pk.Subspace(DefaultParamspace), DefaultCodespace) + bankKeeper.SetSendEnabled(ctx, true) addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) @@ -311,7 +320,8 @@ func TestUndelegateCoins(t *testing.T) { origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)} delCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)} - bankKeeper := NewBaseKeeper(input.ak) + bankKeeper := NewBaseKeeper(input.ak, input.pk.Subspace(DefaultParamspace), DefaultCodespace) + bankKeeper.SetSendEnabled(ctx, true) addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) diff --git a/x/bank/params.go b/x/bank/params.go new file mode 100644 index 000000000000..9aea6adaebe8 --- /dev/null +++ b/x/bank/params.go @@ -0,0 +1,22 @@ +package bank + +import ( + "github.com/cosmos/cosmos-sdk/x/params" +) + +const ( + // default paramspace for params keeper + DefaultParamspace = "bank" + // default send enabled + DefaultSendEnabled = true +) + +// ParamStoreKeySendEnabled is store's key for SendEnabled +var ParamStoreKeySendEnabled = []byte("sendenabled") + +// type declaration for parameters +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeySendEnabled, false, + ) +} diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index e7d16496b72e..c4d4919eb270 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -112,6 +112,9 @@ func sendAndVerifyMsgSend(app *baseapp.BaseApp, mapper auth.AccountKeeper, msg b if handler != nil { res := handler(ctx, msg) if !res.IsOK() { + if res.Code == bank.CodeSendDisabled { + return nil + } // TODO: Do this in a more 'canonical' way return fmt.Errorf("handling msg failed %v", res) } diff --git a/x/distribution/client/cli/query.go b/x/distribution/client/cli/query.go index 09d4b458ac2d..4f4eb93908df 100644 --- a/x/distribution/client/cli/query.go +++ b/x/distribution/client/cli/query.go @@ -143,7 +143,7 @@ func GetCmdQueryValidatorSlashes(queryRoute string, cdc *codec.Codec) *cobra.Com return err } - var slashes types.ValidatorSlashEvent + var slashes types.ValidatorSlashEvents cdc.MustUnmarshalJSON(res, &slashes) return cliCtx.PrintOutput(slashes) }, @@ -154,8 +154,8 @@ func GetCmdQueryValidatorSlashes(queryRoute string, cdc *codec.Codec) *cobra.Com func GetCmdQueryDelegatorRewards(queryRoute string, cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "rewards [delegator] [validator]", - Args: cobra.ExactArgs(2), - Short: "Query distribution delegator rewards", + Args: cobra.RangeArgs(1, 2), + Short: "Query all distribution delegator rewards or rewards from a particular validator", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) @@ -164,26 +164,39 @@ func GetCmdQueryDelegatorRewards(queryRoute string, cdc *codec.Codec) *cobra.Com return err } - validatorAddr, err := sdk.ValAddressFromBech32(args[1]) - if err != nil { - return err + var ( + route string + params distr.QueryDelegationRewardsParams + result sdk.DecCoins + ) + + if len(args) == 1 { + // query for all rewards + params = distr.NewQueryDelegationRewardsParams(delegatorAddr, nil) + route = fmt.Sprintf("custom/%s/all_delegation_rewards", queryRoute) + } else { + // query for rewards from a particular validator + validatorAddr, err := sdk.ValAddressFromBech32(args[1]) + if err != nil { + return err + } + + params = distr.NewQueryDelegationRewardsParams(delegatorAddr, validatorAddr) + route = fmt.Sprintf("custom/%s/delegation_rewards", queryRoute) } - params := distr.NewQueryDelegationRewardsParams(delegatorAddr, validatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { return err } - route := fmt.Sprintf("custom/%s/delegation_rewards", queryRoute) - res, err := cliCtx.QueryWithData(route, bz) + resp, err := cliCtx.QueryWithData(route, bz) if err != nil { return err } - var coins sdk.DecCoins - cdc.MustUnmarshalJSON(res, &coins) - return cliCtx.PrintOutput(coins) + cdc.MustUnmarshalJSON(resp, &result) + return cliCtx.PrintOutput(result) }, } } diff --git a/x/distribution/client/cli/tx.go b/x/distribution/client/cli/tx.go index 67113c2b92b3..9a761d8de6c1 100644 --- a/x/distribution/client/cli/tx.go +++ b/x/distribution/client/cli/tx.go @@ -63,18 +63,11 @@ func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { var msg sdk.Msg switch { case isVal: - addr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } + addr := cliCtx.GetFromAddress() valAddr := sdk.ValAddress(addr.Bytes()) msg = types.NewMsgWithdrawValidatorCommission(valAddr) default: - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + delAddr := cliCtx.GetFromAddress() valAddr, err := sdk.ValAddressFromBech32(onlyFromVal) if err != nil { return err @@ -88,7 +81,7 @@ func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().String(flagOnlyFromValidator, "", "only withdraw from this validator address (in bech)") @@ -109,11 +102,7 @@ func GetCmdSetWithdrawAddr(cdc *codec.Codec) *cobra.Command { WithCodec(cdc). WithAccountDecoder(cdc) - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + delAddr := cliCtx.GetFromAddress() withdrawAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err @@ -122,7 +111,7 @@ func GetCmdSetWithdrawAddr(cdc *codec.Codec) *cobra.Command { msg := types.NewMsgSetWithdrawAddress(delAddr, withdrawAddr) // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } return cmd diff --git a/x/distribution/keeper/querier.go b/x/distribution/keeper/querier.go index e1323aa34318..619f2c39eae0 100644 --- a/x/distribution/keeper/querier.go +++ b/x/distribution/keeper/querier.go @@ -12,11 +12,12 @@ import ( // nolint const ( - QueryParams = "params" - QueryOutstandingRewards = "outstanding_rewards" - QueryValidatorCommission = "validator_commission" - QueryValidatorSlashes = "validator_slashes" - QueryDelegationRewards = "delegation_rewards" + QueryParams = "params" + QueryOutstandingRewards = "outstanding_rewards" + QueryValidatorCommission = "validator_commission" + QueryValidatorSlashes = "validator_slashes" + QueryDelegationRewards = "delegation_rewards" + QueryAllDelegationRewards = "all_delegation_rewards" ParamCommunityTax = "community_tax" ParamBaseProposerReward = "base_proposer_reward" @@ -29,14 +30,22 @@ func NewQuerier(k Keeper) sdk.Querier { switch path[0] { case QueryParams: return queryParams(ctx, path[1:], req, k) + case QueryOutstandingRewards: return queryOutstandingRewards(ctx, path[1:], req, k) + case QueryValidatorCommission: return queryValidatorCommission(ctx, path[1:], req, k) + case QueryValidatorSlashes: return queryValidatorSlashes(ctx, path[1:], req, k) + case QueryDelegationRewards: return queryDelegationRewards(ctx, path[1:], req, k) + + case QueryAllDelegationRewards: + return queryAllDelegationRewards(ctx, path[1:], req, k) + default: return nil, sdk.ErrUnknownRequest("unknown distr query endpoint") } @@ -158,20 +167,57 @@ func NewQueryDelegationRewardsParams(delegatorAddr sdk.AccAddress, validatorAddr } } -func queryDelegationRewards(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { +func queryDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { var params QueryDelegationRewardsParams err := k.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } + + // cache-wrap context as to not persist state changes during querying ctx, _ = ctx.CacheContext() + val := k.stakingKeeper.Validator(ctx, params.ValidatorAddr) del := k.stakingKeeper.Delegation(ctx, params.DelegatorAddr, params.ValidatorAddr) endingPeriod := k.incrementValidatorPeriod(ctx, val) rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + bz, err := codec.MarshalJSONIndent(k.cdc, rewards) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } + + return bz, nil +} + +func queryAllDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryDelegationRewardsParams + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + + // cache-wrap context as to not persist state changes during querying + ctx, _ = ctx.CacheContext() + + var totalRewards sdk.DecCoins + + k.stakingKeeper.IterateDelegations( + ctx, params.DelegatorAddr, + func(_ int64, del sdk.Delegation) (stop bool) { + val := k.stakingKeeper.Validator(ctx, del.GetValidatorAddr()) + endingPeriod := k.incrementValidatorPeriod(ctx, val) + rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) + + totalRewards = totalRewards.Plus(rewards) + return false + }, + ) + + bz, err := codec.MarshalJSONIndent(k.cdc, totalRewards) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil } diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index 420f40ac4943..8e8b157c4106 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -110,7 +110,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) - ck := bank.NewBaseKeeper(accountKeeper) + ck := bank.NewBaseKeeper(accountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) sk.SetPool(ctx, staking.InitialPool()) sk.SetParams(ctx, staking.DefaultParams()) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 89deae745672..69e82abf6b11 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -86,10 +86,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome WithAccountDecoder(cdc) // Get from address - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } + from := cliCtx.GetFromAddress() // Pull associated account account, err := cliCtx.GetAccount(from) @@ -126,7 +123,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome // Build and sign the transaction, then broadcast to Tendermint // proposalID must be returned, and it is a part of response. cliCtx.PrintResponse = true - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -199,11 +196,7 @@ $ gaiacli tx gov deposit 1 10stake --from mykey return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) } - // Get from address - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } + from := cliCtx.GetFromAddress() // Fetch associated account account, err := cliCtx.GetAccount(from) @@ -233,7 +226,7 @@ $ gaiacli tx gov deposit 1 10stake --from mykey } // Build and sign the transaction, then broadcast to a Tendermint node. - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -258,10 +251,7 @@ $ gaiacli tx gov vote 1 yes --from mykey WithAccountDecoder(cdc) // Get voting address - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } + from := cliCtx.GetFromAddress() // validate that the proposal id is a uint proposalID, err := strconv.ParseUint(args[0], 10, 64) @@ -294,7 +284,7 @@ $ gaiacli tx gov vote 1 yes --from mykey } // Build and sign the transaction, then broadcast to a Tendermint node. - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 96142941ef07..e37f7870ecca 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -4,8 +4,9 @@ import ( "fmt" "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" @@ -53,7 +54,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) } type postProposalReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} @@ -62,13 +63,13 @@ type postProposalReq struct { } type depositReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } type voteReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` Voter sdk.AccAddress `json:"voter"` // address of the voter Option string `json:"option"` // option from OptionSet chosen by the voter } @@ -76,9 +77,9 @@ type voteReq struct { func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req postProposalReq - err := utils.ReadRESTReq(w, r, cdc, &req) + err := rest.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -89,7 +90,7 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han proposalType, err := gov.ProposalTypeFromString(govClientUtils.NormalizeProposalType(req.ProposalType)) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -97,11 +98,16 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalType, req.Proposer, req.InitialDeposit) err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -112,17 +118,17 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } var req depositReq - err := utils.ReadRESTReq(w, r, cdc, &req) + err := rest.ReadRESTReq(w, r, cdc, &req) if err != nil { return } @@ -136,11 +142,16 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF msg := gov.NewMsgDeposit(req.Depositor, proposalID, req.Amount) err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -151,17 +162,17 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } var req voteReq - err := utils.ReadRESTReq(w, r, cdc, &req) + err := rest.ReadRESTReq(w, r, cdc, &req) if err != nil { return } @@ -173,7 +184,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc voteOption, err := gov.VoteOptionFromString(govClientUtils.NormalizeVoteOption(req.Option)) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -181,11 +192,16 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc msg := gov.NewMsgVote(req.Voter, proposalID, voteOption) err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -196,11 +212,11 @@ func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Hand res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", gov.QueryParams, paramType), nil) if err != nil { - utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -211,11 +227,11 @@ func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -224,17 +240,17 @@ func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -243,7 +259,7 @@ func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha vars := mux.Vars(r) strProposalID := vars[RestProposalID] - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -252,19 +268,19 @@ func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } var proposal gov.Proposal if err := cdc.UnmarshalJSON(res, &proposal); err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -278,11 +294,11 @@ func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha } if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -291,18 +307,18 @@ func queryProposerHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha vars := mux.Vars(r) strProposalID := vars[RestProposalID] - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } res, err := gcutils.QueryProposerByTxQuery(cdc, cliCtx, proposalID) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -314,24 +330,24 @@ func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } if len(bechDepositorAddr) == 0 { err := errors.New("depositor address required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -339,13 +355,13 @@ func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/deposit", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -358,25 +374,25 @@ func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han if deposit.Empty() { bz, err := cdc.MarshalJSON(gov.NewQueryProposalParams(proposalID)) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err = cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil || len(res) == 0 { err := fmt.Errorf("proposalID %d does not exist", proposalID) - utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } res, err = gcutils.QueryDepositByTxQuery(cdc, cliCtx, params) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -388,24 +404,24 @@ func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Handle if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } if len(bechVoterAddr) == 0 { err := errors.New("voter address required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -413,13 +429,13 @@ func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Handle bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/vote", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -432,25 +448,25 @@ func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Handle if vote.Empty() { bz, err := cdc.MarshalJSON(gov.NewQueryProposalParams(proposalID)) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err = cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil || len(res) == 0 { err := fmt.Errorf("proposalID %d does not exist", proposalID) - utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } res, err = gcutils.QueryVoteByTxQuery(cdc, cliCtx, params) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -462,11 +478,11 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -475,19 +491,19 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } var proposal gov.Proposal if err := cdc.UnmarshalJSON(res, &proposal); err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -501,11 +517,11 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) } if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -522,7 +538,7 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(bechVoterAddr) != 0 { voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.Voter = voterAddr @@ -531,7 +547,7 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(bechDepositorAddr) != 0 { depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.Depositor = depositorAddr @@ -540,13 +556,13 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(strProposalStatus) != 0 { proposalStatus, err := gov.ProposalStatusFromString(govClientUtils.NormalizeProposalStatus(strProposalStatus)) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.ProposalStatus = proposalStatus } if len(strNumLimit) != 0 { - numLimit, ok := utils.ParseUint64OrReturnBadRequest(w, strNumLimit) + numLimit, ok := rest.ParseUint64OrReturnBadRequest(w, strNumLimit) if !ok { return } @@ -555,17 +571,17 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposals", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -577,11 +593,11 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) + proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -590,16 +606,16 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/tally", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/gov/codec.go b/x/gov/codec.go index 6df535449ba4..10fecb3bcd1c 100644 --- a/x/gov/codec.go +++ b/x/gov/codec.go @@ -4,9 +4,10 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ) +var msgCdc = codec.New() + // Register concrete types on codec codec func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/MsgSubmitProposal", nil) cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil) cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil) @@ -15,4 +16,6 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(&TextProposal{}, "gov/TextProposal", nil) } -var msgCdc = codec.New() +func init() { + RegisterCodec(msgCdc) +} diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 1eb60c4f2b8a..952f87f771a3 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -16,6 +16,7 @@ func TestTickExpiredDepositPeriod(t *testing.T) { mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) @@ -58,6 +59,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) @@ -114,6 +116,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) @@ -156,6 +159,7 @@ func TestTickPassedVotingPeriod(t *testing.T) { SortAddresses(addrs) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) + keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) diff --git a/x/gov/errors.go b/x/gov/errors.go index 9b9aa25dbdd4..0bf904d339f3 100644 --- a/x/gov/errors.go +++ b/x/gov/errors.go @@ -46,12 +46,12 @@ func ErrAddressNotStaked(codespace sdk.CodespaceType, address sdk.AccAddress) sd return sdk.NewError(codespace, CodeAddressNotStaked, fmt.Sprintf("Address %s is not staked and is thus ineligible to vote", address)) } -func ErrInvalidTitle(codespace sdk.CodespaceType, title string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidTitle, fmt.Sprintf("Proposal Title '%s' is not valid", title)) +func ErrInvalidTitle(codespace sdk.CodespaceType, errorMsg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidTitle, errorMsg) } -func ErrInvalidDescription(codespace sdk.CodespaceType, description string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidDescription, fmt.Sprintf("Proposal Desciption '%s' is not valid", description)) +func ErrInvalidDescription(codespace sdk.CodespaceType, errorMsg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDescription, errorMsg) } func ErrInvalidProposalType(codespace sdk.CodespaceType, proposalType ProposalKind) sdk.Error { diff --git a/x/gov/genesis.go b/x/gov/genesis.go index e06d79b21fea..9c44b2f007fa 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -184,12 +184,14 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { for _, proposal := range proposals { proposalID := proposal.GetProposalID() depositsIterator := k.GetDeposits(ctx, proposalID) + defer depositsIterator.Close() for ; depositsIterator.Valid(); depositsIterator.Next() { var deposit Deposit k.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) deposits = append(deposits, DepositWithMetadata{proposalID, deposit}) } votesIterator := k.GetVotes(ctx, proposalID) + defer votesIterator.Close() for ; votesIterator.Valid(); votesIterator.Next() { var vote Vote k.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 19fd65734bf1..f59cdfd1534c 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -422,7 +422,7 @@ func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) sdk.Iterato func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) depositsIterator := keeper.GetDeposits(ctx, proposalID) - + defer depositsIterator.Close() for ; depositsIterator.Valid(); depositsIterator.Next() { deposit := &Deposit{} keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) @@ -434,15 +434,13 @@ func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { store.Delete(depositsIterator.Key()) } - - depositsIterator.Close() } // Deletes all the deposits on a specific proposal without refunding them func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { store := ctx.KVStore(keeper.storeKey) depositsIterator := keeper.GetDeposits(ctx, proposalID) - + defer depositsIterator.Close() for ; depositsIterator.Valid(); depositsIterator.Next() { deposit := &Deposit{} keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) @@ -454,8 +452,6 @@ func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { store.Delete(depositsIterator.Key()) } - - depositsIterator.Close() } // ===================================================== diff --git a/x/gov/msgs.go b/x/gov/msgs.go index 3be7ab583a15..534316e4fb44 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -11,6 +11,9 @@ const ( TypeMsgDeposit = "deposit" TypeMsgVote = "vote" TypeMsgSubmitProposal = "submit_proposal" + + MaxDescriptionLength int = 5000 + MaxTitleLength int = 140 ) var _, _, _ sdk.Msg = MsgSubmitProposal{}, MsgDeposit{}, MsgVote{} @@ -42,10 +45,16 @@ func (msg MsgSubmitProposal) Type() string { return TypeMsgSubmitProposal } // Implements Msg. func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { if len(msg.Title) == 0 { - return ErrInvalidTitle(DefaultCodespace, msg.Title) // TODO: Proper Error + return ErrInvalidTitle(DefaultCodespace, "No title present in proposal") + } + if len(msg.Title) > MaxTitleLength { + return ErrInvalidTitle(DefaultCodespace, fmt.Sprintf("Proposal title is longer than max length of %d", MaxTitleLength)) } if len(msg.Description) == 0 { - return ErrInvalidDescription(DefaultCodespace, msg.Description) // TODO: Proper Error + return ErrInvalidDescription(DefaultCodespace, "No description present in proposal") + } + if len(msg.Description) > MaxDescriptionLength { + return ErrInvalidDescription(DefaultCodespace, fmt.Sprintf("Proposal description is longer than max length of %d", MaxDescriptionLength)) } if !validProposalType(msg.ProposalType) { return ErrInvalidProposalType(DefaultCodespace, msg.ProposalType) diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index 83881834f86a..a9fa8839668d 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -1,6 +1,7 @@ package gov import ( + "strings" "testing" "github.com/stretchr/testify/require" @@ -40,6 +41,8 @@ func TestMsgSubmitProposal(t *testing.T) { {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, sdk.AccAddress{}, coinsPos, false}, {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsZero, true}, {"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsMulti, true}, + {strings.Repeat("#", MaxTitleLength*2), "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsMulti, false}, + {"Test Proposal", strings.Repeat("#", MaxDescriptionLength*2), ProposalTypeText, addrs[0], coinsMulti, false}, } for i, tc := range tests { @@ -52,6 +55,15 @@ func TestMsgSubmitProposal(t *testing.T) { } } +func TestMsgDepositGetSignBytes(t *testing.T) { + addr := sdk.AccAddress("addr1") + msg := NewMsgDeposit(addr, 0, coinsPos) + res := msg.GetSignBytes() + + expected := `{"type":"cosmos-sdk/MsgDeposit","value":{"amount":[{"amount":"1000","denom":"stake"}],"depositor":"cosmos1v9jxgu33kfsgr5","proposal_id":"0"}}` + require.Equal(t, expected, string(res)) +} + // test ValidateBasic for MsgDeposit func TestMsgDeposit(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) diff --git a/x/gov/querier.go b/x/gov/querier.go index bc4eb0d2baa7..b9f0fbf7f039 100644 --- a/x/gov/querier.go +++ b/x/gov/querier.go @@ -181,6 +181,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper var deposits []Deposit depositsIterator := keeper.GetDeposits(ctx, params.ProposalID) + defer depositsIterator.Close() for ; depositsIterator.Valid(); depositsIterator.Next() { deposit := Deposit{} keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) @@ -238,6 +239,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke var votes []Vote votesIterator := keeper.GetVotes(ctx, params.ProposalID) + defer votesIterator.Close() for ; votesIterator.Valid(); votesIterator.Next() { vote := Vote{} keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) diff --git a/x/gov/test_common.go b/x/gov/test_common.go index f030d7b84ce6..34a507c26395 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -31,7 +31,7 @@ func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []a keyGov := sdk.NewKVStoreKey(StoreKey) pk := mapp.ParamsKeeper - ck := bank.NewBaseKeeper(mapp.AccountKeeper) + ck := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) sk = staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) keeper = NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index f59b37921b93..8129783d449e 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -21,7 +21,9 @@ func getMockApp(t *testing.T) *mock.App { RegisterCodec(mapp.Cdc) keyIBC := sdk.NewKVStoreKey("ibc") ibcMapper := NewMapper(mapp.Cdc, keyIBC, DefaultCodespace) - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, + mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), + bank.DefaultCodespace) mapp.Router().AddRoute("ibc", NewHandler(ibcMapper, bankKeeper)) require.NoError(t, mapp.CompleteSetup(keyIBC)) diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index 2bea7cc80801..22e70f6a93ec 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -32,11 +32,7 @@ func IBCTransferCmd(cdc *codec.Codec) *cobra.Command { WithCodec(cdc). WithAccountDecoder(cdc) - from, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + from := cliCtx.GetFromAddress() msg, err := buildMsg(from) if err != nil { return err @@ -45,7 +41,7 @@ func IBCTransferCmd(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index f9928c8ef904..231e0917fb05 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -82,11 +82,7 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { toChainID := viper.GetString(FlagToChainID) toChainNode := viper.GetString(FlagToChainNode) - address, err := context.NewCLIContext().GetFromAddress() - if err != nil { - panic(err) - } - + address := context.NewCLIContext().GetFromAddress() c.address = address c.loop(fromChainID, fromChainNode, toChainID, toChainNode) @@ -96,10 +92,7 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) { cliCtx := context.NewCLIContext() - name, err := cliCtx.GetFromName() - if err != nil { - panic(err) - } + name := cliCtx.GetFromName() passphrase, err := keys.ReadPassphraseFromStdin(name) if err != nil { panic(err) @@ -207,11 +200,7 @@ func (c relayCommander) refine(bz []byte, ibcSeq, accSeq uint64, passphrase stri txBldr := authtxb.NewTxBuilderFromCLI().WithSequence(accSeq).WithTxEncoder(utils.GetTxEncoder(c.cdc)) cliCtx := context.NewCLIContext() - name, err := cliCtx.GetFromName() - if err != nil { - panic(err) - } - + name := cliCtx.GetFromName() res, err := txBldr.BuildAndSign(name, passphrase, []sdk.Msg{msg}) if err != nil { panic(err) diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index ab26b6e42046..fd35764b0dbf 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -3,8 +3,9 @@ package rest import ( "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" @@ -19,8 +20,8 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, } type transferReq struct { - BaseReq utils.BaseReq `json:"base_req"` - Amount sdk.Coins `json:"amount"` + BaseReq rest.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } // TransferRequestHandler - http request handler to transfer coins to a address @@ -33,12 +34,12 @@ func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. to, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } var req transferReq - err = utils.ReadRESTReq(w, r, cdc, &req) + err = rest.ReadRESTReq(w, r, cdc, &req) if err != nil { return } @@ -48,18 +49,28 @@ func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - info, err := kb.Get(req.BaseReq.Name) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return + var fromAddr sdk.AccAddress + + if req.BaseReq.GenerateOnly { + // When generate only is supplied, the from field must be a valid Bech32 + // address. + addr, err := sdk.AccAddressFromBech32(req.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + fromAddr = addr } - packet := ibc.NewIBCPacket( - sdk.AccAddress(info.GetPubKey().Address()), to, - req.Amount, req.BaseReq.ChainID, destChainID, - ) + packet := ibc.NewIBCPacket(fromAddr, to, req.Amount, req.BaseReq.ChainID, destChainID) msg := ibc.IBCTransferMsg{IBCPacket: packet} - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index 9b9bbf93a4c9..f2719f7b374b 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -50,7 +50,7 @@ func setupTestInput() testInput { ak := auth.NewAccountKeeper( cdc, authCapKey, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount, ) - bk := bank.NewBaseKeeper(ak) + bk := bank.NewBaseKeeper(ak, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) ctx := sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id"}, false, log.NewNopLogger()) ak.SetParams(ctx, auth.DefaultParams()) diff --git a/x/mock/simulation/simulate.go b/x/mock/simulation/simulate.go index 81e360a1a827..723955f6b367 100644 --- a/x/mock/simulation/simulate.go +++ b/x/mock/simulation/simulate.go @@ -18,8 +18,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// AppStateFn returns the app state json bytes -type AppStateFn func(r *rand.Rand, accs []Account, genesisTimestamp time.Time) json.RawMessage +// AppStateFn returns the app state json bytes, the genesis accounts, and the chain identifier +type AppStateFn func(r *rand.Rand, accs []Account, genesisTimestamp time.Time) (appState json.RawMessage, accounts []Account, chainId string) // Simulate tests application by sending random messages. func Simulate(t *testing.T, app *baseapp.BaseApp, @@ -35,15 +35,18 @@ func Simulate(t *testing.T, app *baseapp.BaseApp, func initChain( r *rand.Rand, params Params, accounts []Account, app *baseapp.BaseApp, appStateFn AppStateFn, genesisTimestamp time.Time, -) mockValidators { +) (mockValidators, []Account) { + + appState, accounts, chainID := appStateFn(r, accounts, genesisTimestamp) req := abci.RequestInitChain{ - AppStateBytes: appStateFn(r, accounts, genesisTimestamp), + AppStateBytes: appState, + ChainId: chainID, } res := app.InitChain(req) validators := newMockValidators(r, res.Validators, params) - return validators + return validators, accounts } // SimulateFromSeed tests an application by running the provided @@ -73,7 +76,11 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Second variable to keep pending validator set (delayed one block since // TM 0.24) Initially this is the same as the initial validator set - validators := initChain(r, params, accs, app, appStateFn, genesisTimestamp) + validators, accs := initChain(r, params, accs, app, appStateFn, genesisTimestamp) + if len(accs) == 0 { + return true, fmt.Errorf("must have greater than zero genesis accounts") + } + nextValidators := validators header := abci.Header{ diff --git a/x/params/keeper_test.go b/x/params/keeper_test.go index 585db3c410e5..cda57de8e887 100644 --- a/x/params/keeper_test.go +++ b/x/params/keeper_test.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -71,7 +72,7 @@ func TestKeeper(t *testing.T) { ctx := defaultContext(skey, tkey) keeper := NewKeeper(cdc, skey, tkey) space := keeper.Subspace("test").WithTypeTable(table) - store := ctx.KVStore(skey).Prefix([]byte("test/")) + store := prefix.NewStore(ctx.KVStore(skey), []byte("test/")) // Set params for i, kv := range kvs { @@ -177,7 +178,7 @@ func TestSubspace(t *testing.T) { []byte("struct"), s{}, ) - store := ctx.KVStore(key).Prefix([]byte("test/")) + store := prefix.NewStore(ctx.KVStore(key), []byte("test/")) space := keeper.Subspace("test").WithTypeTable(table) // Test space.Set, space.Modified diff --git a/x/params/subspace/subspace.go b/x/params/subspace/subspace.go index 2ace1923276d..cf8e345dd8fe 100644 --- a/x/params/subspace/subspace.go +++ b/x/params/subspace/subspace.go @@ -5,6 +5,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/store/prefix" ) const ( @@ -69,14 +71,14 @@ func (s Subspace) WithTypeTable(table TypeTable) Subspace { func (s Subspace) kvStore(ctx sdk.Context) sdk.KVStore { // append here is safe, appends within a function won't cause // weird side effects when its singlethreaded - return ctx.KVStore(s.key).Prefix(append(s.name, '/')) + return prefix.NewStore(ctx.KVStore(s.key), append(s.name, '/')) } // Returns a KVStore identical with ctx.TransientStore(s.tkey).Prefix() func (s Subspace) transientStore(ctx sdk.Context) sdk.KVStore { // append here is safe, appends within a function won't cause // weird side effects when its singlethreaded - return ctx.TransientStore(s.tkey).Prefix(append(s.name, '/')) + return prefix.NewStore(ctx.TransientStore(s.tkey), append(s.name, '/')) } // Get parameter from store diff --git a/x/params/subspace/test_common.go b/x/params/subspace/test_common.go index e39b9c1b2ddf..f6bf9c73f1ca 100644 --- a/x/params/subspace/test_common.go +++ b/x/params/subspace/test_common.go @@ -27,8 +27,8 @@ func DefaultTestComponents(t *testing.T, table TypeTable) (sdk.Context, Subspace tkey := sdk.NewTransientStoreKey(TStoreKey) db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) - ms.WithTracer(os.Stdout) - ms.WithTracingContext(sdk.TraceContext{}) + ms.SetTracer(os.Stdout) + ms.SetTracingContext(sdk.TraceContext{}) ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tkey, sdk.StoreTypeTransient, db) err := ms.LoadLatestVersion() diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 3662d7faafa0..e9c17b89a5b9 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -30,7 +30,7 @@ func getMockApp(t *testing.T) (*mock.App, staking.Keeper, Keeper) { tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) keySlashing := sdk.NewKVStoreKey(StoreKey) - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) stakingKeeper := staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, bankKeeper, mapp.ParamsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) keeper := NewKeeper(mapp.Cdc, keySlashing, stakingKeeper, mapp.ParamsKeeper.Subspace(DefaultParamspace), DefaultCodespace) mapp.Router().AddRoute(staking.RouterKey, staking.NewHandler(stakingKeeper)) diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index fa5f882cc69e..5354e6e23d9e 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -25,15 +25,20 @@ func GetCmdQuerySigningInfo(storeName string, cdc *codec.Codec) *cobra.Command { return err } - key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address())) + consAddr := sdk.ConsAddress(pk.Address()) + key := slashing.GetValidatorSigningInfoKey(consAddr) res, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } + if len(res) == 0 { + return fmt.Errorf("Validator %s not found in slashing store", consAddr) + } + var signingInfo slashing.ValidatorSigningInfo - cdc.MustUnmarshalBinaryLengthPrefixed(res, signingInfo) + cdc.MustUnmarshalBinaryLengthPrefixed(res, &signingInfo) return cliCtx.PrintOutput(signingInfo) }, } diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index 4fc1c9cde490..968a03e91193 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -25,16 +25,13 @@ func GetCmdUnjail(cdc *codec.Codec) *cobra.Command { WithCodec(cdc). WithAccountDecoder(cdc) - valAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } + valAddr := cliCtx.GetFromAddress() msg := slashing.NewMsgUnjail(sdk.ValAddress(valAddr)) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index 59c6493c2352..812e9527f505 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -4,10 +4,11 @@ import ( "fmt" "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/gorilla/mux" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing" @@ -33,7 +34,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code pk, err := sdk.GetConsPubKeyBech32(vars["validatorPubKey"]) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -41,7 +42,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code res, err := cliCtx.QueryStore(key, storeName) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -54,11 +55,11 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code err = cdc.UnmarshalBinaryLengthPrefixed(res, &signingInfo) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, signingInfo, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, signingInfo, cliCtx.Indent) } } @@ -68,10 +69,10 @@ func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Hand res, err := cliCtx.QueryWithData(route, nil) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index add8c6665bee..edbfaec92a2b 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -4,8 +4,9 @@ import ( "bytes" "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" @@ -23,7 +24,7 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec // Unjail TX body type UnjailReq struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` } func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { @@ -33,7 +34,7 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL bech32validator := vars["validatorAddr"] var req UnjailReq - err := utils.ReadRESTReq(w, r, cdc, &req) + err := rest.ReadRESTReq(w, r, cdc, &req) if err != nil { return } @@ -43,24 +44,38 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL return } - info, err := kb.Get(req.BaseReq.Name) + valAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - valAddr, err := sdk.ValAddressFromBech32(bech32validator) + msg := slashing.NewMsgUnjail(valAddr) + err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - if !bytes.Equal(info.GetPubKey().Address(), valAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) return } - msg := slashing.NewMsgUnjail(valAddr) - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + // derive the from account address and name from the Keybase + fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) + + if !bytes.Equal(cliCtx.GetFromAddress(), valAddr) { + rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index c2e7c2e87f1a..4289618545b0 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -72,7 +72,7 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams) accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) - ck := bank.NewBaseKeeper(accountKeeper) + ck := bank.NewBaseKeeper(accountKeeper, paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, ck, paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) genesis := staking.DefaultGenesisState() diff --git a/x/staking/app_test.go b/x/staking/app_test.go index f95bce1326bf..852a9593ab1c 100644 --- a/x/staking/app_test.go +++ b/x/staking/app_test.go @@ -22,7 +22,7 @@ func getMockApp(t *testing.T) (*mock.App, Keeper) { keyStaking := sdk.NewKVStoreKey(StoreKey) tkeyStaking := sdk.NewTransientStoreKey(TStoreKey) - bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper) + bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) keeper := NewKeeper(mApp.Cdc, keyStaking, tkeyStaking, bankKeeper, mApp.ParamsKeeper.Subspace(DefaultParamspace), DefaultCodespace) mApp.Router().AddRoute(RouterKey, NewHandler(keeper)) diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index 4a2ed6eb4def..6838cc0e048b 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -39,7 +39,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -71,11 +71,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { WithCodec(cdc). WithAccountDecoder(cdc) - valAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + valAddr := cliCtx.GetFromAddress() description := staking.Description{ Moniker: viper.GetString(FlagMoniker), Identity: viper.GetString(FlagIdentity), @@ -102,7 +98,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -128,11 +124,7 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { return err } - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + delAddr := cliCtx.GetFromAddress() valAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err @@ -144,7 +136,7 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -167,11 +159,7 @@ func GetCmdRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { var err error - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + delAddr := cliCtx.GetFromAddress() valSrcAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) if err != nil { return err @@ -199,7 +187,7 @@ func GetCmdRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -220,11 +208,7 @@ func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { WithCodec(cdc). WithAccountDecoder(cdc) - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - + delAddr := cliCtx.GetFromAddress() valAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err @@ -247,7 +231,7 @@ func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -265,12 +249,9 @@ func BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr authtxb.TxBuilder return txBldr, nil, err } - valAddr, err := cliCtx.GetFromAddress() - if err != nil { - return txBldr, nil, err - } - + valAddr := cliCtx.GetFromAddress() pkStr := viper.GetString(FlagPubKey) + pk, err := sdk.GetConsPubKeyBech32(pkStr) if err != nil { return txBldr, nil, err diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go index c51f9e7ad0ac..2ffefbda1976 100644 --- a/x/staking/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -4,9 +4,10 @@ import ( "net/http" "strings" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/staking" @@ -122,13 +123,13 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han _, err := sdk.AccAddressFromBech32(delegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } node, err := cliCtx.GetNode() if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -170,17 +171,17 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han for _, action := range actions { foundTxs, errQuery := queryTxs(node, cliCtx, cdc, action, delegatorAddr) if errQuery != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) } txs = append(txs, foundTxs...) } res, err := cdc.MarshalJSON(txs) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -201,7 +202,7 @@ func redelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Ha if len(bechDelegatorAddr) != 0 { delegatorAddr, err := sdk.AccAddressFromBech32(bechDelegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.DelegatorAddr = delegatorAddr @@ -210,7 +211,7 @@ func redelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Ha if len(bechSrcValidatorAddr) != 0 { srcValidatorAddr, err := sdk.ValAddressFromBech32(bechSrcValidatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.SrcValidatorAddr = srcValidatorAddr @@ -219,7 +220,7 @@ func redelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Ha if len(bechDstValidatorAddr) != 0 { dstValidatorAddr, err := sdk.ValAddressFromBech32(bechDstValidatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.DstValidatorAddr = dstValidatorAddr @@ -227,16 +228,16 @@ func redelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Ha bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/staking/redelegations", bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -260,10 +261,10 @@ func validatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handl return func(w http.ResponseWriter, r *http.Request) { res, err := cliCtx.QueryWithData("custom/staking/validators", nil) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -287,10 +288,10 @@ func poolHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc return func(w http.ResponseWriter, r *http.Request) { res, err := cliCtx.QueryWithData("custom/staking/pool", nil) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -299,9 +300,9 @@ func paramsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFu return func(w http.ResponseWriter, r *http.Request) { res, err := cliCtx.QueryWithData("custom/staking/parameters", nil) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/staking/client/rest/tx.go b/x/staking/client/rest/tx.go index 73d86ce423fa..397e61a73370 100644 --- a/x/staking/client/rest/tx.go +++ b/x/staking/client/rest/tx.go @@ -4,8 +4,9 @@ import ( "bytes" "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" @@ -31,14 +32,14 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec type ( msgDelegationsInput struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 Delegation sdk.Coin `json:"delegation"` } msgBeginRedelegateInput struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32 ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32 @@ -46,7 +47,7 @@ type ( } msgUndelegateInput struct { - BaseReq utils.BaseReq `json:"base_req"` + BaseReq rest.BaseReq `json:"base_req"` DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 SharesAmount sdk.Dec `json:"shares"` @@ -57,9 +58,9 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return func(w http.ResponseWriter, r *http.Request) { var req msgDelegationsInput - err := utils.ReadRESTReq(w, r, cdc, &req) + err := rest.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -68,25 +69,33 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - info, err := kb.Get(req.BaseReq.Name) + msg := staking.NewMsgDelegate(req.DelegatorAddr, req.ValidatorAddr, req.Delegation) + err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - if !bytes.Equal(info.GetPubKey().Address(), req.DelegatorAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) return } - msg := staking.NewMsgDelegate(req.DelegatorAddr, req.ValidatorAddr, req.Delegation) - err = msg.ValidateBasic() + // derive the from account address and name from the Keybase + fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) + + if !bytes.Equal(cliCtx.GetFromAddress(), req.DelegatorAddr) { + rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address") + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -94,9 +103,9 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex return func(w http.ResponseWriter, r *http.Request) { var req msgBeginRedelegateInput - err := utils.ReadRESTReq(w, r, cdc, &req) + err := rest.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -105,25 +114,33 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex return } - info, err := kb.Get(req.BaseReq.Name) + msg := staking.NewMsgBeginRedelegate(req.DelegatorAddr, req.ValidatorSrcAddr, req.ValidatorDstAddr, req.SharesAmount) + err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - if !bytes.Equal(info.GetPubKey().Address(), req.DelegatorAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) return } - msg := staking.NewMsgBeginRedelegate(req.DelegatorAddr, req.ValidatorSrcAddr, req.ValidatorDstAddr, req.SharesAmount) - err = msg.ValidateBasic() + // derive the from account address and name from the Keybase + fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) + + if !bytes.Equal(cliCtx.GetFromAddress(), req.DelegatorAddr) { + rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address") + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } @@ -131,9 +148,9 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx return func(w http.ResponseWriter, r *http.Request) { var req msgUndelegateInput - err := utils.ReadRESTReq(w, r, cdc, &req) + err := rest.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -142,24 +159,32 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx return } - info, err := kb.Get(req.BaseReq.Name) + msg := staking.NewMsgUndelegate(req.DelegatorAddr, req.ValidatorAddr, req.SharesAmount) + err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - if !bytes.Equal(info.GetPubKey().Address(), req.DelegatorAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) return } - msg := staking.NewMsgUndelegate(req.DelegatorAddr, req.ValidatorAddr, req.SharesAmount) - err = msg.ValidateBasic() + // derive the from account address and name from the Keybase + fromAddress, fromName, err := context.GetFromFields(req.BaseReq.From) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + cliCtx = cliCtx.WithFromName(fromName).WithFromAddress(fromAddress) + + if !bytes.Equal(cliCtx.GetFromAddress(), req.DelegatorAddr) { + rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address") return } - utils.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/staking/client/rest/utils.go b/x/staking/client/rest/utils.go index 2a95776e3dc0..c9e2ed9d98fe 100644 --- a/x/staking/client/rest/utils.go +++ b/x/staking/client/rest/utils.go @@ -4,11 +4,12 @@ import ( "fmt" "net/http" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/gorilla/mux" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/staking" @@ -59,17 +60,17 @@ func queryRedelegations(cliCtx context.CLIContext, cdc *codec.Codec, endpoint st delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } srcValidatorAddr, err := sdk.ValAddressFromBech32(bech32srcValidator) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } dstValidatorAddr, err := sdk.ValAddressFromBech32(bech32dstValidator) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -81,16 +82,16 @@ func queryRedelegations(cliCtx context.CLIContext, cdc *codec.Codec, endpoint st bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData(endpoint, bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -103,7 +104,7 @@ func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) ht delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -111,16 +112,16 @@ func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) ht bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData(endpoint, bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -131,7 +132,7 @@ func queryDelegator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -139,16 +140,16 @@ func queryDelegator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData(endpoint, bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -159,7 +160,7 @@ func queryValidator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -167,15 +168,15 @@ func queryValidator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData(endpoint, bz) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/staking/keeper/sdk_types.go b/x/staking/keeper/sdk_types.go index 7e610360a5d9..8c5bad161288 100644 --- a/x/staking/keeper/sdk_types.go +++ b/x/staking/keeper/sdk_types.go @@ -14,6 +14,7 @@ var _ sdk.ValidatorSet = Keeper{} func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + defer iterator.Close() i := int64(0) for ; iterator.Valid(); iterator.Next() { validator := types.MustUnmarshalValidator(k.cdc, iterator.Value()) @@ -23,7 +24,6 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato } i++ } - iterator.Close() } // iterate through the bonded validator set and perform the provided function @@ -52,6 +52,7 @@ func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index in // iterate through the active validator set and perform the provided function func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { iterator := k.LastValidatorsIterator(ctx) + defer iterator.Close() i := int64(0) for ; iterator.Valid(); iterator.Next() { address := AddressFromLastValidatorPowerKey(iterator.Key()) @@ -66,7 +67,6 @@ func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, vali } i++ } - iterator.Close() } // get the sdk.validator for a particular address @@ -134,6 +134,7 @@ func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, store := ctx.KVStore(k.storeKey) delegatorPrefixKey := GetDelegationsKey(delAddr) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + defer iterator.Close() for i := int64(0); iterator.Valid(); iterator.Next() { del := types.MustUnmarshalDelegation(k.cdc, iterator.Value()) stop := fn(i, del) @@ -142,5 +143,4 @@ func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, } i++ } - iterator.Close() } diff --git a/x/staking/keeper/test_common.go b/x/staking/keeper/test_common.go index 057b82a2a070..aa95c885ca45 100644 --- a/x/staking/keeper/test_common.go +++ b/x/staking/keeper/test_common.go @@ -112,7 +112,11 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context auth.ProtoBaseAccount, // prototype ) - ck := bank.NewBaseKeeper(accountKeeper) + ck := bank.NewBaseKeeper( + accountKeeper, + pk.Subspace(bank.DefaultParamspace), + bank.DefaultCodespace, + ) keeper := NewKeeper(cdc, keyStaking, tkeyStaking, ck, pk.Subspace(DefaultParamspace), types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) @@ -215,6 +219,7 @@ func TestingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Vali { // Remove any existing power key for validator. store := ctx.KVStore(keeper.storeKey) iterator := sdk.KVStorePrefixIterator(store, ValidatorsByPowerIndexKey) + defer iterator.Close() deleted := false for ; iterator.Valid(); iterator.Next() { valAddr := parseValidatorPowerRankKey(iterator.Key()) diff --git a/x/staking/keeper/val_state_change.go b/x/staking/keeper/val_state_change.go index f611d4e20e0e..319760f931a7 100644 --- a/x/staking/keeper/val_state_change.go +++ b/x/staking/keeper/val_state_change.go @@ -36,6 +36,7 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab // Iterate over validators, highest power to lowest. iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) + defer iterator.Close() count := 0 for ; iterator.Valid() && count < int(maxValidators); iterator.Next() { @@ -248,6 +249,7 @@ func (k Keeper) getLastValidatorsByAddr(ctx sdk.Context) validatorsByAddr { last := make(validatorsByAddr) store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) + defer iterator.Close() for ; iterator.Valid(); iterator.Next() { var valAddr [sdk.AddrLen]byte copy(valAddr[:], iterator.Key()[1:]) diff --git a/x/staking/types/params.go b/x/staking/types/params.go index ef76b0053a9c..9a7ca2748dc1 100644 --- a/x/staking/types/params.go +++ b/x/staking/types/params.go @@ -78,10 +78,10 @@ func DefaultParams() Params { // String returns a human readable string representation of the parameters. func (p Params) String() string { return fmt.Sprintf(`Params: - Unbonding Time: %s - Max Validators: %d - Max Entries: %d - Bonded Coin Denomination: %s`, p.UnbondingTime, + Unbonding Time: %s + Max Validators: %d + Max Entries: %d + Bonded Coin Denom: %s`, p.UnbondingTime, p.MaxValidators, p.MaxEntries, p.BondDenom) } diff --git a/x/staking/types/pool.go b/x/staking/types/pool.go index 3f56285204ad..840e4f609e65 100644 --- a/x/staking/types/pool.go +++ b/x/staking/types/pool.go @@ -71,10 +71,10 @@ func (p Pool) bondedTokensToNotBonded(bondedTokens sdk.Int) Pool { // String returns a human readable string representation of a pool. func (p Pool) String() string { return fmt.Sprintf(`Pool: - Loose Tokens: %s + Loose Tokens: %s Bonded Tokens: %s - Token Supply: %s - Bonded Ratio: %v`, p.NotBondedTokens, + Token Supply: %s + Bonded Ratio: %v`, p.NotBondedTokens, p.BondedTokens, p.TokenSupply(), p.BondedRatio()) }