haystack.gno
3.36 Kb ยท 99 lines
1package haystack
2
3import (
4 "encoding/hex"
5 "errors"
6
7 "gno.land/p/demo/avl"
8 "gno.land/p/n2p5/haystack/needle"
9)
10
11var (
12 // ErrorNeedleNotFound is returned when a needle is not found in the haystack.
13 ErrorNeedleNotFound = errors.New("needle not found")
14 // ErrorNeedleLength is returned when a needle is not the correct length.
15 ErrorNeedleLength = errors.New("invalid needle length")
16 // ErrorHashLength is returned when a needle hash is not the correct length.
17 ErrorHashLength = errors.New("invalid hash length")
18 // ErrorDuplicateNeedle is returned when a needle already exists in the haystack.
19 ErrorDuplicateNeedle = errors.New("needle already exists")
20 // ErrorHashMismatch is returned when a needle hash does not match the needle. This should
21 // never happen and indicates a critical internal storage error.
22 ErrorHashMismatch = errors.New("storage error: hash mismatch")
23 // ErrorValueInvalidType is returned when a needle value is not a byte slice. This should
24 // never happen and indicates a critical internal storage error.
25 ErrorValueInvalidType = errors.New("storage error: invalid value type, expected []byte")
26)
27
28const (
29 // EncodedHashLength is the length of the hex-encoded needle hash.
30 EncodedHashLength = needle.HashLength * 2
31 // EncodedPayloadLength is the length of the hex-encoded needle payload.
32 EncodedPayloadLength = needle.PayloadLength * 2
33 // EncodedNeedleLength is the length of the hex-encoded needle.
34 EncodedNeedleLength = EncodedHashLength + EncodedPayloadLength
35)
36
37// Haystack is a permissionless, append-only, content-addressed key-value store for fix
38// length messages known as needles. A needle is a 192 byte byte slice with a 32 byte
39// hash (sha256) and a 160 byte payload.
40type Haystack struct{ internal *avl.Tree }
41
42// New creates a new instance of a Haystack key-value store.
43func New() *Haystack {
44 return &Haystack{
45 internal: avl.NewTree(),
46 }
47}
48
49// Add takes a fixed-length hex-encoded needle bytes and adds it to the haystack key-value
50// store. The key is the first 32 bytes of the needle hash (64 bytes hex-encoded) of the
51// sha256 sum of the payload. The value is the 160 byte byte slice of the needle payload.
52// An error is returned if the needle is found to be invalid.
53func (h *Haystack) Add(needleHex string) error {
54 if len(needleHex) != EncodedNeedleLength {
55 return ErrorNeedleLength
56 }
57 b, err := hex.DecodeString(needleHex)
58 if err != nil {
59 return err
60 }
61 n, err := needle.FromBytes(b)
62 if err != nil {
63 return err
64 }
65 if h.internal.Has(needleHex[:EncodedHashLength]) {
66 return ErrorDuplicateNeedle
67 }
68 h.internal.Set(needleHex[:EncodedHashLength], n.Payload())
69 return nil
70}
71
72// Get takes a hex-encoded needle hash and returns the complete hex-encoded needle bytes
73// and an error. Errors covers errors that span from the needle not being found, internal
74// storage error inconsistencies, and invalid value types.
75func (h *Haystack) Get(hash string) (string, error) {
76 if len(hash) != EncodedHashLength {
77 return "", ErrorHashLength
78 }
79 if _, err := hex.DecodeString(hash); err != nil {
80 return "", err
81 }
82 v, ok := h.internal.Get(hash)
83 if !ok {
84 return "", ErrorNeedleNotFound
85 }
86 b, ok := v.([]byte)
87 if !ok {
88 return "", ErrorValueInvalidType
89 }
90 n, err := needle.New(b)
91 if err != nil {
92 return "", err
93 }
94 needleHash := hex.EncodeToString(n.Hash())
95 if needleHash != hash {
96 return "", ErrorHashMismatch
97 }
98 return hex.EncodeToString(n.Bytes()), nil
99}