L’utilisation de libp2p dans op-stack

Écrit par Joohhnnn

Applications Libp2P dans Optimism

Dans cette section, l’objectif principal est d’expliquer comment Optimism utilise Libb2P pour compléter la mise en place de réseaux P2P dans Op-Node. Les réseaux P2P sont principalement utilisés pour faire passer des informations dans différents nœuds, tels que les séquenceurs après avoir terminé la construction de blocs de Unsafe, et se propager via le pub/sub du gossiphub de P2P. Libb2P s’occupe également d’autres infrastructures telles que la mise en réseau, l’adressage, etc. dans les réseaux P2P.

En savoir plus sur libp2p

libp2p (abréviation de « library peer-to-peer » ou « library peer-to-peer ») est un framework de mise en réseau peer-to-peer (P2P) qui aide à développer des applications P2P. Il contient un ensemble de protocoles, de spécifications et de bibliothèques qui facilitent la communication P2P entre les participants du réseau (également appelés « pairs » ou « pairs ») (source[2] [3])。 Faisant à l’origine partie du projet IPFS (InterPlanetary File), libp2p a évolué pour devenir un projet autonome qui est devenu une pile de réseau modulaire (source) pour les réseaux distribués )。

libp2p est un projet open source de la communauté IPFS qui accueille les contributions d’un large éventail de communautés, y compris l’aide à la rédaction de spécifications, à l’implémentation de code et à la création d’exemples et de tutoriels[4] [5])。 libp2p est composé de plusieurs blocs de construction, chacun avec une interface très claire, documentée et testée qui les rend composables, remplaçables et donc évolutifs )。 La nature modulaire de libp2p permet aux développeurs de sélectionner et d’utiliser uniquement les composants nécessaires à leurs applications, ce qui favorise la flexibilité et l’efficacité lors de la création d’applications Web P2P.

Ressources connexes

  • Documentation officielle pour libp2p[6]
  • le dépôt GitHub de libp2p[7]
  • Introduction à libp2p sur ProtoSchool[8]

L’architecture modulaire et la nature open source de libp2p fournissent un bon environnement pour le développement d’applications P2P puissantes, évolutives et flexibles, ce qui en fait un acteur important dans le domaine des réseaux distribués et du développement d’applications réseau.

Implémentation de libp2p

Lorsque vous utilisez libp2p, vous devrez implémenter et configurer certains composants de base pour construire votre réseau P2P. Voici quelques-uns des principaux aspects de l’implémentation de libp2p dans l’application :

1. Création et configuration des nœuds :

  • La création et la configuration d’un nœud libp2p est l’étape la plus élémentaire, qui inclut la définition de l’adresse réseau, de l’identité et d’autres paramètres de base du nœud. Codes d’utilisation clés :

libp2p. Nouveau()

2. Protocole de transport :

Choisissez et configurez vos protocoles de transport (par exemple, TCP, WebSockets, etc.) pour assurer la communication entre les nœuds. Codes d’utilisation clés :

tcpTransport := tcp. NewTCPTransport()

3. Multiplexage et contrôle de flux :

  • Implémenter le multiplexage pour permettre le traitement de plusieurs flux de données simultanés sur une seule connexion.
  • Mettre en œuvre un contrôle de flux pour gérer le taux de transmission des données et le taux de traitement. Codes d’utilisation clés :

yamuxTransport := yamux. Nouveau()

4. Sécurité et cryptage :

  • Configurer une couche de transport sécurisée pour assurer la sécurité et la confidentialité des communications.
  • Mettre en œuvre des mécanismes de cryptage et d’authentification pour protéger les données et authentifier les parties communicantes. Codes d’utilisation clés :

tlsTransport := tls. Nouveau()

5. Protocoles et gestion des messages :

Définissez et mettez en œuvre des protocoles personnalisés pour gérer des opérations réseau et des échanges de messages spécifiques.

  • Traiter les messages reçus et envoyer des réponses au besoin. Codes d’utilisation clés :

hôte. SetStreamHandler(« /mon-protocole/1.0.0 », myProtocolHandler)

6. Découverte et routage :

  • Implémenter un mécanisme de découverte de noeuds pour trouver d’autres noeuds dans le réseau.
  • Implémenter une logique de routage pour déterminer comment les messages sont acheminés vers les nœuds appropriés du réseau. Codes d’utilisation clés :

dht := kaddht. NewDHT(ctx, hôte, banque de données. NewMapDatastore())

7. Comportement et politiques du réseau :

Définissez et implémentez des comportements et des stratégies réseau, tels que la gestion des connexions, la gestion des erreurs et la logique de nouvelle tentative. Codes d’utilisation clés :

connManager := connmgr. NewConnManager(lowWater, highWater, gracePeriod)

8. Gestion de l’état et stockage :

Gérez l’état des nœuds et des réseaux, y compris l’état de la connexion, la liste des nœuds et le stockage des données. Codes d’utilisation clés :

peerstore := pstoremem. NewPeerstore()

9. Test et débogage :

  • Ecrire des tests pour votre application libp2p afin de s’assurer de son exactitude et de sa fiabilité.
  • Utilisez des outils de débogage et des journaux pour diagnostiquer et résoudre les problèmes de réseau. Codes d’utilisation clés :

exploitation forestière. SetLogLevel(« libp2p », « DEBUG »)

10. Documentation et soutien communautaire :

  • Consultez la documentation de libp2p pour en savoir plus sur ses différents composants et API.
  • Communiquer avec la communauté libp2p pour obtenir du soutien et résoudre des problèmes.

}

Ce qui précède sont quelques-uns des principaux aspects à prendre en compte et à implémenter lors de l’utilisation de libp2p. L’implémentation spécifique de chaque projet peut varier, mais ces aspects de base sont nécessaires pour construire et exécuter des applications libp2p. Lors de l’implémentation de ces fonctions, vous pouvez vous référer à la documentation officielle de libp2p[9] [10]et les dépôts GitHub Exemple de code et didacticiels.

Utilisation de libp2p dans le noeud OP

Afin de comprendre la relation entre op-node et libp2p, nous devons résoudre plusieurs questions

  • Pourquoi libp2p ? Pourquoi ne pas choisir devp2p (geth utilise devp2p)
  • Quelles données ou quels processus sont étroitement liés au réseau P2P dans le nœud OP
  • Comment ces fonctionnalités sont implémentées au niveau de la couche de code

Pourquoi les op-nodes ont besoin d’un réseau libp2p

Tout d’abord, nous devons comprendre pourquoi Optimism nécessite un réseau P2P ** LibbP2P est un protocole réseau modulaire qui permet aux développeurs de créer des applications peer-to-peer décentralisées pour de multiples cas d’utilisation (sources[11] [12])(la source [13])。 DevP2P, quant à lui, est principalement utilisé dans l’écosystème Ethereum et est adapté aux applications Ethereum (Source )。 La flexibilité et la large applicabilité de libp2p peuvent en faire un choix de premier ordre pour les développeurs.

Op-Node utilise principalement les points de fonction libp2p

  • Utilisé par le séquenceur pour transmettre le bloc non sécurisé résultant à d’autres nœuds non-séquenceurs
  • Pour les autres noeuds en mode non-séquenceur pour une synchronisation rapide en cas d’écart (synchronisation en chaîne inverse)
  • Un bon environnement pour l’adoption d’un système de réputation intégral pour réguler l’ensemble du nœud

Implémentation du code

Initialisation personnalisée de l’hôte

L’hôte peut être compris comme un nœud P2P, lors de l’ouverture de ce nœud, vous devez effectuer une configuration d’initialisation spéciale pour votre propre projet

Regardons maintenant la méthode Host dans le fichier op-node/p2p/host.go.

Cette fonction est principalement utilisée pour configurer l’hôte libp2p et effectuer diverses configurations. Voici les éléments clés de la fonction et une description chinoise simple de chacun d’entre eux :

  1. Vérifiez si le P2P est désactivé
    Si le P2P est désactivé, la fonction retourne directement.
  2. Obtenir l’ID de l’homologue à partir de la clé publique
    Utilisez la clé publique dans la configuration pour générer l’ID d’homologue.
  3. Initialiser Peerstore
    Créez une boutique Peerstore de base.
  4. Initialiser l’extension Peerstore
    En plus du Peerstore de base, créez un Peerstore étendu.
  5. Ajouter des clés privées et publiques à Peerstore
    Stockez les clés privées et publiques de l’homologue dans le magasin d’homologues.
  6. Initialiser la connexion
    Utilisé pour contrôler les connexions réseau.
  7. Initialiser le Gestionnaire de connexions
    Permet de gérer les connexions réseau.
  8. Définir l’adresse de transmission et d’écoute
    Définissez le protocole de transport réseau et l’adresse d’écoute de l’hôte.
  9. Créer un hôte libp2p
    Utilisez tous les paramètres précédents pour créer un nouvel hôte libp2p.
  10. Initialiser l’homologue statique
    Si vous avez configuré un homologue statique, initialisez-le.
  11. Retour à l’hôte
    Enfin, la fonction retourne l’hôte libp2p créé.

Ces sections clés sont responsables de l’initialisation et de la configuration de l’hôte libp2p, et chaque partie est responsable d’un aspect spécifique de la configuration de l’hôte.

func (conf *Config) Host(journal journal. Enregistreur, rapporteur de métriques. Rapporteur, métriques HostMetrics) (hôte. Hôte, erreur) {  
    Si conf. DésactiverP2P {  
        return nil, nil  
    }  
    pub := conf. Priv.GetPublic()  
    pid, err := homologue. IDFromCléPublique(pub)  
    if err != nil {  
        Retourner néant, fmt. Errorf(« Echec de dériver la clé pub à partir de la clé privée du réseau : %w », err)  
    }  
    basePs, err := pstoreds. NewPeerstore(contexte. Background(), conf. Magasin, pstockds. DefaultOpts())  
    if err != nil {  
        Retourner néant, fmt. Errorf(« échec de l’ouverture du peerstore : %w », err)  
    }  
    peerScoreParams := conf. PeerScoringParams()  
     scoreTemps de rétention. Durée  
    if peerScoreParams != nil {  
        Utilisez la même période de conservation que les commérages, le cas échéant.  
        scoreRetention = peerScoreParams.PeerScoring.RetainScore  
    } else {  
        Désactiver le score GC si le score des pairs est désactivé  
        scoreRetention = 0  
    }  
    ps, err := magasin. NewExtendedPeerstore(contexte. Background(), journal, horloge. Horloge, basePs, conf. Stocker, scoreRétention)  
    if err != nil {  
        Retourner néant, fmt. Errorf(« Echec de l’ouverture du peerstore étendu : %w », err)  
    }  
    if err := ps. AddPrivKey(pid, conf. Privé) ; err != nil {  
        Retourner néant, fmt. Errorf(« échec de la configuration du peerstore avec la clé priv : %w », err)  
    }  
    if err := ps. AddPubKey(pid, pub) ; err != nil {  
        Retourner néant, fmt. Errorf(« Echec de la configuration du peerstore avec la clé pub : %w », err)  
    }  
     connGtr gating. BlockingConnectionGater (en anglais seulement)  
    connGtr, err = gating. NewBlockingConnectionGater(conf. Magasin)  
    if err != nil {  
        Retourner néant, fmt. Errorf(« échec de l’ouverture du portail de connexion : %w », err)  
    }  
    connGtr = portail. AddBanExpiry(connGtr, ps, log, clock. Horloge, métriques)  
    connGtr = portail. AddMetering(connGtr, métriques)  
    connMngr, err := DefaultConnManager(conf)  
    if err != nil {  
        Retourner néant, fmt. Errorf(« Echec de l’ouverture du gestionnaire de connexions : %w », err)  
    }  
    listenAddr, err := addrFromIPAndPort(conf. ListenIP, conf. ListenTCPPort)  
    if err != nil {  
        Retourner néant, fmt. Errorf(« échec de l’écoute addr : %w », err)  
    }  
    tcpTransport := libp2p. Transports(  
        Tcp. NewTCPTransport,  
        Tcp. WithConnectionTimeout(heure. Minute*60)) // rompre les connexions inutilisées  
    TODO : techniquement, nous pouvons également exécuter le nœud sur websocket et les transports QUIC Peut-être à l’avenir ?  
     Nat Lconf. NATManagerC // désactivé si nul  
    Si conf. NAT {  
        nat = hôte de base. NewNATManager  
    }  
    opts := []libp2p. Option{  
        libp2p. Identité(conf. Priv),  
        Définir explicitement l’agent utilisateur, afin que nous puissions nous différencier des autres utilisateurs de Go libp2p.  
        libp2p. UserAgent(conf. UserAgent),  
        tcpTransport,  
        libp2p. WithDialTimeout(conf. TimeoutDial),  
        Pas de services de relais, connexions directes entre pairs uniquement.  
        libp2p. DisableRelay(),  
        L’hôte démarrera et écoutera le réseau directement après la construction à partir de la configuration.  
        libp2p. ListenAddrs(listenAddr),  
        libp2p. ConnectionGater(connGtr),  
        libp2p. ConnectionManager(connMngr),  
        libp2p. ResourceManager(nil), // TODO utilise l’interface du gestionnaire de ressources pour mieux gérer les ressources par pair.  
        libp2p. Ping(vrai),  
        libp2p. AutoNATServiceRateLimit(10, 5, heure. Deuxièmement*60),  
    }  
    Si conf. NoTransportSecurity {  
    } else {  
        opts = append(opts, conf. HostSecurity...)  
    }  
    h, err := libp2p. Nouveau(opts...)  
    if err != nil {  
    }  
    for i, peerAddr := range conf. HomologuesStatiques {  
        if err != nil {  
        }  
        staticPeers (Pairs statiques)[i]  = addr  
    }  
    out := &extraHost{  
        Animateur : h,  
        staticPeers : staticPeers,  
    }  
        sortir.monitorStaticPeers()  
    }  






        ...  
        n.gs, err = NewGossipSub(resourcesCtx, n.host, rollupCfg, setup, n.scorer, metrics, log)  
        if err != nil {  
        }  
        ...  
  1. Création de l’authentificateur : Génération de noms de thème de bloc :
  • Générez blocksTopicName à l’aide de la fonction blocksTopicV1, qui formate une chaîne basée sur le L2ChainID dans la configuration (cfg). Les chaînes formatées suivent une structure spécifique : /optimism/{L2ChainID}/0/blocks.

  • en appelant ps. Join(blocksTopicName) tente d’ajouter un thème de potins de bloc. Si une erreur se produit, un message d’erreur indique que la rubrique n’a pas pu être jointe.

  • Génération d’une nouvelle goroutine pour enregistrer les événements de rubrique à l’aide de la fonction LogTopicEvents.

  • Création d’un abonné à l’aide de la fonction MakeSubscriber, qui encapsule un BlocksHandler qui gère les événements OnUnsafeL2Payload à partir de gossipIn. Une nouvelle goroutine a été générée pour exécuter la subion fournie.

    func JoinGossip(p2pCtx context. Contexte, auto peer.ID, ps *pubsub. PubSub, journal de journal. Enregistreur, cfg *rollup. Config, runCfg GossipRuntimeConfig, gossipIn GossipIn) (GossipOut, error) {
    blocksTopicName := blocksTopicV1(cfg) // retourne fmt. Sprintf(« /optimisme/%s/0/blocs », cfg. L2ChainID.String())
    Retourner néant, fmt. Errorf(« Echec de l’enregistrement des blocs, sujet des potins : %w », euh)
    }
    blocksTopic, err := ps. Joindre(blocksTopicName)
    if err != nil {
    }
    if err != nil {
    }
    go LogTopicEvents(p2pCtx, log. Nouveau(« sujet », « blocs »), blocsÉvénementsSujet)
    if err != nil {
    }
    return &publisher{log : log, cfg : cfg, blocksTopic : blocksTopic, runCfg : runCfg}, nil

op-node/rollup/driver/state.go

func (s *Pilote) eventLoop() {  
    for(){  
        ...  
            charge utile, err := s.sequencer.RunNextSequencerAction(ctx)  
            if err != nil {  
                Les erreurs ne sont pas assez graves pour modifier ou arrêter le séquençage, mais elles doivent être consignées et mesurées.  
                if err := s.network.PublishL2Payload(ctx, charge utile) ; err != nil {  
            }  
            planSequencerAction() // planifie la prochaine action du séquenceur pour que le séquençage continue à tourner en boucle  
            }  
    }  
    ...  

Lorsqu’il y a des blocs manquants, synchronisation rapide via P2P

Lorsque les nœuds sont reliés après un temps d’arrêt en raison de circonstances particulières, telles que le relien après un temps d’arrêt, certains blocs (lacunes) peuvent ne pas être synchronisés, et lorsque vous rencontrez cette situation, ils peuvent rapidement se synchroniser via la chaîne inverse du réseau P2P.

Jetons un coup d’œil à la fonction checkForGapInUnsafeQueue dans op-node/rollup/driver/state.go

L’extrait de code définit une méthode appelée checkForGapInUnsafeQueue, qui appartient à la structure Driver. Son but est de vérifier les lacunes de données dans une file d’attente appelée « file d’attente non sécurisée » et de tenter de récupérer les charges utiles manquantes via une autre méthode de synchronisation appelée altSync. Le point clé ici est que la méthode consiste à assurer la continuité des données et à essayer de récupérer les données manquantes à partir d’autres méthodes de synchronisation lorsque des données manquantes sont détectées. Voici les principales étapes de la fonction :

  1. La fonction obtient d’abord UnsafeL2Head et UnsafeL2SyncTarget à partir de s.derivation en tant que points de début et de fin de la plage de vérification.

  2. La fonction vérifie les blocs manquants entre le début et la fin, ce qui se fait en comparant les valeurs numériques de fin et de début.

  3. Si un écart de données est détecté, la fonction demandera la plage de données manquante en appelant s.altSync.RequestL2Range(ctx, start, end). Si end est une référence nulle (c’est-à-dire eth. L2BlockRef{}), la fonction demandera une synchronisation de plage ouverte, en commençant par start.

  4. Lors de la demande de données, la fonction enregistre un journal de débogage indiquant la plage de données qu’elle demande.

  5. La fonction finit par renvoyer une valeur d’erreur. S’il n’y a pas d’erreurs, il renvoie nil

    checkForGapInUnsafeQueue vérifie s’il existe une lacune dans la file d’attente non sécurisée et tente de récupérer les charges utiles manquantes à partir d’une méthode alt-sync.
    ATTENTION : Il ne s’agit que d’un signal sortant, il n’est pas garanti que les blocs soient récupérés.
    Les résultats sont reçus par le biais de la charge utile OnUnsafeL2.
    func (s *Pilote) checkForGapInUnsafeQueue(ctx context. Contexte) erreur {
    start := s.derivation.UnsafeL2Head()
    end := s.derivation.UnsafeL2SyncTarget()
    Vérifiez si nous avons des blocs manquants entre le début et la fin Demandez-les si c’est le cas.
    si fin == (eth. L2BlockRef{}) {
    s.log.Debug(« demande de synchronisation avec plage ouverte », « start », start)
    return s.altSync.RequestL2Range(ctx, start, eth. L2BlockRef{})
    } sinon si fin. Nombre > départ. Nombre+1 {
    s.log.Debug(« demande de plage de blocs L2 non sécurisée manquante », « start », start, « end », end, « size », end. Nombre-début.Nombre)
    return s.altSync.RequestL2Range(ctx, début, fin)
    }
    Retour néant
    }

La fonction RequestL2Range signale le début et la fin du bloc de requêtes au canal de requêtes.

La requête est ensuite distribuée au canal peerRequests via la méthode onRangeRequest, et le canal peerRequests est attendu par la boucle ouverte par plusieurs pairs, c’est-à-dire qu’un seul homologue traitera la requête pour chaque distribution.

func (s *SyncClient) onRangeRequest(ctx context. Context, req rangeRequest) {  
        ...  
        pour i := uint64(0) ; ; i++ {  
        num := req.end.Number - 1 - i  
        if num <= req.start {  
            rendre  
        }  
        vérifier si nous avons déjà quelque chose en quarantaine  
        if h, ok := s.quarantineByNum[num] ; ok {  
            if s.trusted.Contains(h) { // si nous lui faisons confiance, essayez de le promouvoir.  
                s.tryPromote(h)  
            }  
            N’allez pas chercher des choses pour lesquelles nous avons déjà un candidat.  
            Nous l’expulserons de la quarantaine en trouvant un conflit, ou si nous synchronisons suffisamment d’autres blocs  
            continuer  
        }  
        if _, ok := s.inFlight[num] ; ok {  
            .log. Debug(« requête toujours en cours, ne replanifie pas la demande de synchronisation », « num », num)  
            continuer // demande toujours en cours  
        }  
        pr := peerRequest{num : num, complete : new(atomic. Booléen)}  
        .log. Debug(« Planification de la demande de blocage P2P », « num », num)  
        Numéro d’horaire  
        Sélectionnez {  
        case s.peerRequests <- pr :  
            s.inFlight[num]  = pr.complet  
        Affaire <-CTX. Fait() :  
            .log. Info(« n’a pas planifié la plage de synchronisation P2P complète », « current », num, « err », ctx. Err())  
            rendre  
        Par défaut : // Les pairs peuvent tous être déjà occupés à traiter les requêtes  
            .log. Info(« aucun homologue n’est prêt à gérer les demandes de blocage pour plus de requêtes P2P pour l’historique des blocs L2 », « current », num)  
            rendre  
        }  
    }  
}

Voyons ce qui se passe lorsque l’homologue reçoit cette demande.

La première chose que nous devons savoir est que le lien entre l’homologue et le nœud demandeur, ou le message transmis, est transmis par le flux libp2p. La méthode de traitement du flux est implémentée par le nœud homologue récepteur, et la création du flux est ouverte par le nœud émetteur.

Nous pouvons voir ce code dans la fonction d’initialisation précédente, où MakeStreamHandler renvoie un gestionnaire, et SetStreamHandler lie l’ID de protocole à ce gestionnaire, de sorte que chaque fois que le nœud émetteur crée et utilise ce flux, le gestionnaire retourné est déclenché.

n.syncSrv = NewReqRespServer(rollupCfg, l2Chain, métriques)  
Enregistrez le protocole de synchronisation avec l’hôte libp2p  
payloadByNumber := MakeStreamHandler(resourcesCtx, log. Nouveau(« servir », « payloads_by_number »), n.syncSrv.HandleSyncRequest)  
n.host.SetStreamHandler(PayloadByNumberProtocolID(rollupCfg.L2ChainID), payloadByNumber)

Voyons comment il est géré à l’intérieur du gestionnaire La fonction effectue d’abord des vérifications globales et individuelles de la limite de débit pour contrôler la rapidité avec laquelle les demandes sont traitées. Il lit et vérifie ensuite le numéro de bloc demandé, en s’assurant qu’il se situe dans une fourchette raisonnable. La fonction récupère ensuite la charge utile de bloc de la requête à partir de la couche L2 et l’écrit dans le flux de réponse. Lors de l’écriture des données de réponse, il définit une date limite d’écriture pour éviter d’être bloqué par des connexions homologues lentes pendant le processus d’écriture. Enfin, la fonction renvoie le numéro de bloc demandé et les erreurs possibles.

func (srv *ReqRespServer) handleSyncRequest(ctx context. Contexte, réseau de flux. Flux) (uint64, erreur) {  
    peerId := flux. Conn(). RemotePeer()  
    prendre un jeton du limiteur de débit global,  
    pour s’assurer qu’il n’y a pas trop de travail simultané sur le serveur entre différents homologues.  
    if err := srv.globalRequestsRL.Wait(ctx) ; err != nil {  
        Renvoie 0, fmt. Errorf(« Délai d’attente de la limite globale du débit de synchronisation : %w », err)  
    }  
    Rechercher les données de limitation de débit de l’homologue, ou ajouter autrement  
    srv.peerStatsLock.Lock()  
    ps, _ := srv.peerRateLimits.Get(peerId)  
    if ps == nil {  
        ps = &pairStat{  
            Demandes : tarif. NewLimiter(peerServerBlocksRateLimit, peerServerBlocksBurst),  
        }  
        srv.peerRateLimits.Add(peerId, ps)  
        Ps. Requests.Reserve() // compte l’accès, mais le fait retarder la requête suivante plutôt que d’attendre immédiatement  
    } else {  
        N’attendez que s’il s’agit d’un homologue existant, sinon l’appel d’attente de limite de débit instantané génère toujours des erreurs.  
        Si le demandeur pense que nous prenons trop de temps, c’est son problème et il peut se déconnecter.  
        Nous ne nous déconnecterons que lorsque nous ne parviendrons pas à lire/écrire,  
        si le travail n’est pas valide (validation de plage) ou lorsque des sous-tâches individuelles expirent.  
        if err := ps. Demandes.Attendre(ctx) ; err != nil {  
            Renvoie 0, fmt. Errorf(« Délai d’attente de la limite globale du débit de synchronisation : %w », err)  
        }  
    }  
    srv.peerStatsLock.Unlock()  
    Définir une date limite de lecture, le cas échéant  
    _ = flux. SetReadDeadline(heure. Maintenant(). Ajouter(serveurLectureRequestTimeout))  
    Lire la demande  
     req uint64  
    if err := binaire. Read(stream, binaire. LittleEndian, &req) ; err != nil {  
        Renvoie 0, fmt. Errorf(« Impossible de lire le numéro de bloc demandé : %w », err)  
    }  
    if err := flux. FermerLire() ; err != nil {  
        retour req, fmt. Errorf(« échec de la fermeture côté lecture d’un appel de demande de synchronisation P2P : %w », err)  
    }  
    Vérifiez que la demande se situe dans la plage de blocs attendue  
    if req < srv.cfg.Genesis.L2.Number {  
        retour req, fmt. Errorf(« Impossible de servir la demande pour le bloc L2 %d avant genesis %d : %w », req, srv.cfg.Genesis.L2.Number, invalidRequestErr)  
    }  
    max, err := srv.cfg.TargetBlockNumber(uint64(time. Maintenant(). Unix()))  
    if err != nil {  
        retour req, fmt. Errorf(« Impossible de déterminer le nombre maximal de blocs cibles pour vérifier la requête : %w », invalidRequestErr)  
    }  
    if req > max {  
        retour req, fmt. Errorf(« Impossible de servir la requête pour le bloc L2 %d après le bloc attendu max (%v) : %w », req, max, invalidRequestErr)  
    }  
    charge utile, err := srv.l2.PayloadByNumber(ctx, req)  
    if err != nil {  
        si des erreurs. C’est (euh, l’ethereum. NotFound) {  
            retour req, fmt. Errorf(« Bloc inconnu demandé par l’homologue par numéro : %w », err)  
        } else {  
            retour req, fmt. Errorf(« Echec de la récupération de la charge utile à servir à l’homologue : %w », err)  
        }  
    }  
    Nous définissons une date limite d’écriture, si elle est disponible, pour écrire en toute sécurité sans blocage sur une connexion homologue étranglée  
    _ = flux. SetWriteDeadline(heure. Maintenant(). Ajouter(serveurWriteChunkTimeout))  
    0 - resultCode : succès = 0  
    1 :5 - version : 0  
     .tmp [5] octet  
    if _, err := flux. Écrire(tmp[ :]) ; err != nil {  
        retour req, fmt. Errorf(« Echec de l’écriture des données d’en-tête de réponse : %w », err)  
    }  
    w := accrocheur. NewBufferedWriter(flux)  
    if _, err := charge utile. Maréchal SSZ(w) ; err != nil {  
        retour req, fmt. Errorf(« échec de l’écriture de la charge utile pour synchroniser la réponse : %w », err)  
    }  
    if err := w.Fermer() ; err != nil {  
        retour req, fmt. Errorf(« Echec de l’écriture de la charge utile pour synchroniser la réponse : %w », err)  
    }  
    return req, nil  
}

À ce stade, le processus général de demande et de traitement de synchronisation de la chaîne inverse a été expliqué

Système de réputation des points dans les nœuds p2p

Afin d’empêcher certains nœuds de faire des requêtes et des réponses malveillantes qui compromettent la sécurité de l’ensemble du réseau, Optimism utilise également un système de points.

Par exemple, dans op-node/p2p/app_scores.go, il existe une série de fonctions pour définir le score d’un pair

func (s *peerApplicationScorer) onValidResponse(id peer.ID) {  
    _, err := s.scorebook.SetScore(id, store. IncrementValidResponses{Cap : s.params.ValidResponseCap})  
    if err != nil {  
        s.log.Error(« Impossible de mettre à jour le score de l’homologue », « peer », id, « err », err)  
        rendre  
    }  
}  
func (s *peerApplicationScorer) onResponseError(id peer.ID) {  
    _, err := s.scorebook.SetScore(id, store. IncrementErrorResponses{Cap : s.params.ErrorResponseCap})  
    if err != nil {  
        s.log.Error(« Impossible de mettre à jour le score de l’homologue », « peer », id, « err », err)  
        rendre  
    }  
}  
func (s *peerApplicationScorer) onRejectedPayload(id peer.ID) {  
    _, err := s.scorebook.SetScore(id, store. IncrementRejectedPayloads{Cap : s.params.RejectedPayloadCap})  
    if err != nil {  
        s.log.Error(« Impossible de mettre à jour le score de l’homologue », « peer », id, « err », err)  
        rendre  
    }  
}

L’état d’intégration du nouveau nœud est ensuite vérifié avant son ajout

func AddScoring(gater BlockingConnectionGater, scores Scores, minScore float64) *ScoringConnectionGater {  
    return &ScoringConnectionGater{BlockingConnectionGater : gater, scores : scores, minScore : minScore}  
}  
func (g *ScoringConnectionGater) checkScore(p peer.ID) (allow bool) {  
    score, err := g.scores.GetPeerScore(p)  
    if err != nil {  
        Renvoie false  
    }  
    return score >= g.minScore  
}  
func (g *ScoringConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) {  
    return g.BlockingConnectionGater.InterceptPeerDial(p) & g.checkScore(p)  
}  
func (g *ScoringConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr. Multiaddr) (allow bool) {  
    return g.BlockingConnectionGater.InterceptAddrDial(id, ma) && g.checkScore(id)  
}  
func (g *ScoringConnectionGater) InterceptSecured(dir network. Direction, id peer.ID, mas réseau. ConnMultiaddrs) (allow bool) {  
    return g.BlockingConnectionGater.InterceptSecured(dir, id, mas) &g.checkScore(id)  
}

Résumé

La grande configurabilité de libp2p rend l’ensemble du projet p2p hautement personnalisable et modulaire, ce qui précède est la logique principale de l’implémentation personnalisée de libp2p d’optimsim, et d’autres détails peuvent être appris en détail en lisant le code source dans le répertoire p2p.

Voir l'original
Cette page peut inclure du contenu de tiers fourni à des fins d'information uniquement. Gate ne garantit ni l'exactitude ni la validité de ces contenus, n’endosse pas les opinions exprimées, et ne fournit aucun conseil financier ou professionnel à travers ces informations. Voir la section Avertissement pour plus de détails.
  • Récompense
  • Commentaire
  • Partager
Commentaire
0/400
Aucun commentaire
  • Épingler
Trader les cryptos partout et à tout moment
qrCode
Scan pour télécharger Gate app
Communauté
Français (Afrique)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)