From c6b2f0bb20a6527aec806f0631fd82d7b0e923fc Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 2 Oct 2023 14:04:47 -0500 Subject: [PATCH 1/6] Add multiversion store base implementation and tests --- store/multiversion/store.go | 123 +++++++++++++++++++++++++++++++ store/multiversion/store_test.go | 46 ++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 store/multiversion/store.go create mode 100644 store/multiversion/store_test.go diff --git a/store/multiversion/store.go b/store/multiversion/store.go new file mode 100644 index 000000000..169094456 --- /dev/null +++ b/store/multiversion/store.go @@ -0,0 +1,123 @@ +package multiversion + +import ( + "sync" + + "github.com/cosmos/cosmos-sdk/internal/conv" +) + +// base multiversion store - contains all of the values for all indices within the store, allow for forking with specified transaction index +// forked multiversion store - implements KVstore, but also stores the index of the transaction, and so all store actions done through this access with the index passed when initializing the forked store. + +type MultiVersionStore interface { + GetLatest(key []byte) (value MultiVersionValueItem) + GetLatestBeforeIndex(index int, key []byte) (value MultiVersionValueItem) + Set(index int, incarnation int, key []byte, value []byte) + SetEstimate(index int, incarnation int, key []byte) + Delete(index int, incarnation int, key []byte) + Has(index int, key []byte) bool +} + +type Store struct { + mtx sync.RWMutex + // map that stores the key -> MultiVersionValue mapping for accessing from a given key + multiVersionMap map[string]MultiVersionValue + // has to store all the multiversion values + // key -> multiversion value +} + +func NewMultiVersionStore() *Store { + return &Store{ + multiVersionMap: make(map[string]MultiVersionValue), + } +} + +// GetLatest implements MultiVersionStore. +func (s *Store) GetLatest(key []byte) (value MultiVersionValueItem) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + keyString := conv.UnsafeBytesToStr(key) + // if the key doesn't exist in the overall map, return nil + if _, ok := s.multiVersionMap[keyString]; !ok { + return nil + } + val, found := s.multiVersionMap[keyString].GetLatest() + if !found { + return nil + } + return val +} + +// GetLatestBeforeIndex implements MultiVersionStore. +func (s *Store) GetLatestBeforeIndex(index int, key []byte) (value MultiVersionValueItem) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + keyString := conv.UnsafeBytesToStr(key) + // if the key doesn't exist in the overall map, return nil + if _, ok := s.multiVersionMap[keyString]; !ok { + return nil + } + val, found := s.multiVersionMap[keyString].GetLatestBeforeIndex(index) + // otherwise, we may have found a value for that key, but its not written before the index passed in + if !found { + return nil + } + // found a value prior to the passed in index, return that value (could be estimate OR deleted, but it is a definitive value) + return val +} + +// Has implements MultiVersionStore. It checks if the key exists in the multiversion store at or before the specified index. +func (s *Store) Has(index int, key []byte) bool { + s.mtx.RLock() + defer s.mtx.RUnlock() + + keyString := conv.UnsafeBytesToStr(key) + if _, ok := s.multiVersionMap[keyString]; !ok { + return false // this is okay because the caller of this will THEN need to access the parent store to verify that the key doesnt exist there + } + _, found := s.multiVersionMap[keyString].GetLatestBeforeIndex(index) + return found +} + +// This function will try to intialize the multiversion item if it doesn't exist for a key specified by byte array +// NOTE: this should be used within an acquired mutex lock +func (s *Store) tryInitMultiVersionItem(keyString string) { + if _, ok := s.multiVersionMap[keyString]; !ok { + multiVersionValue := NewMultiVersionItem() + s.multiVersionMap[keyString] = multiVersionValue + } +} + +// Set implements MultiVersionStore. +func (s *Store) Set(index int, incarnation int, key []byte, value []byte) { + s.mtx.Lock() + defer s.mtx.Unlock() + + keyString := conv.UnsafeBytesToStr(key) + s.tryInitMultiVersionItem(keyString) + s.multiVersionMap[keyString].Set(index, incarnation, value) +} + +// SetEstimate implements MultiVersionStore. +func (s *Store) SetEstimate(index int, incarnation int, key []byte) { + s.mtx.Lock() + defer s.mtx.Unlock() + + keyString := conv.UnsafeBytesToStr(key) + s.tryInitMultiVersionItem(keyString) + s.multiVersionMap[keyString].SetEstimate(index, incarnation) +} + +// Delete implements MultiVersionStore. +func (s *Store) Delete(index int, incarnation int, key []byte) { + s.mtx.Lock() + defer s.mtx.Unlock() + + keyString := conv.UnsafeBytesToStr(key) + s.tryInitMultiVersionItem(keyString) + s.multiVersionMap[keyString].Delete(index, incarnation) +} + +var _ MultiVersionStore = (*Store)(nil) diff --git a/store/multiversion/store_test.go b/store/multiversion/store_test.go new file mode 100644 index 000000000..bf68ea02a --- /dev/null +++ b/store/multiversion/store_test.go @@ -0,0 +1,46 @@ +package multiversion_test + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/store/multiversion" + "github.com/stretchr/testify/require" +) + +func TestMultiVersionStore(t *testing.T) { + store := multiversion.NewMultiVersionStore() + + // Test Set and GetLatest + store.Set(1, 1, []byte("key1"), []byte("value1")) + store.Set(2, 1, []byte("key1"), []byte("value2")) + store.Set(3, 1, []byte("key2"), []byte("value3")) + require.Equal(t, []byte("value2"), store.GetLatest([]byte("key1")).Value()) + require.Equal(t, []byte("value3"), store.GetLatest([]byte("key2")).Value()) + + // Test SetEstimate + store.SetEstimate(4, 1, []byte("key1")) + require.True(t, store.GetLatest([]byte("key1")).IsEstimate()) + + // Test Delete + store.Delete(5, 1, []byte("key1")) + require.True(t, store.GetLatest([]byte("key1")).IsDeleted()) + + // Test GetLatestBeforeIndex + store.Set(6, 1, []byte("key1"), []byte("value4")) + require.True(t, store.GetLatestBeforeIndex(5, []byte("key1")).IsEstimate()) + require.Equal(t, []byte("value4"), store.GetLatestBeforeIndex(7, []byte("key1")).Value()) + + // Test Has + require.True(t, store.Has(2, []byte("key1"))) + require.False(t, store.Has(0, []byte("key1"))) + require.False(t, store.Has(5, []byte("key4"))) +} + +func TestMultiVersionStoreHasLaterValue(t *testing.T) { + store := multiversion.NewMultiVersionStore() + + store.Set(5, 1, []byte("key1"), []byte("value2")) + + require.Nil(t, store.GetLatestBeforeIndex(4, []byte("key1"))) + require.Equal(t, []byte("value2"), store.GetLatestBeforeIndex(6, []byte("key1")).Value()) +} From 392e139b970e77b01dbe675e8647207e338f2fd7 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 2 Oct 2023 14:05:22 -0500 Subject: [PATCH 2/6] rmeove redundant comment --- store/multiversion/store.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/store/multiversion/store.go b/store/multiversion/store.go index 169094456..20eac2bdd 100644 --- a/store/multiversion/store.go +++ b/store/multiversion/store.go @@ -6,9 +6,6 @@ import ( "github.com/cosmos/cosmos-sdk/internal/conv" ) -// base multiversion store - contains all of the values for all indices within the store, allow for forking with specified transaction index -// forked multiversion store - implements KVstore, but also stores the index of the transaction, and so all store actions done through this access with the index passed when initializing the forked store. - type MultiVersionStore interface { GetLatest(key []byte) (value MultiVersionValueItem) GetLatestBeforeIndex(index int, key []byte) (value MultiVersionValueItem) @@ -22,8 +19,6 @@ type Store struct { mtx sync.RWMutex // map that stores the key -> MultiVersionValue mapping for accessing from a given key multiVersionMap map[string]MultiVersionValue - // has to store all the multiversion values - // key -> multiversion value } func NewMultiVersionStore() *Store { From bfb90660b72fb3c23f979910db2767c973a96932 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 2 Oct 2023 14:07:05 -0500 Subject: [PATCH 3/6] add todos --- store/multiversion/store.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/store/multiversion/store.go b/store/multiversion/store.go index 20eac2bdd..cd2b3147c 100644 --- a/store/multiversion/store.go +++ b/store/multiversion/store.go @@ -13,12 +13,15 @@ type MultiVersionStore interface { SetEstimate(index int, incarnation int, key []byte) Delete(index int, incarnation int, key []byte) Has(index int, key []byte) bool + // TODO: do we want to add helper functions for validations with readsets / applying writesets ? } type Store struct { mtx sync.RWMutex // map that stores the key -> MultiVersionValue mapping for accessing from a given key multiVersionMap map[string]MultiVersionValue + // TODO: do we need to add something here to persist readsets for later validation + // TODO: we need to support iterators as well similar to how cachekv does it } func NewMultiVersionStore() *Store { From e3927161619f16a45018ccd75771512ac28d60db Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Tue, 3 Oct 2023 10:32:38 -0500 Subject: [PATCH 4/6] add todo --- store/multiversion/store.go | 1 + 1 file changed, 1 insertion(+) diff --git a/store/multiversion/store.go b/store/multiversion/store.go index cd2b3147c..832be56af 100644 --- a/store/multiversion/store.go +++ b/store/multiversion/store.go @@ -22,6 +22,7 @@ type Store struct { multiVersionMap map[string]MultiVersionValue // TODO: do we need to add something here to persist readsets for later validation // TODO: we need to support iterators as well similar to how cachekv does it + // TODO: do we need secondary indexing on index -> keys - this way if we need to abort we can replace those keys with ESTIMATE values? - maybe this just means storing writeset } func NewMultiVersionStore() *Store { From 585170e110cbcedb4b64b17cd624d3d03b1116dc Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Tue, 3 Oct 2023 18:12:32 -0500 Subject: [PATCH 5/6] improve test coverage --- store/multiversion/store.go | 2 +- store/multiversion/store_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/store/multiversion/store.go b/store/multiversion/store.go index 832be56af..40a61719a 100644 --- a/store/multiversion/store.go +++ b/store/multiversion/store.go @@ -43,7 +43,7 @@ func (s *Store) GetLatest(key []byte) (value MultiVersionValueItem) { } val, found := s.multiVersionMap[keyString].GetLatest() if !found { - return nil + return nil // this shouldn't be possible } return val } diff --git a/store/multiversion/store_test.go b/store/multiversion/store_test.go index bf68ea02a..91465c435 100644 --- a/store/multiversion/store_test.go +++ b/store/multiversion/store_test.go @@ -44,3 +44,11 @@ func TestMultiVersionStoreHasLaterValue(t *testing.T) { require.Nil(t, store.GetLatestBeforeIndex(4, []byte("key1"))) require.Equal(t, []byte("value2"), store.GetLatestBeforeIndex(6, []byte("key1")).Value()) } + +func TestMultiVersionStoreKeyDNE(t *testing.T) { + store := multiversion.NewMultiVersionStore() + + require.Nil(t, store.GetLatest([]byte("key1"))) + require.Nil(t, store.GetLatestBeforeIndex(0, []byte("key1"))) + require.False(t, store.Has(0, []byte("key1"))) +} From 54a6b4f74235558d9197a3e797f6e17a1df4dee1 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Wed, 4 Oct 2023 11:16:16 -0500 Subject: [PATCH 6/6] use string cast instead of unsafe conv --- store/multiversion/store.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/store/multiversion/store.go b/store/multiversion/store.go index 40a61719a..b52c6af1a 100644 --- a/store/multiversion/store.go +++ b/store/multiversion/store.go @@ -2,8 +2,6 @@ package multiversion import ( "sync" - - "github.com/cosmos/cosmos-sdk/internal/conv" ) type MultiVersionStore interface { @@ -36,7 +34,7 @@ func (s *Store) GetLatest(key []byte) (value MultiVersionValueItem) { s.mtx.RLock() defer s.mtx.RUnlock() - keyString := conv.UnsafeBytesToStr(key) + keyString := string(key) // if the key doesn't exist in the overall map, return nil if _, ok := s.multiVersionMap[keyString]; !ok { return nil @@ -53,7 +51,7 @@ func (s *Store) GetLatestBeforeIndex(index int, key []byte) (value MultiVersionV s.mtx.RLock() defer s.mtx.RUnlock() - keyString := conv.UnsafeBytesToStr(key) + keyString := string(key) // if the key doesn't exist in the overall map, return nil if _, ok := s.multiVersionMap[keyString]; !ok { return nil @@ -72,7 +70,7 @@ func (s *Store) Has(index int, key []byte) bool { s.mtx.RLock() defer s.mtx.RUnlock() - keyString := conv.UnsafeBytesToStr(key) + keyString := string(key) if _, ok := s.multiVersionMap[keyString]; !ok { return false // this is okay because the caller of this will THEN need to access the parent store to verify that the key doesnt exist there } @@ -94,7 +92,7 @@ func (s *Store) Set(index int, incarnation int, key []byte, value []byte) { s.mtx.Lock() defer s.mtx.Unlock() - keyString := conv.UnsafeBytesToStr(key) + keyString := string(key) s.tryInitMultiVersionItem(keyString) s.multiVersionMap[keyString].Set(index, incarnation, value) } @@ -104,7 +102,7 @@ func (s *Store) SetEstimate(index int, incarnation int, key []byte) { s.mtx.Lock() defer s.mtx.Unlock() - keyString := conv.UnsafeBytesToStr(key) + keyString := string(key) s.tryInitMultiVersionItem(keyString) s.multiVersionMap[keyString].SetEstimate(index, incarnation) } @@ -114,7 +112,7 @@ func (s *Store) Delete(index int, incarnation int, key []byte) { s.mtx.Lock() defer s.mtx.Unlock() - keyString := conv.UnsafeBytesToStr(key) + keyString := string(key) s.tryInitMultiVersionItem(keyString) s.multiVersionMap[keyString].Delete(index, incarnation) }