feat!: Add object store (#25470)

Co-authored-by: yihuang <huang@crypto.com>
Co-authored-by: mmsqe <mavis@crypto.com>
Co-authored-by: mmsqe <tqd0800210105@gmail.com>
Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com>
This commit is contained in:
Eric Warehime 2025-10-23 08:55:49 -07:00 committed by GitHub
parent 5e022baf9b
commit d790942c42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 1255 additions and 399 deletions

View File

@ -147,7 +147,7 @@ jobs:
repo-analysis:
runs-on: depot-ubuntu-22.04-4
needs: [tests, test-integration, test-e2e]
needs: [tests, test-integration, test-e2e, test-clientv2, test-core, test-depinject, test-errors, test-math, test-schema, test-collections, test-cosmovisor, test-confix, test-store, test-log, test-x-tx, test-tools-benchmark]
steps:
- uses: actions/checkout@v5
- uses: technote-space/get-diff-action@v6.1.2
@ -184,11 +184,76 @@ jobs:
with:
name: "${{ github.sha }}-e2e-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-clientv2-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-core-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-depinject-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-errors-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-math-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-schema-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-collections-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-cosmovisor-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-confix-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-store-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-log-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-x-tx-coverage"
continue-on-error: true
- uses: actions/download-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-tools-benchmark-coverage"
continue-on-error: true
- name: Upload coverage to Codecov
if: env.GIT_DIFF
uses: codecov/codecov-action@v5
with:
files: ./00profile.out,./01profile.out,./02profile.out,./03profile.out,./tests/integration-profile.out,./tests/e2e-profile.out
files: ./00profile.out,./01profile.out,./02profile.out,./03profile.out,./integration-profile.out,./e2e-profile.out,./client/v2/coverage.out,./core/coverage.out,./depinject/coverage.out,./errors/coverage.out,./math/coverage.out,./schema/coverage.out,./collections/coverage.out,./tools/cosmovisor/coverage.out,./tools/confix/coverage.out,./store/coverage.out,./log/coverage.out,./x/tx/coverage.out,./tools/benchmark/coverage.out
fail_ci_if_error: false
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
@ -248,6 +313,11 @@ jobs:
run: |
cd client/v2
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-clientv2-coverage"
path: ./client/v2/coverage.out
test-core:
runs-on: depot-ubuntu-22.04-4
@ -271,6 +341,11 @@ jobs:
run: |
cd core
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-core-coverage"
path: ./core/coverage.out
test-depinject:
runs-on: depot-ubuntu-22.04-4
@ -294,6 +369,11 @@ jobs:
run: |
cd depinject
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-depinject-coverage"
path: ./depinject/coverage.out
test-errors:
runs-on: depot-ubuntu-22.04-4
@ -317,6 +397,11 @@ jobs:
run: |
cd errors
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-errors-coverage"
path: ./errors/coverage.out
test-math:
runs-on: depot-ubuntu-22.04-4
@ -340,6 +425,11 @@ jobs:
run: |
cd math
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-math-coverage"
path: ./math/coverage.out
test-schema:
runs-on: depot-ubuntu-22.04-4
@ -362,6 +452,11 @@ jobs:
run: |
cd schema
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-schema-coverage"
path: ./schema/coverage.out
test-collections:
runs-on: depot-ubuntu-22.04-4
@ -385,6 +480,11 @@ jobs:
run: |
cd collections
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-collections-coverage"
path: ./collections/coverage.out
test-cosmovisor:
runs-on: depot-ubuntu-22.04-4
@ -408,6 +508,11 @@ jobs:
run: |
cd tools/cosmovisor
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-cosmovisor-coverage"
path: ./tools/cosmovisor/coverage.out
test-confix:
runs-on: depot-ubuntu-22.04-4
@ -431,6 +536,11 @@ jobs:
run: |
cd tools/confix
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-confix-coverage"
path: ./tools/confix/coverage.out
test-store:
runs-on: depot-ubuntu-22.04-4
@ -455,6 +565,11 @@ jobs:
cd store
(cd streaming/abci/examples/file && go build .)
go test -ldflags "-r /usr/local/lib" -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-store-coverage"
path: ./store/coverage.out
test-log:
runs-on: depot-ubuntu-22.04-4
@ -478,6 +593,11 @@ jobs:
run: |
cd log
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-log-coverage"
path: ./log/coverage.out
#############################
### Cosmos SDK x/{module} ###
@ -508,6 +628,11 @@ jobs:
run: |
cd x/tx
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-x-tx-coverage"
path: ./x/tx/coverage.out
test-tools-benchmark:
runs-on: depot-ubuntu-22.04-4
@ -531,3 +656,8 @@ jobs:
run: |
cd tools/benchmark
go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace' ./...
- uses: actions/upload-artifact@v4
if: env.GIT_DIFF
with:
name: "${{ github.sha }}-tools-benchmark-coverage"
path: ./tools/benchmark/coverage.out

View File

@ -42,10 +42,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Breaking Changes
* [#25090](https://github.com/cosmos/cosmos-sdk/pull/25090) Moved deprecated modules to `./contrib`. These modules are still available but will no longer be actively maintained or supported in the Cosmos SDK Bug Bounty program.
* `x/group`
* `x/nft`
* `x/circuit`
* `x/crisis`
* `x/group`
* `x/nft`
* `x/circuit`
* `x/crisis`
* (crypto) [#24414](https://github.com/cosmos/cosmos-sdk/pull/24414) Remove sr25519 support, since it was removed in CometBFT v1.x (see: CometBFT [#3646](https://github.com/cometbft/cometbft/pull/3646)).
### Features
@ -111,6 +111,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/mint) [#24436](https://github.com/cosmos/cosmos-sdk/pull/24436) Allow users to set a custom minting function used in the `x/mint` begin blocker.
* The `InflationCalculationFn` argument to `mint.NewAppModule()` is now ignored and must be nil. To set a custom `InflationCalculationFn` on the default minter, use `mintkeeper.WithMintFn(mintkeeper.DefaultMintFn(customInflationFn))`.
* (api) [#24428](https://github.com/cosmos/cosmos-sdk/pull/24428) Add block height to response headers
* (baseapp) [#25470](https://github.com/cosmos/cosmos-sdk/pull/25470) Support mount object store in baseapp, add `ObjectStore` api in context.
### Improvements

View File

@ -283,6 +283,9 @@ func (app *BaseApp) MountStores(keys ...storetypes.StoreKey) {
case *storetypes.MemoryStoreKey:
app.MountStore(key, storetypes.StoreTypeMemory)
case *storetypes.ObjectStoreKey:
app.MountStore(key, storetypes.StoreTypeObject)
default:
panic(fmt.Sprintf("Unrecognized store key type :%T", key))
}
@ -321,6 +324,16 @@ func (app *BaseApp) MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey
}
}
// MountObjectStores mounts all transient object stores with the BaseApp's internal
// commit multi-store.
func (app *BaseApp) MountObjectStores(keys map[string]*storetypes.ObjectStoreKey) {
skeys := slices.Sorted(maps.Keys(keys))
for _, key := range skeys {
memKey := keys[key]
app.MountStore(memKey, storetypes.StoreTypeObject)
}
}
// MountStore mounts a store to the provided key in the BaseApp multistore,
// using the default DB.
func (app *BaseApp) MountStore(key storetypes.StoreKey, typ storetypes.StoreType) {

View File

@ -988,6 +988,25 @@ func TestGetEmptyConsensusParams(t *testing.T) {
require.Equal(t, uint64(0), suite.baseApp.GetMaximumBlockGas(ctx))
}
func TestMountStores(t *testing.T) {
logger := log.NewNopLogger()
db := dbm.NewMemDB()
name := t.Name()
app := baseapp.NewBaseApp(name, logger, db, nil)
kvKey := storetypes.NewKVStoreKey("kv")
transKey := storetypes.NewTransientStoreKey("trans")
memKey := storetypes.NewMemoryStoreKey("mem")
objKey := storetypes.NewObjectStoreKey("obj")
app.MountStores(kvKey, transKey, memKey, objKey)
objKey2 := storetypes.NewObjectStoreKey("obj2")
app.MountObjectStores(map[string]*storetypes.ObjectStoreKey{"obj2": objKey2})
require.NoError(t, app.LoadLatestVersion())
for _, keyName := range []storetypes.StoreKey{kvKey, transKey, memKey, objKey} {
require.NotNil(t, app.CommitMultiStore().GetStore(keyName))
}
require.NotNil(t, app.CommitMultiStore().GetStore(objKey2))
}
func TestLoadVersionPruning(t *testing.T) {
logger := log.NewNopLogger()
pruningOptions := pruningtypes.NewCustomPruningOptions(10, 15)

View File

@ -14,7 +14,7 @@ Flags:
--fees string Fees to pay along with transaction; eg: 10uatom
--from string Name or address of private key with which to sign
--gas string gas limit to set per-transaction; set to "auto" to calculate sufficient gas automatically. Note: "auto" option doesn't always report accurate results. Set a valid coin value to adjust the result. Can be used instead of "fees". (default 200000)
--gas-adjustment float adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored (default 1)
--gas-adjustment float adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored (default 1)
--gas-prices string Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom)
--generate-only Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase only accessed when providing a key name)
-h, --help help for send

View File

@ -32,7 +32,6 @@ require (
github.com/99designs/keyring v1.2.1 // indirect
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
@ -46,7 +45,7 @@ require (
github.com/cockroachdb/pebble v1.1.5 // indirect
github.com/cockroachdb/redact v1.1.6 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
github.com/cometbft/cometbft v0.38.19 // indirect
github.com/cometbft/cometbft v0.39.0-beta.2 // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.1.3 // indirect
@ -107,7 +106,7 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.9.8 // indirect
github.com/linxGnu/grocksdb v1.10.1 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@ -116,7 +115,6 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pkg/errors v0.9.1 // indirect
@ -136,9 +134,10 @@ require (
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/supranational/blst v0.3.16 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
@ -162,9 +161,17 @@ require (
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.17 // indirect
pgregory.net/rapid v1.2.0 // indirect
)
// replace (
// <temporary replace>
// )
replace (
cosmossdk.io/store => ../../store
github.com/cosmos/cosmos-sdk => ../../.
)

View File

@ -16,8 +16,6 @@ cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U=
cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ=
cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE=
cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI=
cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o=
cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A=
cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA=
cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -44,8 +42,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I=
github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg=
github.com/adlio/schema v1.3.9 h1:MLYk1VX1dn7xHW7Kdm1ywKKLjh19DRnrc65axS5xQA8=
github.com/adlio/schema v1.3.9/go.mod h1:GnxXztHzNh6pIc7qm3sw+jsmHrXgBy/x2RBSkKZ3L4w=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -71,8 +69,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.1 h1:hqnfFbjjk3pxGa5E9Ho3hjoU7odtUuNmJ9Ao+Bo8s1c=
github.com/bits-and-blooms/bitset v1.24.1/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU=
github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c=
@ -135,8 +133,8 @@ github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g=
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/cometbft/cometbft v0.38.19 h1:vNdtCkvhuwUlrcLPAyigV7lQpmmo+tAq8CsB8gZjEYw=
github.com/cometbft/cometbft v0.38.19/go.mod h1:UCu8dlHqvkAsmAFmWDRWNZJPlu6ya2fTWZlDrWsivwo=
github.com/cometbft/cometbft v0.39.0-beta.2 h1:OfP4Qlw2L66xqvrNYSlVufYwXXP9frOPBnwYPyDttOk=
github.com/cometbft/cometbft v0.39.0-beta.2/go.mod h1:6Lt9liF9HxSth+zDotxtv94SuGMzRfuHmI287USgjlw=
github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ=
github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
@ -151,8 +149,6 @@ github.com/cosmos/cosmos-db v1.1.3 h1:7QNT77+vkefostcKkhrzDK9uoIEryzFrU9eoMeaQOP
github.com/cosmos/cosmos-db v1.1.3/go.mod h1:kN+wGsnwUJZYn8Sy5Q2O0vCYA99MJllkKASbs6Unb9U=
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/cosmos-sdk v0.53.4 h1:kPF6vY68+/xi1/VebSZGpoxQqA52qkhUzqkrgeBn3Mg=
github.com/cosmos/cosmos-sdk v0.53.4/go.mod h1:7U3+WHZtI44dEOnU46+lDzBb2tFh1QlMvi8Z5JugopI=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE=
@ -379,8 +375,9 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -457,8 +454,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.9.8 h1:vOIKv9/+HKiqJAElJIEYv3ZLcihRxyP7Suu/Mu8Dxjs=
github.com/linxGnu/grocksdb v1.9.8/go.mod h1:C3CNe9UYc9hlEM2pC82AqiGS3LRW537u9LFV4wIZuHk=
github.com/linxGnu/grocksdb v1.10.1 h1:YX6gUcKvSC3d0s9DaqgbU+CRkZHzlELgHu1Z/kmtslg=
github.com/linxGnu/grocksdb v1.10.1/go.mod h1:C3CNe9UYc9hlEM2pC82AqiGS3LRW537u9LFV4wIZuHk=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@ -540,8 +537,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -685,12 +682,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE=
github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@ -938,8 +937,8 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=

View File

@ -43,6 +43,7 @@ ignore:
- "client/docs/**" # Client documentation
- "**/*.md" # Markdown files
- "**/mock*.go" # Mock files
- "**/mock/**" # Mock directories
- "**/*.pb.gateway.go" # Gateway files
# How to handle multiple coverage reports

View File

@ -26,8 +26,8 @@ func NewMockContext() *MockContext {
}
func (m MockContext) KVStore(key storetypes.StoreKey) storetypes.KVStore {
if s := m.store.GetCommitKVStore(key); s != nil {
return s
if s := m.store.GetCommitStore(key); s != nil {
return s.(storetypes.KVStore)
}
m.store.MountStoreWithDB(key, storetypes.StoreTypeIAVL, m.db)
if err := m.store.LoadLatestVersion(); err != nil {

View File

@ -68,6 +68,7 @@ func init() {
ProvideKVStoreKey,
ProvideTransientStoreKey,
ProvideMemoryStoreKey,
ProvideObjectStoreKey,
ProvideGenesisTxHandler,
ProvideKVStoreService,
ProvideMemoryStoreService,
@ -238,6 +239,12 @@ func ProvideMemoryStoreKey(config *runtimev1alpha1.Module, key depinject.ModuleK
return storeKey
}
func ProvideObjectStoreKey(key depinject.ModuleKey, app *AppBuilder) *storetypes.ObjectStoreKey {
storeKey := storetypes.NewObjectStoreKey(fmt.Sprintf("object:%s", key.Name()))
registerStoreKey(app, storeKey)
return storeKey
}
func ProvideGenesisTxHandler(appBuilder *AppBuilder) genesis.TxHandler {
return appBuilder.app
}

View File

@ -4,8 +4,6 @@ import (
"context"
"io"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/core/store"
storetypes "cosmossdk.io/store/types"
@ -150,7 +148,7 @@ func (s kvStoreAdapter) Set(key, value []byte) {
}
}
func (s kvStoreAdapter) Iterator(start, end []byte) dbm.Iterator {
func (s kvStoreAdapter) Iterator(start, end []byte) storetypes.Iterator {
it, err := s.store.Iterator(start, end)
if err != nil {
panic(err)
@ -158,7 +156,7 @@ func (s kvStoreAdapter) Iterator(start, end []byte) dbm.Iterator {
return it
}
func (s kvStoreAdapter) ReverseIterator(start, end []byte) dbm.Iterator {
func (s kvStoreAdapter) ReverseIterator(start, end []byte) storetypes.Iterator {
it, err := s.store.ReverseIterator(start, end)
if err != nil {
panic(err)

View File

@ -114,6 +114,10 @@ func (ms multiStore) GetKVStore(key storetypes.StoreKey) storetypes.KVStore {
return ms.kv[key]
}
func (ms multiStore) GetObjKVStore(storetypes.StoreKey) storetypes.ObjKVStore {
panic("not implemented")
}
func (ms multiStore) GetStore(key storetypes.StoreKey) storetypes.Store {
panic("not implemented")
}
@ -182,7 +186,7 @@ func (kv kvStore) CacheWrap() storetypes.CacheWrap {
panic("not implemented")
}
func (kv kvStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap {
func (kv kvStore) CacheWrapWithTrace(_ io.Writer, _ storetypes.TraceContext) storetypes.CacheWrap {
panic("not implemented")
}

View File

@ -237,6 +237,7 @@ require (
// replace (
// <temporary replace>
// )
replace cosmossdk.io/store => ../store
// Below are the long-lived replace of the SimApp
replace (

View File

@ -40,8 +40,6 @@ cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U=
cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ=
cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE=
cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI=
cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o=
cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A=
cosmossdk.io/tools/confix v0.1.2 h1:2hoM1oFCNisd0ltSAAZw2i4ponARPmlhuNu3yy0VwI4=
cosmossdk.io/tools/confix v0.1.2/go.mod h1:7XfcbK9sC/KNgVGxgLM0BrFbVcR/+6Dg7MFfpx7duYo=
cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA=
@ -484,8 +482,9 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=

View File

@ -35,6 +35,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [#24090](https://github.com/cosmos/cosmos-sdk/pull/24090) Running the `prune` command now disables async pruning.
### Features
* [#25470](https://github.com/cosmos/cosmos-sdk/pull/25470) Adds object KV stores and refactors the base store to be generic across the value parameter.
## v1.1.1 (September 06, 2024)
### Improvements

View File

@ -14,21 +14,24 @@ import (
// cache shadows (overrides) the parent.
//
// TODO: Optimize by memoizing.
type cacheMergeIterator struct {
parent types.Iterator
cache types.Iterator
type cacheMergeIterator[V any] struct {
parent types.GIterator[V]
cache types.GIterator[V]
ascending bool
valid bool
isZero func(V) bool
}
var _ types.Iterator = (*cacheMergeIterator)(nil)
var _ types.Iterator = (*cacheMergeIterator[[]byte])(nil)
func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) types.Iterator {
iter := &cacheMergeIterator{
func NewCacheMergeIterator[V any](parent, cache types.GIterator[V], ascending bool, isZero func(V) bool) types.GIterator[V] {
iter := &cacheMergeIterator[V]{
parent: parent,
cache: cache,
ascending: ascending,
isZero: isZero,
}
iter.valid = iter.skipUntilExistsOrInvalid()
@ -37,17 +40,17 @@ func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) types.I
// Domain implements Iterator.
// Returns parent domain because cache and parent domains are the same.
func (iter *cacheMergeIterator) Domain() (start, end []byte) {
func (iter *cacheMergeIterator[V]) Domain() (start, end []byte) {
return iter.parent.Domain()
}
// Valid implements Iterator.
func (iter *cacheMergeIterator) Valid() bool {
func (iter *cacheMergeIterator[V]) Valid() bool {
return iter.valid
}
// Next implements Iterator
func (iter *cacheMergeIterator) Next() {
func (iter *cacheMergeIterator[V]) Next() {
iter.assertValid()
switch {
@ -74,7 +77,7 @@ func (iter *cacheMergeIterator) Next() {
}
// Key implements Iterator
func (iter *cacheMergeIterator) Key() []byte {
func (iter *cacheMergeIterator[V]) Key() []byte {
iter.assertValid()
// If parent is invalid, get the cache key.
@ -104,7 +107,7 @@ func (iter *cacheMergeIterator) Key() []byte {
}
// Value implements Iterator
func (iter *cacheMergeIterator) Value() []byte {
func (iter *cacheMergeIterator[V]) Value() V {
iter.assertValid()
// If parent is invalid, get the cache value.
@ -134,7 +137,7 @@ func (iter *cacheMergeIterator) Value() []byte {
}
// Close implements Iterator
func (iter *cacheMergeIterator) Close() error {
func (iter *cacheMergeIterator[V]) Close() error {
err1 := iter.cache.Close()
if err := iter.parent.Close(); err != nil {
return err
@ -145,7 +148,7 @@ func (iter *cacheMergeIterator) Close() error {
// Error returns an error if the cacheMergeIterator is invalid defined by the
// Valid method.
func (iter *cacheMergeIterator) Error() error {
func (iter *cacheMergeIterator[V]) Error() error {
if !iter.Valid() {
return errors.New("invalid cacheMergeIterator")
}
@ -155,14 +158,14 @@ func (iter *cacheMergeIterator) Error() error {
// assertValid checks if not valid, panics.
// NOTE: May have side-effect of iterating over cache.
func (iter *cacheMergeIterator) assertValid() {
func (iter *cacheMergeIterator[V]) assertValid() {
if err := iter.Error(); err != nil {
panic(err)
}
}
// compare is like bytes.Compare but opposite if not ascending.
func (iter *cacheMergeIterator) compare(a, b []byte) int {
func (iter *cacheMergeIterator[V]) compare(a, b []byte) int {
if iter.ascending {
return bytes.Compare(a, b)
}
@ -175,9 +178,9 @@ func (iter *cacheMergeIterator) compare(a, b []byte) int {
// If the current cache item is not a delete item, does nothing.
// If `until` is nil, there is no limit, and cache may end up invalid.
// CONTRACT: cache is valid.
func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) {
func (iter *cacheMergeIterator[V]) skipCacheDeletes(until []byte) {
for iter.cache.Valid() &&
iter.cache.Value() == nil &&
iter.isZero(iter.cache.Value()) &&
(until == nil || iter.compare(iter.cache.Key(), until) < 0) {
iter.cache.Next()
}
@ -186,7 +189,7 @@ func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) {
// skipUntilExistsOrInvalid fast forwards cache (or parent+cache in case of deleted items) until current
// item exists, or until iterator becomes invalid.
// Returns whether the iterator is valid.
func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool {
func (iter *cacheMergeIterator[V]) skipUntilExistsOrInvalid() bool {
for {
// If parent is invalid, fast-forward cache.
if !iter.parent.Valid() {
@ -211,7 +214,7 @@ func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool {
case 0: // parent == cache.
// Skip over if cache item is a delete.
valueC := iter.cache.Value()
if valueC == nil {
if iter.isZero(valueC) {
iter.parent.Next()
iter.cache.Next()
@ -223,7 +226,7 @@ func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool {
case 1: // cache < parent
// Skip over if cache item is a delete.
valueC := iter.cache.Value()
if valueC == nil {
if iter.isZero(valueC) {
iter.skipCacheDeletes(keyP)
continue
}

View File

@ -4,7 +4,7 @@ import (
"strconv"
"testing"
"cosmossdk.io/store/cachekv/internal"
"cosmossdk.io/store/internal/btree"
)
func BenchmarkLargeUnsortedMisses(b *testing.B) {
@ -22,23 +22,23 @@ func BenchmarkLargeUnsortedMisses(b *testing.B) {
}
func generateStore() *Store {
cache := map[string]*cValue{}
cache := map[string]*cValue[[]byte]{}
unsorted := map[string]struct{}{}
for i := 0; i < 5000; i++ {
key := "A" + strconv.Itoa(i)
unsorted[key] = struct{}{}
cache[key] = &cValue{}
cache[key] = &cValue[[]byte]{}
}
for i := 0; i < 5000; i++ {
key := "Z" + strconv.Itoa(i)
unsorted[key] = struct{}{}
cache[key] = &cValue{}
cache[key] = &cValue[[]byte]{}
}
return &Store{
return &GStore[[]byte]{
cache: cache,
unsortedCache: unsorted,
sortedCache: internal.NewBTree(),
sortedCache: btree.NewBTree[[]byte](),
}
}

View File

@ -10,47 +10,70 @@ import (
"cosmossdk.io/math"
"cosmossdk.io/store/cachekv/internal"
"cosmossdk.io/store/internal/btree"
"cosmossdk.io/store/internal/conv"
"cosmossdk.io/store/internal/kv"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
)
// cValue represents a cached value.
// If dirty is true, it indicates the cached value is different from the underlying value.
type cValue struct {
value []byte
type cValue[V any] struct {
value V
dirty bool
}
// Store wraps an in-memory cache around an underlying types.KVStore.
type Store struct {
mtx sync.Mutex
cache map[string]*cValue
unsortedCache map[string]struct{}
sortedCache internal.BTree // always ascending sorted
parent types.KVStore
type kvPair[V any] struct {
Key []byte
Value V
}
type Store = GStore[[]byte]
var _ types.CacheKVStore = (*Store)(nil)
// NewStore creates a new Store object
func NewStore(parent types.KVStore) *Store {
return &Store{
cache: make(map[string]*cValue),
return NewGStore(
parent,
types.BytesIsZero,
types.BytesValueLen,
)
}
// GStore wraps an in-memory cache around an underlying types.KVStore.
type GStore[V any] struct {
mtx sync.Mutex
cache map[string]*cValue[V]
unsortedCache map[string]struct{}
sortedCache btree.BTree[V] // always ascending sorted
parent types.GKVStore[V]
// isZero is a function that returns true if the value is considered "zero", for []byte and pointers the zero value
// is `nil`, the zero value is not allowed to set to a key, and it's returned if the key is not found.
isZero func(V) bool
// valueLen validates the value before it's set
valueLen func(V) int
}
// NewGStore creates a new Store object
func NewGStore[V any](parent types.GKVStore[V], isZero func(V) bool, valueLen func(V) int) *GStore[V] {
return &GStore[V]{
cache: make(map[string]*cValue[V]),
unsortedCache: make(map[string]struct{}),
sortedCache: internal.NewBTree(),
sortedCache: btree.NewBTree[V](),
parent: parent,
isZero: isZero,
valueLen: valueLen,
}
}
// GetStoreType implements Store.
func (store *Store) GetStoreType() types.StoreType {
func (store *GStore[V]) GetStoreType() types.StoreType {
return store.parent.GetStoreType()
}
// Get implements types.KVStore.
func (store *Store) Get(key []byte) (value []byte) {
func (store *GStore[V]) Get(key []byte) (value V) {
store.mtx.Lock()
defer store.mtx.Unlock()
@ -68,9 +91,9 @@ func (store *Store) Get(key []byte) (value []byte) {
}
// Set implements types.KVStore.
func (store *Store) Set(key, value []byte) {
func (store *GStore[V]) Set(key []byte, value V) {
types.AssertValidKey(key)
types.AssertValidValue(value)
types.AssertValidValueGeneric(value, store.isZero, store.valueLen)
store.mtx.Lock()
defer store.mtx.Unlock()
@ -78,28 +101,29 @@ func (store *Store) Set(key, value []byte) {
}
// Has implements types.KVStore.
func (store *Store) Has(key []byte) bool {
func (store *GStore[V]) Has(key []byte) bool {
value := store.Get(key)
return value != nil
return !store.isZero(value)
}
// Delete implements types.KVStore.
func (store *Store) Delete(key []byte) {
func (store *GStore[V]) Delete(key []byte) {
types.AssertValidKey(key)
var zeroValue V
store.mtx.Lock()
defer store.mtx.Unlock()
store.setCacheValue(key, nil, true)
store.setCacheValue(key, zeroValue, true)
}
func (store *Store) resetCaches() {
func (store *GStore[V]) resetCaches() {
if len(store.cache) > 100_000 {
// Cache is too large. We likely did something linear time
// (e.g. Epoch block, Genesis block, etc). Free the old caches from memory, and let them get re-allocated.
// TODO: In a future CacheKV redesign, such linear workloads should get into a different cache instantiation.
// 100_000 is arbitrarily chosen as it solved Osmosis' InitGenesis RAM problem.
store.cache = make(map[string]*cValue)
store.cache = make(map[string]*cValue[V])
store.unsortedCache = make(map[string]struct{})
} else {
// Clear the cache using the map clearing idiom
@ -112,22 +136,22 @@ func (store *Store) resetCaches() {
delete(store.unsortedCache, key)
}
}
store.sortedCache = internal.NewBTree()
store.sortedCache = btree.NewBTree[V]()
}
// Write implements Cachetypes.KVStore.
func (store *Store) Write() {
func (store *GStore[V]) Write() {
store.mtx.Lock()
defer store.mtx.Unlock()
if len(store.cache) == 0 && len(store.unsortedCache) == 0 {
store.sortedCache = internal.NewBTree()
store.sortedCache = btree.NewBTree[V]()
return
}
type cEntry struct {
key string
val *cValue
val *cValue[V]
}
// We need a copy of all of the keys.
@ -152,7 +176,7 @@ func (store *Store) Write() {
// be sure if the underlying store might do a save with the byteslice or
// not. Once we get confirmation that .Delete is guaranteed not to
// save the byteslice, then we can assume only a read-only copy is sufficient.
if obj.val.value != nil {
if !store.isZero(obj.val.value) {
// It already exists in the parent, hence update it.
store.parent.Set([]byte(obj.key), obj.val.value)
} else {
@ -162,29 +186,33 @@ func (store *Store) Write() {
}
// CacheWrap implements CacheWrapper.
func (store *Store) CacheWrap() types.CacheWrap {
return NewStore(store)
func (store *GStore[V]) CacheWrap() types.CacheWrap {
return NewGStore(store, store.isZero, store.valueLen)
}
// CacheWrapWithTrace implements the CacheWrapper interface.
func (store *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap {
return NewStore(tracekv.NewStore(store, w, tc))
func (store *GStore[V]) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap {
// We need to make a type assertion here as the tracekv store requires bytes value types for serialization.
if store, ok := any(store).(*GStore[[]byte]); ok {
return NewStore(tracekv.NewStore(store, w, tc))
}
return store.CacheWrap()
}
//----------------------------------------
// Iteration
// Iterator implements types.KVStore.
func (store *Store) Iterator(start, end []byte) types.Iterator {
func (store *GStore[V]) Iterator(start, end []byte) types.GIterator[V] {
return store.iterator(start, end, true)
}
// ReverseIterator implements types.KVStore.
func (store *Store) ReverseIterator(start, end []byte) types.Iterator {
func (store *GStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] {
return store.iterator(start, end, false)
}
func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
func (store *GStore[V]) iterator(start, end []byte, ascending bool) types.GIterator[V] {
store.mtx.Lock()
defer store.mtx.Unlock()
@ -193,7 +221,7 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
var (
err error
parent, cache types.Iterator
parent, cache types.GIterator[V]
)
if ascending {
@ -207,7 +235,7 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
panic(err)
}
return internal.NewCacheMergeIterator(parent, cache, ascending)
return internal.NewCacheMergeIterator(parent, cache, ascending, store.isZero)
}
func findStartIndex(strL []string, startQ string) int {
@ -293,7 +321,7 @@ const (
const minSortSize = 1024
// dirtyItems constructs a slice of dirty items, to use w/ memIterator.
func (store *Store) dirtyItems(start, end []byte) {
func (store *GStore[V]) dirtyItems(start, end []byte) {
startStr, endStr := conv.UnsafeBytesToStr(start), conv.UnsafeBytesToStr(end)
if end != nil && startStr > endStr {
// Nothing to do here.
@ -301,7 +329,7 @@ func (store *Store) dirtyItems(start, end []byte) {
}
n := len(store.unsortedCache)
unsorted := make([]*kv.Pair, 0)
unsorted := make([]*kvPair[V], 0)
// If the unsortedCache is too big, its costs too much to determine
// what's in the subset we are concerned about.
// If you are interleaving iterator calls with writes, this can easily become an
@ -313,7 +341,7 @@ func (store *Store) dirtyItems(start, end []byte) {
// dbm.IsKeyInDomain is nil safe and returns true iff key is greater than start
if dbm.IsKeyInDomain(conv.UnsafeStrToBytes(key), start, end) {
cacheValue := store.cache[key]
unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value})
unsorted = append(unsorted, &kvPair[V]{Key: []byte(key), Value: cacheValue.value})
}
}
store.clearUnsortedCacheSubset(unsorted, stateUnsorted)
@ -356,18 +384,18 @@ func (store *Store) dirtyItems(start, end []byte) {
}
}
kvL := make([]*kv.Pair, 0, 1+endIndex-startIndex)
kvL := make([]*kvPair[V], 0, 1+endIndex-startIndex)
for i := startIndex; i <= endIndex; i++ {
key := strL[i]
cacheValue := store.cache[key]
kvL = append(kvL, &kv.Pair{Key: []byte(key), Value: cacheValue.value})
kvL = append(kvL, &kvPair[V]{Key: []byte(key), Value: cacheValue.value})
}
// kvL was already sorted so pass it in as is.
store.clearUnsortedCacheSubset(kvL, stateAlreadySorted)
}
func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sortState) {
func (store *GStore[V]) clearUnsortedCacheSubset(unsorted []*kvPair[V], sortState sortState) {
n := len(store.unsortedCache)
if len(unsorted) == n { // This pattern allows the Go compiler to emit the map clearing idiom for the entire map.
for key := range store.unsortedCache {
@ -396,9 +424,9 @@ func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sort
// setCacheValue is the only entrypoint to mutate store.cache.
// A `nil` value means a deletion.
func (store *Store) setCacheValue(key, value []byte, dirty bool) {
func (store *GStore[V]) setCacheValue(key []byte, value V, dirty bool) {
keyStr := conv.UnsafeBytesToStr(key)
store.cache[keyStr] = &cValue{
store.cache[keyStr] = &cValue[V]{
value: value,
dirty: dirty,
}

View File

@ -5,10 +5,6 @@ import (
"io"
"maps"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/cachekv"
"cosmossdk.io/store/dbadapter"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
)
@ -25,12 +21,11 @@ const storeNameCtxKey = "store_name"
// 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
parentStore func(types.StoreKey) types.CacheWrapper
}
var _ types.CacheMultiStore = Store{}
@ -39,26 +34,17 @@ var _ types.CacheMultiStore = Store{}
// CacheWrapper objects and a KVStore as the database. Each CacheWrapper store
// is a branched store.
func NewFromKVStore(
store types.KVStore, stores map[types.StoreKey]types.CacheWrapper,
keys map[string]types.StoreKey, traceWriter io.Writer, traceContext types.TraceContext,
stores map[types.StoreKey]types.CacheWrapper,
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() {
tctx := cms.traceContext.Clone().Merge(types.TraceContext{
storeNameCtxKey: key.Name(),
})
store = tracekv.NewStore(store.(types.KVStore), cms.traceWriter, tctx)
}
cms.stores[key] = cachekv.NewStore(store.(types.KVStore))
cms.initStore(key, store)
}
return cms
@ -67,19 +53,39 @@ func NewFromKVStore(
// NewStore creates a new Store object from a mapping of store keys to
// CacheWrapper objects. Each CacheWrapper store is a branched store.
func NewStore(
db dbm.DB, stores map[types.StoreKey]types.CacheWrapper, keys map[string]types.StoreKey,
stores map[types.StoreKey]types.CacheWrapper,
traceWriter io.Writer, traceContext types.TraceContext,
) Store {
return NewFromKVStore(dbadapter.Store{DB: db}, stores, keys, traceWriter, traceContext)
return NewFromKVStore(stores, traceWriter, traceContext)
}
func newCacheMultiStoreFromCMS(cms Store) Store {
stores := make(map[types.StoreKey]types.CacheWrapper)
for k, v := range cms.stores {
stores[k] = v
// NewFromParent constructs a cache multistore with a parent store lazily,
// the parent is usually another cache multistore or the block-stm multiversion store.
func NewFromParent(
parentStore func(types.StoreKey) types.CacheWrapper,
traceWriter io.Writer, traceContext types.TraceContext,
) Store {
return Store{
stores: make(map[types.StoreKey]types.CacheWrap),
traceWriter: traceWriter,
traceContext: traceContext,
parentStore: parentStore,
}
}
return NewFromKVStore(cms.db, stores, nil, cms.traceWriter, cms.traceContext)
func (cms Store) initStore(key types.StoreKey, store types.CacheWrapper) types.CacheWrap {
if cms.TracingEnabled() {
// only support tracing on KVStore.
if kvstore, ok := store.(types.KVStore); ok {
tctx := cms.traceContext.Clone().Merge(types.TraceContext{
storeNameCtxKey: key.Name(),
})
store = tracekv.NewStore(kvstore, cms.traceWriter, tctx)
}
}
cache := store.CacheWrap()
cms.stores[key] = cache
return cache
}
// SetTracer sets the tracer for the MultiStore that the underlying
@ -120,7 +126,6 @@ func (cms Store) GetStoreType() types.StoreType {
// Write calls Write on each underlying store.
func (cms Store) Write() {
cms.db.Write()
for _, store := range cms.stores {
store.Write()
}
@ -139,7 +144,7 @@ func (cms Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.Cac
// CacheMultiStore implements MultiStore, returns a new CacheMultiStore from the
// underlying CacheMultiStore.
func (cms Store) CacheMultiStore() types.CacheMultiStore {
return newCacheMultiStoreFromCMS(cms)
return NewFromParent(cms.getCacheWrapper, cms.traceWriter, cms.traceContext)
}
// CacheMultiStoreWithVersion implements the MultiStore interface. It will panic
@ -151,20 +156,41 @@ func (cms Store) CacheMultiStoreWithVersion(_ int64) (types.CacheMultiStore, err
panic("cannot branch cached multi-store with a version")
}
// GetStore returns an underlying Store by key.
func (cms Store) GetStore(key types.StoreKey) types.Store {
s := cms.stores[key]
if key == nil || s == nil {
func (cms Store) getCacheWrapper(key types.StoreKey) types.CacheWrapper {
store, ok := cms.stores[key]
if !ok && cms.parentStore != nil {
// load on demand
store = cms.initStore(key, cms.parentStore(key))
}
if key == nil || store == nil {
panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))
}
return s.(types.Store)
return store
}
// GetStore returns an underlying Store by key.
func (cms Store) GetStore(key types.StoreKey) types.Store {
store, ok := cms.getCacheWrapper(key).(types.Store)
if !ok {
panic(fmt.Sprintf("store with key %v is not Store", key))
}
return store
}
// GetKVStore returns an underlying KVStore by key.
func (cms Store) GetKVStore(key types.StoreKey) types.KVStore {
store := cms.stores[key]
if key == nil || store == nil {
panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))
store, ok := cms.getCacheWrapper(key).(types.KVStore)
if !ok {
panic(fmt.Sprintf("store with key %v is not KVStore", key))
}
return store.(types.KVStore)
return store
}
// GetObjKVStore returns an underlying KVStore by key.
func (cms Store) GetObjKVStore(key types.StoreKey) types.ObjKVStore {
store, ok := cms.getCacheWrapper(key).(types.ObjKVStore)
if !ok {
panic(fmt.Sprintf("store with key %v is not ObjKVStore", key))
}
return store
}

View File

@ -8,61 +8,90 @@ import (
var _ types.KVStore = &Store{}
// Store applies gas tracking to an underlying KVStore. It implements the
// KVStore interface.
type Store struct {
gasMeter types.GasMeter
gasConfig types.GasConfig
parent types.KVStore
type Store = GStore[[]byte]
func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store {
return NewGStore(parent, gasMeter, gasConfig,
types.BytesIsZero,
types.BytesValueLen,
)
}
// NewStore returns a reference to a new GasKVStore.
func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store {
kvs := &Store{
type ObjStore = GStore[any]
func NewObjStore(parent types.ObjKVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *ObjStore {
return NewGStore(parent, gasMeter, gasConfig,
types.AnyIsZero,
types.AnyValueLen,
)
}
// GStore applies gas tracking to an underlying KVStore. It implements the
// KVStore interface.
type GStore[V any] struct {
gasMeter types.GasMeter
gasConfig types.GasConfig
parent types.GKVStore[V]
isZero func(V) bool
valueLen func(V) int
}
// NewGStore returns a reference to a new GasKVStore.
func NewGStore[V any](
parent types.GKVStore[V],
gasMeter types.GasMeter,
gasConfig types.GasConfig,
isZero func(V) bool,
valueLen func(V) int,
) *GStore[V] {
kvs := &GStore[V]{
gasMeter: gasMeter,
gasConfig: gasConfig,
parent: parent,
isZero: isZero,
valueLen: valueLen,
}
return kvs
}
// GetStoreType implements Store, consuming no gas and returning the underlying
// store's type.
func (gs *Store) GetStoreType() types.StoreType {
func (gs *GStore[V]) GetStoreType() types.StoreType {
return gs.parent.GetStoreType()
}
// Get implements KVStore, consuming gas based on ReadCostFlat and the read per bytes cost.
func (gs *Store) Get(key []byte) (value []byte) {
func (gs *GStore[V]) Get(key []byte) (value V) {
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc)
value = gs.parent.Get(key)
// TODO overflow-safe math?
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasReadPerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(gs.valueLen(value)), types.GasReadPerByteDesc)
return value
}
// Set implements KVStore, consuming gas based on WriteCostFlat and the write per bytes cost.
func (gs *Store) Set(key, value []byte) {
func (gs *GStore[V]) Set(key []byte, value V) {
types.AssertValidKey(key)
types.AssertValidValue(value)
types.AssertValidValueGeneric(value, gs.isZero, gs.valueLen)
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc)
// TODO overflow-safe math?
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(key)), types.GasWritePerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(value)), types.GasWritePerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(gs.valueLen(value)), types.GasWritePerByteDesc)
gs.parent.Set(key, value)
}
// Has implements KVStore, consuming gas based on HasCost.
func (gs *Store) Has(key []byte) bool {
func (gs *GStore[V]) Has(key []byte) bool {
gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc)
return gs.parent.Has(key)
}
// Delete implements KVStore consuming gas based on DeleteCost.
func (gs *Store) Delete(key []byte) {
func (gs *GStore[V]) Delete(key []byte) {
// charge gas to prevent certain attack vectors even though space is being freed
gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, types.GasDeleteDesc)
gs.parent.Delete(key)
@ -71,7 +100,7 @@ func (gs *Store) Delete(key []byte) {
// 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 *Store) Iterator(start, end []byte) types.Iterator {
func (gs *GStore[V]) Iterator(start, end []byte) types.GIterator[V] {
return gs.iterator(start, end, true)
}
@ -79,99 +108,100 @@ func (gs *Store) Iterator(start, end []byte) types.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 *Store) ReverseIterator(start, end []byte) types.Iterator {
func (gs *GStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] {
return gs.iterator(start, end, false)
}
// CacheWrap implements KVStore - it PANICS as you cannot cache a GasKVStore.
func (gs *Store) CacheWrap() types.CacheWrap {
func (gs *GStore[V]) CacheWrap() types.CacheWrap {
panic("cannot CacheWrap a GasKVStore")
}
// CacheWrapWithTrace implements the KVStore interface.
func (gs *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap {
func (gs *GStore[V]) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap {
panic("cannot CacheWrapWithTrace a GasKVStore")
}
func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator {
var parent types.Iterator
func (gs *GStore[V]) iterator(start, end []byte, ascending bool) types.GIterator[V] {
var parent types.GIterator[V]
if ascending {
parent = gs.parent.Iterator(start, end)
} else {
parent = gs.parent.ReverseIterator(start, end)
}
gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent)
gi.(*gasIterator).consumeSeekGas()
gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent, gs.valueLen)
gi.consumeSeekGas()
return gi
}
type gasIterator struct {
type gasIterator[V any] struct {
gasMeter types.GasMeter
gasConfig types.GasConfig
parent types.Iterator
parent types.GIterator[V]
valueLen func(V) int
}
func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator) types.Iterator {
return &gasIterator{
func newGasIterator[V any](gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.GIterator[V], valueLen func(V) int) *gasIterator[V] {
return &gasIterator[V]{
gasMeter: gasMeter,
gasConfig: gasConfig,
parent: parent,
valueLen: valueLen,
}
}
// Domain implements Iterator, getting the underlying iterator's domain.
func (gi *gasIterator) Domain() (start, end []byte) {
func (gi *gasIterator[V]) Domain() (start, end []byte) {
return gi.parent.Domain()
}
// Valid implements Iterator by checking the underlying iterator.
func (gi *gasIterator) Valid() bool {
func (gi *gasIterator[V]) Valid() bool {
return gi.parent.Valid()
}
// Next implements the Iterator interface. It seeks to the next key/value pair
// in the iterator. It incurs a flat gas cost for seeking and a variable gas
// cost based on the current value's length if the iterator is valid.
func (gi *gasIterator) Next() {
func (gi *gasIterator[V]) Next() {
gi.consumeSeekGas()
gi.parent.Next()
}
// Key implements the Iterator interface. It returns the current key and it does
// not incur any gas cost.
func (gi *gasIterator) Key() (key []byte) {
func (gi *gasIterator[V]) Key() (key []byte) {
key = gi.parent.Key()
return key
}
// Value implements the Iterator interface. It returns the current value and it
// does not incur any gas cost.
func (gi *gasIterator) Value() (value []byte) {
value = gi.parent.Value()
return value
func (gi *gasIterator[V]) Value() (value V) {
return gi.parent.Value()
}
// Close implements Iterator by closing the underlying iterator.
func (gi *gasIterator) Close() error {
func (gi *gasIterator[V]) Close() error {
return gi.parent.Close()
}
// Error delegates the Error call to the parent iterator.
func (gi *gasIterator) Error() error {
func (gi *gasIterator[V]) Error() error {
return gi.parent.Error()
}
// consumeSeekGas consumes on each iteration step a flat gas cost and a variable gas cost
// based on the current value's length.
func (gi *gasIterator) consumeSeekGas() {
func (gi *gasIterator[V]) consumeSeekGas() {
if gi.Valid() {
key := gi.Key()
value := gi.Value()
gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasValuePerByteDesc)
gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasValuePerByteDesc)
gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(gi.valueLen(value)), types.GasValuePerByteDesc)
}
gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, types.GasIterNextCostFlatDesc)
}

View File

@ -1,4 +1,4 @@
package internal
package btree
import (
"bytes"
@ -22,44 +22,46 @@ var errKeyEmpty = errors.New("key cannot be empty")
// we need it to be as fast as possible, while `MemDB` is mainly used as a mocking db in unit tests.
//
// We choose tidwall/btree over google/btree here because it provides API to implement step iterator directly.
type BTree struct {
tree *btree.BTreeG[item]
type BTree[V any] struct {
tree *btree.BTreeG[item[V]]
}
// NewBTree creates a wrapper around `btree.BTreeG`.
func NewBTree() BTree {
return BTree{
tree: btree.NewBTreeGOptions(byKeys, btree.Options{
func NewBTree[V any]() BTree[V] {
return BTree[V]{
tree: btree.NewBTreeGOptions(byKeys[V], btree.Options{
Degree: bTreeDegree,
NoLocks: false,
}),
}
}
func (bt BTree) Set(key, value []byte) {
func (bt BTree[V]) Set(key []byte, value V) {
bt.tree.Set(newItem(key, value))
}
func (bt BTree) Get(key []byte) []byte {
i, found := bt.tree.Get(newItem(key, nil))
func (bt BTree[V]) Get(key []byte) V {
var empty V
i, found := bt.tree.Get(newItem(key, empty))
if !found {
return nil
return empty
}
return i.value
}
func (bt BTree) Delete(key []byte) {
bt.tree.Delete(newItem(key, nil))
func (bt BTree[V]) Delete(key []byte) {
var empty V
bt.tree.Delete(newItem(key, empty))
}
func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) {
func (bt BTree[V]) Iterator(start, end []byte) (types.GIterator[V], error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, errKeyEmpty
}
return newMemIterator(start, end, bt, true), nil
}
func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) {
func (bt BTree[V]) ReverseIterator(start, end []byte) (types.GIterator[V], error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, errKeyEmpty
}
@ -68,24 +70,28 @@ func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) {
// Copy the tree. This is a copy-on-write operation and is very fast because
// it only performs a shadowed copy.
func (bt BTree) Copy() BTree {
return BTree{
func (bt BTree[V]) Copy() BTree[V] {
return BTree[V]{
tree: bt.tree.Copy(),
}
}
func (bt BTree[V]) Clear() {
bt.tree.Clear()
}
// item is a btree item with byte slices as keys and values
type item struct {
type item[V any] struct {
key []byte
value []byte
value V
}
// byKeys compares the items by key
func byKeys(a, b item) bool {
func byKeys[V any](a, b item[V]) bool {
return bytes.Compare(a.key, b.key) == -1
}
// newItem creates a new pair item.
func newItem(key, value []byte) item {
return item{key: key, value: value}
func newItem[V any](key []byte, value V) item[V] {
return item[V]{key: key, value: value}
}

View File

@ -1,4 +1,4 @@
package internal
package btree
import (
"testing"
@ -9,7 +9,7 @@ import (
)
func TestGetSetDelete(t *testing.T) {
db := NewBTree()
db := NewBTree[[]byte]()
// A nonexistent key should return nil.
value := db.Get([]byte("a"))
@ -40,7 +40,7 @@ func TestGetSetDelete(t *testing.T) {
}
func TestDBIterator(t *testing.T) {
db := NewBTree()
db := NewBTree[[]byte]()
for i := 0; i < 10; i++ {
if i != 6 { // but skip 6.
@ -171,7 +171,7 @@ func TestDBIterator(t *testing.T) {
[]int64(nil), "reverse iterator from 2 (ex) to 4")
// Ensure that the iterators don't panic with an empty database.
db2 := NewBTree()
db2 := NewBTree[[]byte]()
itr, err = db2.Iterator(nil, nil)
require.NoError(t, err)

View File

@ -1,4 +1,4 @@
package internal
package btree
import (
"bytes"
@ -9,13 +9,13 @@ import (
"cosmossdk.io/store/types"
)
var _ types.Iterator = (*memIterator)(nil)
var _ types.Iterator = (*memIterator[[]byte])(nil)
// memIterator iterates over iterKVCache items.
// if value is nil, means it was deleted.
// Implements Iterator.
type memIterator struct {
iter btree.IterG[item]
type memIterator[V any] struct {
iter btree.IterG[item[V]]
start []byte
end []byte
@ -23,18 +23,21 @@ type memIterator struct {
valid bool
}
func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator {
func newMemIterator[V any](start, end []byte, items BTree[V], ascending bool) *memIterator[V] {
var (
valid bool
empty V
)
iter := items.tree.Iter()
var valid bool
if ascending {
if start != nil {
valid = iter.Seek(newItem(start, nil))
valid = iter.Seek(newItem(start, empty))
} else {
valid = iter.First()
}
} else {
if end != nil {
valid = iter.Seek(newItem(end, nil))
valid = iter.Seek(newItem(end, empty))
if !valid {
valid = iter.Last()
} else {
@ -46,7 +49,7 @@ func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator
}
}
mi := &memIterator{
mi := &memIterator[V]{
iter: iter,
start: start,
end: end,
@ -61,27 +64,27 @@ func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator
return mi
}
func (mi *memIterator) Domain() (start, end []byte) {
func (mi *memIterator[V]) Domain() (start, end []byte) {
return mi.start, mi.end
}
func (mi *memIterator) Close() error {
func (mi *memIterator[V]) Close() error {
mi.iter.Release()
return nil
}
func (mi *memIterator) Error() error {
func (mi *memIterator[V]) Error() error {
if !mi.Valid() {
return errors.New("invalid memIterator")
}
return nil
}
func (mi *memIterator) Valid() bool {
func (mi *memIterator[V]) Valid() bool {
return mi.valid
}
func (mi *memIterator) Next() {
func (mi *memIterator[V]) Next() {
mi.assertValid()
if mi.ascending {
@ -95,7 +98,7 @@ func (mi *memIterator) Next() {
}
}
func (mi *memIterator) keyInRange(key []byte) bool {
func (mi *memIterator[V]) keyInRange(key []byte) bool {
if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 {
return false
}
@ -105,15 +108,15 @@ func (mi *memIterator) keyInRange(key []byte) bool {
return true
}
func (mi *memIterator) Key() []byte {
func (mi *memIterator[V]) Key() []byte {
return mi.iter.Item().key
}
func (mi *memIterator) Value() []byte {
func (mi *memIterator[V]) Value() V {
return mi.iter.Item().value
}
func (mi *memIterator) assertValid() {
func (mi *memIterator[V]) assertValid() {
if err := mi.Error(); err != nil {
panic(err)
}

View File

@ -0,0 +1,59 @@
package internal
import (
"io"
"cosmossdk.io/store/cachekv"
"cosmossdk.io/store/internal/btree"
"cosmossdk.io/store/types"
)
var _ types.KVStore = (*BTreeStore[[]byte])(nil)
// BTreeStore is a wrapper for a BTree with GKVStore[V] implementation
type BTreeStore[V any] struct {
btree.BTree[V]
isZero func(V) bool
valueLen func(V) int
}
// NewBTreeStore constructs new BTree adapter
func NewBTreeStore[V any](btree btree.BTree[V], isZero func(V) bool, valueLen func(V) int) *BTreeStore[V] {
return &BTreeStore[V]{btree, isZero, valueLen}
}
// Has Implements GKVStore.
func (ts *BTreeStore[V]) Has(key []byte) bool {
return !ts.isZero(ts.Get(key))
}
func (ts *BTreeStore[V]) Iterator(start, end []byte) types.GIterator[V] {
it, err := ts.BTree.Iterator(start, end)
if err != nil {
panic(err)
}
return it
}
func (ts *BTreeStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] {
it, err := ts.BTree.ReverseIterator(start, end)
if err != nil {
panic(err)
}
return it
}
// GetStoreType returns the type of the store.
func (ts *BTreeStore[V]) GetStoreType() types.StoreType {
return types.StoreTypeDB
}
// CacheWrap branches the underlying store.
func (ts *BTreeStore[V]) CacheWrap() types.CacheWrap {
return cachekv.NewGStore(ts, ts.isZero, ts.valueLen)
}
// CacheWrapWithTrace branches the underlying store.
func (ts *BTreeStore[V]) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap {
return cachekv.NewGStore(ts, ts.isZero, ts.valueLen)
}

View File

@ -3,6 +3,7 @@ package listenkv
import (
"io"
"cosmossdk.io/store/cachekv"
"cosmossdk.io/store/types"
)
@ -129,10 +130,9 @@ func (s *Store) GetStoreType() types.StoreType {
return s.parent.GetStoreType()
}
// CacheWrap implements the KVStore interface. It panics as a Store
// cannot be cache wrapped.
// CacheWrap implements the KVStore interface. It branches the kv store via creating a new cachekv around s.
func (s *Store) CacheWrap() types.CacheWrap {
panic("cannot CacheWrap a ListenKVStore")
return cachekv.NewStore(s)
}
// CacheWrapWithTrace implements the KVStore interface. It panics as a

View File

@ -272,7 +272,7 @@ func TestListenKVStoreGetStoreType(t *testing.T) {
func TestListenKVStoreCacheWrap(t *testing.T) {
store := newEmptyListenKVStore(nil)
require.Panics(t, func() { store.CacheWrap() })
store.CacheWrap()
}
func TestListenKVStoreCacheWrapWithTrace(t *testing.T) {

View File

@ -10,20 +10,53 @@ import (
"cosmossdk.io/store/types"
)
var _ types.KVStore = Store{}
type (
Store = GStore[[]byte]
ObjStore = GStore[any]
)
// Store is similar to cometbft/cometbft/libs/db/prefix_db
// both give access only to the limited subset of the store
// for convenience or safety
type Store struct {
parent types.KVStore
prefix []byte
}
var (
_ types.KVStore = Store{}
_ types.ObjKVStore = ObjStore{}
)
func NewStore(parent types.KVStore, prefix []byte) Store {
return Store{
return NewGStore(
parent, prefix,
types.BytesIsZero,
types.BytesValueLen,
)
}
func NewObjStore(parent types.ObjKVStore, prefix []byte) ObjStore {
return NewGStore(
parent, prefix,
types.AnyIsZero,
types.AnyValueLen,
)
}
// GStore is similar to cometbft/cometbft/libs/db/prefix_db
// both give access only to the limited subset of the store
// for convenience or safety
type GStore[V any] struct {
parent types.GKVStore[V]
prefix []byte
isZero func(V) bool
valueLen func(V) int
}
func NewGStore[V any](
parent types.GKVStore[V], prefix []byte,
isZero func(V) bool, valueLen func(V) int,
) GStore[V] {
return GStore[V]{
parent: parent,
prefix: prefix,
isZero: isZero,
valueLen: valueLen,
}
}
@ -34,7 +67,7 @@ func cloneAppend(bz, tail []byte) (res []byte) {
return res
}
func (s Store) key(key []byte) (res []byte) {
func (s GStore[V]) key(key []byte) (res []byte) {
if key == nil {
panic("nil key on Store")
}
@ -43,46 +76,50 @@ func (s Store) key(key []byte) (res []byte) {
}
// GetStoreType implements Store, returning the parent store's type
func (s Store) GetStoreType() types.StoreType {
func (s GStore[V]) GetStoreType() types.StoreType {
return s.parent.GetStoreType()
}
// CacheWrap implements CacheWrap, returning a new CacheWrap with the parent store as the underlying store
func (s Store) CacheWrap() types.CacheWrap {
return cachekv.NewStore(s)
func (s GStore[V]) CacheWrap() types.CacheWrap {
return cachekv.NewGStore(s, s.isZero, s.valueLen)
}
// CacheWrapWithTrace implements the KVStore interface.
func (s Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap {
return cachekv.NewStore(tracekv.NewStore(s, w, tc))
func (s GStore[V]) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap {
// We need to make a type assertion here as the tracekv store requires bytes value types for serialization.
if store, ok := any(s).(*GStore[[]byte]); ok {
return cachekv.NewGStore(tracekv.NewStore(store, w, tc), store.isZero, store.valueLen)
}
return s.CacheWrap()
}
// Get implements KVStore, calls Get on the parent store with the key prefixed with the prefix
func (s Store) Get(key []byte) []byte {
func (s GStore[V]) Get(key []byte) V {
res := s.parent.Get(s.key(key))
return res
}
// Has implements KVStore, calls Has on the parent store with the key prefixed with the prefix
func (s Store) Has(key []byte) bool {
func (s GStore[V]) Has(key []byte) bool {
return s.parent.Has(s.key(key))
}
// Set implements KVStore, calls Set on the parent store with the key prefixed with the prefix
func (s Store) Set(key, value []byte) {
func (s GStore[V]) Set(key []byte, value V) {
types.AssertValidKey(key)
types.AssertValidValue(value)
types.AssertValidValueGeneric(value, s.isZero, s.valueLen)
s.parent.Set(s.key(key), value)
}
// Delete implements KVStore, calls Delete on the parent store with the key prefixed with the prefix
func (s Store) Delete(key []byte) {
func (s GStore[V]) Delete(key []byte) {
s.parent.Delete(s.key(key))
}
// Iterator implements KVStore
// Check https://github.com/cometbft/cometbft-db/blob/main/prefixdb_iterator.go#L106
func (s Store) Iterator(start, end []byte) types.Iterator {
func (s GStore[V]) Iterator(start, end []byte) types.GIterator[V] {
newStart := cloneAppend(s.prefix, start)
var newEnd []byte
@ -99,7 +136,7 @@ func (s Store) Iterator(start, end []byte) types.Iterator {
// ReverseIterator implements KVStore
// Check https://github.com/cometbft/cometbft-db/blob/main/prefixdb_iterator.go#L129
func (s Store) ReverseIterator(start, end []byte) types.Iterator {
func (s GStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] {
newstart := cloneAppend(s.prefix, start)
var newend []byte
@ -114,18 +151,18 @@ func (s Store) ReverseIterator(start, end []byte) types.Iterator {
return newPrefixIterator(s.prefix, start, end, iter)
}
var _ types.Iterator = (*prefixIterator)(nil)
var _ types.Iterator = (*prefixIterator[[]byte])(nil)
type prefixIterator struct {
type prefixIterator[V any] struct {
prefix []byte
start []byte
end []byte
iter types.Iterator
iter types.GIterator[V]
valid bool
}
func newPrefixIterator(prefix, start, end []byte, parent types.Iterator) *prefixIterator {
return &prefixIterator{
func newPrefixIterator[V any](prefix, start, end []byte, parent types.GIterator[V]) *prefixIterator[V] {
return &prefixIterator[V]{
prefix: prefix,
start: start,
end: end,
@ -135,17 +172,17 @@ func newPrefixIterator(prefix, start, end []byte, parent types.Iterator) *prefix
}
// Domain implements Iterator, returning the start and end keys of the prefixIterator.
func (pi *prefixIterator) Domain() ([]byte, []byte) {
func (pi *prefixIterator[V]) Domain() ([]byte, []byte) {
return pi.start, pi.end
}
// Valid implements Iterator, checking if the prefixIterator is valid and if the underlying iterator is valid.
func (pi *prefixIterator) Valid() bool {
func (pi *prefixIterator[V]) Valid() bool {
return pi.valid && pi.iter.Valid()
}
// Next implements Iterator, moving the underlying iterator to the next key/value pair that starts with the prefix.
func (pi *prefixIterator) Next() {
func (pi *prefixIterator[V]) Next() {
if !pi.valid {
panic("prefixIterator invalid, cannot call Next()")
}
@ -157,7 +194,7 @@ func (pi *prefixIterator) Next() {
}
// Key implements Iterator, returning the stripped prefix key
func (pi *prefixIterator) Key() (key []byte) {
func (pi *prefixIterator[V]) Key() (key []byte) {
if !pi.valid {
panic("prefixIterator invalid, cannot call Key()")
}
@ -169,7 +206,7 @@ func (pi *prefixIterator) Key() (key []byte) {
}
// Implements Iterator
func (pi *prefixIterator) Value() []byte {
func (pi *prefixIterator[V]) Value() V {
if !pi.valid {
panic("prefixIterator invalid, cannot call Value()")
}
@ -178,13 +215,13 @@ func (pi *prefixIterator) Value() []byte {
}
// Close implements Iterator, closing the underlying iterator.
func (pi *prefixIterator) Close() error {
func (pi *prefixIterator[V]) Close() error {
return pi.iter.Close()
}
// Error returns an error if the prefixIterator is invalid defined by the Valid
// method.
func (pi *prefixIterator) Error() error {
func (pi *prefixIterator[V]) Error() error {
if !pi.Valid() {
return errors.New("invalid prefixIterator")
}

View File

@ -13,6 +13,7 @@ import (
"cosmossdk.io/store/dbadapter"
"cosmossdk.io/store/gaskv"
"cosmossdk.io/store/iavl"
"cosmossdk.io/store/transient"
"cosmossdk.io/store/types"
"cosmossdk.io/store/wrapper"
)
@ -54,6 +55,52 @@ func setRandomKVPairs(t *testing.T, store types.KVStore) []kvpair {
return kvps
}
func setRandomObjKVPairs(t *testing.T, store types.ObjKVStore) []kvpair {
t.Helper()
kvps := genRandomKVPairs(t)
for _, kvp := range kvps {
store.Set(kvp.key, kvp.value)
}
return kvps
}
func TestObjStorePrefix(t *testing.T) {
baseStore := transient.NewObjStore()
prefix := []byte("test")
prefixStore := NewObjStore(baseStore, prefix)
prefixPrefixStore := NewObjStore(prefixStore, []byte("prefix"))
require.Panics(t, func() { prefixStore.Get(nil) })
require.Panics(t, func() { prefixStore.Set(nil, []byte{}) })
kvps := setRandomObjKVPairs(t, prefixPrefixStore)
for i := 0; i < 20; i++ {
key := kvps[i].key
value := kvps[i].value
require.True(t, prefixPrefixStore.Has(key))
require.Equal(t, value, prefixPrefixStore.Get(key))
key = append([]byte("prefix"), key...)
require.True(t, prefixStore.Has(key))
require.Equal(t, value, prefixStore.Get(key))
key = append(prefix, key...)
require.True(t, baseStore.Has(key))
require.Equal(t, value, baseStore.Get(key))
key = kvps[i].key
prefixPrefixStore.Delete(key)
require.False(t, prefixPrefixStore.Has(key))
require.Nil(t, prefixPrefixStore.Get(key))
key = append([]byte("prefix"), key...)
require.False(t, prefixStore.Has(key))
require.Nil(t, prefixStore.Get(key))
key = append(prefix, key...)
require.False(t, baseStore.Has(key))
require.Nil(t, baseStore.Get(key))
}
}
func testPrefixStore(t *testing.T, baseStore types.KVStore, prefix []byte) {
t.Helper()
prefixStore := NewStore(baseStore, prefix)
@ -105,6 +152,32 @@ func TestPrefixKVStoreNoNilSet(t *testing.T) {
require.Panics(t, func() { gasStore.Set([]byte("key"), nil) }, "setting a nil value should panic")
}
func TestObjPrefixStoreIterate(t *testing.T) {
db := dbm.NewMemDB()
baseStore := dbadapter.Store{DB: db}
prefix := []byte("test")
prefixObjStore := NewObjStore(transient.NewObjStore(), prefix)
setRandomObjKVPairs(t, prefixObjStore)
bIter := types.KVStorePrefixIterator(baseStore, prefix)
objIter := prefixObjStore.Iterator(nil, nil)
start, end := objIter.Domain()
require.Equal(t, start, end)
for bIter.Valid() && objIter.Valid() {
require.Equal(t, bIter.Key(), append(prefix, objIter.Key()...))
require.Equal(t, bIter.Value(), objIter.Value())
bIter.Next()
objIter.Next()
}
bIter.Close()
objIter.Close()
}
func TestPrefixStoreIterate(t *testing.T) {
db := dbm.NewMemDB()
baseStore := dbadapter.Store{DB: db}

View File

@ -67,9 +67,10 @@ type Store struct {
// iavlSyncPruning should rarely be set to true.
// The Prune command will automatically set this to true.
// This allows the prune command to wait for the pruning to finish before returning.
iavlSyncPruning bool
storesParams map[types.StoreKey]storeParams
stores map[types.StoreKey]types.CommitKVStore
iavlSyncPruning bool
storesParams map[types.StoreKey]storeParams
// CommitStore is a common interface to unify generic CommitKVStore of different value types
stores map[types.StoreKey]types.CommitStore
keysByName map[string]types.StoreKey
initialVersion int64
removalMap map[types.StoreKey]bool
@ -99,7 +100,7 @@ func NewStore(db dbm.DB, logger log.Logger, metricGatherer metrics.StoreMetrics)
iavlCacheSize: iavl.DefaultIAVLCacheSize,
iavlDisableFastNode: iavlDisablefastNodeDefault,
storesParams: make(map[types.StoreKey]storeParams),
stores: make(map[types.StoreKey]types.CommitKVStore),
stores: make(map[types.StoreKey]types.CommitStore),
keysByName: make(map[string]types.StoreKey),
listeners: make(map[types.StoreKey]*types.MemoryListener),
removalMap: make(map[types.StoreKey]bool),
@ -166,12 +167,6 @@ func (rs *Store) MountStoreWithDB(key types.StoreKey, typ types.StoreType, db db
// GetCommitStore returns a mounted CommitStore for a given StoreKey. If the
// store is wrapped in an inter-block cache, it will be unwrapped before returning.
func (rs *Store) GetCommitStore(key types.StoreKey) types.CommitStore {
return rs.GetCommitKVStore(key)
}
// GetCommitKVStore returns a mounted CommitKVStore for a given StoreKey. If the
// store is wrapped in an inter-block cache, it will be unwrapped before returning.
func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore {
// If the Store has an inter-block cache, first attempt to lookup and unwrap
// the underlying CommitKVStore by StoreKey. If it does not exist, fallback to
// the main mapping of CommitKVStores.
@ -184,6 +179,17 @@ func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore {
return rs.stores[key]
}
// GetCommitKVStore returns a mounted CommitKVStore for a given StoreKey. If the
// store is wrapped in an inter-block cache, it will be unwrapped before returning.
func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore {
store, ok := rs.GetCommitStore(key).(types.CommitKVStore)
if !ok {
panic(fmt.Sprintf("store with key %v is not CommitKVStore", key))
}
return store
}
// StoreKeysByName returns mapping storeNames -> StoreKeys
func (rs *Store) StoreKeysByName() map[string]types.StoreKey {
return rs.keysByName
@ -232,7 +238,7 @@ func (rs *Store) loadVersion(ver int64, upgrades *types.StoreUpgrades) error {
}
// load each Store (note this doesn't panic on unmounted keys now)
newStores := make(map[types.StoreKey]types.CommitKVStore)
newStores := make(map[types.StoreKey]types.CommitStore)
storesKeys := make([]types.StoreKey, 0, len(rs.storesParams))
@ -574,15 +580,17 @@ func (rs *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.Cac
func (rs *Store) CacheMultiStore() types.CacheMultiStore {
stores := make(map[types.StoreKey]types.CacheWrapper)
for k, v := range rs.stores {
store := types.KVStore(v)
// Wire the listenkv.Store to allow listeners to observe the writes from the cache store,
// set same listeners on cache store will observe duplicated writes.
if rs.ListeningEnabled(k) {
store = listenkv.NewStore(store, k, rs.listeners[k])
store := types.CacheWrapper(v)
if kv, ok := store.(types.KVStore); ok {
// Wire the listenkv.Store to allow listeners to observe the writes from the cache store,
// set same listeners on cache store will observe duplicated writes.
if rs.ListeningEnabled(k) {
store = listenkv.NewStore(kv, k, rs.listeners[k])
}
}
stores[k] = store
}
return cachemulti.NewStore(rs.db, stores, rs.keysByName, rs.traceWriter, rs.getTracingContext())
return cachemulti.NewStore(stores, rs.traceWriter, rs.getTracingContext())
}
// CacheMultiStoreWithVersion is analogous to CacheMultiStore except that it
@ -594,7 +602,7 @@ func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStor
var commitInfo *types.CommitInfo
storeInfos := map[string]bool{}
for key, store := range rs.stores {
var cacheStore types.KVStore
var cacheStore types.CacheWrapper
switch store.GetStoreType() {
case types.StoreTypeIAVL:
// If the store is wrapped with an inter-block cache, we must first unwrap
@ -637,16 +645,18 @@ func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStor
cacheStore = store
}
// Wire the listenkv.Store to allow listeners to observe the writes from the cache store,
// set same listeners on cache store will observe duplicated writes.
if rs.ListeningEnabled(key) {
cacheStore = listenkv.NewStore(cacheStore, key, rs.listeners[key])
if kv, ok := cacheStore.(types.KVStore); ok {
// Wire the listenkv.Store to allow listeners to observe the writes from the cache store,
// set same listeners on cache store will observe duplicated writes.
if rs.ListeningEnabled(key) {
cacheStore = listenkv.NewStore(kv, key, rs.listeners[key])
}
}
cachedStores[key] = cacheStore
}
return cachemulti.NewStore(rs.db, cachedStores, rs.keysByName, rs.traceWriter, rs.getTracingContext()), nil
return cachemulti.NewStore(cachedStores, rs.traceWriter, rs.getTracingContext()), nil
}
// GetStore returns a mounted Store for a given StoreKey. If the StoreKey does
@ -656,7 +666,7 @@ func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStor
// TODO: This isn't used directly upstream. Consider returning the Store as-is
// instead of unwrapping.
func (rs *Store) GetStore(key types.StoreKey) types.Store {
store := rs.GetCommitKVStore(key)
store := rs.GetCommitStore(key)
if store == nil {
panic(fmt.Sprintf("store does not exist for key: %s", key.Name()))
}
@ -675,7 +685,10 @@ func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore {
if s == nil {
panic(fmt.Sprintf("store does not exist for key: %s", key.Name()))
}
store := types.KVStore(s)
store, ok := s.(types.KVStore)
if !ok {
panic(fmt.Sprintf("store with key %v is not KVStore", key))
}
if rs.TracingEnabled() {
store = tracekv.NewStore(store, rs.traceWriter, rs.getTracingContext())
@ -687,6 +700,20 @@ func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore {
return store
}
// GetObjKVStore returns a mounted ObjKVStore for a given StoreKey.
func (rs *Store) GetObjKVStore(key types.StoreKey) types.ObjKVStore {
s := rs.stores[key]
if s == nil {
panic(fmt.Sprintf("store does not exist for key: %s", key.Name()))
}
store, ok := s.(types.ObjKVStore)
if !ok {
panic(fmt.Sprintf("store with key %v is not ObjKVStore", key))
}
return store
}
func (rs *Store) handlePruning(version int64) error {
pruneHeight := rs.pruningManager.GetPruningHeight(version)
rs.logger.Debug("prune start", "height", version)
@ -738,7 +765,7 @@ func (rs *Store) GetStoreByName(name string) types.Store {
return nil
}
return rs.GetCommitKVStore(key)
return rs.GetCommitStore(key)
}
// Query calls substore.Query with the same `req` where `req.Path` is
@ -852,10 +879,10 @@ func (rs *Store) Snapshot(height uint64, protoWriter protoio.Writer) error {
stores := []namedStore{}
keys := keysFromStoreKeyMap(rs.stores)
for _, key := range keys {
switch store := rs.GetCommitKVStore(key).(type) {
switch store := rs.GetCommitStore(key).(type) {
case *iavl.Store:
stores = append(stores, namedStore{name: key.Name(), Store: store})
case *transient.Store, *mem.Store:
case *transient.Store, *mem.Store, *transient.ObjStore:
// Non-persisted stores shouldn't be snapshotted
continue
default:
@ -1015,7 +1042,7 @@ loop:
return snapshotItem, rs.LoadLatestVersion()
}
func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitKVStore, error) {
func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitStore, error) {
var db dbm.DB
if params.db != nil {
@ -1047,20 +1074,26 @@ func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID
return commitDBStoreAdapter{Store: dbadapter.Store{DB: db}}, nil
case types.StoreTypeTransient:
_, ok := key.(*types.TransientStoreKey)
if !ok {
return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String())
if _, ok := key.(*types.TransientStoreKey); !ok {
return nil, fmt.Errorf("unexpected key type for a TransientStoreKey; got: %s, %T", key.String(), key)
}
return transient.NewStore(), nil
case types.StoreTypeMemory:
if _, ok := key.(*types.MemoryStoreKey); !ok {
return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String())
return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s, %T", key.String(), key)
}
return mem.NewStore(), nil
case types.StoreTypeObject:
if _, ok := key.(*types.ObjectStoreKey); !ok {
return nil, fmt.Errorf("unexpected key type for a ObjectStoreKey; got: %s, %T", key.String(), key)
}
return transient.NewObjStore(), nil
default:
panic(fmt.Sprintf("unrecognized store type %v", params.typ))
}
@ -1072,7 +1105,7 @@ func (rs *Store) buildCommitInfo(version int64) *types.CommitInfo {
for _, key := range keys {
store := rs.stores[key]
storeType := store.GetStoreType()
if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory {
if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory || storeType == types.StoreTypeObject {
continue
}
storeInfos = append(storeInfos, types.StoreInfo{
@ -1190,7 +1223,7 @@ func GetLatestVersion(db dbm.DB) int64 {
}
// commitStores commits each store and returns a new commitInfo.
func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore, removalMap map[types.StoreKey]bool) *types.CommitInfo {
func commitStores(version int64, storeMap map[types.StoreKey]types.CommitStore, removalMap map[types.StoreKey]bool) *types.CommitInfo {
storeInfos := make([]types.StoreInfo, 0, len(storeMap))
storeKeys := keysFromStoreKeyMap(storeMap)
@ -1210,7 +1243,7 @@ func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore
}
storeType := store.GetStoreType()
if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory {
if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory || storeType == types.StoreTypeObject {
continue
}

View File

@ -20,6 +20,7 @@ import (
sdkmaps "cosmossdk.io/store/internal/maps"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
"cosmossdk.io/store/transient"
"cosmossdk.io/store/types"
)
@ -29,6 +30,23 @@ func TestStoreType(t *testing.T) {
store.MountStoreWithDB(types.NewKVStoreKey("store1"), types.StoreTypeIAVL, db)
}
func TestGetObjKVStore(t *testing.T) {
var db dbm.DB = dbm.NewMemDB()
ms := newMultiStoreWithMounts(db, pruningtypes.NewPruningOptions(pruningtypes.PruningDefault))
err := ms.LoadLatestVersion()
require.Nil(t, err)
key := ms.keysByName["store6"]
store1 := ms.GetObjKVStore(key)
require.NotNil(t, store1)
require.IsType(t, &transient.ObjStore{}, store1)
store2 := ms.GetCommitStore(key)
require.NotNil(t, store2)
require.IsType(t, &transient.ObjStore{}, store2)
}
func TestGetCommitKVStore(t *testing.T) {
var db dbm.DB = dbm.NewMemDB()
ms := newMultiStoreWithMounts(db, pruningtypes.NewPruningOptions(pruningtypes.PruningDefault))
@ -938,6 +956,8 @@ var (
testStoreKey1 = types.NewKVStoreKey("store1")
testStoreKey2 = types.NewKVStoreKey("store2")
testStoreKey3 = types.NewKVStoreKey("store3")
testStoreKey4 = types.NewKVStoreKey("store4")
testStoreKey6 = types.NewObjectStoreKey("store6")
)
func newMultiStoreWithMounts(db dbm.DB, pruningOpts pruningtypes.PruningOptions) *Store {
@ -947,6 +967,7 @@ func newMultiStoreWithMounts(db dbm.DB, pruningOpts pruningtypes.PruningOptions)
store.MountStoreWithDB(testStoreKey1, types.StoreTypeIAVL, nil)
store.MountStoreWithDB(testStoreKey2, types.StoreTypeIAVL, nil)
store.MountStoreWithDB(testStoreKey3, types.StoreTypeIAVL, nil)
store.MountStoreWithDB(testStoreKey6, types.StoreTypeObject, nil)
return store
}
@ -1010,9 +1031,12 @@ func getExpectedCommitID(store *Store, ver int64) types.CommitID {
}
}
func hashStores(stores map[types.StoreKey]types.CommitKVStore) []byte {
func hashStores(stores map[types.StoreKey]types.CommitStore) []byte {
m := make(map[string][]byte, len(stores))
for key, store := range stores {
if store.GetStoreType() != types.StoreTypeIAVL {
continue
}
name := key.Name()
m[name] = types.StoreInfo{
Name: name,
@ -1065,35 +1089,40 @@ func TestStateListeners(t *testing.T) {
require.Empty(t, ms.PopStateCache())
}
type commitKVStoreStub struct {
types.CommitKVStore
type commitStoreStub struct {
types.CommitStore
Committed int
}
func (stub *commitKVStoreStub) Commit() types.CommitID {
commitID := stub.CommitKVStore.Commit()
func (stub *commitStoreStub) Commit() types.CommitID {
commitID := stub.CommitStore.Commit()
stub.Committed++
return commitID
}
func prepareStoreMap() (map[types.StoreKey]types.CommitKVStore, error) {
func prepareStoreMap() (map[types.StoreKey]types.CommitStore, error) {
var db dbm.DB = dbm.NewMemDB()
store := NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics())
store.MountStoreWithDB(types.NewKVStoreKey("iavl1"), types.StoreTypeIAVL, nil)
store.MountStoreWithDB(types.NewKVStoreKey("iavl2"), types.StoreTypeIAVL, nil)
store.MountStoreWithDB(types.NewTransientStoreKey("trans1"), types.StoreTypeTransient, nil)
store.MountStoreWithDB(types.NewMemoryStoreKey("mem1"), types.StoreTypeMemory, nil)
store.MountStoreWithDB(types.NewObjectStoreKey("obj1"), types.StoreTypeObject, nil)
if err := store.LoadLatestVersion(); err != nil {
return nil, err
}
return map[types.StoreKey]types.CommitKVStore{
testStoreKey1: &commitKVStoreStub{
CommitKVStore: store.GetStoreByName("iavl1").(types.CommitKVStore),
return map[types.StoreKey]types.CommitStore{
testStoreKey1: &commitStoreStub{
CommitStore: store.GetStoreByName("iavl1").(types.CommitStore),
},
testStoreKey2: &commitKVStoreStub{
CommitKVStore: store.GetStoreByName("iavl2").(types.CommitKVStore),
testStoreKey2: &commitStoreStub{
CommitStore: store.GetStoreByName("iavl2").(types.CommitStore),
},
testStoreKey3: &commitKVStoreStub{
CommitKVStore: store.GetStoreByName("trans1").(types.CommitKVStore),
testStoreKey3: &commitStoreStub{
CommitStore: store.GetStoreByName("trans1").(types.CommitStore),
},
testStoreKey4: &commitStoreStub{
CommitStore: store.GetStoreByName("obj1").(types.CommitStore),
},
}, nil
}
@ -1124,7 +1153,7 @@ func TestCommitStores(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
storeMap, err := prepareStoreMap()
require.NoError(t, err)
store := storeMap[testStoreKey1].(*commitKVStoreStub)
store := storeMap[testStoreKey1].(*commitStoreStub)
for i := tc.committed; i > 0; i-- {
store.Commit()
}

View File

@ -161,10 +161,9 @@ func (tkv *Store) GetStoreType() types.StoreType {
return tkv.parent.GetStoreType()
}
// CacheWrap implements the KVStore interface. It panics because a Store
// cannot be branched.
// CacheWrap implements CacheWrapper.
func (tkv *Store) CacheWrap() types.CacheWrap {
panic("cannot CacheWrap a TraceKVStore")
return tkv.parent.CacheWrap()
}
// CacheWrapWithTrace implements the KVStore interface. It panics as a

View File

@ -283,7 +283,7 @@ func TestTraceKVStoreGetStoreType(t *testing.T) {
func TestTraceKVStoreCacheWrap(t *testing.T) {
store := newEmptyTraceKVStore(nil)
require.Panics(t, func() { store.CacheWrap() })
store.CacheWrap()
}
func TestTraceKVStoreCacheWrapWithTrace(t *testing.T) {

View File

@ -1,9 +1,8 @@
package transient
import (
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/dbadapter"
"cosmossdk.io/store/internal"
"cosmossdk.io/store/internal/btree"
pruningtypes "cosmossdk.io/store/pruning/types"
"cosmossdk.io/store/types"
)
@ -11,43 +10,73 @@ import (
var (
_ types.Committer = (*Store)(nil)
_ types.KVStore = (*Store)(nil)
_ types.Committer = (*ObjStore)(nil)
_ types.ObjKVStore = (*ObjStore)(nil)
)
// Store is a wrapper for a MemDB with Committer implementation
type Store struct {
dbadapter.Store
// GStore is a wrapper for a MemDB with Committer implementation
type GStore[V any] struct {
internal.BTreeStore[V]
}
// NewGStore constructs new generic transient store
func NewGStore[V any](isZero func(V) bool, valueLen func(V) int) *GStore[V] {
return &GStore[V]{*internal.NewBTreeStore(btree.NewBTree[V](), isZero, valueLen)}
}
// Store specializes GStore for []byte
type Store struct {
GStore[[]byte]
}
// NewStore constructs new MemDB adapter
func NewStore() *Store {
return &Store{Store: dbadapter.Store{DB: dbm.NewMemDB()}}
return &Store{*NewGStore(
types.BytesIsZero,
types.BytesValueLen,
)}
}
func (*Store) GetStoreType() types.StoreType {
return types.StoreTypeTransient
}
// ObjStore specializes GStore for any
type ObjStore struct {
GStore[any]
}
func NewObjStore() *ObjStore {
return &ObjStore{*NewGStore(
types.AnyIsZero,
types.AnyValueLen,
)}
}
func (*ObjStore) GetStoreType() types.StoreType {
return types.StoreTypeObject
}
// Commit cleans up Store.
// Implements CommitStore
func (ts *Store) Commit() (id types.CommitID) {
ts.Store = dbadapter.Store{DB: dbm.NewMemDB()}
func (ts *GStore[V]) Commit() (id types.CommitID) {
ts.Clear()
return id
}
func (ts *Store) SetPruning(_ pruningtypes.PruningOptions) {}
func (ts *GStore[V]) SetPruning(_ pruningtypes.PruningOptions) {}
// GetPruning is a no-op as pruning options cannot be directly set on this store.
// They must be set on the root commit multi-store.
func (ts *Store) GetPruning() pruningtypes.PruningOptions {
func (ts *GStore[V]) GetPruning() pruningtypes.PruningOptions {
return pruningtypes.NewPruningOptions(pruningtypes.PruningUndefined)
}
// LastCommitID implements CommitStore, returns empty CommitID.
func (ts *Store) LastCommitID() types.CommitID {
func (ts *GStore[V]) LastCommitID() types.CommitID {
return types.CommitID{}
}
func (ts *Store) WorkingHash() []byte {
func (ts *GStore[V]) WorkingHash() []byte {
return []byte{}
}
// GetStoreType implements Store, returns StoreTypeTransient.
func (ts *Store) GetStoreType() types.StoreType {
return types.StoreTypeTransient
}

View File

@ -128,6 +128,7 @@ type MultiStore interface {
// If the store does not exist, panics.
GetStore(StoreKey) Store
GetKVStore(StoreKey) KVStore
GetObjKVStore(StoreKey) ObjKVStore
// TracingEnabled returns if tracing is enabled for the MultiStore.
TracingEnabled() bool
@ -227,25 +228,25 @@ type CommitMultiStore interface {
//---------subsp-------------------------------
// KVStore
// BasicKVStore is a simple interface to get/set data
type BasicKVStore interface {
// GBasicKVStore is a simple interface to get/set data
type GBasicKVStore[V any] interface {
// Get returns nil if key doesn't exist. Panics on nil key.
Get(key []byte) []byte
Get(key []byte) V
// 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)
Set(key []byte, value V)
// Delete deletes the key. Panics on nil key.
Delete(key []byte)
}
// KVStore additionally provides iteration and deletion
type KVStore interface {
// GKVStore additionally provides iteration and deletion
type GKVStore[V any] interface {
Store
BasicKVStore
GBasicKVStore[V]
// Iterator over a domain of keys in ascending order. End is exclusive.
// Start must be less than end, or the Iterator is invalid.
@ -253,18 +254,54 @@ type KVStore interface {
// 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(start, end []byte) GIterator[V]
// 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
ReverseIterator(start, end []byte) GIterator[V]
}
// Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
// GIterator is the generic version of dbm's Iterator
type GIterator[V any] interface {
// Domain returns the start (inclusive) and end (exclusive) limits of the iterator.
// CONTRACT: start, end readonly []byte
Domain() (start, end []byte)
// Valid returns whether the current iterator is valid. Once invalid, the Iterator remains
// invalid forever.
Valid() bool
// Next moves the iterator to the next key in the database, as defined by order of iteration.
// If Valid returns false, this method will panic.
Next()
// Key returns the key at the current position. Panics if the iterator is invalid.
// CONTRACT: key readonly []byte
Key() (key []byte)
// Value returns the value at the current position. Panics if the iterator is invalid.
// CONTRACT: value readonly []byte
Value() (value V)
// Error returns the last error encountered by the iterator, if any.
Error() error
// Close closes the iterator, relasing any allocated resources.
Close() error
}
type (
Iterator = GIterator[[]byte]
BasicKVStore = GBasicKVStore[[]byte]
KVStore = GKVStore[[]byte]
ObjIterator = GIterator[any]
ObjBasicKVStore = GBasicKVStore[any]
ObjKVStore = GKVStore[any]
)
// CacheKVStore branches a KVStore and provides read cache functionality.
// After calling .Write() on the CacheKVStore, all previously created
@ -290,14 +327,10 @@ type CommitKVStore interface {
// a Committer, since Commit ephemeral store make no sense. It can return KVStore,
// HeapStore, SpaceStore, etc.
type CacheWrap interface {
CacheWrapper
// 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 {
@ -330,6 +363,7 @@ const (
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
StoreTypeObject
)
func (st StoreType) String() string {
@ -354,6 +388,9 @@ func (st StoreType) String() string {
case StoreTypePersistent:
return "StoreTypePersistent"
case StoreTypeObject:
return "StoreTypeObject"
}
return "unknown store type"
@ -433,6 +470,29 @@ func (key *TransientStoreKey) String() string {
return fmt.Sprintf("TransientStoreKey{%p, %s}", key, key.name)
}
// ObjectStoreKey is used for indexing transient stores in a MultiStore
type ObjectStoreKey struct {
name string
}
// NewObjectStoreKey constructs new ObjectStoreKey
// Must return a pointer according to the ocap principle
func NewObjectStoreKey(name string) *ObjectStoreKey {
return &ObjectStoreKey{
name: name,
}
}
// Name returns the name of the ObjectStoreKey
func (key *ObjectStoreKey) Name() string {
return key.name
}
// String returns a string representation of the ObjectStoreKey
func (key *ObjectStoreKey) String() string {
return fmt.Sprintf("ObjectStoreKey{%p, %s}", key, key.name)
}
// MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
@ -526,3 +586,17 @@ func NewMemoryStoreKeys(names ...string) map[string]*MemoryStoreKey {
return keys
}
// NewObjectStoreKeys constructs a new map matching store key names to their
// respective ObjectStoreKey references.
// The function will panic if there is a potential conflict in names (see `assertNoPrefix`
// function for more details).
func NewObjectStoreKeys(names ...string) map[string]*ObjectStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*ObjectStoreKey)
for _, n := range names {
keys[n] = NewObjectStoreKey(n)
}
return keys
}

View File

@ -99,6 +99,14 @@ func TestTransientStoreKey(t *testing.T) {
require.Equal(t, fmt.Sprintf("TransientStoreKey{%p, test}", key), key.String())
}
func TestObjectStoreKey(t *testing.T) {
t.Parallel()
key := NewObjectStoreKey("test")
require.Equal(t, "test", key.name)
require.Equal(t, key.name, key.Name())
require.Equal(t, fmt.Sprintf("ObjectStoreKey{%p, test}", key), key.String())
}
func TestMemoryStoreKey(t *testing.T) {
t.Parallel()
key := NewMemoryStoreKey("test")
@ -229,6 +237,11 @@ func TestNewTransientStoreKeys(t *testing.T) {
assert.DeepEqual(t, 1, len(NewTransientStoreKeys("one")))
}
func TestNewObjectStoreKeys(t *testing.T) {
assert.DeepEqual(t, map[string]*ObjectStoreKey{}, NewObjectStoreKeys())
assert.DeepEqual(t, 1, len(NewObjectStoreKeys("one")))
}
func TestNewInfiniteGasMeter(t *testing.T) {
gm := NewInfiniteGasMeter()
require.NotNil(t, gm)

View File

@ -7,6 +7,26 @@ import (
"strings"
)
// BytesIsZero returns whether the byte array is nil
func BytesIsZero(v []byte) bool {
return v == nil
}
// BytesValueLen returns the length of the byte array
func BytesValueLen(v []byte) int {
return len(v)
}
// AnyIsZero returns whether the underlying data is nil
func AnyIsZero(v any) bool {
return v == nil
}
// AnyValueLen returns 16 because the any type has a constant size and functions like unsafe.Sizeof cannot introspect.
func AnyValueLen[V any](v V) int {
return 16
}
// KVStorePrefixIterator iterates over all the keys with a certain prefix in ascending order
func KVStorePrefixIterator(kvs KVStore, prefix []byte) Iterator {
return kvs.Iterator(prefix, PrefixEndBytes(prefix))

View File

@ -1,6 +1,7 @@
package types_test
import (
"fmt"
"testing"
"gotest.tools/v3/assert"
@ -8,6 +9,143 @@ import (
"cosmossdk.io/store/types"
)
func TestBytesIsZero(t *testing.T) {
tests := []struct {
tc []byte
expected bool
}{
{[]byte("foobar"), false},
{[]byte(""), false},
{[]byte{}, false},
{nil, true},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
assert.Equal(t, test.expected, types.BytesIsZero(test.tc))
})
}
}
func TestBytesValueLen(t *testing.T) {
tests := []struct {
tc []byte
expected int
}{
{[]byte("foobar"), 6},
{[]byte(""), 0},
{[]byte{}, 0},
{nil, 0},
}
for _, test := range tests {
assert.Equal(t, test.expected, types.BytesValueLen(test.tc))
}
}
type struct1 struct {
a int
b []string
}
type struct2 struct {
a int
b []string
c bool
}
func TestAnyIsZero(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
obj any
expected bool
}{
{
"string length",
"foobar",
false,
},
{
"struct1 length",
struct1{4, []string{"1", "2", "3"}},
false,
},
{
"struct2 length",
struct2{4, []string{"1", "2", "3"}, true},
false,
},
{
"struct1 pointer length",
&struct2{4, []string{"1", "2", "3"}, true},
false,
},
{
"empty string",
"",
false,
},
{
"nil",
nil,
true,
},
{
"empty array",
[]string{},
false,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, types.AnyIsZero(test.obj))
})
}
}
func TestAnyValueLen(t *testing.T) {
t.Parallel()
type struct1 struct {
a int
b []string
}
type struct2 struct {
a int
b []string
c bool
}
testCases := []struct {
name string
obj any
expectedLen int
}{
{
"string length",
"foobar",
16,
},
{
"struct1 length",
struct1{4, []string{"1", "2", "3"}},
16,
},
{
"struct2 length",
struct2{4, []string{"1", "2", "3"}, true},
16,
},
{
"struct1 pointer length",
&struct2{4, []string{"1", "2", "3"}, true},
16,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expectedLen, types.AnyValueLen(test.obj))
})
}
}
func TestPrefixEndBytes(t *testing.T) {
t.Parallel()
testCases := []struct {

View File

@ -1,5 +1,7 @@
package types
import "errors"
var (
// MaxKeyLength is 128K - 1
MaxKeyLength = (1 << 17) - 1
@ -22,7 +24,20 @@ func AssertValidValue(value []byte) {
if value == nil {
panic("value is nil")
}
if len(value) > MaxValueLength {
panic("value is too large")
AssertValidValueLength(len(value))
}
// AssertValidValueGeneric checks if the value is valid(value is not nil and within length limit)
func AssertValidValueGeneric[V any](value V, isZero func(V) bool, valueLen func(V) int) {
if isZero(value) {
panic("value is nil")
}
AssertValidValueLength(valueLen(value))
}
// AssertValidValueLength checks if the value length is within length limit
func AssertValidValueLength(l int) {
if l > MaxValueLength {
panic(errors.New("value is too large"))
}
}

View File

@ -21,3 +21,37 @@ func TestAssertValidValue(t *testing.T) {
require.NotPanics(t, func() { types.AssertValidValue([]byte{0x01}) })
require.Panics(t, func() { types.AssertValidValue(nil) })
}
func TestAssertValidValueGeneric(t *testing.T) {
t.Parallel()
bytesIsZero := func(b []byte) bool { return b == nil }
bytesValueLen := func(b []byte) int { return len(b) }
require.NotPanics(t, func() {
types.AssertValidValueGeneric(
[]byte{},
bytesIsZero,
bytesValueLen,
)
})
require.NotPanics(t, func() {
types.AssertValidValueGeneric(
[]byte{0x1},
bytesIsZero,
bytesValueLen,
)
})
require.Panics(t, func() {
types.AssertValidValueGeneric(
nil,
bytesIsZero,
bytesValueLen,
)
})
require.Panics(t, func() {
types.AssertValidValueGeneric(
make([]byte, types.MaxValueLength+1),
bytesIsZero,
bytesValueLen,
)
})
}

View File

@ -4,7 +4,7 @@ go 1.24.0
require (
cosmossdk.io/math v1.5.3
github.com/cometbft/cometbft v0.38.19
github.com/cometbft/cometbft v0.39.0-beta.2.0.20251020144122-cd33e1fff685
github.com/cosmos/cosmos-sdk v0.53.4
github.com/creachadair/tomledit v0.0.29
github.com/stretchr/testify v1.11.1
@ -28,7 +28,6 @@ require (
github.com/99designs/keyring v1.2.1 // indirect
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
@ -104,7 +103,7 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.9.8 // indirect
github.com/linxGnu/grocksdb v1.10.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
@ -112,7 +111,6 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pkg/errors v0.9.1 // indirect
@ -134,9 +132,10 @@ require (
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/supranational/blst v0.3.16 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
@ -161,7 +160,7 @@ require (
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
@ -170,3 +169,7 @@ require (
pgregory.net/rapid v1.2.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
replace cosmossdk.io/store => ../store
replace github.com/cosmos/cosmos-sdk => ../

View File

@ -16,8 +16,6 @@ cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U=
cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ=
cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE=
cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI=
cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o=
cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A=
cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA=
cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -44,8 +42,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I=
github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg=
github.com/adlio/schema v1.3.9 h1:MLYk1VX1dn7xHW7Kdm1ywKKLjh19DRnrc65axS5xQA8=
github.com/adlio/schema v1.3.9/go.mod h1:GnxXztHzNh6pIc7qm3sw+jsmHrXgBy/x2RBSkKZ3L4w=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -71,8 +69,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.1 h1:hqnfFbjjk3pxGa5E9Ho3hjoU7odtUuNmJ9Ao+Bo8s1c=
github.com/bits-and-blooms/bitset v1.24.1/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU=
github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c=
@ -131,8 +129,8 @@ github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g=
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/cometbft/cometbft v0.38.19 h1:vNdtCkvhuwUlrcLPAyigV7lQpmmo+tAq8CsB8gZjEYw=
github.com/cometbft/cometbft v0.38.19/go.mod h1:UCu8dlHqvkAsmAFmWDRWNZJPlu6ya2fTWZlDrWsivwo=
github.com/cometbft/cometbft v0.39.0-beta.2.0.20251020144122-cd33e1fff685 h1:yv853Uyyhz68mtHr+AyOSimx/7qpuoEiAhc1Yp9Et1c=
github.com/cometbft/cometbft v0.39.0-beta.2.0.20251020144122-cd33e1fff685/go.mod h1:6Lt9liF9HxSth+zDotxtv94SuGMzRfuHmI287USgjlw=
github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ=
github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
@ -147,8 +145,6 @@ github.com/cosmos/cosmos-db v1.1.3 h1:7QNT77+vkefostcKkhrzDK9uoIEryzFrU9eoMeaQOP
github.com/cosmos/cosmos-db v1.1.3/go.mod h1:kN+wGsnwUJZYn8Sy5Q2O0vCYA99MJllkKASbs6Unb9U=
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/cosmos-sdk v0.53.4 h1:kPF6vY68+/xi1/VebSZGpoxQqA52qkhUzqkrgeBn3Mg=
github.com/cosmos/cosmos-sdk v0.53.4/go.mod h1:7U3+WHZtI44dEOnU46+lDzBb2tFh1QlMvi8Z5JugopI=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE=
@ -377,8 +373,9 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -455,8 +452,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.9.8 h1:vOIKv9/+HKiqJAElJIEYv3ZLcihRxyP7Suu/Mu8Dxjs=
github.com/linxGnu/grocksdb v1.9.8/go.mod h1:C3CNe9UYc9hlEM2pC82AqiGS3LRW537u9LFV4wIZuHk=
github.com/linxGnu/grocksdb v1.10.1 h1:YX6gUcKvSC3d0s9DaqgbU+CRkZHzlELgHu1Z/kmtslg=
github.com/linxGnu/grocksdb v1.10.1/go.mod h1:C3CNe9UYc9hlEM2pC82AqiGS3LRW537u9LFV4wIZuHk=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@ -538,8 +535,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -683,12 +680,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE=
github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@ -742,8 +741,8 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
@ -944,8 +943,8 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=

View File

@ -233,6 +233,7 @@ require (
// replace (
// <temporary replace>
// )
replace cosmossdk.io/store => ../store
// Below are the long-lived replace for tests.
replace (

View File

@ -40,8 +40,6 @@ cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U=
cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ=
cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE=
cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI=
cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o=
cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A=
cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA=
cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -479,8 +477,9 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=

View File

@ -45,7 +45,7 @@ require (
github.com/cockroachdb/pebble v1.1.5 // indirect
github.com/cockroachdb/redact v1.1.6 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
github.com/cometbft/cometbft v0.39.0-beta.2 // indirect
github.com/cometbft/cometbft v0.39.0-beta.2.0.20251020144122-cd33e1fff685 // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.1.3 // indirect
@ -176,3 +176,5 @@ require (
pgregory.net/rapid v1.2.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
replace cosmossdk.io/store => ../../store

View File

@ -16,8 +16,6 @@ cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U=
cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ=
cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE=
cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI=
cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o=
cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A=
cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA=
cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -131,8 +129,8 @@ github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g=
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/cometbft/cometbft v0.39.0-beta.2 h1:OfP4Qlw2L66xqvrNYSlVufYwXXP9frOPBnwYPyDttOk=
github.com/cometbft/cometbft v0.39.0-beta.2/go.mod h1:6Lt9liF9HxSth+zDotxtv94SuGMzRfuHmI287USgjlw=
github.com/cometbft/cometbft v0.39.0-beta.2.0.20251020144122-cd33e1fff685 h1:yv853Uyyhz68mtHr+AyOSimx/7qpuoEiAhc1Yp9Et1c=
github.com/cometbft/cometbft v0.39.0-beta.2.0.20251020144122-cd33e1fff685/go.mod h1:6Lt9liF9HxSth+zDotxtv94SuGMzRfuHmI287USgjlw=
github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ=
github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
@ -375,8 +373,9 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=

View File

@ -351,6 +351,11 @@ func (c Context) TransientStore(key storetypes.StoreKey) storetypes.KVStore {
return gaskv.NewStore(c.ms.GetKVStore(key), c.gasMeter, c.transientKVGasConfig)
}
// ObjectStore fetches an object store from the MultiStore,
func (c Context) ObjectStore(key storetypes.StoreKey) storetypes.ObjKVStore {
return gaskv.NewObjStore(c.ms.GetObjKVStore(key), c.gasMeter, c.transientKVGasConfig)
}
// CacheContext returns a new Context with the multi-store cached and a new
// EventManager. The cached context is written to the context when writeCache
// is called. Note, events are automatically emitted on the parent context's

View File

@ -8,9 +8,13 @@ import (
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttime "github.com/cometbft/cometbft/types/time"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
"cosmossdk.io/log"
"cosmossdk.io/store/metrics"
"cosmossdk.io/store/rootmulti"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
@ -241,3 +245,15 @@ func (s *contextTestSuite) TestUnwrapSDKContext() {
sdkCtx2 = types.UnwrapSDKContext(ctx)
s.Require().Equal(sdkCtx, sdkCtx2)
}
func (s *contextTestSuite) TestMultiStore() {
db := dbm.NewMemDB()
rms := rootmulti.NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics())
ctx := types.NewContext(rms, cmtproto.Header{}, false, nil)
objKey := storetypes.NewObjectStoreKey("obj")
rms.MountStoreWithDB(objKey, storetypes.StoreTypeObject, nil)
s.Require().NoError(rms.LoadLatestVersion())
objKVStore := ctx.ObjectStore(objKey)
s.Require().Equal(objKVStore.GetStoreType(), storetypes.StoreTypeObject)
}