O uso de libp2p em op-stack

Escrito por Joohhnnn

Aplicações Libp2P em Otimismo

Nesta seção, o principal objetivo é explicar como a Optimism usa o Libb2P para concluir o estabelecimento de redes P2P em Op-Node. As redes P2P são usadas principalmente para passar informações em diferentes nós, como sequenciadores depois de completar a construção do bloco de Unsafe, e propagando-se através do pub/sub do gossiphub do P2P. Libb2P também lida com outras infraestruturas, como redes, endereçamento, etc. em redes P2P.

Saiba mais sobre libp2p

libp2p (abreviado de "biblioteca peer-to-peer" ou "biblioteca peer-to-peer") é uma estrutura de rede peer-to-peer (P2P) que ajuda a desenvolver aplicativos P2P. Ele contém um conjunto de protocolos, especificações e bibliotecas que facilitam a comunicação P2P entre os participantes da rede (também conhecidos como "pares" ou "pares") (fonte[2] [3])。 Originalmente parte do projeto IPFS (InterPlanetary File), o libp2p evoluiu para um projeto independente que se tornou uma pilha de rede modular (fonte) para redes distribuídas )。

libp2p é um projeto de código aberto da comunidade IPFS que acolhe contribuições de uma ampla gama de comunidades, incluindo ajudar a escrever especificações, implementações de código e criar exemplos e tutoriais[4] [5])。 libp2p é composto de vários blocos de construção, cada um com uma interface muito clara, documentada e testada que os torna composable, substituíveis e, portanto, atualizáveis )。 A natureza modular da libp2p permite que os desenvolvedores selecionem e usem apenas os componentes necessários para suas aplicações, promovendo flexibilidade e eficiência ao construir aplicações web P2P.

Recursos relacionados

  • Documentação oficial para libp2p[6]
  • repositório GitHub da libp2p[7]
  • Introdução ao libp2p na ProtoSchool[8]

A arquitetura modular e a natureza de código aberto da libp2p fornecem um bom ambiente para o desenvolvimento de aplicações P2P poderosas, escaláveis e flexíveis, tornando-a um ator importante no campo das redes distribuídas e do desenvolvimento de aplicações de rede.

Implementação libp2p

Ao usar libp2p, você precisará implementar e configurar alguns componentes principais para construir sua rede P2P. Aqui estão alguns dos principais aspetos de implementação da libp2p na aplicação:

1. Criação e configuração de nós:

  • Criar e configurar um nó libp2p é a etapa mais básica, que inclui a definição do endereço de rede, identidade e outros parâmetros básicos do nó. Códigos de utilização das chaves:

libp2p. Novo()

2. Protocolo de Transporte:

Escolha e configure seus protocolos de transporte (por exemplo, TCP, WebSockets, etc.) para garantir a comunicação entre nós. Códigos de utilização das chaves:

tcpTransport := tcp. NewTCPTransport()

3. Multiplexação e Controlo de Fluxo:

  • Implementar a multiplexação para permitir que vários fluxos de dados simultâneos sejam processados em uma única conexão.
  • Implementar controle de fluxo para gerenciar a taxa de transmissão de dados e taxa de processamento. Códigos de utilização das chaves:

yamuxTransport := yamux. Novo()

4. Segurança e encriptação:

  • Configurar uma camada de transporte segura para garantir a segurança e privacidade das comunicações.
  • Implementar mecanismos de encriptação e autenticação para proteger os dados e autenticar as partes comunicantes. Códigos de utilização das chaves:

tlsTransport := tls. Novo()

5. Protocolos e Tratamento de Mensagens:

Definir e implementar protocolos personalizados para lidar com operações de rede específicas e trocas de mensagens.

  • Processar mensagens recebidas e enviar respostas conforme necessário. Códigos de utilização das chaves:

anfitrião. SetStreamHandler("/my-protocol/1.0.0", myProtocolHandler)

6. Descoberta e roteamento:

  • Implementar um mecanismo de descoberta de nós para encontrar outros nós na rede.
  • Implementar lógica de roteamento para determinar como as mensagens são roteadas para os nós corretos na rede. Códigos de utilização das chaves:

dht := kaddht. NewDHT(ctx, host, armazenamento de dados. NewMapDatastore())

7. Comportamento e políticas de rede:

Defina e implemente comportamentos e políticas de rede, como gerenciamento de conexões, tratamento de erros e lógica de novas tentativas. Códigos de utilização das chaves:

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

8. Gestão e Armazenamento do Estado:

Gerencie o status de nós e redes, incluindo status de conexão, lista de nós e armazenamento de dados. Códigos de utilização das chaves:

peerstore := pstoremem. NewPeerstore()

9. Teste e depuração:

  • Escreva testes para a sua aplicação libp2p para garantir a sua correção e fiabilidade.
  • Use ferramentas de depuração e logs para diagnosticar e resolver problemas de rede. Códigos de utilização das chaves:

exploração madeireira. SetLogLevel("libp2p", "DEPURAR")

10. Documentação e Suporte da Comunidade:

  • Consulte a documentação do libp2p para saber mais sobre seus vários componentes e APIs.
  • Comunicar com a comunidade libp2p para apoio e resolução de problemas.

}

O acima são alguns dos principais aspetos a considerar e implementar ao usar libp2p. A implementação específica de cada projeto pode variar, mas esses aspetos básicos são necessários para construir e executar aplicativos libp2p. Ao implementar essas funções, você pode consultar a documentação oficial do libp2p[9] [10]e repositórios GitHub Código de exemplo e tutoriais.

Uso de libp2p no nó OP

A fim de descobrir a relação entre op-node e libp2p, temos que descobrir várias perguntas

  • Porquê libp2p? Por que não escolher devp2p (geth usa devp2p)
  • Que dados ou processos estão intimamente relacionados com a rede P2P no nó OP
  • Como esses recursos são implementados na camada de código

Por que os nós operacionais precisam de uma rede libp2p

Primeiro precisamos entender por que o Optimism requer uma rede P2P ** LibbP2P é um protocolo de rede modular que permite aos desenvolvedores construir aplicativos peer-to-peer descentralizados para vários casos de uso (fontes[11] [12])(fonte [13])。 O DevP2P, por outro lado, é usado principalmente no ecossistema Ethereum e é adaptado para aplicações Ethereum (Fonte )。 A flexibilidade e a ampla aplicabilidade do libp2p podem torná-lo uma excelente escolha para desenvolvedores.

Op-Node usa principalmente pontos de função libp2p

  • Usado pelo sequenciador para passar o bloco inseguro resultante para outros nós não sequenciadores
  • Para outros nós no modo não-sequenciador para sincronização rápida quando ocorre uma lacuna (sincronização de cadeia reversa)
  • Um bom ambiente para a adoção de um sistema de reputação integral para regular o nó geral

Implementação de código

inicialização personalizada do host

Host pode ser entendido como um nó P2P, ao abrir este nó, você precisa fazer alguma configuração de inicialização especial para o seu próprio projeto

Agora vamos examinar o método Host no arquivo op-node/p2p/host.go.

Esta função é usada principalmente para configurar o host libp2p e fazer várias configurações. Aqui estão as partes-chave da função e uma descrição chinesa simples de cada uma:

  1. Verifique se o P2P está desativado
    Se P2P estiver desativado, a função retornará diretamente.
  2. Obter ID de Peer da Chave Pública
    Use a chave pública na configuração para gerar o ID de mesmo nível.
  3. Inicializar Peerstore
    Crie um repositório Peerstore base.
  4. Inicialize a extensão Peerstore
    Além do Peerstore base, crie um Peerstore estendido.
  5. Adicione chaves privadas e públicas ao Peerstore
    Armazene as chaves pública e privada do peer no peerstore.
  6. Inicializar Gater de Conexão
    Usado para controlar conexões de rede.
  7. Inicializar o Gerenciador de Conexões
    Usado para gerenciar conexões de rede.
  8. Definir endereço de transmissão e audição
    Defina o protocolo de transporte de rede e o endereço de escuta do host.
  9. Criar host libp2p
    Use todas as configurações anteriores para criar um novo host libp2p.
  10. Inicializar par estático
    Se você tiver um par estático configurado, inicialize-o.
  11. Voltar ao Anfitrião
    Finalmente, a função retorna o host libp2p criado.

Essas seções principais são responsáveis por inicializar e configurar o host libp2p, e cada parte é responsável por um aspeto específico da configuração do host.

func (conf *Config) Host (log log. Logger, métricas do repórter. Reporter, métricas HostMetrics) (host. Host, erro) {  
    se conf. DisableP2P {  
        retorno nulo, nulo  
    }  
    pub := conf. Priv.GetPublic()  
    pid, err := peer. IDFromPublicKey(pub)  
    if err != nil {  
        retorno nulo, fmt. Errorf("falha ao derivar pubkey da chave priv de rede: %w", err)  
    }  
    basePs, err := pstoreds. NewPeerstore(contexto. Antecedentes(), conf. Loja, pstoreds. DefaultOpts())  
    if err != nil {  
        retorno nulo, fmt. Errorf("falha ao abrir peerstore: %w", err)  
    }  
    peerScoreParams := conf. PeerScoringParams()  
     scoreTempo de retenção. Duração  
    if peerScoreParams != nil {  
        Use o mesmo período de retenção que a fofoca, se disponível  
        scoreRetention = peerScoreParams.PeerScoring.RetainScore  
    } else {  
        Desativar a pontuação GC se a pontuação por pares estiver desativada  
        scoreRetention = 0  
    }  
    ps, err := loja. NewExtendedPeerstore(contexto. Background(), log, relógio. Relógio, basePs, conf. Loja, scoreRetention)  
    if err != nil {  
        retorno nulo, fmt. Errorf("falha ao abrir peerstore estendido: %w", erro)  
    }  
    se errar := ps. AddPrivKey(pid, conf. Priv); err != nil {  
        retorno nulo, fmt. Errorf("falha ao configurar peerstore com chave priv: %w", err)  
    }  
    se errar := ps. AddPubKey (pid, pub); err != nil {  
        retorno nulo, fmt. Errorf("falha ao configurar peerstore com chave pub: %w", err)  
    }  
     nGtr datação. BlockingConnectionGater  
    connGtr, err = datação. NewBlockingConnectionGater(conf. Loja)  
    if err != nil {  
        retorno nulo, fmt. Errorf("falha ao abrir o gater de conexão: %w", erro)  
    }  
    connGtr = datação. AddBanExpiry(connGtr, ps, log, relógio. Relógio, métricas)  
    connGtr = datação. AddMetering(connGtr, métricas)  
    connMngr, err := DefaultConnManager(conf)  
    if err != nil {  
        retorno nulo, fmt. Errorf("falha ao abrir o gerenciador de conexões: %w", erro)  
    }  
    listenAddr, err := addrFromIPAndPort(conf. ListenIP, conf. ListenTCPPort)  
    if err != nil {  
        retorno nulo, fmt. Errorf("falha ao fazer ouvir addr: %w", err)  
    }  
    tcpTransport := libp2p. Transportes(  
        TCP. NewTCPTransport,  
        TCP. WithConnectionTimeout(tempo. Minuto*60)) // quebrar ligações não utilizadas  
    TODO: tecnicamente também podemos executar o nó em transportes websocket e QUIC Talvez no futuro?  
     Nat Lconf. NATManagerC // desativado se nulo  
    se conf. NAT {  
        nat = basichost. NewNATManager  
    }  
    opts := []libp2p. Opção{  
        libp2p. Identidade(conf. Priv),  
        Defina explicitamente o user-agent, para que possamos diferenciar de outros usuários do Go libp2p.  
        libp2p. UserAgent(conf. UserAgent),  
        tcpTransport,  
        libp2p. WithDialTimeout(conf. TimeoutDial),  
        Sem serviços de retransmissão, apenas conexões diretas entre pares.  
        libp2p. DisableRelay(),  
        O host iniciará e ouvirá a rede diretamente após a construção a partir da configuração.  
        libp2p. ListenAddrs(listenAddr),  
        libp2p. ConnectionGater (connGtr),  
        libp2p. ConnectionManager(connMngr),  
        libp2p. ResourceManager(nil), // TODO usa a interface do gerenciador de recursos para gerenciar melhor os recursos por par.  
        libp2p. Ping(verdadeiro),  
        libp2p. AutoNATServiceRateLimit(10, 5, tempo. Segundo*60),  
    }  
    se conf. NoTransportSecurity {  
    } else {  
        opts = apêndice(opts, conf. HostSecurity...)  
    }  
    h, err := libp2p. Novo(opts...)  
    if err != nil {  
    }  
    para i, peerAddr := range conf. StaticPeers {  
        if err != nil {  
        }  
        staticPeers[i]  = addr  
    }  
    out := &extraHost{  
        Anfitrião: h,  
        staticPeers: staticPeers,  
    }  
        sair.monitorStaticPeers()  
    }  






        ...  
        n.gs, err = NewGossipSub(resourcesCtx, n.host, rollupCfg, setup, n.scorer, metrics, log)  
        if err != nil {  
        }  
        ...  
  1. Criação do autenticador: Geração de nomes de temas de bloco:
  • Gere blocksTopicName usando a função blocksTopicV1, que formata uma string com base no L2ChainID na configuração (cfg). As cadeias de caracteres formatadas seguem uma estrutura específica: /optimism/{L2ChainID}/0/blocks.

  • função chamando ps. Join(blocksTopicName) tenta adicionar um tema de fofoca de bloco. Se ocorrer um erro, ele retornará uma mensagem de erro indicando que o tópico não pôde ser associado.

  • Gerou uma nova rotina para registrar eventos de tópicos usando a função LogTopicEvents.

  • Criou um assinante usando a função MakeSubscriber, que encapsula um BlocksHandler que lida com eventos OnUnsafeL2Payload de gossipIn. Uma nova rotina foi gerada para executar o subião fornecido.

    func JoinGossip(p2pCtx contexto. Contexto, auto peer.ID, ps *pubsub. PubSub, log de log. Logger, cfg *rollup. Config, runCfg GossipRuntimeConfig, gossipIn GossipIn) (GossipOut, erro) {
    blocksTopicName := blocksTopicV1(cfg) // retorno fmt. Sprintf("/optimism/%s/0/blocks", cfg. L2ChainID.String())
    retorno nulo, fmt. Errorf("falha ao registrar bloqueia tópico de fofoca: %w", err)
    }
    blocksTopic, err := ps. Join(blocksTopicName)
    if err != nil {
    }
    if err != nil {
    }
    vá LogTopicEvents(p2pCtx, log. New("tópico", "blocos"), blocksTopicEvents)
    if err != nil {
    }
    return &publisher{log: log, cfg: cfg, blocksTopic: blocksTopic, runCfg: runCfg}, nil

op-node/rollup/driver/state.go

func (s *Driver) eventLoop() {  
    for(){  
        ...  
            carga útil, err := s.sequencer.RunNextSequencerAction(ctx)  
            if err != nil {  
                Os erros não são graves o suficiente para alterar/interromper o sequenciamento, mas devem ser registrados e medidos.  
                if err := s.network.PublishL2Payload(ctx, payload); err != nil {  
            }  
            planSequencerAction() // agendar a próxima ação do sequenciador para manter o looping de sequenciamento  
            }  
    }  
    ...  

Quando há blocos ausentes, sincronização rápida via P2P

Quando os nós são religados após o tempo de inatividade devido a circunstâncias especiais, como a religação após o tempo de inatividade, alguns blocos (lacunas) podem não estar sincronizados e, ao encontrar esta situação, podem sincronizar rapidamente através da cadeia inversa da rede P2P.

Vamos dar uma olhada na função checkForGapInUnsafeQueue em op-node/rollup/driver/state.go

O trecho de código define um método chamado checkForGapInUnsafeQueue, que pertence à estrutura Driver. Seu objetivo é verificar lacunas de dados em uma fila chamada "fila insegura" e tentar recuperar cargas úteis ausentes por meio de um método de sincronização alternativo chamado altSync. O ponto-chave aqui é que o método é garantir a continuidade dos dados e tentar recuperar dados ausentes de outros métodos de sincronização quando dados ausentes são detetados. Aqui estão os principais passos da função:

  1. A função primeiro obtém UnsafeL2Head e UnsafeL2SyncTarget de s.derivation como os pontos de início e fim do intervalo de verificação.

  2. A função verifica se há blocos ausentes entre o início e o fim, o que é feito comparando os valores numéricos de fim e início.

  3. Se uma lacuna de dados for detetada, a função solicitará o intervalo de dados ausente chamando s.altSync.RequestL2Range(ctx, start, end). Se end é uma referência nula (ou seja, eth. L2BlockRef{}), a função solicitará uma sincronização de intervalo aberto, começando com start.

  4. Ao solicitar dados, a função registra um log de depuração indicando qual intervalo de dados está solicitando.

  5. A função eventualmente retorna um valor de erro. Se não houver erros, ele retorna zero

    checkForGapInUnsafeQueue verifica se há uma lacuna na fila insegura e tenta recuperar as cargas úteis ausentes de um método alt-sync.
    AVISO: Este é apenas um sinal de saída, os blocos não têm garantia de serem recuperados.
    Os resultados são recebidos através de OnUnsafeL2Payload.
    func (s *Driver) checkForGapInUnsafeQueue(contexto ctx. Contexto) erro {
    início := s.derivation.UnsafeL2Head()
    fim := s.derivation.UnsafeL2SyncTarget()
    Verifique se faltam blocos entre o início e o fim Solicite-os se o fizermos.
    se fim == (eth. L2BlockRef{}) {
    s.log.Debug("solicitando sincronização com intervalo aberto", "start", start)
    return s.altSync.RequestL2Range(ctx, start, eth. L2BlockRef{})
    } senão se terminar. Número > início. Número+1 {
    s.log.Debug("solicitando intervalo de blocos L2 inseguros ausentes", "start", start, "end", end, "size", end. Número-início.Número)
    retornar s.altSync.RequestL2Range (ctx, início, fim)
    }
    retorno nulo
    }

A função RequestL2Range sinaliza o início e o fim do bloco de solicitações para o canal de solicitações.

A solicitação é então distribuída para o canal peerRequests através do método onRangeRequest, e o canal peerRequests é aguardado pelo loop aberto por vários pares, ou seja, apenas um par processará a solicitação para cada distribuição.

func (s *SyncClient) onRangeRequest(contexto ctx. Contexto, req rangeRequest) {  
        ...  
        para i := uint64(0); ; i++ {  
        num := req.end.Number - 1 - i  
        if num <= req.start {  
            regresso  
        }  
        verificar se já temos algo em quarentena  
        se h, ok := s.quarantineByNum[num] ; ok {  
            if s.trusted.Contains(h) { // se confiarmos nele, tente promovê-lo.  
                s.tryPromote(h)  
            }  
            Não busquem coisas para as quais já temos candidato.  
            Vamos eliminá-lo da quarentena encontrando um conflito ou se sincronizarmos outros blocos suficientes  
            continuar  
        }  
        if _, ok := s.inFlight[num] ; ok {  
            .log. Debug("solicitação ainda em andamento, não reagendando solicitação de sincronização", "num", num)  
            continuar // solicitar ainda em voo  
        }  
        pr := peerRequest{num: num, complete: new(atomic. Bool)}  
        .log. Debug("Agendando solicitação de bloco P2P", "num", num)  
        número da agenda  
        selecione {  
        caso s.peerRequests <- pr:  
            s.inVoo[num]  = pr.completo  
        Caso <-CTX. Feito():  
            .log. Info("não programou intervalo de sincronização P2P completo", "current", num, "err", ctx. Err())  
            regresso  
        padrão: // os pares podem estar todos ocupados processando solicitações já  
            .log. Info("sem pares prontos para lidar com solicitações de bloco para mais solicitações P2P para histórico de blocos L2", "atual", num)  
            regresso  
        }  
    }  
}

Vamos ver o que acontece quando o par recebe essa solicitação.

A primeira coisa que precisamos saber é que o link entre o peer e o nó solicitante, ou a mensagem que passa, é passada através do fluxo libp2p. O método de processamento do fluxo é implementado pelo nó de mesmo nível de recebimento e a criação do fluxo é aberta pelo nó de envio.

Podemos ver esse código na função init anterior, onde MakeStreamHandler retorna um manipulador e SetStreamHandler vincula a ID do protocolo a esse manipulador, portanto, sempre que o nó de envio cria e usa esse fluxo, o manipulador retornado é acionado.

n.syncSrv = NewReqRespServer(rollupCfg, l2Chain, métricas)  
Registrar o protocolo de sincronização com o host libp2p  
payloadByNumber := MakeStreamHandler(resourcesCtx, log. New("serve", "payloads_by_number"), n.syncSrv.HandleSyncRequest)  
n.host.SetStreamHandler(PayloadByNumberProtocolID(rollupCfg.L2ChainID), payloadByNumber)

Vamos ver como ele é tratado dentro do manipulador A função primeiro executa verificações de limite de taxa global e individual para controlar a rapidez com que as solicitações são processadas. Em seguida, lê e verifica o número de bloco solicitado, garantindo que está dentro de um intervalo razoável. Em seguida, a função obtém a carga útil da solicitação da camada L2 e a grava no fluxo de resposta. Ao gravar dados de resposta, ele define um prazo de gravação para evitar ser bloqueado por conexões de pares lentas durante o processo de gravação. Finalmente, a função retorna o número de bloco solicitado e possíveis erros.

func (srv *ReqRespServer) handleSyncRequest(contexto ctx. Contexto, rede de fluxo. Stream) (uint64, erro) {  
    peerId := fluxo. Conn(). RemotePeer()  
    pegue um token do limitador de taxa global,  
    para garantir que não haja muito trabalho de servidor simultâneo entre diferentes pares.  
    se err := srv.globalRequestsRL.Wait(ctx); err != nil {  
        retorno 0, fmt. Errorf("timed out waiting for global sync rate limit: %w", err)  
    }  
    encontrar dados de limitação de taxa de peer, ou adicionar de outra forma  
    srv.peerStatsLock.Lock()  
    ps, _ := srv.peerRateLimits.Get(peerId)  
    if ps == nil {  
        ps = &peerStat{  
            Pedidos: taxa. NewLimiter(peerServerBlocksRateLimit, peerServerBlocksBurst),  
        }  
        srv.peerRateLimits.Add(peerId, ps)  
        ps. Requests.Reserve() // contar o acerto, mas fazê-lo atrasar a próxima solicitação em vez de esperar imediatamente  
    } else {  
        Aguarde apenas se for um par existente, caso contrário, a chamada de espera de limite de taxa instantânea sempre erros.  
        Se o solicitante acha que estamos demorando muito, então o problema é dele e ele pode se desconectar.  
        Desligamo-nos apenas quando não conseguimos ler/escrever,  
        se o trabalho for inválido (validação de intervalo) ou quando as subtarefas individuais expirarem.  
        se errar := ps. Requests.Wait(ctx); err != nil {  
            retorno 0, fmt. Errorf("timed out waiting for global sync rate limit: %w", err)  
        }  
    }  
    srv.peerStatsLock.Unlock()  
    Definir prazo de leitura, se disponível  
    _ = fluxo. SetReadDeadline(tempo. Agora(). Add(serverReadRequestTimeout))  
    Leia o pedido  
     req uint64  
    if err := binário. Leitura(fluxo, binário. LittleEndian, &req); err != nil {  
        retorno 0, fmt. Errorf("falha ao ler o número do bloco solicitado: %w", erro)  
    }  
    se errar := fluxo. CloseRead(); err != nil {  
        retorno req, fmt. Errorf("falha ao fechar o lado de leitura de uma chamada de solicitação de sincronização P2P: %w", err)  
    }  
    Verifique se a solicitação está dentro do intervalo esperado de blocos  
    if req < srv.cfg.Genesis.L2.Number {  
        retorno req, fmt. Errorf("cannot serve request for L2 block %d before genesis %d: %w", req, srv.cfg.Genesis.L2.Number, invalidRequestErr)  
    }  
    max, err := srv.cfg.TargetBlockNumber(uint64(time. Agora(). Unix()))  
    if err != nil {  
        retorno req, fmt. Errorf("não é possível determinar o número máximo do bloco de destino para verificar a solicitação: %w", invalidRequestErr)  
    }  
    if req > max {  
        retorno req, fmt. Errorf("cannot serve request for L2 block %d after max expected block (%v): %w", req, max, invalidRequestErr)  
    }  
    carga útil, err := srv.l2.PayloadByNumber(ctx, req)  
    if err != nil {  
        se erros. É(err, ethereum. NotFound) {  
            retorno req, fmt. Errorf("peer solicitado bloco desconhecido por número: %w", err)  
        } else {  
            retorno req, fmt. Errorf("falha ao recuperar a carga útil para servir ao ponto: %w", err)  
        }  
    }  
    Definimos o prazo de gravação, se disponível, para escrever com segurança sem bloquear uma conexão de peer de limitação  
    _ = fluxo. SetWriteDeadline(tempo. Agora(). Add(serverWriteChunkTimeout))  
    0 - resultCode: sucesso = 0  
    1:5 - Versão: 0  
     .tmp [5] byte  
    if _, err := fluxo. Escrever(tmp[:]); err != nil {  
        retorno req, fmt. Errorf("falha ao gravar dados do cabeçalho de resposta: %w", erro)  
    }  
    w := rápido. NewBufferedWriter(fluxo)  
    if _, err := carga útil. Marechal SSZ(w); err != nil {  
        retorno req, fmt. Errorf("falha ao gravar a carga útil para sincronizar a resposta: %w", err)  
    }  
    se errar := w.Close(); err != nil {  
        retorno req, fmt. Errorf("falha ao terminar a gravação da carga útil para sincronizar a resposta: %w", err)  
    }  
    retorno req, nil  
}

Neste ponto, o processo geral de solicitação e processamento de sincronização de cadeia reversa foi explicado

Sistema de reputação de pontos em nós p2p

A fim de evitar que certos nós façam solicitações maliciosas e respostas que minam a segurança de toda a rede, o Optimism também usa um sistema de pontos.

Por exemplo, em op-node/p2p/app_scores.go, há uma série de funções para definir a pontuação de um par

func (s *peerApplicationScorer) onValidResponse(id peer.ID) {  
    _, err := s.scorebook.SetScore(id, store. IncrementValidResponses{Cap: s.params.ValidResponseCap})  
    if err != nil {  
        s.log.Error("Não é possível atualizar a pontuação de pares", "peer", id, "err", err)  
        regresso  
    }  
}  
func (s *peerApplicationScorer) onResponseError(id peer.ID) {  
    _, err := s.scorebook.SetScore(id, store. IncrementErrorResponses{Cap: s.params.ErrorResponseCap})  
    if err != nil {  
        s.log.Error("Não é possível atualizar a pontuação de pares", "peer", id, "err", err)  
        regresso  
    }  
}  
func (s *peerApplicationScorer) onRejectedPayload(id peer.ID) {  
    _, err := s.scorebook.SetScore(id, store. IncrementRejectedPayloads{Cap: s.params.RejectedPayloadCap})  
    if err != nil {  
        s.log.Error("Não é possível atualizar a pontuação de pares", "peer", id, "err", err)  
        regresso  
    }  
}

O status de integração do novo nó é verificado antes de ser adicionado

func AddScoring(gater BlockingConnectionGater, pontuações Scores, minScore float64) *ScoringConnectionGater {  
    return &ScoringConnectionGater{BlockingConnectionGater: gater, scores: scores, minScore: minScore}  
}  
func (g *ScoringConnectionGater) checkScore(p peer.ID) (permitir bool) {  
    pontuação, err := g.scores.GetPeerScore(p)  
    if err != nil {  
        devolver falso  
    }  
    pontuação de retorno >= g.minScore  
}  
func (g *ScoringConnectionGater) InterceptPeerDial(p peer.ID) (permitir bool) {  
    retornar g.BlockingConnectionGater.InterceptPeerDial(p) && g.checkScore(p)  
}  
func (g *ScoringConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr. Multiaddr) (permitir bool) {  
    return g.BlockingConnectionGater.InterceptAddrDial(id, ma) && g.checkScore(id)  
}  
func (g *ScoringConnectionGater) InterceptSecured(dir network. Direção, id peer.ID, mas rede. ConnMultiaddrs) (permitir bool) {  
    return g.BlockingConnectionGater.InterceptSecured(dir, id, mas) && g.checkScore(id)  
}

Resumo

A alta configurabilidade do libp2p torna todo o projeto p2p altamente personalizável e modular, o acima é a lógica principal da implementação personalizada do libp2p da optimsim, e outros detalhes podem ser aprendidos em detalhes lendo o código-fonte no diretório p2p.

Ver original
Esta página pode conter conteúdos de terceiros, que são fornecidos apenas para fins informativos (sem representações/garantias) e não devem ser considerados como uma aprovação dos seus pontos de vista pela Gate, nem como aconselhamento financeiro ou profissional. Consulte a Declaração de exoneração de responsabilidade para obter mais informações.
  • Recompensa
  • Comentar
  • Partilhar
Comentar
0/400
Nenhum comentário
  • Pino
Negocie cripto em qualquer lugar e a qualquer hora
qrCode
Digitalizar para transferir a aplicação Gate
Novidades
Português (Portugal)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)