Développement de blockchain pour l'industrie utilisant Go. Partie 1

Depuis quatre mois maintenant, je travaille sur un projet intitulé « Développement d'outils de protection et de gestion des données dans les secteurs gouvernementaux et industriels basés sur la blockchain ».
Je voudrais maintenant vous raconter comment j'ai démarré ce projet, et maintenant je vais décrire le code du programme en détail.

Développement de blockchain pour l'industrie utilisant Go. Partie 1

Ceci est le premier article d’une série d’articles. Ici, je décris le serveur et le protocole. En fait, le lecteur peut même écrire ses propres versions de ces éléments de la blockchain.

Et voici la deuxième partie — sur la blockchain et les structures de données de transaction, ainsi que sur le package qui implémente l'interaction avec la base de données.

L'année dernière, lors du hackathon Digital Breakthrough, ils ont eu l'idée de créer un système utile pour l'industrie et l'économie numérique en utilisant la technologie du grand livre distribué ; une subvention a également été accordée pour le développement par le Fonds d'aide à l'innovation (je devrais écrire un article séparé). article sur la subvention, pour ceux qui commencent tout juste à créer des startups ), et maintenant dans l'ordre.

Le développement s'effectue en langage Go et la base de données dans laquelle les blocs sont stockés est LevelDB.
Les parties principales sont le protocole, le serveur (qui exécute TCP et WebSocket - le premier pour synchroniser la blockchain, le second pour connecter les clients, envoyer des transactions et des commandes depuis JavaScript, par exemple.

Comme cela a été mentionné, cette blockchain est principalement nécessaire pour automatiser et protéger l’échange de produits entre fournisseurs et clients, ou les deux chez une seule personne. Ces gens ne sont pas pressés de se faire confiance. Mais la tâche n'est pas seulement de créer un « chéquier » avec une calculatrice intégrée, mais un système qui automatise la plupart des tâches de routine qui surviennent lorsque l'on travaille avec le cycle de vie du produit. Le bytecode responsable de cette affaire, comme c'est l'habitude avec les blockchains, est stocké dans les entrées et sorties des transactions (les transactions elles-mêmes sont stockées en blocs, les blocs dans LevelDB sont pré-codés au format GOB). Parlons d’abord du protocole et du serveur (alias nœud).

Le protocole n'est pas compliqué, son objectif est de passer au mode de chargement de certaines données, généralement un bloc ou une transaction, en réponse à une ligne de commande spéciale, et il est également nécessaire pour échanger l'inventaire, afin que le nœud sache à qui il appartient. est connecté et comment ils ont des affaires à faire (les nœuds connectés pour la session de synchronisation sont également appelés « voisins » car leur IP est connue et leurs données d'état sont stockées en mémoire).

Les dossiers (répertoires comme Linux les appelle) dans la compréhension des programmeurs Go sont appelés packages, donc au début de chaque fichier avec le code Go de ce répertoire, ils écrivent le package nom_dossier_où se trouve ce_fichier. Sinon, vous ne pourrez pas transmettre le package au compilateur. Eh bien, ce n'est pas un secret pour ceux qui connaissent cette langue. Voici les forfaits :

  • Communication réseau (serveur, client, protocole)
  • Structures des données stockées et transmises (bloc, transaction)
  • Base de données (blockchain)
  • Consensus
  • Machine virtuelle empilée (xvm)
  • Auxiliaire (crypto, types) c'est tout pour l'instant.

Voici le lien vers github

Il s'agit d'une version pédagogique, elle manque d'interaction inter-processus et de plusieurs composants expérimentaux, mais la structure correspond à celle sur laquelle s'effectue le développement. Si vous avez quelque chose à suggérer dans les commentaires, je serai heureux d'en tenir compte lors de développements ultérieurs. Et maintenant pour une explication du serveur et protocole.

Regardons d'abord le serveur.

Le sous-programme serveur agit comme un serveur de données qui s'exécute sur le protocole TCP en utilisant les structures de données du package de protocole.

La routine utilise les packages suivants : serveur, protocole, types. Dans le colis lui-même tcp_server.go contient une structure de données Servir.

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

Il peut accepter les paramètres suivants :

  • Port réseau via lequel les données seront échangées
  • Fichier de configuration du serveur au format JSON
  • Indicateur d'exécution en mode débogage (blockchain privée)

Progrès:

  • Lit la configuration à partir du fichier JSON
  • L'indicateur du mode débogage est coché : s'il est défini, le planificateur de synchronisation réseau n'est pas lancé et la blockchain n'est pas chargée
  • Initialisation de la structure des données de configuration et démarrage du serveur

Server

  • Effectue le lancement du serveur TCP et l'interaction réseau conformément au protocole.
  • Il possède une structure de données Serve composée d'un numéro de port, d'une taille de tampon et d'un pointeur vers la structure. types.Paramètres
  • La méthode Run démarre l'interaction réseau (à l'écoute des connexions entrantes sur un port donné, lorsqu'une nouvelle connexion est reçue, son traitement est transféré à la méthode de handle privé dans un nouveau thread)
  • В manipuler les données de la connexion sont lues dans un tampon, converties en représentation sous forme de chaîne et transmises à protocole.Choix
  • protocole.Choix retourne résultat ou provoque une erreur. résultat puis transféré à protocole.Interpréterqui renvoie intrpr - objet de type Interpréter les données, ou provoque une erreur dans le traitement du résultat de la sélection
  • Ensuite, le changement est exécuté intrpr.Commands[0] qui vérifie l'un des : résultat, inv, erreur et il y a une section défaut
  • Dans la section résultat le commutateur est trouvé par valeur intrpr.Commands[1] qui vérifie les valeurs longueur de la mémoire tampon и version (dans chaque cas, la fonction correspondante est appelée)

fonctions ObtenirVersion и BufferLongueur sont dans le dossier srvlib.go paquet serveur

GetVersion(conn net.Conn, version string)

il imprime simplement sur la console et envoie la version passée en paramètre au client :

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

.
Fonction

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

charge un bloc, une transaction ou d'autres données spécifiques comme suit :

  • Imprime sur la console le type de données spécifié dans le protocole qui doit être accepté :
    fmt.Println("DataType:", intrpr.Commands[2])
  • Lit la valeur intrpr.Corps à une variable numérique buf_len
  • Crée un tampon nouveaubuf taille spécifiée :
    make([]byte, buf_len)
  • Envoie une réponse ok :
    conn.Write([]byte("result:ok"))
  • Remplit complètement le tampon à partir du flux de lecture :
    io.ReadFull(conn, newbuf)

    .

  • Imprime le contenu du tampon sur la console
    fmt.Println(string(newbuf))

    et le nombre d'octets lus

    fmt.Println("Bytes length:", n)
  • Envoie une réponse ok :
    conn.Write([]byte("result:ok"))

Les méthodes du package serveur sont configurées pour traiter les données reçues à l'aide des fonctions du package protocole.

Passerelle

Un protocole sert de moyen qui représente les données dans l'échange réseau.

Choix(str string) (chaîne, erreur) effectue le traitement primaire des données reçues par le serveur, reçoit une représentation sous forme de chaîne des données en entrée et renvoie une chaîne préparée pour Interprète:

  • La chaîne d'entrée est divisée en tête et corps en utilisant ReqParseN2(chaîne)
  • head est divisé en éléments et placé dans une tranche de commandes à l'aide de ReqParseHead(head)
  • В commutateur (commandes [0]) sélectionnez la commande reçue (cmd, clé, adresse ou la section est déclenchée défaut)
  • 2 commandes sont vérifiées dans cmd switch(commandes[1]) — longueur и obtenir la version.
  • longueur vérifie le type de données dans commandes[2] et l'enregistre dans Type de données
  • Vérifie que corps contient une valeur de chaîne
    len(body) < 1
  • Renvoie la chaîne de réponse :
    "result:bufferlength:" + datatype + "/" + body
  • obtenir la version renvoie une chaîne
    return "result:version/auto"

Interprète

Contient la structure InterpreteData et effectue un traitement secondaire des données renvoyées par Choix chaînes et formation d'objets Interpréter les données.

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

Fonction

Interprete(str string) (*InterpreteData, error)

accepte une chaîne résultat et crée et renvoie une référence à l'objet Interpréter les données.

Progrès:

  • De même, Choix la tête et le corps sont extraits à l'aide ReqParseN2(chaîne)
  • la tête est divisée en éléments en utilisant ReqParseHead(tête)
  • L'objet est initialisé Interpréter les données et un pointeur vers celui-ci est renvoyé :

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

Cet objet est utilisé dans serveur.go paquet principal.

Client

Le package client contient les fonctions TCPConnect и TCPResponseData.

Fonction

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

fonctionne comme suit:

  • Une connexion est établie avec la connexion spécifiée dans l'objet de paramètres transmis
    net.Dial("tcp", s.Host + ":" + s.Port)
  • Les données passées dans le paramètre data sont transmises :
    conn.Write(data)
  • La réponse est lue
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    et imprimé sur la console

    fmt.Println(string(resp[:n]))
  • En cas de transfert charge utile puis le transmet
    conn.Write(payload)

    et lit également la réponse du serveur et l'imprime sur la console

Fonction

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

crée un tampon de la taille spécifiée, y lit la réponse du serveur et renvoie ce tampon et le nombre d'octets lus, ainsi qu'un objet d'erreur.

Sous-programme client

Sert à envoyer des commandes aux serveurs de nœuds, ainsi qu'à obtenir de brèves statistiques et des tests.

Peut accepter les paramètres suivants : fichier de configuration au format JSON, données à envoyer au serveur sous forme de chaîne, chemin d'accès au fichier à envoyer à la charge utile, indicateur d'émulation du planificateur de nœud, type de données transférées sous forme de valeur numérique.

  • Obtenir la configuration
    st := types.ParseConfig(*config)
  • Si le drapeau emu est passé, ça démarre planificateur
  • Si le drapeau f indiquant le chemin d'accès au fichier est fourni, alors nous chargeons ses données dans FDB et le contenu est envoyé au serveur
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Si le fichier n'est pas spécifié, alors les données du drapeau sont simplement envoyées -d:
    client.TCPConnect(st, []byte(*data), nil)

Tout cela est une représentation simplifiée montrant la structure du protocole. Lors du développement, les fonctionnalités nécessaires sont ajoutées à sa structure.

Dans la deuxième partie je parlerai des structures de données pour les blocs et les transactions, en 3 du serveur WebSocket pour se connecter depuis JavaScript, en 4 je regarderai le planificateur de synchronisation, puis une machine à pile qui traite le bytecode des entrées et sorties, la cryptographie et pools pour les sorties.

Source: habr.com

Ajouter un commentaire