Arhitectură de aplicații descentralizate: modele de întoarcere, securitate și design

Aplicațiile descentralizate, sau ÐApps, necesită un design special al sistemului pentru a obține securitate și fiabilitate ridicate. În acest articol voi acoperi câteva principii principale despre cum să proiectăm și să implementăm în mod corespunzător contracte inteligente pentru aplicații descentralizate, luând Ethereum ca exemplu principal, deși o mare parte din acesta se va aplica EOS, Tron și alte platforme de date descentralizate.

Repere articol:

  • Cum să stocați cheile private pe partea din spate, fără probleme de securitate
  • Cum să proiectăm în mod corespunzător contracte inteligente și ce să „descentralizăm”
  • Exemple de arhitectură de aplicații descentralizate și semicentralizate
  • Cum să abordați chestiuni la nivel scăzut, cum ar fi încărcarea rețelei și eșecurile

Va fi mare, să o facem!

Programe descentralizate și Blockchain

În ciuda faptului că blockchainul se confruntă cu multe dificultăți de adoptare și reglementare în ziua de azi, este un fel de tehnologie care este aici pentru a rămâne, indiferent dacă este blockchain, hashgraph, tempo sau orice altă tehnologie ledger distribuită încă de venit, indiferent de algoritm.

Valoarea principală pe care o aduc blockchain-ul și alte tehnologii similare poate fi generalizată după cum urmează: le permite oamenilor să scrie și să ruleze programe care, practic, nu pot fi modificate după creare și nici modificate în timpul executării. Cu alte cuvinte, aceste programe rulează întotdeauna așa cum este proiectat și nici o singură parte nu le poate influența comportamentul.

Această definiție este valabilă pentru multe criptomonede care există astăzi dacă le considerăm programe care definesc modul în care monedele pot fi transferate înainte și înapoi. Acest lucru explică, de asemenea, de ce criptocurrențele și multe tipuri de jetoane au o valoare reală: nu pot fi generate din aer subțire, prin „programele de bază” definite.

Platformele Ethereum / EOS / Tron /…, spre deosebire de Bitcoin, implementează un strat de program mai complex, care la rândul său implementează mediul de execuție, permițând oricui să-și scrie propriile programe descentralizate în partea de sus a platformei. Aceste programe definite de utilizator rulează întotdeauna așa cum este proiectat, fără excepții, iar securitatea acestora este garantată de platformă.

Aplicații descentralizate

Aceste programe sigure și neschimbabile care rulează pe o rețea descentralizată în combinație cu tehnologiile tradiționale front-end și back-end sunt ceea ce astăzi numim aplicații descentralizate (ÐApps). Prin unele dintre ele pot fi semicentralizate, o parte majoră a activităților din aplicația cu adevărat descentralizată ar trebui să se întâmple în afara controlului unui partid central.

Dacă cineva mi-ar fi cerut să desenez cum funcționează DApps astăzi, probabil aș fi desenat asta

Pentru a vă imagina ceea ce numim astăzi aplicații descentralizate, luați ca exemplu orice resursă web centralizată existentă, cum ar fi _YouTube_ sau _Instagram_ și imaginați-vă că în loc de un cont centralizat protejat de parolă aveți „identitatea criptă” legată de resursa web / mobilă.

Acest lucru vă oferă aplicația Wallet Software. Cheia privată de la această identitate (un secret, care are, care poate acționa în numele acestei identități) este stocată pe dispozitivul dvs. local și nu merge niciodată online, făcând nimeni să nu poată controla această identitate, ci tu. Cu această identitate, puteți efectua diferite acțiuni atât în ​​sistem centralizat (resursă web controlată de o autoritate centrală), cât și descentralizat (care este o rețea diferită de tradiționalul www, al cărui obiectiv este eliminarea autorității centrale), folosind site-ul web ca punct de acces și / sau ca interfață grafică de utilizator. Întregul punct al acestei „identități cripto” este că acțiunile dvs. sunt securizate criptografic și nimeni nu este în măsură să schimbe ceea ce ați semnat și nici semnătura dvs.

Astăzi, capacitățile de calcul și de stocare ale rețelelor descentralizate tolerante la erori precum Ethereum, EOS sau Tron sunt limitate. Dacă ar fi scalabile, am putea folosi rețele descentralizate pentru a stoca întreaga aplicație descentralizată, inclusiv interfața sa de utilizator grafică, datele și logica de afaceri. În acest caz, am numi aceste aplicații cu adevărat descentralizate / distribuite.

Cu toate acestea, deoarece aceste rețele nu pot fi scalabile astăzi, combinăm diferite abordări pentru a atinge nivelul maxim de descentralizare pentru aplicațiile noastre. Fundalul „tradițional”, întrucât știm că nu merge nicăieri. De exemplu:

  • Utilizăm back end pentru a găzdui front end pentru o aplicație descentralizată.
  • Folosim back end pentru integrări cu orice alte tehnologii și servicii existente. Aplicațiile reale de nivel mondial nu pot trăi într-un mediu izolat.
  • Utilizăm back end pentru a stoca și prelucra orice suficient de mare pentru o rețea descentralizată (în special blockchain). Practic, întreaga aplicație și logica sa de afaceri sunt stocate undeva în lume, exclusiv doar partea blockchain. Ca să nu mai vorbim, IPFS și straturi de stocare similare nu pot garanta accesibilitatea fișierelor, prin urmare, nu ne putem baza pe ele fără să găzduim fișierele noastre. Cu alte cuvinte, este întotdeauna nevoie de un server de rulare dedicat.

Nu există nicio modalitate de a construi o aplicație sigură și parțial descentralizată fără a utiliza un back-end solid ca în ziua de azi, iar întregul articol al acestui articol este să explici cum să faci acest lucru.

(De) centralizare și jetoane

La fel se întâmplă că aproape toate aplicațiile descentralizate de astăzi sunt construite în jurul așa-numitelor jetoane - criptocurrențe personalizate (sau pur și simplu simple clonate) care conduc o anumită aplicație descentralizată. Jetoanele sunt pur și simplu o monedă sau active programabile, adică.

În timp ce contractele inteligente cu simboluri determină modul în care utilizatorii pot transfera token-uri, contractele inteligente pentru aplicații pot extinde tot ceea ce lipsește în contractele inteligente cu simboluri. Ambele contracte inteligente funcționează în topul rețelelor descentralizate

De obicei, un jeton este un „contract inteligent” (un program personalizat) scris pe partea de sus a platformei descentralizate precum Ethereum. Deținând câteva jetoane, puteți obține, în principiu, diverse servicii pe o resursă web sau o aplicație mobilă și puteți schimba acest simbol pentru altceva. Punctul cheie aici este că jetonul trăiește singur și nu este controlat de o autoritate centrală.

Există numeroase exemple de aplicații care se construiesc în jurul token-urilor: de la numeroase jocuri de colecție precum CryptoKitties (jetoane ERC721) la aplicații mai orientate către servicii precum LOOM Network sau chiar browsere precum Brave și platforme de jocuri precum DreamTeam (jetoane compatibile cu ERC20).

Dezvoltatorii înșiși determină și decid cât de mult controlul vor avea (sau nu vor avea) asupra aplicațiilor lor. Aceștia pot construi toată logica de afaceri a aplicației pe baza contractelor inteligente (cum a făcut CryptoKitties) sau, nu pot folosi deloc contracte inteligente, centralizând totul pe serverele lor. Cu toate acestea, cea mai bună abordare este undeva la mijloc.

Back End pentru rețele descentralizate

Din punct de vedere tehnic, trebuie să existe un pod care conectează jetoane și alte contracte inteligente cu aplicația web / mobilă.

În aplicațiile complet descentralizate de astăzi, în care clienții interacționează direct cu contractele inteligente, acest pod se restrânge la capacitățile API JSON RPC ale API-urilor publice sau bazinelor nodale precum Infura, care la rândul lor sunt obligate să existe din cauza faptului că nu orice dispozitiv poate rulați și susțineți nodul său de rețea individual. Cu toate acestea, această API oferă un set de funcții doar de bază și foarte restrâns, care abia permit efectuarea de întrebări simple și nici nu se agregă eficient datele. Din această cauză, în cele din urmă, partea din spate personalizată intră, ceea ce face ca aplicația să fie semi-centralizată.

Întreaga interacțiune cu rețeaua descentralizată poate fi redusă la unul sau două puncte, în funcție de nevoile aplicației:

  1. Ascultarea evenimentelor din rețea (cum ar fi transferurile de jetoane) / citirea stării rețelei.
  2. Tranzacții de publicare (invocarea funcțiilor contractului inteligent care se schimbă de stat, precum transferul token).

Punerea în aplicare a ambelor puncte este destul de complicată, mai ales dacă dorim să construim o soluție de securitate sigură și fiabilă. Iată care sunt principalele puncte pe care le vom descrie mai jos:

  • În primul rând, în Ethereum, preluarea evenimentelor nu este pregătită pentru producție. Din mai multe motive: nodurile de rețea pot eșua în timp ce preluăm un număr mare de evenimente, evenimentele pot dispărea sau se pot schimba din cauza furcilor de rețea, etc. Trebuie să construim un strat de abstractizare pentru a sincroniza evenimentele din rețea și pentru a garanta livrarea fiabilă a acestora.
  • La fel pentru publicarea tranzacțiilor, trebuie să extragem lucrurile la nivel scăzut ale Ethereum, cum ar fi contoarele nonce și estimările gazelor, precum și republicarea tranzacțiilor, oferind o interfață fiabilă și stabilă. Mai mult, publicarea tranzacțiilor presupune utilizarea cheilor private, care necesită o securitate avansată.
  • Securitate. Îl vom lua în serios și vom înțelege că este imposibil să garantăm că cheile private nu vor fi niciodată compromise într-un final. Din fericire, există o abordare a proiectării unei aplicații descentralizate, fără a fi nevoie chiar și de conturi de bază pentru a fi securizate.

În practica noastră, toate acestea ne-au făcut să creăm o soluție robustă pentru Ethereum pe care o numim Ethereum Gateway. Rezumă alte microservicii din distracția lui Ethereum și oferă o API fiabilă pentru a lucra cu ea.

Ca o pornire în creștere rapidă, nu putem dezvălui soluția completă (doar încă) din motive evidente, dar voi împărtăși tot ceea ce trebuie să știi pentru ca aplicația descentralizată să funcționeze perfect. Cu toate acestea, dacă aveți întrebări sau întrebări specifice, nu ezitați să mă contactați. Comentariile la acest articol sunt foarte apreciate!

Back End Monitoring pentru Ethereum. Monitorul demonstrează activități în principal cu privire la caracteristica noastră de facturare recurentă (deși puteți vedea picuri care se petrec în fiecare oră).

Arhitectura aplicațiilor descentralizate

Această parte a articolului depinde foarte mult de nevoile unei anumite aplicații descentralizate, dar vom încerca să sortăm câteva tipare de interacțiune de bază pe baza cărora sunt construite aceste aplicații (ÐPlatform = Platformă descentralizată = Ethereum / EOS / Tron / Whatever):

Client ⬌ latPlatform: aplicații complet descentralizate.

Clientul (browserul sau aplicația mobilă) discută direct cu platforma descentralizată cu ajutorul software-ului „portofel” Ethereum precum Metamask, Trust sau portofele hardware precum Trezor sau Ledger. Exemple de DAPps construite în acest mod sunt CryptoKitties, apelul delegat al lui Loom, portofelele crypto în sine (Metamask, Trust, Tron Wallet, altele), schimburile criptate descentralizate precum Etherdelta ș.a.

ÐPlatform ⬌ Client ⬌ End End ⬌ ÐPlatform: aplicații centralizate sau semi-centralizate.

Interacțiunea clientului cu platforma descentralizată și serverul nu are prea multe în comun. Un bun exemplu în acest sens este orice schimb de criptă (centralizat) în ziua de azi, cum ar fi BitFinex sau Poloniex: valutele pe care le tranzacționați pe burse sunt pur și simplu înregistrate în baza de date tradițională. Puteți „reîncărca” soldul dvs. de bază prin trimiterea activelor la o anumită adresă (ÐPlatform ⬌ Client) și apoi puteți retrage activele după unele acțiuni din aplicație (Back End ⬌ latPlatform), totuși, tot ceea ce faceți în termeni de „ppApp” în sine (Client ⬌ Back End) nu implică interacțiunea dvs. directă cu ÐPlatform.

Un alt exemplu este Etherscan.io, care folosește o abordare semi-centralizată: puteți face toate acțiunile descentralizate utile acolo, dar aplicația în sine nu are sens fără finalizarea lor completă (Etherscan sincronizează continuu tranzacțiile, analizează datele și le stochează, oferind în cele din urmă o API / UI completă).

Ceva între ele: aplicații încă, centralizate sau semi-centralizate.

Abordările de mai sus sunt combinate. De exemplu, putem avea o aplicație care oferă diverse servicii în schimbul crypto, permițându-vă să vă conectați și să semnați informațiile cu identitatea dvs. de criptografie.

Sperăm, modelul de interacțiune al aplicațiilor complet descentralizate (Client ⬌ latPlatform) nu ridică nicio întrebare. Bazându-vă pe astfel de servicii uimitoare precum Infura sau Trongrid, pur și simplu puteți construi o aplicație care nu necesită deloc un server. Aproape toate bibliotecile din partea clientului, precum Ethers.js pentru Ethereum sau Tron-Web pentru Tron, se pot conecta la aceste servicii publice și pot comunica cu rețeaua. Cu toate acestea, pentru interogări și sarcini mai complexe, poate fi necesar să alocați propriul server.

Restul modelelor de interacțiune care implică întoarcerea fac lucrurile mai interesante și mai complexe. Pentru a pune toate acestea într-o imagine, să ne imaginăm un caz în care partea noastră din spate reacționează la un eveniment din rețea. De exemplu, utilizatorul publică o tranzacție de indemnizație care ne dă permisiunea de a le percepe. Pentru a efectua o taxă, trebuie să publicăm tranzacția de taxare ca răspuns la evenimentul de indemnizație emis:

Un exemplu de flux al reacției serverului la acțiunea utilizatorului în rețeaua descentralizată

Din punct de vedere posterior, iată ce se întâmplă:

  1. Ascultăm un anumit eveniment de rețea prin sondarea continuă a rețelei.
  2. După ce obținem un eveniment, efectuăm unele logici de afaceri și apoi decidem să publicăm o tranzacție ca răspuns.
  3. Înainte de publicarea tranzacției, dorim să ne asigurăm că aceasta va fi minată (în Ethereum, estimarea cu succes a gazului de tranzacție înseamnă că nu există erori împotriva stării actuale a rețelei). Cu toate acestea, nu putem garanta că tranzacția va fi extrasă cu succes.
  4. Folosind o cheie privată, semnăm și publicăm tranzacția. În Ethereum, de asemenea, trebuie să stabilim prețul gazului și limita gazului pentru tranzacție.
  5. Dupa publicarea tranzactiei, interzicem continuu reteaua pentru starea acesteia.
  6. Dacă durează prea mult și nu putem obține starea tranzacției, trebuie să o publicăm din nou sau să declanșăm un „scenariu de eșec”. Tranzacțiile pot fi pierdute din diverse motive: congestionarea rețelei, scăderea colegilor, creșterea încărcăturii de rețea etc. În Ethereum, puteți, de asemenea, să luați în considerare resemnarea unei tranzacții cu un preț diferit (actual) al gazului.
  7. După ce în sfârșit ne extragem tranzacția, putem efectua mai multe logici de afaceri, dacă este nevoie. De exemplu, putem notifica alte servicii de retard despre faptul că tranzacția a fost finalizată. De asemenea, aveți în vedere să așteptați câteva confirmări înainte de a lua decizii finale cu privire la tranzacție: rețeaua este distribuită și, prin urmare, rezultatul se poate schimba în câteva secunde.

După cum vedeți, se întâmplă multe! Cu toate acestea, este posibil ca aplicația dvs. să nu necesite unele dintre aceste etape, în funcție de ceea ce încercați să realizați. Dar, construirea unui fundal robust și stabil necesită o soluție pentru toate problemele menționate mai sus. Să dărâmăm asta.

Aplicații descentralizate Back End

Aici vreau să subliniez câteva dintre punctele care apar cele mai multe întrebări, și anume:

  • Ascultarea evenimentelor din rețea și citirea datelor din rețea
  • Tranzacții de publicare și cum se face în siguranță

Ascultarea evenimentelor din rețea

În Ethereum, precum și în alte rețele descentralizate, un concept de evenimente contractuale inteligente (sau jurnalele de evenimente, sau doar jurnalele) permite aplicațiilor din lanț să fie conștiente de ceea ce se întâmplă în blockchain. Aceste evenimente pot fi create de dezvoltatorii de contracte inteligente în orice punct al codului de contract inteligent.

De exemplu, în cadrul binecunoscutului standard de jeton ERC20, fiecare transfer de jeton trebuie să înregistreze evenimentul Transfer, făcând astfel cunoscute aplicațiilor off-chain că există un transfer token. Prin „ascultarea” acestor evenimente putem efectua orice (re) acțiuni. De exemplu, unele portofeluri mobile vă trimit o notificare push / e-mail atunci când token-urile sunt transferate la adresa dvs.

De fapt, nu există o soluție fiabilă pentru ascultarea evenimentelor din rețea. Diferite biblioteci vă permit să urmăriți / să ascultați evenimente, cu toate acestea, există multe cazuri în care ceva poate merge prost, rezultând în evenimente pierdute sau neprocesate. Pentru a evita pierderea evenimentelor, trebuie să construim un back-end personalizat, care va menține procesul de sincronizare a evenimentelor.

În funcție de nevoile dvs., implementarea poate varia. Dar pentru a vă prezenta într-o imagine aici este una dintre opțiunile cum puteți construi furnizarea de evenimente Ethereum fiabile în ceea ce privește arhitectura microserviciului:

Livrarea de încredere a evenimentelor Ethereum către toate serviciile de tip back-end

Aceste componente funcționează în felul următor:

  1. Evenimentele care sincronizează serviciul final în sondaje interzice permanent rețeaua, încercând să preia noi evenimente. Odată ce există câteva evenimente noi disponibile, trimite aceste evenimente în magistrala de mesaje. La trimiterea cu succes a evenimentului în autobuzul de mesaje, ca și pentru blockchain, putem salva blocul ultimului eveniment pentru a solicita evenimente noi din acest bloc data viitoare. Rețineți că recuperarea prea multor evenimente simultan poate duce la eșecul cererilor întotdeauna, deci trebuie să limitați numărul de evenimente / blocuri solicitate din rețea.
  2. Bus-ul de mesaje (de exemplu, Rabbit MQ) rutează evenimentul la fiecare coadă care a fost configurată individual pentru fiecare serviciu de întoarcere. Înainte de publicarea de evenimente, serviciile de sincronizare a evenimentelor specifică cheia de rutare (de exemplu, o adresă inteligentă a contractului + subiectul evenimentului), în timp ce consumatorii (alte servicii intermediare) creează cozi la care sunt abonați doar pentru anumite evenimente.

Drept urmare, fiecare serviciu de întoarcere primește doar acele evenimente de care are nevoie. Mai mult, autobuzul de mesaje garantează livrarea tuturor evenimentelor odată ce i-au fost publicate.

Desigur, puteți utiliza altceva în loc de magistrala de mesaje: apeluri HTTP, prize, etc. În acest caz, va trebui să vă dați seama cum să vă garantați livrarea apelurilor de apeluri: gestionați încercări de apelare exponențială / personalizate, implementați monitorizarea personalizată și curând.

Publicarea tranzacțiilor

Există câteva etape pe care trebuie să le realizăm pentru a publica o tranzacție în rețeaua descentralizată:

  1. Pregătirea tranzacției. Alături de datele tranzacțiilor, acest pas implică solicitarea stării rețelei pentru a afla dacă această tranzacție este valabilă și va fi extrasă (estimarea gazului în Ethereum) și numărul secvențial al tranzacției (nonce în Ethereum). Unele dintre biblioteci încearcă să facă acest lucru sub capotă, însă acești pași sunt importanți.
  2. Semnarea tranzacției. Acest pas implică utilizarea cheii private. Cel mai probabil, aici veți dori să încorporați soluția de asamblare a cheilor private personalizate (de exemplu).
  3. Publicarea și republicarea tranzacției. Unul dintre punctele cheie este că tranzacția dvs. publicată are întotdeauna șansa de a vă pierde sau renunța la rețeaua descentralizată. De exemplu, în Ethereum, tranzacția publicată poate fi abandonată dacă prețul la gaz al rețelei crește brusc. În acest caz, trebuie să republicați tranzacția. Mai mult, este posibil să doriți să republicați tranzacția cu alți parametri (cel puțin cu un preț mai ridicat al gazelor naturale) pentru a-l extrage cât mai curând posibil. Astfel, republicarea tranzacției poate presupune resemnarea acesteia, dacă tranzacția de înlocuire nu a fost semnată în prealabil (cu parametri diferiți).
Punctele de mai sus referitoare la publicarea tranzacțiilor Ethereum vizualizate

Folosind abordările de mai sus, puteți ajunge să construiți ceva similar cu ceea ce este prezentat în diagrama de secvență de mai jos. În această diagramă de secvență particulară, demonstrez (în general!) Cum funcționează facturarea repetată a blockchain-ului (există mai multe într-un articol legat):

  1. Utilizatorul execută o funcție într-un contract inteligent, ceea ce permite în cele din urmă efectuarea unei operațiuni de încărcare cu succes.
  2. Un serviciu de întoarcere responsabil pentru o anumită sarcină ascultă evenimentul alocației de taxe și publică o tranzacție de taxare.
  3. Odată extinsă tranzacția de încărcare, acest serviciu de întoarcere responsabil pentru o anumită sarcină primește un eveniment din rețeaua Ethereum și execută o anumită logică (inclusiv setarea datei de încărcare următoare).
Diagrama de secvență generală a modului în care funcționează facturarea recurentă a blockchainului, care demonstrează interacțiunea dintre serviciile back-end și rețeaua Ethereum

Securitate înapoi și contracte inteligente

Publicarea tranzacțiilor implică întotdeauna utilizarea unei chei private. Este posibil să vă întrebați dacă este posibil să păstrați cheile private în siguranță. Ei bine, da și nu. Există numeroase strategii complexe și diferite tipuri de software care permit stocarea cheilor private pe partea din spate destul de sigur. Unele soluții private de stocare cu cheie folosesc baze de date geo-distribuite, în timp ce altele chiar sugerează utilizarea hardware-ului special. Cu toate acestea, în orice caz, cel mai vulnerabil punct al unei aplicații semicentralizate este acela în care cheia privată este asamblată și folosită pentru a semna o tranzacție (sau, în cazul unui hardware special, un punct de declanșare a unei proceduri de semnare a tranzacțiilor). Prin urmare, teoretic, nu există o soluție 100% fiabilă care să permită protecția antiglonț împotriva compromiterii cheilor private stocate.

Acum gândiți-vă astfel. În multe cazuri, nu este nevoie să protejați de multe ori cheile private pe partea din spate. În schimb, puteți proiecta contracte inteligente și întreaga aplicație astfel încât o scurgere de chei private să nu afecteze comportamentul lor obișnuit. Cu această abordare, nu contează cum interacționează conturile autorizate cu contractul inteligent. Doar „declanșează” un contract inteligent pentru a-și face munca obișnuită, iar contractul inteligent în sine efectuează orice validare necesară. Îl numesc „modelul conturilor operaționale”.

Modelul de conturi operaționale pentru aplicații descentralizate, în care nu aveți nevoie de securitate de grad militar pentru conturile dvs. back-end

Astfel, în caz de urgență:

  • Singurul lucru pe care atacatorul îl poate fura este o cantitate mică de Ether (ca Ethereum) depusă în conturile operaționale pentru publicarea tranzacțiilor
  • Atacantul nu va putea dăuna logicii contractului inteligent și nici a oricui este implicat în proces
  • Conturile operaționale compromise pot fi înlocuite rapid cu altele, cu toate acestea, acest lucru necesită fie înlocuirea manuală (generarea de conturi noi și reautorizarea conturilor în toate contractele inteligente), fie dezvoltarea unei soluții suplimentare care va face toată magia cu o singură tranzacție de la un super -contul principal securizat (hardware sau multi-sig).

De exemplu, în facturarea noastră recurentă pentru soluția Ethereum, indiferent de ce se întâmplă în retard, contractul inteligent de facturare recurent este proiectat astfel încât avem o lună întreagă de timp pentru înlocuirea conturilor compromise dacă unul dintre ele este compromis. .

Dar totuși, dacă doriți să vă asigurați stocarea de chei private cât mai sigur posibil, puteți încerca să folosiți Vault cu un plugin excelent pentru Ethereum, care stochează și gestionează conturile Ethereum (de asemenea, urmăriți modulele open-source - noi urmează să lanseze ceva similar în curând). Nu am de gând să mă arunc în profunzime în detaliile de aici, deși puteți vizita proiectele legate și puteți afla de acolo chiar voi.

Nici măcar nu trebuie să spun. Totuși, acest articol s-a dovedit a fi mult mai lung decât mă așteptam, așa că trebuie să mă opresc. Abonați-vă la rețelele mele medii / alte dacă sunteți interesat de software, crypto, sfaturi de călătorie sau doriți doar să urmați ceva interesant. Sper că am furnizat o informație importantă și vă va fi util. Nu ezitați să comentați și să răspândiți acest articol!