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 }