Dezvoltare blockchain pentru industrie folosind Go. Partea 1

De patru luni lucrez la un proiect numit „Dezvoltarea instrumentelor de management și protecție a datelor în sectoarele guvernamentale și industriale bazate pe blockchain”.
Acum aș dori să vă spun despre cum am început acest proiect, iar acum voi descrie codul programului în detaliu.

Dezvoltare blockchain pentru industrie folosind Go. Partea 1

Acesta este primul articol dintr-o serie de articole. Aici descriu serverul și protocolul. De fapt, cititorul poate chiar să scrie propriile versiuni ale acestor elemente blockchain.

Și iată a doua parte — despre blockchain și structurile de date ale tranzacțiilor, precum și despre pachetul care implementează interacțiunea cu baza de date.

Anul trecut, la hackathon-ul Digital Breakthrough, au venit cu ideea de a realiza un sistem util pentru industrie și economia digitală folosind tehnologia registrului distribuit; a fost acordat și un grant pentru dezvoltare de către Fondul de asistență pentru inovare (ar trebui să scriu un separat articol despre grant, pentru cei care abia încep să facă startup-uri ), iar acum în ordine.

Dezvoltarea are loc în limbajul Go, iar baza de date în care sunt stocate blocurile este LevelDB.
Părțile principale sunt protocolul, serverul (care rulează TCP și WebSocket - prima pentru sincronizarea blockchain-ului, a doua pentru conectarea clienților, trimiterea de tranzacții și comenzi din JavaScript, de exemplu.

După cum sa menționat, acest blockchain este necesar în primul rând pentru automatizarea și protejarea schimbului de produse între furnizori și clienți, sau ambele într-o singură persoană. Acești oameni nu se grăbesc să aibă încredere unul în altul. Dar sarcina nu este doar de a crea un „registru de cecuri” cu un calculator încorporat, ci și un sistem care automatizează majoritatea sarcinilor de rutină care apar atunci când se lucrează cu ciclul de viață al produsului. Bytecode care este responsabil pentru această problemă, așa cum este obișnuit cu blockchain-urile, este stocat în intrările și ieșirile tranzacțiilor (tranzacțiile în sine sunt stocate în blocuri, blocurile din LevelDB sunt pre-codate în format GOB). Mai întâi, să vorbim despre protocol și server (alias nodul).

Protocolul nu este complicat, scopul său este de a trece la modul de încărcare a unor date, de obicei un bloc sau o tranzacție, ca răspuns la o linie de comandă specială și este, de asemenea, necesar pentru schimbul de inventar, astfel încât nodul să știe cine este este conectat și cum au de făcut afaceri (nodurile conectate pentru sesiunea de sincronizare se mai numesc și „învecinate” deoarece IP-ul lor este cunoscut și datele lor de stare sunt stocate în memorie).

Folderele (directoarele așa cum le numește Linux) în înțelegerea programatorilor Go sunt numite pachete, așa că la începutul fiecărui fișier cu cod Go din acest director scriu pachetul folder_name_where_this_file este localizat. În caz contrar, nu veți putea trimite pachetul la compilator. Ei bine, acesta nu este un secret pentru cei care cunosc această limbă. Acestea sunt pachetele:

  • Comunicații în rețea (server, client, protocol)
  • Structuri ale datelor stocate și transmise (bloc, tranzacție)
  • Baza de date (blockchain)
  • Consens
  • Mașină virtuală stivuită (xvm)
  • Auxiliar (cripto, tipuri) asta este tot pentru moment.

Aici este linkul către github

Aceasta este o versiune educațională, îi lipsește interacțiunea inter-proces și mai multe componente experimentale, dar structura corespunde celei pe care se realizează dezvoltarea. Dacă aveți ceva de sugerat în comentarii, voi fi bucuros să țin cont în continuare. Și acum pentru o explicație a serverului și protocol.

Să ne uităm mai întâi la server.

Subrutina serverului acționează ca un server de date care rulează peste protocolul TCP folosind structuri de date din pachetul de protocol.

Rutina folosește următoarele pachete: serverul, protocol, Tipuri. În pachetul în sine tcp_server.go conţine structura de date Servi kk.

type Serve struct {
	Port string
	BufSize int
	ST *types.Settings
}

Poate accepta următorii parametri:

  • Portul de rețea prin care vor fi schimbate datele
  • Fișier de configurare a serverului în format JSON
  • Semnal pentru rularea în modul de depanare (blockchain privat)

Progres:

  • Citește configurația din fișierul JSON
  • Indicatorul modului de depanare este verificat: dacă este setat, planificatorul de sincronizare a rețelei nu este lansat și blockchain-ul nu este încărcat
  • Inițializarea structurii datelor de configurare și pornirea serverului

server de

  • Efectuează lansarea serverului TCP și a interacțiunii cu rețea în conformitate cu protocolul.
  • Are o structură de date Servire constând dintr-un număr de port, o dimensiune de buffer și un pointer către structură tipuri.Setări
  • Metoda Run începe interacțiunea cu rețeaua (ascultând conexiunile de intrare pe un anumit port, când se primește o nouă conexiune, procesarea acesteia este transferată la metoda de gestionare privată într-un fir nou)
  • В manipula datele de la conexiune sunt citite într-un buffer, convertite într-o reprezentare șir și transmise la protocol.Alegere
  • protocol.Alegere se intoarce rezultat sau provoacă o eroare. rezultat apoi transferat la protocol.Interpretcare revine intrpr - obiect de tip InterpreteData, sau provoacă o eroare la procesarea rezultatului selecției
  • Apoi comutarea este executată intrpr.Comenzi[0] care verifică unul dintre: rezultat, inv, eroare și există o secțiune lipsă
  • In sectiunea rezultat comutatorul este găsit după valoare intrpr.Comenzi[1] care verifică valorile lungimea tamponului и versiune (în fiecare caz se numește funcția corespunzătoare)

Funcții GetVersion и BufferLength sunt în dosar srvlib.go pachet de server

GetVersion(conn net.Conn, version string)

pur și simplu imprimă pe consolă și trimite versiunea transmisă în parametru către client:

conn.Write([]byte("result:" + version))

.
Funcție

BufferLength(conn net.Conn, intrpr *protocol.InterpreteData)

încarcă un bloc, o tranzacție sau alte date specifice, după cum urmează:

  • Imprimă pe consolă tipul de date specificat în protocol care trebuie acceptat:
    fmt.Println("DataType:", intrpr.Commands[2])
  • Citește valoarea intrpr.Corp la o variabilă numerică buf_len
  • Creează un buffer newbuf dimensiune specificata:
    make([]byte, buf_len)
  • Trimite un răspuns ok:
    conn.Write([]byte("result:ok"))
  • Umple complet tamponul din fluxul de citire:
    io.ReadFull(conn, newbuf)

    .

  • Imprimă conținutul buffer-ului pe consolă
    fmt.Println(string(newbuf))

    și numărul de octeți citiți

    fmt.Println("Bytes length:", n)
  • Trimite un răspuns ok:
    conn.Write([]byte("result:ok"))

Metodele din pachetul de server sunt configurate pentru a procesa datele primite folosind funcțiile din pachet protocol.

Protocol

Un protocol servește ca mijloc care reprezintă date în schimbul de rețea.

Alegere(string șir) (șir, eroare) efectuează procesarea primară a datelor primite de server, primește o reprezentare șir a datelor ca intrare și returnează un șir pregătit pentru Interpret:

  • Șirul de intrare este împărțit în cap și corp folosind ReqParseN2(str)
  • capul este împărțit în elemente și plasat într-o secțiune de comenzi folosind ReqParseHead(head)
  • В comutați(comenzi[0]) selectați comanda primită (cmd, cheie, adresa sau se declanșează secțiunea lipsă)
  • 2 comenzi sunt verificate în cmd comutator(comenzi[1]) — lungime и getversion.
  • lungime verifică tipul de date comenzi[2] și îl salvează în datatype
  • Verifică asta corp conține o valoare șir
    len(body) < 1
  • Returnează șirul de răspuns:
    "result:bufferlength:" + datatype + "/" + body
  • getversion returnează un șir
    return "result:version/auto"

Interpret

Conține structura InterpreteData și efectuează procesarea secundară a datelor din care returnează Alegere corzile și formarea obiectelor InterpreteData.

type InterpreteData struct {
	Head string
	Commands []string
	Body string
	IsErr bool
	ErrCode int 
	ErrMessage string
}

Funcție

Interprete(str string) (*InterpreteData, error)

acceptă un șir rezultat și creează și returnează o referință la obiect InterpreteData.

Progres:

  • asemănător Alegere capul și corpul sunt extrase folosind ReqParseN2(str)
  • capul este împărțit în elemente folosind ReqParseHead(cap)
  • Obiectul este inițializat InterpreteData și se returnează un pointer către acesta:

res := &InterpreteData{
	Head: head,
	Commands: commands,
	Body: body,
}
return res, nil

Acest obiect este folosit în server.go pachet principal.

Client

Pachetul client conține funcțiile TCPConnect и Datele de răspuns TCP.

Funcție

TCPConnect(s *types.Settings, data []byte, payload []byte)

funcționează astfel:

  • Se realizează o conexiune la conexiunea specificată în obiectul setări transmis
    net.Dial("tcp", s.Host + ":" + s.Port)
  • Datele transmise în parametrul de date sunt transmise:
    conn.Write(data)
  • Se citește răspunsul
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    și imprimat pe consolă

    fmt.Println(string(resp[:n]))
  • Dacă este transferat sarcină utilă apoi o transmite mai departe
    conn.Write(payload)

    și, de asemenea, citește răspunsul serverului, imprimându-l pe consolă

Funcție

 TCPResponseData(conn net.Conn, bufsiz int) ([]byte, int, error)

creează un buffer de dimensiunea specificată, citește răspunsul serverului acolo și returnează acest buffer și numărul de octeți citiți, precum și un obiect de eroare.

Subrutină client

Servește pentru a trimite comenzi către serverele de noduri, precum și pentru a obține statistici și teste scurte.

Poate accepta următorii parametri: fișier de configurare în format JSON, date care urmează să fie trimise către server ca șir, calea către fișierul care urmează să fie trimis la încărcare, steag de emulare a programatorului de noduri, tipul de date transferate ca valoare numerică.

  • Obținerea configurației
    st := types.ParseConfig(*config)
  • Dacă steagul emu este trecut, pornește planificator
  • Dacă este furnizat indicatorul f care indică calea către fișier, atunci încărcăm datele acestuia în fdb iar conținutul este trimis către server
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Dacă fișierul nu este specificat, atunci datele din steag sunt pur și simplu trimise -d:
    client.TCPConnect(st, []byte(*data), nil)

Toate acestea sunt o reprezentare simplificată care arată structura protocolului. În timpul dezvoltării, la structura sa se adaugă funcționalitatea necesară.

În partea a doua voi vorbi despre structurile de date pentru blocuri și tranzacții, în 3 despre serverul WebSocket pentru conectarea din JavaScript, în 4 mă voi uita la planificatorul de sincronizare, apoi o mașină de stivă care procesează bytecode de la intrări și ieșiri, criptografie și piscine pentru ieșiri.

Sursa: www.habr.com

Adauga un comentariu