~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
101 lines · 4.0 KB Raw History
package bidder

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/market"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/contract/guestcp"
)

var errGuestEventsUnavailable = errors.New("bidder: the guest event queue is not available")

func (b *Bidder) PublishGuestEvent(ctx context.Context, event guestcp.GuestEvent) error {
	if !market.IsGuestEventNSID(event.Collection) {
		return fmt.Errorf("bidder: %q is not a guest event collection", event.Collection)
	}
	held, ok := b.vms.Get(event.ComputeVM)
	if !ok {
		return fmt.Errorf("bidder: the ComputeVM %s is not known", event.ComputeVM)
	}
	vm := &held
	if vm.Spec.Owner.Handle != b.cfg.ProviderHandle || vm.DeletionTimestamp != nil || b.guests.Ended(vm) || b.guests.Releasing(vm) {
		return fmt.Errorf("bidder: the ComputeVM %s is not a live guest of this bidder", vm.Name)
	}
	record := map[string]any{}
	if err := json.Unmarshal(event.Record, &record); err != nil {
		return fmt.Errorf("bidder: the guest event record is not JSON: %w", err)
	}
	if kind, _ := record["$type"].(string); kind != event.Collection {
		return fmt.Errorf("bidder: the guest event record $type %q is not %s", kind, event.Collection)
	}
	name, err := b.ensureGuestEventSync(ctx, vm, event.Collection, event.Tag, event.Kind, record)
	if err != nil {
		return err
	}
	b.log.Info("a guest event is published to the requester", "computevm", vm.Name, "collection", event.Collection,
		"tag", event.Tag, "kind", event.Kind, "stateSync", name)
	return nil
}

func (b *Bidder) applyGuestEvent(ctx context.Context, uri, cid string, record map[string]any, event *market.Event, issuer string) error {
	if b.releasedByReceipt(event.Receipt.URI) {
		return nil
	}
	info, vm, found := b.localContractForReceipt(event.Receipt.URI)
	if !found {
		return fmt.Errorf("bidder: no ComputeVM holds the receipt %s yet", event.Receipt.URI)
	}
	if info.acceptAuthor == "" || info.acceptAuthor != issuer || market.ATURIAuthority(uri) != issuer {
		return ErrEventForbidden
	}
	if vm.DeletionTimestamp != nil || b.guests.Ended(vm) || b.guests.Releasing(vm) {
		return nil
	}
	if event.Receipt.CID != info.receiptCID {
		return fmt.Errorf("%w: the event names another receipt", ErrEventInvalid)
	}
	if market.ATURIAuthority(event.Payload.URI) != issuer {
		return fmt.Errorf("%w: the payload is not the requester's", ErrEventInvalid)
	}
	if err := b.verifier.VerifyRecord(ctx, market.ResolvedRecord{URI: uri, CID: cid, Value: record}); err != nil {
		return fmt.Errorf("%w: the event is not signed: %v", ErrEventInvalid, err)
	}
	resolved, err := b.records.Resolve(ctx, market.RecordRef{URI: event.Payload.URI, CID: event.Payload.CID})
	if err != nil {
		return fmt.Errorf("bidder: resolving the guest event payload: %w", err)
	}
	if resolved.CID != event.Payload.CID {
		return fmt.Errorf("%w: the payload CID %s is not the event's %s", ErrEventInvalid, resolved.CID, event.Payload.CID)
	}
	nsid, err := b.guests.CheckGuestEvent(resolved.Value)
	if err != nil {
		return fmt.Errorf("%w: %v", ErrEventInvalid, err)
	}
	if nsid != market.NSIDFromURI(event.Payload.URI) {
		return fmt.Errorf("%w: the payload $type %s is not its collection", ErrEventInvalid, nsid)
	}
	if !market.IsRequesterGuestEventNSID(nsid) {
		return fmt.Errorf("%w: %s is the guest's to send, not the requester's", ErrEventInvalid, nsid)
	}
	if err := b.verifier.VerifyRecord(ctx, resolved); err != nil {
		return fmt.Errorf("%w: the payload is not signed: %v", ErrEventInvalid, err)
	}
	if b.guestEvents == nil {
		return errGuestEventsUnavailable
	}
	raw, err := json.Marshal(resolved.Value)
	if err != nil {
		return fmt.Errorf("%w: %v", ErrEventInvalid, err)
	}
	appended, err := b.guestEvents.Append(ctx, vm.Name, guestcp.QueuedEvent{URI: resolved.URI, CID: resolved.CID, Record: raw})
	if err != nil {
		return fmt.Errorf("bidder: queueing the guest event: %w", err)
	}
	if appended {
		b.log.Info("a requester guest event is queued for the guest", "computevm", vm.Name, "collection", nsid, "payload", resolved.URI)
	}
	return nil
}