github.com/ethersphere/bee/v2@v2.2.0/pkg/postage/batchservice/batchservice.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 batchservice
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"hash"
    14  	"math/big"
    15  
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethersphere/bee/v2/pkg/log"
    18  	"github.com/ethersphere/bee/v2/pkg/postage"
    19  	"github.com/ethersphere/bee/v2/pkg/storage"
    20  	"golang.org/x/crypto/sha3"
    21  )
    22  
    23  // loggerName is the tree path name of the logger for this package.
    24  const loggerName = "batchservice"
    25  
    26  const (
    27  	dirtyDBKey    = "batchservice_dirty_db"
    28  	checksumDBKey = "batchservice_checksum"
    29  )
    30  
    31  var ErrZeroValueBatch = errors.New("low balance batch")
    32  
    33  type batchService struct {
    34  	stateStore    storage.StateStorer
    35  	storer        postage.Storer
    36  	logger        log.Logger
    37  	listener      postage.Listener
    38  	owner         []byte
    39  	batchListener postage.BatchEventListener
    40  
    41  	checksum hash.Hash // checksum hasher
    42  	resync   bool
    43  }
    44  
    45  type Interface interface {
    46  	postage.EventUpdater
    47  }
    48  
    49  // New will create a new BatchService.
    50  func New(
    51  	stateStore storage.StateStorer,
    52  	storer postage.Storer,
    53  	logger log.Logger,
    54  	listener postage.Listener,
    55  	owner []byte,
    56  	batchListener postage.BatchEventListener,
    57  	checksumFunc func() hash.Hash,
    58  	resync bool,
    59  ) (Interface, error) {
    60  	if checksumFunc == nil {
    61  		checksumFunc = sha3.New256
    62  	}
    63  	var (
    64  		b   string
    65  		sum = checksumFunc()
    66  	)
    67  
    68  	dirty := false
    69  	err := stateStore.Get(dirtyDBKey, &dirty)
    70  	if err != nil && !errors.Is(err, storage.ErrNotFound) {
    71  		return nil, err
    72  	}
    73  
    74  	if resync {
    75  		if err := stateStore.Delete(checksumDBKey); err != nil {
    76  			return nil, err
    77  		}
    78  	} else if !dirty {
    79  		if err := stateStore.Get(checksumDBKey, &b); err != nil {
    80  			if !errors.Is(err, storage.ErrNotFound) {
    81  				return nil, err
    82  			}
    83  		} else {
    84  			s, err := hex.DecodeString(b)
    85  			if err != nil {
    86  				return nil, err
    87  			}
    88  			n, err := sum.Write(s)
    89  			if err != nil {
    90  				return nil, err
    91  			}
    92  			if n != len(s) {
    93  				return nil, errors.New("batchstore checksum init")
    94  			}
    95  		}
    96  	}
    97  
    98  	return &batchService{stateStore, storer, logger.WithName(loggerName).Register(), listener, owner, batchListener, sum, resync}, nil
    99  }
   100  
   101  // Create will create a new batch with the given ID, owner value and depth and
   102  // stores it in the BatchedStore.
   103  func (svc *batchService) Create(id, owner []byte, totalAmout, normalisedBalance *big.Int, depth, bucketDepth uint8, immutable bool, txHash common.Hash) error {
   104  	// don't add batches which have value which equals total cumulative
   105  	// payout or that are going to expire already within the next couple of blocks
   106  	val := big.NewInt(0).Add(svc.storer.GetChainState().TotalAmount, svc.storer.GetChainState().CurrentPrice)
   107  	if normalisedBalance.Cmp(val) <= 0 {
   108  		// don't do anything
   109  		return fmt.Errorf("batch service: batch %x: %w", id, ErrZeroValueBatch)
   110  	}
   111  	batch := &postage.Batch{
   112  		ID:          id,
   113  		Owner:       owner,
   114  		Value:       normalisedBalance,
   115  		Start:       svc.storer.GetChainState().Block,
   116  		Depth:       depth,
   117  		BucketDepth: bucketDepth,
   118  		Immutable:   immutable,
   119  	}
   120  
   121  	err := svc.storer.Save(batch)
   122  	if err != nil {
   123  		return fmt.Errorf("put: %w", err)
   124  	}
   125  
   126  	amount := big.NewInt(0).Div(totalAmout, big.NewInt(int64(1<<(batch.Depth))))
   127  
   128  	if bytes.Equal(svc.owner, owner) && svc.batchListener != nil {
   129  		if err := svc.batchListener.HandleCreate(batch, amount); err != nil {
   130  			return fmt.Errorf("create batch: %w", err)
   131  		}
   132  	}
   133  
   134  	cs, err := svc.updateChecksum(txHash)
   135  	if err != nil {
   136  		return fmt.Errorf("update checksum: %w", err)
   137  	}
   138  
   139  	svc.logger.Debug("batch created", "batch_id", hex.EncodeToString(batch.ID), "tx", txHash, "tx_checksum", cs)
   140  	return nil
   141  }
   142  
   143  // TopUp implements the EventUpdater interface. It tops ups a batch with the
   144  // given ID with the given amount.
   145  func (svc *batchService) TopUp(id []byte, totalAmout, normalisedBalance *big.Int, txHash common.Hash) error {
   146  	b, err := svc.storer.Get(id)
   147  	if err != nil {
   148  		return fmt.Errorf("get: %w", err)
   149  	}
   150  
   151  	err = svc.storer.Update(b, normalisedBalance, b.Depth)
   152  	if err != nil {
   153  		return fmt.Errorf("update: %w", err)
   154  	}
   155  
   156  	topUpAmount := big.NewInt(0).Div(totalAmout, big.NewInt(int64(1<<(b.Depth))))
   157  
   158  	if bytes.Equal(svc.owner, b.Owner) && svc.batchListener != nil {
   159  		svc.batchListener.HandleTopUp(id, topUpAmount)
   160  	}
   161  
   162  	cs, err := svc.updateChecksum(txHash)
   163  	if err != nil {
   164  		return fmt.Errorf("update checksum: %w", err)
   165  	}
   166  
   167  	svc.logger.Debug("topped up batch", "batch_id", hex.EncodeToString(b.ID), "old_value", b.Value, "new_value", normalisedBalance, "tx", txHash, "tx_checksum", cs)
   168  	return nil
   169  }
   170  
   171  // UpdateDepth implements the EventUpdater interface. It sets the new depth of a
   172  // batch with the given ID.
   173  func (svc *batchService) UpdateDepth(id []byte, depth uint8, normalisedBalance *big.Int, txHash common.Hash) error {
   174  	b, err := svc.storer.Get(id)
   175  	if err != nil {
   176  		return fmt.Errorf("get: %w", err)
   177  	}
   178  	err = svc.storer.Update(b, normalisedBalance, depth)
   179  	if err != nil {
   180  		return fmt.Errorf("put: %w", err)
   181  	}
   182  
   183  	if bytes.Equal(svc.owner, b.Owner) && svc.batchListener != nil {
   184  		svc.batchListener.HandleDepthIncrease(id, depth)
   185  	}
   186  
   187  	cs, err := svc.updateChecksum(txHash)
   188  	if err != nil {
   189  		return fmt.Errorf("update checksum: %w", err)
   190  	}
   191  
   192  	svc.logger.Debug("updated depth of batch", "batch_id", hex.EncodeToString(b.ID), "old_depth", b.Depth, "new_depth", depth, "tx", txHash, "tx_checksum", cs)
   193  	return nil
   194  }
   195  
   196  // UpdatePrice implements the EventUpdater interface. It sets the current
   197  // price from the chain in the service chain state.
   198  func (svc *batchService) UpdatePrice(price *big.Int, txHash common.Hash) error {
   199  	cs := svc.storer.GetChainState()
   200  	cs.CurrentPrice = price
   201  	if err := svc.storer.PutChainState(cs); err != nil {
   202  		return fmt.Errorf("put chain state: %w", err)
   203  	}
   204  
   205  	sum, err := svc.updateChecksum(txHash)
   206  	if err != nil {
   207  		return fmt.Errorf("update checksum: %w", err)
   208  	}
   209  
   210  	svc.logger.Debug("updated chain price", "new_price", price, "tx_hash", txHash, "tx_checksum", sum)
   211  	return nil
   212  }
   213  
   214  func (svc *batchService) UpdateBlockNumber(blockNumber uint64) error {
   215  	cs := svc.storer.GetChainState()
   216  	if blockNumber == cs.Block {
   217  		return nil
   218  	}
   219  	if blockNumber < cs.Block {
   220  		return fmt.Errorf("batch service: block number moved backwards from %d to %d", cs.Block, blockNumber)
   221  	}
   222  	diff := big.NewInt(0).SetUint64(blockNumber - cs.Block)
   223  
   224  	cs.TotalAmount.Add(cs.TotalAmount, diff.Mul(diff, cs.CurrentPrice))
   225  	cs.Block = blockNumber
   226  	if err := svc.storer.PutChainState(cs); err != nil {
   227  		return fmt.Errorf("put chain state: %w", err)
   228  	}
   229  
   230  	svc.logger.Debug("block height updated", "new_block", blockNumber)
   231  	return nil
   232  }
   233  func (svc *batchService) TransactionStart() error {
   234  	return svc.stateStore.Put(dirtyDBKey, true)
   235  }
   236  func (svc *batchService) TransactionEnd() error {
   237  	return svc.stateStore.Delete(dirtyDBKey)
   238  }
   239  
   240  var ErrInterruped = errors.New("postage sync interrupted")
   241  
   242  func (svc *batchService) Start(ctx context.Context, startBlock uint64, initState *postage.ChainSnapshot) (err error) {
   243  	dirty := false
   244  	err = svc.stateStore.Get(dirtyDBKey, &dirty)
   245  	if err != nil && !errors.Is(err, storage.ErrNotFound) {
   246  		return err
   247  	}
   248  
   249  	if dirty || svc.resync || initState != nil {
   250  
   251  		if dirty {
   252  			svc.logger.Warning("batch service: dirty shutdown detected, resetting batch store")
   253  		} else {
   254  			svc.logger.Warning("batch service: resync requested, resetting batch store")
   255  		}
   256  
   257  		if err := svc.storer.Reset(); err != nil {
   258  			return err
   259  		}
   260  		if err := svc.stateStore.Delete(dirtyDBKey); err != nil {
   261  			return err
   262  		}
   263  		svc.logger.Warning("batch service: batch store has been reset. your node will now resync chain data. this might take a while...")
   264  	}
   265  
   266  	cs := svc.storer.GetChainState()
   267  	if cs.Block > startBlock {
   268  		startBlock = cs.Block
   269  	}
   270  
   271  	if initState != nil && initState.LastBlockNumber > startBlock {
   272  		startBlock = initState.LastBlockNumber
   273  	}
   274  
   275  	syncedChan := svc.listener.Listen(ctx, startBlock+1, svc, initState)
   276  
   277  	return <-syncedChan
   278  }
   279  
   280  // updateChecksum updates the batchservice checksum once an event gets
   281  // processed. It swaps the existing checksum which is in the hasher
   282  // with the new checksum and persists it in the statestore.
   283  func (svc *batchService) updateChecksum(txHash common.Hash) (string, error) {
   284  	n, err := svc.checksum.Write(txHash.Bytes())
   285  	if err != nil {
   286  		return "", err
   287  	}
   288  	if l := len(txHash.Bytes()); l != n {
   289  		return "", fmt.Errorf("update checksum wrote %d bytes but want %d bytes", n, l)
   290  	}
   291  	s := svc.checksum.Sum(nil)
   292  	svc.checksum.Reset()
   293  	n, err = svc.checksum.Write(s)
   294  	if err != nil {
   295  		return "", err
   296  	}
   297  	if l := len(s); l != n {
   298  		return "", fmt.Errorf("swap checksum wrote %d bytes but want %d bytes", n, l)
   299  	}
   300  
   301  	b := hex.EncodeToString(s)
   302  
   303  	return b, svc.stateStore.Put(checksumDBKey, b)
   304  }