github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/pebble/lookup.go (about) 1 package pebble 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 8 "github.com/onflow/flow-go/model/flow" 9 "github.com/onflow/flow-go/storage/pebble/registers" 10 ) 11 12 // latestHeightKey is a special case of a lookupKey 13 // with keyLatestBlockHeight as key, no owner and a placeholder height of 0. 14 // This is to ensure SeekPrefixGE in pebble does not break 15 var latestHeightKey = binary.BigEndian.AppendUint64( 16 []byte{codeLatestBlockHeight, byte('/'), byte('/')}, placeHolderHeight) 17 18 // firstHeightKey is a special case of a lookupKey 19 // with keyFirstBlockHeight as key, no owner and a placeholder height of 0. 20 // This is to ensure SeekPrefixGE in pebble does not break 21 var firstHeightKey = binary.BigEndian.AppendUint64( 22 []byte{codeFirstBlockHeight, byte('/'), byte('/')}, placeHolderHeight) 23 24 // lookupKey is the encoded format of the storage key for looking up register value 25 type lookupKey struct { 26 encoded []byte 27 } 28 29 // newLookupKey takes a height and registerID, returns the key for storing the register value in storage 30 func newLookupKey(height uint64, reg flow.RegisterID) *lookupKey { 31 key := lookupKey{ 32 // 1 byte gaps for db prefix and '/' separators 33 encoded: make([]byte, 0, MinLookupKeyLen+len(reg.Owner)+len(reg.Key)), 34 } 35 36 // append DB prefix 37 key.encoded = append(key.encoded, codeRegister) 38 39 // The lookup key used to find most recent value for a register. 40 // 41 // The "<owner>/<key>" part is the register key, which is used as a prefix to filter and iterate 42 // through updated values at different heights, and find the most recent updated value at or below 43 // a certain height. 44 key.encoded = append(key.encoded, []byte(reg.Owner)...) 45 key.encoded = append(key.encoded, '/') 46 key.encoded = append(key.encoded, []byte(reg.Key)...) 47 key.encoded = append(key.encoded, '/') 48 49 // Encode the height getting it to 1s compliment (all bits flipped) and big-endian byte order. 50 // 51 // Registers are a sparse dataset stored with a single entry per update. To find the value at a particular 52 // height, we need to do a scan across the entries to find the highest height that is less than or equal 53 // to the target height. 54 // 55 // Pebble does not support reverse iteration, so we use the height's one's complement to effectively 56 // reverse sort on the height. This allows us to use a bitwise forward scan for the next most recent 57 // entry. 58 onesCompliment := ^height 59 key.encoded = binary.BigEndian.AppendUint64(key.encoded, onesCompliment) 60 61 return &key 62 } 63 64 // lookupKeyToRegisterID takes a lookup key and decode it into height and RegisterID 65 func lookupKeyToRegisterID(lookupKey []byte) (uint64, flow.RegisterID, error) { 66 if len(lookupKey) < MinLookupKeyLen { 67 return 0, flow.RegisterID{}, fmt.Errorf("invalid lookup key format: expected >= %d bytes, got %d bytes", 68 MinLookupKeyLen, len(lookupKey)) 69 } 70 71 // check and exclude db prefix 72 prefix := lookupKey[0] 73 if prefix != codeRegister { 74 return 0, flow.RegisterID{}, fmt.Errorf("incorrect prefix %d for register lookup key, expected %d", 75 prefix, codeRegister) 76 } 77 lookupKey = lookupKey[1:] 78 79 // Find the first slash to split the lookup key and decode the owner. 80 firstSlash := bytes.IndexByte(lookupKey, '/') 81 if firstSlash == -1 { 82 return 0, flow.RegisterID{}, fmt.Errorf("invalid lookup key format: cannot find first slash") 83 } 84 85 owner := string(lookupKey[:firstSlash]) 86 87 // Find the last slash to split encoded height. 88 lastSlashPos := bytes.LastIndexByte(lookupKey, '/') 89 if lastSlashPos == firstSlash { 90 return 0, flow.RegisterID{}, fmt.Errorf("invalid lookup key format: expected 2 separators, got 1 separator") 91 } 92 encodedHeightPos := lastSlashPos + 1 93 if len(lookupKey)-encodedHeightPos != registers.HeightSuffixLen { 94 return 0, flow.RegisterID{}, 95 fmt.Errorf("invalid lookup key format: expected %d bytes of encoded height, got %d bytes", 96 registers.HeightSuffixLen, len(lookupKey)-encodedHeightPos) 97 } 98 99 // Decode height. 100 heightBytes := lookupKey[encodedHeightPos:] 101 102 oneCompliment := binary.BigEndian.Uint64(heightBytes) 103 height := ^oneCompliment 104 105 // Decode the remaining bytes into the key. 106 keyBytes := lookupKey[firstSlash+1 : lastSlashPos] 107 key := string(keyBytes) 108 109 regID := flow.RegisterID{Owner: owner, Key: key} 110 111 return height, regID, nil 112 } 113 114 // Bytes returns the encoded lookup key. 115 func (h lookupKey) Bytes() []byte { 116 return h.encoded 117 } 118 119 // String returns the encoded lookup key as a string. 120 func (h lookupKey) String() string { 121 return string(h.encoded) 122 } 123 124 // encodedUint64 encodes uint64 for storing as a pebble payload 125 func encodedUint64(height uint64) []byte { 126 payload := make([]byte, 0, 8) 127 return binary.BigEndian.AppendUint64(payload, height) 128 }