github.com/iotexproject/iotex-core@v1.14.1-rc1/nodeinfo/manager.go (about)

     1  // Copyright (c) 2022 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package nodeinfo
     7  
     8  import (
     9  	"context"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/iotexproject/go-pkgs/cache/lru"
    14  	"github.com/iotexproject/go-pkgs/crypto"
    15  	"github.com/iotexproject/go-pkgs/hash"
    16  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    17  	"github.com/libp2p/go-libp2p-core/peer"
    18  	"github.com/pkg/errors"
    19  	"github.com/prometheus/client_golang/prometheus"
    20  	"go.uber.org/zap"
    21  	"golang.org/x/exp/slices"
    22  	"google.golang.org/protobuf/proto"
    23  	"google.golang.org/protobuf/types/known/timestamppb"
    24  
    25  	"github.com/iotexproject/iotex-core/pkg/lifecycle"
    26  	"github.com/iotexproject/iotex-core/pkg/log"
    27  	"github.com/iotexproject/iotex-core/pkg/routine"
    28  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    29  	"github.com/iotexproject/iotex-core/pkg/version"
    30  )
    31  
    32  type (
    33  	transmitter interface {
    34  		BroadcastOutbound(context.Context, proto.Message) error
    35  		UnicastOutbound(context.Context, peer.AddrInfo, proto.Message) error
    36  		Info() (peer.AddrInfo, error)
    37  	}
    38  
    39  	chain interface {
    40  		TipHeight() uint64
    41  	}
    42  
    43  	// Info node infomation
    44  	Info struct {
    45  		Version   string
    46  		Height    uint64
    47  		Timestamp time.Time
    48  		Address   string
    49  		PeerID    string
    50  	}
    51  
    52  	// InfoManager manage delegate node info
    53  	InfoManager struct {
    54  		lifecycle.Lifecycle
    55  		version              string
    56  		address              string
    57  		broadcastList        atomic.Value // []string, whitelist to force enable broadcast
    58  		nodeMap              *lru.Cache
    59  		transmitter          transmitter
    60  		chain                chain
    61  		privKey              crypto.PrivateKey
    62  		getBroadcastListFunc getBroadcastListFunc
    63  	}
    64  
    65  	getBroadcastListFunc func() []string
    66  )
    67  
    68  var _nodeInfoHeightGauge = prometheus.NewGaugeVec(
    69  	prometheus.GaugeOpts{
    70  		Name: "iotex_node_info_height_gauge",
    71  		Help: "height info of node",
    72  	},
    73  	[]string{"address", "version"},
    74  )
    75  
    76  func init() {
    77  	prometheus.MustRegister(_nodeInfoHeightGauge)
    78  }
    79  
    80  // NewInfoManager new info manager
    81  func NewInfoManager(cfg *Config, t transmitter, ch chain, privKey crypto.PrivateKey, broadcastListFunc getBroadcastListFunc) *InfoManager {
    82  	dm := &InfoManager{
    83  		nodeMap:              lru.New(cfg.NodeMapSize),
    84  		transmitter:          t,
    85  		chain:                ch,
    86  		privKey:              privKey,
    87  		version:              version.PackageVersion,
    88  		address:              privKey.PublicKey().Address().String(),
    89  		getBroadcastListFunc: broadcastListFunc,
    90  	}
    91  	dm.broadcastList.Store([]string{})
    92  	// init recurring tasks
    93  	broadcastTask := routine.NewRecurringTask(func() {
    94  		// broadcastlist or nodes who are turned on will broadcast
    95  		if cfg.EnableBroadcastNodeInfo || dm.inBroadcastList() {
    96  			if err := dm.BroadcastNodeInfo(context.Background()); err != nil {
    97  				log.L().Error("nodeinfo manager broadcast node info failed", zap.Error(err))
    98  			}
    99  		} else {
   100  			log.L().Debug("nodeinfo manager general node disabled node info broadcast")
   101  		}
   102  	}, cfg.BroadcastNodeInfoInterval)
   103  	updateBroadcastListTask := routine.NewRecurringTask(func() {
   104  		dm.updateBroadcastList()
   105  	}, cfg.BroadcastListTTL)
   106  	dm.AddModels(updateBroadcastListTask, broadcastTask)
   107  	return dm
   108  }
   109  
   110  // Start start delegate broadcast task
   111  func (dm *InfoManager) Start(ctx context.Context) error {
   112  	dm.updateBroadcastList()
   113  	return dm.OnStart(ctx)
   114  }
   115  
   116  // Stop stop delegate broadcast task
   117  func (dm *InfoManager) Stop(ctx context.Context) error {
   118  	return dm.OnStop(ctx)
   119  }
   120  
   121  // HandleNodeInfo handle node info message
   122  func (dm *InfoManager) HandleNodeInfo(ctx context.Context, peerID string, msg *iotextypes.NodeInfo) {
   123  	log.L().Debug("nodeinfo manager handle node info")
   124  	// recover pubkey
   125  	hash := hashNodeInfo(msg.Info)
   126  	pubKey, err := crypto.RecoverPubkey(hash[:], msg.Signature)
   127  	if err != nil {
   128  		log.L().Warn("nodeinfo manager recover pubkey failed", zap.Error(err))
   129  		return
   130  	}
   131  	// verify signature
   132  	if addr := pubKey.Address().String(); addr != msg.Info.Address {
   133  		log.L().Warn("nodeinfo manager node info message verify failed", zap.String("expected", addr), zap.String("recieved", msg.Info.Address))
   134  		return
   135  	}
   136  
   137  	dm.updateNode(&Info{
   138  		Version:   msg.Info.Version,
   139  		Height:    msg.Info.Height,
   140  		Timestamp: msg.Info.Timestamp.AsTime(),
   141  		Address:   msg.Info.Address,
   142  		PeerID:    peerID,
   143  	})
   144  }
   145  
   146  // updateNode update node info
   147  func (dm *InfoManager) updateNode(node *Info) {
   148  	addr := node.Address
   149  	// update dm.nodeMap
   150  	dm.nodeMap.Add(addr, *node)
   151  	// update metric
   152  	_nodeInfoHeightGauge.WithLabelValues(addr, node.Version).Set(float64(node.Height))
   153  }
   154  
   155  // GetNodeInfo get node info by address
   156  func (dm *InfoManager) GetNodeInfo(addr string) (Info, bool) {
   157  	info, ok := dm.nodeMap.Get(addr)
   158  	if !ok {
   159  		return Info{}, false
   160  	}
   161  	return info.(Info), true
   162  }
   163  
   164  // BroadcastNodeInfo broadcast request node info message
   165  func (dm *InfoManager) BroadcastNodeInfo(ctx context.Context) error {
   166  	log.L().Debug("nodeinfo manager broadcast node info")
   167  	req, err := dm.genNodeInfoMsg()
   168  	if err != nil {
   169  		return err
   170  	}
   171  	// broadcast request meesage
   172  	if err := dm.transmitter.BroadcastOutbound(ctx, req); err != nil {
   173  		return err
   174  	}
   175  	// manually update self node info for broadcast message to myself will be ignored
   176  	peer, err := dm.transmitter.Info()
   177  	if err != nil {
   178  		return err
   179  	}
   180  	dm.updateNode(&Info{
   181  		Version:   req.Info.Version,
   182  		Height:    req.Info.Height,
   183  		Timestamp: req.Info.Timestamp.AsTime(),
   184  		Address:   req.Info.Address,
   185  		PeerID:    peer.ID.Pretty(),
   186  	})
   187  	return nil
   188  }
   189  
   190  // RequestSingleNodeInfoAsync unicast request node info message
   191  func (dm *InfoManager) RequestSingleNodeInfoAsync(ctx context.Context, peer peer.AddrInfo) error {
   192  	log.L().Debug("nodeinfo manager request one node info", zap.String("peer", peer.ID.Pretty()))
   193  	return dm.transmitter.UnicastOutbound(ctx, peer, &iotextypes.NodeInfoRequest{})
   194  }
   195  
   196  // HandleNodeInfoRequest tell node info to peer
   197  func (dm *InfoManager) HandleNodeInfoRequest(ctx context.Context, peer peer.AddrInfo) error {
   198  	log.L().Debug("nodeinfo manager tell node info", zap.Any("peer", peer.ID.Pretty()))
   199  	req, err := dm.genNodeInfoMsg()
   200  	if err != nil {
   201  		return err
   202  	}
   203  	return dm.transmitter.UnicastOutbound(ctx, peer, req)
   204  }
   205  
   206  func (dm *InfoManager) genNodeInfoMsg() (*iotextypes.NodeInfo, error) {
   207  	req := &iotextypes.NodeInfo{
   208  		Info: &iotextypes.NodeInfoCore{
   209  			Version:   dm.version,
   210  			Height:    dm.chain.TipHeight(),
   211  			Timestamp: timestamppb.Now(),
   212  			Address:   dm.address,
   213  		},
   214  	}
   215  	// add sig for msg
   216  	h := hashNodeInfo(req.Info)
   217  	sig, err := dm.privKey.Sign(h[:])
   218  	if err != nil {
   219  		return nil, errors.Wrap(err, "sign node info message failed")
   220  	}
   221  	req.Signature = sig
   222  	return req, nil
   223  }
   224  
   225  func (dm *InfoManager) inBroadcastList() bool {
   226  	return slices.Contains(dm.broadcastList.Load().([]string), dm.address)
   227  }
   228  
   229  func (dm *InfoManager) updateBroadcastList() {
   230  	if dm.getBroadcastListFunc != nil {
   231  		list := dm.getBroadcastListFunc()
   232  		dm.broadcastList.Store(list)
   233  		log.L().Debug("nodeinfo manaager updateBroadcastList", zap.Strings("list", list))
   234  	}
   235  }
   236  
   237  func hashNodeInfo(msg *iotextypes.NodeInfoCore) hash.Hash256 {
   238  	return hash.Hash256b(byteutil.Must(proto.Marshal(msg)))
   239  }