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 }