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