From 9b9222f46118a5c19b31aa0d13bcbd5c07fe6984 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 5 Jan 2021 16:53:26 +0100 Subject: [PATCH] store: order-preserving varint key encoding (#5771) --- CHANGELOG_PENDING.md | 3 ++ evidence/pool.go | 44 +++++++++------ go.mod | 1 + go.sum | 2 + light/store/db/db.go | 109 +++++++++++++++++++------------------- light/store/db/db_test.go | 3 ++ state/store.go | 51 ++++++++++++------ store/store.go | 85 +++++++++++++++++++---------- store/store_test.go | 20 ++++--- 9 files changed, 193 insertions(+), 125 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 7788c27a2..7267e6a4b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -38,6 +38,9 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - Blockchain Protocol +- Data Storage + - [store/state/evidence/light] \#5771 Use an order-preserving varint key encoding (@cmwaters) + ### FEATURES ### IMPROVEMENTS diff --git a/evidence/pool.go b/evidence/pool.go index 77dbf1a39..15404114e 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -11,6 +11,7 @@ import ( "github.com/gogo/protobuf/proto" gogotypes "github.com/gogo/protobuf/types" + "github.com/google/orderedcode" dbm "github.com/tendermint/tm-db" clist "github.com/tendermint/tendermint/libs/clist" @@ -21,8 +22,9 @@ import ( ) const ( - baseKeyCommitted = byte(0x00) - baseKeyPending = byte(0x01) + // prefixes are unique across all tm db's + prefixCommitted = int64(8) + prefixPending = int64(9) ) // Pool maintains a pool of valid evidence to be broadcasted and committed @@ -67,7 +69,7 @@ func NewPool(evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore) (*Pool, // if pending evidence already in db, in event of prior failure, then check for expiration, // update the size and load it back to the evidenceList pool.pruningHeight, pool.pruningTime = pool.removeExpiredPendingEvidence() - evList, _, err := pool.listEvidence(baseKeyPending, -1) + evList, _, err := pool.listEvidence(prefixPending, -1) if err != nil { return nil, err } @@ -84,7 +86,7 @@ func (evpool *Pool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64) { if evpool.Size() == 0 { return []types.Evidence{}, 0 } - evidence, size, err := evpool.listEvidence(baseKeyPending, maxBytes) + evidence, size, err := evpool.listEvidence(prefixPending, maxBytes) if err != nil { evpool.logger.Error("Unable to retrieve pending evidence", "err", err) } @@ -402,7 +404,7 @@ func (evpool *Pool) markEvidenceAsCommitted(evidence types.EvidenceList) { // listEvidence retrieves lists evidence from oldest to newest within maxBytes. // If maxBytes is -1, there's no cap on the size of returned evidence. -func (evpool *Pool) listEvidence(prefixKey byte, maxBytes int64) ([]types.Evidence, int64, error) { +func (evpool *Pool) listEvidence(prefixKey int64, maxBytes int64) ([]types.Evidence, int64, error) { var ( evSize int64 totalSize int64 @@ -410,11 +412,12 @@ func (evpool *Pool) listEvidence(prefixKey byte, maxBytes int64) ([]types.Eviden evList tmproto.EvidenceList // used for calculating the bytes size ) - iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{prefixKey}) + iter, err := dbm.IteratePrefix(evpool.evidenceStore, prefixToBytes(prefixKey)) if err != nil { return nil, totalSize, fmt.Errorf("database error: %v", err) } defer iter.Close() + for ; iter.Valid(); iter.Next() { var evpb tmproto.Evidence err := evpb.Unmarshal(iter.Value()) @@ -446,7 +449,7 @@ func (evpool *Pool) listEvidence(prefixKey byte, maxBytes int64) ([]types.Eviden } func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) { - iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{baseKeyPending}) + iter, err := dbm.IteratePrefix(evpool.evidenceStore, prefixToBytes(prefixPending)) if err != nil { evpool.logger.Error("Unable to iterate over pending evidence", "err", err) return evpool.State().LastBlockHeight, evpool.State().LastBlockTime @@ -511,19 +514,28 @@ func evMapKey(ev types.Evidence) string { return string(ev.Hash()) } -// big endian padded hex -func bE(h int64) string { - return fmt.Sprintf("%0.16X", h) +func prefixToBytes(prefix int64) []byte { + key, err := orderedcode.Append(nil, prefix) + if err != nil { + panic(err) + } + return key } func keyCommitted(evidence types.Evidence) []byte { - return append([]byte{baseKeyCommitted}, keySuffix(evidence)...) + var height int64 = evidence.Height() + key, err := orderedcode.Append(nil, prefixCommitted, height, string(evidence.Hash())) + if err != nil { + panic(err) + } + return key } func keyPending(evidence types.Evidence) []byte { - return append([]byte{baseKeyPending}, keySuffix(evidence)...) -} - -func keySuffix(evidence types.Evidence) []byte { - return []byte(fmt.Sprintf("%s/%X", bE(evidence.Height()), evidence.Hash())) + var height int64 = evidence.Height() + key, err := orderedcode.Append(nil, prefixPending, height, string(evidence.Hash())) + if err != nil { + panic(err) + } + return key } diff --git a/go.mod b/go.mod index 1ef81e621..08fc9efc4 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-logfmt/logfmt v0.5.0 github.com/gogo/protobuf v1.3.1 github.com/golang/protobuf v1.4.3 + github.com/google/orderedcode v0.0.1 github.com/gorilla/websocket v1.4.2 github.com/gtank/merlin v0.1.1 github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff diff --git a/go.sum b/go.sum index 4b34f8e26..d110a35e2 100644 --- a/go.sum +++ b/go.sum @@ -221,6 +221,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= diff --git a/light/store/db/db.go b/light/store/db/db.go index db87608e7..25991f476 100644 --- a/light/store/db/db.go +++ b/light/store/db/db.go @@ -3,9 +3,8 @@ package db import ( "encoding/binary" "fmt" - "regexp" - "strconv" + "github.com/google/orderedcode" dbm "github.com/tendermint/tm-db" tmsync "github.com/tendermint/tendermint/libs/sync" @@ -14,8 +13,9 @@ import ( "github.com/tendermint/tendermint/types" ) -var ( - sizeKey = []byte("size") +const ( + prefixLightBlock = int64(0x0a) + prefixSize = int64(0x0b) ) type dbs struct { @@ -30,13 +30,17 @@ type dbs struct { // want to use one DB with many light clients). func New(db dbm.DB, prefix string) store.Store { + lightStore := &dbs{db: db, prefix: prefix} + + // retrieve the size of the db size := uint16(0) - bz, err := db.Get(sizeKey) + bz, err := lightStore.db.Get(lightStore.sizeKey()) if err == nil && len(bz) > 0 { size = unmarshalSize(bz) } + lightStore.size = size - return &dbs{db: db, prefix: prefix, size: size} + return lightStore } // SaveLightBlock persists LightBlock to the db. @@ -65,7 +69,7 @@ func (s *dbs) SaveLightBlock(lb *types.LightBlock) error { if err = b.Set(s.lbKey(lb.Height), lbBz); err != nil { return err } - if err = b.Set(sizeKey, marshalSize(s.size+1)); err != nil { + if err = b.Set(s.sizeKey(), marshalSize(s.size+1)); err != nil { return err } if err = b.WriteSync(); err != nil { @@ -93,7 +97,7 @@ func (s *dbs) DeleteLightBlock(height int64) error { if err := b.Delete(s.lbKey(height)); err != nil { return err } - if err := b.Set(sizeKey, marshalSize(s.size-1)); err != nil { + if err := b.Set(s.sizeKey(), marshalSize(s.size-1)); err != nil { return err } if err := b.WriteSync(); err != nil { @@ -147,13 +151,8 @@ func (s *dbs) LastLightBlockHeight() (int64, error) { } defer itr.Close() - for itr.Valid() { - key := itr.Key() - _, height, ok := parseLbKey(key) - if ok { - return height, nil - } - itr.Next() + if itr.Valid() { + return s.decodeLbKey(itr.Key()) } return -1, itr.Error() @@ -172,13 +171,8 @@ func (s *dbs) FirstLightBlockHeight() (int64, error) { } defer itr.Close() - for itr.Valid() { - key := itr.Key() - _, height, ok := parseLbKey(key) - if ok { - return height, nil - } - itr.Next() + if itr.Valid() { + return s.decodeLbKey(itr.Key()) } return -1, itr.Error() @@ -202,13 +196,12 @@ func (s *dbs) LightBlockBefore(height int64) (*types.LightBlock, error) { } defer itr.Close() - for itr.Valid() { - key := itr.Key() - _, existingHeight, ok := parseLbKey(key) - if ok { - return s.LightBlock(existingHeight) + if itr.Valid() { + existingHeight, err := s.decodeLbKey(itr.Key()) + if err != nil { + return nil, err } - itr.Next() + return s.LightBlock(existingHeight) } if err = itr.Error(); err != nil { return nil, err @@ -248,11 +241,12 @@ func (s *dbs) Prune(size uint16) error { pruned := 0 for itr.Valid() && numToPrune > 0 { key := itr.Key() - _, height, ok := parseLbKey(key) - if ok { - if err = b.Delete(s.lbKey(height)); err != nil { - return err - } + height, err := s.decodeLbKey(key) + if err != nil { + return err + } + if err = b.Delete(s.lbKey(height)); err != nil { + return err } itr.Next() numToPrune-- @@ -273,7 +267,7 @@ func (s *dbs) Prune(size uint16) error { s.size -= uint16(pruned) - if wErr := s.db.SetSync(sizeKey, marshalSize(s.size)); wErr != nil { + if wErr := s.db.SetSync(s.sizeKey(), marshalSize(size)); wErr != nil { return fmt.Errorf("failed to persist size: %w", wErr) } @@ -289,32 +283,39 @@ func (s *dbs) Size() uint16 { return s.size } -func (s *dbs) lbKey(height int64) []byte { - return []byte(fmt.Sprintf("lb/%s/%020d", s.prefix, height)) +func (s *dbs) sizeKey() []byte { + key, err := orderedcode.Append(nil, s.prefix, prefixSize) + if err != nil { + panic(err) + } + return key } -var keyPattern = regexp.MustCompile(`^(lb)/([^/]*)/([0-9]+)$`) - -func parseKey(key []byte) (part string, prefix string, height int64, ok bool) { - submatch := keyPattern.FindSubmatch(key) - if submatch == nil { - return "", "", 0, false - } - part = string(submatch[1]) - prefix = string(submatch[2]) - height, err := strconv.ParseInt(string(submatch[3]), 10, 64) +func (s *dbs) lbKey(height int64) []byte { + key, err := orderedcode.Append(nil, s.prefix, prefixLightBlock, height) if err != nil { - return "", "", 0, false + panic(err) } - ok = true // good! - return + return key } -func parseLbKey(key []byte) (prefix string, height int64, ok bool) { - var part string - part, prefix, height, ok = parseKey(key) - if part != "lb" { - return "", 0, false +func (s *dbs) decodeLbKey(key []byte) (height int64, err error) { + var ( + dbPrefix string + lightBlockPrefix int64 + ) + remaining, err := orderedcode.Parse(string(key), &dbPrefix, &lightBlockPrefix, &height) + if err != nil { + err = fmt.Errorf("failed to parse light block key: %w", err) + } + if len(remaining) != 0 { + err = fmt.Errorf("expected no remainder when parsing light block key but got: %s", remaining) + } + if lightBlockPrefix != prefixLightBlock { + err = fmt.Errorf("expected light block prefix but got: %d", lightBlockPrefix) + } + if dbPrefix != s.prefix { + err = fmt.Errorf("parsed key has a different prefix. Expected: %s, got: %s", s.prefix, dbPrefix) } return } diff --git a/light/store/db/db_test.go b/light/store/db/db_test.go index ef9710694..1912baca3 100644 --- a/light/store/db/db_test.go +++ b/light/store/db/db_test.go @@ -89,6 +89,9 @@ func Test_LightBlockBefore(t *testing.T) { if assert.NotNil(t, h) { assert.EqualValues(t, 2, h.Height) } + + _, err = dbStore.LightBlockBefore(2) + require.Error(t, err) } func Test_Prune(t *testing.T) { diff --git a/state/store.go b/state/store.go index 5bd2d9fc7..4868057ed 100644 --- a/state/store.go +++ b/state/store.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/gogo/protobuf/proto" + "github.com/google/orderedcode" dbm "github.com/tendermint/tm-db" abci "github.com/tendermint/tendermint/abci/types" @@ -25,16 +26,31 @@ const ( //------------------------------------------------------------------------ -func calcValidatorsKey(height int64) []byte { - return []byte(fmt.Sprintf("validatorsKey:%v", height)) +const ( + // prefixes are unique across all tm db's + prefixValidators = int64(5) + prefixConsensusParams = int64(6) + prefixABCIResponses = int64(7) +) + +func encodeKey(prefix int64, height int64) []byte { + res, err := orderedcode.Append(nil, prefix, height) + if err != nil { + panic(err) + } + return res +} + +func validatorsKey(height int64) []byte { + return encodeKey(prefixValidators, height) } -func calcConsensusParamsKey(height int64) []byte { - return []byte(fmt.Sprintf("consensusParamsKey:%v", height)) +func consensusParamsKey(height int64) []byte { + return encodeKey(prefixConsensusParams, height) } -func calcABCIResponsesKey(height int64) []byte { - return []byte(fmt.Sprintf("abciResponsesKey:%v", height)) +func abciResponsesKey(height int64) []byte { + return encodeKey(prefixABCIResponses, height) } //---------------------- @@ -276,13 +292,13 @@ func (store dbStore) PruneStates(from int64, to int64) error { if err != nil { return err } - err = batch.Set(calcValidatorsKey(h), bz) + err = batch.Set(validatorsKey(h), bz) if err != nil { return err } } } else { - err = batch.Delete(calcValidatorsKey(h)) + err = batch.Delete(validatorsKey(h)) if err != nil { return err } @@ -306,19 +322,19 @@ func (store dbStore) PruneStates(from int64, to int64) error { return err } - err = batch.Set(calcConsensusParamsKey(h), bz) + err = batch.Set(consensusParamsKey(h), bz) if err != nil { return err } } } else { - err = batch.Delete(calcConsensusParamsKey(h)) + err = batch.Delete(consensusParamsKey(h)) if err != nil { return err } } - err = batch.Delete(calcABCIResponsesKey(h)) + err = batch.Delete(abciResponsesKey(h)) if err != nil { return err } @@ -361,7 +377,7 @@ func ABCIResponsesResultsHash(ar *tmstate.ABCIResponses) []byte { // before we called s.Save(). It can also be used to produce Merkle proofs of // the result of txs. func (store dbStore) LoadABCIResponses(height int64) (*tmstate.ABCIResponses, error) { - buf, err := store.db.Get(calcABCIResponsesKey(height)) + buf, err := store.db.Get(abciResponsesKey(height)) if err != nil { return nil, err } @@ -403,7 +419,7 @@ func (store dbStore) SaveABCIResponses(height int64, abciResponses *tmstate.ABCI return err } - err = store.db.SetSync(calcABCIResponsesKey(height), bz) + err = store.db.SetSync(abciResponsesKey(height), bz) if err != nil { return err } @@ -416,6 +432,7 @@ func (store dbStore) SaveABCIResponses(height int64, abciResponses *tmstate.ABCI // LoadValidators loads the ValidatorSet for a given height. // Returns ErrNoValSetForHeight if the validator set can't be found for this height. func (store dbStore) LoadValidators(height int64) (*types.ValidatorSet, error) { + valInfo, err := loadValidatorsInfo(store.db, height) if err != nil { return nil, ErrNoValSetForHeight{height} @@ -462,7 +479,7 @@ func lastStoredHeightFor(height, lastHeightChanged int64) int64 { // CONTRACT: Returned ValidatorsInfo can be mutated. func loadValidatorsInfo(db dbm.DB, height int64) (*tmstate.ValidatorsInfo, error) { - buf, err := db.Get(calcValidatorsKey(height)) + buf, err := db.Get(validatorsKey(height)) if err != nil { return nil, err } @@ -510,7 +527,7 @@ func (store dbStore) saveValidatorsInfo(height, lastHeightChanged int64, valSet return err } - err = store.db.Set(calcValidatorsKey(height), bz) + err = store.db.Set(validatorsKey(height), bz) if err != nil { return err } @@ -549,7 +566,7 @@ func (store dbStore) LoadConsensusParams(height int64) (tmproto.ConsensusParams, } func (store dbStore) loadConsensusParamsInfo(height int64) (*tmstate.ConsensusParamsInfo, error) { - buf, err := store.db.Get(calcConsensusParamsKey(height)) + buf, err := store.db.Get(consensusParamsKey(height)) if err != nil { return nil, err } @@ -585,7 +602,7 @@ func (store dbStore) saveConsensusParamsInfo(nextHeight, changeHeight int64, par return err } - err = store.db.Set(calcConsensusParamsKey(nextHeight), bz) + err = store.db.Set(consensusParamsKey(nextHeight), bz) if err != nil { return err } diff --git a/store/store.go b/store/store.go index 9ae4d555d..e19d2ecc8 100644 --- a/store/store.go +++ b/store/store.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/gogo/protobuf/proto" + "github.com/google/orderedcode" dbm "github.com/tendermint/tm-db" tmsync "github.com/tendermint/tendermint/libs/sync" @@ -126,7 +127,7 @@ func (bs *BlockStore) LoadBlock(height int64) *types.Block { // If no block is found for that hash, it returns nil. // Panics if it fails to parse height associated with the given hash. func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block { - bz, err := bs.db.Get(calcBlockHashKey(hash)) + bz, err := bs.db.Get(blockHashKey(hash)) if err != nil { panic(err) } @@ -149,7 +150,7 @@ func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block { func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part { var pbpart = new(tmproto.Part) - bz, err := bs.db.Get(calcBlockPartKey(height, index)) + bz, err := bs.db.Get(blockPartKey(height, index)) if err != nil { panic(err) } @@ -173,7 +174,7 @@ func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part { // If no block is found for the given height, it returns nil. func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { var pbbm = new(tmproto.BlockMeta) - bz, err := bs.db.Get(calcBlockMetaKey(height)) + bz, err := bs.db.Get(blockMetaKey(height)) if err != nil { panic(err) @@ -202,7 +203,7 @@ func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { // If no commit is found for the given height, it returns nil. func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit { var pbc = new(tmproto.Commit) - bz, err := bs.db.Get(calcBlockCommitKey(height)) + bz, err := bs.db.Get(blockCommitKey(height)) if err != nil { panic(err) } @@ -225,7 +226,7 @@ func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit { // a new block at `height + 1` that includes this commit in its block.LastCommit. func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit { var pbc = new(tmproto.Commit) - bz, err := bs.db.Get(calcSeenCommitKey(height)) + bz, err := bs.db.Get(seenCommitKey(height)) if err != nil { panic(err) } @@ -285,20 +286,20 @@ func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) { if meta == nil { // assume already deleted continue } - if err := batch.Delete(calcBlockMetaKey(h)); err != nil { + if err := batch.Delete(blockMetaKey(h)); err != nil { return 0, err } - if err := batch.Delete(calcBlockHashKey(meta.BlockID.Hash)); err != nil { + if err := batch.Delete(blockHashKey(meta.BlockID.Hash)); err != nil { return 0, err } - if err := batch.Delete(calcBlockCommitKey(h)); err != nil { + if err := batch.Delete(blockCommitKey(h)); err != nil { return 0, err } - if err := batch.Delete(calcSeenCommitKey(h)); err != nil { + if err := batch.Delete(seenCommitKey(h)); err != nil { return 0, err } for p := 0; p < int(meta.BlockID.PartSetHeader.Total); p++ { - if err := batch.Delete(calcBlockPartKey(h, p)); err != nil { + if err := batch.Delete(blockPartKey(h, p)); err != nil { return 0, err } } @@ -359,17 +360,17 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s panic("nil blockmeta") } metaBytes := mustEncode(pbm) - if err := bs.db.Set(calcBlockMetaKey(height), metaBytes); err != nil { + if err := bs.db.Set(blockMetaKey(height), metaBytes); err != nil { panic(err) } - if err := bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil { + if err := bs.db.Set(blockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil { panic(err) } // Save block commit (duplicate and separate from the Block) pbc := block.LastCommit.ToProto() blockCommitBytes := mustEncode(pbc) - if err := bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes); err != nil { + if err := bs.db.Set(blockCommitKey(height-1), blockCommitBytes); err != nil { panic(err) } @@ -377,7 +378,7 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s // NOTE: we can delete this at a later height pbsc := seenCommit.ToProto() seenCommitBytes := mustEncode(pbsc) - if err := bs.db.Set(calcSeenCommitKey(height), seenCommitBytes); err != nil { + if err := bs.db.Set(seenCommitKey(height), seenCommitBytes); err != nil { panic(err) } @@ -399,7 +400,7 @@ func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) { panic(fmt.Errorf("unable to make part into proto: %w", err)) } partBytes := mustEncode(pbp) - if err := bs.db.Set(calcBlockPartKey(height, index), partBytes); err != nil { + if err := bs.db.Set(blockPartKey(height, index), partBytes); err != nil { panic(err) } } @@ -421,29 +422,59 @@ func (bs *BlockStore) SaveSeenCommit(height int64, seenCommit *types.Commit) err if err != nil { return fmt.Errorf("unable to marshal commit: %w", err) } - return bs.db.Set(calcSeenCommitKey(height), seenCommitBytes) + return bs.db.Set(seenCommitKey(height), seenCommitBytes) } -//----------------------------------------------------------------------------- +//---------------------------------- KEY ENCODING ----------------------------------------- + +// key prefixes +const ( + // prefixes are unique across all tm db's + prefixBlockMeta = int64(0) + prefixBlockPart = int64(1) + prefixBlockCommit = int64(2) + prefixSeenCommit = int64(3) + prefixBlockHash = int64(4) +) -func calcBlockMetaKey(height int64) []byte { - return []byte(fmt.Sprintf("H:%v", height)) +func blockMetaKey(height int64) []byte { + key, err := orderedcode.Append(nil, prefixBlockMeta, height) + if err != nil { + panic(err) + } + return key } -func calcBlockPartKey(height int64, partIndex int) []byte { - return []byte(fmt.Sprintf("P:%v:%v", height, partIndex)) +func blockPartKey(height int64, partIndex int) []byte { + key, err := orderedcode.Append(nil, prefixBlockPart, height, int64(partIndex)) + if err != nil { + panic(err) + } + return key } -func calcBlockCommitKey(height int64) []byte { - return []byte(fmt.Sprintf("C:%v", height)) +func blockCommitKey(height int64) []byte { + key, err := orderedcode.Append(nil, prefixBlockCommit, height) + if err != nil { + panic(err) + } + return key } -func calcSeenCommitKey(height int64) []byte { - return []byte(fmt.Sprintf("SC:%v", height)) +func seenCommitKey(height int64) []byte { + key, err := orderedcode.Append(nil, prefixSeenCommit, height) + if err != nil { + panic(err) + } + return key } -func calcBlockHashKey(hash []byte) []byte { - return []byte(fmt.Sprintf("BH:%x", hash)) +func blockHashKey(hash []byte) []byte { + key, err := orderedcode.Append(nil, prefixBlockHash, string(hash)) + if err != nil { + panic(err) + } + return key } //----------------------------------------------------------------------------- diff --git a/store/store_test.go b/store/store_test.go index ea07c73e6..be87cad67 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -156,7 +156,6 @@ func TestMain(m *testing.M) { } // TODO: This test should be simplified ... - func TestBlockStoreSaveLoadBlock(t *testing.T) { state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) defer cleanup() @@ -193,7 +192,6 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } // End of setup, test data - commitAtH10 := makeTestCommit(10, tmtime.Now()) tuples := []struct { block *types.Block @@ -302,29 +300,29 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } if tuple.corruptBlockInDB { - err := db.Set(calcBlockMetaKey(tuple.block.Height), []byte("block-bogus")) + err := db.Set(blockMetaKey(tuple.block.Height), []byte("block-bogus")) require.NoError(t, err) } bBlock := bs.LoadBlock(tuple.block.Height) bBlockMeta := bs.LoadBlockMeta(tuple.block.Height) if tuple.eraseSeenCommitInDB { - err := db.Delete(calcSeenCommitKey(tuple.block.Height)) + err := db.Delete(seenCommitKey(tuple.block.Height)) require.NoError(t, err) } if tuple.corruptSeenCommitInDB { - err := db.Set(calcSeenCommitKey(tuple.block.Height), []byte("bogus-seen-commit")) + err := db.Set(seenCommitKey(tuple.block.Height), []byte("bogus-seen-commit")) require.NoError(t, err) } bSeenCommit := bs.LoadSeenCommit(tuple.block.Height) commitHeight := tuple.block.Height - 1 if tuple.eraseCommitInDB { - err := db.Delete(calcBlockCommitKey(commitHeight)) + err := db.Delete(blockCommitKey(commitHeight)) require.NoError(t, err) } if tuple.corruptCommitInDB { - err := db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus")) + err := db.Set(blockCommitKey(commitHeight), []byte("foo-bogus")) require.NoError(t, err) } bCommit := bs.LoadBlockCommit(commitHeight) @@ -404,7 +402,7 @@ func TestLoadBlockPart(t *testing.T) { require.Nil(t, res, "a non-existent block part should return nil") // 2. Next save a corrupted block then try to load it - err := db.Set(calcBlockPartKey(height, index), []byte("Tendermint")) + err := db.Set(blockPartKey(height, index), []byte("Tendermint")) require.NoError(t, err) res, _, panicErr = doFn(loadPart) require.NotNil(t, panicErr, "expecting a non-nil panic") @@ -413,7 +411,7 @@ func TestLoadBlockPart(t *testing.T) { // 3. A good block serialized and saved to the DB should be retrievable pb1, err := part1.ToProto() require.NoError(t, err) - err = db.Set(calcBlockPartKey(height, index), mustEncode(pb1)) + err = db.Set(blockPartKey(height, index), mustEncode(pb1)) require.NoError(t, err) gotPart, _, panicErr := doFn(loadPart) require.Nil(t, panicErr, "an existent and proper block should not panic") @@ -524,7 +522,7 @@ func TestLoadBlockMeta(t *testing.T) { require.Nil(t, res, "a non-existent blockMeta should return nil") // 2. Next save a corrupted blockMeta then try to load it - err := db.Set(calcBlockMetaKey(height), []byte("Tendermint-Meta")) + err := db.Set(blockMetaKey(height), []byte("Tendermint-Meta")) require.NoError(t, err) res, _, panicErr = doFn(loadMeta) require.NotNil(t, panicErr, "expecting a non-nil panic") @@ -535,7 +533,7 @@ func TestLoadBlockMeta(t *testing.T) { Version: tmversion.Consensus{ Block: version.BlockProtocol, App: 0}, Height: 1, ProposerAddress: tmrand.Bytes(crypto.AddressSize)}} pbm := meta.ToProto() - err = db.Set(calcBlockMetaKey(height), mustEncode(pbm)) + err = db.Set(blockMetaKey(height), mustEncode(pbm)) require.NoError(t, err) gotMeta, _, panicErr := doFn(loadMeta) require.Nil(t, panicErr, "an existent and proper block should not panic")