diff --git a/CHANGELOG.md b/CHANGELOG.md index 025ebb8dfe3a..cd10006696ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,30 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## [Unreleased] +## [v0.53.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-25 + +This patch update also includes minor dependency bumps. + +### Features + +* (abci_utils) [#25008](https://github.com/cosmos/cosmos-sdk/pull/24861) add the ability to assign a custom signer extraction adapter in `DefaultProposalHandler`. + +## [v0.53.3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.3) - 2025-07-08 + +### Bug Fixes + +* [GHSA-p22h-3m2v-cmgh](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-p22h-3m2v-cmgh) Fix x/distribution can halt when historical rewards overflow. + + +## [v0.53.2](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.2) - 2025-06-02 + +This patch update also includes minor dependency bumps. + +### Bug Fixes + +* (x/epochs) [#24770](https://github.com/cosmos/cosmos-sdk/pull/24770) Fix register of epoch hooks in `InvokeSetHooks`. + +## [v0.53.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.53.0) - 2025-04-29 ### Breaking Changes @@ -99,6 +122,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (genutil) [#24018](https://github.com/cosmos/cosmos-sdk/pull/24018) Allow manually setting the consensus key type in genesis * (client) [#18557](https://github.com/cosmos/cosmos-sdk/pull/18557) Add `--qrcode` flag to `keys show` command to support displaying keys address QR code. * (x/auth) [#24030](https://github.com/cosmos/cosmos-sdk/pull/24030) Allow usage of ed25519 keys for transaction signing. +* (baseapp) [#24159](https://github.com/cosmos/cosmos-sdk/pull/24159) Support mount object store in baseapp, add `ObjectStore` api in context. * (baseapp) [#24163](https://github.com/cosmos/cosmos-sdk/pull/24163) Add `StreamingManager` to baseapp to extend the abci listeners. * (x/protocolpool) [#23933](https://github.com/cosmos/cosmos-sdk/pull/23933) Add x/protocolpool module. * x/distribution can now utilize an externally managed community pool. NOTE: this will make the message handlers for FundCommunityPool and CommunityPoolSpend error, as well as the query handler for CommunityPool. @@ -107,6 +131,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) [#24458](https://github.com/cosmos/cosmos-sdk/pull/24458) Add `Executor` to support custom execution logic and incarnation cache for performance optimisation ### Improvements diff --git a/Makefile b/Makefile index bebced3438c5..d27232695d52 100644 --- a/Makefile +++ b/Makefile @@ -390,7 +390,6 @@ lint: @$(MAKE) lint-install @./scripts/go-lint-all.bash --timeout=15m - lint-fix: @echo "--> Running linter" @$(MAKE) lint-install @@ -532,4 +531,4 @@ build-v53: else \ echo "No changes to reapply"; \ fi -.PHONY: build-v53 \ No newline at end of file +.PHONY: build-v53 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e0df3fc10bd9..727953359b20 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,10 +1,8 @@ -# Cosmos SDK v0.53.0 Release Notes - -πŸ’¬ [**Release Discussion**](https://github.com/orgs/cosmos/discussions/58) +# Cosmos SDK v0.53.4 Release Notes ## πŸš€ Highlights -Announcing Cosmos SDK v0.53 +This patch release includes minor dependency and non-breaking functionality additions. We are pleased to announce the release of Cosmos SDK v0.53! We’re excited to be delivering a new version of the Cosmos SDK that provides key features and updates while minimizing breaking changes so you can focus on what matters most: building. @@ -14,4 +12,5 @@ For more upgrade information, check out our [upgrading guide](https://github.com ## πŸ“ Changelog -Check out the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/CHANGELOG.md) for an exhaustive list of changes, or [compare changes](https://github.com/cosmos/cosmos-sdk/compare/v0.50.12...v0.53.0) from the last release. +Check out the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.53.4/CHANGELOG.md) for an exhaustive list of changes or [compare changes](https://github.com/cosmos/cosmos-sdk/compare/v0.53.3...v0.53.4) from the last release. + diff --git a/baseapp/abci.go b/baseapp/abci.go index 662087e58106..e51a188764a4 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -21,6 +21,7 @@ import ( storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp/state" + "github.com/cosmos/cosmos-sdk/blockstm" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" @@ -355,7 +356,7 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er } if app.abciHandlers.CheckTxHandler == nil { - gasInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil) + gasInfo, result, anteEvents, err := app.RunTx(mode, req.Tx, nil, -1, nil, nil) if err != nil { return sdkerrors.ResponseCheckTxWithEvents(err, gasInfo.GasWanted, gasInfo.GasUsed, anteEvents, app.trace), nil } @@ -371,7 +372,7 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er // Create wrapper to avoid users overriding the execution mode runTx := func(txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { - return app.runTx(mode, txBytes, tx) + return app.RunTx(mode, txBytes, tx, -1, nil, nil) } return app.abciHandlers.CheckTxHandler(runTx, req) @@ -395,6 +396,14 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc return nil, errors.New("PrepareProposal handler not set") } + // Abort any running OE so it cannot overlap with `PrepareProposal`. This could happen if optimistic + // `internalFinalizeBlock` from previous round takes a long time, but consensus has moved on to next round. + // Overlap is undesirable, since `internalFinalizeBlock` and `PrepareProoposal` could share access to + // in-memory structs depending on application implementation. + // No-op if OE is not enabled. + // Similar call to Abort() is done in `ProcessProposal`. + app.optimisticExec.Abort() + // Always reset state given that PrepareProposal can timeout and be called // again in a subsequent round. header := cmtproto.Header{ @@ -798,47 +807,45 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request // Reset the gas meter so that the AnteHandlers aren't required to gasMeter = app.getBlockGasMeter(finalizeState.Context()) - finalizeState.SetContext(finalizeState.Context().WithBlockGasMeter(gasMeter)) + finalizeState.SetContext( + finalizeState.Context(). + WithBlockGasMeter(gasMeter). + WithTxCount(len(req.Txs))) // Iterate over all raw transactions in the proposal and attempt to execute // them, gathering the execution results. // // NOTE: Not all raw transactions may adhere to the sdk.Tx interface, e.g. // vote extensions, so skip those. - txResults := make([]*abci.ExecTxResult, 0, len(req.Txs)) - for _, rawTx := range req.Txs { - var response *abci.ExecTxResult - - if _, err := app.txDecoder(rawTx); err == nil { - response = app.deliverTx(rawTx) - } else { - // In the case where a transaction included in a block proposal is malformed, - // we still want to return a default response to comet. This is because comet - // expects a response for each transaction included in a block proposal. - response = sdkerrors.ResponseExecTxResultWithEvents( - sdkerrors.ErrTxDecode, - 0, - 0, - nil, - false, - ) - } - - // check after every tx if we should abort - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - // continue - } - - txResults = append(txResults, response) + txResults, err := app.executeTxsWithExecutor(ctx, finalizeState.MultiStore, req.Txs) + if err != nil { + // usually due to canceled + return nil, err } if finalizeState.MultiStore.TracingEnabled() { finalizeState.MultiStore = finalizeState.MultiStore.SetTracingContext(nil).(storetypes.CacheMultiStore) } + var ( + blockGasUsed uint64 + blockGasWanted uint64 + ) + for _, res := range txResults { + // GasUsed should not be -1 but just in case + if res.GasUsed > 0 { + blockGasUsed += uint64(res.GasUsed) + } + // GasWanted could be -1 if the tx is invalid + if res.GasWanted > 0 { + blockGasWanted += uint64(res.GasWanted) + } + } + finalizeState.SetContext( + finalizeState.Context(). + WithBlockGasUsed(blockGasUsed). + WithBlockGasWanted(blockGasWanted), + ) endBlock, err := app.endBlock(finalizeState.Context()) if err != nil { return nil, err @@ -863,6 +870,16 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request }, nil } +func (app *BaseApp) executeTxsWithExecutor(ctx context.Context, ms storetypes.MultiStore, txs [][]byte) ([]*abci.ExecTxResult, error) { + if app.txRunner == nil { + app.txRunner = blockstm.NewDefaultRunner( + app.txDecoder, + ) + } + + return app.txRunner.Run(ctx, ms, txs, app.deliverTx) +} + // FinalizeBlock will execute the block proposal provided by RequestFinalizeBlock. // Specifically, it will execute an application's BeginBlock (if defined), followed // by the transactions in the proposal, finally followed by the application's diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 91ef3e1c8f9d..9b4c8d66fd9c 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -27,6 +27,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp/config" "github.com/cosmos/cosmos-sdk/baseapp/oe" "github.com/cosmos/cosmos-sdk/baseapp/state" + "github.com/cosmos/cosmos-sdk/blockstm" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" servertypes "github.com/cosmos/cosmos-sdk/server/types" @@ -161,6 +162,9 @@ type BaseApp struct { // // SAFETY: it's safe to do if validators validate the total gas wanted in the `ProcessProposal`, which is the case in the default handler. disableBlockGasMeter bool + + // Optional alternative tx runner, used for block-stm parallel transaction execution. If nil, default txRunner is used. + txRunner blockstm.TxRunner } // NewBaseApp returns a reference to an initialized BaseApp. It accepts a @@ -283,6 +287,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 +328,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) { @@ -589,9 +606,7 @@ func (app *BaseApp) getBlockGasMeter(ctx sdk.Context) storetypes.GasMeter { return storetypes.NewInfiniteGasMeter() } -// getContextForTx retrieves the context for the tx w/ txBytes and other memoized values. -// retrieve the context for the tx w/ txBytes and other memoized values. -func (app *BaseApp) getContextForTx(mode sdk.ExecMode, txBytes []byte) sdk.Context { +func (app *BaseApp) getContextForTx(mode sdk.ExecMode, txBytes []byte, txIndex int) sdk.Context { app.mu.Lock() defer app.mu.Unlock() @@ -601,6 +616,7 @@ func (app *BaseApp) getContextForTx(mode sdk.ExecMode, txBytes []byte) sdk.Conte } ctx := modeState.Context(). WithTxBytes(txBytes). + WithTxIndex(txIndex). WithGasMeter(storetypes.NewInfiniteGasMeter()) // WithVoteInfos(app.voteInfos) // TODO: identify if this is needed @@ -685,7 +701,7 @@ func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, er return resp, nil } -func (app *BaseApp) deliverTx(tx []byte) *abci.ExecTxResult { +func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txIndex int, incarnationCache map[string]any) *abci.ExecTxResult { gInfo := sdk.GasInfo{} resultStr := "successful" @@ -698,7 +714,7 @@ func (app *BaseApp) deliverTx(tx []byte) *abci.ExecTxResult { telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") }() - gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx, nil) + gInfo, result, anteEvents, err := app.RunTx(execModeFinalize, tx, nil, txIndex, txMultiStore, incarnationCache) if err != nil { resultStr = "failed" resp = sdkerrors.ResponseExecTxResultWithEvents( @@ -748,7 +764,7 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { return endblock, nil } -// runTx processes a transaction within a given execution mode, encoded transaction +// RunTx processes a transaction within a given execution mode, encoded transaction // bytes, and the decoded transaction itself. All state transitions occur through // a cached Context depending on the mode provided. State only gets persisted // if all messages get executed successfully and the execution mode is DeliverTx. @@ -757,17 +773,23 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { // and execute successfully. An error is returned otherwise. // both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice // passing the decoded tx to runTX is optional, it will be decoded if the tx is nil -func (app *BaseApp) runTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { +func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is // determined by the GasMeter. We need access to the context to get the gas // meter, so we initialize upfront. var gasWanted uint64 - ctx := app.getContextForTx(mode, txBytes) + ctx := app.getContextForTx(mode, txBytes, txIndex) + if incarnationCache != nil { + ctx = ctx.WithIncarnationCache(incarnationCache) + } + if txMultiStore != nil { + ctx = ctx.WithMultiStore(txMultiStore) + } ms := ctx.MultiStore() // only run the tx if there is block gas remaining - if mode == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() { + if mode == sdk.ExecModeFinalize && ctx.BlockGasMeter().IsOutOfGas() { return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx") } @@ -956,6 +978,8 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me break } + ctx = ctx.WithMsgIndex(i) + handler := app.msgServiceRouter.Handler(msg) if handler == nil { return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) @@ -1054,7 +1078,7 @@ func (app *BaseApp) PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) { return nil, err } - _, _, _, err = app.runTx(execModePrepareProposal, bz, tx) + _, _, _, err = app.RunTx(execModePrepareProposal, bz, tx, -1, nil, nil) if err != nil { return nil, err } @@ -1073,7 +1097,7 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) { return nil, err } - _, _, _, err = app.runTx(execModeProcessProposal, txBz, tx) + _, _, _, err = app.RunTx(execModeProcessProposal, txBz, tx, -1, nil, nil) if err != nil { return nil, err } diff --git a/baseapp/genesis.go b/baseapp/genesis.go index 4662d1187b4a..547a1322d8a7 100644 --- a/baseapp/genesis.go +++ b/baseapp/genesis.go @@ -13,7 +13,7 @@ var _ genesis.TxHandler = (*BaseApp)(nil) // ExecuteGenesisTx implements genesis.GenesisState from // cosmossdk.io/core/genesis to set initial state in genesis func (ba *BaseApp) ExecuteGenesisTx(tx []byte) error { - res := ba.deliverTx(tx) + res := ba.deliverTx(tx, nil, -1, nil) if res.Code != types.CodeTypeOK { return errors.New(res.Log) diff --git a/baseapp/options.go b/baseapp/options.go index 94cf81334015..bf30e98e0711 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -14,6 +14,7 @@ import ( storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp/oe" + "github.com/cosmos/cosmos-sdk/blockstm" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -124,6 +125,11 @@ func SetOptimisticExecution(opts ...func(*oe.OptimisticExecution)) func(*BaseApp } } +// SetBlockSTMTxRunner sets the block stm tx runner for the BaseApp for parallel execution. +func (app *BaseApp) SetBlockSTMTxRunner(txRunner blockstm.TxRunner) { + app.txRunner = txRunner +} + // DisableBlockGasMeter disables the block gas meter. func DisableBlockGasMeter() func(*BaseApp) { return func(app *BaseApp) { app.SetDisableBlockGasMeter(true) } diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index c254a2463055..6144552490b2 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -19,13 +19,13 @@ func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, * return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.runTx(execModeCheck, bz, tx) + gasInfo, result, _, err := app.RunTx(execModeCheck, bz, tx, -1, nil, nil) return gasInfo, result, err } // Simulate executes a tx in simulate mode to get result and gas info. func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { - gasInfo, result, _, err := app.runTx(execModeSimulate, txBytes, nil) + gasInfo, result, _, err := app.RunTx(execModeSimulate, txBytes, nil, -1, nil, nil) return gasInfo, result, err } @@ -36,7 +36,7 @@ func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.runTx(execModeFinalize, bz, tx) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil) return gasInfo, result, err } @@ -47,7 +47,7 @@ func (app *BaseApp) SimTxFinalizeBlock(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk. return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.runTx(execModeFinalize, bz, tx) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil) return gasInfo, result, err } @@ -77,9 +77,9 @@ func (app *BaseApp) NewUncachedContext(isCheckTx bool, header cmtproto.Header) s } func (app *BaseApp) GetContextForFinalizeBlock(txBytes []byte) sdk.Context { - return app.getContextForTx(execModeFinalize, txBytes) + return app.getContextForTx(execModeFinalize, txBytes, -1) } func (app *BaseApp) GetContextForCheckTx(txBytes []byte) sdk.Context { - return app.getContextForTx(execModeCheck, txBytes) + return app.getContextForTx(execModeCheck, txBytes, -1) } diff --git a/blockstm/bench_test.go b/blockstm/bench_test.go new file mode 100644 index 000000000000..ddbe3c752acf --- /dev/null +++ b/blockstm/bench_test.go @@ -0,0 +1,54 @@ +package blockstm + +import ( + "context" + "strconv" + "testing" + + "github.com/test-go/testify/require" + + storetypes "cosmossdk.io/store/types" +) + +func BenchmarkBlockSTM(b *testing.B) { + stores := map[storetypes.StoreKey]int{StoreKeyAuth: 0, StoreKeyBank: 1} + for i := 0; i < 26; i++ { + key := storetypes.NewKVStoreKey(strconv.FormatInt(int64(i), 10)) + stores[key] = i + 2 + } + storage := NewMultiMemDB(stores) + testCases := []struct { + name string + block *MockBlock + }{ + {"random-10000/100", testBlock(10000, 100)}, + {"no-conflict-10000", noConflictBlock(10000)}, + {"worst-case-10000", worstCaseBlock(10000)}, + {"iterate-10000/100", iterateBlock(10000, 100)}, + } + for _, tc := range testCases { + b.Run(tc.name+"-sequential", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + runSequential(storage, tc.block) + } + }) + for _, worker := range []int{1, 5, 10, 15, 20} { + b.Run(tc.name+"-worker-"+strconv.Itoa(worker), func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + require.NoError( + b, + ExecuteBlock(context.Background(), tc.block.Size(), stores, storage, worker, tc.block.ExecuteTx), + ) + } + }) + } + } +} + +func runSequential(storage MultiStore, block *MockBlock) { + for i, tx := range block.Txs { + block.Results[i] = tx(storage) + } +} diff --git a/blockstm/btree.go b/blockstm/btree.go new file mode 100644 index 000000000000..b5f3627a0c34 --- /dev/null +++ b/blockstm/btree.go @@ -0,0 +1,88 @@ +package blockstm + +import ( + "sync/atomic" + + "github.com/tidwall/btree" +) + +// BTree wraps an atomic pointer to an unsafe btree.BTreeG +type BTree[T any] struct { + atomic.Pointer[btree.BTreeG[T]] +} + +// NewBTree returns a new BTree. +func NewBTree[T any](less func(a, b T) bool, degree int) *BTree[T] { + tree := btree.NewBTreeGOptions(less, btree.Options{ + NoLocks: true, + ReadOnly: true, + Degree: degree, + }) + t := &BTree[T]{} + t.Store(tree) + return t +} + +func (bt *BTree[T]) Get(item T) (result T, ok bool) { + return bt.Load().Get(item) +} + +func (bt *BTree[T]) GetOrDefault(item T, fillDefaults func(*T)) T { + for { + t := bt.Load() + result, ok := t.Get(item) + if ok { + return result + } + fillDefaults(&item) + c := t.Copy() + c.Set(item) + if bt.CompareAndSwap(t, c) { + return item + } + } +} + +func (bt *BTree[T]) Set(item T) (prev T, ok bool) { + for { + t := bt.Load() + c := t.Copy() + prev, ok = c.Set(item) + if bt.CompareAndSwap(t, c) { + return + } + } +} + +func (bt *BTree[T]) Delete(item T) (prev T, ok bool) { + for { + t := bt.Load() + c := t.Copy() + prev, ok = c.Delete(item) + if bt.CompareAndSwap(t, c) { + return + } + } +} + +func (bt *BTree[T]) Scan(iter func(item T) bool) { + bt.Load().Scan(iter) +} + +func (bt *BTree[T]) Max() (T, bool) { + return bt.Load().Max() +} + +func (bt *BTree[T]) Iter() btree.IterG[T] { + return bt.Load().Iter() +} + +// ReverseSeek returns the first item that is less than or equal to the pivot +func (bt *BTree[T]) ReverseSeek(pivot T) (result T, ok bool) { + bt.Load().Descend(pivot, func(item T) bool { + result = item + ok = true + return false + }) + return +} diff --git a/blockstm/btreeiterator.go b/blockstm/btreeiterator.go new file mode 100644 index 000000000000..efc47e54ff5e --- /dev/null +++ b/blockstm/btreeiterator.go @@ -0,0 +1,121 @@ +package blockstm + +import ( + "bytes" + "errors" + + "github.com/tidwall/btree" +) + +// BTreeIteratorG iterates over btree. +// Implements Iterator. +type BTreeIteratorG[T KeyItem] struct { + iter btree.IterG[T] + + start []byte + end []byte + ascending bool + valid bool +} + +func NewBTreeIteratorG[T KeyItem]( + startItem, endItem T, + iter btree.IterG[T], + ascending bool, +) *BTreeIteratorG[T] { + start := startItem.GetKey() + end := endItem.GetKey() + + var valid bool + if ascending { + if start != nil { + valid = iter.Seek(startItem) + } else { + valid = iter.First() + } + } else { + if end != nil { + valid = iter.Seek(endItem) + if !valid { + valid = iter.Last() + } else { + // end is exclusive + valid = iter.Prev() + } + } else { + valid = iter.Last() + } + } + + mi := &BTreeIteratorG[T]{ + iter: iter, + start: start, + end: end, + ascending: ascending, + valid: valid, + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } + + return mi +} + +func (mi *BTreeIteratorG[T]) Domain() (start, end []byte) { + return mi.start, mi.end +} + +func (mi *BTreeIteratorG[T]) Close() error { + mi.iter.Release() + return nil +} + +func (mi *BTreeIteratorG[T]) Error() error { + if !mi.Valid() { + return errors.New("invalid memIterator") + } + return nil +} + +func (mi *BTreeIteratorG[T]) Valid() bool { + return mi.valid +} + +func (mi *BTreeIteratorG[T]) Next() { + mi.assertValid() + + if mi.ascending { + mi.valid = mi.iter.Next() + } else { + mi.valid = mi.iter.Prev() + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } +} + +func (mi *BTreeIteratorG[T]) keyInRange(key []byte) bool { + if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 { + return false + } + if !mi.ascending && mi.start != nil && bytes.Compare(key, mi.start) < 0 { + return false + } + return true +} + +func (mi *BTreeIteratorG[T]) Item() T { + return mi.iter.Item() +} + +func (mi *BTreeIteratorG[T]) Key() []byte { + return mi.Item().GetKey() +} + +func (mi *BTreeIteratorG[T]) assertValid() { + if err := mi.Error(); err != nil { + panic(err) + } +} diff --git a/blockstm/condvar.go b/blockstm/condvar.go new file mode 100644 index 000000000000..543de1d7188c --- /dev/null +++ b/blockstm/condvar.go @@ -0,0 +1,30 @@ +package blockstm + +import "sync" + +type Condvar struct { + sync.Mutex + notified bool + cond sync.Cond +} + +func NewCondvar() *Condvar { + c := &Condvar{} + c.cond = *sync.NewCond(c) + return c +} + +func (cv *Condvar) Wait() { + cv.Lock() + for !cv.notified { + cv.cond.Wait() + } + cv.Unlock() +} + +func (cv *Condvar) Notify() { + cv.Lock() + cv.notified = true + cv.Unlock() + cv.cond.Signal() +} diff --git a/blockstm/executor.go b/blockstm/executor.go new file mode 100644 index 000000000000..17f1167db067 --- /dev/null +++ b/blockstm/executor.go @@ -0,0 +1,85 @@ +package blockstm + +import ( + "context" +) + +// Executor fields are not mutated during execution. +type Executor struct { + ctx context.Context // context for cancellation + scheduler *Scheduler // scheduler for task management + txExecutor TxExecutor // callback to actually execute a transaction + mvMemory *MVMemory // multi-version memory for the executor + + // index of the executor, used for debugging output + i int +} + +func NewExecutor( + ctx context.Context, + scheduler *Scheduler, + txExecutor TxExecutor, + mvMemory *MVMemory, + i int, +) *Executor { + return &Executor{ + ctx: ctx, + scheduler: scheduler, + txExecutor: txExecutor, + mvMemory: mvMemory, + i: i, + } +} + +// Run executes all tasks until completion +// Invariant `num_active_tasks`: +// - `NextTask` increases it if returns a valid task. +// - `TryExecute` and `NeedsReexecution` don't change it if it returns a new valid task to run, +// otherwise it decreases it. +func (e *Executor) Run() { + var kind TaskKind + version := InvalidTxnVersion + for !e.scheduler.Done() { + if !version.Valid() { + // check for cancellation + select { + case <-e.ctx.Done(): + return + default: + } + + version, kind = e.scheduler.NextTask() + continue + } + + switch kind { + case TaskKindExecution: + version, kind = e.TryExecute(version) + case TaskKindValidation: + version, kind = e.NeedsReexecution(version) + } + } +} + +func (e *Executor) TryExecute(version TxnVersion) (TxnVersion, TaskKind) { + e.scheduler.executedTxns.Add(1) + view := e.execute(version.Index) + wroteNewLocation := e.mvMemory.Record(version, view) + return e.scheduler.FinishExecution(version, wroteNewLocation) +} + +func (e *Executor) NeedsReexecution(version TxnVersion) (TxnVersion, TaskKind) { + e.scheduler.validatedTxns.Add(1) + valid := e.mvMemory.ValidateReadSet(version.Index) + aborted := !valid && e.scheduler.TryValidationAbort(version) + if aborted { + e.mvMemory.ConvertWritesToEstimates(version.Index) + } + return e.scheduler.FinishValidation(version.Index, aborted) +} + +func (e *Executor) execute(txn TxnIndex) *MultiMVMemoryView { + view := e.mvMemory.View(txn) + e.txExecutor(txn, view) + return view +} diff --git a/blockstm/go.mod b/blockstm/go.mod new file mode 100644 index 000000000000..789ec3463122 --- /dev/null +++ b/blockstm/go.mod @@ -0,0 +1,117 @@ +module github.com/cosmos/cosmos-sdk/blockstm + +go 1.25.0 + +replace github.com/tidwall/btree => github.com/cosmos/btree v0.0.0-20250924232609-2c6195d95951 + +require ( + cosmossdk.io/collections v1.3.1 + cosmossdk.io/store v1.1.2 + github.com/cometbft/cometbft v0.38.18 + github.com/cosmos/cosmos-sdk v0.0.0-00010101000000-000000000000 + github.com/test-go/testify v1.1.4 + github.com/tidwall/btree v1.8.1 +) + +require ( + cosmossdk.io/api v0.9.2 // indirect + cosmossdk.io/core v0.11.3 // indirect + cosmossdk.io/errors v1.0.2 // indirect + cosmossdk.io/log v1.6.1 // indirect + cosmossdk.io/math v1.5.3 // indirect + cosmossdk.io/schema v1.1.0 // indirect + cosmossdk.io/x/tx v0.14.0 // indirect + github.com/DataDog/datadog-go v3.2.0+incompatible // indirect + github.com/DataDog/zstd v1.5.7 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cockroachdb/errors v1.12.0 // indirect + github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect + github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect + 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-db v0.14.1 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-db v1.1.3 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect + github.com/cosmos/gogoproto v1.7.0 // indirect + github.com/cosmos/ics23/go v0.11.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dgraph-io/badger/v4 v4.6.0 // indirect + github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/getsentry/sentry-go v0.35.0 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nxadm/tail v1.4.11 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/onsi/gomega v1.26.0 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tendermint/go-amino v0.16.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + go.etcd.io/bbolt v1.4.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // 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/grpc v1.75.1 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) + +replace ( + cosmossdk.io/store => ../store + github.com/cosmos/cosmos-sdk => ../. +) diff --git a/blockstm/go.sum b/blockstm/go.sum new file mode 100644 index 000000000000..714492931319 --- /dev/null +++ b/blockstm/go.sum @@ -0,0 +1,598 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= +cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= +cosmossdk.io/collections v1.3.1 h1:09e+DUId2brWsNOQ4nrk+bprVmMUaDH9xvtZkeqIjVw= +cosmossdk.io/collections v1.3.1/go.mod h1:ynvkP0r5ruAjbmedE+vQ07MT6OtJ0ZIDKrtJHK7Q/4c= +cosmossdk.io/core v0.11.3 h1:mei+MVDJOwIjIniaKelE3jPDqShCc/F4LkNNHh+4yfo= +cosmossdk.io/core v0.11.3/go.mod h1:9rL4RE1uDt5AJ4Tg55sYyHWXA16VmpHgbe0PbJc6N2Y= +cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= +cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= +cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= +cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= +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/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA= +cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= +github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= +github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +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/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= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= +github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo= +github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g= +github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 h1:pU88SPhIFid6/k0egdR5V6eALQYq2qbSmukrkgIh/0A= +github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= +github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw= +github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo= +github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= +github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +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/cometbft/cometbft v0.38.18 h1:1ZHYMdu0S75YxFM13LlPXnOwiIpUW5z9TKMQtTIALpw= +github.com/cometbft/cometbft v0.38.18/go.mod h1:PlOQgf3jQorep+g6oVnJgtP65TJvBJoLiXjGaMdNxBE= +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/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= +github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= +github.com/cosmos/btree v0.0.0-20250924232609-2c6195d95951 h1:dC3GJcS8bJiSEe7VAFDDFgFnVM1G9nBdGOgqJsmsZwM= +github.com/cosmos/btree v0.0.0-20250924232609-2c6195d95951/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= +github.com/cosmos/cosmos-db v1.1.3 h1:7QNT77+vkefostcKkhrzDK9uoIEryzFrU9eoMeaQOPY= +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/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= +github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= +github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= +github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/cosmos/iavl v1.2.6 h1:Hs3LndJbkIB+rEvToKJFXZvKo6Vy0Ex1SJ54hhtioIs= +github.com/cosmos/iavl v1.2.6/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= +github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= +github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= +github.com/cosmos/ledger-cosmos-go v0.16.0 h1:YKlWPG9NnGZIEUb2bEfZ6zhON1CHlNTg0QKRRGcNEd0= +github.com/cosmos/ledger-cosmos-go v0.16.0/go.mod h1:WrM2xEa8koYoH2DgeIuZXNarF7FGuZl3mrIOnp3Dp0o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/dgraph-io/badger/v4 v4.6.0 h1:acOwfOOZ4p1dPRnYzvkVm7rUk2Y21TgPVepCy5dJdFQ= +github.com/dgraph-io/badger/v4 v4.6.0/go.mod h1:KSJ5VTuZNC3Sd+YhvVjk2nYua9UZnnTr/SkXvdtiPgI= +github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= +github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= +github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= +github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getsentry/sentry-go v0.35.0 h1:+FJNlnjJsZMG3g0/rmmP7GiKjQoUF5EXfEtBwtPtkzY= +github.com/getsentry/sentry-go v0.35.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-uuid v1.0.0/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/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w= +github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/linxGnu/grocksdb v1.10.1 h1:YX6gUcKvSC3d0s9DaqgbU+CRkZHzlELgHu1Z/kmtslg= +github.com/linxGnu/grocksdb v1.10.1/go.mod h1:C3CNe9UYc9hlEM2pC82AqiGS3LRW537u9LFV4wIZuHk= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +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/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/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= +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= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= +github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v1.0.1 h1:Ks/2tz/dOF+dbRynfZ0dEhcdL1lqw43Sa0zMXHpQ3aQ= +github.com/zondax/ledger-go v1.0.1/go.mod h1:j7IgMY39f30apthJYMd1YsHZRqdyu4KbVmUp0nU78X0= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +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.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +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-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.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/blockstm/memdb.go b/blockstm/memdb.go new file mode 100644 index 000000000000..cdc656d9f08e --- /dev/null +++ b/blockstm/memdb.go @@ -0,0 +1,209 @@ +package blockstm + +import ( + "io" + + "github.com/tidwall/btree" + + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/tracekv" + storetypes "cosmossdk.io/store/types" +) + +type ( + MemDB = GMemDB[[]byte] + ObjMemDB = GMemDB[any] +) + +var ( + _ storetypes.KVStore = (*MemDB)(nil) + _ storetypes.ObjKVStore = (*ObjMemDB)(nil) +) + +func NewMemDB() *MemDB { + return NewGMemDB(BytesIsZero, BytesLen) +} + +func NewObjMemDB() *ObjMemDB { + return NewGMemDB(ObjIsZero, ObjLen) +} + +type GMemDB[V any] struct { + btree.BTreeG[memdbItem[V]] + isZero func(V) bool + valueLen func(V) int +} + +func NewGMemDB[V any]( + isZero func(V) bool, + valueLen func(V) int, +) *GMemDB[V] { + return &GMemDB[V]{ + BTreeG: *btree.NewBTreeG[memdbItem[V]](KeyItemLess), + isZero: isZero, + valueLen: valueLen, + } +} + +// NewGMemDBNonConcurrent returns a new BTree which is not concurrency safe. +func NewGMemDBNonConcurrent[V any]( + isZero func(V) bool, + valueLen func(V) int, +) *GMemDB[V] { + return &GMemDB[V]{ + BTreeG: *btree.NewBTreeGOptions[memdbItem[V]](KeyItemLess, btree.Options{ + NoLocks: true, + }), + isZero: isZero, + valueLen: valueLen, + } +} + +func (db *GMemDB[V]) Scan(cb func(key Key, value V) bool) { + db.BTreeG.Scan(func(item memdbItem[V]) bool { + return cb(item.key, item.value) + }) +} + +func (db *GMemDB[V]) Get(key []byte) V { + item, ok := db.BTreeG.Get(memdbItem[V]{key: key}) + if !ok { + var empty V + return empty + } + return item.value +} + +func (db *GMemDB[V]) Has(key []byte) bool { + return !db.isZero(db.Get(key)) +} + +func (db *GMemDB[V]) Set(key []byte, value V) { + if db.isZero(value) { + panic("nil value not allowed") + } + db.BTreeG.Set(memdbItem[V]{key: key, value: value}) +} + +func (db *GMemDB[V]) Delete(key []byte) { + db.BTreeG.Delete(memdbItem[V]{key: key}) +} + +// OverlayGet returns a value from the btree and true if we found a value. +// When used as an overlay (e.g. WriteSet), it stores the `nil` value to represent deleted keys, +// so we return separate bool value for found status. +func (db *GMemDB[V]) OverlayGet(key Key) (V, bool) { + item, ok := db.BTreeG.Get(memdbItem[V]{key: key}) + if !ok { + var zero V + return zero, false + } + return item.value, true +} + +// OverlaySet sets a value in the btree +// When used as an overlay (e.g. WriteSet), it stores the `nil` value to represent deleted keys, +func (db *GMemDB[V]) OverlaySet(key Key, value V) { + db.BTreeG.Set(memdbItem[V]{key: key, value: value}) +} + +func (db *GMemDB[V]) Iterator(start, end []byte) storetypes.GIterator[V] { + return db.iterator(start, end, true) +} + +func (db *GMemDB[V]) ReverseIterator(start, end []byte) storetypes.GIterator[V] { + return db.iterator(start, end, false) +} + +func (db *GMemDB[V]) iterator(start, end Key, ascending bool) storetypes.GIterator[V] { + return NewMemDBIterator(start, end, db.Iter(), ascending) +} + +func (db *GMemDB[V]) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeIAVL +} + +// CacheWrap implements types.KVStore. +func (db *GMemDB[V]) CacheWrap() storetypes.CacheWrap { + return cachekv.NewGStore(db, db.isZero, db.valueLen) +} + +// CacheWrapWithTrace implements types.KVStore. +func (db *GMemDB[V]) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + if store, ok := any(db).(*GMemDB[[]byte]); ok { + return cachekv.NewGStore(tracekv.NewStore(store, w, tc), store.isZero, store.valueLen) + } + return db.CacheWrap() +} + +type MemDBIterator[V any] struct { + BTreeIteratorG[memdbItem[V]] +} + +var _ storetypes.Iterator = (*MemDBIterator[[]byte])(nil) + +func NewMemDBIterator[V any](start, end Key, iter btree.IterG[memdbItem[V]], ascending bool) *MemDBIterator[V] { + return &MemDBIterator[V]{*NewBTreeIteratorG( + memdbItem[V]{key: start}, + memdbItem[V]{key: end}, + iter, + ascending, + )} +} + +func NewNoopIterator[V any](start, end Key, ascending bool) storetypes.GIterator[V] { + return &MemDBIterator[V]{BTreeIteratorG[memdbItem[V]]{ + start: start, + end: end, + ascending: ascending, + valid: false, + }} +} + +func (it *MemDBIterator[V]) Value() V { + return it.Item().value +} + +type memdbItem[V any] struct { + key Key + value V +} + +var _ KeyItem = memdbItem[[]byte]{} + +func (item memdbItem[V]) GetKey() []byte { + return item.key +} + +type MultiMemDB struct { + dbs map[storetypes.StoreKey]storetypes.Store +} + +var _ MultiStore = (*MultiMemDB)(nil) + +func NewMultiMemDB(stores map[storetypes.StoreKey]int) *MultiMemDB { + dbs := make(map[storetypes.StoreKey]storetypes.Store, len(stores)) + for name := range stores { + switch name.(type) { + case *storetypes.ObjectStoreKey: + dbs[name] = NewObjMemDB() + default: + dbs[name] = NewMemDB() + } + } + return &MultiMemDB{ + dbs: dbs, + } +} + +func (mmdb *MultiMemDB) GetStore(store storetypes.StoreKey) storetypes.Store { + return mmdb.dbs[store] +} + +func (mmdb *MultiMemDB) GetKVStore(store storetypes.StoreKey) storetypes.KVStore { + return mmdb.GetStore(store).(storetypes.KVStore) +} + +func (mmdb *MultiMemDB) GetObjKVStore(store storetypes.StoreKey) storetypes.ObjKVStore { + return mmdb.GetStore(store).(storetypes.ObjKVStore) +} diff --git a/blockstm/mergeiterator.go b/blockstm/mergeiterator.go new file mode 100644 index 000000000000..a0b55506be56 --- /dev/null +++ b/blockstm/mergeiterator.go @@ -0,0 +1,247 @@ +package blockstm + +import ( + "bytes" + "errors" + + "cosmossdk.io/store/types" +) + +// cacheMergeIterator merges a parent Iterator and a cache Iterator. +// The cache iterator may return nil keys to signal that an item +// had been deleted (but not deleted in the parent). +// If the cache iterator has the same key as the parent, the +// cache shadows (overrides) the parent. +// +// TODO: Optimize by memoizing. +type cacheMergeIterator[V any] struct { + parent types.GIterator[V] + cache types.GIterator[V] + onClose func(types.GIterator[V]) + isZero func(V) bool + + ascending bool + valid bool +} + +var _ types.Iterator = (*cacheMergeIterator[[]byte])(nil) + +func NewCacheMergeIterator[V any]( + parent, cache types.GIterator[V], + ascending bool, onClose func(types.GIterator[V]), + isZero func(V) bool, +) types.GIterator[V] { + iter := &cacheMergeIterator[V]{ + parent: parent, + cache: cache, + ascending: ascending, + onClose: onClose, + isZero: isZero, + } + + iter.valid = iter.skipUntilExistsOrInvalid() + return iter +} + +// Domain implements Iterator. +// Returns parent domain because cache and parent domains are the same. +func (iter *cacheMergeIterator[V]) Domain() (start, end []byte) { + return iter.parent.Domain() +} + +// Valid implements Iterator. +func (iter *cacheMergeIterator[V]) Valid() bool { + return iter.valid +} + +// Next implements Iterator +func (iter *cacheMergeIterator[V]) Next() { + iter.assertValid() + + switch { + case !iter.parent.Valid(): + // If parent is invalid, get the next cache item. + iter.cache.Next() + case !iter.cache.Valid(): + // If cache is invalid, get the next parent item. + iter.parent.Next() + default: + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + switch iter.compare(keyP, keyC) { + case -1: // parent < cache + iter.parent.Next() + case 0: // parent == cache + iter.parent.Next() + iter.cache.Next() + case 1: // parent > cache + iter.cache.Next() + } + } + iter.valid = iter.skipUntilExistsOrInvalid() +} + +// Key implements Iterator +func (iter *cacheMergeIterator[V]) Key() []byte { + iter.assertValid() + + // If parent is invalid, get the cache key. + if !iter.parent.Valid() { + return iter.cache.Key() + } + + // If cache is invalid, get the parent key. + if !iter.cache.Valid() { + return iter.parent.Key() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return keyP + case 0: // parent == cache + return keyP + case 1: // parent > cache + return keyC + default: + panic("invalid compare result") + } +} + +// Value implements Iterator +func (iter *cacheMergeIterator[V]) Value() V { + iter.assertValid() + + // If parent is invalid, get the cache value. + if !iter.parent.Valid() { + return iter.cache.Value() + } + + // If cache is invalid, get the parent value. + if !iter.cache.Valid() { + return iter.parent.Value() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return iter.parent.Value() + case 0: // parent == cache + return iter.cache.Value() + case 1: // parent > cache + return iter.cache.Value() + default: + panic("invalid comparison result") + } +} + +// Close implements Iterator +func (iter *cacheMergeIterator[V]) Close() error { + if iter.onClose != nil { + iter.onClose(iter) + } + + err1 := iter.cache.Close() + if err := iter.parent.Close(); err != nil { + return err + } + + return err1 +} + +// Error returns an error if the cacheMergeIterator is invalid defined by the +// Valid method. +func (iter *cacheMergeIterator[V]) Error() error { + if !iter.Valid() { + return errors.New("invalid cacheMergeIterator") + } + + return nil +} + +// If not valid, panics. +// NOTE: May have side-effect of iterating over cache. +func (iter *cacheMergeIterator[V]) assertValid() { + if err := iter.Error(); err != nil { + panic(err) + } +} + +// Like bytes.Compare but opposite if not ascending. +func (iter *cacheMergeIterator[V]) compare(a, b []byte) int { + if iter.ascending { + return bytes.Compare(a, b) + } + + return bytes.Compare(a, b) * -1 +} + +// Skip all delete-items from the cache w/ `key < until`. After this function, +// current cache item is a non-delete-item, or `until <= key`. +// 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[V]) skipCacheDeletes(until []byte) { + for iter.cache.Valid() && + iter.isZero(iter.cache.Value()) && + (until == nil || iter.compare(iter.cache.Key(), until) < 0) { + iter.cache.Next() + } +} + +// 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[V]) skipUntilExistsOrInvalid() bool { + for { + // If parent is invalid, fast-forward cache. + if !iter.parent.Valid() { + iter.skipCacheDeletes(nil) + return iter.cache.Valid() + } + // Parent is valid. + + if !iter.cache.Valid() { + return true + } + // Parent is valid, cache is valid. + + // Compare parent and cache. + keyP := iter.parent.Key() + keyC := iter.cache.Key() + + switch iter.compare(keyP, keyC) { + case -1: // parent < cache. + return true + + case 0: // parent == cache. + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if iter.isZero(valueC) { + iter.parent.Next() + iter.cache.Next() + + continue + } + // Cache is not a delete. + + return true // cache exists. + case 1: // cache < parent + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if iter.isZero(valueC) { + iter.skipCacheDeletes(keyP) + continue + } + // Cache is not a delete. + + return true // cache exists. + } + } +} diff --git a/blockstm/mock_block.go b/blockstm/mock_block.go new file mode 100644 index 000000000000..6649e2166ab7 --- /dev/null +++ b/blockstm/mock_block.go @@ -0,0 +1,169 @@ +package blockstm + +import ( + cryptorand "crypto/rand" + "encoding/binary" + "fmt" + "strings" + + "github.com/cometbft/cometbft/crypto/secp256k1" + + storetypes "cosmossdk.io/store/types" +) + +var ( + StoreKeyAuth = storetypes.NewKVStoreKey("acc") + StoreKeyBank = storetypes.NewKVStoreKey("bank") +) + +type Tx func(MultiStore) error + +type MockBlock struct { + Txs []Tx + Results []error +} + +func NewMockBlock(txs []Tx) *MockBlock { + return &MockBlock{ + Txs: txs, + Results: make([]error, len(txs)), + } +} + +func (b *MockBlock) Size() int { + return len(b.Txs) +} + +func (b *MockBlock) ExecuteTx(txn TxnIndex, store MultiStore) { + b.Results[txn] = b.Txs[txn](store) +} + +// Simulated transaction logic for tests and benchmarks + +// NoopTx verifies a signature and increases the nonce of the sender +func NoopTx(i int, sender string) Tx { + verifySig := genRandomSignature() + return func(store MultiStore) error { + verifySig() + return increaseNonce(i, sender, store.GetKVStore(StoreKeyAuth)) + } +} + +func BankTransferTx(i int, sender, receiver string, amount uint64) Tx { + base := NoopTx(i, sender) + return func(store MultiStore) error { + if err := base(store); err != nil { + return err + } + + return bankTransfer(i, sender, receiver, amount, store.GetKVStore(StoreKeyBank)) + } +} + +func IterateTx(i int, sender, receiver string, amount uint64) Tx { + base := BankTransferTx(i, sender, receiver, amount) + return func(store MultiStore) error { + if err := base(store); err != nil { + return err + } + + // find a nearby account, do a bank transfer + accStore := store.GetKVStore(StoreKeyAuth) + + { + it := accStore.Iterator([]byte("nonce"+sender), nil) + defer it.Close() + + var j int + for ; it.Valid(); it.Next() { + j++ + if j > 5 { + recipient := strings.TrimPrefix(string(it.Key()), "nonce") + return bankTransfer(i, sender, recipient, amount, store.GetKVStore(StoreKeyBank)) + } + } + } + + { + it := accStore.ReverseIterator([]byte("nonce"), []byte("nonce"+sender)) + defer it.Close() + + var j int + for ; it.Valid(); it.Next() { + j++ + if j > 5 { + recipient := strings.TrimPrefix(string(it.Key()), "nonce") + return bankTransfer(i, sender, recipient, amount, store.GetKVStore(StoreKeyBank)) + } + } + } + + return nil + } +} + +func genRandomSignature() func() { + privKey := secp256k1.GenPrivKey() + signBytes := make([]byte, 1024) + if _, err := cryptorand.Read(signBytes); err != nil { + panic(err) + } + sig, _ := privKey.Sign(signBytes) + pubKey := privKey.PubKey() + + return func() { + pubKey.VerifySignature(signBytes, sig) + } +} + +func increaseNonce(i int, sender string, store storetypes.KVStore) error { + nonceKey := []byte("nonce" + sender) + var nonce uint64 + v := store.Get(nonceKey) + if v != nil { + nonce = binary.BigEndian.Uint64(v) + } + + var bz [8]byte + binary.BigEndian.PutUint64(bz[:], nonce+1) + store.Set(nonceKey, bz[:]) + + v = store.Get(nonceKey) + if binary.BigEndian.Uint64(v) != nonce+1 { + return fmt.Errorf("nonce not incremented: %d", binary.BigEndian.Uint64(v)) + } + + return nil +} + +func bankTransfer(i int, sender, receiver string, amount uint64, store storetypes.KVStore) error { + senderKey := []byte("balance" + sender) + receiverKey := []byte("balance" + receiver) + + var senderBalance, receiverBalance uint64 + v := store.Get(senderKey) + if v != nil { + senderBalance = binary.BigEndian.Uint64(v) + } + + v = store.Get(receiverKey) + if v != nil { + receiverBalance = binary.BigEndian.Uint64(v) + } + + if senderBalance >= amount { + // avoid the failure + senderBalance -= amount + } + + receiverBalance += amount + + var bz1, bz2 [8]byte + binary.BigEndian.PutUint64(bz1[:], senderBalance) + store.Set(senderKey, bz1[:]) + + binary.BigEndian.PutUint64(bz2[:], receiverBalance) + store.Set(receiverKey, bz2[:]) + + return nil +} diff --git a/blockstm/multimvview.go b/blockstm/multimvview.go new file mode 100644 index 000000000000..af7523b50d1f --- /dev/null +++ b/blockstm/multimvview.go @@ -0,0 +1,65 @@ +package blockstm + +import storetypes "cosmossdk.io/store/types" + +const ViewsPreAllocate = 4 + +// MultiMVMemoryView don't need to be thread-safe, there's a dedicated instance for each tx execution. +type MultiMVMemoryView struct { + stores map[storetypes.StoreKey]int + views map[storetypes.StoreKey]MVView + newMVView func(storetypes.StoreKey, TxnIndex) MVView + txn TxnIndex +} + +var _ MultiStore = (*MultiMVMemoryView)(nil) + +func NewMultiMVMemoryView( + stores map[storetypes.StoreKey]int, + newMVView func(storetypes.StoreKey, TxnIndex) MVView, + txn TxnIndex, +) *MultiMVMemoryView { + return &MultiMVMemoryView{ + stores: stores, + views: make(map[storetypes.StoreKey]MVView, ViewsPreAllocate), + newMVView: newMVView, + txn: txn, + } +} + +func (mv *MultiMVMemoryView) getViewOrInit(name storetypes.StoreKey) MVView { + view, ok := mv.views[name] + if !ok { + view = mv.newMVView(name, mv.txn) + mv.views[name] = view + } + return view +} + +func (mv *MultiMVMemoryView) GetStore(name storetypes.StoreKey) storetypes.Store { + return mv.getViewOrInit(name) +} + +func (mv *MultiMVMemoryView) GetKVStore(name storetypes.StoreKey) storetypes.KVStore { + return mv.GetStore(name).(storetypes.KVStore) +} + +func (mv *MultiMVMemoryView) GetObjKVStore(name storetypes.StoreKey) storetypes.ObjKVStore { + return mv.GetStore(name).(storetypes.ObjKVStore) +} + +func (mv *MultiMVMemoryView) ReadSet() *MultiReadSet { + rs := make(MultiReadSet, len(mv.views)) + for key, view := range mv.views { + rs[mv.stores[key]] = view.ReadSet() + } + return &rs +} + +func (mv *MultiMVMemoryView) ApplyWriteSet(version TxnVersion) MultiLocations { + newLocations := make(MultiLocations, len(mv.views)) + for key, view := range mv.views { + newLocations[mv.stores[key]] = view.ApplyWriteSet(version) + } + return newLocations +} diff --git a/blockstm/mvdata.go b/blockstm/mvdata.go new file mode 100644 index 000000000000..d43f68c52514 --- /dev/null +++ b/blockstm/mvdata.go @@ -0,0 +1,233 @@ +package blockstm + +import ( + "bytes" + + storetypes "cosmossdk.io/store/types" +) + +const ( + OuterBTreeDegree = 4 // Since we do copy-on-write a lot, smaller degree means smaller allocations + InnerBTreeDegree = 4 +) + +type MVData = GMVData[[]byte] + +func NewMVData() *MVData { + return NewGMVData(BytesIsZero, BytesLen) +} + +type GMVData[V any] struct { + BTree[dataItem[V]] + isZero func(V) bool + valueLen func(V) int +} + +func NewMVStore(key storetypes.StoreKey) MVStore { + switch key.(type) { + case *storetypes.ObjectStoreKey: + return NewGMVData(ObjIsZero, ObjLen) + default: + return NewGMVData(BytesIsZero, BytesLen) + } +} + +func NewGMVData[V any](isZero func(V) bool, valueLen func(V) int) *GMVData[V] { + return &GMVData[V]{ + BTree: *NewBTree(KeyItemLess[dataItem[V]], OuterBTreeDegree), + isZero: isZero, + valueLen: valueLen, + } +} + +// getTree returns `nil` if not found +func (d *GMVData[V]) getTree(key Key) *BTree[secondaryDataItem[V]] { + outer, _ := d.Get(dataItem[V]{Key: key}) + return outer.Tree +} + +// getTreeOrDefault set a new tree atomically if not found. +func (d *GMVData[V]) getTreeOrDefault(key Key) *BTree[secondaryDataItem[V]] { + return d.GetOrDefault(dataItem[V]{Key: key}, func(item *dataItem[V]) { + if item.Tree == nil { + item.Tree = NewBTree(secondaryLesser[V], InnerBTreeDegree) + } + }).Tree +} + +func (d *GMVData[V]) Write(key Key, value V, version TxnVersion) { + tree := d.getTreeOrDefault(key) + tree.Set(secondaryDataItem[V]{Index: version.Index, Incarnation: version.Incarnation, Value: value}) +} + +func (d *GMVData[V]) WriteEstimate(key Key, txn TxnIndex) { + tree := d.getTreeOrDefault(key) + tree.Set(secondaryDataItem[V]{Index: txn, Estimate: true}) +} + +func (d *GMVData[V]) Delete(key Key, txn TxnIndex) { + tree := d.getTreeOrDefault(key) + tree.Delete(secondaryDataItem[V]{Index: txn}) +} + +// Read returns the value and the version of the value that's less than the given txn. +// If the key is not found, returns `(nil, InvalidTxnVersion, false)`. +// If the key is found but value is an estimate, returns `(nil, BlockingTxn, true)`. +// If the key is found, returns `(value, version, false)`, `value` can be `nil` which means deleted. +func (d *GMVData[V]) Read(key Key, txn TxnIndex) (V, TxnVersion, bool) { + var zero V + if txn == 0 { + return zero, InvalidTxnVersion, false + } + + tree := d.getTree(key) + if tree == nil { + return zero, InvalidTxnVersion, false + } + + // find the closing txn that's less than the given txn + item, ok := seekClosestTxn(tree, txn) + if !ok { + return zero, InvalidTxnVersion, false + } + + return item.Value, item.Version(), item.Estimate +} + +func (d *GMVData[V]) Iterator( + opts IteratorOptions, txn TxnIndex, + waitFn func(TxnIndex), +) *MVIterator[V] { + return NewMVIterator(opts, txn, d.Iter(), waitFn) +} + +// ValidateReadSet validates the read descriptors, +// returns true if valid. +func (d *GMVData[V]) ValidateReadSet(txn TxnIndex, rs *ReadSet) bool { + for _, desc := range rs.Reads { + _, version, estimate := d.Read(desc.Key, txn) + if estimate { + // previously read entry from data, now ESTIMATE + return false + } + if version != desc.Version { + // previously read entry from data, now NOT_FOUND, + // or read some entry, but not the same version as before + return false + } + } + + for _, desc := range rs.Iterators { + if !d.validateIterator(desc, txn) { + return false + } + } + + return true +} + +// validateIterator validates the iteration descriptor by replaying and compare the recorded reads. +// returns true if valid. +func (d *GMVData[V]) validateIterator(desc IteratorDescriptor, txn TxnIndex) bool { + it := NewMVIterator(desc.IteratorOptions, txn, d.Iter(), nil) + defer it.Close() + + var i int + for ; it.Valid(); it.Next() { + if desc.Stop != nil { + if BytesBeyond(it.Key(), desc.Stop, desc.Ascending) { + break + } + } + + if i >= len(desc.Reads) { + return false + } + + read := desc.Reads[i] + if read.Version != it.Version() || !bytes.Equal(read.Key, it.Key()) { + return false + } + + i++ + } + + // we read an estimate value, fail the validation. + if it.ReadEstimateValue() { + return false + } + + return i == len(desc.Reads) +} + +func (d *GMVData[V]) Snapshot() (snapshot []GKVPair[V]) { + d.SnapshotTo(func(key Key, value V) bool { + snapshot = append(snapshot, GKVPair[V]{key, value}) + return true + }) + return +} + +func (d *GMVData[V]) SnapshotTo(cb func(Key, V) bool) { + d.Scan(func(outer dataItem[V]) bool { + item, ok := outer.Tree.Max() + if !ok { + return true + } + + if item.Estimate { + return true + } + + return cb(outer.Key, item.Value) + }) +} + +func (d *GMVData[V]) SnapshotToStore(store storetypes.Store) { + kv := store.(storetypes.GKVStore[V]) + d.SnapshotTo(func(key Key, value V) bool { + if d.isZero(value) { + kv.Delete(key) + } else { + kv.Set(key, value) + } + return true + }) +} + +type GKVPair[V any] struct { + Key Key + Value V +} +type KVPair = GKVPair[[]byte] + +type dataItem[V any] struct { + Key Key + Tree *BTree[secondaryDataItem[V]] +} + +var _ KeyItem = dataItem[[]byte]{} + +func (item dataItem[V]) GetKey() []byte { + return item.Key +} + +type secondaryDataItem[V any] struct { + Index TxnIndex + Incarnation Incarnation + Value V + Estimate bool +} + +func secondaryLesser[V any](a, b secondaryDataItem[V]) bool { + return a.Index < b.Index +} + +func (item secondaryDataItem[V]) Version() TxnVersion { + return TxnVersion{Index: item.Index, Incarnation: item.Incarnation} +} + +// seekClosestTxn returns the closest txn that's less than the given txn. +func seekClosestTxn[V any](tree *BTree[secondaryDataItem[V]], txn TxnIndex) (secondaryDataItem[V], bool) { + return tree.ReverseSeek(secondaryDataItem[V]{Index: txn - 1}) +} diff --git a/blockstm/mvdata_test.go b/blockstm/mvdata_test.go new file mode 100644 index 000000000000..1cf8dd3a8b38 --- /dev/null +++ b/blockstm/mvdata_test.go @@ -0,0 +1,111 @@ +package blockstm + +import ( + "errors" + "fmt" + "testing" + + "github.com/test-go/testify/require" +) + +func TestEmptyMVData(t *testing.T) { + data := NewMVData() + value, _, estimate := data.Read([]byte("a"), 1) + require.False(t, estimate) + require.Nil(t, value) +} + +func TestMVData(t *testing.T) { + data := NewMVData() + + // read closest version + data.Write([]byte("a"), []byte("1"), TxnVersion{Index: 1, Incarnation: 1}) + data.Write([]byte("a"), []byte("2"), TxnVersion{Index: 2, Incarnation: 1}) + data.Write([]byte("a"), []byte("3"), TxnVersion{Index: 3, Incarnation: 1}) + data.Write([]byte("b"), []byte("2"), TxnVersion{Index: 2, Incarnation: 1}) + + // read closest version + value, _, estimate := data.Read([]byte("a"), 1) + require.False(t, estimate) + require.Nil(t, value) + + // read closest version + value, version, estimate := data.Read([]byte("a"), 4) + require.False(t, estimate) + require.Equal(t, []byte("3"), value) + require.Equal(t, TxnVersion{Index: 3, Incarnation: 1}, version) + + // read closest version + value, version, estimate = data.Read([]byte("a"), 3) + require.False(t, estimate) + require.Equal(t, []byte("2"), value) + require.Equal(t, TxnVersion{Index: 2, Incarnation: 1}, version) + + // read closest version + value, version, estimate = data.Read([]byte("b"), 3) + require.False(t, estimate) + require.Equal(t, []byte("2"), value) + require.Equal(t, TxnVersion{Index: 2, Incarnation: 1}, version) + + // new incarnation overrides old + data.Write([]byte("a"), []byte("3-2"), TxnVersion{Index: 3, Incarnation: 2}) + value, version, estimate = data.Read([]byte("a"), 4) + require.False(t, estimate) + require.Equal(t, []byte("3-2"), value) + require.Equal(t, TxnVersion{Index: 3, Incarnation: 2}, version) + + // read estimate + data.WriteEstimate([]byte("a"), 3) + _, version, estimate = data.Read([]byte("a"), 4) + require.True(t, estimate) + require.Equal(t, TxnIndex(3), version.Index) + + // delete value + data.Delete([]byte("a"), 3) + value, version, estimate = data.Read([]byte("a"), 4) + require.False(t, estimate) + require.Equal(t, []byte("2"), value) + require.Equal(t, TxnVersion{Index: 2, Incarnation: 1}, version) + + data.Delete([]byte("b"), 2) + value, _, estimate = data.Read([]byte("b"), 4) + require.False(t, estimate) + require.Nil(t, value) +} + +func TestReadErrConversion(t *testing.T) { + err := fmt.Errorf("wrap: %w", ErrReadError{BlockingTxn: 1}) + var readErr ErrReadError + require.True(t, errors.As(err, &readErr)) + require.Equal(t, TxnIndex(1), readErr.BlockingTxn) +} + +func TestSnapshot(t *testing.T) { + storage := NewMemDB() + // initial value + storage.Set([]byte("a"), []byte("0")) + storage.Set([]byte("d"), []byte("0")) + + data := NewMVData() + // read closest version + data.Write([]byte("a"), []byte("1"), TxnVersion{Index: 1, Incarnation: 1}) + data.Write([]byte("a"), []byte("2"), TxnVersion{Index: 2, Incarnation: 1}) + data.Write([]byte("a"), []byte("3"), TxnVersion{Index: 3, Incarnation: 1}) + data.Write([]byte("b"), []byte("2"), TxnVersion{Index: 2, Incarnation: 1}) + data.Write([]byte("d"), []byte("1"), TxnVersion{Index: 2, Incarnation: 1}) + // delete the key "d" in tx 3 + data.Write([]byte("d"), nil, TxnVersion{Index: 3, Incarnation: 1}) + data.WriteEstimate([]byte("c"), 2) + + require.Equal(t, []KVPair{ + {[]byte("a"), []byte("3")}, + {[]byte("b"), []byte("2")}, + {[]byte("d"), nil}, + }, data.Snapshot()) + + data.SnapshotToStore(storage) + require.Equal(t, []byte("3"), storage.Get([]byte("a"))) + require.Equal(t, []byte("2"), storage.Get([]byte("b"))) + require.Nil(t, storage.Get([]byte("d"))) + require.Equal(t, 2, storage.Len()) +} diff --git a/blockstm/mviterator.go b/blockstm/mviterator.go new file mode 100644 index 000000000000..9fba4faace1c --- /dev/null +++ b/blockstm/mviterator.go @@ -0,0 +1,124 @@ +package blockstm + +import ( + "github.com/tidwall/btree" + + storetypes "cosmossdk.io/store/types" +) + +// MVIterator is an iterator for a multi-versioned store. +type MVIterator[V any] struct { + BTreeIteratorG[dataItem[V]] + txn TxnIndex + + // cache current found value and version + value V + version TxnVersion + + // record the observed reads during iteration during execution + reads []ReadDescriptor + // blocking call to wait for dependent transaction to finish, `nil` in validation mode + waitFn func(TxnIndex) + // signal the validation to fail + readEstimateValue bool +} + +var _ storetypes.Iterator = (*MVIterator[[]byte])(nil) + +func NewMVIterator[V any]( + opts IteratorOptions, txn TxnIndex, iter btree.IterG[dataItem[V]], + waitFn func(TxnIndex), +) *MVIterator[V] { + it := &MVIterator[V]{ + BTreeIteratorG: *NewBTreeIteratorG( + dataItem[V]{Key: opts.Start}, + dataItem[V]{Key: opts.End}, + iter, + opts.Ascending, + ), + txn: txn, + waitFn: waitFn, + } + it.resolveValue() + return it +} + +// Executing returns if the iterator is running in execution mode. +func (it *MVIterator[V]) Executing() bool { + return it.waitFn != nil +} + +func (it *MVIterator[V]) Next() { + it.BTreeIteratorG.Next() + it.resolveValue() +} + +func (it *MVIterator[V]) Value() V { + return it.value +} + +func (it *MVIterator[V]) Version() TxnVersion { + return it.version +} + +func (it *MVIterator[V]) Reads() []ReadDescriptor { + return it.reads +} + +func (it *MVIterator[V]) ReadEstimateValue() bool { + return it.readEstimateValue +} + +// resolveValue skips the non-exist values in the iterator based on the txn index, and caches the first existing one. +func (it *MVIterator[V]) resolveValue() { + inner := &it.BTreeIteratorG + for ; inner.Valid(); inner.Next() { + v, ok := it.resolveValueInner(inner.Item().Tree) + if !ok { + // abort the iterator + it.valid = false + // signal the validation to fail + it.readEstimateValue = true + return + } + if v == nil { + continue + } + + it.value = v.Value + it.version = v.Version() + if it.Executing() { + it.reads = append(it.reads, ReadDescriptor{ + Key: inner.Item().Key, + Version: it.version, + }) + } + return + } +} + +// resolveValueInner loop until we find a value that is not an estimate, +// wait for dependency if gets an ESTIMATE. +// returns: +// - (nil, true) if the value is not found +// - (nil, false) if the value is an estimate and we should fail the validation +// - (v, true) if the value is found +func (it *MVIterator[V]) resolveValueInner(tree *BTree[secondaryDataItem[V]]) (*secondaryDataItem[V], bool) { + for { + v, ok := seekClosestTxn(tree, it.txn) + if !ok { + return nil, true + } + + if v.Estimate { + if it.Executing() { + it.waitFn(v.Index) + continue + } + // in validation mode, it should fail validation immediately + return nil, false + } + + return &v, true + } +} diff --git a/blockstm/mvmemory.go b/blockstm/mvmemory.go new file mode 100644 index 000000000000..f93186695adb --- /dev/null +++ b/blockstm/mvmemory.go @@ -0,0 +1,149 @@ +package blockstm + +import ( + "sync/atomic" + + storetypes "cosmossdk.io/store/types" +) + +type ( + Locations []Key // keys are sorted + MultiLocations map[int]Locations +) + +// MVMemory implements `Algorithm 2 The MVMemory module` +type MVMemory struct { + storage MultiStore + scheduler *Scheduler + stores map[storetypes.StoreKey]int + data []MVStore + lastWrittenLocations []atomic.Pointer[MultiLocations] + lastReadSet []atomic.Pointer[MultiReadSet] +} + +func NewMVMemory( + block_size int, stores map[storetypes.StoreKey]int, + storage MultiStore, scheduler *Scheduler, +) *MVMemory { + return NewMVMemoryWithEstimates(block_size, stores, storage, scheduler, nil) +} + +func NewMVMemoryWithEstimates( + block_size int, stores map[storetypes.StoreKey]int, + storage MultiStore, scheduler *Scheduler, estimates []MultiLocations, +) *MVMemory { + data := make([]MVStore, len(stores)) + for key, i := range stores { + data[i] = NewMVStore(key) + } + + mv := &MVMemory{ + storage: storage, + scheduler: scheduler, + stores: stores, + data: data, + lastWrittenLocations: make([]atomic.Pointer[MultiLocations], block_size), + lastReadSet: make([]atomic.Pointer[MultiReadSet], block_size), + } + + // init with pre-estimates + for txn, est := range estimates { + mv.rcuUpdateWrittenLocations(TxnIndex(txn), est) + mv.ConvertWritesToEstimates(TxnIndex(txn)) + } + + return mv +} + +func (mv *MVMemory) Record(version TxnVersion, view *MultiMVMemoryView) bool { + newLocations := view.ApplyWriteSet(version) + wroteNewLocation := mv.rcuUpdateWrittenLocations(version.Index, newLocations) + mv.lastReadSet[version.Index].Store(view.ReadSet()) + return wroteNewLocation +} + +// newLocations are sorted +func (mv *MVMemory) rcuUpdateWrittenLocations(txn TxnIndex, newLocations MultiLocations) bool { + var wroteNewLocation bool + + prevLocations := mv.readLastWrittenLocations(txn) + for i, newLoc := range newLocations { + prevLoc, ok := prevLocations[i] + if !ok { + if len(newLocations[i]) > 0 { + wroteNewLocation = true + } + continue + } + + DiffOrderedList(prevLoc, newLoc, func(key Key, is_new bool) bool { + if is_new { + wroteNewLocation = true + } else { + mv.data[i].Delete(key, txn) + } + return true + }) + } + + // delete all the keys in un-touched stores + for i, prevLoc := range prevLocations { + if _, ok := newLocations[i]; ok { + continue + } + + for _, key := range prevLoc { + mv.data[i].Delete(key, txn) + } + } + + mv.lastWrittenLocations[txn].Store(&newLocations) + return wroteNewLocation +} + +func (mv *MVMemory) ConvertWritesToEstimates(txn TxnIndex) { + for i, locations := range mv.readLastWrittenLocations(txn) { + for _, key := range locations { + mv.data[i].WriteEstimate(key, txn) + } + } +} + +func (mv *MVMemory) ValidateReadSet(txn TxnIndex) bool { + // Invariant: at least one `Record` call has been made for `txn` + rs := *mv.lastReadSet[txn].Load() + for store, readSet := range rs { + if !mv.data[store].ValidateReadSet(txn, readSet) { + return false + } + } + return true +} + +func (mv *MVMemory) readLastWrittenLocations(txn TxnIndex) MultiLocations { + p := mv.lastWrittenLocations[txn].Load() + if p != nil { + return *p + } + return nil +} + +func (mv *MVMemory) WriteSnapshot(storage MultiStore) { + for name, i := range mv.stores { + mv.data[i].SnapshotToStore(storage.GetStore(name)) + } +} + +// View creates a view for a particular transaction. +func (mv *MVMemory) View(txn TxnIndex) *MultiMVMemoryView { + return NewMultiMVMemoryView(mv.stores, mv.newMVView, txn) +} + +func (mv *MVMemory) newMVView(name storetypes.StoreKey, txn TxnIndex) MVView { + i := mv.stores[name] + return NewMVView(i, mv.storage.GetStore(name), mv.GetMVStore(i), mv.scheduler, txn) +} + +func (mv *MVMemory) GetMVStore(i int) MVStore { + return mv.data[i] +} diff --git a/blockstm/mvmemory_test.go b/blockstm/mvmemory_test.go new file mode 100644 index 000000000000..ec7d3939aff1 --- /dev/null +++ b/blockstm/mvmemory_test.go @@ -0,0 +1,219 @@ +package blockstm + +import ( + "testing" + + "github.com/test-go/testify/require" + + storetypes "cosmossdk.io/store/types" +) + +func TestMVMemoryRecord(t *testing.T) { + stores := map[storetypes.StoreKey]int{StoreKeyAuth: 0} + storage := NewMultiMemDB(stores) + scheduler := NewScheduler(16) + mv := NewMVMemory(16, stores, storage, scheduler) + + var views []*MultiMVMemoryView + for i := TxnIndex(0); i < 3; i++ { + version := TxnVersion{i, 0} + view := mv.View(version.Index) + store := view.GetKVStore(StoreKeyAuth) + + _ = store.Get([]byte("a")) + _ = store.Get([]byte("d")) + store.Set([]byte("a"), []byte("1")) + store.Set([]byte("b"), []byte("1")) + store.Set([]byte("c"), []byte("1")) + + views = append(views, view) + } + + for i, view := range views { + wroteNewLocation := mv.Record(TxnVersion{TxnIndex(i), 0}, view) + require.True(t, wroteNewLocation) + } + + require.True(t, mv.ValidateReadSet(0)) + require.False(t, mv.ValidateReadSet(1)) + require.False(t, mv.ValidateReadSet(2)) + + // abort 2 and 3 + mv.ConvertWritesToEstimates(1) + mv.ConvertWritesToEstimates(2) + + resultCh := make(chan struct{}, 1) + go func() { + view := mv.View(3) + store := view.GetKVStore(StoreKeyAuth) + // will wait for tx 2 + store.Get([]byte("a")) + wroteNewLocation := mv.Record(TxnVersion{3, 1}, view) + require.False(t, wroteNewLocation) + require.True(t, mv.ValidateReadSet(3)) + resultCh <- struct{}{} + }() + + { + data := mv.GetMVStore(0).(*MVData) + value, version, estimate := data.Read(Key("a"), 1) + require.False(t, estimate) + require.Equal(t, []byte("1"), value) + require.Equal(t, TxnVersion{0, 0}, version) + + _, version, estimate = data.Read(Key("a"), 2) + require.True(t, estimate) + require.Equal(t, TxnIndex(1), version.Index) + + _, version, estimate = data.Read(Key("a"), 3) + require.True(t, estimate) + require.Equal(t, TxnIndex(2), version.Index) + } + + // rerun tx 1 + { + view := mv.View(1) + store := view.GetKVStore(StoreKeyAuth) + + _ = store.Get([]byte("a")) + _ = store.Get([]byte("d")) + store.Set([]byte("a"), []byte("2")) + store.Set([]byte("b"), []byte("2")) + store.Set([]byte("c"), []byte("2")) + + wroteNewLocation := mv.Record(TxnVersion{1, 1}, view) + require.False(t, wroteNewLocation) + require.True(t, mv.ValidateReadSet(1)) + } + + // rerun tx 2 + // don't write `c` this time + { + version := TxnVersion{2, 1} + view := mv.View(version.Index) + store := view.GetKVStore(StoreKeyAuth) + + _ = store.Get([]byte("a")) + _ = store.Get([]byte("d")) + store.Set([]byte("a"), []byte("3")) + store.Set([]byte("b"), []byte("3")) + + wroteNewLocation := mv.Record(version, view) + require.False(t, wroteNewLocation) + require.True(t, mv.ValidateReadSet(2)) + + scheduler.FinishExecution(version, wroteNewLocation) + + // wait for dependency to finish + <-resultCh + } + + // run tx 3 + { + view := mv.View(3) + store := view.GetKVStore(StoreKeyAuth) + + _ = store.Get([]byte("a")) + + wroteNewLocation := mv.Record(TxnVersion{3, 1}, view) + require.False(t, wroteNewLocation) + require.True(t, mv.ValidateReadSet(3)) + } + + { + data := mv.GetMVStore(0).(*MVData) + value, version, estimate := data.Read(Key("a"), 2) + require.False(t, estimate) + require.Equal(t, []byte("2"), value) + require.Equal(t, TxnVersion{1, 1}, version) + + value, version, estimate = data.Read(Key("a"), 3) + require.False(t, estimate) + require.Equal(t, []byte("3"), value) + require.Equal(t, TxnVersion{2, 1}, version) + + value, version, estimate = data.Read(Key("c"), 3) + require.False(t, estimate) + require.Equal(t, []byte("2"), value) + require.Equal(t, TxnVersion{1, 1}, version) + } +} + +func TestMVMemoryDelete(t *testing.T) { + nonceKey, balanceKey := []byte("nonce"), []byte("balance") + + stores := map[storetypes.StoreKey]int{StoreKeyAuth: 0, StoreKeyBank: 1} + storage := NewMultiMemDB(stores) + { + // genesis state + authStore := storage.GetKVStore(StoreKeyAuth) + authStore.Set(nonceKey, []byte{0}) + bankStore := storage.GetKVStore(StoreKeyBank) + bankStore.Set(balanceKey, []byte{100}) + } + scheduler := NewScheduler(16) + mv := NewMVMemory(16, stores, storage, scheduler) + + genMockTx := func(txNonce int) func(*MultiMVMemoryView) bool { + return func(view *MultiMVMemoryView) bool { + bankStore := view.GetKVStore(StoreKeyBank) + balance := int(bankStore.Get(balanceKey)[0]) + if balance < 50 { + // insurfficient balance + return false + } + + authStore := view.GetKVStore(StoreKeyAuth) + nonce := int(authStore.Get(nonceKey)[0]) + // do a set no matter what + authStore.Set(nonceKey, []byte{byte(nonce)}) + if nonce != txNonce { + // invalid nonce + return false + } + + authStore.Set(nonceKey, []byte{byte(nonce + 1)}) + bankStore.Set(balanceKey, []byte{byte(balance - 50)}) + return true + } + } + + tx0, tx1, tx2 := genMockTx(0), genMockTx(1), genMockTx(2) + + view0 := mv.View(0) + require.True(t, tx0(view0)) + view1 := mv.View(1) + require.False(t, tx1(view1)) + view2 := mv.View(2) + require.False(t, tx2(view2)) + + require.True(t, mv.Record(TxnVersion{1, 0}, view1)) + require.True(t, mv.Record(TxnVersion{2, 0}, view2)) + require.True(t, mv.Record(TxnVersion{0, 0}, view0)) + + require.True(t, mv.ValidateReadSet(0)) + require.False(t, mv.ValidateReadSet(1)) + mv.ConvertWritesToEstimates(1) + require.False(t, mv.ValidateReadSet(2)) + mv.ConvertWritesToEstimates(2) + + // re-execute tx 1 and 2 + view1 = mv.View(1) + require.True(t, tx1(view1)) + mv.Record(TxnVersion{1, 1}, view1) + require.True(t, mv.ValidateReadSet(1)) + + view2 = mv.View(2) + // tx 2 fail due to insufficient balance, but stm validation is successful. + require.False(t, tx2(view2)) + mv.Record(TxnVersion{2, 1}, view2) + require.True(t, mv.ValidateReadSet(2)) + + mv.WriteSnapshot(storage) + { + authStore := storage.GetKVStore(StoreKeyAuth) + require.Equal(t, []byte{2}, authStore.Get(nonceKey)) + bankStore := storage.GetKVStore(StoreKeyBank) + require.Equal(t, []byte{0}, bankStore.Get(balanceKey)) + } +} diff --git a/blockstm/mvview.go b/blockstm/mvview.go new file mode 100644 index 000000000000..5254542cd9b4 --- /dev/null +++ b/blockstm/mvview.go @@ -0,0 +1,204 @@ +package blockstm + +import ( + "io" + + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/tracekv" + storetypes "cosmossdk.io/store/types" +) + +var ( + _ storetypes.KVStore = (*GMVMemoryView[[]byte])(nil) + _ storetypes.ObjKVStore = (*GMVMemoryView[any])(nil) + _ MVView = (*GMVMemoryView[[]byte])(nil) + _ MVView = (*GMVMemoryView[any])(nil) +) + +// GMVMemoryView wraps `MVMemory` for execution of a single transaction. +type GMVMemoryView[V any] struct { + storage storetypes.GKVStore[V] + mvData *GMVData[V] + scheduler *Scheduler + store int + + txn TxnIndex + readSet *ReadSet + writeSet *GMemDB[V] +} + +func NewMVView(store int, storage storetypes.Store, mvData MVStore, scheduler *Scheduler, txn TxnIndex) MVView { + switch data := mvData.(type) { + case *GMVData[any]: + return NewGMVMemoryView(store, storage.(storetypes.ObjKVStore), data, scheduler, txn) + case *GMVData[[]byte]: + return NewGMVMemoryView(store, storage.(storetypes.KVStore), data, scheduler, txn) + default: + panic("unsupported value type") + } +} + +func NewGMVMemoryView[V any](store int, storage storetypes.GKVStore[V], mvData *GMVData[V], scheduler *Scheduler, txn TxnIndex) *GMVMemoryView[V] { + return &GMVMemoryView[V]{ + store: store, + storage: storage, + mvData: mvData, + scheduler: scheduler, + txn: txn, + readSet: new(ReadSet), + } +} + +func (s *GMVMemoryView[V]) init() { + if s.writeSet == nil { + s.writeSet = NewGMemDBNonConcurrent(s.mvData.isZero, s.mvData.valueLen) + } +} + +func (s *GMVMemoryView[V]) waitFor(txn TxnIndex) { + cond := s.scheduler.WaitForDependency(s.txn, txn) + if cond != nil { + cond.Wait() + } +} + +func (s *GMVMemoryView[V]) ApplyWriteSet(version TxnVersion) Locations { + if s.writeSet == nil || s.writeSet.Len() == 0 { + return nil + } + + newLocations := make([]Key, 0, s.writeSet.Len()) + s.writeSet.Scan(func(key Key, value V) bool { + s.mvData.Write(key, value, version) + newLocations = append(newLocations, key) + return true + }) + + return newLocations +} + +func (s *GMVMemoryView[V]) ReadSet() *ReadSet { + return s.readSet +} + +func (s *GMVMemoryView[V]) Get(key []byte) V { + if s.writeSet != nil { + if value, found := s.writeSet.OverlayGet(key); found { + // value written by this txn + // nil value means deleted + return value + } + } + + for { + value, version, estimate := s.mvData.Read(key, s.txn) + if estimate { + // read ESTIMATE mark, wait for the blocking txn to finish + s.waitFor(version.Index) + continue + } + + // record the read version, invalid version is βŠ₯. + // if not found, record version βŠ₯ when reading from storage. + s.readSet.Reads = append(s.readSet.Reads, ReadDescriptor{key, version}) + if !version.Valid() { + return s.storage.Get(key) + } + return value + } +} + +func (s *GMVMemoryView[V]) Has(key []byte) bool { + return !s.mvData.isZero(s.Get(key)) +} + +func (s *GMVMemoryView[V]) Set(key []byte, value V) { + if s.mvData.isZero(value) { + panic("nil value is not allowed") + } + s.init() + s.writeSet.OverlaySet(key, value) +} + +func (s *GMVMemoryView[V]) Delete(key []byte) { + var empty V + s.init() + s.writeSet.OverlaySet(key, empty) +} + +func (s *GMVMemoryView[V]) Iterator(start, end []byte) storetypes.GIterator[V] { + return s.iterator(IteratorOptions{Start: start, End: end, Ascending: true}) +} + +func (s *GMVMemoryView[V]) ReverseIterator(start, end []byte) storetypes.GIterator[V] { + return s.iterator(IteratorOptions{Start: start, End: end, Ascending: false}) +} + +func (s *GMVMemoryView[V]) iterator(opts IteratorOptions) storetypes.GIterator[V] { + mvIter := s.mvData.Iterator(opts, s.txn, s.waitFor) + + var parentIter, wsIter storetypes.GIterator[V] + + if s.writeSet == nil { + wsIter = NewNoopIterator[V](opts.Start, opts.End, opts.Ascending) + } else { + wsIter = s.writeSet.iterator(opts.Start, opts.End, opts.Ascending) + } + + if opts.Ascending { + parentIter = s.storage.Iterator(opts.Start, opts.End) + } else { + parentIter = s.storage.ReverseIterator(opts.Start, opts.End) + } + + onClose := func(iter storetypes.GIterator[V]) { + reads := mvIter.Reads() + + var stopKey Key + if iter.Valid() { + stopKey = iter.Key() + + // if the iterator is not exhausted, the merge iterator may have read one more key which is not observed by + // the caller, in that case we remove that read descriptor. + if len(reads) > 0 { + lastRead := reads[len(reads)-1].Key + if BytesBeyond(lastRead, stopKey, opts.Ascending) { + reads = reads[:len(reads)-1] + } + } + } + + s.readSet.Iterators = append(s.readSet.Iterators, IteratorDescriptor{ + IteratorOptions: opts, + Stop: stopKey, + Reads: reads, + }) + } + + // three-way merge iterator + return NewCacheMergeIterator( + NewCacheMergeIterator(parentIter, mvIter, opts.Ascending, nil, s.mvData.isZero), + wsIter, + opts.Ascending, + onClose, + s.mvData.isZero, + ) +} + +// CacheWrap implements types.Store. +func (s *GMVMemoryView[V]) CacheWrap() storetypes.CacheWrap { + return cachekv.NewGStore(s, s.mvData.isZero, s.mvData.valueLen) +} + +// CacheWrapWithTrace implements types.Store. +func (s *GMVMemoryView[V]) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + if store, ok := any(s).(*GMVMemoryView[[]byte]); ok { + return cachekv.NewGStore(tracekv.NewStore(store, w, tc), store.mvData.isZero, store.mvData.valueLen) + } + return s.CacheWrap() +} + +// GetStoreType implements types.Store. +func (s *GMVMemoryView[V]) GetStoreType() storetypes.StoreType { + return s.storage.GetStoreType() +} diff --git a/blockstm/mvview_test.go b/blockstm/mvview_test.go new file mode 100644 index 000000000000..edacf88d1d90 --- /dev/null +++ b/blockstm/mvview_test.go @@ -0,0 +1,181 @@ +package blockstm + +import ( + "fmt" + "testing" + + "github.com/test-go/testify/require" + + storetypes "cosmossdk.io/store/types" +) + +func TestMVMemoryViewDelete(t *testing.T) { + stores := map[storetypes.StoreKey]int{ + StoreKeyAuth: 0, + } + storage := NewMultiMemDB(stores) + mv := NewMVMemory(16, stores, storage, nil) + + mview := mv.View(0) + view := mview.GetKVStore(StoreKeyAuth) + view.Set(Key("a"), []byte("1")) + view.Set(Key("b"), []byte("1")) + view.Set(Key("c"), []byte("1")) + require.True(t, mv.Record(TxnVersion{0, 0}, mview)) + + mview = mv.View(1) + view = mview.GetKVStore(StoreKeyAuth) + view.Delete(Key("a")) + view.Set(Key("b"), []byte("2")) + require.True(t, mv.Record(TxnVersion{1, 0}, mview)) + + mview = mv.View(2) + view = mview.GetKVStore(StoreKeyAuth) + require.Nil(t, view.Get(Key("a"))) + require.False(t, view.Has(Key("a"))) +} + +func TestMVMemoryViewIteration(t *testing.T) { + stores := map[storetypes.StoreKey]int{StoreKeyAuth: 0} + storage := NewMultiMemDB(stores) + mv := NewMVMemory(16, stores, storage, nil) + { + parentState := []KVPair{ + {Key("a"), []byte("1")}, + {Key("A"), []byte("1")}, + } + parent := storage.GetKVStore(StoreKeyAuth) + for _, kv := range parentState { + parent.Set(kv.Key, kv.Value) + } + } + + sets := [][]KVPair{ + {{Key("a"), []byte("1")}, {Key("b"), []byte("1")}, {Key("c"), []byte("1")}}, + {{Key("b"), []byte("2")}, {Key("c"), []byte("2")}, {Key("d"), []byte("2")}}, + {{Key("c"), []byte("3")}, {Key("d"), []byte("3")}, {Key("e"), []byte("3")}}, + {{Key("d"), []byte("4")}, {Key("f"), []byte("4")}}, + {{Key("e"), []byte("5")}, {Key("f"), []byte("5")}, {Key("g"), []byte("5")}}, + {{Key("f"), []byte("6")}, {Key("g"), []byte("6")}, {Key("a"), []byte("6")}}, + } + deletes := [][]Key{ + {}, + {}, + {Key("a")}, + {Key("A"), Key("e")}, + {}, + {Key("b"), Key("c"), Key("d")}, + } + + for i, pairs := range sets { + mview := mv.View(TxnIndex(i)) + view := mview.GetKVStore(StoreKeyAuth) + for _, kv := range pairs { + view.Set(kv.Key, kv.Value) + } + for _, key := range deletes[i] { + view.Delete(key) + } + require.True(t, mv.Record(TxnVersion{TxnIndex(i), 0}, mview)) + } + + testCases := []struct { + index TxnIndex + start, end Key + ascending bool + expect []KVPair + }{ + {2, nil, nil, true, []KVPair{ + {Key("A"), []byte("1")}, + {Key("a"), []byte("1")}, + {Key("b"), []byte("2")}, + {Key("c"), []byte("2")}, + {Key("d"), []byte("2")}, + }}, + {3, nil, nil, true, []KVPair{ + {Key("A"), []byte("1")}, + {Key("b"), []byte("2")}, + {Key("c"), []byte("3")}, + {Key("d"), []byte("3")}, + {Key("e"), []byte("3")}, + }}, + {3, nil, nil, false, []KVPair{ + {Key("e"), []byte("3")}, + {Key("d"), []byte("3")}, + {Key("c"), []byte("3")}, + {Key("b"), []byte("2")}, + {Key("A"), []byte("1")}, + }}, + {4, nil, nil, true, []KVPair{ + {Key("b"), []byte("2")}, + {Key("c"), []byte("3")}, + {Key("d"), []byte("4")}, + {Key("f"), []byte("4")}, + }}, + {5, nil, nil, true, []KVPair{ + {Key("b"), []byte("2")}, + {Key("c"), []byte("3")}, + {Key("d"), []byte("4")}, + {Key("e"), []byte("5")}, + {Key("f"), []byte("5")}, + {Key("g"), []byte("5")}, + }}, + {6, nil, nil, true, []KVPair{ + {Key("a"), []byte("6")}, + {Key("e"), []byte("5")}, + {Key("f"), []byte("6")}, + {Key("g"), []byte("6")}, + }}, + {6, Key("e"), Key("g"), true, []KVPair{ + {Key("e"), []byte("5")}, + {Key("f"), []byte("6")}, + }}, + {6, Key("e"), Key("g"), false, []KVPair{ + {Key("f"), []byte("6")}, + {Key("e"), []byte("5")}, + }}, + {6, Key("b"), nil, true, []KVPair{ + {Key("e"), []byte("5")}, + {Key("f"), []byte("6")}, + {Key("g"), []byte("6")}, + }}, + {6, Key("b"), nil, false, []KVPair{ + {Key("g"), []byte("6")}, + {Key("f"), []byte("6")}, + {Key("e"), []byte("5")}, + }}, + {6, nil, Key("g"), true, []KVPair{ + {Key("a"), []byte("6")}, + {Key("e"), []byte("5")}, + {Key("f"), []byte("6")}, + }}, + {6, nil, Key("g"), false, []KVPair{ + {Key("f"), []byte("6")}, + {Key("e"), []byte("5")}, + {Key("a"), []byte("6")}, + }}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("version-%d", tc.index), func(t *testing.T) { + view := mv.View(tc.index).GetKVStore(StoreKeyAuth) + var iter storetypes.Iterator + if tc.ascending { + iter = view.Iterator(tc.start, tc.end) + } else { + iter = view.ReverseIterator(tc.start, tc.end) + } + require.Equal(t, tc.expect, CollectIterator(iter)) + require.NoError(t, iter.Close()) + }) + } +} + +func CollectIterator[V any](iter storetypes.GIterator[V]) []GKVPair[V] { + var res []GKVPair[V] + for iter.Valid() { + res = append(res, GKVPair[V]{iter.Key(), iter.Value()}) + iter.Next() + } + return res +} diff --git a/blockstm/scheduler.go b/blockstm/scheduler.go new file mode 100644 index 000000000000..fa590c0cc7bd --- /dev/null +++ b/blockstm/scheduler.go @@ -0,0 +1,215 @@ +package blockstm + +import ( + "fmt" + "runtime" + "sync" + "sync/atomic" +) + +type TaskKind int + +const ( + TaskKindExecution TaskKind = iota + TaskKindValidation +) + +type TxDependency struct { + sync.Mutex + dependents []TxnIndex +} + +func (t *TxDependency) Swap(new []TxnIndex) []TxnIndex { + t.Lock() + old := t.dependents + t.dependents = new + t.Unlock() + return old +} + +// Scheduler implements the scheduler for the block-stm +// ref: `Algorithm 4 The Scheduler module, variables, utility APIs and next task logic` +type Scheduler struct { + block_size int + + // An index that tracks the next transaction to try and execute. + execution_idx atomic.Uint64 + // A similar index for tracking validation. + validation_idx atomic.Uint64 + // Number of times validation_idx or execution_idx was decreased + decrease_cnt atomic.Uint64 + // Number of ongoing validation and execution tasks + num_active_tasks atomic.Uint64 + // Marker for completion + done_marker atomic.Bool + + // txn_idx to a mutex-protected set of dependent transaction indices + txn_dependency []TxDependency + // txn_idx to a mutex-protected pair (incarnation_number, status), where status ∈ {READY_TO_EXECUTE, EXECUTING, EXECUTED, ABORTING}. + txn_status []StatusEntry + + // metrics + executedTxns atomic.Int64 + validatedTxns atomic.Int64 +} + +func NewScheduler(block_size int) *Scheduler { + return &Scheduler{ + block_size: block_size, + txn_dependency: make([]TxDependency, block_size), + txn_status: make([]StatusEntry, block_size), + } +} + +func (s *Scheduler) Done() bool { + return s.done_marker.Load() +} + +func (s *Scheduler) DecreaseValidationIdx(target TxnIndex) { + StoreMin(&s.validation_idx, uint64(target)) + s.decrease_cnt.Add(1) +} + +func (s *Scheduler) CheckDone() { + observed_cnt := s.decrease_cnt.Load() + if s.execution_idx.Load() >= uint64(s.block_size) && + s.validation_idx.Load() >= uint64(s.block_size) && + s.num_active_tasks.Load() == 0 { + if observed_cnt == s.decrease_cnt.Load() { + s.done_marker.Store(true) + } + } + // avoid busy waiting + runtime.Gosched() +} + +// TryIncarnate tries to incarnate a transaction index to execute. +// Returns the transaction version if successful, otherwise returns invalid version. +// +// Invariant `num_active_tasks`: decreased if an invalid task is returned. +func (s *Scheduler) TryIncarnate(idx TxnIndex) TxnVersion { + if int(idx) < s.block_size { + if incarnation, ok := s.txn_status[idx].TrySetExecuting(); ok { + return TxnVersion{idx, incarnation} + } + } + DecrAtomic(&s.num_active_tasks) + return InvalidTxnVersion +} + +// NextVersionToExecute get the next transaction index to execute, +// returns invalid version if no task is available +// +// Invariant `num_active_tasks`: increased if a valid task is returned. +func (s *Scheduler) NextVersionToExecute() TxnVersion { + if s.execution_idx.Load() >= uint64(s.block_size) { + s.CheckDone() + return InvalidTxnVersion + } + IncrAtomic(&s.num_active_tasks) + idx_to_execute := s.execution_idx.Add(1) - 1 + return s.TryIncarnate(TxnIndex(idx_to_execute)) +} + +// NextVersionToValidate get the next transaction index to validate, +// returns invalid version if no task is available. +// +// Invariant `num_active_tasks`: increased if a valid task is returned. +func (s *Scheduler) NextVersionToValidate() TxnVersion { + if s.validation_idx.Load() >= uint64(s.block_size) { + s.CheckDone() + return InvalidTxnVersion + } + IncrAtomic(&s.num_active_tasks) + idx_to_validate := FetchIncr(&s.validation_idx) + if idx_to_validate < uint64(s.block_size) { + if ok, incarnation := s.txn_status[idx_to_validate].IsExecuted(); ok { + return TxnVersion{TxnIndex(idx_to_validate), incarnation} + } + } + + DecrAtomic(&s.num_active_tasks) + return InvalidTxnVersion +} + +// NextTask returns the transaction index and task kind for the next task to execute or validate, +// returns invalid version if no task is available. +// +// Invariant `num_active_tasks`: increased if a valid task is returned. +func (s *Scheduler) NextTask() (TxnVersion, TaskKind) { + validation_idx := s.validation_idx.Load() + execution_idx := s.execution_idx.Load() + if validation_idx < execution_idx { + return s.NextVersionToValidate(), TaskKindValidation + } else { + return s.NextVersionToExecute(), TaskKindExecution + } +} + +func (s *Scheduler) WaitForDependency(txn, blocking_txn TxnIndex) *Condvar { + cond := NewCondvar() + entry := &s.txn_dependency[blocking_txn] + entry.Lock() + + // thread holds 2 locks + if ok, _ := s.txn_status[blocking_txn].IsExecuted(); ok { + // dependency resolved before locking in Line 148 + entry.Unlock() + return nil + } + + s.txn_status[txn].Suspend(cond) + entry.dependents = append(entry.dependents, txn) + entry.Unlock() + + return cond +} + +func (s *Scheduler) ResumeDependencies(txns []TxnIndex) { + for _, txn := range txns { + s.txn_status[txn].Resume() + } +} + +// FinishExecution marks an execution task as complete. +// Invariant `num_active_tasks`: decreased if an invalid task is returned. +func (s *Scheduler) FinishExecution(version TxnVersion, wroteNewPath bool) (TxnVersion, TaskKind) { + s.txn_status[version.Index].SetExecuted() + + deps := s.txn_dependency[version.Index].Swap(nil) + s.ResumeDependencies(deps) + if s.validation_idx.Load() > uint64(version.Index) { // otherwise index already small enough + if !wroteNewPath { + // schedule validation for current tx only, don't decrease num_active_tasks + return version, TaskKindValidation + } + // schedule validation for txn_idx and higher txns + s.DecreaseValidationIdx(version.Index) + } + DecrAtomic(&s.num_active_tasks) + return InvalidTxnVersion, 0 +} + +func (s *Scheduler) TryValidationAbort(version TxnVersion) bool { + return s.txn_status[version.Index].TryValidationAbort(version.Incarnation) +} + +// FinishValidation marks a validation task as complete. +// Invariant `num_active_tasks`: decreased if an invalid task is returned. +func (s *Scheduler) FinishValidation(txn TxnIndex, aborted bool) (TxnVersion, TaskKind) { + if aborted { + s.txn_status[txn].SetReadyStatus() + s.DecreaseValidationIdx(txn + 1) + if s.execution_idx.Load() > uint64(txn) { + return s.TryIncarnate(txn), TaskKindExecution + } + } + + DecrAtomic(&s.num_active_tasks) + return InvalidTxnVersion, 0 +} + +func (s *Scheduler) Stats() string { + return fmt.Sprintf("executed: %d, validated: %d", + s.executedTxns.Load(), s.validatedTxns.Load()) +} diff --git a/blockstm/status.go b/blockstm/status.go new file mode 100644 index 000000000000..1bb2ef6937b8 --- /dev/null +++ b/blockstm/status.go @@ -0,0 +1,118 @@ +package blockstm + +import "sync" + +type Status uint + +const ( + StatusReadyToExecute Status = iota + StatusExecuting + StatusExecuted + StatusAborting + StatusSuspended +) + +// StatusEntry is a state machine for the status of a transaction, all the transitions are atomic protected by a mutex. +// +// ```mermaid +// stateDiagram-v2 +// +// [*] --> ReadyToExecute +// ReadyToExecute --> Executing: TrySetExecuting() +// Executing --> Executed: SetExecuted() +// Executing --> Suspended: Suspend(cond)\nset cond +// Executed --> Aborting: TryValidationAbort(incarnation) +// Aborting --> ReadyToExecute: SetReadyStatus()\nincarnation++ +// Suspended --> Executing: Resume() +// +// ``` +type StatusEntry struct { + sync.Mutex + + incarnation Incarnation + status Status + + cond *Condvar +} + +func (s *StatusEntry) IsExecuted() (ok bool, incarnation Incarnation) { + s.Lock() + + if s.status == StatusExecuted { + ok = true + incarnation = s.incarnation + } + + s.Unlock() + return +} + +func (s *StatusEntry) TrySetExecuting() (Incarnation, bool) { + s.Lock() + + if s.status == StatusReadyToExecute { + s.status = StatusExecuting + incarnation := s.incarnation + + s.Unlock() + return incarnation, true + } + + s.Unlock() + return 0, false +} + +func (s *StatusEntry) setStatus(status Status) { + s.Lock() + s.status = status + s.Unlock() +} + +func (s *StatusEntry) Resume() { + // status must be SUSPENDED and cond != nil + s.Lock() + + s.status = StatusExecuting + s.cond.Notify() + s.cond = nil + + s.Unlock() +} + +func (s *StatusEntry) SetExecuted() { + // status must have been EXECUTING + s.setStatus(StatusExecuted) +} + +func (s *StatusEntry) TryValidationAbort(incarnation Incarnation) bool { + s.Lock() + + if s.incarnation == incarnation && s.status == StatusExecuted { + s.status = StatusAborting + + s.Unlock() + return true + } + + s.Unlock() + return false +} + +func (s *StatusEntry) SetReadyStatus() { + s.Lock() + + s.incarnation++ + // status must be ABORTING + s.status = StatusReadyToExecute + + s.Unlock() +} + +func (s *StatusEntry) Suspend(cond *Condvar) { + s.Lock() + + s.cond = cond + s.status = StatusSuspended + + s.Unlock() +} diff --git a/blockstm/stm.go b/blockstm/stm.go new file mode 100644 index 000000000000..dcc414145ad8 --- /dev/null +++ b/blockstm/stm.go @@ -0,0 +1,78 @@ +package blockstm + +import ( + "context" + "errors" + "fmt" + "runtime" + "sync" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" +) + +func ExecuteBlock( + ctx context.Context, + blockSize int, + stores map[storetypes.StoreKey]int, + storage MultiStore, + executors int, + txExecutor TxExecutor, +) error { + return ExecuteBlockWithEstimates( + ctx, blockSize, stores, storage, executors, + nil, txExecutor, + ) +} + +func ExecuteBlockWithEstimates( + ctx context.Context, + blockSize int, + stores map[storetypes.StoreKey]int, + storage MultiStore, + executors int, + estimates []MultiLocations, // txn -> multi-locations + txExecutor TxExecutor, +) error { + if executors < 0 { + return fmt.Errorf("invalid number of executors: %d", executors) + } + if executors == 0 { + executors = maxParallelism() + } + + // Create a new scheduler + scheduler := NewScheduler(blockSize) + mvMemory := NewMVMemoryWithEstimates(blockSize, stores, storage, scheduler, estimates) + + var wg sync.WaitGroup + wg.Add(executors) + for i := 0; i < executors; i++ { + e := NewExecutor(ctx, scheduler, txExecutor, mvMemory, i) + go func() { + defer wg.Done() + e.Run() + }() + } + wg.Wait() + + if !scheduler.Done() { + if ctx.Err() != nil { + // canceled + return ctx.Err() + } + + return errors.New("scheduler did not complete") + } + + telemetry.SetGauge(float32(scheduler.executedTxns.Load()), TelemetrySubsystem, KeyExecutedTxs) + telemetry.SetGauge(float32(scheduler.validatedTxns.Load()), TelemetrySubsystem, KeyValidatedTxs) + + // Write the snapshot into the storage + mvMemory.WriteSnapshot(storage) + return nil +} + +func maxParallelism() int { + return min(runtime.GOMAXPROCS(0), runtime.NumCPU()) +} diff --git a/blockstm/stm_test.go b/blockstm/stm_test.go new file mode 100644 index 000000000000..485bafd4bf28 --- /dev/null +++ b/blockstm/stm_test.go @@ -0,0 +1,175 @@ +package blockstm + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "math/rand" + "testing" + + "github.com/test-go/testify/require" + + storetypes "cosmossdk.io/store/types" +) + +func accountName(i int64) string { + return fmt.Sprintf("account%05d", i) +} + +func testBlock(size, accounts int) *MockBlock { + txs := make([]Tx, size) + g := rand.New(rand.NewSource(0)) + for i := 0; i < size; i++ { + sender := g.Int63n(int64(accounts)) + receiver := g.Int63n(int64(accounts)) + txs[i] = BankTransferTx(i, accountName(sender), accountName(receiver), 1) + } + return NewMockBlock(txs) +} + +func iterateBlock(size, accounts int) *MockBlock { + txs := make([]Tx, size) + g := rand.New(rand.NewSource(0)) + for i := 0; i < size; i++ { + sender := g.Int63n(int64(accounts)) + receiver := g.Int63n(int64(accounts)) + txs[i] = IterateTx(i, accountName(sender), accountName(receiver), 1) + } + return NewMockBlock(txs) +} + +func noConflictBlock(size int) *MockBlock { + txs := make([]Tx, size) + for i := 0; i < size; i++ { + sender := accountName(int64(i)) + txs[i] = BankTransferTx(i, sender, sender, 1) + } + return NewMockBlock(txs) +} + +func worstCaseBlock(size int) *MockBlock { + txs := make([]Tx, size) + for i := 0; i < size; i++ { + // all transactions are from the same account + sender := "account0" + txs[i] = BankTransferTx(i, sender, sender, 1) + } + return NewMockBlock(txs) +} + +func determisticBlock() *MockBlock { + return NewMockBlock([]Tx{ + NoopTx(0, "account0"), + NoopTx(1, "account1"), + NoopTx(2, "account1"), + NoopTx(3, "account1"), + NoopTx(4, "account3"), + NoopTx(5, "account1"), + NoopTx(6, "account4"), + NoopTx(7, "account5"), + NoopTx(8, "account6"), + }) +} + +func TestSTM(t *testing.T) { + stores := map[storetypes.StoreKey]int{StoreKeyAuth: 0, StoreKeyBank: 1} + testCases := []struct { + name string + blk *MockBlock + executors int + }{ + { + name: "testBlock(100,80),10", + blk: testBlock(100, 80), + executors: 10, + }, + { + name: "testBlock(100,3),10", + blk: testBlock(100, 3), + executors: 10, + }, + { + name: "determisticBlock(),5", + blk: determisticBlock(), + executors: 5, + }, + { + name: "noConflictBlock(100),5", + blk: noConflictBlock(100), + executors: 5, + }, + { + name: "worstCaseBlock(100),5", + blk: worstCaseBlock(100), + executors: 5, + }, + { + name: "iterateBlock(100,80),10", + blk: iterateBlock(100, 80), + executors: 10, + }, + { + name: "iterateBlock(100,10),10", + blk: iterateBlock(100, 10), + executors: 10, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + storage := NewMultiMemDB(stores) + require.NoError(t, + ExecuteBlock(context.Background(), tc.blk.Size(), stores, storage, tc.executors, tc.blk.ExecuteTx), + ) + for _, err := range tc.blk.Results { + require.NoError(t, err) + } + + crossCheck := NewMultiMemDB(stores) + runSequential(crossCheck, tc.blk) + + // check parallel execution matches sequential execution + for store := range stores { + require.True(t, StoreEqual(crossCheck.GetKVStore(store), storage.GetKVStore(store))) + } + + // check total nonce increased the same amount as the number of transactions + var total uint64 + store := storage.GetKVStore(StoreKeyAuth) + it := store.Iterator(nil, nil) + defer it.Close() + + for ; it.Valid(); it.Next() { + if !bytes.HasPrefix(it.Key(), []byte("nonce")) { + continue + } + total += binary.BigEndian.Uint64(it.Value()) + continue + } + require.Equal(t, uint64(tc.blk.Size()), total) + }) + } +} + +func StoreEqual(a, b storetypes.KVStore) bool { + // compare with iterators + iter1 := a.Iterator(nil, nil) + iter2 := b.Iterator(nil, nil) + defer iter1.Close() + defer iter2.Close() + + for { + if !iter1.Valid() && !iter2.Valid() { + return true + } + if !iter1.Valid() || !iter2.Valid() { + return false + } + if !bytes.Equal(iter1.Key(), iter2.Key()) || !bytes.Equal(iter1.Value(), iter2.Value()) { + return false + } + iter1.Next() + iter2.Next() + } +} diff --git a/blockstm/txnrunner.go b/blockstm/txnrunner.go new file mode 100644 index 000000000000..e7e8a124cd25 --- /dev/null +++ b/blockstm/txnrunner.go @@ -0,0 +1,227 @@ +package blockstm + +import ( + "context" + "sync" + "sync/atomic" + + abci "github.com/cometbft/cometbft/abci/types" + + "cosmossdk.io/collections" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +type ( + DeliverTxFunc func(tx []byte, ms storetypes.MultiStore, txIndex int, incarnationCache map[string]any) *abci.ExecTxResult + TxRunner interface { + Run(context.Context, storetypes.MultiStore, [][]byte, DeliverTxFunc) ([]*abci.ExecTxResult, error) + } +) + +var ( + _ TxRunner = DefaultRunner{} + _ TxRunner = STMRunner{} +) + +func NewDefaultRunner(txDecoder sdk.TxDecoder) *DefaultRunner { + return &DefaultRunner{ + txDecoder: txDecoder, + } +} + +// DefaultRunner default executor without parallelism +type DefaultRunner struct { + txDecoder sdk.TxDecoder +} + +func (d DefaultRunner) Run(ctx context.Context, _ storetypes.MultiStore, txs [][]byte, deliverTx DeliverTxFunc) ([]*abci.ExecTxResult, error) { + // Fallback to the default execution logic + txResults := make([]*abci.ExecTxResult, 0, len(txs)) + for i, rawTx := range txs { + var response *abci.ExecTxResult + + if _, err := d.txDecoder(rawTx); err == nil { + response = deliverTx(rawTx, nil, i, nil) + } else { + // In the case where a transaction included in a block proposal is malformed, + // we still want to return a default response to comet. This is because comet + // expects a response for each transaction included in a block proposal. + response = sdkerrors.ResponseExecTxResultWithEvents( + sdkerrors.ErrTxDecode, + 0, + 0, + nil, + false, + ) + } + + // check after every tx if we should abort + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // continue + } + + txResults = append(txResults, response) + } + return txResults, nil +} + +func NewSTMRunner( + txDecoder sdk.TxDecoder, + stores []storetypes.StoreKey, + workers int, estimate bool, coinDenom string, +) *STMRunner { + return &STMRunner{ + txDecoder: txDecoder, + stores: stores, + workers: workers, + estimate: estimate, + coinDenom: coinDenom, + } +} + +// STMRunner simple implementation of block-stm +type STMRunner struct { + txDecoder sdk.TxDecoder + stores []storetypes.StoreKey + workers int + estimate bool + coinDenom string +} + +func (e STMRunner) Run(ctx context.Context, ms storetypes.MultiStore, txs [][]byte, deliverTx DeliverTxFunc) ([]*abci.ExecTxResult, error) { + var authStore, bankStore int + index := make(map[storetypes.StoreKey]int, len(e.stores)) + for i, k := range e.stores { + switch k.Name() { + case "acc": + authStore = i + case "bank": + bankStore = i + } + index[k] = i + } + + blockSize := len(txs) + if blockSize == 0 { + return nil, nil + } + results := make([]*abci.ExecTxResult, blockSize) + incarnationCache := make([]atomic.Pointer[map[string]any], blockSize) + for i := 0; i < blockSize; i++ { + m := make(map[string]any) + incarnationCache[i].Store(&m) + } + + var ( + estimates []MultiLocations + memTxs [][]byte + ) + + if e.estimate { + memTxs, estimates = preEstimates(txs, e.workers, authStore, bankStore, e.coinDenom, e.txDecoder) + } + + if err := ExecuteBlockWithEstimates( + ctx, + blockSize, + index, + stmMultiStoreWrapper{ms}, + e.workers, + estimates, + func(txn TxnIndex, ms MultiStore) { + var cache map[string]any + + // only one of the concurrent incarnations gets the cache if there are any, otherwise execute without + // cache, concurrent incarnations should be rare. + v := incarnationCache[txn].Swap(nil) + if v != nil { + cache = *v + } + + var memTx []byte + if memTxs != nil { + memTx = memTxs[txn] + } + results[txn] = deliverTx(memTx, msWrapper{ms}, int(txn), cache) + + if v != nil { + incarnationCache[txn].Store(v) + } + }, + ); err != nil { + return nil, err + } + + return results, nil +} + +// preEstimates returns a static estimation of the written keys for each transaction. +// NOTE: make sure it sync with the latest sdk logic when sdk upgrade. +func preEstimates(txs [][]byte, workers, authStore, bankStore int, coinDenom string, txDecoder sdk.TxDecoder) ([][]byte, []MultiLocations) { + memTxs := make([][]byte, len(txs)) + estimates := make([]MultiLocations, len(txs)) + + job := func(start, end int) { + for i := start; i < end; i++ { + rawTx := txs[i] + tx, err := txDecoder(rawTx) + if err != nil { + continue + } + memTxs[i] = rawTx + + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + continue + } + feePayer := sdk.AccAddress(feeTx.FeePayer()) + + // account key + accKey, err := collections.EncodeKeyWithPrefix( + collections.NewPrefix(1), + sdk.AccAddressKey, + feePayer, + ) + if err != nil { + continue + } + + // balance key + balanceKey, err := collections.EncodeKeyWithPrefix( + collections.NewPrefix(2), + collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), + collections.Join(feePayer, coinDenom), + ) + if err != nil { + continue + } + + estimates[i] = MultiLocations{ + authStore: {accKey}, + bankStore: {balanceKey}, + } + } + } + + blockSize := len(txs) + chunk := (blockSize + workers - 1) / workers + var wg sync.WaitGroup + for i := 0; i < blockSize; i += chunk { + start := i + end := min(i+chunk, blockSize) + wg.Add(1) + go func() { + defer wg.Done() + job(start, end) + }() + } + wg.Wait() + + return memTxs, estimates +} diff --git a/blockstm/types.go b/blockstm/types.go new file mode 100644 index 000000000000..221115123545 --- /dev/null +++ b/blockstm/types.go @@ -0,0 +1,94 @@ +package blockstm + +import ( + "bytes" + + storetypes "cosmossdk.io/store/types" +) + +const ( + TelemetrySubsystem = "blockstm" + KeyExecutedTxs = "executed_txs" + KeyValidatedTxs = "validated_txs" +) + +type ( + TxnIndex int + Incarnation uint +) + +type TxnVersion struct { + Index TxnIndex + Incarnation Incarnation +} + +var InvalidTxnVersion = TxnVersion{-1, 0} + +func (v TxnVersion) Valid() bool { + return v.Index >= 0 +} + +type Key []byte + +type ReadDescriptor struct { + Key Key + // invalid Version means the key is read from storage + Version TxnVersion +} + +type IteratorOptions struct { + // [Start, End) is the range of the iterator + Start Key + End Key + Ascending bool +} + +type IteratorDescriptor struct { + IteratorOptions + // Stop is not `nil` if the iteration is not exhausted and stops at a key before reaching the end of the range, + // the effective range is `[start, stop]`. + // when replaying, it should also stops at the stop key. + Stop Key + // Reads is the list of keys that is observed by the iterator. + Reads []ReadDescriptor +} + +type ReadSet struct { + Reads []ReadDescriptor + Iterators []IteratorDescriptor +} + +type MultiReadSet = map[int]*ReadSet + +type KeyItem interface { + GetKey() []byte +} + +func KeyItemLess[T KeyItem](a, b T) bool { + return bytes.Compare(a.GetKey(), b.GetKey()) < 0 +} + +// TxExecutor executes transactions on top of a multi-version memory view. +type TxExecutor func(TxnIndex, MultiStore) + +type MultiStore interface { + GetStore(storetypes.StoreKey) storetypes.Store + GetKVStore(storetypes.StoreKey) storetypes.KVStore + GetObjKVStore(storetypes.StoreKey) storetypes.ObjKVStore +} + +// MVStore is a value type agnostic interface for `MVData`, to keep `MVMemory` value type agnostic. +type MVStore interface { + Delete(Key, TxnIndex) + WriteEstimate(Key, TxnIndex) + ValidateReadSet(TxnIndex, *ReadSet) bool + SnapshotToStore(storetypes.Store) +} + +// MVView is a value type agnostic interface for `MVMemoryView`, to keep `MultiMVMemoryView` value type agnostic. +type MVView interface { + storetypes.Store + + ApplyWriteSet(TxnVersion) Locations + ReadSet() *ReadSet +} diff --git a/blockstm/utils.go b/blockstm/utils.go new file mode 100644 index 000000000000..7bc78b7397ef --- /dev/null +++ b/blockstm/utils.go @@ -0,0 +1,100 @@ +package blockstm + +import ( + "bytes" + "fmt" + "sync/atomic" +) + +type ErrReadError struct { + BlockingTxn TxnIndex +} + +func (e ErrReadError) Error() string { + return fmt.Sprintf("read error: blocked by txn %d", e.BlockingTxn) +} + +// StoreMin implements a compare-and-swap operation that stores the minimum of the current value and the given value. +func StoreMin(a *atomic.Uint64, b uint64) { + for { + old := a.Load() + if old <= b { + return + } + if a.CompareAndSwap(old, b) { + return + } + } +} + +// DecrAtomic decreases the atomic value by 1 +func DecrAtomic(a *atomic.Uint64) { + a.Add(^uint64(0)) +} + +// IncrAtomic increases the atomic value by 1 +func IncrAtomic(a *atomic.Uint64) { + a.Add(1) +} + +// FetchIncr increaes the atomic value by 1 and returns the old value +func FetchIncr(a *atomic.Uint64) uint64 { + return a.Add(1) - 1 +} + +// DiffOrderedList compares two ordered lists +// callback arguments: (value, is_new) +func DiffOrderedList(old, new []Key, callback func(Key, bool) bool) { + i, j := 0, 0 + for i < len(old) && j < len(new) { + switch bytes.Compare(old[i], new[j]) { + case -1: + if !callback(old[i], false) { + return + } + i++ + case 1: + if !callback(new[j], true) { + return + } + j++ + default: + i++ + j++ + } + } + for ; i < len(old); i++ { + if !callback(old[i], false) { + return + } + } + for ; j < len(new); j++ { + if !callback(new[j], true) { + return + } + } +} + +// BytesBeyond returns if a is beyond b in specified iteration order +func BytesBeyond(a, b []byte, ascending bool) bool { + if ascending { + return bytes.Compare(a, b) > 0 + } + return bytes.Compare(a, b) < 0 +} + +func BytesIsZero(v []byte) bool { + return v == nil +} + +func BytesLen(v []byte) int { + return len(v) +} + +func ObjIsZero(v any) bool { + return v == nil +} + +func ObjLen(v any) int { + return 1 +} diff --git a/blockstm/utils_test.go b/blockstm/utils_test.go new file mode 100644 index 000000000000..cc7d644b5689 --- /dev/null +++ b/blockstm/utils_test.go @@ -0,0 +1,81 @@ +package blockstm + +import ( + "testing" + + "github.com/test-go/testify/require" +) + +type DiffEntry struct { + Key Key + IsNew bool +} + +func TestDiffOrderedList(t *testing.T) { + testCases := []struct { + name string + old []Key + new []Key + expected []DiffEntry + }{ + { + name: "empty lists", + old: []Key{}, + new: []Key{}, + expected: []DiffEntry{}, + }, + { + name: "old is longer", + old: []Key{ + []byte("a"), + []byte("b"), + []byte("c"), + []byte("d"), + []byte("e"), + }, + new: []Key{ + []byte("b"), + []byte("c"), + []byte("f"), + }, + expected: []DiffEntry{ + {Key: []byte("a"), IsNew: false}, + {Key: []byte("d"), IsNew: false}, + {Key: []byte("e"), IsNew: false}, + {Key: []byte("f"), IsNew: true}, + }, + }, + { + name: "new is longer", + old: []Key{ + []byte("a"), + []byte("c"), + []byte("e"), + }, + new: []Key{ + []byte("b"), + []byte("c"), + []byte("d"), + []byte("e"), + []byte("f"), + }, + expected: []DiffEntry{ + {Key: []byte("a"), IsNew: false}, + {Key: []byte("b"), IsNew: true}, + {Key: []byte("d"), IsNew: true}, + {Key: []byte("f"), IsNew: true}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := []DiffEntry{} + DiffOrderedList(tc.old, tc.new, func(key Key, leftOrRight bool) bool { + result = append(result, DiffEntry{key, leftOrRight}) + return true + }) + require.Equal(t, tc.expected, result) + }) + } +} diff --git a/blockstm/wrappers.go b/blockstm/wrappers.go new file mode 100644 index 000000000000..bc5b2d667216 --- /dev/null +++ b/blockstm/wrappers.go @@ -0,0 +1,89 @@ +package blockstm + +import ( + "io" + + "cosmossdk.io/store/cachemulti" + storetypes "cosmossdk.io/store/types" +) + +var ( + _ storetypes.MultiStore = msWrapper{} + _ MultiStore = stmMultiStoreWrapper{} +) + +type msWrapper struct { + MultiStore +} + +func (ms msWrapper) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO implement me + panic("implement me") +} + +func (ms msWrapper) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { + // TODO implement me + panic("implement me") +} + +func (ms msWrapper) LatestVersion() int64 { + // TODO implement me + panic("implement me") +} + +func (ms msWrapper) getCacheWrapper(key storetypes.StoreKey) storetypes.CacheWrapper { + return ms.GetStore(key) +} + +func (ms msWrapper) GetStore(key storetypes.StoreKey) storetypes.Store { + return ms.MultiStore.GetStore(key) +} + +func (ms msWrapper) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + return ms.MultiStore.GetKVStore(key) +} + +func (ms msWrapper) GetObjKVStore(key storetypes.StoreKey) storetypes.ObjKVStore { + return ms.MultiStore.GetObjKVStore(key) +} + +func (ms msWrapper) CacheMultiStore() storetypes.CacheMultiStore { + return cachemulti.NewFromParent(ms.getCacheWrapper, nil, nil) +} + +// CacheWrap Implements CacheWrapper. +func (ms msWrapper) CacheWrap() storetypes.CacheWrap { + return ms.CacheMultiStore().(storetypes.CacheWrap) +} + +// GetStoreType returns the type of the store. +func (ms msWrapper) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeMulti +} + +// SetTracer Implements interface MultiStore +func (ms msWrapper) SetTracer(io.Writer) storetypes.MultiStore { + return nil +} + +// SetTracingContext Implements interface MultiStore +func (ms msWrapper) SetTracingContext(storetypes.TraceContext) storetypes.MultiStore { + return nil +} + +// TracingEnabled Implements interface MultiStore +func (ms msWrapper) TracingEnabled() bool { + return false +} + +type stmMultiStoreWrapper struct { + storetypes.MultiStore +} + +func (ms stmMultiStoreWrapper) GetStore(key storetypes.StoreKey) storetypes.Store { + return ms.MultiStore.GetStore(key) +} + +func (ms stmMultiStoreWrapper) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + return ms.MultiStore.GetKVStore(key) +} diff --git a/client/v2/CHANGELOG.md b/client/v2/CHANGELOG.md index bf334e7769cd..5631bd5ea3a2 100644 --- a/client/v2/CHANGELOG.md +++ b/client/v2/CHANGELOG.md @@ -34,11 +34,12 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## UNRELEASED +## [v2.0.0-beta.11](https://github.com/cosmos/cosmos-sdk/tree/client/v2.0.0-beta.11) - 2025-05-30 ### Bug Fixes -* [#24722](https://github.com/cosmos/cosmos-sdk/pull/24722) Fix msg parsing when no pulsar file is present. +* [#24722](https://github.com/cosmos/cosmos-sdk/pull/24722) Fix msg parsing in when no pulsar file is present. + ## [v2.0.0-beta.9](https://github.com/cosmos/cosmos-sdk/tree/client/v2.0.0-beta.9) - 2025-04-24 @@ -48,7 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements -* [#22890](https://github.com/cosmos/cosmos-sdk/pull/22890) Added support for flattening inner message fields in autocli as positional arguments. +* [#22890](https://github.com/cosmos/cosmos-sdk/pull/22890) Added support for flattening inner message fields in autocli as positional arguments. ### Bug Fixes diff --git a/client/v2/go.mod b/client/v2/go.mod index 75249948583d..3e5173f6f8d6 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -107,7 +107,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 @@ -138,7 +138,7 @@ require ( github.com/subosito/gotenv v1.6.0 // 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 diff --git a/client/v2/go.sum b/client/v2/go.sum index cd6f92c42dc1..6c76182cda29 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -468,8 +468,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= @@ -703,8 +703,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd 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= diff --git a/client/v2/internal/testpbgogo/msg.proto b/client/v2/internal/testpbgogo/msg.proto index 65f978f0bd8f..c8699413995a 100644 --- a/client/v2/internal/testpbgogo/msg.proto +++ b/client/v2/internal/testpbgogo/msg.proto @@ -25,4 +25,4 @@ message MsgRequestGogoOnly { message MsgResponseGoGoOnly { MsgRequestGogoOnly request = 1; -} \ No newline at end of file +} diff --git a/client/v2/internal/testpbpulsar/msg.proto b/client/v2/internal/testpbpulsar/msg.proto index ce1f35a12aac..6cedc40d2dcc 100644 --- a/client/v2/internal/testpbpulsar/msg.proto +++ b/client/v2/internal/testpbpulsar/msg.proto @@ -59,4 +59,4 @@ message MsgResponse { message MsgClawbackRequest {} -message MsgClawbackResponse {} \ No newline at end of file +message MsgClawbackResponse {} diff --git a/contrib/x/group/internal/orm/testsupport.go b/contrib/x/group/internal/orm/testsupport.go index b4fe3d0354ee..b03fcec42ba9 100644 --- a/contrib/x/group/internal/orm/testsupport.go +++ b/contrib/x/group/internal/orm/testsupport.go @@ -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 { diff --git a/core/go.mod b/core/go.mod index 4c094332bda4..fce95ea211c2 100644 --- a/core/go.mod +++ b/core/go.mod @@ -46,6 +46,7 @@ require ( github.com/spf13/cast v1.8.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sys v0.34.0 // indirect @@ -53,7 +54,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) // Version tagged too early and incompatible with v0.50 (latest at the time of tagging) diff --git a/core/go.sum b/core/go.sum index 0d217bd06cf9..ab510420861e 100644 --- a/core/go.sum +++ b/core/go.sum @@ -75,7 +75,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -159,6 +158,10 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -255,5 +258,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/go.mod b/go.mod index 35f1b4a30a24..c55f2e889b75 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -go 1.24.0 +go 1.25.0 module github.com/cosmos/cosmos-sdk @@ -22,6 +22,7 @@ require ( github.com/cosmos/btcutil v1.0.5 github.com/cosmos/cosmos-db v1.1.3 github.com/cosmos/cosmos-proto v1.0.0-beta.5 + github.com/cosmos/cosmos-sdk/blockstm v0.0.0-00010101000000-000000000000 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogogateway v1.2.0 github.com/cosmos/gogoproto v1.7.0 @@ -175,7 +176,6 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.11 // 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 @@ -235,6 +235,10 @@ require ( // Here are the short-lived replace from the Cosmos SDK // Replace here are pending PRs, or version to be tagged +replace ( + cosmossdk.io/store => ./store + github.com/cosmos/cosmos-sdk/blockstm => ./blockstm +) // Below are the long-lived replace of the Cosmos SDK replace ( @@ -250,9 +254,10 @@ replace ( github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 ) -replace cosmossdk.io/store => ./store - retract ( + // incorrect tag for patch version + v0.53.1 + // false start by tagging the wrong branch v0.50.0 // revert fix https://github.com/cosmos/cosmos-sdk/pull/16331 diff --git a/go.sum b/go.sum index 4dfd78842a45..7649f6a30752 100644 --- a/go.sum +++ b/go.sum @@ -303,7 +303,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= @@ -811,6 +810,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 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/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= 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= @@ -1027,7 +1028,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/runtime/module.go b/runtime/module.go index d71c668ef49b..bd098b767e99 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -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 } diff --git a/runtime/store.go b/runtime/store.go index 4cfe131c5071..9a956d4459e8 100644 --- a/runtime/store.go +++ b/runtime/store.go @@ -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) diff --git a/server/mock/store.go b/server/mock/store.go index b9775c543ef0..affa995734b1 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -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") } diff --git a/simapp/app.go b/simapp/app.go index be6cbcee4ac8..4d8547817fc5 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -114,7 +114,8 @@ var ( stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, govtypes.ModuleName: {authtypes.Burner}, protocolpooltypes.ModuleName: nil, - protocolpooltypes.ProtocolPoolEscrowAccount: nil} + protocolpooltypes.ProtocolPoolEscrowAccount: nil, + } ) var ( @@ -521,6 +522,7 @@ func NewSimApp( epochstypes.ModuleName, ) app.ModuleManager.SetOrderEndBlockers( + banktypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, genutiltypes.ModuleName, diff --git a/simapp/app_config.go b/simapp/app_config.go index 1e5b27df1406..bd78bdead489 100644 --- a/simapp/app_config.go +++ b/simapp/app_config.go @@ -109,6 +109,7 @@ var ( epochstypes.ModuleName, }, EndBlockers: []string{ + banktypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, feegrant.ModuleName, diff --git a/simapp/go.mod b/simapp/go.mod index 4e7ae2f22c61..6568ae41af29 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/simapp -go 1.24.0 +go 1.25.0 require ( cosmossdk.io/api v0.9.2 @@ -85,6 +85,7 @@ require ( github.com/cometbft/cometbft-db v0.14.1 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect + github.com/cosmos/cosmos-sdk/blockstm v0.0.0-00010101000000-000000000000 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v1.2.6 // indirect @@ -238,6 +239,12 @@ require ( // // ) +// cosmossdk.io/store => ../store +replace ( + cosmossdk.io/store => ../store + github.com/cosmos/cosmos-sdk/blockstm => ../blockstm/ +) + // Below are the long-lived replace of the SimApp replace ( // use cosmos fork of keyring diff --git a/simapp/go.sum b/simapp/go.sum index 35e15392c272..dd6a8331ee30 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -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= @@ -497,8 +495,9 @@ github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoD 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= @@ -821,6 +820,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 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/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= 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= diff --git a/store/CHANGELOG.md b/store/CHANGELOG.md index 0724a1e8ad1c..0b6e5ba8c93e 100644 --- a/store/CHANGELOG.md +++ b/store/CHANGELOG.md @@ -35,12 +35,17 @@ 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 + +* [#24159](https://github.com/cosmos/cosmos-sdk/pull/24159) Support mount object store in baseapp, add `ObjectStore` api in context. + ## v1.1.1 (September 06, 2024) ### Improvements * [#21574](https://github.com/cosmos/cosmos-sdk/pull/21574) Upgrade IVL to IAVL 1.2.0. + ## v1.1.0 (March 20, 2024) ### Improvements diff --git a/store/cachekv/internal/mergeiterator.go b/store/cachekv/internal/mergeiterator.go index 15b215841931..c652e9546122 100644 --- a/store/cachekv/internal/mergeiterator.go +++ b/store/cachekv/internal/mergeiterator.go @@ -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 { +// Like bytes.Compare but opposite if not ascending. +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 } diff --git a/store/cachekv/search_benchmark_test.go b/store/cachekv/search_benchmark_test.go index ecdc86a8e43a..f0e29bc4ec18 100644 --- a/store/cachekv/search_benchmark_test.go +++ b/store/cachekv/search_benchmark_test.go @@ -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](), } } diff --git a/store/cachekv/store.go b/store/cachekv/store.go index 8fa3c421dc14..b72a60e58b7a 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -10,47 +10,71 @@ 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, + func(v []byte) bool { return v == nil }, + func(v []byte) int { return len(v) }, + ) +} + +// 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`, zero value is not allowed to set to a key, and it's returned if the key is not found. + isZero func(V) bool + zeroValue V + // 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 +92,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 +102,28 @@ 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) store.mtx.Lock() defer store.mtx.Unlock() - store.setCacheValue(key, nil, true) + store.setCacheValue(key, store.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,32 @@ 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 { + 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 +220,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 +234,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 { @@ -292,8 +319,8 @@ const ( const minSortSize = 1024 -// dirtyItems constructs a slice of dirty items, to use w/ memIterator. -func (store *Store) dirtyItems(start, end []byte) { +// Constructs a slice of dirty items, to use w/ memIterator. +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 +328,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 +340,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 +383,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 +423,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, } diff --git a/store/cachemulti/store.go b/store/cachemulti/store.go index e834f4a8591f..6c890bc598fd 100644 --- a/store/cachemulti/store.go +++ b/store/cachemulti/store.go @@ -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") } +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 store +} + // 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 { - panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) + store, ok := cms.getCacheWrapper(key).(types.Store) + if !ok { + panic(fmt.Sprintf("store with key %v is not Store", key)) } - return s.(types.Store) + 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 +} + +// 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.(types.KVStore) + return store } diff --git a/store/gaskv/store.go b/store/gaskv/store.go index 75d379a03e2f..848fdd1a2151 100644 --- a/store/gaskv/store.go +++ b/store/gaskv/store.go @@ -6,63 +6,94 @@ import ( "cosmossdk.io/store/types" ) +// ObjectValueLength is the emulated number of bytes for storing transient objects in gas accounting. +const ObjectValueLength = 16 + var _ types.KVStore = &Store{} -// Store applies gas tracking to an underlying KVStore. It implements the +type Store = GStore[[]byte] + +func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store { + return NewGStore(parent, gasMeter, gasConfig, + func(v []byte) bool { return v == nil }, + func(v []byte) int { return len(v) }, + ) +} + +type ObjStore = GStore[any] + +func NewObjStore(parent types.ObjKVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *ObjStore { + return NewGStore(parent, gasMeter, gasConfig, + func(v any) bool { return v == nil }, + func(v any) int { return ObjectValueLength }, + ) +} + +// GStore applies gas tracking to an underlying KVStore. It implements the // KVStore interface. -type Store struct { +type GStore[V any] struct { gasMeter types.GasMeter gasConfig types.GasConfig - parent types.KVStore + parent types.GKVStore[V] + + isZero func(V) bool + valueLen func(V) int } -// NewStore returns a reference to a new GasKVStore. -func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store { - kvs := &Store{ +// 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 { +// GetStoreType calls GetStoreType on the wrapped store. +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) { +// Get retrieves a key from the wrapped store. +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) { +// Set stores a value in the wrapped store. +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 { +// Has checks if the wrapped store contains the key. +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) { +// Delete removes a key from the wrapped store. +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 +102,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 +110,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 { +// CacheWrap implements KVStore. +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) { +// Implements Iterator. +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 { +// Implements Iterator. +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 { +// Implements Iterator. +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) } diff --git a/store/go.mod b/store/go.mod index 786f0045b921..1b9995bf8b28 100644 --- a/store/go.mod +++ b/store/go.mod @@ -2,6 +2,8 @@ module cosmossdk.io/store go 1.24.0 +replace github.com/tidwall/btree => github.com/cosmos/btree v0.0.0-20250924232609-2c6195d95951 + require ( cosmossdk.io/errors v1.0.2 cosmossdk.io/log v1.6.1 diff --git a/store/go.sum b/store/go.sum index 5ae6015bb055..fa5b8f8b6c8f 100644 --- a/store/go.sum +++ b/store/go.sum @@ -55,6 +55,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1: github.com/cometbft/cometbft v0.38.18 h1:1ZHYMdu0S75YxFM13LlPXnOwiIpUW5z9TKMQtTIALpw= github.com/cometbft/cometbft v0.38.18/go.mod h1:PlOQgf3jQorep+g6oVnJgtP65TJvBJoLiXjGaMdNxBE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cosmos/btree v0.0.0-20250924232609-2c6195d95951 h1:dC3GJcS8bJiSEe7VAFDDFgFnVM1G9nBdGOgqJsmsZwM= +github.com/cosmos/btree v0.0.0-20250924232609-2c6195d95951/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNCM= github.com/cosmos/cosmos-db v1.1.1/go.mod h1:AghjcIPqdhSLP/2Z0yha5xPH3nLnskz81pBx3tcVSAw= github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= @@ -296,8 +298,6 @@ 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/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/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= -github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= 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= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= diff --git a/store/cachekv/internal/btree.go b/store/internal/btree/btree.go similarity index 63% rename from store/cachekv/internal/btree.go rename to store/internal/btree/btree.go index 209f7e58c4dd..0fe28def41ee 100644 --- a/store/cachekv/internal/btree.go +++ b/store/internal/btree/btree.go @@ -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} } diff --git a/store/cachekv/internal/btree_test.go b/store/internal/btree/btree_test.go similarity index 98% rename from store/cachekv/internal/btree_test.go rename to store/internal/btree/btree_test.go index 06437997f636..89b5827e6156 100644 --- a/store/cachekv/internal/btree_test.go +++ b/store/internal/btree/btree_test.go @@ -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) diff --git a/store/cachekv/internal/memiterator.go b/store/internal/btree/memiterator.go similarity index 64% rename from store/cachekv/internal/memiterator.go rename to store/internal/btree/memiterator.go index 9dbba7587071..e98ae9cd834e 100644 --- a/store/cachekv/internal/memiterator.go +++ b/store/internal/btree/memiterator.go @@ -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) } diff --git a/store/internal/btreeadaptor.go b/store/internal/btreeadaptor.go new file mode 100644 index 000000000000..a2b350ba3cd7 --- /dev/null +++ b/store/internal/btreeadaptor.go @@ -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) +} diff --git a/store/listenkv/store.go b/store/listenkv/store.go index f9b6a1f6d9eb..0f01247e0f38 100644 --- a/store/listenkv/store.go +++ b/store/listenkv/store.go @@ -3,6 +3,7 @@ package listenkv import ( "io" + "cosmossdk.io/store/cachekv" "cosmossdk.io/store/types" ) @@ -132,7 +133,7 @@ func (s *Store) GetStoreType() types.StoreType { // CacheWrap implements the KVStore interface. It panics as a Store // cannot be cache wrapped. func (s *Store) CacheWrap() types.CacheWrap { - panic("cannot CacheWrap a ListenKVStore") + return cachekv.NewStore(s) } // CacheWrapWithTrace implements the KVStore interface. It panics as a diff --git a/store/listenkv/store_test.go b/store/listenkv/store_test.go index 51b88912c2e1..a664593e85da 100644 --- a/store/listenkv/store_test.go +++ b/store/listenkv/store_test.go @@ -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) { diff --git a/store/prefix/store.go b/store/prefix/store.go index 26fc0cb50d9a..d69aff5b0285 100644 --- a/store/prefix/store.go +++ b/store/prefix/store.go @@ -10,20 +10,53 @@ import ( "cosmossdk.io/store/types" ) -var _ types.KVStore = Store{} +type ( + Store = GStore[[]byte] + ObjStore = GStore[any] +) + +var ( + _ types.KVStore = Store{} + _ types.ObjKVStore = ObjStore{} +) -// 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 +func NewStore(parent types.KVStore, prefix []byte) Store { + return NewGStore( + parent, prefix, + func(v []byte) bool { return v == nil }, + func(v []byte) int { return len(v) }, + ) +} + +func NewObjStore(parent types.ObjKVStore, prefix []byte) ObjStore { + return NewGStore( + parent, prefix, + func(v any) bool { return v == nil }, + func(v any) int { return 1 }, + ) +} + +// GStore is similar with cometbft/cometbft/libs/db/prefix_db +// both gives access only to the limited subset of the store +// for convinience or safety +type GStore[V any] struct { + parent types.GKVStore[V] prefix []byte + + isZero func(V) bool + valueLen func(V) int } -func NewStore(parent types.KVStore, prefix []byte) Store { - return Store{ +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 } -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") } @@ -42,48 +75,51 @@ func (s Store) key(key []byte) (res []byte) { return } -// GetStoreType implements Store, returning the parent store's type -func (s Store) GetStoreType() types.StoreType { +// GetStoreType implements the Store interface +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) +// CacheWrap implements the CacheWrap interface +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 { + 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 { +// Get implements the KVStore interface. +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 { +// Has implements the KVStore interface. +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) { +// Set implements the KVStore interface. +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) { +// Delete implements the KVStore interface. +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 { - newStart := cloneAppend(s.prefix, start) +// Iterator implements the KVStore interface. +// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L106 +func (s GStore[V]) Iterator(start, end []byte) types.GIterator[V] { + newstart := cloneAppend(s.prefix, start) var newEnd []byte if end == nil { @@ -92,14 +128,14 @@ func (s Store) Iterator(start, end []byte) types.Iterator { newEnd = cloneAppend(s.prefix, end) } - iter := s.parent.Iterator(newStart, newEnd) + iter := s.parent.Iterator(newstart, newEnd) return newPrefixIterator(s.prefix, start, end, iter) } // 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 { +// Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L129 +func (s GStore[V]) ReverseIterator(start, end []byte) types.GIterator[V] { newstart := cloneAppend(s.prefix, start) var newend []byte @@ -114,18 +150,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, @@ -134,18 +170,18 @@ 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) { +// Implements Iterator +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 { +// Implements Iterator +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() { +// Implements Iterator +func (pi *prefixIterator[V]) Next() { if !pi.valid { panic("prefixIterator invalid, cannot call Next()") } @@ -156,8 +192,8 @@ func (pi *prefixIterator) Next() { } } -// Key implements Iterator, returning the stripped prefix key -func (pi *prefixIterator) Key() (key []byte) { +// Implements Iterator +func (pi *prefixIterator[V]) Key() (key []byte) { if !pi.valid { panic("prefixIterator invalid, cannot call Key()") } @@ -169,7 +205,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()") } @@ -177,14 +213,14 @@ func (pi *prefixIterator) Value() []byte { return pi.iter.Value() } -// Close implements Iterator, closing the underlying iterator. -func (pi *prefixIterator) Close() error { +// Implements Iterator +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") } diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 071d94873ff7..7e6f22c50818 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -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{ @@ -1189,8 +1222,8 @@ func GetLatestVersion(db dbm.DB) int64 { return latestVersion } -// 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 { +// Commits each store and returns a new 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 } diff --git a/store/rootmulti/store_test.go b/store/rootmulti/store_test.go index ee61a491e036..5d8979f37f31 100644 --- a/store/rootmulti/store_test.go +++ b/store/rootmulti/store_test.go @@ -938,6 +938,7 @@ var ( testStoreKey1 = types.NewKVStoreKey("store1") testStoreKey2 = types.NewKVStoreKey("store2") testStoreKey3 = types.NewKVStoreKey("store3") + testStoreKey4 = types.NewKVStoreKey("store4") ) func newMultiStoreWithMounts(db dbm.DB, pruningOpts pruningtypes.PruningOptions) *Store { @@ -1010,7 +1011,7 @@ 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 { name := key.Name() @@ -1065,35 +1066,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 +1130,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() } diff --git a/store/tracekv/store.go b/store/tracekv/store.go index ba6df431da16..b0960cdb51e8 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -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 diff --git a/store/tracekv/store_test.go b/store/tracekv/store_test.go index 2c42734baefd..2e91c338a3fb 100644 --- a/store/tracekv/store_test.go +++ b/store/tracekv/store_test.go @@ -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) { diff --git a/store/transient/store.go b/store/transient/store.go index da06691970bb..92e7fda07a73 100644 --- a/store/transient/store.go +++ b/store/transient/store.go @@ -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,72 @@ 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 +// GStore is a wrapper for a MemDB with Commiter 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 { - dbadapter.Store + GStore[[]byte] } -// NewStore constructs new MemDB adapter func NewStore() *Store { - return &Store{Store: dbadapter.Store{DB: dbm.NewMemDB()}} + return &Store{*NewGStore( + func(v []byte) bool { return v == nil }, + func(v []byte) int { return len(v) }, + )} +} + +func (*Store) GetStoreType() types.StoreType { + return types.StoreTypeTransient +} + +// ObjStore specializes GStore for any +type ObjStore struct { + GStore[any] +} + +func NewObjStore() *ObjStore { + return &ObjStore{*NewGStore( + func(v any) bool { return v == nil }, + func(v any) int { return 1 }, // for value length validation + )} +} + +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 } -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 { +// LastCommitID implements CommitStore +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 -} diff --git a/store/types/store.go b/store/types/store.go index a9da1fdbabba..79d3a497498d 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -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 key's name field +func (key *ObjectStoreKey) Name() string { + return key.name +} + +// String returns a human readable key, name string +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 +} diff --git a/store/types/validity.go b/store/types/validity.go index f7ecfdfa9a2a..c9fe21b8d16b 100644 --- a/store/types/validity.go +++ b/store/types/validity.go @@ -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")) } } diff --git a/systemtests/go.mod b/systemtests/go.mod index 8bb23b26e548..a9e6e75ab2d1 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -104,7 +104,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 @@ -136,7 +136,7 @@ require ( github.com/subosito/gotenv v1.6.0 // 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 @@ -170,3 +170,5 @@ require ( pgregory.net/rapid v1.2.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +replace cosmossdk.io/store => ../store diff --git a/systemtests/go.sum b/systemtests/go.sum index b0bc5a687b18..e840e38ebd22 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -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= @@ -385,8 +383,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= @@ -466,8 +465,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= @@ -701,8 +700,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd 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= @@ -758,8 +757,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= diff --git a/tests/go.mod b/tests/go.mod index eb5bd6394d44..efbc8a9c0cd5 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -1,6 +1,6 @@ module github.com/cosmos/cosmos-sdk/tests -go 1.24.0 +go 1.25.0 require ( cosmossdk.io/api v0.9.2 @@ -87,6 +87,7 @@ require ( github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect github.com/cometbft/cometbft-db v0.14.1 // indirect github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-sdk/blockstm v0.0.0-00010101000000-000000000000 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v1.2.6 // indirect github.com/cosmos/ics23/go v0.11.0 // indirect @@ -233,6 +234,10 @@ require ( // replace ( // // ) +replace ( + cosmossdk.io/store => ../store + github.com/cosmos/cosmos-sdk/blockstm => ../blockstm +) // Below are the long-lived replace for tests. replace ( diff --git a/tests/go.sum b/tests/go.sum index 43d5ccf2c8ea..2a3c59d8d5e9 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -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= @@ -492,8 +490,9 @@ github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoD 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= @@ -820,6 +819,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd 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/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= 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= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index 475c6dae2e78..5850bc66d337 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -1,6 +1,6 @@ module cosmossdk.io/tests/systemtests -go 1.24.0 +go 1.25.0 replace ( // always use latest versions in tests @@ -50,6 +50,7 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.1.3 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect + github.com/cosmos/cosmos-sdk/blockstm v0.0.0-00010101000000-000000000000 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/gogoproto v1.7.0 // indirect @@ -175,3 +176,8 @@ require ( pgregory.net/rapid v1.2.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +replace ( + cosmossdk.io/store => ../../store + github.com/cosmos/cosmos-sdk/blockstm => ../../blockstm +) diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index a3cf0e080c12..c2fd541ed266 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -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= @@ -383,8 +381,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= @@ -699,6 +698,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd 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/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= 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= diff --git a/testutil/context.go b/testutil/context.go index 288f7fc55bcd..cd31fdf6b126 100644 --- a/testutil/context.go +++ b/testutil/context.go @@ -80,3 +80,18 @@ func DefaultContextWithDB(tb testing.TB, key, tkey storetypes.StoreKey) TestCont return TestContext{ctx, db, cms} } + +func DefaultContextWithObjectStore(tb testing.TB, key, tkey, okey storetypes.StoreKey) TestContext { + tb.Helper() + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) + cms.MountStoreWithDB(tkey, storetypes.StoreTypeTransient, db) + cms.MountStoreWithDB(okey, storetypes.StoreTypeObject, db) + err := cms.LoadLatestVersion() + assert.NoError(tb, err) + + ctx := sdk.NewContext(cms, cmtproto.Header{Time: time.Now()}, false, log.NewNopLogger()) + + return TestContext{ctx, db, cms} +} diff --git a/tools/benchmark/go.mod b/tools/benchmark/go.mod index 31b271d40cb3..04dd6ff74a86 100644 --- a/tools/benchmark/go.mod +++ b/tools/benchmark/go.mod @@ -16,8 +16,9 @@ require ( google.golang.org/grpc v1.75.1 ) +require cosmossdk.io/collections v1.3.1 // indirect + require ( - cosmossdk.io/collections v1.3.1 // indirect cosmossdk.io/errors v1.0.2 // indirect cosmossdk.io/math v1.5.3 // indirect cosmossdk.io/schema v1.1.0 // indirect diff --git a/tools/confix/go.mod b/tools/confix/go.mod index ed1f3e7a0bdd..854f70c9f3c9 100644 --- a/tools/confix/go.mod +++ b/tools/confix/go.mod @@ -12,15 +12,17 @@ require ( gotest.tools/v3 v3.5.2 ) +require go.yaml.in/yaml/v2 v2.4.2 // indirect + require ( + cosmossdk.io/api v1.0.0-rc.1 // indirect + cosmossdk.io/collections v1.3.1 // indirect github.com/zondax/golem v0.27.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect ) require ( - cosmossdk.io/api v1.0.0-rc.1 // indirect - cosmossdk.io/collections v1.3.1 // indirect cosmossdk.io/core v0.11.3 // indirect cosmossdk.io/depinject v1.2.1 // indirect cosmossdk.io/errors v1.0.2 // indirect @@ -144,7 +146,7 @@ require ( github.com/tidwall/btree v1.7.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/zondax/hid v0.9.2 // indirect - github.com/zondax/ledger-go v1.0.1 // indirect; indirectÎ + github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect @@ -152,7 +154,6 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/arch v0.17.0 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect diff --git a/types/context.go b/types/context.go index 4b5cbdeca55b..4e762cc15943 100644 --- a/types/context.go +++ b/types/context.go @@ -64,6 +64,19 @@ type Context struct { streamingManager storetypes.StreamingManager cometInfo comet.BlockInfo headerInfo header.Info + + // For block-stm + // // the index of the current tx in the block, -1 means not in finalize block context + txIndex int + // the index of the current msg in the tx, -1 means not in finalize block context + msgIndex int + // the total number of transactions in current block + txCount int + // sum the gas used by all the transactions in the current block, only accessible by end blocker + blockGasUsed uint64 + incarnationCache map[string]any // incarnationCache is shared between multiple incarnations of the same transaction, it must only cache stateless computation results that only depends on tx body and block level information that don't change during block execution, like the result of tx signature verification. + // sum the gas wanted by all the transactions in the current block, only accessible by end blocker + blockGasWanted uint64 } // Proposed rename, not done to avoid API breakage @@ -94,6 +107,12 @@ func (c Context) TransientKVGasConfig() storetypes.GasConfig { return c.trans func (c Context) StreamingManager() storetypes.StreamingManager { return c.streamingManager } func (c Context) CometInfo() comet.BlockInfo { return c.cometInfo } func (c Context) HeaderInfo() header.Info { return c.headerInfo } +func (c Context) TxIndex() int { return c.txIndex } +func (c Context) MsgIndex() int { return c.msgIndex } +func (c Context) TxCount() int { return c.txCount } +func (c Context) BlockGasUsed() uint64 { return c.blockGasUsed } +func (c Context) IncarnationCache() map[string]any { return c.incarnationCache } +func (c Context) BlockGasWanted() uint64 { return c.blockGasWanted } // BlockHeader returns the header by value. func (c Context) BlockHeader() cmtproto.Header { @@ -139,6 +158,8 @@ func NewContext(ms storetypes.MultiStore, header cmtproto.Header, isCheckTx bool eventManager: NewEventManager(), kvGasConfig: storetypes.KVGasConfig(), transientKVGasConfig: storetypes.TransientGasConfig(), + txIndex: -1, + msgIndex: -1, } } @@ -318,6 +339,31 @@ func (c Context) WithHeaderInfo(headerInfo header.Info) Context { return c } +func (c Context) WithTxIndex(txIndex int) Context { + c.txIndex = txIndex + return c +} + +func (c Context) WithTxCount(txCount int) Context { + c.txCount = txCount + return c +} + +func (c Context) WithMsgIndex(msgIndex int) Context { + c.msgIndex = msgIndex + return c +} + +func (c Context) WithBlockGasUsed(gasUsed uint64) Context { + c.blockGasUsed = gasUsed + return c +} + +func (c Context) WithBlockGasWanted(gasWanted uint64) Context { + c.blockGasWanted = gasWanted + return c +} + // TODO: remove??? func (c Context) IsZero() bool { @@ -351,6 +397,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 @@ -367,6 +418,27 @@ func (c Context) CacheContext() (cc Context, writeCache func()) { return cc, writeCache } +func (c Context) GetIncarnationCache(key string) (any, bool) { + if c.incarnationCache == nil { + return nil, false + } + val, ok := c.incarnationCache[key] + return val, ok +} + +func (c Context) SetIncarnationCache(key string, value any) { + if c.incarnationCache == nil { + // noop if cache is not initialized + return + } + c.incarnationCache[key] = value +} + +func (c Context) WithIncarnationCache(cache map[string]any) Context { + c.incarnationCache = cache + return c +} + var ( _ context.Context = Context{} _ storetypes.Context = Context{} diff --git a/x/auth/ante/sigverify.go b/x/auth/ante/sigverify.go index b65800a151aa..1c027d4f7e79 100644 --- a/x/auth/ante/sigverify.go +++ b/x/auth/ante/sigverify.go @@ -32,6 +32,8 @@ var ( key = make([]byte, secp256k1.PubKeySize) simSecp256k1Pubkey = &secp256k1.PubKey{Key: key} simSecp256k1Sig [64]byte + + SigVerificationResultCacheKey = "ante:SigVerificationResult" ) func init() { @@ -303,10 +305,10 @@ func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool { } } -func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { +func (svd SigVerificationDecorator) anteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) error { sigTx, ok := tx.(authsigning.Tx) if !ok { - return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") + return errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } utx, ok := tx.(sdk.TxWithUnordered) @@ -314,24 +316,24 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul unorderedEnabled := svd.ak.UnorderedTransactionsEnabled() if isUnordered && !unorderedEnabled { - return ctx, errorsmod.Wrap(sdkerrors.ErrNotSupported, "unordered transactions are not enabled") + return errorsmod.Wrap(sdkerrors.ErrNotSupported, "unordered transactions are not enabled") } // stdSigs contains the sequence number, account number, and signatures. // When simulating, this would just be a 0-length slice. sigs, err := sigTx.GetSignaturesV2() if err != nil { - return ctx, err + return err } signers, err := sigTx.GetSigners() if err != nil { - return ctx, err + return err } // check that signer length and signature length are the same if len(sigs) != len(signers) { - return ctx, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(sigs)) + return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(sigs)) } // In normal transactions, each signer has a sequence value. In unordered transactions, the nonce value is at the tx body level, @@ -339,29 +341,29 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul // Because of this, we verify the unordered nonce outside the sigs loop, to avoid verifying the same nonce multiple times. if isUnordered { if err := svd.verifyUnorderedNonce(ctx, utx); err != nil { - return ctx, err + return err } } for i, sig := range sigs { if sig.Sequence > 0 && isUnordered { - return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "sequence is not allowed for unordered transactions") + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "sequence is not allowed for unordered transactions") } acc, err := GetSignerAcc(ctx, svd.ak, signers[i]) if err != nil { - return ctx, err + return err } // retrieve pubkey pubKey := acc.GetPubKey() if !simulate && pubKey == nil { - return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") + return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") } // Check account sequence number. if !isUnordered { if sig.Sequence != acc.GetSequence() { - return ctx, errorsmod.Wrapf( + return errorsmod.Wrapf( sdkerrors.ErrWrongSequence, "account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence, ) @@ -392,7 +394,7 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } adaptableTx, ok := tx.(authsigning.V2AdaptableTx) if !ok { - return ctx, fmt.Errorf("expected tx to implement V2AdaptableTx, got %T", tx) + return fmt.Errorf("expected tx to implement V2AdaptableTx, got %T", tx) } txData := adaptableTx.GetSigningTxData() err = authsigning.VerifySignature(ctx, pubKey, signerData, sig.Data, svd.signModeHandler, txData) @@ -405,12 +407,28 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } else { errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s): (%s)", accNum, chainID, err.Error()) } - return ctx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, errMsg) + return errorsmod.Wrap(sdkerrors.ErrUnauthorized, errMsg) } } } + return nil +} + +func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if v, ok := ctx.GetIncarnationCache(SigVerificationResultCacheKey); ok { + // can't convert `nil` to interface + if v != nil { + err = v.(error) + } + } else { + err = svd.anteHandle(ctx, tx, simulate) + ctx.SetIncarnationCache(SigVerificationResultCacheKey, err) + } + if err != nil { + return ctx, err + } return next(ctx, tx, simulate) } diff --git a/x/bank/keeper/collections_test.go b/x/bank/keeper/collections_test.go index 97b5e3e8bca0..6818fa0f6fca 100644 --- a/x/bank/keeper/collections_test.go +++ b/x/bank/keeper/collections_test.go @@ -26,7 +26,8 @@ import ( func TestBankStateCompatibility(t *testing.T) { key := storetypes.NewKVStoreKey(banktypes.StoreKey) - testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) + oKey := storetypes.NewObjectStoreKey(banktypes.ObjectStoreKey) + testCtx := testutil.DefaultContextWithObjectStore(t, key, storetypes.NewTransientStoreKey("transient_test"), oKey) ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()}) encCfg := moduletestutil.MakeTestEncodingConfig() @@ -45,6 +46,7 @@ func TestBankStateCompatibility(t *testing.T) { authtypes.NewModuleAddress("gov").String(), log.NewNopLogger(), ) + k = k.WithObjStoreKey(oKey) // test we can decode balances without problems // using the old value format of the denom to address index diff --git a/x/bank/keeper/keeper.go b/x/bank/keeper/keeper.go index ec5520d12f4b..ed5b3492c982 100644 --- a/x/bank/keeper/keeper.go +++ b/x/bank/keeper/keeper.go @@ -8,6 +8,7 @@ import ( errorsmod "cosmossdk.io/errors" "cosmossdk.io/log" "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -24,6 +25,7 @@ var _ Keeper = (*BaseKeeper)(nil) type Keeper interface { SendKeeper WithMintCoinsRestriction(types.MintingRestrictionFn) BaseKeeper + WithObjStoreKey(storetypes.StoreKey) BaseKeeper InitGenesis(context.Context, *types.GenesisState) ExportGenesis(context.Context) *types.GenesisState @@ -46,6 +48,12 @@ type Keeper interface { MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + SendCoinsFromAccountToModuleVirtual(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccountVirtual(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + CreditVirtualAccounts(ctx context.Context) error + SendCoinsFromVirtual(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsToVirtual(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error + DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error UndelegateCoins(ctx context.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error @@ -63,6 +71,11 @@ type BaseKeeper struct { logger log.Logger } +func (k BaseKeeper) WithObjStoreKey(okey storetypes.StoreKey) BaseKeeper { + k.objStoreKey = okey + return k +} + // GetPaginatedTotalSupply queries for the supply, ignoring 0 coins, with a given pagination func (k BaseKeeper) GetPaginatedTotalSupply(ctx context.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) { coins, pageResp, err := query.CollectionPaginate(ctx, k.Supply, pagination, func(key string, value math.Int) (sdk.Coin, error) { diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index e6f8b65a2b68..224f1efc0b5e 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -132,7 +132,8 @@ func TestKeeperTestSuite(t *testing.T) { func (suite *KeeperTestSuite) SetupTest() { key := storetypes.NewKVStoreKey(banktypes.StoreKey) - testCtx := testutil.DefaultContextWithDB(suite.T(), key, storetypes.NewTransientStoreKey("transient_test")) + oKey := storetypes.NewObjectStoreKey(banktypes.ObjectStoreKey) + testCtx := testutil.DefaultContextWithObjectStore(suite.T(), key, storetypes.NewTransientStoreKey("transient_test"), oKey) ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()}) encCfg := moduletestutil.MakeTestEncodingConfig() @@ -152,6 +153,7 @@ func (suite *KeeperTestSuite) SetupTest() { authtypes.NewModuleAddress(govtypes.ModuleName).String(), log.NewNopLogger(), ) + suite.bankKeeper = suite.bankKeeper.WithObjStoreKey(oKey) banktypes.RegisterInterfaces(encCfg.InterfaceRegistry) @@ -174,6 +176,16 @@ func (suite *KeeperTestSuite) mockMintCoins(moduleAcc *authtypes.ModuleAccount) suite.authKeeper.EXPECT().GetModuleAccount(suite.ctx, moduleAcc.Name).Return(moduleAcc) } +func (suite *KeeperTestSuite) mockSendCoinsFromAccountToModuleVirtual(acc *authtypes.BaseAccount, moduleAcc *authtypes.ModuleAccount) { + suite.authKeeper.EXPECT().GetModuleAccount(suite.ctx, moduleAcc.Name).Return(moduleAcc) + suite.authKeeper.EXPECT().GetAccount(suite.ctx, acc.GetAddress()).Return(acc) +} + +func (suite *KeeperTestSuite) mockSendCoinsFromModuleToAccountVirtual(moduleAcc *authtypes.ModuleAccount, accAddr sdk.AccAddress) { + suite.authKeeper.EXPECT().GetModuleAddress(moduleAcc.Name).Return(moduleAcc.GetAddress()) + suite.authKeeper.EXPECT().HasAccount(suite.ctx, accAddr).Return(true) +} + func (suite *KeeperTestSuite) mockSendCoinsFromModuleToAccount(moduleAcc *authtypes.ModuleAccount, accAddr sdk.AccAddress) { suite.authKeeper.EXPECT().GetModuleAddress(moduleAcc.Name).Return(moduleAcc.GetAddress()) suite.authKeeper.EXPECT().GetAccount(suite.ctx, moduleAcc.GetAddress()).Return(moduleAcc) @@ -634,6 +646,38 @@ func (suite *KeeperTestSuite) TestSendCoinsNewAccount() { require.Equal(acc1Balances, updatedAcc1Bal) } +func (suite *KeeperTestSuite) TestSendCoinsVirtual() { + ctx := suite.ctx + require := suite.Require() + keeper := suite.bankKeeper + sdkCtx := sdk.UnwrapSDKContext(ctx) + acc0 := authtypes.NewBaseAccountWithAddress(accAddrs[0]) + feeDenom1 := "fee1" + feeDenom2 := "fee2" + + balances := sdk.NewCoins(sdk.NewInt64Coin(feeDenom1, 100), sdk.NewInt64Coin(feeDenom2, 100)) + suite.mockFundAccount(accAddrs[0]) + require.NoError(banktestutil.FundAccount(ctx, suite.bankKeeper, accAddrs[0], balances)) + + sendAmt := sdk.NewCoins(sdk.NewInt64Coin(feeDenom1, 50), sdk.NewInt64Coin(feeDenom2, 50)) + suite.mockSendCoinsFromAccountToModuleVirtual(acc0, burnerAcc) + require.NoError( + keeper.SendCoinsFromAccountToModuleVirtual(sdkCtx, accAddrs[0], authtypes.Burner, sendAmt), + ) + + refundAmt := sdk.NewCoins(sdk.NewInt64Coin(feeDenom1, 25), sdk.NewInt64Coin(feeDenom2, 25)) + suite.mockSendCoinsFromModuleToAccountVirtual(burnerAcc, accAddrs[0]) + require.NoError( + keeper.SendCoinsFromModuleToAccountVirtual(sdkCtx, authtypes.Burner, accAddrs[0], refundAmt), + ) + + suite.authKeeper.EXPECT().HasAccount(suite.ctx, burnerAcc.GetAddress()).Return(true) + require.NoError(keeper.CreditVirtualAccounts(ctx)) + + require.Equal(math.NewInt(25), keeper.GetBalance(suite.ctx, burnerAcc.GetAddress(), feeDenom1).Amount) + require.Equal(math.NewInt(25), keeper.GetBalance(suite.ctx, burnerAcc.GetAddress(), feeDenom2).Amount) +} + func (suite *KeeperTestSuite) TestInputOutputNewAccount() { ctx := suite.ctx require := suite.Require() diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 96fa6f2a089b..38322c77322d 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -9,6 +9,7 @@ import ( errorsmod "cosmossdk.io/errors" "cosmossdk.io/log" "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/telemetry" @@ -60,6 +61,7 @@ type BaseSendKeeper struct { ak types.AccountKeeper storeService store.KVStoreService logger log.Logger + objStoreKey storetypes.StoreKey // list of addresses that are restricted from receiving transactions blockedAddrs map[string]bool @@ -240,6 +242,12 @@ func (k BaseSendKeeper) SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccA return err } + k.ensureAccountCreated(ctx, toAddr) + k.emitSendCoinsEvents(ctx, fromAddr, toAddr, amt) + return nil +} + +func (k BaseSendKeeper) ensureAccountCreated(ctx context.Context, toAddr sdk.AccAddress) { // Create account if recipient does not exist. // // NOTE: This should ultimately be removed in favor a more flexible approach @@ -249,7 +257,10 @@ func (k BaseSendKeeper) SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccA defer telemetry.IncrCounter(1, "new", "account") k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr)) } +} +// emitSendCoinsEvents emit send coins events. +func (k BaseSendKeeper) emitSendCoinsEvents(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) { // bech32 encoding is expensive! Only do it once for fromAddr fromAddrString := fromAddr.String() sdkCtx := sdk.UnwrapSDKContext(ctx) @@ -265,8 +276,6 @@ func (k BaseSendKeeper) SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccA sdk.NewAttribute(types.AttributeKeySender, fromAddrString), ), }) - - return nil } // subUnlockedCoins removes the unlocked amt coins of the given account. diff --git a/x/bank/keeper/virtual.go b/x/bank/keeper/virtual.go new file mode 100644 index 000000000000..d56ce71ea6d0 --- /dev/null +++ b/x/bank/keeper/virtual.go @@ -0,0 +1,184 @@ +package keeper + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "fmt" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// SendCoinsFromAccountToModuleVirtual sends coins from account to a virtual module account. +func (k BaseSendKeeper) SendCoinsFromAccountToModuleVirtual( + ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins, +) error { + recipientAcc := k.ak.GetModuleAccount(ctx, recipientModule) + if recipientAcc == nil { + panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", recipientModule)) + } + + return k.SendCoinsToVirtual(ctx, senderAddr, recipientAcc.GetAddress(), amt) +} + +// SendCoinsFromModuleToAccountVirtual sends coins from account to a virtual module account. +func (k BaseSendKeeper) SendCoinsFromModuleToAccountVirtual( + ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, +) error { + senderAddr := k.ak.GetModuleAddress(senderModule) + if senderAddr == nil { + panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", senderModule)) + } + + if k.BlockedAddr(recipientAddr) { + return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", recipientAddr) + } + + return k.SendCoinsFromVirtual(ctx, senderAddr, recipientAddr, amt) +} + +// SendCoinsToVirtual accumulate the recipient's coins in a per-transaction transient state, +// which are sumed up and added to the real account at the end of block. +// Events are emiited the same as normal send. +func (k BaseSendKeeper) SendCoinsToVirtual(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error { + var err error + err = k.subUnlockedCoins(ctx, fromAddr, amt) + if err != nil { + return err + } + + toAddr, err = k.sendRestriction.apply(ctx, fromAddr, toAddr, amt) + if err != nil { + return err + } + + k.addVirtualCoins(ctx, toAddr, amt) + k.emitSendCoinsEvents(ctx, fromAddr, toAddr, amt) + return nil +} + +// SendCoinsFromVirtual deduct coins from virtual from account and send to recipient account. +func (k BaseSendKeeper) SendCoinsFromVirtual(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error { + var err error + err = k.subVirtualCoins(ctx, fromAddr, amt) + if err != nil { + return err + } + + toAddr, err = k.sendRestriction.apply(ctx, fromAddr, toAddr, amt) + if err != nil { + return err + } + + err = k.addCoins(ctx, toAddr, amt) + if err != nil { + return err + } + + k.ensureAccountCreated(ctx, toAddr) + k.emitSendCoinsEvents(ctx, fromAddr, toAddr, amt) + return nil +} + +func (k BaseSendKeeper) addVirtualCoins(ctx context.Context, addr sdk.AccAddress, amt sdk.Coins) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + store := sdkCtx.ObjectStore(k.objStoreKey) + + // bytes containing account address followed by the txn index + key := make([]byte, len(addr)+8) + copy(key, addr) + binary.BigEndian.PutUint64(key[len(addr):], uint64(sdkCtx.TxIndex())) + + var coins sdk.Coins + value := store.Get(key) + if value != nil { + coins = value.(sdk.Coins) + } + coins = coins.Add(amt...) + store.Set(key, coins) +} + +func (k BaseSendKeeper) subVirtualCoins(ctx context.Context, addr sdk.AccAddress, amt sdk.Coins) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + store := sdkCtx.ObjectStore(k.objStoreKey) + + key := make([]byte, len(addr)+8) + copy(key, addr) + binary.BigEndian.PutUint64(key[len(addr):], uint64(sdkCtx.TxIndex())) + + value := store.Get(key) + if value == nil { + return errorsmod.Wrapf( + sdkerrors.ErrInsufficientFunds, + "spendable balance 0 is smaller than %s", + amt, + ) + } + spendable := value.(sdk.Coins) + balance, hasNeg := spendable.SafeSub(amt...) + if hasNeg { + return errorsmod.Wrapf( + sdkerrors.ErrInsufficientFunds, + "spendable balance %s is smaller than %s", + spendable, amt, + ) + } + if balance.IsZero() { + store.Delete(key) + } else { + store.Set(key, balance) + } + + return nil +} + +// CreditVirtualAccounts sum up the transient coins and add them to the real account, +// should be called at end blocker. +func (k BaseSendKeeper) CreditVirtualAccounts(ctx context.Context) error { + // No-op if we're not using the objStore to accumulate to module accounts + if k.objStoreKey == nil { + return nil + } + store := sdk.UnwrapSDKContext(ctx).ObjectStore(k.objStoreKey) + + var toAddr sdk.AccAddress + sum := sdk.NewMapCoins(nil) + flushCurrentAddr := func() error { + if len(sum) == 0 { + // nothing to flush + return nil + } + + if err := k.addCoins(ctx, toAddr, sum.ToCoins()); err != nil { + return err + } + clear(sum) + + k.ensureAccountCreated(ctx, toAddr) + return nil + } + + it := store.Iterator(nil, nil) + defer it.Close() + for ; it.Valid(); it.Next() { + if len(it.Key()) <= 8 { + return fmt.Errorf("unexpected key length: %s", hex.EncodeToString(it.Key())) + } + + addr := it.Key()[:len(it.Key())-8] + if !bytes.Equal(toAddr, addr) { + if err := flushCurrentAddr(); err != nil { + return err + } + toAddr = addr + } + + sum.Add(it.Value().(sdk.Coins)...) + } + + return flushCurrentAddr() +} diff --git a/x/bank/module.go b/x/bank/module.go index e7140af204a9..b14032e1f6ac 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -44,7 +44,8 @@ var ( _ module.HasGenesis = AppModule{} _ module.HasServices = AppModule{} - _ appmodule.AppModule = AppModule{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasEndBlocker = AppModule{} ) // AppModuleBasic defines the basic application module used by the bank module. @@ -202,6 +203,10 @@ func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Re reg.Add(weights.Get("msg_multisend", 10), simulation.MsgMultiSendFactory()) } +func (am AppModule) EndBlock(ctx context.Context) error { + return am.keeper.CreditVirtualAccounts(ctx) +} + // App Wiring Setup func init() { diff --git a/x/bank/types/keys.go b/x/bank/types/keys.go index b4ea683d4b69..485d1cdb9ef0 100644 --- a/x/bank/types/keys.go +++ b/x/bank/types/keys.go @@ -17,6 +17,9 @@ const ( // RouterKey defines the module's message routing key RouterKey = ModuleName + + // ObjectStoreKey defines the store name for the object store + ObjectStoreKey = "object:" + ModuleName ) // KVStore keys diff --git a/x/gov/testutil/expected_keepers_mocks.go b/x/gov/testutil/expected_keepers_mocks.go index 64a678b21741..3c5b098bb417 100644 --- a/x/gov/testutil/expected_keepers_mocks.go +++ b/x/gov/testutil/expected_keepers_mocks.go @@ -15,11 +15,12 @@ import ( address "cosmossdk.io/core/address" math "cosmossdk.io/math" - types "github.com/cosmos/cosmos-sdk/types" + types "cosmossdk.io/store/types" + types0 "github.com/cosmos/cosmos-sdk/types" query "github.com/cosmos/cosmos-sdk/types/query" keeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - types0 "github.com/cosmos/cosmos-sdk/x/bank/types" - types1 "github.com/cosmos/cosmos-sdk/x/staking/types" + types1 "github.com/cosmos/cosmos-sdk/x/bank/types" + types2 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "go.uber.org/mock/gomock" ) @@ -62,10 +63,10 @@ func (mr *MockAccountKeeperMockRecorder) AddressCodec() *gomock.Call { } // GetAccount mocks base method. -func (m *MockAccountKeeper) GetAccount(ctx context.Context, addr types.AccAddress) types.AccountI { +func (m *MockAccountKeeper) GetAccount(ctx context.Context, addr types0.AccAddress) types0.AccountI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAccount", ctx, addr) - ret0, _ := ret[0].(types.AccountI) + ret0, _ := ret[0].(types0.AccountI) return ret0 } @@ -76,10 +77,10 @@ func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr any) *gomock.Call } // GetModuleAccount mocks base method. -func (m *MockAccountKeeper) GetModuleAccount(ctx context.Context, name string) types.ModuleAccountI { +func (m *MockAccountKeeper) GetModuleAccount(ctx context.Context, name string) types0.ModuleAccountI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetModuleAccount", ctx, name) - ret0, _ := ret[0].(types.ModuleAccountI) + ret0, _ := ret[0].(types0.ModuleAccountI) return ret0 } @@ -90,10 +91,10 @@ func (mr *MockAccountKeeperMockRecorder) GetModuleAccount(ctx, name any) *gomock } // GetModuleAddress mocks base method. -func (m *MockAccountKeeper) GetModuleAddress(name string) types.AccAddress { +func (m *MockAccountKeeper) GetModuleAddress(name string) types0.AccAddress { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetModuleAddress", name) - ret0, _ := ret[0].(types.AccAddress) + ret0, _ := ret[0].(types0.AccAddress) return ret0 } @@ -104,7 +105,7 @@ func (mr *MockAccountKeeperMockRecorder) GetModuleAddress(name any) *gomock.Call } // IterateAccounts mocks base method. -func (m *MockAccountKeeper) IterateAccounts(ctx context.Context, cb func(types.AccountI) bool) { +func (m *MockAccountKeeper) IterateAccounts(ctx context.Context, cb func(types0.AccountI) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateAccounts", ctx, cb) } @@ -116,7 +117,7 @@ func (mr *MockAccountKeeperMockRecorder) IterateAccounts(ctx, cb any) *gomock.Ca } // SetModuleAccount mocks base method. -func (m *MockAccountKeeper) SetModuleAccount(arg0 context.Context, arg1 types.ModuleAccountI) { +func (m *MockAccountKeeper) SetModuleAccount(arg0 context.Context, arg1 types0.ModuleAccountI) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetModuleAccount", arg0, arg1) } @@ -152,10 +153,10 @@ func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { } // AllBalances mocks base method. -func (m *MockBankKeeper) AllBalances(arg0 context.Context, arg1 *types0.QueryAllBalancesRequest) (*types0.QueryAllBalancesResponse, error) { +func (m *MockBankKeeper) AllBalances(arg0 context.Context, arg1 *types1.QueryAllBalancesRequest) (*types1.QueryAllBalancesResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AllBalances", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryAllBalancesResponse) + ret0, _ := ret[0].(*types1.QueryAllBalancesResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -167,7 +168,7 @@ func (mr *MockBankKeeperMockRecorder) AllBalances(arg0, arg1 any) *gomock.Call { } // AppendSendRestriction mocks base method. -func (m *MockBankKeeper) AppendSendRestriction(restriction types0.SendRestrictionFn) { +func (m *MockBankKeeper) AppendSendRestriction(restriction types1.SendRestrictionFn) { m.ctrl.T.Helper() m.ctrl.Call(m, "AppendSendRestriction", restriction) } @@ -179,10 +180,10 @@ func (mr *MockBankKeeperMockRecorder) AppendSendRestriction(restriction any) *go } // Balance mocks base method. -func (m *MockBankKeeper) Balance(arg0 context.Context, arg1 *types0.QueryBalanceRequest) (*types0.QueryBalanceResponse, error) { +func (m *MockBankKeeper) Balance(arg0 context.Context, arg1 *types1.QueryBalanceRequest) (*types1.QueryBalanceResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Balance", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryBalanceResponse) + ret0, _ := ret[0].(*types1.QueryBalanceResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -194,7 +195,7 @@ func (mr *MockBankKeeperMockRecorder) Balance(arg0, arg1 any) *gomock.Call { } // BlockedAddr mocks base method. -func (m *MockBankKeeper) BlockedAddr(addr types.AccAddress) bool { +func (m *MockBankKeeper) BlockedAddr(addr types0.AccAddress) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BlockedAddr", addr) ret0, _ := ret[0].(bool) @@ -208,7 +209,7 @@ func (mr *MockBankKeeperMockRecorder) BlockedAddr(addr any) *gomock.Call { } // BurnCoins mocks base method. -func (m *MockBankKeeper) BurnCoins(ctx context.Context, moduleName string, amt types.Coins) error { +func (m *MockBankKeeper) BurnCoins(ctx context.Context, moduleName string, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BurnCoins", ctx, moduleName, amt) ret0, _ := ret[0].(error) @@ -233,8 +234,22 @@ func (mr *MockBankKeeperMockRecorder) ClearSendRestriction() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearSendRestriction", reflect.TypeOf((*MockBankKeeper)(nil).ClearSendRestriction)) } +// CreditVirtualAccounts mocks base method. +func (m *MockBankKeeper) CreditVirtualAccounts(ctx context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreditVirtualAccounts", ctx) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreditVirtualAccounts indicates an expected call of CreditVirtualAccounts. +func (mr *MockBankKeeperMockRecorder) CreditVirtualAccounts(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreditVirtualAccounts", reflect.TypeOf((*MockBankKeeper)(nil).CreditVirtualAccounts), ctx) +} + // DelegateCoins mocks base method. -func (m *MockBankKeeper) DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr types.AccAddress, amt types.Coins) error { +func (m *MockBankKeeper) DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr types0.AccAddress, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DelegateCoins", ctx, delegatorAddr, moduleAccAddr, amt) ret0, _ := ret[0].(error) @@ -248,7 +263,7 @@ func (mr *MockBankKeeperMockRecorder) DelegateCoins(ctx, delegatorAddr, moduleAc } // DelegateCoinsFromAccountToModule mocks base method. -func (m *MockBankKeeper) DelegateCoinsFromAccountToModule(ctx context.Context, senderAddr types.AccAddress, recipientModule string, amt types.Coins) error { +func (m *MockBankKeeper) DelegateCoinsFromAccountToModule(ctx context.Context, senderAddr types0.AccAddress, recipientModule string, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DelegateCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt) ret0, _ := ret[0].(error) @@ -279,10 +294,10 @@ func (mr *MockBankKeeperMockRecorder) DeleteSendEnabled(ctx any, denoms ...any) } // DenomMetadata mocks base method. -func (m *MockBankKeeper) DenomMetadata(arg0 context.Context, arg1 *types0.QueryDenomMetadataRequest) (*types0.QueryDenomMetadataResponse, error) { +func (m *MockBankKeeper) DenomMetadata(arg0 context.Context, arg1 *types1.QueryDenomMetadataRequest) (*types1.QueryDenomMetadataResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DenomMetadata", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryDenomMetadataResponse) + ret0, _ := ret[0].(*types1.QueryDenomMetadataResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -294,10 +309,10 @@ func (mr *MockBankKeeperMockRecorder) DenomMetadata(arg0, arg1 any) *gomock.Call } // DenomMetadataByQueryString mocks base method. -func (m *MockBankKeeper) DenomMetadataByQueryString(arg0 context.Context, arg1 *types0.QueryDenomMetadataByQueryStringRequest) (*types0.QueryDenomMetadataByQueryStringResponse, error) { +func (m *MockBankKeeper) DenomMetadataByQueryString(arg0 context.Context, arg1 *types1.QueryDenomMetadataByQueryStringRequest) (*types1.QueryDenomMetadataByQueryStringResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DenomMetadataByQueryString", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryDenomMetadataByQueryStringResponse) + ret0, _ := ret[0].(*types1.QueryDenomMetadataByQueryStringResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -309,10 +324,10 @@ func (mr *MockBankKeeperMockRecorder) DenomMetadataByQueryString(arg0, arg1 any) } // DenomOwners mocks base method. -func (m *MockBankKeeper) DenomOwners(arg0 context.Context, arg1 *types0.QueryDenomOwnersRequest) (*types0.QueryDenomOwnersResponse, error) { +func (m *MockBankKeeper) DenomOwners(arg0 context.Context, arg1 *types1.QueryDenomOwnersRequest) (*types1.QueryDenomOwnersResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DenomOwners", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryDenomOwnersResponse) + ret0, _ := ret[0].(*types1.QueryDenomOwnersResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -324,10 +339,10 @@ func (mr *MockBankKeeperMockRecorder) DenomOwners(arg0, arg1 any) *gomock.Call { } // DenomOwnersByQuery mocks base method. -func (m *MockBankKeeper) DenomOwnersByQuery(arg0 context.Context, arg1 *types0.QueryDenomOwnersByQueryRequest) (*types0.QueryDenomOwnersByQueryResponse, error) { +func (m *MockBankKeeper) DenomOwnersByQuery(arg0 context.Context, arg1 *types1.QueryDenomOwnersByQueryRequest) (*types1.QueryDenomOwnersByQueryResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DenomOwnersByQuery", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryDenomOwnersByQueryResponse) + ret0, _ := ret[0].(*types1.QueryDenomOwnersByQueryResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -339,10 +354,10 @@ func (mr *MockBankKeeperMockRecorder) DenomOwnersByQuery(arg0, arg1 any) *gomock } // DenomsMetadata mocks base method. -func (m *MockBankKeeper) DenomsMetadata(arg0 context.Context, arg1 *types0.QueryDenomsMetadataRequest) (*types0.QueryDenomsMetadataResponse, error) { +func (m *MockBankKeeper) DenomsMetadata(arg0 context.Context, arg1 *types1.QueryDenomsMetadataRequest) (*types1.QueryDenomsMetadataResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DenomsMetadata", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryDenomsMetadataResponse) + ret0, _ := ret[0].(*types1.QueryDenomsMetadataResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -354,10 +369,10 @@ func (mr *MockBankKeeperMockRecorder) DenomsMetadata(arg0, arg1 any) *gomock.Cal } // ExportGenesis mocks base method. -func (m *MockBankKeeper) ExportGenesis(arg0 context.Context) *types0.GenesisState { +func (m *MockBankKeeper) ExportGenesis(arg0 context.Context) *types1.GenesisState { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ExportGenesis", arg0) - ret0, _ := ret[0].(*types0.GenesisState) + ret0, _ := ret[0].(*types1.GenesisState) return ret0 } @@ -368,10 +383,10 @@ func (mr *MockBankKeeperMockRecorder) ExportGenesis(arg0 any) *gomock.Call { } // GetAccountsBalances mocks base method. -func (m *MockBankKeeper) GetAccountsBalances(ctx context.Context) []types0.Balance { +func (m *MockBankKeeper) GetAccountsBalances(ctx context.Context) []types1.Balance { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAccountsBalances", ctx) - ret0, _ := ret[0].([]types0.Balance) + ret0, _ := ret[0].([]types1.Balance) return ret0 } @@ -382,10 +397,10 @@ func (mr *MockBankKeeperMockRecorder) GetAccountsBalances(ctx any) *gomock.Call } // GetAllBalances mocks base method. -func (m *MockBankKeeper) GetAllBalances(ctx context.Context, addr types.AccAddress) types.Coins { +func (m *MockBankKeeper) GetAllBalances(ctx context.Context, addr types0.AccAddress) types0.Coins { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllBalances", ctx, addr) - ret0, _ := ret[0].(types.Coins) + ret0, _ := ret[0].(types0.Coins) return ret0 } @@ -396,10 +411,10 @@ func (mr *MockBankKeeperMockRecorder) GetAllBalances(ctx, addr any) *gomock.Call } // GetAllDenomMetaData mocks base method. -func (m *MockBankKeeper) GetAllDenomMetaData(ctx context.Context) []types0.Metadata { +func (m *MockBankKeeper) GetAllDenomMetaData(ctx context.Context) []types1.Metadata { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllDenomMetaData", ctx) - ret0, _ := ret[0].([]types0.Metadata) + ret0, _ := ret[0].([]types1.Metadata) return ret0 } @@ -410,10 +425,10 @@ func (mr *MockBankKeeperMockRecorder) GetAllDenomMetaData(ctx any) *gomock.Call } // GetAllSendEnabledEntries mocks base method. -func (m *MockBankKeeper) GetAllSendEnabledEntries(ctx context.Context) []types0.SendEnabled { +func (m *MockBankKeeper) GetAllSendEnabledEntries(ctx context.Context) []types1.SendEnabled { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllSendEnabledEntries", ctx) - ret0, _ := ret[0].([]types0.SendEnabled) + ret0, _ := ret[0].([]types1.SendEnabled) return ret0 } @@ -438,10 +453,10 @@ func (mr *MockBankKeeperMockRecorder) GetAuthority() *gomock.Call { } // GetBalance mocks base method. -func (m *MockBankKeeper) GetBalance(ctx context.Context, addr types.AccAddress, denom string) types.Coin { +func (m *MockBankKeeper) GetBalance(ctx context.Context, addr types0.AccAddress, denom string) types0.Coin { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBalance", ctx, addr, denom) - ret0, _ := ret[0].(types.Coin) + ret0, _ := ret[0].(types0.Coin) return ret0 } @@ -466,10 +481,10 @@ func (mr *MockBankKeeperMockRecorder) GetBlockedAddresses() *gomock.Call { } // GetDenomMetaData mocks base method. -func (m *MockBankKeeper) GetDenomMetaData(ctx context.Context, denom string) (types0.Metadata, bool) { +func (m *MockBankKeeper) GetDenomMetaData(ctx context.Context, denom string) (types1.Metadata, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDenomMetaData", ctx, denom) - ret0, _ := ret[0].(types0.Metadata) + ret0, _ := ret[0].(types1.Metadata) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -481,10 +496,10 @@ func (mr *MockBankKeeperMockRecorder) GetDenomMetaData(ctx, denom any) *gomock.C } // GetPaginatedTotalSupply mocks base method. -func (m *MockBankKeeper) GetPaginatedTotalSupply(ctx context.Context, pagination *query.PageRequest) (types.Coins, *query.PageResponse, error) { +func (m *MockBankKeeper) GetPaginatedTotalSupply(ctx context.Context, pagination *query.PageRequest) (types0.Coins, *query.PageResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPaginatedTotalSupply", ctx, pagination) - ret0, _ := ret[0].(types.Coins) + ret0, _ := ret[0].(types0.Coins) ret1, _ := ret[1].(*query.PageResponse) ret2, _ := ret[2].(error) return ret0, ret1, ret2 @@ -497,10 +512,10 @@ func (mr *MockBankKeeperMockRecorder) GetPaginatedTotalSupply(ctx, pagination an } // GetParams mocks base method. -func (m *MockBankKeeper) GetParams(ctx context.Context) types0.Params { +func (m *MockBankKeeper) GetParams(ctx context.Context) types1.Params { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetParams", ctx) - ret0, _ := ret[0].(types0.Params) + ret0, _ := ret[0].(types1.Params) return ret0 } @@ -511,10 +526,10 @@ func (mr *MockBankKeeperMockRecorder) GetParams(ctx any) *gomock.Call { } // GetSendEnabledEntry mocks base method. -func (m *MockBankKeeper) GetSendEnabledEntry(ctx context.Context, denom string) (types0.SendEnabled, bool) { +func (m *MockBankKeeper) GetSendEnabledEntry(ctx context.Context, denom string) (types1.SendEnabled, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSendEnabledEntry", ctx, denom) - ret0, _ := ret[0].(types0.SendEnabled) + ret0, _ := ret[0].(types1.SendEnabled) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -526,10 +541,10 @@ func (mr *MockBankKeeperMockRecorder) GetSendEnabledEntry(ctx, denom any) *gomoc } // GetSupply mocks base method. -func (m *MockBankKeeper) GetSupply(ctx context.Context, denom string) types.Coin { +func (m *MockBankKeeper) GetSupply(ctx context.Context, denom string) types0.Coin { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSupply", ctx, denom) - ret0, _ := ret[0].(types.Coin) + ret0, _ := ret[0].(types0.Coin) return ret0 } @@ -540,7 +555,7 @@ func (mr *MockBankKeeperMockRecorder) GetSupply(ctx, denom any) *gomock.Call { } // HasBalance mocks base method. -func (m *MockBankKeeper) HasBalance(ctx context.Context, addr types.AccAddress, amt types.Coin) bool { +func (m *MockBankKeeper) HasBalance(ctx context.Context, addr types0.AccAddress, amt types0.Coin) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasBalance", ctx, addr, amt) ret0, _ := ret[0].(bool) @@ -582,7 +597,7 @@ func (mr *MockBankKeeperMockRecorder) HasSupply(ctx, denom any) *gomock.Call { } // InitGenesis mocks base method. -func (m *MockBankKeeper) InitGenesis(arg0 context.Context, arg1 *types0.GenesisState) { +func (m *MockBankKeeper) InitGenesis(arg0 context.Context, arg1 *types1.GenesisState) { m.ctrl.T.Helper() m.ctrl.Call(m, "InitGenesis", arg0, arg1) } @@ -594,7 +609,7 @@ func (mr *MockBankKeeperMockRecorder) InitGenesis(arg0, arg1 any) *gomock.Call { } // InputOutputCoins mocks base method. -func (m *MockBankKeeper) InputOutputCoins(ctx context.Context, input types0.Input, outputs []types0.Output) error { +func (m *MockBankKeeper) InputOutputCoins(ctx context.Context, input types1.Input, outputs []types1.Output) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InputOutputCoins", ctx, input, outputs) ret0, _ := ret[0].(error) @@ -608,7 +623,7 @@ func (mr *MockBankKeeperMockRecorder) InputOutputCoins(ctx, input, outputs any) } // IsSendEnabledCoin mocks base method. -func (m *MockBankKeeper) IsSendEnabledCoin(ctx context.Context, coin types.Coin) bool { +func (m *MockBankKeeper) IsSendEnabledCoin(ctx context.Context, coin types0.Coin) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsSendEnabledCoin", ctx, coin) ret0, _ := ret[0].(bool) @@ -622,7 +637,7 @@ func (mr *MockBankKeeperMockRecorder) IsSendEnabledCoin(ctx, coin any) *gomock.C } // IsSendEnabledCoins mocks base method. -func (m *MockBankKeeper) IsSendEnabledCoins(ctx context.Context, coins ...types.Coin) error { +func (m *MockBankKeeper) IsSendEnabledCoins(ctx context.Context, coins ...types0.Coin) error { m.ctrl.T.Helper() varargs := []any{ctx} for _, a := range coins { @@ -655,7 +670,7 @@ func (mr *MockBankKeeperMockRecorder) IsSendEnabledDenom(ctx, denom any) *gomock } // IterateAccountBalances mocks base method. -func (m *MockBankKeeper) IterateAccountBalances(ctx context.Context, addr types.AccAddress, cb func(types.Coin) bool) { +func (m *MockBankKeeper) IterateAccountBalances(ctx context.Context, addr types0.AccAddress, cb func(types0.Coin) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateAccountBalances", ctx, addr, cb) } @@ -667,7 +682,7 @@ func (mr *MockBankKeeperMockRecorder) IterateAccountBalances(ctx, addr, cb any) } // IterateAllBalances mocks base method. -func (m *MockBankKeeper) IterateAllBalances(ctx context.Context, cb func(types.AccAddress, types.Coin) bool) { +func (m *MockBankKeeper) IterateAllBalances(ctx context.Context, cb func(types0.AccAddress, types0.Coin) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateAllBalances", ctx, cb) } @@ -679,7 +694,7 @@ func (mr *MockBankKeeperMockRecorder) IterateAllBalances(ctx, cb any) *gomock.Ca } // IterateAllDenomMetaData mocks base method. -func (m *MockBankKeeper) IterateAllDenomMetaData(ctx context.Context, cb func(types0.Metadata) bool) { +func (m *MockBankKeeper) IterateAllDenomMetaData(ctx context.Context, cb func(types1.Metadata) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateAllDenomMetaData", ctx, cb) } @@ -703,7 +718,7 @@ func (mr *MockBankKeeperMockRecorder) IterateSendEnabledEntries(ctx, cb any) *go } // IterateTotalSupply mocks base method. -func (m *MockBankKeeper) IterateTotalSupply(ctx context.Context, cb func(types.Coin) bool) { +func (m *MockBankKeeper) IterateTotalSupply(ctx context.Context, cb func(types0.Coin) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateTotalSupply", ctx, cb) } @@ -715,10 +730,10 @@ func (mr *MockBankKeeperMockRecorder) IterateTotalSupply(ctx, cb any) *gomock.Ca } // LockedCoins mocks base method. -func (m *MockBankKeeper) LockedCoins(ctx context.Context, addr types.AccAddress) types.Coins { +func (m *MockBankKeeper) LockedCoins(ctx context.Context, addr types0.AccAddress) types0.Coins { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LockedCoins", ctx, addr) - ret0, _ := ret[0].(types.Coins) + ret0, _ := ret[0].(types0.Coins) return ret0 } @@ -729,7 +744,7 @@ func (mr *MockBankKeeperMockRecorder) LockedCoins(ctx, addr any) *gomock.Call { } // MintCoins mocks base method. -func (m *MockBankKeeper) MintCoins(ctx context.Context, moduleName string, amt types.Coins) error { +func (m *MockBankKeeper) MintCoins(ctx context.Context, moduleName string, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MintCoins", ctx, moduleName, amt) ret0, _ := ret[0].(error) @@ -743,10 +758,10 @@ func (mr *MockBankKeeperMockRecorder) MintCoins(ctx, moduleName, amt any) *gomoc } // Params mocks base method. -func (m *MockBankKeeper) Params(arg0 context.Context, arg1 *types0.QueryParamsRequest) (*types0.QueryParamsResponse, error) { +func (m *MockBankKeeper) Params(arg0 context.Context, arg1 *types1.QueryParamsRequest) (*types1.QueryParamsResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Params", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryParamsResponse) + ret0, _ := ret[0].(*types1.QueryParamsResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -758,7 +773,7 @@ func (mr *MockBankKeeperMockRecorder) Params(arg0, arg1 any) *gomock.Call { } // PrependSendRestriction mocks base method. -func (m *MockBankKeeper) PrependSendRestriction(restriction types0.SendRestrictionFn) { +func (m *MockBankKeeper) PrependSendRestriction(restriction types1.SendRestrictionFn) { m.ctrl.T.Helper() m.ctrl.Call(m, "PrependSendRestriction", restriction) } @@ -770,7 +785,7 @@ func (mr *MockBankKeeperMockRecorder) PrependSendRestriction(restriction any) *g } // SendCoins mocks base method. -func (m *MockBankKeeper) SendCoins(ctx context.Context, fromAddr, toAddr types.AccAddress, amt types.Coins) error { +func (m *MockBankKeeper) SendCoins(ctx context.Context, fromAddr, toAddr types0.AccAddress, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoins", ctx, fromAddr, toAddr, amt) ret0, _ := ret[0].(error) @@ -784,7 +799,7 @@ func (mr *MockBankKeeperMockRecorder) SendCoins(ctx, fromAddr, toAddr, amt any) } // SendCoinsFromAccountToModule mocks base method. -func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx context.Context, senderAddr types.AccAddress, recipientModule string, amt types.Coins) error { +func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx context.Context, senderAddr types0.AccAddress, recipientModule string, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt) ret0, _ := ret[0].(error) @@ -797,8 +812,22 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromAccountToModule(ctx, senderAd return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromAccountToModule", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromAccountToModule), ctx, senderAddr, recipientModule, amt) } +// SendCoinsFromAccountToModuleVirtual mocks base method. +func (m *MockBankKeeper) SendCoinsFromAccountToModuleVirtual(ctx context.Context, senderAddr types0.AccAddress, recipientModule string, amt types0.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromAccountToModuleVirtual", ctx, senderAddr, recipientModule, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromAccountToModuleVirtual indicates an expected call of SendCoinsFromAccountToModuleVirtual. +func (mr *MockBankKeeperMockRecorder) SendCoinsFromAccountToModuleVirtual(ctx, senderAddr, recipientModule, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromAccountToModuleVirtual", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromAccountToModuleVirtual), ctx, senderAddr, recipientModule, amt) +} + // SendCoinsFromModuleToAccount mocks base method. -func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types.AccAddress, amt types.Coins) error { +func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types0.AccAddress, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt) ret0, _ := ret[0].(error) @@ -811,8 +840,22 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToAccount(ctx, senderMo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromModuleToAccount", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromModuleToAccount), ctx, senderModule, recipientAddr, amt) } +// SendCoinsFromModuleToAccountVirtual mocks base method. +func (m *MockBankKeeper) SendCoinsFromModuleToAccountVirtual(ctx context.Context, senderModule string, recipientAddr types0.AccAddress, amt types0.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromModuleToAccountVirtual", ctx, senderModule, recipientAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromModuleToAccountVirtual indicates an expected call of SendCoinsFromModuleToAccountVirtual. +func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToAccountVirtual(ctx, senderModule, recipientAddr, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromModuleToAccountVirtual", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromModuleToAccountVirtual), ctx, senderModule, recipientAddr, amt) +} + // SendCoinsFromModuleToModule mocks base method. -func (m *MockBankKeeper) SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt types.Coins) error { +func (m *MockBankKeeper) SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromModuleToModule", ctx, senderModule, recipientModule, amt) ret0, _ := ret[0].(error) @@ -825,11 +868,39 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToModule(ctx, senderMod return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromModuleToModule", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromModuleToModule), ctx, senderModule, recipientModule, amt) } +// SendCoinsFromVirtual mocks base method. +func (m *MockBankKeeper) SendCoinsFromVirtual(ctx context.Context, fromAddr, toAddr types0.AccAddress, amt types0.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromVirtual", ctx, fromAddr, toAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromVirtual indicates an expected call of SendCoinsFromVirtual. +func (mr *MockBankKeeperMockRecorder) SendCoinsFromVirtual(ctx, fromAddr, toAddr, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromVirtual", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromVirtual), ctx, fromAddr, toAddr, amt) +} + +// SendCoinsToVirtual mocks base method. +func (m *MockBankKeeper) SendCoinsToVirtual(ctx context.Context, fromAddr, toAddr types0.AccAddress, amt types0.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsToVirtual", ctx, fromAddr, toAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsToVirtual indicates an expected call of SendCoinsToVirtual. +func (mr *MockBankKeeperMockRecorder) SendCoinsToVirtual(ctx, fromAddr, toAddr, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsToVirtual", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsToVirtual), ctx, fromAddr, toAddr, amt) +} + // SendEnabled mocks base method. -func (m *MockBankKeeper) SendEnabled(arg0 context.Context, arg1 *types0.QuerySendEnabledRequest) (*types0.QuerySendEnabledResponse, error) { +func (m *MockBankKeeper) SendEnabled(arg0 context.Context, arg1 *types1.QuerySendEnabledRequest) (*types1.QuerySendEnabledResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendEnabled", arg0, arg1) - ret0, _ := ret[0].(*types0.QuerySendEnabledResponse) + ret0, _ := ret[0].(*types1.QuerySendEnabledResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -841,7 +912,7 @@ func (mr *MockBankKeeperMockRecorder) SendEnabled(arg0, arg1 any) *gomock.Call { } // SetAllSendEnabled mocks base method. -func (m *MockBankKeeper) SetAllSendEnabled(ctx context.Context, sendEnableds []*types0.SendEnabled) { +func (m *MockBankKeeper) SetAllSendEnabled(ctx context.Context, sendEnableds []*types1.SendEnabled) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetAllSendEnabled", ctx, sendEnableds) } @@ -853,7 +924,7 @@ func (mr *MockBankKeeperMockRecorder) SetAllSendEnabled(ctx, sendEnableds any) * } // SetDenomMetaData mocks base method. -func (m *MockBankKeeper) SetDenomMetaData(ctx context.Context, denomMetaData types0.Metadata) { +func (m *MockBankKeeper) SetDenomMetaData(ctx context.Context, denomMetaData types1.Metadata) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetDenomMetaData", ctx, denomMetaData) } @@ -865,7 +936,7 @@ func (mr *MockBankKeeperMockRecorder) SetDenomMetaData(ctx, denomMetaData any) * } // SetParams mocks base method. -func (m *MockBankKeeper) SetParams(ctx context.Context, params types0.Params) error { +func (m *MockBankKeeper) SetParams(ctx context.Context, params types1.Params) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetParams", ctx, params) ret0, _ := ret[0].(error) @@ -891,10 +962,10 @@ func (mr *MockBankKeeperMockRecorder) SetSendEnabled(ctx, denom, value any) *gom } // SpendableBalanceByDenom mocks base method. -func (m *MockBankKeeper) SpendableBalanceByDenom(arg0 context.Context, arg1 *types0.QuerySpendableBalanceByDenomRequest) (*types0.QuerySpendableBalanceByDenomResponse, error) { +func (m *MockBankKeeper) SpendableBalanceByDenom(arg0 context.Context, arg1 *types1.QuerySpendableBalanceByDenomRequest) (*types1.QuerySpendableBalanceByDenomResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SpendableBalanceByDenom", arg0, arg1) - ret0, _ := ret[0].(*types0.QuerySpendableBalanceByDenomResponse) + ret0, _ := ret[0].(*types1.QuerySpendableBalanceByDenomResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -906,10 +977,10 @@ func (mr *MockBankKeeperMockRecorder) SpendableBalanceByDenom(arg0, arg1 any) *g } // SpendableBalances mocks base method. -func (m *MockBankKeeper) SpendableBalances(arg0 context.Context, arg1 *types0.QuerySpendableBalancesRequest) (*types0.QuerySpendableBalancesResponse, error) { +func (m *MockBankKeeper) SpendableBalances(arg0 context.Context, arg1 *types1.QuerySpendableBalancesRequest) (*types1.QuerySpendableBalancesResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SpendableBalances", arg0, arg1) - ret0, _ := ret[0].(*types0.QuerySpendableBalancesResponse) + ret0, _ := ret[0].(*types1.QuerySpendableBalancesResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -921,10 +992,10 @@ func (mr *MockBankKeeperMockRecorder) SpendableBalances(arg0, arg1 any) *gomock. } // SpendableCoin mocks base method. -func (m *MockBankKeeper) SpendableCoin(ctx context.Context, addr types.AccAddress, denom string) types.Coin { +func (m *MockBankKeeper) SpendableCoin(ctx context.Context, addr types0.AccAddress, denom string) types0.Coin { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SpendableCoin", ctx, addr, denom) - ret0, _ := ret[0].(types.Coin) + ret0, _ := ret[0].(types0.Coin) return ret0 } @@ -935,10 +1006,10 @@ func (mr *MockBankKeeperMockRecorder) SpendableCoin(ctx, addr, denom any) *gomoc } // SpendableCoins mocks base method. -func (m *MockBankKeeper) SpendableCoins(ctx context.Context, addr types.AccAddress) types.Coins { +func (m *MockBankKeeper) SpendableCoins(ctx context.Context, addr types0.AccAddress) types0.Coins { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) - ret0, _ := ret[0].(types.Coins) + ret0, _ := ret[0].(types0.Coins) return ret0 } @@ -949,10 +1020,10 @@ func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr any) *gomock.Call } // SupplyOf mocks base method. -func (m *MockBankKeeper) SupplyOf(arg0 context.Context, arg1 *types0.QuerySupplyOfRequest) (*types0.QuerySupplyOfResponse, error) { +func (m *MockBankKeeper) SupplyOf(arg0 context.Context, arg1 *types1.QuerySupplyOfRequest) (*types1.QuerySupplyOfResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SupplyOf", arg0, arg1) - ret0, _ := ret[0].(*types0.QuerySupplyOfResponse) + ret0, _ := ret[0].(*types1.QuerySupplyOfResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -964,10 +1035,10 @@ func (mr *MockBankKeeperMockRecorder) SupplyOf(arg0, arg1 any) *gomock.Call { } // TotalSupply mocks base method. -func (m *MockBankKeeper) TotalSupply(arg0 context.Context, arg1 *types0.QueryTotalSupplyRequest) (*types0.QueryTotalSupplyResponse, error) { +func (m *MockBankKeeper) TotalSupply(arg0 context.Context, arg1 *types1.QueryTotalSupplyRequest) (*types1.QueryTotalSupplyResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "TotalSupply", arg0, arg1) - ret0, _ := ret[0].(*types0.QueryTotalSupplyResponse) + ret0, _ := ret[0].(*types1.QueryTotalSupplyResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -979,7 +1050,7 @@ func (mr *MockBankKeeperMockRecorder) TotalSupply(arg0, arg1 any) *gomock.Call { } // UndelegateCoins mocks base method. -func (m *MockBankKeeper) UndelegateCoins(ctx context.Context, moduleAccAddr, delegatorAddr types.AccAddress, amt types.Coins) error { +func (m *MockBankKeeper) UndelegateCoins(ctx context.Context, moduleAccAddr, delegatorAddr types0.AccAddress, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UndelegateCoins", ctx, moduleAccAddr, delegatorAddr, amt) ret0, _ := ret[0].(error) @@ -993,7 +1064,7 @@ func (mr *MockBankKeeperMockRecorder) UndelegateCoins(ctx, moduleAccAddr, delega } // UndelegateCoinsFromModuleToAccount mocks base method. -func (m *MockBankKeeper) UndelegateCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types.AccAddress, amt types.Coins) error { +func (m *MockBankKeeper) UndelegateCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types0.AccAddress, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UndelegateCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt) ret0, _ := ret[0].(error) @@ -1007,7 +1078,7 @@ func (mr *MockBankKeeperMockRecorder) UndelegateCoinsFromModuleToAccount(ctx, se } // ValidateBalance mocks base method. -func (m *MockBankKeeper) ValidateBalance(ctx context.Context, addr types.AccAddress) error { +func (m *MockBankKeeper) ValidateBalance(ctx context.Context, addr types0.AccAddress) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidateBalance", ctx, addr) ret0, _ := ret[0].(error) @@ -1021,7 +1092,7 @@ func (mr *MockBankKeeperMockRecorder) ValidateBalance(ctx, addr any) *gomock.Cal } // WithMintCoinsRestriction mocks base method. -func (m *MockBankKeeper) WithMintCoinsRestriction(arg0 types0.MintingRestrictionFn) keeper.BaseKeeper { +func (m *MockBankKeeper) WithMintCoinsRestriction(arg0 types1.MintingRestrictionFn) keeper.BaseKeeper { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "WithMintCoinsRestriction", arg0) ret0, _ := ret[0].(keeper.BaseKeeper) @@ -1034,6 +1105,20 @@ func (mr *MockBankKeeperMockRecorder) WithMintCoinsRestriction(arg0 any) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithMintCoinsRestriction", reflect.TypeOf((*MockBankKeeper)(nil).WithMintCoinsRestriction), arg0) } +// WithObjStoreKey mocks base method. +func (m *MockBankKeeper) WithObjStoreKey(arg0 types.StoreKey) keeper.BaseKeeper { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithObjStoreKey", arg0) + ret0, _ := ret[0].(keeper.BaseKeeper) + return ret0 +} + +// WithObjStoreKey indicates an expected call of WithObjStoreKey. +func (mr *MockBankKeeperMockRecorder) WithObjStoreKey(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithObjStoreKey", reflect.TypeOf((*MockBankKeeper)(nil).WithObjStoreKey), arg0) +} + // MockStakingKeeper is a mock of StakingKeeper interface. type MockStakingKeeper struct { ctrl *gomock.Controller @@ -1074,7 +1159,7 @@ func (mr *MockStakingKeeperMockRecorder) BondDenom(ctx any) *gomock.Call { } // IterateBondedValidatorsByPower mocks base method. -func (m *MockStakingKeeper) IterateBondedValidatorsByPower(arg0 context.Context, arg1 func(int64, types1.ValidatorI) bool) error { +func (m *MockStakingKeeper) IterateBondedValidatorsByPower(arg0 context.Context, arg1 func(int64, types2.ValidatorI) bool) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IterateBondedValidatorsByPower", arg0, arg1) ret0, _ := ret[0].(error) @@ -1088,7 +1173,7 @@ func (mr *MockStakingKeeperMockRecorder) IterateBondedValidatorsByPower(arg0, ar } // IterateDelegations mocks base method. -func (m *MockStakingKeeper) IterateDelegations(ctx context.Context, delegator types.AccAddress, fn func(int64, types1.DelegationI) bool) error { +func (m *MockStakingKeeper) IterateDelegations(ctx context.Context, delegator types0.AccAddress, fn func(int64, types2.DelegationI) bool) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IterateDelegations", ctx, delegator, fn) ret0, _ := ret[0].(error) @@ -1169,7 +1254,7 @@ func (m *MockDistributionKeeper) EXPECT() *MockDistributionKeeperMockRecorder { } // FundCommunityPool mocks base method. -func (m *MockDistributionKeeper) FundCommunityPool(ctx context.Context, amount types.Coins, sender types.AccAddress) error { +func (m *MockDistributionKeeper) FundCommunityPool(ctx context.Context, amount types0.Coins, sender types0.AccAddress) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FundCommunityPool", ctx, amount, sender) ret0, _ := ret[0].(error) diff --git a/x/protocolpool/README.md b/x/protocolpool/README.md index d88b1ee1289a..6a695375a011 100644 --- a/x/protocolpool/README.md +++ b/x/protocolpool/README.md @@ -54,7 +54,7 @@ CommunityPoolSpend can be called by the module authority (default governance mod ### CreateContinuousFund CreateContinuousFund is a message used to initiate a continuous fund for a specific recipient. The proposed percentage of funds will be distributed only on withdraw request for the recipient. The fund distribution continues until expiry time is reached or continuous fund request is canceled. -NOTE: This feature is designed to work with the SDK's default bond denom. +NOTE: This feature is designed to work with the SDK's default bond denom. ```protobuf // CreateContinuousFund defines a method to distribute a percentage of funds to an address continuously. @@ -91,7 +91,7 @@ https://github.com/cosmos/cosmos-sdk/blob/release/v0.53.x/proto/cosmos/protocolp ```go func (k Keeper) FundCommunityPool(ctx context.Context, amount sdk.Coins, sender sdk.AccAddress) error { - return k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount) + return k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount) } ``` @@ -110,7 +110,7 @@ The message will fail under the following conditions: ```go func (k Keeper) DistributeFromCommunityPool(ctx context.Context, amount sdk.Coins, receiveAddr sdk.AccAddress) error { - return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, receiveAddr, amount) + return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, receiveAddr, amount) } ``` @@ -138,7 +138,7 @@ https://github.com/cosmos/cosmos-sdk/blob/release/v0.53.x/x/protocolpool/keeper/ ### MsgCancelContinuousFund -This message is used to cancel an existing continuous fund proposal for a specific recipient. Once canceled, the continuous fund will no longer distribute funds at each begin block, and the state object will be removed. +This message is used to cancel an existing continuous fund proposal for a specific recipient. Once canceled, the continuous fund will no longer distribute funds at each begin block, and the state object will be removed. ```protobuf reference https://github.com/cosmos/cosmos-sdk/blob/release/v0.53.x/x/protocolpool/proto/cosmos/protocolpool/v1/tx.proto#L136-L161