From 810403735a544c22e9547b74b6989d73c0fb3067 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Tue, 10 Jan 2023 23:48:27 -0500 Subject: [PATCH 01/76] add cachekv btree --- core/state/types/state_test.go | 2 +- core/state/utils/utils_test.go | 2 +- store/cachekv/internal/btree.go | 106 ++++++++++++++ store/cachekv/internal/btree_test.go | 203 ++++++++++++++++++++++++++ store/cachekv/internal/memiterator.go | 134 +++++++++++++++++ store/journal/manager_test.go | 2 +- 6 files changed, 446 insertions(+), 3 deletions(-) create mode 100644 store/cachekv/internal/btree.go create mode 100644 store/cachekv/internal/btree_test.go create mode 100644 store/cachekv/internal/memiterator.go diff --git a/core/state/types/state_test.go b/core/state/types/state_test.go index 021e996e8..63f1dcd18 100644 --- a/core/state/types/state_test.go +++ b/core/state/types/state_test.go @@ -23,7 +23,7 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("State Test", func() { +var _ = Describe("core/state/types", func() { var state types.State key := common.Hash{}.Bytes() value := common.Hash{}.Bytes() diff --git a/core/state/utils/utils_test.go b/core/state/utils/utils_test.go index 304c476fb..6a128f012 100644 --- a/core/state/utils/utils_test.go +++ b/core/state/utils/utils_test.go @@ -24,7 +24,7 @@ import ( func TestUtilsPkg(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "State Suite") + RunSpecs(t, "core/state/utils") } var _ = Describe("UnsafeStrToBytes", func() { diff --git a/store/cachekv/internal/btree.go b/store/cachekv/internal/btree.go new file mode 100644 index 000000000..496f48f52 --- /dev/null +++ b/store/cachekv/internal/btree.go @@ -0,0 +1,106 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package internal + +import ( + "bytes" + "errors" + + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/tidwall/btree" +) + +const ( + // The approximate number of items and children per B-tree node. Tuned with benchmarks. + // copied from memdb. + bTreeDegree = 32 +) + +var ErrKeyEmpty = errors.New("key cannot be empty") + +// BTree implements the sorted cache for cachekv store, +// we don't use MemDB here because cachekv is used extensively in sdk core path, +// 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] +} + +// NewBTree creates a wrapper around `btree.BTreeG`. +func NewBTree() BTree { + return BTree{ + tree: btree.NewBTreeGOptions(byKeys, btree.Options{ + Degree: bTreeDegree, + NoLocks: false, + }), + } +} + +func (bt BTree) Set(key, value []byte) { + bt.tree.Set(newItem(key, value)) +} + +func (bt BTree) Get(key []byte) []byte { + i, found := bt.tree.Get(newItem(key, nil)) + if !found { + return nil + } + return i.value +} + +func (bt BTree) Delete(key []byte) { + bt.tree.Delete(newItem(key, nil)) +} + +//nolint:nolintlint,ireturn +func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, ErrKeyEmpty + } + return newMemIterator(start, end, bt, true), nil +} + +//nolint:nolintlint,ireturn +func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, ErrKeyEmpty + } + return newMemIterator(start, end, bt, false), nil +} + +// 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{ + tree: bt.tree.Copy(), + } +} + +// item is a btree item with byte slices as keys and values. +type item struct { + key []byte + value []byte +} + +// byKeys compares the items by key. +func byKeys(a, b item) 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} +} diff --git a/store/cachekv/internal/btree_test.go b/store/cachekv/internal/btree_test.go new file mode 100644 index 000000000..f4ecc35e2 --- /dev/null +++ b/store/cachekv/internal/btree_test.go @@ -0,0 +1,203 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package internal_test + +import ( + "testing" + + "github.com/berachain/stargazer/store/cachekv/internal" + "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "cachekv/internal") +} + +var _ = Describe("GetSetDelete", func() { + var db internal.BTree + + BeforeEach(func() { + db = internal.NewBTree() + }) + + It("should return nil for a nonexistent key", func() { + value := db.Get([]byte("a")) + Expect(value).To(BeNil()) + }) + + It("should set and get a value", func() { + db.Set([]byte("a"), []byte{0x01}) + db.Set([]byte("b"), []byte{0x02}) + + value := db.Get([]byte("a")) + Expect(value).To(Equal([]byte{0x01})) + + value = db.Get([]byte("b")) + Expect(value).To(Equal([]byte{0x02})) + }) + + It("should delete a value", func() { + db.Set([]byte("a"), []byte{0x01}) + db.Set([]byte("b"), []byte{0x02}) + + db.Delete([]byte("a")) + + value := db.Get([]byte("a")) + Expect(value).To(BeNil()) + + db.Delete([]byte("b")) + + value = db.Get([]byte("b")) + Expect(value).To(BeNil()) + }) +}) +var _ = Describe("DBIterator", func() { + var db internal.BTree + + BeforeEach(func() { + db = internal.NewBTree() + + for i := 0; i < 10; i++ { + if i != 6 { // but skip 6. + db.Set(int642Bytes(int64(i)), []byte{}) + } + } + }) + + It("should error with blank iterator keys", func() { + _, err := db.ReverseIterator([]byte{}, nil) + Expect(err).To(Equal(internal.ErrKeyEmpty)) + _, err = db.ReverseIterator(nil, []byte{}) + Expect(err).To(Equal(internal.ErrKeyEmpty)) + }) + + It("should iterate forward", func() { + itr, err := db.Iterator(nil, nil) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator") + }) + + It("should iterate reverse", func() { + ritr, err := db.ReverseIterator(nil, nil) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator") + }) + + It("should iterate to 0", func() { + itr, err := db.Iterator(nil, int642Bytes(0)) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(itr, []int64(nil), "forward iterator to 0") + }) + + It("should iterate from 10 (ex)", func() { + ritr, err := db.ReverseIterator(int642Bytes(10), nil) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(ritr, []int64(nil), "reverse iterator from 10 (ex)") + }) + + It("should iterate from 0", func() { + itr, err := db.Iterator(int642Bytes(0), nil) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0") + }) + + It("should iterate from 1", func() { + itr, err := db.Iterator(int642Bytes(1), nil) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(itr, []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1") + }) + + It("should iterate reverse from 10 (ex)", func() { + ritr, err := db.ReverseIterator(nil, int642Bytes(10)) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)") + }) + + It("should iterate reverse from 9 (ex)", func() { + ritr, err := db.ReverseIterator(nil, int642Bytes(9)) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(ritr, []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") + }) + + It("should iterate reverse from 8 (ex)", func() { + ritr, err := db.ReverseIterator(nil, int642Bytes(8)) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(ritr, []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)") + }) + + It("should iterate forward from 5 to 6", func() { + itr, err := db.Iterator(int642Bytes(5), int642Bytes(6)) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(itr, []int64{5}, "forward iterator from 5 to 6") + }) + + It("should iterate forward from 5 to 7", func() { + itr, err := db.Iterator(int642Bytes(5), int642Bytes(7)) + Expect(err).ToNot(HaveOccurred()) + verifyIterator(itr, []int64{5}, "forward iterator from 5 to 7") + }) + + It("should reverse iterator from 5 (ex) to 4", func() { + ritr, err := db.ReverseIterator(int642Bytes(4), int642Bytes(5)) + Expect(err).Should(BeNil()) + verifyIterator(ritr, []int64{4}, "reverse iterator from 5 (ex) to 4") + }) + + It("should reverse iterator from 6 (ex) to 4", func() { + ritr, err := db.ReverseIterator(int642Bytes(4), int642Bytes(6)) + Expect(err).Should(BeNil()) + verifyIterator(ritr, []int64{5, 4}, "reverse iterator from 6 (ex) to 4") + }) + + It("should return a reverse iterator from 10 (ex)", func() { + ritr, err := db.ReverseIterator(nil, int642Bytes(10)) + Expect(err).NotTo(HaveOccurred()) + verifyIterator(ritr, []int64(nil), "reverse iterator from 10 (ex)") + }) + + It("should return a reverse iterator from 9 (ex)", func() { + ritr, err := db.ReverseIterator(nil, int642Bytes(9)) + Expect(err).NotTo(HaveOccurred()) + verifyIterator(ritr, []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") + }) + +}) + +func verifyIterator(itr types.Iterator, expected []int64, _ string) { + i := 0 + t := GinkgoT() + for itr.Valid() { + key := itr.Key() + require.Equal(t, expected[i], bytes2Int64(key), "iterator: %d mismatches", i) + itr.Next() + i++ + } + require.Equal(t, i, len(expected), "expected to have fully iterated over all the elements in iter") + require.NoError(t, itr.Close()) +} + +func int642Bytes(i int64) []byte { + return sdk.Uint64ToBigEndian(uint64(i)) +} + +func bytes2Int64(buf []byte) int64 { + return int64(sdk.BigEndianToUint64(buf)) +} diff --git a/store/cachekv/internal/memiterator.go b/store/cachekv/internal/memiterator.go new file mode 100644 index 000000000..53898b415 --- /dev/null +++ b/store/cachekv/internal/memiterator.go @@ -0,0 +1,134 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package internal + +import ( + "bytes" + "errors" + + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/tidwall/btree" +) + +var _ types.Iterator = (*memIterator)(nil) + +// memIterator iterates over iterKVCache items. +// if value is nil, means it was deleted. +// Implements Iterator. +type memIterator struct { + iter btree.GenericIter[item] + + start []byte + end []byte + ascending bool + valid bool +} + +func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { + iter := items.tree.Iter() + var valid bool + //nolint:nestif // from sdk. + if ascending { + if start != nil { + valid = iter.Seek(newItem(start, nil)) + } else { + valid = iter.First() + } + } else { + if end != nil { + valid = iter.Seek(newItem(end, nil)) + if !valid { + valid = iter.Last() + } else { + // end is exclusive + valid = iter.Prev() + } + } else { + valid = iter.Last() + } + } + + mi := &memIterator{ + iter: iter, + start: start, + end: end, + ascending: ascending, + valid: valid, + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } + + return mi +} + +func (mi *memIterator) Domain() ([]byte, []byte) { + return mi.start, mi.end +} + +func (mi *memIterator) Close() error { + mi.iter.Release() + return nil +} + +func (mi *memIterator) Error() error { + if !mi.Valid() { + return errors.New("invalid memIterator") + } + return nil +} + +func (mi *memIterator) Valid() bool { + return mi.valid +} + +func (mi *memIterator) Next() { + mi.assertValid() + + if mi.ascending { + mi.valid = mi.iter.Next() + } else { + mi.valid = mi.iter.Prev() + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } +} + +func (mi *memIterator) keyInRange(key []byte) bool { + if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 { + return false + } + if !mi.ascending && mi.start != nil && bytes.Compare(key, mi.start) < 0 { + return false + } + return true +} + +func (mi *memIterator) Key() []byte { + return mi.iter.Item().key +} + +func (mi *memIterator) Value() []byte { + return mi.iter.Item().value +} + +func (mi *memIterator) assertValid() { + if err := mi.Error(); err != nil { + panic(err) + } +} diff --git a/store/journal/manager_test.go b/store/journal/manager_test.go index 82f81a1b5..d500a3cd0 100644 --- a/store/journal/manager_test.go +++ b/store/journal/manager_test.go @@ -28,7 +28,7 @@ import ( func TestJournalManager(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Journal Manager Test Suite") + RunSpecs(t, "store/journal") } var _ = Describe("Journal", func() { From d5e0489cf23cf6324276fd959b2f00967d47afe6 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Tue, 10 Jan 2023 23:56:10 -0500 Subject: [PATCH 02/76] bing bong --- store/cachekv/internal/btree_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/store/cachekv/internal/btree_test.go b/store/cachekv/internal/btree_test.go index f4ecc35e2..1d6d98196 100644 --- a/store/cachekv/internal/btree_test.go +++ b/store/cachekv/internal/btree_test.go @@ -167,12 +167,6 @@ var _ = Describe("DBIterator", func() { verifyIterator(ritr, []int64{5, 4}, "reverse iterator from 6 (ex) to 4") }) - It("should return a reverse iterator from 10 (ex)", func() { - ritr, err := db.ReverseIterator(nil, int642Bytes(10)) - Expect(err).NotTo(HaveOccurred()) - verifyIterator(ritr, []int64(nil), "reverse iterator from 10 (ex)") - }) - It("should return a reverse iterator from 9 (ex)", func() { ritr, err := db.ReverseIterator(nil, int642Bytes(9)) Expect(err).NotTo(HaveOccurred()) From 8afe9109ea4f9cc34e6c501c4269cee19316bf3d Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 10:45:39 -0500 Subject: [PATCH 03/76] stack --- core/state/types/state_test.go | 2 +- core/state/types/storage_test.go | 2 +- lib/README.md | 3 + {common => lib/common}/address.go | 0 {common => lib/common}/imported.go | 0 lib/ds/ds_test.go | 27 +++++++ lib/ds/stack.go | 95 +++++++++++++++++++++++++ lib/ds/stack_test.go | 83 +++++++++++++++++++++ {core/state => lib}/utils/utils.go | 0 {core/state => lib}/utils/utils_test.go | 2 +- store/journal/manager.go | 69 +++++------------- store/journal/manager_test.go | 22 +++--- 12 files changed, 239 insertions(+), 66 deletions(-) create mode 100644 lib/README.md rename {common => lib/common}/address.go (100%) rename {common => lib/common}/imported.go (100%) create mode 100644 lib/ds/ds_test.go create mode 100644 lib/ds/stack.go create mode 100644 lib/ds/stack_test.go rename {core/state => lib}/utils/utils.go (100%) rename {core/state => lib}/utils/utils_test.go (97%) diff --git a/core/state/types/state_test.go b/core/state/types/state_test.go index 63f1dcd18..047c1be17 100644 --- a/core/state/types/state_test.go +++ b/core/state/types/state_test.go @@ -17,8 +17,8 @@ package types_test import ( "math/rand" - "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state/types" + "github.com/berachain/stargazer/lib/common" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/core/state/types/storage_test.go b/core/state/types/storage_test.go index b636ef574..3744592dc 100644 --- a/core/state/types/storage_test.go +++ b/core/state/types/storage_test.go @@ -15,8 +15,8 @@ package types_test import ( - "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state/types" + "github.com/berachain/stargazer/lib/common" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 000000000..6c7c77831 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,3 @@ +# stargazer-lib + +## todo: move this folder to another repo later on. \ No newline at end of file diff --git a/common/address.go b/lib/common/address.go similarity index 100% rename from common/address.go rename to lib/common/address.go diff --git a/common/imported.go b/lib/common/imported.go similarity index 100% rename from common/imported.go rename to lib/common/imported.go diff --git a/lib/ds/ds_test.go b/lib/ds/ds_test.go new file mode 100644 index 000000000..b07eab4b3 --- /dev/null +++ b/lib/ds/ds_test.go @@ -0,0 +1,27 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ds_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestDS(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "lib/ds") +} diff --git a/lib/ds/stack.go b/lib/ds/stack.go new file mode 100644 index 000000000..c5036c6ac --- /dev/null +++ b/lib/ds/stack.go @@ -0,0 +1,95 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LIiNSE for liinsing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVIiS; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENi OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//nolint:ireturn // StackI uses generics. +package ds + +// `StackI` is an interfai that defines the methods that a items Stack must implement. +// items Stacks support holding cache entries and reverting to a irtain index. +type StackI[Item any] interface { + // `Peek` returns the Item at the top of the stack + Peek() Item + + // `PeekAt` returns the Item at the given index. + PeekAt(i int) Item + + // `Push` adds a new Item to the top of the stack. The Size method returns the current + // number of entries in the items. + Push(i Item) + + // `Pop` returns the Item at the top of the stack and removes it from the stack. + Pop() Item + + // `RevertToSize` reverts and discards all items entries after and including the given size. + PopToSize(newSize int) + + // `Size` returns the current number of entries in the items. + Size() int +} + +// Compile-time check to ensure `Stack` implements `StackI`. +var _ StackI[any] = (*Stack[any])(nil) + +// `Stack` is a struct that holds a slii of Item instanis. +type Stack[Item any] struct { + items []Item +} + +// `NewStack` creates and returns a new Stack instani with an empty items. +func NewStack[Item any]() *Stack[Item] { + return &Stack[Item]{ + items: make([]Item, 0), + } +} + +// `Push` implements `StackI`. +func (s *Stack[Item]) Push(i Item) { + s.items = append(s.items, i) +} + +// `Size` implements `StackI`. +func (s *Stack[Item]) Size() int { + return len(s.items) +} + +// `Peek` returns nil if index `i` is invalid. +// +// `Peek` implements `StackI`. +func (s *Stack[Item]) Peek() Item { + return s.items[len(s.items)-1] +} + +// `Peek` returns nil if index `i` is invalid. +// +// `Peek` implements `StackI`. +func (s *Stack[Item]) PeekAt(i int) Item { + return s.items[i] +} + +// `Pop` returns nil if index `i` is invalid. +// +// `Pop` implements `StackI`. +func (s *Stack[Item]) Pop() Item { + newLen := len(s.items) - 1 + item := s.items[newLen] + s.items = s.items[:newLen] // exclusive to chop off last item + return item +} + +// `PopToSize` will panic if `newSize` is greater than the current size. +// +// `PopToSize` implements `StackI`. +func (s *Stack[Item]) PopToSize(newSize int) { + s.items = s.items[:newSize] // todo: help the GC? +} diff --git a/lib/ds/stack_test.go b/lib/ds/stack_test.go new file mode 100644 index 000000000..921d80e2e --- /dev/null +++ b/lib/ds/stack_test.go @@ -0,0 +1,83 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ds_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/berachain/stargazer/lib/ds" +) + +var _ = Describe("Stack", func() { + var stack *ds.Stack[int] + + BeforeEach(func() { + stack = ds.NewStack[int]() + }) + + It("should be empty", func() { + Expect(stack.Size()).To(BeZero()) + }) + + When("pushing an element", func() { + BeforeEach(func() { + stack.Push(1) + }) + + It("should not be empty", func() { + Expect(stack.Size()).To(Equal(1)) + }) + + It("should return the correct element", func() { + Expect(stack.Peek()).To(Equal(1)) + }) + + It("should return the correct element", func() { + Expect(stack.PeekAt(0)).To(Equal(1)) + + It("should return the correct element", func() { + Expect(stack.Pop()).To(Equal(1)) + }) + + When("popping an element", func() { + BeforeEach(func() { + stack.Pop() + }) + + It("should be empty", func() { + Expect(stack.Size()).To(BeZero()) + }) + }) + }) + + When("pushing more elements", func() { + BeforeEach(func() { + stack.Push(2) + stack.Push(3) + }) + + It("should return the correct element", func() { + Expect(stack.Peek()).To(Equal(3)) + Expect(stack.PeekAt(2)).To(Equal(3)) + Expect(stack.PeekAt(1)).To(Equal(3)) + }) + + It("should have the correct size", func() { + Expect(stack.Size()).To(Equal(3)) + }) + }) + }) +}) diff --git a/core/state/utils/utils.go b/lib/utils/utils.go similarity index 100% rename from core/state/utils/utils.go rename to lib/utils/utils.go diff --git a/core/state/utils/utils_test.go b/lib/utils/utils_test.go similarity index 97% rename from core/state/utils/utils_test.go rename to lib/utils/utils_test.go index 6a128f012..1e1bec55d 100644 --- a/core/state/utils/utils_test.go +++ b/lib/utils/utils_test.go @@ -17,7 +17,7 @@ package utils_test import ( "testing" - "github.com/berachain/stargazer/core/state/utils" + "github.com/berachain/stargazer/lib/utils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/store/journal/manager.go b/store/journal/manager.go index 142ae13a7..4ea80fa94 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -15,23 +15,16 @@ //nolint:ireturn // ManagerI returns interfaces by design. package journal -import "github.com/berachain/stargazer/types" +import ( + ds "github.com/berachain/stargazer/lib/ds" + "github.com/berachain/stargazer/types" +) // `ManagerI` is an interface that defines the methods that a journal manager must implement. // Journal managers support holding cache entries and reverting to a certain index. type ManagerI[T any] interface { - // `Append` adds a new CacheEntry instance to the journal. The Size method returns the current - // number of entries in the journal. - Append(ce CacheEntry) - - // `Size` returns the current number of entries in the journal. - Size() int - - // `Get` returns the CacheEntry instance at the given index. - Get(i int) CacheEntry - - // `RevertToSize` reverts and discards all journal entries after and including the given size. - RevertToSize(newSize int) + // The journal manager is a stack. + ds.StackI[CacheEntry] // `ManagerI` implements `Cloneable`. types.Cloneable[T] @@ -42,63 +35,35 @@ var _ ManagerI[*Manager] = (*Manager)(nil) // `Manager` is a struct that holds a slice of CacheEntry instances. type Manager struct { - journal []CacheEntry + *ds.Stack[CacheEntry] } // `NewManager` creates and returns a new Manager instance with an empty journal. func NewManager() *Manager { return &Manager{ - journal: make([]CacheEntry, 0), - } -} - -// `Append` implements `ManagerI`. -func (jm *Manager) Append(ce CacheEntry) { - jm.journal = append(jm.journal, ce) -} - -// `Size` implements `ManagerI`. -func (jm *Manager) Size() int { - return len(jm.journal) -} - -// `Get` returns nil if index `i` is invalid. -// -// `Get` implements `ManagerI`. -func (jm *Manager) Get(i int) CacheEntry { - if i < 0 || i >= len(jm.journal) { - return nil + ds.NewStack[CacheEntry](), } - return jm.journal[i] } // `RevertToSize` does not modify the journal if `newSize` is invalid. // // `RevertToSize` implements `ManagerI`. -func (jm *Manager) RevertToSize(newSize int) { - if newSize > len(jm.journal) { - return - } - +func (jm *Manager) PopToSize(newSize int) { // Revert and discard all journal entries after and including newSize. - for j := len(jm.journal) - 1; j >= newSize; j-- { - jm.journal[j].Revert() + for i := jm.Size() - 1; i >= newSize; i-- { + jm.Stack.PeekAt(i).Revert() } - - // Discard all journal entries after and including newSize, such that now - // len(jm.journal) == newSize. - jm.journal = jm.journal[:newSize] + // Call parent + jm.Stack.PopToSize(newSize) } // `Clone` returns a cloned journal by deep copying each CacheEntry. // // `Clone` implements `ManagerI[*Manager]`. func (jm *Manager) Clone() *Manager { - newJournal := make([]CacheEntry, len(jm.journal)) - for i := 0; i < len(jm.journal); i++ { - newJournal[i] = jm.journal[i].Clone() - } - return &Manager{ - journal: newJournal, + newManager := NewManager() + for i := 0; i < jm.Size(); i++ { + newManager.Push(jm.PeekAt(i).Clone()) } + return newManager } diff --git a/store/journal/manager_test.go b/store/journal/manager_test.go index d500a3cd0..46ae8ed49 100644 --- a/store/journal/manager_test.go +++ b/store/journal/manager_test.go @@ -32,7 +32,7 @@ func TestJournalManager(t *testing.T) { } var _ = Describe("Journal", func() { - var jm journal.ManagerI[*journal.Manager] + var jm (journal.ManagerI[*journal.Manager]) var entries []*mock.CacheEntry BeforeEach(func() { @@ -43,9 +43,9 @@ var _ = Describe("Journal", func() { } }) - When("the journal is appended to", func() { + When("the journal is Pushed to", func() { BeforeEach(func() { - jm.Append(entries[0]) + jm.Push(entries[0]) }) It("should have a size of 1", func() { @@ -54,7 +54,7 @@ var _ = Describe("Journal", func() { When("the journal is reverted to size 0", func() { BeforeEach(func() { - jm.RevertToSize(0) + jm.PopToSize(0) }) It("should have a size of 0", func() { @@ -62,10 +62,10 @@ var _ = Describe("Journal", func() { }) }) - When("the journal is appended to 9 more times", func() { + When("the journal is Pushed to 9 more times", func() { BeforeEach(func() { for i := 1; i <= 9; i++ { - jm.Append(entries[i]) + jm.Push(entries[i]) } }) @@ -76,7 +76,7 @@ var _ = Describe("Journal", func() { size := rand.Int() % 10 When(fmt.Sprintf("the journal is reverted to size, %d", size), func() { BeforeEach(func() { - jm.RevertToSize(size) + jm.PopToSize(size) }) It(fmt.Sprintf("should have a size of %d", size), func() { @@ -86,7 +86,7 @@ var _ = Describe("Journal", func() { When("the journal is reverted to size 5", func() { BeforeEach(func() { - jm.RevertToSize(5) + jm.PopToSize(5) }) It("should have a size of 5", func() { @@ -117,14 +117,14 @@ var _ = Describe("Journal", func() { It("should be a deep copy", func() { for i := 0; i < 5; i++ { - Expect(jm2.Get(i)).To(Equal(jm.Get(i))) - Expect(jm2.Get(0)).ToNot(BeIdenticalTo(jm.Get(0))) + Expect(jm2.PeekAt(i)).To(Equal(jm.PeekAt(i))) + Expect(jm2.PeekAt(0)).ToNot(BeIdenticalTo(jm.PeekAt(0))) } }) When("the original journal is reverted to size 0", func() { BeforeEach(func() { - jm.RevertToSize(0) + jm.PopToSize(0) }) It("the clone should stillhave a size of 5", func() { From 9303cf66f4fdc920ecf724810f303740d4a750ea Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 10:48:33 -0500 Subject: [PATCH 04/76] gomod --- go.mod | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2d0522cc1..9a6399501 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,10 @@ go 1.19 replace github.com/cosmos/cosmos-sdk => github.com/berachain/cosmos-sdk v0.47.0-alpha2.0.20221228180048-31eec31bde07 require ( + cosmossdk.io/errors v1.0.0-beta.7 github.com/bufbuild/buf v1.7.0 github.com/cosmos/cosmos-sdk v0.0.0-00010101000000-000000000000 + github.com/cosmos/gogoproto v1.4.3 github.com/cosmos/gosec/v2 v2.0.0-20221105100203-f3d05e8e9019 github.com/ethereum/go-ethereum v1.10.26 github.com/golangci/golangci-lint v1.50.1 @@ -17,6 +19,8 @@ require ( github.com/onsi/ginkgo/v2 v2.7.0 github.com/onsi/gomega v1.24.1 github.com/segmentio/golines v0.11.0 + github.com/stretchr/testify v1.8.1 + github.com/tidwall/btree v1.5.2 golang.org/x/tools v0.4.0 golang.org/x/tools/gopls v0.11.0 ) @@ -26,7 +30,6 @@ require ( cosmossdk.io/api v0.2.6 // indirect cosmossdk.io/core v0.3.2 // indirect cosmossdk.io/depinject v1.0.0-alpha.3 // indirect - cosmossdk.io/errors v1.0.0-beta.7 // indirect cosmossdk.io/math v1.0.0-beta.4 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -72,7 +75,6 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.1 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/gogoproto v1.4.3 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.4 // indirect github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect @@ -275,7 +277,6 @@ require ( github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tdakkota/asciicheck v0.1.1 // indirect @@ -285,7 +286,6 @@ require ( github.com/tendermint/tendermint v0.37.0-rc2 // indirect github.com/tendermint/tm-db v0.6.7 // indirect github.com/tetafro/godot v1.4.11 // indirect - github.com/tidwall/btree v1.5.2 // indirect github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect github.com/timonwong/loggercheck v0.9.3 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect From 3534072afd7fc59792fea1532dbcf4d3cb55bdcb Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 10:53:13 -0500 Subject: [PATCH 05/76] format --- lib/ds/stack_test.go | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/ds/stack_test.go b/lib/ds/stack_test.go index 921d80e2e..31b16682c 100644 --- a/lib/ds/stack_test.go +++ b/lib/ds/stack_test.go @@ -28,10 +28,6 @@ var _ = Describe("Stack", func() { stack = ds.NewStack[int]() }) - It("should be empty", func() { - Expect(stack.Size()).To(BeZero()) - }) - When("pushing an element", func() { BeforeEach(func() { stack.Push(1) @@ -47,19 +43,18 @@ var _ = Describe("Stack", func() { It("should return the correct element", func() { Expect(stack.PeekAt(0)).To(Equal(1)) + }) + It("should return the correct element", func() { + Expect(stack.Pop()).To(Equal(1)) + }) - It("should return the correct element", func() { - Expect(stack.Pop()).To(Equal(1)) + When("popping an element", func() { + BeforeEach(func() { + stack.Pop() }) - When("popping an element", func() { - BeforeEach(func() { - stack.Pop() - }) - - It("should be empty", func() { - Expect(stack.Size()).To(BeZero()) - }) + It("should be empty", func() { + Expect(stack.Size()).To(BeZero()) }) }) @@ -72,12 +67,27 @@ var _ = Describe("Stack", func() { It("should return the correct element", func() { Expect(stack.Peek()).To(Equal(3)) Expect(stack.PeekAt(2)).To(Equal(3)) - Expect(stack.PeekAt(1)).To(Equal(3)) + Expect(stack.PeekAt(1)).To(Equal(2)) }) It("should have the correct size", func() { Expect(stack.Size()).To(Equal(3)) }) + + When("calling poptosize with a size smaller than the current size", func() { + BeforeEach(func() { + stack.PopToSize(1) + }) + + It("should have the correct size", func() { + Expect(stack.Size()).To(Equal(1)) + }) + + It("should return the correct element", func() { + Expect(stack.Peek()).To(Equal(1)) + Expect(stack.PeekAt(0)).To(Equal(1)) + }) + }) }) }) }) From 537676657dae152ae804da3e78c9c99c3f42ed73 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:18:19 -0500 Subject: [PATCH 06/76] lmao what --- store/journal/{exported.go => cache_entry.go} | 0 store/journal/manager.go | 11 ++++------- 2 files changed, 4 insertions(+), 7 deletions(-) rename store/journal/{exported.go => cache_entry.go} (100%) diff --git a/store/journal/exported.go b/store/journal/cache_entry.go similarity index 100% rename from store/journal/exported.go rename to store/journal/cache_entry.go diff --git a/store/journal/manager.go b/store/journal/manager.go index 4ea80fa94..00df6ca4f 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -11,8 +11,6 @@ // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//nolint:ireturn // ManagerI returns interfaces by design. package journal import ( @@ -27,6 +25,7 @@ type ManagerI[T any] interface { ds.StackI[CacheEntry] // `ManagerI` implements `Cloneable`. + types.Cloneable[T] } @@ -45,9 +44,7 @@ func NewManager() *Manager { } } -// `RevertToSize` does not modify the journal if `newSize` is invalid. -// -// `RevertToSize` implements `ManagerI`. +// `PopToSize` implements `StackI`. func (jm *Manager) PopToSize(newSize int) { // Revert and discard all journal entries after and including newSize. for i := jm.Size() - 1; i >= newSize; i-- { @@ -57,9 +54,9 @@ func (jm *Manager) PopToSize(newSize int) { jm.Stack.PopToSize(newSize) } -// `Clone` returns a cloned journal by deep copying each CacheEntry. -// // `Clone` implements `ManagerI[*Manager]`. +// +// `Clone` returns a cloned journal by deep copying each CacheEntry. func (jm *Manager) Clone() *Manager { newManager := NewManager() for i := 0; i < jm.Size(); i++ { From fce73d8251a4e7e7b1e07e7b6f5b3473ef106c80 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:27:22 -0500 Subject: [PATCH 07/76] lint --- core/state/types/state.pb.go | 3 ++- lib/ds/stack.go | 8 -------- store/journal/manager.go | 3 +-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/core/state/types/state.pb.go b/core/state/types/state.pb.go index ebadd42e2..445ca65f7 100644 --- a/core/state/types/state.pb.go +++ b/core/state/types/state.pb.go @@ -5,10 +5,11 @@ package types import ( fmt "fmt" - proto "github.com/cosmos/gogoproto/proto" io "io" math "math" math_bits "math/bits" + + proto "github.com/cosmos/gogoproto/proto" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/lib/ds/stack.go b/lib/ds/stack.go index c5036c6ac..bff54fd15 100644 --- a/lib/ds/stack.go +++ b/lib/ds/stack.go @@ -63,22 +63,16 @@ func (s *Stack[Item]) Size() int { return len(s.items) } -// `Peek` returns nil if index `i` is invalid. -// // `Peek` implements `StackI`. func (s *Stack[Item]) Peek() Item { return s.items[len(s.items)-1] } -// `Peek` returns nil if index `i` is invalid. -// // `Peek` implements `StackI`. func (s *Stack[Item]) PeekAt(i int) Item { return s.items[i] } -// `Pop` returns nil if index `i` is invalid. -// // `Pop` implements `StackI`. func (s *Stack[Item]) Pop() Item { newLen := len(s.items) - 1 @@ -87,8 +81,6 @@ func (s *Stack[Item]) Pop() Item { return item } -// `PopToSize` will panic if `newSize` is greater than the current size. -// // `PopToSize` implements `StackI`. func (s *Stack[Item]) PopToSize(newSize int) { s.items = s.items[:newSize] // todo: help the GC? diff --git a/store/journal/manager.go b/store/journal/manager.go index 00df6ca4f..89512ccd1 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -54,9 +54,8 @@ func (jm *Manager) PopToSize(newSize int) { jm.Stack.PopToSize(newSize) } -// `Clone` implements `ManagerI[*Manager]`. -// // `Clone` returns a cloned journal by deep copying each CacheEntry. +// `Clone` implements `ManagerI[*Manager]`. func (jm *Manager) Clone() *Manager { newManager := NewManager() for i := 0; i < jm.Size(); i++ { From 390c8a366417333155421ae44ab8889e26b9acc0 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:32:40 -0500 Subject: [PATCH 08/76] deep copy --- store/cachekv/internal/btree_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/store/cachekv/internal/btree_test.go b/store/cachekv/internal/btree_test.go index 1d6d98196..082b28070 100644 --- a/store/cachekv/internal/btree_test.go +++ b/store/cachekv/internal/btree_test.go @@ -82,6 +82,22 @@ var _ = Describe("DBIterator", func() { } }) + It("should be deep copyable", func() { + db2 := db.Copy() + + itr, err := db.Iterator(nil, nil) + Expect(err).ToNot(HaveOccurred()) + defer itr.Close() + + for itr.Valid() { + key := itr.Key() + value := itr.Value() + itr.Next() + value2 := db2.Get(key) + Expect(value).To(Equal(value2)) + } + }) + It("should error with blank iterator keys", func() { _, err := db.ReverseIterator([]byte{}, nil) Expect(err).To(Equal(internal.ErrKeyEmpty)) @@ -175,6 +191,10 @@ var _ = Describe("DBIterator", func() { }) +var _ = Describe("Copy", func() { + +}) + func verifyIterator(itr types.Iterator, expected []int64, _ string) { i := 0 t := GinkgoT() From 1f4ff6cd734f73e673c52399c102eb78b3418033 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:42:00 -0500 Subject: [PATCH 09/76] trees to lib --- .../cachekv/internal => lib/ds/trees}/btree.go | 2 +- .../internal => lib/ds/trees}/btree_test.go | 16 ++++++++-------- .../internal => lib/ds/trees}/memiterator.go | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) rename {store/cachekv/internal => lib/ds/trees}/btree.go (99%) rename {store/cachekv/internal => lib/ds/trees}/btree_test.go (95%) rename {store/cachekv/internal => lib/ds/trees}/memiterator.go (99%) diff --git a/store/cachekv/internal/btree.go b/lib/ds/trees/btree.go similarity index 99% rename from store/cachekv/internal/btree.go rename to lib/ds/trees/btree.go index 496f48f52..7e4341d9b 100644 --- a/store/cachekv/internal/btree.go +++ b/lib/ds/trees/btree.go @@ -12,7 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package internal +package trees import ( "bytes" diff --git a/store/cachekv/internal/btree_test.go b/lib/ds/trees/btree_test.go similarity index 95% rename from store/cachekv/internal/btree_test.go rename to lib/ds/trees/btree_test.go index 082b28070..10ee6bb77 100644 --- a/store/cachekv/internal/btree_test.go +++ b/lib/ds/trees/btree_test.go @@ -12,12 +12,12 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package internal_test +package trees_test import ( "testing" - "github.com/berachain/stargazer/store/cachekv/internal" + "github.com/berachain/stargazer/lib/ds/trees" "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -32,10 +32,10 @@ func TestSuite(t *testing.T) { } var _ = Describe("GetSetDelete", func() { - var db internal.BTree + var db trees.BTree BeforeEach(func() { - db = internal.NewBTree() + db = trees.NewBTree() }) It("should return nil for a nonexistent key", func() { @@ -70,10 +70,10 @@ var _ = Describe("GetSetDelete", func() { }) }) var _ = Describe("DBIterator", func() { - var db internal.BTree + var db trees.BTree BeforeEach(func() { - db = internal.NewBTree() + db = trees.NewBTree() for i := 0; i < 10; i++ { if i != 6 { // but skip 6. @@ -100,9 +100,9 @@ var _ = Describe("DBIterator", func() { It("should error with blank iterator keys", func() { _, err := db.ReverseIterator([]byte{}, nil) - Expect(err).To(Equal(internal.ErrKeyEmpty)) + Expect(err).To(Equal(trees.ErrKeyEmpty)) _, err = db.ReverseIterator(nil, []byte{}) - Expect(err).To(Equal(internal.ErrKeyEmpty)) + Expect(err).To(Equal(trees.ErrKeyEmpty)) }) It("should iterate forward", func() { diff --git a/store/cachekv/internal/memiterator.go b/lib/ds/trees/memiterator.go similarity index 99% rename from store/cachekv/internal/memiterator.go rename to lib/ds/trees/memiterator.go index 53898b415..c71beb901 100644 --- a/store/cachekv/internal/memiterator.go +++ b/lib/ds/trees/memiterator.go @@ -12,7 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package internal +package trees import ( "bytes" From e49053f1a1ed651e6ffe114b0cae5e6e89b48264 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:43:03 -0500 Subject: [PATCH 10/76] common --- {lib/common => common}/address.go | 0 {lib/common => common}/imported.go | 0 core/state/types/state_test.go | 2 +- core/state/types/storage_test.go | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename {lib/common => common}/address.go (100%) rename {lib/common => common}/imported.go (100%) diff --git a/lib/common/address.go b/common/address.go similarity index 100% rename from lib/common/address.go rename to common/address.go diff --git a/lib/common/imported.go b/common/imported.go similarity index 100% rename from lib/common/imported.go rename to common/imported.go diff --git a/core/state/types/state_test.go b/core/state/types/state_test.go index 047c1be17..63f1dcd18 100644 --- a/core/state/types/state_test.go +++ b/core/state/types/state_test.go @@ -17,8 +17,8 @@ package types_test import ( "math/rand" + "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state/types" - "github.com/berachain/stargazer/lib/common" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/core/state/types/storage_test.go b/core/state/types/storage_test.go index 3744592dc..b636ef574 100644 --- a/core/state/types/storage_test.go +++ b/core/state/types/storage_test.go @@ -15,8 +15,8 @@ package types_test import ( + "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state/types" - "github.com/berachain/stargazer/lib/common" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) From 130a1c691124a00f3845e90794c19293dbbd173a Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:44:25 -0500 Subject: [PATCH 11/76] remove sdk alias --- lib/ds/trees/btree.go | 6 +++--- lib/ds/trees/memiterator.go | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/ds/trees/btree.go b/lib/ds/trees/btree.go index 7e4341d9b..9d52c7534 100644 --- a/lib/ds/trees/btree.go +++ b/lib/ds/trees/btree.go @@ -18,7 +18,7 @@ import ( "bytes" "errors" - "github.com/cosmos/cosmos-sdk/store/types" + dbm "github.com/tendermint/tm-db" "github.com/tidwall/btree" ) @@ -66,7 +66,7 @@ func (bt BTree) Delete(key []byte) { } //nolint:nolintlint,ireturn -func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { +func (bt BTree) Iterator(start, end []byte) (dbm.Iterator, error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, ErrKeyEmpty } @@ -74,7 +74,7 @@ func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { } //nolint:nolintlint,ireturn -func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { +func (bt BTree) ReverseIterator(start, end []byte) (dbm.Iterator, error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, ErrKeyEmpty } diff --git a/lib/ds/trees/memiterator.go b/lib/ds/trees/memiterator.go index c71beb901..b16744dfc 100644 --- a/lib/ds/trees/memiterator.go +++ b/lib/ds/trees/memiterator.go @@ -18,11 +18,12 @@ import ( "bytes" "errors" - "github.com/cosmos/cosmos-sdk/store/types" "github.com/tidwall/btree" + + dbm "github.com/tendermint/tm-db" ) -var _ types.Iterator = (*memIterator)(nil) +var _ dbm.Iterator = (*memIterator)(nil) // memIterator iterates over iterKVCache items. // if value is nil, means it was deleted. From e986d0269dabed10be73bc6e947fda30152c5579 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:44:55 -0500 Subject: [PATCH 12/76] lint imports --- lib/ds/trees/memiterator.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ds/trees/memiterator.go b/lib/ds/trees/memiterator.go index b16744dfc..06da990cf 100644 --- a/lib/ds/trees/memiterator.go +++ b/lib/ds/trees/memiterator.go @@ -18,9 +18,8 @@ import ( "bytes" "errors" - "github.com/tidwall/btree" - dbm "github.com/tendermint/tm-db" + "github.com/tidwall/btree" ) var _ dbm.Iterator = (*memIterator)(nil) From 705269794d35e4cfd20cc8bb1d7388c7cb723d9a Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:47:54 -0500 Subject: [PATCH 13/76] small cleanup --- store/journal/manager.go | 2 +- store/journal/manager_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/store/journal/manager.go b/store/journal/manager.go index 89512ccd1..dfb4e3e3b 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -14,7 +14,7 @@ package journal import ( - ds "github.com/berachain/stargazer/lib/ds" + "github.com/berachain/stargazer/lib/ds" "github.com/berachain/stargazer/types" ) diff --git a/store/journal/manager_test.go b/store/journal/manager_test.go index 46ae8ed49..5ae5c9925 100644 --- a/store/journal/manager_test.go +++ b/store/journal/manager_test.go @@ -32,7 +32,7 @@ func TestJournalManager(t *testing.T) { } var _ = Describe("Journal", func() { - var jm (journal.ManagerI[*journal.Manager]) + var jm journal.ManagerI[*journal.Manager] var entries []*mock.CacheEntry BeforeEach(func() { From b3ce5f589328fcc980074948a6bebfd2f9d99591 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:48:02 -0500 Subject: [PATCH 14/76] small cleanup --- store/journal/manager.go | 1 - 1 file changed, 1 deletion(-) diff --git a/store/journal/manager.go b/store/journal/manager.go index dfb4e3e3b..19eed3246 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -25,7 +25,6 @@ type ManagerI[T any] interface { ds.StackI[CacheEntry] // `ManagerI` implements `Cloneable`. - types.Cloneable[T] } From a5e4365efc422958e155f46e6dd4872dfeb9a84e Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 11:48:24 -0500 Subject: [PATCH 15/76] small cleanup --- store/journal/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/journal/manager.go b/store/journal/manager.go index 19eed3246..b846cdf00 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -49,7 +49,7 @@ func (jm *Manager) PopToSize(newSize int) { for i := jm.Size() - 1; i >= newSize; i-- { jm.Stack.PeekAt(i).Revert() } - // Call parent + // Call parent. jm.Stack.PopToSize(newSize) } From 23e1bcec954597c29be2d68f6ebc3644991e3e6c Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:07:16 -0500 Subject: [PATCH 16/76] upgrade cachekv --- lib/ds/trees/btree.go | 8 +- lib/ds/trees/btree_test.go | 4 +- lib/ds/trees/memiterator.go | 2 +- store/cachekv/cache_value.go | 36 ++ store/cachekv/cache_values.go | 125 ++++++ store/cachekv/cache_values_test.go | 202 ++++++++++ store/cachekv/mergeiterator.go | 235 +++++++++++ store/cachekv/store.go | 451 +++++++++++++++++++++ store/cachekv/store_test.go | 608 +++++++++++++++++++++++++++++ 9 files changed, 1664 insertions(+), 7 deletions(-) create mode 100644 store/cachekv/cache_value.go create mode 100644 store/cachekv/cache_values.go create mode 100644 store/cachekv/cache_values_test.go create mode 100644 store/cachekv/mergeiterator.go create mode 100644 store/cachekv/store.go create mode 100644 store/cachekv/store_test.go diff --git a/lib/ds/trees/btree.go b/lib/ds/trees/btree.go index 9d52c7534..83fbc6961 100644 --- a/lib/ds/trees/btree.go +++ b/lib/ds/trees/btree.go @@ -40,8 +40,8 @@ type BTree struct { } // NewBTree creates a wrapper around `btree.BTreeG`. -func NewBTree() BTree { - return BTree{ +func NewBTree() *BTree { + return &BTree{ tree: btree.NewBTreeGOptions(byKeys, btree.Options{ Degree: bTreeDegree, NoLocks: false, @@ -70,7 +70,7 @@ func (bt BTree) Iterator(start, end []byte) (dbm.Iterator, error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, ErrKeyEmpty } - return newMemIterator(start, end, bt, true), nil + return NewMemIterator(start, end, bt, true), nil } //nolint:nolintlint,ireturn @@ -78,7 +78,7 @@ func (bt BTree) ReverseIterator(start, end []byte) (dbm.Iterator, error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, ErrKeyEmpty } - return newMemIterator(start, end, bt, false), nil + return NewMemIterator(start, end, bt, false), nil } // Copy the tree. This is a copy-on-write operation and is very fast because diff --git a/lib/ds/trees/btree_test.go b/lib/ds/trees/btree_test.go index 10ee6bb77..50bd4f0a0 100644 --- a/lib/ds/trees/btree_test.go +++ b/lib/ds/trees/btree_test.go @@ -32,7 +32,7 @@ func TestSuite(t *testing.T) { } var _ = Describe("GetSetDelete", func() { - var db trees.BTree + var db *trees.BTree BeforeEach(func() { db = trees.NewBTree() @@ -70,7 +70,7 @@ var _ = Describe("GetSetDelete", func() { }) }) var _ = Describe("DBIterator", func() { - var db trees.BTree + var db *trees.BTree BeforeEach(func() { db = trees.NewBTree() diff --git a/lib/ds/trees/memiterator.go b/lib/ds/trees/memiterator.go index 06da990cf..1385ba15d 100644 --- a/lib/ds/trees/memiterator.go +++ b/lib/ds/trees/memiterator.go @@ -36,7 +36,7 @@ type memIterator struct { valid bool } -func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { +func NewMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { iter := items.tree.Iter() var valid bool //nolint:nestif // from sdk. diff --git a/store/cachekv/cache_value.go b/store/cachekv/cache_value.go new file mode 100644 index 000000000..d202c6f5a --- /dev/null +++ b/store/cachekv/cache_value.go @@ -0,0 +1,36 @@ +package cachekv + +// 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 + dirty bool +} + +//nolint:revive +func NewCValue(v []byte, d bool) *cValue { + return &cValue{ + value: v, + dirty: d, + } +} + +func (cv *cValue) Dirty() bool { + return cv.dirty +} + +func (cv *cValue) Value() []byte { + return cv.value +} + +// deepCopy creates a new cValue object with the same value and dirty flag as the original +// cValue object. This function is used to create a deep copy of the prev field in +// DeleteCacheValue and SetCacheValue objects, so that modifications to the original prev value do +// not affect the cloned DeleteCacheValue or SetCacheValue object. +func (cv *cValue) deepCopy() *cValue { + // Return a new cValue with the same value and dirty flag + return &cValue{ + value: append([]byte(nil), cv.value...), + dirty: cv.dirty, + } +} diff --git a/store/cachekv/cache_values.go b/store/cachekv/cache_values.go new file mode 100644 index 000000000..1742fef70 --- /dev/null +++ b/store/cachekv/cache_values.go @@ -0,0 +1,125 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachekv + +import "github.com/berachain/stargazer/store/journal" + +type ( + // DeleteCacheValue is a struct that contains information needed to delete a value + // from a cache. + DeleteCacheValue struct { + Store *Store // pointer to the cache Store + Key string // Key of the value to be deleted + Prev *cValue // deep copy of object in cache map + } + + // SetCacheValue is a struct that contains information needed to set a value in a cache. + SetCacheValue struct { + Store *Store // pointer to the cache Store + Key string // Key of the value to be set + Prev *cValue // deep copy of object in cache map + } +) + +// Revert restores the previous cache entry for the Key, if it exists. +// +// implements journal.CacheEntry +func (dcv *DeleteCacheValue) Revert() { + // Check if there is a previous entry for the Key being deleted + if dcv.Prev != nil { + // If the previous entry is not dirty + // (i.e., it has not been modified since it was added to the cache) + if !dcv.Prev.dirty { + // Remove the Key being deleted from the cache and restore the previous entry + delete(dcv.Store.UnsortedCache, dcv.Key) + dcv.Store.Cache[dcv.Key] = dcv.Prev + } else { + // If the previous entry is dirty and has a non-nil value + if dcv.Prev.value != nil { + // Remove the Key being deleted from the "deleted" set and restore the + // previous entry to the cache + dcv.Store.Cache[dcv.Key] = dcv.Prev + } + } + } else { + // If there is no previous entry for the Key being deleted, remove the Key from the cache + delete(dcv.Store.Cache, dcv.Key) + delete(dcv.Store.UnsortedCache, dcv.Key) + } +} + +// Clone creates a deep copy of the DeleteCacheValue object. +// The deep copy contains the same Store and Key fields as the original, +// and a deep copy of the Prev field, if it is not nil. +// +// implements journal.CacheEntry +func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { + // Create a deep copy of the Prev field, if it is not nil + var prevDeepCopy *cValue + if dcv.Prev != nil { + prevDeepCopy = dcv.Prev.deepCopy() + } + + // Return a new DeleteCacheValue object with the same Store and Key fields as the original, + // and the Prev field set to the deep copy of the original Prev field + // (or nil if the original was nil) + return &DeleteCacheValue{ + Store: dcv.Store, + Key: dcv.Key, + Prev: prevDeepCopy, + } +} + +// Revert reverts a set operation on a cache entry by setting the previous value of the entry as +// the current value in the cache map. +// +// implements journal.CacheEntry +func (scv *SetCacheValue) Revert() { + // If there was a previous value, set it as the current value in the cache map + if scv.Prev != nil { + scv.Store.Cache[scv.Key] = scv.Prev + // If the previous value was not dirty, remove the Key from the unsorted cache set + if !scv.Prev.dirty { + delete(scv.Store.UnsortedCache, scv.Key) + } + } else { + // If there was no previous value, remove the Key from the + // cache map and the unsorted cache set + delete(scv.Store.Cache, scv.Key) + delete(scv.Store.UnsortedCache, scv.Key) + } +} + +// Clone creates a deep copy of the SetCacheValue object. +// The deep copy contains the same Store and Key fields as the original, +// and a deep copy of the Prev field, if it is not nil. +// +// implements journal.CacheEntry +func (scv *SetCacheValue) Clone() journal.CacheEntry { + // Create a deep copy of the Prev field, if it is not nil + var prevDeepCopy *cValue + if scv.Prev != nil { + prevDeepCopy = scv.Prev.deepCopy() + } + + // Return a new SetCacheValue object with the same Store and Key fields as the original, + // and the Prev field set to the deep copy of the original Prev field + // (or nil if the original was nil) + return &SetCacheValue{ + Store: scv.Store, + Key: scv.Key, + Prev: prevDeepCopy, + } +} diff --git a/store/cachekv/cache_values_test.go b/store/cachekv/cache_values_test.go new file mode 100644 index 000000000..6a46f36d9 --- /dev/null +++ b/store/cachekv/cache_values_test.go @@ -0,0 +1,202 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachekv_test + +import ( + "reflect" + "testing" + + sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/stretchr/testify/suite" + dbm "github.com/tendermint/tm-db" + + "github.com/berachain/stargazer/lib/utils" + "github.com/berachain/stargazer/store/cachekv" + "github.com/berachain/stargazer/store/journal" +) + +var ( + byte0 = []byte{0} + byte1 = []byte{1} + byte0Str = utils.UnsafeBytesToStr(byte0) + byte1Str = utils.UnsafeBytesToStr(byte1) +) + +type CacheValueSuite struct { + suite.Suite + cacheKVStore *cachekv.Store +} + +func TestCacheValueSuite(t *testing.T) { + suite.Run(t, new(CacheValueSuite)) +} + +func (s *CacheValueSuite) SetupTest() { + parent := sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) + parent.Set(byte0, byte0) + s.cacheKVStore = cachekv.NewStore(parent, journal.NewManager()) +} + +func (s *CacheValueSuite) TestRevertDeleteAfterNothing() { + // delete after nothing happened to key + snapshot := s.cacheKVStore.JournalMgr().Size() + // delete key: 0 + s.cacheKVStore.Delete(byte0) + s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].Value()) + s.Require().True(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) + // revert delete key: 0 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().NotContains(s.cacheKVStore.Cache, byte0Str) + s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) +} + +func (s *CacheValueSuite) TestRevertDeleteAfterGet() { + // delete after Get called on key + _ = s.cacheKVStore.Get(byte0) + snapshot := s.cacheKVStore.JournalMgr().Size() + // delete key: 0 + s.cacheKVStore.Delete(byte0) + // revert delete key: 0 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].Value()) + s.Require().False(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) +} + +func (s *CacheValueSuite) TestRevertDeleteAfterSet() { + // delete after Set called on key + s.cacheKVStore.Set(byte1, byte1) + snapshot := s.cacheKVStore.JournalMgr().Size() + // delete key: 1 + s.cacheKVStore.Delete(byte1) + // revert delete key: 1 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].Value()) + s.Require().True(s.cacheKVStore.Cache[byte1Str].Dirty()) + s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) +} + +func (s *CacheValueSuite) TestRevertDeleteAfterDelete() { + // delete after Delete called on key + s.cacheKVStore.Delete(byte0) + snapshot := s.cacheKVStore.JournalMgr().Size() + // delete key: 0 + s.cacheKVStore.Delete(byte0) + // revert delete key: 0 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].Value()) + s.Require().True(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) +} + +func (s *CacheValueSuite) TestRevertSetAfterNothing() { + // set after nothing happened to key + snapshot := s.cacheKVStore.JournalMgr().Size() + // set key: 1 + s.cacheKVStore.Set(byte1, byte1) + s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].Value()) + s.Require().True(s.cacheKVStore.Cache[byte1Str].Dirty()) + s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) + // revert set key: 1 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().NotContains(s.cacheKVStore.Cache, byte1Str) + s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte1Str) +} + +func (s *CacheValueSuite) TestRevertSetAfterGet() { + // set after get called on key + _ = s.cacheKVStore.Get(byte0) + snapshot := s.cacheKVStore.JournalMgr().Size() + // set key: 0 to val: 1 + s.cacheKVStore.Set(byte0, byte1) + // revert set key: 1 to val: 1 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].Value()) + s.Require().False(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) +} + +func (s *CacheValueSuite) TestRevertSetAfterDelete() { + // set after delete called on key + s.cacheKVStore.Delete(byte0) + snapshot := s.cacheKVStore.JournalMgr().Size() + // set key: 0 to val: 0 + s.cacheKVStore.Set(byte0, byte0) + // revert set key: 0 to val: 0 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].Value()) + s.Require().True(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) +} + +func (s *CacheValueSuite) TestRevertSetAfterSet() { + // set after set called on key + s.cacheKVStore.Set(byte1, byte1) + snapshot := s.cacheKVStore.JournalMgr().Size() + // set key: 1 to val: 0 + s.cacheKVStore.Set(byte1, byte0) + // revert set key: 1 to val: 0 + s.cacheKVStore.JournalMgr().PopToSize(snapshot) + s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].Value()) + s.Require().True(s.cacheKVStore.Cache[byte1Str].Dirty()) + s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) +} + +func (s *CacheValueSuite) TestCloneDelete() { + dcvNonNil := &cachekv.DeleteCacheValue{ + Store: s.cacheKVStore, + Key: byte1Str, + Prev: cachekv.NewCValue(byte1, true), + } + + dcvNonNilClone := dcvNonNil.Clone().(*cachekv.DeleteCacheValue) + s.Require().Equal(byte1Str, dcvNonNilClone.Key) + s.Require().True(dcvNonNilClone.Prev.Dirty()) + s.Require().Equal(byte1, dcvNonNilClone.Prev.Value()) + + dcvNil := &cachekv.DeleteCacheValue{ + Store: s.cacheKVStore, + Key: "", + Prev: nil, + } + dcvNilClone := dcvNil.Clone().(*cachekv.DeleteCacheValue) + s.Require().Equal("", dcvNilClone.Key) + s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) + // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) +} + +func (s *CacheValueSuite) TestCloneSet() { + dcvNonNil := &cachekv.SetCacheValue{ + Store: s.cacheKVStore, + Key: byte1Str, + Prev: cachekv.NewCValue(byte1, true), + } + dcvNonNilClone := dcvNonNil.Clone().(*cachekv.SetCacheValue) + s.Require().Equal(byte1Str, dcvNonNilClone.Key) + s.Require().True(dcvNonNilClone.Prev.Dirty()) + s.Require().Equal(byte1, dcvNonNilClone.Prev.Value()) + + dcvNil := &cachekv.SetCacheValue{ + Store: s.cacheKVStore, + Key: "", + Prev: nil, + } + dcvNilClone := dcvNil.Clone().(*cachekv.SetCacheValue) + s.Require().Equal("", dcvNilClone.Key) + // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) + s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) +} diff --git a/store/cachekv/mergeiterator.go b/store/cachekv/mergeiterator.go new file mode 100644 index 000000000..0e17116c0 --- /dev/null +++ b/store/cachekv/mergeiterator.go @@ -0,0 +1,235 @@ +package cachekv + +import ( + "bytes" + "errors" + + "github.com/cosmos/cosmos-sdk/store/types" +) + +// cacheMergeIterator merges a parent Iterator and a cache Iterator. +// The cache iterator may return nil keys to signal that an item +// had been deleted (but not deleted in the parent). +// If the cache iterator has the same key as the parent, the +// cache shadows (overrides) the parent. +// +// TODO: Optimize by memoizing. +type cacheMergeIterator struct { + parent types.Iterator + cache types.Iterator + ascending bool + + valid bool +} + +var _ types.Iterator = (*cacheMergeIterator)(nil) + +func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheMergeIterator { //nolint:revive + iter := &cacheMergeIterator{ + parent: parent, + cache: cache, + ascending: ascending, + } + + iter.valid = iter.skipUntilExistsOrInvalid() + return iter +} + +// Domain implements Iterator. +// Returns parent domain because cache and parent domains are the same. +func (iter *cacheMergeIterator) Domain() (start, end []byte) { + return iter.parent.Domain() +} + +// Valid implements Iterator. +func (iter *cacheMergeIterator) Valid() bool { + return iter.valid +} + +// Next implements Iterator +func (iter *cacheMergeIterator) Next() { + iter.assertValid() + + switch { + case !iter.parent.Valid(): + // If parent is invalid, get the next cache item. + iter.cache.Next() + case !iter.cache.Valid(): + // If cache is invalid, get the next parent item. + iter.parent.Next() + default: + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + switch iter.compare(keyP, keyC) { + case -1: // parent < cache + iter.parent.Next() + case 0: // parent == cache + iter.parent.Next() + iter.cache.Next() + case 1: // parent > cache + iter.cache.Next() + } + } + iter.valid = iter.skipUntilExistsOrInvalid() +} + +// Key implements Iterator +func (iter *cacheMergeIterator) Key() []byte { + iter.assertValid() + + // If parent is invalid, get the cache key. + if !iter.parent.Valid() { + return iter.cache.Key() + } + + // If cache is invalid, get the parent key. + if !iter.cache.Valid() { + return iter.parent.Key() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return keyP + case 0: // parent == cache + return keyP + case 1: // parent > cache + return keyC + default: + panic("invalid compare result") + } +} + +// Value implements Iterator +func (iter *cacheMergeIterator) Value() []byte { + iter.assertValid() + + // If parent is invalid, get the cache value. + if !iter.parent.Valid() { + return iter.cache.Value() + } + + // If cache is invalid, get the parent value. + if !iter.cache.Valid() { + return iter.parent.Value() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return iter.parent.Value() + case 0: // parent == cache + return iter.cache.Value() + case 1: // parent > cache + return iter.cache.Value() + default: + panic("invalid comparison result") + } +} + +// Close implements Iterator +func (iter *cacheMergeIterator) Close() error { + err1 := iter.cache.Close() + if err := iter.parent.Close(); err != nil { + return err + } + + return err1 +} + +// Error returns an error if the cacheMergeIterator is invalid defined by the +// Valid method. +func (iter *cacheMergeIterator) Error() error { + if !iter.Valid() { + return errors.New("invalid cacheMergeIterator") + } + + return nil +} + +// If not valid, panics. +// NOTE: May have side-effect of iterating over cache. +func (iter *cacheMergeIterator) 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 { + if iter.ascending { + return bytes.Compare(a, b) + } + + return bytes.Compare(a, b) * -1 +} + +// Skip all delete-items from the cache w/ `key < until`. After this function, +// current cache item is a non-delete-item, or `until <= key`. +// If the current cache item is not a delete item, does nothing. +// If `until` is nil, there is no limit, and cache may end up invalid. +// CONTRACT: cache is valid. +func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { + for iter.cache.Valid() && + iter.cache.Value() == nil && + (until == nil || iter.compare(iter.cache.Key(), until) < 0) { + iter.cache.Next() + } +} + +// Fast forwards cache (or parent+cache in case of deleted items) until current +// item exists, or until iterator becomes invalid. +// Returns whether the iterator is valid. +func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { + for { + // If parent is invalid, fast-forward cache. + if !iter.parent.Valid() { + iter.skipCacheDeletes(nil) + return iter.cache.Valid() + } + // Parent is valid. + + if !iter.cache.Valid() { + return true + } + // Parent is valid, cache is valid. + + // Compare parent and cache. + keyP := iter.parent.Key() + keyC := iter.cache.Key() + + switch iter.compare(keyP, keyC) { + case -1: // parent < cache. + return true + + case 0: // parent == cache. + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.parent.Next() + iter.cache.Next() + + continue + } + // Cache is not a delete. + + return true // cache exists. + case 1: // cache < parent + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.skipCacheDeletes(keyP) + continue + } + // Cache is not a delete. + + return true // cache exists. + } + } +} diff --git a/store/cachekv/store.go b/store/cachekv/store.go new file mode 100644 index 000000000..0e49a8824 --- /dev/null +++ b/store/cachekv/store.go @@ -0,0 +1,451 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachekv + +import ( + "bytes" + "io" + "sort" + "sync" + + "github.com/cosmos/cosmos-sdk/store/listenkv" + "github.com/cosmos/cosmos-sdk/store/tracekv" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/tendermint/tendermint/libs/math" + dbm "github.com/tendermint/tm-db" + + "github.com/berachain/stargazer/lib/ds/trees" + "github.com/berachain/stargazer/lib/utils" + "github.com/berachain/stargazer/store/journal" +) + +type StateDBCacheKVStore interface { + storetypes.CacheKVStore + GetParent() storetypes.KVStore +} + +var _ StateDBCacheKVStore = (*Store)(nil) + +// Store wraps an in-memory cache around an underlying storetypes.KVStore. +// If a cached value is nil but deleted is defined for the corresponding key, +// it means the parent doesn't have the key. (No need to delete upon Write()) +type Store struct { + mtx sync.RWMutex + Cache map[string]*cValue + UnsortedCache map[string]struct{} + SortedCache *trees.BTree // always ascending sorted + Parent storetypes.KVStore + journalMgr *journal.Manager +} + +// NewStore creates a new Store object +func NewStore(parent storetypes.KVStore, journalMgr *journal.Manager) *Store { + return &Store{ + Cache: make(map[string]*cValue), + UnsortedCache: make(map[string]struct{}), + SortedCache: trees.NewBTree(), + Parent: parent, + journalMgr: journalMgr, + } +} + +func (store *Store) JournalMgr() *journal.Manager { + return store.journalMgr +} + +// GetStoreType implements storetypes.KVStore. +func (store *Store) GetStoreType() storetypes.StoreType { + return store.Parent.GetStoreType() +} + +// Get implements storetypes.KVStore. +// This function retrieves a value associated with the specified key in the store. +func (store *Store) Get(key []byte) (value []byte) { + store.mtx.RLock() + defer store.mtx.RUnlock() + // Assert that the key is valid. + storetypes.AssertValidKey(key) + + // Check if the key is in the store's cache. + cacheValue, ok := store.Cache[utils.UnsafeBytesToStr(key)] + if !ok { + value = store.Parent.Get(key) + store.setCacheValue(key, value, false) + } else { + value = cacheValue.value + } + + return value +} + +func (store *Store) GetParent() storetypes.KVStore { + return store.Parent +} + +// Set implements storetypes.KVStore. +func (store *Store) Set(key []byte, value []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + storetypes.AssertValidKey(key) + storetypes.AssertValidValue(value) + store.setCacheValue(key, value, true) +} + +// Has implements storetypes.KVStore. +func (store *Store) Has(key []byte) bool { + value := store.Get(key) + return value != nil +} + +// Delete implements storetypes.KVStore. +func (store *Store) Delete(key []byte) { + storetypes.AssertValidKey(key) + store.mtx.Lock() + defer store.mtx.Unlock() + store.setCacheValue(key, nil, true) +} + +// Implements Cachetypes.KVStore. +func (store *Store) Write() { + store.mtx.Lock() + defer store.mtx.Unlock() + + if len(store.Cache) == 0 && len(store.UnsortedCache) == 0 { + store.SortedCache = trees.NewBTree() + return + } + + // We need a copy of all of the keys. + // Not the best, but probably not a bottleneck depending. + keys := make([]string, 0, len(store.Cache)) + + for key, dbValue := range store.Cache { + if dbValue.dirty { + keys = append(keys, key) + } + } + + sort.Strings(keys) + + // TODO: Consider allowing usage of Batch, which would allow the write to + // at least happen atomically. + for _, key := range keys { + // We use []byte(key) instead of utils.UnsafeStrToBytes because we cannot + // 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. + cacheValue := store.Cache[key] + if cacheValue.value != nil { + // It already exists in the parent, hence update it. + store.Parent.Set([]byte(key), cacheValue.value) + } else { + store.Parent.Delete([]byte(key)) + } + } + + // Clear the journal entries + store.journalMgr = journal.NewManager() + + // Clear the cache using the map clearing idiom + // and not allocating fresh objects. + // Please see https://bencher.orijtech.com/perfclinic/mapclearing/ + for key := range store.Cache { + delete(store.Cache, key) + } + for key := range store.UnsortedCache { + delete(store.UnsortedCache, key) + } + + store.SortedCache = trees.NewBTree() +} + +// CacheWrap implements CacheWrapper. +func (store *Store) CacheWrap() storetypes.CacheWrap { + return NewStore(store, store.journalMgr.Clone()) +} + +// CacheWrapWithTrace implements the CacheWrapper interface. +func (store *Store) CacheWrapWithTrace( + w io.Writer, + tc storetypes.TraceContext, +) storetypes.CacheWrap { + return NewStore(tracekv.NewStore(store, w, tc), store.journalMgr.Clone()) +} + +// CacheWrapWithListeners implements the CacheWrapper interface. +func (store *Store) CacheWrapWithListeners( + storeKey storetypes.StoreKey, + listeners []storetypes.WriteListener, +) storetypes.CacheWrap { + return NewStore(listenkv.NewStore(store, storeKey, listeners), store.journalMgr.Clone()) +} + +//================================================ +// Iteration + +// Iterator implements storetypes.KVStore. +func (store *Store) Iterator(start, end []byte) storetypes.Iterator { + return store.iterator(start, end, true) +} + +// ReverseIterator implements storetypes.KVStore. +func (store *Store) ReverseIterator(start, end []byte) storetypes.Iterator { + return store.iterator(start, end, false) +} + +func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { + store.mtx.Lock() + defer store.mtx.Unlock() + + store.dirtyItems(start, end) + isoSortedCache := store.SortedCache.Copy() + + var ( + err error + parent, cache types.Iterator + ) + + if ascending { + parent = store.Parent.Iterator(start, end) + cache, err = isoSortedCache.Iterator(start, end) + } else { + parent = store.Parent.ReverseIterator(start, end) + cache, err = isoSortedCache.ReverseIterator(start, end) + } + if err != nil { + panic(err) + } + + return NewCacheMergeIterator(parent, cache, ascending) +} + +func findStartIndex(strL []string, startQ string) int { + // Modified binary search to find the very first element in >=startQ. + if len(strL) == 0 { + return -1 + } + + var left, right, mid int + right = len(strL) - 1 + for left <= right { + mid = (left + right) >> 1 + midStr := strL[mid] + if midStr == startQ { + // Handle condition where there might be multiple values equal to startQ. + // We are looking for the very first value < midStL, that i+1 will be the first + // element >= midStr. + for i := mid - 1; i >= 0; i-- { + if strL[i] != midStr { + return i + 1 + } + } + return 0 + } + if midStr < startQ { + left = mid + 1 + } else { // midStrL > startQ + right = mid - 1 + } + } + if left >= 0 && left < len(strL) && strL[left] >= startQ { + return left + } + return -1 +} + +func findEndIndex(strL []string, endQ string) int { + if len(strL) == 0 { + return -1 + } + + // Modified binary search to find the very first element > 1 + midStr := strL[mid] + if midStr == endQ { + // Handle condition where there might be multiple values equal to startQ. + // We are looking for the very first value < midStL, that i+1 will be the first + // element >= midStr. + for i := mid - 1; i >= 0; i-- { + if strL[i] < midStr { + return i + 1 + } + } + return 0 + } + if midStr < endQ { + left = mid + 1 + } else { // midStrL > startQ + right = mid - 1 + } + } + + // Binary search failed, now let's find a value less than endQ. + for i := right; i >= 0; i-- { + if strL[i] < endQ { + return i + } + } + + return -1 +} + +type sortState int + +const ( + stateUnsorted sortState = iota + stateAlreadySorted +) + +const minSortSize = 1024 + +// Constructs a slice of dirty items, to use w/ memIterator. +func (store *Store) dirtyItems(start, end []byte) { + startStr, endStr := utils.UnsafeBytesToStr(start), utils.UnsafeBytesToStr(end) + if end != nil && startStr > endStr { + // Nothing to do here. + return + } + + n := len(store.UnsortedCache) + unsorted := make([]*kv.Pair, 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 + // O(N^2) overhead. + // Even without that, too many range checks eventually becomes more expensive + // than just not having the cache. + if n < minSortSize { + for key := range store.UnsortedCache { + // dbm.IsKeyInDomain is nil safe and returns true iff key is greater than start + if dbm.IsKeyInDomain(utils.UnsafeStrToBytes(key), start, end) { + cacheValue := store.Cache[key] + unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value}) + } + } + store.clearUnsortedCacheSubset(unsorted, stateUnsorted) + return + } + + // Otherwise it is large so perform a modified binary search to find + // the target ranges for the keys that we should be looking for. + strL := make([]string, 0, n) + for key := range store.UnsortedCache { + strL = append(strL, key) + } + sort.Strings(strL) + + // Now find the values within the domain + // [start, end) + startIndex := findStartIndex(strL, startStr) + if startIndex < 0 { + startIndex = 0 + } + + var endIndex int + if end == nil { + endIndex = len(strL) - 1 + } else { + endIndex = findEndIndex(strL, endStr) + } + if endIndex < 0 { + endIndex = len(strL) - 1 + } + + // Since we spent cycles to sort the values, we should process and remove a reasonable amount + // ensure start to end is at least minSortSize in size + // if below minSortSize, expand it to cover additional values + // this amortizes the cost of processing elements across multiple calls + if endIndex-startIndex < minSortSize { + endIndex = math.MinInt(startIndex+minSortSize, len(strL)-1) + if endIndex-startIndex < minSortSize { + startIndex = math.MaxInt(endIndex-minSortSize, 0) + } + } + + kvL := make([]*kv.Pair, 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 was already sorted so pass it in as is. + store.clearUnsortedCacheSubset(kvL, stateAlreadySorted) +} + +func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, 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 { + delete(store.UnsortedCache, key) + } + } else { // Otherwise, normally delete the unsorted keys from the map. + for _, kv := range unsorted { + delete(store.UnsortedCache, utils.UnsafeBytesToStr(kv.Key)) + } + } + + if sortState == stateUnsorted { + sort.Slice(unsorted, func(i, j int) bool { + return bytes.Compare(unsorted[i].Key, unsorted[j].Key) < 0 + }) + } + + for _, item := range unsorted { + // sortedCache is able to store `nil` value to represent deleted items. + store.SortedCache.Set(item.Key, item.Value) + } +} + +//================================================ +// etc + +// Only entrypoint to mutate store.Cache. +func (store *Store) setCacheValue(key, value []byte, dirty bool) { + deleted := value == nil + keyStr := utils.UnsafeBytesToStr(key) + + // add cache value (deep copy) to journal manager if dirty (Set or Delete) + if dirty { + var cv journal.CacheEntry + if deleted { + cv = &DeleteCacheValue{ + Store: store, + Key: keyStr, + Prev: store.Cache[keyStr], + } + } else { + cv = &SetCacheValue{ + Store: store, + Key: keyStr, + Prev: store.Cache[keyStr], + } + } + store.journalMgr.Push(cv.Clone()) + } + + store.Cache[keyStr] = &cValue{ + value: value, + dirty: dirty, + } + if dirty { + store.UnsortedCache[keyStr] = struct{}{} + } +} diff --git a/store/cachekv/store_test.go b/store/cachekv/store_test.go new file mode 100644 index 000000000..9918e342e --- /dev/null +++ b/store/cachekv/store_test.go @@ -0,0 +1,608 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachekv_test + +import ( + "bytes" + "fmt" + "testing" + + sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/stretchr/testify/require" + tmrand "github.com/tendermint/tendermint/libs/rand" + dbm "github.com/tendermint/tm-db" + + "github.com/berachain/stargazer/store/cachekv" + "github.com/berachain/stargazer/store/journal" +) + +func newParent() types.CacheKVStore { + return sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) +} + +func newCacheKVStoreFromParent(parent types.CacheKVStore) types.CacheKVStore { + return cachekv.NewStore(parent, journal.NewManager()) +} + +func TestSdkConsistency(t *testing.T) { + sdkParent := dbadapter.Store{DB: dbm.NewMemDB()} + sdkCacheKV := sdkcachekv.NewStore(sdkParent) + parent := newParent() + cacheKV := newCacheKVStoreFromParent(parent) + + // do an op, test the iterator + for i := 0; i < 2000; i++ { + doRandomOp(t, cacheKV, sdkCacheKV, 1000) + assertIterateDomainCompare(t, cacheKV, sdkCacheKV) + } +} + +func TestGetStoreType(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + require.Equal(t, parent.GetStoreType(), st.GetStoreType()) +} + +func TestHas(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + st.Set(keyFmt(1), keyFmt(1)) + require.True(t, st.Has(keyFmt(1))) + require.False(t, parent.Has(keyFmt(1))) + st.Write() + require.True(t, parent.Has(keyFmt(1))) +} + +func TestCacheKVReverseIterator(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + + // Use the parent to check values on the merge iterator + setRange(t, st, parent, 0, 40) + st.Write() + + itr1 := st.ReverseIterator(nil, nil) + itr2 := parent.ReverseIterator(nil, nil) + checkIterators(t, itr1, itr2) +} + +func TestCacheWrap(t *testing.T) { + st := cachekv.NewStore(newParent(), journal.NewManager()) + + // test before initializing cache wraps + st.Set(keyFmt(1), valFmt(1)) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + + stWrap := st.CacheWrap().(types.KVStore) + stTrace := st.CacheWrapWithTrace( + bytes.NewBuffer(nil), + types.TraceContext(map[string]interface{}{"blockHeight": 64}), + ).(types.KVStore) + stListeners := st.CacheWrapWithListeners( + types.NewKVStoreKey("acc"), + []types.WriteListener{}, + ).(types.KVStore) + + // test after initializing cache wraps + st.Set(keyFmt(2), valFmt(2)) + require.Equal(t, valFmt(2), st.Get(keyFmt(2))) + + // both keys 1 and 2 should be set in all cache wraps + require.Equal(t, valFmt(1), stWrap.Get(keyFmt(1))) + require.Equal(t, valFmt(1), stTrace.Get(keyFmt(1))) + require.Equal(t, valFmt(1), stListeners.Get(keyFmt(1))) + require.Equal(t, valFmt(2), stWrap.Get(keyFmt(2))) + require.Equal(t, valFmt(2), stTrace.Get(keyFmt(2))) + require.Equal(t, valFmt(2), stListeners.Get(keyFmt(2))) +} + +// Tests below taken from Cosmos SDK and adapted to our custom CacheKVStore to ensure +// the same logical behavior + +func bz(s string) []byte { return []byte(s) } + +func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } + +func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } + +func TestCacheKVStore(t *testing.T) { + parent := newParent() + st := newCacheKVStoreFromParent(parent) + + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + + // put something in parent and in cache + parent.Set(keyFmt(1), valFmt(1)) + st.Set(keyFmt(1), valFmt(1)) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + + // update it in cache, shoudn't change parent + st.Set(keyFmt(1), valFmt(2)) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + require.Equal(t, valFmt(1), parent.Get(keyFmt(1))) + + // write it. should change parent + st.Write() + require.Equal(t, valFmt(2), parent.Get(keyFmt(1))) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // more writes and checks + st.Write() + st.Write() + require.Equal(t, valFmt(2), parent.Get(keyFmt(1))) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // make a new one, check it + st = newCacheKVStoreFromParent(parent) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // make a new one and delete - should not be removed from parent + st = newCacheKVStoreFromParent(parent) + st.Delete(keyFmt(1)) + require.Empty(t, st.Get(keyFmt(1))) + require.Equal(t, parent.Get(keyFmt(1)), valFmt(2)) + + // Write. should now be removed from both + st.Write() + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + require.Empty(t, parent.Get(keyFmt(1)), "Expected `key1` to be empty") +} + +func TestCacheKVStoreNoNilSet(t *testing.T) { + parent := newParent() + st := newCacheKVStoreFromParent(parent) + require.Panics(t, func() { st.Set([]byte("key"), nil) }, "setting a nil value should panic") + require.Panics(t, func() { st.Set(nil, []byte("value")) }, "setting a nil key should panic") + require.Panics( + t, + func() { st.Set([]byte(""), []byte("value")) }, + "setting an empty key should panic", + ) +} + +func TestCacheKVStoreNested(t *testing.T) { + parent := newParent() + st := newCacheKVStoreFromParent(parent) + + // set. check its there on st and not on parent. + st.Set(keyFmt(1), valFmt(1)) + require.Empty(t, parent.Get(keyFmt(1))) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + + // make a new from st and check + st2 := sdkcachekv.NewStore(st) + require.Equal(t, valFmt(1), st2.Get(keyFmt(1))) + + // update the value on st2, check it only effects st2 + st2.Set(keyFmt(1), valFmt(3)) + require.Equal(t, []byte(nil), parent.Get(keyFmt(1))) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + require.Equal(t, valFmt(3), st2.Get(keyFmt(1))) + + // st2 writes to its parent, st. doesnt effect parent + st2.Write() + require.Equal(t, []byte(nil), parent.Get(keyFmt(1))) + require.Equal(t, valFmt(3), st.Get(keyFmt(1))) + + // updates parent + st.Write() + require.Equal(t, valFmt(3), parent.Get(keyFmt(1))) +} + +func TestCacheKVIteratorBounds(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + + // set some items + nItems := 5 + for i := 0; i < nItems; i++ { + st.Set(keyFmt(i), valFmt(i)) + } + + // iterate over all of them + itr := st.Iterator(nil, nil) + i := 0 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, nItems, i) + + // iterate over none + itr = st.Iterator(bz("money"), nil) + i = 0 + for ; itr.Valid(); itr.Next() { + i++ + } + require.Equal(t, 0, i) + + // iterate over lower + itr = st.Iterator(keyFmt(0), keyFmt(3)) + i = 0 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, 3, i) + + // iterate over upper + itr = st.Iterator(keyFmt(2), keyFmt(4)) + i = 2 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, 4, i) +} + +func TestCacheKVMergeIteratorBasics(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + + // set and delete an item in the cache, iterator should be empty + k, v := keyFmt(0), valFmt(0) + st.Set(k, v) + st.Delete(k) + assertIterateDomain(t, st, 0) + + // now set it and assert its there + st.Set(k, v) + assertIterateDomain(t, st, 1) + + // write it and assert its there + st.Write() + assertIterateDomain(t, st, 1) + + // remove it in cache and assert its not + st.Delete(k) + assertIterateDomain(t, st, 0) + + // write the delete and assert its not there + st.Write() + assertIterateDomain(t, st, 0) + + // add two keys and assert theyre there + k1, v1 := keyFmt(1), valFmt(1) + st.Set(k, v) + st.Set(k1, v1) + assertIterateDomain(t, st, 2) + + // write it and assert theyre there + st.Write() + assertIterateDomain(t, st, 2) + + // remove one in cache and assert its not + st.Delete(k1) + assertIterateDomain(t, st, 1) + + // write the delete and assert its not there + st.Write() + assertIterateDomain(t, st, 1) + + // delete the other key in cache and asserts its empty + st.Delete(k) + assertIterateDomain(t, st, 0) +} + +func TestCacheKVMergeIteratorDeleteLast(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + + // set some items and write them + nItems := 5 + for i := 0; i < nItems; i++ { + st.Set(keyFmt(i), valFmt(i)) + } + st.Write() + + // set some more items and leave dirty + for i := nItems; i < nItems*2; i++ { + st.Set(keyFmt(i), valFmt(i)) + } + + // iterate over all of them + assertIterateDomain(t, st, nItems*2) + + // delete them all + for i := 0; i < nItems*2; i++ { + last := nItems*2 - 1 - i + st.Delete(keyFmt(last)) + assertIterateDomain(t, st, last) + } +} + +func TestCacheKVMergeIteratorDeletes(t *testing.T) { + parent := newParent() + st := newCacheKVStoreFromParent(parent) + + // set some items and write them + nItems := 10 + for i := 0; i < nItems; i++ { + doOp(t, st, parent, opSet, i) + } + st.Write() + + // delete every other item, starting from 0 + for i := 0; i < nItems; i += 2 { + doOp(t, st, parent, opDel, i) + assertIterateDomainCompare(t, st, parent) + } + + // reset + parent = newParent() + st = newCacheKVStoreFromParent(parent) + + // set some items and write them + for i := 0; i < nItems; i++ { + doOp(t, st, parent, opSet, i) + } + st.Write() + + // delete every other item, starting from 1 + for i := 1; i < nItems; i += 2 { + doOp(t, st, parent, opDel, i) + assertIterateDomainCompare(t, st, parent) + } +} + +func TestCacheKVMergeIteratorChunks(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + + // Use the parent to check values on the merge iterator + // sets to the parent + setRange(t, st, parent, 0, 20) + setRange(t, st, parent, 40, 60) + st.Write() + + // sets to the cache + setRange(t, st, parent, 20, 40) + setRange(t, st, parent, 60, 80) + assertIterateDomainCheck(t, st, parent, []keyRange{{0, 80}}) + + // remove some parents and some cache + deleteRange(t, st, parent, 15, 25) + assertIterateDomainCheck(t, st, parent, []keyRange{{0, 15}, {25, 80}}) + + // remove some parents and some cache + deleteRange(t, st, parent, 35, 45) + assertIterateDomainCheck(t, st, parent, []keyRange{{0, 15}, {25, 35}, {45, 80}}) + + // write, add more to the cache, and delete some cache + st.Write() + setRange(t, st, parent, 38, 42) + deleteRange(t, st, parent, 40, 43) + assertIterateDomainCheck(t, st, parent, []keyRange{{0, 15}, {25, 35}, {38, 40}, {45, 80}}) +} + +func TestCacheKVMergeIteratorRandom(t *testing.T) { + parent := newParent() + st := cachekv.NewStore(parent, journal.NewManager()) + + start, end := 25, 975 + max := 1000 + setRange(t, st, parent, start, end) + + // do an op, test the iterator + for i := 0; i < 2000; i++ { + doRandomOp(t, st, parent, max) + assertIterateDomainCompare(t, st, parent) + } +} + +//============================================================- +// do some random ops + +const ( + opSet = 0 + opSetRange = 1 + opDel = 2 + opDelRange = 3 + opWrite = 4 + + totalOps = 5 // number of possible operations +) + +func randInt(n int) int { + return tmrand.NewRand().Int() % n +} + +// useful for replaying a error case if we find one +func doOp(t *testing.T, st types.CacheKVStore, truth types.KVStore, op int, args ...int) { + switch op { + case opSet: + k := args[0] + st.Set(keyFmt(k), valFmt(k)) + truth.Set(keyFmt(k), valFmt(k)) + case opSetRange: + start := args[0] + end := args[1] + setRange(t, st, truth, start, end) + case opDel: + k := args[0] + st.Delete(keyFmt(k)) + truth.Delete(keyFmt(k)) + case opDelRange: + start := args[0] + end := args[1] + deleteRange(t, st, truth, start, end) + case opWrite: + st.Write() + } +} + +func doRandomOp(t *testing.T, st types.CacheKVStore, truth types.KVStore, maxKey int) { + r := randInt(totalOps) + switch r { + case opSet: + k := randInt(maxKey) + st.Set(keyFmt(k), valFmt(k)) + truth.Set(keyFmt(k), valFmt(k)) + case opSetRange: + start := randInt(maxKey - 2) + end := randInt(maxKey-start) + start + setRange(t, st, truth, start, end) + case opDel: + k := randInt(maxKey) + st.Delete(keyFmt(k)) + truth.Delete(keyFmt(k)) + case opDelRange: + start := randInt(maxKey - 2) + end := randInt(maxKey-start) + start + deleteRange(t, st, truth, start, end) + case opWrite: + st.Write() + } +} + +//============================================================- + +// iterate over whole domain +func assertIterateDomain(t *testing.T, st types.KVStore, expectedN int) { + itr := st.Iterator(nil, nil) + i := 0 + for ; itr.Valid(); itr.Next() { + k, v := itr.Key(), itr.Value() + require.Equal(t, keyFmt(i), k) + require.Equal(t, valFmt(i), v) + i++ + } + require.Equal(t, expectedN, i) +} + +func assertIterateDomainCheck(t *testing.T, st types.KVStore, mem types.KVStore, r []keyRange) { + // iterate over each and check they match the other + itr := st.Iterator(nil, nil) + itr2 := mem.Iterator(nil, nil) // ground truth + + krc := newKeyRangeCounter(r) + i := 0 + + for ; krc.valid(); krc.next() { + require.True(t, itr.Valid()) + require.True(t, itr2.Valid()) + + // check the key/val matches the ground truth + k, v := itr.Key(), itr.Value() + k2, v2 := itr2.Key(), itr2.Value() + require.Equal(t, k, k2) + require.Equal(t, v, v2) + + // check they match the counter + require.Equal(t, k, keyFmt(krc.key())) + + itr.Next() + itr2.Next() + i++ + } + + require.False(t, itr.Valid()) + require.False(t, itr2.Valid()) +} + +func assertIterateDomainCompare(t *testing.T, st types.KVStore, mem types.KVStore) { + // iterate over each and check they match the other + itr := st.Iterator(nil, nil) + itr2 := mem.Iterator(nil, nil) // ground truth + checkIterators(t, itr, itr2) + checkIterators(t, itr2, itr) +} + +func checkIterators(t *testing.T, itr, itr2 types.Iterator) { + for ; itr.Valid(); itr.Next() { + require.True(t, itr2.Valid()) + k, v := itr.Key(), itr.Value() + k2, v2 := itr2.Key(), itr2.Value() + require.Equal(t, k, k2) + require.Equal(t, v, v2) + itr2.Next() + } + require.False(t, itr.Valid()) + require.False(t, itr2.Valid()) +} + +//====================================-- + +func setRange(_ *testing.T, st types.KVStore, mem types.KVStore, start, end int) { + for i := start; i < end; i++ { + st.Set(keyFmt(i), valFmt(i)) + mem.Set(keyFmt(i), valFmt(i)) + } +} + +func deleteRange(_ *testing.T, st types.KVStore, mem types.KVStore, start, end int) { + for i := start; i < end; i++ { + st.Delete(keyFmt(i)) + mem.Delete(keyFmt(i)) + } +} + +//====================================-- + +type keyRange struct { + start int + end int +} + +func (kr keyRange) len() int { + return kr.end - kr.start +} + +func newKeyRangeCounter(kr []keyRange) *keyRangeCounter { + return &keyRangeCounter{keyRanges: kr} +} + +// we can iterate over this and make sure our real iterators have all the right keys +type keyRangeCounter struct { + rangeIdx int + idx int + keyRanges []keyRange +} + +func (krc *keyRangeCounter) valid() bool { + maxRangeIdx := len(krc.keyRanges) - 1 + maxRange := krc.keyRanges[maxRangeIdx] + + // if we're not in the max range, we're valid + if krc.rangeIdx <= maxRangeIdx && + krc.idx < maxRange.len() { + return true + } + + return false +} + +func (krc *keyRangeCounter) next() { + thisKeyRange := krc.keyRanges[krc.rangeIdx] + if krc.idx == thisKeyRange.len()-1 { + krc.rangeIdx++ + krc.idx = 0 + } else { + krc.idx++ + } +} + +func (krc *keyRangeCounter) key() int { + thisKeyRange := krc.keyRanges[krc.rangeIdx] + return thisKeyRange.start + krc.idx +} From 7c9e9acb98480810f732db1aaf4179e30377b694 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:19:52 -0500 Subject: [PATCH 17/76] improvements --- build/.golangci.yaml | 1 - lib/ds/trees/btree.go | 4 +-- lib/ds/trees/memiterator.go | 2 +- store/cachekv/cache_value.go | 40 +++++++++++++++++-------- store/cachekv/cache_values.go | 48 ++++++++++++++---------------- store/cachekv/cache_values_test.go | 16 ++++++---- store/cachekv/mergeiterator.go | 27 +++++++++++++---- store/cachekv/store.go | 30 ++++++++++--------- store/cachekv/store_test.go | 27 +++++++++-------- 9 files changed, 116 insertions(+), 79 deletions(-) diff --git a/build/.golangci.yaml b/build/.golangci.yaml index 01ee1d03e..f829e1282 100644 --- a/build/.golangci.yaml +++ b/build/.golangci.yaml @@ -226,7 +226,6 @@ linters: - usestdlibvars # detects the possibility to use variables/constants from the Go standard library - wastedassign # finds wasted assignment statements - whitespace # detects leading and trailing whitespace - - wrapcheck # checks that errors returned from external packages are wrapped ## you may want to enable #- decorder # checks declaration order and count of types, constants, variables and functions diff --git a/lib/ds/trees/btree.go b/lib/ds/trees/btree.go index 83fbc6961..d42e7ad34 100644 --- a/lib/ds/trees/btree.go +++ b/lib/ds/trees/btree.go @@ -70,7 +70,7 @@ func (bt BTree) Iterator(start, end []byte) (dbm.Iterator, error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, ErrKeyEmpty } - return NewMemIterator(start, end, bt, true), nil + return newMemIterator(start, end, bt, true), nil } //nolint:nolintlint,ireturn @@ -78,7 +78,7 @@ func (bt BTree) ReverseIterator(start, end []byte) (dbm.Iterator, error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, ErrKeyEmpty } - return NewMemIterator(start, end, bt, false), nil + return newMemIterator(start, end, bt, false), nil } // Copy the tree. This is a copy-on-write operation and is very fast because diff --git a/lib/ds/trees/memiterator.go b/lib/ds/trees/memiterator.go index 1385ba15d..06da990cf 100644 --- a/lib/ds/trees/memiterator.go +++ b/lib/ds/trees/memiterator.go @@ -36,7 +36,7 @@ type memIterator struct { valid bool } -func NewMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { +func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { iter := items.tree.Iter() var valid bool //nolint:nestif // from sdk. diff --git a/store/cachekv/cache_value.go b/store/cachekv/cache_value.go index d202c6f5a..4a15b326d 100644 --- a/store/cachekv/cache_value.go +++ b/store/cachekv/cache_value.go @@ -1,35 +1,51 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + package cachekv -// cValue represents a cached value. +// `CValue` represents a cached value. // If dirty is true, it indicates the cached value is different from the underlying value. -type cValue struct { +type CValue struct { value []byte dirty bool } -//nolint:revive -func NewCValue(v []byte, d bool) *cValue { - return &cValue{ +// `NewCValue` creates a new CValue object with the given value and dirty flag. +func NewCValue(v []byte, d bool) *CValue { + return &CValue{ value: v, dirty: d, } } -func (cv *cValue) Dirty() bool { +// `Dirty` returns the dirty flag of the CValue object. +func (cv *CValue) Dirty() bool { return cv.dirty } -func (cv *cValue) Value() []byte { +// `Value` returns the value of the CValue object. +func (cv *CValue) Value() []byte { return cv.value } -// deepCopy creates a new cValue object with the same value and dirty flag as the original -// cValue object. This function is used to create a deep copy of the prev field in +// `deepCopy` creates a new CValue object with the same value and dirty flag as the original +// CValue object. This function is used to create a deep copy of the prev field in // DeleteCacheValue and SetCacheValue objects, so that modifications to the original prev value do // not affect the cloned DeleteCacheValue or SetCacheValue object. -func (cv *cValue) deepCopy() *cValue { - // Return a new cValue with the same value and dirty flag - return &cValue{ +func (cv *CValue) deepCopy() *CValue { + // Return a new CValue with the same value and dirty flag + return &CValue{ value: append([]byte(nil), cv.value...), dirty: cv.dirty, } diff --git a/store/cachekv/cache_values.go b/store/cachekv/cache_values.go index 1742fef70..fe52bf464 100644 --- a/store/cachekv/cache_values.go +++ b/store/cachekv/cache_values.go @@ -12,6 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// nolint: ireturn // by design. package cachekv import "github.com/berachain/stargazer/store/journal" @@ -22,41 +23,38 @@ type ( DeleteCacheValue struct { Store *Store // pointer to the cache Store Key string // Key of the value to be deleted - Prev *cValue // deep copy of object in cache map + Prev *CValue // deep copy of object in cache map } // SetCacheValue is a struct that contains information needed to set a value in a cache. SetCacheValue struct { Store *Store // pointer to the cache Store Key string // Key of the value to be set - Prev *cValue // deep copy of object in cache map + Prev *CValue // deep copy of object in cache map } ) // Revert restores the previous cache entry for the Key, if it exists. // -// implements journal.CacheEntry +// implements journal.CacheEntry. func (dcv *DeleteCacheValue) Revert() { - // Check if there is a previous entry for the Key being deleted - if dcv.Prev != nil { + //nolint: gocritic // by design. + if dcv.Prev == nil { + delete(dcv.Store.Cache, dcv.Key) + delete(dcv.Store.UnsortedCache, dcv.Key) + return + } else if !dcv.Prev.dirty { + // If there is no previous entry for the Key being deleted, remove the Key from the cache // If the previous entry is not dirty // (i.e., it has not been modified since it was added to the cache) - if !dcv.Prev.dirty { - // Remove the Key being deleted from the cache and restore the previous entry - delete(dcv.Store.UnsortedCache, dcv.Key) - dcv.Store.Cache[dcv.Key] = dcv.Prev - } else { - // If the previous entry is dirty and has a non-nil value - if dcv.Prev.value != nil { - // Remove the Key being deleted from the "deleted" set and restore the - // previous entry to the cache - dcv.Store.Cache[dcv.Key] = dcv.Prev - } - } - } else { - // If there is no previous entry for the Key being deleted, remove the Key from the cache - delete(dcv.Store.Cache, dcv.Key) + // Remove the Key being deleted from the cache and restore the previous entry delete(dcv.Store.UnsortedCache, dcv.Key) + dcv.Store.Cache[dcv.Key] = dcv.Prev + } else if dcv.Prev.value != nil { + // If the previous entry is dirty and has a non-nil value + // Remove the Key being deleted from the "deleted" set and restore the + // previous entry to the cache + dcv.Store.Cache[dcv.Key] = dcv.Prev } } @@ -64,10 +62,10 @@ func (dcv *DeleteCacheValue) Revert() { // The deep copy contains the same Store and Key fields as the original, // and a deep copy of the Prev field, if it is not nil. // -// implements journal.CacheEntry +// implements journal.CacheEntry. func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *cValue + var prevDeepCopy *CValue if dcv.Prev != nil { prevDeepCopy = dcv.Prev.deepCopy() } @@ -85,7 +83,7 @@ func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Revert reverts a set operation on a cache entry by setting the previous value of the entry as // the current value in the cache map. // -// implements journal.CacheEntry +// implements journal.CacheEntry. func (scv *SetCacheValue) Revert() { // If there was a previous value, set it as the current value in the cache map if scv.Prev != nil { @@ -106,10 +104,10 @@ func (scv *SetCacheValue) Revert() { // The deep copy contains the same Store and Key fields as the original, // and a deep copy of the Prev field, if it is not nil. // -// implements journal.CacheEntry +// implements journal.CacheEntry. func (scv *SetCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *cValue + var prevDeepCopy *CValue if scv.Prev != nil { prevDeepCopy = scv.Prev.deepCopy() } diff --git a/store/cachekv/cache_values_test.go b/store/cachekv/cache_values_test.go index 6a46f36d9..ba6c42449 100644 --- a/store/cachekv/cache_values_test.go +++ b/store/cachekv/cache_values_test.go @@ -163,7 +163,8 @@ func (s *CacheValueSuite) TestCloneDelete() { Prev: cachekv.NewCValue(byte1, true), } - dcvNonNilClone := dcvNonNil.Clone().(*cachekv.DeleteCacheValue) + dcvNonNilClone, ok := dcvNonNil.Clone().(*cachekv.DeleteCacheValue) + s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) s.Require().True(dcvNonNilClone.Prev.Dirty()) s.Require().Equal(byte1, dcvNonNilClone.Prev.Value()) @@ -173,10 +174,11 @@ func (s *CacheValueSuite) TestCloneDelete() { Key: "", Prev: nil, } - dcvNilClone := dcvNil.Clone().(*cachekv.DeleteCacheValue) + dcvNilClone, ok := dcvNil.Clone().(*cachekv.DeleteCacheValue) + s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) - // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) + // s.Require().Equal((*CValue)(nil), dcvNilClone.Prev) } func (s *CacheValueSuite) TestCloneSet() { @@ -185,7 +187,8 @@ func (s *CacheValueSuite) TestCloneSet() { Key: byte1Str, Prev: cachekv.NewCValue(byte1, true), } - dcvNonNilClone := dcvNonNil.Clone().(*cachekv.SetCacheValue) + dcvNonNilClone, ok := dcvNonNil.Clone().(*cachekv.SetCacheValue) + s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) s.Require().True(dcvNonNilClone.Prev.Dirty()) s.Require().Equal(byte1, dcvNonNilClone.Prev.Value()) @@ -195,8 +198,9 @@ func (s *CacheValueSuite) TestCloneSet() { Key: "", Prev: nil, } - dcvNilClone := dcvNil.Clone().(*cachekv.SetCacheValue) + dcvNilClone, ok := dcvNil.Clone().(*cachekv.SetCacheValue) + s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) - // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) + // s.Require().Equal((*CValue)(nil), dcvNilClone.Prev) s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) } diff --git a/store/cachekv/mergeiterator.go b/store/cachekv/mergeiterator.go index 0e17116c0..ccb96fe6c 100644 --- a/store/cachekv/mergeiterator.go +++ b/store/cachekv/mergeiterator.go @@ -1,3 +1,17 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + package cachekv import ( @@ -24,7 +38,8 @@ type cacheMergeIterator struct { var _ types.Iterator = (*cacheMergeIterator)(nil) -func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheMergeIterator { //nolint:revive +// `NewCacheMergeIterator` creates a new cacheMergeIterator. +func newCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheMergeIterator { iter := &cacheMergeIterator{ parent: parent, cache: cache, @@ -37,7 +52,7 @@ func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) *cacheM // Domain implements Iterator. // Returns parent domain because cache and parent domains are the same. -func (iter *cacheMergeIterator) Domain() (start, end []byte) { +func (iter *cacheMergeIterator) Domain() ([]byte, []byte) { return iter.parent.Domain() } @@ -46,7 +61,7 @@ func (iter *cacheMergeIterator) Valid() bool { return iter.valid } -// Next implements Iterator +// Next implements Iterator. func (iter *cacheMergeIterator) Next() { iter.assertValid() @@ -73,7 +88,7 @@ func (iter *cacheMergeIterator) Next() { iter.valid = iter.skipUntilExistsOrInvalid() } -// Key implements Iterator +// Key implements Iterator. func (iter *cacheMergeIterator) Key() []byte { iter.assertValid() @@ -103,7 +118,7 @@ func (iter *cacheMergeIterator) Key() []byte { } } -// Value implements Iterator +// Value implements Iterator. func (iter *cacheMergeIterator) Value() []byte { iter.assertValid() @@ -133,7 +148,7 @@ func (iter *cacheMergeIterator) Value() []byte { } } -// Close implements Iterator +// Close implements Iterator. func (iter *cacheMergeIterator) Close() error { err1 := iter.cache.Close() if err := iter.parent.Close(); err != nil { diff --git a/store/cachekv/store.go b/store/cachekv/store.go index 0e49a8824..dcdbdce61 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -11,7 +11,8 @@ // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - +// +//nolint:ireturn // cachekv needs to return interfaces. package cachekv import ( @@ -42,20 +43,20 @@ var _ StateDBCacheKVStore = (*Store)(nil) // Store wraps an in-memory cache around an underlying storetypes.KVStore. // If a cached value is nil but deleted is defined for the corresponding key, -// it means the parent doesn't have the key. (No need to delete upon Write()) +// it means the parent doesn't have the key. (No need to delete upon Write()). type Store struct { mtx sync.RWMutex - Cache map[string]*cValue + Cache map[string]*CValue UnsortedCache map[string]struct{} SortedCache *trees.BTree // always ascending sorted Parent storetypes.KVStore journalMgr *journal.Manager } -// NewStore creates a new Store object +// NewStore creates a new Store object. func NewStore(parent storetypes.KVStore, journalMgr *journal.Manager) *Store { return &Store{ - Cache: make(map[string]*cValue), + Cache: make(map[string]*CValue), UnsortedCache: make(map[string]struct{}), SortedCache: trees.NewBTree(), Parent: parent, @@ -74,7 +75,8 @@ func (store *Store) GetStoreType() storetypes.StoreType { // Get implements storetypes.KVStore. // This function retrieves a value associated with the specified key in the store. -func (store *Store) Get(key []byte) (value []byte) { +func (store *Store) Get(key []byte) []byte { + var bz []byte store.mtx.RLock() defer store.mtx.RUnlock() // Assert that the key is valid. @@ -83,13 +85,13 @@ func (store *Store) Get(key []byte) (value []byte) { // Check if the key is in the store's cache. cacheValue, ok := store.Cache[utils.UnsafeBytesToStr(key)] if !ok { - value = store.Parent.Get(key) - store.setCacheValue(key, value, false) + bz = store.Parent.Get(key) + store.setCacheValue(key, bz, false) } else { - value = cacheValue.value + bz = cacheValue.value } - return value + return bz } func (store *Store) GetParent() storetypes.KVStore { @@ -194,7 +196,7 @@ func (store *Store) CacheWrapWithListeners( return NewStore(listenkv.NewStore(store, storeKey, listeners), store.journalMgr.Clone()) } -//================================================ +// ================================================ // Iteration // Iterator implements storetypes.KVStore. @@ -230,7 +232,7 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { panic(err) } - return NewCacheMergeIterator(parent, cache, ascending) + return newCacheMergeIterator(parent, cache, ascending) } func findStartIndex(strL []string, startQ string) int { @@ -414,7 +416,7 @@ func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sort } } -//================================================ +// ================================================ // etc // Only entrypoint to mutate store.Cache. @@ -441,7 +443,7 @@ func (store *Store) setCacheValue(key, value []byte, dirty bool) { store.journalMgr.Push(cv.Clone()) } - store.Cache[keyStr] = &cValue{ + store.Cache[keyStr] = &CValue{ value: value, dirty: dirty, } diff --git a/store/cachekv/store_test.go b/store/cachekv/store_test.go index 9918e342e..0119e8e89 100644 --- a/store/cachekv/store_test.go +++ b/store/cachekv/store_test.go @@ -30,11 +30,11 @@ import ( "github.com/berachain/stargazer/store/journal" ) -func newParent() types.CacheKVStore { +func newParent() types.CacheKVStore { //nolint: ireturn // by design. return sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) } -func newCacheKVStoreFromParent(parent types.CacheKVStore) types.CacheKVStore { +func newCacheKVStoreFromParent(parent types.CacheKVStore) types.CacheKVStore { //nolint: ireturn // by design. return cachekv.NewStore(parent, journal.NewManager()) } @@ -87,15 +87,18 @@ func TestCacheWrap(t *testing.T) { st.Set(keyFmt(1), valFmt(1)) require.Equal(t, valFmt(1), st.Get(keyFmt(1))) - stWrap := st.CacheWrap().(types.KVStore) - stTrace := st.CacheWrapWithTrace( + stWrap, ok := st.CacheWrap().(types.KVStore) + require.True(t, ok) + stTrace, ok := st.CacheWrapWithTrace( bytes.NewBuffer(nil), types.TraceContext(map[string]interface{}{"blockHeight": 64}), ).(types.KVStore) - stListeners := st.CacheWrapWithListeners( + require.True(t, ok) + stListeners, ok := st.CacheWrapWithListeners( types.NewKVStoreKey("acc"), []types.WriteListener{}, ).(types.KVStore) + require.True(t, ok) // test after initializing cache wraps st.Set(keyFmt(2), valFmt(2)) @@ -410,7 +413,7 @@ func TestCacheKVMergeIteratorRandom(t *testing.T) { } } -//============================================================- +// ============================================================- // do some random ops const ( @@ -427,7 +430,7 @@ func randInt(n int) int { return tmrand.NewRand().Int() % n } -// useful for replaying a error case if we find one +// useful for replaying a error case if we find one. func doOp(t *testing.T, st types.CacheKVStore, truth types.KVStore, op int, args ...int) { switch op { case opSet: @@ -475,9 +478,9 @@ func doRandomOp(t *testing.T, st types.CacheKVStore, truth types.KVStore, maxKey } } -//============================================================- +// ============================================================- -// iterate over whole domain +// iterate over whole domain. func assertIterateDomain(t *testing.T, st types.KVStore, expectedN int) { itr := st.Iterator(nil, nil) i := 0 @@ -541,7 +544,7 @@ func checkIterators(t *testing.T, itr, itr2 types.Iterator) { require.False(t, itr2.Valid()) } -//====================================-- +// ====================================-- func setRange(_ *testing.T, st types.KVStore, mem types.KVStore, start, end int) { for i := start; i < end; i++ { @@ -557,7 +560,7 @@ func deleteRange(_ *testing.T, st types.KVStore, mem types.KVStore, start, end i } } -//====================================-- +// ====================================-- type keyRange struct { start int @@ -572,7 +575,7 @@ func newKeyRangeCounter(kr []keyRange) *keyRangeCounter { return &keyRangeCounter{keyRanges: kr} } -// we can iterate over this and make sure our real iterators have all the right keys +// we can iterate over this and make sure our real iterators have all the right keys. type keyRangeCounter struct { rangeIdx int idx int From b8beaeb71e0c2b04fdfaa80fd4914679a67af5c4 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:22:02 -0500 Subject: [PATCH 18/76] optimize control flow --- store/cachekv/cache_values.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/store/cachekv/cache_values.go b/store/cachekv/cache_values.go index fe52bf464..b1167b0be 100644 --- a/store/cachekv/cache_values.go +++ b/store/cachekv/cache_values.go @@ -12,7 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// nolint: ireturn // by design. +//nolint: ireturn // by design. package cachekv import "github.com/berachain/stargazer/store/journal" @@ -86,17 +86,17 @@ func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // implements journal.CacheEntry. func (scv *SetCacheValue) Revert() { // If there was a previous value, set it as the current value in the cache map - if scv.Prev != nil { - scv.Store.Cache[scv.Key] = scv.Prev - // If the previous value was not dirty, remove the Key from the unsorted cache set - if !scv.Prev.dirty { - delete(scv.Store.UnsortedCache, scv.Key) - } - } else { + if scv.Prev == nil { // If there was no previous value, remove the Key from the // cache map and the unsorted cache set delete(scv.Store.Cache, scv.Key) delete(scv.Store.UnsortedCache, scv.Key) + return + } + scv.Store.Cache[scv.Key] = scv.Prev + // If the previous value was not dirty, remove the Key from the unsorted cache set + if !scv.Prev.dirty { + delete(scv.Store.UnsortedCache, scv.Key) } } From 4e28d4b06a610bcac29af026c619df87a15c8421 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:23:58 -0500 Subject: [PATCH 19/76] cvalue --- store/cachekv/cache_value.go | 28 ++++++++++++++-------------- store/cachekv/cache_values.go | 8 ++++---- store/cachekv/cache_values_test.go | 4 ++-- store/cachekv/store.go | 6 +++--- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/store/cachekv/cache_value.go b/store/cachekv/cache_value.go index 4a15b326d..167943ad8 100644 --- a/store/cachekv/cache_value.go +++ b/store/cachekv/cache_value.go @@ -14,38 +14,38 @@ package cachekv -// `CValue` represents a cached value. +// `cValue` represents a cached value. // If dirty is true, it indicates the cached value is different from the underlying value. -type CValue struct { +type cValue struct { value []byte dirty bool } -// `NewCValue` creates a new CValue object with the given value and dirty flag. -func NewCValue(v []byte, d bool) *CValue { - return &CValue{ +// `NewCValue` creates a new cValue object with the given value and dirty flag. +func NewCValue(v []byte, d bool) *cValue { //nolint: revive // todo:revist. + return &cValue{ value: v, dirty: d, } } -// `Dirty` returns the dirty flag of the CValue object. -func (cv *CValue) Dirty() bool { +// `Dirty` returns the dirty flag of the cValue object. +func (cv *cValue) Dirty() bool { return cv.dirty } -// `Value` returns the value of the CValue object. -func (cv *CValue) Value() []byte { +// `Value` returns the value of the cValue object. +func (cv *cValue) Value() []byte { return cv.value } -// `deepCopy` creates a new CValue object with the same value and dirty flag as the original -// CValue object. This function is used to create a deep copy of the prev field in +// `deepCopy` creates a new cValue object with the same value and dirty flag as the original +// cValue object. This function is used to create a deep copy of the prev field in // DeleteCacheValue and SetCacheValue objects, so that modifications to the original prev value do // not affect the cloned DeleteCacheValue or SetCacheValue object. -func (cv *CValue) deepCopy() *CValue { - // Return a new CValue with the same value and dirty flag - return &CValue{ +func (cv *cValue) deepCopy() *cValue { + // Return a new cValue with the same value and dirty flag + return &cValue{ value: append([]byte(nil), cv.value...), dirty: cv.dirty, } diff --git a/store/cachekv/cache_values.go b/store/cachekv/cache_values.go index b1167b0be..649cbee20 100644 --- a/store/cachekv/cache_values.go +++ b/store/cachekv/cache_values.go @@ -23,14 +23,14 @@ type ( DeleteCacheValue struct { Store *Store // pointer to the cache Store Key string // Key of the value to be deleted - Prev *CValue // deep copy of object in cache map + Prev *cValue // deep copy of object in cache map } // SetCacheValue is a struct that contains information needed to set a value in a cache. SetCacheValue struct { Store *Store // pointer to the cache Store Key string // Key of the value to be set - Prev *CValue // deep copy of object in cache map + Prev *cValue // deep copy of object in cache map } ) @@ -65,7 +65,7 @@ func (dcv *DeleteCacheValue) Revert() { // implements journal.CacheEntry. func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *CValue + var prevDeepCopy *cValue if dcv.Prev != nil { prevDeepCopy = dcv.Prev.deepCopy() } @@ -107,7 +107,7 @@ func (scv *SetCacheValue) Revert() { // implements journal.CacheEntry. func (scv *SetCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *CValue + var prevDeepCopy *cValue if scv.Prev != nil { prevDeepCopy = scv.Prev.deepCopy() } diff --git a/store/cachekv/cache_values_test.go b/store/cachekv/cache_values_test.go index ba6c42449..88075d7b8 100644 --- a/store/cachekv/cache_values_test.go +++ b/store/cachekv/cache_values_test.go @@ -178,7 +178,7 @@ func (s *CacheValueSuite) TestCloneDelete() { s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) - // s.Require().Equal((*CValue)(nil), dcvNilClone.Prev) + // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) } func (s *CacheValueSuite) TestCloneSet() { @@ -201,6 +201,6 @@ func (s *CacheValueSuite) TestCloneSet() { dcvNilClone, ok := dcvNil.Clone().(*cachekv.SetCacheValue) s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) - // s.Require().Equal((*CValue)(nil), dcvNilClone.Prev) + // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) } diff --git a/store/cachekv/store.go b/store/cachekv/store.go index dcdbdce61..bd17379ea 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -46,7 +46,7 @@ var _ StateDBCacheKVStore = (*Store)(nil) // it means the parent doesn't have the key. (No need to delete upon Write()). type Store struct { mtx sync.RWMutex - Cache map[string]*CValue + Cache map[string]*cValue UnsortedCache map[string]struct{} SortedCache *trees.BTree // always ascending sorted Parent storetypes.KVStore @@ -56,7 +56,7 @@ type Store struct { // NewStore creates a new Store object. func NewStore(parent storetypes.KVStore, journalMgr *journal.Manager) *Store { return &Store{ - Cache: make(map[string]*CValue), + Cache: make(map[string]*cValue), UnsortedCache: make(map[string]struct{}), SortedCache: trees.NewBTree(), Parent: parent, @@ -443,7 +443,7 @@ func (store *Store) setCacheValue(key, value []byte, dirty bool) { store.journalMgr.Push(cv.Clone()) } - store.Cache[keyStr] = &CValue{ + store.Cache[keyStr] = &cValue{ value: value, dirty: dirty, } From fb19474a33833ab26fd25c4c1738db78066c32fe Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:26:36 -0500 Subject: [PATCH 20/76] bing bong --- store/cachekv/cache_values.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/store/cachekv/cache_values.go b/store/cachekv/cache_values.go index 649cbee20..498617762 100644 --- a/store/cachekv/cache_values.go +++ b/store/cachekv/cache_values.go @@ -11,8 +11,6 @@ // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -//nolint: ireturn // by design. package cachekv import "github.com/berachain/stargazer/store/journal" @@ -63,6 +61,8 @@ func (dcv *DeleteCacheValue) Revert() { // and a deep copy of the Prev field, if it is not nil. // // implements journal.CacheEntry. +// +//nolint:ireturn // by design. func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil var prevDeepCopy *cValue @@ -105,6 +105,8 @@ func (scv *SetCacheValue) Revert() { // and a deep copy of the Prev field, if it is not nil. // // implements journal.CacheEntry. +// +//nolint:ireturn // by design. func (scv *SetCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil var prevDeepCopy *cValue From 29e490c77f3058e8743042e9b0c9436ff1bb6abb Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:29:49 -0500 Subject: [PATCH 21/76] lint --- store/cachekv/cache_values.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/store/cachekv/cache_values.go b/store/cachekv/cache_values.go index 498617762..2abef081a 100644 --- a/store/cachekv/cache_values.go +++ b/store/cachekv/cache_values.go @@ -36,20 +36,25 @@ type ( // // implements journal.CacheEntry. func (dcv *DeleteCacheValue) Revert() { - //nolint: gocritic // by design. + // If the previous entry is nil, remove the Key from the cache if dcv.Prev == nil { delete(dcv.Store.Cache, dcv.Key) delete(dcv.Store.UnsortedCache, dcv.Key) return - } else if !dcv.Prev.dirty { - // If there is no previous entry for the Key being deleted, remove the Key from the cache - // If the previous entry is not dirty - // (i.e., it has not been modified since it was added to the cache) + } + + // If there is no previous entry for the Key being deleted, remove the Key from the cache + // If the previous entry is not dirty + // (i.e., it has not been modified since it was added to the cache) + if !dcv.Prev.dirty { // Remove the Key being deleted from the cache and restore the previous entry delete(dcv.Store.UnsortedCache, dcv.Key) dcv.Store.Cache[dcv.Key] = dcv.Prev - } else if dcv.Prev.value != nil { - // If the previous entry is dirty and has a non-nil value + return + } + + // If the previous entry is dirty and has a non-nil value + if dcv.Prev.value != nil { // Remove the Key being deleted from the "deleted" set and restore the // previous entry to the cache dcv.Store.Cache[dcv.Key] = dcv.Prev @@ -62,7 +67,7 @@ func (dcv *DeleteCacheValue) Revert() { // // implements journal.CacheEntry. // -//nolint:ireturn // by design. +//nolint:nolintlint,ireturn // by design. func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil var prevDeepCopy *cValue @@ -93,7 +98,10 @@ func (scv *SetCacheValue) Revert() { delete(scv.Store.UnsortedCache, scv.Key) return } + + // If there was a previous value, set it as the current value in the cache map. scv.Store.Cache[scv.Key] = scv.Prev + // If the previous value was not dirty, remove the Key from the unsorted cache set if !scv.Prev.dirty { delete(scv.Store.UnsortedCache, scv.Key) @@ -106,7 +114,7 @@ func (scv *SetCacheValue) Revert() { // // implements journal.CacheEntry. // -//nolint:ireturn // by design. +//nolint:nolintlint,ireturn // by design. func (scv *SetCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil var prevDeepCopy *cValue From e6ef005396e81ce16f7c994797f74f0c6aeac051 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:35:00 -0500 Subject: [PATCH 22/76] bing bong --- store/cachekv/cache_values.go | 32 ++++++++++++++++---------------- store/cachekv/mergeiterator.go | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/store/cachekv/cache_values.go b/store/cachekv/cache_values.go index 2abef081a..09ce7ed6b 100644 --- a/store/cachekv/cache_values.go +++ b/store/cachekv/cache_values.go @@ -16,25 +16,25 @@ package cachekv import "github.com/berachain/stargazer/store/journal" type ( - // DeleteCacheValue is a struct that contains information needed to delete a value + // `DeleteCacheValue` is a struct that contains information needed to delete a value // from a cache. DeleteCacheValue struct { - Store *Store // pointer to the cache Store - Key string // Key of the value to be deleted - Prev *cValue // deep copy of object in cache map + Store *Store // Pointer to the cache store. + Key string // Key of the value to be deleted. + Prev *cValue // Deep copy of object in cache map. } - // SetCacheValue is a struct that contains information needed to set a value in a cache. + // `SetCacheValue` is a struct that contains information needed to set a value in a cache. SetCacheValue struct { - Store *Store // pointer to the cache Store - Key string // Key of the value to be set - Prev *cValue // deep copy of object in cache map + Store *Store // Pointer to the cache store. + Key string // Key of the value to be set. + Prev *cValue // Deep copy of object in cache map. } ) -// Revert restores the previous cache entry for the Key, if it exists. +// `Revert` restores the previous cache entry for the Key, if it exists. // -// implements journal.CacheEntry. +// `Revert` implements journal.CacheEntry. func (dcv *DeleteCacheValue) Revert() { // If the previous entry is nil, remove the Key from the cache if dcv.Prev == nil { @@ -61,11 +61,11 @@ func (dcv *DeleteCacheValue) Revert() { } } -// Clone creates a deep copy of the DeleteCacheValue object. +// `Clone` creates a deep copy of the DeleteCacheValue object. // The deep copy contains the same Store and Key fields as the original, // and a deep copy of the Prev field, if it is not nil. // -// implements journal.CacheEntry. +// `Clone` implements journal.CacheEntry. // //nolint:nolintlint,ireturn // by design. func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { @@ -85,10 +85,10 @@ func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { } } -// Revert reverts a set operation on a cache entry by setting the previous value of the entry as +// `Revert` reverts a set operation on a cache entry by setting the previous value of the entry as // the current value in the cache map. // -// implements journal.CacheEntry. +// `Revert` implements journal.CacheEntry. func (scv *SetCacheValue) Revert() { // If there was a previous value, set it as the current value in the cache map if scv.Prev == nil { @@ -108,11 +108,11 @@ func (scv *SetCacheValue) Revert() { } } -// Clone creates a deep copy of the SetCacheValue object. +// `Clone` creates a deep copy of the SetCacheValue object. // The deep copy contains the same Store and Key fields as the original, // and a deep copy of the Prev field, if it is not nil. // -// implements journal.CacheEntry. +// `Clone` implements `journal.CacheEntry`. // //nolint:nolintlint,ireturn // by design. func (scv *SetCacheValue) Clone() journal.CacheEntry { diff --git a/store/cachekv/mergeiterator.go b/store/cachekv/mergeiterator.go index ccb96fe6c..66f5937f1 100644 --- a/store/cachekv/mergeiterator.go +++ b/store/cachekv/mergeiterator.go @@ -21,7 +21,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/types" ) -// cacheMergeIterator merges a parent Iterator and a cache Iterator. +// `cacheMergeIterator` merges a parent Iterator and a cache Iterator. // The cache iterator may return nil keys to signal that an item // had been deleted (but not deleted in the parent). // If the cache iterator has the same key as the parent, the From d1d4d565a58eb0b2cad804ace941c4eccf9bc675 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 12:45:19 -0500 Subject: [PATCH 23/76] cleanup test commands a bit --- build/mage/alias.go | 5 +++-- build/mage/test.go | 27 +++++---------------------- core/state/types/types_test.go | 2 +- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/build/mage/alias.go b/build/mage/alias.go index 58ed4b1a0..6b4d8c4c7 100644 --- a/build/mage/alias.go +++ b/build/mage/alias.go @@ -23,8 +23,9 @@ var ( goBuild = mi.RunCmdV("go", "build", "-mod=readonly") goRun = mi.RunCmdV("go", "run") goTest = mi.RunCmdV("go", "test", "-mod=readonly") - ginkgoTestCover = mi.RunCmdV("ginkgo", "-r", "--randomize-all", - "--randomize-suites", "--fail-on-pending", "-trace", "--junit-report", "out.xml") + ginkgoTest = mi.RunCmdV("ginkgo", "-r", "--randomize-all", "--fail-on-pending", "-trace") + ginkgoCoverArgs = []string{"--junit-report", "out.xml", "--cover", + "--coverprofile", "testUnitCover.txt", "--covermode", "atomic"} goGenerate = mi.RunCmdV("go", "generate") goModVerify = mi.RunCmdV("go", "mod", "verify") goModTidy = mi.RunCmdV("go", "mod", "tidy") diff --git a/build/mage/test.go b/build/mage/test.go index 837a06186..8f53b234c 100644 --- a/build/mage/test.go +++ b/build/mage/test.go @@ -56,17 +56,14 @@ func Test() error { // Runs the unit tests. func TestUnit() error { - if err := ForgeBuild(); err != nil { - return err - } + // if err := ForgeBuild(); err != nil { + // return err + // } return testUnit() } func testUnit() error { - args := []string{} - return goTest( - append(args, packagesUnit...)..., - ) + return ginkgoTest() } // Runs the unit tests with coverage. @@ -74,21 +71,7 @@ func TestUnitCover() error { // if err := ForgeBuild(); err != nil { // return err // } - if err := ginkgoTestCover(); err != nil { - return err - } - - return testUnitCover() -} - -func testUnitCover() error { - args := []string{ - "-coverprofile=coverage-testUnitCover.txt", - "-covermode=atomic", - } - return goTest( - append(args, packagesUnit...)..., - ) + return ginkgoTest(ginkgoCoverArgs...) } // Runs the unit tests with race detection. diff --git a/core/state/types/types_test.go b/core/state/types/types_test.go index 6a9c160bd..42c31724c 100644 --- a/core/state/types/types_test.go +++ b/core/state/types/types_test.go @@ -23,5 +23,5 @@ import ( func TestStateTypes(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "StateTypes Test Suite") + RunSpecs(t, "core/state/types") } From 35ead991f8ff447ef30494c064fea02035479c49 Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Wed, 11 Jan 2023 13:08:03 -0500 Subject: [PATCH 24/76] Update lib/ds/stack.go Co-authored-by: Cal Bera Signed-off-by: Devon Bear --- lib/ds/stack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ds/stack.go b/lib/ds/stack.go index bff54fd15..aabe5a435 100644 --- a/lib/ds/stack.go +++ b/lib/ds/stack.go @@ -15,7 +15,7 @@ //nolint:ireturn // StackI uses generics. package ds -// `StackI` is an interfai that defines the methods that a items Stack must implement. +// `StackI` is an interface that defines the methods that an items Stack must implement. // items Stacks support holding cache entries and reverting to a irtain index. type StackI[Item any] interface { // `Peek` returns the Item at the top of the stack From f4cd91536e1b32bb6547c31807a66aba2f08fabb Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Wed, 11 Jan 2023 13:08:08 -0500 Subject: [PATCH 25/76] Update lib/ds/stack.go Co-authored-by: Cal Bera Signed-off-by: Devon Bear --- lib/ds/stack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ds/stack.go b/lib/ds/stack.go index aabe5a435..3c8f07962 100644 --- a/lib/ds/stack.go +++ b/lib/ds/stack.go @@ -16,7 +16,7 @@ package ds // `StackI` is an interface that defines the methods that an items Stack must implement. -// items Stacks support holding cache entries and reverting to a irtain index. +// items Stacks support holding cache entries and reverting to a certain index. type StackI[Item any] interface { // `Peek` returns the Item at the top of the stack Peek() Item From 1a11eac1414dc2d7e4810f04dfa6c74b7fca982f Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 13:15:46 -0500 Subject: [PATCH 26/76] typo --- lib/ds/stack.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ds/stack.go b/lib/ds/stack.go index 3c8f07962..f22c783d3 100644 --- a/lib/ds/stack.go +++ b/lib/ds/stack.go @@ -41,12 +41,12 @@ type StackI[Item any] interface { // Compile-time check to ensure `Stack` implements `StackI`. var _ StackI[any] = (*Stack[any])(nil) -// `Stack` is a struct that holds a slii of Item instanis. +// `Stack` is a struct that holds a slice of Items. type Stack[Item any] struct { items []Item } -// `NewStack` creates and returns a new Stack instani with an empty items. +// `NewStack` creates and returns a new `Stack` with an no items. func NewStack[Item any]() *Stack[Item] { return &Stack[Item]{ items: make([]Item, 0), From 9d09be4d7d47b149a42cc7d36a1b5a9fcc7c7003 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 13:18:55 -0500 Subject: [PATCH 27/76] comments --- store/journal/manager.go | 82 ++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/store/journal/manager.go b/store/journal/manager.go index b846cdf00..d3a832d66 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -11,18 +11,27 @@ // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//nolint:ireturn // ManagerI returns interfaces by design. package journal -import ( - "github.com/berachain/stargazer/lib/ds" - "github.com/berachain/stargazer/types" -) +import "github.com/berachain/stargazer/types" // `ManagerI` is an interface that defines the methods that a journal manager must implement. -// Journal managers support holding cache entries and reverting to a certain index. +// Journal managers support holding `CacheEntry`s and reverting to a certain index. type ManagerI[T any] interface { - // The journal manager is a stack. - ds.StackI[CacheEntry] + // `Append` adds a new `CacheEntry` instance to the journal. The Size method returns the current + // number of entries in the journal. + Append(ce CacheEntry) + + // `Size` returns the current number of entries in the journal. + Size() int + + // `Get` returns the `CacheEntry` instance at the given index. + Get(i int) CacheEntry + + // `RevertToSize` reverts and discards all journal entries after and including the given size. + RevertToSize(newSize int) // `ManagerI` implements `Cloneable`. types.Cloneable[T] @@ -31,34 +40,65 @@ type ManagerI[T any] interface { // Compile-time check to ensure `Manager` implements `ManagerI`. var _ ManagerI[*Manager] = (*Manager)(nil) -// `Manager` is a struct that holds a slice of CacheEntry instances. +// `Manager` is a struct that holds a slice of `CacheEntry` instances. type Manager struct { - *ds.Stack[CacheEntry] + journal []CacheEntry } // `NewManager` creates and returns a new Manager instance with an empty journal. func NewManager() *Manager { return &Manager{ - ds.NewStack[CacheEntry](), + journal: make([]CacheEntry, 0), } } -// `PopToSize` implements `StackI`. -func (jm *Manager) PopToSize(newSize int) { +// `Append` implements `ManagerI`. +func (jm *Manager) Append(ce CacheEntry) { + jm.journal = append(jm.journal, ce) +} + +// `Size` implements `ManagerI`. +func (jm *Manager) Size() int { + return len(jm.journal) +} + +// `Get` returns nil if index `i` is invalid. +// +// `Get` implements `ManagerI`. +func (jm *Manager) Get(i int) CacheEntry { + if i < 0 || i >= len(jm.journal) { + return nil + } + return jm.journal[i] +} + +// `RevertToSize` does not modify the journal if `newSize` is invalid. +// +// `RevertToSize` implements `ManagerI`. +func (jm *Manager) RevertToSize(newSize int) { + if newSize > len(jm.journal) { + return + } + // Revert and discard all journal entries after and including newSize. - for i := jm.Size() - 1; i >= newSize; i-- { - jm.Stack.PeekAt(i).Revert() + for j := len(jm.journal) - 1; j >= newSize; j-- { + jm.journal[j].Revert() } - // Call parent. - jm.Stack.PopToSize(newSize) + + // Discard all journal entries after and including newSize, such that now + // len(jm.journal) == newSize. + jm.journal = jm.journal[:newSize] } -// `Clone` returns a cloned journal by deep copying each CacheEntry. +// `Clone` returns a cloned journal by deep copying each `CacheEntry`. +// // `Clone` implements `ManagerI[*Manager]`. func (jm *Manager) Clone() *Manager { - newManager := NewManager() - for i := 0; i < jm.Size(); i++ { - newManager.Push(jm.PeekAt(i).Clone()) + newJournal := make([]CacheEntry, len(jm.journal)) + for i := 0; i < len(jm.journal); i++ { + newJournal[i] = jm.journal[i].Clone() + } + return &Manager{ + journal: newJournal, } - return newManager } From cd3e74ead624825219a3b89cd5a3f3f6985536b1 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 13:19:33 -0500 Subject: [PATCH 28/76] Revert "comments" This reverts commit 9d09be4d7d47b149a42cc7d36a1b5a9fcc7c7003. --- store/journal/manager.go | 82 ++++++++++------------------------------ 1 file changed, 21 insertions(+), 61 deletions(-) diff --git a/store/journal/manager.go b/store/journal/manager.go index d3a832d66..b846cdf00 100644 --- a/store/journal/manager.go +++ b/store/journal/manager.go @@ -11,27 +11,18 @@ // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//nolint:ireturn // ManagerI returns interfaces by design. package journal -import "github.com/berachain/stargazer/types" +import ( + "github.com/berachain/stargazer/lib/ds" + "github.com/berachain/stargazer/types" +) // `ManagerI` is an interface that defines the methods that a journal manager must implement. -// Journal managers support holding `CacheEntry`s and reverting to a certain index. +// Journal managers support holding cache entries and reverting to a certain index. type ManagerI[T any] interface { - // `Append` adds a new `CacheEntry` instance to the journal. The Size method returns the current - // number of entries in the journal. - Append(ce CacheEntry) - - // `Size` returns the current number of entries in the journal. - Size() int - - // `Get` returns the `CacheEntry` instance at the given index. - Get(i int) CacheEntry - - // `RevertToSize` reverts and discards all journal entries after and including the given size. - RevertToSize(newSize int) + // The journal manager is a stack. + ds.StackI[CacheEntry] // `ManagerI` implements `Cloneable`. types.Cloneable[T] @@ -40,65 +31,34 @@ type ManagerI[T any] interface { // Compile-time check to ensure `Manager` implements `ManagerI`. var _ ManagerI[*Manager] = (*Manager)(nil) -// `Manager` is a struct that holds a slice of `CacheEntry` instances. +// `Manager` is a struct that holds a slice of CacheEntry instances. type Manager struct { - journal []CacheEntry + *ds.Stack[CacheEntry] } // `NewManager` creates and returns a new Manager instance with an empty journal. func NewManager() *Manager { return &Manager{ - journal: make([]CacheEntry, 0), + ds.NewStack[CacheEntry](), } } -// `Append` implements `ManagerI`. -func (jm *Manager) Append(ce CacheEntry) { - jm.journal = append(jm.journal, ce) -} - -// `Size` implements `ManagerI`. -func (jm *Manager) Size() int { - return len(jm.journal) -} - -// `Get` returns nil if index `i` is invalid. -// -// `Get` implements `ManagerI`. -func (jm *Manager) Get(i int) CacheEntry { - if i < 0 || i >= len(jm.journal) { - return nil - } - return jm.journal[i] -} - -// `RevertToSize` does not modify the journal if `newSize` is invalid. -// -// `RevertToSize` implements `ManagerI`. -func (jm *Manager) RevertToSize(newSize int) { - if newSize > len(jm.journal) { - return - } - +// `PopToSize` implements `StackI`. +func (jm *Manager) PopToSize(newSize int) { // Revert and discard all journal entries after and including newSize. - for j := len(jm.journal) - 1; j >= newSize; j-- { - jm.journal[j].Revert() + for i := jm.Size() - 1; i >= newSize; i-- { + jm.Stack.PeekAt(i).Revert() } - - // Discard all journal entries after and including newSize, such that now - // len(jm.journal) == newSize. - jm.journal = jm.journal[:newSize] + // Call parent. + jm.Stack.PopToSize(newSize) } -// `Clone` returns a cloned journal by deep copying each `CacheEntry`. -// +// `Clone` returns a cloned journal by deep copying each CacheEntry. // `Clone` implements `ManagerI[*Manager]`. func (jm *Manager) Clone() *Manager { - newJournal := make([]CacheEntry, len(jm.journal)) - for i := 0; i < len(jm.journal); i++ { - newJournal[i] = jm.journal[i].Clone() - } - return &Manager{ - journal: newJournal, + newManager := NewManager() + for i := 0; i < jm.Size(); i++ { + newManager.Push(jm.PeekAt(i).Clone()) } + return newManager } From 2e168ad3c9e5a7f685446b9d04ff9eda7ba69208 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 13:21:25 -0500 Subject: [PATCH 29/76] comments --- core/state/types/types_test.go | 2 +- store/journal/manager_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/state/types/types_test.go b/core/state/types/types_test.go index 6a9c160bd..42c31724c 100644 --- a/core/state/types/types_test.go +++ b/core/state/types/types_test.go @@ -23,5 +23,5 @@ import ( func TestStateTypes(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "StateTypes Test Suite") + RunSpecs(t, "core/state/types") } diff --git a/store/journal/manager_test.go b/store/journal/manager_test.go index 5ae5c9925..947a83bda 100644 --- a/store/journal/manager_test.go +++ b/store/journal/manager_test.go @@ -43,7 +43,7 @@ var _ = Describe("Journal", func() { } }) - When("the journal is Pushed to", func() { + When("the journal is pushed to", func() { BeforeEach(func() { jm.Push(entries[0]) }) @@ -62,7 +62,7 @@ var _ = Describe("Journal", func() { }) }) - When("the journal is Pushed to 9 more times", func() { + When("the journal is pushed to 9 more times", func() { BeforeEach(func() { for i := 1; i <= 9; i++ { jm.Push(entries[i]) From cfd316db1f1030f7ba164539067212fa78449e64 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 14:13:03 -0500 Subject: [PATCH 30/76] lint --- core/state/types/state.go | 2 +- core/state/types/state.pb.go | 2 +- core/state/types/storage.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/state/types/state.go b/core/state/types/state.go index 74f6b2b2a..f4d6827c1 100644 --- a/core/state/types/state.go +++ b/core/state/types/state.go @@ -15,7 +15,7 @@ package types import ( - fmt "fmt" + "fmt" "strings" "cosmossdk.io/errors" diff --git a/core/state/types/state.pb.go b/core/state/types/state.pb.go index 445ca65f7..d6f7d8c3e 100644 --- a/core/state/types/state.pb.go +++ b/core/state/types/state.pb.go @@ -4,7 +4,7 @@ package types import ( - fmt "fmt" + "fmt" io "io" math "math" math_bits "math/bits" diff --git a/core/state/types/storage.go b/core/state/types/storage.go index b4b1880dd..3a9352dc6 100644 --- a/core/state/types/storage.go +++ b/core/state/types/storage.go @@ -15,7 +15,7 @@ package types import ( - fmt "fmt" + "fmt" "cosmossdk.io/errors" "github.com/berachain/stargazer/types" From 3ea6816731b80a51944db9ea1cd0281b87090aaa Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Wed, 11 Jan 2023 18:06:07 -0500 Subject: [PATCH 31/76] Apply suggestions from code review Co-authored-by: Cal Bera Signed-off-by: Devon Bear --- lib/ds/stack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ds/stack.go b/lib/ds/stack.go index f22c783d3..6f3ecca2c 100644 --- a/lib/ds/stack.go +++ b/lib/ds/stack.go @@ -31,7 +31,7 @@ type StackI[Item any] interface { // `Pop` returns the Item at the top of the stack and removes it from the stack. Pop() Item - // `RevertToSize` reverts and discards all items entries after and including the given size. + // `PopToSize` discards all items entries after and including the given size. PopToSize(newSize int) // `Size` returns the current number of entries in the items. From 08ce21f0c1419cb99c15cfa67a1df68d9f4e919b Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 21:08:30 -0500 Subject: [PATCH 32/76] bing bong --- .../state/store}/cachekv/cache_value.go | 0 .../state/store}/cachekv/cache_values.go | 2 +- .../state/store}/cachekv/cache_values_test.go | 4 +- core/state/store/cachekv/evm_store.go | 68 ++++++++++ core/state/store/cachekv/evm_store_test.go | 116 ++++++++++++++++++ .../state/store}/cachekv/mergeiterator.go | 0 {store => core/state/store}/cachekv/store.go | 2 +- .../state/store}/cachekv/store_test.go | 4 +- core/state/store/cachemulti/store.go | 76 ++++++++++++ core/state/store/cachemulti/store_test.go | 106 ++++++++++++++++ .../state/store}/journal/cache_entry.go | 0 .../state/store}/journal/manager.go | 0 .../state/store}/journal/manager_test.go | 4 +- .../store}/journal/mock/cache_entry.mock.go | 2 +- core/state/types/types.go | 17 +++ store/utils/utils.go | 42 ------- 16 files changed, 392 insertions(+), 51 deletions(-) rename {store => core/state/store}/cachekv/cache_value.go (100%) rename {store => core/state/store}/cachekv/cache_values.go (98%) rename {store => core/state/store}/cachekv/cache_values_test.go (98%) create mode 100644 core/state/store/cachekv/evm_store.go create mode 100644 core/state/store/cachekv/evm_store_test.go rename {store => core/state/store}/cachekv/mergeiterator.go (100%) rename {store => core/state/store}/cachekv/store.go (99%) rename {store => core/state/store}/cachekv/store_test.go (99%) create mode 100644 core/state/store/cachemulti/store.go create mode 100644 core/state/store/cachemulti/store_test.go rename {store => core/state/store}/journal/cache_entry.go (100%) rename {store => core/state/store}/journal/manager.go (100%) rename {store => core/state/store}/journal/manager_test.go (96%) rename {store => core/state/store}/journal/mock/cache_entry.mock.go (96%) create mode 100644 core/state/types/types.go delete mode 100644 store/utils/utils.go diff --git a/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go similarity index 100% rename from store/cachekv/cache_value.go rename to core/state/store/cachekv/cache_value.go diff --git a/store/cachekv/cache_values.go b/core/state/store/cachekv/cache_values.go similarity index 98% rename from store/cachekv/cache_values.go rename to core/state/store/cachekv/cache_values.go index 09ce7ed6b..32e44ade6 100644 --- a/store/cachekv/cache_values.go +++ b/core/state/store/cachekv/cache_values.go @@ -13,7 +13,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package cachekv -import "github.com/berachain/stargazer/store/journal" +import "github.com/berachain/stargazer/core/state/store/journal" type ( // `DeleteCacheValue` is a struct that contains information needed to delete a value diff --git a/store/cachekv/cache_values_test.go b/core/state/store/cachekv/cache_values_test.go similarity index 98% rename from store/cachekv/cache_values_test.go rename to core/state/store/cachekv/cache_values_test.go index 88075d7b8..46be3466a 100644 --- a/store/cachekv/cache_values_test.go +++ b/core/state/store/cachekv/cache_values_test.go @@ -23,9 +23,9 @@ import ( "github.com/stretchr/testify/suite" dbm "github.com/tendermint/tm-db" + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/journal" "github.com/berachain/stargazer/lib/utils" - "github.com/berachain/stargazer/store/cachekv" - "github.com/berachain/stargazer/store/journal" ) var ( diff --git a/core/state/store/cachekv/evm_store.go b/core/state/store/cachekv/evm_store.go new file mode 100644 index 000000000..fb94f29e4 --- /dev/null +++ b/core/state/store/cachekv/evm_store.go @@ -0,0 +1,68 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachekv + +import ( + storetypes "github.com/cosmos/cosmos-sdk/store/types" + + "github.com/berachain/stargazer/core/state/store/journal" + "github.com/berachain/stargazer/lib/utils" +) + +var _ storetypes.CacheKVStore = (*EvmStore)(nil) + +// Avoid the mutex lock for EVM stores (codes/storage) +// Writes to the EVM are thread-safe because the EVM interpreter is guaranteed to be single +// threaded. All entry points to the EVM check that only a single execution context is running. +type EvmStore struct { + *Store +} + +// NewEvmStore creates a new Store object. +func NewEvmStore(parent storetypes.KVStore, journalMgr *journal.Manager) *EvmStore { + return &EvmStore{ + NewStore(parent, journalMgr), + } +} + +// Get shadows Store.Get +// This function retrieves a value associated with the specified key in the store. +func (store *EvmStore) Get(key []byte) []byte { + var bz []byte + // Check if the key is in the store's cache. + cacheValue, ok := store.Cache[utils.UnsafeBytesToStr(key)] + if ok { + // If the key is in the cache, return the value. + return cacheValue.value + } + + // If the key is not found in the cache, query the parent store. + bz = store.Parent.Get(key) + + // Add the key-value pair to the cache. + store.setCacheValue(key, bz, false) + + return bz +} + +// Set shadows Store.Set. +func (store *EvmStore) Set(key []byte, value []byte) { + store.setCacheValue(key, value, true) +} + +// Delete shadows Store.Delete. +func (store *EvmStore) Delete(key []byte) { + store.setCacheValue(key, nil, true) +} diff --git a/core/state/store/cachekv/evm_store_test.go b/core/state/store/cachekv/evm_store_test.go new file mode 100644 index 000000000..fb7defe18 --- /dev/null +++ b/core/state/store/cachekv/evm_store_test.go @@ -0,0 +1,116 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachekv_test + +import ( + "testing" + + sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + dbm "github.com/tendermint/tm-db" + + "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/journal" +) + +var ( + zeroCodeHash = common.Hash{} + nonZeroCodeHash = common.BytesToHash([]byte{0x05}) +) + +type EvmStoreSuite struct { + suite.Suite + parent storetypes.KVStore + evmStore *cachekv.EvmStore +} + +func TestEvmStoreSuite(t *testing.T) { + suite.Run(t, new(EvmStoreSuite)) + require.True(t, true) +} + +func (s *EvmStoreSuite) SetupTest() { + parent := sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) + s.parent = parent + s.evmStore = cachekv.NewEvmStore(s.parent, journal.NewManager()) +} + +// TODO: determine if this is allowable behavior, should be fine? +func (s *EvmStoreSuite) TestWarmSlotVia0() { + s.SetupTest() + + // set [key: 1, val: zeroCodeHash] + s.evmStore.Set(byte1, zeroCodeHash.Bytes()) + // write Store + s.evmStore.Write() + + require.Equal(s.T(), zeroCodeHash.Bytes(), s.parent.Get(byte1)) +} + +// parent != nil, set value == zeroCodeHash -> should write key ?? +func (s *EvmStoreSuite) TestWriteZeroValParentNotNil() { + s.SetupTest() + + // set [key: 0, val: zeroCodeHash] + s.evmStore.Set(byte0, zeroCodeHash.Bytes()) + // write Store + s.evmStore.Write() + // check written + require.Equal(s.T(), zeroCodeHash.Bytes(), s.parent.Get(byte0)) +} + +// parent == nil, set value != zeroCodeHash -> should write key. +func (s *EvmStoreSuite) TestWriteNonZeroValParentNil() { + s.SetupTest() + + // set [key: 1, val: nonZeroCodeHash] + s.evmStore.Set(byte1, nonZeroCodeHash.Bytes()) + // write Store + s.evmStore.Write() + // check written + require.Equal(s.T(), nonZeroCodeHash.Bytes(), s.parent.Get(byte1)) +} + +// parent != nil, set value != zeroCodeHash -> should write key. +func (s *EvmStoreSuite) TestWriteNonZeroValParentNotNil() { + s.SetupTest() + + // set [key: 0, val: nonZeroCodeHash] + s.evmStore.Set(byte0, nonZeroCodeHash.Bytes()) + // write Store + s.evmStore.Write() + // check written + require.Equal(s.T(), nonZeroCodeHash.Bytes(), s.parent.Get(byte0)) +} + +func (s *EvmStoreSuite) TestWriteAfterDelete() { + s.SetupTest() + + // parent store has [key: 1, value: NIL] (does not contain key: 1) + // set [key: 1, val: zeroCodeHash] to cache evm store + s.evmStore.Set(byte1, zeroCodeHash.Bytes()) + // the cache evm store SHOULD have value set (to zeroCodeHash) for key: 1 + require.Equal(s.T(), zeroCodeHash.Bytes(), s.evmStore.Get(byte1)) + // delete [key: 1, val: zeroCodeHash] from cache evm store + s.evmStore.Delete(byte1) + require.False(s.T(), s.evmStore.Has(byte1)) + s.evmStore.Write() + // parent store SHOULD NOT be written to; stll [key: 1, value: NIL] + require.False(s.T(), s.parent.Has(byte1)) +} diff --git a/store/cachekv/mergeiterator.go b/core/state/store/cachekv/mergeiterator.go similarity index 100% rename from store/cachekv/mergeiterator.go rename to core/state/store/cachekv/mergeiterator.go diff --git a/store/cachekv/store.go b/core/state/store/cachekv/store.go similarity index 99% rename from store/cachekv/store.go rename to core/state/store/cachekv/store.go index bd17379ea..6b475e782 100644 --- a/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -29,9 +29,9 @@ import ( "github.com/tendermint/tendermint/libs/math" dbm "github.com/tendermint/tm-db" + "github.com/berachain/stargazer/core/state/store/journal" "github.com/berachain/stargazer/lib/ds/trees" "github.com/berachain/stargazer/lib/utils" - "github.com/berachain/stargazer/store/journal" ) type StateDBCacheKVStore interface { diff --git a/store/cachekv/store_test.go b/core/state/store/cachekv/store_test.go similarity index 99% rename from store/cachekv/store_test.go rename to core/state/store/cachekv/store_test.go index 0119e8e89..a29dddad7 100644 --- a/store/cachekv/store_test.go +++ b/core/state/store/cachekv/store_test.go @@ -26,8 +26,8 @@ import ( tmrand "github.com/tendermint/tendermint/libs/rand" dbm "github.com/tendermint/tm-db" - "github.com/berachain/stargazer/store/cachekv" - "github.com/berachain/stargazer/store/journal" + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/journal" ) func newParent() types.CacheKVStore { //nolint: ireturn // by design. diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go new file mode 100644 index 000000000..7ae535c3c --- /dev/null +++ b/core/state/store/cachemulti/store.go @@ -0,0 +1,76 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachemulti + +import ( + storetypes "github.com/cosmos/cosmos-sdk/store/types" + + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/journal" + statetypes "github.com/berachain/stargazer/core/state/types" +) + +var _ storetypes.CacheMultiStore = (*Store)(nil) + +type Store struct { + storetypes.MultiStore + stores map[storetypes.StoreKey]storetypes.CacheKVStore + JournalMgr *journal.Manager +} + +func NewStoreFrom(ms storetypes.MultiStore) *Store { + return &Store{ + MultiStore: ms, + stores: make(map[storetypes.StoreKey]storetypes.CacheKVStore), + + JournalMgr: journal.NewManager(), + } +} + +// GetKVStore shadows cosmos sdk storetypes.MultiStore function. Routes native module calls to +// read the dirty state during an eth tx. Any state that is modified by evm statedb, and using the +// context passed in to StateDB, will be routed to a tx-specific cache kv store. +func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolint:ireturn // must return a CacheKVStore. + // check if cache kv store already used + if cacheKVStore, exists := s.stores[key]; exists { + return cacheKVStore + } + // get kvstore from cachemultistore and set cachekv to memory + kvstore := s.MultiStore.GetKVStore(key) + s.stores[key] = s.newCacheKVStore(key, kvstore) + return s.stores[key] +} + +// implements cosmos sdk storetypes.CacheMultiStore +// Write commits each of the individual cachekv stores to its corresponding parent kv stores. +func (s *Store) Write() { + // Safe from non-determinism, since order in which + // we write to the parent kv stores does not matter. + // + //#nosec:G705 + for _, cacheKVStore := range s.stores { + cacheKVStore.Write() + } +} + +func (s *Store) newCacheKVStore( //nolint:ireturn // must return a CacheKVStore. + key storetypes.StoreKey, + kvstore storetypes.KVStore, +) storetypes.CacheKVStore { + if key.Name() == statetypes.EvmStoreKey { + return cachekv.NewEvmStore(kvstore, s.JournalMgr) + } + return cachekv.NewStore(kvstore, s.JournalMgr) +} diff --git a/core/state/store/cachemulti/store_test.go b/core/state/store/cachemulti/store_test.go new file mode 100644 index 000000000..b3246d6f7 --- /dev/null +++ b/core/state/store/cachemulti/store_test.go @@ -0,0 +1,106 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachemulti_test + +import ( + "reflect" + "testing" + + sdkcachemulti "github.com/cosmos/cosmos-sdk/store/cachemulti" + "github.com/cosmos/cosmos-sdk/store/dbadapter" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + dbm "github.com/tendermint/tm-db" + + "github.com/berachain/stargazer/core/state/store/cachemulti" +) + +var ( + byte1 = []byte{1} + evmStoreKey = storetypes.NewKVStoreKey("evm") + accStoreKey = storetypes.NewKVStoreKey("acc") +) + +type CacheMultiSuite struct { + suite.Suite + ms storetypes.MultiStore +} + +func TestCacheMultiSuite(t *testing.T) { + suite.Run(t, new(CacheMultiSuite)) +} + +func (s *CacheMultiSuite) SetupTest() { + stores := map[storetypes.StoreKey]storetypes.CacheWrapper{ + evmStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, + accStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, + } + s.ms = sdkcachemulti.NewStore( + dbm.NewMemDB(), + stores, map[string]storetypes.StoreKey{}, + nil, + nil, + ) +} + +func (s *CacheMultiSuite) TestCorrectStoreType() { + s.SetupTest() + + cms := cachemulti.NewStoreFrom(s.ms) + evmStore := cms.GetKVStore(evmStoreKey) + evmStoreType := reflect.TypeOf(evmStore).String() + require.Equal(s.T(), "*cachekv.EvmStore", evmStoreType) + + accStore := cms.GetKVStore(accStoreKey) + accStoreType := reflect.TypeOf(accStore).String() + require.Equal(s.T(), "*cachekv.Store", accStoreType) +} + +func (s *CacheMultiSuite) TestWriteToParent() { + accStoreParent := s.ms.GetKVStore(accStoreKey) + evmStoreParent := s.ms.GetKVStore(evmStoreKey) + accStoreParent.Set(byte1, byte1) + + // simulate writes to cache store as it would through context + cms := cachemulti.NewStoreFrom(s.ms) + cms.GetKVStore(evmStoreKey).Set(byte1, byte1) + cms.GetKVStore(accStoreKey).Delete(byte1) + + // should not write to parent + require.False(s.T(), evmStoreParent.Has(byte1)) + require.Equal(s.T(), byte1, accStoreParent.Get(byte1)) + + cms.Write() + + // accStore should be empty, evmStore should have key: 1 to val: 1 + require.False(s.T(), accStoreParent.Has(byte1)) + require.Equal(s.T(), byte1, evmStoreParent.Get(byte1)) +} + +func (s *CacheMultiSuite) TestGetCachedStore() { + accStoreParent := s.ms.GetKVStore(accStoreKey) + cms := cachemulti.NewStoreFrom(s.ms) + accStoreCache := cms.GetKVStore(accStoreKey) + accStoreCache.Set(byte1, byte1) + + // check that accStoreCache is not equal to accStoreParent + require.True(s.T(), accStoreCache.Has(byte1)) + require.False(s.T(), accStoreParent.Has(byte1)) + + // check that getting accStore from cms is not the same as parent + accStoreCache2 := cms.GetKVStore(accStoreKey) + require.True(s.T(), accStoreCache2.Has(byte1)) +} diff --git a/store/journal/cache_entry.go b/core/state/store/journal/cache_entry.go similarity index 100% rename from store/journal/cache_entry.go rename to core/state/store/journal/cache_entry.go diff --git a/store/journal/manager.go b/core/state/store/journal/manager.go similarity index 100% rename from store/journal/manager.go rename to core/state/store/journal/manager.go diff --git a/store/journal/manager_test.go b/core/state/store/journal/manager_test.go similarity index 96% rename from store/journal/manager_test.go rename to core/state/store/journal/manager_test.go index 947a83bda..c151b813f 100644 --- a/store/journal/manager_test.go +++ b/core/state/store/journal/manager_test.go @@ -22,8 +22,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/berachain/stargazer/store/journal" - "github.com/berachain/stargazer/store/journal/mock" + "github.com/berachain/stargazer/core/state/store/journal" + "github.com/berachain/stargazer/core/state/store/journal/mock" ) func TestJournalManager(t *testing.T) { diff --git a/store/journal/mock/cache_entry.mock.go b/core/state/store/journal/mock/cache_entry.mock.go similarity index 96% rename from store/journal/mock/cache_entry.mock.go rename to core/state/store/journal/mock/cache_entry.mock.go index 331e320fd..d37a786f1 100644 --- a/store/journal/mock/cache_entry.mock.go +++ b/core/state/store/journal/mock/cache_entry.mock.go @@ -15,7 +15,7 @@ package mock import ( - "github.com/berachain/stargazer/store/journal" + "github.com/berachain/stargazer/core/state/store/journal" ) // `MockCacheEntry` is a basic CacheEntry which increases num by 1 on `Revert()`. diff --git a/core/state/types/types.go b/core/state/types/types.go new file mode 100644 index 000000000..534832a1b --- /dev/null +++ b/core/state/types/types.go @@ -0,0 +1,17 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package types + +const EvmStoreKey = "evm" diff --git a/store/utils/utils.go b/store/utils/utils.go deleted file mode 100644 index 42c5c2da3..000000000 --- a/store/utils/utils.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package utils - -import ( - "reflect" //#nosec: G702 // the sdk uses it so - "unsafe" //#nosec: G702 // yolo -) - -// UnsafeStrToBytes uses unsafe to convert string into byte array. Returned bytes -// must not be altered after this function is called as it will cause a segmentation fault. -func UnsafeStrToBytes(s string) []byte { - var buf []byte - //#nosec:G103 tHe uSe oF uNsAfE cALLs shOuLd Be AuDiTeD - sHdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) - //#nosec:G103 tHe uSe oF uNsAfE cALLs shOuLd Be AuDiTeD - bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) - bufHdr.Data = sHdr.Data - bufHdr.Cap = sHdr.Len - bufHdr.Len = sHdr.Len - return buf -} - -// UnsafeBytesToStr is meant to make a zero allocation conversion -// from []byte -> string to speed up operations, it is not meant -// to be used generally, but for a specific pattern to delete keys -// from a map. -func UnsafeBytesToStr(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) //#nosec:G103 tHe uSe oF uNsAfE cALLs shOuLd Be AuDiTeD -} From a32c8998cf5e9129d83a498f8c71ea46fa10aa46 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Wed, 11 Jan 2023 21:47:13 -0500 Subject: [PATCH 33/76] statedb reader --- core/state/statedb_reader.go | 179 +++++++++++++++++++++++++ core/state/statedb_reader_test.go | 15 +++ core/state/statedb_test.go | 17 +++ core/state/store/cachekv/store_test.go | 5 +- core/state/store/cachemulti/store.go | 4 +- core/state/types/keys.go | 47 +++++++ 6 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 core/state/statedb_reader.go create mode 100644 core/state/statedb_reader_test.go create mode 100644 core/state/statedb_test.go create mode 100644 core/state/types/keys.go diff --git a/core/state/statedb_reader.go b/core/state/statedb_reader.go new file mode 100644 index 000000000..50b424d63 --- /dev/null +++ b/core/state/statedb_reader.go @@ -0,0 +1,179 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state + +import ( + "math/big" + + "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/journal" + "github.com/berachain/stargazer/core/state/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type StateReader interface { //nolint:revive // we like the vibe. + GetBalance(addr common.Address) *big.Int + GetCode(addr common.Address) []byte + GetCodeSize(addr common.Address) int + GetCodeHash(addr common.Address) common.Hash + GetNonce(addr common.Address) uint64 + GetState(addr common.Address, hash common.Hash) common.Hash + GetCommittedState(addr common.Address, hash common.Hash) common.Hash + Exist(addr common.Address) bool + Empty(addr common.Address) bool +} + +var _ StateReader = (*StateDBReader)(nil) + +type StateDBReader struct { //nolint:revive // we like the vibe. + + // keepers used for balance and account information + ak AccountKeeper + bk BankKeeper + + // the evm denom + evmDenom string + + // the live context + liveCtx sdk.Context + + // eth state stores: required for vm.StateDB + // We store references to these stores, so that we can access them + // directly, without having to go through the MultiStore interface. + liveEthState cachekv.StateDBCacheKVStore +} + +func NewStateDBReader( + ctx sdk.Context, + ak AccountKeeper, + bk BankKeeper, + storeKey storetypes.StoreKey, + evmDenom string, +) *StateDBReader { + return &StateDBReader{ + ak: ak, + bk: bk, + evmDenom: evmDenom, + liveCtx: ctx, + liveEthState: cachekv.NewEvmStore(ctx.KVStore(storeKey), journal.NewManager()), + } +} + +// ============================================================================== +// Balance +// ============================================================================== + +func (sdbr *StateDBReader) GetBalance(addr common.Address) *big.Int { + // Note: bank keeper will return 0 if account/state_object is not found + return sdbr.bk.GetBalance(sdbr.liveCtx, addr[:], sdbr.evmDenom).Amount.BigInt() +} + +// ============================================================================== +// Code +// ============================================================================== + +// GetCodeHash implements the GethStateDB interface by returning +// the code hash of account. +func (sdbr *StateDBReader) GetCodeHash(addr common.Address) common.Hash { + if sdbr.ak.HasAccount(sdbr.liveCtx, addr[:]) { + if ch := prefix.NewStore(sdbr.liveEthState, types.KeyPrefixCodeHash). + Get(addr[:]); ch != nil { + return common.BytesToHash(ch) + } + return types.EmptyCodeHash + } + // if account at addr does not exist, return ZeroCodeHash + return types.ZeroCodeHash +} + +// GetCode implements the GethStateDB interface by returning +// the code of account (nil if not exists). +func (sdbr *StateDBReader) GetCode(addr common.Address) []byte { + codeHash := sdbr.GetCodeHash(addr) + // if account at addr does not exist, GetCodeHash returns ZeroCodeHash so return nil + // if codeHash is empty, i.e. crypto.Keccak256(nil), also return nil + if codeHash == types.ZeroCodeHash || codeHash == types.EmptyCodeHash { + return nil + } + return prefix.NewStore(sdbr.liveEthState, types.KeyPrefixCode).Get(codeHash.Bytes()) +} + +// GetCodeSize implements the GethStateDB interface by returning the size of the +// code associated with the given GethStateDB. +func (sdbr *StateDBReader) GetCodeSize(addr common.Address) int { + return len(sdbr.GetCode(addr)) +} + +// ============================================================================== +// State +// ============================================================================== + +// GetCommittedState implements the GethStateDB interface by returning the +// committed state of an address. +func (sdbr *StateDBReader) GetCommittedState( + addr common.Address, + hash common.Hash, +) common.Hash { + if value := prefix.NewStore(sdbr.liveEthState.GetParent(), + types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { + return common.BytesToHash(value) + } + return common.Hash{} +} + +// GetState implements the GethStateDB interface by returning the current state of an +// address. +func (sdbr *StateDBReader) GetState(addr common.Address, hash common.Hash) common.Hash { + if value := prefix.NewStore(sdbr.liveEthState, + types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { + return common.BytesToHash(value) + } + return common.Hash{} +} + +// ============================================================================== +// Account +// ============================================================================== + +// Exist implements the GethStateDB interface by reporting whether the given account address +// exists in the state. Notably this also returns true for suicided accounts, which is accounted +// for since, `RemoveAccount()` is not called until Commit. +func (sdbr *StateDBReader) Exist(addr common.Address) bool { + return sdbr.ak.HasAccount(sdbr.liveCtx, addr[:]) +} + +// GetNonce implements the GethStateDB interface by returning the nonce +// of an account. +func (sdbr *StateDBReader) GetNonce(addr common.Address) uint64 { + acc := sdbr.ak.GetAccount(sdbr.liveCtx, addr[:]) + if acc == nil { + return 0 + } + return acc.GetSequence() +} + +// Empty implements the GethStateDB interface by returning whether the state object +// is either non-existent or empty according to the EIP161 specification +// (balance = nonce = code = 0) +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md +func (sdbr *StateDBReader) Empty(addr common.Address) bool { + ch := sdbr.GetCodeHash(addr) + return sdbr.GetNonce(addr) == 0 && + (ch == types.EmptyCodeHash || ch == types.ZeroCodeHash) && + sdbr.GetBalance(addr).Sign() == 0 +} diff --git a/core/state/statedb_reader_test.go b/core/state/statedb_reader_test.go new file mode 100644 index 000000000..1bd6ea9ca --- /dev/null +++ b/core/state/statedb_reader_test.go @@ -0,0 +1,15 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state_test diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go new file mode 100644 index 000000000..5234bc3a7 --- /dev/null +++ b/core/state/statedb_test.go @@ -0,0 +1,17 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state_test + +// test codecov diff --git a/core/state/store/cachekv/store_test.go b/core/state/store/cachekv/store_test.go index a29dddad7..678ac696c 100644 --- a/core/state/store/cachekv/store_test.go +++ b/core/state/store/cachekv/store_test.go @@ -30,11 +30,12 @@ import ( "github.com/berachain/stargazer/core/state/store/journal" ) -func newParent() types.CacheKVStore { //nolint: ireturn // by design. +func newParent() types.CacheKVStore { //nolint:ireturn // must return interface. return sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) } -func newCacheKVStoreFromParent(parent types.CacheKVStore) types.CacheKVStore { //nolint: ireturn // by design. +//nolint:nolintlint,ireturn // must return interface. +func newCacheKVStoreFromParent(parent types.CacheKVStore) types.CacheKVStore { return cachekv.NewStore(parent, journal.NewManager()) } diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go index 7ae535c3c..3198c9577 100644 --- a/core/state/store/cachemulti/store.go +++ b/core/state/store/cachemulti/store.go @@ -42,7 +42,7 @@ func NewStoreFrom(ms storetypes.MultiStore) *Store { // GetKVStore shadows cosmos sdk storetypes.MultiStore function. Routes native module calls to // read the dirty state during an eth tx. Any state that is modified by evm statedb, and using the // context passed in to StateDB, will be routed to a tx-specific cache kv store. -func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolint:ireturn // must return a CacheKVStore. +func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolint:ireturn // must return interface. // check if cache kv store already used if cacheKVStore, exists := s.stores[key]; exists { return cacheKVStore @@ -65,7 +65,7 @@ func (s *Store) Write() { } } -func (s *Store) newCacheKVStore( //nolint:ireturn // must return a CacheKVStore. +func (s *Store) newCacheKVStore( //nolint:ireturn // must return interface. key storetypes.StoreKey, kvstore storetypes.KVStore, ) storetypes.CacheKVStore { diff --git a/core/state/types/keys.go b/core/state/types/keys.go new file mode 100644 index 000000000..724dcd037 --- /dev/null +++ b/core/state/types/keys.go @@ -0,0 +1,47 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package types + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + keyPrefixCode byte = iota + keyPrefixHash + keyPrefixStorage +) + +var ( + // EmptyCodeHash is the code hash of an empty code + // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. + EmptyCodeHash = crypto.Keccak256Hash(nil) + ZeroCodeHash = common.Hash{} + + KeyPrefixCode = []byte{keyPrefixCode} + KeyPrefixCodeHash = []byte{keyPrefixHash} + KeyPrefixStorage = []byte{keyPrefixStorage} +) + +// AddressStoragePrefix returns a prefix to iterate over a given account storage. +func AddressStoragePrefix(address common.Address) []byte { + return append(KeyPrefixStorage, address.Bytes()...) +} + +// StateKey defines the full key under which an account state is stored. +func StateKey(address common.Address, key []byte) []byte { + return append(AddressStoragePrefix(address), key...) +} From 1903e7a8a0f479f305e5562f128c1f0a83ee8345 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 10:32:11 -0500 Subject: [PATCH 34/76] cleanup --- common/imported.go | 4 + core/evm.go | 1 + core/state/cache_entries.go | 67 ++ core/state/state_test.go | 13 + core/state/statedb.go | 719 +++++++++++++++++- core/state/statedb_reader.go | 179 ----- core/state/statedb_test.go | 471 +++++++++++- core/state/store/cachekv/store.go | 2 +- core/state/store/cachekv/store_test.go | 2 +- core/state/store/cachemulti/store.go | 4 +- core/types/imported.go | 10 + go.mod | 10 +- go.sum | 24 + .../crypto/imported.go | 16 +- testutil/setup.go | 104 +++ 15 files changed, 1435 insertions(+), 191 deletions(-) create mode 100644 core/evm.go create mode 100644 core/state/cache_entries.go create mode 100644 core/state/state_test.go delete mode 100644 core/state/statedb_reader.go create mode 100644 core/types/imported.go rename core/state/statedb_reader_test.go => lib/crypto/imported.go (67%) create mode 100644 testutil/setup.go diff --git a/common/imported.go b/common/imported.go index 2fb567981..ca84d58bc 100644 --- a/common/imported.go +++ b/common/imported.go @@ -26,4 +26,8 @@ type ( var ( BytesToAddress = common.BytesToAddress BytesToHash = common.BytesToHash + Bytes2Hex = common.Bytes2Hex + HexToAddress = common.HexToAddress + Hex2Bytes = common.Hex2Bytes + HexToHash = common.HexToHash ) diff --git a/core/evm.go b/core/evm.go new file mode 100644 index 000000000..9a8bc9592 --- /dev/null +++ b/core/evm.go @@ -0,0 +1 @@ +package core diff --git a/core/state/cache_entries.go b/core/state/cache_entries.go new file mode 100644 index 000000000..c082a051a --- /dev/null +++ b/core/state/cache_entries.go @@ -0,0 +1,67 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state + +import ( + "github.com/berachain/stargazer/core/state/store/journal" +) + +var ( + _ journal.CacheEntry = &RefundChange{} + _ journal.CacheEntry = &AddLogChange{} +) + +type ( + AddLogChange struct { + sdb *StateDB + } + RefundChange struct { + sdb *StateDB + prev uint64 + } +) + +// ============================================================================== +// AddLogChange +// ============================================================================== + +// implements journal.CacheEntry. +func (ce *AddLogChange) Revert() { + ce.sdb.logs = ce.sdb.logs[:len(ce.sdb.logs)-1] +} + +// implements journal.CacheEntry. +func (ce *AddLogChange) Clone() journal.CacheEntry { + return &AddLogChange{ + sdb: ce.sdb, + } +} + +// ============================================================================== +// RefundChange +// ============================================================================== + +// implements journal.CacheEntry. +func (ce *RefundChange) Revert() { + ce.sdb.refund = ce.prev +} + +// implements journal.CacheEntry. +func (ce *RefundChange) Clone() journal.CacheEntry { + return &RefundChange{ + sdb: ce.sdb, + prev: ce.prev, + } +} diff --git a/core/state/state_test.go b/core/state/state_test.go new file mode 100644 index 000000000..b8e6de8a3 --- /dev/null +++ b/core/state/state_test.go @@ -0,0 +1,13 @@ +package state_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestState(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "core/state") +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 2a6d67a76..7d6e4d8c8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Copyright (C) 2022, Berachain Foundation. All rights reserved. // See the file LICENSE for licensing terms. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" @@ -14,4 +14,719 @@ package state -// test codecov +import ( + "bytes" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/store/prefix" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + gevm "github.com/ethereum/go-ethereum/core/vm" + + "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/cachemulti" + "github.com/berachain/stargazer/core/state/types" + coretypes "github.com/berachain/stargazer/core/types" + "github.com/berachain/stargazer/lib/crypto" +) + +type GethStateDB = gevm.StateDB + +// ExtStateDB defines an extension to the interface provided by the go-ethereum codebase to +// support additional state transition functionalities. In particular it supports getting the +// cosmos sdk context for natively running stateful precompiled contracts. +type ExtStateDB interface { + storetypes.MultiStore + GethStateDB + + // GetContext returns the cosmos sdk context with the statedb multistore attached + GetContext() sdk.Context + + // GetSavedErr returns the error saved in the statedb + GetSavedErr() error + + // GetLogs returns the logs generated during the transaction + Logs() []*coretypes.Log + + // Commit writes the state to the underlying multi-store + Commit() error +} + +type IntraBlockStateDB interface { + ExtStateDB + + // PrepareForTransition prepares the statedb for a new transition + // by setting the block hash, tx hash, tx index and tx log index. + PrepareForTransition(common.Hash, common.Hash, uint, uint) + + // Reset resets the statedb to the initial state. + Reset(sdk.Context) +} + +const ( + keyPrefixCode byte = iota + keyPrefixHash +) + +var ( + // EmptyCodeHash is the code hash of an empty code + // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. + EmptyCodeHash = crypto.Keccak256Hash(nil) + ZeroCodeHash = common.Hash{} + + KeyPrefixCode = []byte{keyPrefixCode} + KeyPrefixCodeHash = []byte{keyPrefixHash} + // todo: add later? + // StateDBStoreKey = sdk.NewKVStoreKey(types.StoreKey). + _ ExtStateDB = (*StateDB)(nil) +) + +// Welcome to the StateDB implementation. This is a wrapper around a cachemulti.Store +// so that precompiles querying Eth-modified state can directly read from the statedb +// objects. It adheres to the Geth vm.StateDB and Cosmos MultiStore interfaces, which allows it +// to be used as a MultiStore in the Cosmos SDK context. +// The StateDB is a very fun and interesting part of the EVM implementation. But if you want to +// join circus you need to know the rules. So here thet are: +// +// 1. You must ensure that the StateDB is only ever used in a single thread. This is because the +// StateDB is not thread safe. And there are a bunch of optimizations made that are only safe +// to do in a single thread. +// 2. You must discard the StateDB after use. +// 3. When accessing or mutating the StateDB, you must ensure that the underlying account exists. +// in the AccountKeeper, for performance reasons, this implementation of the StateDB will not +// create accounts that do not exist. Notably calling `SetState()` on an account that does not +// exist is completely possible, and the StateDB will not prevent you doing so. This lazy +// creation improves performance a ton, as it prevents calling into the ak on +// every SSTORE. The only accounts that should ever have `SetState()` called on them are +// accounts that represent smart contracts. Because of this assumption, the only place that we +// explicitly create accounts is in `CreateAccount()`, since `CreateAccount()` is called when +// deploying a smart contract. +// 4. Accounts that are sent `evmDenom` coins during an eth transaction, will have an account +// created for them, automatically by the Bank Module. However, these accounts will have a +// codeHash of 0x000... This is because the Bank Module does not know that the account is an +// EVM account, and so it does not set the codeHash. This is totally fine, we just need to +// check both for both the codeHash being 0x000... as well as the codeHash being 0x567... +type StateDB struct { //nolint: revive + *cachemulti.Store + + // eth state stores: required for vm.StateDB + // We store references to these stores, so that we can access them + // directly, without having to go through the MultiStore interface. + liveEthState cachekv.StateDBCacheKVStore + + // cosmos state ctx: required for sdk.MultiStore + ctx sdk.Context + + // keepers used for balance and account information + ak AccountKeeper + bk BankKeeper + + // DB error. + // State objects are used by the consensus core and VM which are + // unable to deal with database-level errors. Any error that occurs + // during a database read is memoized here and will eventually be returned + // by StateDB.Commit. + savedErr error + + // we load the evm denom in the constructor, to prevent going to + // the params to get it mid interpolation. + evmDenom string + + // The refund counter, also used by state transitioning. + refund uint64 + + // The storekey used during execution + storeKey storetypes.StoreKey + + // Per-transaction logs + logs []*coretypes.Log + + // Transaction and logging bookkeeping + txHash common.Hash + blockHash common.Hash + txIndex uint + logIndex uint + + // Dirty tracking of suicided accounts, we have to keep track of these manually, in order + // for the code and state to still be accessible even after the account has been deleted. + // We chose to keep track of them in a separate slice, rather than a map, because the + // number of accounts that will be suicided in a single transaction is expected to be + // very low. + suicides []common.Address +} + +// returns a *StateDB using the MultiStore belonging to ctx. +func NewStateDB( + ctx sdk.Context, + ak AccountKeeper, + bk BankKeeper, + storeKey storetypes.StoreKey, + evmDenom string, +) *StateDB { + sdb := &StateDB{ + Store: cachemulti.NewStoreFrom(ctx.MultiStore()), + ak: ak, + bk: bk, + evmDenom: evmDenom, + storeKey: storeKey, + } + sdb.ctx = ctx.WithMultiStore(sdb) + + // Must support directly accessing the parent store. + sdb.liveEthState, _ = sdb.ctx.MultiStore(). + GetKVStore(storeKey).(cachekv.StateDBCacheKVStore) + return sdb +} + +func (sdb *StateDB) GetEvmDenom() string { + return sdb.evmDenom +} + +// CreateAccount implements the GethStateDB interface by creating a new account +// in the account keeper. It will allow accounts to be overridden. +func (sdb *StateDB) CreateAccount(addr common.Address) { + acc := sdb.ak.NewAccountWithAddress(sdb.ctx, addr[:]) + + // save the new account in the account keeper + sdb.ak.SetAccount(sdb.ctx, acc) + + // initialize the code hash to empty + prefix.NewStore(sdb.liveEthState, KeyPrefixCodeHash).Set(addr[:], EmptyCodeHash[:]) +} + +// ============================================================================= +// Transaction Handling +// ============================================================================= + +// Prepare sets the current transaction hash and index and block hash which is +// used for logging events. +func (sdb *StateDB) PrepareForTransition(blockHash, txHash common.Hash, ti, li uint) { + sdb.blockHash = blockHash + sdb.txHash = txHash + sdb.txIndex = ti + sdb.logIndex = li +} + +// Reset clears the journal and other state objects. It also clears the +// refund counter and the access list. +func (sdb *StateDB) Reset(ctx sdk.Context) { + // TODO: figure out why not fully reallocating the object causes + // the gas shit to fail + // sdb.MultiStore = cachemulti.NewStoreFrom(ctx.MultiStore()) + // sdb.ctx = ctx.WithMultiStore(sdb.MultiStore) + // // // Must support directly accessing the parent store. + // // sdb.liveEthState = sdb.ctx.MultiStore(). + // // GetKVStore(sdb.storeKey).(cachekv.StateDBCacheKVStore) + // sdb.savedErr = nil + // sdb.refund = 0 + + // sdb.logs = make([]*coretypes.Log, 0) + // sdb.accessList = newAccessList() + // sdb.suicides = make([]common.Address, 0) + // TODO: unghetto this + *sdb = *NewStateDB(ctx, sdb.ak, sdb.bk, sdb.storeKey, sdb.evmDenom) +} + +// ============================================================================= +// Balance +// ============================================================================= + +// GetBalance implements GethStateDB interface. +func (sdb *StateDB) GetBalance(addr common.Address) *big.Int { + // Note: bank keeper will return 0 if account/state_object is not found + return sdb.bk.GetBalance(sdb.ctx, addr[:], sdb.evmDenom).Amount.BigInt() +} + +// AddBalance implements the GethStateDB interface by adding the given amount +// from the account associated with addr. If the account does not exist, it will be +// created. +func (sdb *StateDB) AddBalance(addr common.Address, amount *big.Int) { + coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) + + // Mint the coins to the evm module account + if err := sdb.bk.MintCoins(sdb.ctx, "evm", coins); err != nil { + sdb.setErrorUnsafe(err) + return + } + + // Send the coins from the evm module account to the destination address. + if err := sdb.bk.SendCoinsFromModuleToAccount(sdb.ctx, "evm", addr[:], coins); err != nil { + sdb.setErrorUnsafe(err) + } +} + +// SubBalance implements the GethStateDB interface by subtracting the given amount +// from the account associated with addr. +func (sdb *StateDB) SubBalance(addr common.Address, amount *big.Int) { + coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) + + // Send the coins from the source address to the evm module account. + if err := sdb.bk.SendCoinsFromAccountToModule(sdb.ctx, addr[:], "evm", coins); err != nil { + sdb.setErrorUnsafe(err) + return + } + + // Burn the coins from the evm module account. + if err := sdb.bk.BurnCoins(sdb.ctx, "evm", coins); err != nil { + sdb.setErrorUnsafe(err) + return + } +} + +// `SendBalance` sends the given amount from one account to another. It will +// error if the sender does not have enough funds to send. +func (sdb *StateDB) SendBalance(from, to common.Address, amount *big.Int) { + coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) + + // Send the coins from the source address to the destination address. + if err := sdb.bk.SendCoins(sdb.ctx, from[:], to[:], coins); err != nil { + sdb.setErrorUnsafe(err) + } +} + +// ============================================================================= +// Nonce +// ============================================================================= + +// GetNonce implements the GethStateDB interface by returning the nonce +// of an account. +func (sdb *StateDB) GetNonce(addr common.Address) uint64 { + acc := sdb.ak.GetAccount(sdb.ctx, addr[:]) + if acc == nil { + return 0 + } + return acc.GetSequence() +} + +// SetNonce implements the GethStateDB interface by setting the nonce +// of an account. +func (sdb *StateDB) SetNonce(addr common.Address, nonce uint64) { + // get the account or create a new one if doesn't exist + acc := sdb.ak.GetAccount(sdb.ctx, addr[:]) + if acc == nil { + acc = sdb.ak.NewAccountWithAddress(sdb.ctx, addr[:]) + } + + if err := acc.SetSequence(nonce); err != nil { + sdb.setErrorUnsafe(err) + } + + sdb.ak.SetAccount(sdb.ctx, acc) +} + +// ============================================================================= +// Code +// ============================================================================= + +// GetCodeHash implements the GethStateDB interface by returning +// the code hash of account. +func (sdb *StateDB) GetCodeHash(addr common.Address) common.Hash { + if sdb.ak.HasAccount(sdb.ctx, addr[:]) { + if ch := prefix.NewStore(sdb.liveEthState, + KeyPrefixCodeHash).Get(addr[:]); ch != nil { + return common.BytesToHash(ch) + } + return EmptyCodeHash + } + // if account at addr does not exist, return ZeroCodeHash + return ZeroCodeHash +} + +// GetCode implements the GethStateDB interface by returning +// the code of account (nil if not exists). +func (sdb *StateDB) GetCode(addr common.Address) []byte { + codeHash := sdb.GetCodeHash(addr) + // if account at addr does not exist, GetCodeHash returns ZeroCodeHash so return nil + // if codeHash is empty, i.e. crypto.Keccak256(nil), also return nil + if codeHash == ZeroCodeHash || codeHash == EmptyCodeHash { + return nil + } + return prefix.NewStore(sdb.liveEthState, KeyPrefixCode).Get(codeHash.Bytes()) +} + +// SetCode implements the GethStateDB interface by setting the code hash and +// code for the given account. +func (sdb *StateDB) SetCode(addr common.Address, code []byte) { + codeHash := crypto.Keccak256Hash(code) + + prefix.NewStore(sdb.liveEthState, KeyPrefixCodeHash).Set(addr[:], codeHash[:]) + + // store or delete code + if len(code) == 0 { + prefix.NewStore(sdb.liveEthState, KeyPrefixCode).Delete(codeHash[:]) + } else { + prefix.NewStore(sdb.liveEthState, KeyPrefixCode).Set(codeHash[:], code) + } +} + +// GetCodeSize implements the GethStateDB interface by returning the size of the +// code associated with the given GethStateDB. +func (sdb *StateDB) GetCodeSize(addr common.Address) int { + return len(sdb.GetCode(addr)) +} + +// ============================================================================= +// Refund +// ============================================================================= + +// AddRefund implements the GethStateDB interface by adding gas to the +// refund counter. +func (sdb *StateDB) AddRefund(gas uint64) { + sdb.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) + sdb.refund += gas +} + +// SubRefund implements the GethStateDB interface by subtracting gas from the +// refund counter. If the gas is greater than the refund counter, it will panic. +func (sdb *StateDB) SubRefund(gas uint64) { + sdb.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) + if gas > sdb.refund { + panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, sdb.refund)) + } + sdb.refund -= gas +} + +// GetRefund implements the GethStateDB interface by returning the current +// value of the refund counter. +func (sdb *StateDB) GetRefund() uint64 { + return sdb.refund +} + +// ============================================================================= +// State +// ============================================================================= + +// GetCommittedState implements the GethStateDB interface by returning the +// committed state of an address. +func (sdb *StateDB) GetCommittedState( + addr common.Address, + hash common.Hash, +) common.Hash { + if value := prefix.NewStore(sdb.liveEthState.GetParent(), + types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { + return common.BytesToHash(value) + } + return common.Hash{} +} + +// GetState implements the GethStateDB interface by returning the current state of an +// address. +func (sdb *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + if value := prefix.NewStore(sdb.liveEthState, + types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { + return common.BytesToHash(value) + } + return common.Hash{} +} + +// SetState implements the GethStateDB interface by setting the state of an +// address. +func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { + // For performance reasons, we don't check to ensure the account exists before we execute. + // This is reasonably safe since under normal operation, SetState is only ever called by the + // SSTORE opcode in the EVM, which will only ever be called on an account that exists, since + // it would with 100% certainty have been created by a prior Create, thus setting its code + // hash. + // CONTRACT: never manually call SetState outside of the the `opSstore` in the interpreter. + + store := prefix.NewStore(sdb.liveEthState, types.AddressStoragePrefix(addr)) + if len(value) == 0 || value == ZeroCodeHash { + store.Delete(key[:]) + } else { + store.Set(key[:], value[:]) + } +} + +// ============================================================================= +// Suicide +// ============================================================================= + +// Suicide implements the GethStateDB interface by marking the given address as suicided. +// This clears the account balance, but the code and state of the address remains available +// until after Commit is called. +func (sdb *StateDB) Suicide(addr common.Address) bool { + // only smart contracts can commit suicide + ch := sdb.GetCodeHash(addr) + if ch == ZeroCodeHash || ch == EmptyCodeHash { + return false + } + + // Reduce it's balance to 0. + bal := sdb.bk.GetBalance(sdb.ctx, addr[:], sdb.evmDenom) + sdb.SubBalance(addr, bal.Amount.BigInt()) + + // Mark the underlying account for deletion in `Commit()`. + sdb.suicides = append(sdb.suicides, addr) + return true +} + +// HasSuicided implements the GethStateDB interface by returning if the contract was suicided +// in current transaction. +func (sdb *StateDB) HasSuicided(addr common.Address) bool { + for _, suicide := range sdb.suicides { + if bytes.Equal(suicide[:], addr[:]) { + return true + } + } + return false +} + +// ============================================================================= +// Exist & Empty +// ============================================================================= + +// Exist implements the GethStateDB interface by reporting whether the given account address +// exists in the state. Notably this also returns true for suicided accounts, which is accounted +// for since, `RemoveAccount()` is not called until Commit. +func (sdb *StateDB) Exist(addr common.Address) bool { + return sdb.ak.HasAccount(sdb.ctx, addr[:]) +} + +// Empty implements the GethStateDB interface by returning whether the state object +// is either non-existent or empty according to the EIP161 specification +// (balance = nonce = code = 0) +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md +func (sdb *StateDB) Empty(addr common.Address) bool { + ch := sdb.GetCodeHash(addr) + return sdb.GetNonce(addr) == 0 && + (ch == EmptyCodeHash || ch == ZeroCodeHash) && + sdb.GetBalance(addr).Sign() == 0 +} + +// ============================================================================= +// Snapshot +// ============================================================================= + +// `RevertToSnapshot` implements `StateDB`. +func (sdb *StateDB) RevertToSnapshot(id int) { + // revert and discard all journal entries after snapshot id + sdb.JournalMgr.PopToSize(id) +} + +func (sdb *StateDB) Snapshot() int { + return sdb.JournalMgr.Size() +} + +// ============================================================================= +// Logs +// ============================================================================= + +// AddLog implements the GethStateDB interface by adding the given log to the current transaction. +func (sdb *StateDB) AddLog(log *coretypes.Log) { + sdb.JournalMgr.Push(&AddLogChange{sdb}) + log.TxHash = sdb.txHash + log.BlockHash = sdb.blockHash + log.TxIndex = sdb.txIndex + log.Index = sdb.logIndex + sdb.logs = append(sdb.logs, log) + sdb.logIndex++ // erigon intra +} + +// Logs returns the logs of current transaction. +func (sdb *StateDB) Logs() []*coretypes.Log { + return sdb.logs +} + +// ============================================================================= +// ForEachStorage +// ============================================================================= + +// ForEachStorage implements the GethStateDB interface by iterating through the contract state +// contract storage, the iteration order is not defined. +// +// Note: We do not support iterating through any storage that is modified before calling +// ForEachStorage; only committed state is iterated through. +func (sdb *StateDB) ForEachStorage( + addr common.Address, + cb func(key, value common.Hash) bool, +) error { + it := sdk.KVStorePrefixIterator(sdb.liveEthState, types.AddressStoragePrefix(addr)) + defer it.Close() + + for ; it.Valid(); it.Next() { + committedValue := it.Value() + if len(committedValue) > 0 { + if !cb(common.BytesToHash(it.Key()), common.BytesToHash(committedValue)) { + // stop iteration + return nil + } + } + } + + return nil +} + +// AddPreimage implements the the GethStateDB interface, but currently +// performs a no-op since the EnablePreimageRecording flag is disabled. +func (sdb *StateDB) AddPreimage(hash common.Hash, preimage []byte) {} + +// ============================================================================= +// MultiStore +// ============================================================================= + +// Commit implements storetypes.MultiStore by writing the dirty states of the +// liveStateCtx to the committedStateCtx. It also handles sucidal accounts. +func (sdb *StateDB) Commit() error { + // If we saw an error during the execution, we return it here. + if sdb.savedErr != nil { + return sdb.savedErr + } + + // Manually delete all suicidal accounts. + for _, suicidalAddr := range sdb.suicides { + acct := sdb.ak.GetAccount(sdb.ctx, suicidalAddr[:]) + if acct == nil { + // handles the double suicide case + continue + } + + // clear storage + if err := sdb.ForEachStorage(suicidalAddr, + func(key, _ common.Hash) bool { + sdb.SetState(suicidalAddr, key, common.Hash{}) + return true + }); err != nil { + return err + } + + // clear the codehash from this account + prefix.NewStore(sdb.liveEthState, KeyPrefixCodeHash).Delete(suicidalAddr[:]) + + // remove auth account + sdb.ak.RemoveAccount(sdb.ctx, acct) + } + + // write all cache stores to parent stores, effectively writing temporary state in ctx to + // the underlying parent store. + sdb.Store.Write() + return nil +} + +// ============================================================================= +// ExtStateDB +// ============================================================================= + +// GetContext implements ExtStateDB +// returns the StateDB's live context. +func (sdb *StateDB) GetContext() sdk.Context { + return sdb.ctx +} + +// GetSavedErr implements ExtStateDB +// any errors that pop up during store operations should be checked here +// called upon the conclusion. +func (sdb *StateDB) GetSavedErr() error { + return sdb.savedErr +} + +// setErrorUnsafe sets error but should be called in medhods that already have locks +func (sdb *StateDB) setErrorUnsafe(err error) { + if sdb.savedErr == nil { + sdb.savedErr = err + } +} + +// ============================================================================= +// Genesis +// ============================================================================= + +// // ImportStateData imports the given genesis accounts into the state database. We pass in a +// // temporary context to bypass the StateDB caching capabilities to speed up the import. +// func (sdb *StateDB) ImportStateData(accounts []types.GenesisAccount) error { +// for _, account := range accounts { +// addr := common.HexToAddress(account.Address) + +// acc := sdb.ak.GetAccount(sdb.ctx, addr[:]) +// if acc == nil { +// panic(fmt.Errorf("account not found for address %s", account.Address)) +// } + +// code := common.Hex2Bytes(account.Code) +// codeHash := crypto.Keccak256Hash(code) +// storedCodeHash := sdb.GetCodeHash(addr) + +// // we ignore the empty Code hash checking, see ethermint PR#1234 +// if len(account.Code) != 0 && storedCodeHash != codeHash { +// s := "the evm state code doesn't match with the codehash\n" +// panic(fmt.Sprintf("%s account: %s , evm state codehash: %v,"+ +// " ethAccount codehash: %v, evm state code: %s\n", +// s, account.Address, codeHash, storedCodeHash, account.Code)) +// } + +// // Set the code for this contract +// sdb.SetCode(addr, code) + +// // Set the state for this contract +// for _, storage := range account.Storage { +// sdb.SetState(addr, common.HexToHash(storage.Key), common.HexToHash(storage.Value)) +// } + +// // Commit the changes to save them to the underlying KVStore. +// if err := sdb.Commit(); err != nil { +// return err +// } +// } + +// return nil +// } + +// // ExportStateData exports all the data stored in the StateDB to a GenesisAccount slice. +// // We pass in a temporary context to bypass the StateDB caching capabilities to +// // speed up the import. +// func (sdb *StateDB) ExportStateData() (ethAccs []types.GenesisAccount) { +// sdb.ak.IterateAccounts(sdb.ctx, func(account authtypes.AccountI) bool { +// addr := common.BytesToAddress(account.GetAddress().Bytes()) + +// // Load Storage +// var storage types.Storage +// if err := sdb.ForEachStorage(addr, +// func(key, value common.Hash) bool { +// storage = append(storage, types.NewState(key, value)) +// return true +// }, +// ); err != nil { +// panic(err) +// } + +// genAccount := types.GenesisAccount{ +// Address: addr.String(), +// Code: common.Bytes2Hex(sdb.GetCode(addr)), +// Storage: storage, +// } + +// ethAccs = append(ethAccs, genAccount) +// return false +// }) +// return ethAccs +// } + +// ============================================================================= +// AccessList +// ============================================================================= + +func (sdb *StateDB) PrepareAccessList( + sender common.Address, + dst *common.Address, + precompiles []common.Address, + list coretypes.AccessList, +) { + panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") +} + +func (sdb *StateDB) AddAddressToAccessList(addr common.Address) { + panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") +} + +func (sdb *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") +} + +func (sdb *StateDB) AddressInAccessList(addr common.Address) bool { + panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") +} + +func (sdb *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (bool, bool) { + panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") +} diff --git a/core/state/statedb_reader.go b/core/state/statedb_reader.go deleted file mode 100644 index 50b424d63..000000000 --- a/core/state/statedb_reader.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package state - -import ( - "math/big" - - "github.com/berachain/stargazer/common" - "github.com/berachain/stargazer/core/state/store/cachekv" - "github.com/berachain/stargazer/core/state/store/journal" - "github.com/berachain/stargazer/core/state/types" - "github.com/cosmos/cosmos-sdk/store/prefix" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type StateReader interface { //nolint:revive // we like the vibe. - GetBalance(addr common.Address) *big.Int - GetCode(addr common.Address) []byte - GetCodeSize(addr common.Address) int - GetCodeHash(addr common.Address) common.Hash - GetNonce(addr common.Address) uint64 - GetState(addr common.Address, hash common.Hash) common.Hash - GetCommittedState(addr common.Address, hash common.Hash) common.Hash - Exist(addr common.Address) bool - Empty(addr common.Address) bool -} - -var _ StateReader = (*StateDBReader)(nil) - -type StateDBReader struct { //nolint:revive // we like the vibe. - - // keepers used for balance and account information - ak AccountKeeper - bk BankKeeper - - // the evm denom - evmDenom string - - // the live context - liveCtx sdk.Context - - // eth state stores: required for vm.StateDB - // We store references to these stores, so that we can access them - // directly, without having to go through the MultiStore interface. - liveEthState cachekv.StateDBCacheKVStore -} - -func NewStateDBReader( - ctx sdk.Context, - ak AccountKeeper, - bk BankKeeper, - storeKey storetypes.StoreKey, - evmDenom string, -) *StateDBReader { - return &StateDBReader{ - ak: ak, - bk: bk, - evmDenom: evmDenom, - liveCtx: ctx, - liveEthState: cachekv.NewEvmStore(ctx.KVStore(storeKey), journal.NewManager()), - } -} - -// ============================================================================== -// Balance -// ============================================================================== - -func (sdbr *StateDBReader) GetBalance(addr common.Address) *big.Int { - // Note: bank keeper will return 0 if account/state_object is not found - return sdbr.bk.GetBalance(sdbr.liveCtx, addr[:], sdbr.evmDenom).Amount.BigInt() -} - -// ============================================================================== -// Code -// ============================================================================== - -// GetCodeHash implements the GethStateDB interface by returning -// the code hash of account. -func (sdbr *StateDBReader) GetCodeHash(addr common.Address) common.Hash { - if sdbr.ak.HasAccount(sdbr.liveCtx, addr[:]) { - if ch := prefix.NewStore(sdbr.liveEthState, types.KeyPrefixCodeHash). - Get(addr[:]); ch != nil { - return common.BytesToHash(ch) - } - return types.EmptyCodeHash - } - // if account at addr does not exist, return ZeroCodeHash - return types.ZeroCodeHash -} - -// GetCode implements the GethStateDB interface by returning -// the code of account (nil if not exists). -func (sdbr *StateDBReader) GetCode(addr common.Address) []byte { - codeHash := sdbr.GetCodeHash(addr) - // if account at addr does not exist, GetCodeHash returns ZeroCodeHash so return nil - // if codeHash is empty, i.e. crypto.Keccak256(nil), also return nil - if codeHash == types.ZeroCodeHash || codeHash == types.EmptyCodeHash { - return nil - } - return prefix.NewStore(sdbr.liveEthState, types.KeyPrefixCode).Get(codeHash.Bytes()) -} - -// GetCodeSize implements the GethStateDB interface by returning the size of the -// code associated with the given GethStateDB. -func (sdbr *StateDBReader) GetCodeSize(addr common.Address) int { - return len(sdbr.GetCode(addr)) -} - -// ============================================================================== -// State -// ============================================================================== - -// GetCommittedState implements the GethStateDB interface by returning the -// committed state of an address. -func (sdbr *StateDBReader) GetCommittedState( - addr common.Address, - hash common.Hash, -) common.Hash { - if value := prefix.NewStore(sdbr.liveEthState.GetParent(), - types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { - return common.BytesToHash(value) - } - return common.Hash{} -} - -// GetState implements the GethStateDB interface by returning the current state of an -// address. -func (sdbr *StateDBReader) GetState(addr common.Address, hash common.Hash) common.Hash { - if value := prefix.NewStore(sdbr.liveEthState, - types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { - return common.BytesToHash(value) - } - return common.Hash{} -} - -// ============================================================================== -// Account -// ============================================================================== - -// Exist implements the GethStateDB interface by reporting whether the given account address -// exists in the state. Notably this also returns true for suicided accounts, which is accounted -// for since, `RemoveAccount()` is not called until Commit. -func (sdbr *StateDBReader) Exist(addr common.Address) bool { - return sdbr.ak.HasAccount(sdbr.liveCtx, addr[:]) -} - -// GetNonce implements the GethStateDB interface by returning the nonce -// of an account. -func (sdbr *StateDBReader) GetNonce(addr common.Address) uint64 { - acc := sdbr.ak.GetAccount(sdbr.liveCtx, addr[:]) - if acc == nil { - return 0 - } - return acc.GetSequence() -} - -// Empty implements the GethStateDB interface by returning whether the state object -// is either non-existent or empty according to the EIP161 specification -// (balance = nonce = code = 0) -// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md -func (sdbr *StateDBReader) Empty(addr common.Address) bool { - ch := sdbr.GetCodeHash(addr) - return sdbr.GetNonce(addr) == 0 && - (ch == types.EmptyCodeHash || ch == types.ZeroCodeHash) && - sdbr.GetBalance(addr).Sign() == 0 -} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 5234bc3a7..2cd2fc4df 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Copyright (C) 2022, Berachain Foundation. All rights reserved. // See the file LICENSE for licensing terms. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" @@ -14,4 +14,471 @@ package state_test -// test codecov +import ( + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/core/state" + "github.com/berachain/stargazer/core/state/types" + coretypes "github.com/berachain/stargazer/core/types" + "github.com/berachain/stargazer/lib/crypto" + "github.com/berachain/stargazer/testutil" +) + +var alice = testutil.Alice +var bob = testutil.Bob + +var _ = Describe("StateDB", func() { + var ak state.AccountKeeper + var bk state.BankKeeper + var ctx sdk.Context + var sdb *state.StateDB + + BeforeEach(func() { + ctx, ak, bk, _ = testutil.SetupMinimalKeepers() + sdb = state.NewStateDB(ctx, ak, bk, testutil.EvmKey, "abera") + }) + + Describe("TestCreateAccount", func() { + AfterEach(func() { + Expect(sdb.GetSavedErr()).To(BeNil()) + }) + + It("should create account", func() { + sdb.CreateAccount(alice) + Expect(sdb.Exist(alice)).To(BeTrue()) + }) + + }) + + Describe("TestBalance", func() { + + It("should have start with zero balance", func() { + Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) + }) + + Context("TestAddBalance", func() { + It("should be able to add zero", func() { + Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) + sdb.AddBalance(alice, new(big.Int)) + Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) + }) + + It("should have 100 balance", func() { + sdb.AddBalance(alice, big.NewInt(100)) + Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(100))) + }) + + It("should panic if using negative value", func() { + Expect(func() { + sdb.AddBalance(alice, big.NewInt(-100)) + }).To(Panic()) + }) + }) + + Context("TestSubBalance", func() { + It("should not set balance to negative value", func() { + sdb.SubBalance(alice, big.NewInt(100)) + Expect(sdb.GetSavedErr()).To(HaveOccurred()) + Expect(errors.Unwrap(errors.Unwrap(sdb.GetSavedErr()))).To( + Equal(sdkerrors.ErrInsufficientFunds)) + Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) + }) + It("should panic if using negative value", func() { + Expect(func() { + sdb.SubBalance(alice, big.NewInt(-100)) + }).To(Panic()) + }) + }) + + It("should handle complex balance updates", func() { + // Initial balance for alice should be 0 + Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) + + // Add some balance to alice + sdb.AddBalance(alice, big.NewInt(100)) + Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(100))) + + // Subtract some balance from alice + sdb.SubBalance(alice, big.NewInt(50)) + Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(50))) + + // Add some balance to alice + sdb.AddBalance(alice, big.NewInt(100)) + Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(150))) + + // Subtract some balance from alice + sdb.SubBalance(alice, big.NewInt(200)) + Expect(sdb.GetSavedErr()).To(HaveOccurred()) + Expect(errors.Unwrap(errors.Unwrap(sdb.GetSavedErr()))).To( + Equal(sdkerrors.ErrInsufficientFunds)) + Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(150))) + + }) + }) + + Describe("TestNonce", func() { + When("account exists", func() { + BeforeEach(func() { + sdb.CreateAccount(alice) + }) + It("should have start with zero nonce", func() { + Expect(sdb.GetNonce(alice)).To(Equal(uint64(0))) + }) + It("should have 100 nonce", func() { + sdb.SetNonce(alice, 100) + Expect(sdb.GetNonce(alice)).To(Equal(uint64(100))) + }) + }) + When("account does not exist", func() { + It("should have start with zero nonce", func() { + Expect(sdb.GetNonce(alice)).To(Equal(uint64(0))) + }) + + It("should have 100 nonce", func() { + sdb.SetNonce(alice, 100) + Expect(sdb.GetNonce(alice)).To(Equal(uint64(100))) + }) + }) + }) + + Describe("TestCode & CodeHash", func() { + When("account does not exist", func() { + It("should have empty code hash", func() { + Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) + }) + It("should not have code", func() { // ensure account exists + Expect(sdb.GetCode(alice)).To(BeNil()) + Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) + }) + It("cannot set code", func() { // ensure account exists + sdb.SetCode(alice, []byte("code")) + Expect(sdb.GetCode(alice)).To(BeNil()) + Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) + }) + }) + When("account exists", func() { + BeforeEach(func() { + sdb.CreateAccount(alice) + }) + It("should have zero code hash", func() { + Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash(nil))) + }) + It("should have code", func() { + sdb.SetCode(alice, []byte("code")) + Expect(sdb.GetCode(alice)).To(Equal([]byte("code"))) + Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash([]byte("code")))) + }) + }) + }) + + Describe("TestRefund", func() { + It("should have 0 refund", func() { + Expect(sdb.GetRefund()).To(Equal(uint64(0))) + }) + It("should have 100 refund", func() { + sdb.AddRefund(100) + Expect(sdb.GetRefund()).To(Equal(uint64(100))) + }) + It("should have 0 refund", func() { + sdb.AddRefund(100) + sdb.SubRefund(100) + Expect(sdb.GetRefund()).To(Equal(uint64(0))) + }) + It("should panic and over refund", func() { + Expect(func() { + sdb.SubRefund(200) + }).To(Panic()) + }) + }) + + Describe("Test State", func() { + It("should have empty state", func() { + Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{})) + }) + When("set basic state", func() { + BeforeEach(func() { + sdb.SetState(alice, common.Hash{3}, common.Hash{1}) + }) + + It("should have state", func() { + Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{1})) + }) + + It("should have state changed", func() { + sdb.SetState(alice, common.Hash{3}, common.Hash{2}) + Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{2})) + Expect(sdb.GetCommittedState(alice, common.Hash{3})).To(Equal(common.Hash{})) + }) + + When("state is committed", func() { + BeforeEach(func() { + Expect(sdb.Commit()).Should(BeNil()) + It("should have committed state", func() { + Expect(sdb.GetCommittedState(alice, common.Hash{3})).To(Equal(common.Hash{1})) + }) + It("should maintain committed state", func() { + sdb.SetState(alice, common.Hash{3}, common.Hash{4}) + Expect(sdb.GetCommittedState(alice, common.Hash{3})). + To(Equal(common.Hash{1})) + Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{4})) + }) + }) + }) + }) + + Describe("TestExist", func() { + It("should not exist", func() { + Expect(sdb.Exist(alice)).To(BeFalse()) + }) + When("account is created", func() { + BeforeEach(func() { + sdb.CreateAccount(alice) + }) + It("should exist", func() { + Expect(sdb.Exist(alice)).To(BeTrue()) + }) + When("suicided", func() { + BeforeEach(func() { + // Only contracts can be suicided + sdb.SetCode(alice, []byte("code")) + Expect(sdb.Suicide(alice)).To(BeTrue()) + }) + It("should still exist", func() { + Expect(sdb.Exist(alice)).To(BeTrue()) + }) + When("commit", func() { + BeforeEach(func() { + Expect(sdb.Commit()).To(BeNil()) + }) + It("should not exist", func() { + Expect(sdb.Exist(alice)).To(BeFalse()) + }) + }) + }) + }) + }) + + Describe("TestReset", func() { + BeforeEach(func() { + sdb.AddRefund(1000) + sdb.AddLog(&coretypes.Log{}) + sdb.PrepareForTransition(common.Hash{1}, common.Hash{2}, 3, 4) + + sdb.CreateAccount(alice) + sdb.SetCode(alice, []byte("code")) + sdb.Suicide(alice) + }) + It("should have reset state", func() { + sdb.Reset(ctx) + Expect(sdb.GetNonce(alice)).To(Equal(uint64(0))) + Expect(sdb.Logs()).To(BeNil()) + Expect(sdb.GetRefund()).To(Equal(uint64(0))) + Expect(sdb.GetSavedErr()).To(BeNil()) + Expect(sdb.HasSuicided(alice)).To(BeFalse()) + // todo check the txhash and blockhash stuff + Expect(sdb, state.NewStateDB(ctx, ak, bk, testutil.EvmKey, "bera")) + }) + }) + + Describe("TestEmpty", func() { + When("account does not exist", func() { + It("should return true", func() { + Expect(sdb.Empty(alice)).To(BeTrue()) + }) + }) + When("account exists", func() { + BeforeEach(func() { + sdb.CreateAccount(alice) + }) + It("new account", func() { + Expect(sdb.Empty(alice)).To(BeTrue()) + }) + It("has a balance", func() { + sdb.AddBalance(alice, big.NewInt(1)) + Expect(sdb.Empty(alice)).To(BeFalse()) + }) + It("has a nonce", func() { + sdb.SetNonce(alice, 1) + Expect(sdb.Empty(alice)).To(BeFalse()) + }) + It("has code", func() { + sdb.SetCode(alice, []byte{0x69}) + Expect(sdb.Empty(alice)).To(BeFalse()) + }) + }) + }) + + Describe("TestSuicide", func() { + BeforeEach(func() { + sdb.CreateAccount(alice) + }) + It("cannot suicide eoa", func() { + Expect(sdb.Suicide(alice)).To(BeFalse()) + Expect(sdb.HasSuicided(alice)).To(BeFalse()) + }) + + initialAliceBal := big.NewInt(69) + initialBobBal := big.NewInt(420) + aliceCode := []byte("alicecode") + bobCode := []byte("bobcode") + + When("address has code and balance", func() { + BeforeEach(func() { + sdb.SetCode(alice, aliceCode) + sdb.SetCode(bob, bobCode) + sdb.AddBalance(alice, initialAliceBal) + sdb.AddBalance(bob, initialBobBal) + // Give Alice some state + for i := 0; i < 5; i++ { + sdb.SetState(alice, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), + common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) + } + // Give Bob some state + for i := 5; i < 15; i++ { + sdb.SetState(bob, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), + common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) + } + }) + + When("alice commits suicide", func() { + BeforeEach(func() { + Expect(sdb.Suicide(alice)).To(BeTrue()) + Expect(sdb.HasSuicided(alice)).To(BeTrue()) + }) + + It("alice should be marked as suicidal, but not bob", func() { + Expect(sdb.HasSuicided(alice)).To(BeTrue()) + Expect(sdb.HasSuicided(bob)).To(BeFalse()) + }) + + It("alice should have her balance set to 0", func() { + Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) + Expect(sdb.GetBalance(bob)).To(Equal(initialBobBal)) + }) + + It("both alice and bob should have their code and state untouched", func() { + Expect(sdb.GetCode(alice)).To(Equal(aliceCode)) + Expect(sdb.GetCode(bob)).To(Equal(bobCode)) + for i := 0; i < 5; i++ { + Expect(sdb.GetState(alice, + common.BytesToHash([]byte(fmt.Sprintf("key%d", i))))). + To(Equal(common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))) + } + + for i := 5; i < 15; i++ { + Expect(sdb.GetState(bob, + common.BytesToHash([]byte(fmt.Sprintf("key%d", i))))). + To(Equal(common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))) + } + }) + + When("commit is called", func() { + BeforeEach(func() { + _ = sdb.Commit() + }) + + It("alice should have her code and state wiped, but not bob", func() { + Expect(sdb.GetCode(alice)).To(BeNil()) + Expect(sdb.GetCode(bob)).To(Equal(bobCode)) + var aliceStorage types.Storage + err := sdb.ForEachStorage(alice, + func(key, value common.Hash) bool { + aliceStorage = append(aliceStorage, + types.NewState(key, value)) + return true + }) + Expect(err).To(BeNil()) + Expect(len(aliceStorage)).To(BeZero()) + + var bobStorage types.Storage + err = sdb.ForEachStorage(bob, + func(key, value common.Hash) bool { + bobStorage = append(bobStorage, types.NewState(key, value)) + return true + }) + Expect(err).To(BeNil()) + Expect(len(bobStorage)).To(Equal(10)) + }) + }) + + }) + }) + }) + Describe("TestAccount", func() { + It("account does not exist", func() { + Expect(sdb.Exist(alice)).To(BeFalse()) + Expect(sdb.Empty(alice)).To(BeTrue()) + Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) + Expect(sdb.GetNonce(alice)).To(BeZero()) + Expect(sdb.GetCodeHash(alice)).To(Equal(common.Hash{})) + Expect(sdb.GetCode(alice)).To(BeNil()) + Expect(sdb.GetCodeSize(alice)).To(BeZero()) + Expect(sdb.GetState(alice, common.Hash{})).To(Equal(common.Hash{})) + Expect(sdb.GetRefund()).To(BeZero()) + Expect(sdb.GetCommittedState(alice, common.Hash{})).To(Equal(common.Hash{})) + }) + When("account exists", func() { + BeforeEach(func() { + sdb.AddBalance(alice, big.NewInt(56)) + sdb.SetNonce(alice, 59) + }) + + It("accidental override account", func() { + // override + sdb.CreateAccount(alice) + + // check balance is not reset + Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(56))) + }) + }) + + }) + + Describe("TestSnapshot", func() { + key := common.BytesToHash([]byte("key")) + value1 := common.BytesToHash([]byte("value1")) + value2 := common.BytesToHash([]byte("value2")) + It("simple revert", func() { + revision := sdb.Snapshot() + Expect(revision).To(Equal(0)) + sdb.SetState(alice, key, value1) + Expect(sdb.GetState(alice, key)).To(Equal(value1)) + sdb.RevertToSnapshot(revision) + Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{})) + }) + It("nested snapshot & revert", func() { + revision1 := sdb.Snapshot() + Expect(revision1).To(Equal(0)) + sdb.SetState(alice, key, value1) + revision2 := sdb.Snapshot() + Expect(revision2).To(Equal(1)) + sdb.SetState(alice, key, value2) + Expect(sdb.GetState(alice, key)).To(Equal(value2)) + + sdb.RevertToSnapshot(revision2) + Expect(sdb.GetState(alice, key)).To(Equal(value1)) + + sdb.RevertToSnapshot(revision1) + Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{})) + }) + It("jump revert", func() { + revision1 := sdb.Snapshot() + Expect(revision1).To(Equal(0)) + sdb.SetState(alice, key, value1) + sdb.Snapshot() + sdb.SetState(alice, key, value2) + Expect(sdb.GetState(alice, key)).To(Equal(value2)) + sdb.RevertToSnapshot(revision1) + Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{})) + }) + }) + }) +}) diff --git a/core/state/store/cachekv/store.go b/core/state/store/cachekv/store.go index 6b475e782..9065b02d9 100644 --- a/core/state/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -12,7 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -//nolint:ireturn // cachekv needs to return interfaces. + package cachekv import ( diff --git a/core/state/store/cachekv/store_test.go b/core/state/store/cachekv/store_test.go index 678ac696c..9db95e1fc 100644 --- a/core/state/store/cachekv/store_test.go +++ b/core/state/store/cachekv/store_test.go @@ -30,7 +30,7 @@ import ( "github.com/berachain/stargazer/core/state/store/journal" ) -func newParent() types.CacheKVStore { //nolint:ireturn // must return interface. +func newParent() types.CacheKVStore { return sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) } diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go index 3198c9577..2b9c65f67 100644 --- a/core/state/store/cachemulti/store.go +++ b/core/state/store/cachemulti/store.go @@ -42,7 +42,7 @@ func NewStoreFrom(ms storetypes.MultiStore) *Store { // GetKVStore shadows cosmos sdk storetypes.MultiStore function. Routes native module calls to // read the dirty state during an eth tx. Any state that is modified by evm statedb, and using the // context passed in to StateDB, will be routed to a tx-specific cache kv store. -func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolint:ireturn // must return interface. +func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { // check if cache kv store already used if cacheKVStore, exists := s.stores[key]; exists { return cacheKVStore @@ -65,7 +65,7 @@ func (s *Store) Write() { } } -func (s *Store) newCacheKVStore( //nolint:ireturn // must return interface. +func (s *Store) newCacheKVStore( key storetypes.StoreKey, kvstore storetypes.KVStore, ) storetypes.CacheKVStore { diff --git a/core/types/imported.go b/core/types/imported.go new file mode 100644 index 000000000..90d3f9b62 --- /dev/null +++ b/core/types/imported.go @@ -0,0 +1,10 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/core/types" +) + +type ( + AccessList = types.AccessList + Log = types.Log +) diff --git a/go.mod b/go.mod index 9a6399501..c9744d51c 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,8 @@ require ( github.com/onsi/gomega v1.24.1 github.com/segmentio/golines v0.11.0 github.com/stretchr/testify v1.8.1 + github.com/tendermint/tendermint v0.37.0-rc2 + github.com/tendermint/tm-db v0.6.7 github.com/tidwall/btree v1.5.2 golang.org/x/tools v0.4.0 golang.org/x/tools/gopls v0.11.0 @@ -75,6 +77,7 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.1 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.4 // indirect github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect @@ -102,6 +105,7 @@ require ( github.com/ettle/strcase v0.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fatih/structtag v1.2.0 // indirect + github.com/felixge/httpsnoop v1.0.2 // indirect github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -129,10 +133,12 @@ require ( github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/uuid v4.2.0+incompatible // indirect + github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect @@ -150,6 +156,8 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/gookit/color v1.5.2 // indirect github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect + github.com/gorilla/handlers v1.5.1 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect @@ -283,8 +291,6 @@ require ( github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tendermint/tendermint v0.37.0-rc2 // indirect - github.com/tendermint/tm-db v0.6.7 // indirect github.com/tetafro/godot v1.4.11 // indirect github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect github.com/timonwong/loggercheck v0.9.3 // indirect diff --git a/go.sum b/go.sum index e019fd3f0..470d45e7c 100644 --- a/go.sum +++ b/go.sum @@ -179,6 +179,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w= github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= @@ -197,6 +203,8 @@ github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXy github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= +github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= +github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gogoproto v1.4.3 h1:RP3yyVREh9snv/lsOvmsAPQt8f44LgL281X0IOIhhcI= github.com/cosmos/gogoproto v1.4.3/go.mod h1:0hLIG5TR7IvV1fme1HCFKjfzW9X2x0Mo+RooWXCnOWU= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= @@ -275,6 +283,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= @@ -289,7 +299,9 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= @@ -375,7 +387,9 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -400,6 +414,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -461,6 +476,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -496,6 +512,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 h1:PVRE9d4AQKmbelZ7emNig1+NT27DUmKZn5qXxfio54U= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -1068,6 +1085,7 @@ go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY= go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1292,6 +1310,7 @@ golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1494,6 +1513,7 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c= google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1513,7 +1533,10 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1529,6 +1552,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= diff --git a/core/state/statedb_reader_test.go b/lib/crypto/imported.go similarity index 67% rename from core/state/statedb_reader_test.go rename to lib/crypto/imported.go index 1bd6ea9ca..9b7142adc 100644 --- a/core/state/statedb_reader_test.go +++ b/lib/crypto/imported.go @@ -1,4 +1,4 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// Copyright (C) 2022, Berachain Foundation. All rights reserved. // See the file LICENSE for licensing terms. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" @@ -12,4 +12,16 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package state_test +package crypto + +import "github.com/ethereum/go-ethereum/crypto" + +var ( + Keccak256 = crypto.Keccak256 + Keccak256Hash = crypto.Keccak256Hash + CreateAddress = crypto.CreateAddress + ToECDSA = crypto.ToECDSA + FromECDSA = crypto.FromECDSA + HexToECDSA = crypto.HexToECDSA + PubkeyToAddress = crypto.PubkeyToAddress +) diff --git a/testutil/setup.go b/testutil/setup.go new file mode 100644 index 000000000..1c683a1d3 --- /dev/null +++ b/testutil/setup.go @@ -0,0 +1,104 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package testutil + +import ( + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/cosmos/cosmos-sdk/x/auth" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + db "github.com/tendermint/tm-db" + + "github.com/berachain/stargazer/common" +) + +var ( + AccKey = sdk.NewKVStoreKey("acc") + BankKey = sdk.NewKVStoreKey("bank") + EvmKey = sdk.NewKVStoreKey("evm") + StakingKey = sdk.NewKVStoreKey("staking") + Alice = common.BytesToAddress([]byte("alice")) + Bob = common.BytesToAddress([]byte("bob")) +) + +func SetupMinimalKeepers() (sdk.Context, + authkeeper.AccountKeeper, bankkeeper.Keeper, stakingkeeper.Keeper) { + dbm := db.NewMemDB() + ms := store.NewCommitMultiStore(dbm) + + ms.MountStoreWithDB(AccKey, storetypes.StoreTypeIAVL, dbm) + ms.MountStoreWithDB(BankKey, storetypes.StoreTypeIAVL, dbm) + ms.MountStoreWithDB(EvmKey, storetypes.StoreTypeIAVL, dbm) + ms.MountStoreWithDB(StakingKey, storetypes.StoreTypeIAVL, dbm) + + err := ms.LoadLatestVersion() + if err != nil { + panic(err) + } + + ctx := sdk.NewContext(ms, tmproto.Header{}, false, log.TestingLogger()) + + encodingConfig := testutil.MakeTestEncodingConfig( + auth.AppModuleBasic{}, + bank.AppModuleBasic{}, + staking.AppModuleBasic{}, + ) + + ak := authkeeper.NewAccountKeeper( + encodingConfig.Codec, + AccKey, + authtypes.ProtoBaseAccount, + map[string][]string{ + stakingtypes.NotBondedPoolName: {authtypes.Minter, authtypes.Burner}, + stakingtypes.BondedPoolName: {authtypes.Minter, authtypes.Burner}, + "evm": {authtypes.Minter, authtypes.Burner}, + "staking": {authtypes.Minter, authtypes.Burner}, + }, + "bera", + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + ak.SetModuleAccount(ctx, + authtypes.NewEmptyModuleAccount("evm", authtypes.Minter, authtypes.Burner)) + + bk := bankkeeper.NewBaseKeeper( + encodingConfig.Codec, + BankKey, + ak, + nil, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + sk := stakingkeeper.NewKeeper( + encodingConfig.Codec, + StakingKey, + ak, + bk, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + return ctx, ak, bk, *sk +} From f77aab1da4a4f88682d32b80d328f8d0dc3b3766 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 10:42:20 -0500 Subject: [PATCH 35/76] lint --- core/evm.go | 14 ++++++++++++++ core/state/cache_entries.go | 9 +++++---- core/state/state_test.go | 14 ++++++++++++++ core/state/statedb.go | 4 ++-- core/state/store/cachekv/store.go | 1 + core/state/store/cachekv/store_test.go | 1 + core/state/store/cachemulti/store.go | 1 + core/types/imported.go | 14 ++++++++++++++ testutil/setup.go | 2 +- 9 files changed, 53 insertions(+), 7 deletions(-) diff --git a/core/evm.go b/core/evm.go index 9a8bc9592..85191de01 100644 --- a/core/evm.go +++ b/core/evm.go @@ -1 +1,15 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + package core diff --git a/core/state/cache_entries.go b/core/state/cache_entries.go index c082a051a..06ba5f656 100644 --- a/core/state/cache_entries.go +++ b/core/state/cache_entries.go @@ -12,6 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//nolint:ireturn // all `CacheEntries` must adhere to the same interface. package state import ( @@ -37,12 +38,12 @@ type ( // AddLogChange // ============================================================================== -// implements journal.CacheEntry. +// `Revert` implements `journal.CacheEntry`. func (ce *AddLogChange) Revert() { ce.sdb.logs = ce.sdb.logs[:len(ce.sdb.logs)-1] } -// implements journal.CacheEntry. +// `Clone` implements `journal.CacheEntry`. func (ce *AddLogChange) Clone() journal.CacheEntry { return &AddLogChange{ sdb: ce.sdb, @@ -53,12 +54,12 @@ func (ce *AddLogChange) Clone() journal.CacheEntry { // RefundChange // ============================================================================== -// implements journal.CacheEntry. +// `Revert` implements `journal.CacheEntry`. func (ce *RefundChange) Revert() { ce.sdb.refund = ce.prev } -// implements journal.CacheEntry. +// `Clone` implements `journal.CacheEntry`. func (ce *RefundChange) Clone() journal.CacheEntry { return &RefundChange{ sdb: ce.sdb, diff --git a/core/state/state_test.go b/core/state/state_test.go index b8e6de8a3..5549b4d57 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -1,3 +1,17 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + package state_test import ( diff --git a/core/state/statedb.go b/core/state/statedb.go index 7d6e4d8c8..9b01d5df5 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -108,7 +108,7 @@ var ( // codeHash of 0x000... This is because the Bank Module does not know that the account is an // EVM account, and so it does not set the codeHash. This is totally fine, we just need to // check both for both the codeHash being 0x000... as well as the codeHash being 0x567... -type StateDB struct { //nolint: revive +type StateDB struct { //nolint: revive // we like the vibe. *cachemulti.Store // eth state stores: required for vm.StateDB @@ -621,7 +621,7 @@ func (sdb *StateDB) GetSavedErr() error { return sdb.savedErr } -// setErrorUnsafe sets error but should be called in medhods that already have locks +// setErrorUnsafe sets error but should be called in medhods that already have locks. func (sdb *StateDB) setErrorUnsafe(err error) { if sdb.savedErr == nil { sdb.savedErr = err diff --git a/core/state/store/cachekv/store.go b/core/state/store/cachekv/store.go index 9065b02d9..220e532ff 100644 --- a/core/state/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -13,6 +13,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +//nolint:ireturn // all Iterators must conform to the same interface. package cachekv import ( diff --git a/core/state/store/cachekv/store_test.go b/core/state/store/cachekv/store_test.go index 9db95e1fc..0b4943fa5 100644 --- a/core/state/store/cachekv/store_test.go +++ b/core/state/store/cachekv/store_test.go @@ -30,6 +30,7 @@ import ( "github.com/berachain/stargazer/core/state/store/journal" ) +//nolint:nolintlint,ireturn // must return interface. func newParent() types.CacheKVStore { return sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) } diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go index 2b9c65f67..98597eb96 100644 --- a/core/state/store/cachemulti/store.go +++ b/core/state/store/cachemulti/store.go @@ -12,6 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//nolint:ireturn // needs to be generic. package cachemulti import ( diff --git a/core/types/imported.go b/core/types/imported.go index 90d3f9b62..4513f7502 100644 --- a/core/types/imported.go +++ b/core/types/imported.go @@ -1,3 +1,17 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + package types import ( diff --git a/testutil/setup.go b/testutil/setup.go index 1c683a1d3..7bf7cd5ac 100644 --- a/testutil/setup.go +++ b/testutil/setup.go @@ -45,7 +45,7 @@ var ( ) func SetupMinimalKeepers() (sdk.Context, - authkeeper.AccountKeeper, bankkeeper.Keeper, stakingkeeper.Keeper) { + authkeeper.AccountKeeper, bankkeeper.BaseKeeper, stakingkeeper.Keeper) { dbm := db.NewMemDB() ms := store.NewCommitMultiStore(dbm) From c40bf965419a18d2281723afe4a0e786c884f40d Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 10:43:34 -0500 Subject: [PATCH 36/76] lint --- core/state/store/journal/manager.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/state/store/journal/manager.go b/core/state/store/journal/manager.go index b846cdf00..c15f398e1 100644 --- a/core/state/store/journal/manager.go +++ b/core/state/store/journal/manager.go @@ -21,7 +21,7 @@ import ( // `ManagerI` is an interface that defines the methods that a journal manager must implement. // Journal managers support holding cache entries and reverting to a certain index. type ManagerI[T any] interface { - // The journal manager is a stack. + // `ManagerI` implements `ds.StackI[CacheEntry]`. ds.StackI[CacheEntry] // `ManagerI` implements `Cloneable`. @@ -33,6 +33,7 @@ var _ ManagerI[*Manager] = (*Manager)(nil) // `Manager` is a struct that holds a slice of CacheEntry instances. type Manager struct { + // The journal manager is a stack. *ds.Stack[CacheEntry] } From 02c03598d6e58ae51c1f151ade315b8a14a8dc54 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 10:45:09 -0500 Subject: [PATCH 37/76] lint --- core/state/store/cachekv/store.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/state/store/cachekv/store.go b/core/state/store/cachekv/store.go index 220e532ff..28e9958b9 100644 --- a/core/state/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -11,7 +11,6 @@ // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// //nolint:ireturn // all Iterators must conform to the same interface. package cachekv From 6eb5c7ea636079785cef2b8d6ce2e0a81e28144a Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 12:19:12 -0500 Subject: [PATCH 38/76] statedb minor improvements --- core/state/statedb.go | 255 +++++++++------------------ core/state/store/cachemulti/store.go | 11 +- 2 files changed, 96 insertions(+), 170 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 9b01d5df5..5cdb012bf 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,6 +18,7 @@ import ( "bytes" "fmt" "math/big" + "reflect" "github.com/cosmos/cosmos-sdk/store/prefix" storetypes "github.com/cosmos/cosmos-sdk/store/types" @@ -38,12 +39,8 @@ type GethStateDB = gevm.StateDB // support additional state transition functionalities. In particular it supports getting the // cosmos sdk context for natively running stateful precompiled contracts. type ExtStateDB interface { - storetypes.MultiStore GethStateDB - // GetContext returns the cosmos sdk context with the statedb multistore attached - GetContext() sdk.Context - // GetSavedErr returns the error saved in the statedb GetSavedErr() error @@ -65,22 +62,8 @@ type IntraBlockStateDB interface { Reset(sdk.Context) } -const ( - keyPrefixCode byte = iota - keyPrefixHash -) - var ( - // EmptyCodeHash is the code hash of an empty code - // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. - EmptyCodeHash = crypto.Keccak256Hash(nil) - ZeroCodeHash = common.Hash{} - - KeyPrefixCode = []byte{keyPrefixCode} - KeyPrefixCodeHash = []byte{keyPrefixHash} - // todo: add later? - // StateDBStoreKey = sdk.NewKVStoreKey(types.StoreKey). - _ ExtStateDB = (*StateDB)(nil) + _ IntraBlockStateDB = (*StateDB)(nil) ) // Welcome to the StateDB implementation. This is a wrapper around a cachemulti.Store @@ -109,17 +92,19 @@ var ( // EVM account, and so it does not set the codeHash. This is totally fine, we just need to // check both for both the codeHash being 0x000... as well as the codeHash being 0x567... type StateDB struct { //nolint: revive // we like the vibe. - *cachemulti.Store + // We maintain a context in the StateDB, so that we can pass it with the correctly + // configured multi-store to the precompiled contracts. + ctx sdk.Context + + // Store a reference to the multi-store, in `ctx` so that we can access it directly. + cms cachemulti.StateDBCacheMultistore // eth state stores: required for vm.StateDB // We store references to these stores, so that we can access them // directly, without having to go through the MultiStore interface. liveEthState cachekv.StateDBCacheKVStore - // cosmos state ctx: required for sdk.MultiStore - ctx sdk.Context - - // keepers used for balance and account information + // keepers used for balance and account information. ak AccountKeeper bk BankKeeper @@ -132,7 +117,7 @@ type StateDB struct { //nolint: revive // we like the vibe. // we load the evm denom in the constructor, to prevent going to // the params to get it mid interpolation. - evmDenom string + evmDenom string // todo: get from params ( we have a store so like why not ) // The refund counter, also used by state transitioning. refund uint64 @@ -165,24 +150,34 @@ func NewStateDB( storeKey storetypes.StoreKey, evmDenom string, ) *StateDB { + var ok bool sdb := &StateDB{ - Store: cachemulti.NewStoreFrom(ctx.MultiStore()), + ctx: ctx.WithMultiStore(cachemulti.NewStoreFrom(ctx.MultiStore())), ak: ak, bk: bk, evmDenom: evmDenom, storeKey: storeKey, } - sdb.ctx = ctx.WithMultiStore(sdb) - // Must support directly accessing the parent store. - sdb.liveEthState, _ = sdb.ctx.MultiStore(). - GetKVStore(storeKey).(cachekv.StateDBCacheKVStore) + // Save a pointer and check to make sure that the MultiStore is a StateDBCacheMultistore. + if sdb.cms, ok = sdb.ctx.MultiStore().(cachemulti.StateDBCacheMultistore); !ok { + sdb.savedErr = fmt.Errorf( + "expected MultiStore to be a StateDBCacheMultistore, got %T", + reflect.TypeOf(sdb.ctx.MultiStore()), + ) + return sdb + } + + // Must support directly accessing the parent store + sdb.liveEthState, _ = sdb.cms. + GetKVStore(sdb.storeKey).(cachekv.StateDBCacheKVStore) + return sdb } -func (sdb *StateDB) GetEvmDenom() string { - return sdb.evmDenom -} +// =========================================================================== +// Account +// =========================================================================== // CreateAccount implements the GethStateDB interface by creating a new account // in the account keeper. It will allow accounts to be overridden. @@ -193,7 +188,8 @@ func (sdb *StateDB) CreateAccount(addr common.Address) { sdb.ak.SetAccount(sdb.ctx, acc) // initialize the code hash to empty - prefix.NewStore(sdb.liveEthState, KeyPrefixCodeHash).Set(addr[:], EmptyCodeHash[:]) + prefix.NewStore(sdb.liveEthState, types.KeyPrefixCodeHash). + Set(addr[:], types.EmptyCodeHash[:]) } // ============================================================================= @@ -217,7 +213,7 @@ func (sdb *StateDB) Reset(ctx sdk.Context) { // sdb.MultiStore = cachemulti.NewStoreFrom(ctx.MultiStore()) // sdb.ctx = ctx.WithMultiStore(sdb.MultiStore) // // // Must support directly accessing the parent store. - // // sdb.liveEthState = sdb.ctx.MultiStore(). + // // sdb.liveEthState = sdb.ctx.cms. // // GetKVStore(sdb.storeKey).(cachekv.StateDBCacheKVStore) // sdb.savedErr = nil // sdb.refund = 0 @@ -233,13 +229,13 @@ func (sdb *StateDB) Reset(ctx sdk.Context) { // Balance // ============================================================================= -// GetBalance implements GethStateDB interface. +// GetBalance implements `GethStateDB` interface. func (sdb *StateDB) GetBalance(addr common.Address) *big.Int { // Note: bank keeper will return 0 if account/state_object is not found return sdb.bk.GetBalance(sdb.ctx, addr[:], sdb.evmDenom).Amount.BigInt() } -// AddBalance implements the GethStateDB interface by adding the given amount +// AddBalance implements the `GethStateDB` interface by adding the given amount // from the account associated with addr. If the account does not exist, it will be // created. func (sdb *StateDB) AddBalance(addr common.Address, amount *big.Int) { @@ -257,7 +253,7 @@ func (sdb *StateDB) AddBalance(addr common.Address, amount *big.Int) { } } -// SubBalance implements the GethStateDB interface by subtracting the given amount +// SubBalance implements the `GethStateDB` interface by subtracting the given amount // from the account associated with addr. func (sdb *StateDB) SubBalance(addr common.Address, amount *big.Int) { coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) @@ -290,7 +286,7 @@ func (sdb *StateDB) SendBalance(from, to common.Address, amount *big.Int) { // Nonce // ============================================================================= -// GetNonce implements the GethStateDB interface by returning the nonce +// GetNonce implements the `GethStateDB` interface by returning the nonce // of an account. func (sdb *StateDB) GetNonce(addr common.Address) uint64 { acc := sdb.ak.GetAccount(sdb.ctx, addr[:]) @@ -300,7 +296,7 @@ func (sdb *StateDB) GetNonce(addr common.Address) uint64 { return acc.GetSequence() } -// SetNonce implements the GethStateDB interface by setting the nonce +// SetNonce implements the `GethStateDB` interface by setting the nonce // of an account. func (sdb *StateDB) SetNonce(addr common.Address, nonce uint64) { // get the account or create a new one if doesn't exist @@ -320,49 +316,49 @@ func (sdb *StateDB) SetNonce(addr common.Address, nonce uint64) { // Code // ============================================================================= -// GetCodeHash implements the GethStateDB interface by returning +// GetCodeHash implements the `GethStateDB` interface by returning // the code hash of account. func (sdb *StateDB) GetCodeHash(addr common.Address) common.Hash { if sdb.ak.HasAccount(sdb.ctx, addr[:]) { if ch := prefix.NewStore(sdb.liveEthState, - KeyPrefixCodeHash).Get(addr[:]); ch != nil { + types.KeyPrefixCodeHash).Get(addr[:]); ch != nil { return common.BytesToHash(ch) } - return EmptyCodeHash + return types.EmptyCodeHash } // if account at addr does not exist, return ZeroCodeHash - return ZeroCodeHash + return types.ZeroCodeHash } -// GetCode implements the GethStateDB interface by returning +// GetCode implements the `GethStateDB` interface by returning // the code of account (nil if not exists). func (sdb *StateDB) GetCode(addr common.Address) []byte { codeHash := sdb.GetCodeHash(addr) // if account at addr does not exist, GetCodeHash returns ZeroCodeHash so return nil // if codeHash is empty, i.e. crypto.Keccak256(nil), also return nil - if codeHash == ZeroCodeHash || codeHash == EmptyCodeHash { + if codeHash == types.ZeroCodeHash || codeHash == types.EmptyCodeHash { return nil } - return prefix.NewStore(sdb.liveEthState, KeyPrefixCode).Get(codeHash.Bytes()) + return prefix.NewStore(sdb.liveEthState, types.KeyPrefixCode).Get(codeHash.Bytes()) } -// SetCode implements the GethStateDB interface by setting the code hash and +// SetCode implements the `GethStateDB` interface by setting the code hash and // code for the given account. func (sdb *StateDB) SetCode(addr common.Address, code []byte) { codeHash := crypto.Keccak256Hash(code) - prefix.NewStore(sdb.liveEthState, KeyPrefixCodeHash).Set(addr[:], codeHash[:]) + prefix.NewStore(sdb.liveEthState, types.KeyPrefixCodeHash).Set(addr[:], codeHash[:]) // store or delete code if len(code) == 0 { - prefix.NewStore(sdb.liveEthState, KeyPrefixCode).Delete(codeHash[:]) + prefix.NewStore(sdb.liveEthState, types.KeyPrefixCode).Delete(codeHash[:]) } else { - prefix.NewStore(sdb.liveEthState, KeyPrefixCode).Set(codeHash[:], code) + prefix.NewStore(sdb.liveEthState, types.KeyPrefixCode).Set(codeHash[:], code) } } -// GetCodeSize implements the GethStateDB interface by returning the size of the -// code associated with the given GethStateDB. +// GetCodeSize implements the `GethStateDB` interface by returning the size of the +// code associated with the given `GethStateDB`. func (sdb *StateDB) GetCodeSize(addr common.Address) int { return len(sdb.GetCode(addr)) } @@ -371,24 +367,24 @@ func (sdb *StateDB) GetCodeSize(addr common.Address) int { // Refund // ============================================================================= -// AddRefund implements the GethStateDB interface by adding gas to the +// `AddRefund` implements the `GethStateDB` interface by adding gas to the // refund counter. func (sdb *StateDB) AddRefund(gas uint64) { - sdb.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) + sdb.cms.JournalManager().Push(&RefundChange{sdb, sdb.refund}) sdb.refund += gas } -// SubRefund implements the GethStateDB interface by subtracting gas from the +// `SubRefund` implements the `GethStateDB` interface by subtracting gas from the // refund counter. If the gas is greater than the refund counter, it will panic. func (sdb *StateDB) SubRefund(gas uint64) { - sdb.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) + sdb.cms.JournalManager().Push(&RefundChange{sdb, sdb.refund}) if gas > sdb.refund { panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, sdb.refund)) } sdb.refund -= gas } -// GetRefund implements the GethStateDB interface by returning the current +// `GetRefund` implements the `GethStateDB` interface by returning the current // value of the refund counter. func (sdb *StateDB) GetRefund() uint64 { return sdb.refund @@ -398,7 +394,7 @@ func (sdb *StateDB) GetRefund() uint64 { // State // ============================================================================= -// GetCommittedState implements the GethStateDB interface by returning the +// `GetCommittedState` implements the `GethStateDB` interface by returning the // committed state of an address. func (sdb *StateDB) GetCommittedState( addr common.Address, @@ -411,7 +407,7 @@ func (sdb *StateDB) GetCommittedState( return common.Hash{} } -// GetState implements the GethStateDB interface by returning the current state of an +// `GetState` implements the `GethStateDB` interface by returning the current state of an // address. func (sdb *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { if value := prefix.NewStore(sdb.liveEthState, @@ -421,7 +417,7 @@ func (sdb *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash return common.Hash{} } -// SetState implements the GethStateDB interface by setting the state of an +// `SetState` implements the `GethStateDB` interface by setting the state of an // address. func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { // For performance reasons, we don't check to ensure the account exists before we execute. @@ -429,10 +425,10 @@ func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { // SSTORE opcode in the EVM, which will only ever be called on an account that exists, since // it would with 100% certainty have been created by a prior Create, thus setting its code // hash. - // CONTRACT: never manually call SetState outside of the the `opSstore` in the interpreter. - + // + // CONTRACT: never manually call SetState outside of `opSstore`, or InitGenesis. store := prefix.NewStore(sdb.liveEthState, types.AddressStoragePrefix(addr)) - if len(value) == 0 || value == ZeroCodeHash { + if len(value) == 0 || value == types.ZeroCodeHash { store.Delete(key[:]) } else { store.Set(key[:], value[:]) @@ -449,7 +445,7 @@ func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { func (sdb *StateDB) Suicide(addr common.Address) bool { // only smart contracts can commit suicide ch := sdb.GetCodeHash(addr) - if ch == ZeroCodeHash || ch == EmptyCodeHash { + if ch == types.ZeroCodeHash || ch == types.EmptyCodeHash { return false } @@ -462,7 +458,7 @@ func (sdb *StateDB) Suicide(addr common.Address) bool { return true } -// HasSuicided implements the GethStateDB interface by returning if the contract was suicided +// `HasSuicided` implements the `GethStateDB` interface by returning if the contract was suicided // in current transaction. func (sdb *StateDB) HasSuicided(addr common.Address) bool { for _, suicide := range sdb.suicides { @@ -477,21 +473,21 @@ func (sdb *StateDB) HasSuicided(addr common.Address) bool { // Exist & Empty // ============================================================================= -// Exist implements the GethStateDB interface by reporting whether the given account address +// `Exist` implements the `GethStateDB` interface by reporting whether the given account address // exists in the state. Notably this also returns true for suicided accounts, which is accounted // for since, `RemoveAccount()` is not called until Commit. func (sdb *StateDB) Exist(addr common.Address) bool { return sdb.ak.HasAccount(sdb.ctx, addr[:]) } -// Empty implements the GethStateDB interface by returning whether the state object +// `Empty` implements the `GethStateDB` interface by returning whether the state object // is either non-existent or empty according to the EIP161 specification // (balance = nonce = code = 0) // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md func (sdb *StateDB) Empty(addr common.Address) bool { ch := sdb.GetCodeHash(addr) return sdb.GetNonce(addr) == 0 && - (ch == EmptyCodeHash || ch == ZeroCodeHash) && + (ch == types.EmptyCodeHash || ch == types.ZeroCodeHash) && sdb.GetBalance(addr).Sign() == 0 } @@ -502,11 +498,12 @@ func (sdb *StateDB) Empty(addr common.Address) bool { // `RevertToSnapshot` implements `StateDB`. func (sdb *StateDB) RevertToSnapshot(id int) { // revert and discard all journal entries after snapshot id - sdb.JournalMgr.PopToSize(id) + sdb.cms.JournalManager().PopToSize(id) } +// `Snapshot` implements `StateDB`. func (sdb *StateDB) Snapshot() int { - return sdb.JournalMgr.Size() + return sdb.cms.JournalManager().Size() } // ============================================================================= @@ -515,7 +512,7 @@ func (sdb *StateDB) Snapshot() int { // AddLog implements the GethStateDB interface by adding the given log to the current transaction. func (sdb *StateDB) AddLog(log *coretypes.Log) { - sdb.JournalMgr.Push(&AddLogChange{sdb}) + sdb.cms.JournalManager().Push(&AddLogChange{sdb}) log.TxHash = sdb.txHash log.BlockHash = sdb.blockHash log.TxIndex = sdb.txIndex @@ -533,11 +530,11 @@ func (sdb *StateDB) Logs() []*coretypes.Log { // ForEachStorage // ============================================================================= -// ForEachStorage implements the GethStateDB interface by iterating through the contract state +// `ForEachStorage` implements the `GethStateDB` interface by iterating through the contract state // contract storage, the iteration order is not defined. // // Note: We do not support iterating through any storage that is modified before calling -// ForEachStorage; only committed state is iterated through. +// `ForEachStorage`; only committed state is iterated through. func (sdb *StateDB) ForEachStorage( addr common.Address, cb func(key, value common.Hash) bool, @@ -558,16 +555,8 @@ func (sdb *StateDB) ForEachStorage( return nil } -// AddPreimage implements the the GethStateDB interface, but currently -// performs a no-op since the EnablePreimageRecording flag is disabled. -func (sdb *StateDB) AddPreimage(hash common.Hash, preimage []byte) {} - -// ============================================================================= -// MultiStore -// ============================================================================= - -// Commit implements storetypes.MultiStore by writing the dirty states of the -// liveStateCtx to the committedStateCtx. It also handles sucidal accounts. +// `Commit` is called when we are complete with the state transition and want to commit the changes +// to the underlying store. func (sdb *StateDB) Commit() error { // If we saw an error during the execution, we return it here. if sdb.savedErr != nil { @@ -592,7 +581,7 @@ func (sdb *StateDB) Commit() error { } // clear the codehash from this account - prefix.NewStore(sdb.liveEthState, KeyPrefixCodeHash).Delete(suicidalAddr[:]) + prefix.NewStore(sdb.liveEthState, types.KeyPrefixCodeHash).Delete(suicidalAddr[:]) // remove auth account sdb.ak.RemoveAccount(sdb.ctx, acct) @@ -600,7 +589,7 @@ func (sdb *StateDB) Commit() error { // write all cache stores to parent stores, effectively writing temporary state in ctx to // the underlying parent store. - sdb.Store.Write() + sdb.cms.CacheMultiStore().Write() return nil } @@ -608,100 +597,20 @@ func (sdb *StateDB) Commit() error { // ExtStateDB // ============================================================================= -// GetContext implements ExtStateDB -// returns the StateDB's live context. -func (sdb *StateDB) GetContext() sdk.Context { - return sdb.ctx -} - -// GetSavedErr implements ExtStateDB -// any errors that pop up during store operations should be checked here +// `GetSavedErr` implements `ExtStateDB` +// Any errors that pop up during store operations should be checked here // called upon the conclusion. func (sdb *StateDB) GetSavedErr() error { return sdb.savedErr } -// setErrorUnsafe sets error but should be called in medhods that already have locks. +// `setErrorUnsafe` sets error but should be called in medhods that already have locks. func (sdb *StateDB) setErrorUnsafe(err error) { if sdb.savedErr == nil { sdb.savedErr = err } } -// ============================================================================= -// Genesis -// ============================================================================= - -// // ImportStateData imports the given genesis accounts into the state database. We pass in a -// // temporary context to bypass the StateDB caching capabilities to speed up the import. -// func (sdb *StateDB) ImportStateData(accounts []types.GenesisAccount) error { -// for _, account := range accounts { -// addr := common.HexToAddress(account.Address) - -// acc := sdb.ak.GetAccount(sdb.ctx, addr[:]) -// if acc == nil { -// panic(fmt.Errorf("account not found for address %s", account.Address)) -// } - -// code := common.Hex2Bytes(account.Code) -// codeHash := crypto.Keccak256Hash(code) -// storedCodeHash := sdb.GetCodeHash(addr) - -// // we ignore the empty Code hash checking, see ethermint PR#1234 -// if len(account.Code) != 0 && storedCodeHash != codeHash { -// s := "the evm state code doesn't match with the codehash\n" -// panic(fmt.Sprintf("%s account: %s , evm state codehash: %v,"+ -// " ethAccount codehash: %v, evm state code: %s\n", -// s, account.Address, codeHash, storedCodeHash, account.Code)) -// } - -// // Set the code for this contract -// sdb.SetCode(addr, code) - -// // Set the state for this contract -// for _, storage := range account.Storage { -// sdb.SetState(addr, common.HexToHash(storage.Key), common.HexToHash(storage.Value)) -// } - -// // Commit the changes to save them to the underlying KVStore. -// if err := sdb.Commit(); err != nil { -// return err -// } -// } - -// return nil -// } - -// // ExportStateData exports all the data stored in the StateDB to a GenesisAccount slice. -// // We pass in a temporary context to bypass the StateDB caching capabilities to -// // speed up the import. -// func (sdb *StateDB) ExportStateData() (ethAccs []types.GenesisAccount) { -// sdb.ak.IterateAccounts(sdb.ctx, func(account authtypes.AccountI) bool { -// addr := common.BytesToAddress(account.GetAddress().Bytes()) - -// // Load Storage -// var storage types.Storage -// if err := sdb.ForEachStorage(addr, -// func(key, value common.Hash) bool { -// storage = append(storage, types.NewState(key, value)) -// return true -// }, -// ); err != nil { -// panic(err) -// } - -// genAccount := types.GenesisAccount{ -// Address: addr.String(), -// Code: common.Bytes2Hex(sdb.GetCode(addr)), -// Storage: storage, -// } - -// ethAccs = append(ethAccs, genAccount) -// return false -// }) -// return ethAccs -// } - // ============================================================================= // AccessList // ============================================================================= @@ -730,3 +639,11 @@ func (sdb *StateDB) AddressInAccessList(addr common.Address) bool { func (sdb *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (bool, bool) { panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") } + +// ============================================================================= +// PreImage +// ============================================================================= + +// AddPreimage implements the the `StateDB“ interface, but currently +// performs a no-op since the EnablePreimageRecording flag is disabled. +func (sdb *StateDB) AddPreimage(hash common.Hash, preimage []byte) {} diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go index 98597eb96..757fd20f0 100644 --- a/core/state/store/cachemulti/store.go +++ b/core/state/store/cachemulti/store.go @@ -23,7 +23,12 @@ import ( statetypes "github.com/berachain/stargazer/core/state/types" ) -var _ storetypes.CacheMultiStore = (*Store)(nil) +type StateDBCacheMultistore interface { + storetypes.MultiStore + JournalManager() *journal.Manager +} + +var _ StateDBCacheMultistore = (*Store)(nil) type Store struct { storetypes.MultiStore @@ -54,6 +59,10 @@ func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { return s.stores[key] } +func (s *Store) JournalManager() *journal.Manager { + return s.JournalMgr +} + // implements cosmos sdk storetypes.CacheMultiStore // Write commits each of the individual cachekv stores to its corresponding parent kv stores. func (s *Store) Write() { From ef13e1d183d6003259f05cfcf52eb920a89cd1f9 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 12:43:54 -0500 Subject: [PATCH 39/76] transfer functions --- core/evm.go | 24 ++++++++++++++++++++++++ core/state/statedb.go | 7 +++++-- core/vm/imported.go | 29 +++++++++++++++++++++++++++++ core/vm/interface.go | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 core/vm/imported.go create mode 100644 core/vm/interface.go diff --git a/core/evm.go b/core/evm.go index 85191de01..04e970725 100644 --- a/core/evm.go +++ b/core/evm.go @@ -13,3 +13,27 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package core + +import ( + "math/big" + + "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/core/state" + "github.com/berachain/stargazer/core/vm" +) + +// Compile-time interface check. +var _ vm.CanTransferFunc = CanTransfer +var _ vm.TransferFunc = Transfer + +// CanTransfer checks whether there are enough funds in the address' account to make a transfer. +// This does not take the necessary gas in to account to make the transfer valid. +func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { + return db.GetBalance(addr).Cmp(amount) >= 0 +} + +// Transfer subtracts amount from sender and adds amount to recipient using the given Db. +func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { + // We use `TransferBalance` to use the same logic as the native transfer in x/bank. + db.(state.ExtStateDB).TransferBalance(sender, recipient, amount) +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 5cdb012bf..fd56657a6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -41,6 +41,9 @@ type GethStateDB = gevm.StateDB type ExtStateDB interface { GethStateDB + // TransferBalance transfers the balance from one account to another + TransferBalance(common.Address, common.Address, *big.Int) + // GetSavedErr returns the error saved in the statedb GetSavedErr() error @@ -271,9 +274,9 @@ func (sdb *StateDB) SubBalance(addr common.Address, amount *big.Int) { } } -// `SendBalance` sends the given amount from one account to another. It will +// `TransferBalance` sends the given amount from one account to another. It will // error if the sender does not have enough funds to send. -func (sdb *StateDB) SendBalance(from, to common.Address, amount *big.Int) { +func (sdb *StateDB) TransferBalance(from, to common.Address, amount *big.Int) { coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) // Send the coins from the source address to the destination address. diff --git a/core/vm/imported.go b/core/vm/imported.go new file mode 100644 index 000000000..c69d10363 --- /dev/null +++ b/core/vm/imported.go @@ -0,0 +1,29 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package vm + +import ( + "github.com/ethereum/go-ethereum/core/vm" +) + +type ( + BlockContext = vm.BlockContext + CanTransferFunc = vm.CanTransferFunc + ContractRef = vm.ContractRef + Config = vm.Config + TransferFunc = vm.TransferFunc + TxContext = vm.TxContext + StateDB = vm.StateDB +) diff --git a/core/vm/interface.go b/core/vm/interface.go new file mode 100644 index 000000000..59a4614ea --- /dev/null +++ b/core/vm/interface.go @@ -0,0 +1,37 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package vm + +import ( + "github.com/berachain/stargazer/common" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +type VMInterface interface { //nolint:revive // we like the vibe. + Reset(txCtx TxContext, sdb StateDB) + Create(caller ContractRef, code []byte, + gas uint64, value *uint256.Int, + ) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) + Call(caller ContractRef, addr common.Address, input []byte, + gas uint64, value *uint256.Int, bailout bool, + ) (ret []byte, leftOverGas uint64, err error) + Config() Config + ChainConfig() *params.ChainConfig + ChainRules() *params.Rules + Context() BlockContext + StateDB() StateDB + TxContext() TxContext +} From bb8b5ff9dc88f944c66de69857ba40c8878e2d5d Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 12:45:05 -0500 Subject: [PATCH 40/76] improve comment --- core/evm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/evm.go b/core/evm.go index 04e970725..3784a106e 100644 --- a/core/evm.go +++ b/core/evm.go @@ -26,13 +26,13 @@ import ( var _ vm.CanTransferFunc = CanTransfer var _ vm.TransferFunc = Transfer -// CanTransfer checks whether there are enough funds in the address' account to make a transfer. -// This does not take the necessary gas in to account to make the transfer valid. +// `CanTransfer` checks whether there are enough funds in the address' account to make a transfer. +// NOTE: This does not take the necessary gas in to account to make the transfer valid. func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { return db.GetBalance(addr).Cmp(amount) >= 0 } -// Transfer subtracts amount from sender and adds amount to recipient using the given Db. +// `Transfer` subtracts amount from sender and adds amount to recipient using the `vm.StateDB`. func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { // We use `TransferBalance` to use the same logic as the native transfer in x/bank. db.(state.ExtStateDB).TransferBalance(sender, recipient, amount) From f2318371437424eb96049b77d02a8ee343a584e7 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 12:45:50 -0500 Subject: [PATCH 41/76] name --- core/evm.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/evm.go b/core/evm.go index 3784a106e..4932dbe47 100644 --- a/core/evm.go +++ b/core/evm.go @@ -28,12 +28,12 @@ var _ vm.TransferFunc = Transfer // `CanTransfer` checks whether there are enough funds in the address' account to make a transfer. // NOTE: This does not take the necessary gas in to account to make the transfer valid. -func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { - return db.GetBalance(addr).Cmp(amount) >= 0 +func CanTransfer(sdb vm.StateDB, addr common.Address, amount *big.Int) bool { + return sdb.GetBalance(addr).Cmp(amount) >= 0 } // `Transfer` subtracts amount from sender and adds amount to recipient using the `vm.StateDB`. -func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { +func Transfer(sdb vm.StateDB, sender, recipient common.Address, amount *big.Int) { // We use `TransferBalance` to use the same logic as the native transfer in x/bank. - db.(state.ExtStateDB).TransferBalance(sender, recipient, amount) + sdb.(state.ExtStateDB).TransferBalance(sender, recipient, amount) } From 01ba137f59be92eb0a15bd63dc68e1cfb0e3a0fe Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 12:54:11 -0500 Subject: [PATCH 42/76] bing bong --- core/state/statedb.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index fd56657a6..4740cdb80 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -398,23 +398,27 @@ func (sdb *StateDB) GetRefund() uint64 { // ============================================================================= // `GetCommittedState` implements the `GethStateDB` interface by returning the -// committed state of an address. +// committed state of slot in the given address. func (sdb *StateDB) GetCommittedState( addr common.Address, - hash common.Hash, + slot common.Hash, ) common.Hash { - if value := prefix.NewStore(sdb.liveEthState.GetParent(), - types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { - return common.BytesToHash(value) - } - return common.Hash{} + return sdb.getStateFromStore(sdb.liveEthState.GetParent(), addr, slot) } -// `GetState` implements the `GethStateDB` interface by returning the current state of an -// address. -func (sdb *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { - if value := prefix.NewStore(sdb.liveEthState, - types.AddressStoragePrefix(addr)).Get(hash[:]); value != nil { +// `GetState` implements the `GethStateDB` interface by returning the current state +// of slot in the given address. +func (sdb *StateDB) GetState(addr common.Address, slot common.Hash) common.Hash { + return sdb.getStateFromStore(sdb.liveEthState, addr, slot) +} + +// `getStateFromStore` returns the current state of the slot in the given address. +func (sdb *StateDB) getStateFromStore( + store storetypes.KVStore, + addr common.Address, slot common.Hash, +) common.Hash { + if value := prefix.NewStore(store, types.AddressStoragePrefix(addr)). + Get(slot[:]); value != nil { return common.BytesToHash(value) } return common.Hash{} From 56b93f657a85595d7b28819b8180efd1ea910a31 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 12:57:15 -0500 Subject: [PATCH 43/76] cleaner --- core/state/statedb.go | 10 +++++----- core/state/types/keys.go | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 4740cdb80..320174fe8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -330,7 +330,7 @@ func (sdb *StateDB) GetCodeHash(addr common.Address) common.Hash { return types.EmptyCodeHash } // if account at addr does not exist, return ZeroCodeHash - return types.ZeroCodeHash + return common.Hash{} } // GetCode implements the `GethStateDB` interface by returning @@ -339,7 +339,7 @@ func (sdb *StateDB) GetCode(addr common.Address) []byte { codeHash := sdb.GetCodeHash(addr) // if account at addr does not exist, GetCodeHash returns ZeroCodeHash so return nil // if codeHash is empty, i.e. crypto.Keccak256(nil), also return nil - if codeHash == types.ZeroCodeHash || codeHash == types.EmptyCodeHash { + if (codeHash == common.Hash{}) || codeHash == types.EmptyCodeHash { return nil } return prefix.NewStore(sdb.liveEthState, types.KeyPrefixCode).Get(codeHash.Bytes()) @@ -435,7 +435,7 @@ func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { // // CONTRACT: never manually call SetState outside of `opSstore`, or InitGenesis. store := prefix.NewStore(sdb.liveEthState, types.AddressStoragePrefix(addr)) - if len(value) == 0 || value == types.ZeroCodeHash { + if len(value) == 0 || (value == common.Hash{}) { store.Delete(key[:]) } else { store.Set(key[:], value[:]) @@ -452,7 +452,7 @@ func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { func (sdb *StateDB) Suicide(addr common.Address) bool { // only smart contracts can commit suicide ch := sdb.GetCodeHash(addr) - if ch == types.ZeroCodeHash || ch == types.EmptyCodeHash { + if (ch == common.Hash{}) || ch == types.EmptyCodeHash { return false } @@ -494,7 +494,7 @@ func (sdb *StateDB) Exist(addr common.Address) bool { func (sdb *StateDB) Empty(addr common.Address) bool { ch := sdb.GetCodeHash(addr) return sdb.GetNonce(addr) == 0 && - (ch == types.EmptyCodeHash || ch == types.ZeroCodeHash) && + (ch == types.EmptyCodeHash || ch == common.Hash{}) && sdb.GetBalance(addr).Sign() == 0 } diff --git a/core/state/types/keys.go b/core/state/types/keys.go index 724dcd037..02c24eb6d 100644 --- a/core/state/types/keys.go +++ b/core/state/types/keys.go @@ -29,7 +29,6 @@ var ( // EmptyCodeHash is the code hash of an empty code // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. EmptyCodeHash = crypto.Keccak256Hash(nil) - ZeroCodeHash = common.Hash{} KeyPrefixCode = []byte{keyPrefixCode} KeyPrefixCodeHash = []byte{keyPrefixHash} From bec6b4ee048090d8e6fd528b02bb34dee5edc01f Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 13:32:19 -0500 Subject: [PATCH 44/76] remove prefix store for performance increase --- core/state/statedb.go | 44 +++++++++++++++++++--------------------- core/state/types/keys.go | 14 ++++++++++--- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 320174fe8..434f4d558 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -20,7 +20,6 @@ import ( "math/big" "reflect" - "github.com/cosmos/cosmos-sdk/store/prefix" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" gevm "github.com/ethereum/go-ethereum/core/vm" @@ -105,7 +104,7 @@ type StateDB struct { //nolint: revive // we like the vibe. // eth state stores: required for vm.StateDB // We store references to these stores, so that we can access them // directly, without having to go through the MultiStore interface. - liveEthState cachekv.StateDBCacheKVStore + ethStore cachekv.StateDBCacheKVStore // keepers used for balance and account information. ak AccountKeeper @@ -172,7 +171,7 @@ func NewStateDB( } // Must support directly accessing the parent store - sdb.liveEthState, _ = sdb.cms. + sdb.ethStore, _ = sdb.cms. GetKVStore(sdb.storeKey).(cachekv.StateDBCacheKVStore) return sdb @@ -191,8 +190,7 @@ func (sdb *StateDB) CreateAccount(addr common.Address) { sdb.ak.SetAccount(sdb.ctx, acc) // initialize the code hash to empty - prefix.NewStore(sdb.liveEthState, types.KeyPrefixCodeHash). - Set(addr[:], types.EmptyCodeHash[:]) + sdb.ethStore.Set(types.CodeHashKeyFor(addr), types.EmptyCodeHash[:]) } // ============================================================================= @@ -216,7 +214,7 @@ func (sdb *StateDB) Reset(ctx sdk.Context) { // sdb.MultiStore = cachemulti.NewStoreFrom(ctx.MultiStore()) // sdb.ctx = ctx.WithMultiStore(sdb.MultiStore) // // // Must support directly accessing the parent store. - // // sdb.liveEthState = sdb.ctx.cms. + // // sdb.ethStore = sdb.ctx.cms. // // GetKVStore(sdb.storeKey).(cachekv.StateDBCacheKVStore) // sdb.savedErr = nil // sdb.refund = 0 @@ -323,8 +321,7 @@ func (sdb *StateDB) SetNonce(addr common.Address, nonce uint64) { // the code hash of account. func (sdb *StateDB) GetCodeHash(addr common.Address) common.Hash { if sdb.ak.HasAccount(sdb.ctx, addr[:]) { - if ch := prefix.NewStore(sdb.liveEthState, - types.KeyPrefixCodeHash).Get(addr[:]); ch != nil { + if ch := sdb.ethStore.Get(types.CodeHashKeyFor(addr)); ch != nil { return common.BytesToHash(ch) } return types.EmptyCodeHash @@ -342,7 +339,7 @@ func (sdb *StateDB) GetCode(addr common.Address) []byte { if (codeHash == common.Hash{}) || codeHash == types.EmptyCodeHash { return nil } - return prefix.NewStore(sdb.liveEthState, types.KeyPrefixCode).Get(codeHash.Bytes()) + return sdb.ethStore.Get(types.CodeKeyFor(codeHash)) } // SetCode implements the `GethStateDB` interface by setting the code hash and @@ -350,13 +347,12 @@ func (sdb *StateDB) GetCode(addr common.Address) []byte { func (sdb *StateDB) SetCode(addr common.Address, code []byte) { codeHash := crypto.Keccak256Hash(code) - prefix.NewStore(sdb.liveEthState, types.KeyPrefixCodeHash).Set(addr[:], codeHash[:]) - + sdb.ethStore.Set(types.CodeHashKeyFor(addr), codeHash[:]) // store or delete code if len(code) == 0 { - prefix.NewStore(sdb.liveEthState, types.KeyPrefixCode).Delete(codeHash[:]) + sdb.ethStore.Delete(types.CodeKeyFor(codeHash)) } else { - prefix.NewStore(sdb.liveEthState, types.KeyPrefixCode).Set(codeHash[:], code) + sdb.ethStore.Set(types.CodeKeyFor(codeHash), code) } } @@ -403,13 +399,13 @@ func (sdb *StateDB) GetCommittedState( addr common.Address, slot common.Hash, ) common.Hash { - return sdb.getStateFromStore(sdb.liveEthState.GetParent(), addr, slot) + return sdb.getStateFromStore(sdb.ethStore.GetParent(), addr, slot) } // `GetState` implements the `GethStateDB` interface by returning the current state // of slot in the given address. func (sdb *StateDB) GetState(addr common.Address, slot common.Hash) common.Hash { - return sdb.getStateFromStore(sdb.liveEthState, addr, slot) + return sdb.getStateFromStore(sdb.ethStore, addr, slot) } // `getStateFromStore` returns the current state of the slot in the given address. @@ -417,8 +413,7 @@ func (sdb *StateDB) getStateFromStore( store storetypes.KVStore, addr common.Address, slot common.Hash, ) common.Hash { - if value := prefix.NewStore(store, types.AddressStoragePrefix(addr)). - Get(slot[:]); value != nil { + if value := store.Get(types.StateKeyFor(addr, slot)); value != nil { return common.BytesToHash(value) } return common.Hash{} @@ -434,12 +429,15 @@ func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { // hash. // // CONTRACT: never manually call SetState outside of `opSstore`, or InitGenesis. - store := prefix.NewStore(sdb.liveEthState, types.AddressStoragePrefix(addr)) + + // If empty value is given, delete the state entry. if len(value) == 0 || (value == common.Hash{}) { - store.Delete(key[:]) - } else { - store.Set(key[:], value[:]) + sdb.ethStore.Delete(types.StateKeyFor(addr, key)) + return } + + // Set the state entry. + sdb.ethStore.Set(types.StateKeyFor(addr, key), value[:]) } // ============================================================================= @@ -546,7 +544,7 @@ func (sdb *StateDB) ForEachStorage( addr common.Address, cb func(key, value common.Hash) bool, ) error { - it := sdk.KVStorePrefixIterator(sdb.liveEthState, types.AddressStoragePrefix(addr)) + it := sdk.KVStorePrefixIterator(sdb.ethStore, types.AddressStoragePrefix(addr)) defer it.Close() for ; it.Valid(); it.Next() { @@ -588,7 +586,7 @@ func (sdb *StateDB) Commit() error { } // clear the codehash from this account - prefix.NewStore(sdb.liveEthState, types.KeyPrefixCodeHash).Delete(suicidalAddr[:]) + sdb.ethStore.Delete(types.CodeHashKeyFor(suicidalAddr)) // remove auth account sdb.ak.RemoveAccount(sdb.ctx, acct) diff --git a/core/state/types/keys.go b/core/state/types/keys.go index 02c24eb6d..27d4a3974 100644 --- a/core/state/types/keys.go +++ b/core/state/types/keys.go @@ -37,10 +37,18 @@ var ( // AddressStoragePrefix returns a prefix to iterate over a given account storage. func AddressStoragePrefix(address common.Address) []byte { - return append(KeyPrefixStorage, address.Bytes()...) + return append(KeyPrefixStorage, address[:]...) } // StateKey defines the full key under which an account state is stored. -func StateKey(address common.Address, key []byte) []byte { - return append(AddressStoragePrefix(address), key...) +func StateKeyFor(address common.Address, key common.Hash) []byte { + return append(AddressStoragePrefix(address), key[:]...) +} + +func CodeHashKeyFor(address common.Address) []byte { + return append(KeyPrefixCodeHash, address[:]...) +} + +func CodeKeyFor(codeHash common.Hash) []byte { + return append(KeyPrefixCode, codeHash[:]...) } From 92a8f48aee93d93d7aacd453cd7ad0de21a9cee1 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 13:44:39 -0500 Subject: [PATCH 45/76] cleanup --- core/state/statedb.go | 17 ++++++++++++----- core/state/types/keys.go | 36 +++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 434f4d558..359314bea 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -34,6 +34,13 @@ import ( type GethStateDB = gevm.StateDB +var ( + // EmptyCodeHash is the code hash of an empty code + // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. + emptyCodeHash = crypto.Keccak256Hash(nil) + emptyCodeHashBytes = emptyCodeHash.Bytes() +) + // ExtStateDB defines an extension to the interface provided by the go-ethereum codebase to // support additional state transition functionalities. In particular it supports getting the // cosmos sdk context for natively running stateful precompiled contracts. @@ -190,7 +197,7 @@ func (sdb *StateDB) CreateAccount(addr common.Address) { sdb.ak.SetAccount(sdb.ctx, acc) // initialize the code hash to empty - sdb.ethStore.Set(types.CodeHashKeyFor(addr), types.EmptyCodeHash[:]) + sdb.ethStore.Set(types.CodeHashKeyFor(addr), emptyCodeHashBytes) } // ============================================================================= @@ -324,7 +331,7 @@ func (sdb *StateDB) GetCodeHash(addr common.Address) common.Hash { if ch := sdb.ethStore.Get(types.CodeHashKeyFor(addr)); ch != nil { return common.BytesToHash(ch) } - return types.EmptyCodeHash + return emptyCodeHash } // if account at addr does not exist, return ZeroCodeHash return common.Hash{} @@ -336,7 +343,7 @@ func (sdb *StateDB) GetCode(addr common.Address) []byte { codeHash := sdb.GetCodeHash(addr) // if account at addr does not exist, GetCodeHash returns ZeroCodeHash so return nil // if codeHash is empty, i.e. crypto.Keccak256(nil), also return nil - if (codeHash == common.Hash{}) || codeHash == types.EmptyCodeHash { + if (codeHash == common.Hash{}) || codeHash == emptyCodeHash { return nil } return sdb.ethStore.Get(types.CodeKeyFor(codeHash)) @@ -450,7 +457,7 @@ func (sdb *StateDB) SetState(addr common.Address, key, value common.Hash) { func (sdb *StateDB) Suicide(addr common.Address) bool { // only smart contracts can commit suicide ch := sdb.GetCodeHash(addr) - if (ch == common.Hash{}) || ch == types.EmptyCodeHash { + if (ch == common.Hash{}) || ch == emptyCodeHash { return false } @@ -492,7 +499,7 @@ func (sdb *StateDB) Exist(addr common.Address) bool { func (sdb *StateDB) Empty(addr common.Address) bool { ch := sdb.GetCodeHash(addr) return sdb.GetNonce(addr) == 0 && - (ch == types.EmptyCodeHash || ch == common.Hash{}) && + (ch == emptyCodeHash || ch == common.Hash{}) && sdb.GetBalance(addr).Sign() == 0 } diff --git a/core/state/types/keys.go b/core/state/types/keys.go index 27d4a3974..9c7332206 100644 --- a/core/state/types/keys.go +++ b/core/state/types/keys.go @@ -16,7 +16,6 @@ package types import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) const ( @@ -25,30 +24,37 @@ const ( keyPrefixStorage ) -var ( - // EmptyCodeHash is the code hash of an empty code - // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. - EmptyCodeHash = crypto.Keccak256Hash(nil) - - KeyPrefixCode = []byte{keyPrefixCode} - KeyPrefixCodeHash = []byte{keyPrefixHash} - KeyPrefixStorage = []byte{keyPrefixStorage} -) +// NOTE: we use copy to build keys for max performance: https://github.com/golang/go/issues/55905 // AddressStoragePrefix returns a prefix to iterate over a given account storage. func AddressStoragePrefix(address common.Address) []byte { - return append(KeyPrefixStorage, address[:]...) + bz := make([]byte, 1+common.AddressLength) + copy(bz, []byte{keyPrefixStorage}) + copy(bz[1:], address[:]) + return bz } -// StateKey defines the full key under which an account state is stored. +// `StateKeyFor` defines the full key under which an account state is stored. func StateKeyFor(address common.Address, key common.Hash) []byte { - return append(AddressStoragePrefix(address), key[:]...) + bz := make([]byte, 1+common.AddressLength+common.HashLength) + copy(bz, []byte{keyPrefixStorage}) + copy(bz[1:], address[:]) + copy(bz[1+common.AddressLength:], key[:]) + return bz } +// `CodeHashKeyFor` defines the full key under which an addreses codehash is stored. func CodeHashKeyFor(address common.Address) []byte { - return append(KeyPrefixCodeHash, address[:]...) + bz := make([]byte, 1+common.AddressLength) + copy(bz, []byte{keyPrefixCode}) + copy(bz[1:], address[:]) + return bz } +// `CodeKeyFor` defines the full key for which a codehash's corresponding code is stored. func CodeKeyFor(codeHash common.Hash) []byte { - return append(KeyPrefixCode, codeHash[:]...) + bz := make([]byte, 1+common.HashLength) + copy(bz, []byte{keyPrefixCode}) + copy(bz[1:], codeHash[:]) + return bz } From da306b54d7f94e784cfe63d93a7eeb3ccb227cc8 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 13:51:03 -0500 Subject: [PATCH 46/76] more changes --- core/state/statedb.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 359314bea..1e02b820d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,7 +18,6 @@ import ( "bytes" "fmt" "math/big" - "reflect" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -159,25 +158,18 @@ func NewStateDB( storeKey storetypes.StoreKey, evmDenom string, ) *StateDB { - var ok bool sdb := &StateDB{ - ctx: ctx.WithMultiStore(cachemulti.NewStoreFrom(ctx.MultiStore())), ak: ak, bk: bk, evmDenom: evmDenom, storeKey: storeKey, } - // Save a pointer and check to make sure that the MultiStore is a StateDBCacheMultistore. - if sdb.cms, ok = sdb.ctx.MultiStore().(cachemulti.StateDBCacheMultistore); !ok { - sdb.savedErr = fmt.Errorf( - "expected MultiStore to be a StateDBCacheMultistore, got %T", - reflect.TypeOf(sdb.ctx.MultiStore()), - ) - return sdb - } + // Wire up the `CacheMultiStore` & `sdk.Context`. + sdb.cms = cachemulti.NewStoreFrom(ctx.MultiStore()) + sdb.ctx = ctx.WithMultiStore(sdb.cms) - // Must support directly accessing the parent store + // Store a reference to the EVM state store for performance reasons. sdb.ethStore, _ = sdb.cms. GetKVStore(sdb.storeKey).(cachekv.StateDBCacheKVStore) From d555d0b18438e918dfe0db6f42e5aecf9d82dcd0 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 13:56:15 -0500 Subject: [PATCH 47/76] dont wanna break the chain on god fr fr --- core/state/statedb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 1e02b820d..7b83b6c28 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -637,11 +637,11 @@ func (sdb *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { } func (sdb *StateDB) AddressInAccessList(addr common.Address) bool { - panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") + return false } func (sdb *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (bool, bool) { - panic("not implemented, as accesslists are not valuable in the Cosmos-SDK context") + return false, false } // ============================================================================= From 3013b3747240e7ace7fc4cf6a6f4dd76a433230c Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 14:00:21 -0500 Subject: [PATCH 48/76] bing bong --- core/state/statedb.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 7b83b6c28..c0f30ad83 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -105,7 +105,7 @@ type StateDB struct { //nolint: revive // we like the vibe. ctx sdk.Context // Store a reference to the multi-store, in `ctx` so that we can access it directly. - cms cachemulti.StateDBCacheMultistore + cms *cachemulti.Store // eth state stores: required for vm.StateDB // We store references to these stores, so that we can access them @@ -368,14 +368,14 @@ func (sdb *StateDB) GetCodeSize(addr common.Address) int { // `AddRefund` implements the `GethStateDB` interface by adding gas to the // refund counter. func (sdb *StateDB) AddRefund(gas uint64) { - sdb.cms.JournalManager().Push(&RefundChange{sdb, sdb.refund}) + sdb.cms.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) sdb.refund += gas } // `SubRefund` implements the `GethStateDB` interface by subtracting gas from the // refund counter. If the gas is greater than the refund counter, it will panic. func (sdb *StateDB) SubRefund(gas uint64) { - sdb.cms.JournalManager().Push(&RefundChange{sdb, sdb.refund}) + sdb.cms.JournalMgr.Push(&RefundChange{sdb, sdb.refund}) if gas > sdb.refund { panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, sdb.refund)) } @@ -502,12 +502,12 @@ func (sdb *StateDB) Empty(addr common.Address) bool { // `RevertToSnapshot` implements `StateDB`. func (sdb *StateDB) RevertToSnapshot(id int) { // revert and discard all journal entries after snapshot id - sdb.cms.JournalManager().PopToSize(id) + sdb.cms.JournalMgr.PopToSize(id) } // `Snapshot` implements `StateDB`. func (sdb *StateDB) Snapshot() int { - return sdb.cms.JournalManager().Size() + return sdb.cms.JournalMgr.Size() } // ============================================================================= @@ -516,7 +516,7 @@ func (sdb *StateDB) Snapshot() int { // AddLog implements the GethStateDB interface by adding the given log to the current transaction. func (sdb *StateDB) AddLog(log *coretypes.Log) { - sdb.cms.JournalManager().Push(&AddLogChange{sdb}) + sdb.cms.JournalMgr.Push(&AddLogChange{sdb}) log.TxHash = sdb.txHash log.BlockHash = sdb.blockHash log.TxIndex = sdb.txIndex From e236118ad032ee3400c821abc635745dac251aea Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 14:22:03 -0500 Subject: [PATCH 49/76] bing bong --- core/state/interface.go | 45 ++++++++++++++++++++++++ core/state/statedb.go | 52 ++++------------------------ core/state/statedb_ext.go | 20 +++++++++++ core/state/statedb_test.go | 4 +-- core/state/store/cachemulti/store.go | 11 +----- core/state/{ => types}/interfaces.go | 2 +- 6 files changed, 75 insertions(+), 59 deletions(-) create mode 100644 core/state/interface.go create mode 100644 core/state/statedb_ext.go rename core/state/{ => types}/interfaces.go (99%) diff --git a/core/state/interface.go b/core/state/interface.go new file mode 100644 index 000000000..ddd0979e2 --- /dev/null +++ b/core/state/interface.go @@ -0,0 +1,45 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state + +import ( + "math/big" + + "github.com/berachain/stargazer/common" + coretypes "github.com/berachain/stargazer/core/types" +) + +// ExtStateDB defines an extension to the interface provided by the go-ethereum codebase to +// support additional state transition functionalities. In particular it supports getting the +// cosmos sdk context for natively running stateful precompiled contracts. +type StargazerStateDB interface { + GethStateDB + + // TransferBalance transfers the balance from one account to another + TransferBalance(common.Address, common.Address, *big.Int) + + // GetSavedErr returns the error saved in the statedb + GetSavedErr() error + + // GetLogs returns the logs generated during the transaction + Logs() []*coretypes.Log + + // Commit writes the state to the underlying multi-store + Commit() error + + // PrepareForTransition prepares the statedb for a new transition + // by setting the block hash, tx hash, tx index and tx log index. + PrepareForTransition(common.Hash, common.Hash, uint, uint) +} diff --git a/core/state/statedb.go b/core/state/statedb.go index c0f30ad83..cb1a48db1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -40,44 +40,8 @@ var ( emptyCodeHashBytes = emptyCodeHash.Bytes() ) -// ExtStateDB defines an extension to the interface provided by the go-ethereum codebase to -// support additional state transition functionalities. In particular it supports getting the -// cosmos sdk context for natively running stateful precompiled contracts. -type ExtStateDB interface { - GethStateDB +var _ StargazerStateDB = (*StateDB)(nil) - // TransferBalance transfers the balance from one account to another - TransferBalance(common.Address, common.Address, *big.Int) - - // GetSavedErr returns the error saved in the statedb - GetSavedErr() error - - // GetLogs returns the logs generated during the transaction - Logs() []*coretypes.Log - - // Commit writes the state to the underlying multi-store - Commit() error -} - -type IntraBlockStateDB interface { - ExtStateDB - - // PrepareForTransition prepares the statedb for a new transition - // by setting the block hash, tx hash, tx index and tx log index. - PrepareForTransition(common.Hash, common.Hash, uint, uint) - - // Reset resets the statedb to the initial state. - Reset(sdk.Context) -} - -var ( - _ IntraBlockStateDB = (*StateDB)(nil) -) - -// Welcome to the StateDB implementation. This is a wrapper around a cachemulti.Store -// so that precompiles querying Eth-modified state can directly read from the statedb -// objects. It adheres to the Geth vm.StateDB and Cosmos MultiStore interfaces, which allows it -// to be used as a MultiStore in the Cosmos SDK context. // The StateDB is a very fun and interesting part of the EVM implementation. But if you want to // join circus you need to know the rules. So here thet are: // @@ -113,8 +77,8 @@ type StateDB struct { //nolint: revive // we like the vibe. ethStore cachekv.StateDBCacheKVStore // keepers used for balance and account information. - ak AccountKeeper - bk BankKeeper + ak types.AccountKeeper + bk types.BankKeeper // DB error. // State objects are used by the consensus core and VM which are @@ -153,8 +117,8 @@ type StateDB struct { //nolint: revive // we like the vibe. // returns a *StateDB using the MultiStore belonging to ctx. func NewStateDB( ctx sdk.Context, - ak AccountKeeper, - bk BankKeeper, + ak types.AccountKeeper, + bk types.BankKeeper, storeKey storetypes.StoreKey, evmDenom string, ) *StateDB { @@ -597,11 +561,7 @@ func (sdb *StateDB) Commit() error { return nil } -// ============================================================================= -// ExtStateDB -// ============================================================================= - -// `GetSavedErr` implements `ExtStateDB` +// `GetSavedErr` implements `StargazerStateDB` // Any errors that pop up during store operations should be checked here // called upon the conclusion. func (sdb *StateDB) GetSavedErr() error { diff --git a/core/state/statedb_ext.go b/core/state/statedb_ext.go new file mode 100644 index 000000000..881768ec9 --- /dev/null +++ b/core/state/statedb_ext.go @@ -0,0 +1,20 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state + +type ExtStateDB struct { + // StateDB is the underlying state database. + *StateDB +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 2cd2fc4df..1f3dd976c 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -36,8 +36,8 @@ var alice = testutil.Alice var bob = testutil.Bob var _ = Describe("StateDB", func() { - var ak state.AccountKeeper - var bk state.BankKeeper + var ak types.AccountKeeper + var bk types.BankKeeper var ctx sdk.Context var sdb *state.StateDB diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go index 757fd20f0..48c3e75a4 100644 --- a/core/state/store/cachemulti/store.go +++ b/core/state/store/cachemulti/store.go @@ -23,12 +23,7 @@ import ( statetypes "github.com/berachain/stargazer/core/state/types" ) -type StateDBCacheMultistore interface { - storetypes.MultiStore - JournalManager() *journal.Manager -} - -var _ StateDBCacheMultistore = (*Store)(nil) +var _ storetypes.MultiStore = (*Store)(nil) type Store struct { storetypes.MultiStore @@ -59,10 +54,6 @@ func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { return s.stores[key] } -func (s *Store) JournalManager() *journal.Manager { - return s.JournalMgr -} - // implements cosmos sdk storetypes.CacheMultiStore // Write commits each of the individual cachekv stores to its corresponding parent kv stores. func (s *Store) Write() { diff --git a/core/state/interfaces.go b/core/state/types/interfaces.go similarity index 99% rename from core/state/interfaces.go rename to core/state/types/interfaces.go index 8bb39763f..b4b5724d7 100644 --- a/core/state/interfaces.go +++ b/core/state/types/interfaces.go @@ -12,7 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package state +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" From c809c24e3082d7af9b35164b6903ad489b963149 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 15:19:38 -0500 Subject: [PATCH 50/76] test get refund --- core/state/statedb_ext_test.go | 15 +++++++++++++++ core/state/statedb_test.go | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 core/state/statedb_ext_test.go diff --git a/core/state/statedb_ext_test.go b/core/state/statedb_ext_test.go new file mode 100644 index 000000000..1bd6ea9ca --- /dev/null +++ b/core/state/statedb_ext_test.go @@ -0,0 +1,15 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state_test diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 1f3dd976c..cb489d901 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -480,5 +480,25 @@ var _ = Describe("StateDB", func() { Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{})) }) }) + + Describe("Test Refund", func() { + It("simple refund", func() { + sdb.AddRefund(10) + Expect(sdb.GetRefund()).To(Equal(uint64(10))) + sdb.AddRefund(200) + Expect(sdb.GetRefund()).To(Equal(uint64(210))) + }) + + It("nested refund", func() { + sdb.AddRefund(uint64(10)) + sdb.SubRefund(uint64(5)) + Expect(sdb.GetRefund()).To(Equal(uint64(5))) + }) + + It("negativ refund", func() { + sdb.AddRefund(5) + Expect(func() { sdb.SubRefund(10) }).To(Panic()) + }) + }) }) }) From b4e04be2346d562fe3dd86362bd137501683330a Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 15:33:55 -0500 Subject: [PATCH 51/76] add log tests --- core/state/statedb_test.go | 67 +++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index cb489d901..90145a57d 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -495,10 +495,75 @@ var _ = Describe("StateDB", func() { Expect(sdb.GetRefund()).To(Equal(uint64(5))) }) - It("negativ refund", func() { + It("negative refund", func() { sdb.AddRefund(5) Expect(func() { sdb.SubRefund(10) }).To(Panic()) }) }) + Describe("Test Log", func() { + txHash := common.BytesToHash([]byte("tx")) + blockHash := common.BytesToHash([]byte("block")) + data := []byte("bing bong bananas") + blockNumber := uint64(13) + + BeforeEach(func() { + sdb.PrepareForTransition(blockHash, txHash, 0, 0) + }) + When("We add a log to the state", func() { + BeforeEach(func() { + + sdb.AddLog(&coretypes.Log{ + Address: alice, + Topics: []common.Hash{}, + Data: data, + BlockNumber: blockNumber, + TxHash: txHash, + TxIndex: 0, + BlockHash: blockHash, + Index: 0, + Removed: false, + }) + }) + It("should have the correct log", func() { + logs := sdb.Logs() + Expect(logs).To(HaveLen(1)) + Expect(logs[0].Address).To(Equal(alice)) + Expect(logs[0].Data).To(Equal(data)) + Expect(logs[0].BlockNumber).To(Equal(blockNumber)) + Expect(logs[0].TxHash).To(Equal(txHash)) + Expect(logs[0].TxIndex).To(Equal(uint(0))) + Expect(logs[0].BlockHash).To(Equal(blockHash)) + Expect(logs[0].Index).To(Equal(uint(0))) + Expect(logs[0].Removed).To(BeFalse()) + }) + When("we add a second log", func() { + BeforeEach(func() { + sdb.AddLog(&coretypes.Log{ + Address: alice, + Topics: []common.Hash{}, + Data: data, + BlockNumber: blockNumber, + TxHash: txHash, + TxIndex: 0, + BlockHash: blockHash, + Index: 1, + Removed: false, + }) + }) + It("should have the correct logs", func() { + logs := sdb.Logs() + Expect(logs).To(HaveLen(2)) + Expect(logs[1].Address).To(Equal(alice)) + Expect(logs[1].Data).To(Equal(data)) + Expect(logs[1].BlockNumber).To(Equal(blockNumber)) + Expect(logs[1].TxHash).To(Equal(txHash)) + Expect(logs[1].TxIndex).To(Equal(uint(0))) + Expect(logs[1].BlockHash).To(Equal(blockHash)) + Expect(logs[1].Index).To(Equal(uint(1))) + Expect(logs[1].Removed).To(BeFalse()) + }) + }) + }) + }) }) }) From 122e77c9f789457a1a28bbbdab3b3f3037382255 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 16:24:12 -0500 Subject: [PATCH 52/76] finish statedb tests --- core/state/statedb_test.go | 91 +++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 90145a57d..c1c326a81 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -199,7 +199,7 @@ var _ = Describe("StateDB", func() { }) }) - Describe("Test State", func() { + Describe("TestState", func() { It("should have empty state", func() { Expect(sdb.GetState(alice, common.Hash{3})).To(Equal(common.Hash{})) }) @@ -565,5 +565,94 @@ var _ = Describe("StateDB", func() { }) }) }) + + Describe("TestSavedErr", func() { + When("if we see an error", func() { + It("should have an error", func() { + sdb.TransferBalance(alice, bob, big.NewInt(100)) + Expect(sdb.GetSavedErr()).To(HaveOccurred()) + }) + }) + + }) + + Describe("TestRevertSnapshot", func() { + key := common.BytesToHash([]byte("key")) + value := common.BytesToHash([]byte("value")) + + When("We make a bunch of arbitrary changes", func() { + BeforeEach(func() { + sdb.SetNonce(alice, 1) + sdb.AddBalance(alice, big.NewInt(100)) + sdb.SetCode(alice, []byte("hello world")) + sdb.SetState(alice, key, value) + sdb.SetNonce(bob, 1) + }) + When("we take a snapshot", func() { + var revision int + BeforeEach(func() { + revision = sdb.Snapshot() + }) + When("we do more changes", func() { + AfterEach(func() { + sdb.RevertToSnapshot(revision) + Expect(sdb.GetNonce(alice)).To(Equal(uint64(1))) + Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(100))) + Expect(sdb.GetCode(alice)).To(Equal([]byte("hello world"))) + Expect(sdb.GetState(alice, key)).To(Equal(value)) + Expect(sdb.GetNonce(bob)).To(Equal(uint64(1))) + }) + + It("if we change balance", func() { + sdb.AddBalance(alice, big.NewInt(100)) + }) + + It("if we change nonce", func() { + sdb.SetNonce(alice, 2) + }) + + It("if we change code", func() { + sdb.SetCode(alice, []byte("goodbye world")) + }) + + It("if we change state", func() { + sdb.SetState(alice, key, common.Hash{}) + }) + + It("if we change nonce of another account", func() { + sdb.SetNonce(bob, 2) + }) + }) + + When("we make a nested snapshot", func() { + var revision2 int + BeforeEach(func() { + sdb.SetState(alice, key, common.Hash{2}) + revision2 = sdb.Snapshot() + }) + + When("we revert to snapshot ", (func() { + It("revision 2", func() { + sdb.RevertToSnapshot(revision2) + Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{2})) + }) + + It("revision 1", func() { + sdb.RevertToSnapshot(revision) + Expect(sdb.GetState(alice, key)).To(Equal(value)) + }) + })) + }) + }) + + When("we revert to an invalid snapshot", func() { + It("should panic", func() { + Expect(func() { + sdb.RevertToSnapshot(100) + }).To(Panic()) + }) + }) + }) + }) }) }) From 1b0108e70b44f7a097d70c5dfb950f2a7614d7a8 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 16:32:57 -0500 Subject: [PATCH 53/76] improve code coverage --- core/state/statedb_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index c1c326a81..84181d28a 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -171,10 +171,19 @@ var _ = Describe("StateDB", func() { It("should have zero code hash", func() { Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash(nil))) }) - It("should have code", func() { - sdb.SetCode(alice, []byte("code")) - Expect(sdb.GetCode(alice)).To(Equal([]byte("code"))) - Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash([]byte("code")))) + When("account has code", func() { + BeforeEach(func() { + sdb.SetCode(alice, []byte("code")) + }) + It("should have code", func() { + Expect(sdb.GetCode(alice)).To(Equal([]byte("code"))) + Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash([]byte("code")))) + }) + It("should have empty code hash", func() { + sdb.SetCode(alice, nil) + Expect(sdb.GetCode(alice)).To(BeNil()) + Expect(sdb.GetCodeHash(alice)).To(Equal(crypto.Keccak256Hash(nil))) + }) }) }) }) From a43dc6dfa9800f3978b17a79eb36efa87b07ae52 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 16:44:52 -0500 Subject: [PATCH 54/76] cache entry test --- build/.golangci.yaml | 1 - core/state/cache_entries_test.go | 86 ++++++++++++++++++++++++++++++++ core/state/statedb_test.go | 16 ------ 3 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 core/state/cache_entries_test.go diff --git a/build/.golangci.yaml b/build/.golangci.yaml index f829e1282..1e9475072 100644 --- a/build/.golangci.yaml +++ b/build/.golangci.yaml @@ -219,7 +219,6 @@ linters: - stylecheck # is a replacement for golint - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 - testableexamples # checks if examples are testable (have an expected output) - - testpackage # makes you use a separate _test package - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes - unconvert # removes unnecessary type conversions - unparam # reports unused function parameters diff --git a/core/state/cache_entries_test.go b/core/state/cache_entries_test.go new file mode 100644 index 000000000..ec6594483 --- /dev/null +++ b/core/state/cache_entries_test.go @@ -0,0 +1,86 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state + +import ( + "github.com/berachain/stargazer/core/state/store/journal" + coretypes "github.com/berachain/stargazer/core/types" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("AddLogChange", func() { + var ( + ce *AddLogChange + sdb *StateDB + ) + BeforeEach(func() { + sdb = &StateDB{ + logs: []*coretypes.Log{}, + } + ce = &AddLogChange{ + sdb: sdb, + } + }) + It("implements journal.CacheEntry", func() { + var _ journal.CacheEntry = ce + Expect(ce).To(BeAssignableToTypeOf(&AddLogChange{})) + }) + It("Revert should remove the last log", func() { + sdb.logs = append(sdb.logs, &coretypes.Log{}) + ce.Revert() + Expect(len(sdb.logs)).To(Equal(0)) + }) + It("Clone should return a new AddLogChange with the same sdb", func() { + cloned, ok := ce.Clone().(*AddLogChange) + Expect(ok).To(BeTrue()) + Expect(cloned.sdb).To(Equal(sdb)) + Expect(cloned).ToNot(BeIdenticalTo(ce)) + }) +}) + +var _ = Describe("RefundChange", func() { + var ( + ce *RefundChange + sdb *StateDB + ) + + BeforeEach(func() { + sdb = &StateDB{ + refund: 0, + } + ce = &RefundChange{ + sdb: sdb, + prev: 0, + } + }) + It("implements journal.CacheEntry", func() { + var _ journal.CacheEntry = ce + Expect(ce).To(BeAssignableToTypeOf(&RefundChange{})) + }) + It("Revert should restore the previous refund value", func() { + sdb.refund = 100 + ce.prev = 50 + ce.Revert() + Expect(sdb.refund).To(Equal(uint64(50))) + }) + It("Clone should return a new RefundChange with the same sdb and prev", func() { + cloned, ok := ce.Clone().(*RefundChange) + Expect(ok).To(BeTrue()) + Expect(cloned.sdb).To(Equal(sdb)) + Expect(cloned.prev).To(Equal(ce.prev)) + Expect(cloned).ToNot(BeIdenticalTo(ce)) + }) +}) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 84181d28a..d08dfbf10 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -50,32 +50,26 @@ var _ = Describe("StateDB", func() { AfterEach(func() { Expect(sdb.GetSavedErr()).To(BeNil()) }) - It("should create account", func() { sdb.CreateAccount(alice) Expect(sdb.Exist(alice)).To(BeTrue()) }) - }) Describe("TestBalance", func() { - It("should have start with zero balance", func() { Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) }) - Context("TestAddBalance", func() { It("should be able to add zero", func() { Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) sdb.AddBalance(alice, new(big.Int)) Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) }) - It("should have 100 balance", func() { sdb.AddBalance(alice, big.NewInt(100)) Expect(sdb.GetBalance(alice)).To(Equal(big.NewInt(100))) }) - It("should panic if using negative value", func() { Expect(func() { sdb.AddBalance(alice, big.NewInt(-100)) @@ -356,23 +350,19 @@ var _ = Describe("StateDB", func() { common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) } }) - When("alice commits suicide", func() { BeforeEach(func() { Expect(sdb.Suicide(alice)).To(BeTrue()) Expect(sdb.HasSuicided(alice)).To(BeTrue()) }) - It("alice should be marked as suicidal, but not bob", func() { Expect(sdb.HasSuicided(alice)).To(BeTrue()) Expect(sdb.HasSuicided(bob)).To(BeFalse()) }) - It("alice should have her balance set to 0", func() { Expect(sdb.GetBalance(alice)).To(Equal(new(big.Int))) Expect(sdb.GetBalance(bob)).To(Equal(initialBobBal)) }) - It("both alice and bob should have their code and state untouched", func() { Expect(sdb.GetCode(alice)).To(Equal(aliceCode)) Expect(sdb.GetCode(bob)).To(Equal(bobCode)) @@ -388,12 +378,10 @@ var _ = Describe("StateDB", func() { To(Equal(common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))) } }) - When("commit is called", func() { BeforeEach(func() { _ = sdb.Commit() }) - It("alice should have her code and state wiped, but not bob", func() { Expect(sdb.GetCode(alice)).To(BeNil()) Expect(sdb.GetCode(bob)).To(Equal(bobCode)) @@ -439,7 +427,6 @@ var _ = Describe("StateDB", func() { sdb.AddBalance(alice, big.NewInt(56)) sdb.SetNonce(alice, 59) }) - It("accidental override account", func() { // override sdb.CreateAccount(alice) @@ -639,13 +626,11 @@ var _ = Describe("StateDB", func() { sdb.SetState(alice, key, common.Hash{2}) revision2 = sdb.Snapshot() }) - When("we revert to snapshot ", (func() { It("revision 2", func() { sdb.RevertToSnapshot(revision2) Expect(sdb.GetState(alice, key)).To(Equal(common.Hash{2})) }) - It("revision 1", func() { sdb.RevertToSnapshot(revision) Expect(sdb.GetState(alice, key)).To(Equal(value)) @@ -653,7 +638,6 @@ var _ = Describe("StateDB", func() { })) }) }) - When("we revert to an invalid snapshot", func() { It("should panic", func() { Expect(func() { From 7d6892e178a31cf170a5cb7edbc1a4247c3cf8b0 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 16:58:35 -0500 Subject: [PATCH 55/76] cache entry test --- core/state/types/keys.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/state/types/keys.go b/core/state/types/keys.go index 9c7332206..0633acd7c 100644 --- a/core/state/types/keys.go +++ b/core/state/types/keys.go @@ -25,6 +25,7 @@ const ( ) // NOTE: we use copy to build keys for max performance: https://github.com/golang/go/issues/55905 +// TODO: look into using a sync.pool for the keys to reduce allocs // AddressStoragePrefix returns a prefix to iterate over a given account storage. func AddressStoragePrefix(address common.Address) []byte { From ace2abf525aa82bee4f47f09fe661c9d84ef1690 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 17:21:34 -0500 Subject: [PATCH 56/76] fix test --- core/state/types/keys.go | 1 - core/state/types/keys_test.go | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 core/state/types/keys_test.go diff --git a/core/state/types/keys.go b/core/state/types/keys.go index 0633acd7c..9c7332206 100644 --- a/core/state/types/keys.go +++ b/core/state/types/keys.go @@ -25,7 +25,6 @@ const ( ) // NOTE: we use copy to build keys for max performance: https://github.com/golang/go/issues/55905 -// TODO: look into using a sync.pool for the keys to reduce allocs // AddressStoragePrefix returns a prefix to iterate over a given account storage. func AddressStoragePrefix(address common.Address) []byte { diff --git a/core/state/types/keys_test.go b/core/state/types/keys_test.go new file mode 100644 index 000000000..7fbf235e2 --- /dev/null +++ b/core/state/types/keys_test.go @@ -0,0 +1,65 @@ +// Copyright (C) 2023, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package types + +import ( + "github.com/ethereum/go-ethereum/common" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("TestKeys", func() { + var _ = Describe("AddressStoragePrefix", func() { + It("returns a prefix to iterate over a given account storage", func() { + address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + prefix := AddressStoragePrefix(address) + Expect(prefix).To(HaveLen(1 + common.AddressLength)) + Expect(prefix[0]).To(Equal(keyPrefixStorage)) + Expect(prefix[1:]).To(Equal(address.Bytes())) + }) + }) + + var _ = Describe("StateKeyFor", func() { + It("returns a storage key for a given account and storage slot", func() { + address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + slot := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") + key := StateKeyFor(address, slot) + Expect(key).To(HaveLen(1 + common.AddressLength + common.HashLength)) + Expect(key[0]).To(Equal(keyPrefixStorage)) + Expect(key[1 : 1+common.AddressLength]).To(Equal(address.Bytes())) + Expect(key[1+common.AddressLength:]).To(Equal(slot.Bytes())) + }) + }) + + var _ = Describe("CodeHashKeyFor", func() { + It("returns a code hash key for a given account", func() { + address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + key := CodeHashKeyFor(address) + Expect(key).To(HaveLen(1 + common.AddressLength)) + Expect(key[0]).To(Equal(keyPrefixCode)) + Expect(key[1:]).To(Equal(address.Bytes())) + }) + }) + + var _ = Describe("CodeKeyFor", func() { + It("returns a code key for a given account", func() { + address := common.HexToHash("0x1234567890abcdef1234567890abcdef12345678") + key := CodeKeyFor(address) + Expect(key).To(HaveLen(1 + common.HashLength)) + Expect(key[0]).To(Equal(keyPrefixCode)) + Expect(key[1:]).To(Equal(address.Bytes())) + }) + }) +}) From 897ba67bb8cbb25c15e79ff17466d27ffe10c170 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 17:22:31 -0500 Subject: [PATCH 57/76] fix --- core/state/types/keys_test.go | 68 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/core/state/types/keys_test.go b/core/state/types/keys_test.go index 7fbf235e2..4c096416b 100644 --- a/core/state/types/keys_test.go +++ b/core/state/types/keys_test.go @@ -20,46 +20,42 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("TestKeys", func() { - var _ = Describe("AddressStoragePrefix", func() { - It("returns a prefix to iterate over a given account storage", func() { - address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - prefix := AddressStoragePrefix(address) - Expect(prefix).To(HaveLen(1 + common.AddressLength)) - Expect(prefix[0]).To(Equal(keyPrefixStorage)) - Expect(prefix[1:]).To(Equal(address.Bytes())) - }) +var _ = Describe("AddressStoragePrefix", func() { + It("returns a prefix to iterate over a given account storage", func() { + address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + prefix := AddressStoragePrefix(address) + Expect(prefix).To(HaveLen(1 + common.AddressLength)) + Expect(prefix[0]).To(Equal(keyPrefixStorage)) + Expect(prefix[1:]).To(Equal(address.Bytes())) }) +}) - var _ = Describe("StateKeyFor", func() { - It("returns a storage key for a given account and storage slot", func() { - address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - slot := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") - key := StateKeyFor(address, slot) - Expect(key).To(HaveLen(1 + common.AddressLength + common.HashLength)) - Expect(key[0]).To(Equal(keyPrefixStorage)) - Expect(key[1 : 1+common.AddressLength]).To(Equal(address.Bytes())) - Expect(key[1+common.AddressLength:]).To(Equal(slot.Bytes())) - }) +var _ = Describe("StateKeyFor", func() { + It("returns a storage key for a given account and storage slot", func() { + address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + slot := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") + key := StateKeyFor(address, slot) + Expect(key).To(HaveLen(1 + common.AddressLength + common.HashLength)) + Expect(key[0]).To(Equal(keyPrefixStorage)) + Expect(key[1 : 1+common.AddressLength]).To(Equal(address.Bytes())) + Expect(key[1+common.AddressLength:]).To(Equal(slot.Bytes())) }) +}) - var _ = Describe("CodeHashKeyFor", func() { - It("returns a code hash key for a given account", func() { - address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - key := CodeHashKeyFor(address) - Expect(key).To(HaveLen(1 + common.AddressLength)) - Expect(key[0]).To(Equal(keyPrefixCode)) - Expect(key[1:]).To(Equal(address.Bytes())) - }) - }) +var _ = Describe("CodeHashKeyFo or a given account", func() { + address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + key := CodeHashKeyFor(address) + Expect(key).To(HaveLen(1 + common.AddressLength)) + Expect(key[0]).To(Equal(keyPrefixCode)) + Expect(key[1:]).To(Equal(address.Bytes())) +}) - var _ = Describe("CodeKeyFor", func() { - It("returns a code key for a given account", func() { - address := common.HexToHash("0x1234567890abcdef1234567890abcdef12345678") - key := CodeKeyFor(address) - Expect(key).To(HaveLen(1 + common.HashLength)) - Expect(key[0]).To(Equal(keyPrefixCode)) - Expect(key[1:]).To(Equal(address.Bytes())) - }) +var _ = Describe("CodeKeyFor", func() { + It("returns a code key for a given account", func() { + address := common.HexToHash("0x1234567890abcdef1234567890abcdef12345678") + key := CodeKeyFor(address) + Expect(key).To(HaveLen(1 + common.HashLength)) + Expect(key[0]).To(Equal(keyPrefixCode)) + Expect(key[1:]).To(Equal(address.Bytes())) }) }) From 381d0a37e225920820b2448ba7a159ce4412d73b Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Thu, 12 Jan 2023 20:24:30 -0500 Subject: [PATCH 58/76] opt --- core/state/interface.go | 9 ++++++--- core/state/statedb.go | 10 ++-------- core/state/store/journal/cache_entry.go | 4 ++-- core/state/store/journal/manager.go | 4 ++-- core/state/types/state.go | 4 ++-- core/state/types/storage.go | 4 ++-- {types => lib/gointerfaces}/interfaces.go | 2 +- 7 files changed, 17 insertions(+), 20 deletions(-) rename {types => lib/gointerfaces}/interfaces.go (97%) diff --git a/core/state/interface.go b/core/state/interface.go index ddd0979e2..1ac157b72 100644 --- a/core/state/interface.go +++ b/core/state/interface.go @@ -17,13 +17,16 @@ package state import ( "math/big" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/berachain/stargazer/common" coretypes "github.com/berachain/stargazer/core/types" ) -// ExtStateDB defines an extension to the interface provided by the go-ethereum codebase to -// support additional state transition functionalities. In particular it supports getting the -// cosmos sdk context for natively running stateful precompiled contracts. +type GethStateDB = vm.StateDB + +// `StargazerStateDB` defines an extension to the interface provided by go-ethereum to +// support additional state transition functionalities that are useful in a Cosmos SDK context. type StargazerStateDB interface { GethStateDB diff --git a/core/state/statedb.go b/core/state/statedb.go index cb1a48db1..48008e5af 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -21,7 +21,6 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - gevm "github.com/ethereum/go-ethereum/core/vm" "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state/store/cachekv" @@ -31,8 +30,6 @@ import ( "github.com/berachain/stargazer/lib/crypto" ) -type GethStateDB = gevm.StateDB - var ( // EmptyCodeHash is the code hash of an empty code // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. @@ -80,11 +77,8 @@ type StateDB struct { //nolint: revive // we like the vibe. ak types.AccountKeeper bk types.BankKeeper - // DB error. - // State objects are used by the consensus core and VM which are - // unable to deal with database-level errors. Any error that occurs - // during a database read is memoized here and will eventually be returned - // by StateDB.Commit. + // Any error that occurs during an sdk module read or write is + // memoized here and is eventually be returned by `Commit`. savedErr error // we load the evm denom in the constructor, to prevent going to diff --git a/core/state/store/journal/cache_entry.go b/core/state/store/journal/cache_entry.go index af18ae540..fc6351491 100644 --- a/core/state/store/journal/cache_entry.go +++ b/core/state/store/journal/cache_entry.go @@ -14,12 +14,12 @@ package journal -import "github.com/berachain/stargazer/types" +import "github.com/berachain/stargazer/lib/gointerfaces" // `CacheEntry` is an interface for journal entries. type CacheEntry interface { // `CacheEntry` implements `Cloneable`. - types.Cloneable[CacheEntry] + gointerfaces.Cloneable[CacheEntry] // `Revert` undoes the changes made by the entry. Revert() diff --git a/core/state/store/journal/manager.go b/core/state/store/journal/manager.go index c15f398e1..6d65ca9ee 100644 --- a/core/state/store/journal/manager.go +++ b/core/state/store/journal/manager.go @@ -15,7 +15,7 @@ package journal import ( "github.com/berachain/stargazer/lib/ds" - "github.com/berachain/stargazer/types" + "github.com/berachain/stargazer/lib/gointerfaces" ) // `ManagerI` is an interface that defines the methods that a journal manager must implement. @@ -25,7 +25,7 @@ type ManagerI[T any] interface { ds.StackI[CacheEntry] // `ManagerI` implements `Cloneable`. - types.Cloneable[T] + gointerfaces.Cloneable[T] } // Compile-time check to ensure `Manager` implements `ManagerI`. diff --git a/core/state/types/state.go b/core/state/types/state.go index f4d6827c1..234268932 100644 --- a/core/state/types/state.go +++ b/core/state/types/state.go @@ -19,12 +19,12 @@ import ( "strings" "cosmossdk.io/errors" - "github.com/berachain/stargazer/types" + "github.com/berachain/stargazer/lib/gointerfaces" "github.com/ethereum/go-ethereum/common" ) // Compile-time interface assertions. -var _ types.Cloneable[State] = &State{} +var _ gointerfaces.Cloneable[State] = &State{} var _ fmt.Stringer = Storage{} // `NewState` creates a new State instance. diff --git a/core/state/types/storage.go b/core/state/types/storage.go index 3a9352dc6..b566c1fae 100644 --- a/core/state/types/storage.go +++ b/core/state/types/storage.go @@ -18,11 +18,11 @@ import ( "fmt" "cosmossdk.io/errors" - "github.com/berachain/stargazer/types" + "github.com/berachain/stargazer/lib/gointerfaces" ) // Compile-time type assertions. -var _ types.Cloneable[Storage] = Storage{} +var _ gointerfaces.Cloneable[Storage] = Storage{} var _ fmt.Stringer = Storage{} // `Storage` represents the account Storage map as a slice of single key value diff --git a/types/interfaces.go b/lib/gointerfaces/interfaces.go similarity index 97% rename from types/interfaces.go rename to lib/gointerfaces/interfaces.go index c6da64b46..033f89273 100644 --- a/types/interfaces.go +++ b/lib/gointerfaces/interfaces.go @@ -12,7 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package types +package gointerfaces // `Cloneable` is an interface that defines a `Clone` method. type Cloneable[T any] interface { From 1a8c381273bd8c3e79ff179915adff294a24f22f Mon Sep 17 00:00:00 2001 From: Cal Bera Date: Thu, 12 Jan 2023 19:34:22 -0800 Subject: [PATCH 59/76] cache value implements Cloneable --- core/state/store/cachekv/cache_value.go | 21 +++--- core/state/store/cachekv/cache_values.go | 4 +- core/state/store/cachekv/cache_values_test.go | 67 +++++++++---------- core/state/store/cachekv/evm_store_test.go | 4 +- core/state/store/cachekv/store.go | 7 +- 5 files changed, 49 insertions(+), 54 deletions(-) diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index 167943ad8..71f886134 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -14,7 +14,12 @@ package cachekv -// `cValue` represents a cached value. +import "github.com/berachain/stargazer/types" + +// `cValue` implements `Cloneable`. +var _ types.Cloneable[*cValue] = (*cValue)(nil) + +// `cValue` represents a cached value in the cachekv store. // If dirty is true, it indicates the cached value is different from the underlying value. type cValue struct { value []byte @@ -29,21 +34,11 @@ func NewCValue(v []byte, d bool) *cValue { //nolint: revive // todo:revist. } } -// `Dirty` returns the dirty flag of the cValue object. -func (cv *cValue) Dirty() bool { - return cv.dirty -} - -// `Value` returns the value of the cValue object. -func (cv *cValue) Value() []byte { - return cv.value -} - -// `deepCopy` creates a new cValue object with the same value and dirty flag as the original +// `Clone` creates a new cValue object with the same value and dirty flag as the original // cValue object. This function is used to create a deep copy of the prev field in // DeleteCacheValue and SetCacheValue objects, so that modifications to the original prev value do // not affect the cloned DeleteCacheValue or SetCacheValue object. -func (cv *cValue) deepCopy() *cValue { +func (cv *cValue) Clone() *cValue { // Return a new cValue with the same value and dirty flag return &cValue{ value: append([]byte(nil), cv.value...), diff --git a/core/state/store/cachekv/cache_values.go b/core/state/store/cachekv/cache_values.go index 32e44ade6..a4a37b6be 100644 --- a/core/state/store/cachekv/cache_values.go +++ b/core/state/store/cachekv/cache_values.go @@ -72,7 +72,7 @@ func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil var prevDeepCopy *cValue if dcv.Prev != nil { - prevDeepCopy = dcv.Prev.deepCopy() + prevDeepCopy = dcv.Prev.Clone() } // Return a new DeleteCacheValue object with the same Store and Key fields as the original, @@ -119,7 +119,7 @@ func (scv *SetCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil var prevDeepCopy *cValue if scv.Prev != nil { - prevDeepCopy = scv.Prev.deepCopy() + prevDeepCopy = scv.Prev.Clone() } // Return a new SetCacheValue object with the same Store and Key fields as the original, diff --git a/core/state/store/cachekv/cache_values_test.go b/core/state/store/cachekv/cache_values_test.go index 46be3466a..631129ada 100644 --- a/core/state/store/cachekv/cache_values_test.go +++ b/core/state/store/cachekv/cache_values_test.go @@ -12,7 +12,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package cachekv_test +package cachekv import ( "reflect" @@ -23,7 +23,6 @@ import ( "github.com/stretchr/testify/suite" dbm "github.com/tendermint/tm-db" - "github.com/berachain/stargazer/core/state/store/cachekv" "github.com/berachain/stargazer/core/state/store/journal" "github.com/berachain/stargazer/lib/utils" ) @@ -37,7 +36,7 @@ var ( type CacheValueSuite struct { suite.Suite - cacheKVStore *cachekv.Store + cacheKVStore *Store } func TestCacheValueSuite(t *testing.T) { @@ -47,7 +46,7 @@ func TestCacheValueSuite(t *testing.T) { func (s *CacheValueSuite) SetupTest() { parent := sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) parent.Set(byte0, byte0) - s.cacheKVStore = cachekv.NewStore(parent, journal.NewManager()) + s.cacheKVStore = NewStore(parent, journal.NewManager()) } func (s *CacheValueSuite) TestRevertDeleteAfterNothing() { @@ -55,8 +54,8 @@ func (s *CacheValueSuite) TestRevertDeleteAfterNothing() { snapshot := s.cacheKVStore.JournalMgr().Size() // delete key: 0 s.cacheKVStore.Delete(byte0) - s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].Value()) - s.Require().True(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].value) + s.Require().True(s.cacheKVStore.Cache[byte0Str].dirty) s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) // revert delete key: 0 s.cacheKVStore.JournalMgr().PopToSize(snapshot) @@ -72,8 +71,8 @@ func (s *CacheValueSuite) TestRevertDeleteAfterGet() { s.cacheKVStore.Delete(byte0) // revert delete key: 0 s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].Value()) - s.Require().False(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].value) + s.Require().False(s.cacheKVStore.Cache[byte0Str].dirty) s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) } @@ -85,8 +84,8 @@ func (s *CacheValueSuite) TestRevertDeleteAfterSet() { s.cacheKVStore.Delete(byte1) // revert delete key: 1 s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].Value()) - s.Require().True(s.cacheKVStore.Cache[byte1Str].Dirty()) + s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].value) + s.Require().True(s.cacheKVStore.Cache[byte1Str].dirty) s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) } @@ -98,8 +97,8 @@ func (s *CacheValueSuite) TestRevertDeleteAfterDelete() { s.cacheKVStore.Delete(byte0) // revert delete key: 0 s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].Value()) - s.Require().True(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].value) + s.Require().True(s.cacheKVStore.Cache[byte0Str].dirty) s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) } @@ -108,8 +107,8 @@ func (s *CacheValueSuite) TestRevertSetAfterNothing() { snapshot := s.cacheKVStore.JournalMgr().Size() // set key: 1 s.cacheKVStore.Set(byte1, byte1) - s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].Value()) - s.Require().True(s.cacheKVStore.Cache[byte1Str].Dirty()) + s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].value) + s.Require().True(s.cacheKVStore.Cache[byte1Str].dirty) s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) // revert set key: 1 s.cacheKVStore.JournalMgr().PopToSize(snapshot) @@ -125,8 +124,8 @@ func (s *CacheValueSuite) TestRevertSetAfterGet() { s.cacheKVStore.Set(byte0, byte1) // revert set key: 1 to val: 1 s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].Value()) - s.Require().False(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Equal(byte0, s.cacheKVStore.Cache[byte0Str].value) + s.Require().False(s.cacheKVStore.Cache[byte0Str].dirty) s.Require().NotContains(s.cacheKVStore.UnsortedCache, byte0Str) } @@ -138,8 +137,8 @@ func (s *CacheValueSuite) TestRevertSetAfterDelete() { s.cacheKVStore.Set(byte0, byte0) // revert set key: 0 to val: 0 s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].Value()) - s.Require().True(s.cacheKVStore.Cache[byte0Str].Dirty()) + s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].value) + s.Require().True(s.cacheKVStore.Cache[byte0Str].dirty) s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) } @@ -151,30 +150,30 @@ func (s *CacheValueSuite) TestRevertSetAfterSet() { s.cacheKVStore.Set(byte1, byte0) // revert set key: 1 to val: 0 s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].Value()) - s.Require().True(s.cacheKVStore.Cache[byte1Str].Dirty()) + s.Require().Equal(byte1, s.cacheKVStore.Cache[byte1Str].value) + s.Require().True(s.cacheKVStore.Cache[byte1Str].dirty) s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) } func (s *CacheValueSuite) TestCloneDelete() { - dcvNonNil := &cachekv.DeleteCacheValue{ + dcvNonNil := &DeleteCacheValue{ Store: s.cacheKVStore, Key: byte1Str, - Prev: cachekv.NewCValue(byte1, true), + Prev: NewCValue(byte1, true), } - dcvNonNilClone, ok := dcvNonNil.Clone().(*cachekv.DeleteCacheValue) + dcvNonNilClone, ok := dcvNonNil.Clone().(*DeleteCacheValue) s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) - s.Require().True(dcvNonNilClone.Prev.Dirty()) - s.Require().Equal(byte1, dcvNonNilClone.Prev.Value()) + s.Require().True(dcvNonNilClone.Prev.dirty) + s.Require().Equal(byte1, dcvNonNilClone.Prev.value) - dcvNil := &cachekv.DeleteCacheValue{ + dcvNil := &DeleteCacheValue{ Store: s.cacheKVStore, Key: "", Prev: nil, } - dcvNilClone, ok := dcvNil.Clone().(*cachekv.DeleteCacheValue) + dcvNilClone, ok := dcvNil.Clone().(*DeleteCacheValue) s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) @@ -182,23 +181,23 @@ func (s *CacheValueSuite) TestCloneDelete() { } func (s *CacheValueSuite) TestCloneSet() { - dcvNonNil := &cachekv.SetCacheValue{ + dcvNonNil := &SetCacheValue{ Store: s.cacheKVStore, Key: byte1Str, - Prev: cachekv.NewCValue(byte1, true), + Prev: NewCValue(byte1, true), } - dcvNonNilClone, ok := dcvNonNil.Clone().(*cachekv.SetCacheValue) + dcvNonNilClone, ok := dcvNonNil.Clone().(*SetCacheValue) s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) - s.Require().True(dcvNonNilClone.Prev.Dirty()) - s.Require().Equal(byte1, dcvNonNilClone.Prev.Value()) + s.Require().True(dcvNonNilClone.Prev.dirty) + s.Require().Equal(byte1, dcvNonNilClone.Prev.value) - dcvNil := &cachekv.SetCacheValue{ + dcvNil := &SetCacheValue{ Store: s.cacheKVStore, Key: "", Prev: nil, } - dcvNilClone, ok := dcvNil.Clone().(*cachekv.SetCacheValue) + dcvNilClone, ok := dcvNil.Clone().(*SetCacheValue) s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) diff --git a/core/state/store/cachekv/evm_store_test.go b/core/state/store/cachekv/evm_store_test.go index fb7defe18..0672e8aa9 100644 --- a/core/state/store/cachekv/evm_store_test.go +++ b/core/state/store/cachekv/evm_store_test.go @@ -30,8 +30,10 @@ import ( ) var ( - zeroCodeHash = common.Hash{} + byte0 = []byte{0} + byte1 = []byte{1} nonZeroCodeHash = common.BytesToHash([]byte{0x05}) + zeroCodeHash = common.Hash{} ) type EvmStoreSuite struct { diff --git a/core/state/store/cachekv/store.go b/core/state/store/cachekv/store.go index 6b475e782..625b7b712 100644 --- a/core/state/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -421,20 +421,19 @@ func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sort // Only entrypoint to mutate store.Cache. func (store *Store) setCacheValue(key, value []byte, dirty bool) { - deleted := value == nil keyStr := utils.UnsafeBytesToStr(key) // add cache value (deep copy) to journal manager if dirty (Set or Delete) if dirty { var cv journal.CacheEntry - if deleted { - cv = &DeleteCacheValue{ + if value != nil { + cv = &SetCacheValue{ Store: store, Key: keyStr, Prev: store.Cache[keyStr], } } else { - cv = &SetCacheValue{ + cv = &DeleteCacheValue{ Store: store, Key: keyStr, Prev: store.Cache[keyStr], From ff4a88224fa4cfac81f70bfe5f6b465ee7b067e0 Mon Sep 17 00:00:00 2001 From: Cal Bera Date: Thu, 12 Jan 2023 19:57:08 -0800 Subject: [PATCH 60/76] clean struct creators --- core/state/store/cachekv/cache_value.go | 27 +++++------ core/state/store/cachekv/cache_values.go | 46 ++++++++++++------- core/state/store/cachekv/cache_values_test.go | 25 ++-------- core/state/store/cachekv/store.go | 21 ++------- 4 files changed, 50 insertions(+), 69 deletions(-) diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index 71f886134..a94175eeb 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -16,32 +16,29 @@ package cachekv import "github.com/berachain/stargazer/types" -// `cValue` implements `Cloneable`. -var _ types.Cloneable[*cValue] = (*cValue)(nil) +// `cacheValue` implements `Cloneable`. +var _ types.Cloneable[*cacheValue] = (*cacheValue)(nil) -// `cValue` represents a cached value in the cachekv store. +// `cacheValue` represents a cached value in the cachekv store. // If dirty is true, it indicates the cached value is different from the underlying value. -type cValue struct { +type cacheValue struct { value []byte dirty bool } -// `NewCValue` creates a new cValue object with the given value and dirty flag. -func NewCValue(v []byte, d bool) *cValue { //nolint: revive // todo:revist. - return &cValue{ +// `NewCacheValue` creates a new `cacheValue` object with the given `value` and `dirty` flag. +func NewCacheValue(v []byte, d bool) *cacheValue { + return &cacheValue{ value: v, dirty: d, } } -// `Clone` creates a new cValue object with the same value and dirty flag as the original -// cValue object. This function is used to create a deep copy of the prev field in +// `Clone` creates a new cacheValue object with the same value and dirty flag as the original +// cacheValue object. This function is used to create a deep copy of the prev field in // DeleteCacheValue and SetCacheValue objects, so that modifications to the original prev value do // not affect the cloned DeleteCacheValue or SetCacheValue object. -func (cv *cValue) Clone() *cValue { - // Return a new cValue with the same value and dirty flag - return &cValue{ - value: append([]byte(nil), cv.value...), - dirty: cv.dirty, - } +func (cv *cacheValue) Clone() *cacheValue { + // Return a new cacheValue with the same value and dirty flag + return NewCacheValue(append([]byte(nil), cv.value...), cv.dirty) } diff --git a/core/state/store/cachekv/cache_values.go b/core/state/store/cachekv/cache_values.go index a4a37b6be..3258a5818 100644 --- a/core/state/store/cachekv/cache_values.go +++ b/core/state/store/cachekv/cache_values.go @@ -19,19 +19,29 @@ type ( // `DeleteCacheValue` is a struct that contains information needed to delete a value // from a cache. DeleteCacheValue struct { - Store *Store // Pointer to the cache store. - Key string // Key of the value to be deleted. - Prev *cValue // Deep copy of object in cache map. + Store *Store // Pointer to the cache store. + Key string // Key of the value to be deleted. + Prev *cacheValue // Deep copy of object in cache map. } // `SetCacheValue` is a struct that contains information needed to set a value in a cache. SetCacheValue struct { - Store *Store // Pointer to the cache store. - Key string // Key of the value to be set. - Prev *cValue // Deep copy of object in cache map. + Store *Store // Pointer to the cache store. + Key string // Key of the value to be set. + Prev *cacheValue // Deep copy of object in cache map. } ) +// `NewDeleteCacheValue` creates a new `DeleteCacheValue` object for the given `store`, `key`, and +// `prev` cache value. +func NewDeleteCacheValue(store *Store, key string, prev *cacheValue) *DeleteCacheValue { + return &DeleteCacheValue{ + Store: store, + Key: key, + Prev: prev, + } +} + // `Revert` restores the previous cache entry for the Key, if it exists. // // `Revert` implements journal.CacheEntry. @@ -70,7 +80,7 @@ func (dcv *DeleteCacheValue) Revert() { //nolint:nolintlint,ireturn // by design. func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *cValue + var prevDeepCopy *cacheValue if dcv.Prev != nil { prevDeepCopy = dcv.Prev.Clone() } @@ -78,10 +88,16 @@ func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { // Return a new DeleteCacheValue object with the same Store and Key fields as the original, // and the Prev field set to the deep copy of the original Prev field // (or nil if the original was nil) - return &DeleteCacheValue{ - Store: dcv.Store, - Key: dcv.Key, - Prev: prevDeepCopy, + return NewDeleteCacheValue(dcv.Store, dcv.Key, prevDeepCopy) +} + +// `NewSetCacheValue` creates a new `SetCacheValue` object for the given `store`, `key`, and `prev` +// cache value. +func NewSetCacheValue(store *Store, key string, prev *cacheValue) *SetCacheValue { + return &SetCacheValue{ + Store: store, + Key: key, + Prev: prev, } } @@ -117,7 +133,7 @@ func (scv *SetCacheValue) Revert() { //nolint:nolintlint,ireturn // by design. func (scv *SetCacheValue) Clone() journal.CacheEntry { // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *cValue + var prevDeepCopy *cacheValue if scv.Prev != nil { prevDeepCopy = scv.Prev.Clone() } @@ -125,9 +141,5 @@ func (scv *SetCacheValue) Clone() journal.CacheEntry { // Return a new SetCacheValue object with the same Store and Key fields as the original, // and the Prev field set to the deep copy of the original Prev field // (or nil if the original was nil) - return &SetCacheValue{ - Store: scv.Store, - Key: scv.Key, - Prev: prevDeepCopy, - } + return NewSetCacheValue(scv.Store, scv.Key, prevDeepCopy) } diff --git a/core/state/store/cachekv/cache_values_test.go b/core/state/store/cachekv/cache_values_test.go index 631129ada..8f75e26dc 100644 --- a/core/state/store/cachekv/cache_values_test.go +++ b/core/state/store/cachekv/cache_values_test.go @@ -156,23 +156,14 @@ func (s *CacheValueSuite) TestRevertSetAfterSet() { } func (s *CacheValueSuite) TestCloneDelete() { - dcvNonNil := &DeleteCacheValue{ - Store: s.cacheKVStore, - Key: byte1Str, - Prev: NewCValue(byte1, true), - } - + dcvNonNil := NewDeleteCacheValue(s.cacheKVStore, byte1Str, NewCacheValue(byte1, true)) dcvNonNilClone, ok := dcvNonNil.Clone().(*DeleteCacheValue) s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) s.Require().True(dcvNonNilClone.Prev.dirty) s.Require().Equal(byte1, dcvNonNilClone.Prev.value) - dcvNil := &DeleteCacheValue{ - Store: s.cacheKVStore, - Key: "", - Prev: nil, - } + dcvNil := NewDeleteCacheValue(s.cacheKVStore, "", nil) dcvNilClone, ok := dcvNil.Clone().(*DeleteCacheValue) s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) @@ -181,22 +172,14 @@ func (s *CacheValueSuite) TestCloneDelete() { } func (s *CacheValueSuite) TestCloneSet() { - dcvNonNil := &SetCacheValue{ - Store: s.cacheKVStore, - Key: byte1Str, - Prev: NewCValue(byte1, true), - } + dcvNonNil := NewSetCacheValue(s.cacheKVStore, byte1Str, NewCacheValue(byte1, true)) dcvNonNilClone, ok := dcvNonNil.Clone().(*SetCacheValue) s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) s.Require().True(dcvNonNilClone.Prev.dirty) s.Require().Equal(byte1, dcvNonNilClone.Prev.value) - dcvNil := &SetCacheValue{ - Store: s.cacheKVStore, - Key: "", - Prev: nil, - } + dcvNil := NewSetCacheValue(s.cacheKVStore, "", nil) dcvNilClone, ok := dcvNil.Clone().(*SetCacheValue) s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) diff --git a/core/state/store/cachekv/store.go b/core/state/store/cachekv/store.go index 625b7b712..111d996ef 100644 --- a/core/state/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -46,7 +46,7 @@ var _ StateDBCacheKVStore = (*Store)(nil) // it means the parent doesn't have the key. (No need to delete upon Write()). type Store struct { mtx sync.RWMutex - Cache map[string]*cValue + Cache map[string]*cacheValue UnsortedCache map[string]struct{} SortedCache *trees.BTree // always ascending sorted Parent storetypes.KVStore @@ -56,7 +56,7 @@ type Store struct { // NewStore creates a new Store object. func NewStore(parent storetypes.KVStore, journalMgr *journal.Manager) *Store { return &Store{ - Cache: make(map[string]*cValue), + Cache: make(map[string]*cacheValue), UnsortedCache: make(map[string]struct{}), SortedCache: trees.NewBTree(), Parent: parent, @@ -427,25 +427,14 @@ func (store *Store) setCacheValue(key, value []byte, dirty bool) { if dirty { var cv journal.CacheEntry if value != nil { - cv = &SetCacheValue{ - Store: store, - Key: keyStr, - Prev: store.Cache[keyStr], - } + cv = NewSetCacheValue(store, keyStr, store.Cache[keyStr]) } else { - cv = &DeleteCacheValue{ - Store: store, - Key: keyStr, - Prev: store.Cache[keyStr], - } + cv = NewDeleteCacheValue(store, keyStr, store.Cache[keyStr]) } store.journalMgr.Push(cv.Clone()) } - store.Cache[keyStr] = &cValue{ - value: value, - dirty: dirty, - } + store.Cache[keyStr] = NewCacheValue(value, dirty) if dirty { store.UnsortedCache[keyStr] = struct{}{} } From 62e806cffa9986b6fcba6d1121d18ec0eb1977cd Mon Sep 17 00:00:00 2001 From: Cal Bera Date: Thu, 12 Jan 2023 20:08:27 -0800 Subject: [PATCH 61/76] comments --- core/state/store/cachekv/cache_value.go | 2 +- core/state/store/cachekv/evm_store.go | 11 ++++++----- core/state/store/cachemulti/store.go | 26 +++++++++++++++---------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index a94175eeb..af637f368 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -27,7 +27,7 @@ type cacheValue struct { } // `NewCacheValue` creates a new `cacheValue` object with the given `value` and `dirty` flag. -func NewCacheValue(v []byte, d bool) *cacheValue { +func NewCacheValue(v []byte, d bool) *cacheValue { //nolint:revive // TODO: explain. return &cacheValue{ value: v, dirty: d, diff --git a/core/state/store/cachekv/evm_store.go b/core/state/store/cachekv/evm_store.go index fb94f29e4..6e8ec17d8 100644 --- a/core/state/store/cachekv/evm_store.go +++ b/core/state/store/cachekv/evm_store.go @@ -21,23 +21,24 @@ import ( "github.com/berachain/stargazer/lib/utils" ) +// Compile-time check to ensure `EvmStore` implements `storetypes.CacheKVStore`. var _ storetypes.CacheKVStore = (*EvmStore)(nil) -// Avoid the mutex lock for EVM stores (codes/storage) +// `EVMStore` is a cache kv store that avoids the mutex lock for EVM stores (codes/storage). // Writes to the EVM are thread-safe because the EVM interpreter is guaranteed to be single // threaded. All entry points to the EVM check that only a single execution context is running. type EvmStore struct { *Store } -// NewEvmStore creates a new Store object. +// `NewEvmStore` creates a new Store object. func NewEvmStore(parent storetypes.KVStore, journalMgr *journal.Manager) *EvmStore { return &EvmStore{ NewStore(parent, journalMgr), } } -// Get shadows Store.Get +// `Get` shadows Store.Get // This function retrieves a value associated with the specified key in the store. func (store *EvmStore) Get(key []byte) []byte { var bz []byte @@ -57,12 +58,12 @@ func (store *EvmStore) Get(key []byte) []byte { return bz } -// Set shadows Store.Set. +// `Set` shadows Store.Set. func (store *EvmStore) Set(key []byte, value []byte) { store.setCacheValue(key, value, true) } -// Delete shadows Store.Delete. +// `Delete` shadows Store.Delete. func (store *EvmStore) Delete(key []byte) { store.setCacheValue(key, nil, true) } diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go index 7ae535c3c..de175e11c 100644 --- a/core/state/store/cachemulti/store.go +++ b/core/state/store/cachemulti/store.go @@ -22,27 +22,30 @@ import ( statetypes "github.com/berachain/stargazer/core/state/types" ) +// Compile-time check to ensure `Store` implements `storetypes.CacheMultiStore`. var _ storetypes.CacheMultiStore = (*Store)(nil) +// `Store` is a wrapper around the Cosmos SDK `MultiStore` which injects a custom EVM CacheKVStore. type Store struct { storetypes.MultiStore stores map[storetypes.StoreKey]storetypes.CacheKVStore JournalMgr *journal.Manager } +// `NewStoreFrom` creates and returns a new `Store` from a given MultiStore. func NewStoreFrom(ms storetypes.MultiStore) *Store { return &Store{ MultiStore: ms, stores: make(map[storetypes.StoreKey]storetypes.CacheKVStore), - JournalMgr: journal.NewManager(), } } -// GetKVStore shadows cosmos sdk storetypes.MultiStore function. Routes native module calls to -// read the dirty state during an eth tx. Any state that is modified by evm statedb, and using the -// context passed in to StateDB, will be routed to a tx-specific cache kv store. -func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolint:ireturn // must return a CacheKVStore. +// `GetKVStore` shadows the Cosmos SDK `storetypes.MultiStore` function. Routes native module calls +// to read the dirty state during an eth tx. Any state that is modified by evm statedb, and using +// the context passed in to StateDB, will be routed to a tx-specific cache kv store. This function +// always returns a `storetypes.CacheKVStore`. +func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolint:ireturn // TODO. // check if cache kv store already used if cacheKVStore, exists := s.stores[key]; exists { return cacheKVStore @@ -53,9 +56,10 @@ func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolin return s.stores[key] } -// implements cosmos sdk storetypes.CacheMultiStore -// Write commits each of the individual cachekv stores to its corresponding parent kv stores. -func (s *Store) Write() { +// `Write` commits each of the individual cachekv stores to its corresponding parent kv stores. +// +// `Write` implements Cosmos SDK `storetypes.CacheMultiStore`. +func (s *Store) Write() { //nolint:ireturn // TODO. // Safe from non-determinism, since order in which // we write to the parent kv stores does not matter. // @@ -65,10 +69,12 @@ func (s *Store) Write() { } } -func (s *Store) newCacheKVStore( //nolint:ireturn // must return a CacheKVStore. +// `newCacheKVStore` returns a new CacheKVStore. If the `key` is an EVM storekey, it will return +// an EVM CacheKVStore. +func (s *Store) newCacheKVStore( key storetypes.StoreKey, kvstore storetypes.KVStore, -) storetypes.CacheKVStore { +) storetypes.CacheKVStore { //nolint:ireturn // TODO. if key.Name() == statetypes.EvmStoreKey { return cachekv.NewEvmStore(kvstore, s.JournalMgr) } From 74a6573a20eec9a4f7256c776defdcfa10f0dcad Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 08:12:14 -0500 Subject: [PATCH 62/76] linter --- core/state/store/cachemulti/store.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/state/store/cachemulti/store.go b/core/state/store/cachemulti/store.go index de175e11c..e6e90c021 100644 --- a/core/state/store/cachemulti/store.go +++ b/core/state/store/cachemulti/store.go @@ -45,8 +45,9 @@ func NewStoreFrom(ms storetypes.MultiStore) *Store { // to read the dirty state during an eth tx. Any state that is modified by evm statedb, and using // the context passed in to StateDB, will be routed to a tx-specific cache kv store. This function // always returns a `storetypes.CacheKVStore`. -func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolint:ireturn // TODO. - // check if cache kv store already used +// +//nolint:nolintlint,ireturn // must return interface. +func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { if cacheKVStore, exists := s.stores[key]; exists { return cacheKVStore } @@ -59,7 +60,7 @@ func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { //nolin // `Write` commits each of the individual cachekv stores to its corresponding parent kv stores. // // `Write` implements Cosmos SDK `storetypes.CacheMultiStore`. -func (s *Store) Write() { //nolint:ireturn // TODO. +func (s *Store) Write() { // Safe from non-determinism, since order in which // we write to the parent kv stores does not matter. // @@ -71,10 +72,12 @@ func (s *Store) Write() { //nolint:ireturn // TODO. // `newCacheKVStore` returns a new CacheKVStore. If the `key` is an EVM storekey, it will return // an EVM CacheKVStore. +// +//nolint:nolintlint,ireturn // must return interface. func (s *Store) newCacheKVStore( key storetypes.StoreKey, kvstore storetypes.KVStore, -) storetypes.CacheKVStore { //nolint:ireturn // TODO. +) storetypes.CacheKVStore { if key.Name() == statetypes.EvmStoreKey { return cachekv.NewEvmStore(kvstore, s.JournalMgr) } From e92845c53c9123320dea4081bea93f8897a8c3e0 Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Fri, 13 Jan 2023 16:10:57 -0500 Subject: [PATCH 63/76] impr(cachekv): Remove DeleteCacheValue (#19) Co-authored-by: Cal Bera --- core/state/store/cachekv/cache_entry.go | 85 ++++++++++ ...che_values_test.go => cache_entry_test.go} | 28 +--- core/state/store/cachekv/cache_value.go | 7 +- core/state/store/cachekv/cache_values.go | 145 ------------------ core/state/store/cachekv/store.go | 16 +- 5 files changed, 98 insertions(+), 183 deletions(-) create mode 100644 core/state/store/cachekv/cache_entry.go rename core/state/store/cachekv/{cache_values_test.go => cache_entry_test.go} (85%) delete mode 100644 core/state/store/cachekv/cache_values.go diff --git a/core/state/store/cachekv/cache_entry.go b/core/state/store/cachekv/cache_entry.go new file mode 100644 index 000000000..dad0c5d66 --- /dev/null +++ b/core/state/store/cachekv/cache_entry.go @@ -0,0 +1,85 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package cachekv + +import ( + "github.com/berachain/stargazer/core/state/store/journal" +) + +// Compile-time check to ensure `CacheEntry` implements `journal.CacheEntry`. +var _ journal.CacheEntry = (*cacheEntry)(nil) + +// `CacheEntry` is a struct that contains information needed to set a value in a cache. +type cacheEntry struct { + Store *Store // Pointer to the cache store. + Key string // Key of the value to be set. + Prev *cacheValue // Deep copy of object in cache map. +} + +// `NewCacheEntry` creates a new `CacheEntry` object for the given `store`, `key`, and `prev` +// cache value. +func newCacheEntry(store *Store, key string, prev *cacheValue) *cacheEntry { + if prev != nil { + prev = prev.Clone() + } + + return &cacheEntry{ + Store: store, + Key: key, + Prev: prev, + } +} + +// `Revert` reverts a set operation on a cache entry by setting the previous value of the entry as +// the current value in the cache map. +// +// `Revert` implements journal.CacheEntry. +func (ce *cacheEntry) Revert() { + // If there was a previous value, set it as the current value in the cache map + if ce.Prev == nil { + // If there was no previous value, remove the Key from the + // cache map and the unsorted cache set + delete(ce.Store.Cache, ce.Key) + delete(ce.Store.UnsortedCache, ce.Key) + return + } + + // If there was a previous value, set it sas the current value in the cache map. + ce.Store.Cache[ce.Key] = ce.Prev + + // If the previous value was not dirty, remove the Key from the unsorted cache set + if !ce.Prev.dirty { + delete(ce.Store.UnsortedCache, ce.Key) + } +} + +// `Clone` creates a deep copy of the CacheEntry object. +// The deep copy contains the same Store and Key fields as the original, +// and a deep copy of the Prev field, if it is not nil.s +// +// `Clone` implements `journal.CacheEntry`. +// +//nolint:nolintlint,ireturn // by design. +func (ce *cacheEntry) Clone() journal.CacheEntry { + // Create a deep copy of the Prev field, if it is not nil + var prevDeepCopy *cacheValue + if ce.Prev != nil { + prevDeepCopy = ce.Prev.Clone() + } + + // Return a new CacheEntry object with the same Store and Key fields as the original, + // and the Prev field set to the deep copy of the original Prev field + // (or nil if the original was nil) + return newCacheEntry(ce.Store, ce.Key, prevDeepCopy) +} diff --git a/core/state/store/cachekv/cache_values_test.go b/core/state/store/cachekv/cache_entry_test.go similarity index 85% rename from core/state/store/cachekv/cache_values_test.go rename to core/state/store/cachekv/cache_entry_test.go index 8f75e26dc..1c42f78d8 100644 --- a/core/state/store/cachekv/cache_values_test.go +++ b/core/state/store/cachekv/cache_entry_test.go @@ -137,7 +137,7 @@ func (s *CacheValueSuite) TestRevertSetAfterDelete() { s.cacheKVStore.Set(byte0, byte0) // revert set key: 0 to val: 0 s.cacheKVStore.JournalMgr().PopToSize(snapshot) - s.Require().Equal(([]byte)(nil), s.cacheKVStore.Cache[byte0Str].value) + s.Require().Nil(s.cacheKVStore.Cache[byte0Str].value) s.Require().True(s.cacheKVStore.Cache[byte0Str].dirty) s.Require().Contains(s.cacheKVStore.UnsortedCache, byte0Str) } @@ -155,34 +155,18 @@ func (s *CacheValueSuite) TestRevertSetAfterSet() { s.Require().Contains(s.cacheKVStore.UnsortedCache, byte1Str) } -func (s *CacheValueSuite) TestCloneDelete() { - dcvNonNil := NewDeleteCacheValue(s.cacheKVStore, byte1Str, NewCacheValue(byte1, true)) - dcvNonNilClone, ok := dcvNonNil.Clone().(*DeleteCacheValue) - s.Require().True(ok) - s.Require().Equal(byte1Str, dcvNonNilClone.Key) - s.Require().True(dcvNonNilClone.Prev.dirty) - s.Require().Equal(byte1, dcvNonNilClone.Prev.value) - - dcvNil := NewDeleteCacheValue(s.cacheKVStore, "", nil) - dcvNilClone, ok := dcvNil.Clone().(*DeleteCacheValue) - s.Require().True(ok) - s.Require().Equal("", dcvNilClone.Key) - s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) - // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) -} - func (s *CacheValueSuite) TestCloneSet() { - dcvNonNil := NewSetCacheValue(s.cacheKVStore, byte1Str, NewCacheValue(byte1, true)) - dcvNonNilClone, ok := dcvNonNil.Clone().(*SetCacheValue) + dcvNonNil := newCacheEntry(s.cacheKVStore, byte1Str, NewCacheValue(byte1, true)) + dcvNonNilClone, ok := dcvNonNil.Clone().(*cacheEntry) s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) s.Require().True(dcvNonNilClone.Prev.dirty) s.Require().Equal(byte1, dcvNonNilClone.Prev.value) - dcvNil := NewSetCacheValue(s.cacheKVStore, "", nil) - dcvNilClone, ok := dcvNil.Clone().(*SetCacheValue) + dcvNil := newCacheEntry(s.cacheKVStore, "", nil) + dcvNilClone, ok := dcvNil.Clone().(*cacheEntry) s.Require().True(ok) s.Require().Equal("", dcvNilClone.Key) - // s.Require().Equal((*cValue)(nil), dcvNilClone.Prev) + s.Require().Equal(dcvNil.Prev, dcvNilClone.Prev) s.Require().True(reflect.ValueOf(dcvNilClone.Prev).IsNil()) } diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index af637f368..630f0f361 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -16,7 +16,7 @@ package cachekv import "github.com/berachain/stargazer/types" -// `cacheValue` implements `Cloneable`. +// Compile-time assertion that `cacheValue` implements `types.Cloneable`. var _ types.Cloneable[*cacheValue] = (*cacheValue)(nil) // `cacheValue` represents a cached value in the cachekv store. @@ -34,10 +34,7 @@ func NewCacheValue(v []byte, d bool) *cacheValue { //nolint:revive // TODO: expl } } -// `Clone` creates a new cacheValue object with the same value and dirty flag as the original -// cacheValue object. This function is used to create a deep copy of the prev field in -// DeleteCacheValue and SetCacheValue objects, so that modifications to the original prev value do -// not affect the cloned DeleteCacheValue or SetCacheValue object. +// `Clone` implements `types.Cloneable`. func (cv *cacheValue) Clone() *cacheValue { // Return a new cacheValue with the same value and dirty flag return NewCacheValue(append([]byte(nil), cv.value...), cv.dirty) diff --git a/core/state/store/cachekv/cache_values.go b/core/state/store/cachekv/cache_values.go deleted file mode 100644 index 3258a5818..000000000 --- a/core/state/store/cachekv/cache_values.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package cachekv - -import "github.com/berachain/stargazer/core/state/store/journal" - -type ( - // `DeleteCacheValue` is a struct that contains information needed to delete a value - // from a cache. - DeleteCacheValue struct { - Store *Store // Pointer to the cache store. - Key string // Key of the value to be deleted. - Prev *cacheValue // Deep copy of object in cache map. - } - - // `SetCacheValue` is a struct that contains information needed to set a value in a cache. - SetCacheValue struct { - Store *Store // Pointer to the cache store. - Key string // Key of the value to be set. - Prev *cacheValue // Deep copy of object in cache map. - } -) - -// `NewDeleteCacheValue` creates a new `DeleteCacheValue` object for the given `store`, `key`, and -// `prev` cache value. -func NewDeleteCacheValue(store *Store, key string, prev *cacheValue) *DeleteCacheValue { - return &DeleteCacheValue{ - Store: store, - Key: key, - Prev: prev, - } -} - -// `Revert` restores the previous cache entry for the Key, if it exists. -// -// `Revert` implements journal.CacheEntry. -func (dcv *DeleteCacheValue) Revert() { - // If the previous entry is nil, remove the Key from the cache - if dcv.Prev == nil { - delete(dcv.Store.Cache, dcv.Key) - delete(dcv.Store.UnsortedCache, dcv.Key) - return - } - - // If there is no previous entry for the Key being deleted, remove the Key from the cache - // If the previous entry is not dirty - // (i.e., it has not been modified since it was added to the cache) - if !dcv.Prev.dirty { - // Remove the Key being deleted from the cache and restore the previous entry - delete(dcv.Store.UnsortedCache, dcv.Key) - dcv.Store.Cache[dcv.Key] = dcv.Prev - return - } - - // If the previous entry is dirty and has a non-nil value - if dcv.Prev.value != nil { - // Remove the Key being deleted from the "deleted" set and restore the - // previous entry to the cache - dcv.Store.Cache[dcv.Key] = dcv.Prev - } -} - -// `Clone` creates a deep copy of the DeleteCacheValue object. -// The deep copy contains the same Store and Key fields as the original, -// and a deep copy of the Prev field, if it is not nil. -// -// `Clone` implements journal.CacheEntry. -// -//nolint:nolintlint,ireturn // by design. -func (dcv *DeleteCacheValue) Clone() journal.CacheEntry { - // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *cacheValue - if dcv.Prev != nil { - prevDeepCopy = dcv.Prev.Clone() - } - - // Return a new DeleteCacheValue object with the same Store and Key fields as the original, - // and the Prev field set to the deep copy of the original Prev field - // (or nil if the original was nil) - return NewDeleteCacheValue(dcv.Store, dcv.Key, prevDeepCopy) -} - -// `NewSetCacheValue` creates a new `SetCacheValue` object for the given `store`, `key`, and `prev` -// cache value. -func NewSetCacheValue(store *Store, key string, prev *cacheValue) *SetCacheValue { - return &SetCacheValue{ - Store: store, - Key: key, - Prev: prev, - } -} - -// `Revert` reverts a set operation on a cache entry by setting the previous value of the entry as -// the current value in the cache map. -// -// `Revert` implements journal.CacheEntry. -func (scv *SetCacheValue) Revert() { - // If there was a previous value, set it as the current value in the cache map - if scv.Prev == nil { - // If there was no previous value, remove the Key from the - // cache map and the unsorted cache set - delete(scv.Store.Cache, scv.Key) - delete(scv.Store.UnsortedCache, scv.Key) - return - } - - // If there was a previous value, set it as the current value in the cache map. - scv.Store.Cache[scv.Key] = scv.Prev - - // If the previous value was not dirty, remove the Key from the unsorted cache set - if !scv.Prev.dirty { - delete(scv.Store.UnsortedCache, scv.Key) - } -} - -// `Clone` creates a deep copy of the SetCacheValue object. -// The deep copy contains the same Store and Key fields as the original, -// and a deep copy of the Prev field, if it is not nil. -// -// `Clone` implements `journal.CacheEntry`. -// -//nolint:nolintlint,ireturn // by design. -func (scv *SetCacheValue) Clone() journal.CacheEntry { - // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *cacheValue - if scv.Prev != nil { - prevDeepCopy = scv.Prev.Clone() - } - - // Return a new SetCacheValue object with the same Store and Key fields as the original, - // and the Prev field set to the deep copy of the original Prev field - // (or nil if the original was nil) - return NewSetCacheValue(scv.Store, scv.Key, prevDeepCopy) -} diff --git a/core/state/store/cachekv/store.go b/core/state/store/cachekv/store.go index 111d996ef..fec23d38b 100644 --- a/core/state/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -423,19 +423,13 @@ func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sort func (store *Store) setCacheValue(key, value []byte, dirty bool) { keyStr := utils.UnsafeBytesToStr(key) - // add cache value (deep copy) to journal manager if dirty (Set or Delete) + // Append a new journal entry if the value is dirty, in order to remember the previous state. + // Also add the key to the unsorted cache. if dirty { - var cv journal.CacheEntry - if value != nil { - cv = NewSetCacheValue(store, keyStr, store.Cache[keyStr]) - } else { - cv = NewDeleteCacheValue(store, keyStr, store.Cache[keyStr]) - } - store.journalMgr.Push(cv.Clone()) + store.journalMgr.Push(newCacheEntry(store, keyStr, store.Cache[keyStr])) + store.UnsortedCache[keyStr] = struct{}{} } + // Cache the value for the key. store.Cache[keyStr] = NewCacheValue(value, dirty) - if dirty { - store.UnsortedCache[keyStr] = struct{}{} - } } From 15eee26b06eccb35e09865a12e4954ec16d59077 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 16:42:08 -0500 Subject: [PATCH 64/76] benchmarks --- .../store/cachekv/store_benchmark_test.go | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 core/state/store/cachekv/store_benchmark_test.go diff --git a/core/state/store/cachekv/store_benchmark_test.go b/core/state/store/cachekv/store_benchmark_test.go new file mode 100644 index 000000000..1f182fb48 --- /dev/null +++ b/core/state/store/cachekv/store_benchmark_test.go @@ -0,0 +1,65 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package cachekv_test + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" + + "github.com/berachain/stargazer/core/state/store/cachemulti" +) + +func DoBenchmarkGet(b *testing.B, custom bool, keyStr string) { + key := storetypes.NewKVStoreKey(keyStr) + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) + _ = cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, tmproto.Header{}, false, log.NewNopLogger()) + if custom { + ctx = ctx.WithMultiStore(cachemulti.NewStoreFrom(cms)) + } else { + ctx = ctx.WithMultiStore(cms.CacheMultiStore()) + } + store := ctx.KVStore(key) + + for i := 0; i < b.N; i++ { + store.Set([]byte("key"), []byte("value")) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + store.Get([]byte("key")) + } +} + +func BenchmarkGetStandardSdkCache(b *testing.B) { + DoBenchmarkGet(b, false, "test") +} + +func BenchmarkGetCustomCache(b *testing.B) { + DoBenchmarkGet(b, true, "test") +} + +func BenchmarkGetCustomEvmCache(b *testing.B) { + DoBenchmarkGet(b, true, "evm") +} From 6944532523476788cfef8b4802cf02198b6847af Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 17:08:31 -0500 Subject: [PATCH 65/76] convert more tests --- core/state/store/cachekv/evm_store_test.go | 147 ++++++++------------- core/state/store/cachemulti/store_test.go | 146 ++++++++++---------- 2 files changed, 125 insertions(+), 168 deletions(-) diff --git a/core/state/store/cachekv/evm_store_test.go b/core/state/store/cachekv/evm_store_test.go index 0672e8aa9..69b3e808b 100644 --- a/core/state/store/cachekv/evm_store_test.go +++ b/core/state/store/cachekv/evm_store_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// Copyright (C) 2023, Berachain Foundation. All rights reserved. // See the file LICENSE for licensing terms. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" @@ -17,102 +17,65 @@ package cachekv_test import ( "testing" + "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/journal" sdkcachekv "github.com/cosmos/cosmos-sdk/store/cachekv" "github.com/cosmos/cosmos-sdk/store/dbadapter" storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" dbm "github.com/tendermint/tm-db" - - "github.com/berachain/stargazer/common" - "github.com/berachain/stargazer/core/state/store/cachekv" - "github.com/berachain/stargazer/core/state/store/journal" ) -var ( - byte0 = []byte{0} - byte1 = []byte{1} - nonZeroCodeHash = common.BytesToHash([]byte{0x05}) - zeroCodeHash = common.Hash{} -) - -type EvmStoreSuite struct { - suite.Suite - parent storetypes.KVStore - evmStore *cachekv.EvmStore -} - -func TestEvmStoreSuite(t *testing.T) { - suite.Run(t, new(EvmStoreSuite)) - require.True(t, true) -} - -func (s *EvmStoreSuite) SetupTest() { - parent := sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) - s.parent = parent - s.evmStore = cachekv.NewEvmStore(s.parent, journal.NewManager()) -} - -// TODO: determine if this is allowable behavior, should be fine? -func (s *EvmStoreSuite) TestWarmSlotVia0() { - s.SetupTest() - - // set [key: 1, val: zeroCodeHash] - s.evmStore.Set(byte1, zeroCodeHash.Bytes()) - // write Store - s.evmStore.Write() - - require.Equal(s.T(), zeroCodeHash.Bytes(), s.parent.Get(byte1)) -} - -// parent != nil, set value == zeroCodeHash -> should write key ?? -func (s *EvmStoreSuite) TestWriteZeroValParentNotNil() { - s.SetupTest() - - // set [key: 0, val: zeroCodeHash] - s.evmStore.Set(byte0, zeroCodeHash.Bytes()) - // write Store - s.evmStore.Write() - // check written - require.Equal(s.T(), zeroCodeHash.Bytes(), s.parent.Get(byte0)) -} - -// parent == nil, set value != zeroCodeHash -> should write key. -func (s *EvmStoreSuite) TestWriteNonZeroValParentNil() { - s.SetupTest() - - // set [key: 1, val: nonZeroCodeHash] - s.evmStore.Set(byte1, nonZeroCodeHash.Bytes()) - // write Store - s.evmStore.Write() - // check written - require.Equal(s.T(), nonZeroCodeHash.Bytes(), s.parent.Get(byte1)) +func TestJournalManager(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "core/state/store/cachemulti") } -// parent != nil, set value != zeroCodeHash -> should write key. -func (s *EvmStoreSuite) TestWriteNonZeroValParentNotNil() { - s.SetupTest() - - // set [key: 0, val: nonZeroCodeHash] - s.evmStore.Set(byte0, nonZeroCodeHash.Bytes()) - // write Store - s.evmStore.Write() - // check written - require.Equal(s.T(), nonZeroCodeHash.Bytes(), s.parent.Get(byte0)) -} - -func (s *EvmStoreSuite) TestWriteAfterDelete() { - s.SetupTest() - - // parent store has [key: 1, value: NIL] (does not contain key: 1) - // set [key: 1, val: zeroCodeHash] to cache evm store - s.evmStore.Set(byte1, zeroCodeHash.Bytes()) - // the cache evm store SHOULD have value set (to zeroCodeHash) for key: 1 - require.Equal(s.T(), zeroCodeHash.Bytes(), s.evmStore.Get(byte1)) - // delete [key: 1, val: zeroCodeHash] from cache evm store - s.evmStore.Delete(byte1) - require.False(s.T(), s.evmStore.Has(byte1)) - s.evmStore.Write() - // parent store SHOULD NOT be written to; stll [key: 1, value: NIL] - require.False(s.T(), s.parent.Has(byte1)) -} +var _ = Describe("CacheMulti", func() { + var ( + byte0 = []byte{0} + byte1 = []byte{1} + nonZeroCodeHash = common.BytesToHash([]byte{0x05}) + zeroCodeHash = common.Hash{} + parent storetypes.KVStore + evmStore *cachekv.EvmStore + ) + + BeforeEach(func() { + parent = sdkcachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) + evmStore = cachekv.NewEvmStore(parent, journal.NewManager()) + }) + + It("TestWarmSlotVia0", func() { + evmStore.Set(byte1, zeroCodeHash.Bytes()) + evmStore.Write() + Expect(parent.Get(byte1)).To(Equal(zeroCodeHash.Bytes())) + }) + It("TestWriteZeroValParentNotNil", func() { + evmStore.Set(byte0, zeroCodeHash.Bytes()) + evmStore.Write() + Expect(parent.Get(byte0)).To(Equal(zeroCodeHash.Bytes())) + }) + It("TestWriteNonZeroValParentNil", func() { + evmStore.Set(byte0, nonZeroCodeHash.Bytes()) + Expect(parent.Get(byte0)).To(BeNil()) + evmStore.Write() + Expect(parent.Get(byte0)).To(Equal(nonZeroCodeHash.Bytes())) + }) + It("TestWriteNonZeroValParentNotNil", func() { + evmStore.Set(byte0, nonZeroCodeHash.Bytes()) + Expect(parent.Get(byte0)).To(BeNil()) + evmStore.Write() + Expect(parent.Get(byte0)).To(Equal(nonZeroCodeHash.Bytes())) + }) + It("TestWriteAfterDelete", func() { + evmStore.Set(byte1, zeroCodeHash.Bytes()) + Expect(evmStore.Get(byte1)).To(Equal(zeroCodeHash.Bytes())) + evmStore.Delete(byte1) + Expect(evmStore.Get(byte1)).To(BeNil()) + evmStore.Write() + Expect(parent.Get(byte1)).To(BeNil()) + }) +}) diff --git a/core/state/store/cachemulti/store_test.go b/core/state/store/cachemulti/store_test.go index b3246d6f7..f2ae4cec9 100644 --- a/core/state/store/cachemulti/store_test.go +++ b/core/state/store/cachemulti/store_test.go @@ -18,89 +18,83 @@ import ( "reflect" "testing" + "github.com/berachain/stargazer/core/state/store/cachekv" + "github.com/berachain/stargazer/core/state/store/cachemulti" sdkcachemulti "github.com/cosmos/cosmos-sdk/store/cachemulti" "github.com/cosmos/cosmos-sdk/store/dbadapter" storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" dbm "github.com/tendermint/tm-db" - - "github.com/berachain/stargazer/core/state/store/cachemulti" ) -var ( - byte1 = []byte{1} - evmStoreKey = storetypes.NewKVStoreKey("evm") - accStoreKey = storetypes.NewKVStoreKey("acc") -) - -type CacheMultiSuite struct { - suite.Suite - ms storetypes.MultiStore +func TestJournalManager(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "core/state/store/cachemulti") } -func TestCacheMultiSuite(t *testing.T) { - suite.Run(t, new(CacheMultiSuite)) -} - -func (s *CacheMultiSuite) SetupTest() { - stores := map[storetypes.StoreKey]storetypes.CacheWrapper{ - evmStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, - accStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, - } - s.ms = sdkcachemulti.NewStore( - dbm.NewMemDB(), - stores, map[string]storetypes.StoreKey{}, - nil, - nil, +var _ = Describe("CacheMulti", func() { + var ( + byte1 = []byte{1} + cms storetypes.CacheMultiStore + ms storetypes.MultiStore + accStoreParent storetypes.KVStore + accStoreCache storetypes.KVStore + accStoreKey = storetypes.NewKVStoreKey("acc") + evmStoreParent storetypes.KVStore + evmStoreCache storetypes.KVStore + evmStoreKey = storetypes.NewKVStoreKey("evm") ) -} - -func (s *CacheMultiSuite) TestCorrectStoreType() { - s.SetupTest() - cms := cachemulti.NewStoreFrom(s.ms) - evmStore := cms.GetKVStore(evmStoreKey) - evmStoreType := reflect.TypeOf(evmStore).String() - require.Equal(s.T(), "*cachekv.EvmStore", evmStoreType) - - accStore := cms.GetKVStore(accStoreKey) - accStoreType := reflect.TypeOf(accStore).String() - require.Equal(s.T(), "*cachekv.Store", accStoreType) -} - -func (s *CacheMultiSuite) TestWriteToParent() { - accStoreParent := s.ms.GetKVStore(accStoreKey) - evmStoreParent := s.ms.GetKVStore(evmStoreKey) - accStoreParent.Set(byte1, byte1) - - // simulate writes to cache store as it would through context - cms := cachemulti.NewStoreFrom(s.ms) - cms.GetKVStore(evmStoreKey).Set(byte1, byte1) - cms.GetKVStore(accStoreKey).Delete(byte1) - - // should not write to parent - require.False(s.T(), evmStoreParent.Has(byte1)) - require.Equal(s.T(), byte1, accStoreParent.Get(byte1)) - - cms.Write() - - // accStore should be empty, evmStore should have key: 1 to val: 1 - require.False(s.T(), accStoreParent.Has(byte1)) - require.Equal(s.T(), byte1, evmStoreParent.Get(byte1)) -} - -func (s *CacheMultiSuite) TestGetCachedStore() { - accStoreParent := s.ms.GetKVStore(accStoreKey) - cms := cachemulti.NewStoreFrom(s.ms) - accStoreCache := cms.GetKVStore(accStoreKey) - accStoreCache.Set(byte1, byte1) - - // check that accStoreCache is not equal to accStoreParent - require.True(s.T(), accStoreCache.Has(byte1)) - require.False(s.T(), accStoreParent.Has(byte1)) - - // check that getting accStore from cms is not the same as parent - accStoreCache2 := cms.GetKVStore(accStoreKey) - require.True(s.T(), accStoreCache2.Has(byte1)) -} + BeforeEach(func() { + stores := map[storetypes.StoreKey]storetypes.CacheWrapper{ + evmStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, + accStoreKey: dbadapter.Store{DB: dbm.NewMemDB()}, + } + ms = sdkcachemulti.NewStore( + dbm.NewMemDB(), + stores, map[string]storetypes.StoreKey{}, + nil, + nil, + ) + accStoreParent = ms.GetKVStore(accStoreKey) + evmStoreParent = ms.GetKVStore(evmStoreKey) + cms = cachemulti.NewStoreFrom(ms) + accStoreCache = cms.GetKVStore(accStoreKey) + evmStoreCache = cms.GetKVStore(evmStoreKey) + }) + + It("CorrectStoreType", func() { + // Test that the correct store type is returned + Expect(reflect.TypeOf(cms.GetKVStore(evmStoreKey))).To(Equal(reflect.TypeOf(&cachekv.EvmStore{}))) + Expect(reflect.TypeOf(cms.GetKVStore(accStoreKey))).To(Equal(reflect.TypeOf(&cachekv.Store{}))) + }) + + It("TestWrite", func() { + // Test that the cache multi store writes to the underlying stores + evmStoreCache.Set(byte1, byte1) + accStoreCache.Set(byte1, byte1) + Expect(evmStoreParent.Get(byte1)).To(BeNil()) + Expect(accStoreParent.Get(byte1)).To(BeNil()) + Expect(evmStoreCache.Get(byte1)).To(Equal(byte1)) + Expect(accStoreCache.Get(byte1)).To(Equal(byte1)) + + cms.Write() + + Expect(evmStoreParent.Get(byte1)).To(Equal(byte1)) + Expect(evmStoreParent.Get(byte1)).To(Equal(byte1)) + Expect(evmStoreCache.Get(byte1)).To(Equal(byte1)) + Expect(accStoreCache.Get(byte1)).To(Equal(byte1)) + }) + + It("TestWriteCacheMultiStore", func() { + // check that accStoreCache is not equal to accStoreParent + accStoreCache.Set(byte1, byte1) + Expect(accStoreCache.Has(byte1)).To(BeTrue()) + Expect(accStoreParent.Has(byte1)).To(BeFalse()) + + // check that getting accStore from cms is not the same as parent + accStoreCache2 := cms.GetKVStore(accStoreKey) + Expect(accStoreCache2.Has(byte1)).To(BeTrue()) + }) +}) From 5f661ab1099260a0b6283efc76933d773276e708 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 17:09:26 -0500 Subject: [PATCH 66/76] rename --- core/state/store/cachekv/evm_store_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/store/cachekv/evm_store_test.go b/core/state/store/cachekv/evm_store_test.go index 69b3e808b..1cfd54bca 100644 --- a/core/state/store/cachekv/evm_store_test.go +++ b/core/state/store/cachekv/evm_store_test.go @@ -28,9 +28,9 @@ import ( dbm "github.com/tendermint/tm-db" ) -func TestJournalManager(t *testing.T) { +func TestCacheKv(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "core/state/store/cachemulti") + RunSpecs(t, "core/state/store/cachekv") } var _ = Describe("CacheMulti", func() { From 8162272687f0ced4bd40a3432d54e429440d76ac Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 17:12:16 -0500 Subject: [PATCH 67/76] test name --- core/state/store/cachemulti/store_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/store/cachemulti/store_test.go b/core/state/store/cachemulti/store_test.go index f2ae4cec9..e84786ba0 100644 --- a/core/state/store/cachemulti/store_test.go +++ b/core/state/store/cachemulti/store_test.go @@ -28,7 +28,7 @@ import ( dbm "github.com/tendermint/tm-db" ) -func TestJournalManager(t *testing.T) { +func TestCacheMulti(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "core/state/store/cachemulti") } From 8479b36e41146a67728c3d130b7c41fe52d2e143 Mon Sep 17 00:00:00 2001 From: Cal Bera Date: Fri, 13 Jan 2023 14:18:03 -0800 Subject: [PATCH 68/76] renaming, cleanup --- core/state/store/cachekv/cache_entry.go | 31 +++++++------------ core/state/store/cachekv/cache_entry_test.go | 2 +- core/state/store/cachekv/cache_value.go | 6 ++-- core/state/store/cachekv/store.go | 2 +- .../store/journal/mock/cache_entry.mock.go | 6 ++-- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/core/state/store/cachekv/cache_entry.go b/core/state/store/cachekv/cache_entry.go index dad0c5d66..d63cd1392 100644 --- a/core/state/store/cachekv/cache_entry.go +++ b/core/state/store/cachekv/cache_entry.go @@ -13,23 +13,22 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package cachekv -import ( - "github.com/berachain/stargazer/core/state/store/journal" -) +import "github.com/berachain/stargazer/core/state/store/journal" -// Compile-time check to ensure `CacheEntry` implements `journal.CacheEntry`. +// Compile-time check to ensure `cacheEntry` implements `journal.CacheEntry`. var _ journal.CacheEntry = (*cacheEntry)(nil) -// `CacheEntry` is a struct that contains information needed to set a value in a cache. +// `cacheEntry` is a struct that contains information needed to set a value in a cache. type cacheEntry struct { Store *Store // Pointer to the cache store. Key string // Key of the value to be set. Prev *cacheValue // Deep copy of object in cache map. } -// `NewCacheEntry` creates a new `CacheEntry` object for the given `store`, `key`, and `prev` +// `newCacheEntry` creates a new `cacheEntry` object for the given `store`, `key`, and `prev` // cache value. func newCacheEntry(store *Store, key string, prev *cacheValue) *cacheEntry { + // create a deep copy of the Prev field, if it is not nil. if prev != nil { prev = prev.Clone() } @@ -44,7 +43,7 @@ func newCacheEntry(store *Store, key string, prev *cacheValue) *cacheEntry { // `Revert` reverts a set operation on a cache entry by setting the previous value of the entry as // the current value in the cache map. // -// `Revert` implements journal.CacheEntry. +// `Revert` implements journal.cacheEntry. func (ce *cacheEntry) Revert() { // If there was a previous value, set it as the current value in the cache map if ce.Prev == nil { @@ -64,22 +63,16 @@ func (ce *cacheEntry) Revert() { } } -// `Clone` creates a deep copy of the CacheEntry object. +// `Clone` creates a deep copy of the cacheEntry object. // The deep copy contains the same Store and Key fields as the original, // and a deep copy of the Prev field, if it is not nil.s // -// `Clone` implements `journal.CacheEntry`. +// `Clone` implements `journal.cacheEntry`. // //nolint:nolintlint,ireturn // by design. func (ce *cacheEntry) Clone() journal.CacheEntry { - // Create a deep copy of the Prev field, if it is not nil - var prevDeepCopy *cacheValue - if ce.Prev != nil { - prevDeepCopy = ce.Prev.Clone() - } - - // Return a new CacheEntry object with the same Store and Key fields as the original, - // and the Prev field set to the deep copy of the original Prev field - // (or nil if the original was nil) - return newCacheEntry(ce.Store, ce.Key, prevDeepCopy) + // Return a new cacheEntry object with the same Store and Key fields as the original, + // and the Prev field set to the deep copy of the original Prev field (or nil if the original + // was nil). + return newCacheEntry(ce.Store, ce.Key, ce.Prev) } diff --git a/core/state/store/cachekv/cache_entry_test.go b/core/state/store/cachekv/cache_entry_test.go index 1c42f78d8..03b3f493f 100644 --- a/core/state/store/cachekv/cache_entry_test.go +++ b/core/state/store/cachekv/cache_entry_test.go @@ -156,7 +156,7 @@ func (s *CacheValueSuite) TestRevertSetAfterSet() { } func (s *CacheValueSuite) TestCloneSet() { - dcvNonNil := newCacheEntry(s.cacheKVStore, byte1Str, NewCacheValue(byte1, true)) + dcvNonNil := newCacheEntry(s.cacheKVStore, byte1Str, newCacheValue(byte1, true)) dcvNonNilClone, ok := dcvNonNil.Clone().(*cacheEntry) s.Require().True(ok) s.Require().Equal(byte1Str, dcvNonNilClone.Key) diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index 630f0f361..1c22a3ced 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -26,8 +26,8 @@ type cacheValue struct { dirty bool } -// `NewCacheValue` creates a new `cacheValue` object with the given `value` and `dirty` flag. -func NewCacheValue(v []byte, d bool) *cacheValue { //nolint:revive // TODO: explain. +// `newCacheValue` creates a new `cacheValue` object with the given `value` and `dirty` flag. +func newCacheValue(v []byte, d bool) *cacheValue { //nolint:revive // TODO: explain. return &cacheValue{ value: v, dirty: d, @@ -37,5 +37,5 @@ func NewCacheValue(v []byte, d bool) *cacheValue { //nolint:revive // TODO: expl // `Clone` implements `types.Cloneable`. func (cv *cacheValue) Clone() *cacheValue { // Return a new cacheValue with the same value and dirty flag - return NewCacheValue(append([]byte(nil), cv.value...), cv.dirty) + return newCacheValue(append([]byte(nil), cv.value...), cv.dirty) } diff --git a/core/state/store/cachekv/store.go b/core/state/store/cachekv/store.go index fec23d38b..5c56b3338 100644 --- a/core/state/store/cachekv/store.go +++ b/core/state/store/cachekv/store.go @@ -431,5 +431,5 @@ func (store *Store) setCacheValue(key, value []byte, dirty bool) { } // Cache the value for the key. - store.Cache[keyStr] = NewCacheValue(value, dirty) + store.Cache[keyStr] = newCacheValue(value, dirty) } diff --git a/core/state/store/journal/mock/cache_entry.mock.go b/core/state/store/journal/mock/cache_entry.mock.go index d37a786f1..a9b9f9458 100644 --- a/core/state/store/journal/mock/cache_entry.mock.go +++ b/core/state/store/journal/mock/cache_entry.mock.go @@ -14,9 +14,7 @@ package mock -import ( - "github.com/berachain/stargazer/core/state/store/journal" -) +import "github.com/berachain/stargazer/core/state/store/journal" // `MockCacheEntry` is a basic CacheEntry which increases num by 1 on `Revert()`. type CacheEntry struct { @@ -34,7 +32,7 @@ func (m *CacheEntry) Revert() { } // `Clone` implements `CacheEntry`. -func (m *CacheEntry) Clone() journal.CacheEntry { //nolint: ireturn // by design. +func (m *CacheEntry) Clone() *journal.CacheEntry { return &CacheEntry{num: m.num} } From d8d81d332a3fd5efe843623478f26e4beaf58fbb Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 17:25:59 -0500 Subject: [PATCH 69/76] fix merge --- core/state/store/cachekv/cache_value.go | 2 +- core/state/store/journal/mock/cache_entry.mock.go | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index 1c22a3ced..5d49b2fc8 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -27,7 +27,7 @@ type cacheValue struct { } // `newCacheValue` creates a new `cacheValue` object with the given `value` and `dirty` flag. -func newCacheValue(v []byte, d bool) *cacheValue { //nolint:revive // TODO: explain. +func newCacheValue(v []byte, d bool) *cacheValue { return &cacheValue{ value: v, dirty: d, diff --git a/core/state/store/journal/mock/cache_entry.mock.go b/core/state/store/journal/mock/cache_entry.mock.go index a7ea2c45f..feecc74ff 100644 --- a/core/state/store/journal/mock/cache_entry.mock.go +++ b/core/state/store/journal/mock/cache_entry.mock.go @@ -32,11 +32,7 @@ func (m *CacheEntry) Revert() { } // `Clone` implements `CacheEntry`. -<<<<<<< HEAD -func (m *CacheEntry) Clone() *journal.CacheEntry { -======= func (m *CacheEntry) Clone() journal.CacheEntry { ->>>>>>> 8162272687f0ced4bd40a3432d54e429440d76ac return &CacheEntry{num: m.num} } From 5c8679aa2e6ef5b3c1522e7df9750aa4ec0b3b74 Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 17:29:00 -0500 Subject: [PATCH 70/76] lint --- core/state/store/cachekv/cache_value.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index 5d49b2fc8..2b8de5a4c 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -27,7 +27,7 @@ type cacheValue struct { } // `newCacheValue` creates a new `cacheValue` object with the given `value` and `dirty` flag. -func newCacheValue(v []byte, d bool) *cacheValue { +func newCacheValue(v []byte, d bool) *cacheValue { return &cacheValue{ value: v, dirty: d, From 9dcb6a3fb7747b477f896e609215ae5e6647b0ec Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 17:53:07 -0500 Subject: [PATCH 71/76] merge --- core/state/store/cachekv/cache_value.go | 4 ++-- core/types/types.go | 19 ------------------- crypto/crypto.go | 19 ------------------- 3 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 core/types/types.go delete mode 100644 crypto/crypto.go diff --git a/core/state/store/cachekv/cache_value.go b/core/state/store/cachekv/cache_value.go index 2b8de5a4c..2b014f7b3 100644 --- a/core/state/store/cachekv/cache_value.go +++ b/core/state/store/cachekv/cache_value.go @@ -14,10 +14,10 @@ package cachekv -import "github.com/berachain/stargazer/types" +import "github.com/berachain/stargazer/lib/gointerfaces" // Compile-time assertion that `cacheValue` implements `types.Cloneable`. -var _ types.Cloneable[*cacheValue] = (*cacheValue)(nil) +var _ gointerfaces.Cloneable[*cacheValue] = (*cacheValue)(nil) // `cacheValue` represents a cached value in the cachekv store. // If dirty is true, it indicates the cached value is different from the underlying value. diff --git a/core/types/types.go b/core/types/types.go deleted file mode 100644 index f78039b0f..000000000 --- a/core/types/types.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package types - -import "github.com/ethereum/go-ethereum/core/types" - -type Log = types.Log diff --git a/crypto/crypto.go b/crypto/crypto.go deleted file mode 100644 index 41d232870..000000000 --- a/crypto/crypto.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2023, Berachain Foundation. All rights reserved. -// See the file LICENSE for licensing terms. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package crypto - -import "github.com/ethereum/go-ethereum/crypto" - -var Keccak256Hash = crypto.Keccak256Hash From ab229b78ea3eaf1087ee0a18f929e46f56aeb95e Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Fri, 13 Jan 2023 17:56:16 -0500 Subject: [PATCH 72/76] evm namespace --- core/state/statedb.go | 8 ++++---- core/state/types/types.go | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 48008e5af..b6993cc87 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -200,13 +200,13 @@ func (sdb *StateDB) AddBalance(addr common.Address, amount *big.Int) { coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) // Mint the coins to the evm module account - if err := sdb.bk.MintCoins(sdb.ctx, "evm", coins); err != nil { + if err := sdb.bk.MintCoins(sdb.ctx, types.EvmNamespace, coins); err != nil { sdb.setErrorUnsafe(err) return } // Send the coins from the evm module account to the destination address. - if err := sdb.bk.SendCoinsFromModuleToAccount(sdb.ctx, "evm", addr[:], coins); err != nil { + if err := sdb.bk.SendCoinsFromModuleToAccount(sdb.ctx, types.EvmNamespace, addr[:], coins); err != nil { sdb.setErrorUnsafe(err) } } @@ -217,13 +217,13 @@ func (sdb *StateDB) SubBalance(addr common.Address, amount *big.Int) { coins := sdk.NewCoins(sdk.NewCoin(sdb.evmDenom, sdk.NewIntFromBigInt(amount))) // Send the coins from the source address to the evm module account. - if err := sdb.bk.SendCoinsFromAccountToModule(sdb.ctx, addr[:], "evm", coins); err != nil { + if err := sdb.bk.SendCoinsFromAccountToModule(sdb.ctx, addr[:], types.EvmNamespace, coins); err != nil { sdb.setErrorUnsafe(err) return } // Burn the coins from the evm module account. - if err := sdb.bk.BurnCoins(sdb.ctx, "evm", coins); err != nil { + if err := sdb.bk.BurnCoins(sdb.ctx, types.EvmNamespace, coins); err != nil { sdb.setErrorUnsafe(err) return } diff --git a/core/state/types/types.go b/core/state/types/types.go index 534832a1b..1177a3189 100644 --- a/core/state/types/types.go +++ b/core/state/types/types.go @@ -15,3 +15,4 @@ package types const EvmStoreKey = "evm" +const EvmNamespace = "evm" From 22a37a555c65afb0112b32a2512c1cf1502889ad Mon Sep 17 00:00:00 2001 From: Dev Bear Date: Sat, 14 Jan 2023 21:12:14 -0500 Subject: [PATCH 73/76] bing bong --- core/state/statedb.go | 5 ++- core/state/statedb_factory.go | 60 ++++++++++++++++++++++++++++++++++ core/state/statedb_test.go | 5 +-- core/state/types/interfaces.go | 5 +++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 core/state/statedb_factory.go diff --git a/core/state/statedb.go b/core/state/statedb.go index b6993cc87..ccdf50428 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -76,6 +76,7 @@ type StateDB struct { //nolint: revive // we like the vibe. // keepers used for balance and account information. ak types.AccountKeeper bk types.BankKeeper + lf types.EthereumLogFactory // Any error that occurs during an sdk module read or write is // memoized here and is eventually be returned by `Commit`. @@ -113,12 +114,14 @@ func NewStateDB( ctx sdk.Context, ak types.AccountKeeper, bk types.BankKeeper, + lf types.EthereumLogFactory, storeKey storetypes.StoreKey, evmDenom string, ) *StateDB { sdb := &StateDB{ ak: ak, bk: bk, + lf: lf, evmDenom: evmDenom, storeKey: storeKey, } @@ -180,7 +183,7 @@ func (sdb *StateDB) Reset(ctx sdk.Context) { // sdb.accessList = newAccessList() // sdb.suicides = make([]common.Address, 0) // TODO: unghetto this - *sdb = *NewStateDB(ctx, sdb.ak, sdb.bk, sdb.storeKey, sdb.evmDenom) + *sdb = *NewStateDB(ctx, sdb.ak, sdb.bk, sdb.lf, sdb.storeKey, sdb.evmDenom) } // ============================================================================= diff --git a/core/state/statedb_factory.go b/core/state/statedb_factory.go new file mode 100644 index 000000000..4641dabdd --- /dev/null +++ b/core/state/statedb_factory.go @@ -0,0 +1,60 @@ +// Copyright (C) 2022, Berachain Foundation. All rights reserved. +// See the file LICENSE for licensing terms. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package state + +import ( + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + // "github.com/berachain/stargazer/params". + "github.com/berachain/stargazer/core/state/types" +) + +type StateDBFactory struct { //nolint:revive // the vibes are good. + // Cosmos Keeper References + ak types.AccountKeeper + bk types.BankKeeper + + // evmStoreKey is the store key for the EVM store. + evmStoreKey storetypes.StoreKey + + // evmDenom is the denom used for EVM transactions. + // evmDenom params.Retriever[params.EVMDenom] + + lf types.EthereumLogFactory +} + +// NewStateDBFactory returns a new StateDBFactory instance. +func NewStateDBFactory( + ak types.AccountKeeper, + bk types.BankKeeper, + evmStoreKey storetypes.StoreKey, + // evmDenom params.Retriever[params.EVMDenom], + logFactory types.EthereumLogFactory, +) *StateDBFactory { + return &StateDBFactory{ + ak: ak, + bk: bk, + lf: logFactory, + evmStoreKey: evmStoreKey, + // evmDenom: evmDenom, + // er: er, + } +} + +// BuildNewStateDB returns a new StateDB instance. +func (sdf *StateDBFactory) BuildStateDB(ctx sdk.Context) *StateDB { + return NewStateDB(ctx, sdf.ak, sdf.bk, sdf.lf, sdf.evmStoreKey, "abera") // sdf.evmDenom.Get(ctx), sdf.er) +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index d08dfbf10..36ff27a1c 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -28,6 +28,7 @@ import ( "github.com/berachain/stargazer/core/state" "github.com/berachain/stargazer/core/state/types" coretypes "github.com/berachain/stargazer/core/types" + "github.com/berachain/stargazer/core/vm/precompile" "github.com/berachain/stargazer/lib/crypto" "github.com/berachain/stargazer/testutil" ) @@ -43,7 +44,7 @@ var _ = Describe("StateDB", func() { BeforeEach(func() { ctx, ak, bk, _ = testutil.SetupMinimalKeepers() - sdb = state.NewStateDB(ctx, ak, bk, testutil.EvmKey, "abera") + sdb = state.NewStateDB(ctx, ak, bk, nil, testutil.EvmKey, "abera") // todo use lf }) Describe("TestCreateAccount", func() { @@ -287,7 +288,7 @@ var _ = Describe("StateDB", func() { Expect(sdb.GetSavedErr()).To(BeNil()) Expect(sdb.HasSuicided(alice)).To(BeFalse()) // todo check the txhash and blockhash stuff - Expect(sdb, state.NewStateDB(ctx, ak, bk, testutil.EvmKey, "bera")) + Expect(sdb, state.NewStateDB(ctx, ak, bk, precompile.NewEthereumLogFactory(), testutil.EvmKey, "bera")) }) }) diff --git a/core/state/types/interfaces.go b/core/state/types/interfaces.go index b4b5724d7..ed6eb7570 100644 --- a/core/state/types/interfaces.go +++ b/core/state/types/interfaces.go @@ -15,6 +15,7 @@ package types import ( + coretypes "github.com/berachain/stargazer/core/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) @@ -42,3 +43,7 @@ type BankKeeper interface { MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error } + +type EthereumLogFactory interface { + BuildLog(event *sdk.Event) (*coretypes.Log, error) +} From c7febf6a49595ef65d5fe859756dddf22947062b Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Mon, 16 Jan 2023 14:14:21 -0500 Subject: [PATCH 74/76] remove log factory from statedb --- core/state/statedb.go | 5 +---- core/state/statedb_factory.go | 5 +---- core/state/statedb_test.go | 5 ++--- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index ccdf50428..b6993cc87 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -76,7 +76,6 @@ type StateDB struct { //nolint: revive // we like the vibe. // keepers used for balance and account information. ak types.AccountKeeper bk types.BankKeeper - lf types.EthereumLogFactory // Any error that occurs during an sdk module read or write is // memoized here and is eventually be returned by `Commit`. @@ -114,14 +113,12 @@ func NewStateDB( ctx sdk.Context, ak types.AccountKeeper, bk types.BankKeeper, - lf types.EthereumLogFactory, storeKey storetypes.StoreKey, evmDenom string, ) *StateDB { sdb := &StateDB{ ak: ak, bk: bk, - lf: lf, evmDenom: evmDenom, storeKey: storeKey, } @@ -183,7 +180,7 @@ func (sdb *StateDB) Reset(ctx sdk.Context) { // sdb.accessList = newAccessList() // sdb.suicides = make([]common.Address, 0) // TODO: unghetto this - *sdb = *NewStateDB(ctx, sdb.ak, sdb.bk, sdb.lf, sdb.storeKey, sdb.evmDenom) + *sdb = *NewStateDB(ctx, sdb.ak, sdb.bk, sdb.storeKey, sdb.evmDenom) } // ============================================================================= diff --git a/core/state/statedb_factory.go b/core/state/statedb_factory.go index 4641dabdd..883df4836 100644 --- a/core/state/statedb_factory.go +++ b/core/state/statedb_factory.go @@ -32,8 +32,6 @@ type StateDBFactory struct { //nolint:revive // the vibes are good. // evmDenom is the denom used for EVM transactions. // evmDenom params.Retriever[params.EVMDenom] - - lf types.EthereumLogFactory } // NewStateDBFactory returns a new StateDBFactory instance. @@ -47,7 +45,6 @@ func NewStateDBFactory( return &StateDBFactory{ ak: ak, bk: bk, - lf: logFactory, evmStoreKey: evmStoreKey, // evmDenom: evmDenom, // er: er, @@ -56,5 +53,5 @@ func NewStateDBFactory( // BuildNewStateDB returns a new StateDB instance. func (sdf *StateDBFactory) BuildStateDB(ctx sdk.Context) *StateDB { - return NewStateDB(ctx, sdf.ak, sdf.bk, sdf.lf, sdf.evmStoreKey, "abera") // sdf.evmDenom.Get(ctx), sdf.er) + return NewStateDB(ctx, sdf.ak, sdf.bk, sdf.evmStoreKey, "abera") // sdf.evmDenom.Get(ctx), sdf.er) } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 36ff27a1c..8476beca7 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -28,7 +28,6 @@ import ( "github.com/berachain/stargazer/core/state" "github.com/berachain/stargazer/core/state/types" coretypes "github.com/berachain/stargazer/core/types" - "github.com/berachain/stargazer/core/vm/precompile" "github.com/berachain/stargazer/lib/crypto" "github.com/berachain/stargazer/testutil" ) @@ -44,7 +43,7 @@ var _ = Describe("StateDB", func() { BeforeEach(func() { ctx, ak, bk, _ = testutil.SetupMinimalKeepers() - sdb = state.NewStateDB(ctx, ak, bk, nil, testutil.EvmKey, "abera") // todo use lf + sdb = state.NewStateDB(ctx, ak, bk, testutil.EvmKey, "abera") // todo use lf }) Describe("TestCreateAccount", func() { @@ -288,7 +287,7 @@ var _ = Describe("StateDB", func() { Expect(sdb.GetSavedErr()).To(BeNil()) Expect(sdb.HasSuicided(alice)).To(BeFalse()) // todo check the txhash and blockhash stuff - Expect(sdb, state.NewStateDB(ctx, ak, bk, precompile.NewEthereumLogFactory(), testutil.EvmKey, "bera")) + Expect(sdb, state.NewStateDB(ctx, ak, bk, testutil.EvmKey, "bera")) }) }) From a807952dfa2cee4f0134868eb2c2a7a4a41eee0e Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Mon, 16 Jan 2023 14:38:46 -0500 Subject: [PATCH 75/76] merge --- core/evm.go | 2 +- core/state/interface.go | 2 +- core/state/statedb.go | 2 +- core/state/statedb_test.go | 2 +- core/vm/interface.go | 2 +- testutil/setup.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/evm.go b/core/evm.go index 4932dbe47..fcd294370 100644 --- a/core/evm.go +++ b/core/evm.go @@ -17,9 +17,9 @@ package core import ( "math/big" - "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state" "github.com/berachain/stargazer/core/vm" + "github.com/berachain/stargazer/lib/common" ) // Compile-time interface check. diff --git a/core/state/interface.go b/core/state/interface.go index 1ac157b72..b096e0a61 100644 --- a/core/state/interface.go +++ b/core/state/interface.go @@ -19,8 +19,8 @@ import ( "github.com/ethereum/go-ethereum/core/vm" - "github.com/berachain/stargazer/common" coretypes "github.com/berachain/stargazer/core/types" + "github.com/berachain/stargazer/lib/common" ) type GethStateDB = vm.StateDB diff --git a/core/state/statedb.go b/core/state/statedb.go index b6993cc87..e104ac318 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -22,11 +22,11 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state/store/cachekv" "github.com/berachain/stargazer/core/state/store/cachemulti" "github.com/berachain/stargazer/core/state/types" coretypes "github.com/berachain/stargazer/core/types" + "github.com/berachain/stargazer/lib/common" "github.com/berachain/stargazer/lib/crypto" ) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 8476beca7..07b6bd755 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -24,10 +24,10 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/berachain/stargazer/common" "github.com/berachain/stargazer/core/state" "github.com/berachain/stargazer/core/state/types" coretypes "github.com/berachain/stargazer/core/types" + "github.com/berachain/stargazer/lib/common" "github.com/berachain/stargazer/lib/crypto" "github.com/berachain/stargazer/testutil" ) diff --git a/core/vm/interface.go b/core/vm/interface.go index 59a4614ea..5605b3626 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -15,7 +15,7 @@ package vm import ( - "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/lib/common" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) diff --git a/testutil/setup.go b/testutil/setup.go index 7bf7cd5ac..fae6cd619 100644 --- a/testutil/setup.go +++ b/testutil/setup.go @@ -32,7 +32,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" db "github.com/tendermint/tm-db" - "github.com/berachain/stargazer/common" + "github.com/berachain/stargazer/lib/common" ) var ( From 2d951fbf3eb7444e6715dc7f08d7f18bcef4f583 Mon Sep 17 00:00:00 2001 From: Devon Bear Date: Mon, 16 Jan 2023 14:44:53 -0500 Subject: [PATCH 76/76] remove from interface for now --- core/state/interface.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/core/state/interface.go b/core/state/interface.go index b096e0a61..bac1905d2 100644 --- a/core/state/interface.go +++ b/core/state/interface.go @@ -19,7 +19,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" - coretypes "github.com/berachain/stargazer/core/types" "github.com/berachain/stargazer/lib/common" ) @@ -32,17 +31,4 @@ type StargazerStateDB interface { // TransferBalance transfers the balance from one account to another TransferBalance(common.Address, common.Address, *big.Int) - - // GetSavedErr returns the error saved in the statedb - GetSavedErr() error - - // GetLogs returns the logs generated during the transaction - Logs() []*coretypes.Log - - // Commit writes the state to the underlying multi-store - Commit() error - - // PrepareForTransition prepares the statedb for a new transition - // by setting the block hash, tx hash, tx index and tx log index. - PrepareForTransition(common.Hash, common.Hash, uint, uint) }