From b71c03a6874ea2cf92e6db22773ebebf0aed020b Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 5 Dec 2018 15:51:19 +0800 Subject: [PATCH 1/3] Add unit tests for store and merge PR: https://github.com/cosmos/cosmos-sdk/pull/2900 and https://github.com/cosmos/cosmos-sdk/pull/2932 --- Gopkg.lock | 28 +- Gopkg.toml | 10 +- client/lcd/lcd.go | 11 +- client/lcd/test_helpers.go | 11 +- client/tendermint/rpc/validatorset.go | 16 +- docker-compose.yml | 8 +- modules/gov/handler.go | 2 +- store/cachekvstore.go | 11 +- store/cachekvstore_test.go | 518 ++++++++++++++++++++++++++ store/gaskvstore_test.go | 105 ++++++ store/iavlstore_test.go | 489 ++++++++++++++++++++++++ store/list_test.go | 75 ++++ store/memiterator.go | 3 +- store/multistoreproof_test.go | 174 +++++++++ store/prefixstore.go | 12 +- store/prefixstore_test.go | 422 +++++++++++++++++++++ store/queue_test.go | 102 +++++ store/rootmultistore_test.go | 276 ++++++++++++++ store/tracekvstore_test.go | 283 ++++++++++++++ store/transientstore_test.go | 23 ++ types/store.go | 2 +- 21 files changed, 2533 insertions(+), 48 deletions(-) create mode 100644 store/cachekvstore_test.go create mode 100644 store/gaskvstore_test.go create mode 100644 store/iavlstore_test.go create mode 100644 store/list_test.go create mode 100644 store/multistoreproof_test.go create mode 100644 store/prefixstore_test.go create mode 100644 store/queue_test.go create mode 100644 store/rootmultistore_test.go create mode 100644 store/tracekvstore_test.go create mode 100644 store/transientstore_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 0f81f747a..25c5dc489 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -433,14 +433,16 @@ version = "v1.0.0" [[projects]] - digest = "1:c1a04665f9613e082e1209cf288bf64f4068dcd6c87a64bf1c4ff006ad422ba0" + digest = "1:26663fafdea73a38075b07e8e9d82fc0056379d2be8bb4e13899e8fda7c7dd23" name = "github.com/prometheus/client_golang" packages = [ "prometheus", + "prometheus/internal", "prometheus/promhttp", ] pruneopts = "UT" - revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632" + revision = "abad2d1bd44235a26707c172eab6bca5bf2dbad3" + version = "v0.9.1" [[projects]] branch = "master" @@ -490,6 +492,14 @@ pruneopts = "UT" revision = "e2704e165165ec55d062f5919b4b29494e9fa790" +[[projects]] + digest = "1:b0c25f00bad20d783d259af2af8666969e2fc343fa0dc9efe52936bbd67fb758" + name = "github.com/rs/cors" + packages = ["."] + pruneopts = "UT" + revision = "9a47f48565a795472d43519dd49aac781f3034fb" + version = "v1.6.0" + [[projects]] digest = "1:3fb45284d554f369d08136a0fd4d01f50897f6f79469358c41fb5a30a6379dfe" name = "github.com/shirou/gopsutil" @@ -620,15 +630,17 @@ version = "v0.14.0" [[projects]] - digest = "1:9f8c4c93658315a795ffd3e0c943d39f78067dd8382b8d7bcfaf6686b92f3978" + branch = "irisnet/v0.12.0-iris" + digest = "1:bfbae02a6a9430f804a8a7fee56edbb93c38e08a777ab4a6a33b03b4780b97f6" name = "github.com/tendermint/iavl" packages = ["."] pruneopts = "UT" - revision = "fa74114f764f9827c4ad5573f990ed25bf8c4bac" - version = "v0.11.1" + revision = "4114c0fad8f5909e46327dc967259e33e0e80508" + source = "https://github.com/irisnet/iavl.git" [[projects]] - digest = "1:d05123b0e2037d03251d777ea6cdb17b19fcfc5b6bc6b0d0dc2d2a6df6b60ac8" + branch = "irisnet/v0.27.0-dev1-iris" + digest = "1:0be8656e9d8eb385caa0f30b71158126b6b4294a3c8fb6f0eb5aab350bd74712" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -659,7 +671,6 @@ "libs/clist", "libs/common", "libs/db", - "libs/errors", "libs/events", "libs/fail", "libs/flowrate", @@ -694,9 +705,8 @@ "version", ] pruneopts = "UT" - revision = "6c882b92f297ca99d26b6f8cda983ec0e969f6b7" + revision = "9dd2007de1363fa77f11f71d30b59e747d2c3d99" source = "https://github.com/irisnet/tendermint.git" - version = "v0.26.1-rc3-iris" [[projects]] digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e" diff --git a/Gopkg.toml b/Gopkg.toml index 3bc4a8851..9355de3e7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -26,16 +26,14 @@ [[override]] name = "github.com/tendermint/iavl" - version = "=v0.11.1" +# version = "=v0.11.1" + source = "https://github.com/irisnet/iavl.git" + branch = "irisnet/v0.12.0-iris" [[override]] name = "github.com/tendermint/tendermint" source = "https://github.com/irisnet/tendermint.git" - version = "=v0.26.1-rc3-iris" - -[[constraint]] - name = "github.com/prometheus/client_golang" - revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632" + branch = "irisnet/v0.27.0-dev1-iris" [[constraint]] name = "github.com/emicklei/proto" diff --git a/client/lcd/lcd.go b/client/lcd/lcd.go index fd2bd49a4..6fbe2737f 100644 --- a/client/lcd/lcd.go +++ b/client/lcd/lcd.go @@ -51,14 +51,21 @@ func ServeLCDStartCommand(cdc *codec.Codec) *cobra.Command { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "irislcd") maxOpen := viper.GetInt(flagMaxOpenConnections) - listener, err := tmserver.StartHTTPServer( - listenAddr, router, logger, + listener, err := tmserver.Listen( + listenAddr, tmserver.Config{MaxOpenConnections: maxOpen}, ) if err != nil { return err } + logger.Info("Starting IRISLCD service...") + + err = tmserver.StartHTTPServer(listener, router, logger) + if err != nil { + return err + } + logger.Info("IRISLCD server started") // wait forever and cleanup diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 490f37d23..977bad439 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -286,7 +286,16 @@ func startTM( // // NOTE: This causes the thread to block. func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec) (net.Listener, error) { - return tmrpc.StartHTTPServer(listenAddr, createHandler(cdc), logger, tmrpc.Config{}) + listener, err := tmrpc.Listen(listenAddr, tmrpc.Config{}) + if err != nil { + return nil, err + } + + err = tmrpc.StartHTTPServer(listener, createHandler(cdc), logger) + if err != nil { + return nil, err + } + return listener, nil } // Request makes a test LCD test request. It returns a response object and a diff --git a/client/tendermint/rpc/validatorset.go b/client/tendermint/rpc/validatorset.go index ab5bcbe22..7377f9890 100644 --- a/client/tendermint/rpc/validatorset.go +++ b/client/tendermint/rpc/validatorset.go @@ -35,10 +35,10 @@ func ValidatorCommand() *cobra.Command { // Validator output in bech32 format type ValidatorOutput struct { - Address sdk.ValAddress `json:"address"` // in bech32 - PubKey string `json:"pub_key"` // in bech32 - Accum int64 `json:"accum"` - VotingPower int64 `json:"voting_power"` + Address sdk.ValAddress `json:"address"` // in bech32 + PubKey string `json:"pub_key"` // in bech32 + ProposerPriority int64 `json:"proposer_priority"` + VotingPower int64 `json:"voting_power"` } // Validators at a certain height output in bech32 format @@ -54,10 +54,10 @@ func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error } return ValidatorOutput{ - Address: sdk.ValAddress(validator.Address), - PubKey: bechValPubkey, - Accum: validator.Accum, - VotingPower: validator.VotingPower, + Address: sdk.ValAddress(validator.Address), + PubKey: bechValPubkey, + ProposerPriority: validator.ProposerPriority, + VotingPower: validator.VotingPower, }, nil } diff --git a/docker-compose.yml b/docker-compose.yml index 4df0896be..4a7b77a5f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: - "26656-26657:26656-26657" volumes: - ./build:/home - command: /bin/bash -c 'cd /home/nodecluster/node0 && ../../iris start --home iris' + command: /bin/bash -c 'cd /home/nodecluster/node0 && ../../iris start --home iris --log_level info' networks: localnet: ipv4_address: 192.168.10.2 @@ -20,7 +20,7 @@ services: - "26659-26660:26656-26657" volumes: - ./build:/home - command: /bin/bash -c 'cd /home/nodecluster/node1 && ../../iris start --home iris' + command: /bin/bash -c 'cd /home/nodecluster/node1 && ../../iris start --home iris --log_level info' networks: localnet: ipv4_address: 192.168.10.3 @@ -32,7 +32,7 @@ services: - "26661-26662:26656-26657" volumes: - ./build:/home - command: /bin/bash -c 'cd /home/nodecluster/node2 && ../../iris start --home iris' + command: /bin/bash -c 'cd /home/nodecluster/node2 && ../../iris start --home iris --log_level info' networks: localnet: ipv4_address: 192.168.10.4 @@ -44,7 +44,7 @@ services: - "26663-26664:26656-26657" volumes: - ./build:/home - command: /bin/bash -c 'cd /home/nodecluster/node3 && ../../iris start --home iris' + command: /bin/bash -c 'cd /home/nodecluster/node3 && ../../iris start --home iris --log_level info' networks: localnet: ipv4_address: 192.168.10.5 diff --git a/modules/gov/handler.go b/modules/gov/handler.go index 274460d87..efd51ffdb 100644 --- a/modules/gov/handler.go +++ b/modules/gov/handler.go @@ -128,7 +128,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { resTags = sdk.NewTags() if ctx.BlockHeight() == keeper.GetTerminatorHeight(ctx) { - resTags = resTags.AppendTag(tmstate.TerminateTagKey,[]byte(tmstate.TerminateTagValue)) + resTags = resTags.AppendTag(tmstate.HaltTagKey,[]byte(tmstate.HaltTagValue)) logger.Info(fmt.Sprintf("Terminator Start!!!")) } diff --git a/store/cachekvstore.go b/store/cachekvstore.go index 4a2940f4d..ba38e2936 100644 --- a/store/cachekvstore.go +++ b/store/cachekvstore.go @@ -7,6 +7,7 @@ import ( "sync" cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" ) // If value is nil but deleted is false, it means the parent doesn't have the @@ -159,22 +160,24 @@ func (ci *cacheKVStore) iterator(start, end []byte, ascending bool) Iterator { parent = ci.parent.ReverseIterator(start, end) } - items := ci.dirtyItems(ascending) + items := ci.dirtyItems(start, end, ascending) cache = newMemIterator(start, end, items) return newCacheMergeIterator(parent, cache, ascending) } // Constructs a slice of dirty items, to use w/ memIterator. -func (ci *cacheKVStore) dirtyItems(ascending bool) []cmn.KVPair { - items := make([]cmn.KVPair, 0, len(ci.cache)) +func (ci *cacheKVStore) dirtyItems(start, end []byte, ascending bool) []cmn.KVPair { + items := make([]cmn.KVPair, 0) for key, cacheValue := range ci.cache { if !cacheValue.dirty { continue } - items = append(items, cmn.KVPair{Key: []byte(key), Value: cacheValue.value}) + if dbm.IsKeyInDomain([]byte(key), start, end) { + items = append(items, cmn.KVPair{Key: []byte(key), Value: cacheValue.value}) + } } sort.Slice(items, func(i, j int) bool { diff --git a/store/cachekvstore_test.go b/store/cachekvstore_test.go new file mode 100644 index 000000000..37e0364fb --- /dev/null +++ b/store/cachekvstore_test.go @@ -0,0 +1,518 @@ +package store + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" +) + +func newCacheKVStore() CacheKVStore { + mem := dbStoreAdapter{dbm.NewMemDB()} + return NewCacheKVStore(mem) +} + +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) { + mem := dbStoreAdapter{dbm.NewMemDB()} + st := NewCacheKVStore(mem) + + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + + // put something in mem and in cache + mem.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 mem + st.Set(keyFmt(1), valFmt(2)) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + require.Equal(t, valFmt(1), mem.Get(keyFmt(1))) + + // write it. should change mem + st.Write() + require.Equal(t, valFmt(2), mem.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), mem.Get(keyFmt(1))) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // make a new one, check it + st = NewCacheKVStore(mem) + require.Equal(t, valFmt(2), st.Get(keyFmt(1))) + + // make a new one and delete - should not be removed from mem + st = NewCacheKVStore(mem) + st.Delete(keyFmt(1)) + require.Empty(t, st.Get(keyFmt(1))) + require.Equal(t, mem.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, mem.Get(keyFmt(1)), "Expected `key1` to be empty") +} + +func TestCacheKVStoreNested(t *testing.T) { + mem := dbStoreAdapter{dbm.NewMemDB()} + st := NewCacheKVStore(mem) + + // set. check its there on st and not on mem. + st.Set(keyFmt(1), valFmt(1)) + require.Empty(t, mem.Get(keyFmt(1))) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + + // make a new from st and check + st2 := NewCacheKVStore(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), mem.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 mem + st2.Write() + require.Equal(t, []byte(nil), mem.Get(keyFmt(1))) + require.Equal(t, valFmt(3), st.Get(keyFmt(1))) + + // updates mem + st.Write() + require.Equal(t, valFmt(3), mem.Get(keyFmt(1))) +} + +func TestCacheKVIteratorBounds(t *testing.T) { + st := newCacheKVStore() + + // 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) + var 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) { + st := newCacheKVStore() + + // 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) { + st := newCacheKVStore() + + // 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) { + st := newCacheKVStore() + truth := dbm.NewMemDB() + + // set some items and write them + nItems := 10 + for i := 0; i < nItems; i++ { + doOp(st, truth, opSet, i) + } + st.Write() + + // delete every other item, starting from 0 + for i := 0; i < nItems; i += 2 { + doOp(st, truth, opDel, i) + assertIterateDomainCompare(t, st, truth) + } + + // reset + st = newCacheKVStore() + truth = dbm.NewMemDB() + + // set some items and write them + for i := 0; i < nItems; i++ { + doOp(st, truth, opSet, i) + } + st.Write() + + // delete every other item, starting from 1 + for i := 1; i < nItems; i += 2 { + doOp(st, truth, opDel, i) + assertIterateDomainCompare(t, st, truth) + } +} + +func TestCacheKVMergeIteratorChunks(t *testing.T) { + st := newCacheKVStore() + + // Use the truth to check values on the merge iterator + truth := dbm.NewMemDB() + + // sets to the parent + setRange(st, truth, 0, 20) + setRange(st, truth, 40, 60) + st.Write() + + // sets to the cache + setRange(st, truth, 20, 40) + setRange(st, truth, 60, 80) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 80}}) + + // remove some parents and some cache + deleteRange(st, truth, 15, 25) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 80}}) + + // remove some parents and some cache + deleteRange(st, truth, 35, 45) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {45, 80}}) + + // write, add more to the cache, and delete some cache + st.Write() + setRange(st, truth, 38, 42) + deleteRange(st, truth, 40, 43) + assertIterateDomainCheck(t, st, truth, []keyRange{{0, 15}, {25, 35}, {38, 40}, {45, 80}}) +} + +func TestCacheKVMergeIteratorRandom(t *testing.T) { + st := newCacheKVStore() + truth := dbm.NewMemDB() + + start, end := 25, 75 + max := 100 + setRange(st, truth, start, end) + + // do an op, test the iterator + for i := 0; i < 2000; i++ { + doRandomOp(st, truth, max) + assertIterateDomainCompare(t, st, truth) + } +} + +//------------------------------------------------------------------------------------------- +// 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 cmn.RandInt() % n +} + +// useful for replaying a error case if we find one +func doOp(st CacheKVStore, truth dbm.DB, 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(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(st, truth, start, end) + case opWrite: + st.Write() + } +} + +func doRandomOp(st CacheKVStore, truth dbm.DB, 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(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(st, truth, start, end) + case opWrite: + st.Write() + } +} + +//------------------------------------------------------------------------------------------- + +// iterate over whole domain +func assertIterateDomain(t *testing.T, st KVStore, expectedN int) { + itr := st.Iterator(nil, nil) + var 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 KVStore, mem dbm.DB, 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 KVStore, mem dbm.DB) { + // 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 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(st KVStore, mem dbm.DB, start, end int) { + for i := start; i < end; i++ { + st.Set(keyFmt(i), valFmt(i)) + mem.Set(keyFmt(i), valFmt(i)) + } +} + +func deleteRange(st KVStore, mem dbm.DB, 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 +} + +//-------------------------------------------------------- + +func bz(s string) []byte { return []byte(s) } + +func BenchmarkCacheKVStoreGetNoKeyFound(b *testing.B) { + st := newCacheKVStore() + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) + } +} + +func BenchmarkCacheKVStoreGetKeyFound(b *testing.B) { + st := newCacheKVStore() + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + st.Set(arr, arr) + } + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + st.Get([]byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}) + } +} diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go new file mode 100644 index 000000000..78e0d6552 --- /dev/null +++ b/store/gaskvstore_test.go @@ -0,0 +1,105 @@ +package store + +import ( + "testing" + + dbm "github.com/tendermint/tendermint/libs/db" + + sdk "github.com/irisnet/irishub/types" + + "github.com/stretchr/testify/require" +) + +func newGasKVStore() KVStore { + meter := sdk.NewGasMeter(1000) + mem := dbStoreAdapter{dbm.NewMemDB()} + return NewGasKVStore(meter, sdk.KVGasConfig(), mem) +} + +func TestGasKVStoreBasic(t *testing.T) { + mem := dbStoreAdapter{dbm.NewMemDB()} + meter := sdk.NewGasMeter(1000) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + st.Set(keyFmt(1), valFmt(1)) + require.Equal(t, valFmt(1), st.Get(keyFmt(1))) + st.Delete(keyFmt(1)) + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + require.Equal(t, meter.GasConsumed(), sdk.Gas(193)) +} + +func TestGasKVStoreIterator(t *testing.T) { + mem := dbStoreAdapter{dbm.NewMemDB()} + meter := sdk.NewGasMeter(1000) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) + require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") + require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty") + st.Set(keyFmt(1), valFmt(1)) + st.Set(keyFmt(2), valFmt(2)) + iterator := st.Iterator(nil, nil) + ka := iterator.Key() + require.Equal(t, ka, keyFmt(1)) + va := iterator.Value() + require.Equal(t, va, valFmt(1)) + iterator.Next() + kb := iterator.Key() + require.Equal(t, kb, keyFmt(2)) + vb := iterator.Value() + require.Equal(t, vb, valFmt(2)) + iterator.Next() + require.False(t, iterator.Valid()) + require.Panics(t, iterator.Next) + require.Equal(t, meter.GasConsumed(), sdk.Gas(384)) +} + +func TestGasKVStoreOutOfGasSet(t *testing.T) { + mem := dbStoreAdapter{dbm.NewMemDB()} + meter := sdk.NewGasMeter(0) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) + require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas") +} + +func TestGasKVStoreOutOfGasIterator(t *testing.T) { + mem := dbStoreAdapter{dbm.NewMemDB()} + meter := sdk.NewGasMeter(200) + st := NewGasKVStore(meter, sdk.KVGasConfig(), mem) + st.Set(keyFmt(1), valFmt(1)) + iterator := st.Iterator(nil, nil) + iterator.Next() + require.Panics(t, func() { iterator.Value() }, "Expected out-of-gas") +} + +func testGasKVStoreWrap(t *testing.T, store KVStore) { + meter := sdk.NewGasMeter(10000) + + store = store.Gas(meter, sdk.GasConfig{HasCost: 10}) + require.Equal(t, int64(0), meter.GasConsumed()) + + store.Has([]byte("key")) + require.Equal(t, int64(10), meter.GasConsumed()) + + store = store.Gas(meter, sdk.GasConfig{HasCost: 20}) + + store.Has([]byte("key")) + require.Equal(t, int64(40), meter.GasConsumed()) +} + +func TestGasKVStoreWrap(t *testing.T) { + db := dbm.NewMemDB() + tree, _ := newTree(t, db) + iavl := newIAVLStore(tree, numRecent, storeEvery) + testGasKVStoreWrap(t, iavl) + + st := NewCacheKVStore(iavl) + testGasKVStoreWrap(t, st) + + pref := st.Prefix([]byte("prefix")) + testGasKVStoreWrap(t, pref) + + dsa := dbStoreAdapter{dbm.NewMemDB()} + testGasKVStoreWrap(t, dsa) + + ts := newTransientStore() + testGasKVStoreWrap(t, ts) + +} diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go new file mode 100644 index 000000000..0cfabb044 --- /dev/null +++ b/store/iavlstore_test.go @@ -0,0 +1,489 @@ +package store + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/iavl" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + + sdk "github.com/irisnet/irishub/types" +) + +var ( + cacheSize = 100 + numRecent int64 = 5 + storeEvery int64 = 3 +) + +var ( + treeData = map[string]string{ + "hello": "goodbye", + "aloha": "shalom", + } + nMoreData = 0 +) + +// make a tree and save it +func newTree(t *testing.T, db dbm.DB) (*iavl.MutableTree, CommitID) { + tree := iavl.NewMutableTree(db, cacheSize) + for k, v := range treeData { + tree.Set([]byte(k), []byte(v)) + } + for i := 0; i < nMoreData; i++ { + key := cmn.RandBytes(12) + value := cmn.RandBytes(50) + tree.Set(key, value) + } + hash, ver, err := tree.SaveVersion() + require.Nil(t, err) + return tree, CommitID{ver, hash} +} + +func TestIAVLStoreGetSetHasDelete(t *testing.T) { + db := dbm.NewMemDB() + tree, _ := newTree(t, db) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + + key := "hello" + + exists := iavlStore.Has([]byte(key)) + require.True(t, exists) + + value := iavlStore.Get([]byte(key)) + require.EqualValues(t, value, treeData[key]) + + value2 := "notgoodbye" + iavlStore.Set([]byte(key), []byte(value2)) + + value = iavlStore.Get([]byte(key)) + require.EqualValues(t, value, value2) + + iavlStore.Delete([]byte(key)) + + exists = iavlStore.Has([]byte(key)) + require.False(t, exists) +} + +func TestIAVLIterator(t *testing.T) { + db := dbm.NewMemDB() + tree, _ := newTree(t, db) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iter := iavlStore.Iterator([]byte("aloha"), []byte("hellz")) + expected := []string{"aloha", "hello"} + var i int + + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) + i++ + } + require.Equal(t, len(expected), i) + + iter = iavlStore.Iterator([]byte("golang"), []byte("rocks")) + expected = []string{"hello"} + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) + i++ + } + require.Equal(t, len(expected), i) + + iter = iavlStore.Iterator(nil, []byte("golang")) + expected = []string{"aloha"} + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) + i++ + } + require.Equal(t, len(expected), i) + + iter = iavlStore.Iterator(nil, []byte("shalom")) + expected = []string{"aloha", "hello"} + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) + i++ + } + require.Equal(t, len(expected), i) + + iter = iavlStore.Iterator(nil, nil) + expected = []string{"aloha", "hello"} + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) + i++ + } + require.Equal(t, len(expected), i) + + iter = iavlStore.Iterator([]byte("golang"), nil) + expected = []string{"hello"} + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, treeData[expectedKey]) + i++ + } + require.Equal(t, len(expected), i) +} + +func TestIAVLSubspaceIterator(t *testing.T) { + db := dbm.NewMemDB() + tree, _ := newTree(t, db) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + + iavlStore.Set([]byte("test1"), []byte("test1")) + iavlStore.Set([]byte("test2"), []byte("test2")) + iavlStore.Set([]byte("test3"), []byte("test3")) + iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(0)}, []byte("test4")) + iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(1)}, []byte("test4")) + iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(255)}, []byte("test4")) + iavlStore.Set([]byte{byte(255), byte(255), byte(0)}, []byte("test4")) + iavlStore.Set([]byte{byte(255), byte(255), byte(1)}, []byte("test4")) + iavlStore.Set([]byte{byte(255), byte(255), byte(255)}, []byte("test4")) + + var i int + + iter := sdk.KVStorePrefixIterator(iavlStore, []byte("test")) + expected := []string{"test1", "test2", "test3"} + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, expectedKey) + i++ + } + iter.Close() + require.Equal(t, len(expected), i) + + iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) + expected2 := [][]byte{ + {byte(55), byte(255), byte(255), byte(0)}, + {byte(55), byte(255), byte(255), byte(1)}, + {byte(55), byte(255), byte(255), byte(255)}, + } + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected2[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) + i++ + } + iter.Close() + require.Equal(t, len(expected), i) + + iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) + expected2 = [][]byte{ + {byte(255), byte(255), byte(0)}, + {byte(255), byte(255), byte(1)}, + {byte(255), byte(255), byte(255)}, + } + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected2[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) + i++ + } + iter.Close() + require.Equal(t, len(expected), i) +} + +func TestIAVLReverseSubspaceIterator(t *testing.T) { + db := dbm.NewMemDB() + tree, _ := newTree(t, db) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + + iavlStore.Set([]byte("test1"), []byte("test1")) + iavlStore.Set([]byte("test2"), []byte("test2")) + iavlStore.Set([]byte("test3"), []byte("test3")) + iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(0)}, []byte("test4")) + iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(1)}, []byte("test4")) + iavlStore.Set([]byte{byte(55), byte(255), byte(255), byte(255)}, []byte("test4")) + iavlStore.Set([]byte{byte(255), byte(255), byte(0)}, []byte("test4")) + iavlStore.Set([]byte{byte(255), byte(255), byte(1)}, []byte("test4")) + iavlStore.Set([]byte{byte(255), byte(255), byte(255)}, []byte("test4")) + + var i int + + iter := sdk.KVStoreReversePrefixIterator(iavlStore, []byte("test")) + expected := []string{"test3", "test2", "test1"} + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, expectedKey) + i++ + } + require.Equal(t, len(expected), i) + + iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)}) + expected2 := [][]byte{ + {byte(55), byte(255), byte(255), byte(255)}, + {byte(55), byte(255), byte(255), byte(1)}, + {byte(55), byte(255), byte(255), byte(0)}, + } + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected2[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) + i++ + } + require.Equal(t, len(expected), i) + + iter = sdk.KVStoreReversePrefixIterator(iavlStore, []byte{byte(255), byte(255)}) + expected2 = [][]byte{ + {byte(255), byte(255), byte(255)}, + {byte(255), byte(255), byte(1)}, + {byte(255), byte(255), byte(0)}, + } + for i = 0; iter.Valid(); iter.Next() { + expectedKey := expected2[i] + key, value := iter.Key(), iter.Value() + require.EqualValues(t, key, expectedKey) + require.EqualValues(t, value, []byte("test4")) + i++ + } + require.Equal(t, len(expected), i) +} + +func nextVersion(iavl *iavlStore) { + key := []byte(fmt.Sprintf("Key for tree: %d", iavl.LastCommitID().Version)) + value := []byte(fmt.Sprintf("Value for tree: %d", iavl.LastCommitID().Version)) + iavl.Set(key, value) + iavl.Commit() +} + +func TestIAVLDefaultPruning(t *testing.T) { + //Expected stored / deleted version numbers for: + //numRecent = 5, storeEvery = 3 + var states = []pruneState{ + {[]int64{}, []int64{}}, + {[]int64{1}, []int64{}}, + {[]int64{1, 2}, []int64{}}, + {[]int64{1, 2, 3}, []int64{}}, + {[]int64{1, 2, 3, 4}, []int64{}}, + {[]int64{1, 2, 3, 4, 5}, []int64{}}, + {[]int64{1, 2, 3, 4, 5, 6}, []int64{}}, + {[]int64{2, 3, 4, 5, 6, 7}, []int64{1}}, + {[]int64{3, 4, 5, 6, 7, 8}, []int64{1, 2}}, + {[]int64{3, 4, 5, 6, 7, 8, 9}, []int64{1, 2}}, + {[]int64{3, 5, 6, 7, 8, 9, 10}, []int64{1, 2, 4}}, + {[]int64{3, 6, 7, 8, 9, 10, 11}, []int64{1, 2, 4, 5}}, + {[]int64{3, 6, 7, 8, 9, 10, 11, 12}, []int64{1, 2, 4, 5}}, + {[]int64{3, 6, 8, 9, 10, 11, 12, 13}, []int64{1, 2, 4, 5, 7}}, + {[]int64{3, 6, 9, 10, 11, 12, 13, 14}, []int64{1, 2, 4, 5, 7, 8}}, + {[]int64{3, 6, 9, 10, 11, 12, 13, 14, 15}, []int64{1, 2, 4, 5, 7, 8}}, + } + testPruning(t, int64(5), int64(3), states) +} + +func TestIAVLAlternativePruning(t *testing.T) { + //Expected stored / deleted version numbers for: + //numRecent = 3, storeEvery = 5 + var states = []pruneState{ + {[]int64{}, []int64{}}, + {[]int64{1}, []int64{}}, + {[]int64{1, 2}, []int64{}}, + {[]int64{1, 2, 3}, []int64{}}, + {[]int64{1, 2, 3, 4}, []int64{}}, + {[]int64{2, 3, 4, 5}, []int64{1}}, + {[]int64{3, 4, 5, 6}, []int64{1, 2}}, + {[]int64{4, 5, 6, 7}, []int64{1, 2, 3}}, + {[]int64{5, 6, 7, 8}, []int64{1, 2, 3, 4}}, + {[]int64{5, 6, 7, 8, 9}, []int64{1, 2, 3, 4}}, + {[]int64{5, 7, 8, 9, 10}, []int64{1, 2, 3, 4, 6}}, + {[]int64{5, 8, 9, 10, 11}, []int64{1, 2, 3, 4, 6, 7}}, + {[]int64{5, 9, 10, 11, 12}, []int64{1, 2, 3, 4, 6, 7, 8}}, + {[]int64{5, 10, 11, 12, 13}, []int64{1, 2, 3, 4, 6, 7, 8, 9}}, + {[]int64{5, 10, 11, 12, 13, 14}, []int64{1, 2, 3, 4, 6, 7, 8, 9}}, + {[]int64{5, 10, 12, 13, 14, 15}, []int64{1, 2, 3, 4, 6, 7, 8, 9, 11}}, + } + testPruning(t, int64(3), int64(5), states) +} + +type pruneState struct { + stored []int64 + deleted []int64 +} + +func testPruning(t *testing.T, numRecent int64, storeEvery int64, states []pruneState) { + db := dbm.NewMemDB() + tree := iavl.NewMutableTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + for step, state := range states { + for _, ver := range state.stored { + require.True(t, iavlStore.VersionExists(ver), + "Missing version %d with latest version %d. Should save last %d and every %d", + ver, step, numRecent, storeEvery) + } + for _, ver := range state.deleted { + require.False(t, iavlStore.VersionExists(ver), + "Unpruned version %d with latest version %d. Should prune all but last %d and every %d", + ver, step, numRecent, storeEvery) + } + nextVersion(iavlStore) + } +} + +func TestIAVLNoPrune(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewMutableTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, int64(1)) + nextVersion(iavlStore) + for i := 1; i < 100; i++ { + for j := 1; j <= i; j++ { + require.True(t, iavlStore.VersionExists(int64(j)), + "Missing version %d with latest version %d. Should be storing all versions", + j, i) + } + nextVersion(iavlStore) + } +} + +func TestIAVLPruneEverything(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewMutableTree(db, cacheSize) + iavlStore := newIAVLStore(tree, int64(0), int64(0)) + nextVersion(iavlStore) + for i := 1; i < 100; i++ { + for j := 1; j < i; j++ { + require.False(t, iavlStore.VersionExists(int64(j)), + "Unpruned version %d with latest version %d. Should prune all old versions", + j, i) + } + require.True(t, iavlStore.VersionExists(int64(i)), + "Missing current version on step %d, should not prune current state tree", + i) + nextVersion(iavlStore) + } +} + +func TestIAVLStoreQuery(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewMutableTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + + k1, v1 := []byte("key1"), []byte("val1") + k2, v2 := []byte("key2"), []byte("val2") + v3 := []byte("val3") + + ksub := []byte("key") + KVs0 := []KVPair{} + KVs1 := []KVPair{ + {Key: k1, Value: v1}, + {Key: k2, Value: v2}, + } + KVs2 := []KVPair{ + {Key: k1, Value: v3}, + {Key: k2, Value: v2}, + } + valExpSubEmpty := cdc.MustMarshalBinaryLengthPrefixed(KVs0) + valExpSub1 := cdc.MustMarshalBinaryLengthPrefixed(KVs1) + valExpSub2 := cdc.MustMarshalBinaryLengthPrefixed(KVs2) + + cid := iavlStore.Commit() + ver := cid.Version + query := abci.RequestQuery{Path: "/key", Data: k1, Height: ver} + querySub := abci.RequestQuery{Path: "/subspace", Data: ksub, Height: ver} + + // query subspace before anything set + qres := iavlStore.Query(querySub) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, valExpSubEmpty, qres.Value) + + // set data + iavlStore.Set(k1, v1) + iavlStore.Set(k2, v2) + + // set data without commit, doesn't show up + qres = iavlStore.Query(query) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Nil(t, qres.Value) + + // commit it, but still don't see on old version + cid = iavlStore.Commit() + qres = iavlStore.Query(query) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Nil(t, qres.Value) + + // but yes on the new version + query.Height = cid.Version + qres = iavlStore.Query(query) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v1, qres.Value) + + // and for the subspace + qres = iavlStore.Query(querySub) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, valExpSub1, qres.Value) + + // modify + iavlStore.Set(k1, v3) + cid = iavlStore.Commit() + + // query will return old values, as height is fixed + qres = iavlStore.Query(query) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v1, qres.Value) + + // update to latest in the query and we are happy + query.Height = cid.Version + qres = iavlStore.Query(query) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v3, qres.Value) + query2 := abci.RequestQuery{Path: "/key", Data: k2, Height: cid.Version} + + qres = iavlStore.Query(query2) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v2, qres.Value) + // and for the subspace + qres = iavlStore.Query(querySub) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, valExpSub2, qres.Value) + + // default (height 0) will show latest -1 + query0 := abci.RequestQuery{Path: "/key", Data: k1} + qres = iavlStore.Query(query0) + require.Equal(t, uint32(sdk.CodeOK), qres.Code) + require.Equal(t, v1, qres.Value) +} + +func BenchmarkIAVLIteratorNext(b *testing.B) { + db := dbm.NewMemDB() + treeSize := 1000 + tree := iavl.NewMutableTree(db, cacheSize) + for i := 0; i < treeSize; i++ { + key := cmn.RandBytes(4) + value := cmn.RandBytes(50) + tree.Set(key, value) + } + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + iterators := make([]Iterator, b.N/treeSize) + for i := 0; i < len(iterators); i++ { + iterators[i] = iavlStore.Iterator([]byte{0}, []byte{255, 255, 255, 255, 255}) + } + b.ResetTimer() + for i := 0; i < len(iterators); i++ { + iter := iterators[i] + for j := 0; j < treeSize; j++ { + iter.Next() + } + } +} diff --git a/store/list_test.go b/store/list_test.go new file mode 100644 index 000000000..8e6ea6164 --- /dev/null +++ b/store/list_test.go @@ -0,0 +1,75 @@ +package store + +import ( + "math/rand" + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + lm := NewList(cdc, store) + + val := S{1, true} + var res S + + lm.Push(val) + require.Equal(t, uint64(1), lm.Len()) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{2, false} + lm.Set(uint64(0), val) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{100, false} + lm.Push(val) + require.Equal(t, uint64(2), lm.Len()) + lm.Get(uint64(1), &res) + require.Equal(t, val, res) + + lm.Delete(uint64(1)) + require.Equal(t, uint64(2), lm.Len()) + + lm.Iterate(&res, func(index uint64) (brk bool) { + var temp S + lm.Get(index, &temp) + require.Equal(t, temp, res) + + require.True(t, index != 1) + return + }) + + lm.Iterate(&res, func(index uint64) (brk bool) { + lm.Set(index, S{res.I + 1, !res.B}) + return + }) + + lm.Get(uint64(0), &res) + require.Equal(t, S{3, true}, res) +} + +func TestListRandom(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + list := NewList(cdc, store) + mocklist := []uint32{} + + for i := 0; i < 100; i++ { + item := rand.Uint32() + list.Push(item) + mocklist = append(mocklist, item) + } + + for k, v := range mocklist { + var i uint32 + require.NotPanics(t, func() { list.Get(uint64(k), &i) }) + require.Equal(t, v, i) + } +} diff --git a/store/memiterator.go b/store/memiterator.go index a72418db6..c9a026cb5 100644 --- a/store/memiterator.go +++ b/store/memiterator.go @@ -18,8 +18,7 @@ type memIterator struct { func newMemIterator(start, end []byte, items []cmn.KVPair) *memIterator { itemsInDomain := make([]cmn.KVPair, 0) for _, item := range items { - ascending := keyCompare(start, end) < 0 - if dbm.IsKeyInDomain(item.Key, start, end, !ascending) { + if dbm.IsKeyInDomain(item.Key, start, end) { itemsInDomain = append(itemsInDomain, item) } } diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go new file mode 100644 index 000000000..ac1f37493 --- /dev/null +++ b/store/multistoreproof_test.go @@ -0,0 +1,174 @@ +package store + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" +) + +func TestVerifyIAVLStoreQueryProof(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + iStore, err := LoadIAVLStore(db, CommitID{}, sdk.PruneNothing, false) + store := iStore.(*iavlStore) + require.Nil(t, err) + store.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY_NOT", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE_NOT")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte(nil)) + require.NotNil(t, err) +} + +func TestVerifyMultiStoreQueryProof(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0, false) + + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) + + // Verify proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.Nil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY_NOT", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE_NOT")) + require.NotNil(t, err) + + // Verify (bad) proof. + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte(nil)) + require.NotNil(t, err) +} + +func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0, false) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) +} + +func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0, false) + + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYABSENTKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyAbsence(res.Proof, cid.Hash, "/MYABSENTKEY") + require.NotNil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY", []byte("")) + require.NotNil(t, err) +} diff --git a/store/prefixstore.go b/store/prefixstore.go index 7561de16a..ec8f5a7c7 100644 --- a/store/prefixstore.go +++ b/store/prefixstore.go @@ -99,24 +99,16 @@ func (s prefixStore) Iterator(start, end []byte) Iterator { // Implements KVStore // Check https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db.go#L129 func (s prefixStore) ReverseIterator(start, end []byte) Iterator { - var newstart []byte - if start == nil { - newstart = cpIncr(s.prefix) - } else { - newstart = cloneAppend(s.prefix, start) - } + newstart := cloneAppend(s.prefix, start) var newend []byte if end == nil { - newend = cpDecr(s.prefix) + newend = cpIncr(s.prefix) } else { newend = cloneAppend(s.prefix, end) } iter := s.parent.ReverseIterator(newstart, newend) - if start == nil { - skipOne(iter, cpIncr(s.prefix)) - } return newPrefixIterator(s.prefix, start, end, iter) } diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go new file mode 100644 index 000000000..13f89922a --- /dev/null +++ b/store/prefixstore_test.go @@ -0,0 +1,422 @@ +package store + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/iavl" + dbm "github.com/tendermint/tendermint/libs/db" + + sdk "github.com/irisnet/irishub/types" +) + +type kvpair struct { + key []byte + value []byte +} + +func genRandomKVPairs(t *testing.T) []kvpair { + kvps := make([]kvpair, 20) + + for i := 0; i < 20; i++ { + kvps[i].key = make([]byte, 32) + rand.Read(kvps[i].key) + kvps[i].value = make([]byte, 32) + rand.Read(kvps[i].value) + } + + return kvps +} + +func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { + kvps := genRandomKVPairs(t) + for _, kvp := range kvps { + store.Set(kvp.key, kvp.value) + } + return kvps +} + +func testPrefixStore(t *testing.T, baseStore KVStore, prefix []byte) { + prefixStore := baseStore.Prefix(prefix) + prefixPrefixStore := prefixStore.Prefix([]byte("prefix")) + + require.Panics(t, func() { prefixStore.Get(nil) }) + require.Panics(t, func() { prefixStore.Set(nil, []byte{}) }) + + kvps := setRandomKVPairs(t, prefixPrefixStore) + + for i := 0; i < 20; i++ { + key := kvps[i].key + value := kvps[i].value + require.True(t, prefixPrefixStore.Has(key)) + require.Equal(t, value, prefixPrefixStore.Get(key)) + + key = append([]byte("prefix"), key...) + require.True(t, prefixStore.Has(key)) + require.Equal(t, value, prefixStore.Get(key)) + key = append(prefix, key...) + require.True(t, baseStore.Has(key)) + require.Equal(t, value, baseStore.Get(key)) + + key = kvps[i].key + prefixPrefixStore.Delete(key) + require.False(t, prefixPrefixStore.Has(key)) + require.Nil(t, prefixPrefixStore.Get(key)) + key = append([]byte("prefix"), key...) + require.False(t, prefixStore.Has(key)) + require.Nil(t, prefixStore.Get(key)) + key = append(prefix, key...) + require.False(t, baseStore.Has(key)) + require.Nil(t, baseStore.Get(key)) + } +} + +func TestIAVLStorePrefix(t *testing.T) { + db := dbm.NewMemDB() + tree := iavl.NewMutableTree(db, cacheSize) + iavlStore := newIAVLStore(tree, numRecent, storeEvery) + + testPrefixStore(t, iavlStore, []byte("test")) +} + +func TestCacheKVStorePrefix(t *testing.T) { + cacheStore := newCacheKVStore() + + testPrefixStore(t, cacheStore, []byte("test")) +} + +func TestGasKVStorePrefix(t *testing.T) { + meter := sdk.NewGasMeter(100000000) + mem := dbStoreAdapter{dbm.NewMemDB()} + gasStore := NewGasKVStore(meter, sdk.KVGasConfig(), mem) + + testPrefixStore(t, gasStore, []byte("test")) +} + +func TestPrefixStoreIterate(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + prefix := []byte("test") + prefixStore := baseStore.Prefix(prefix) + + setRandomKVPairs(t, prefixStore) + + bIter := sdk.KVStorePrefixIterator(baseStore, prefix) + pIter := sdk.KVStorePrefixIterator(prefixStore, nil) + + for bIter.Valid() && pIter.Valid() { + require.Equal(t, bIter.Key(), append(prefix, pIter.Key()...)) + require.Equal(t, bIter.Value(), pIter.Value()) + + bIter.Next() + pIter.Next() + } + + bIter.Close() + pIter.Close() +} + +func incFirstByte(bz []byte) { + bz[0]++ +} + +func TestCloneAppend(t *testing.T) { + kvps := genRandomKVPairs(t) + for _, kvp := range kvps { + bz := cloneAppend(kvp.key, kvp.value) + require.Equal(t, bz, append(kvp.key, kvp.value...)) + + incFirstByte(bz) + require.NotEqual(t, bz, append(kvp.key, kvp.value...)) + + bz = cloneAppend(kvp.key, kvp.value) + incFirstByte(kvp.key) + require.NotEqual(t, bz, append(kvp.key, kvp.value...)) + + bz = cloneAppend(kvp.key, kvp.value) + incFirstByte(kvp.value) + require.NotEqual(t, bz, append(kvp.key, kvp.value...)) + } +} + +func TestPrefixStoreIteratorEdgeCase(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + + // overflow in cpIncr + prefix := []byte{0xAA, 0xFF, 0xFF} + prefixStore := baseStore.Prefix(prefix) + + // ascending order + baseStore.Set([]byte{0xAA, 0xFF, 0xFE}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x00}, []byte{}) + + iter := prefixStore.Iterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() +} + +func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + + // overflow in cpIncr + prefix := []byte{0xAA, 0xFF, 0xFF} + prefixStore := baseStore.Prefix(prefix) + + // descending order + baseStore.Set([]byte{0xAB, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE}, []byte{}) + + iter := prefixStore.ReverseIterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() + + db = dbm.NewMemDB() + baseStore = dbStoreAdapter{db} + + // underflow in cpDecr + prefix = []byte{0xAA, 0x00, 0x00} + prefixStore = baseStore.Prefix(prefix) + + baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x01}, []byte{}) + baseStore.Set([]byte{0xAA, 0x00, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xA9, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xA9, 0xFF, 0xFF}, []byte{}) + + iter = prefixStore.ReverseIterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() +} + +// Tests below are ported from https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db_test.go + +func mockStoreWithStuff() sdk.KVStore { + db := dbm.NewMemDB() + store := dbStoreAdapter{db} + // Under "key" prefix + store.Set(bz("key"), bz("value")) + store.Set(bz("key1"), bz("value1")) + store.Set(bz("key2"), bz("value2")) + store.Set(bz("key3"), bz("value3")) + store.Set(bz("something"), bz("else")) + store.Set(bz(""), bz("")) + store.Set(bz("k"), bz("val")) + store.Set(bz("ke"), bz("valu")) + store.Set(bz("kee"), bz("valuu")) + return store +} + +func checkValue(t *testing.T, store sdk.KVStore, key []byte, expected []byte) { + bz := store.Get(key) + require.Equal(t, expected, bz) +} + +func checkValid(t *testing.T, itr sdk.Iterator, expected bool) { + valid := itr.Valid() + require.Equal(t, expected, valid) +} + +func checkNext(t *testing.T, itr sdk.Iterator, expected bool) { + itr.Next() + valid := itr.Valid() + require.Equal(t, expected, valid) +} + +func checkDomain(t *testing.T, itr sdk.Iterator, start, end []byte) { + ds, de := itr.Domain() + require.Equal(t, start, ds) + require.Equal(t, end, de) +} + +func checkItem(t *testing.T, itr sdk.Iterator, key, value []byte) { + require.Exactly(t, key, itr.Key()) + require.Exactly(t, value, itr.Value()) +} + +func checkInvalid(t *testing.T, itr sdk.Iterator) { + checkValid(t, itr, false) + checkKeyPanics(t, itr) + checkValuePanics(t, itr) + checkNextPanics(t, itr) +} + +func checkKeyPanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Key() }) +} + +func checkValuePanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Value() }) +} + +func checkNextPanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Next() }) +} + +func TestPrefixDBSimple(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + checkValue(t, pstore, bz("key"), nil) + checkValue(t, pstore, bz(""), bz("value")) + checkValue(t, pstore, bz("key1"), nil) + checkValue(t, pstore, bz("1"), bz("value1")) + checkValue(t, pstore, bz("key2"), nil) + checkValue(t, pstore, bz("2"), bz("value2")) + checkValue(t, pstore, bz("key3"), nil) + checkValue(t, pstore, bz("3"), bz("value3")) + checkValue(t, pstore, bz("something"), nil) + checkValue(t, pstore, bz("k"), nil) + checkValue(t, pstore, bz("ke"), nil) + checkValue(t, pstore, bz("kee"), nil) +} + +func TestPrefixDBIterator1(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(nil, nil) + checkDomain(t, itr, nil, nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator2(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(nil, bz("")) + checkDomain(t, itr, nil, bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator3(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(bz(""), nil) + checkDomain(t, itr, bz(""), nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator4(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(bz(""), bz("")) + checkDomain(t, itr, bz(""), bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator1(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(nil, nil) + checkDomain(t, itr, nil, nil) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator2(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(bz(""), nil) + checkDomain(t, itr, bz(""), nil) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator3(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(nil, bz("")) + checkDomain(t, itr, nil, bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator4(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(bz(""), bz("")) + checkInvalid(t, itr) + itr.Close() +} diff --git a/store/queue_test.go b/store/queue_test.go new file mode 100644 index 000000000..a287b1550 --- /dev/null +++ b/store/queue_test.go @@ -0,0 +1,102 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" +) + +type S struct { + I uint64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { + db := dbm.NewMemDB() + cms := NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) + cdc := codec.New() + return ctx, cdc +} + +func TestQueue(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + + qm := NewQueue(cdc, store) + + val := S{1, true} + var res S + + qm.Push(val) + qm.Peek(&res) + require.Equal(t, val, res) + + qm.Pop() + empty := qm.IsEmpty() + + require.True(t, empty) + require.NotNil(t, qm.Peek(&res)) + + qm.Push(S{1, true}) + qm.Push(S{2, true}) + qm.Push(S{3, true}) + qm.Flush(&res, func() (brk bool) { + if res.I == 3 { + brk = true + } + return + }) + + require.False(t, qm.IsEmpty()) + + qm.Pop() + require.True(t, qm.IsEmpty()) +} + +func TestKeys(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + queue := NewQueue(cdc, store) + + for i := 0; i < 10; i++ { + queue.Push(i) + } + + var len uint64 + var top uint64 + var expected int + var actual int + + // Checking keys.LengthKey + err := cdc.UnmarshalBinaryLengthPrefixed(store.Get(LengthKey()), &len) + require.Nil(t, err) + require.Equal(t, len, queue.List.Len()) + + // Checking keys.ElemKey + for i := 0; i < 10; i++ { + queue.List.Get(uint64(i), &expected) + bz := store.Get(ElemKey(uint64(i))) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &actual) + require.Nil(t, err) + require.Equal(t, expected, actual) + } + + queue.Pop() + + err = cdc.UnmarshalBinaryLengthPrefixed(store.Get(TopKey()), &top) + require.Nil(t, err) + require.Equal(t, top, queue.getTop()) +} diff --git a/store/rootmultistore_test.go b/store/rootmultistore_test.go new file mode 100644 index 000000000..dd8dc65d8 --- /dev/null +++ b/store/rootmultistore_test.go @@ -0,0 +1,276 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" + dbm "github.com/tendermint/tendermint/libs/db" + + sdk "github.com/irisnet/irishub/types" +) + +const useDebugDB = false + +func TestStoreType(t *testing.T) { + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store1"), sdk.StoreTypeIAVL, db) + +} + +func TestStoreMount(t *testing.T) { + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + + key1 := sdk.NewKVStoreKey("store1") + key2 := sdk.NewKVStoreKey("store2") + dup1 := sdk.NewKVStoreKey("store1") + + require.NotPanics(t, func() { store.MountStoreWithDB(key1, sdk.StoreTypeIAVL, db) }) + require.NotPanics(t, func() { store.MountStoreWithDB(key2, sdk.StoreTypeIAVL, db) }) + + require.Panics(t, func() { store.MountStoreWithDB(key1, sdk.StoreTypeIAVL, db) }) + require.Panics(t, func() { store.MountStoreWithDB(dup1, sdk.StoreTypeIAVL, db) }) +} + +func TestMultistoreCommitLoad(t *testing.T) { + var db dbm.DB = dbm.NewMemDB() + if useDebugDB { + db = dbm.NewDebugDB("CMS", db) + } + store := newMultiStoreWithMounts(db) + err := store.LoadLatestVersion() + require.Nil(t, err) + + // New store has empty last commit. + commitID := CommitID{} + checkStore(t, store, commitID, commitID) + + // Make sure we can get stores by name. + s1 := store.getStoreByName("store1") + require.NotNil(t, s1) + s3 := store.getStoreByName("store3") + require.NotNil(t, s3) + s77 := store.getStoreByName("store77") + require.Nil(t, s77) + + // Make a few commits and check them. + nCommits := int64(3) + for i := int64(0); i < nCommits; i++ { + commitID = store.Commit() + expectedCommitID := getExpectedCommitID(store, i+1) + checkStore(t, store, expectedCommitID, commitID) + } + + // Load the latest multistore again and check version. + store = newMultiStoreWithMounts(db) + err = store.LoadLatestVersion() + require.Nil(t, err) + commitID = getExpectedCommitID(store, nCommits) + checkStore(t, store, commitID, commitID) + + // Commit and check version. + commitID = store.Commit() + expectedCommitID := getExpectedCommitID(store, nCommits+1) + checkStore(t, store, expectedCommitID, commitID) + + // Load an older multistore and check version. + ver := nCommits - 1 + store = newMultiStoreWithMounts(db) + err = store.LoadVersion(ver, false) + require.Nil(t, err) + commitID = getExpectedCommitID(store, ver) + checkStore(t, store, commitID, commitID) + + // XXX: commit this older version + commitID = store.Commit() + expectedCommitID = getExpectedCommitID(store, ver+1) + checkStore(t, store, expectedCommitID, commitID) + + // XXX: confirm old commit is overwritten and we have rolled back + // LatestVersion + store = newMultiStoreWithMounts(db) + err = store.LoadLatestVersion() + require.Nil(t, err) + commitID = getExpectedCommitID(store, ver+1) + checkStore(t, store, commitID, commitID) +} + +//////////////////// iris/cosmos-sdk begin /////////////////////////// +func TestCommitStoreLoadersNotUsed(t *testing.T) { + var db dbm.DB = dbm.NewMemDB() + if useDebugDB { + db = dbm.NewDebugDB("CMS", db) + } + store := newMultiStoreWithMounts(db) + err := store.LoadLatestVersion() + require.Nil(t, err) + // New store has empty last commit. + commitID := CommitID{} + checkStore(t, store, commitID, commitID) + // Make sure we can get stores by name. + s1 := store.getStoreByName("store1") + require.NotNil(t, s1) + s3 := store.getStoreByName("store3") + require.NotNil(t, s3) + s5 := store.getStoreByName("store5") + require.Nil(t, s5) + store = newMultiStoreWithMountsNewVersion(db) + err = store.LoadLatestVersion() + require.Nil(t, err) + // Make sure we can get stores by name. + s1 = store.getStoreByName("store1") + require.NotNil(t, s1) + s3 = store.getStoreByName("store3") + require.NotNil(t, s3) + s5 = store.getStoreByName("store5") + require.NotNil(t, s5) +} +func newMultiStoreWithMountsNewVersion(db dbm.DB) *rootMultiStore { + store := NewCommitMultiStore(db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store1"), sdk.StoreTypeIAVL, db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store2"), sdk.StoreTypeIAVL, db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store3"), sdk.StoreTypeIAVL, db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store4"), sdk.StoreTypeIAVL, db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store5"), sdk.StoreTypeIAVL, db) + return store +} +//////////////////// iris/cosmos-sdk end /////////////////////////// + +func TestParsePath(t *testing.T) { + _, _, err := parsePath("foo") + require.Error(t, err) + + store, subpath, err := parsePath("/foo") + require.NoError(t, err) + require.Equal(t, store, "foo") + require.Equal(t, subpath, "") + + store, subpath, err = parsePath("/fizz/bang/baz") + require.NoError(t, err) + require.Equal(t, store, "fizz") + require.Equal(t, subpath, "/bang/baz") + + substore, subsubpath, err := parsePath(subpath) + require.NoError(t, err) + require.Equal(t, substore, "bang") + require.Equal(t, subsubpath, "/baz") + +} + +func TestMultiStoreQuery(t *testing.T) { + db := dbm.NewMemDB() + multi := newMultiStoreWithMounts(db) + err := multi.LoadLatestVersion() + require.Nil(t, err) + + k, v := []byte("wind"), []byte("blows") + k2, v2 := []byte("water"), []byte("flows") + // v3 := []byte("is cold") + + cid := multi.Commit() + + // Make sure we can get by name. + garbage := multi.getStoreByName("bad-name") + require.Nil(t, garbage) + + // Set and commit data in one store. + store1 := multi.getStoreByName("store1").(KVStore) + store1.Set(k, v) + + // ... and another. + store2 := multi.getStoreByName("store2").(KVStore) + store2.Set(k2, v2) + + // Commit the multistore. + cid = multi.Commit() + ver := cid.Version + + // Reload multistore from database + multi = newMultiStoreWithMounts(db) + err = multi.LoadLatestVersion() + require.Nil(t, err) + + // Test bad path. + query := abci.RequestQuery{Path: "/key", Data: k, Height: ver} + qres := multi.Query(query) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + + query.Path = "h897fy32890rf63296r92" + qres = multi.Query(query) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + + // Test invalid store name. + query.Path = "/garbage/key" + qres = multi.Query(query) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnknownRequest), sdk.ABCICodeType(qres.Code)) + + // Test valid query with data. + query.Path = "/store1/key" + qres = multi.Query(query) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + require.Equal(t, v, qres.Value) + + // Test valid but empty query. + query.Path = "/store2/key" + query.Prove = true + qres = multi.Query(query) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + require.Nil(t, qres.Value) + + // Test store2 data. + query.Data = k2 + qres = multi.Query(query) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), sdk.ABCICodeType(qres.Code)) + require.Equal(t, v2, qres.Value) +} + +//----------------------------------------------------------------------- +// utils + +func newMultiStoreWithMounts(db dbm.DB) *rootMultiStore { + store := NewCommitMultiStore(db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store1"), sdk.StoreTypeIAVL, nil) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store2"), sdk.StoreTypeIAVL, nil) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store3"), sdk.StoreTypeIAVL, nil) + return store +} + +func checkStore(t *testing.T, store *rootMultiStore, expect, got CommitID) { + require.Equal(t, expect, got) + require.Equal(t, expect, store.LastCommitID()) + +} + +func getExpectedCommitID(store *rootMultiStore, ver int64) CommitID { + return CommitID{ + Version: ver, + Hash: hashStores(store.stores), + } +} + +func hashStores(stores map[StoreKey]CommitStore) []byte { + m := make(map[string][]byte, len(stores)) + for key, store := range stores { + name := key.Name() + m[name] = storeInfo{ + Name: name, + Core: storeCore{ + CommitID: store.LastCommitID(), + // StoreType: store.GetStoreType(), + }, + }.Hash() + } + return merkle.SimpleHashFromMap(m) +} diff --git a/store/tracekvstore_test.go b/store/tracekvstore_test.go new file mode 100644 index 000000000..887fcf96e --- /dev/null +++ b/store/tracekvstore_test.go @@ -0,0 +1,283 @@ +package store + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/tendermint/tendermint/libs/db" +) + +var kvPairs = []KVPair{ + {Key: keyFmt(1), Value: valFmt(1)}, + {Key: keyFmt(2), Value: valFmt(2)}, + {Key: keyFmt(3), Value: valFmt(3)}, +} + +func newTraceKVStore(w io.Writer) *TraceKVStore { + store := newEmptyTraceKVStore(w) + + for _, kvPair := range kvPairs { + store.Set(kvPair.Key, kvPair.Value) + } + + return store +} + +func newEmptyTraceKVStore(w io.Writer) *TraceKVStore { + memDB := dbStoreAdapter{dbm.NewMemDB()} + tc := TraceContext(map[string]interface{}{"blockHeight": 64}) + + return NewTraceKVStore(memDB, w, tc) +} + +func TestTraceKVStoreGet(t *testing.T) { + testCases := []struct { + key []byte + expectedValue []byte + expectedOut string + }{ + { + key: []byte{}, + expectedValue: nil, + expectedOut: "{\"operation\":\"read\",\"key\":\"\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + key: kvPairs[0].Key, + expectedValue: kvPairs[0].Value, + expectedOut: "{\"operation\":\"read\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + key: []byte("does-not-exist"), + expectedValue: nil, + expectedOut: "{\"operation\":\"read\",\"key\":\"ZG9lcy1ub3QtZXhpc3Q=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + }, + } + + for _, tc := range testCases { + var buf bytes.Buffer + + store := newTraceKVStore(&buf) + buf.Reset() + value := store.Get(tc.key) + + require.Equal(t, tc.expectedValue, value) + require.Equal(t, tc.expectedOut, buf.String()) + } +} + +func TestTraceKVStoreSet(t *testing.T) { + testCases := []struct { + key []byte + value []byte + expectedOut string + }{ + { + key: []byte{}, + value: nil, + expectedOut: "{\"operation\":\"write\",\"key\":\"\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + key: kvPairs[0].Key, + value: kvPairs[0].Value, + expectedOut: "{\"operation\":\"write\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + } + + for _, tc := range testCases { + var buf bytes.Buffer + + store := newEmptyTraceKVStore(&buf) + buf.Reset() + store.Set(tc.key, tc.value) + + require.Equal(t, tc.expectedOut, buf.String()) + } +} + +func TestTraceKVStoreDelete(t *testing.T) { + testCases := []struct { + key []byte + expectedOut string + }{ + { + key: []byte{}, + expectedOut: "{\"operation\":\"delete\",\"key\":\"\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + key: kvPairs[0].Key, + expectedOut: "{\"operation\":\"delete\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + }, + } + + for _, tc := range testCases { + var buf bytes.Buffer + + store := newTraceKVStore(&buf) + buf.Reset() + store.Delete(tc.key) + + require.Equal(t, tc.expectedOut, buf.String()) + } +} + +func TestTraceKVStoreHas(t *testing.T) { + testCases := []struct { + key []byte + expected bool + }{ + { + key: []byte{}, + expected: false, + }, + { + key: kvPairs[0].Key, + expected: true, + }, + } + + for _, tc := range testCases { + var buf bytes.Buffer + + store := newTraceKVStore(&buf) + buf.Reset() + ok := store.Has(tc.key) + + require.Equal(t, tc.expected, ok) + } +} + +func TestTestTraceKVStoreIterator(t *testing.T) { + var buf bytes.Buffer + + store := newTraceKVStore(&buf) + iterator := store.Iterator(nil, nil) + + s, e := iterator.Domain() + require.Equal(t, []byte(nil), s) + require.Equal(t, []byte(nil), e) + + testCases := []struct { + expectedKey []byte + expectedValue []byte + expectedKeyOut string + expectedvalueOut string + }{ + { + expectedKey: kvPairs[0].Key, + expectedValue: kvPairs[0].Value, + expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + expectedKey: kvPairs[1].Key, + expectedValue: kvPairs[1].Value, + expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDI=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMg==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + expectedKey: kvPairs[2].Key, + expectedValue: kvPairs[2].Value, + expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDM=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMw==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + } + + for _, tc := range testCases { + buf.Reset() + ka := iterator.Key() + require.Equal(t, tc.expectedKeyOut, buf.String()) + + buf.Reset() + va := iterator.Value() + require.Equal(t, tc.expectedvalueOut, buf.String()) + + require.Equal(t, tc.expectedKey, ka) + require.Equal(t, tc.expectedValue, va) + + iterator.Next() + } + + require.False(t, iterator.Valid()) + require.Panics(t, iterator.Next) + require.NotPanics(t, iterator.Close) +} + +func TestTestTraceKVStoreReverseIterator(t *testing.T) { + var buf bytes.Buffer + + store := newTraceKVStore(&buf) + iterator := store.ReverseIterator(nil, nil) + + s, e := iterator.Domain() + require.Equal(t, []byte(nil), s) + require.Equal(t, []byte(nil), e) + + testCases := []struct { + expectedKey []byte + expectedValue []byte + expectedKeyOut string + expectedvalueOut string + }{ + { + expectedKey: kvPairs[2].Key, + expectedValue: kvPairs[2].Value, + expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDM=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMw==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + expectedKey: kvPairs[1].Key, + expectedValue: kvPairs[1].Value, + expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDI=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMg==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + { + expectedKey: kvPairs[0].Key, + expectedValue: kvPairs[0].Value, + expectedKeyOut: "{\"operation\":\"iterKey\",\"key\":\"a2V5MDAwMDAwMDE=\",\"value\":\"\",\"metadata\":{\"blockHeight\":64}}\n", + expectedvalueOut: "{\"operation\":\"iterValue\",\"key\":\"\",\"value\":\"dmFsdWUwMDAwMDAwMQ==\",\"metadata\":{\"blockHeight\":64}}\n", + }, + } + + for _, tc := range testCases { + buf.Reset() + ka := iterator.Key() + require.Equal(t, tc.expectedKeyOut, buf.String()) + + buf.Reset() + va := iterator.Value() + require.Equal(t, tc.expectedvalueOut, buf.String()) + + require.Equal(t, tc.expectedKey, ka) + require.Equal(t, tc.expectedValue, va) + + iterator.Next() + } + + require.False(t, iterator.Valid()) + require.Panics(t, iterator.Next) + require.NotPanics(t, iterator.Close) +} + +func TestTraceKVStorePrefix(t *testing.T) { + store := newEmptyTraceKVStore(nil) + pStore := store.Prefix([]byte("trace_prefix")) + require.IsType(t, prefixStore{}, pStore) +} + +func TestTraceKVStoreGetStoreType(t *testing.T) { + memDB := dbStoreAdapter{dbm.NewMemDB()} + store := newEmptyTraceKVStore(nil) + require.Equal(t, memDB.GetStoreType(), store.GetStoreType()) +} + +func TestTraceKVStoreCacheWrap(t *testing.T) { + store := newEmptyTraceKVStore(nil) + require.Panics(t, func() { store.CacheWrap() }) +} +func TestTraceKVStoreCacheWrapWithTrace(t *testing.T) { + store := newEmptyTraceKVStore(nil) + require.Panics(t, func() { store.CacheWrapWithTrace(nil, nil) }) +} diff --git a/store/transientstore_test.go b/store/transientstore_test.go new file mode 100644 index 000000000..1c9e98cfa --- /dev/null +++ b/store/transientstore_test.go @@ -0,0 +1,23 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var k, v = []byte("hello"), []byte("world") + +func TestTransientStore(t *testing.T) { + tstore := newTransientStore() + + require.Nil(t, tstore.Get(k)) + + tstore.Set(k, v) + + require.Equal(t, v, tstore.Get(k)) + + tstore.Commit() + + require.Nil(t, tstore.Get(k)) +} diff --git a/types/store.go b/types/store.go index 1f7cabe09..02a4edaf0 100644 --- a/types/store.go +++ b/types/store.go @@ -142,7 +142,7 @@ type KVStore interface { Iterator(start, end []byte) Iterator // Iterator over a domain of keys in descending order. End is exclusive. - // Start must be greater than end, or the Iterator is invalid. + // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. // CONTRACT: No writes may happen within a domain while an iterator exists over it. ReverseIterator(start, end []byte) Iterator From 20edf86adb1fa436fcfa08e187b0e1563b2bb88f Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 5 Dec 2018 17:56:03 +0800 Subject: [PATCH 2/3] Add slashing result into tag --- modules/slashing/keeper.go | 15 +++++++------ modules/slashing/tick.go | 6 ++++-- modules/stake/keeper/slash.go | 40 +++++++++++++++++++++++------------ types/stake.go | 2 +- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/modules/slashing/keeper.go b/modules/slashing/keeper.go index 55a8f2353..be31e7662 100644 --- a/modules/slashing/keeper.go +++ b/modules/slashing/keeper.go @@ -36,7 +36,7 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspa // handle a validator signing two blocks at the same height // power: power of the double-signing validator at the height of infraction -func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractionHeight int64, timestamp time.Time, power int64) { +func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractionHeight int64, timestamp time.Time, power int64) (tags sdk.Tags) { logger := ctx.Logger().With("module", "x/slashing") time := ctx.BlockHeader().Time age := time.Sub(timestamp) @@ -52,14 +52,14 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // Defensive. // Simulation doesn't take unbonding periods into account, and // Tendermint might break this assumption at some point. - return + return nil } // Double sign too old maxEvidenceAge := k.MaxEvidenceAge(ctx) if age > maxEvidenceAge { logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge)) - return + return nil } // Double sign confirmed @@ -83,7 +83,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // ABCI, and now received as evidence. // The revisedFraction (which is the new fraction to be slashed) is passed // in separately to separately slash unbonding and rebonding delegations. - k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, revisedFraction) + tags = k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, revisedFraction) // Jail validator if not already jailed if !validator.GetJailed() { @@ -97,11 +97,12 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio } signInfo.JailedUntil = time.Add(k.DoubleSignUnbondDuration(ctx)) k.setValidatorSigningInfo(ctx, consAddr, signInfo) + return } // handle a validator signature, must be called once per validator per block // TODO refactor to take in a consensus address, additionally should maybe just take in the pubkey too -func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) { +func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) (tags sdk.Tags) { logger := ctx.Logger().With("module", "x/slashing") height := ctx.BlockHeight() consAddr := sdk.ConsAddress(addr) @@ -153,7 +154,8 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. // That's fine since this is just used to filter unbonding delegations & redelegations. distributionHeight := height - stake.ValidatorUpdateDelay - 1 - k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) + slashTags := k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) + tags = tags.AppendTags(slashTags) k.validatorSet.Jail(ctx, consAddr) signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx)) // We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding. @@ -169,6 +171,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Set the updated signing info k.setValidatorSigningInfo(ctx, consAddr, signInfo) + return } func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { diff --git a/modules/slashing/tick.go b/modules/slashing/tick.go index d5a509a6d..ebca95bef 100644 --- a/modules/slashing/tick.go +++ b/modules/slashing/tick.go @@ -21,7 +21,8 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags // store whether or not they have actually signed it and slash/unbond any // which have missed too many blocks in a row (downtime slashing) for _, voteInfo := range req.LastCommitInfo.GetVotes() { - sk.handleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) + absenceSlashTags := sk.handleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) + tags = tags.AppendTags(absenceSlashTags) } // Iterate through any newly discovered evidence of infraction @@ -30,7 +31,8 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags for _, evidence := range req.ByzantineValidators { switch evidence.Type { case tmtypes.ABCIEvidenceTypeDuplicateVote: - sk.handleDoubleSign(ctx, evidence.Validator.Address, evidence.Height, evidence.Time, evidence.Validator.Power) + doubleSignSlashTag := sk.handleDoubleSign(ctx, evidence.Validator.Address, evidence.Height, evidence.Time, evidence.Validator.Power) + tags = tags.AppendTags(doubleSignSlashTag) default: ctx.Logger().With("module", "x/slashing").Error(fmt.Sprintf("ignored unknown evidence type: %s", evidence.Type)) } diff --git a/modules/stake/keeper/slash.go b/modules/stake/keeper/slash.go index f4535ec65..1b14f2989 100644 --- a/modules/stake/keeper/slash.go +++ b/modules/stake/keeper/slash.go @@ -7,6 +7,16 @@ import ( "github.com/irisnet/irishub/modules/stake/types" ) +const ( + // slash-unbondind-[delegatorAddr]-[validatorAddr] + SlashUnbondindDelegation = "slash-unbondind-%s-%s" + // slash-redelegation-[delegatorAddr]-[validatorAddrSrc]-[validatorAddrDst] + SlashRedelegation = "slash-redelegation-%s-%s-%s" + // slash-validator-[validatorAddr] + SlashValidator = "slash-validator-%s" + // slash-validator-redelegation-[validatorAddrDst]-[validatorAddrSrc]-[delegatorAddr] + SlashValidatorRedelegation = "slash-validator-redelegation-%s-%s-%s" +) // Slash a validator for an infraction committed at a known height // Find the contributing stake at that height and burn the specified slashFactor // of it, updating unbonding delegation & redelegations appropriately @@ -21,7 +31,7 @@ import ( // CONTRACT: // Infraction committed at the current height or at a past height, // not at a height in the future -func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) { +func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) (tags sdk.Tags) { logger := ctx.Logger().With("module", "x/stake") if slashFactor.LT(sdk.ZeroDec()) { @@ -81,7 +91,8 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh // Iterate through unbonding delegations from slashed validator unbondingDelegations := k.GetUnbondingDelegationsFromValidator(ctx, operatorAddress) for _, unbondingDelegation := range unbondingDelegations { - amountSlashed := k.slashUnbondingDelegation(ctx, unbondingDelegation, infractionHeight, slashFactor) + amountSlashed, slashUnbondingTags := k.slashUnbondingDelegation(ctx, unbondingDelegation, infractionHeight, slashFactor) + tags = tags.AppendTags(slashUnbondingTags) if amountSlashed.IsZero() { continue } @@ -91,7 +102,8 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh // Iterate through redelegations from slashed validator redelegations := k.GetRedelegationsFromValidator(ctx, operatorAddress) for _, redelegation := range redelegations { - amountSlashed := k.slashRedelegation(ctx, validator, redelegation, infractionHeight, slashFactor) + amountSlashed, slashRedelegationTags := k.slashRedelegation(ctx, validator, redelegation, infractionHeight, slashFactor) + tags = tags.AppendTags(slashRedelegationTags) if amountSlashed.IsZero() { continue } @@ -102,7 +114,7 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh // cannot decrease balance below zero tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens) tokensToBurn = sdk.MaxDec(tokensToBurn, sdk.ZeroDec()) // defensive. - + tags = tags.AppendTag(fmt.Sprintf(SlashValidator, validator.OperatorAddr),[]byte(tokensToBurn.String())) // Deduct from validator's bonded tokens and update the validator. // The deducted tokens are returned to pool.LooseTokens. validator = k.RemoveValidatorTokens(ctx, validator, tokensToBurn) @@ -146,19 +158,19 @@ func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { // (the amount actually slashed may be less if there's // insufficient stake remaining) func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation, - infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Dec) { + infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Dec, tags sdk.Tags) { now := ctx.BlockHeader().Time // If unbonding started before this height, stake didn't contribute to infraction if unbondingDelegation.CreationHeight < infractionHeight { - return sdk.ZeroDec() + return sdk.ZeroDec(), nil } if unbondingDelegation.MinTime.Before(now) { // Unbonding delegation no longer eligible for slashing, skip it // TODO Settle and delete it automatically? - return sdk.ZeroDec() + return sdk.ZeroDec(), nil } // Calculate slash amount proportional to stake contributing to infraction @@ -173,6 +185,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty // Update unbonding delegation if necessary if !unbondingSlashAmount.IsZero() { unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount) + tags = tags.AppendTag(fmt.Sprintf(SlashUnbondindDelegation, unbondingDelegation.DelegatorAddr, unbondingDelegation.ValidatorAddr),[]byte(unbondingSlashAmount.String())) k.SetUnbondingDelegation(ctx, unbondingDelegation) pool := k.GetPool(ctx) @@ -192,19 +205,19 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty // insufficient stake remaining) // nolint: unparam func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, redelegation types.Redelegation, - infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Dec) { + infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Dec, tags sdk.Tags) { now := ctx.BlockHeader().Time // If redelegation started before this height, stake didn't contribute to infraction if redelegation.CreationHeight < infractionHeight { - return sdk.ZeroDec() + return sdk.ZeroDec(), nil } if redelegation.MinTime.Before(now) { // Redelegation no longer eligible for slashing, skip it // TODO Delete it automatically? - return sdk.ZeroDec() + return sdk.ZeroDec(), nil } // Calculate slash amount proportional to stake contributing to infraction @@ -220,6 +233,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re if !redelegationSlashAmount.IsZero() { redelegation.Balance.Amount = redelegation.Balance.Amount.Sub(redelegationSlashAmount) k.SetRedelegation(ctx, redelegation) + tags = tags.AppendTag(fmt.Sprintf(SlashRedelegation, redelegation.DelegatorAddr, redelegation.ValidatorSrcAddr, redelegation.ValidatorDstAddr), []byte(redelegationSlashAmount.String())) } // Unbond from target validator @@ -228,7 +242,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re delegation, found := k.GetDelegation(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr) if !found { // If deleted, delegation has zero shares, and we can't unbond any more - return slashAmount + return slashAmount, nil } if sharesToUnbond.GT(delegation.Shares) { sharesToUnbond = delegation.Shares @@ -238,12 +252,12 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re if err != nil { panic(fmt.Errorf("error unbonding delegator: %v", err)) } - + tags = tags.AppendTag(fmt.Sprintf(SlashValidatorRedelegation, redelegation.ValidatorDstAddr, redelegation.ValidatorSrcAddr, redelegation.DelegatorAddr), []byte(tokensToBurn.String())) // Burn loose tokens pool := k.GetPool(ctx) pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) k.SetPool(ctx, pool) } - return slashAmount + return } diff --git a/types/stake.go b/types/stake.go index 38f1c0c9b..b51725fd4 100644 --- a/types/stake.go +++ b/types/stake.go @@ -76,7 +76,7 @@ type ValidatorSet interface { TotalPower(Context) Dec // total power of the validator set // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction - Slash(Context, ConsAddress, int64, int64, Dec) + Slash(Context, ConsAddress, int64, int64, Dec) Tags Jail(Context, ConsAddress) // jail a validator Unjail(Context, ConsAddress) // unjail a validator From b7ec95a30013b0e7752f8e523323707ec203a215 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 5 Dec 2018 18:05:32 +0800 Subject: [PATCH 3/3] Resolve comment --- modules/slashing/keeper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/slashing/keeper.go b/modules/slashing/keeper.go index be31e7662..77e7ac762 100644 --- a/modules/slashing/keeper.go +++ b/modules/slashing/keeper.go @@ -52,14 +52,14 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // Defensive. // Simulation doesn't take unbonding periods into account, and // Tendermint might break this assumption at some point. - return nil + return } // Double sign too old maxEvidenceAge := k.MaxEvidenceAge(ctx) if age > maxEvidenceAge { logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge)) - return nil + return } // Double sign confirmed