Sviluppo blockchain per l'industria utilizzando Go. Parte 1

Da quattro mesi sto lavorando a un progetto chiamato “Sviluppo di strumenti di protezione e gestione dei dati nei settori governativi e industriali basati su blockchain”.
Ora vorrei raccontarvi come ho iniziato questo progetto, e ora descriverò in dettaglio il codice del programma.

Sviluppo blockchain per l'industria utilizzando Go. Parte 1

Questo è il primo articolo di una serie di articoli. Qui descrivo il server e il protocollo. In effetti, il lettore può persino scrivere la propria versione di questi elementi blockchain.

Ed ecco la seconda parte — sulla blockchain e sulle strutture dei dati delle transazioni, nonché sul pacchetto che implementa l'interazione con il database.

L’anno scorso, al Digital Breakthrough hackathon, è venuta l’idea di realizzare un sistema utile per l’industria e l’economia digitale utilizzando la tecnologia dei registri distribuiti; per lo sviluppo è stato concesso anche un contributo da parte dell’Innovation Assistance Fund (dovrei scrivere un articolo a parte) articolo sul sussidio, per chi ha appena avviato una startup ), e ora in ordine.

Lo sviluppo avviene in linguaggio Go, e il database in cui sono archiviati i blocchi è LevelDB.
Le parti principali sono il protocollo, il server (che esegue TCP e WebSocket: il primo per sincronizzare la blockchain, il secondo per connettere i client, inviare transazioni e comandi da JavaScript, ad esempio.

Come accennato in precedenza, questa blockchain è necessaria principalmente per automatizzare e proteggere lo scambio di prodotti tra fornitori e clienti, o entrambi in una sola persona. Queste persone non hanno fretta di fidarsi l'una dell'altra. Ma il compito non è solo creare un “libretto degli assegni” con una calcolatrice incorporata, ma un sistema che automatizzi la maggior parte delle attività di routine che sorgono quando si lavora con il ciclo di vita del prodotto. Il bytecode responsabile di questo problema, come è consuetudine con le blockchain, è memorizzato negli input e negli output delle transazioni (le transazioni stesse sono archiviate in blocchi, i blocchi in LevelDB sono precodificati nel formato GOB). Innanzitutto parliamo del protocollo e del server (noto anche come nodo).

Il protocollo non è complicato, il suo scopo è passare alla modalità di caricamento di alcuni dati, solitamente un blocco o una transazione, in risposta a una riga di comando speciale, ed è necessario anche per lo scambio di inventario, in modo che il nodo sappia chi è è connesso e come hanno affari da svolgere (i nodi collegati per la sessione di sincronizzazione sono anche detti “vicini” perché il loro IP è noto e i loro dati sullo stato sono archiviati in memoria).

Le cartelle (directory come le chiama Linux) nella comprensione dei programmatori Go sono chiamate pacchetti, quindi all'inizio di ogni file con il codice Go da questa directory scrivono il pacchetto nome_cartella_dove_si trova_questo_file. Altrimenti, non sarai in grado di fornire il pacchetto al compilatore. Ebbene, questo non è un segreto per chi conosce questa lingua. Questi sono i pacchetti:

  • Comunicazione di rete (server, client, protocollo)
  • Strutture dei dati memorizzati e trasmessi (blocco, transazione)
  • Banca dati (blockchain)
  • Consenso
  • Macchina virtuale in pila (xvm)
  • Ausiliari (criptovalute, tipi) per ora è tutto.

Ecco il collegamento a github

Questa è una versione educativa, manca di interazione tra processi e di diversi componenti sperimentali, ma la struttura corrisponde a quella su cui si sta svolgendo lo sviluppo. Se hai qualcosa da suggerire nei commenti, sarò felice di tenerne conto in ulteriori sviluppi. E ora per una spiegazione del server e protocollo.

Diamo prima un'occhiata al server.

La subroutine del server agisce come un server dati che viene eseguito sul protocollo TCP utilizzando le strutture dati del pacchetto protocollo.

La routine utilizza i seguenti pacchetti: server, protocollo, Tipi di. Nel pacchetto stesso tcp_server.go contiene la struttura dei dati Servire.

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

Può accettare i seguenti parametri:

  • Porta di rete attraverso la quale verranno scambiati i dati
  • File di configurazione del server in formato JSON
  • Flag per l'esecuzione in modalità debug (blockchain privata)

Progresso:

  • Legge la configurazione dal file JSON
  • Il flag della modalità debug è selezionato: se è impostato, lo scheduler della sincronizzazione di rete non viene avviato e la blockchain non viene caricata
  • Inizializzazione della struttura dei dati di configurazione e avvio del server

server

  • Esegue l'avvio del server TCP e l'interazione di rete in conformità con il protocollo.
  • Ha una struttura dati Serve composta da un numero di porta, una dimensione del buffer e un puntatore alla struttura tipi.Impostazioni
  • Il metodo Run avvia l'interazione di rete (ascoltando le connessioni in entrata su una determinata porta, quando viene ricevuta una nuova connessione, la sua elaborazione viene trasferita al metodo handle privato in un nuovo thread)
  • В maniglia i dati dalla connessione vengono letti in un buffer, convertiti in una rappresentazione di stringa e passati a protocollo.Scelta
  • protocollo.Scelta ritorna colpevole o provoca un errore. colpevole poi trasferito a protocollo.Interpretareche ritorna intrpr - oggetto di tipo Interpretare i datio provoca un errore nell'elaborazione del risultato della selezione
  • Quindi viene eseguito il passaggio intrpr.Commands[0] che controlla uno di: risultato, inv, errore e c'è una sezione difetto
  • Nella sezione colpevole l'interruttore viene trovato in base al valore intrpr.Commands[1] che controlla i valori lunghezza del buffer и versione (in ogni caso viene chiamata la funzione corrispondente)

funzioni OttieniVersione и Lunghezza buffer sono nel fascicolo srvlib.go pacchetto server

GetVersion(conn net.Conn, version string)

stampa semplicemente sulla console e invia al client la versione passata nel parametro:

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

.
Funzione

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

carica un blocco, una transazione o altri dati specifici come segue:

  • Stampa sulla console il tipo di dati specificato nel protocollo che deve essere accettato:
    fmt.Println("DataType:", intrpr.Commands[2])
  • Legge il valore intrpr.Body ad una variabile numerica buf_len
  • Crea un buffer newbuff dimensione specificata:
    make([]byte, buf_len)
  • Invia una risposta ok:
    conn.Write([]byte("result:ok"))
  • Riempie completamente il buffer dal flusso di lettura:
    io.ReadFull(conn, newbuf)

    .

  • Stampa il contenuto del buffer sulla console
    fmt.Println(string(newbuf))

    e il numero di byte letti

    fmt.Println("Bytes length:", n)
  • Invia una risposta ok:
    conn.Write([]byte("result:ok"))

I metodi del pacchetto server sono configurati per elaborare i dati ricevuti utilizzando le funzioni del pacchetto protocollo.

Protocollo

Un protocollo funge da mezzo per rappresentare i dati nello scambio di rete.

Choice(str stringa) (stringa, errore) esegue l'elaborazione primaria dei dati ricevuti dal server, riceve una rappresentazione di stringa dei dati come input e restituisce una stringa preparata Interpretare:

  • La stringa di input viene divisa in head e body utilizzando ReqParseN2(str)
  • head viene diviso in elementi e inserito in una sezione di comandi utilizzando ReqParseHead(head)
  • В interruttore(comandi[0]) selezionare il comando ricevuto (cmd, chiave, indirizzo oppure la sezione viene attivata difetto)
  • 2 comandi vengono controllati in cmd switch(comandi[1]) — lunghezza и getversione.
  • lunghezza controlla il tipo di dati comandi[2] e lo salva tipo di dati
  • Controlla quello stile di vita contiene un valore stringa
    len(body) < 1
  • Restituisce la stringa di risposta:
    "result:bufferlength:" + datatype + "/" + body
  • getversione restituisce una stringa
    return "result:version/auto"

Interpretare

Contiene la struttura InterpreteData ed esegue l'elaborazione secondaria dei dati restituiti Scelta stringhe e formazione di oggetti Interpretare i dati.

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

Funzione

Interprete(str string) (*InterpreteData, error)

accetta una stringa colpevole e crea e restituisce un riferimento all'oggetto Interpretare i dati.

Progresso:

  • allo stesso modo Scelta testa e corpo vengono estratti utilizzando ReqParseN2(str)
  • la testa è divisa in elementi utilizzando ReqParseHead(testa)
  • L'oggetto viene inizializzato Interpretare i dati e viene restituito un puntatore ad esso:

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

Questo oggetto è utilizzato in server.go pacchetto principale.

.

Il pacchetto client contiene le funzioni TCPConnetti и TCPResponseData.

Funzione

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

funziona come segue:

  • Viene stabilita una connessione alla connessione specificata nell'oggetto delle impostazioni passate
    net.Dial("tcp", s.Host + ":" + s.Port)
  • I dati passati nel parametro data vengono trasmessi:
    conn.Write(data)
  • La risposta è letta
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    e stampato sulla console

    fmt.Println(string(resp[:n]))
  • Se trasferito carico utile poi lo trasmette
    conn.Write(payload)

    e legge anche la risposta del server, stampandola sulla console

Funzione

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

crea un buffer della dimensione specificata, legge lì la risposta del server e restituisce questo buffer e il numero di byte letti, nonché un oggetto errore.

Sottoprogramma del cliente

Serve per inviare comandi ai server dei nodi, nonché ottenere brevi statistiche e test.

Può accettare i seguenti parametri: file di configurazione in formato JSON, dati da inviare al server come stringa, percorso del file da inviare al payload, flag di emulazione dello scheduler del nodo, tipo di dati trasferiti come valore numerico.

  • Ottenere la configurazione
    st := types.ParseConfig(*config)
  • Se viene passato il flag emu, si avvia programmatore
  • Se viene fornito il flag f che indica il percorso del file, ne carichiamo i dati FDB e il contenuto viene inviato al server
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Se il file non è specificato, i dati del flag vengono semplicemente inviati -d:
    client.TCPConnect(st, []byte(*data), nil)

Tutto questo è una rappresentazione semplificata che mostra la struttura del protocollo. Durante lo sviluppo, alla sua struttura vengono aggiunte le funzionalità necessarie.

Nella seconda parte parlerò delle strutture dati per blocchi e transazioni, in 3 del server WebSocket per la connessione da JavaScript, in 4 guarderò lo scheduler di sincronizzazione, quindi una macchina stack che elabora bytecode da input e output, crittografia e pool per gli output.

Fonte: habr.com

Aggiungi un commento