github.com/ethersphere/bee/v2@v2.2.0/pkg/postage/service.go (about) 1 // Copyright 2020 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 postage 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/hex" 11 "errors" 12 "fmt" 13 "io" 14 "math/big" 15 "sync" 16 17 "github.com/ethersphere/bee/v2/pkg/log" 18 "github.com/ethersphere/bee/v2/pkg/storage" 19 ) 20 21 // loggerName is the tree path name of the logger for this package. 22 const loggerName = "postage" 23 24 const ( 25 // blockThreshold is used to allow threshold no of blocks to be synced before a 26 // batch is usable. 27 blockThreshold = 10 28 ) 29 30 var ( 31 // ErrNotFound is the error returned when issuer with given batch ID does not exist. 32 ErrNotFound = errors.New("not found") 33 // ErrNotUsable is the error returned when issuer with given batch ID is not usable. 34 ErrNotUsable = errors.New("not usable") 35 ) 36 37 // Service is the postage service interface. 38 type Service interface { 39 Add(*StampIssuer) error 40 StampIssuers() []*StampIssuer 41 GetStampIssuer([]byte) (*StampIssuer, func() error, error) 42 IssuerUsable(*StampIssuer) bool 43 BatchEventListener 44 BatchExpiryHandler 45 io.Closer 46 } 47 48 // service handles postage batches 49 // stores the active batches. 50 type service struct { 51 logger log.Logger 52 mtx sync.Mutex 53 store storage.Store 54 postageStore Storer 55 chainID int64 56 issuers []*StampIssuer 57 } 58 59 // NewService constructs a new Service. 60 func NewService(logger log.Logger, store storage.Store, postageStore Storer, chainID int64) (Service, error) { 61 s := &service{ 62 logger: logger.WithName(loggerName).Register(), 63 store: store, 64 postageStore: postageStore, 65 chainID: chainID, 66 } 67 68 return s, s.store.Iterate( 69 storage.Query{ 70 Factory: func() storage.Item { 71 return new(StampIssuerItem) 72 }, 73 }, func(result storage.Result) (bool, error) { 74 issuer := result.Entry.(*StampIssuerItem).Issuer 75 _ = s.add(issuer) 76 return false, nil 77 }) 78 } 79 80 // Add adds a stamp issuer to the active issuers. 81 func (ps *service) Add(st *StampIssuer) error { 82 ps.mtx.Lock() 83 defer ps.mtx.Unlock() 84 85 if !ps.add(st) { 86 return nil 87 } 88 return ps.save(st) 89 } 90 91 // HandleCreate implements the BatchEventListener interface. This is fired on receiving 92 // a batch creation event from the blockchain listener to ensure that if a stamp 93 // issuer was not created initially, we will create it here. 94 func (ps *service) HandleCreate(b *Batch, amount *big.Int) error { 95 return ps.Add(NewStampIssuer( 96 "recovered", 97 string(b.Owner), 98 b.ID, 99 amount, 100 b.Depth, 101 b.BucketDepth, 102 b.Start, 103 b.Immutable, 104 )) 105 } 106 107 // HandleTopUp implements the BatchEventListener interface. This is fired on receiving 108 // a batch topup event from the blockchain to update stampissuer details 109 func (ps *service) HandleTopUp(batchID []byte, amount *big.Int) { 110 ps.mtx.Lock() 111 defer ps.mtx.Unlock() 112 113 for _, v := range ps.issuers { 114 if bytes.Equal(v.data.BatchID, batchID) { 115 v.data.BatchAmount.Add(v.data.BatchAmount, amount) 116 return 117 } 118 } 119 } 120 121 func (ps *service) HandleDepthIncrease(batchID []byte, newDepth uint8) { 122 ps.mtx.Lock() 123 defer ps.mtx.Unlock() 124 125 for _, v := range ps.issuers { 126 if bytes.Equal(batchID, v.data.BatchID) { 127 if newDepth > v.data.BatchDepth { 128 v.data.BatchDepth = newDepth 129 } 130 return 131 } 132 } 133 } 134 135 // StampIssuers returns the currently active stamp issuers. 136 func (ps *service) StampIssuers() []*StampIssuer { 137 ps.mtx.Lock() 138 defer ps.mtx.Unlock() 139 return ps.issuers 140 } 141 142 func (ps *service) IssuerUsable(st *StampIssuer) bool { 143 cs := ps.postageStore.GetChainState() 144 145 // this checks at least threshold blocks are seen on the blockchain after 146 // the batch creation, before we start using a stamp issuer. The threshold 147 // is meant to allow enough time for upstream peers to see the batch and 148 // hence validate the stamps issued 149 if cs.Block < st.data.BlockNumber || (cs.Block-st.data.BlockNumber) < blockThreshold { 150 return false 151 } 152 return true 153 } 154 155 // GetStampIssuer finds a stamp issuer by batch ID. 156 func (ps *service) GetStampIssuer(batchID []byte) (*StampIssuer, func() error, error) { 157 ps.mtx.Lock() 158 defer ps.mtx.Unlock() 159 160 for _, st := range ps.issuers { 161 if bytes.Equal(batchID, st.data.BatchID) { 162 if !ps.IssuerUsable(st) { 163 return nil, nil, ErrNotUsable 164 } 165 return st, func() error { 166 ps.mtx.Lock() 167 defer ps.mtx.Unlock() 168 return ps.save(st) 169 }, nil 170 } 171 } 172 return nil, nil, ErrNotFound 173 } 174 175 // save persists the specified stamp issuer to the stamperstore. 176 func (ps *service) save(st *StampIssuer) error { 177 st.mtx.Lock() 178 defer st.mtx.Unlock() 179 180 if err := ps.store.Put(&StampIssuerItem{ 181 Issuer: st, 182 }); err != nil { 183 return err 184 } 185 return nil 186 } 187 188 func (ps *service) Close() error { 189 ps.mtx.Lock() 190 defer ps.mtx.Unlock() 191 var err error 192 for _, issuer := range ps.issuers { 193 err = errors.Join(err, ps.save(issuer)) 194 } 195 return err 196 } 197 198 // HandleStampExpiry handles stamp expiry for a given id. 199 func (ps *service) HandleStampExpiry(ctx context.Context, id []byte) error { 200 201 exists, err := ps.removeIssuer(id) 202 if err != nil { 203 return err 204 } 205 206 if exists { 207 return ps.removeStampItems(ctx, id) 208 } 209 210 return nil 211 } 212 213 // removeStampItems 214 func (ps *service) removeStampItems(ctx context.Context, batchID []byte) error { 215 216 ps.logger.Debug("removing expired stamp items", "batchID", hex.EncodeToString(batchID)) 217 218 deleteItemC := make(chan *StampItem) 219 go func() { 220 for item := range deleteItemC { 221 _ = ps.store.Delete(item) 222 } 223 }() 224 225 count := 0 226 227 defer func() { 228 close(deleteItemC) 229 ps.logger.Debug("removed expired stamps", "batchID", hex.EncodeToString(batchID), "count", count) 230 }() 231 232 return ps.store.Iterate( 233 storage.Query{ 234 Factory: func() storage.Item { return new(StampItem) }, 235 Prefix: string(batchID), 236 }, func(result storage.Result) (bool, error) { 237 select { 238 case deleteItemC <- result.Entry.(*StampItem): 239 case <-ctx.Done(): 240 return false, ctx.Err() 241 } 242 count++ 243 return false, nil 244 }) 245 } 246 247 // SetExpired removes all expired batches from the stamp issuers. 248 func (ps *service) removeIssuer(batchID []byte) (bool, error) { 249 ps.mtx.Lock() 250 defer ps.mtx.Unlock() 251 252 for i, issuer := range ps.issuers { 253 if bytes.Equal(batchID, issuer.data.BatchID) { 254 if err := ps.store.Delete(&StampIssuerItem{Issuer: issuer}); err != nil { 255 return true, fmt.Errorf("set expired: delete stamp data for batch %s: %w", hex.EncodeToString(issuer.ID()), err) 256 } 257 ps.issuers = append(ps.issuers[:i], ps.issuers[i+1:]...) 258 return true, nil 259 } 260 } 261 262 return false, nil 263 } 264 265 // add adds a stamp issuer to the active issuers and returns false if it is already present. 266 // Must be mutex locked before usage. 267 func (ps *service) add(st *StampIssuer) bool { 268 for _, v := range ps.issuers { 269 if bytes.Equal(st.data.BatchID, v.data.BatchID) { 270 return false 271 } 272 } 273 ps.issuers = append(ps.issuers, st) 274 return true 275 }