query-for-deleted.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"fmt"
   7  
   8  	"next.orly.dev/pkg/lol/chk"
   9  	"next.orly.dev/pkg/lol/errorf"
  10  	"next.orly.dev/pkg/database/indexes/types"
  11  	"next.orly.dev/pkg/nostr/encoders/event"
  12  	"next.orly.dev/pkg/nostr/encoders/filter"
  13  	"next.orly.dev/pkg/nostr/encoders/hex"
  14  	"next.orly.dev/pkg/nostr/encoders/kind"
  15  	"next.orly.dev/pkg/nostr/encoders/tag"
  16  	"next.orly.dev/pkg/nostr/encoders/tag/atag"
  17  	"next.orly.dev/pkg/interfaces/store"
  18  )
  19  
  20  // CheckForDeleted checks if the event is deleted, and returns an error with
  21  // prefix "blocked:" if it is. This function also allows designating admin
  22  // pubkeys that also may delete the event, normally only the author is allowed
  23  // to delete an event.
  24  func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) {
  25  	// log.T.F("CheckForDeleted: checking event %x", ev.ID)
  26  	keys := append([][]byte{ev.Pubkey}, admins...)
  27  	authors := tag.NewFromBytesSlice(keys...)
  28  	// if the event is addressable, check for a deletion event with the same
  29  	// kind/pubkey/dtag
  30  	if kind.IsParameterizedReplaceable(ev.Kind) {
  31  		var idxs []Range
  32  		// construct a tag
  33  		t := ev.Tags.GetFirst([]byte("d"))
  34  		a := atag.T{
  35  			Kind:   kind.New(ev.Kind),
  36  			Pubkey: ev.Pubkey,
  37  			DTag:   t.Value(),
  38  		}
  39  		at := a.Marshal(nil)
  40  		if idxs, err = GetIndexesFromFilter(
  41  			&filter.F{
  42  				Authors: authors,
  43  				Kinds:   kind.NewS(kind.Deletion),
  44  				Tags:    tag.NewS(tag.NewFromAny("#a", at)),
  45  			},
  46  		); chk.E(err) {
  47  			return
  48  		}
  49  		var sers types.Uint40s
  50  		for _, idx := range idxs {
  51  			var s types.Uint40s
  52  			if s, err = d.GetSerialsByRange(idx); chk.E(err) {
  53  				return
  54  			}
  55  			sers = append(sers, s...)
  56  		}
  57  		if len(sers) > 0 {
  58  			// there can be multiple of these because the author/kind/tag is a
  59  			// stable value but refers to any event from the author, of the
  60  			// kind, with the identifier. so we need to fetch the full ID index
  61  			// to get the timestamp and ensure that the event post-dates it.
  62  			// otherwise, it should be rejected.
  63  			var idPkTss []*store.IdPkTs
  64  			var tmp []*store.IdPkTs
  65  			if tmp, err = d.GetFullIdPubkeyBySerials(sers); chk.E(err) {
  66  				return
  67  			}
  68  			idPkTss = append(idPkTss, tmp...)
  69  			// find the newest deletion timestamp without sorting to reduce cost
  70  			maxTs := idPkTss[0].Ts
  71  			for i := 1; i < len(idPkTss); i++ {
  72  				if idPkTss[i].Ts > maxTs {
  73  					maxTs = idPkTss[i].Ts
  74  				}
  75  			}
  76  			if ev.CreatedAt < maxTs {
  77  				err = errorf.E(
  78  					"blocked: %0x was deleted by address %s because it is older than the delete: event: %d delete: %d",
  79  					ev.ID, at, ev.CreatedAt, maxTs,
  80  				)
  81  				return
  82  			}
  83  			return
  84  		}
  85  		return
  86  	}
  87  	// if the event is replaceable, check if there is a deletion event newer
  88  	// than the event, it must specify the same kind/pubkey. this type of delete
  89  	// only has the k tag to specify the kind, it can be what an author would
  90  	// use, as the author is part of the replaceable event specification.
  91  	if kind.IsReplaceable(ev.Kind) {
  92  		var idxs []Range
  93  		if idxs, err = GetIndexesFromFilter(
  94  			&filter.F{
  95  				Authors: tag.NewFromBytesSlice(ev.Pubkey),
  96  				Kinds:   kind.NewS(kind.Deletion),
  97  				Tags: tag.NewS(
  98  					tag.NewFromAny("#k", fmt.Sprint(ev.Kind)),
  99  				),
 100  			},
 101  		); chk.E(err) {
 102  			return
 103  		}
 104  		var sers types.Uint40s
 105  		for _, idx := range idxs {
 106  			var s types.Uint40s
 107  			if s, err = d.GetSerialsByRange(idx); chk.E(err) {
 108  				return
 109  			}
 110  			sers = append(sers, s...)
 111  		}
 112  		if len(sers) > 0 {
 113  			var idPkTss []*store.IdPkTs
 114  			var tmp []*store.IdPkTs
 115  			if tmp, err = d.GetFullIdPubkeyBySerials(sers); chk.E(err) {
 116  				return
 117  			}
 118  			idPkTss = append(idPkTss, tmp...)
 119  			// find the newest deletion without sorting to reduce cost
 120  			maxTs := idPkTss[0].Ts
 121  			maxId := idPkTss[0].Id
 122  			for i := 1; i < len(idPkTss); i++ {
 123  				if idPkTss[i].Ts > maxTs {
 124  					maxTs = idPkTss[i].Ts
 125  					maxId = idPkTss[i].Id
 126  				}
 127  			}
 128  			if ev.CreatedAt < maxTs {
 129  				err = fmt.Errorf(
 130  					"blocked: %0x was deleted: the event is older than the delete event %0x: event: %d delete: %d",
 131  					ev.ID, maxId, ev.CreatedAt, maxTs,
 132  				)
 133  				return
 134  			}
 135  		}
 136  		// this type of delete can also use an a tag to specify kind and
 137  		// author, which would be required for admin deletes
 138  		idxs = nil
 139  		// construct a tag
 140  		a := atag.T{
 141  			Kind:   kind.New(ev.Kind),
 142  			Pubkey: ev.Pubkey,
 143  		}
 144  		at := a.Marshal(nil)
 145  		if idxs, err = GetIndexesFromFilter(
 146  			&filter.F{
 147  				Authors: authors,
 148  				Kinds:   kind.NewS(kind.Deletion),
 149  				Tags:    tag.NewS(tag.NewFromAny("#a", at)),
 150  			},
 151  		); chk.E(err) {
 152  			return
 153  		}
 154  		sers = nil
 155  		for _, idx := range idxs {
 156  			var s types.Uint40s
 157  			if s, err = d.GetSerialsByRange(idx); chk.E(err) {
 158  				return
 159  			}
 160  			sers = append(sers, s...)
 161  		}
 162  		if len(sers) > 0 {
 163  			var idPkTss []*store.IdPkTs
 164  			var tmp []*store.IdPkTs
 165  			if tmp, err = d.GetFullIdPubkeyBySerials(sers); chk.E(err) {
 166  				return
 167  			}
 168  			idPkTss = append(idPkTss, tmp...)
 169  			// find the newest deletion without sorting to reduce cost
 170  			maxTs := idPkTss[0].Ts
 171  			// maxId := idPkTss[0].Id
 172  			for i := 1; i < len(idPkTss); i++ {
 173  				if idPkTss[i].Ts > maxTs {
 174  					maxTs = idPkTss[i].Ts
 175  					// maxId = idPkTss[i].Id
 176  				}
 177  			}
 178  			if ev.CreatedAt < maxTs {
 179  				err = errorf.E(
 180  					"blocked: %0x was deleted by address %s because it is older than the delete: event: %d delete: %d",
 181  					ev.ID, at, ev.CreatedAt, maxTs,
 182  				)
 183  				return
 184  			}
 185  			return
 186  		}
 187  		return
 188  	}
 189  	// otherwise we check for a delete by event id
 190  	// log.T.F("CheckForDeleted: checking for e-tag deletion of event %x", ev.ID)
 191  	// log.T.F("CheckForDeleted: authors filter: %v", authors)
 192  	// log.T.F("CheckForDeleted: looking for tag e with value: %s", hex.Enc(ev.ID))
 193  	var idxs []Range
 194  	if idxs, err = GetIndexesFromFilter(
 195  		&filter.F{
 196  			Authors: authors,
 197  			Kinds:   kind.NewS(kind.Deletion),
 198  			Tags: tag.NewS(
 199  				tag.NewFromAny("e", hex.Enc(ev.ID)),
 200  			),
 201  		},
 202  	); chk.E(err) {
 203  		return
 204  	}
 205  	// log.T.F("CheckForDeleted: found %d indexes", len(idxs))
 206  	var sers types.Uint40s
 207  	for _, idx := range idxs {
 208  		// log.T.F("CheckForDeleted: checking index %d: %v", i, idx)
 209  		var s types.Uint40s
 210  		if s, err = d.GetSerialsByRange(idx); chk.E(err) {
 211  			return
 212  		}
 213  		// log.T.F("CheckForDeleted: index %d returned %d serials", i, len(s))
 214  		if len(s) > 0 {
 215  			// Any e-tag deletion found means the exact event was deleted and cannot be resubmitted
 216  			// log.T.F("CheckForDeleted: found e-tag deletion for event %x", ev.ID)
 217  			err = errorf.E("blocked: %0x has been deleted", ev.ID)
 218  			return
 219  		}
 220  	}
 221  	if len(sers) > 0 {
 222  		// Any e-tag deletion found means the exact event was deleted and cannot be resubmitted
 223  		err = errorf.E("blocked: %0x has been deleted", ev.ID)
 224  		return
 225  	}
 226  
 227  	return
 228  }
 229