Desenvolvemento de blockchain para a industria usando Go. Parte 1

Hai catro meses que estou traballando nun proxecto chamado "Desenvolvemento de ferramentas de xestión e protección de datos nos sectores gobernamentais e industriais baseados en blockchain".
Agora gustaríame falarche de como comecei este proxecto e agora describirei o código do programa en detalle.

Desenvolvemento de blockchain para a industria usando Go. Parte 1

Este é o primeiro artigo dunha serie de artigos. Aquí describo o servidor e o protocolo. De feito, o lector pode incluso escribir as súas propias versións destes elementos blockchain.

E aquí está a segunda parte — sobre a cadea de bloques e as estruturas de datos de transaccións, así como sobre o paquete que implementa a interacción coa base de datos.

O ano pasado, no hackathon Digital Breakthrough, ocorréuselles a idea de facer un sistema útil para a industria e a economía dixital mediante a tecnoloxía de libros de contas distribuídas; tamén se emitiu unha subvención para o desenvolvemento polo Fondo de Asistencia á Innovación (debería escribir un documento separado). artigo sobre a subvención, para os que están empezando a crear startups ), e agora en orde.

O desenvolvemento ten lugar na linguaxe Go, e a base de datos na que se almacenan os bloques é LevelDB.
As partes principais son o protocolo, o servidor (que executa TCP e WebSocket - o primeiro para sincronizar a cadea de bloques, o segundo para conectar clientes, enviar transaccións e comandos desde JavaScript, por exemplo.

Como se mencionou, esta cadea de bloques é necesaria principalmente para automatizar e protexer o intercambio de produtos entre provedores e clientes, ou ambos nunha soa persoa. Estas persoas non teñen présa por confiar entre si. Pero a tarefa non é só crear un "libro de cheques" cunha calculadora incorporada, senón un sistema que automatice a maioría das tarefas rutineiras que xorden cando se traballa co ciclo de vida do produto. O bytecode responsable deste asunto, como é habitual coas cadeas de bloques, almacénase nas entradas e saídas das transaccións (as propias transaccións almacénanse en bloques, os bloques en LevelDB están precodificados no formato GOB). En primeiro lugar, imos falar do protocolo e do servidor (tamén coñecido como nodo).

O protocolo non é complicado, o seu obxectivo é cambiar ao modo de cargar algúns datos, normalmente un bloque ou transacción, en resposta a unha liña de comandos especial, e tamén é necesario para intercambiar inventario, para que o nodo saiba quen é. está conectado e como teñen que facer negocios (os nodos conectados para a sesión de sincronización tamén se denominan "veciños" porque se coñece a súa IP e os datos do seu estado están almacenados na memoria).

Os cartafoles (directorios como os chama Linux) no entendemento dos programadores de Go chámanse paquetes, polo que ao comezo de cada ficheiro con código Go deste directorio escriben o paquete nome_cartafol_onde_este_ficheiro. En caso contrario, non poderás enviar o paquete ao compilador. Ben, isto non é ningún segredo para os que coñecen esta lingua. Estes son os paquetes:

  • Comunicación de rede (servidor, cliente, protocolo)
  • Estruturas dos datos almacenados e transmitidos (bloque, transacción)
  • Base de datos (blockchain)
  • Consenso
  • Máquina virtual apilada (xvm)
  • Auxiliar (cripto, tipos) iso é todo por agora.

Aquí está a ligazón a github

Trátase dunha versión educativa, carece de interacción entre procesos e de varios compoñentes experimentais, pero a estrutura corresponde á que se está a desenvolver. Se tes algo que suxerir nos comentarios, estarei encantado de telo en conta no desenvolvemento posterior. E agora unha explicación do servidor e Protocolo.

Vexamos primeiro o servidor.

A subrutina do servidor actúa como un servidor de datos que se executa enriba do protocolo TCP utilizando estruturas de datos do paquete de protocolos.

A rutina usa os seguintes paquetes: servidor, Protocolo, tipo. No propio paquete tcp_server.go contén estrutura de datos Servir.

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

Pode aceptar os seguintes parámetros:

  • Porto de rede a través do cal se intercambiarán datos
  • Ficheiro de configuración do servidor en formato JSON
  • Marcar para executar no modo de depuración (chain de bloques privado)

Progreso:

  • Le a configuración do ficheiro JSON
  • Compróbase a marca do modo de depuración: se está definida, o planificador de sincronización de rede non se inicia e a cadea de bloques non se carga
  • Iniciando a estrutura de datos de configuración e iniciando o servidor

servidor

  • Realiza o lanzamento do servidor TCP e a interacción da rede de acordo co protocolo.
  • Ten unha estrutura de datos Servir que consta dun número de porto, un tamaño de búfer e un punteiro á estrutura tipos.Configuración
  • O método Executar inicia a interacción da rede (escoitando as conexións entrantes nun porto determinado, cando se recibe unha nova conexión, o seu procesamento transfírese ao método de control privado nun novo fío).
  • В manipular os datos da conexión lense nun búfer, convértense nunha representación de cadea e pásanse a protocolo.Escolla
  • protocolo.Escolla volve resultar ou provoca un erro. resultar despois transferido a protocolo.Interpretarque volve intrpr -Obxecto do tipo Interpretar datos, ou provoca un erro ao procesar o resultado da selección
  • Despois execútase o cambio intrpr.Comandos[0] que verifica un dos seguintes: resultado, inv, erro e hai unha sección defecto
  • Na sección resultar o interruptor atópase polo valor intrpr.Comandos[1] que verifica os valores lonxitude do buffer и versión (en cada caso chámase a función correspondente)

Funcións GetVersion и Lonxitude do buffer están no ficheiro srvlib.go paquete de servidor

GetVersion(conn net.Conn, version string)

simplemente imprime na consola e envía a versión pasada no parámetro ao cliente:

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

.
Función

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

carga un bloque, transacción ou outros datos específicos como segue:

  • Imprime na consola o tipo de datos especificados no protocolo que se deben aceptar:
    fmt.Println("DataType:", intrpr.Commands[2])
  • Le o valor intrpr.Corpo a unha variable numérica buf_len
  • Crea un buffer novobuf tamaño especificado:
    make([]byte, buf_len)
  • Envía unha resposta correcta:
    conn.Write([]byte("result:ok"))
  • Enche completamente o búfer do fluxo de lectura:
    io.ReadFull(conn, newbuf)

    .

  • Imprime o contido do búfer na consola
    fmt.Println(string(newbuf))

    e o número de bytes lidos

    fmt.Println("Bytes length:", n)
  • Envía unha resposta correcta:
    conn.Write([]byte("result:ok"))

Os métodos do paquete do servidor están configurados para procesar os datos recibidos mediante funcións do paquete Protocolo.

Protocolo

Un protocolo serve como medio que representa datos no intercambio de rede.

Choice(string string) (cadea, erro) realiza o procesamento primario dos datos recibidos polo servidor, recibe unha cadea de representación dos datos como entrada e devolve unha cadea preparada para Intérprete:

  • A cadea de entrada divídese en cabeza e corpo usando ReqParseN2(str)
  • head divídese en elementos e colócase nun segmento de comandos usando ReqParseHead(head)
  • В cambiar(comandos[0]) seleccione o comando recibido (cmd, chave, enderezo ou se activa a sección defecto)
  • 2 comandos están marcados en cmd switch(comandos[1]) — lonxitude и getversion.
  • lonxitude comproba o tipo de datos comandos [2] e gárdao tipo de datos
  • Comproba iso corpo contén un valor de cadea
    len(body) < 1
  • Devolve a cadea de resposta:
    "result:bufferlength:" + datatype + "/" + body
  • getversion devolve unha cadea
    return "result:version/auto"

Intérprete

Contén a estrutura InterpreteData e realiza un procesamento secundario dos datos devoltos Escolla cordas e formación de obxectos 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 unha cadea resultar e crea e devolve unha referencia ao obxecto Interpretar datos.

Progreso:

  • Do mesmo xeito Escolla a cabeza e o corpo extráense utilizando ReqParseN2(str)
  • cabeza divídese en elementos usando ReqParseHead(cabeza)
  • O obxecto está inicializado Interpretar datos e devólvese un punteiro:

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

Este obxecto úsase en servidor.go paquete principal.

Cliente

O paquete cliente contén as funcións TCPConnect и Datos de resposta TCP.

Función

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

funciona así:

  • Realízase unha conexión coa conexión especificada no obxecto de configuración pasado
    net.Dial("tcp", s.Host + ":" + s.Port)
  • Os datos pasados ​​no parámetro de datos transmítense:
    conn.Write(data)
  • A resposta é lida
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    e impreso na consola

    fmt.Println(string(resp[:n]))
  • Se se traslada carga de pago despois pásao
    conn.Write(payload)

    e tamén le a resposta do servidor, imprimíndoa na consola

Función

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

crea un búfer do tamaño especificado, le a resposta do servidor alí e devolve este búfer e o número de bytes lidos, así como un obxecto de erro.

Subrutina do cliente

Serve para enviar comandos aos servidores de nodos, así como para obter estatísticas e probas breves.

Pode aceptar os seguintes parámetros: ficheiro de configuración en formato JSON, datos que se enviarán ao servidor como cadea, ruta do ficheiro que se enviará á carga útil, bandeira de emulación do planificador de nodos, tipo de datos transferidos como valor numérico.

  • Conseguindo a configuración
    st := types.ParseConfig(*config)
  • Se se pasa a bandeira emú, comeza planificador
  • Se se proporciona a marca f que indica o camiño ao ficheiro, entón cargamos os seus datos fdb e o contido envíase ao servidor
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Se non se especifica o ficheiro, simplemente envíanse os datos da bandeira -d:
    client.TCPConnect(st, []byte(*data), nil)

Todo isto é unha representación simplificada que mostra a estrutura do protocolo. Durante o desenvolvemento, engádese á súa estrutura a funcionalidade necesaria.

Na segunda parte falarei das estruturas de datos para bloques e transaccións, na 3 sobre o servidor WebSocket para conectarse desde JavaScript, na 4 verei o planificador de sincronización, despois unha máquina de pila que procesa bytecode de entradas e saídas, criptografía e piscinas para saídas.

Fonte: www.habr.com

Engadir un comentario