Desenvolvimento de Blockchain para indústria usando Go. Parte 1

Há quatro meses venho trabalhando em um projeto chamado “Desenvolvimento de ferramentas de proteção e gerenciamento de dados em setores governamentais e industriais baseados em blockchain”.
Agora gostaria de contar como comecei este projeto e agora descreverei o código do programa em detalhes.

Desenvolvimento de Blockchain para indústria usando Go. Parte 1

Este é o primeiro artigo de uma série de artigos. Aqui descrevo o servidor e o protocolo. Na verdade, o leitor pode até escrever suas próprias versões desses elementos do blockchain.

E aqui está a segunda parte — sobre blockchain e estruturas de dados de transação, bem como sobre o pacote que implementa a interação com o banco de dados.

No ano passado, no hackathon Digital Breakthrough, eles tiveram a ideia de criar um sistema útil para a indústria e a economia digital usando tecnologia de registro distribuído; também foi emitida uma subvenção para o desenvolvimento pelo Fundo de Assistência à Inovação (devo escrever um artigo separado artigo sobre a bolsa, para quem está começando startups ), e agora na ordem.

O desenvolvimento ocorre na linguagem Go, e o banco de dados no qual os blocos são armazenados é o LevelDB.
As partes principais são o protocolo, o servidor (que roda TCP e WebSocket – o primeiro para sincronizar o blockchain, o segundo para conectar clientes, enviar transações e comandos de JavaScript, por exemplo.

Como foi mencionado, esta blockchain é necessária principalmente para automatizar e proteger a troca de produtos entre fornecedores e clientes, ou ambos numa só pessoa. Essas pessoas não têm pressa em confiar umas nas outras. Mas a tarefa não é apenas criar um “talão de cheques” com calculadora embutida, mas um sistema que automatize a maioria das tarefas rotineiras que surgem ao trabalhar com o ciclo de vida do produto. O bytecode responsável por este assunto, como é habitual nos blockchains, é armazenado nas entradas e saídas das transações (as próprias transações são armazenadas em blocos, os blocos no LevelDB são pré-codificados no formato GOB). Primeiro, vamos falar sobre o protocolo e o servidor (também conhecido como nó).

O protocolo não é complicado, seu objetivo é mudar para o modo de carregamento de alguns dados, geralmente um bloco ou transação, em resposta a uma linha de comando especial, e também é necessário para troca de inventário, para que o nó saiba quem é está conectado e como eles têm negócios a fazer (os nós conectados para a sessão de sincronização também são chamados de “vizinhos” porque seu IP é conhecido e seus dados de estado são armazenados na memória).

Pastas (diretórios como o Linux os chama) no entendimento dos programadores Go são chamadas de pacotes, portanto, no início de cada arquivo com o código Go deste diretório, eles escrevem o pacote nome_da_pasta_onde_este_arquivo está localizado. Caso contrário, você não conseguirá alimentar o pacote no compilador. Bom, isso não é segredo para quem conhece esse idioma. Estes são os pacotes:

  • Comunicação em rede (servidor, cliente, protocolo)
  • Estruturas de dados armazenados e transmitidos (bloco, transação)
  • Banco de dados (blockchain)
  • Consenso
  • Máquina virtual empilhada (xvm)
  • Auxiliar (cripto, tipos) por enquanto é tudo.

Aqui está o link para o github

Esta é uma versão educacional, carece de interação entre processos e de vários componentes experimentais, mas a estrutura corresponde àquela em que o desenvolvimento está sendo realizado. Se você tiver algo a sugerir nos comentários, ficarei feliz em levar isso em consideração no desenvolvimento futuro. E agora para uma explicação do servidor e protocolo.

Vejamos primeiro o servidor.

A sub-rotina do servidor atua como um servidor de dados executado sobre o protocolo TCP usando estruturas de dados do pacote de protocolos.

A rotina usa os seguintes pacotes: servidor, protocolo, tipos. No próprio pacote tcp_server.go contém estrutura de dados Servir.

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

Pode aceitar os seguintes parâmetros:

  • Porta de rede através da qual os dados serão trocados
  • Arquivo de configuração do servidor no formato JSON
  • Sinalizador para execução em modo de depuração (blockchain privado)

Progresso:

  • Lê a configuração do arquivo JSON
  • O sinalizador do modo de depuração está marcado: se estiver definido, o agendador de sincronização de rede não será iniciado e o blockchain não será carregado
  • Inicializando a estrutura de dados de configuração e iniciando o servidor

servidor

  • Realiza o lançamento do servidor TCP e a interação da rede de acordo com o protocolo.
  • Possui uma estrutura de dados Serve que consiste em um número de porta, tamanho de buffer e um ponteiro para a estrutura tipos.Configurações
  • O método Run inicia a interação de rede (ouvindo conexões de entrada em uma determinada porta, quando uma nova conexão é recebida, seu processamento é transferido para o método de identificador privado em um novo thread)
  • В manipular os dados da conexão são lidos em um buffer, convertidos em uma representação de string e passados ​​para protocolo.Escolha
  • protocolo.Escolha devolve resultar ou causa um erro. resultar então transferido para protocolo.Interpretarque retorna intrpr - objeto do tipo Interpretar Dados, ou causa um erro no processamento do resultado da seleção
  • Então a mudança é executada intrpr.Comandos[0] que verifica um dos seguintes: resultado, inv, erro e há uma seção omissão
  • Na seção resultar switch é encontrado por valor intrpr.Comandos[1] que verifica os valores comprimento do buffer и versão (em cada caso a função correspondente é chamada)

funções Obter versão и Comprimento do buffer estão no arquivo srvlib.go pacote de servidor

GetVersion(conn net.Conn, version string)

ele simplesmente imprime no console e envia a versão passada no parâmetro para o cliente:

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

.
Função

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

carrega um bloco, transação ou outros dados específicos da seguinte forma:

  • Imprime no console o tipo de dados especificado no protocolo que precisa ser aceito:
    fmt.Println("DataType:", intrpr.Commands[2])
  • Lê o valor intrpr.Body para uma variável numérica buf_len
  • Cria um buffer novobuf tamanho especificado:
    make([]byte, buf_len)
  • Envia uma resposta ok:
    conn.Write([]byte("result:ok"))
  • Preenche completamente o buffer do fluxo de leitura:
    io.ReadFull(conn, newbuf)

    .

  • Imprime o conteúdo do buffer no console
    fmt.Println(string(newbuf))

    e o número de bytes lidos

    fmt.Println("Bytes length:", n)
  • Envia uma resposta ok:
    conn.Write([]byte("result:ok"))

Os métodos do pacote do servidor são configurados para processar os dados recebidos usando funções do pacote protocolo.

Protocolo

Um protocolo serve como um meio que representa dados na troca de rede.

Escolha (string string) (string, erro) executa o processamento primário dos dados recebidos pelo servidor, recebe uma representação em string dos dados como entrada e retorna uma string preparada para Intérprete:

  • A string de entrada é dividida em cabeça e corpo usando ReqParseN2(str)
  • head é dividido em elementos e colocado em uma fatia de comandos usando ReqParseHead(head)
  • В mudar(comandos[0]) selecione o comando recebido (cmd, chave, endereço ou a seção é acionada omissão)
  • 2 comandos são verificados no cmd switch(comandos[1]) — comprimento и obterversão.
  • comprimento verifica o tipo de dados em comandos[2] e salva em tipo de dados
  • Verifica isso corpo contém um valor de string
    len(body) < 1
  • Retorna a string de resposta:
    "result:bufferlength:" + datatype + "/" + body
  • obterversão retorna uma string
    return "result:version/auto"

Intérprete

Contém a estrutura InterpreteData e realiza o processamento secundário dos dados retornados do Escolha strings e formação de objetos Interpretar Dados.

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

Função

Interprete(str string) (*InterpreteData, error)

aceita uma string resultar e cria e retorna uma referência ao objeto Interpretar Dados.

Progresso:

  • Da mesma forma, Escolha cabeça e corpo são extraídos usando ReqParseN2(str)
  • cabeça é dividida em elementos usando ReqParseHead(cabeça)
  • O objeto é inicializado Interpretar Dados e um ponteiro para ele é retornado:

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

Este objeto é usado em servidor.go pacote principal.

Cliente

O pacote do cliente contém as funções Conexão TCP и TCPResponseData.

Função

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

funciona da seguinte maneira:

  • Uma conexão é feita com a conexão especificada no objeto de configurações passado
    net.Dial("tcp", s.Host + ":" + s.Port)
  • Os dados passados ​​no parâmetro data são transmitidos:
    conn.Write(data)
  • A resposta é lida
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    e impresso no console

    fmt.Println(string(resp[:n]))
  • Se transferido carga paga então passa adiante
    conn.Write(payload)

    e também lê a resposta do servidor, imprimindo-a no console

Função

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

cria um buffer do tamanho especificado, lê a resposta do servidor e retorna esse buffer e o número de bytes lidos, bem como um objeto de erro.

Sub-rotina do cliente

Serve para enviar comandos aos servidores dos nós, bem como obter breves estatísticas e testes.

Pode aceitar os seguintes parâmetros: arquivo de configuração no formato JSON, dados a serem enviados ao servidor como string, caminho do arquivo a ser enviado para payload, sinalizador de emulação do agendador de nós, tipo de dados transferidos como valor numérico.

  • Obtendo a configuração
    st := types.ParseConfig(*config)
  • Se o sinalizador emu for passado, ele começa planejador
  • Se o sinalizador f indicando o caminho para o arquivo for fornecido, então carregamos seus dados em fdb e o conteúdo é enviado para o servidor
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Se o arquivo não for especificado, os dados do sinalizador serão simplesmente enviados -d:
    client.TCPConnect(st, []byte(*data), nil)

Tudo isso é uma representação simplificada que mostra a estrutura do protocolo. Durante o desenvolvimento, as funcionalidades necessárias são adicionadas à sua estrutura.

Na segunda parte falarei sobre estruturas de dados para blocos e transações, em 3 sobre o servidor WebSocket para conexão de JavaScript, em 4 considerarei o agendador de sincronização, depois uma máquina de pilha que processa bytecode de entradas e saídas, criptografia e pools para saídas.

Fonte: habr.com

Adicionar um comentário