github.com/ethersphere/bee/v2@v2.2.0/pkg/status/status.go (about)

     1  // Copyright 2023 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 status
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  
    11  	"github.com/ethersphere/bee/v2/pkg/log"
    12  	"github.com/ethersphere/bee/v2/pkg/p2p"
    13  	"github.com/ethersphere/bee/v2/pkg/p2p/protobuf"
    14  	"github.com/ethersphere/bee/v2/pkg/postage"
    15  	"github.com/ethersphere/bee/v2/pkg/status/internal/pb"
    16  	"github.com/ethersphere/bee/v2/pkg/swarm"
    17  	"github.com/ethersphere/bee/v2/pkg/topology"
    18  )
    19  
    20  // loggerName is the tree path name of the logger for this package.
    21  const loggerName = "status"
    22  
    23  const (
    24  	protocolName    = "status"
    25  	protocolVersion = "1.1.1"
    26  	streamName      = "status"
    27  )
    28  
    29  // Snapshot is the current snapshot of the system.
    30  type Snapshot pb.Snapshot
    31  
    32  // SyncReporter defines the interface to report syncing rate.
    33  type SyncReporter interface {
    34  	SyncRate() float64
    35  }
    36  
    37  // Reserve defines the reserve storage related information required.
    38  type Reserve interface {
    39  	ReserveSize() int
    40  	ReserveSizeWithinRadius() uint64
    41  	StorageRadius() uint8
    42  }
    43  
    44  type topologyDriver interface {
    45  	topology.PeerIterator
    46  	IsReachable() bool
    47  }
    48  
    49  // Service is the status service.
    50  type Service struct {
    51  	logger         log.Logger
    52  	streamer       p2p.Streamer
    53  	topologyDriver topologyDriver
    54  
    55  	beeMode    string
    56  	reserve    Reserve
    57  	sync       SyncReporter
    58  	chainState postage.ChainStateGetter
    59  }
    60  
    61  // NewService creates a new status service.
    62  func NewService(
    63  	logger log.Logger,
    64  	streamer p2p.Streamer,
    65  	topology topologyDriver,
    66  	beeMode string,
    67  	chainState postage.ChainStateGetter,
    68  	reserve Reserve,
    69  ) *Service {
    70  	return &Service{
    71  		logger:         logger.WithName(loggerName).Register(),
    72  		streamer:       streamer,
    73  		topologyDriver: topology,
    74  		beeMode:        beeMode,
    75  		chainState:     chainState,
    76  		reserve:        reserve,
    77  	}
    78  }
    79  
    80  // LocalSnapshot returns the current status snapshot of this node.
    81  func (s *Service) LocalSnapshot() (*Snapshot, error) {
    82  	var (
    83  		storageRadius           uint8
    84  		syncRate                float64
    85  		reserveSize             uint64
    86  		reserveSizeWithinRadius uint64
    87  		connectedPeers          uint64
    88  		neighborhoodSize        uint64
    89  	)
    90  
    91  	if s.reserve != nil {
    92  		storageRadius = s.reserve.StorageRadius()
    93  		reserveSize = uint64(s.reserve.ReserveSize())
    94  		reserveSizeWithinRadius = s.reserve.ReserveSizeWithinRadius()
    95  	}
    96  
    97  	if s.sync != nil {
    98  		syncRate = s.sync.SyncRate()
    99  	}
   100  
   101  	commitment, err := s.chainState.Commitment()
   102  	if err != nil {
   103  		return nil, fmt.Errorf("batchstore commitment: %w", err)
   104  	}
   105  
   106  	err = s.topologyDriver.EachConnectedPeer(
   107  		func(_ swarm.Address, po uint8) (bool, bool, error) {
   108  			connectedPeers++
   109  			if po >= storageRadius {
   110  				neighborhoodSize++
   111  			}
   112  			return false, false, nil
   113  		},
   114  		topology.Select{},
   115  	)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("iterate connected peers: %w", err)
   118  	}
   119  
   120  	return &Snapshot{
   121  		BeeMode:                 s.beeMode,
   122  		ReserveSize:             reserveSize,
   123  		ReserveSizeWithinRadius: reserveSizeWithinRadius,
   124  		PullsyncRate:            syncRate,
   125  		StorageRadius:           uint32(storageRadius),
   126  		ConnectedPeers:          connectedPeers,
   127  		NeighborhoodSize:        neighborhoodSize + 1, // include self
   128  		BatchCommitment:         commitment,
   129  		IsReachable:             s.topologyDriver.IsReachable(),
   130  		LastSyncedBlock:         s.chainState.GetChainState().Block,
   131  	}, nil
   132  }
   133  
   134  // PeerSnapshot sends request for status snapshot to the peer.
   135  func (s *Service) PeerSnapshot(ctx context.Context, peer swarm.Address) (*Snapshot, error) {
   136  	stream, err := s.streamer.NewStream(ctx, peer, nil, protocolName, protocolVersion, streamName)
   137  	if err != nil {
   138  		return nil, fmt.Errorf("new stream: %w", err)
   139  	}
   140  	defer func() {
   141  		go stream.FullClose()
   142  	}()
   143  
   144  	w, r := protobuf.NewWriterAndReader(stream)
   145  
   146  	if err := w.WriteMsgWithContext(ctx, new(pb.Get)); err != nil {
   147  		return nil, fmt.Errorf("write message failed: %w", err)
   148  	}
   149  
   150  	ss := new(pb.Snapshot)
   151  	if err := r.ReadMsgWithContext(ctx, ss); err != nil {
   152  		return nil, fmt.Errorf("read message failed: %w", err)
   153  	}
   154  	return (*Snapshot)(ss), nil
   155  }
   156  
   157  // Protocol returns the protocol specification.
   158  func (s *Service) Protocol() p2p.ProtocolSpec {
   159  	return p2p.ProtocolSpec{
   160  		Name:    protocolName,
   161  		Version: protocolVersion,
   162  		StreamSpecs: []p2p.StreamSpec{{
   163  			Name:    streamName,
   164  			Handler: s.handler,
   165  		}},
   166  	}
   167  }
   168  
   169  // handler handles the status stream request/response.
   170  func (s *Service) handler(ctx context.Context, _ p2p.Peer, stream p2p.Stream) error {
   171  	loggerV2 := s.logger.V(2).Register()
   172  
   173  	w, r := protobuf.NewWriterAndReader(stream)
   174  	defer func() {
   175  		if err := stream.FullClose(); err != nil {
   176  			loggerV2.Debug("stream full close failed: %v", "error", err)
   177  		}
   178  	}()
   179  
   180  	var msgGet pb.Get
   181  	if err := r.ReadMsgWithContext(ctx, &msgGet); err != nil {
   182  		loggerV2.Debug("read message failed", "error", err)
   183  		return fmt.Errorf("read message: %w", err)
   184  	}
   185  
   186  	snapshot, err := s.LocalSnapshot()
   187  	if err != nil {
   188  		loggerV2.Debug("local snapshot failed", "error", err)
   189  		return fmt.Errorf("local snapshot: %w", err)
   190  	}
   191  
   192  	if err := w.WriteMsgWithContext(ctx, (*pb.Snapshot)(snapshot)); err != nil {
   193  		loggerV2.Debug("write message failed", "error", err)
   194  		return fmt.Errorf("write message: %w", err)
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func (s *Service) SetSync(sync SyncReporter) {
   201  	s.sync = sync
   202  }