Mina 文档 - 开发者

  • MinaFans
  • 更新于 2021-12-19 12:26
  • 阅读 3214

Mina开发者中文文档

5.1 开发者

欢迎来到Mina开发者文档-请快速熟悉一下下面的链接,了解一下情况。

探索Github上的代码库

请参阅贡献指南来开始为Mina贡献代码。协议CLI是用OCaml编写的。在这些工具中的任何级别的经验水平都是可以的。

与贡献代码相关的其他文档包括:

风格指南

代码评审指南

库结构

BIP44信息

Mina是完全开源的,代码是按照Apache License 2.0的条款发布的。

5.2 代码库概况

Mina 区块链协议是由静态函数式编程语言OCaml编写而成。

OCaml的初学者可通过浏览 Real World OCaml,详细了解该语言,如有兴趣,可对特定主题深入探究。若对OCaml已有基本了解,可在此处了解更多其在Mina区块链协议中的应用。

代码结构

Repository Structure page

编译

OCaml编译器针对的是字节码和本地编译。我们的代码与一些程序库静态链接,因此无法编译成字节码。此外,在REP环境下,不能很好地发挥作用。我们的构建系统是Dune。Dune有一个文件夹的概念,文件夹代表模块,其中的每个文件都代表一个模块。若文件夹中有一个与该文件夹同名的文件,那这个文件本质上就等同于Node中的index.js

OCaml还有接口文件——其文件扩展名为.mli,其中包含了一个模块的类型签名和结构。与之相对应的编译器必须有相同的文件名,但文件扩展名为.ml。从其他模块只能访问接口规定的内容。若模块没有接口文件,所有内容默认都可以被公开访问。注意:.rei 和 .re 文件扩展名也遵循同样的逻辑。

在链接步骤,dune使在后台使用ldd。还可使用-O3之类的命令实现最优化。可使用gdb调试程序,虽然OCaml不能完全支持这一调试工具,但这是一种有效的方式。即将发布的OCaml(4.08)可与gdb兼容。

文档

学习OCaml的一个难点在于程序库文档的定位和读取。特别是Core库,其结构如下:

Base
        |
     Core_kernel -> Async_kernel
        |               |
  Unix  <-  Core  ->    Async

事实上,Core文档有多个源文件。要找到正确的文档,首先要登录GitHub,然后使用该平台定位正确文档。若未能找到所需模块,即登录Core_kernel,若仍未找到,则查找Base。还需要注意的是若字节段并未扩展,则该查询无效。

与其使用HTML文档,更好的方法是利用如merlin等提供类型提示的集成编辑器。但Merlin的一个缺点是只能在你的代码能够编译的情况下运行。此外还可提供代码跳转功能,如选中代码并转至其初始定义的代码段。

OPAM是OCmal的程序包管理器,通常与程序库共同运载文档。可通过merlin访问该管理器。

文件扩展

OCaml有一个编译器插件的概念,名为ppx。这只是允许编译时代码生成的钩子(触发器)。

下面是一个类型签名扩展的例子

type t =
   | A
   | B [@ to_yojson f]

  [@@ deriving yojson]

在以上例子中,单个@ 表示对单个表达式的扩展,@@表明该表达式将在调用上下文的范围内扩展。

结构或值的扩展要用到以下语法。

%返回一个值/表达式

%%插入一个语句

let x = [% ...]
  [%% ...]
  let y =
   let%... z = ... in
   match%... ... with
   | ...
   | ... in

  [%% if x]
   let x = y
  [%% else]
   let x = z
  [%% endif]

总之,你在任何时候看到的 [@ ...] [@@ ...] [% ...] [%% ...],都是扩展语言。

单体

其函数式可非常直接地书写运算,无需样板代码。单体允许我们抽象上升到更高的运算,同时提供胶水函数。换句话说,单体是可编程的分号。

例如,下列命令式运算:

function example(x) {
   if ( x == null ) return null;
   x = x + 1;
   if ( !isEven(x) ) return null;
   return x;
  }

同样可由单体函数式用option来表达:

type a' option =
   | None
   | Some of 'a

  let return x = Some x

  (* Bind infix operation, applies f to m *)
  let (>>=) m f =
   match m with
   | Some x -> f x
   | None  -> None

  (**
   Map infix operation
   Essentially the same as bind, but the inner function unwraps the value.
   **)
  let (>>|) m f = m >>= (fun x -> return (f x))

现在我们可以使用这些原语重新运行上述命令示例,如下图所示。

let add_one = ((+) 1)

  let bind_even : int -> int option =
   fun x -> if x mod 2 = 0 then Some x else None

  let example x = x >>| add_one >>= bind_even;

OCaml has a `ppx` that makes writing monads much easier to follow, using the let syntax.

  let%bind x = y in
   f x

  (* This compiles down to the following *)
  y >>= (fun x -> f x)

本质上,这个语法是从let语句取值,放在bind-infix-call函数式左侧,将赋值插入lambda表达式。

Async函数

Async函数在后台运行单子,但用的是Ivar。a' Ivar.t本质上是一个只能填入一次的互斥量。一旦返回计算的结果值,就会填入Ivar。剩下的语法糖会取Ivar的值,并借Deferred单体让数值通过。

确实有一个yield 函数,但是由于其有某些奇怪特性,我们尽量避免使用。相反,我们通常运行Deferred 约束之间的包裹值。

警告:需要注意的另一点是Async.Pipe,它的操作本质上就像一个缓冲期。请勿使用。这是不安全的,因为其默认不限制缓存(内存溢出),奇怪函数会影响管道功能,产生古怪的行为。取而代之的是使用Strict_pipe,它封装了管道,但为我们使用提供确定的保障。我们编写了一种更加个性化的助手Broadcast_pipe,可允许单个管道连接到多个下游管道。此外,在编写之前的Linear_pipe之时,我们尚未真正明白管道的缺陷,相比于更佳的Strict_pipeBroadcast_pipe来说,前者现已过时。

5.3 库结构

本文描述了Mina仓库的文件结构,以及各种文件扮演的角色:

● dockerfiles /

包含Docker相关脚本- TODO能够更好地解释这个内容

● docs /

这里有关于代码和贡献过程的文档。带有预排文档的文档网站位于frontend/website/docs.

● frontend/

​ 所有与Mina前端UI和产品相关的代码

- wallet/

​ Mina钱包的源代码

- website/

​ 代码为 https://minaprotocol.com

​ - docs /

​ 关于加入Mina网络的文档和说明 https://minaprotocol.com/docs

​ - posts/

​ 博客文章的Markdown文档

​ - src /

​ 该网站的源代码

​ - static/

​ 静态文件,如图像等。

● rfcs /

​ 这个目录包含了所有根据RFC流程做出的接受的RFC(或“评论请求”)。

● scripts/

● src /

所有协议源代码,包括应用程序和库代码,都在这个目录中。

- opam

​ 这些文件是我们的构建dune系统所需要的。lib中的每个库都必须有一个。当您创建库lib/foo_lib时,使用一个dune文件,将库的名称指定为foo_lib,您必须创建一个foo_lib.opam文件。

- config/

​ 构建时间配置——这些.mlh文件定义了编译时间常量及其值。

- app /

​ 应用程序在这里。

​ - cli /

​ 这是mina客户端/守护进程。它是您用来运行staker、snarker或发送和接收交易的简单客户机的工具。

​ - website/

​ 很快就会被弃用的网站目录-大部分代码已经迁移到前端/网站/

​ - reformat/

​ 这个程序在源代码树中的大多数文件上运行ocamlformat,只有少数例外。

​ - logproc /

​ 这个实用程序从stdin读取数据,可以过滤和打印mina守护进程发出的日志消息。

​ - libp2p_helper /

​ 这个程序使用go-libp2p来实现Mina守护进程所需要的点对点管道。

- external/

外部库的本地副本,我们需要做一些调整。

- lib /

所有库都支持mina。这里的库基本上分为两类。

  1. 通用数据类型和功能。这包括snarky、fold_lib、vrf_lib、sgn等。
  2. 应用程序特定的功能,结构为一个库。这包括syncable_ledger、staged_ledger、transaction_snark等。

5.4 BIP44 信息

index hexa symbol coin
12586 0x8000312a MINA Mina

Mina使用BIP44中指定的5级BIP32路径格式

m / purpose' / coin_type' / account' / change / address_index

密钥对派生时,只改变account,同时保持changeaddress_index为0

BIP32 路径格式

m / 44' / 12586' / account' / 0 / 0

示例

account path
0 m/44'/12586'/0'/0/0
271 m/44'/12586'/271'/0/0

5.5 代码评审指南

一个好的拉取请求:

● 做了一件事(新特性,bug修复,等等)

● 为任何新功能添加测试和文档

● 当修复一个bug时,添加或修复可以捕获该bug的测试

与OCaml有关

● 是否遵循了样式指南?

● 签名有意义吗?它们是最小的和可重复使用的吗?

● 有什么需要被更改的吗?

● 是否存在没有正确处理的错误情况?

● 对_exn函数的调用是否合理?它们不抛出异常的先决条件是否满足?它抛出的异常有用吗?

● 不应该有注释掉的代码。

● 没有零散的调试代码。

● 任何日志记录都是合适的。所有日志记录器。跟踪日志应该是无关紧要的,因为它们在默认情况下不会显示给任何人。

● 这个代码应该保存在它的库中吗?它应该放在另一个库吗?

● 代码让你感到迷惑吗?也许应该有一个评论,或者它应该有不同的结构。

● 一个行为改变,是否会打破对其他代码作出的假设?

5.6 风格指南

Ocaml

通则

我们的风格指南是对几个现有风格指南的扩展。第一个是ocamlformat,相当于我们编码风格的主要事实来源。实际上,ocamlformat是对持续集成(CI)的阻断,因此你的代码必须经过编程风格格式化以便并入母版页。但是ocamlformat并不会处理所有风格问题,因为它只是用来定义代码如何间隔和缩进。对于ocamlformat没有涉及到的内容,可以查阅Jane Street styleguide 。我们在此处阐释的这一风格指南旨在成为janestreet 风格指南的扩展,注重涉及我们代码库常用一些特定结构的细节。

Mli文件

.ml自动派生接口不同,则*.mli文件不会被包括在前者中。我们代码库中许多*.ml文件只包含签名和一个仿函数。在这种情况下,重新定义*.mli文件没有意义,因为该文件中没有新信息或限制性信息。若一个*.ml文件包含root结构上的实现,则多半可能会创建一个*.mli 文件。

模块

优选标准化短名称

tT和S等是模块中常见的短名称,用来表达特定含义。

t用来指模块的root类型。例如,若Account模块中包含了与账户有关的类型和值,则Account.t就是账户的类型。若模块的root类型应只有一个值,t还可用作一个值。如,若你想在Logger模块里包含单个全局记录器,Logger.t可作为全局记录器的类型,其值就是Logger.t类型的全局记录器值。

T用于简述一个模块的root类型相关的root类型和基本定义。在你想实例化一个模块的root类型的一些仿函数,让该模块本身有实例的情况下,这是一种常用方法。例如,我们通常将Comparable.Make称为仿函数以便衍生不同的comparable类型的helper值或模块。在这种情况下,若我们又有一个Account模块,并且想衍生Comparable.S签名,则我们会在定义一个root类型tAccount中定义一个T模块以及Comparable.Make仿函数实际参数(在这种情况下为compare)所需函数。有了T模块,我们则可以将include Tinclude Comparable.Make (T)包含在Account模块中,以便向Account.t类型引入所有相关值或模块。下面是一个完整例子:

module Account = struct
 module T = struct
  type t = ... [@@deriving compare]
 end

 include T
 include Comparable.Make (T)
end

模块类型名S用于定义一个模块的root签名,这种方法最常用于当你有一个包含一个仿函数的模块的情况。在此情况下,我们往往定义Make这个函数,并声明这个函数的返回值的类型为S,将这些值放在同一个模块里。回看我们之前的例子,Core_kernelComparable模块遵循了这个模式:Comparable.Make是一个返回值为Comparable.S的仿函数。

每个模块选择一种类型

根据一般经验,每一个模块都应限定单一类型。这种模式有助于分离关注点,并且反过来允许值的名称变得更短,因为它们都被固定在上下文中。以Merkle_tree模块为例,该模块需要一个Merkle_tree.t的类型,该类型代表着整个树算法(或上面的一个节点)。一个Merkle_tree也需要一个path 类型。最好将path 类型放在其嵌套的模块中(Merkle_tree.Path.t而不是Merkle_tree.path)。现在,Merkle_tree包含的值(函数)不仅与树算法类型本身有关,还与树算法的path有关。为了清楚起见,自然而然假设所有的值名称与path_类型的路径有关(如path_map,path_length等)。通过限定Path自己的模块,我们可以缩短这些名称,值的上下文保持清楚。此外,如果我们选择这种方式,可以压缩 Path的实现细节,使用一个限定性签名,通过编译器实施会让关注点分离更清楚。

勿用MONKEY补丁

我们代码库明确没有monkey补丁模块。monkey补丁的定义是将现有的模块用扩展或修改值重新定义。更简单的说,只是表单上的改变。

module A = struct
 module M = struct
  let x = ...
 end
end

module M = struct
 include A.M
 let y = ...
 (* or `let x = ...` *)
end

有时monkey补丁或许是编译代码的最简单方法,但总的来说,会造成代码库的混乱和/或技术债务。若你需要对一个模块运行monkey补丁,前提下你应有充分的理由。

仿函数签名等式

仿函数所产生的模块签名的With语句签名应尽可能地限定在S with module M1 = M2表单中。代替等式:=应限于签名比例需限定的情况下(比如,签名的一个嵌套模块在现有的结构范围内已有定义的情况)的include语句中。此外,也不会优先考虑S with type t = ...表单,因为随着一个仿函数所涉及的签名之间的一般依赖数目提升,该表单表现不佳,要注意这点更加强调janestreet指南风格中“优先选择标准签名而非手写接口”的规定。

仿函数元数

仿函数最多可有3个元数(元数是实际参数的数量;在这种情况下,指的是嵌套仿函数的数量,嵌套仿函数指的是返回值为仿函数的仿函数)。若一个仿函数需要3个以上模块作为实际参数,则所需模块应嵌套进一个模块中。其标准模式是为你的仿函数定义一个Inputs_intf签名,这反过来会定义仿函数的模块实际参数。如下所示:

module type Inputs_intf = sig
 module A : A.S
 module B : B.S
 module C : C.S
 module D : D.S
end

module type S = sig
 include Inputs_intf
 (* ... *)
end

module Make (Inputs : Inputs_intf)
 : S
  with module A = Inputs.A
   and module B = Inputs.B
   and module C = Inputs.C
   and module D = Inputs.D =
struct
 open Inputs

 (* ... *)
end

代码特性

我们采用了OCaml的独特风格。以下是一些要点。

参数化记录

type ('payload, 'pk, 'signature) t_ =
 {payload: 'payload; sender: 'pk; signature: 'signature}
[@@deriving eq, sexp, hash]

type t = (Payload.t, Public_key.t, Signature.t) t_
[@@deriving eq, sexp, hash]

(* ... *)

type var = (Payload.var, Public_key.var, Signature.var) t_

我们用所有类型的记录字段的类型定义基本类型变量t_。然后用这些类型变量定义记录。最终,我们用type t将记录实例化,这就是OCaml类型。此外,在一个SNARK(简短无交互证明)电路中type var是这个值的类型。我们稍后会就这点展开介绍。无论我们想在SNARK(简短无交互证明)电路中程序化什么内容,我们通过这种方式来定义,以便跨越两种类型重复利用记录定义。

还有一些说法是转向OCaml对象类型因此不必去处理位置实参。或许我(@bkase)将不定时详写一份与之相关的RFC。

Ppx_deriving

type t = int [@@deriving sexp, eq]

这是我们首次看到宏指令。此处我们用了ppx_janesexpppx_derivingeq

Stable.V1

module Stable : sig
 module V1 : sig
  type t = (* ... *)
  [@@deriving bin_io, (*...*)]
 end
end

对我们而言,只要是可序列化的类型,一旦发布了稳定版,保持向后兼容非常重要。理想情况下,我们不会以bin_io定义除Stable.V1外的任何类型定义。若我们改变数据类型结构,我们会在Stable之下创建一个V2

基于属性的测试

Core运行着我们尽可能在单元测试的时候使用的QuickCheck。举一个付款的Quickcheck.Generator.t的签名示例。

(* Generate a single payment between
 \* $a, b \in keys$
 \* for fee $\in [0,max_fee]$
 \* and an amount $\in [1,max_amount]$
 *)

val gen :
   keys:Signature_keypair.t array
 -> max_amount:int
 -> max_fee:int
 -> t Quickcheck.Generator.t

类型安全不变式(帮助命名这一字段)

关于Mina,很多时候我们需要对特定数据组进行重要检测。例如。我们需要确认在网络上收到的用户命令的签名是有效的。这样的检测可能非常昂贵,因此我们只想检测一次。但是我们要记得已经做过的检测。

(* inside user_command.mli *)

module With_valid_signature : sig
 type nonrec t = private t [@@deriving sexp, eq]

 (*...*)
end

val check : t -> With_valid_signature.t option

现在我们定义With_valid_signature(常用的是User_command.With_valid_signature.t),利用type nonrec t = private t允许其向上转型至User_command.t,但是避免其向下转型。将User_command.t转化为User_command.With_valid_signature.t的唯一方法是对它进行check。于是编译器会捕捉我们的错误。

单元测试

我们使用 ppx_inline_test来进行单元测试。当然只要条件允许,我们会将其与QuickCheck相结合。

let%test_unit =
 Quickcheck.test ~sexp:[%sexp_of: Int.t] Int.quickcheck_generator
  ~f:(fun x -> assert (Int.equal (f_inv (f x)) x))

仿函数

我们正在转化成使用模块签名等式——见the above sectionthe rfc for rationale,但是我们仍然有大量使用类型替换(with type foo := bar)的代码。

首先我们定义仿函数的结果模块类型,将所有我们要处理的仿函数抽象化。

module type S = sig
 type boolean_var
 type curve
 type curve_var
 (*...*)
end

然后我们定义这个仿函数:

module Schnorr
 (Impl : Snark_intf.S)
 (Curve : sig (*...*) end)
 (Message : Message_intf
  with type boolean_var := Impl.Boolean.var
  (*...*))
: S with type boolean_var := Impl.Boolean.var
   and type curve := Curve.t
   and type curve_var := Curve.var
   (*...*)
= struct
 (* here we implement the signature described in S *)
end

自定义SNARK(简短无交互证明)电路逻辑

这也是我们首次提到自定义SNARK(简短交互证明)电路逻辑。我们所使用的模式适用于你想在子模块module Checked下的SNARK中运行的所有操作。

例如,在sgn.mli 中,我们可以看到:

(* ... *)
val negate : t -> t

module Checked : sig
 val negate : var -> var
end

negate 是OCmal中执行的函数版本,Checked.negate是在一个SNARK(简短交互证明)电路中执行的版本。

5.7 沙盒节点

Mina 沙盒节点将使您能够测试并熟悉协议的核心特性,并在稳定的环境中构建工具。它是一个使用和现有测试网相同配置的单节点私有网络。这个沙盒支持多个帐户,在它们之间发送交易,还将支持执行SNARK工作、委托和质押。事实上,由于这是一个单节点网络,你将获得所有的区块奖励!

注意

沙盒不能将您连接到一个运行中网络。

安装

Docker是一个可移植运行应用程序的工具。Docker打包了Mina 沙盒节点。它很容易安装——我们建议使用Docker桌面

安装Docker后,运行以下命令启动Mina沙盒。

docker run \
 --publish 3085:3085 \
 -d \
 --name mina \
 minaprotocol/mina-demo:sandbox-32a.1

这个命令将在docker容器内启动一个后台程序,并向您的计算机公开GraphQL端口(3085),该端口用于与客户端通信。这个后台程序将自动在后台运行,并带有块生成器和snark worker。

您可以通过执行操作查看日志。

docker logs --follow mina

运行日志来停止mina。

docker stop mina

您可以使用Mina CLI与沙盒节点交互。下面的命令在docker容器中打开一个shell,在那里你可以发出任何可用的mina命令

docker exec -it mina bash

帐户详细信息

容器有一个具有此公钥的帐户:

B62qrPN5Y5yq8kGE3FbVKbGTdTAJNdtNtB5sNVpxyRwWGcDEhpMzc8g

该帐户的密码是空字符串(没有密码——可以将密码字段留空)。

如何使用沙盒

现在你可以用你的沙盒做以下几件事:

● 像往常一样安装Mina并使用许多客户端命令。因为这个后台程序已经在容器中运行了,所以你不需要运行mina后台程序!

● 安装GUI钱包应用程序以使用图形界面到您的节点。在安装过程中输入‘127.0.0.1’作为节点的主机。

● 请访问http://localhost:3085/graphql直接使用GraphQL API。

5.8 GraphQL API

警告

● Mina API仍在构建中,因此这些端点可能会发生变化。

● 默认情况下,GraphQL端口会绑定到本地主机。向互联网公开GraphQL API这一操作使得任何人都可以从已知帐户将Mina发送给守护进程。

Mina守护进程会公开一个GraphQL API,这是用于向运行中的节点请求信息并向其提交命令的。默认情况下,HTTP服务器使用3085端口,可以通过守护进程启动命令中的-rest-server参数来进行配置。

要使用GraphQL API,请将GraphQL客户机连接到http://localhost:3085/graphql或在浏览器中打开以使用GraphQL IDE。默认情况下,为了安全起见,本项操作只允许来自本地主机的连接。如果要监听所有接口,守护进程的启动命令中添加-insecure-rest-server参数。

除了关于正在运行的节点信息之外,GraphQL API还可以将网络最新块的数据返回。然而,由于区块链的历史状态没有在Mina中持久化,只有在节点的转换边界中的区块才能返回,也就是最后的k块。对于其他历史数据来说,应该使用归档节点,而该节点的设计目的是保留和检索历史数据。

这里提供了完整的Mina GraphQL模式。

查询:

Mina GraphQL API有许多从运行节点中提取数据的查询GraphQL查询允许指定响应的数据。例如,获取守护进程已知的最新块和创建者信息:

query {
 bestChain(maxLength: 1) {
  creator
  stateHash
  protocolState {
   consensusState {
    blockHeight
   }
   previousStateHash
  }
  transactions {
   coinbase
  }
 }
}

下面的请求是在查询交易池中所有待处理的交易及其手续费。此查询可用于估算交易的建议费用:

query {
 pooledUserCommands {
  id,
  fee
 }
}

注意

交易返回的memo字段是经过Base58Check编码的。

为了获取主mina代币帐户的详细信息:

query {
 account(publicKey: "<B62...>") {
  balance {
   total
  }
  delegate
  nonce
 }
}

您可以从节点的命令行提交GraphQL请求。例如,使用cURL获取节点已知的最后10个区块创建者:

curl -d '{"query": "{
 bestChain(maxLength: 10) {
  creator
 }
}"}' -H 'Content-Type: application/json' http://localhost:3085/graphql

突变:

GraphQL突变以某种方式修改正在运行的节点。例如,突变可以用于发送支付、创建新帐户或添加额外的对等节点。

注意

查询GraphQL模式以获取所有可用的突变。

添加一个新的对等节点:

mutation {
  addPeers(peers:{
   libp2p_port:10511,
   host:"34.73.68.198",
   peer_id:"12D3KooWSJB2gZWi3ruVmtTF9JBCEBpCrJfuWCWzzRr8mMQWFQ9U"
  })
}

更新一个snark工作者要支付0.1 mina的费用:

mutation {
 setSnarkWorkFee(input: {fee: "100000000"})
}

订阅:

GraphQL订阅允许在事件发生时向GraphQL客户机推送数据。在Mina中,有以下若干订阅情况:

● newSyncUpdate——当节点的同步状态改变时发生。

● newBlock -当接收到一个新的区块时发生。

● chainReorganisation——当节点的最佳TIP以一种不正常的方式改变时发生。

例如,订阅所有产生的新区块:

subscription {
 newBlock {
  creator
  stateHash
  protocolState {
   consensusState {
    blockHeight
   }
   previousStateHash
  }
 }
}

新的区块订阅也可能被限制为只返回由已定义的带publicKey参数的公钥创建的新区块。

GraphQL公共代理:

虽然不建议向网络公开GraphQL API,但可以使用一个GraphQL代理实现来删除那些构成威胁的查询,但仍然允许您从公共端点查询节点的数据。

资源:

关于Mina GraphQL API的5分钟介绍视频

使用Mina GraphQL API的第一步

GraphQL概论

5.9 客户端SDK

Mina Client Javascript SDK允许生成密钥对、签名和验证消息,以及可以将签名交易在网络上进行广播。

该项目包含Typescript和ReasonML的类型,但也可以从普通的NodeJS中使用。有以下几种方法:

● genKeys -生成公钥/私钥对。

● derivePublicKey -导出对应私钥的公钥。

● signMessage -对任意消息进行签名。

● verifyMessage -验证签名是否与消息匹配。

● signPayment -使用私钥对支付进行签名。

● signstakes delegation -使用私钥对权益委托进行签名。

注意

客户端SDK允许在从不向网络公开私钥的设备上生成和进行交易签名。

安装

yarn add @o1labs/client-sdk
\# or with npm:
npm install --save @o1labs/client-sdk

使用

Typescript

import * as MinaSDK from "@o1labs/client-sdk";

let keys = MinaSDK.genKeys();
let signed = MinaSDK.signMessage("hello", keys);
if (MinaSDK.verifyMessage(signed)) {
  console.log("Message was verified successfully")
};

let signedPayment = MinaSDK.signPayment({
  to: keys.publicKey,
  from: keys.publicKey,
  amount: 1,
  fee: 1,
  nonce: 0
 }, keys);

NodeJS

const MinaSDK = require("@o1labs/client-sdk");

let keys = MinaSDK.genKeys();
let signed = MinaSDK.signMessage("hello", keys);
if (MinaSDK.verifyMessage(signed)) {
  console.log("Message was verified successfully")
};

let signedPayment = MinaSDK.signPayment({
  to: keys.publicKey,
  from: keys.publicKey,
  amount: 1,
  fee: 1,
  nonce: 0
 }, keys);

ReasonML

● 安装 gentype: yarn add -D gentype

● 安装 bs-platform: yarn add -D bs-platform

● 编译依赖: yarn bsb -make-world

module MinaSDK = O1labsClientSdk.CodaSDK;

let keys = MinaSDK.genKeys();
let signed = MinaSDK.signMessage(. "hello", keys);
if (MinaSDK.verifyMessage(. signed)) {
 Js.log("Message was verified successfully");
};

let signedPayment = MinaSDK.signPayment({
  to_: keys.publicKey,
  from: keys.publicKey,
  amount: "1",
  fee: "1",
  nonce: "0"
 }, keys);

注意

当产生交易时,您需要指定帐户。您可以通过CLI使用mina advanced get-nonce -address </public_key>或使用GraphQL来获得这个账户。

Links need to be raplaced

https://docs.minaprotocol.com/en/developers/graphql-api

5.10 日志记录

Mina日志位于配置目录~/. Mina -config中。该目录包含以下日志文件:

● mina.log -该文件包含守护进程的最新日志。每个日志文件的大小限制为10兆字节(MiB),并可以在50个日志文件之间轮换。轮转后的日志文件命名为mina.log.x。0到mina.log.50。

● mina-best-tip.log -这个文件包含守护进程的最佳tip日志。这个文件使得从节点更容易地去收集所需的日志,从而确定任何硬分叉的最佳状态。该文件的大小被限制为5兆字节(MiB),并可在单个日志文件mina-best-tip.log.0上进行旋转。

● mina-prover.log -这个文件包含关于验证程序内存使用情况和批量处理大小的日志。它被限制为128兆字节(MiB),并在单个日志文件上进行轮转。

● mina-verifier.log -这个文件包含验证器的内存使用和批量处理大小的日志。它被限制为128兆字节(MiB),并在单个日志文件上进行旋转。

要跟踪日志,请使用以下命令之一,这取决于您如何启动守护进程:

● Docker: Docker log --follow mina

● 作为一个服务运行:journalctl --user -u mina -n 1000 -f

● Other: tail -f ~/.mina-config/mina.log

日志级别:

Mina守护进程按照以下优先级顺序,对以下级别的日志进行了记录:

● Spam,垃圾邮件

● Trace,跟踪

● Debug,调试

● Info,信息

● Warn,警告

● Error,错误

● Fatal,致命

注意

指定一个Info级别的log-level 将只显示来自该日志级别及以上的消息,以及显著的减少日志的详细信息。

日志格式:

日志的格式定义如下:

{
 "timestamp": timestamp,
 "level": level,
 "source": {
  "module": string,
  "location": string
 },
 "message": format_string,
 "metadata": {{...}}
}

错误信息的示例如下:

{
 "timestamp": "2020-12-23 21:25:04.616526Z",
 "level": "Error",
 "source": {
  "module": "Transition_router",
  "location": "File \"src/lib/transition_router/transition_router.ml\", line 231, characters 12-24"
 },
 "message": "Failed to find any peers during initialization (crashing because this is not a seed node)",
 "metadata": {
  "host": "48.241.118.000",
  "peer_id": "12D3KooWGQSPgo7ypy9717D3Vgz2RHaqB2ndDRHEFcAfJDAv284q",
  "pid": 12,

  "port": 8302
 }
}

导出日志:

Mina的守护进程提供了一个CLI助手,用于压缩和打包来自节点的可用日志文件。

对于正在运行的守护进程,可以运行mina client export-logs,其中可以传递一个可选的标志-tarfile来指定tar存档文件的文件名,该文件名默认为当前日期-时间。该命令压缩可用的日志,并将它们导出到~/ mina-config目录中的exported_logs目录之下。

注意

您可以通过GraphQL从运行中的节点导出日志,从而使用exportLogs变种。

如果需要从未运行的节点导出日志,可以使用mina client export-local-logs命令压缩可用的本地日志文件。通过tarfile选项指定tar归档文件的文件名,日志将被压缩并导出到~/中的exported_logs目录。mina-config目录。

注意

在GitHub上创建或更新问题时,可以将导出的tar.gz文件直接上传到问题中。

日志记录选项:

在启动守护进程时可以设置以下选项来定制日志记录行为:

● -file-log-level日志文件的日志级别(默认为Trace)

● -log-level -通过标准输出使之输出到终端的日志级别(默认为Info)

● -log- JSON -以JSON的形式打印日志输出(默认为纯文本)

● -log-block-creation -记录在一个区块中包括交易和snark工作的步骤(默认:true)

● -log-received-blocks -从对等节点接收到的日志区块数(默认为false)

● -log-snark-work-gossip -log从对等节点收到的snark-pool差异(默认为false)

● -log-txn-pool-gossip -从对等节点收到的交易池差异日志(默认:false)

● -log-precompute -blocks -在日志中包含预计算的区块(默认为false)

崩溃报告:

如果守护进程崩溃但能够使用,那么它将生成一个崩溃报告。只有最新的崩溃报告将被保留,文件名表示崩溃的日期时间,并且压缩文件将被导出到~/.mina-config 目录之中。您可以上传这个崩溃报告到任何有关GitHub问题中,确保有新的内容和可供调试的援助。

崩溃报告将包含以下部分或全部日志文件:

● crash_summary.json -包含崩溃瘫痪的摘要,包括系统信息、版本信息和最终的日志输出。

● mina_status.json -一个mina client status命令的输出,它是守护进程最终状态的摘要。

● mina_short.log -包含mina.log文件的最后4MiB。

● registered_mask.dot-包含最新的账本分类账可视化。

● frontier.dot -包含最新的前端前沿可视化。

● daemon.json一个守护进程配置文件的副本。

5.11 向Mina贡献

Mina是一个开源项目,其使命是建立一个包容和可持续的社区驱动协议。因此,Mina欢迎任何希望进一步实现这一目标的人提供贡献。

目前,该协议正在开发中,将从编写代码、用户测试、文档和社区支持等形式的支持中受益。下面,您将看到在这些领域中作出支持的具体说明。

如果有任何关于如何参与的一般性问题,请随时联系Discord server上的Mina社区。

加入公共测试网

Mina公开测试网beta已经上线。技术技能不是必需的,我们鼓励任何对加密货币和区块链感兴趣的人参与进来。

具体来说,我们需要在报告故障、验证经济假设、测试节点基础设施、普遍参与共识和压缩区块链等方面获得帮助。请加入Discord server,并检查#testnet-general和#mentor-nodes通道而后加入进来。您也可以在这里注册初始计划,我们会随时更新您的消息!

前往测试网络登陆页面,看看如何参与每周挑战,并获得测试网络排行榜。

开发人员

Mina是完全开源的,代码是按照Apache License 2.0的条款进行发布的。

Mina资助

O(1)实验室为某些与Mina开发相关的项目提供资助——请访问Mina资助页面了解更多细节。

目前,这些项目主要集中在编程方面,但更多的项目将加入设计和社区开发的领域。如果您对资助计划有任何疑问,请联系Discord。

报告问题

如果您注意到任何违反行为准则的行为,请按照该文件中的报告指导方针提交报告,并将任何不良行为反应到社区。

如果您在协议中遇到任何严重的错误或漏洞,请向security@minaprotocol.com报告。对于小故障和问题,请在Github上进行创建。

5.12 代码特性

我们使用一种特定的OCaml样式。这里有一些重要的事情。

参数化的记录

type ('payload, 'pk, 'signature) t_ =
 {payload: 'payload; sender: 'pk; signature: 'signature}
[@@deriving eq, sexp, hash]
type t = (Payload.t, Public_key.t, Signature.t) t_
[@@deriving eq, sexp, hash]
(* ... *)

type var = (Payload.var, Public_key.var, Signature.var) t_

我们用所有类型的记录字段的类型变量定义基类型t_。然后,我们使用这些类型变量来定义记录。最后,我们用类型t实例化记录,这是OCaml类型。也可以输入var这是在SNARK电路中值的类型。稍后我们将详细介绍这一点。每当我们想要在一个SNARK电路中进行编程时,我们就以这种方式定义它,这样我们就可以跨两种类型重用记录定义。

有人说要使用OCaml对象类型来做这类事情,这样我们就不需要处理位置参数了。也许我(@bkase)会在某个时候为此写一个RFC。

Ppx_deriving

type t = int [@@deriving sexp, eq]

这是我们第一次看到宏观层面。这里我们使用ppx_jane中的sexp和ppx_derivative中的eq。

Stable.V1

module Stable : sig
 module V1 : sig
  type t = (* ... *)
  [@@deriving bin_io, (*...*)]
 endend

只要类型是可序列化的,一旦我们有了稳定的发行版,就必须保持向后兼容性。理想情况下,我们不会在Stable.V1之外的任何类型上定义bin_io。当我们改变数据类型的结构时,我们将在Stable下创建一个V2。

资产基础测试

Core有一个QuickCheck的实现,我们可以在单元测试中随时使用它。下面是支付的Quickcheck.Generator.t的签名示例。

(* Generate a single payment between
 \* $a, b \in keys$
 \* for fee $\in [0,max_fee]$
 \* and an amount $\in [1,max_amount]$
 *)

val gen :
   keys:Signature_keypair.t array
 -> max_amount:int
 -> max_fee:int
 -> t Quickcheck.Generator.t
类型安全变量(帮助命名本节)

通常在Mina中,我们需要对特定的数据片段执行非常重要的检查。例如,我们需要确认签名对我们通过网络接收到的用户命令是有效的。这样的检查可能很昂贵,所以我们只想做一次,但我们要记住我们已经做过了。

(* inside user_command.mli *)
module With_valid_signature : sig
 type nonrec t = private t [@@deriving sexp, eq]

 (*...*)end
val check : t -> With_valid_signature.t option

这里我们定义了With_valid_signature(用法是User_command.With_valid_signature.t),使用类型nonrec t = private t来允许向上转换到User_command.t,但防止向下投射。将一个User_command转换成一个User_command.With_valid_signature.t的唯一办法就是是检查它。现在编译器会发现我们的错误。

单元测试

我们使用ppx_inline_test进行单元测试。当然,只要有可能,我们就会把它和QuickCheck结合起来。

let%test_unit =
 Quickcheck.test ~sexp:[%sexp_of: Int.t] Int.quickcheck_generator
  ~f:(fun x -> assert (Int.equal (f_inv (f x)) x))
函子

我们正处于迁移去使用模块签名等式的过程中——基本原理见上一节和rfc,但是我们仍然有很多使用类型替换(foo:= bar类型)的代码。

首先,我们定义functor的结果模块类型,然后我们要保持functor的所有类型都是抽象的。

module type S = sig
 type boolean_var
 type curve
 type curve_var
 (*...*)end

然后定义函子:

module Schnorr
 (Impl : Snark_intf.S)
 (Curve : sig (*...*) end)
 (Message : Message_intf
  with type boolean_var := Impl.Boolean.var
  (*...*))

: S with type boolean_var := Impl.Boolean.var
   and type curve := Curve.t
   and type curve_var := Curve.var

   (*...*)
= struct
 (* here we implement the signature described in S *)end
自定义SNARK电路逻辑

这也是我们第一次看到自定义SNARK电路逻辑。我们一直使用的一个模式是将您想要在检查的子模块下的SNARK中运行的所有操作限定为范围。

例如,在sgn.mli 内部我们看到:

(* ... *)val negate : t -> t
module Checked : sig
 val negate : var -> varend

negate是在OCaml中运行并被检查的函数的版本。Checked.negate是在snark电路中运行的。

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
MinaFans
MinaFans
minafans.tech