github.com/MetalBlockchain/metalgo@v1.11.9/x/archivedb/key.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package archivedb
     5  
     6  import (
     7  	"encoding/binary"
     8  	"errors"
     9  
    10  	"github.com/MetalBlockchain/metalgo/utils/wrappers"
    11  )
    12  
    13  var (
    14  	ErrParsingKeyLength   = errors.New("failed reading key length")
    15  	ErrIncorrectKeyLength = errors.New("incorrect key length")
    16  
    17  	heightKey = newDBKeyFromMetadata([]byte{})
    18  )
    19  
    20  // The requirements of a database key are:
    21  //
    22  // 1. A given user key must have a unique database key prefix. This guarantees
    23  // that user keys can not overlap on disk.
    24  // 2. Inside of a database key prefix, the database keys must be sorted by
    25  // decreasing height.
    26  // 3. User keys must never overlap with any metadata keys.
    27  
    28  // newDBKeyFromUser converts a user key and height into a database formatted
    29  // key.
    30  //
    31  // To meet the requirements of a database key, the prefix is defined by
    32  // concatenating the length of the user key and the user key. The suffix of the
    33  // database key is the negation of the big endian encoded height. This suffix
    34  // guarantees the keys are sorted correctly.
    35  //
    36  //	Example (Asumming heights are 1 byte):
    37  //	 |  User key  |  Stored as  |
    38  //	 |------------|-------------|
    39  //	 |   foo:10   |  3:foo:245  |
    40  //	 |   foo:20   |  3:foo:235  |
    41  //
    42  // Returns:
    43  // - The database key
    44  // - The database key prefix, which is independent of the height
    45  func newDBKeyFromUser(key []byte, height uint64) ([]byte, []byte) {
    46  	keyLen := len(key)
    47  	dbKeyMaxSize := binary.MaxVarintLen64 + keyLen + wrappers.LongLen
    48  	dbKey := make([]byte, dbKeyMaxSize)
    49  	offset := binary.PutUvarint(dbKey, uint64(keyLen))
    50  	offset += copy(dbKey[offset:], key)
    51  	prefixOffset := offset
    52  	binary.BigEndian.PutUint64(dbKey[offset:], ^height)
    53  	offset += wrappers.LongLen
    54  	return dbKey[:offset], dbKey[:prefixOffset]
    55  }
    56  
    57  // parseDBKeyFromUser takes a database formatted key and returns the user key
    58  // along with its height.
    59  //
    60  // Note: An error should only be returned from this function if the database has
    61  // been corrupted.
    62  func parseDBKeyFromUser(dbKey []byte) ([]byte, uint64, error) {
    63  	keyLen, offset := binary.Uvarint(dbKey)
    64  	if offset <= 0 {
    65  		return nil, 0, ErrParsingKeyLength
    66  	}
    67  
    68  	heightIndex := uint64(offset) + keyLen
    69  	if uint64(len(dbKey)) != heightIndex+wrappers.LongLen {
    70  		return nil, 0, ErrIncorrectKeyLength
    71  	}
    72  
    73  	key := dbKey[offset:heightIndex]
    74  	height := ^binary.BigEndian.Uint64(dbKey[heightIndex:])
    75  	return key, height, nil
    76  }
    77  
    78  // newDBKeyFromMetadata converts a metadata key into a database formatted key.
    79  //
    80  // To meet the requirements of a database key, the key is defined by
    81  // concatenating the length of the metadata key + 1 and the metadata key.
    82  //
    83  //	Example:
    84  //	 |  Metadata key  |  Stored as  |
    85  //	 |----------------|-------------|
    86  //	 |       foo      |    4:foo    |
    87  //	 |       fo       |    3:fo     |
    88  func newDBKeyFromMetadata(key []byte) []byte {
    89  	keyLen := len(key)
    90  	dbKeyMaxSize := binary.MaxVarintLen64 + keyLen
    91  	dbKey := make([]byte, dbKeyMaxSize)
    92  	offset := binary.PutUvarint(dbKey, uint64(keyLen)+1)
    93  	offset += copy(dbKey[offset:], key)
    94  	return dbKey[:offset]
    95  }