github.com/amazechain/amc@v0.1.3/internal/sync/subscriber.go (about) 1 package sync 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/amazechain/amc/common/hexutil" 7 "github.com/amazechain/amc/common/types" 8 "github.com/amazechain/amc/internal/p2p" 9 "github.com/amazechain/amc/internal/p2p/peers" 10 "github.com/amazechain/amc/log" 11 "github.com/amazechain/amc/utils" 12 "github.com/holiman/uint256" 13 "runtime/debug" 14 "strings" 15 "time" 16 17 pubsub "github.com/libp2p/go-libp2p-pubsub" 18 "github.com/libp2p/go-libp2p/core/host" 19 "github.com/libp2p/go-libp2p/core/peer" 20 "go.opencensus.io/trace" 21 "google.golang.org/protobuf/proto" 22 ) 23 24 const pubsubMessageTimeout = 30 * time.Second 25 26 // wrappedVal represents a gossip validator which also returns an error along with the result. 27 type wrappedVal func(context.Context, peer.ID, *pubsub.Message) (pubsub.ValidationResult, error) 28 29 // subHandler represents handler for a given subscription. 30 type subHandler func(context.Context, proto.Message) error 31 32 // noopValidator is a no-op that only decodes the message, but does not check its contents. 33 func (s *Service) noopValidator(_ context.Context, _ peer.ID, msg *pubsub.Message) (pubsub.ValidationResult, error) { 34 m, err := s.decodePubsubMessage(msg) 35 if err != nil { 36 log.Debug("Could not decode message", "err", err) 37 return pubsub.ValidationReject, nil 38 } 39 msg.ValidatorData = m 40 return pubsub.ValidationAccept, nil 41 } 42 43 // Register PubSub subscribers 44 func (s *Service) registerSubscribers(digest [4]byte) { 45 s.subscribe( 46 p2p.BlockTopicFormat, 47 s.validateBlockPubSub, 48 s.blockSubscriber, 49 digest, 50 ) 51 //todo txs? 52 //s.subscribe( 53 // p2p.TransactionTopicFormat, 54 // digest, 55 //) 56 } 57 58 // subscribe to a given topic with a given validator and subscription handler. 59 // The base protobuf message is used to initialize new messages for decoding. 60 func (s *Service) subscribe(topic string, validator wrappedVal, handle subHandler, digest [4]byte) *pubsub.Subscription { 61 //todo 62 //genRoot := s.chain.GenesisBlock().Hash() 63 //_, e, err := utils.RetrieveForkDataFromDigest(digest, genRoot[:]) 64 //if err != nil { 65 // // Impossible condition as it would mean digest does not exist. 66 // panic(err) 67 //} 68 base := p2p.GossipTopicMappings(topic) 69 if base == nil { 70 // Impossible condition as it would mean topic does not exist. 71 panic(fmt.Sprintf("%s is not mapped to any message in GossipTopicMappings", topic)) 72 } 73 return s.subscribeWithBase(s.addDigestToTopic(topic, digest), validator, handle) 74 } 75 76 func (s *Service) subscribeWithBase(topic string, validator wrappedVal, handle subHandler) *pubsub.Subscription { 77 topic += s.cfg.p2p.Encoding().ProtocolSuffix() 78 //log := log.WithField("topic", topic) 79 80 // Do not resubscribe already seen subscriptions. 81 ok := s.subHandler.topicExists(topic) 82 if ok { 83 log.Debug(fmt.Sprintf("Provided topic already has an active subscription running: %s", topic), "topic", topic) 84 return nil 85 } 86 87 if err := s.cfg.p2p.PubSub().RegisterTopicValidator(s.wrapAndReportValidation(topic, validator)); err != nil { 88 log.Error("Could not register validator for topic", "topic", topic) 89 return nil 90 } 91 92 sub, err := s.cfg.p2p.SubscribeToTopic(topic) 93 if err != nil { 94 // Any error subscribing to a PubSub topic would be the result of a misconfiguration of 95 // libp2p PubSub library or a subscription request to a topic that fails to match the topic 96 // subscription filter. 97 log.Error("Could not subscribe topic", "topic", topic, "err", err) 98 return nil 99 } 100 s.subHandler.addTopic(sub.Topic(), sub) 101 102 // Pipeline decodes the incoming subscription data, runs the validation, and handles the 103 // message. 104 pipeline := func(msg *pubsub.Message) { 105 ctx, cancel := context.WithTimeout(s.ctx, pubsubMessageTimeout) 106 defer cancel() 107 ctx, span := trace.StartSpan(ctx, "sync.pubsub") 108 defer span.End() 109 110 defer func() { 111 if r := recover(); r != nil { 112 //tracing.AnnotateError(span, fmt.Errorf("panic occurred: %v", r)) 113 log.Error("Panic occurred", "err", r, "topic", topic) 114 debug.PrintStack() 115 } 116 }() 117 118 span.AddAttributes(trace.StringAttribute("topic", topic)) 119 120 if msg.ValidatorData == nil { 121 log.Error("Received nil message on pubsub") 122 messageFailedProcessingCounter.WithLabelValues(topic).Inc() 123 return 124 } 125 126 if err := handle(ctx, msg.ValidatorData.(proto.Message)); err != nil { 127 //tracing.AnnotateError(span, err) 128 log.Error("Could not handle p2p pubsub", "err", err, "topic", topic) 129 messageFailedProcessingCounter.WithLabelValues(topic).Inc() 130 return 131 } 132 } 133 134 // The main message loop for receiving incoming messages from this subscription. 135 messageLoop := func() { 136 for { 137 msg, err := sub.Next(s.ctx) 138 if err != nil { 139 // This should only happen when the context is cancelled or subscription is cancelled. 140 if err != pubsub.ErrSubscriptionCancelled { // Only log a warning on unexpected errors. 141 log.Warn("Subscription next failed", "err", err) 142 } 143 // Cancel subscription in the event of an error, as we are 144 // now exiting topic event loop. 145 sub.Cancel() 146 return 147 } 148 149 if msg.ReceivedFrom == s.cfg.p2p.PeerID() { 150 continue 151 } 152 153 go pipeline(msg) 154 } 155 } 156 157 go messageLoop() 158 log.Info("Subscribed to topic", "topic", topic) 159 return sub 160 } 161 162 // Wrap the pubsub validator with a metric monitoring function. This function increments the 163 // appropriate counter if the particular message fails to validate. 164 func (s *Service) wrapAndReportValidation(topic string, v wrappedVal) (string, pubsub.ValidatorEx) { 165 return topic, func(ctx context.Context, pid peer.ID, msg *pubsub.Message) (res pubsub.ValidationResult) { 166 defer s.handlePanic(ctx, msg) 167 res = pubsub.ValidationIgnore // Default: ignore any message that panics. 168 ctx, cancel := context.WithTimeout(ctx, pubsubMessageTimeout) 169 defer cancel() 170 messageReceivedCounter.WithLabelValues(topic).Inc() 171 if msg.Topic == nil { 172 messageFailedValidationCounter.WithLabelValues(topic).Inc() 173 return pubsub.ValidationReject 174 } 175 retDigest, err := p2p.ExtractGossipDigest(topic) 176 if err != nil { 177 log.Error(fmt.Sprintf("Invalid topic format of pubsub topic: %v", err), "topic", topic) 178 return pubsub.ValidationIgnore 179 } 180 currDigest, err := s.currentForkDigest() 181 if err != nil { 182 log.Error(fmt.Sprintf("Unable to retrieve fork data: %v", err), "topic", topic) 183 return pubsub.ValidationIgnore 184 } 185 if currDigest != retDigest { 186 log.Debug(fmt.Sprintf("Received message from outdated fork digest %#x", retDigest), "topic", topic) 187 return pubsub.ValidationIgnore 188 } 189 b, err := v(ctx, pid, msg) 190 191 var fields = make([]interface{}, 0) 192 fields = append(fields, "topic", topic) 193 fields = append(fields, "peer id", pid.String()) 194 fields = append(fields, "multiaddress", multiAddr(pid, s.cfg.p2p.Peers())) 195 fields = append(fields, "agent", agentString(pid, s.cfg.p2p.Host())) 196 fields = append(fields, "gossip score", s.cfg.p2p.Peers().Scorers().GossipScorer().Score(pid)) 197 198 if b == pubsub.ValidationReject { 199 if enableFullSSZDataLogging { 200 fields = append(fields, "message", hexutil.Encode(msg.Data)) 201 } 202 fields = append(fields, "err", err) 203 log.Debug("Gossip message was rejected", fields...) 204 messageFailedValidationCounter.WithLabelValues(topic).Inc() 205 } 206 if b == pubsub.ValidationIgnore { 207 if err != nil { 208 log.Debug("Gossip message was ignored", fields...) 209 } 210 messageIgnoredValidationCounter.WithLabelValues(topic).Inc() 211 } 212 return b 213 } 214 } 215 216 // revalidate that our currently connected subnets are valid. 217 func (s *Service) reValidateSubscriptions(subscriptions map[uint64]*pubsub.Subscription, 218 wantedSubs []uint64, topicFormat string, digest [4]byte) { 219 for k, v := range subscriptions { 220 var wanted bool 221 for _, idx := range wantedSubs { 222 if k == idx { 223 wanted = true 224 break 225 } 226 } 227 if !wanted && v != nil { 228 v.Cancel() 229 fullTopic := fmt.Sprintf(topicFormat, digest, k) + s.cfg.p2p.Encoding().ProtocolSuffix() 230 s.unSubscribeFromTopic(fullTopic) 231 delete(subscriptions, k) 232 } 233 } 234 } 235 236 func (s *Service) unSubscribeFromTopic(topic string) { 237 log.Debug("Unsubscribing from topic", "topic", topic) 238 if err := s.cfg.p2p.PubSub().UnregisterTopicValidator(topic); err != nil { 239 log.Error("Could not unregister topic validator", "err", err) 240 } 241 sub := s.subHandler.subForTopic(topic) 242 if sub != nil { 243 sub.Cancel() 244 } 245 s.subHandler.removeTopic(topic) 246 if err := s.cfg.p2p.LeaveTopic(topic); err != nil { 247 log.Error("Unable to leave topic", "err", err) 248 } 249 } 250 251 // Add fork digest to topic. 252 func (_ *Service) addDigestToTopic(topic string, digest [4]byte) string { 253 if !strings.Contains(topic, "%x") { 254 log.Crit("Topic does not have appropriate formatter for digest") 255 } 256 return fmt.Sprintf(topic, digest) 257 } 258 259 // Add the digest and index to subnet topic. 260 func (_ *Service) addDigestAndIndexToTopic(topic string, digest [4]byte, idx uint64) string { 261 if !strings.Contains(topic, "%x") { 262 log.Crit("Topic does not have appropriate formatter for digest") 263 } 264 return fmt.Sprintf(topic, digest, idx) 265 } 266 267 func (s *Service) currentForkDigest() ([4]byte, error) { 268 genRoot := s.cfg.chain.GenesisBlock().Header().Hash() 269 return utils.CreateForkDigest(s.cfg.chain.CurrentBlock().Number64(), genRoot) 270 } 271 272 // Checks if the provided digest matches up with the current supposed digest. 273 func isDigestValid(digest [4]byte, blockNr *uint256.Int, genValRoot types.Hash) (bool, error) { 274 retDigest, err := utils.CreateForkDigest(blockNr, genValRoot) 275 if err != nil { 276 return false, err 277 } 278 //isNextEpoch, err := utils.CreateForkDigest(new(uint256.Int).AddUint64(blockNr, 1), genValRoot) 279 //if err != nil { 280 // return false, err 281 //} 282 283 return retDigest == digest, nil 284 } 285 286 func agentString(pid peer.ID, hst host.Host) string { 287 agString := "" 288 ok := false 289 rawVersion, storeErr := hst.Peerstore().Get(pid, "AgentVersion") 290 agString, ok = rawVersion.(string) 291 if storeErr != nil || !ok { 292 agString = "" 293 } 294 return agString 295 } 296 297 func multiAddr(pid peer.ID, stat *peers.Status) string { 298 addrs, err := stat.Address(pid) 299 if err != nil || addrs == nil { 300 return "" 301 } 302 return addrs.String() 303 }