Desarrollo de blockchain para la industria utilizando Go. Parte 1

Desde hace cuatro meses trabajo en un proyecto llamado “Desarrollo de herramientas de gestión y protección de datos en sectores gubernamentales e industriales basados ​​en blockchain”.
Ahora me gustaría contarles cómo comencé este proyecto y ahora describiré el código del programa en detalle.

Desarrollo de blockchain para la industria utilizando Go. Parte 1

Este es el primer artículo de una serie de artículos. Aquí describo el servidor y el protocolo. De hecho, el lector puede incluso escribir sus propias versiones de estos elementos de blockchain.

Y aquí está la segunda parte. — sobre blockchain y estructuras de datos de transacciones, así como sobre el paquete que implementa la interacción con la base de datos.

El año pasado, en el hackathon Digital Breakthrough, se les ocurrió la idea de crear un sistema útil para la industria y la economía digital utilizando tecnología de contabilidad distribuida; también se otorgó una subvención para el desarrollo por parte del Fondo de Asistencia a la Innovación (debería escribir un artículo aparte artículo sobre la subvención, para aquellos que recién están empezando a hacer startups), y ahora en orden.

El desarrollo se realiza en el lenguaje Go y la base de datos en la que se almacenan los bloques es LevelDB.
Las partes principales son el protocolo, el servidor (que ejecuta TCP y WebSocket; el primero para sincronizar la cadena de bloques, el segundo para conectar clientes, enviar transacciones y comandos desde JavaScript, por ejemplo).

Como se mencionó, esta cadena de bloques se necesita principalmente para automatizar y proteger el intercambio de productos entre proveedores y clientes, o ambos en una sola persona. Estas personas no tienen prisa por confiar unos en otros. Pero la tarea no es sólo crear una “chequera” con una calculadora incorporada, sino un sistema que automatice la mayoría de las tareas rutinarias que surgen cuando se trabaja con el ciclo de vida del producto. El código de bytes responsable de este asunto, como es habitual en las cadenas de bloques, se almacena en las entradas y salidas de las transacciones (las transacciones en sí se almacenan en bloques, los bloques en LevelDB están precodificados en formato GOB). Primero, hablemos del protocolo y el servidor (también conocido como nodo).

El protocolo no es complicado, su objetivo es cambiar al modo de cargar algunos datos, generalmente un bloque o transacción, en respuesta a una línea de comando especial, y también es necesario para intercambiar inventario, para que el nodo sepa quién es. está conectado y cómo tienen negocios que hacer (los nodos conectados para la sesión de sincronización también se denominan "vecinos" porque se conoce su IP y los datos de su estado se almacenan en la memoria).

Las carpetas (directorios como las llama Linux) en el entendimiento de los programadores de Go se llaman paquetes, por lo que al comienzo de cada archivo con código Go de este directorio escriben el paquete nombre_carpeta_dónde_este_archivo. De lo contrario, no podrá enviar el paquete al compilador. Bueno, esto no es ningún secreto para quienes conocen este idioma. Estos son los paquetes:

  • Comunicación de red (servidor, cliente, protocolo)
  • Estructuras de datos almacenados y transmitidos (bloque, transacción)
  • Base de datos (cadena de bloques)
  • Consenso
  • Máquina virtual apilada (xvm)
  • Auxiliares (criptomonedas, tipos) eso es todo por ahora.

Aquí está el enlace a github.

Esta es una versión educativa, carece de interacción entre procesos y de varios componentes experimentales, pero la estructura corresponde a aquella sobre la que se está desarrollando. Si tiene algo que sugerir en los comentarios, estaré encantado de tenerlo en cuenta en un futuro desarrollo. Y ahora una explicación del servidor y protocolo.

Veamos primero el servidor.

La subrutina del servidor actúa como un servidor de datos que se ejecuta sobre el protocolo TCP utilizando estructuras de datos del paquete de protocolo.

La rutina utiliza los siguientes paquetes: servidor, protocolo, tipos. En el paquete mismo tcp_server.go contiene estructura de datos Servicio.

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

Puede aceptar los siguientes parámetros:

  • Puerto de red a través del cual se intercambiarán datos
  • Archivo de configuración del servidor en formato JSON
  • Bandera para ejecutar en modo de depuración (blockchain privada)

Progreso:

  • Lee la configuración del archivo JSON
  • El indicador del modo de depuración está marcado: si está configurado, el programador de sincronización de red no se inicia y la cadena de bloques no se carga
  • Inicializando la estructura de datos de configuración e iniciando el servidor

Servidor

  • Realiza el lanzamiento del servidor TCP y la interacción de la red de acuerdo con el protocolo.
  • Tiene una estructura de datos de servicio que consta de un número de puerto, un tamaño de búfer y un puntero a la estructura. tipos.Configuraciones
  • El método Ejecutar inicia la interacción de la red (escucha las conexiones entrantes en un puerto determinado, cuando se recibe una nueva conexión, su procesamiento se transfiere al método de identificador privado en un nuevo hilo)
  • В encargarse de Los datos de la conexión se leen en un búfer, se convierten en una representación de cadena y se pasan a protocolo.Elección
  • protocolo.Elección devuelve resultado o provoca un error. resultado luego transferido a protocolo.Interpretarque regresa intrpre - objeto de tipo Interpretar datos, o causa un error al procesar el resultado de la selección
  • Luego se ejecuta el cambio. intrpr.Comandos[0] que comprueba uno de: resultado, inv, error y hay una sección tu préstamo estudiantil
  • En la sección resultado el interruptor se encuentra por valor intrpr.Comandos[1] que comprueba los valores longitud del buffer и versión (en cada caso se llama a la función correspondiente)

funciones ObtenerVersión и Longitud del búfer están en el archivo srvlib.go paquete de servidor

GetVersion(conn net.Conn, version string)

simplemente imprime en la consola y envía la versión pasada en el parámetro al cliente:

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

.
Función

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

carga un bloque, transacción u otros datos específicos de la siguiente manera:

  • Imprime en la consola el tipo de datos especificados en el protocolo que deben aceptarse:
    fmt.Println("DataType:", intrpr.Commands[2])
  • Lee el valor intrpr.Cuerpo a una variable numérica buf_len
  • Crea un búfer nuevobuf tamaño especificado:
    make([]byte, buf_len)
  • Envía una respuesta aceptable:
    conn.Write([]byte("result:ok"))
  • Llena completamente el búfer del flujo de lectura:
    io.ReadFull(conn, newbuf)

    .

  • Imprime el contenido del buffer en la consola.
    fmt.Println(string(newbuf))

    y el número de bytes leídos

    fmt.Println("Bytes length:", n)
  • Envía una respuesta aceptable:
    conn.Write([]byte("result:ok"))

Los métodos del paquete del servidor están configurados para procesar los datos recibidos utilizando funciones del paquete. protocolo.

Protocolo

Un protocolo sirve como medio que representa datos en el intercambio de red.

Elección (cadena de cadena) (cadena, error) realiza el procesamiento primario de los datos recibidos por el servidor, recibe una representación de cadena de los datos como entrada y devuelve una cadena preparada para Intérprete:

  • La cadena de entrada se divide en cabeza y cuerpo usando ReqParseN2(cadena)
  • head se divide en elementos y se coloca en un segmento de comandos usando ReqParseHead(head)
  • В cambiar(comandos[0]) seleccione el comando recibido (cmd, clave, dirección o la sección se activa tu préstamo estudiantil)
  • 2 comandos están marcados en cmd cambiar(comandos[1]) - longitud и obtener versión.
  • de largo comprueba el tipo de datos en comandos[2] y lo guarda en tipo de datos
  • comprueba que cuerpo contiene un valor de cadena
    len(body) < 1
  • Devuelve la cadena de respuesta:
    "result:bufferlength:" + datatype + "/" + body
  • obtener versión devuelve una cadena
    return "result:version/auto"

Intérprete

Contiene la estructura InterpreteData y realiza el procesamiento secundario de los datos devueltos por Libertad de decidir cuerdas y formación de objetos Interpretar datos.

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

Función

Interprete(str string) (*InterpreteData, error)

acepta una cadena resultado y crea y devuelve una referencia al objeto Interpretar datos.

Progreso:

  • Del mismo modo, Libertad de decidir La cabeza y el cuerpo se extraen mediante ReqParseN2(cadena)
  • la cabeza se divide en elementos usando ReqParseHead(cabeza)
  • El objeto se inicializa. Interpretar datos y se devuelve un puntero a él:

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

Este objeto se utiliza en servidor.ir paquete principal.

Cliente

El paquete del cliente contiene las funciones. Conexión TCP и Datos de respuesta TCP.

Función

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

funciona de la siguiente manera:

  • Se realiza una conexión a la conexión especificada en el objeto de configuración pasado.
    net.Dial("tcp", s.Host + ":" + s.Port)
  • Los datos pasados ​​en el parámetro de datos se transmiten:
    conn.Write(data)
  • La respuesta se lee.
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    e impreso en la consola

    fmt.Println(string(resp[:n]))
  • Si se transfiere carga útil luego lo pasa
    conn.Write(payload)

    y también lee la respuesta del servidor, imprimiéndola en la consola

Función

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

crea un búfer del tamaño especificado, lee allí la respuesta del servidor y devuelve este búfer y el número de bytes leídos, así como un objeto de error.

subrutina de cliente

Sirve para enviar comandos a servidores de nodos, así como obtener breves estadísticas y pruebas.

Puede aceptar los siguientes parámetros: archivo de configuración en formato JSON, datos que se enviarán al servidor como una cadena, ruta al archivo que se enviará a la carga útil, indicador de emulación del programador de nodos, tipo de datos transferidos como un valor numérico.

  • Obteniendo la configuración
    st := types.ParseConfig(*config)
  • Si se pasa la bandera emu, comienza programador
  • Si se proporciona la bandera f que indica la ruta al archivo, entonces cargamos sus datos en FDB y el contenido se envía al servidor
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Si no se especifica el archivo, entonces los datos de la bandera simplemente se envían -d:
    client.TCPConnect(st, []byte(*data), nil)

Todo esto es una representación simplificada que muestra la estructura del protocolo. Durante el desarrollo, se agrega la funcionalidad necesaria a su estructura.

En la segunda parte hablaré sobre estructuras de datos para bloques y transacciones, en 3 sobre el servidor WebSocket para conectarse desde JavaScript, en 4 miraré el programador de sincronización, luego una máquina de pila que procesa código de bytes de entradas y salidas, criptografía y piscinas para salidas.

Fuente: habr.com

Añadir un comentario