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  }