Blockchain-Entwicklung für die Industrie mit Go. Teil 1

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.

Blockchain-Entwicklung für die Industrie mit Go. Teil 1

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.

Und hier ist der zweite Teil — über Blockchain- und Transaktionsdatenstrukturen sowie über das Paket, das die Interaktion mit der Datenbank implementiert.

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.

Hier ist der Link zu Github

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

Kommentar hinzufügen