fetch-event-by-serial.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "bytes"
7 "fmt"
8
9 "github.com/dgraph-io/badger/v4"
10 "next.orly.dev/pkg/lol/chk"
11 "next.orly.dev/pkg/database/indexes"
12 "next.orly.dev/pkg/database/indexes/types"
13 "next.orly.dev/pkg/nostr/encoders/event"
14 )
15
16 // FetchEventBySerial fetches a single event by its serial.
17 // This function tries multiple storage formats in order:
18 // 1. cmp (compact format with serial references) - newest, most space-efficient
19 // 2. sev (small event inline) - legacy Reiser4 optimization
20 // 3. evt (traditional separate storage) - legacy fallback
21 func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) {
22 // Create resolver for compact event decoding
23 resolver := NewDatabaseSerialResolver(d, d.serialCache)
24
25 if err = d.View(
26 func(txn *badger.Txn) (err error) {
27 // Try cmp (compact format) first - most efficient
28 ev, err = d.fetchCompactEvent(txn, ser, resolver)
29 if err == nil && ev != nil {
30 return nil
31 }
32 err = nil // Reset error, try legacy formats
33
34 // Helper function to extract inline event data from key
35 extractInlineData := func(key []byte, prefixLen int) (*event.E, error) {
36 if len(key) > prefixLen+2 {
37 sizeIdx := prefixLen
38 size := int(key[sizeIdx])<<8 | int(key[sizeIdx+1])
39 dataStart := sizeIdx + 2
40
41 if len(key) >= dataStart+size {
42 eventData := key[dataStart : dataStart+size]
43
44 // Check if this is compact format (starts with version byte 1)
45 // Note: Legacy events whose ID starts with 0x01 will also match this check,
46 // so we fall back to legacy format if the SerialEventId mapping doesn't exist.
47 if len(eventData) > 0 && eventData[0] == CompactFormatVersion {
48 eventId, idErr := d.GetEventIdBySerial(ser)
49 if idErr == nil {
50 // SerialEventId mapping exists - this is compact format
51 return UnmarshalCompactEvent(eventData, eventId, resolver)
52 }
53 // No SerialEventId mapping - this is likely a legacy event whose ID starts with 0x01
54 // Fall through to legacy unmarshal
55 }
56
57 // Legacy binary format
58 ev := new(event.E)
59 if err := ev.UnmarshalBinary(bytes.NewBuffer(eventData)); err != nil {
60 return nil, fmt.Errorf(
61 "error unmarshaling inline event (size=%d): %w",
62 size, err,
63 )
64 }
65 return ev, nil
66 }
67 }
68 return nil, nil
69 }
70
71 // Try sev (small event inline) prefix - Reiser4 optimization
72 smallBuf := new(bytes.Buffer)
73 if err = indexes.SmallEventEnc(ser).MarshalWrite(smallBuf); chk.E(err) {
74 return
75 }
76
77 opts := badger.DefaultIteratorOptions
78 opts.Prefix = smallBuf.Bytes()
79 opts.PrefetchValues = true
80 opts.PrefetchSize = 1
81 it := txn.NewIterator(opts)
82 defer it.Close()
83
84 it.Rewind()
85 if it.Valid() {
86 // Found in sev table - extract inline data
87 key := it.Item().Key()
88 // Key format: sev|serial|size_uint16|event_data
89 if ev, err = extractInlineData(key, 8); err != nil {
90 return err
91 }
92 if ev != nil {
93 return nil
94 }
95 }
96
97 // Not found in sev table, try evt (traditional) prefix
98 buf := new(bytes.Buffer)
99 if err = indexes.EventEnc(ser).MarshalWrite(buf); chk.E(err) {
100 return
101 }
102 var item *badger.Item
103 if item, err = txn.Get(buf.Bytes()); err != nil {
104 return
105 }
106 var v []byte
107 if v, err = item.ValueCopy(nil); chk.E(err) {
108 return
109 }
110
111 // Check if this is compact format (starts with version byte 1)
112 // Note: Legacy events whose ID starts with 0x01 will also match this check,
113 // so we fall back to legacy format if the SerialEventId mapping doesn't exist.
114 if len(v) > 0 && v[0] == CompactFormatVersion {
115 eventId, idErr := d.GetEventIdBySerial(ser)
116 if idErr == nil {
117 // SerialEventId mapping exists - this is compact format
118 ev, err = UnmarshalCompactEvent(v, eventId, resolver)
119 return
120 }
121 // No SerialEventId mapping - this is likely a legacy event whose ID starts with 0x01
122 // Fall through to legacy unmarshal
123 }
124
125 // Check if we have valid data before attempting to unmarshal
126 if len(v) < 32+32+1+2+1+1+64 { // ID + Pubkey + min varint fields + Sig
127 err = fmt.Errorf(
128 "incomplete event data: got %d bytes, expected at least %d",
129 len(v), 32+32+1+2+1+1+64,
130 )
131 return
132 }
133 ev = new(event.E)
134 if err = ev.UnmarshalBinary(bytes.NewBuffer(v)); err != nil {
135 // Add more context to EOF errors for debugging
136 if err.Error() == "EOF" {
137 err = fmt.Errorf(
138 "EOF while unmarshaling event (serial=%v, data_len=%d): %w",
139 ser, len(v), err,
140 )
141 }
142 return
143 }
144 return
145 },
146 ); err != nil {
147 return
148 }
149 return
150 }
151