|
|
|
@ -19,6 +19,13 @@ import ( |
|
|
|
"next.orly.dev/pkg/encoders/tag" |
|
|
|
"next.orly.dev/pkg/encoders/tag" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
|
|
|
// ErrOlderThanExisting is returned when a candidate event is older than an existing replaceable/addressable event.
|
|
|
|
|
|
|
|
ErrOlderThanExisting = errors.New("older than existing event") |
|
|
|
|
|
|
|
// ErrMissingDTag is returned when a parameterized replaceable event lacks the required 'd' tag.
|
|
|
|
|
|
|
|
ErrMissingDTag = errors.New("event is missing a d tag identifier") |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
func (d *D) GetSerialsFromFilter(f *filter.F) ( |
|
|
|
func (d *D) GetSerialsFromFilter(f *filter.F) ( |
|
|
|
sers types.Uint40s, err error, |
|
|
|
sers types.Uint40s, err error, |
|
|
|
) { |
|
|
|
) { |
|
|
|
@ -36,6 +43,65 @@ func (d *D) GetSerialsFromFilter(f *filter.F) ( |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// WouldReplaceEvent checks if the provided event would replace existing events
|
|
|
|
|
|
|
|
// based on Nostr's replaceable or parameterized replaceable semantics. It
|
|
|
|
|
|
|
|
// returns true along with the serials of events that should be replaced if the
|
|
|
|
|
|
|
|
// candidate is newer-or-equal. If an existing event is newer, it returns
|
|
|
|
|
|
|
|
// (false, serials, ErrOlderThanExisting). If no conflicts exist, it returns
|
|
|
|
|
|
|
|
// (false, nil, nil).
|
|
|
|
|
|
|
|
func (d *D) WouldReplaceEvent(ev *event.E) (bool, types.Uint40s, error) { |
|
|
|
|
|
|
|
// Only relevant for replaceable or parameterized replaceable kinds
|
|
|
|
|
|
|
|
if !(kind.IsReplaceable(ev.Kind) || kind.IsParameterizedReplaceable(ev.Kind)) { |
|
|
|
|
|
|
|
return false, nil, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var f *filter.F |
|
|
|
|
|
|
|
if kind.IsReplaceable(ev.Kind) { |
|
|
|
|
|
|
|
f = &filter.F{ |
|
|
|
|
|
|
|
Authors: tag.NewFromBytesSlice(ev.Pubkey), |
|
|
|
|
|
|
|
Kinds: kind.NewS(kind.New(ev.Kind)), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// parameterized replaceable requires 'd' tag
|
|
|
|
|
|
|
|
dTag := ev.Tags.GetFirst([]byte("d")) |
|
|
|
|
|
|
|
if dTag == nil { |
|
|
|
|
|
|
|
return false, nil, ErrMissingDTag |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
f = &filter.F{ |
|
|
|
|
|
|
|
Authors: tag.NewFromBytesSlice(ev.Pubkey), |
|
|
|
|
|
|
|
Kinds: kind.NewS(kind.New(ev.Kind)), |
|
|
|
|
|
|
|
Tags: tag.NewS( |
|
|
|
|
|
|
|
tag.NewFromAny("d", dTag.Value()), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sers, err := d.GetSerialsFromFilter(f) |
|
|
|
|
|
|
|
if chk.E(err) { |
|
|
|
|
|
|
|
return false, nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if len(sers) == 0 { |
|
|
|
|
|
|
|
return false, nil, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Determine if any existing event is newer than the candidate
|
|
|
|
|
|
|
|
shouldReplace := true |
|
|
|
|
|
|
|
for _, s := range sers { |
|
|
|
|
|
|
|
oldEv, ferr := d.FetchEventBySerial(s) |
|
|
|
|
|
|
|
if chk.E(ferr) { |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if ev.CreatedAt < oldEv.CreatedAt { |
|
|
|
|
|
|
|
shouldReplace = false |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if shouldReplace { |
|
|
|
|
|
|
|
return true, sers, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false, sers, ErrOlderThanExisting |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// SaveEvent saves an event to the database, generating all the necessary indexes.
|
|
|
|
// SaveEvent saves an event to the database, generating all the necessary indexes.
|
|
|
|
func (d *D) SaveEvent(c context.Context, ev *event.E) (kc, vc int, err error) { |
|
|
|
func (d *D) SaveEvent(c context.Context, ev *event.E) (kc, vc int, err error) { |
|
|
|
if ev == nil { |
|
|
|
if ev == nil { |
|
|
|
@ -68,118 +134,38 @@ func (d *D) SaveEvent(c context.Context, ev *event.E) (kc, vc int, err error) { |
|
|
|
err = fmt.Errorf("blocked: %s", err.Error()) |
|
|
|
err = fmt.Errorf("blocked: %s", err.Error()) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
// check for replacement
|
|
|
|
// check for replacement (separated check vs deletion)
|
|
|
|
if kind.IsReplaceable(ev.Kind) { |
|
|
|
if kind.IsReplaceable(ev.Kind) || kind.IsParameterizedReplaceable(ev.Kind) { |
|
|
|
// find the events and check timestamps before deleting
|
|
|
|
var wouldReplace bool |
|
|
|
f := &filter.F{ |
|
|
|
|
|
|
|
Authors: tag.NewFromBytesSlice(ev.Pubkey), |
|
|
|
|
|
|
|
Kinds: kind.NewS(kind.New(ev.Kind)), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var sers types.Uint40s |
|
|
|
var sers types.Uint40s |
|
|
|
if sers, err = d.GetSerialsFromFilter(f); chk.E(err) { |
|
|
|
var werr error |
|
|
|
return |
|
|
|
if wouldReplace, sers, werr = d.WouldReplaceEvent(ev); werr != nil { |
|
|
|
} |
|
|
|
if errors.Is(werr, ErrOlderThanExisting) { |
|
|
|
// if found, check timestamps before deleting
|
|
|
|
if kind.IsReplaceable(ev.Kind) { |
|
|
|
if len(sers) > 0 { |
|
|
|
|
|
|
|
var shouldReplace bool = true |
|
|
|
|
|
|
|
for _, s := range sers { |
|
|
|
|
|
|
|
var oldEv *event.E |
|
|
|
|
|
|
|
if oldEv, err = d.FetchEventBySerial(s); chk.E(err) { |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Only replace if the new event is newer or same timestamp
|
|
|
|
|
|
|
|
if ev.CreatedAt < oldEv.CreatedAt { |
|
|
|
|
|
|
|
// log.I.F(
|
|
|
|
|
|
|
|
// "SaveEvent: rejecting older replaceable event ID=%s (created_at=%d) - existing event ID=%s (created_at=%d)",
|
|
|
|
|
|
|
|
// hex.Enc(ev.ID), ev.CreatedAt, hex.Enc(oldEv.ID),
|
|
|
|
|
|
|
|
// oldEv.CreatedAt,
|
|
|
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
shouldReplace = false |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if shouldReplace { |
|
|
|
|
|
|
|
for _, s := range sers { |
|
|
|
|
|
|
|
var oldEv *event.E |
|
|
|
|
|
|
|
if oldEv, err = d.FetchEventBySerial(s); chk.E(err) { |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// log.I.F(
|
|
|
|
|
|
|
|
// "SaveEvent: replacing older replaceable event ID=%s (created_at=%d) with newer event ID=%s (created_at=%d)",
|
|
|
|
|
|
|
|
// hex.Enc(oldEv.ID), oldEv.CreatedAt, hex.Enc(ev.ID),
|
|
|
|
|
|
|
|
// ev.CreatedAt,
|
|
|
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
if err = d.DeleteEventBySerial( |
|
|
|
|
|
|
|
c, s, oldEv, |
|
|
|
|
|
|
|
); chk.E(err) { |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Don't save the older event - return an error
|
|
|
|
|
|
|
|
err = errors.New("blocked: event is older than existing replaceable event") |
|
|
|
err = errors.New("blocked: event is older than existing replaceable event") |
|
|
|
return |
|
|
|
} else { |
|
|
|
} |
|
|
|
err = errors.New("blocked: event is older than existing addressable event") |
|
|
|
} |
|
|
|
} |
|
|
|
} else if kind.IsParameterizedReplaceable(ev.Kind) { |
|
|
|
|
|
|
|
// find the events and check timestamps before deleting
|
|
|
|
|
|
|
|
dTag := ev.Tags.GetFirst([]byte("d")) |
|
|
|
|
|
|
|
if dTag == nil { |
|
|
|
|
|
|
|
err = errors.New("event is missing a d tag identifier") |
|
|
|
|
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
f := &filter.F{ |
|
|
|
if errors.Is(werr, ErrMissingDTag) { |
|
|
|
Authors: tag.NewFromBytesSlice(ev.Pubkey), |
|
|
|
// keep behavior consistent with previous implementation
|
|
|
|
Kinds: kind.NewS(kind.New(ev.Kind)), |
|
|
|
err = ErrMissingDTag |
|
|
|
Tags: tag.NewS( |
|
|
|
|
|
|
|
tag.NewFromAny("d", dTag.Value()), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var sers types.Uint40s |
|
|
|
|
|
|
|
if sers, err = d.GetSerialsFromFilter(f); chk.E(err) { |
|
|
|
|
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
// if found, check timestamps before deleting
|
|
|
|
// any other error
|
|
|
|
if len(sers) > 0 { |
|
|
|
return |
|
|
|
var shouldReplace bool = true |
|
|
|
|
|
|
|
for _, s := range sers { |
|
|
|
|
|
|
|
var oldEv *event.E |
|
|
|
|
|
|
|
if oldEv, err = d.FetchEventBySerial(s); chk.E(err) { |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Only replace if the new event is newer or same timestamp
|
|
|
|
|
|
|
|
if ev.CreatedAt < oldEv.CreatedAt { |
|
|
|
|
|
|
|
// log.I.F(
|
|
|
|
|
|
|
|
// "SaveEvent: rejecting older addressable event ID=%s (created_at=%d) - existing event ID=%s (created_at=%d)",
|
|
|
|
|
|
|
|
// hex.Enc(ev.ID), ev.CreatedAt, hex.Enc(oldEv.ID),
|
|
|
|
|
|
|
|
// oldEv.CreatedAt,
|
|
|
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
shouldReplace = false |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
if shouldReplace { |
|
|
|
if wouldReplace { |
|
|
|
for _, s := range sers { |
|
|
|
for _, s := range sers { |
|
|
|
var oldEv *event.E |
|
|
|
var oldEv *event.E |
|
|
|
if oldEv, err = d.FetchEventBySerial(s); chk.E(err) { |
|
|
|
if oldEv, err = d.FetchEventBySerial(s); chk.E(err) { |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
// log.I.F(
|
|
|
|
if err = d.DeleteEventBySerial(c, s, oldEv); chk.E(err) { |
|
|
|
// "SaveEvent: replacing older addressable event ID=%s (created_at=%d) with newer event ID=%s (created_at=%d)",
|
|
|
|
|
|
|
|
// hex.Enc(oldEv.ID), oldEv.CreatedAt, hex.Enc(ev.ID),
|
|
|
|
|
|
|
|
// ev.CreatedAt,
|
|
|
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
if err = d.DeleteEventBySerial( |
|
|
|
|
|
|
|
c, s, oldEv, |
|
|
|
|
|
|
|
); chk.E(err) { |
|
|
|
|
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
|
|
|
|
// Don't save the older event - return an error
|
|
|
|
|
|
|
|
err = errors.New("blocked: event is older than existing addressable event") |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Get the next sequence number for the event
|
|
|
|
// Get the next sequence number for the event
|
|
|
|
|