Bu bölümde temel amaç, Optimism'in Op-Node'da P2P ağlarının kurulumunu tamamlamak için Libb2P'yi nasıl kullandığını açıklamaktır.
P2P ağları esas olarak, Unsafe'in blok yapısını tamamladıktan sonra sıralayıcılar gibi farklı düğümlerde bilgi iletmek ve P2P'nin gossiphub'ının pub/sub'ı aracılığıyla yayılmak için kullanılır. Libb2P ayrıca P2P ağlarında ağ oluşturma, adresleme vb. gibi diğer altyapılarla da ilgilenir.
libp2p hakkında bilgi edinin
libp2p ("Library Peer-to-Peer" veya "Library Peer-to-Peer" olarak kısaltılır), P2P uygulamalarının geliştirilmesine yardımcı olan bir eşler arası (P2P) ağ çerçevesidir. Ağ katılımcıları ("eşler" veya "eşler" olarak da bilinir) arasındaki P2P iletişimini kolaylaştıran bir dizi protokol, özellik ve kitaplık içerir (kaynak[2] [3])。 Başlangıçta IPFS (Gezegenler Arası Dosya) projesinin bir parçası olan libp2p, dağıtılmış ağlar için modüler bir ağ yığını (kaynak) haline gelen bağımsız bir projeye dönüştü )。
libp2p, IPFS topluluğunun, spesifikasyonların yazılmasına, kod uygulamalarına ve örnekler ve öğreticiler oluşturmaya yardımcı olmak da dahil olmak üzere çok çeşitli toplulukların katkılarını memnuniyetle karşılayan açık kaynaklı bir projesidir[4] [5])。 libp2p, her biri çok net, belgelenmiş ve test edilmiş bir arayüze sahip birden fazla yapı taşından oluşur ve bu da onları birleştirilebilir, değiştirilebilir ve dolayısıyla yükseltilebilir kılar )。 libp2p'nin modüler yapısı, geliştiricilerin yalnızca uygulamaları için gerekli bileşenleri seçmelerine ve kullanmalarına olanak tanıyarak P2P web uygulamaları oluştururken esnekliği ve verimliliği artırır.
İlgili kaynaklar
libp2p için resmi belgeler[6]
libp2p'nin GitHub deposu[7]
ProtoSchool'da libp2p'ye giriş[8]
libp2p'nin modüler mimarisi ve açık kaynak yapısı, güçlü, ölçeklenebilir ve esnek P2P uygulamaları geliştirmek için iyi bir ortam sağlayarak onu dağıtılmış ağlar ve ağ uygulaması geliştirme alanında önemli bir oyuncu haline getirir.
libp2p uygulaması
libp2p kullanırken, P2P ağınızı oluşturmak için bazı temel bileşenleri uygulamanız ve yapılandırmanız gerekecektir. Uygulamada libp2p'nin ana uygulama yönlerinden bazıları şunlardır:
1. Düğüm Oluşturma ve Yapılandırma:
Bir libp2p düğümü oluşturmak ve yapılandırmak, düğümün ağ adresini, kimliğini ve diğer temel parametrelerini ayarlamayı içeren en temel adımdır.
Anahtar kullanım kodları:
libp2p'yi seçin. Yeni()
2. Taşıma Protokolü:
Düğümler arasında iletişimi sağlamak için aktarım protokollerinizi (ör. TCP, WebSockets vb.) seçin ve yapılandırın.
Anahtar kullanım kodları:
tcpTransport := tcp. YeniTCPTransport()
3. Çoğullama ve Akış Kontrolü:
Birden fazla eşzamanlı veri akışının tek bir bağlantıda işlenmesine izin vermek için çoğullama uygulayın.
Veri iletim hızını ve işleme hızını yönetmek için akış kontrolünü uygulayın.
Anahtar kullanım kodları:
yamuxTransport := yamux. Yeni()
4. Güvenlik ve şifreleme:
İletişimin güvenliğini ve gizliliğini sağlamak için güvenli bir taşıma katmanı yapılandırın.
Verileri korumak ve iletişim kuran tarafların kimliğini doğrulamak için şifreleme ve kimlik doğrulama mekanizmaları uygulayın.
Anahtar kullanım kodları:
tlsTransport := tls. Yeni()
5. Protokoller ve Mesaj İşleme:
Belirli ağ işlemlerini ve mesaj alışverişlerini işlemek için özel protokoller tanımlayın ve uygulayın.
Alınan mesajları işleyin ve gerektiğinde yanıtları gönderin.
Anahtar kullanım kodları:
ev sahibi. SetStreamHandler("/protokolüm/1.0.0", myProtocolHandler)
6. Keşif ve Yönlendirme:
Ağdaki diğer düğümleri bulmak için bir düğüm keşif mekanizması uygulayın.
İletilerin ağdaki doğru düğümlere nasıl yönlendirildiğini belirlemek için yönlendirme mantığı uygulayın.
Anahtar kullanım kodları:
dht := kaddht. NewDHT(ctx, ana bilgisayar, veri deposu. NewMapDatastore())
7. Ağ davranışı ve politikaları:
Bağlantı yönetimi, hata işleme ve yeniden deneme mantığı gibi ağ davranışlarını ve ilkelerini tanımlayın ve uygulayın.
Anahtar kullanım kodları:
Bağlantı durumu, düğüm listesi ve veri depolama dahil olmak üzere düğümlerin ve ağların durumunu yönetin.
Anahtar kullanım kodları:
peerstore := pstoremem. YeniEşler Deposu()
9. Test edin ve hata ayıklayın:
Doğruluğundan ve güvenilirliğinden emin olmak için libp2p uygulamanız için testler yazın.
Ağ sorunlarını tanılamak ve çözmek için hata ayıklama araçlarını ve günlüklerini kullanın.
Anahtar kullanım kodları:
günlük tutmak. SetLogLevel("libp2p", "HATA AYIKLA")
10. Dokümantasyon ve Topluluk Desteği:
Çeşitli bileşenleri ve API'leri hakkında bilgi edinmek için libp2p'nin belgelerine bakın.
Destek ve problem çözme için libp2p topluluğuyla iletişim kurun.
}
Yukarıdakiler, libp2p kullanırken göz önünde bulundurulması ve uygulanması gereken ana hususlardan bazılarıdır. Her projenin özel uygulaması değişebilir, ancak bu temel yönler libp2p uygulamaları oluşturmak ve çalıştırmak için gereklidir. Bu işlevleri uygularken, libp2p'nin resmi belgelerine başvurabilirsiniz[9] [10]ve GitHub depoları Örnek kod ve öğreticiler.
OP düğümünde libp2p kullanımı
Op-node ve libp2p arasındaki ilişkiyi anlamak için birkaç soruyu çözmemiz gerekiyor
Neden libp2p? Neden devp2p'yi seçmiyorsunuz (geth devp2p kullanıyor)
OP düğümündeki P2P ağıyla hangi veriler veya süreçler yakından ilişkilidir?
Bu özellikler kod katmanında nasıl uygulanır?
Op-node'ların neden bir libp2p ağına ihtiyacı var?
Öncelikle Optimism'in neden bir P2P ağı gerektirdiğini anlamamız gerekiyor ** LibbP2P, geliştiricilerin birden fazla kullanım durumu için merkezi olmayan eşler arası uygulamalar oluşturmasına olanak tanıyan modüler bir ağ protokolüdür (kaynaklar[11] [12])(kaynak [13])。 DevP2P ise esas olarak Ethereum ekosisteminde kullanılır ve Ethereum uygulamaları için uyarlanmıştır (Kaynak )。 Libp2p'nin esnekliği ve geniş uygulanabilirliği, onu geliştiriciler için en iyi seçim haline getirebilir.
Op-Node esas olarak libp2p işlev noktalarını kullanır
Sıralayıcı tarafından, ortaya çıkan güvenli olmayan bloğu sıralayıcı olmayan diğer düğümlere iletmek için kullanılır
Bir boşluk oluştuğunda hızlı senkronizasyon için sıralayıcı olmayan moddaki diğer düğümler için (ters zincir senkronizasyonu)
Genel düğümü düzenlemek için entegre bir itibar sistemini benimsemek için iyi bir ortam
Kod uygulaması
ana bilgisayar özel başlatma
Ana bilgisayar bir P2P düğümü olarak anlaşılabilir, bu düğümü açarken kendi projeniz için bazı özel başlatma yapılandırmaları yapmanız gerekir
Şimdi op-node/p2p/host.go dosyasındaki Host metoduna bakalım.
Bu işlev esas olarak libp2p ana bilgisayarını kurmak ve çeşitli yapılandırmalar yapmak için kullanılır. İşte işlevin temel kısımları ve her birinin basit bir Çince açıklaması:
P2P'nin devre dışı olup olmadığını kontrol edin
P2P devre dışı bırakılırsa, işlev doğrudan geri döner.
Ortak Anahtardan Eş Kimliği Alın
Eş Kimliği oluşturmak için yapılandırmadaki ortak anahtarı kullanın.
Peerstore'u Başlat
Temel bir Peerstore mağazası oluşturun.
Peerstore uzantısını başlatın
Temel Peerstore'un üstünde, genişletilmiş bir Peerstore oluşturun.
Peerstore'a özel ve genel anahtarlar ekleyin
Eşin özel ve genel anahtarlarını eş deposunda depolayın.
Bağlantı Gater'ı Başlat
Ağ bağlantılarını kontrol etmek için kullanılır.
Bağlantı Yöneticisini Başlat
Ağ bağlantılarını yönetmek için kullanılır.
İletim ve Dinleme Adresini Ayarlayın
Ağ aktarım protokolünü ve ana bilgisayarın dinleme adresini ayarlayın.
libp2p ana bilgisayarı oluşturun
Yeni bir libp2p ana bilgisayarı oluşturmak için önceki tüm ayarları kullanın.
Statik eşi başlat
Yapılandırılmış bir statik eşiniz varsa, bunu başlatın.
Ev Sahibine Geri Dön
Son olarak, işlev oluşturulan libp2p ana bilgisayarını döndürür.
Bu anahtar bölümler libp2p ana bilgisayarını başlatmaktan ve kurmaktan sorumludur ve her bölüm ana bilgisayar yapılandırmasının belirli bir yönünden sorumludur.
func (conf *Config) Host(log log. Kaydedici, muhabir metrikleri. Muhabir, metrikler HostMetrics) (ana bilgisayar. Ana bilgisayar, hata) {
eğer conf. DisableP2P {
Dönüş sıfır, sıfır
}
pub := conf. Priv.GetPublic()
pid, err := eş. IDFromPublicKey(pub)
eğer err != nil {
Dönüş sıfır, fmt. Errorf("ağ özel anahtarından pubkey türetilemedi: %w", hata)
}
basePs, err := pstoreds. NewPeerstore(bağlam. background(), conf. Depo, depolanmış. DefaultOpts())
eğer err != nil {
Dönüş sıfır, fmt. Errorf("peerstore açılamadı: %w", hata)
}
peerScoreParams := conf. PeerScoringParams()
scoreRetention süresi. Süre
if peerScoreParams != nil {
Varsa, dedikodularla aynı saklama süresini kullanın
scoreRetention = peerScoreParams.PeerScoring.RetainScore
} başka {
Eş puanlaması devre dışı bırakılmışsa GC puanını devre dışı bırak
scoreRetention = 0
}
ps, err := mağaza. NewExtendedPeerstore(bağlam. background(), log, clock. Saat, baseP'ler, conf. Mağaza, scoreRetention)
eğer err != nil {
Dönüş sıfır, fmt. Errorf("genişletilmiş eş deposu açılamadı: %w", hata)
}
eğer hata := ps. AddPrivKey(pid, conf. Priv); err != nil {
Dönüş sıfır, fmt. Errorf("priv anahtarıyla peerstore kurulamadı: %w", hata)
}
eğer hata := ps. AddPubKey(pid, pub); err != nil {
Dönüş sıfır, fmt. Errorf("pub anahtarıyla peerstore kurulamadı: %w", hata)
}
connGtr geçit. BlockingConnectionGater
connGtr, err = geçit. NewBlockingConnectionGater(conf. Mağaza)
eğer err != nil {
Dönüş sıfır, fmt. Errorf("bağlantı kapısı açılamadı: %w", hata)
}
connGtr = geçit. AddBanExpiry(connGtr, ps, günlük, saat. Saat, metrikler)
connGtr = geçit. AddMetering(connGtr, metrikler)
connMngr, err := DefaultConnManager(conf)
eğer err != nil {
Dönüş sıfır, fmt. Errorf("bağlantı yöneticisi açılamadı: %w", hata)
}
listenAddr, err := addrFromIPAndPort(conf. ListenIP, conf. ListenTCPPort)
eğer err != nil {
Dönüş sıfır, fmt. Errorf("dinleme yapılamadı addr: %w", hata)
}
tcpTransport := libp2p. Taşıma(
TCP. NewTCPTransport,
TCP. WithConnectionTimeout(zaman. Dakika*60)) // kullanılmayan bağlantıları kes
TODO: teknik olarak düğümü websocket ve QUIC aktarımlarında da çalıştırabiliriz Belki gelecekte?
nat lconf. NATManagerC // sıfır ise devre dışı
eğer conf. NAT {
nat = temel ana bilgisayar. YeniNATManager
}
opts := []libp2p. Seçenek{
libp2p'yi seçin. Kimlik(conf. Özel),
Diğer Go libp2p kullanıcılarından ayırt edebilmemiz için kullanıcı aracısını açıkça ayarlayın.
libp2p'yi seçin. UserAgent(conf. UserAgent),
tcpTransport,
libp2p'yi seçin. WithDialTimeout(conf. Zaman Aşımı Kadranı),
Aktarma hizmeti yok, yalnızca eşler arasında doğrudan bağlantılar.
libp2p'yi seçin. DisableRelay(),
Ana bilgisayar, yapılandırmadan oluşturulduktan hemen sonra ağı başlatacak ve dinleyecektir.
libp2p'yi seçin. ListenAddrs(listenAddr),
libp2p'yi seçin. ConnectionGater(connGtr),
libp2p'yi seçin. ConnectionManager(connMngr),
libp2p'yi seçin. ResourceManager(nil), // TODO, eş başına kaynakları daha iyi yönetmek için kaynak yöneticisi arabirimini kullanır.
libp2p'yi seçin. Ping(doğru),
libp2p'yi seçin. AutoNATServiceRateLimit(10, 5, zaman. İkinci*60),
}
eğer conf. NoTransportSecurity {
} başka {
opts = append(opts, conf. Ana Bilgisayar Güvenliği...)
}
h, err := libp2p. Yeni(isteğe bağlı...)
eğer err != nil {
}
for i, peerAddr := range conf. StaticPeers {
eğer err != nil {
}
staticPeers[i] = addr
}
out := &extraHost{
Ev sahibi: h,
staticPeers: staticPeers,
}
dışarı çık.monitorStaticPeers()
}
...
n.gs, err = NewGossipSub(resourcesCtx, n.host, rollupCfg, kurulum, n.scorer, metrikler, günlük)
eğer err != nil {
}
...
Kimlik Doğrulayıcı Oluşturma:
Blok Tema Adı Oluşturma:
Yapılandırmadaki (cfg) L2ChainID'ye göre bir dizeyi biçimlendiren blocksTopicV1 işlevini kullanarak blocksTopicName oluşturun. Biçimlendirilmiş dizeler belirli bir yapıyı izler: /optimism/{L2ChainID}/0/blocks.
ps'yi çağırarak işlev görür. Join(blocksTopicName) bir blok dedikodu teması eklemeye çalışır. Bir hata oluşursa, konunun birleştirilemediğini belirten bir hata iletisi döndürür.
LogTopicEvents işlevini kullanarak konu olaylarını günlüğe kaydetmek için yeni bir goroutine oluşturuldu.
GossipIn'den OnUnsafeL2Payload olaylarını işleyen bir BlocksHandler'ı kapsülleyen MakeSubscriber işlevini kullanarak bir abone oluşturuldu. Sağlanan alt iyonu çalıştırmak için yeni bir goroutine oluşturuldu.
func (s *Sürücü) eventLoop() {
for(){
...
payload, err := s.sequencer.RunNextSequencerAction(ctx)
eğer err != nil {
Hatalar, sıralamayı değiştirecek/durduracak kadar ciddi değildir, ancak günlüğe kaydedilmeli ve ölçülmelidir.
if err := s.network.PublishL2Payload(ctx, payload); err != nil {
}
planSequencerAction() // sıralama döngüsünü korumak için bir sonraki sıralayıcı eylemini planlayın
}
}
...
Eksik bloklar olduğunda, P2P ile hızlı senkronizasyon
Kesinti süresinden sonra yeniden bağlanma gibi özel durumlar nedeniyle düğümler kesinti süresinden sonra yeniden bağlandığında, bazı bloklar (boşluklar) senkronize edilemeyebilir ve bu durumla karşılaştıklarında P2P ağının ters zinciri üzerinden hızlı bir şekilde senkronize olabilirler.
Op-node/rollup/driver/state.go içindeki checkForGapInUnsafeQueue işlevine bir göz atalım
Kod parçacığı, Driver yapısına ait olan checkForGapInUnsafeQueue adlı bir yöntemi tanımlar. Amacı, "güvenli olmayan kuyruk" adı verilen bir kuyruktaki veri boşluklarını kontrol etmek ve altSync adlı alternatif bir senkronizasyon yöntemi aracılığıyla eksik yükleri almaya çalışmaktır. Buradaki kilit nokta, yöntemin veri sürekliliğini sağlamak ve eksik veri tespit edildiğinde eksik verileri diğer senkronizasyon yöntemlerinden almaya çalışmak olmasıdır. İşte fonksiyonun ana adımları:
İşlev ilk olarak kontrol aralığının başlangıç ve bitiş noktaları olarak s.derivation'dan UnsafeL2Head ve UnsafeL2SyncTarget'ı alır.
Fonksiyon, bitiş ve başlangıç Sayı değerlerini karşılaştırarak yapılan başlangıç ve bitiş arasındaki eksik blokları kontrol eder.
Bir veri boşluğu algılanırsa, işlev s.altSync.RequestL2Range(ctx, start, end) öğesini çağırarak eksik veri aralığını isteyecektir. End boş bir referanssa (örn. L2BlockRef{}), işlev start ile başlayan bir açık uçlu aralık senkronizasyonu isteyecektir.
Veri talep ederken, işlev hangi veri aralığını istediğini gösteren bir hata ayıklama günlüğü kaydeder.
İşlev sonunda bir hata değeri döndürür. Herhangi bir hata yoksa, nil döndürür
checkForGapInUnsafeQueue, güvenli olmayan kuyrukta bir boşluk olup olmadığını kontrol eder ve eksik yükleri bir alt-sync yönteminden almaya çalışır.
UYARI: Bu sadece giden bir sinyaldir, blokların geri alınması garanti edilmez.
Sonuçlar OnUnsafeL2Payload aracılığıyla alınır.
func (s *Sürücü) checkForGapInUnsafeQueue(ctx context. Bağlam) hatası {
start := s.derivation.UnsafeL2Head()
end := s.derivation.UnsafeL2SyncTarget()
Başlangıç ve bitiş arasında eksik bloklarımız olup olmadığını kontrol edin Varsa talep edin.
if end == (eth. L2BlockRef{}) {
s.log.Debug("açık uçlu aralıkla senkronizasyon isteniyor", "start", start)
return s.altSync.RequestL2Range(ctx, start, eth. L2BlockRef{})
} else if end. > numara başlar. Sayı+1 {
s.log.Debug("eksik güvenli olmayan L2 blok aralığı isteniyor", "başlangıç", başlangıç, "bitiş", bitiş, "boyut", bitiş. Sayı-başlangıç.Sayı)
return s.altSync.RequestL2Range(ctx, başlangıç, bitiş)
}
Dönüş sıfır
}
RequestL2Range işlevi, istek bloğunun başlangıcını ve bitişini istekler kanalına bildirir.
İstek daha sonra onRangeRequest yöntemi aracılığıyla peerRequests kanalına dağıtılır ve peerRequests kanalı birden çok eş tarafından açılan döngü tarafından beklenir, yani her dağıtım için isteği yalnızca bir eş işleyecektir.
func (s *SyncClient) onRangeRequest(ctx context. Context, req rangeRequest) {
...
i için := uint64(0); ; i++ {
num := req.end.Sayı - 1 - i
if num <= req.start {
dönmek
}
Karantinada bir şeyimiz olup olmadığını kontrol edin
if h, tamam := s.quarantineByNum[num] ; tamam {
if s.trusted.Contains(h) { // güveniyorsak tanıtmaya çalışın.
s.tryPromote(h)
}
Zaten adayımız olan şeyleri getirmeyin.
Bir çakışma bularak veya yeterince başka blokları senkronize edersek karantinadan çıkaracağız
devam etmek
}
if _, ok := s.inFlight[num] ; tamam {
.log. Debug("istek hala yolda, senkronizasyon isteğini yeniden zamanlama değil", "num", num)
Devamı // Talep Hala Uçuşta
}
pr := peerRequest{num: num, complete: new(atomic. Bool)}
.log. Debug("P2P blok isteğini zamanlama", "num", num)
Program numarası
select {
case s.peerRequests <- pr:
s.inFlight (Uçuş)[num] = pr.tamamlandı
Vaka <-CTX. Bitti():
.log. Info("tam P2P senkronizasyon aralığı planlanmadı", "current", num, "err", ctx. Hata())
dönmek
Varsayılan: // eşlerin tümü zaten istekleri işlemekle meşgul olabilir
.log. Info("L2 blok geçmişi için daha fazla P2P isteği için blok isteklerini işlemeye hazır eş yok", "current", num)
dönmek
}
}
}
Eş bu isteği aldığında ne olacağını görelim.
Bilmemiz gereken ilk şey, eş ve istekte bulunan düğüm arasındaki bağlantının veya iletilen mesajın libp2p akışından geçirildiğidir. Akışın işleme yöntemi, alıcı eş düğüm tarafından uygulanır ve akışın oluşturulması, gönderen düğüm tarafından açılır.
Bu kodu, MakeStreamHandler'ın bir işleyici döndürdüğü ve SetStreamHandler'ın protokol kimliğini bu işleyiciye bağladığı önceki init işlevinde görebiliriz, böylece gönderen düğüm bu akışı her oluşturup kullandığında, döndürülen işleyici tetiklenir.
İşleyici içinde nasıl ele alındığına bakalım
İşlev, isteklerin ne kadar hızlı işlendiğini denetlemek için önce genel ve bireysel hız sınırı denetimleri gerçekleştirir. Ardından, istenen blok numarasını okuyup doğrulayarak makul bir aralıkta olmasını sağlar. İşlev daha sonra L2 katmanından isteğin öbek yükünü alır ve yanıt akışına yazar. Yanıt verilerini yazarken, yazma işlemi sırasında yavaş eş bağlantıları tarafından engellenmeyi önlemek için bir yazma son tarihi ayarlar. Son olarak, fonksiyon istenen blok numarasını ve olası hataları döndürür.
func (srv *ReqRespServer) handleSyncRequest(ctx context. Bağlam, akış ağı. Akış) (uint64, hata) {
peerId := akış. Conn() öğesini seçin. RemotePeer()
küresel oran sınırlayıcıdan bir jeton alın,
Farklı eşler arasında çok fazla eşzamanlı sunucu çalışması olmadığından emin olmak için.
if err := srv.globalRequestsRL.Wait(ctx); err != nil {
0, fmt dönüşü. Errorf("genel eşitleme hızı sınırı beklenirken zaman aşımına uğradı: %w", hata)
}
Eşin hız sınırlayıcı verilerini bulun veya başka bir şekilde ekleyin
srv.peerStatsLock.Lock()
ps, _ := srv.peerRateLimits.Get(peerId)
eğer ps == nil {
ps = &peerStat{
İstekler: oran. NewLimiter(peerServerBlocksRateLimit, peerServerBlocksBurst),
}
srv.peerRateLimits.Add(peerId, ps)
Ps. Requests.Reserve() // isabeti sayın, ancak hemen beklemek yerine bir sonraki isteği geciktirin
} başka {
Yalnızca mevcut bir eşse bekleyin, aksi takdirde anlık hız sınırı Bekle çağrısı her zaman hata verir.
Talepte bulunan kişi çok uzun sürdüğümüzü düşünüyorsa, bu onların sorunudur ve bağlantıyı kesebilir.
Sadece okumayı/yazmayı başaramadığımızda bağlantımızı keseceğiz,
Çalışma geçersizse (aralık doğrulaması) veya tek tek alt görevler zaman aşımına uğradığında.
eğer hata := ps. İstekler.Bekle(ctx); err != nil {
0, fmt dönüşü. Errorf("genel eşitleme hızı sınırı beklenirken zaman aşımına uğradı: %w", hata)
}
}
srv.peerStatsLock.Unlock()
Varsa son okuma tarihini ayarlayın
_ = akış. SetReadDeadline(zaman. Şimdi(). Add(serverReadRequestTimeout))
İsteği okuyun
req uint64
if err := ikili. Read(akış, ikili. LittleEndian, &req); err != nil {
0, fmt dönüşü. Errorf("istenen blok numarası okunamadı: %w", hata)
}
if err := akış. CloseRead(); err != nil {
Dönüş Gereksinimi, FMT. Errorf("P2P eşitleme isteği çağrısının okuma tarafı kapatılamadı: %w", hata)
}
İsteğin beklenen blok aralığında olup olmadığını kontrol edin
if req < srv.cfg.Genesis.L2.Number {
Dönüş Gereksinimi, FMT. Errorf("genesis %d: %w", req, srv.cfg.Genesis.L2.Number, invalidRequestErr'den önce L2 bloğu için istek sunulamıyor)
}
max, err := srv.cfg.TargetBlockNumber(uint64(time. Şimdi(). Unix()))
eğer err != nil {
Dönüş Gereksinimi, FMT. Errorf("isteği doğrulamak için maksimum hedef blok sayısı belirlenemiyor: %w", invalidRequestErr)
}
if req > max {
Dönüş Gereksinimi, FMT. Errorf("beklenen maksimum blok (%v): %w", req, max, invalidRequestErr'den sonra %d L2 bloğu için istek sunulamıyor)
}
payload, err := srv.l2.PayloadByNumber(ctx, req)
eğer err != nil {
hata verirse. Is(err, ethereum. Bulunamadı) {
Dönüş Gereksinimi, FMT. Errorf("eş, sayıya göre bilinmeyen blok istedi: %w", hata)
} başka {
Dönüş Gereksinimi, FMT. Errorf("eşe hizmet etmek için yük alınamadı: %w", hata)
}
}
Varsa, kısıtlamalı bir eş bağlantısını engellemeden güvenli bir şekilde yazmak için yazma son tarihini ayarlıyoruz
_ = akış. SetWriteDeadline(zaman. Şimdi(). Ekle(serverWriteChunkTimeout))
0 - resultCode: başarı = 0
1:5 - Sürüm: 0
.tmp [5] bayt
if _, err := akış. Yaz(tmp[:]); err != nil {
Dönüş Gereksinimi, FMT. Errorf("yanıt başlığı verileri yazılamadı: %w", hata)
}
w := çabuk. NewBufferedWriter(akış)
if _, err := yük. MareşalSSZ(w); err != nil {
Dönüş Gereksinimi, FMT. Errorf("yanıtı eşitlemek için yük yazılamadı: %w", hata)
}
if err := w.Close(); err != nil {
Dönüş Gereksinimi, FMT. Errorf("yanıtı eşitlemek için yükün yazılması tamamlanamadı: %w", hata)
}
dönüş gerekliliği, sıfır
}
Bu noktada, ters zincir senkronizasyon isteği ve işlemenin genel süreci açıklanmıştır
P2p düğümlerinde puan itibar sistemi
Belirli düğümlerin tüm ağın güvenliğini baltalayan kötü niyetli istekler ve yanıtlar vermesini önlemek için Optimism ayrıca bir puan sistemi kullanır.
Örneğin, op-node/p2p/app_scores.go içinde, bir eşin puanını ayarlamak için bir dizi işlev vardır
Libp2p'nin yüksek yapılandırılabilirliği, tüm proje p2p'sini son derece özelleştirilebilir ve modüler hale getirir, yukarıdakiler optimsim'in kişiselleştirilmiş libp2p uygulamasının ana mantığıdır ve diğer ayrıntılar p2p dizinindeki kaynak kodunu okuyarak ayrıntılı olarak öğrenilebilir.
View Original
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
Op-stack'te libp2p kullanımı
Joohhnnn tarafından yazıldı.
İyimserlikte Libp2P uygulamaları
Bu bölümde temel amaç, Optimism'in Op-Node'da P2P ağlarının kurulumunu tamamlamak için Libb2P'yi nasıl kullandığını açıklamaktır. P2P ağları esas olarak, Unsafe'in blok yapısını tamamladıktan sonra sıralayıcılar gibi farklı düğümlerde bilgi iletmek ve P2P'nin gossiphub'ının pub/sub'ı aracılığıyla yayılmak için kullanılır. Libb2P ayrıca P2P ağlarında ağ oluşturma, adresleme vb. gibi diğer altyapılarla da ilgilenir.
libp2p hakkında bilgi edinin
libp2p ("Library Peer-to-Peer" veya "Library Peer-to-Peer" olarak kısaltılır), P2P uygulamalarının geliştirilmesine yardımcı olan bir eşler arası (P2P) ağ çerçevesidir. Ağ katılımcıları ("eşler" veya "eşler" olarak da bilinir) arasındaki P2P iletişimini kolaylaştıran bir dizi protokol, özellik ve kitaplık içerir (kaynak[2] [3])。 Başlangıçta IPFS (Gezegenler Arası Dosya) projesinin bir parçası olan libp2p, dağıtılmış ağlar için modüler bir ağ yığını (kaynak) haline gelen bağımsız bir projeye dönüştü )。
libp2p, IPFS topluluğunun, spesifikasyonların yazılmasına, kod uygulamalarına ve örnekler ve öğreticiler oluşturmaya yardımcı olmak da dahil olmak üzere çok çeşitli toplulukların katkılarını memnuniyetle karşılayan açık kaynaklı bir projesidir[4] [5])。 libp2p, her biri çok net, belgelenmiş ve test edilmiş bir arayüze sahip birden fazla yapı taşından oluşur ve bu da onları birleştirilebilir, değiştirilebilir ve dolayısıyla yükseltilebilir kılar )。 libp2p'nin modüler yapısı, geliştiricilerin yalnızca uygulamaları için gerekli bileşenleri seçmelerine ve kullanmalarına olanak tanıyarak P2P web uygulamaları oluştururken esnekliği ve verimliliği artırır.
İlgili kaynaklar
libp2p'nin modüler mimarisi ve açık kaynak yapısı, güçlü, ölçeklenebilir ve esnek P2P uygulamaları geliştirmek için iyi bir ortam sağlayarak onu dağıtılmış ağlar ve ağ uygulaması geliştirme alanında önemli bir oyuncu haline getirir.
libp2p uygulaması
libp2p kullanırken, P2P ağınızı oluşturmak için bazı temel bileşenleri uygulamanız ve yapılandırmanız gerekecektir. Uygulamada libp2p'nin ana uygulama yönlerinden bazıları şunlardır:
1. Düğüm Oluşturma ve Yapılandırma:
libp2p'yi seçin. Yeni()
2. Taşıma Protokolü:
Düğümler arasında iletişimi sağlamak için aktarım protokollerinizi (ör. TCP, WebSockets vb.) seçin ve yapılandırın. Anahtar kullanım kodları:
tcpTransport := tcp. YeniTCPTransport()
3. Çoğullama ve Akış Kontrolü:
yamuxTransport := yamux. Yeni()
4. Güvenlik ve şifreleme:
tlsTransport := tls. Yeni()
5. Protokoller ve Mesaj İşleme:
Belirli ağ işlemlerini ve mesaj alışverişlerini işlemek için özel protokoller tanımlayın ve uygulayın.
ev sahibi. SetStreamHandler("/protokolüm/1.0.0", myProtocolHandler)
6. Keşif ve Yönlendirme:
dht := kaddht. NewDHT(ctx, ana bilgisayar, veri deposu. NewMapDatastore())
7. Ağ davranışı ve politikaları:
Bağlantı yönetimi, hata işleme ve yeniden deneme mantığı gibi ağ davranışlarını ve ilkelerini tanımlayın ve uygulayın. Anahtar kullanım kodları:
connManager := connmgr. NewConnManager(lowWater, highWater, gracePeriod)
8. Durum Yönetimi ve Depolama:
Bağlantı durumu, düğüm listesi ve veri depolama dahil olmak üzere düğümlerin ve ağların durumunu yönetin. Anahtar kullanım kodları:
peerstore := pstoremem. YeniEşler Deposu()
9. Test edin ve hata ayıklayın:
günlük tutmak. SetLogLevel("libp2p", "HATA AYIKLA")
10. Dokümantasyon ve Topluluk Desteği:
}
Yukarıdakiler, libp2p kullanırken göz önünde bulundurulması ve uygulanması gereken ana hususlardan bazılarıdır. Her projenin özel uygulaması değişebilir, ancak bu temel yönler libp2p uygulamaları oluşturmak ve çalıştırmak için gereklidir. Bu işlevleri uygularken, libp2p'nin resmi belgelerine başvurabilirsiniz[9] [10]ve GitHub depoları Örnek kod ve öğreticiler.
OP düğümünde libp2p kullanımı
Op-node ve libp2p arasındaki ilişkiyi anlamak için birkaç soruyu çözmemiz gerekiyor
Op-node'ların neden bir libp2p ağına ihtiyacı var?
Öncelikle Optimism'in neden bir P2P ağı gerektirdiğini anlamamız gerekiyor ** LibbP2P, geliştiricilerin birden fazla kullanım durumu için merkezi olmayan eşler arası uygulamalar oluşturmasına olanak tanıyan modüler bir ağ protokolüdür (kaynaklar[11] [12])(kaynak [13])。 DevP2P ise esas olarak Ethereum ekosisteminde kullanılır ve Ethereum uygulamaları için uyarlanmıştır (Kaynak )。 Libp2p'nin esnekliği ve geniş uygulanabilirliği, onu geliştiriciler için en iyi seçim haline getirebilir.
Op-Node esas olarak libp2p işlev noktalarını kullanır
Kod uygulaması
ana bilgisayar özel başlatma
Ana bilgisayar bir P2P düğümü olarak anlaşılabilir, bu düğümü açarken kendi projeniz için bazı özel başlatma yapılandırmaları yapmanız gerekir
Şimdi op-node/p2p/host.go dosyasındaki Host metoduna bakalım.
Bu işlev esas olarak libp2p ana bilgisayarını kurmak ve çeşitli yapılandırmalar yapmak için kullanılır. İşte işlevin temel kısımları ve her birinin basit bir Çince açıklaması:
P2P devre dışı bırakılırsa, işlev doğrudan geri döner.
Eş Kimliği oluşturmak için yapılandırmadaki ortak anahtarı kullanın.
Temel bir Peerstore mağazası oluşturun.
Temel Peerstore'un üstünde, genişletilmiş bir Peerstore oluşturun.
Eşin özel ve genel anahtarlarını eş deposunda depolayın.
Ağ bağlantılarını kontrol etmek için kullanılır.
Ağ bağlantılarını yönetmek için kullanılır.
Ağ aktarım protokolünü ve ana bilgisayarın dinleme adresini ayarlayın.
Yeni bir libp2p ana bilgisayarı oluşturmak için önceki tüm ayarları kullanın.
Yapılandırılmış bir statik eşiniz varsa, bunu başlatın.
Son olarak, işlev oluşturulan libp2p ana bilgisayarını döndürür.
Bu anahtar bölümler libp2p ana bilgisayarını başlatmaktan ve kurmaktan sorumludur ve her bölüm ana bilgisayar yapılandırmasının belirli bir yönünden sorumludur.
Yapılandırmadaki (cfg) L2ChainID'ye göre bir dizeyi biçimlendiren blocksTopicV1 işlevini kullanarak blocksTopicName oluşturun. Biçimlendirilmiş dizeler belirli bir yapıyı izler: /optimism/{L2ChainID}/0/blocks.
ps'yi çağırarak işlev görür. Join(blocksTopicName) bir blok dedikodu teması eklemeye çalışır. Bir hata oluşursa, konunun birleştirilemediğini belirten bir hata iletisi döndürür.
LogTopicEvents işlevini kullanarak konu olaylarını günlüğe kaydetmek için yeni bir goroutine oluşturuldu.
GossipIn'den OnUnsafeL2Payload olaylarını işleyen bir BlocksHandler'ı kapsülleyen MakeSubscriber işlevini kullanarak bir abone oluşturuldu. Sağlanan alt iyonu çalıştırmak için yeni bir goroutine oluşturuldu.
func JoinGossip(p2pCtx context. Bağlam, öz peer.ID, ps *pubsub. PubSub, günlük günlüğü. Kaydedici, cfg *toplama. Config, runCfg GossipRuntimeConfig, gossipIn GossipIn) (GossipOut, hata) {
blocksTopicName := blocksTopicV1(cfg) // return fmt. Sprintf("/iyimserlik/%s/0/bloklar", cfg. L2ChainID.String())
Dönüş sıfır, fmt. Errorf("bloklar kaydedilemedi dedikodu konusu: %w", hata)
}
blocksTopic, err := ps. Join(blocksTopicName)
eğer err != nil {
}
eğer err != nil {
}
git LogTopicEvents(p2pCtx, günlük. New("konu", "bloklar"), blocksTopicEvents)
eğer err != nil {
}
return &publisher{log: log, cfg: cfg, blocksTopic, runCfg: runCfg}, nil
op-node/rollup/driver/state.go
Eksik bloklar olduğunda, P2P ile hızlı senkronizasyon
Kesinti süresinden sonra yeniden bağlanma gibi özel durumlar nedeniyle düğümler kesinti süresinden sonra yeniden bağlandığında, bazı bloklar (boşluklar) senkronize edilemeyebilir ve bu durumla karşılaştıklarında P2P ağının ters zinciri üzerinden hızlı bir şekilde senkronize olabilirler.
Op-node/rollup/driver/state.go içindeki checkForGapInUnsafeQueue işlevine bir göz atalım
Kod parçacığı, Driver yapısına ait olan checkForGapInUnsafeQueue adlı bir yöntemi tanımlar. Amacı, "güvenli olmayan kuyruk" adı verilen bir kuyruktaki veri boşluklarını kontrol etmek ve altSync adlı alternatif bir senkronizasyon yöntemi aracılığıyla eksik yükleri almaya çalışmaktır. Buradaki kilit nokta, yöntemin veri sürekliliğini sağlamak ve eksik veri tespit edildiğinde eksik verileri diğer senkronizasyon yöntemlerinden almaya çalışmak olmasıdır. İşte fonksiyonun ana adımları:
İşlev ilk olarak kontrol aralığının başlangıç ve bitiş noktaları olarak s.derivation'dan UnsafeL2Head ve UnsafeL2SyncTarget'ı alır.
Fonksiyon, bitiş ve başlangıç Sayı değerlerini karşılaştırarak yapılan başlangıç ve bitiş arasındaki eksik blokları kontrol eder.
Bir veri boşluğu algılanırsa, işlev s.altSync.RequestL2Range(ctx, start, end) öğesini çağırarak eksik veri aralığını isteyecektir. End boş bir referanssa (örn. L2BlockRef{}), işlev start ile başlayan bir açık uçlu aralık senkronizasyonu isteyecektir.
Veri talep ederken, işlev hangi veri aralığını istediğini gösteren bir hata ayıklama günlüğü kaydeder.
İşlev sonunda bir hata değeri döndürür. Herhangi bir hata yoksa, nil döndürür
checkForGapInUnsafeQueue, güvenli olmayan kuyrukta bir boşluk olup olmadığını kontrol eder ve eksik yükleri bir alt-sync yönteminden almaya çalışır.
UYARI: Bu sadece giden bir sinyaldir, blokların geri alınması garanti edilmez.
Sonuçlar OnUnsafeL2Payload aracılığıyla alınır.
func (s *Sürücü) checkForGapInUnsafeQueue(ctx context. Bağlam) hatası {
start := s.derivation.UnsafeL2Head()
end := s.derivation.UnsafeL2SyncTarget()
Başlangıç ve bitiş arasında eksik bloklarımız olup olmadığını kontrol edin Varsa talep edin.
if end == (eth. L2BlockRef{}) {
s.log.Debug("açık uçlu aralıkla senkronizasyon isteniyor", "start", start)
return s.altSync.RequestL2Range(ctx, start, eth. L2BlockRef{})
} else if end. > numara başlar. Sayı+1 {
s.log.Debug("eksik güvenli olmayan L2 blok aralığı isteniyor", "başlangıç", başlangıç, "bitiş", bitiş, "boyut", bitiş. Sayı-başlangıç.Sayı)
return s.altSync.RequestL2Range(ctx, başlangıç, bitiş)
}
Dönüş sıfır
}
RequestL2Range işlevi, istek bloğunun başlangıcını ve bitişini istekler kanalına bildirir.
İstek daha sonra onRangeRequest yöntemi aracılığıyla peerRequests kanalına dağıtılır ve peerRequests kanalı birden çok eş tarafından açılan döngü tarafından beklenir, yani her dağıtım için isteği yalnızca bir eş işleyecektir.
Eş bu isteği aldığında ne olacağını görelim.
Bilmemiz gereken ilk şey, eş ve istekte bulunan düğüm arasındaki bağlantının veya iletilen mesajın libp2p akışından geçirildiğidir. Akışın işleme yöntemi, alıcı eş düğüm tarafından uygulanır ve akışın oluşturulması, gönderen düğüm tarafından açılır.
Bu kodu, MakeStreamHandler'ın bir işleyici döndürdüğü ve SetStreamHandler'ın protokol kimliğini bu işleyiciye bağladığı önceki init işlevinde görebiliriz, böylece gönderen düğüm bu akışı her oluşturup kullandığında, döndürülen işleyici tetiklenir.
İşleyici içinde nasıl ele alındığına bakalım İşlev, isteklerin ne kadar hızlı işlendiğini denetlemek için önce genel ve bireysel hız sınırı denetimleri gerçekleştirir. Ardından, istenen blok numarasını okuyup doğrulayarak makul bir aralıkta olmasını sağlar. İşlev daha sonra L2 katmanından isteğin öbek yükünü alır ve yanıt akışına yazar. Yanıt verilerini yazarken, yazma işlemi sırasında yavaş eş bağlantıları tarafından engellenmeyi önlemek için bir yazma son tarihi ayarlar. Son olarak, fonksiyon istenen blok numarasını ve olası hataları döndürür.
Bu noktada, ters zincir senkronizasyon isteği ve işlemenin genel süreci açıklanmıştır
P2p düğümlerinde puan itibar sistemi
Belirli düğümlerin tüm ağın güvenliğini baltalayan kötü niyetli istekler ve yanıtlar vermesini önlemek için Optimism ayrıca bir puan sistemi kullanır.
Örneğin, op-node/p2p/app_scores.go içinde, bir eşin puanını ayarlamak için bir dizi işlev vardır
Yeni düğümün entegrasyon durumu daha sonra eklenmeden önce kontrol edilir
Özet
Libp2p'nin yüksek yapılandırılabilirliği, tüm proje p2p'sini son derece özelleştirilebilir ve modüler hale getirir, yukarıdakiler optimsim'in kişiselleştirilmiş libp2p uygulamasının ana mantığıdır ve diğer ayrıntılar p2p dizinindeki kaynak kodunu okuyarak ayrıntılı olarak öğrenilebilir.