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.
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.
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.
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