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