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 }