query-addressable.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "bytes"
7
8 "github.com/dgraph-io/badger/v4"
9 "next.orly.dev/pkg/lol/chk"
10 "next.orly.dev/pkg/lol/log"
11 "next.orly.dev/pkg/database/bufpool"
12 "next.orly.dev/pkg/database/indexes"
13 "next.orly.dev/pkg/database/indexes/types"
14 "next.orly.dev/pkg/nostr/encoders/filter"
15 "next.orly.dev/pkg/nostr/encoders/kind"
16 )
17
18 // IsAddressableEventQuery checks if a filter matches the NIP-33 addressable event
19 // query pattern: exactly one kind (30000-39999), one author, and one d-tag.
20 // This pattern uniquely identifies a single parameterized replaceable event.
21 func IsAddressableEventQuery(f *filter.F) bool {
22 // Must have exactly one kind
23 if f.Kinds == nil || f.Kinds.Len() != 1 {
24 return false
25 }
26 // Kind must be parameterized replaceable (30000-39999)
27 kindVal := f.Kinds.K[0].K
28 if !kind.IsParameterizedReplaceable(kindVal) {
29 return false
30 }
31 // Must have exactly one author
32 if f.Authors == nil || f.Authors.Len() != 1 {
33 return false
34 }
35 // Must have a d-tag filter
36 if f.Tags == nil {
37 return false
38 }
39 dTagFilter := f.Tags.GetFirst([]byte("#d"))
40 if dTagFilter == nil || dTagFilter.Len() != 2 {
41 return false
42 }
43 // Must not have IDs filter (would bypass this optimization)
44 if f.Ids != nil && f.Ids.Len() > 0 {
45 return false
46 }
47 return true
48 }
49
50 // QueryForAddressableEvent performs a direct O(1) lookup for a NIP-33 parameterized
51 // replaceable event using the AddressableEvent index.
52 // Returns the serial if found, nil if not found, or an error.
53 func (d *D) QueryForAddressableEvent(f *filter.F) (serial *types.Uint40, err error) {
54 if !IsAddressableEventQuery(f) {
55 return nil, nil
56 }
57
58 // Extract components from filter
59 kindVal := f.Kinds.K[0].K
60 author := f.Authors.T[0]
61 dTagFilter := f.Tags.GetFirst([]byte("#d"))
62 dTagValue := dTagFilter.T[1]
63
64 // Build pubkey hash
65 pubHash := new(types.PubHash)
66 if err = pubHash.FromPubkey(author); chk.E(err) {
67 return nil, err
68 }
69
70 // Build kind type
71 kindType := new(types.Uint16)
72 kindType.Set(kindVal)
73
74 // Build d-tag hash
75 dTagHash := new(types.Ident)
76 dTagHash.FromIdent(dTagValue)
77
78 // Build the AddressableEvent index key
79 aevKey := indexes.AddressableEventEnc(pubHash, kindType, dTagHash)
80 keyBuf := bufpool.GetSmall()
81 defer bufpool.PutSmall(keyBuf)
82 if err = aevKey.MarshalWrite(keyBuf); chk.E(err) {
83 return nil, err
84 }
85 keyBytes := bufpool.CopyBytes(keyBuf)
86
87 // Direct key lookup - O(1)
88 err = d.View(func(txn *badger.Txn) error {
89 item, err := txn.Get(keyBytes)
90 if err == badger.ErrKeyNotFound {
91 // Not found - this is not an error
92 return nil
93 }
94 if err != nil {
95 return err
96 }
97
98 // Read the serial from the value
99 return item.Value(func(val []byte) error {
100 if len(val) < 5 {
101 log.W.F("QueryForAddressableEvent: invalid value length %d", len(val))
102 return nil
103 }
104 serial = new(types.Uint40)
105 rdr := bytes.NewReader(val)
106 if err := serial.UnmarshalRead(rdr); err != nil {
107 log.W.F("QueryForAddressableEvent: failed to read serial: %v", err)
108 serial = nil
109 return nil
110 }
111 return nil
112 })
113 })
114
115 if err != nil {
116 return nil, err
117 }
118
119 if serial != nil {
120 log.T.F("QueryForAddressableEvent: found serial %d for kind=%d author=%x d=%s",
121 serial.Get(), kindVal, author[:8], string(dTagValue))
122 }
123
124 return serial, nil
125 }
126
127 // BuildAddressableEventKey builds the key for an AddressableEvent index entry.
128 // This is used by both save-event.go (for writing) and deletion (for cleanup).
129 func BuildAddressableEventKey(pubkey []byte, eventKind uint16, dTagValue []byte) ([]byte, error) {
130 pubHash := new(types.PubHash)
131 if err := pubHash.FromPubkey(pubkey); err != nil {
132 return nil, err
133 }
134
135 kindType := new(types.Uint16)
136 kindType.Set(eventKind)
137
138 dTagHash := new(types.Ident)
139 dTagHash.FromIdent(dTagValue)
140
141 aevKey := indexes.AddressableEventEnc(pubHash, kindType, dTagHash)
142 keyBuf := bufpool.GetSmall()
143 defer bufpool.PutSmall(keyBuf)
144 if err := aevKey.MarshalWrite(keyBuf); err != nil {
145 return nil, err
146 }
147
148 return bufpool.CopyBytes(keyBuf), nil
149 }
150