Seit vier Monaten arbeite ich an einem Projekt mit dem Titel „Entwicklung von Datenschutz- und Managementtools im Regierungs- und Industriesektor auf Basis von Blockchain“.
Jetzt möchte ich Ihnen erzählen, wie ich dieses Projekt gestartet habe, und nun werde ich den Programmcode im Detail beschreiben.
Dies ist der erste Artikel einer Artikelserie. Hier beschreibe ich den Server und das Protokoll. Tatsächlich kann der Leser sogar seine eigenen Versionen dieser Blockchain-Elemente schreiben.
Letztes Jahr kamen sie beim Digital Breakthrough-Hackathon auf die Idee, mithilfe der Distributed-Ledger-Technologie ein nützliches System für die Industrie und die digitale Wirtschaft zu entwickeln. Außerdem wurde für die Entwicklung ein Zuschuss vom Innovation Assistance Fund gewährt (ich sollte einen separaten Artikel schreiben). Artikel über das Stipendium, für diejenigen, die gerade ein Startup gründen), und jetzt in Ordnung.
Die Entwicklung erfolgt in der Go-Sprache und die Datenbank, in der die Blöcke gespeichert sind, ist LevelDB.
Die Hauptbestandteile sind das Protokoll, der Server (auf dem TCP und WebSocket ausgeführt werden – der erste zum Synchronisieren der Blockchain, der zweite zum Verbinden von Clients, Senden von Transaktionen und Befehlen beispielsweise aus JavaScript).
Wie bereits erwähnt, wird diese Blockchain in erster Linie benötigt, um den Produktaustausch zwischen Lieferanten und Kunden oder beiden in einer Person zu automatisieren und zu schützen. Diese Menschen haben es nicht eilig, einander zu vertrauen. Die Aufgabe besteht jedoch nicht nur darin, ein „Scheckbuch“ mit integriertem Taschenrechner zu erstellen, sondern ein System, das die meisten Routineaufgaben automatisiert, die bei der Arbeit mit dem Produktlebenszyklus anfallen. Der dafür zuständige Bytecode wird, wie bei Blockchains üblich, in den Ein- und Ausgängen von Transaktionen gespeichert (die Transaktionen selbst werden in Blöcken gespeichert, die Blöcke in LevelDB sind im GOB-Format vorcodiert). Lassen Sie uns zunächst über das Protokoll und den Server (auch bekannt als Knoten) sprechen.
Das Protokoll ist nicht kompliziert, sein Sinn besteht darin, als Reaktion auf eine spezielle Befehlszeile in den Modus zum Laden einiger Daten, normalerweise eines Blocks oder einer Transaktion, zu wechseln, und es wird auch für den Austausch von Inventar benötigt, damit der Knoten weiß, wer es ist verbunden ist und wie sie Geschäfte erledigen (die für die Synchronisierungssitzung verbundenen Knoten werden auch „benachbarte“ Knoten genannt, da ihre IP bekannt ist und ihre Statusdaten im Speicher gespeichert sind).
Ordner (Verzeichnisse, wie Linux sie nennt) werden im Verständnis von Go-Programmierern als Pakete bezeichnet. Am Anfang jeder Datei mit Go-Code aus diesem Verzeichnis schreiben sie also Paketordnername_wo_diese_Datei liegt. Andernfalls können Sie das Paket nicht an den Compiler weiterleiten. Nun, das ist kein Geheimnis für diejenigen, die diese Sprache beherrschen. Das sind die Pakete:
- Netzwerkkommunikation (Server, Client, Protokoll)
- Strukturen gespeicherter und übertragener Daten (Block, Transaktion)
- Datenbank (Blockchain)
- Konsens
- Gestapelte virtuelle Maschine (xvm)
- Hilfsmittel (Krypto, Typen) das ist vorerst alles.
Dies ist eine pädagogische Version, ihr fehlen die Interaktion zwischen Prozessen und mehrere experimentelle Komponenten, aber die Struktur entspricht der, auf der die Entwicklung durchgeführt wird. Wenn Sie in den Kommentaren Anregungen haben, werde ich diese gerne bei der weiteren Entwicklung berücksichtigen. Und nun zur Erklärung des Servers und Protokoll.
Schauen wir uns zunächst den Server an.
Die Server-Subroutine fungiert als Datenserver, der auf dem TCP-Protokoll basiert und Datenstrukturen aus dem Protokollpaket verwendet.
Die Routine verwendet die folgenden Pakete: Server, Protokoll, Typen. Im Paket selbst tcp_server.go enthält Datenstruktur Dienen.
type Serve struct {
Port string
BufSize int
ST *types.Settings
}
Es kann die folgenden Parameter akzeptieren:
- Netzwerkport, über den Daten ausgetauscht werden
- Serverkonfigurationsdatei im JSON-Format
- Flag für die Ausführung im Debug-Modus (private Blockchain)
Fortschritt:
- Liest die Konfiguration aus der JSON-Datei
- Das Debug-Modus-Flag wird überprüft: Wenn es gesetzt ist, wird der Netzwerksynchronisierungsplaner nicht gestartet und die Blockchain wird nicht geladen
- Initialisierung der Konfigurationsdatenstruktur und Starten des Servers
Server
- Führt den Start des TCP-Servers und die Netzwerkinteraktion gemäß dem Protokoll durch.
- Es verfügt über eine Serve-Datenstruktur, die aus einer Portnummer, einer Puffergröße und einem Zeiger auf die Struktur besteht Typen.Einstellungen
- Die Run-Methode startet die Netzwerkinteraktion (lauscht auf eingehende Verbindungen an einem bestimmten Port. Wenn eine neue Verbindung empfangen wird, wird deren Verarbeitung in einem neuen Thread an die private Handle-Methode übertragen).
- В Griff Daten aus der Verbindung werden in einen Puffer gelesen, in eine Zeichenfolgendarstellung umgewandelt und an übergeben Protokoll.Auswahl
- Protokoll.Auswahl kehrt zurück Folge oder verursacht einen Fehler. Folge dann übertragen auf Protokoll.Interpretierenwas zurückkommt intrpr - Objekttyp Daten interpretieren, oder verursacht einen Fehler bei der Verarbeitung des Auswahlergebnisses
- Anschließend wird der Wechsel ausgeführt intrpr.Commands[0] das prüft eines von: Ergebnis, Inventar, Fehler und es gibt einen Abschnitt Standard
- Im Bereich Folge Der Schalter wird nach Wert gefunden intrpr.Commands[1] welches die Werte überprüft Pufferlänge и Version (es wird jeweils die entsprechende Funktion aufgerufen)
Funktionen GetVersion и Pufferlänge sind in der Datei srvlib.go Serverpaket
GetVersion(conn net.Conn, version string)
Es wird einfach auf der Konsole gedruckt und die im Parameter übergebene Version an den Client gesendet:
conn.Write([]byte("result:" + version))
.
Funktion
BufferLength(conn net.Conn, intrpr *protocol.InterpreteData)
lädt einen Block, eine Transaktion oder andere spezifische Daten wie folgt:
- Gibt den im Protokoll angegebenen Datentyp, der akzeptiert werden muss, an die Konsole aus:
fmt.Println("DataType:", intrpr.Commands[2])
- Liest den Wert intrpr.Body auf eine numerische Variable buf_len
- Erstellt einen Puffer newbuf angegebene Größe:
make([]byte, buf_len)
- Sendet eine OK-Antwort:
conn.Write([]byte("result:ok"))
- Füllt den Puffer vollständig aus dem Lesestream:
io.ReadFull(conn, newbuf)
.
- Gibt den Inhalt des Puffers an die Konsole aus
fmt.Println(string(newbuf))
und die Anzahl der gelesenen Bytes
fmt.Println("Bytes length:", n)
- Sendet eine OK-Antwort:
conn.Write([]byte("result:ok"))
Methoden aus dem Serverpaket sind so konfiguriert, dass sie empfangene Daten mithilfe von Funktionen aus dem Paket verarbeiten Protokoll.
Protokoll
Ein Protokoll dient als Mittel zur Darstellung von Daten im Netzwerkaustausch.
Choice(str string) (string, error) Führt die primäre Verarbeitung der vom Server empfangenen Daten durch, empfängt eine Zeichenfolgendarstellung der Daten als Eingabe und gibt eine vorbereitete Zeichenfolge zurück Dolmetscher:
- Die Eingabezeichenfolge wird mithilfe von in Kopf und Körper aufgeteilt ReqParseN2(str)
- head wird in Elemente aufgeteilt und mit ReqParseHead(head) in einem Befehls-Slice platziert.
- В switch(commands[0]) Wählen Sie den empfangenen Befehl aus (cmd, Schlüssel, Adresse oder der Abschnitt wird ausgelöst Standard)
- 2 Befehle werden in cmd geprüft switch(commands[1]) – Länge и getversion.
- Länge prüft den Datentyp Befehle[2] und speichert es in Datentyp
- Überprüft das Körper enthält einen String-Wert
len(body) < 1
- Gibt die Antwortzeichenfolge zurück:
"result:bufferlength:" + datatype + "/" + body
- getversion gibt einen String zurück
return "result:version/auto"
Dolmetscher
Enthält die InterpreteData-Struktur und führt eine sekundäre Verarbeitung der von zurückgegebenen Daten durch Wahl Saiten und Objektbildung Daten interpretieren.
type InterpreteData struct {
Head string
Commands []string
Body string
IsErr bool
ErrCode int
ErrMessage string
}
Funktion
Interprete(str string) (*InterpreteData, error)
akzeptiert eine Zeichenfolge Folge und erstellt einen Verweis auf das Objekt und gibt ihn zurück Daten interpretieren.
Fortschritt:
- Ähnlich Wahl Kopf und Körper werden mit extrahiert ReqParseN2(str)
- Der Kopf wird mit in Elemente aufgeteilt ReqParseHead(Kopf)
- Das Objekt wird initialisiert Daten interpretieren und ein Zeiger darauf wird zurückgegeben:
res := &InterpreteData{
Head: head,
Commands: commands,
Body: body,
}
return res, nil
Dieses Objekt wird verwendet in server.go Paket main.
Kunden
Das Client-Paket enthält die Funktionen TCPConnect и TCPResponseData.
Funktion
TCPConnect(s *types.Settings, data []byte, payload []byte)
funktioniert wie folgt:
- Es wird eine Verbindung zu der im übergebenen Einstellungsobjekt angegebenen Verbindung hergestellt
net.Dial("tcp", s.Host + ":" + s.Port)
- Die im Datenparameter übergebenen Daten werden übertragen:
conn.Write(data)
- Die Antwort wird gelesen
resp, n, _ := TCPResponseData(conn, s.BufSize)
und auf der Konsole ausgedruckt
fmt.Println(string(resp[:n]))
- Falls übertragen Nutzlast gibt es dann weiter
conn.Write(payload)
Außerdem liest er die Serverantwort und gibt sie auf der Konsole aus
Funktion
TCPResponseData(conn net.Conn, bufsiz int) ([]byte, int, error)
Erstellt einen Puffer der angegebenen Größe, liest dort die Serverantwort und gibt diesen Puffer und die Anzahl der gelesenen Bytes sowie ein Fehlerobjekt zurück.
Client-Unterprogramm
Dient zum Senden von Befehlen an Knotenserver sowie zum Abrufen kurzer Statistiken und Tests.
Kann die folgenden Parameter akzeptieren: Konfigurationsdatei im JSON-Format, Daten, die als Zeichenfolge an den Server gesendet werden sollen, Pfad zur Datei, die an die Nutzlast gesendet werden soll, Node-Scheduler-Emulationsflag, Art der als numerischer Wert übertragenen Daten.
- Konfiguration abrufen
st := types.ParseConfig(*config)
- Wird das Emu-Flag übergeben, geht es los Planer
- Wenn das f-Flag, das den Pfad zur Datei angibt, angegeben wird, laden wir deren Daten hinein FDB und der Inhalt wird an den Server gesendet
client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
- Wenn die Datei nicht angegeben ist, werden einfach die Daten aus dem Flag gesendet -d:
client.TCPConnect(st, []byte(*data), nil)
Dies alles ist eine vereinfachte Darstellung, die die Struktur des Protokolls zeigt. Während der Entwicklung wird seiner Struktur die notwendige Funktionalität hinzugefügt.
Im zweiten Teil werde ich über Datenstrukturen für Blöcke und Transaktionen sprechen, in 3 über den WebSocket-Server für die Verbindung von JavaScript, in 4 werde ich mir den Synchronisationsplaner ansehen, dann eine Stapelmaschine, die Bytecode von Ein- und Ausgängen verarbeitet, Kryptographie usw Pools für Ausgänge.
Source: habr.com