github.com/ethersphere/bee/v2@v2.2.0/pkg/settlement/pseudosettle/pseudosettle.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 pseudosettle 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "math/big" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/ethersphere/bee/v2/pkg/log" 17 "github.com/ethersphere/bee/v2/pkg/p2p" 18 "github.com/ethersphere/bee/v2/pkg/p2p/protobuf" 19 "github.com/ethersphere/bee/v2/pkg/settlement" 20 pb "github.com/ethersphere/bee/v2/pkg/settlement/pseudosettle/pb" 21 "github.com/ethersphere/bee/v2/pkg/storage" 22 "github.com/ethersphere/bee/v2/pkg/swarm" 23 ) 24 25 // loggerName is the tree path name of the logger for this package. 26 const loggerName = "pseudosettle" 27 28 const ( 29 protocolName = "pseudosettle" 30 protocolVersion = "1.0.0" 31 streamName = "pseudosettle" 32 ) 33 34 var ( 35 SettlementReceivedPrefix = "pseudosettle_total_received_" 36 SettlementSentPrefix = "pseudosettle_total_sent_" 37 38 ErrSettlementTooSoon = errors.New("settlement too soon") 39 ErrNoPseudoSettlePeer = errors.New("settlement peer not found") 40 ErrDisconnectAllowanceCheckFailed = errors.New("settlement allowance below enforced amount") 41 ErrTimeOutOfSyncAlleged = errors.New("settlement allowance timestamps from peer were decreasing") 42 ErrTimeOutOfSyncRecent = errors.New("settlement allowance timestamps from peer differed from our measurement by more than 2 seconds") 43 ErrTimeOutOfSyncInterval = errors.New("settlement allowance interval from peer differed from local interval by more than 3 seconds") 44 ErrRefreshmentBelowExpected = errors.New("refreshment below expected") 45 ErrRefreshmentAboveExpected = errors.New("refreshment above expected") 46 ) 47 48 type Service struct { 49 streamer p2p.Streamer 50 logger log.Logger 51 store storage.StateStorer 52 accounting settlement.Accounting 53 metrics metrics 54 refreshRate *big.Int 55 lightRefreshRate *big.Int 56 p2pService p2p.Service 57 timeNow func() time.Time 58 peersMu sync.Mutex 59 peers map[string]*pseudoSettlePeer 60 } 61 62 type pseudoSettlePeer struct { 63 lock sync.Mutex // lock to be held during receiving a payment from this peer 64 fullNode bool 65 } 66 67 type lastPayment struct { 68 Timestamp int64 69 CheckTimestamp int64 70 Total *big.Int 71 } 72 73 func New(streamer p2p.Streamer, logger log.Logger, store storage.StateStorer, accounting settlement.Accounting, refreshRate, lightRefreshRate *big.Int, p2pService p2p.Service) *Service { 74 return &Service{ 75 streamer: streamer, 76 logger: logger.WithName(loggerName).Register(), 77 metrics: newMetrics(), 78 store: store, 79 accounting: accounting, 80 p2pService: p2pService, 81 refreshRate: refreshRate, 82 lightRefreshRate: lightRefreshRate, 83 timeNow: time.Now, 84 peers: make(map[string]*pseudoSettlePeer), 85 } 86 } 87 88 func (s *Service) Protocol() p2p.ProtocolSpec { 89 return p2p.ProtocolSpec{ 90 Name: protocolName, 91 Version: protocolVersion, 92 StreamSpecs: []p2p.StreamSpec{ 93 { 94 Name: streamName, 95 Handler: s.handler, 96 }, 97 }, 98 ConnectIn: s.init, 99 ConnectOut: s.init, 100 DisconnectIn: s.terminate, 101 DisconnectOut: s.terminate, 102 } 103 } 104 105 func (s *Service) init(ctx context.Context, p p2p.Peer) error { 106 s.peersMu.Lock() 107 defer s.peersMu.Unlock() 108 109 _, ok := s.peers[p.Address.String()] 110 if !ok { 111 peerData := &pseudoSettlePeer{fullNode: p.FullNode} 112 s.peers[p.Address.String()] = peerData 113 } 114 115 go s.accounting.Connect(p.Address, p.FullNode) 116 return nil 117 } 118 119 func (s *Service) terminate(p p2p.Peer) error { 120 s.peersMu.Lock() 121 defer s.peersMu.Unlock() 122 123 delete(s.peers, p.Address.String()) 124 125 go s.accounting.Disconnect(p.Address) 126 return nil 127 } 128 129 func totalKey(peer swarm.Address, prefix string) string { 130 return fmt.Sprintf("%v%v", prefix, peer.String()) 131 } 132 133 func totalKeyPeer(key []byte, prefix string) (peer swarm.Address, err error) { 134 k := string(key) 135 136 split := strings.SplitAfter(k, prefix) 137 if len(split) != 2 { 138 return swarm.ZeroAddress, errors.New("no peer in key") 139 } 140 return swarm.ParseHexAddress(split[1]) 141 } 142 143 // peerAllowance computes the maximum incoming payment value we accept 144 // this is the time based allowance or the peers actual debt, whichever is less 145 func (s *Service) peerAllowance(peer swarm.Address, fullNode bool) (limit *big.Int, stamp int64, err error) { 146 var lastTime lastPayment 147 err = s.store.Get(totalKey(peer, SettlementReceivedPrefix), &lastTime) 148 if err != nil { 149 if !errors.Is(err, storage.ErrNotFound) { 150 return nil, 0, err 151 } 152 lastTime.Timestamp = int64(0) 153 } 154 155 currentTime := s.timeNow().Unix() 156 if currentTime == lastTime.Timestamp { 157 return nil, 0, ErrSettlementTooSoon 158 } 159 160 var refreshRateUsed *big.Int 161 162 if fullNode { 163 refreshRateUsed = s.refreshRate 164 } else { 165 refreshRateUsed = s.lightRefreshRate 166 } 167 168 maxAllowance := new(big.Int).Mul(big.NewInt(currentTime-lastTime.Timestamp), refreshRateUsed) 169 170 peerDebt, err := s.accounting.PeerDebt(peer) 171 if err != nil { 172 return nil, 0, err 173 } 174 175 if peerDebt.Cmp(maxAllowance) >= 0 { 176 return maxAllowance, currentTime, nil 177 } 178 179 return peerDebt, currentTime, nil 180 } 181 182 func (s *Service) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) (err error) { 183 loggerV1 := s.logger.V(1).Register() 184 185 w, r := protobuf.NewWriterAndReader(stream) 186 defer func() { 187 if err != nil { 188 _ = stream.Reset() 189 s.metrics.ReceivedPseudoSettlementsErrors.Inc() 190 } else { 191 stream.FullClose() 192 } 193 }() 194 var req pb.Payment 195 if err := r.ReadMsgWithContext(ctx, &req); err != nil { 196 return fmt.Errorf("read request from peer %v: %w", p.Address, err) 197 } 198 199 attemptedAmount := big.NewInt(0).SetBytes(req.Amount) 200 201 paymentAmount := new(big.Int).Set(attemptedAmount) 202 203 s.peersMu.Lock() 204 pseudoSettlePeer, ok := s.peers[p.Address.String()] 205 s.peersMu.Unlock() 206 if !ok { 207 return ErrNoPseudoSettlePeer 208 } 209 210 pseudoSettlePeer.lock.Lock() 211 defer pseudoSettlePeer.lock.Unlock() 212 213 allowance, timestamp, err := s.peerAllowance(p.Address, pseudoSettlePeer.fullNode) 214 if err != nil { 215 return err 216 } 217 218 if allowance.Cmp(attemptedAmount) < 0 { 219 paymentAmount.Set(allowance) 220 } 221 loggerV1.Debug("pseudosettle accepting payment message from peer", "peer_address", p.Address, "amount", paymentAmount) 222 223 if paymentAmount.Cmp(big.NewInt(0)) < 0 { 224 paymentAmount.Set(big.NewInt(0)) 225 } 226 227 err = w.WriteMsgWithContext(ctx, &pb.PaymentAck{ 228 Amount: paymentAmount.Bytes(), 229 Timestamp: timestamp, 230 }) 231 if err != nil { 232 return err 233 } 234 235 var lastTime lastPayment 236 err = s.store.Get(totalKey(p.Address, SettlementReceivedPrefix), &lastTime) 237 if err != nil { 238 if !errors.Is(err, storage.ErrNotFound) { 239 return err 240 } 241 lastTime.Total = big.NewInt(0) 242 } 243 244 lastTime.Total = lastTime.Total.Add(lastTime.Total, paymentAmount) 245 lastTime.Timestamp = timestamp 246 247 err = s.store.Put(totalKey(p.Address, SettlementReceivedPrefix), lastTime) 248 if err != nil { 249 return err 250 } 251 252 receivedPaymentF64, _ := big.NewFloat(0).SetInt(paymentAmount).Float64() 253 s.metrics.TotalReceivedPseudoSettlements.Add(receivedPaymentF64) 254 s.metrics.ReceivedPseudoSettlements.Inc() 255 return s.accounting.NotifyRefreshmentReceived(p.Address, paymentAmount, timestamp) 256 } 257 258 // Pay initiates a payment to the given peer 259 func (s *Service) Pay(ctx context.Context, peer swarm.Address, amount *big.Int) { 260 loggerV1 := s.logger.V(1).Register() 261 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 262 defer cancel() 263 264 var err error 265 266 defer func() { 267 if err != nil { 268 s.metrics.SentPseudoSettlementsErrors.Inc() 269 } 270 }() 271 272 var lastTime lastPayment 273 err = s.store.Get(totalKey(peer, SettlementSentPrefix), &lastTime) 274 if err != nil { 275 if !errors.Is(err, storage.ErrNotFound) { 276 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err) 277 return 278 } 279 lastTime.Total = big.NewInt(0) 280 lastTime.Timestamp = 0 281 } 282 283 stream, err := s.streamer.NewStream(ctx, peer, nil, protocolName, protocolVersion, streamName) 284 if err != nil { 285 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err) 286 return 287 } 288 defer func() { 289 if err != nil { 290 _ = stream.Reset() 291 } else { 292 _ = stream.FullClose() 293 } 294 }() 295 296 loggerV1.Debug("pseudosettle sending payment message to peer", "peer_address", peer, "amount", amount) 297 w, r := protobuf.NewWriterAndReader(stream) 298 299 err = w.WriteMsgWithContext(ctx, &pb.Payment{ 300 Amount: amount.Bytes(), 301 }) 302 if err != nil { 303 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err) 304 return 305 } 306 307 var paymentAck pb.PaymentAck 308 err = r.ReadMsgWithContext(ctx, &paymentAck) 309 if err != nil { 310 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err) 311 return 312 } 313 314 checkTime := s.timeNow().UnixMilli() 315 316 acceptedAmount := new(big.Int).SetBytes(paymentAck.Amount) 317 if acceptedAmount.Cmp(amount) > 0 { 318 err = fmt.Errorf("pseudosettle: peer %v: %w", peer, ErrRefreshmentAboveExpected) 319 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err) 320 return 321 } 322 323 experiencedInterval := checkTime/1000 - lastTime.CheckTimestamp 324 allegedInterval := paymentAck.Timestamp - lastTime.Timestamp 325 326 if allegedInterval < 0 { 327 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, ErrTimeOutOfSyncAlleged) 328 return 329 } 330 331 experienceDifferenceRecent := paymentAck.Timestamp - checkTime/1000 332 333 if experienceDifferenceRecent < -2 || experienceDifferenceRecent > 2 { 334 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, ErrTimeOutOfSyncRecent) 335 return 336 } 337 338 experienceDifferenceInterval := experiencedInterval - allegedInterval 339 if experienceDifferenceInterval < -3 || experienceDifferenceInterval > 3 { 340 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, ErrTimeOutOfSyncInterval) 341 return 342 } 343 344 lastTime.Total = lastTime.Total.Add(lastTime.Total, acceptedAmount) 345 lastTime.Timestamp = paymentAck.Timestamp 346 lastTime.CheckTimestamp = checkTime / 1000 347 348 err = s.store.Put(totalKey(peer, SettlementSentPrefix), lastTime) 349 if err != nil { 350 s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err) 351 return 352 } 353 354 amountFloat, _ := new(big.Float).SetInt(acceptedAmount).Float64() 355 s.metrics.TotalSentPseudoSettlements.Add(amountFloat) 356 s.metrics.SentPseudoSettlements.Inc() 357 358 s.accounting.NotifyRefreshmentSent(peer, amount, acceptedAmount, checkTime, allegedInterval, nil) 359 } 360 361 func (s *Service) SetAccounting(accounting settlement.Accounting) { 362 s.accounting = accounting 363 } 364 365 // TotalSent returns the total amount sent to a peer 366 func (s *Service) TotalSent(peer swarm.Address) (totalSent *big.Int, err error) { 367 var lastTime lastPayment 368 369 err = s.store.Get(totalKey(peer, SettlementSentPrefix), &lastTime) 370 if err != nil { 371 if !errors.Is(err, storage.ErrNotFound) { 372 return nil, settlement.ErrPeerNoSettlements 373 } 374 lastTime.Total = big.NewInt(0) 375 } 376 377 return lastTime.Total, nil 378 } 379 380 // TotalReceived returns the total amount received from a peer 381 func (s *Service) TotalReceived(peer swarm.Address) (totalReceived *big.Int, err error) { 382 var lastTime lastPayment 383 384 err = s.store.Get(totalKey(peer, SettlementReceivedPrefix), &lastTime) 385 if err != nil { 386 if !errors.Is(err, storage.ErrNotFound) { 387 return nil, settlement.ErrPeerNoSettlements 388 } 389 lastTime.Total = big.NewInt(0) 390 } 391 392 return lastTime.Total, nil 393 } 394 395 // SettlementsSent returns all stored sent settlement values for a given type of prefix 396 func (s *Service) SettlementsSent() (map[string]*big.Int, error) { 397 sent := make(map[string]*big.Int) 398 err := s.store.Iterate(SettlementSentPrefix, func(key, val []byte) (stop bool, err error) { 399 addr, err := totalKeyPeer(key, SettlementSentPrefix) 400 if err != nil { 401 return false, fmt.Errorf("parse address from key: %s: %w", string(key), err) 402 } 403 if _, ok := sent[addr.String()]; !ok { 404 var storevalue lastPayment 405 err = s.store.Get(totalKey(addr, SettlementSentPrefix), &storevalue) 406 if err != nil { 407 return false, fmt.Errorf("get peer %s settlement balance: %w", addr.String(), err) 408 } 409 410 sent[addr.String()] = storevalue.Total 411 } 412 return false, nil 413 }) 414 if err != nil { 415 return nil, err 416 } 417 return sent, nil 418 } 419 420 // SettlementsReceived returns all stored received settlement values for a given type of prefix 421 func (s *Service) SettlementsReceived() (map[string]*big.Int, error) { 422 received := make(map[string]*big.Int) 423 err := s.store.Iterate(SettlementReceivedPrefix, func(key, val []byte) (stop bool, err error) { 424 addr, err := totalKeyPeer(key, SettlementReceivedPrefix) 425 if err != nil { 426 return false, fmt.Errorf("parse address from key: %s: %w", string(key), err) 427 } 428 if _, ok := received[addr.String()]; !ok { 429 var storevalue lastPayment 430 err = s.store.Get(totalKey(addr, SettlementReceivedPrefix), &storevalue) 431 if err != nil { 432 return false, fmt.Errorf("get peer %s settlement balance: %w", addr.String(), err) 433 } 434 435 received[addr.String()] = storevalue.Total 436 } 437 return false, nil 438 }) 439 if err != nil { 440 return nil, err 441 } 442 return received, nil 443 }