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}