Go Mixin Go

mixin kernel

推荐Go语言入门:Go编程时光对应的微信公众号系列Go 语言入门指南

文章写的通俗易懂,很快就可以过一遍Go语言学习的最小必要知识点。——先“眼到”,至少可以先“读懂”开源项目的代码,然后尝试在开源项目上捣鼓,锻炼“手到”的能力。

当然入门后,遇到问题问题可以进一步搜索对应的资源,例如关于“切片”的内容——

读完入门系列,实操了Mixin大群部署,下一步开始学习Mixin Supergroup的代码逻辑,正好前后端一起来。

计划每天学习内容:

  • 源码阅读
  • 涉及到的API调用可以独立练手
  • 整理出来一段文字记录

第一周

第一天

fork出来一份代码,新建一个分支,直接在源码上添加注释进行学习。

简单读完入门系列,现在大群的代码至少可以看下去了。配合这张图就比较好理解,部署的时候启动的两个服务:http, message

  • 读取配置文件
  • 创建数据库连接
  • 根据传递的参数启动两个服务

Message服务启动:使用结构体Hub进行包装:使用Context,并对ctx进行一些列设置,最后启动接口方法 MessageService.Run()。

这个实现里具体的业务逻辑还没完全搞清楚,大概意思是利用数据库做中转:判断具体的群业务?在loop中做消息处理,使用WebSocker的传输方式。

对比起来,Go代码的确看着舒服——这样,就完成了并发下的业务处理逻辑

go distribute(ctx)
go loopInactiveUsers(ctx)
go loopPendingMessages(ctx)
go handlePendingParticipants(ctx)
go handleExpiredPackets(ctx)
go handlePendingRewards(ctx)
go loopPendingSuccessMessages(ctx)

知识点:

有关包导入

  • 匿名导入 _ "net/http/pprof",显示导入时不使用会报错,采用匿名导入的方式,默认会执行导入的包的init方法。init方法先于main方法执行,并且深度优先。“导入的导入的导入的包”的init方法最新执行

包引用关系:main→A→B→C,那么初始化顺序为C.init→B.init→A.init→main,编译时,匿名导入的包依然会编译到可执行文件中。

第二天

Mixin Messenger 机器人接入指南

APP_BUTTON 的 action 支持 input:SOMETHING 。例如当前的 App Button 的 action 是 “input:subscribe” ,当用户点击这个按钮时客户端会自动发送一条 “subscribe” 的消息给机器人,开发者可以任意指定 input 后面的文字。

根据大群的代码以及API说明,action也可以为url地址,作为认证首页使用

  {
    "id": "UUID",
    "action": "CREATE_MESSAGE",
    "params": {
      "conversation_id": "UUID",
      "category": "APP_BUTTON_GROUP",
      "status": "SENT",
      "message_id": "UUID",
      "data": "Base64 encoded data"
    }
  }

# data format
[{"label": "Mixin Website", "color": "#46B8DA", "action": "https://mixin.one"}, ...]

记得签到答题机器人有对话式式的交互。下载下来读了下源码,一直没搞清楚的关键点是:“提供给用户选项,用户选择后,可以知道用户的选择是什么?”

  • 定位到了如何提供用户选项
  • 后面用户选择,以及用户选择了什么一直没搞清楚

搜索之后,还是长老的教程里说得明白。参见如上。长老的另外一篇文章Mixin 公链,传说中的一篇文章搞懂系列——这篇全部理解透彻,算得上Mixin专家了。

第二周

执行数据库事务。这种实现可以理解业务逻辑。

func closure

models目录下的property.go中包含以下方法——将函数作为参数传递给函数。这里对bool变量b的赋值操作有点秀?(为什么可以这样操作?)

func ReadProhibitedProperty(ctx context.Context) (bool, error) {
  var b bool
  //传递匿名函数
	err := session.Database(ctx).RunInTransaction(ctx, nil, func(ctx context.Context, tx *sql.Tx) error {
		var err error
		b, err = readPropertyAsBool(ctx, tx, ProhibitedMessage)
		return err
	})
	if err != nil {
		return false, session.TransactionError(ctx, err)
	}
	return b, nil
}

// 实际执行的函数
func readPropertyAsBool(ctx context.Context, tx *sql.Tx, name string) (bool, error) {
	query := fmt.Sprintf("SELECT %s FROM properties WHERE name=$1", strings.Join(propertiesColumns, ","))
	row := tx.QueryRowContext(ctx, query, name)
	property, err := propertyFromRow(row)
	if err != nil || property == nil {
		return false, err
	}
	return property.Value == "true", nil
}

被调用的函数定义在durable目录下的database.go文件中——

func (d *Database) RunInTransaction(ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context, tx *sql.Tx) error) error {
	tx, err := d.BeginTx(ctx, opts)
	if err != nil {
		return err
	}
	if err := fn(ctx, tx); err != nil {
		if err := tx.Rollback(); err != nil {
			return err
		}
		return err
	}
	return tx.Commit()
}

上面的代码,原理上理解还有点困难,这些资料需要参考一下:

Can functions be passed as parameters?

go tour: Function closures 完整的的go tour (现在居然对有些国家不提供服务?)

Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is “bound” to the variables.

For example, the adder function returns a closure. Each closure is bound to its own sum variable.

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

Kernel

代码只有15K就完成了白皮书的技术实现。可以读文档,结合代码学习一下。

140 text files.
140 unique files.
43 files ignored.
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
Go                              113           1839             89          15140
Markdown                          9            327              0           1249
JSON                              2              0              0            196
XML                               4              0              0             81
TOML                              1              4             14             16
Bourne Again Shell                1              4              0             12
Bourne Shell                      1              1              0              4
--------------------------------------------------------------------------------
SUM:                            131           2175            103          16698
--------------------------------------------------------------------------------

第一步编译,Mac上没有问题,Windows下提示exec: “gcc”: executable file not found in %PATH%,需要安装一下gcc的环境,参考——

  • mingw-w64这里下载编译好的MinGW-W64 GCC-8.1.0,选择x86_64版本的
  • 解压后将目前下的bin文件夹添加到环境变量

重新编译后生成mixin.exe文件。可以直接使用——这一点go语言的确有优势,直接打包出来的产出物就可以使用

创建运行测试网络mixin setuptestnet会直接在exe所在目录创建对应的目录……一系列逻辑参考源码吧:生成七个地址,创建七个目录。为下一步启动主网做准备。

依次执行mixin kernel -dir /tmp/mixin-7001 -port 7001 启动七个节点。可以根据log输出结合代码看看运行逻辑。运行了一会,产生了28G的数据。

主网代币转账的说明应该无法在测试环境下实现。因为没有实际的Domain将资产接入到主网。

昨天的RPC调用一直不成功,今天阅读源码之后,就知道正确的姿势了——本地返回大概是这样滴,线上环境类似,只是数据更多一些。

getInfo

mixin -n http://127.0.0.1:8001 getinfo 端口默认+1000;返回值最后的node字段代表自己

{
    "epoch":"2021-01-28T16:41:41+08:00",
    "graph":{
        "cache":Object{...},
        "consensus":[
            Object{...},
            Object{...},
            Object{...},
            Object{...},
            Object{...},
            Object{...},
            Object{...}
        ],
        "final":Object{...},
        "sps":0,
        "topology":7
    },
    "mint":{
        "batch":0,
        "pool":"500000.00000000"
    },
    "network":"28f297fa972963a7dde67c02dc5169bb7f5d2227d22b71ab458560c7fd70b8ed",
    "node":"4fd6038742ef3f3c4b8a3a08567e11ba5921171b2e7638b520b8e75f707a04c8",
    "queue":{
        "caches":0,
        "finals":0
    },
    "timestamp":"2021-01-28T16:41:41.000000001+08:00",
    "uptime":"57.732462s",
    "version":"v0.9.9-BUILD_VERSION"
}

如何知道主网地址对应的node?

跟踪了一些——CV了一些代码,获取到IdForNetwork的值。不知道私钥的情况下是无法将genesis.json中的地址跟config中的host对应起来的。这样也启动隐私保护的作用。根据IdForNetwork来对应host即可。获取不同host的getinfo即可

JSON解析:cannot unmarshal string into go struct field 需要让对应的struct实现MarshalJSON(b []byte) errorUnmarshalJSON(b []byte) error方法,就可以完成自定义的解析,官方示例,详细介绍blog golang json

测试网络如何正常关闭?否则重启——

badger 2021/01/29 20:58:18 INFO: All 0 tables opened in 0s
badger 2021/01/29 20:58:18 INFO: Replaying file id: 0 at offset: 0
badger 2021/01/29 20:58:18 WARNING: Truncate Needed. File /tmp/mixin-7001/snapshots\000000.vlog size: 2147483646 Endoffset: 19566
During db.vlog.open: Value log truncate required to run DB. This might result in data loss

badger issue 744 Win7 will be prompted, Mac can start normally开起来是正常现象。启动参数添加opt.Truncate=true可解决,参考

kernel components

可以梳理一些kernel都用到了哪些开源组件。查看项目的go.mod文件可以有获取全部信息。

  • Badger Fast key-value DB in Go. 存储数据,所以需要使用SSD
  • edwards25519 google内Go团队负责密码和安全的大牛实现的edwards25519椭圆曲线实现,创建地址的基础

主网转账

转账方式

  • 机器人的授权令牌调用 POST /transactions 转入到主网地址。transfer-to-mainnet浏览器默认英文环境,没有这个api的说明(默认中文环境时有),
  • 使用官方提供的机器人bot转账,最终也是调用的相同API——调用成功Mixin transfer success snapshotId: 7cf6d709-c048-4c74-a7da-b822e9d84e09, transaction hash: 6cabed5cff5becd72a3533a318d49ee002ae38d27c18cba9b4710f4c770a624b。转账成功后,调用任何一个节点的gettransactionAPI都可以查询到结果

Java的api还没调通,看起来加密错误,目前提示:PIN incorrect.下一步参考go的实现debug一下。Java下的转账api工作OK的

地址格式

每个地址/秘钥都是基于twisted Edwards curve生成的地址,用来进行非对称加密。

-x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2

第一步:生成创建地址的随机64位字节的seed:

  • 创建随机64位byte作为seed1,对其进行SHA3-256哈希取值获取到32位byte数组hash1
  • 将hash1再做一次SHA3-256哈希获得hash2
  • 将hash1和hash2合并得到64位的seed2

第二步:生成地址

  • 使用seed1生成32位byte长度的Key作为spend Key
  • 使用seed2生成32位byte长度的Key作为view Key

view key可以查看对应地址的转账信息;发起转账信息同时需要spend key和view key。

地址包含了一对私钥,公钥可以通过私钥计算得出

A Scalar is an integer modulo 
    l = 2^252 + 27742317777372353535851937790883648493
which is the prime order of the edwards25519 group.
This type works similarly to math/big.Int, and all arguments and receivers are allowed to alias.
The zero value is a valid zero element.
 
comments powered by Disqus