使用 Go 进行工业区块链开发。 第1部分

四个月以来,我一直在致力于一个名为“基于区块链的政府和工业部门数据保护和管理工具的开发”的项目。
现在我想向大家介绍一下我是如何开始这个项目的,现在我将详细描述程序代码。

使用 Go 进行工业区块链开发。 第1部分

这是系列文章中的第一篇文章。 这里我描述一下服务器和协议。 事实上,读者甚至可以编写自己的这些区块链元素版本。

这是第二部分 ——关于区块链和交易数据结构,以及实现与数据库交互的包。

去年,在数字突破黑客马拉松上,他们提出了一个想法,利用分布式账本技术为工业和数字经济打造一个有用的系统;创新援助基金还为开发提供了一笔资助(我应该单独写一篇)关于补助金的文章(适用于那些刚刚创办初创公司的人),现在按顺序进行。

使用Go语言进行开发,存储块的数据库是LevelDB。
主要部分是协议、服务器(运行 TCP 和 WebSocket - 第一个用于同步区块链,第二个用于连接客户端、从 JavaScript 发送交易和命令等)。

如前所述,该区块链主要用于自动化和保护供应商和客户之间或两者之间的产品交换。 这些人并不急于互相信任。 但任务不仅仅是创建一个带有内置计算器的“支票簿”,而是一个能够自动执行产品生命周期中出现的大部分日常任务的系统。 按照区块链的惯例,负责此事的字节码存储在交易的输入和输出中(交易本身存储在块中,LevelDB 中的块以 GOB 格式预编码)。 首先,我们来谈谈协议和服务器(又名节点)。

该协议并不复杂,其要点是切换到加载一些数据的模式,通常是块或交易,以响应特殊的命令行,并且还需要交换库存,以便节点知道它是谁连接到以及它们如何处理事务(为同步会话而连接的节点也称为“邻居”,因为它们的 IP 已知并且它们的状态数据存储在内存中)。

在 Go 程序员的理解中,文件夹(Linux 称之为目录)被称为包,因此在该目录中包含 Go 代码的每个文件的开头,他们会写 package_name_where_this_file 所在的位置。 否则,您将无法将包提供给编译器。 嗯,对于那些了解这种语言的人来说,这不是什么秘密。 这些是包:

  • 网络通信(服务器、客户端、协议)
  • 存储和传输数据的结构(区块、交易)
  • 数据库(区块链)
  • 共识
  • 堆叠虚拟机 (xvm)
  • 辅助(加密、类型)现在就这些了。

这是github的链接

这是一个教育版本,缺乏进程间交互和几个实验组件,但结构与正在开发的结构相对应。 如果您在评论中有什么建议,我很乐意在进一步的开发中考虑。 现在对服务器进行解释 协议.

我们先看服务器。

服务器子例程充当数据服务器,使用协议包中的数据结构在 TCP 协议之上运行。

该例程使用以下包: 服务器, 协议, 类型。 在包装本身 tcp_server.go 包含数据结构 即可享用。.

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

它可以接受以下参数:

  • 交换数据的网络端口
  • JSON 格式的服务器配置文件
  • 在调试模式下运行的标志(私有区块链)

进步:

  • 从 JSON 文件读取配置
  • 检查调试模式标志:如果设置,则不会启动网络同步调度程序并且不会加载区块链
  • 初始化配置数据结构并启动服务器

服务器

  • 按照协议进行TCP服务器的启动和网络交互。
  • 它有一个 Serve 数据结构,由端口号、缓冲区大小和指向该结构的指针组成 类型.设置
  • Run方法启动网络交互(监听给定端口上的传入连接,当接收到新连接时,其处理将转移到新线程中的私有句柄方法)
  • В 处理 来自连接的数据被读入缓冲区,转换为字符串表示形式并传递给 协议.选择
  • 协议.选择 回报 导致 或导致错误。 导致 然后转移到 协议.解释返回 内部 - 类型对象 解释数据,或者导致处理选择结果时出错
  • 然后执行switch intrpr.命令[0] 它检查以下之一: 结果、inv、错误 还有一个部分 默认
  • 在该部分 导致 通过值找到开关 intrpr.命令[1] 检查值 缓冲区长度 и 版本 (每种情况都会调用相应的函数)

功能 获取版本 и 缓冲区长度 都在文件里 srvlib.go 服务器包

GetVersion(conn net.Conn, version string)

它只是打印到控制台并将参数中传递的版本发送到客户端:

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

.
功能

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

加载区块、交易或其他特定数据,如下所示:

  • 将协议中指定的需要接受的数据类型打印到控制台:
    fmt.Println("DataType:", intrpr.Commands[2])
  • 读取值 内部体 到数字变量 缓冲区长度
  • 创建一个缓冲区 新缓冲区 指定尺寸:
    make([]byte, buf_len)
  • 发送一个好的响应:
    conn.Write([]byte("result:ok"))
  • 从读取流完全填充缓冲区:
    io.ReadFull(conn, newbuf)

    .

  • 将缓冲区的内容打印到控制台
    fmt.Println(string(newbuf))

    和读取的字节数

    fmt.Println("Bytes length:", n)
  • 发送一个好的响应:
    conn.Write([]byte("result:ok"))

服务器包中的方法被配置为使用包中的函数处理接收到的数据 协议.

协议

协议充当表示网络交换中的数据的手段。

选择(str字符串)(字符串,错误) 对服务器接收到的数据进行初步处理,接收数据的字符串表示形式作为输入,并返回准备好的字符串 口译员:

  • 使用以下命令将输入​​字符串分为头部和主体 ReqParseN2(str)
  • head 被分割成元素并使用 ReqParseHead(head) 放入命令切片中
  • В 开关(命令[0]) 选择接收到的命令(cmd、密钥、地址 或者该部分被触发 默认)
  • cmd中检查2条命令 switch(命令[1]) — 长度 и 获取版本.
  • 长度 检查数据类型 命令[2] 并将其保存在 数据类型
  • 检查 身体 包含一个字符串值
    len(body) < 1
  • 返回响应字符串:
    "result:bufferlength:" + datatype + "/" + body
  • 获取版本 返回一个字符串
    return "result:version/auto"

口译员

包含InterpreteData结构体,并对从返回的数据进行二次处理 选择 字符串和对象的形成 解释数据.

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

功能

Interprete(str string) (*InterpreteData, error)

接受一个字符串 导致 并创建并返回对该对象的引用 解释数据.

进步:

  • 同样地, 选择 头部和身体被提取使用 ReqParseN2(str)
  • head 被分成元素使用 ReqParseHead(头)
  • 对象已初始化 解释数据 并返回指向它的指针:

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

该对象用于 服务器.go 包主.

客户

客户端包包含的功能 TCP连接 и TCP响应数据.

功能

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

工作原理如下:

  • 与传递的设置对象中指定的连接建立连接
    net.Dial("tcp", s.Host + ":" + s.Port)
  • 传输data参数中传入的数据:
    conn.Write(data)
  • 答案已读
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    并打印在控制台上

    fmt.Println(string(resp[:n]))
  • 如果转移 有效载荷 然后传递它
    conn.Write(payload)

    并读取服务器响应,将其打印到控制台

功能

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

创建指定大小的缓冲区,读取服务器响应并返回该缓冲区和读取的字节数以及错误对象。

客户端子程序

用于向节点服务器发送命令,以及获取简要统计和测试。

可以接受以下参数:JSON 格式的配置文件、要作为字符串发送到服务器的数据、要发送到负载的文件的路径、节点调度程序模拟标志、作为数值传输的数据类型。

  • 获取配置
    st := types.ParseConfig(*config)
  • 如果emu标志被传递,则开始 调度器
  • 如果提供了指示文件路径的 f 标志,那么我们将其数据加载到 FDB 并将内容发送到服务器
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • 如果未指定文件,则仅发送标志中的数据 -d:
    client.TCPConnect(st, []byte(*data), nil)

所有这些都是显示协议结构的简化表示。 在开发过程中,必要的功能被添加到其结构中。

在第二部分中,我将讨论块和事务的数据结构,在第 3 部分中,我将讨论用于从 JavaScript 连接的 WebSocket 服务器,在第 4 部分中,我将讨论同步调度程序,然后是处理来自输入和输出的字节码、密码学和输出池。

来源: habr.com

添加评论