Desarrollo de blockchain para la industria en marcha. Parte 1

Durante cuatro meses, he estado trabajando en un proyecto llamado "Desarrollo de herramientas de seguridad y gestión de datos basadas en blockchain en el gobierno y la industria".
Ahora me gustaría hablar sobre cómo comencé este proyecto, y ahora describiré el código del programa en detalle.

imagen

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

Pero la segunda parte trata sobre las estructuras de datos de blockchain y las transacciones, así como sobre el paquete que implementa la interacción con la base de datos.

El año pasado, en el hackathon Digital Break, plantearon la idea de que para la industria y la economía digital para crear un sistema útil para la tecnología de los registros distribuidos, el Fondo de Asistencia para la Innovación también emitió una subvención para el desarrollo (sobre la subvención, debería escribir un artículo separado para aquellos que recién comienzan a hacer startups ), y ahora en orden.

El desarrollo se lleva a cabo en 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. No tienen prisa por confiar el uno en el otro. Pero la tarea no es solo hacer una "chequera" con una calculadora incorporada, sino un sistema con automatización de la mayoría de las tareas de rutina que surgen cuando se trabaja con el ciclo de vida del producto. El código de bytes que es responsable de este negocio, como es habitual en blockchains, se almacena en las entradas y salidas de las transacciones (las transacciones en sí mismas están en bloques, los bloques en LevelDB están precodificados en el formato GOB). Primero, hablemos sobre el protocolo y el servidor (también conocido como nodo).

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

Las carpetas (directorios como los llama Linux) se denominan paquetes en la comprensión de los programadores de Go, por lo que al comienzo de cada archivo con Go-code de este directorio al principio escriben el paquete folder_name_where_sit_this_file. De lo contrario, el paquete no se puede alimentar al compilador. Bueno, esto no es un secreto para quienes conocen este idioma. Estos paquetes son:

  • Redes (servidor, cliente, protocolo)
  • Estructuras de datos almacenados y transmitidos (bloque, transacción)
  • (blockchain)
  • (consensus)
  • (xvm)
  • (crypto, types) .

Aquí hay un enlace a github:

esta es una versión de capacitación, carece de comunicación entre procesos y varios componentes experimentales, para los cuales la estructura corresponde a la que se está desarrollando. Si tiene algo que contar en los comentarios, con mucho gusto tendré en cuenta el desarrollo posterior. Y ahora para el servidor y los paquetes de protocolo .

Primero, eche un vistazo al servidor.

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

La rutina usa los siguientes paquetes: servidor , protocolo , tipos . El paquete en contiene la estructura de datos tcp_server.go Servir.

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

Puede tomar los siguientes parámetros:

  • Puerto de red a través del cual se intercambiarán datos
  • Archivo de configuración del servidor JSON
  • Indicador de ejecución en modo de depuración (blockchain privado)

Progreso:

  • Leer la configuración del archivo JSON
  • Se marca el indicador de modo de depuración: si está configurado, entonces 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


  • Inicia el servidor TCP y la comunicación de 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 los tipos.
  • Run ( , handle )
  • handle , protocol.Choice
  • protocol.Choice result . result protocol.Interprete, intrprInterpreteData,
  • Luego, el interruptor es ejecutado por intrpr.Commands [0] en el que se verifica uno de los siguientes: resultado, inv, error y hay una sección predeterminada
  • En la sección de resultados , hay un cambio por el valor de intrpr.Commands [1] que verifica los valores de bufferlength y versión (en cada caso se llama a la función correspondiente)

Las funciones GetVersion y BufferLength se encuentran en el archivo srvlib.go del paquete del servidor.

GetVersion(conn net.Conn, version string)

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

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

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

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

  • Imprime en la consola el tipo de datos especificado en el protocolo que debe aceptarse:

    fmt.Println("DataType:", intrpr.Commands[2])
  • Lee el intrpr.Body en la variable numérica buf_len
  • Crea un búfer newbuf del tamaño especificado:

    make([]byte, buf_len)
  • Envía una respuesta correcta:

    conn.Write([]byte("result:ok"))
  • Produce un búfer completo a partir de la secuencia de lectura:

    io.ReadFull(conn, newbuf)
    .
  • Imprime el contenido del búfer en la consola.

    fmt.Println(string(newbuf))

    y el número de bytes leídos

    fmt.Println("Bytes length:", n)
  • Envía una respuesta correcta:

    conn.Write([]byte("result:ok"))

Los métodos del paquete del servidor están configurados de tal manera que procesan los datos recibidos con funciones del paquete del protocolo .

Protocolo


El protocolo sirve como una herramienta que presenta datos durante la comunicación de red.

Choice (cadena de cadena) (cadena, error) realiza el procesamiento inicial de los datos recibidos por el servidor, recibe una representación de cadena de los datos y devuelve la cadena preparada para Interpretar :

  • La cadena de entrada se divide en cabeza y cuerpo usando ReqParseN2 (str)
  • la cabeza se divide en elementos y se coloca en el segmento de comandos usando ReqParseHead (cabeza)
  • En el interruptor (comandos [0]) seleccionamos el comando recibido ( cmd, clave, dirección o se activa la sección predeterminada )
  • En cmd, se verifican 2 comandos de cambio (comandos [1]): longitud y getversion .
  • length comprueba el tipo de datos en los comandos [2] y los almacena en el tipo de datos
  • Comprueba que el cuerpo contiene un valor de cadena

    len(body) < 1
  • Devuelve una cadena de respuesta:

    "result:bufferlength:" + datatype + "/" + body
  • getversion devuelve una cadena

    return "result:version/auto"

Interpretar


Contiene la estructura InterpreteData y realiza el procesamiento secundario de la cadena devuelta por Choice y la formación del objeto InterpreteData .

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

Función

Interprete(str string) (*InterpreteData, error)

acepta el resultado de la cadena y crea una referencia al objeto InterpreteData .

Progreso:

  • Del mismo modo, Choice recupera la cabeza y el cuerpo usando ReqParseN2 (str)
  • la cabeza se divide en elementos usando ReqParseHead (cabeza)
  • El objeto InterpreteData se inicializa y se devuelve un puntero al mismo:

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

Este objeto se usa en server.go del paquete principal.

Cliente


El paquete del cliente contiene las funciones TCPConnect y TCPResponseData .

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 transferido

    net.Dial("tcp", s.Host + ":" + s.Port)
  • Los datos pasados ​​en el parámetro de datos se transfieren:

    conn.Write(data)
  • Leer respuesta

    resp, n, _ := TCPResponseData(conn, s.BufSize)

    e impreso en la consola

    fmt.Println(string(resp[:n]))
  • Si la carga útil se transmite, se transmite

    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 la respuesta del servidor allí y devuelve este búfer y la cantidad de bytes leídos, así como un objeto de error.

Rutina del cliente


Sirve para transmitir comandos a los servidores del nodo, así como para obtener breves estadísticas y pruebas.

Puede tomar los siguientes parámetros: un archivo de configuración en formato JSON, datos que se transmitirán al servidor como una cadena, una ruta al archivo para enviar a la carga útil, un indicador de emulación de planificador de nodo, el tipo de datos que se transferirán como un valor numérico.

  • Obtén la configuración

    st := types.ParseConfig(*config)
  • Si te dan una bandera de la UEM, comienza el programador
  • Si se especifica el indicador f que indica la ruta al archivo, cargue sus datos en fdb y envíe el contenido al servidor

    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Si no se especifica el archivo, los datos del indicador -d simplemente se envían :

    client.TCPConnect(st, []byte(*data), nil)

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

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

All Articles