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 }