From c5e1ada87a94573b143cde251925c1c12014f5b2 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 11:29:46 +0800 Subject: [PATCH 01/12] Revert "build(deps): Bump github.com/cosmos/iavl from 1.0.1 to 1.1.1 in store (#19770)" This reverts commit ff30f6e0a4463f6de19678581e8d1114646cb1c7. --- store/CHANGELOG.md | 4 ---- store/cache/cache_test.go | 11 +++++------ store/go.mod | 16 ++++++++-------- store/go.sum | 36 ++++++++++++++++++------------------ store/iavl/store.go | 3 +-- store/iavl/store_test.go | 21 ++++++++++----------- store/iavl/tree.go | 5 +++-- store/iavl/tree_test.go | 3 +-- store/prefix/store_test.go | 3 +-- store/wrapper/wrapper.go | 34 ---------------------------------- 10 files changed, 47 insertions(+), 89 deletions(-) delete mode 100644 store/wrapper/wrapper.go diff --git a/store/CHANGELOG.md b/store/CHANGELOG.md index 95a55120ae45..b56a47dd9af1 100644 --- a/store/CHANGELOG.md +++ b/store/CHANGELOG.md @@ -31,10 +31,6 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## v1.1.0 (March 20, 2024) -### Improvements - -* [#19770](https://github.com/cosmos/cosmos-sdk/pull/19770) Upgrade IAVL to IAVL v1.1.1. - ## v1.0.2 (January 10, 2024) ### Bug Fixes diff --git a/store/cache/cache_test.go b/store/cache/cache_test.go index efbf22c8e18e..0340f7ecbd8d 100644 --- a/store/cache/cache_test.go +++ b/store/cache/cache_test.go @@ -13,11 +13,10 @@ import ( "cosmossdk.io/store/cachekv" iavlstore "cosmossdk.io/store/iavl" "cosmossdk.io/store/types" - "cosmossdk.io/store/wrapper" ) func TestGetOrSetStoreCache(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() mngr := cache.NewCommitKVStoreCacheManager(cache.DefaultCommitKVStoreCacheSize) sKey := types.NewKVStoreKey("test") @@ -30,7 +29,7 @@ func TestGetOrSetStoreCache(t *testing.T) { } func TestUnwrap(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() mngr := cache.NewCommitKVStoreCacheManager(cache.DefaultCommitKVStoreCacheSize) sKey := types.NewKVStoreKey("test") @@ -43,7 +42,7 @@ func TestUnwrap(t *testing.T) { } func TestStoreCache(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() mngr := cache.NewCommitKVStoreCacheManager(cache.DefaultCommitKVStoreCacheSize) sKey := types.NewKVStoreKey("test") @@ -69,7 +68,7 @@ func TestStoreCache(t *testing.T) { } func TestReset(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() mngr := cache.NewCommitKVStoreCacheManager(cache.DefaultCommitKVStoreCacheSize) sKey := types.NewKVStoreKey("test") @@ -89,7 +88,7 @@ func TestReset(t *testing.T) { } func TestCacheWrap(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() mngr := cache.NewCommitKVStoreCacheManager(cache.DefaultCommitKVStoreCacheSize) sKey := types.NewKVStoreKey("test") diff --git a/store/go.mod b/store/go.mod index f587701c2fdf..c9950b02ce56 100644 --- a/store/go.mod +++ b/store/go.mod @@ -9,7 +9,7 @@ require ( github.com/cometbft/cometbft v0.38.6 github.com/cosmos/cosmos-db v1.0.2 github.com/cosmos/gogoproto v1.4.11 - github.com/cosmos/iavl v1.1.1 + github.com/cosmos/iavl v1.0.1 github.com/cosmos/ics23/go v0.10.0 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 // indirect @@ -20,7 +20,7 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/stretchr/testify v1.8.4 github.com/tidwall/btree v1.7.0 - golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 google.golang.org/grpc v1.60.0 google.golang.org/protobuf v1.33.0 gotest.tools/v3 v3.5.1 @@ -61,17 +61,17 @@ require ( github.com/petermattis/goid v0.0.0-20221215004737-a150e88a970d // 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.19.0 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.50.0 // indirect - github.com/prometheus/procfs v0.13.0 // indirect + github.com/prometheus/common v0.47.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/zerolog v1.32.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/store/go.sum b/store/go.sum index a2692063e174..5b83ff072607 100644 --- a/store/go.sum +++ b/store/go.sum @@ -50,8 +50,8 @@ github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAK github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA= github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= -github.com/cosmos/iavl v1.1.1 h1:64nTi8s3gEoGqhA8TyAWFWfz7/pg0anKzHNSc1ETc7Q= -github.com/cosmos/iavl v1.1.1/go.mod h1:jLeUvm6bGT1YutCaL2fIar/8vGUE8cPZvh/gXEWDaDM= +github.com/cosmos/iavl v1.0.1 h1:D+mYbcRO2wptYzOM1Hxl9cpmmHU1ZEt9T2Wv5nZTeUw= +github.com/cosmos/iavl v1.0.1/go.mod h1:8xIUkgVvwvVrBu81scdPty+/Dx9GqwHnAvXz4cwF7RY= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -211,8 +211,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH 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.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= 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= @@ -220,13 +220,13 @@ github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZ github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= 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.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ= -github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ= +github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= +github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= 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.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= -github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -260,10 +260,10 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf 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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw= -golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= 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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -279,8 +279,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b 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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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= @@ -288,8 +288,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -320,8 +320,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/store/iavl/store.go b/store/iavl/store.go index e73dcd6c1fdd..c52feb89544d 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -16,7 +16,6 @@ import ( "cosmossdk.io/store/metrics" pruningtypes "cosmossdk.io/store/pruning/types" "cosmossdk.io/store/types" - "cosmossdk.io/store/wrapper" ) const ( @@ -50,7 +49,7 @@ func LoadStore(db dbm.DB, logger log.Logger, key types.StoreKey, id types.Commit // provided DB. An error is returned if the version fails to load, or if called with a positive // version on an empty tree. func LoadStoreWithInitialVersion(db dbm.DB, logger log.Logger, key types.StoreKey, id types.CommitID, initialVersion uint64, cacheSize int, disableFastNode bool, metrics metrics.StoreMetrics) (types.CommitKVStore, error) { - tree := iavl.NewMutableTree(wrapper.NewDBWrapper(db), cacheSize, disableFastNode, logger, iavl.InitialVersionOption(initialVersion)) + tree := iavl.NewMutableTree(db, cacheSize, disableFastNode, logger, iavl.InitialVersionOption(initialVersion)) isUpgradeable, err := tree.IsUpgradeable() if err != nil { diff --git a/store/iavl/store_test.go b/store/iavl/store_test.go index 46d9fa62371c..d9a47b21fb7b 100644 --- a/store/iavl/store_test.go +++ b/store/iavl/store_test.go @@ -17,7 +17,6 @@ import ( "cosmossdk.io/store/internal/kv" "cosmossdk.io/store/metrics" "cosmossdk.io/store/types" - "cosmossdk.io/store/wrapper" ) var ( @@ -38,7 +37,7 @@ func randBytes(numBytes int) []byte { // make a tree with data from above and save it func newAlohaTree(t *testing.T, db dbm.DB) (*iavl.MutableTree, types.CommitID) { t.Helper() - tree := iavl.NewMutableTree(wrapper.NewDBWrapper(db), cacheSize, false, log.NewNopLogger()) + tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) for k, v := range treeData { _, err := tree.Set([]byte(k), []byte(v)) @@ -286,7 +285,7 @@ func TestIAVLIterator(t *testing.T) { } func TestIAVLReverseIterator(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) @@ -320,7 +319,7 @@ func TestIAVLReverseIterator(t *testing.T) { } func TestIAVLPrefixIterator(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) iavlStore := UnsafeNewStore(tree) @@ -383,7 +382,7 @@ func TestIAVLPrefixIterator(t *testing.T) { } func TestIAVLReversePrefixIterator(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) iavlStore := UnsafeNewStore(tree) @@ -450,7 +449,7 @@ func nextVersion(iavl *Store) { } func TestIAVLNoPrune(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) iavlStore := UnsafeNewStore(tree) @@ -468,7 +467,7 @@ func TestIAVLNoPrune(t *testing.T) { } func TestIAVLStoreQuery(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) iavlStore := UnsafeNewStore(tree) @@ -580,7 +579,7 @@ func TestIAVLStoreQuery(t *testing.T) { func BenchmarkIAVLIteratorNext(b *testing.B) { b.ReportAllocs() - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() treeSize := 1000 tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) @@ -616,7 +615,7 @@ func TestSetInitialVersion(t *testing.T) { { "works with a mutable tree", func(db *dbm.MemDB) *Store { - tree := iavl.NewMutableTree(wrapper.NewDBWrapper(db), cacheSize, false, log.NewNopLogger()) + tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) store := UnsafeNewStore(tree) return store @@ -625,7 +624,7 @@ func TestSetInitialVersion(t *testing.T) { { "throws error on immutable tree", func(db *dbm.MemDB) *Store { - tree := iavl.NewMutableTree(wrapper.NewDBWrapper(db), cacheSize, false, log.NewNopLogger()) + tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) store := UnsafeNewStore(tree) _, version, err := store.tree.SaveVersion() require.NoError(t, err) @@ -670,7 +669,7 @@ func TestChangeSets(t *testing.T) { treeSize := 1000 treeVersion := int64(10) targetVersion := int64(6) - tree := iavl.NewMutableTree(wrapper.NewDBWrapper(db), cacheSize, false, log.NewNopLogger(), iavl.FlushThresholdOption(math.MaxInt)) + tree := iavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger(), iavl.FlushThresholdOption(math.MaxInt)) for j := int64(0); j < treeVersion; j++ { keys := [][]byte{} diff --git a/store/iavl/tree.go b/store/iavl/tree.go index 889fc1d5a07f..a44559784a86 100644 --- a/store/iavl/tree.go +++ b/store/iavl/tree.go @@ -4,7 +4,8 @@ import ( "fmt" "github.com/cosmos/iavl" - idb "github.com/cosmos/iavl/db" + + "cosmossdk.io/store/types" ) var ( @@ -31,7 +32,7 @@ type ( GetVersioned(key []byte, version int64) ([]byte, error) GetImmutable(version int64) (*iavl.ImmutableTree, error) SetInitialVersion(version uint64) - Iterator(start, end []byte, ascending bool) (idb.Iterator, error) + Iterator(start, end []byte, ascending bool) (types.Iterator, error) AvailableVersions() []int LoadVersionForOverwriting(targetVersion int64) error TraverseStateChanges(startVersion, endVersion int64, fn func(version int64, changeSet *iavl.ChangeSet) error) error diff --git a/store/iavl/tree_test.go b/store/iavl/tree_test.go index 243355e42e08..63a12a2e5d28 100644 --- a/store/iavl/tree_test.go +++ b/store/iavl/tree_test.go @@ -8,12 +8,11 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/log" - "cosmossdk.io/store/wrapper" ) func TestImmutableTreePanics(t *testing.T) { t.Parallel() - immTree := iavl.NewImmutableTree(wrapper.NewDBWrapper(dbm.NewMemDB()), 100, false, log.NewNopLogger()) + immTree := iavl.NewImmutableTree(dbm.NewMemDB(), 100, false, log.NewNopLogger()) it := &immutableTree{immTree} require.Panics(t, func() { _, err := it.Set([]byte{}, []byte{}) diff --git a/store/prefix/store_test.go b/store/prefix/store_test.go index 818733033e09..af2605818c52 100644 --- a/store/prefix/store_test.go +++ b/store/prefix/store_test.go @@ -14,7 +14,6 @@ import ( "cosmossdk.io/store/gaskv" "cosmossdk.io/store/iavl" "cosmossdk.io/store/types" - "cosmossdk.io/store/wrapper" ) // copied from iavl/store_test.go @@ -91,7 +90,7 @@ func testPrefixStore(t *testing.T, baseStore types.KVStore, prefix []byte) { } func TestIAVLStorePrefix(t *testing.T) { - db := wrapper.NewDBWrapper(dbm.NewMemDB()) + db := dbm.NewMemDB() tree := tiavl.NewMutableTree(db, cacheSize, false, log.NewNopLogger()) iavlStore := iavl.UnsafeNewStore(tree) diff --git a/store/wrapper/wrapper.go b/store/wrapper/wrapper.go deleted file mode 100644 index 5ccb4aef4698..000000000000 --- a/store/wrapper/wrapper.go +++ /dev/null @@ -1,34 +0,0 @@ -package wrapper - -import ( - dbm "github.com/cosmos/cosmos-db" - idb "github.com/cosmos/iavl/db" -) - -var _ idb.DB = &DBWrapper{} - -// DBwrapper is a simple wrapper of dbm.DB that implements the iavl.DB interface. -type DBWrapper struct { - dbm.DB -} - -// NewDBWrapper creates a new DBWrapper instance. -func NewDBWrapper(db dbm.DB) *DBWrapper { - return &DBWrapper{db} -} - -func (dbw *DBWrapper) NewBatch() idb.Batch { - return dbw.DB.NewBatch() -} - -func (dbw *DBWrapper) NewBatchWithSize(size int) idb.Batch { - return dbw.DB.NewBatchWithSize(size) -} - -func (dbw *DBWrapper) Iterator(start, end []byte) (idb.Iterator, error) { - return dbw.DB.Iterator(start, end) -} - -func (dbw *DBWrapper) ReverseIterator(start, end []byte) (idb.Iterator, error) { - return dbw.DB.ReverseIterator(start, end) -} From 8f4e8175027fb2f0a0f1dc466edf3ee95f638824 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 11:30:38 +0800 Subject: [PATCH 02/12] Problem: store package has dependency issues Solution: - revert the iavl bump, v1.0.2 is the version referenced by outer packages --- go.mod | 1 + go.sum | 2 -- simapp/go.mod | 1 + simapp/go.sum | 2 -- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 22ae5fe8ebed..cf4eb93e189a 100644 --- a/go.mod +++ b/go.mod @@ -169,6 +169,7 @@ require ( // Below are the long-lived replace of the Cosmos SDK replace ( + cosmossdk.io/store => ./store // use cosmos fork of keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 // dgrijalva/jwt-go is deprecated and doesn't receive security updates. diff --git a/go.sum b/go.sum index a7a2741a8ed3..2dcc7475ba29 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI= cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM= cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= -cosmossdk.io/store v1.0.2 h1:lSg5BTvJBHUDwswNNyeh4K/CbqiHER73VU4nDNb8uk0= -cosmossdk.io/store v1.0.2/go.mod h1:EFtENTqVTuWwitGW1VwaBct+yDagk7oG/axBMPH+FXs= cosmossdk.io/x/tx v0.13.1 h1:Mg+EMp67Pz+NukbJqYxuo8uRp7N/a9uR+oVS9pONtj8= cosmossdk.io/x/tx v0.13.1/go.mod h1:CBCU6fsRVz23QGFIQBb1DNX2DztJCf3jWyEkHY2nJQ0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= diff --git a/simapp/go.mod b/simapp/go.mod index c8dd9837ccc4..19c716a5db9c 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -207,6 +207,7 @@ require ( // Below are the long-lived replace of the SimApp replace ( + cosmossdk.io/store => ../store // use cosmos fork of keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 // Simapp always use the latest version of the cosmos-sdk diff --git a/simapp/go.sum b/simapp/go.sum index c2b293c55ffc..304effe160a3 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -200,8 +200,6 @@ cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI= cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM= cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= -cosmossdk.io/store v1.0.2 h1:lSg5BTvJBHUDwswNNyeh4K/CbqiHER73VU4nDNb8uk0= -cosmossdk.io/store v1.0.2/go.mod h1:EFtENTqVTuWwitGW1VwaBct+yDagk7oG/axBMPH+FXs= cosmossdk.io/tools/confix v0.1.1 h1:aexyRv9+y15veH3Qw16lxQwo+ki7r2I+g0yNTEFEQM8= cosmossdk.io/tools/confix v0.1.1/go.mod h1:nQVvP1tHsGXS83PonPVWJtSbddIqyjEw99L4M3rPJyQ= cosmossdk.io/x/circuit v0.1.0 h1:IAej8aRYeuOMritczqTlljbUVHq1E85CpBqaCTwYgXs= From 5cf108d6e5e301d23c971e8f042f0777de3cd46d Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 10:09:15 +0800 Subject: [PATCH 03/12] Support object store generic interface generic btree generic cachekv generic transient store support ObjStore changelog Update CHANGELOG.md Signed-off-by: yihuang object store key Apply review suggestions fix merge conflict fix snapshot revert dependers value validation seems still need to replace store object store in api gas accounting for object store --- CHANGELOG.md | 1 + baseapp/baseapp.go | 11 ++ runtime/store.go | 5 - server/mock/store.go | 4 - store/CHANGELOG.md | 1 + store/cachekv/internal/mergeiterator.go | 43 ++++--- store/cachekv/search_benchmark_test.go | 12 +- store/cachekv/store.go | 119 +++++++++++------- store/cachemulti/store.go | 45 +++++-- store/gaskv/store.go | 116 +++++++++++------ store/iavl/tree.go | 5 +- .../internal => internal/btree}/btree.go | 48 +++---- .../internal => internal/btree}/btree_test.go | 8 +- .../btree}/memiterator.go | 39 +++--- store/internal/btreeadaptor.go | 52 ++++++++ store/listenkv/store.go | 3 +- store/listenkv/store_test.go | 2 +- store/rootmulti/store.go | 110 ++++++++++------ store/rootmulti/store_test.go | 33 ++--- store/tracekv/store.go | 3 +- store/tracekv/store_test.go | 2 +- store/transient/store.go | 60 ++++++--- store/types/store.go | 91 +++++++++++--- store/types/validity.go | 11 +- types/context.go | 5 + 25 files changed, 564 insertions(+), 265 deletions(-) rename store/{cachekv/internal => internal/btree}/btree.go (63%) rename store/{cachekv/internal => internal/btree}/btree_test.go (98%) rename store/{cachekv/internal => internal/btree}/memiterator.go (64%) create mode 100644 store/internal/btreeadaptor.go diff --git a/CHANGELOG.md b/CHANGELOG.md index cb45d1180abe..330a5499f075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features * (baseapp) [#205](https://github.com/crypto-org-chain/cosmos-sdk/pull/205) Add `TxExecutor` baseapp option, add `TxIndex`/`TxCount`/`MsgIndex`/`BlockGasUsed` fields to `Context, to support tx parallel execution. +* (baseapp) [#205](https://github.com/crypto-org-chain/cosmos-sdk/pull/205) Support mount object store in baseapp, add `ObjectStore` api in context.. ## [Unreleased-Upstream] diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 998cd19fa25b..cbd28bcca977 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -344,6 +344,17 @@ func (app *BaseApp) MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey } } +// MountObjectStores mounts all in-memory KVStores with the BaseApp's internal +// commit multi-store. +func (app *BaseApp) MountObjectStores(keys map[string]*storetypes.ObjectStoreKey) { + skeys := maps.Keys(keys) + sort.Strings(skeys) + 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) { diff --git a/runtime/store.go b/runtime/store.go index 230f53c38c5d..3731295cf03d 100644 --- a/runtime/store.go +++ b/runtime/store.go @@ -2,7 +2,6 @@ package runtime import ( "context" - "io" dbm "github.com/cosmos/cosmos-db" @@ -104,10 +103,6 @@ func (kvStoreAdapter) CacheWrap() storetypes.CacheWrap { panic("unimplemented") } -func (kvStoreAdapter) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - panic("unimplemented") -} - func (kvStoreAdapter) GetStoreType() storetypes.StoreType { panic("unimplemented") } diff --git a/server/mock/store.go b/server/mock/store.go index 6e8a30364614..3b4ac8f584dd 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -178,10 +178,6 @@ func (kv kvStore) CacheWrap() storetypes.CacheWrap { panic("not implemented") } -func (kv kvStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - panic("not implemented") -} - func (kv kvStore) CacheWrapWithListeners(_ storetypes.StoreKey, _ []storetypes.MemoryListener) storetypes.CacheWrap { panic("not implemented") } diff --git a/store/CHANGELOG.md b/store/CHANGELOG.md index b56a47dd9af1..4066039f3f7e 100644 --- a/store/CHANGELOG.md +++ b/store/CHANGELOG.md @@ -28,6 +28,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements * [#207](https://github.com/crypto-org-chain/cosmos-sdk/pull/207) Remove api CacheWrapWithTrace. +* [#205](https://github.com/crypto-org-chain/cosmos-sdk/pull/205) Support object store. ## v1.1.0 (March 20, 2024) diff --git a/store/cachekv/internal/mergeiterator.go b/store/cachekv/internal/mergeiterator.go index 58e9497b3028..c9a1e7d3a677 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 { // 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) } } // Like bytes.Compare but opposite if not ascending. -func (iter *cacheMergeIterator) compare(a, b []byte) int { +func (iter *cacheMergeIterator[V]) compare(a, b []byte) int { if iter.ascending { return bytes.Compare(a, b) } @@ -175,9 +178,9 @@ func (iter *cacheMergeIterator) compare(a, b []byte) int { // If the current cache item is not a delete item, does nothing. // If `until` is nil, there is no limit, and cache may end up invalid. // CONTRACT: cache is valid. -func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { +func (iter *cacheMergeIterator[V]) skipCacheDeletes(until []byte) { for iter.cache.Valid() && - iter.cache.Value() == nil && + iter.isZero(iter.cache.Value()) && (until == nil || iter.compare(iter.cache.Key(), until) < 0) { iter.cache.Next() } @@ -186,7 +189,7 @@ func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { // 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 9c062c8ce619..9f7ff680db60 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -9,46 +9,70 @@ import ( "cosmossdk.io/math" "cosmossdk.io/store/cachekv/internal" + "cosmossdk.io/store/internal/btree" "cosmossdk.io/store/internal/conv" - "cosmossdk.io/store/internal/kv" "cosmossdk.io/store/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 +} + +// NewStore 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() @@ -65,10 +89,17 @@ func (store *Store) Get(key []byte) (value []byte) { return value } +func (store *GStore[V]) assertValidValue(value V) { + if store.isZero(value) { + panic("value is nil") + } + types.AssertValidValueLength(store.valueLen(value)) +} + // 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) + store.assertValidValue(value) store.mtx.Lock() defer store.mtx.Unlock() @@ -76,28 +107,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 @@ -110,22 +141,22 @@ func (store *Store) resetCaches() { delete(store.unsortedCache, key) } } - store.sortedCache = internal.NewBTree() + store.sortedCache = btree.NewBTree[V]() } // 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. @@ -150,7 +181,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 { @@ -160,24 +191,24 @@ 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) } //---------------------------------------- // 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() @@ -186,7 +217,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 { @@ -200,7 +231,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 { @@ -286,7 +317,7 @@ const ( const minSortSize = 1024 // Constructs a slice of dirty items, to use w/ memIterator. -func (store *Store) dirtyItems(start, end []byte) { +func (store *GStore[V]) dirtyItems(start, end []byte) { startStr, endStr := conv.UnsafeBytesToStr(start), conv.UnsafeBytesToStr(end) if end != nil && startStr > endStr { // Nothing to do here. @@ -294,7 +325,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 // whats in the subset we are concerned about. // If you are interleaving iterator calls with writes, this can easily become an @@ -306,7 +337,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) @@ -349,18 +380,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 { @@ -389,9 +420,9 @@ func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sort // 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 37fadb7790f2..5f6b4856052b 100644 --- a/store/cachemulti/store.go +++ b/store/cachemulti/store.go @@ -6,7 +6,6 @@ import ( dbm "github.com/cosmos/cosmos-db" - "cosmossdk.io/store/cachekv" "cosmossdk.io/store/dbadapter" "cosmossdk.io/store/tracekv" "cosmossdk.io/store/types" @@ -24,7 +23,7 @@ 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 + db types.CacheWrap stores map[types.StoreKey]types.CacheWrap keys map[string]types.StoreKey @@ -38,11 +37,11 @@ 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, + store types.CacheWrapper, stores map[types.StoreKey]types.CacheWrapper, keys map[string]types.StoreKey, traceWriter io.Writer, traceContext types.TraceContext, ) Store { cms := Store{ - db: cachekv.NewStore(store), + db: store.CacheWrap(), stores: make(map[types.StoreKey]types.CacheWrap, len(stores)), keys: keys, traceWriter: traceWriter, @@ -51,13 +50,16 @@ func NewFromKVStore( 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) + // 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) + } } - cms.stores[key] = cachekv.NewStore(store.(types.KVStore)) + cms.stores[key] = store.CacheWrap() } return cms @@ -155,11 +157,28 @@ func (cms Store) GetStore(key types.StoreKey) types.Store { return s.(types.Store) } -// GetKVStore returns an underlying KVStore by key. -func (cms Store) GetKVStore(key types.StoreKey) types.KVStore { +func (cms Store) getCacheWrap(key types.StoreKey) types.CacheWrap { store := cms.stores[key] if key == nil || store == nil { panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) } - return store.(types.KVStore) + return store +} + +// GetKVStore returns an underlying KVStore by key. +func (cms Store) GetKVStore(key types.StoreKey) types.KVStore { + store, ok := cms.getCacheWrap(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.getCacheWrap(key).(types.ObjKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not ObjKVStore", key)) + } + return store } diff --git a/store/gaskv/store.go b/store/gaskv/store.go index 59247d80008a..7aab63b0863a 100644 --- a/store/gaskv/store.go +++ b/store/gaskv/store.go @@ -1,65 +1,102 @@ package gaskv -import ( - "cosmossdk.io/store/types" -) +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 } // Implements Store. -func (gs *Store) GetStoreType() types.StoreType { +func (gs *GStore[V]) GetStoreType() types.StoreType { return gs.parent.GetStoreType() } // Implements KVStore. -func (gs *Store) Get(key []byte) (value []byte) { +func (gs *GStore[V]) Get(key []byte) (value V) { gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc) value = gs.parent.Get(key) // TODO overflow-safe math? gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasReadPerByteDesc) - gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc) + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(gs.valueLen(value)), types.GasReadPerByteDesc) return value } +func (gs *GStore[V]) assertValidValue(value V) { + if gs.isZero(value) { + panic("value is nil") + } + types.AssertValidValueLength(gs.valueLen(value)) +} + // Implements KVStore. -func (gs *Store) Set(key, value []byte) { +func (gs *GStore[V]) Set(key []byte, value V) { types.AssertValidKey(key) - types.AssertValidValue(value) + gs.assertValidValue(value) 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) } // Implements KVStore. -func (gs *Store) Has(key []byte) bool { +func (gs *GStore[V]) Has(key []byte) bool { gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc) return gs.parent.Has(key) } // Implements KVStore. -func (gs *Store) Delete(key []byte) { +func (gs *GStore[V]) Delete(key []byte) { // charge gas to prevent certain attack vectors even though space is being freed gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, types.GasDeleteDesc) gs.parent.Delete(key) @@ -68,7 +105,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) } @@ -76,94 +113,95 @@ 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) } // Implements KVStore. -func (gs *Store) CacheWrap() types.CacheWrap { +func (gs *GStore[V]) CacheWrap() types.CacheWrap { panic("cannot CacheWrap 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, } } // Implements Iterator. -func (gi *gasIterator) Domain() (start, end []byte) { +func (gi *gasIterator[V]) Domain() (start, end []byte) { return gi.parent.Domain() } // Implements Iterator. -func (gi *gasIterator) Valid() bool { +func (gi *gasIterator[V]) Valid() bool { return gi.parent.Valid() } // Next implements the Iterator interface. It seeks to the next key/value pair // in the iterator. It incurs a flat gas cost for seeking and a variable gas // cost based on the current value's length if the iterator is valid. -func (gi *gasIterator) Next() { +func (gi *gasIterator[V]) Next() { gi.consumeSeekGas() gi.parent.Next() } // Key implements the Iterator interface. It returns the current key and it does // not incur any gas cost. -func (gi *gasIterator) Key() (key []byte) { +func (gi *gasIterator[V]) Key() (key []byte) { key = gi.parent.Key() return key } // Value implements the Iterator interface. It returns the current value and it // does not incur any gas cost. -func (gi *gasIterator) Value() (value []byte) { - value = gi.parent.Value() - return value +func (gi *gasIterator[V]) Value() (value V) { + return gi.parent.Value() } // Implements Iterator. -func (gi *gasIterator) Close() error { +func (gi *gasIterator[V]) Close() error { return gi.parent.Close() } // Error delegates the Error call to the parent iterator. -func (gi *gasIterator) Error() error { +func (gi *gasIterator[V]) Error() error { return gi.parent.Error() } // consumeSeekGas consumes on each iteration step a flat gas cost and a variable gas cost // based on the current value's length. -func (gi *gasIterator) consumeSeekGas() { +func (gi *gasIterator[V]) consumeSeekGas() { if gi.Valid() { key := gi.Key() value := gi.Value() gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasValuePerByteDesc) - gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasValuePerByteDesc) + gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(gi.valueLen(value)), types.GasValuePerByteDesc) } gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, types.GasIterNextCostFlatDesc) } diff --git a/store/iavl/tree.go b/store/iavl/tree.go index a44559784a86..6819029f2642 100644 --- a/store/iavl/tree.go +++ b/store/iavl/tree.go @@ -3,9 +3,8 @@ package iavl import ( "fmt" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/iavl" - - "cosmossdk.io/store/types" ) var ( @@ -32,7 +31,7 @@ type ( GetVersioned(key []byte, version int64) ([]byte, error) GetImmutable(version int64) (*iavl.ImmutableTree, error) SetInitialVersion(version uint64) - Iterator(start, end []byte, ascending bool) (types.Iterator, error) + Iterator(start, end []byte, ascending bool) (dbm.Iterator, error) AvailableVersions() []int LoadVersionForOverwriting(targetVersion int64) error TraverseStateChanges(startVersion, endVersion int64, fn func(version int64, changeSet *iavl.ChangeSet) error) error 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..322e0f4152fe --- /dev/null +++ b/store/internal/btreeadaptor.go @@ -0,0 +1,52 @@ +package internal + +import ( + "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} +} + +// Hash 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) +} diff --git a/store/listenkv/store.go b/store/listenkv/store.go index 343be611b9f0..43d69fa663fc 100644 --- a/store/listenkv/store.go +++ b/store/listenkv/store.go @@ -1,6 +1,7 @@ package listenkv import ( + "cosmossdk.io/store/cachekv" "cosmossdk.io/store/types" ) @@ -130,5 +131,5 @@ 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) } diff --git a/store/listenkv/store_test.go b/store/listenkv/store_test.go index 1f9ca4b2942e..f688f5c4c0d9 100644 --- a/store/listenkv/store_test.go +++ b/store/listenkv/store_test.go @@ -272,5 +272,5 @@ func TestListenKVStoreGetStoreType(t *testing.T) { func TestListenKVStoreCacheWrap(t *testing.T) { store := newEmptyListenKVStore(nil) - require.Panics(t, func() { store.CacheWrap() }) + store.CacheWrap() } diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 39b6d35e5f43..2914e61b01d4 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -63,17 +63,18 @@ type Store struct { iavlCacheSize int iavlDisableFastNode bool storesParams map[types.StoreKey]storeParams - stores map[types.StoreKey]types.CommitKVStore - keysByName map[string]types.StoreKey - initialVersion int64 - removalMap map[types.StoreKey]bool - traceWriter io.Writer - traceContext types.TraceContext - traceContextMutex sync.Mutex - interBlockCache types.MultiStorePersistentCache - listeners map[types.StoreKey]*types.MemoryListener - metrics metrics.StoreMetrics - commitHeader cmtproto.Header + // 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 + traceWriter io.Writer + traceContext types.TraceContext + traceContextMutex sync.Mutex + interBlockCache types.MultiStorePersistentCache + listeners map[types.StoreKey]*types.MemoryListener + metrics metrics.StoreMetrics + commitHeader cmtproto.Header } var ( @@ -92,7 +93,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), @@ -155,12 +156,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. @@ -173,6 +168,18 @@ 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 @@ -221,7 +228,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)) @@ -549,11 +556,13 @@ func (rs *Store) CacheWrap() types.CacheWrap { 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 } @@ -569,7 +578,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 @@ -608,10 +617,12 @@ 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 @@ -627,7 +638,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())) } @@ -646,7 +657,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()) @@ -658,6 +672,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) @@ -709,7 +737,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 @@ -824,7 +852,7 @@ 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: @@ -988,7 +1016,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 { @@ -1037,12 +1065,20 @@ func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID return transient.NewStore(), nil case types.StoreTypeMemory: + _, ok := key.(*types.ObjectStoreKey) + if !ok { + return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String()) + } + if _, ok := key.(*types.MemoryStoreKey); !ok { return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String()) } return mem.NewStore(), nil + case types.StoreTypeObject: + return transient.NewObjStore(), nil + default: panic(fmt.Sprintf("unrecognized store type %v", params.typ)) } @@ -1054,7 +1090,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{ @@ -1172,7 +1208,7 @@ func GetLatestVersion(db dbm.DB) int64 { } // Commits each store and returns a new commitInfo. -func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore, removalMap map[types.StoreKey]bool) *types.CommitInfo { +func commitStores(version int64, storeMap map[types.StoreKey]types.CommitStore, removalMap map[types.StoreKey]bool) *types.CommitInfo { storeInfos := make([]types.StoreInfo, 0, len(storeMap)) storeKeys := keysFromStoreKeyMap(storeMap) @@ -1192,7 +1228,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 983fc411f3da..34a3d8b1f626 100644 --- a/store/rootmulti/store_test.go +++ b/store/rootmulti/store_test.go @@ -786,6 +786,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 { @@ -858,7 +859,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() @@ -913,35 +914,39 @@ 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.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 } @@ -972,7 +977,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 7df8b0f97ba7..9fbc1bb4a3ef 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -6,6 +6,7 @@ import ( "io" "cosmossdk.io/errors" + "cosmossdk.io/store/cachekv" "cosmossdk.io/store/types" ) @@ -164,7 +165,7 @@ func (tkv *Store) GetStoreType() types.StoreType { // CacheWrap implements the KVStore interface. It panics because a Store // cannot be branched. func (tkv *Store) CacheWrap() types.CacheWrap { - panic("cannot CacheWrap a TraceKVStore") + return cachekv.NewStore(tkv) } // writeOperation writes a KVStore operation to the underlying io.Writer as diff --git a/store/tracekv/store_test.go b/store/tracekv/store_test.go index d276accd530a..00e4406c7574 100644 --- a/store/tracekv/store_test.go +++ b/store/tracekv/store_test.go @@ -283,5 +283,5 @@ func TestTraceKVStoreGetStoreType(t *testing.T) { func TestTraceKVStoreCacheWrap(t *testing.T) { store := newEmptyTraceKVStore(nil) - require.Panics(t, func() { store.CacheWrap() }) + store.CacheWrap() } diff --git a/store/transient/store.go b/store/transient/store.go index 6f393279f571..3d744d99ae9c 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,70 @@ 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 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] } -// 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 }, nil)} +} + +func (*ObjStore) GetStoreType() types.StoreType { + return types.StoreTypeObject } // Implements CommitStore // Commit cleans up Store. -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) } // Implements CommitStore -func (ts *Store) LastCommitID() types.CommitID { +func (ts *GStore[V]) LastCommitID() types.CommitID { return types.CommitID{} } -func (ts *Store) WorkingHash() []byte { +func (ts *GStore[V]) WorkingHash() []byte { return []byte{} } - -// Implements Store. -func (ts *Store) GetStoreType() types.StoreType { - return types.StoreTypeTransient -} diff --git a/store/types/store.go b/store/types/store.go index f37e9860baaa..8de49284105a 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -136,6 +136,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 @@ -229,25 +230,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. @@ -255,18 +256,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 []byte, 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 @@ -292,11 +329,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 } type CacheWrapper interface { @@ -326,6 +362,7 @@ const ( StoreTypeMemory StoreTypeSMT StoreTypePersistent + StoreTypeObject ) func (st StoreType) String() string { @@ -350,6 +387,9 @@ func (st StoreType) String() string { case StoreTypePersistent: return "StoreTypePersistent" + + case StoreTypeObject: + return "StoreTypeObject" } return "unknown store type" @@ -429,6 +469,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 +} + +// Constructs new ObjectStoreKey +// Must return a pointer according to the ocap principle +func NewObjectStoreKey(name string) *ObjectStoreKey { + return &ObjectStoreKey{ + name: name, + } +} + +// Implements StoreKey +func (key *ObjectStoreKey) Name() string { + return key.name +} + +// Implements StoreKey +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 diff --git a/store/types/validity.go b/store/types/validity.go index a1fbaba999c7..a1363ff28b1d 100644 --- a/store/types/validity.go +++ b/store/types/validity.go @@ -1,5 +1,7 @@ package types +import "errors" + var ( // 128K - 1 MaxKeyLength = (1 << 17) - 1 @@ -22,7 +24,12 @@ func AssertValidValue(value []byte) { if value == nil { panic("value is nil") } - if len(value) > MaxValueLength { - panic("value is too large") + AssertValidValueLength(len(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/types/context.go b/types/context.go index 36c5ccdc8094..248b5af95793 100644 --- a/types/context.go +++ b/types/context.go @@ -377,6 +377,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) OjectStore(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 From 9c4a6265294513bfd2225f0ba571ed84857ceafa Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 11:44:19 +0800 Subject: [PATCH 04/12] fix interface --- runtime/store.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/runtime/store.go b/runtime/store.go index 3731295cf03d..aee2487b5c9d 100644 --- a/runtime/store.go +++ b/runtime/store.go @@ -3,8 +3,6 @@ package runtime import ( "context" - dbm "github.com/cosmos/cosmos-db" - "cosmossdk.io/core/store" storetypes "cosmossdk.io/store/types" @@ -137,7 +135,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) @@ -145,7 +143,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) From 5c6f18d6bbd9e9a44ac0eb71e178016008e7445f Mon Sep 17 00:00:00 2001 From: yihuang Date: Mon, 25 Mar 2024 11:47:06 +0800 Subject: [PATCH 05/12] Update baseapp/baseapp.go Signed-off-by: yihuang --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index cbd28bcca977..eeb8a414d643 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -344,7 +344,7 @@ func (app *BaseApp) MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey } } -// MountObjectStores mounts all in-memory KVStores with the BaseApp's internal +// MountObjectStores mounts all transient object stores with the BaseApp's internal // commit multi-store. func (app *BaseApp) MountObjectStores(keys map[string]*storetypes.ObjectStoreKey) { skeys := maps.Keys(keys) From 9a25b090ec06f3dfd2a11d2d5411d47e642add66 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 11:54:26 +0800 Subject: [PATCH 06/12] fix obj store --- store/transient/store.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/store/transient/store.go b/store/transient/store.go index 3d744d99ae9c..53332e9f33c1 100644 --- a/store/transient/store.go +++ b/store/transient/store.go @@ -47,7 +47,10 @@ type ObjStore struct { } func NewObjStore() *ObjStore { - return &ObjStore{*NewGStore(func(v any) bool { return v == nil }, nil)} + 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 { From 8592be88990558110f68035b978ac1f21e35cfa1 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 13:26:23 +0800 Subject: [PATCH 07/12] fix test --- server/mock/store.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/mock/store.go b/server/mock/store.go index 3b4ac8f584dd..c6829bb18b6b 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") } From f3421db845cefdb3d1918f6f07a860279eeb0f7b Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 13:34:20 +0800 Subject: [PATCH 08/12] bump go-version in ci --- .github/workflows/test.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24ca58bfb2a4..8236052e83cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -291,7 +291,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: core/go.sum @@ -322,7 +322,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: depinject/go.sum @@ -351,7 +351,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: errors/go.sum @@ -382,7 +382,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: math/go.sum @@ -480,7 +480,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: orm/go.sum @@ -604,7 +604,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: store/go.sum @@ -635,7 +635,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true cache: true cache-dependency-path: log/go.sum From a9b39e922ce5df06ad67b3abbb8fba2693fdfbea Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 13:39:19 +0800 Subject: [PATCH 09/12] fix e2e tests --- tests/go.mod | 1 + tests/go.sum | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/go.mod b/tests/go.mod index c5e52f2bf018..4dd82ea6c82e 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -207,6 +207,7 @@ require ( replace ( // We always want to test against the latest version of the simapp. cosmossdk.io/simapp => ../simapp + cosmossdk.io/store => ../store github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 // We always want to test against the latest version of the SDK. github.com/cosmos/cosmos-sdk => ../. diff --git a/tests/go.sum b/tests/go.sum index e97a332d6581..9853a5bdfd93 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -200,8 +200,6 @@ cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI= cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM= cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE= cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k= -cosmossdk.io/store v1.0.2 h1:lSg5BTvJBHUDwswNNyeh4K/CbqiHER73VU4nDNb8uk0= -cosmossdk.io/store v1.0.2/go.mod h1:EFtENTqVTuWwitGW1VwaBct+yDagk7oG/axBMPH+FXs= cosmossdk.io/x/circuit v0.1.0 h1:IAej8aRYeuOMritczqTlljbUVHq1E85CpBqaCTwYgXs= cosmossdk.io/x/circuit v0.1.0/go.mod h1:YDzblVE8+E+urPYQq5kq5foRY/IzhXovSYXb4nwd39w= cosmossdk.io/x/evidence v0.1.0 h1:J6OEyDl1rbykksdGynzPKG5R/zm6TacwW2fbLTW4nCk= From 34a147346fadea97a6ec55095c0f365dd72177e6 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 13:39:55 +0800 Subject: [PATCH 10/12] gofump --- store/rootmulti/store.go | 1 - 1 file changed, 1 deletion(-) diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 2914e61b01d4..7908ac118303 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -174,7 +174,6 @@ 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 From 47c10650868377e5f80797635a1594e776313d42 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 13:46:08 +0800 Subject: [PATCH 11/12] fix lint --- store/types/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/types/store.go b/store/types/store.go index 8de49284105a..2def13f3f2d3 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -270,7 +270,7 @@ type GKVStore[V any] interface { type GIterator[V any] interface { // Domain returns the start (inclusive) and end (exclusive) limits of the iterator. // CONTRACT: start, end readonly []byte - Domain() (start []byte, end []byte) + Domain() (start, end []byte) // Valid returns whether the current iterator is valid. Once invalid, the Iterator remains // invalid forever. From 7b660023f18076e17118e71c60731b5834eb8f74 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Mon, 25 Mar 2024 14:03:32 +0800 Subject: [PATCH 12/12] fix test --- x/group/internal/orm/testsupport.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/group/internal/orm/testsupport.go b/x/group/internal/orm/testsupport.go index b4fe3d0354ee..b03fcec42ba9 100644 --- a/x/group/internal/orm/testsupport.go +++ b/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 {