Qué es Animagus Parte 2: Ejecutándolo de verdad

Escrito por @xxuejie

En el último post presentamos Animagus. Ahora vamos a empezar a ensuciarnos las manos– implementaremos el verificador de equilibrio AST y ejecutaremos Animagus con él, para que podamos interactuar con él a través de llamadas RPC.

Este post requiere varias dependencias en el sistema:

  • Docker: mientras que Docker no es, estrictamente una dependencia para ejecutar Animagus en producción, vamos a ver a continuación cómo nos ayuda a estructurar rápidamente un entorno de desarrollo

  • Go: Animagus está escrito en Go. Ya que vamos a construir Animagus desde esta fuente, necesitarás instalar Go en tu máquina

  • Ruby: usaremos Ruby para construir el verificador de equilibrio AST. Si bien podrías utilizar cualquier lenguaje soportado por GRPC, Ruby tiene una REPL muy agradable con la que podemos experimentar

  • GRPC: en particular, necesitaremos dependencias de desarrollo de GRPC para Go y Ruby, consulta la sección Quick Start en la documentación de GRPC, y busca los pasos para Go y Ruby

Por favor, asegúrate de que los componentes anteriores están instalados y listos antes de probar los siguientes pasos en este post.

Nota: funciona mejor si intentas esos pasos en un entorno Unix, como Linux, macOS o una solución emulada en Windows. Tu kilometraje puede variar si estás probando en otras plataformas.

Primero, compilemos Animagus

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

En producción, Animagus necesitaría de las siguientes dependencias:

Para ahorrarte esfuerzo, he preparado una imagen docker que empaqueta CKB junto con el servidor CKB GraphQL, por lo que en un solo comando puedes iniciar una instancia de CKB que esté lista para comenzar. El resultado, es que podemos iniciar todas las dependencias necesarias con 2 contenedores docker con los siguientes comandos:

$ 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

Toma en cuenta que nuestra imagen docker empaquetada para servir a CKB tiene una función de limitación de velocidad. Estamos generando los valores aquí para reducir el tiempo de espera en el trabajo de prueba, pero si estás ejecutando un nodo orientado al público con esta imagen docker, asegúrate de ajustar los valores para no sobrecargar tu contenedor.

Mientras esperamos a que CKB se sincronice, activemos Ruby e implementemos el AST del verificador de equilibrio. En primer lugar, crearemos la parte de consulta en el verificador de equilibrio AST. Ten en cuenta que el nodo de consulta completo es solo la función de consulta que se muestra a continuación adjunta a un nodo 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

A pesar de que el código es bastante largo aquí, notarás que contiene una gran cantidad de trabajo repetitivo, ya que el código funciona directamente en AST. Con algunas funciones definidas por el principio DRY, ya deberíamos ser capaces de construir un DSL mínimo para trabajar con el Animagus AST.

También implementaremos la parte transformadora aquí:

$ 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

Ahora estamos listos para construir el archivo AST que necesita Animagus

$ 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))

Como se mencionó anteriormente, podríamos haber construido el AST en otros idiomas, como Go, que también se utiliza para construir Animagus. Podríamos haber escrito todo en un solo archivo en lugar de distribuirlo en 2 archivos y una parte REPL. La razón por la que he elegido este enfoque es que Ruby proporciona una REPL que permite una experimentación rápida.

Si deseas cambiar el AST a saldos agregados de algunos otros nodos, puedes modificar el AST existente de inmediato. Si solo deseas contar el número de células en lugar de recopilar saldos, también puedes hacerlo. La mejor manera de aprender el diseño AST de Animagus es realmente jugar con él, así que no dudes en hacer cambios en el AST que construimos aquí. Tal vez el Ruby REPL te parecerá súper útil.

Ahora vamos a activar Animagus en una ventana de terminal diferente (es posible que quieras mantener el Ruby REPL abierto ya que usaremos Ruby para ejecutar llamadas a Animagus más adelante):

$ 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..)

Te darás cuenta de que Animagus ha comenzado a indexar bloques desde CKB. Aunque Animagus está indexando, ya puedes empezar a atender solicitudes GRPC utilizando datos indexados actualmente. Ten en mente que, hasta que Animagus se haya puesto al día con los últimos bloques de CKB, los datos podrían no ser precisos, ya que los bloques indexados posteriormente pueden incluir depósitos y retiros adicionales.

Para probarlo, ejecutaremos las solicitudes reales ahora. Sin embargo, en producción, es posible que desees esperar hasta que Animagus se ponga al día con la punta de cadena actual.

Ahora volvamos a nuestra anterior 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: []>

En el ejemplo anterior, he elegido esta dirección en CKB mainnet. (No tengo idea de quién es esta dirección, simplemente estoy usando una dirección del bloque de génesis que aún no se ha gastado). De esta manera Animagus debería ser capaz de indexar su valor rápidamente sin mucho retraso. A partir de la llamada GRPC real, hemos rellenado 0x903a0633f6ab457de09efb8f84dc271dc488bf62, que es la parte args del script de la dirección, como se utiliza en el verificador de equilibrio AST param 0. Puedes ver que hemos logrado obtener el resultado, que es 897322000000 shannons o 897322 CKB. Siéntete libre de cambiar el parámetro a los argumentos de script de otra dirección y pruébalo.

Ahora hemos implementado un verificador de equilibrio de direcciones completo usando Animagus. Aunque este ejemplo es bastante simple, puedes adaptarlo para crear ejemplos más complicados, como recopilar saldos de varias direcciones o recopilar depósitos DAO de Nervos.

Animagus puede transformarse en lo que quieras, siempre y cuando le alimentes un AST utilizado para extraer y transformar los datos que deseas.

Hasta ahora, espero que estés de acuerdo en que el nombre Animagus es bastante adecuado para este proyecto.

Esto completa nuestro post sobre la ejecución de Animagus. Lo siguiente será que veremos cómo integrar la especificación UDT Simple usando Animagus. No solo veremos cómo agregar saldos UDT, también mostraremos cómo podemos usar Animagus para transferir UDT de una cuenta a otra. ¡Por favor, manténte atento!


Únete a nuestra comunidad: Github Forum Reddit Twitter

Para discusiones o preguntas, únete a la conversación en Discord o en uno de los canales de Telegram de nuestra comunidad: inglés, coreano, ruso, japonés, español, vietnamita y chino

2 Likes