github.com/ethersphere/bee/v2@v2.2.0/pkg/api/feed.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package api
     6  
     7  import (
     8  	"encoding/binary"
     9  	"encoding/hex"
    10  	"errors"
    11  	"fmt"
    12  	"net/http"
    13  	"time"
    14  
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethersphere/bee/v2/pkg/accesscontrol"
    17  	"github.com/ethersphere/bee/v2/pkg/feeds"
    18  	"github.com/ethersphere/bee/v2/pkg/file/loadsave"
    19  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    20  	"github.com/ethersphere/bee/v2/pkg/manifest"
    21  	"github.com/ethersphere/bee/v2/pkg/manifest/mantaray"
    22  	"github.com/ethersphere/bee/v2/pkg/manifest/simple"
    23  	"github.com/ethersphere/bee/v2/pkg/postage"
    24  	"github.com/ethersphere/bee/v2/pkg/soc"
    25  	"github.com/ethersphere/bee/v2/pkg/storage"
    26  	"github.com/ethersphere/bee/v2/pkg/storer"
    27  	"github.com/ethersphere/bee/v2/pkg/swarm"
    28  	"github.com/gorilla/mux"
    29  )
    30  
    31  const (
    32  	feedMetadataEntryOwner = "swarm-feed-owner"
    33  	feedMetadataEntryTopic = "swarm-feed-topic"
    34  	feedMetadataEntryType  = "swarm-feed-type"
    35  )
    36  
    37  var errInvalidFeedUpdate = errors.New("invalid feed update")
    38  
    39  type feedReferenceResponse struct {
    40  	Reference swarm.Address `json:"reference"`
    41  }
    42  
    43  func (s *Service) feedGetHandler(w http.ResponseWriter, r *http.Request) {
    44  	logger := s.logger.WithName("get_feed").Build()
    45  
    46  	paths := struct {
    47  		Owner common.Address `map:"owner" validate:"required"`
    48  		Topic []byte         `map:"topic" validate:"required"`
    49  	}{}
    50  	if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
    51  		response("invalid path params", logger, w)
    52  		return
    53  	}
    54  
    55  	queries := struct {
    56  		At    int64  `map:"at"`
    57  		After uint64 `map:"after"`
    58  	}{}
    59  	if response := s.mapStructure(r.URL.Query(), &queries); response != nil {
    60  		response("invalid query params", logger, w)
    61  		return
    62  	}
    63  	if queries.At == 0 {
    64  		queries.At = time.Now().Unix()
    65  	}
    66  
    67  	f := feeds.New(paths.Topic, paths.Owner)
    68  	lookup, err := s.feedFactory.NewLookup(feeds.Sequence, f)
    69  	if err != nil {
    70  		logger.Debug("new lookup failed", "owner", paths.Owner, "error", err)
    71  		logger.Error(nil, "new lookup failed")
    72  		switch {
    73  		case errors.Is(err, feeds.ErrFeedTypeNotFound):
    74  			jsonhttp.NotFound(w, "feed type not found")
    75  		default:
    76  			jsonhttp.InternalServerError(w, "new lookup failed")
    77  		}
    78  		return
    79  	}
    80  
    81  	ch, cur, next, err := lookup.At(r.Context(), queries.At, queries.After)
    82  	if err != nil {
    83  		logger.Debug("lookup at failed", "at", queries.At, "error", err)
    84  		logger.Error(nil, "lookup at failed")
    85  		jsonhttp.NotFound(w, "lookup at failed")
    86  		return
    87  	}
    88  
    89  	// KLUDGE: if a feed was never updated, the chunk will be nil
    90  	if ch == nil {
    91  		logger.Debug("no update found")
    92  		logger.Error(nil, "no update found")
    93  		jsonhttp.NotFound(w, "no update found")
    94  		return
    95  	}
    96  
    97  	ref, _, err := parseFeedUpdate(ch)
    98  	if err != nil {
    99  		logger.Debug("mapStructure feed update failed", "error", err)
   100  		logger.Error(nil, "mapStructure feed update failed")
   101  		jsonhttp.InternalServerError(w, "mapStructure feed update failed")
   102  		return
   103  	}
   104  
   105  	curBytes, err := cur.MarshalBinary()
   106  	if err != nil {
   107  		logger.Debug("marshal current index failed", "error", err)
   108  		logger.Error(nil, "marshal current index failed")
   109  		jsonhttp.InternalServerError(w, "marshal current index failed")
   110  		return
   111  	}
   112  
   113  	nextBytes, err := next.MarshalBinary()
   114  	if err != nil {
   115  		logger.Debug("marshal next index failed", "error", err)
   116  		logger.Error(nil, "marshal next index failed")
   117  		jsonhttp.InternalServerError(w, "marshal next index failed")
   118  		return
   119  	}
   120  
   121  	w.Header().Set(SwarmFeedIndexHeader, hex.EncodeToString(curBytes))
   122  	w.Header().Set(SwarmFeedIndexNextHeader, hex.EncodeToString(nextBytes))
   123  	w.Header().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", SwarmFeedIndexHeader, SwarmFeedIndexNextHeader))
   124  
   125  	jsonhttp.OK(w, feedReferenceResponse{Reference: ref})
   126  }
   127  
   128  func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) {
   129  	logger := s.logger.WithName("post_feed").Build()
   130  
   131  	paths := struct {
   132  		Owner common.Address `map:"owner" validate:"required"`
   133  		Topic []byte         `map:"topic" validate:"required"`
   134  	}{}
   135  	if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
   136  		response("invalid path params", logger, w)
   137  		return
   138  	}
   139  
   140  	headers := struct {
   141  		BatchID        []byte        `map:"Swarm-Postage-Batch-Id" validate:"required"`
   142  		Pin            bool          `map:"Swarm-Pin"`
   143  		Deferred       *bool         `map:"Swarm-Deferred-Upload"`
   144  		Act            bool          `map:"Swarm-Act"`
   145  		HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"`
   146  	}{}
   147  	if response := s.mapStructure(r.Header, &headers); response != nil {
   148  		response("invalid header params", logger, w)
   149  		return
   150  	}
   151  
   152  	var (
   153  		tag      storer.SessionInfo
   154  		err      error
   155  		deferred = defaultUploadMethod(headers.Deferred)
   156  	)
   157  	if deferred || headers.Pin {
   158  		tag, err = s.storer.NewSession()
   159  		if err != nil {
   160  			logger.Debug("get or create tag failed", "error", err)
   161  			logger.Error(nil, "get or create tag failed")
   162  			switch {
   163  			case errors.Is(err, storage.ErrNotFound):
   164  				jsonhttp.NotFound(w, "tag not found")
   165  			default:
   166  				jsonhttp.InternalServerError(w, "cannot get or create tag")
   167  			}
   168  			return
   169  		}
   170  	}
   171  
   172  	putter, err := s.newStamperPutter(r.Context(), putterOptions{
   173  		BatchID:  headers.BatchID,
   174  		TagID:    tag.TagID,
   175  		Pin:      headers.Pin,
   176  		Deferred: deferred,
   177  	})
   178  	if err != nil {
   179  		logger.Debug("get putter failed", "error", err)
   180  		logger.Error(nil, "get putter failed")
   181  		switch {
   182  		case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable):
   183  			jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist")
   184  		case errors.Is(err, postage.ErrNotFound):
   185  			jsonhttp.NotFound(w, "batch with id not found")
   186  		case errors.Is(err, errInvalidPostageBatch):
   187  			jsonhttp.BadRequest(w, "invalid batch id")
   188  		case errors.Is(err, errUnsupportedDevNodeOperation):
   189  			jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation)
   190  		default:
   191  			jsonhttp.BadRequest(w, nil)
   192  		}
   193  		return
   194  	}
   195  
   196  	ow := &cleanupOnErrWriter{
   197  		ResponseWriter: w,
   198  		onErr:          putter.Cleanup,
   199  		logger:         logger,
   200  	}
   201  
   202  	l := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(r.Context(), putter, false, 0))
   203  	feedManifest, err := manifest.NewDefaultManifest(l, false)
   204  	if err != nil {
   205  		logger.Debug("create manifest failed", "error", err)
   206  		logger.Error(nil, "create manifest failed")
   207  		switch {
   208  		case errors.Is(err, manifest.ErrInvalidManifestType):
   209  			jsonhttp.BadRequest(ow, "invalid manifest type")
   210  		default:
   211  			jsonhttp.InternalServerError(ow, "create manifest failed")
   212  		}
   213  	}
   214  
   215  	meta := map[string]string{
   216  		feedMetadataEntryOwner: hex.EncodeToString(paths.Owner.Bytes()),
   217  		feedMetadataEntryTopic: hex.EncodeToString(paths.Topic),
   218  		feedMetadataEntryType:  feeds.Sequence.String(), // only sequence allowed for now
   219  	}
   220  
   221  	emptyAddr := make([]byte, 32)
   222  
   223  	// a feed manifest stores the metadata at the root "/" path
   224  	err = feedManifest.Add(r.Context(), "/", manifest.NewEntry(swarm.NewAddress(emptyAddr), meta))
   225  	if err != nil {
   226  		logger.Debug("add manifest entry failed", "error", err)
   227  		logger.Error(nil, "add manifest entry failed")
   228  		switch {
   229  		case errors.Is(err, simple.ErrEmptyPath):
   230  			jsonhttp.NotFound(ow, "invalid or empty path")
   231  		case errors.Is(err, mantaray.ErrEmptyPath):
   232  			jsonhttp.NotFound(ow, "invalid path or mantaray path is empty")
   233  		default:
   234  			jsonhttp.InternalServerError(ow, "add manifest entry failed")
   235  		}
   236  		return
   237  	}
   238  	ref, err := feedManifest.Store(r.Context())
   239  	if err != nil {
   240  		logger.Debug("store manifest failed", "error", err)
   241  		logger.Error(nil, "store manifest failed")
   242  		switch {
   243  		case errors.Is(err, postage.ErrBucketFull):
   244  			jsonhttp.PaymentRequired(ow, "batch is overissued")
   245  		default:
   246  			jsonhttp.InternalServerError(ow, "store manifest failed")
   247  		}
   248  		return
   249  	}
   250  
   251  	encryptedReference := ref
   252  	if headers.Act {
   253  		encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, ref, headers.HistoryAddress)
   254  		if err != nil {
   255  			logger.Debug("access control upload failed", "error", err)
   256  			logger.Error(nil, "access control upload failed")
   257  			switch {
   258  			case errors.Is(err, accesscontrol.ErrNotFound):
   259  				jsonhttp.NotFound(w, "act or history entry not found")
   260  			case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity):
   261  				jsonhttp.BadRequest(w, "invalid public key")
   262  			case errors.Is(err, accesscontrol.ErrUnexpectedType):
   263  				jsonhttp.BadRequest(w, "failed to create history")
   264  			default:
   265  				jsonhttp.InternalServerError(w, errActUpload)
   266  			}
   267  			return
   268  		}
   269  	}
   270  
   271  	err = putter.Done(ref)
   272  	if err != nil {
   273  		logger.Debug("done split failed", "error", err)
   274  		logger.Error(nil, "done split failed")
   275  		jsonhttp.InternalServerError(ow, "done split failed")
   276  		return
   277  	}
   278  
   279  	jsonhttp.Created(w, feedReferenceResponse{Reference: encryptedReference})
   280  }
   281  
   282  func parseFeedUpdate(ch swarm.Chunk) (swarm.Address, int64, error) {
   283  	s, err := soc.FromChunk(ch)
   284  	if err != nil {
   285  		return swarm.ZeroAddress, 0, fmt.Errorf("soc unmarshal: %w", err)
   286  	}
   287  
   288  	update := s.WrappedChunk().Data()
   289  	// split the timestamp and reference
   290  	// possible values right now:
   291  	// unencrypted ref: span+timestamp+ref => 8+8+32=48
   292  	// encrypted ref: span+timestamp+ref+decryptKey => 8+8+64=80
   293  	if len(update) != 48 && len(update) != 80 {
   294  		return swarm.ZeroAddress, 0, errInvalidFeedUpdate
   295  	}
   296  	ts := binary.BigEndian.Uint64(update[8:16])
   297  	ref := swarm.NewAddress(update[16:])
   298  	return ref, int64(ts), nil
   299  }