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  }