Animagus 系列文章(二):实际运行

Animagus 的设计者正是 CKB-VM 的设计者与核心开发者 Xuejie。在第一篇文章中,Xuejie 向我们介绍了 Animagus。本文是该系列的第二篇,将详细介绍如何运行 Animagus。

作者:Xuejie
原文链接:What’s Animagus Part 2: Running it For Real | by Nervos Network | Nervos Network | Medium

我们将实现 balance checker(余额检查器)AST,然后用 Animagus 启动它,这样就可以通过调用 RPC 请求来和 Animagus 进行交互了。

在正式开始之前,你的系统上需要有这几个依赖项:

  • Docker :虽然 Docker 不是在生产环境中运行 Animagus 的严格依赖项,但之后你将看到它可以帮助我们快速配置开发环境。

  • Go :Animagus 用 Go 语言编写。因为我们是从源代码构建 Animagus,所以需要在计算机上安装 Go。

  • Ruby: 我们使用 Ruby 构建 balance checker AST。虽然你可以使用 GRPC 支持的任何语言,但 Ruby 拥有非常好的 REPL ,可以用来更好地测试。

  • GRPC: 我们需要 Go 和 Ruby 的 GRPC 开发依赖项,可以参阅 GRPC 文档中的「快速入门」部分,并查找 Go 和 Ruby 的操作步骤。

在尝试开始下面步骤之前,请确保已安装上述组件并准备就绪。

注意:在 Unix 环境(例如 Linux,macOS 或 Windows 上的模拟解决方案)下尝试这些步骤会更好。如果在其他平台上进行测试,性能可能会有所不同。

首先,我们来编译 Animagus:

$ export TOP=$(pwd)
$ git clone https://github.com/xxuejie/animagus
$ cd animagus (https://github.com/xxuejie/animagus)
$ git checkout afb9f1ec5ca14493835c8aeb4651b5121580ce39
$ go build ./cmd/animagus

在生产环境中,Animagus 需要以下依赖项:

为了简化大家的测试流程,我准备了一个 docker 镜像,它将 CKB 与 CKB GraphQL server 打包在一起,只需一个命令,就可以启动已经准备就绪的 CKB 实例。这里的结果是,依次输入以下命令,我们可以通过两个 docker 容器启动所需的所有的依赖项:

$ cd $TOP
$ mkdir ckb-data
$ docker run -d --rm -p 9115:9115 -v `pwd`/ckb-data:/data --env RPC_RATE=200000 --env GRAPHQL_RATE=200000 --name ckb xxuejie/perkins-tent
$ docker run -d --rm -p 6379:6379 --name redis redis:alpine

注意:为 CKB 提供的打包好的 docker 镜像是限制的。这里,我们对一些值做了修改,是为了减少测试过程中的等待时间,但如果要使用 docker 镜像运行公共节点,请确保将修改过的的值调整回去,以免造成 container 被滥用。

在等待 CKB 同步时,我们来启动 Ruby,并实现 balance checker AST。首先,我们要 在 balance checker AST 中构建查询部分 。请注意,完整的查询节点就是下面显示的附加到 QueryCell 节点的查询函数。

$ cd $TOP
$ cat << EOF > animagus/ruby/query_cell.rb
require_relative "ast_pb"

def build_query_cell_function
  cell = Ast::Value::new(
     t: Ast::Value::Type::ARG,
     u: 0
  )

  script = Ast::Value::new(
    t: Ast::Value::Type::GET_LOCK,
    children: [cell]
  )

  expected_code_hash = [
    "9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"
  ].pack("H*")
  code_hash_value = Ast::Value::new(
    t: Ast::Value::Type::BYTES,
    raw: expected_code_hash
  )

  code_hash = Ast::Value::new(
    t: Ast::Value::Type::GET_CODE_HASH,
    children: [script]
  )
  code_hash_test = Ast::Value::new(
    t: Ast::Value::Type::EQUAL,
    children: [
      code_hash,
      code_hash_value
    ]
  )

  hash_type_value = Ast::Value::new(
    t: Ast::Value::Type::UINT64,
    u: 1
  )
  hash_type = Ast::Value::new(
    t: Ast::Value::Type::GET_HASH_TYPE,
    children: [script]
  )
  hash_type_test = Ast::Value::new(
    t: Ast::Value::Type::EQUAL,
    children: [
      hash_type,
      hash_type_value
    ]
  )

  args_value = Ast::Value::new(
    t: Ast::Value::Type::PARAM,
    u: 0
  )
  args = Ast::Value::new(
    t: Ast::Value::Type::GET_ARGS,
    children: [script]
  )
  args_test = Ast::Value::new(
    t: Ast::Value::Type::EQUAL,
    children: [
      args,
      args_value
    ]
  )

  Ast::Value::new(
    t: Ast::Value::Type::AND,
    children: [
      code_hash_test,
      hash_type_test,
      args_test
    ]
  )
end

def build_query_cell
  Ast::Value::new(
    t: Ast::Value::Type::QUERY_CELLS,
    children: [
      build_query_cell_function
    ]
  )
end
EOF

这里的代码很长,但有大量的代码都是重复的,这是因为代码直接是在 AST 上实现的。有了 DRY 原理定义的一些函数,我们就应该能够构建出最小的 DSL,和 Animagus AST 一起使用。

下面就是转换的实现部分:
Copy of Animagus_ account layer for CKB (4)

$ cd $TOP
$ cat << EOF > animagus/ruby/execution.rb
require_relative "ast_pb"

def build_execution_node(query_cell_node)
  get_capacity_function = Ast::Value::new(
    t: Ast::Value::Type::GET_CAPACITY,
    children: [
      Ast::Value::new(
        t: Ast::Value::Type::ARG,
        u: 0
      )
    ]
  )
  capacities = Ast::Value::new(
    t: Ast::Value::Type::MAP,
    children: [
      get_capacity_function,
      query_cell_node
    ]
  )

  add_balance_function = Ast::Value::new(
    t: Ast::Value::Type::ADD,
    children: [
      Ast::Value::new(
        t: Ast::Value::Type::ARG,
        u: 0
      ),
      Ast::Value::new(
        t: Ast::Value::Type::ARG,
        u: 1
      )
    ]
  )
  initial_balance = Ast::Value::new(
    t: Ast::Value::Type::UINT64,
    u: 0
  )
  Ast::Value::new(
    t: Ast::Value::Type::REDUCE,
    children: [
      add_balance_function,
      initial_balance,
      capacities
    ]
  )
end

def build_root(name, node)
  Ast::Root::new(
    calls: [
      Ast::Call::new(
        name: name,
        result: node
      )
    ]
  )
end
EOF

现在我们准备构建 Animagus 所需的 AST 文件:

$ cd $TOP
$ pry -I animagus/ruby
[1] pry(main)> require 'query_cell'
[2] pry(main)> require 'execution'
[3] pry(main)> query_cell_node = build_query_cell
[4] pry(main)> execution_node = build_execution_node(query_cell_node)
[5] pry(main)> root = build_root("balance", execution_node)
[6] pry(main)> File.write("balance.bin", Ast::Root.encode(root))

正如上一篇文章中所说,我们可以用其他语言构建 AST,比如 Go,它也可以用来构建 Animagus。我们可以将所有内容都写在一个文件中,而不是将其扩展为两个文件和一个 REPL 部分。之所以选择这种方法,是因为 Ruby 提供了一个允许快速测试的 REPL。

如果你想要汇总一些其他节点的余额,可直接修改现有的 AST;如果只是想计算 cell 的数量而不是余额汇总,也可以修改 AST。学习 Animagus 的 AST 设计,其最好方法就是实际使用 AST。因此你可以随意修改我们在这里构建的 AST,也许你会发现 Ruby REPL 非常有用。

下面,我们在另一个终端窗口启动 Animagus(稍后我们将会用到 Ruby 来执行调用 Animagus,所以这里的 Ruby REPL 保持打开状态):

$ cd $TOP
$ ./animagus/animagus -astFile=balance.bin -graphqlUrl=http://127.0.0.1:9115/graphql
2020/03/04 03:41:34 Indexed block 92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5, block number 0   
2020/03/04 03:41:34 Indexed block 2567f226c73b04a6cb3ef04b3bb10ab99f37850794cd9569be7de00bac4db875, block number 1                                                        
2020/03/04 03:41:34 Indexed block 2af0fc6ec802df6d1da3db2bfdd59159d210645092a3df82125d20b523e0ea83, block number 2   
2020/03/04 03:41:34 Indexed block 247167d03a723f6b8999da09d94b61fadf47f94364d729cb6272edc1f20009b7, block number 3                                                        
2020/03/04 03:41:34 Indexed block 83832d6367429901a4bf763a6d6cbdc658a2624a8a4cda7427edd6fad65d0f7d, block number 4   
2020/03/04 03:41:34 Indexed block 67a0604ef3147f82f81c44d8b74f7329ac4ebabc29a2931a88d64c8f44b77735, block number 5                                                        
(logs omitted..)

你会看到 Animagus 已经开始索引 CKB 的区块。也就是说即使 Animagus 还在索引,但它已经可以使用当前已索引的数据为 GRPC 提供请求了。这里需要注意,在 Animagus 还没赶上 CKB 的最新区块之前,数据可能并不准确。(因为后边索引的区块可能包含其他存取款信息)

为了达到测试的目的,现在我们运行实际的请求。但在生产环境中,可能需要等到 Animagus 赶上当时链的最新区块再运行。

现在我们回到之前的 Ruby REPL:

[12] pry(main)> require 'generic_pb'
[13] pry(main)> require 'generic_services_pb'
[14] pry(main)> stub = Generic::GenericService::Stub.new("127.0.0.1:4000", :this_channel_is_insecure)
[15] pry(main)> param = Ast::Value::new(t: Ast::Value::Type::BYTES, raw: ["903a0633f6ab457de09efb8f84dc271dc488bf62"].pack("H*"))
[16] pry(main)> request = Generic::GenericParams.new(name: "balance", params: [param])
[17] pry(main)> stub.call(request)
=> <Ast::Value: t: :UINT64, b: false, u: 89732200000000, raw: "", children: []>

在上面的示例中,我选择了 CKB 主网上的这个地址。我并不知道这是谁的地址,这只是一个创世块中尚未被使用的地址。用这样的一个地址,Animagus 能够快速而且不会有太多延迟地索引它的值。从实际的 GRPC 调用中,我们已经填写了 0x903a0633f6ab457de09efb8f84dc271dc488bf62 ,这是地址的 args 部分,在 balance checker AST 的参数 0 中使用。可以看到,我们已经获取了结果,即 89732200000000 shannons 或 897322 CKB。你可以随意将参数交换到另一个地址的 script args 进行尝试。

现在,我们已经使用 Animagus 实现了一个完整的地址余额查询。此示例比较简单,但你可以将其应用于构建更为复杂的示例,例如从多个地址汇总余额或 NervosDAO 的存款数。

只要你可以为 Animagus 提供一个用于提取和转换数据的 AST,Animagus 就可以转换成任何你想要的东西 。到这里,我想你会赞同 Animagus 这个名字非常适合这个项目。

以上就是有关如何运行 Animagus 的内容了,接下来,我们将研究如何使用 Animagus 集成 Simple UDT 规范。我们不仅会研究如何汇总 UDT 余额,还将展示如何使用 Animagus 将 UDT 从一个帐户转移到另一个帐户。

敬请期待!

1 Like