构建数字生态系统的公共区块链平台

关于该文档

GAChain 是一个构建数字生态系统的公共区块链平台。

该文档为政务链GAChain系统平台的技术开发文档,包含平台描述、节点部署、开发示例、合约开发语言 GALang 、模版开发语言 GAStyle 的使用方法等。

关于该平台

GAChain 区块链是一个安全,简单和可扩展的区块链基础设施,适用于快速增长的协作经济部门。中小型企业将在降低运营成本、加快上市时间、早期发展阶段的筹资方案、业务流程自动化和可扩展性、综合结算系统、合法的反洗钱基础设施、不信任的经济合作、其产品和服务的影响力中受益。GAChain 平台的详细描述位于官网(gachain.org)的白皮书。

GAChain 平台的使用,包括许可条款,受 GAChain 法律文件中规定的条款和条件的约束,该条款和条件可从官网(gachain.org)下载。

政务链平台的基础研发于2011年开展,2016年正式启动政务链(GAChain)的研发,经过政务流程团队、电子政务专家顾问团队和技术团队的努力,至2016第三季度,完成了如下工作:

  1. 创建了主要网络协议和轻量级软件客户端;
  2. 开发了智能合约的概念;
  3. 发布了早期版本的接口界面和合约编程语言。

由于该平台最初是为了实施电子政务领域的项目而开发的,因此团队决定将项目命名为 政务链 (电子政务即服务)。2017年秋季,创建了政务链第一版本(v1)的测试网络。至2017年年底,为广东省南沙自贸区电子服务中心、中央扶贫办、天津市市场监督管理局、香港机场服务管理公司等政企机构开发出相关概念验证项目和解决方案,如多部门电子政务自动协同办公和流转、社会化代收税电子发票系统、精准扶贫平台、供应链管理等。

也是在2017年冬季,政务链团队决定在现有平台的基础上建立一个公共区块链网络,与相关公共安全部门合作,将KYC(个人身份识别)程序整合到用户账户的注册阶段。

由于该项目将不再只适应于政务服务项目,还可广泛用于商业、个人等项目的开发、应用,因此团队将项目的品牌标识做了一定修改:

  1. 面对政府、事业单位的项目,依然使用“政务链”,英文名为 GAChain ( Government Affairs Chain);
  2. 面对商业机构、个人客户,中文使用“智乾链”,英文名依然为 GAChain

GAChain 平台的软件客户端名为Govis,其编程语言:智语言(GALang Language)为合约语言和乾语言(GAStyle Language)为页面编辑语言。同时,启动了源代码重构过程:软件客户端的代码被完全重写(基于JavaScript React库),创建RESTful API v2的新版接口,智语言和乾语言进行显著优化改善。另外,开发虚拟专用生态系统的概念和功能以及视觉页面设计器,并且实现了一些其它相关性改变和改进。

到2018年初,政务链(GAChain)平台已经做好与当前火热的区块链平台Ethereum、NEO相竞争的准备,团队相信,政务链(GAChain)具有颠覆性的产品规划设计、优秀性能等优势,必将对现有区块链产品带来从概念到实际应用的冲击。

区块链颠覆了现有常规技术和观念,政务链颠覆了现有常规区块链!

本文档包含平台功能的最新描述,并且在更改或添加新功能时不断更新。

目录

GAChain 概述

特性

GAChain 区块链平台目的是构建数字生态系统,该平台包括一个集成的应用程序开发环境,可以对数据、用户页面和智能合约进行多级访问权限控制系统。

就其结构和功能而言,GAChain 平台与大多数现有的区块链平台有较大不同:

  • GAChain 平台应用程序的开发和使用在名为 生态系统 的自治软件环境中,每个生态系统都有自己的成员规则,这些规则最初是由生态系统创建者建立的;
  • 生态系统的活动基于使用 智能合约(Smart Contracts) 去创建 寄存器(Registers),例如数据库表(Database Table) 和记录或更新所涉及的数据,而在大多数其他区块链平台中,活动基于在账户之间交易交换;
  • 寄存器 的访问权限和生态系统成员间的关系控制由一套称为 智能法律(Smart Laws) 的规则来管理。

架构

网络

GAChain 平台基于点对点网络构建。

网络中的全节点存储着最新版本的区块链数据库,其中记录了 GAChain 平台的区块链的最新状态

网络用户可通过使用软件客户端(Govis)或接口(REST API命令)从全节点数据库发送请求来接收数据,新请求被用户签名后以交易的二进制格式发送到网络。这些交易本质上是修改数据库中记录的命令,交易以区块的形式聚合在一起,然后将区块发送到网络节点的区块链中,每个全节点将处理该区块中的交易,从而更新其数据库中的相应数据。

验证节点

网络中有权生成新区块的全节点称为 验证节点 。在平台参数表中定义了验证节点数量,这表明验证节点的数量是有限的。

交易

交易由软件客户端(Govis)生成,其中包括用于执行 智能合约 的数据。

交易由用户持有人的私钥签名,密钥和Govis的签名功能可以存储在浏览器,软件客户端,SIM卡或专用物理设备上。

每笔交易有以下格式结构:

  • ID - 执行合约的ID;
  • Params - 发送给合约的参数;
  • KeyID - 发送交易的用户ID;
  • PublicKey - 验证节点的节点公钥;
  • Time - 交易产生时间戳;
  • EcosystemID - 交易产生的所在生态系统ID;
  • ТokenEcosystem - 生态系统ID,默认为 1,其生态内的通证用于支付交易费用。

网络协议

用户将交易发送到一个验证节点,该交易在该节点进行基本验证以确保交易格式的正确性,然后将交易添加到交易队列中。该交易还会发送到网络上的其他验证节点,并将交易添加到交易队列中。

验证节点在特定时间段内有权产生新区块,时间段根据 full_nodes 平台参数和特殊算法决定,验证节点从交易队列中检索交易并将交易发送到区块生成器。在产生新区块的期间,也会对该新区块中的交易进行处理:将每个交易发送到虚拟机,虚拟机执行交易参数中对应的合约,从而更新数据库中的记录。

验证新区块是否出现错误,如果验证通过,则将该区块发送到其他网络上的其他验证节点。

其他验证节点将接收到的新区块添加到区块队列,在验证该区块通过后,该区块会被添加到区块所在的验证节点区块链中,并处理该区块中的交易,从而更新数据库中的记录。

区块和交易验证

验证节点在产生新区块块之后执行新区块验证,以及在收到此区块后在所有其他验证节点上验证此区块,包括以下验证:

  • 接收数据的一个字节应该是为 0 ,如果不是,则接收的数据不被视为区块;

  • 接收的区块的生成时间戳应该在当前时间戳之前;

  • 区块的生成时间戳应该与验证节点有权产生新区块的时间间隔相对应;

  • 新区块高度应该大于现有区块链上最大区块高度;

  • 不能超过该区块允许交易的最大费用限额;

  • 该区块必须被其节点的节点密钥正确签名,签名数据为:

    • 该区块高度、前一个区块的哈希值、该区块时间戳、该区块所在的生态系统ID、该区块的验证节点的账户地址;
    • 验证节点在平台参数full_nodes数组的位置、该区块中所有交易的默克尔树(MrklRoot)、前一个区块的回滚哈希值。

通过以下方式检查区块中的每笔事务的正确性:

  • 每笔交易的哈希值必须是唯一的;
  • 一个密钥签名的交易不能超过限制(max_tx_block_per_user);
  • 不能超过最大交易大小限制(max_tx_size);
  • 交易时间不能大于区块生成时间,交易时间不能大于区块生成时间加上600秒,不能小于区块生成时间减去86400秒;
  • 交易必须被正确签名;
  • 执行合约的用户必须在其帐户中具有足够数量的通证来支付执行交易所需的费用。

数据库

GAChain 平台使用 PostgreSQL 作为数据库管理系统。

GAChain 平台的数据库在网络的每个全节点上存储和维护着最新的副本,用于存储大量的数据以及通过合约和接口快速检索数据。在产生新区块并将区块添加到区块链中后,平台上的所有全节点都同时更新数据库表。因此,数据库存储着区块链当前最新的状态,这确保了所有全节点上数据的等同性和所有验证节点上合约执行的正确性。当一个新的全节点被添加到网络时,随后将执行区块链内所有交易,可以将其数据库更新到最新状态。

生态系统

GAChain 平台的数据空间被划分为多个相对独立的集群,称为 生态系统,其实现了网络上用户的活动。生态系统是一个自治软件环境,由一定数量的应用程序和用户组成,用户创建这些应用程序并使用它们。任何用户都可以创建一个新的生态系统。

生态系统的软件基础是 应用程序 的集合。

集成开发环境

Govis软件客户端包括用于创建区块链应用程序的全套集成开发环境(IDE)。使用此IDE不需要软件开发人员对区块链技术有深刻的了解。

Govis软件客户端提供了数据库表管理工具,合约编辑器,页面编辑器以及在生态系统中创建应用程序所需的其他功能,而无需借助任何其他软件模块。

IDE主要包括以下部分:

  • 生态系统参数表;
  • 合约编辑器;
  • 数据库表管理工具;
  • 页面编辑器和可视化页面设计器;
  • 多语言资源编辑器;
  • 应用程序导入/导出功能。

应用程序

应用程序是具有配置访问权限的数据库表、智能合约和用户页面等元素的集合。应用程序元素所属的生态系统由元素名称中的前缀表示,例如 @1ElementName,其中生态系统ID在 @ 符号后的数字 1 表示。在当前生态系统中使用应用程序元素时,可以省略前缀 @1。这些应用程序可执行有用的功能或实现各种服务。

数据表

在 GAChain 平台数据库,每个生态系统可以创建无限数量的数据表。特定生态系统的数据表可以通过包含生态系统ID的前缀来标识,该生态系统ID在该特定生态系统中工作时不会显示在Govis软件客户端中。

数据表不以任何方式绑定和属于某个合约,在数据表访问权限范围内,其可以被所有应用程序使用。

每个生态系统都可以为其应用程序的开发创建一组数据表。这并不排除通过指定表名前缀来访问其他生态系统表的可能性。

通过智能法律配置访问权限来将数据记录至数据表。智能法律用于权限管理。

数据表管理工具

用于管理数据表的工具可从Govis软件客户端的 数据表(Tables) 菜单中找到。具有以下功能:

  • 查看数据表列表及其条目内容;
  • 创建新数据表;
  • 添加表字段并指定字段数据类型:TextDate/TimeVarcharCharacterJSONNumberMoneyDoubleBinary
  • 管理插入数据、更新数据和更改表结构的权限。
表数据操作

为了更好数据库操作,智语言(GALang Language)合约语言和乾语言(GAStyle Language)模板语言都具有 DBFind 函数,该函数用于从数据表中检索值和数据数组。

合约语言 DBInsert 函数用于向数据表添加条目。DBUpdateDBUpdateExt 函数用于更新现有条目的值,当更新值时,数据表对应数据会更新,而区块链会添加新的交易,同时保留着所有历史交易。数据表的数据只可以被修改,不能被删除。

为了最小化执行合约的时间,DBFind 函数不能同时查询多个数据表,因此不支持 JOIN 请求,需要很多的冗余数据和字段。这就是为什么不建议规范化数据表,而是将所有可用的信息存储在条目中,或者重复其他数据表可用的信息。这不仅是一种强制性措施,还是区块链应用程序的必要条件。就这种情况而言,存储的数据应该是完整的数据,即使其他表的相同数据更新了,该数据也是无法更新的,这在关系型数据库中该数据是同步更新的)。

生态系统参数

生态系统参数表( 1_parameters )可从Govis软件客户端生态系统参数菜单查看和投票编辑。生态系统参数可分为以下几组:

  • 一般参数:生态系统创建者的帐户(founder_account)以及其他信息;

  • 访问权限参数:定义应用程序元素的访问权限

    • 更新数据表结构( changing_tables );
    • 更新合约( changing_contracts );
    • 更新用户页面( changing_page );
    • 更新菜单( changing_menu );
    • 更新多语言资源 ( changing_language )。
  • 技术参数:定义用户样式( stylesheet )等;

  • 用户参数:定义应用程序工作所需的常量或列表(以逗号分隔)。

可以为每个生态系统的参数指定编辑权限。

要检索生态系统参数的值,可使用智语言或乾语言的 EcosysParam 函数,将生态系统参数名称作为参数传递给该函数。要从列表中检索元素,将生态系统参数名称作为第一个参数传递给该函数,将所需元素的计数做为该函数的第二个参数。

平台参数

平台的所有参数都存储在平台参数表( 1_system_parameters ),详情查看 平台参数

更改平台参数只能使用 UpdateSysParam 合约,该合约的管理在平台的法律系统中定义。法律系统的合约(智能法律)是在网络启动之前创建的,并实施白皮书“平台法律系统”部分规定的权利和标准。

访问权限控制机制

GAChain 拥有多级访问权限管理系统。可以配置访问权限来创建和更改应用程序的任何元素:合约,数据表,用户页面,生态系统参数。也可以配置更改访问权限的权限。

默认情况下, GAChain 平台生态系统中的所有权限都由其创始人管理(这在 MainCondition 合约中定义,默认情况下每个生态系统都有)。但是,在创建智能法律之后,访问权限控制可以转移到所有生态系统成员或一组此类成员。

访问权限控制操作

访问权限在合约表( 1_contracts )、数据表( 1_tables )、用户页面表( 1_pages )、菜单表( 1_menu )、模块表( 1_blocks )的权限字段中定义,可以Govis客户端找到对应的菜单部分。

访问权限管理方法

访问权限的规则在权限字段填入对应合约表达式 ContractConditions("@1MainCondition")ContractAccess("@1MainCondition") 或者逻辑表达式,如果请求表示式的结果通过( true ),则授予访问权限。否则拒绝访问权限并终止相关操作。

定义权限的简单方法是在权限字段输入逻辑表达式。例如 $key_id == 8919730491904441614,其中 $keyid 表示生态系统成员的ID。

定义权限的最通用和推荐的方法是使用 ContractConditions("@1ContractsName1","@1ContractsName2") 函数,合约名称 ContractsName 作为参数传递给该函数,合约结果必须是逻辑表达式的结果(true或者false)。

定义权限的另一种方法是使用 ContractAccess("@1ContractsName3","@1ContractsName4") 函数。有资格实现相应操作的合约 ContractsName 可以作为参数传递给该函数。例如,如果 amount 列的权限字段配置为 ContractAccess("@1TokenTransfer"),那么想要更改 amount 列中的值,只能执行 @1TokenTransfer 合约来更改。访问合约本身的权限可以在条件部分( conditions)进行管理。它们可能相当复杂,可能包含许多其他合约。

特殊权限

考虑到解决突发情况或对生态系统运行至关重要的情况,生态系统参数表( 1_parameters )有许多特殊参数( changing_contractschanging_pages )等,参数定义了访问当前生态系统的所有合约,数据表和页面的权限,这些权限使用起关键作用的合约配置的。

虚拟专用生态系统

GAChain 平台可以创建虚拟专用生态系统 Virtual Dedicated Ecosystems(VDE),也叫做链下服务 Off-Blockchain Servers (OBS) ,它具有标准生态系统的全套功能,但在区块链之外工作。在VDE中,可以使用和创建合约和模板语言、数据库表,可以使用Govis软件客户端创建应用程序。可以使用接口方式调用区块链生态系统上的合约。

请求web资源

VDE和标准生态系统之间的主要区别在于可以使用(HTTPRequest)和(HTTPPostJSON)合约函数通过 HTTP / HTTPS 请求方式在合约内向任何Web资源发出请求。传递给此函数的参数为:URL,请求方法(GET或POST),请求头和请求参数。

读取数据权限

由于VDE中的数据未保存到区块链(但可用于读取),因此可以选择配置读取数据表的权限。可以为单独的列设置读取权限,也可以为使用特殊合约的任何行设置读取权限。

创建VDE

可以在网络上创建VDE节点。VDE节点的管理员定义允许使用VDE功能的生态系统列表,并指定将拥有生态系统创建者权限的用户可以安装应用程序,接受新成员以及配置资源访问权限。

使用VDE

VDE可以创建注册表单,通过邮件或者电话向用户发送验证信息,存储在外部公共访问的数据。可以编写和测试应用程序,然后将应用程序导入至区块链生态系统。在VDE中可以使用调度合约任务,可以创建预言机(oracles),用于从web资源接收数据并发送该数据至区块链生态系统中。

GAChain 区块链

该章节介绍如何使用 GAChain 平台。

入门级

如果您有兴趣在 GAChain 上开发使用应用程序和管理生态系统,那么您可能根本不需要了解 GAChain 区块链。

在 GAChain 平台中,区块链和区块链网络对生态系统成员、管理员和应用程序开发者都是隐藏的。GAChain 已为所有这些用户组提供了 RESTful API 接口。这些接口提供对区块链防篡改分布式 全局状态 的访问。

应用程序开发者

在技术术语中,全局状态global state 是一组数据。GAChain 平台全局状态的实现就是数据库。从应用程序开发者的角度来看,应用程序是通过查询,插入和更新数据库表来与该数据库进行交互。

在 GAChain 链底层,通过执行合约将交易写入区块链中,这些交易调用由区块链网络节点执行的合约代码,这会导致全局状态数据库的更改。

对于应用程序开发者来说,合约就是一种函数,执行合约时,数据将写入数据库。页面就像脚本。页面代码是一组 Gastyle 函数。其中一些函数显示页面元素,其他数据来自数据库。应用程序开发者无需了解交易、区块生成和共识算法即可使用 GAChain 区块链。

生态系统成员

应用程序开发者编写的应用程序在 生态系统 的环境中工作,生态系统通常服务于特定的目的,结合多个应用程序来达到该目的的不同方面。

要访问生态系统的应用程序,用户必须成为该生态系统成员。一个用户可以是多个生态系统的成员。

生态系统成员可以从应用程序页面查看和修改数据库,就像在常见的Web应用程序中一样,可以填写表单,点击按钮和导航页面。

生态系统应用程序和平台应用程序

应用可按范围划分为 生态系统应用程序平台应用程序

生态系统应用程序实现该生态系统的某些独有功能或业务流程。生态系统应用程序仅在其生态系统中可用。

平台应用程序适用于所有生态系统。任何应用程序都可以开发为平台应用程序。GAChain 开发者提供支持生态系统治理核心功能的平台应用程序,例如投票、通知和生态系统成员角色管理等应用程序。

底层模型

层次定义

GAChain 分为几个层次:

  • 用户交互层

    生态系统成员通过页面和页面元素与应用程序交互。

  • 应用程序层

    应用程序开发者通过合约代码和页面代码与全局状态(数据库表)交互。

  • 全局状态层

    根据写入分布式操作分类帐本(区块链)的操作更新和同步全局状态(数据库)

  • 区块链层

    使用新区块更新分布式操作分类帐本。新区块保存的操作(交易)必须在全局状态上执行。

  • 节点网络层

    网络节点实现了 GAChain 区块链网络协议。网络协议在网络节点中分发交易、验证交易和生成新区块。同样的,新区块被网络节点分发和验证。

    所有节点的分布式操作分类帐本保持同步。如果节点发生冲突,则节点会识别哪些区块链被视为有效链并回滚无效区块链。

  • 交易层

    交易是生成区块和区块链协议的基础,交易本身是在用户交互层执行的操作结果。交易由Govis软件客户端生成。

    当用户或开发者执行诸如单击页面上的按钮或从代码编辑器执行合约等操作时,Govis会将此操作转换为交易并将其发送到与其连接的网络节点。

因此,交易流程方向如下:

  • 用户页面中的用户操作会创建交易;
  • 该交易包含在一个区块中;
  • 该区块包含在区块链中;
  • 更改操作会导致区块链全局状态发生变化,该操作将应用于数据库;
  • 数据库更改显示在应用程序中。

实现

GAChain 平台的两个主要组成部分是服务端 go-gachain 和Govis客户端 gachain-front

Govis客户端:

  • 提供用户页面;

  • 提供用于应用程序开发的IDE;

  • 存储用户帐户的公钥并执行授权;

  • 从应用页面请求数据库数据,并向用户显示应用页面;

  • 通过 REST API 将交易发送到服务端;

    为了方便用户操作自动创建交易,应用程序开发人员从IDE执行合约时,Govis会将该操作转换为交易。

服务端:

  • 保持节点的全局状态(数据库);
  • 实现区块链协议;
  • 虚拟机 执行合约代码;
  • 模版引擎 执行页面代码;
  • 实现 RESTful API 接口。

权威证明共识

该章节描述权威证明共识及其在 GAChain 平台中的实现。

什么是权威证明共识

在区块链平台中,现有的共识机制可以分为免许可区块链(比特币,以太坊)和许可区块链(以太坊私链)。

但是 GAChain 是介于这两者之间的一种新型的共识机制,即主节点采用的是经由投票机制的“许可区块链共识”,而链上生态及其子节点采用“免许可区块链共识”,但总得来说,政务链GAChain属于许可区块链。

在区块链中,所有节点都经过预先验证。使用这种共识机制的优点除了其他优势外,还能提高交易率。这种共识机制之一就是权威证明Proof-Of-Authority(PoA)

权威证明Proof-of-Authority (PoA) 是一种新的共识算法,可提供高性能和容错性。在PoA中,生成新区块的权利被授予已经证明有权产生区块的节点,这样的节点必须通过初步验证。

PoA共识优势

与工作量证明Proof-of-Work(PoW)和权益证明Proof-of-Stake(PoS)共识机制相比,PoA具有以下几点优势:

  • 不需要高性能硬件,与PoW共识相比,PoA共识不要求节点花费计算资源来解决复杂的数学逻辑;
  • 生成新区块的时间间隔可预测,对于PoW和PoS共识,这个时间会有所不同;
  • 高交易率,授权的网络节点按指定的时间间隔按顺序生成块,这提高了交易验证的速度;
  • 容忍被攻击和恶意节点,只要51%的节点不受攻击。GAChain 实现了节点的禁止和撤销区块生成权的机制。

PoA共识常见攻击手段

拒绝服务攻击

攻击者向目标网络节点发送大量交易和区块,试图中断节点活动使其不可用。

PoA机制可以抵御这种攻击:

  • 由于网络节点已预先通过身份验证,因此只能为可承受DoS攻击的节点授予区块生成权限;
  • 如果某个验证节点在一段时间内不可用,则可以将其从验证节点列表中排除。

51%攻击

在PoA共识中,51%的攻击要求攻击者获得对51%的网络节点的控制权。这与攻击者需要获得51%的网络计算能力的Proof-of-Work共识的51%攻击不同。获得许可区块链网络中的节点的控制权比获得计算能力要困难得多。

例如,在PoW共识网络中,攻击者可以增加受控网络段的计算能力(性能),从而增加受控百分比。这对于PoA共识没有意义,因为节点的计算能力对区块链网络决策没有影响。

GAChain 实现PoA共识

验证节点

在 GAChain 平台,只有验证节点才有权产生新区块,这些节点维护区块链网络和分布式账本。

验证节点列表保存在区块链注册表中。此列表中的节点顺序决定了节点生成新区块的顺序。

领导节点

以下公式确定当前 领导节点(leader node),即必须在当前时间生成新区块的节点。

leader = ((time - first) / step) % nodes
leader

当前领导节点。

time

当前时间(UNIX)。

first

创始区块生成时间 (UNIX)。

step

区块生成间隔中的秒数。

nodes

节点总数量。

生成新区块

新区块由当前时间间隔的 领导节点 生成。在每个时间间隔,领导节点从验证节点列表传递到下一个验证节点。

_images/block-generation.png
新区块生成步骤

主要步骤如下:

  1. 从节点的交易队列中收集所有新交易;
  2. 逐个执行交易。无效或无法执行的交易将被拒绝;
  3. 检查是否符合 区块生成限制范围
  4. 生成具有有效交易的新区块,并使用验证节点的私钥(ECDSA算法)对其区块进行签名;
  5. 将该区块发送到其他验证节点。
验证新区块

其他验证节点验证步骤:

  1. 接收并验证新区块:

    • 新区块是否由当前时间间隔的领导节点生成;
    • 当前时间间隔的领导节点没有生成其他区块;
    • 新区块被正确签名。
  2. 逐个执行区块中的交易。检查交易是否成功执行并且在 区块生成限制范围 内。

  3. 接受或拒绝该区块,具体取决于上一步:

    • 如果区块验证成功,则将新区块添加到当前节点的区块链中;
    • 如果区块验证失败,则拒绝该区块,标记该区块并发送 坏区块 交易;
    • 如果生成坏区块的验证节点继续生成该类坏区块,则可以从验证节点列表中禁用或排除该验证节点。

分叉

分叉(fork) 是区块链的替代版本。分叉包含一个或多个独立于区块链其余部分生成的区块。

分叉通常在发生网络节点的一部分不同步,影响分叉概率的因素是高网络延迟,有意或无意的时间限制违规,节点的系统时间不同步。如果网络节点具有显着的地理分布,则必须增加区块生成间隔。

通过遵循最长的区块链规则来解析分叉。当检测到两个版本的区块链时,验证节点将回滚较短版本并接受较长版本。

_images/block-fork-resolution.png

术语和定义

区块链相关术语

区块链

一种存储数据的信息系统,在系统内传输和处理数据,可以防止数据被伪造和丢失,同时保持数据可靠性; 通过以下方式实现数据保护:

  1. 将数据写入一系列加密区块的区块链中;
  2. 在对等网络中分散存储区块链副本;
  3. 使用共识机制对所有节点上的区块链进行同步;
  4. 通过在区块链中存储数据传输和处理合约的算法,确保在使用网络执行数据操作时,保证数据可靠性。
对等网络

由计算机网络的对等节点组成(没有中央服务器)。

哈希

又叫做散列,任意文件或数据集长度的二进制值映射为较短的固定长度的二进制值。

区块

在验证交易的格式和签名之后,由验证节点分组到特定数据结构中的交易集合。一个区块包含一个哈希指针作为到前一个区块的链接,这是确保区块链加密安全性的措施之一。

区块验证

验证区块结构、生成时间、与前一个区块的兼容性、交易签名以及交易与区块数据的对应关系的正确性。

共识

验证节点在向区块链添加新区块过程中使用的验证协议或该类协议的算法。

交易

区块链网络上的数据传输操作,或区块链中该类事务的记录。

通证

区块链上可流通的加密数字权益证明。存储在寄存器中的一组可识别的数字记录,包括在这些记录之间交换权利份额的机制。

标识符

用于识别系统中用户的加密程序。

唯一标识符

将账户和用户联系起来的程序,需要法律和组织的努力或其他程序来实现生物识别,以便将用户名与实际用户联系起来。

私钥

由其拥有者密存的一串字符串,用于该拥有者访问在网络上的虚拟帐户并签署交易。

公钥

用于检查私钥真实性的一串字符,公钥由私钥唯一派生生成。

数字签名

文档或消息经数据加密处理后获得的属性,数字签名用于检查文档的完整性(没有修改)和真实性(验证发件人的身份)。

智能合约

在区块链中的执行数据存储操作的程序,所有合约都存储在区块链中。

交易费用

向验证节点支付执行交易的费用。

双重支付

一种对区块链网络攻击方法,结果是一笔交易花费两次同样的通证。

在区块链分叉时会导致这种攻击发生。只有当攻击者控制了网络验证能力的50%以上时,才能执行该类攻击。

加密

一种数字数据转换的方式,只有拥有对应解密密钥的一方才能读取它。

私有链

所有节点和数据访问权限由单个组织(政府、公司或私人)集中控制的区块链网络。

公有链

不受任何组织控制的区块链网络,所有决策都是通过在网络参与者之间达成共识来决定,每个人都可以获取和访问区块链网络的数据。

委托权益证明

委托权益证明Delegated Proof of Stake(DPoS),一种区块链网络共识算法,验证节点是由通证所有者分配的,他们使用自己的权利份额进行投票。

GAChain 平台术语

测试网

用于测试的区块链网络版本。

主网

区块链网络主版本。

通证

用于支付节点网络资源的交易费用。

交易

调用合约并将参数传递给合约的操作命令,验证节点执行介意的结果是数据库的更新。

燃料

用于计算在节点网络上执行某些操作的费用的常规单位,燃料汇率由验证节点投票决定。

账户地址

存储通证的数据记录,可以通过一对密钥(私钥和公钥)访问。

钱包地址

节点网络上用户的字符编码标识符,作为该用户的虚拟帐户的名称。

Govis

用于连接节点网络的软件客户端,有桌面版本和Web浏览器版本。

Govis集成了平台开发环境,包括创建和编辑数据表、页面和合约。用户可在Govis在构建生态系统、创建和使用应用程序。

生态系统

一个相对封闭或开放的软件编程环境,包括了应用程序和生态系统成员。

生态系统成员可以发行属于生态系统的专属通证、使用智能合约建立成员间的交互规则、设置成员访问应用程序元素的权限。

生态系统参数

一组可配置的生态系统参数,有生态系统创建者账户、更改应用程序元素权限等参数,在参数表中可更改。

生态系统成员

可以访问特定生态系统和应用程序功能的用户。

虚拟专用生态系统

虚拟专用生态系统Virtual Dedicated Ecosystems(VDE),也叫做链下服务Off-Blockchain Servers(OBS),它具有标准生态系统的全套功能,但在区块链之外工作。在VDE中,可以使用和创建合约和模板语言、数据库表,可以使用Govis软件客户端创建应用程序。可以使用接口方式调用区块链生态系统上的合约。

权威证明

权威证明Proof-of-Authority(PoA)是一种新的共识算法,可提供高性能和容错性。在PoA中,生成新区块的权利被授予已经证明有权产生区块的节点,这样的节点必须通过初步验证。

GALang

智语言 GALang,用于创建智能合约的脚本语言。GALang 可以处理从用户页面接收的数据功能,以及用于在数据库表中执行的值操作。

可以在Govis软件客户端的编辑器中创建和编辑合约。

GAStyle

乾语言 GAStyle,用于创建页面的模版语言。GAStyle 可以从数据库表中获取值、构建用户页面、将用户输入数据传递到合约的 数据data 部分。

集成开发环境

集成开发环境Integrated Development Environment(IDE)是一组用于创建应用程序的软件工具。

Govis软件客户端的集成开发环境包括合约编辑器,页面编辑器,数据库表管理工具,多语言资源编辑器以及应用程序导出和导入功能。集成开发环境与基于语义工具的可视化页面设计器相辅相成。

页面编辑器

Govis软件客户端中通过直接在屏幕上排列基本应用程序元素HTML容器,表单域,按钮等工具,可以来创建应用程序页面。

可视化页面设计器

Govis软件客户端中创建应用程序页面的工具,包括界面设计器和 GAStyle 语言的页面代码生成器。

合约编辑器

Govis软件客户端中使用可视化页面创建合约的工具。

多语言资源

Govis软件客户端中应用程序页面本地化的模块,它将应用程序页面上的标签与所选语言的文本值相关联。

导出应用程序

将应用程序的所有数据表、页面和合约等源代码保存为文件。

导入应用程序

将应用程序包含在导出文件中的所有数据表,页面和合约加载到生态系统中。

智能法律

智能法律Smart Law是包含监管信息的一组特殊智能合约。用于管理控制合约的操作和寄存器访问权限。

法律制度

在智慧法律中制定的一套规则机制,该规则可以规范生态系统用户之间的关系,定义更改协议参数的程序规则,还有定义各种具有挑战性的解决方案。

应用程序

在Govis软件客户端的集成开发环境中创建功能完备的软件产品。

应用程序是具有配置访问权限的数据库表、智能合约和用户页面等元素的集合。

页面

使用 GAStyle 模板语言编写的程序代码从而在屏幕上形成一个可交互的界面。

模块

使用 GAStyle 模板语言编写的程序代码,可以重复包含在应用程序页面中的代码块。

访问权限

获取创建和编辑数据表,合约和页面的访问权限的条件。

对数据表的访问权限可以设置添加行和列,以及编辑列中值的权限。

验证节点

网络节点中有权生成和验证区块的节点。验证节点也称作主节点。

全节点

网络上的一个节点,用于存储完整区块链的最新版本。

并发事务处理

一种通过同时处理来自不同生态系统的数据来提高交易处理速度的方法。

常见问题

  1. 请简短描述一下 |platform| 平台?

    • 是一个区块链平台,旨在构建一个基于集成应用程序开发环境的数字生态系统,该环境具有用于管理数据、接口和智能合约访问权限的多级权限系统。
  2. GAChain 平台是否适用于比特币、以太坊或其他区块链?

    • 不适用,GAChain 构建在自身原始区块链的基础上。
  3. GAChain 与其他内置执行智能合约机制的公共区块链平台(Ethereum、Qtum)和仍在设计中的平台(Tezos、EOS)的主要区别是什么?

    • GAChain 具有上述区块链平台无法找到的独特功能:

      • 在单个客户端软件中实现集成应用程序开发环境;
      • 用于页面设计的专用模版语言 GAStyle 与合约构建语言 GALang 相互协调;
      • 具有用于管理数据、接口和智能合约访问权限的多级权限系统,其中可以将权限授予成员、角色和合约;
      • 生态系统,用于创建区块链应用程序和用户与其交互的自治软件环境;
      • 法律体系,一套以智能法律(专用的智能合约)编写的规则,规范了平台用户之间的关系,定义了用于解决问题的协议参数变化过程。
  4. GAChain 有自己的加密货币吗?

    • 有,GAChain 使用自己的通证GAC。
  5. 什么是验证节点?

    • 验证节点是有权验证交易和生成新区块的网络节点。
  6. 谁可以维护验证节点?

    • 具有足够处理能力和容错能力的任何网络节点都可以成为验证节点。GAChain 使用权威证明(PoA)共识机制,节点可以基于生态系统的投票成为验证节点,但只有被投资者(平台通证拥有者)证明具有正常运作能力的生态系统才能参与此类投票。使用这种授权算法,验证节点由主要生态系统运行,因为维护网络运行最符合他们的利益。
  7. 什么是平台生态系统?

    • 生态系统实际上是用于创建区块链应用程序和其中用户的操作的自治软件环境。
  8. 谁可以创建生态系统?

    • 平台的所有用户都可以创建新的生态系统。
  9. 用户如何成为生态系统的成员?

    • 平台网络的生态系统成员注册可在现有任何生态系统中进行,生态系统的策略定义了不同的成员加入程序,该策略在专门的生态系统目录中发布了新生态系统的关键公开信息。
  10. 一位用户可以创建多个生态系统吗?

    • 是的,每位用户都可以创建任意数量的生态系统,同时也可以成为多个生态系统的成员。
  11. 什么是平台应用程序?

    • 应用程序是实现功能或服务的完整软件产品。应用程序由数据库表、合约和页面组成。
  12. 什么编程语言用于创建应用程序?

    • 合约使用 GALang 语言编写,该语言由平台团队开发,更多参阅:智能合约
    • 页面使用 GAStyle 语言编写,是一种页面模版语言,更多参阅:模版语言
  13. 什么软件用于创建应用程序和用户交互?

    • 应用程序在Govis软件客户端中编写和执行,不需要其他软件。
  14. 平台合约可以使用第三方API接口访问数据吗?

    • 不可以,合约只能直接访问区块链中存储的数据,VDE 用于处理外部数据源。
  15. 保存在区块链中的合约可以更改吗?

    • 是的,合约可以更改。更改合约的权限由其创建者指定,合约创建者可以指定拒绝更改的权限,或者指定合约和成员进行更改的权限,或者在智能法律中配置一组复杂的条件。
    • Govis软件客户端提供对所有合约版本的访问。
  16. 什么是智能法律?

    • 智慧法律是一种合约,旨在控制和限制常规合约的运作,从而控制和限制生态系统成员的活动。
    • 一套智能法律可以被视为一个生态系统的法律体系。
  17. 合约可以调用执行其他合约吗?

    • 可以,合约可以通过直接寻址的方式并为其提供参数来调用其他合约,或者通过链接名称调用合约,更多参阅:智能合约
  18. 应用程序工作是否需要主合约?

    • 不需要,合约是执行某些功能的自治程序模块。每个合约配置了接收指定的数据,然后检查这些数据的正确性,并执行一些操作,这些操作当作交易被记录在数据库。
  19. 应用程序可以为不同语言本地化吗?

    • 可以,软件客户端拥有内置的本地化支持机制,可以创建任何语言的页面。
  20. 可以在不使用GAStyle模板语言的情况下创建页面吗?

  21. 页面是否存储在区块链中?

    • 是的,页面和合约都存储在区块链中,这可以防止它们被伪造。
  22. 哪些类型的数据库可以用于合约的操作?

    • 目前使用PostgreSQL数据库,后续会更改。
  23. 如何管理对数据表中数据的访问?

    • 可以为生态系统成员、角色或指定合约配置添加新字段、新条目或更改列中数据的权限。但执行特定操作而创建的合约除外。
  24. 生态系统中的应用程序可以与来自另一个生态系统的应用程序交换数据吗?

    • 可以,通过适用于所有生态系统的全局数据表可以组织数据交换。
  25. 是否应该从头开始编写新生态系统中的所有应用程序?

    • 不需要,每个新的生态系统都有一些开箱即用的应用程序:

      • 管理生态系统成员和角色的机制;
      • 发行和配置其他通证;
      • 投票系统;
      • 通知系统;
      • 生态系统成员间的消息通信。

    可以对这些应用程序进行编辑和配置,以满足任何生态系统的特殊需求。

  26. 应用程序的运作是否有任何费用?

    • 是的,使用验证节点的资源需要在平台中支付通证。
  27. 谁支付应用程序的运作费用?

    绑定账户地址,目前有4种方式支付应用程序的运作费用:

    • 合约调用者,默认账户地址,当用户调用合约时,该用户的账户地址支付;
    • 合约绑定者,合约创建者指定的账户地址,所有用户调用该合约的费用,由该账户地址支付;
    • 生态系统创建者,生态系统内所有应用程序的运作费用由生态系统创建者支付;
    • 生态系统专属钱包,每个生态系统都有独有的账户地址,如果生态系统创建者激活了该账户地址,生态系统内所有应用程序的运作费用由该账户地址支付。

    支付优先级:生态系统专属钱包 > 生态系统创建者 > 合约绑定者 > 合约调用者

  28. 如何保护生态系统内的应用程序免受其漏洞的攻击?

    • 平台团队也知道没有办法完全避免应用程序代码中的错误,特别是考虑到应用程序可以由任何用户编写。这就是我们决定建立一种消除利用漏洞后果机制的原因。法律体系可以停止应用程序的攻击操作,并使用一些交易来恢复到原来状态。法律体系中规定了执行该类合约的权限和授予这些权限的投票程序。
  29. GAChain在未来的计划中实现哪些新功能

    • 可视化智能合约设计器;
    • 支持混合数据库(SQL和NoSQL) ;
    • 来自不同生态系统的交易的并行多线程处理;
    • 在客户端执行资源密集型计算;
    • 生态系统托管和计算能力交换;
    • 子节点,只存储服务器上部分区块;
    • 语义参考(本体)用于统一平台内数据的操作等。
  30. 如何证明GAChain的可操作性??

    • 在 GAChain 平台上实施了一系列概念论证项目和案例:社会化代收税及电子发票生成和流转系统、医疗器械监管及防伪追溯系统、融资及监管系统、投票/民调系统、工商登记、贸易金融工具、资产登记合约管理系统等。

应用程序开发教程

本章节介绍如何在 GAChain 平台编写一个简单的应用程序。

目标

应用程序开始时很简单,随着教程的深入,其复杂性也在增加。

应用程序的最终版本在数据表中存储为简单消息(字符串),其中包含时间戳和消息发送者的帐户标识符。用户可以访问这些消息列表并从应用程序页面添加新消息。该应用程序的页面可以从生态系统菜单中访问。

第1部分:环境

Govis

Govis是 GAChain 的唯一客户端,Govis为所有用户和生态系统角色提供功能。应用程序开发人员可以在Govis中开发和测试应用程序,生态系统管理员使用Govis来管理生态系统,用户可以通过Govis与生态系统应用进行交互。

在本教程中,你将在Govis客户端中编写合约代码、页面模版代码和执行其他所有操作。Govis还提供一种恢复、保存和执行合约代码,管理数据结构(数据表),分配访问权限和创建应用程序的方法。

每个节点都有自己的Govis客户端实例。

第2部分:合约

您的第一个简单的应用程序为“Hello,World!”。

注解

应用程序在表中存储为字符串。它没有任何用户页面。

创始人账户

具有 Developer 角色的帐户可以使用生态系统的“root”特权。默认情况下,该角色可以访问所有操作。在新的生态系统中,创始人帐户被分配给 Admin 角色。您必须使用这个帐户来引入生态系统的重大更改,比如创建新的应用程序和数据表。

使用创始人的帐户登录生态系统。

新应用程序

一旦您作为生态系统的创始人登录,您就可以创建一个新的应用程序。

创建新应用程序:

  1. 转到 Developer 选项卡;

  2. 在左侧的菜单中选择 应用程序

  3. 应用程序 页面选择 创建

  4. 名称 字段中指定应用程序的名称;

  5. 更改条件 指定 true

    true 表示任何人都可以更改应用程序;

    另一种选择是指定 ContractConditions("MainCondition")。除了创始人之外,将禁止任何人对进行应用程序更改。

  6. 您的应用程序将显示在应用程序列表中,单击指定应用程序的名称字段使其激活。

    注解

    Developer 选项卡中选择应用程序可以更轻松地导航与所选应用程序相关的资源,它对生态系统没有影响。无论选择哪一个,所有生态系统应用程序仍然可用。

新数据表

要存储数据,应用程序需要一个数据表。在Govis创建该数据表。

创建数据表:

  1. Developer 选项卡,选择 应用程序 - 名称 > 数据表

    这显示所选应用程序的所有数据表。如果该列表为空,则您的应用还没有任何数据表。

  2. 单击 创建

    Govis将显示创建数据表页面。

  3. 名称 字段中指定您的数据表名称;

    本教程使用 apptable 数据表名称。

  4. 添加列。名为 message,其类型为 Text

    结果,该数据表必须有两列: id (预定义)和 message。您稍后会添加更多列。

    _images/app-tut-table.png
  5. 对于读写权限,在每个字段指定为 true

    这将允许任何人在数据表上执行插入条目、更改条目、添加列和读取条目数据;

    作为选项,您可以限定读写权限给创始人帐户,在这种情况下,请在该字段中指定为 ContractConditions("MainCondition")

新合约

合约代码部分

每个合约都有三个部分,更多请参阅:合约结构

创建合约
  1. Developer 选项卡选择 应用程序 - 名称 > 合约

    这将显示所选应用程序的所有合约。新应用程序该列表将为空。

  2. 单击 创建

    将在编辑器中打开一个新的合约模版。

空合约模版如下所示:

contract ... {
    data {

    }
    conditions {

    }
    action {

    }
}
合约名称

首先,给合约命名。

contract AppContract {
数据部分

填写 data 部分。

在如下示例中,Message 是变量名称,string 是其类型。

data {
    Message string
}
条件部分

填写 conditions 部分。简单的验证条件是指定的字符串不能为空,如果 Message 长度为 0,则合约将在执行时生成带有已定义的消息警告。

conditions {
    // avoid writing empty strings
    if Size($Message) == 0 {
        error "Message is empty"
    }
}
操作部分

填写 action 部分。 简单的操作是将 Message 写入数据表中。

action {
    DBInsert("apptable", {message: $Message})
}
完整合约代码

以下部分是完整合约的代码。

GAChain 的所有合约都像这样构建,包含 dataconditionsaction 部分。

contract AppContract {
    data {
        Message string
    }
    conditions {
        // avoid writing empty strings
        if Size($Message) == 0 {
            error "Message is empty"
        }
    }
    action {
        DBInsert("apptable", {message: $Message})
    }
}
保存并执行

合约准备进行测试:

  1. 在编辑器菜单中,单击 保存

    这样就会更改合约代码,更改的版本可供所有网络节点使用。

  2. 在编辑器菜单中,单击 执行

    这将显示 执行合约 页面。

  3. 执行合约 页面。填写合约的输入参数;

    该合约有一个参数 Message,所以在 指定 Message,在 指定 Hello, World

    _images/app-tut-execute.png
  4. 单击 执行

    结果将显示在右侧。

如果成功添加了字符串,则结果将包含引入更改交易的区块ID和结果代码。

{
   "block": "31",
   "result": null
}

第3部分:页面

在合约生效之后,是时候把它扩展成更有用的东西了。在这部分中,您将实现UI和其他功能。

注解

该应用程序将字符串存储在表中,就像日志中的条目一样。每个字符串都有一个作者和一个时间戳。

用户可以从应用程序页面查看存储的字符串列表,此时该页面是一个简单的表格。

新字段

与之前一样,从 Developer 选项卡 > 应用程序 - 名称 > 数据表 页面编辑数据表;

将以下字段添加到 apptable 数据表:

  • author 字段,类型 Number更改 设置为 true

    该字段将存储作者帐户的标识符。

  • timestamp 字段,类型 Date/Time更改 设置为 true

更改合约

更改合约代码来处理作者ID和时间戳。

作者ID是生态系统帐户ID。时间戳是以Unix时间格式执行合约的日期和时间。

这两个值都由 预定义变量 提供。所以无需输入或验证预定义变量,因此仅在操作部分中进行更改。

更改合约,以便在添加消息时将作者的ID和时间戳写入数据表中。作者的ID由 $key_id 定义,时间戳由定义 $time

action {
    DBInsert("apptable", {message: $Message, author: $key_id, timestamp: $time})
}

页面

对于此部分,应用程序的页面是一个显示存储在表中的信息的简单页面。

就像所有其他资源一样,可以在Govis中创建UI页面:

  1. 导航到 Developer 选项卡 > 应用程序 - 名称 > 页面

  2. 单击 创建

    可视化设计器将在新选项卡中打开。

设计器视图

默认页面为空。您可以使用预定义的结构快速填充页面。

_images/app-tut-designer.png

创建一个基本的表单:

  1. 在右侧的视图选择器中,单击 视图化(Designer)

    视图将切换到可视化设计器。

  2. 从左侧菜单中,选择 Table With Header 并将其拖到页面上。

    将出现包含多个元素的表格。

开发者视图

GAChain 的用户页面用 Gastyle 编写。您需要为页面编写代码,因此请切换到开发者(Developer)的视图。

_images/app-tut-developer.png

切换到开发者(Developer)视图。

  1. 在右侧的视图选择器中,单击 开发者

    视图将切换到包含页面代码的代码编辑器。

获取数据表数据

目前为止,页面模版并没有做什么。接下来就得更改代码,以便页面显示来自 apptable 表的数据。

  1. 想要请求表中数据,使用 DBFind 函数;

    以下示例中该函数调用从 apptable 表中获取数据,并将其放入 src_table 源中。并按时间戳字段对其进行排序。该 src_table 源稍后用作页面上表视图的数据源。

    DBFind(Name: apptable, Source: src_table).Columns(Columns: "author,timestamp,message").Order(timestamp)
    
  2. 想要显示 src_table 源中的数据,在 Table 函数中将其指定为一个源以及列标题。

    Table(Columns: "AUTHOR=author,TIME=timestamp,MESSAGE=message", Source: src_table)
    
  3. 在右侧的视图选择器中,单击 预览 以检查数据是否正确显示。

完整页面代码

以下是该部分的完整页面代码。该基本页面将在稍后进行扩展。

DBFind(Name: apptable, Source: src_table).Columns(Columns: "author,timestamp,message").Order(timestamp)

Div(Class: panel panel-primary) {
    Div(Class: panel-heading, Body: Table block)
    Table(Columns: "AUTHOR=author,TIME=timestamp,MESSAGE=message", Source: src_table)
    Div(Class: panel-footer text-right) {
        Button(Class: btn btn-primary, Contract: ContractName, Body: More)
    }
}
保存页面

单击 保存 以保存页面:

  1. 页面名称 字段中为页面指定 AppPage 或任何其他名称;
  2. 菜单 中选择 default_menu
  3. 指定 更改条件true
  4. 单击 确认

第4部分:应用程序

在前面的部分中,您创建了一个合约,一个用于存储数据的表,以及一个用于显示该数据的基本UI页面。

在该部分中,您将确定最终的应用程序,因此它的外观和操作类似于实际应用程序。

菜单

页面需要链接到一个菜单,例如,在 Home 选项卡上显示的 default_page 页面链接到默认生态系统菜单 default_menu

由于应用程序教程很简单(只有一个页面),因此无需为其创建单独的菜单。默认菜单中的新菜单项就足够了。

注解

您可以通过在 Developer 选项卡 > 应用程序 - 名称 > 页面 中编辑页面属性来定义页面显示的菜单。例如,如果您的应用程序有多个页面,则可能需要创建一个菜单以在这些页面之间导航并将其分配给应用程序的所有页面。

添加菜单项

与所有其他资源一样,可以在Govis中创建和编辑菜单:

  1. 导航到 Developer 选项卡 > 菜单;

    _images/app-tut-menu-list.png
  2. 单击 default_menu 条目名称;

    编辑器将在新选项卡中打开。

  3. 将新菜单项添加到模版的末尾。该菜单项将打开应用程序页面。该图标来自 FontAwesome 图标集。

    MenuItem(Title:Messages, Page:AppPage, Icon:"fa fa-envelope")
    
  4. 单击 保存

测试新菜单项

检查新菜单项是否有效:

  1. 打开 Home 选项卡;

  2. 单击菜单中的 刷新

    将出现标题为 Messages 的条目项;

    _images/app-tut-menu-messages.png
  3. 单击 Messages.

    该应用程序的页面将打开。

发送消息

GAStyle 中的按钮可以执行合约和打开页面,具体取决于参数。

Button 函数有合约的两个参数:

  • Contract

    激活的合约名称。

  • Params

    合约的输入参数。

表单

要将数据发送到合约,请将表单添加到应用程序页面。该表单必须具有消息的输入字段,以及将激活AppContract合约的按钮。

以下是该类表格的示例。它嵌套在自己的 Div 中。将它放在包含表单视图的Div元素之后,该表单定义了 Input 字段有一个已定义的名称 message_input。按钮使用这个名称向合约发送 Message 参数值。最后,Val 函数用于获取输入字段的值。

Div(Class: panel panel-primary) {
  Form() {
        Input(Name: message_input, Class: form-control, Type: text, Placeholder: "Write a message...", )
        Button(Class: btn btn-primary, Body: Send, Contract: AppContract, Params: "Message=Val(message_input)")
  }
}

您可能会注意到通过发送消息测试该新功能时,表单不会刷新。这将在 页面刷新 介绍。

表格导航

页面上的默认表格视图第一页仅显示25个条目。添加一个简单的导航,允许用户导航所有表格条目。

导航按钮

该导航将使用两个按钮。每个按钮都会重新加载应用程序的页面并将参数传递给它。

  • Previous 按钮将显示前25个条目。如果没有其他条目,则不会显示该按钮;
  • Next 按钮将显示下25个条目。如果没有其他条目,则不会显示该按钮。
变量

该导航需要两个变量来存储表视图状态:

  • #table_view_offset#

    该变量存储当前表视图偏移量。

    导航按钮将在重新加载页面时将其作为参数传递。

  • #record_count#

    该变量存储表中的条目总数。

    将计算该值。

条目计数

要计算 #record_count#,请修改现有的 DBFind 函数调用。在 . count() 调用中指定的变量将存储条目计数。

DBFind(Name: apptable, Source: src_table).Columns(Columns: "author,timestamp,message").Order(timestamp).Count(record_count)
表格偏移量

必须在打开页面时将表视图偏移传递给页面。如果 #table_view_offset# 未获得值则指定未 0

将以下代码添加到页面的顶部。

If(GetVar(table_view_offset)){
}.Else{
    SetVar(table_view_offset, 0)
}

再次修改 DBFind 函数调用。这次它必须使用新的表视图偏移量。

DBFind(Name: apptable, Source: src_table).Columns(Columns: "author,timestamp,message").Order(timestamp).Count(record_count).Offset(#table_view_offset#)
按钮代码

找到定义页脚的 Div 函数调用:Div(Class:panel-footer text-right)。将按钮代码添加到其中。

Div(Class: panel-footer text-right) {

}

Previous 按钮只有在至少有一个 Next 要返回时才会显示。当添加按钮时,将计算页面的新表视图偏移量 offset_previous。参数被传递到重新打开页面的 PageParams 参数中。

If(#table_view_offset# >= 25) {
    SetVar(offset_previous, Calculate(#table_view_offset# - 25))
    Button(Class: btn btn-primary, Body: Previous, Page: AppPage, PageParams:"table_view_offset=#offset_previous#")
}

仅当总记录数大于页面上显示的数量时,才会显示 Next 按钮。当添加按钮时,将计算页面的新表视图偏移量 offset_next。参数被传递到重新打开页面的 PageParams 参数中。

If(#record_count# >= Calculate(#table_view_offset# + 25)) {
    SetVar(offset_next, Calculate(#table_view_offset# + 25))
    Button(Class: btn btn-primary, Body: Next, Page: AppPage, PageParams:"table_view_offset=#offset_next#")
}
_images/app-tut-navigation.png

添加按钮后,保存页面并从 Home > Messages 菜单项进行测试。

页面刷新

实现的最后一项功能就是自动更新位于页面上的表格,当用户发送新消息时,它必须显示在表格中。

除了执行合同之外,您还可以通过 Send 按钮重新打开当前页面来实现这一点。必须将 #table_view_offset# 参数传递到该页面,而不进行任何更改。

添加 PagePageParams 参数到 Send 按钮,代码如下所示:

Button(Class: btn btn-primary, Body: Send, Contract: AppContract, Params: "Message=Val(message_input)", Page:AppPage, PageParams:"table_view_offset=#table_view_offset#")

完整页面代码

这部分介绍了应用程序页面的许多更改。以下是该应用程序页面的完整代码。

If(GetVar(table_view_offset)){
}.Else{
    SetVar(table_view_offset, 0)
}

DBFind(Name: apptable, Source: src_table).Columns(Columns: "author,timestamp,message").Order(timestamp).Count(record_count).Offset(#table_view_offset#)

Div(Class: panel panel-primary) {
 Div(Class: panel-heading, Body: Table block)
 Table(Columns: "AUTHOR=author,TIME=timestamp,MESSAGE=message", Source: src_table)
 Div(Class: panel-footer text-right) {

  If(#table_view_offset# >= 25) {
    SetVar(offset_previous, Calculate(#table_view_offset# - 25))
    Button(Class: btn btn-primary, Body: Previous, Page: AppPage, PageParams:"table_view_offset=#offset_previous#")
  }

  If(#record_count# >= Calculate(#table_view_offset# + 25)) {
    SetVar(offset_next, Calculate(#table_view_offset# + 25))
    Button(Class: btn btn-primary, Body: Next, Page: AppPage, PageParams:"table_view_offset=#offset_next#")
  }

 }
}

Div(Class: panel panel-primary) {
  Form() {
        Input(Name: message_input, Class: form-control, Type: text, Placeholder: "Write a message...", )
        Button(Class: btn btn-primary, Body: Send, Contract: AppContract, Params: "Message=Val(message_input)", Page:AppPage, PageParams:"table_view_offset=#table_view_offset#")
  }
}

结论

本教程将介绍生态系统的基本应用程序。它没有为应用程序开发者解析其他重要的主题,比如布局样式、管理访问权限以及应用程序和资源之间的交互。有关这些高级主题的更多信息,请参阅其他文档。

智能合约

智能合约(下文称为合约)是应用程序的基本元素。用户在页面中执行合约通常是单个操作,结果是更改或创建数据库的条目。应用程序的所有数据操作形成了合约系统,这些合约通过数据库或者合约内容的函数彼此交互。

合约结构

使用 contract 关键字声明合约,后面接上合约名称,合约内容必须用大括号括起来。合约结构有三个主要部分:

  1. data - 数据部分,声明输入数据的变量,包括变量名称和变量类型;
  2. conditions - 条件部分,验证数据的正确性;
  3. action - 操作部分,执行数据操作的动作。
contract MyContract {
    data {
        FromId int
        ToId   int
        Amount money
    }
    func conditions {
        ...
    }
    func action {
        ...
    }
}

数据部分

data 部分描述了合约数据输入以及接收的表单参数。

每行的依次顺序结构:

  • 变量名称 - 只接收变量,不支持接收数组;
  • 变量数据类型 - 变量的 数据类型
  • optional - 可选参数,不需要填充的表单元素。
contract my {
  data {
      Name string
      RequestId int
      Photo file "optional"
      Amount money
      Private bytes
  }
  ...
}

条件部分

conditions 部分描述了对接收的数据进行验证。

以下命令用于错误警告:严重性错误 error、警告性错误 warning、提示性错误 info,这三种命令都会生成一个终止合约执行的错误,每个错误都会打印不同类型的错误日志信息。例如:

if fuel == 0 {
      error "fuel cannot be zero!"
}
if money < limit {
      warning Sprintf("You don't have enough money: %v < %v", money, limit)
}
if idexist > 0 {
      info "You have already been registered"
}

操作部分

action 部分描述了合约的主要代码,该代码检索其他数据并将结果值记录到数据库表中。例如:

action {
DBUpdate("keys", $key_id, {"-amount": $amount})
DBUpdate("keys", $recipient, {"+amount": $amount, "pub": $Pub})
}

变量

data 部分声明的变量通过 $ 符号后面跟上变量名称传递给其他合约部分。$ 符号也可以声明其他不在数据部分内的变量。这些变量被认为是这个合约和所有嵌套该合约的全局变量。

合约内可以使用预定义变量,这些变量包含调用该合约的交易数据:

  • $time – 交易时间戳;
  • $ecosystem_id – 生态系统ID;
  • $block – 包含该交易的区块ID;
  • $key_id – 签署当前交易的账户地址;
  • $type 虚拟机中合约ID;
  • $block_key_id – 生成区块的节点账户地址;
  • $block_time – 区块生成时间戳;
  • $original_contract – 最初进行交易处理的合约名称。如果该变量为空字符串,表示交易在验证过程中调用了该合约。要检查该合约是由另一个合约调用还是直接从交易调用,需要比较 $original_contract$this_contract 的值。如果它们相等,则表示合约是从交易调用的;
  • $this_contract – 当前执行合约名称;
  • $guest_key – 访客账户地址;
  • $stack – 合约数组堆栈,array 类型,包含所有执行的合约,数组第一个元素表示当前执行的合约名称,最后一个元素表示最初进行交易处理的合约名称;
  • $node_position – 区块所在的验证节点数组的索引号;
  • $txhash – 交易哈希;
  • $contract – 当前合约结构数组。

预定义变量不仅可以在合约中访问,还可以在定义应用程序元素的访问权限条件的权限字段中访问。当用于权限字段时,关于区块信息的预定义变量始终等于零,例如 $time$block 等。

预定义变量 $result 赋值于合约的返回结果。

contract my {
  data {
      Name string
      Amount money
  }
  func conditions {
      if $Amount <= 0 {
         error "Amount cannot be 0"
      }
      $ownerId = 1232
  }
  func action {
      var amount money
      amount = $Amount - 10
      DBUpdate("mytable", $ownerId, {name: $Name,amount: amount})
      DBUpdate("mytable2", $ownerId, {amount: 10})
  }
}

嵌套合约

在合约的 conditionsaction 部分可以嵌套合约。嵌套合约可以直接调用,合约参数在其合约名称后面的括号中指定,例如,@1NameContract(Params)。也可以使用 CallContract 函数调用。

文件上传

使用 multipart/form-data 格式的表单上传文件,合约的数据类型必须是 file

contract Upload {
    data {
        File file
    }
    ...
}

UploadBinary 合约用于上传和存储文件。在页面编辑器使用 GAStyle 语言的函数 Binary 可以获取文件下载链接。

JSON格式查询语句

在合约语言中,JSON 格式类型可以指定为字段类型。使用语法: columnname->fieldname 来处理条目字段。获得的值记录在 columnname.fieldname。 上述语法可以在 DBFind 函数的 Columns,One,Where 中使用。

var ret map
var val str
var list array
ret = DBFind("mytable").Columns("myname,doc,doc->ind").WhereId($Id).Row()
val = ret["doc.ind"]
val = DBFind("mytable").Columns("myname,doc->type").WhereId($Id).One("doc->type")
list = DBFind("mytable").Columns("myname,doc,doc->ind").Where("doc->ind = ?", "101")
val = DBFind("mytable").WhereId($Id).One("doc->check")

日期时间格式查询语句

合约语言函数不能直接查询和更新日期时间,但是可以像示例中在Where语句中使用PostgreSQL的函数和功能。例如,需要比较字段 date_column 和当前时间,如果 date_column 是timestamp类型,那么表达式为 date_column < NOW();如果 date_column 是Unix类型,那么表达式为 to_timestamp(date_column) > NOW()

Where("to_timestamp(date_column) > NOW()")
Where("date_column < NOW() - 30 * interval '1 day'")

以下 GALang 函数是处理SQL格式的日期和时间:

GALang 合约语言

合约由 GAChain 平台开发团队使用原始图灵脚本语言编写并编译成字节码,称为 GALang 合约语言。该语言包括一组函数、运算符和结构,可实现数据算法处理和数据库操作。

在编辑合约权限不为 false 的条件下,合约内容可以修改。对合约更改的完整历史记录存储在区块链中,从Govis客户端可获知更改情况。

区块链中的数据操作由最新版本的合约执行。

数据类型和变量

每个变量必须定义数据类型,通常情况下,数据类型会自动转换。可以使用以下数据类型:

  • bool - 布尔值,truefalse

  • bytes - 字节格式;

  • int - 64位整数型;

  • array - 任意类型值的数组;

  • map - 对象数组;

  • money - 大整数类型;

  • float - 64位浮点型;

  • string - 字符串,双引号或转义格式: "This is a string" 或者 `This is a string`;

  • file - 对象数组:

    • Name - 文件名称,string 类型;
    • MimeType - mime-type 文件格式,string 类型;
    • Body - 文件内容,,bytes 类型。

所有标识符,包括变量、函数和合约等的名称都区分大小写(MyFunc和myFunc是不同的名称)。

使用 var 关键字声明变量,后跟变量的名称和类型。在大括号内声明的变量必须在同一对大括号内使用。

声明的变量具有默认零值:bool类型的零值false,所有数字类型的零值0,字符串类型的零值空字符串,变量声明示例:

func myfunc( val int) int {
    var mystr1 mystr2 string, mypar int
    var checked bool
    ...
    if checked {
         var temp int
         ...
    }
}

数组

该语言支持两种数组类型:

  • array - 索引从0开始的数组;
  • map - 对象数组。

分配和检索数组元素时,索引必须放在方括号中。数组中不支持多个索引,不能将数组元素作为 myarr[i][j] 来处理。

var myarr array
var mymap map
var s string

myarr[0] = 100
myarr[1] = "This is a line"
mymap["value"] = 777
mymap["param"] = "Parameter"

s = Sprintf("%v, %v, %v", myarr[0] + mymap["value"], myarr[1], mymap["param"])
// s = 877, This is a line, Parameter

您还可以在 [] 通过指定元素定义 array 类型。对于 map 类型数组,请使用 {}

var my map
my={"key1": "value1", key2: i, "key3": $Name}
var mya array
mya=["value1", {key2: i}, $Name]

您可以在表达式中使用这样的初始化。例如,在函数参数中使用。

DBFind...Where({id: 1})

对于对象数组,您必须指定一个键。键用双引号("")指定字符串。如果键名仅限于字母,数字和下划线,则可以省略双引号。

{key1: "value1", key2: "value2"}

数组可以包含字符串、数字、任何类型的变量名称和带 $ 符号的变量名称。支持嵌套数组,可以将不同的映射或数组指定为值。

表达式不能用作数组元素,使用一个变量来存储表达式结果并指定这个变量为数组元素。

[1+2, myfunc(), name["param"]] // don't do this
[1, 3.4, mystr, "string", $ext, myarr, mymap, {"ids": [1,2, i], company: {"Name": "MyCompany"}} ] // this is ok

var val string
val = my["param"]
MyFunc({key: val, sub: {name: "My name", "color": "Red"}})

If和While语句

合约语言支持标准 if 条件语句和 while 循环,可以在合约和函数中使用。这些语句可以相互嵌套。

ifwhile 关键字后必须跟条件语句。如果条件语句返回一个数字,则当其值为0,被视为 false

val == 0 等于 !valval != 0 等于 valif 语句可以有 else 代码块,在 if 条件语句为 false 时执行 else

以下比较运算符可用于条件语句:<, >, >=, <=, ==, !=, ||, &&

if val > 10 || id != $block_key_id {
    ...
} else {
    ...
}

while 循环的条件语句为 true 时执行代码块。break 表示结束代码块的循环,想要从头开始循环请使用 continue

while true {
    if i > 100 {
        break
    }
    ...
    if i == 50 {
        continue
    }
    ...
}

除了条件语句外,GALang 还支持标准的算术运算:+, -, *, /

stringbytes 类型可以作为条件语句,如果类型长度大于零时,条件为 true,反之为 false

函数

函数可以对合约 数据部分 接收的数据执行一些操作:读取和写入数据库的数据、转换值的类型以及建立合约之间的交互。

函数声明

使用 func 关键词声明一个函数,后面是函数名称和传递给它的参数列表及其参数类型,所有参数都用小括号括起来,用逗号分隔。在小括号之后,必须声明函数返回值的数据类型。函数体必须用大括号括起来。如果函数没有参数,那么大括号可以省略。使用 return 关键字返回函数返回值。

func myfunc(left int, right int) int {
    return left*right + left - right
}
func test int {
    return myfunc(10, 30) + myfunc(20, 50)
}
func ooops {
    error "Ooops..."
}

函数不会返回错误,因为所有错误检查都是自动执行的。当在任何函数中出现错误时,合约将停止其操作并显示包含错误描述的窗口。

可变长度参数

函数可以定义可变长度参数,使用 ... 符号作为函数的最后一个参数类型,表示可变长度参数,其数据类型为 array。可变长度参数包含从调用传递该参数变量开始的所有变量。任何类型的变量都可以传递,但是您需要处理与数据类型不匹配的冲突。

func sum(out string, values ...) {
    var i, res int

    while i < Len(values) {
       res = res + values[i]
       i = i + 1
    }
    Println(out, res)
}

func main() {
   sum("Sum:", 10, 20, 30, 40)
}
可选参数

函数有很多参数,但在调用它时我们只需要其中某些参数。这样的情况下,您可以通过以下方式声明可选参数:func myfunc(name string).Param1(param string).Param2(param2 int) {...},这样您就可以调用任意顺序指定的参数:myfunc("name").Param2(100)

在函数体中,您可以像正常处理这些变量。如果未调用指定的可选参数,它们默认为零值。您还可以使用 ... 指定可变长度参数:func DBFind(table string).Where(request string, params ...) 然后调用它:DBFind("mytable").Where({"id": $myid, "type": 2})

func DBFind(table string).Columns(columns string).Where(format string, tail ...)
         .Limit(limit int).Offset(offset int) string  {
   ...
}

func names() string {
   ...
   return DBFind("table").Columns("name").Where({"id": 100}).Limit(1)
}

GALang 函数参考

AppParam

返回指定应用程序参数的值(来自应用程序参数表 app_params)。

语法
AppParam(app int, name string, ecosystemid int) string
app

应用程序ID。

name

应用程序参数名称。

ecosystemid

生态系统ID。

示例
AppParam(1, "app_account", 1)

DBFind

根据指定参数从指定数据表中查询数据。返回一个由对象数组 map 组成的数组 array

.Row() 可获得请求记录的第一个 map 元素,.One(column string) 可获得请求记录中指定列的第一个 map 元素。

语法
DBFind(table string)
    [.Columns(columns array|string)]
    [.Where(where map)]
    [.WhereId(id int)]
    [.Order(order string)]
    [.Limit(limit int)]
    [.Offset(offset int)]
    [.Row()]
    [.One(column string)]
    [.Ecosystem(ecosystemid int)] array
table

数据表名称。

сolumns

返回列的列表。如果未指定,则将返回所有列。

该值为数组或使用逗号分隔的字符串。

where

查询条件。

示例: .Where({name: "John"}) 或者 .Where({"id": {"$gte": 4}})

此参数必须包含具有搜索条件的对象数组。该数组可以包含嵌套元素。

遵循句法结构如下:

  • {"field1": "value1", "field2" : "value2"}

    等价于 field1 = "value1" AND field2 = "value2"

  • {"field1": {"$eq":"value"}}

    等价于 field = "value"

  • {"field1": {"$neq": "value"}}

    等价于 field != "value"

  • {"field1: {"$in": [1,2,3]}

    等价于 field IN (1,2,3)

  • {"field1": {"$nin" : [1,2,3]}

    等价于 field NOT IN (1,2,3)

  • {"field": {"$lt": 12}}

    等价于 field < 12

  • {"field": {"$lte": 12}}

    等价于 field <= 12

  • {"field": {"$gt": 12}}

    等价于 field > 12

  • {"field": {"$gte": 12}}

    等价于 field >= 12

  • {"$and": [<expr1>, <expr2>, <expr3>]}

    等价于 expr1 AND expr2 AND expr3

  • {"$or": [<expr1>, <expr2>, <expr3>]}

    等价于 expr1 OR expr2 OR expr3

  • {field: {"$like": "value"}}

    等价于 field like '%value%' (模糊搜索)。

  • {field: {"$begin": "value"}}

    等价于 field like 'value%' (以 value 开头)。

  • {field: {"$end": "value"}}

    等价于 field like '%value' (以 value 结尾)。

  • {field: "$isnull"}

    等价于 field is null

请确保不要覆盖对象数组的键。例如,如果想用 id>2 and id<5 语句查询,不能使用 {id:{"$gt": 2}, id:{"$lt": 5}},因为第一个元素会被第二个元素覆盖。应该使用如下结构查询:

{id: [{"$gt": 2}, {"$lt": 5}]}
{"$and": [{id:{"$gt": 2}}, {id:{"$lt": 5}}]}
id

根据ID查询。例如,.WhereId(1)

order

用于根据指定的列对结果集进行排序。默认按照 id 排序。

如果仅用一个字段进行排序,则可以将其指定为字符串。多个字段排序需要指定一个字符串对象数组:

降序: {"field": "-1"} 等价于 field desc

升序: {"field": "1"} 等价于 field asc

limit

返回条目数。默认25条,最大10000条。

offset

偏移量。

ecosystemid

生态系统ID。默认为查询当前生态系统的数据表。

示例
var i int
var ret string

ret = DBFind("contracts").Columns("id,value").Where({id: [{"$gt": 2}, {"$lt": 5}]}).Order("id")
while i < Len(ret) {
    var vals map
    vals = ret[0]
    Println(vals["value"])
    i = i + 1
}
ret = DBFind("contracts").Columns("id,value").WhereId(10).One("value")
if ret != nil {
 Println(ret)
     Println(ret)
 Println(ret)
}

DBRow

根据指定参数从指定数据表中查询数据。返回一个由对象数组 map 组成的数组 array

语法
DBRow(table string)
    [.Columns(columns array|string)]
    [.Where(where map)]
    [.WhereId(id int)]
    [.Order(order array|string)]
    [.Ecosystem(ecosystemid int)] map
table

数据表名称。

columns

返回列的列表。如果未指定,则将返回所有列。

该值为数组或使用逗号分隔的字符串。

where

查询条件。

例如: .Where({name: "John"}) 或者 .Where({"id": {"$gte": 4}})

更多详细信息,请参考 DBFind

id

根据ID查询。例如,.WhereId(1)

order

用于根据指定的列对结果集进行排序。默认按照 id 排序。

更多详细信息,请参考 DBFind

ecosystemid

生态系统ID。默认为查询当前生态系统的数据表。

示例
var ret map
ret = DBRow("contracts").Columns(["id","value"]).Where({id: 1})
Println(ret)

DBSelectMetrics

返回指标的聚合数据。

每生成100个区块时都会更新指标标准。以1天为周期存储聚合数据。

语法
DBSelectMetrics(metric string, timeInterval string, aggregateFunc string) array
metric

指标名称

ecosystem_pages

生态系统页面数。

返回值: key - 生态系统ID, value - 生态系统页面数。

ecosystem_members

生态系统成员数。

返回值: key - 生态系统ID, value - 生态系统成员数。

ecosystem_tx

生态系统交易数。

返回值: key - 生态系统ID, value - 生态系统交易数。

timeInterval

聚合指标数据的时间间隔。例如:1 day30 days

aggregateFunc

聚合函数。例如 max, min, avg

示例
var rows array
rows = DBSelectMetrics("ecosystem_tx", "30 days", "avg")

var i int
while(i < Len(rows)) {
   var row map
   row = rows[i]
   i = i + 1
}

EcosysParam

返回生态系统参数表 parameters 指定参数的值。

语法
EcosysParam(name string) string
name

参数名称。

示例
Println(EcosysParam("founder_account"))

GetHistory

返回指定数据表中条目更改的历史记录。

语法
GetHistory(table string, id int) array
table

数据表名称。

id

条目ID。

返回值

返回 map 类型的对象数组。这些数组指定数据表中条目更改的历史记录。

每个数组都包含下一次更改之前的记录字段。

数组按从最近更改顺序排序。

数组中 id 字段指向 rollback_tx 表的 idblock_id 代表区块ID,block_time 代表区块生成时间戳。

示例
var list array
var item map
list = GetHistory("blocks", 1)
if Len(list) > 0 {
   item = list[0]
}

GetHistoryRow

从指定数据表中指定条目的更改历史记录返回单个快照。

语法
GetHistoryRow(table string, id int, rollbackId int) map
table

数据表名称。

id

条目ID。

rollbackId

rollback_tx 表的条目ID。

$result = GetHistoryRow("contracts",205,2358)

GetColumnType

返回指定表中指定字段的数据类型。

语法
GetColumnType(table, column string) string
table

数据表名称。

column

字段名称。

返回值

返回以下类型:text, varchar, number, money, double, bytes, json, datetime, double

示例
var coltype string
coltype = GetColumnType("members", "member_name")

GetDataFromXLSX

XLSX 电子表格返回数据。

语法
GetDataFromXLSX(binId int, line int, count int, sheet int) string
binId

二进制表 binary 中XLSX格式的ID。

line

开始行数,默认从0开始。

count

需要返回的行数。

sheet

列表编号,默认从1开始。

示例
var a array
a = GetDataFromXLSX(3, 12, 10, 1)

GetRowsCountXLSX

返回指定XLSX文件行数。

语法
GetRowsCountXLSX(binId int, sheet int) int
binId

二进制表 binary 中XLSX格式的ID。

sheet

列表编号,默认从1开始。

示例
var count int
count = GetRowsCountXLSX(binid, 1)

LangRes

返回指定多语言资源。其标签 lang 为双字符语言代码,例如:en, zh。如果所选的语言标签没有语言资源,则返回 en 标签的语言资源。

语法
LangRes(label string, lang string) string
label

语言资源名称。

lang

双字符语言代码。

示例
warning LangRes("@1confirm", "en")
error LangRes("@1problems", "zh")

GetBlock

返回指定区块的相关信息。

语法
GetBlock(blockID int64) map
blockID

区块ID。

返回值

返回一个对象数组:

  • id

    区块ID。

  • time

    区块生成时间戳。

  • key_id

    生成该区块的验证节点账户地址。

示例
var b map
b = GetBlock(1)
Println(b)

DBInsert

添加条目到指定数据表并返回条目的ID。

语法
DBInsert(table string, params map) int
tblname

数据表名称。

params

对象数组,其中键是字段名称,值是插入的值。

示例
DBInsert("mytable", {name: "John Smith", amount: 100})

DBUpdate

更改指定数据表中指定条目ID的列值,如果该表中不存在该条目ID,则返回错误。

语法
DBUpdate(tblname string, id int, params map)
tblname

数据表名称。

id

条目ID。

params

对象数组,其中键是字段名称,值是更改的新值。

示例
DBUpdate("mytable", myid, {name: "John Smith", amount: 100})

DBUpdateExt

更改指定数据表中与查询条件的匹配的列值。

语法
DBUpdateExt(tblname string, where map, params map)
tblname

数据表名称。

where

查询条件。

更多详细信息,请参考 DBFind

params

对象数组,其中键是字段名称,值是更改的新值。

示例
DBUpdateExt("mytable", {id: $key_id, ecosystem: $ecosystem_id}, {name: "John Smith", amount: 100})

DelColumn

删除指定表中的字段。该表必须没有记录。

语法
DelColumn(tblname string, column string)
tblname

数据表名称。

column

需要删除的字段。

DelColumn("mytable", "mycolumn")

DelTable

删除指定的数据表。该表必须没有记录。

语法
DelTable(tblname string)
tblname

数据表名称。

示例
DelTable("mytable")

Append

将任何类型的 val 插入到 src 数组中。

语法
Append(src array, val anyType) array
src

原始数组。

val

需要插入的值。

示例
var list array
list = Append(list, "new_val")

Join

in 数组的元素合并到具有指定 sep 分隔符的字符串中。

语法
Join(in array, sep string) string
in

数组名称。

sep

分隔符。

示例
var val string, myarr array
myarr[0] = "first"
myarr[1] = 10
val = Join(myarr, ",")

Split

使用 sep 分隔符将 in 字符串拆分为元素,并将它们放入数组中。

语法
Split(in string, sep string) array
in

字符串。

sep

分隔符。

示例
var myarr array
myarr = Split("first,second,third", ",")

Len

返回指定数组元素个数。

语法
Len(val array) int
val

数组。

示例
if Len(mylist) == 0 {
  ...
}

Row

The list parameter must not be specified in this case. 返回数组列表的第一个对象数组。如果列表为空,则返回结果为空。该函数主要与 DBFind 函数一起使用,使用时该函数不能指定参数。

语法
Row(list array) map
list

DBFind 函数返回的对象数组。

示例
var ret map
ret = DBFind("contracts").Columns("id,value").WhereId(10).Row()
Println(ret)

One

返回数组列表中第一个对象数组的字段值。如果列表数组为空,则返回nil。该函数主要与 DBFind 函数一起使用,使用时该函数不能指定参数。

语法
One(list array, column string) string
list
  • DBFind 函数返回的对象数组。
column
  • 字段名称。
示例
var ret string
ret = DBFind("contracts").Columns("id,value").WhereId(10).One("value")
if ret != nil {
   Println(ret)
}

GetMapKeys

返回对象数组中的键数组。

语法
GetMapKeys(val map) array
val

对象数组。

示例
var val map
var arr array
val["k1"] = "v1"
val["k2"] = "v2"
arr = GetMapKeys(val)

SortedKeys

返回对象数组中的键数组,该数组已排序。

语法
SortedKeys(val map) array
val

对象数组。

示例
var val map
var arr array
val["k2"] = "v2"
val["k1"] = "v1"
arr = SortedKeys(val)

CallContract

调用指定名称的合约。合约数据部分的所有参数必须列入一个对象数组中。该函数返回指定合约分配给 $result 变量的值。

语法
CallContract(name string, params map)
name

被调用合同的名称。

params

合约输入数据的关联数组。

示例
var par map
par["Name"] = "My Name"
CallContract("MyContract", par)

ContractAccess

检查执行合约的名称是否与参数中列出的名称之一匹配。通常用于控制合约对数据表访问。编辑数据表字段或在表权限部分的插入和新列字段时,在权限字段中指定该函数。

语法
ContractAccess(name string, [name string]) bool
name

合约名称。

示例
ContractAccess("MyContract")
ContractAccess("MyContract","SimpleContract")

ContractConditions

从指定名称的合约中调用 conditions 条件部分。

对于该类的合约,数据部分必须为空。如果条件部分执行没有错误,则返回 true。如果在执行期间生成错误,则父合约也将以此错误结束。该函数通常用于控制合约对表的访问,并且可以在编辑系统表时在权限字段中调用。

语法
ContractConditions(name string, [name string]) bool
name

合约名称。

示例
ContractConditions("MainCondition")

EvalCondition

tablename 表中获取带有 'name' 字段记录中的 condfield 字段的值,并检查 condfield 字段值的条件。

语法
EvalCondition(tablename string, name string, condfield string)
tablename

数据表名称。

name

根据 'name' 字段查询值。

condfield

需要检查条件的字段名称。

示例
EvalCondition(`menu`, $Name, `conditions`)

GetContractById

该函数通过合约ID返回其合约名称。如果找不到合约,则返回空字符串。

语法
GetContractById(id int) string
id

在合约表 contracts 的合约ID。

示例
var name string
name = GetContractById($IdContract)

GetContractByName

该函数通过合约名称返回其合约ID。如果找不到合约,则返回零。

语法
GetContractByName(name string) int
name

在合约表 contracts 的合约名称。

示例
var id int
id = GetContractByName(`NewBlock`)

RoleAccess

检查合约调用者的角色ID是否与参数中指定的ID之一匹配。

使用该函数可以控制对数据表和其他数据的合约访问。

语法
RoleAccess(id int, [id int]) bool
id

角色ID。

示例
RoleAccess(1)
RoleAccess(1, 3)

TransactionInfo

按指定的哈希值查询交易并返回已执行的合约及其参数的信息。

语法
TransactionInfo(hash: string)
hash

十六进制字符串格式的交易哈希。

返回值

该函数返回json格式的字符串:

{"contract":"ContractName", "params":{"key": "val"}, "block": "N"}
contract

合约名称。

params

传递给合约参数的数据。

block

处理该交易的区块ID。

示例
var out map
out = JSONDecode(TransactionInfo(hash))

Throw

生成 exception 异常类型的错误。

语法
Throw(ErrorId string, ErrDescription string)
ErrorId

错误标识符。

ErrDescription

错误描述。

返回值

该类交易结果的格式:

{"type":"exception","error":"Error description","id":"Error ID"}
示例
Throw("Problem", "There is a problem")

ValidateCondition

尝试编译 condition 参数指定的条件。如果在编译过程中发生错误,则生成错误并终止调用合约。该函数旨在检查条件格式的正确性。

语法
ValidateCondition(condition string, state int)
condition

需要验证的条件格式。

state

生态系统ID。如果检查全局条件,请指定为0

示例
ValidateCondition(`ContractAccess("@1MyContract")`, 1)

AddressToId

根据钱包地址返回对应的账户地址。如果指定了无效的地址,则返回 '0'。

语法
AddressToId(address string) int
address

钱包地址 XXXX-...-XXXX 格式或数字形式。

示例
wallet = AddressToId($Recipient)

IdToAddress

根据账户地址返回对应的钱包地址。如果指定了无效的地址,则返回无效地址 'invalid'。

语法
IdToAddress(id int) string
id

账户地址。

示例
$address = IdToAddress($id)

PubToID

通过十六进制编码格式的公钥返回帐户地址。

语法
PubToID(hexkey string) int
hexkey

十六进制编码格式的公钥。

示例
var wallet int
wallet = PubToID("04fa5e78.....34abd6")

DecodeBase64

通过指定base64编码格式返回字符串

语法
DecodeBase64(input string) string
input

base64编码格式字符串。

示例
val = DecodeBase64(mybase64)

EncodeBase64

通过指定字符串返回base64编码格式的字符串。

语法
EncodeBase64(input string) string
input

需要编码的字符串。

示例
var base64str string
base64str = EncodeBase64("my text")

Float

将整数或字符串转换为浮点数。

语法
Float(val int|string) float
val

整数或字符串。

示例
val = Float("567.989") + Float(232)

HexToBytes

将十六进制编码格式的字符串转换为字节类型 bytes

语法
HexToBytes(hexdata string) bytes
hexdata

十六进制编码格式的字符串。

示例
var val bytes
val = HexToBytes("34fe4501a4d80094")

FormatMoney

返回 exp / 10 ^ digit 的字符串值。

语法
FormatMoney(exp string, digit int) string
exp

数字的字符串格式。

digit

exp/10^digit 表达式中10的指数,该值可以是正数或负数。正值决定了小数点后的位数。

示例
s = FormatMoney("78236475917384", 0)

Random

返回min和max之间的随机数(min <= result <max)。min和max都必须是正数。

语法
Random(min int, max int) int
min

随机数的最小值。

max

随机数的上限。生成的随机数将小于该值。

示例
i = Random(10,5000)

Int

将字符串值转换为整数。

语法
Int(val string) int
val

数字的字符串格式。

示例
mystr = "-37763499007332"
val = Int(mystr)

Hash

返回指定字节数组或字符串的哈希,由系统加密库crypto生成的哈希。

语法
Hash(val interface{}) string, error
val

字符串或字节数组。

示例
var hash string
hash = Hash("Test message")

Sha256

返回指定字符串的 SHA256 哈希值。

语法
Sha256(val string) string
val

需要 Sha256 哈希运算的字符串。

示例
var sha string
sha = Sha256("Test message")

Str

将整数 int 或浮点数 float 转换为字符串。

语法
Str(val int|float) string
val

整数或浮点数。

示例
myfloat = 5.678
val = Str(myfloat)

JSONEncode

将数字、字符串或数组转换为JSON格式的字符串。

语法
JSONEncode(src int|float|string|map|array) string
src

需转换的数据。

示例
var mydata map
mydata["key"] = 1
var json string
json = JSONEncode(mydata)

JSONEncodeIndent

使用指定的缩进将数字、字符串或数组转换为JSON格式的字符串。

语法
JSONEncodeIndent(src int|float|string|map|array, indent string) string
src

需要转换的数据。

indent

用作缩进的字符串。

示例
var mydata map
mydata["key"] = 1
var json string
json = JSONEncodeIndent(mydata, "\t")

JSONDecode

将JSON格式的字符串转换为数字,字符串或数组。

语法
JSONDecode(src string) int|float|string|map|array
src

包含JSON格式数据的字符串。

示例
var mydata map
mydata = JSONDecode(`{"name": "John Smith", "company": "Smith's company"}`)

HasPrefix

检查字符串是否以指定的字符串开头。

语法
HasPrefix(s string, prefix string) bool
s

字符串。

prefix

需要检查的前缀。

返回值

如果字符串以指定的字符串开头,则返回 true

示例
if HasPrefix($Name, `my`) {
...
}

Contains

检查字符串是否包含指定的子字符串。

语法
Contains(s string, substr string) bool
s

字符串。

substr

子字符串。

返回值

如果字符串包含子字符串,则返回 true

示例
if Contains($Name, `my`) {
...
}

Replace

把字符串中的 old(旧字符串) 替换成 new(新字符串)。

语法
Replace(s string, old string, new string) string
s

原始字符串。

old

将被替换的子字符串。

new

新字符串。

示例
s = Replace($Name, `me`, `you`)

Size

返回指定字符串的字节数。

语法
Size(val string) int
val

字符串

示例
var len int
len = Size($Name)

Sprintf

该函数根据指定的模板和参数创建一个字符串。

可用的通配符:

  • %d (整数)
  • %s (字符串)
  • %f (浮点型)
  • %v (任何类型)
语法
Sprintf(pattern string, val ...) string
pattern

字符串的模板。

示例
out = Sprintf("%s=%d", mypar, 6448)

Substr

返回从偏移量 offset (默认从0开始计算)开始的指定字符串中获取的子字符串,其最大长度限制为 length

如果偏移量或长度限制小于零、偏移量大于长度限制的值,则返回一个空字符串。

如果偏移量和长度限制的总和大于字符串字节数,则子字符串将从偏移量开始返回到指定字符串的末尾。

语法
Substr(s string, offset int, length int) string
val

字符串。

offset

偏移量。

length

子字符串的长度限制。

示例
var s string
s = Substr($Name, 1, 10)

ToLower

以小写形式返回指定的字符串。

语法
ToLower(val string) string
val

字符串。

示例
val = ToLower(val)

ToUpper

以大写形式返回指定的字符串。

语法
ToUpper(val string) string
val

字符串。

示例
val = ToUpper(val)

TrimSpace

删除指定字符串的前后空格、制表符和换行符号。

语法
TrimSpace(val string) string
val

字符串。

示例
var val string
val = TrimSpace("  mystr  ")

Floor

返回小于或等于指定数字、浮点数和字符串的最大整数值。

语法
Floor(x float|int|string) int
x

数字、浮点数和字符串。

示例
val = Floor(5.6) // returns 5

Log

返回指定数字、浮点数和字符串的自然对数。

语法
Log(x float|int|string) float
x

数字、浮点数和字符串。

示例
val = Log(10)

Log10

返回指定数字、浮点数和字符串的以10为底的对数。

语法
Log10(x float|int|string) float
x

数字、浮点数和字符串。

示例
val = Log10(100)

Pow

返回(xy),y是以x为基数的指数。

语法
Pow(x float|int|string, y float|int|string) float
x

基数。

y

指数。

示例
val = Pow(2, 3)

Round

返回指定数字四舍五入到最近整数的值。

语法
Round(x float|int|string) int
x

数字。

示例
val = Round(5.6)

Sqrt

返回指定数字的平方根。

Sqrt(x float|int|string) float
x

数字。

示例
val = Sqrt(225)

StringToBytes

将字符串转换为字节。

语法
StringToBytes(src string) bytes
src

字符串。

示例
var b bytes
b = StringToBytes("my string")

BytesToString

将字节转换为字符串。

语法
BytesToString(src bytes) string
src

字节。

示例
var s string
s = BytesToString($Bytes)

SysParamString

返回指定平台参数的值。

语法
SysParamString(name string) string
name

参数名称。

示例
url = SysParamString(`blockchain_url`)

SysParamInt

以数字的形式返回指定平台参数的值。

语法
SysParamInt(name string) int
name

参数名称。

示例
maxcol = SysParam(`max_columns`)

DBUpdateSysParam

更新平台参数的值和条件。如果您不需要更改值或条件,请在相应参数中指定空字符串。

语法
DBUpdateSysParam(name, value, conditions string)
name

平台参数名称。

value

参数的新值。

conditions

更改参数的新条件。

示例
DBUpdateSysParam(`fuel_rate`, `400000000000`, ``)

UpdateNotifications

从数据库中获取指定键的通知列表,并将获取的通知发送到Centrifugo。

语法
UpdateNotifications(ecosystemID int, keys int ...)
ecosystemID

生态系统ID。

key

账户地址列表,以逗号分隔。或您可以使用一个数组指定账户地址列表。

示例
UpdateNotifications($ecosystem_id, $key_id, 23345355454, 35545454554)
UpdateNotifications(1, [$key_id, 23345355454, 35545454554] )

UpdateRolesNotifications

获取数据库中指定角色ID的所有账户地址的通知列表,并将获取的通知发送到Centrifugo。

语法
UpdateRolesNotifications(ecosystemID int, roles int ...)
ecosystemID

生态系统ID。

roles

角色ID列表,以逗号分隔。或您可以使用一个数组指定角色ID列表。

示例
UpdateRolesNotifications(1, 1, 2)

HTTPRequest

将HTTP请求发送到指定的地址。

注解

该函数仅可用于VDE合约。

语法
HTTPRequest(url string, method string, heads map, pars map) string
url

发送的请求地址。

method

请求类型(GET或POST)。

heads

请求头,对象数组。

pars

请求参数。

示例
var ret string
    var ret string
var ret string
var pars, heads, json map
heads["Authorization"] = "Bearer " + $auth_token
pars["obs"] = "true"
ret = HTTPRequest("http://localhost:7079/api/v2/content/page/default_page", "POST", heads, pars)
json = JSONToMap(ret)

HTTPPostJSON

该函数类似于 HTTPRequest 函数,但它发送POST请求,请求参数为字符串。

注解

该函数仅可用于VDE合约。

语法
HTTPPostJSON(url string, heads map, pars string) string
url

发送的请求地址。

heads

请求头,对象数组。

pars

请求参数,JSON字符串。

示例
var ret string
    var ret string
var ret string
var heads, json map
heads["Authorization"] = "Bearer " + $auth_token
ret = HTTPPostJSON("http://localhost:7079/api/v2/content/page/default_page", heads, `{"obs":"true"}`)
json = JSONToMap(ret)

BlockTime

以SQL格式返回区块的生成时间。

语法
BlockTime()
示例
var mytime string
mytime = BlockTime()
DBInsert("mytable", myid, {time: mytime})

DateTime

将时间戳unixtime转换为 YYYY-MM-DD HH:MI:SS 格式的字符串。

语法
DateTime(unixtime int) string
示例
DateTime(1532325250)

UnixDateTime

YYYY-MM-DD HH:MI:SS 格式的字符串转换为时间戳unixtime。

语法
UnixDateTime(datetime string) int
示例
UnixDateTime("2018-07-20 14:23:10")

CreateOBS

创建子VDE。

该函数只能在主VDE模式下使用。

语法
CreateOBS(OBSName string, DBUser string, DBPassword string, OBSAPIPort int)
OBSName

VDE的名称。

DBUser

数据库的角色名称。

DBPassword

该角色的密码。

OBSAPIPort

API请求的端口。

示例
CreateOBS("obsname", "obsuser", "obspwd", 8095)

GetOBSList

返回子VDE列表。

该函数只能在主VDE模式下使用。

语法
GetOBSList()
返回值

一个对象数组,其中键是VDE名称和值是进程状态。

RunOBS

运行VDE的进程。

该函数只能在主VDE模式下使用。

语法
RunOBS(OBSName string)
OBSName

VDE名称。

只能包含字母和数字,不能使用空格符号。

StopOBS

停止指定VDE的进程。

该函数只能在主VDE模式下使用。

语法
StopOBS(OBSName string)
OBSName

VDE名称。

只能包含字母和数字,不能使用空格符号。

RemoveOBS

删除指定VDE的进程。

该函数只能在主VDE模式下使用。

语法
RemoveOBS(OBSName string)
OBSName

VDE名称。

只能包含字母和数字,不能使用空格符号。

系统合约

系统合约是在启动 GAChain 平台 时默认创建的。所有这些合约都是在第一个生态系统中创建的,这就是为什么你需要指定其全名来从其他生态系统中调用它们,例如,@1NewContract

NewEcosystem

创建一个新的生态系统,要获取创建的生态系统ID,必须引用在 txstatus 中返回的 result 字段

参数:

  • Name string - 生态系统的名称,可以更改该名称。

EditEcosystemName

更改 1_ecosystems 表中生态系统的名称,该表仅存在于第一个生态系统中。

参数:

  • EcosystemID int - 更改其名称的生态系统ID;
  • NewName string - 生态系统新名称。

NewContract

在当前生态系统中创建一个新合约。

参数:
  • ApplicationId int - 新合约所属的应用程序;
  • Value string - 合约源码,上层必须只有一个合约;
  • Conditions string - 更改该合约的条件;
  • TokenEcosystem int "optional" - 生态系统ID,当合约被激活时,将使用哪种通证进行交易。

EditContract

编辑当前生态系统中的合约。

参数:

  • Id int - 更改的合约ID;
  • Value string "optional" - 合约源码;
  • Conditions string "optional" - 更改该合约的条件。

BindWallet

将合约绑定到当前生态系统中的钱包地址。合同绑定后,该地址将支付执行该合约的费用。 参数:

  • Id int - 要绑定的合约ID。
  • WalletId string "optional" - 合约绑定的钱包地址。

UnbindWallet

从当前生态系统中的钱包地址取消绑定合约,只有已绑定该合约的地址才能取消绑定。合约未绑定后,执行合约的用户将支付执行费用。

参数:

  • Id int - 绑定的合同ID。

NewParameter

早当前生态系统添加了一个新生态系统参数。

参数:

  • Name string - 参数名称;
  • Value string - 参数值;
  • Conditions string - 更改参数的条件。

EditParameter

更改当前生态系统中的现有生态系统参数。

参数:

  • Name string - 要更改的参数名称;
  • Value string - 新参数值;
  • Conditions string - 更改参数的新条件。

NewMenu

在当前生态系统中添加一个新菜单。

参数:

  • Name string - 菜单名称;
  • Value string - 菜单源码;
  • Title string "optional" - 菜单标题;
  • Conditions string - 更改菜单的条件。

EditMenu

更改当前生态系统中的现有菜单。

参数:

  • Id int - 要更改的菜单ID;
  • Value string "optional" - 新菜单源码;
  • Title string "optional" - 新菜单标题;
  • Conditions string "optional" - 更改菜单的新条件。

AppendMenu

将源码内容添加到当前生态系统中的现有菜单

参数:

  • Id int - 菜单ID;
  • Value string - 要添加的源码。

NewPage

在当前生态系统中添加新页面。

参数:

  • Name string - 页面名称;
  • Value string - 页面源码;
  • Menu string - 关联该页面的菜单名称;
  • Conditions string - 更改页面的条件;
  • ValidateCount int "optional" - 页面验证所需的节点数。如果未指定此参数,则使用 min_page_validate_count 生态系统参数值。该值不能小于 min_page_validate_count 且大于 max_page_validate_count
  • ValidateMode int "optional" - 页面有效性检查模式。值为0表示在加载页面时检查页面。值为1表示在加载和离开页面时检查页面。

EditPage

更改当前生态系统中的现有页面。

参数:

  • Id int - 要更改的页面ID;
  • Value string "optional" - 新页面源码;
  • Menu string "optional" - 关联该页面的新菜单名称;
  • Conditions string "optional" - 更改页面的新条件;
  • ValidateCount int "optional" - 页面验证所需的节点数。如果未指定此参数,则使用 min_page_validate_count 生态系统参数值。该值不能小于 min_page_validate_count 且大于 max_page_validate_count
  • ValidateMode int "optional" - 页面有效性检查模式。值为0表示在加载页面时检查页面。值为1表示在加载和离开页面时检查页面。

AppendPage

将源码内容添加到当前生态系统中的现有页面。

参数:

  • Id int - 要更改的页面ID;
  • Value string - 要添加的源码。

NewBlock

在当前生态系统添加一个页面模块。

参数:

  • Name string - 模块名称;
  • Value string - 模块源码;
  • Conditions string - 更改模块的条件。

EditBlock

更改当前生态系统中的现有页面模块。

Parameters

  • Id int - 要更改的模块ID;
  • Value string - 新模块源码;
  • Conditions string - 更改模块的新条件。

NewTable

在当前生态系统中添加一个新数据表。

参数:
  • ApplicationId int - 关联数据表的应用程序ID;

  • Name string - 新数据表名称;

  • Columns string - JSON格式的字段数组 [{"name":"...", "type":"...","index": "0", "conditions":"..."},...],其中

    • name - 字段名称,仅限拉丁字符;
    • type - 数据类型 varchar,bytea,number,datetime,money,text,double,character
    • index - 非主键字段 0,主键 1
    • conditions - 更改字段中数据的条件,必须以JSON格式指定访问权限 {"update":"ContractConditions(`MainCondition`)", "read":"ContractConditions(`MainCondition`)"}
  • Permissions string - JSON格式访问权限 {"insert": "...", "new_column": "...", "update": "...", "read": "..."}

    • insert - 插入条目的权限;
    • new_column - 添加新列的权限;
    • update - 更改条目数据的权限;
    • read - 读取条目数据的权限。

EditTable

更改当前生态系统中数据表的访问权限。

参数:

  • Name string - 数据表名称。
  • InsertPerm string - 将条目插入数据表中的权限;
  • UpdatePerm string - 更新表中条目的权限;
  • ReadPerm string - 读取表中条目的权限;
  • NewColumnPerm string -创建新列的权限;

NewColumn

在当前生态系统的数据表中添加一个新字段。

参数:

  • TableName string - 数据表名称;
  • Name string - 拉丁字符字段名称;
  • Type string - 数据类型 varchar,bytea,number,money,datetime,text,double,character
  • UpdatePerm string - 更改列中值的权限;
  • ReadPerm string - 读取列中值的权限。

EditColumn

更改当前生态系统中的指定数据表字段的权限。

参数:

  • TableName string - 数据表名称;
  • Name string - 要更改的拉丁字符字段名称;
  • UpdatePerm string - 更改列中值的新权限;
  • ReadPerm string - 读取列中值的新权限。

NewLang

在当前生态系统中新增多语言资源,添加权限在生态系统参数的 changing_language 参数中设置。

参数:

  • Name string - 拉丁字符多语言资源的名称;
  • Trans string - JSON格式的字符串,双字符语言代码作为键,翻译字符串作为值。例如, {"en": "English text", "zh": "Chinese text"}

EditLang

更改当前生态系统中的语言资源。更改权限在生态系统参数的 changing_language 参数中设置。

参数:

  • Id int - 多语言资源ID。
  • Trans - JSON格式的字符串,双字符语言代码作为键,翻译字符串作为值。例如,{"en": "English text", "zh": "Chinese text"}

Import

将应用程序导入当前生态系统。导入来自 ImportUpload 合约加载的数据。

参数:

  • Data string - 以文本内容格式导入的数据,该数据来自生态系统导出的文件。

ImportUpload

将外部应用程序文件加载到当前生态系统的 buffer_data 表中,以便后续导入。

参数:

  • InputFile file - 写入当前生态系统的 buffer_data 表的文件。

NewAppParam

当前生态系统添加新的应用程序参数。

参数:

  • ApplicationId int - 应用程序ID;
  • Name string - 参数名称;
  • Value string - 参数值;
  • Conditions string - 更改参数的权限。

EditAppParam

更改当前生态系统中的现有应用程序参数。

参数:

  • Id int - 应用程序参数ID;
  • Value string "optional" - 新参数值;
  • Conditions string "optional" - 更改参数的新权限。

NewDelayedContract

向延迟调度合约守护进程添加新任务。

延迟调度合约守护进程运行当前生成的区块所需的合约。

参数:

  • Contract string - 合约名称;
  • EveryBlock int - 合约将在指定每隔区块数量后执行;
  • Conditions string - 更改任务的权限;
  • BlockID int "optional" - 开始启动合约的区块ID,如果未指定,则自动计算“当前区块ID”+ EveryBlock
  • Limit int "optional" - 任务的启动次数,如果未指定,则启动合约的任务将无限次执行。

EditDelayedContract

更改延迟调度合约守护进程中的任务。

参数:

  • Id int - 任务ID。
  • Contract string - 合约名称。
  • EveryBlock int - 合约将在指定每隔区块数量后执行;
  • Conditions string - 更改任务的权限;
  • BlockID int "optional" - 开始启动合约的区块ID,如果未指定,则自动计算“当前区块ID”+ EveryBlock
  • Limit int "optional" - 任务的启动次数,如果未指定,则启动合约的任务将无限次执行。
  • Deleted int "optional" - 任务切换。值为 1 将禁用该任务。值为 0 将启用该任务。

UploadBinary

X_binaries 表中添加或覆盖静态文件。通过HTTP API调用合约时,请求格式必须使用 multipart/form-data;该DataMimeType参数将与表单数据一起使用。

参数:

  • Name string - 静态文件名称;
  • Data bytes - 静态文件的内容;
  • DataMimeType string "optional" - mime-type 文件格式的静态文件;
  • ApplicationId int - 关联 X_binaries 表的应用程序ID。

如果未传递 DataMimeType 参数,则默认使用 application/octet-stream 格式。

模版语言

页面构建

Govis软件客户端的集成开发环境使用 JavaScript React库 创建,包括页面编辑器和可视化页面设计器。页面是应用程序的基本组成部分,它提供从数据库表中检索和显示数据,创建用于接收用户输入数据的表单,将数据传递给合约以及在应用程序页面之间导航。页面和合约一样,都存储在区块链中,这可以确保在软件客户端中加载它们时防止篡改。

模版引擎

页面元素(页面和菜单)是由开发者在Govis软件客户端的页面编辑器中使用模版语言在验证节点的 模版引擎 中形成的。所有页面均使用由 GAChain 平台开发团队开发的 GAStyle 语言构建。使用 content/... API命令从网络上的节点请求页面。模版引擎作为对此类请求的回复发送的内容不是HTML页面,而是由HTML标记组成的JSON代码,这些标记根据模版结构形成树 模版引擎对此类请求的响应发送的不是HTML页面,而是由HTML标记组成的JSON代码,这些标记根据模版结构形成树。如果想要测试模版引擎可参考 content API接口。

创建页面

可以使用页面编辑器创建和编辑页面,该编辑器可在Govis的管理工具的 页面Pages 部分中找到。该编辑器提供:

  • 编写页面代码,突出显示 GAStyle 模版语言的关键字;
  • 选择菜单,这些菜单将显示在页面上;
  • 编辑菜单页面;
  • 配置更改页面的权限,通过在 ContractConditions 函数中指定具有权限的合约名称,或通过在 更改条件Change conditions 中直接指定访问权限;
  • 启动可视化页面设计器;
  • 页面预览。
可视化页面设计器

可视化页面设计器可以创建页面设计而无需借助 GAStyle 语言中的界面代码。视图化Designer可以使用拖放操作在页面上设置表单元素和文本的位置,以及配置页面块大小。视图化提供了一组用于显示标准数据模型的即用型块:带有标题,表单和信息面板。在视图化中创建页面后,可在页面编辑器中编写接收数据和条件结构的程序逻辑。未来我们计划创建一个更加完整的可视化页面设计器。

样式使用

默认使用Angular的Bootstrap Angle类样式风格显示页面。如果需要,用户可以创建自己的样式。样式存储在生态系统参数表的样式参数 stylesheet 中。

页面模块

要在多个页面中使用代码片段,可以创建页面模块并将其嵌入到页面代码。在Govis的 模块Blocks 中可创建和编辑这些页面模块。和页面一样,可定义编辑权限。

多语言资源编辑器

Govis软件客户端包括一个使用 GAStyle 模版语言的函数 LangRes 进行页面本地化的机制。它将页面上的语言资源标签替换为用户在软件客户端或浏览器中选择的语言对应的文本行。 可以使用简短的语法 $lable$ 代替 LangRes 函数。由合约发起的弹出窗口中的消息翻译是由 GALang 语言的 LangRes 函数执行的。

可以在Govis软件客户端的 多语言资源Language resources 部分中创建和编辑语言资源。语言资源由一个标签名称和该名称在不同语言中的翻译组成,并标记相应的双字符语言标识符(EN、ZH、JP等)。

可以使用与其他数据表相同的方式定义添加和更改语言资源的权限。

GAStyle 模版语言

GAStyle 函数提供以下操作:

  • 从数据库中检索值:DBFind,将从数据库检索的数据表示为表格和图表;
  • 分配和显示变量值的数据操作:SetVar, GetVar, Data
  • 显示和比较日期/时间值:DateTime, Now, CmpTime
  • 使用各种用户数据输入字段构建表单:Form, ImageInput, Input, RadioGroup, Select
  • 通过显示错误消息验证表单字段中的数据:Validate, InputErr
  • 导航元素的显示:AddToolButton, LinkPage, Button
  • 调用合约:Button
  • 创建HTML页面布局元素,包括各种标签,可选择指定css类: Div, P, Span,
  • 将图像嵌入页面并上传图像:Image, ImageInput
  • 页面布局片段的条件显示: If, ElseIf, Else
  • 创建多级菜单;
  • 页面本地化。

GAStyle 概述

GAStyle 页面模版语言是一种函数式语言,允许使用函数调用函数 FuncName(parameters),并将函数嵌套到彼此中。可以指定参数而不带引号,可以删除不必要的参数。

Text FuncName(parameter number 1, parameter number 2) another text.
FuncName(parameter 1,,,parameter 4)

如果参数包含逗号,应将其括在引号(后引号或双引号)中。如果一个函数只能有一个参数,可以在其中使用逗号而不带引号。此外,如果参数具有不成对的右括号,应使用引号。

FuncName("parameter number 1, the second part of first paremeter")
FuncName(`parameter number 1, the second part of first paremeter`)

如果将参数放在引号中,但参数本身包含引号,可以在文本中使用不同类型的引号或多个引号。

FuncName("parameter number 1, ""the second part of first"" paremeter")
FuncName(`parameter number 1, "the second part of first" paremeter`)

在函数定义中,每个参数都有一个特定的名称。您可以按声明的顺序调用函数和指定参数,或者按名称的任意顺序指定任何参数集:Parameter_name: Parameter_value。该方法允许安全地添加新的函数参数,而不会破坏与当前模版的兼容性:

FuncName(myclass, This is value, Div(divclass, This is paragraph.))
FuncName(Body: Div(divclass, This is paragraph.))
FuncName(myclass, Body: Div(divclass, This is paragraph.))
FuncName(Value: This is value, Body:
     Div(divclass, This is paragraph.)
)
FuncName(myclass, Value without Body)

函数可以返回文本,生成HTML元素(例如,Input),或者创建具有嵌套HTML元素的HTML元素(Div,P,Span)。在后一种情况下,使用具有预定义名称 Body 的参数来定义嵌套元素。例如,在另一个div中嵌套两个div如下所示:

Div(Body:
   Div(class1, This is the first div.)
   Div(class2, This is the second div.)
)

要定义 Body 参数中描述的嵌套元素,可以使用以下表示:FuncName(...){...}。嵌套元素用花括号指定:

Div(){
   Div(class1){
      P(This is the first div.)
      Div(class2){
          Span(This is the second div.)
      }
   }
}

如果需要连续多次指定相同的函数,则可以使用点号 . 而不是每次都写入函数名。例如,以下是相同的:

Span(Item 1)Span(Item 2)Span(Item 3)
Span(Item 1).(Item 2).(Item 3)

该语言可以使用 SetVar 函数分配变量,引用变量值使用 #name#

SetVar(name, My Name)
Span(Your name: #name#)

要引用生态系统的语言资源,可以使用 $langres$,其中 langres 是语言源的名称。

Span($yourname$: #name#)

预定义了以下变量:

  • #key_id# - 当前用户的帐户地址;
  • #ecosystem_id# - 当前生态系统ID;
  • #guest_key# - 访客账户地址;
  • #isMobile# - 如果Govis客户端在移动设备上运行,则为1。
使用PageParams将参数传递给页面

有很多函数都支持 PageParams 参数,该参数用于在重定向到新页面时传递参数。例如:PageParams: "param1=value1,param2=value2"。参数值既可以是简单的字符串,也可以是具有引用值的变量。将参数传递给页面时,会创建带参数名称的变量。例如,#param1##param2#

  • PageParams: "hello=world" - 新页面以world为值接收hello参数;
  • PageParams: "hello=#world#" - 新页面接收带有world变量值的hello参数。

此外,Val 函数允许从表单中获取数据,这些数据是在重定向中指定的。

  • PageParams: "hello=Val(world)" - 新页面接收带有world表单元素值的hello参数。
调用合约

GAStyle 通过单击表单中的按钮 Button 函数来实现合约调用。一旦启动该事件,用户在页面表单字段中输入的数据将传递给合约,如果表单字段的名称对应于被调用合约的数据部分中的变量名称,则会自动传输数据。Button 函数允许打开一个模式窗口,用于用户验证合约执行,并在成功执行合约后启动重定向到指定页面的操作,并将某些参数传递到该页面。

GAStyle 函数参考

Address

该函数返回指定账户地址的钱包地址 xxxx-xxxx-...-xxxx;如果没有指定地址,以当前用户的账户地址作为参数。

语法
Address(account)
Address
account

账户地址。

示例
Span(Your wallet: Address(#account#))

AddressToId

该函数返回指定钱包地址 xxxx-xxxx-...-xxxx 的账户地址。

语法
AddressToId(Wallet)
AddressToId
Wallet

钱包地址 XXXX-...-XXXX 格式。

示例
AddressToId(#wallet#)

AddToolButton

创建一个 addtoolbutton 元素的按钮面板。

语法
AddToolButton(Title, Icon, Page, PageParams)
    [.Popup(Width, Header)]
AddToolButton
Title

按钮标题。

Icon

按钮图标样式。

Page

跳转的页面名称。

PageParams

传递给页面的参数。

Popup

弹出模态窗口。

Header

窗口标题。

Width

窗口宽度百分比。

该参数的值范围是1到100。

示例
AddToolButton(Title: $@1broadcast$, Page: @1notifications_broadcast, Icon: icon-plus).Popup(Header: $@1notifications_broadcast$, Width: "50")

And

该函数返回执行 and 逻辑运算的结果,括号中列出的所有参数以逗号分隔。如果有一个参数为空字符串、零或 false,参数值为 false,其他情况参数值为 true。如果参数值为 true,则该函数返回 1,其他情况返回 0

语法
And(parameters)
示例
If(And(#myval1#,#myval2#), Span(OK))

AppParam

输出应用程序参数值,该值取自当前生态系统的 app_params 表。如果存在具有指定定名称的语言资源,其值将自动替换。

语法
AppParam(App, Name, Index, Source)
AppParam
App

应用程序ID。

Name

参数名称。

Index

当参数值是以逗号分隔的列表时,可以使用该参数。

参数元素的索引,从1开始。例如,如果 type = full,light,那么 AppParam(1, type, 2) 返回 light

该参数不能与 Source 参数一起使用。

Source

当参数值是以逗号分隔的列表时,可以使用该参数。

创建 data 对象,该对象的元素是指定参数的值。该对象可用作 TableSelect 函数的数据源。

该参数不能与 Index 参数一起使用。

示例
AppParam(1, type, Source: mytype)

ArrayToSource

创建一个 arraytosource 元素,并用JSON数组的键值对填充它。得到的数据被放入 Source 元素,该元素稍后可以在源输入的函数中使用(例如 Table)。

语法
ArrayToSource(Source, Data)
ArrayToSource
Source

数据源名称。

Data

JSON数组或包含JSON数组的变量名称(#name#)。

示例
ArrayToSource(src, #myjsonarr#)
ArrayToSource(dat, [1, 2, 3])

Binary

返回存储在二进制表 binaries 中的静态文件的链接。

语法
Binary(Name, AppID, MemberID)[.ById(ID)][.Ecosystem(ecosystem)]
Binary
Name

文件名称。

AppID

应用程序ID。

MemberID

账户地址,默认0。

ID

静态文件ID。

ecosystem

生态系统ID。如果未指定该参数,从当前生态系统请求二进制文件。

示例
Image(Src: Binary("my_image", 1))
Image(Src: Binary().ById(2))
Image(Src: Binary().ById(#id#).Ecosystem(#eco#))

Button

创建一个 button HTML元素。该元素创建一个按钮,用于调用合约或打开页面。

语法
Button(Body, Page, Class, Contract, Params, PageParams)
    [.CompositeContract(Contract, Data)]
    [.Alert(Text, ConfirmButton, CancelButton, Icon)]
    [.Popup(Width, Header)]
    [.Style(Style)]
    [.ErrorRedirect((ErrorID,PageName,PageParams)]
Button
Body

子文本或元素。

Page

重定向的页面名称。

Class

按钮类。

Contract

调用的合约名称。

Params

传递给合约的值列表。通常情况下,合约参数的值(data 部分)是从具有相似名称的 id 的HTML元素(例如输入字段)中获得。如果元素 id 与合约参数的名称不同,那么应该使用 contractField1=idname1, contractField2=idname2 格式赋值。该参数作为对象 {contractField1: idname1, contractField2: idname2} 返回给 attr

PageParams

传递给重定向页面的参数的格式 pageField1=idname1, pageField2=idname2。目标页面参数名称为 #pageField1#pageField2 的变量在目标页面上创建,并分配指定的值。更多参数传递规范 使用PageParams将参数传递给页面)。

CompositeContract

用于为按钮添加额外合约。CompositeContract可以多次使用。

Name

合约名称。

Data

合约参数为JSON数组。

Alert

显示消息。

Text

消息文本。

ConfirmButton

确认按钮标题。

CancelButton

取消按钮标题。

Icon

按钮图标。

Popup

输出模态窗口。

Header

窗口标题。

Width

窗口宽度百分比。

该参数的值范围是1到100。

Style

指定CSS样式。

Style

CSS样式。

ErrorRedirect

指定一个重定向页面,当:ref:galang-Throw 函数在合约执行期间生成错误时,将使用该重定向页面。可以有几个 ErrorRedirect 调用。因此返回*errredirect*属性时,其属性的键为 ErrorID ,值为参数列表。

ErrorID

错误ID。

PageName

重定向页面的名称。

PageParams

传递给该页面的参数。

示例
Button(Submit, default_page, mybtn_class).Alert(Alert message)
Button(Contract: MyContract, Body:My Contract, Class: myclass, Params:"Name=myid,Id=i10,Value")

Calculate

该函数返回 Exp 参数中传递的算术表达式的结果。可以使用以下操作:+, -, *, / 和括号 ()

语法
Calculate(Exp, Type, Prec)
Calculate
Exp

算术表达式。可以包含数字和 #name# 变量。

Type

结果数据类型:int, float, money。如果未指定,如果有带小数点的数字,则结果类型为 float,其他情况则为 int

Prec

floatmoney 类型指定小数点后的有效位数。

示例
Calculate( Exp: (342278783438+5000)\*(#val#-932780000), Type: money, Prec:18 )
Calculate(10000-(34+5)\*#val#)
Calculate("((10+#val#-45)\*3.0-10)/4.5 + #val#", Prec: 4)

Chart

创建HTML图表。

语法
Chart(Type, Source, FieldLabel, FieldValue, Colors)
Chart
Type

图表类型。

Source

数据源的名称,例如,从 DBFind 函数获取。

FieldLabel

标头的字段的名称。

FieldValue

值的字段的名称。

Colors

颜色列表。

示例
Data(mysrc,"name,count"){
    John Silver,10
    "Mark, Smith",20
    "Unknown ""Person""",30
}
Chart(Type: "bar", Source: mysrc, FieldLabel: "name", FieldValue: "count", Colors: "red, green")

CmpTime

该函数比较相同格式的两个时间值。

格式支持 unixtime,YYYY-MM-DD HH:MM:SS 和任意时间格式,例如从年到秒 YYYYMMDD

语法
CmpTime(Time1, Time2)
返回值
  • -1 - Time1 < Time2;
  • 0 - Time1 = Time2;
  • 1 - Time1 > Time2。
示例
If(CmpTime(#time1#, #time2#)<0){...}

Code

创建用于显示指定代码的 code 元素。

该函数用变量的值替换变量(例如 #name#)。

语法
Code(Text)
Code
Text

源代码。

示例
Code( P(This is the first line.
    Span(This is the second line.))
)

CodeAsIs

创建用于显示指定代码的 code 元素。

此函数不会将变量替换为其值。例如,#name# 将按原样显示。

语法
CodeAsIs(Text)
CodeAsIs
Text

源代码。

示例
CodeAsIs( P(This is the #test1#.
    Span(This is the #test2#.))
)

Data

创建一个 data 元素并用指定的数据填充它并放入 Source 中,然后可以在 Table 和其他函数中接收 Source 作为数据输入。列名序列对应于 data 条目值的序列。

语法
Data(Source,Columns,Data)
    [.Custom(Column){Body}]
Data
Source

数据源名称。您可以指定任何名称,稍后可以将其作为数据源传递到其他函数中。

Columns

列名的列表,以逗号分隔。

Data

数据集。

每行一条记录。列值必须用逗号分隔。DataColumns 应设置相同的顺序。

对于带有逗号的值,将该值放在双引号中 ("example1, example2", 1, 2)。 对于带引号的值,将该值放在两个双引号中 ("""example", "example2""", 1, 2)。

Custom

可以为 Data 分配计算列。例如,您可以为按钮和其他页面布局元素指定字段模版。这些字段模版通常分配给 Table 和其他函数来接收数据。

如果想要分配多个计算列,请使用多个 Custom 函数。

Column

列名。必须指定唯一名称。

Body

代码片段。您可以使用 #columnname# 从该条目中的其他列获取值,然后在代码片段中使用这些值。

示例
Data(mysrc,"id,name"){
"1",John Silver
2,"Mark, Smith"
3,"Unknown ""Person"""
 }.Custom(link){Button(Body: View, Class: btn btn-link, Page: user, PageParams: "id=#id#"}

DateTime

以指定格式显示时间和日期。

语法
DateTime(DateTime, Format)
DateTime
DateTime

以unixtime或标准格式表示时间和日期 2006-01-02T15:04:05

Format

格式模版: 2位数年份格式 YY,4位数年份格式 YYYY,月份 MM,天数 DD,小时 HH,分钟 MM,秒数 SS,例如:YY/MM/DD HH:MM

如果没有指定或缺少该参数,将使用 YYYY-MM-DD HH:MI:SS 格式。

示例
DateTime(2017-11-07T17:51:08)
DateTime(#mytime#,HH:MI DD.MM.YYYY)

DBFind

创建 dbfind 元素,用 table 表的数据填充它并将其放到 Source 结构中。该 Source 结构可以在随后用于 Table 和其他函数 Source 的输入数据。

语法
DBFind(table, Source)
    [.Columns(columns)]
    [.Where(conditions)]
    [.WhereId(id)]
    [.Order(name)]
    [.Limit(limit)]
    [.Offset(offset)]
    [.Count(countvar)]
    [.Ecosystem(id)]
    [.Cutoff(columns)]
    [.Custom(Column){Body}]
    [.Vars(Prefix)]
DBFind
table

数据表名称。

Source

数据源名称。

Columns
columns

返回的字段列表,如果未指定,将返回所有字段。如果存在JSON类型的字段,可以使用以下语法来处理记录字段:columnname->fieldname。在这种情况下,生成的字段名称为 columnname.fieldname

Where
conditions

数据查询条件。请参阅 DBFind

如果存在JSON类型的字段,可以使用以下语法来处理记录字段:columnname->fieldname

WhereId

根据ID查询,例如,.WhereId(1)

id

条目ID。

Order

按字段排序。

有关排序语法的详细信息,请参阅 DBFind

name

字段名称

Limit
limit

返回的条目数。默认为25条,最大数为10000条。

Offset
offset

偏移量。

Count

指定 Where 条件的总行数。

除了存储在变量中之外,还会在 dbfind 元素的 count 参数中返回总计数。

如果未指定 WhereWhereID,将返回数据表的总行数。

countvar

保存行计数的变量名称。

Ecosystem
id

生态系统ID。默认情况下,数据来自当前生态系统中的指定表。

Cutoff

用于剪切和显示大量文本数据。

columns

由逗号分隔的字段列表,这些字段必须由 Cutoff 函数处理。

字段值被一个JSON对象替换,该对象有两个字段: 链接 link 和标题 title 。如果字段值大于32个字符,则返回指向全文前32个字符的 link。如果值为32个字符且更短,则 link 为空,title 包含完整的字段值。

Custom

可以为 Data 分配计算列。例如,您可以为按钮和其他页面布局元素指定字段模版。这些字段模版通常分配给 Table 和其他函数来接收数据。

如果想要分配多个计算列,请使用多个 Custom 函数。

Column

列名。必须指定唯一名称。

Body

代码片段。您可以使用 #columnname# 从该条目中的其他列获取值,然后在代码片段中使用这些值。

Vars

通过查询获得的第一行生成一组具有值的变量。当指定这个函数时,Limit 参数自动变为1,并且只返回一条记录。

Prefix

添加到变量名称的前缀。格式为 #prefix_columnname#,其中列名紧跟下划线符号。如果有包含JSON字段的列,那么生成的变量将采用以下格式:#prefix_columnname_field#

Example

DBFind(parameters,myparam)
DBFind(parameters,myparam).Columns(name,value).Where({name:"money"})
DBFind(parameters,myparam).Custom(myid){Strong(#id#)}.Custom(myname){
   Strong(Em(#name#))Div(myclass, #company#)
}

Div

创建 div HTML元素。

语法
Div(Class, Body)
    [.Style(Style)]
    [.Show(Condition)]
    [.Hide(Condition)]
Div
Class

div 的类名。

Body

子元素。

Style

指定CSS样式。

Style

CSS样式。

Show
定义显示Div的条件。
Condition

见下面 Hide

Hide

定义隐藏Div的条件。

Condition

表达式格式 InputName=Value,当所有表达式都为真时,Condition 为真,当 InputName 的值等于 ValueCondition 为真。如果调用了多个 ShowHide,则至少有一个 Condition 参数必须为真。

示例
Form(){
    Div(text-left){
        Input(Name: "broadcast", Type: "checkbox", Value: "false")
    }
    Div(text-left){
        hello
    }.Show("broadcast=false")
    Div(text-left){
        world
    }.Hide("broadcast=false")
}

EcosysParam

该函数从当前生态系统的生态系统参数表中获取参数值。如果返回结果名称有语言资源,则会相应地进行翻译。

语法
EcosysParam(Name, Index, Source)
EcosysParam
Name

参数名称。

Index

如果请求的参数是以逗号分隔的元素列表,可以指定从1开始的索引。例如,如果 gender = male,female,那么 gender = male,female 返回 female

该参数不能与 Source 参数一起使用。

Source

当参数值是以逗号分隔的列表时,可以使用该参数。

创建 data 对象,该对象的元素是指定参数的值。该对象可用作 TableSelect 函数的数据源。

该参数不能与 Index 参数一起使用。

Address(EcosysParam(founder_account))
EcosysParam(gender, Source: mygender)

EcosysParam(Name: gender_list, Source: src_gender)
Select(Name: gender, Source: src_gender, NameColumn: name, ValueColumn: id)

Em

创建 em HTML元素。

语法
Em(Body, Class)
Em
Body

子文本或元素。

Class

em 的类名。

示例
This is an Em(important news).

ForList

Body 中设置的模版格式显示 Source 数据源中的元素列表,并创建 forlist 元素。

语法
ForList(Source, Index){Body}
ForList
Source

DBFindData 函数获取的数据源。

Index

迭代计数器的变量。计数从1开始。

可选参数。如果未指定,则将迭代计数值写入 [Source] _index 变量。

Body

用于插入元素的模版。

ForList(mysrc){Span(#mysrc_index#. #name#)}

Form

创建 form HTML元素。

语法
Form(Class, Body) [.Style(Style)]
Form
Body

子文本或元素。

Class

form 的类名。

Style

指定CSS样式。

Style

CSS样式。

示例
Form(class1 class2, Input(myid))

GetColumnType

返回指定数据表的字段数据类型。

返回以下类型:text, varchar, number, money, double, bytes, json, datetime, double

语法
GetColumnType(Table, Column)
GetColumnType
Table

数据表名称。

Column

字段名称。

示例
SetVar(coltype,GetColumnType(members, member_name))Div(){#coltype#}

GetHistory

创建 gethistory 元素,使用指定数据表的条目的历史更改记录来填充它。生成的数据将放入 Source 元素中。该元素稍后可以在源输入的函数中使用(例如 Table)。

数组按从最近更改顺序排序。

数组中 id 字段指向 rollback_tx 表的 idblock_id 代表区块ID,block_time 代表区块生成时间戳。

语法
GetHistory(Source, Name, Id, RollbackId)
GetHistory
Source

数据源名称。

Name

数据表名称。

Id

条目ID。

RollbackId

可选参数。如果指定,只从 rollback_tx 表返回一个具有指定ID的记录。

示例
GetHistory(blocks, BlockHistory, 1)

GetVar

该函数返回已存在的指定变量值,如果不存在则返回空字符串。

只有在请求编辑树时,才会创建 getvar 元素。GetVar(varname)#varname 间的区别是,如果 varname 不存在,GetVar 将返回一个空字符串,而 #varname# 将被解释为一个字符串值。

语法
GetVar(Name)
GetVar
Name

变量名称。

示例
If(GetVar(name)){#name#}.Else{Name is unknown}

Hint

创建 hint 元素,用于提示。

语法
Hint(Icon,Title,Text)
Hint
Icon

图标名称。

Title

提示标题。

Text

提示文本。

示例
Hint(Icon: "icon-wrench",Title:$@1pa_settings$,Text: This is a hint text)

If

条件声明。

返回满足 Condition 的第一个 IfElseIf 的子元素。否则返回 Else 的子元素。

语法
If(Condition){ Body }
    [.ElseIf(Condition){ Body }]
    [.Else{ Body }]
If
Condition

如果条件等于 空字符串0false,则认为该条件未满足。在所有其他情况下,该条件被认为是满足的。

Body

子元素。

示例
If(#value#){
   Span(Value)
}.ElseIf(#value2#){Span(Value 2)
}.ElseIf(#value3#){Span(Value 3)}.Else{
   Span(Nothing)
}

Image

创建 image HTML元素。

语法
Image(Src, Alt, Class)
    [.Style(Style)]
Image
Src

图像源,文件或 data:...

Alt

无法显示图像时的替代文本。

Сlass

图像类名。

示例
Image(Src: Binary().ById(#id#), Class: preview).Style(height: 40px; widht 40px;)

ImageInput

为图像上传创建 imageinput 元素。

语法
ImageInput(Name, Width, Ratio, Format)
ImageInput
Name

元素名称。

Width

裁剪图像的宽度。

Ratio

宽高比或图像高度。

Format

上传图像的格式。

示例
ImageInput(avatar, 100, 2/1)

Include

将具有指定名称的模版插入到页面代码中。

语法
Include(Name)
Include
Name

模版名称。

示例
Div(myclass, Include(mywidget))

Input

创建 input HTML元素。

语法
Input(Name, Class, Placeholder, Type, Value, Disabled)
    [.Validate(validation parameters)]
    [.Style(Style)]
Input
Name

元素名称。

Class

类名。

Placeholder

输入字段预期值的提示信息。

Type

input 类型。

Value

元素值。

Disabled

禁用 input 元素。

Validate

验证参数。

Style

指定CSS样式。

Style

CSS样式。

示例
Input(Name: name, Type: text, Placeholder: Enter your name)
Input(Name: num, Type: text).Validate(minLength: 6, maxLength: 20)

InputErr

创建 inputerr 元素,用于验证错误文本。

语法
InputErr(Name,validation errors)]
InputErr
Name

对应于 Input 元素的名称。

validation errors

一个或多个参数的验证错误消息。

示例
InputErr(Name: name,
    minLength: Value is too short,
    maxLength: The length of the value must be less than 20 characters)

InputMap

创建地址文本输入字段。提供在地图上选择坐标的功能。

语法
InputMap(Name, Type, MapType, Value)
InputMap
Name

元素名称。

Value

默认值。

该值是字符串格式的对象。例如,{"coords":[{"lat":number,"lng":number},]}{"zoom":int, "center":{"lat":number,"lng":number}}。当使用预定义的 Value 创建InputMap时,地址字段可用于保存地址值,因此地址字段不为空。

Type

地图标点测绘类型:

  • polygon - 表示多点闭环的面积;
  • Line - 表示多点无闭环的折线;
  • Point - 表示单点坐标。
MapType

地图类型。

该参数有以下值: hybrid, roadmap, satellite, terrain

示例
InputMap(Name: Coords,Type: polygon, MapType: hybrid, Value: `{"zoom":8, "center":{"lat":55.749942860682545,"lng":37.6207172870636}}`)

JsonToSource

创建一个 jsontosource 元素,并用JSON数组的键值对填充它。得到的数据被放入 Source 元素,该元素稍后可以在源输入的函数中使用(例如 Table)。

结果数据中的记录按JSON键的字母顺序排序。

语法
JsonToSource(Source, Data)
JsonToSource
Source

数据源名称。

Data

JSON对象或包含JSON对象的变量名称(#name#)。

示例
JsonToSource(src, #myjson#)
JsonToSource(dat, {"param":"value", "param2": "value 2"})

Label

创建 label HTML元素。

语法
Label(Body, Class, For)
    [.Style(Style)]
Label
Body

子文本或元素。

Class

类名。

For

绑定到某个表单元素。

Style

指定CSS样式。

Style

CSS样式。

示例
Label(The first item).

LangRes

返回指定的语言资源。如果请求对树进行编辑,则返回 langres 元素,可以使用简短格式符号 $langres$

语法
LangRes(Name, Lang)
LangRes
Name

语言资源的名称。

Lang

双字符语言资源ID。

默认情况下,返回 Accept-Language 请求中定义的语言。

可以指定 Lang 标识符,例如 en-US,en-GB。如果找不到请求的值,例如 en-US,将在 en 中查找语言资源。

示例
LangRes(name)
LangRes(myres, zh)

LinkPage

创建 linkpage 元素,指向页面的链接。

语法
LinkPage(Body, Page, Class, PageParams)
    [.Style(Style)]
LinkPage
Body

子文本或元素。

Page

重定向的页面名称。

Class

按钮类名。

PageParams

重定向的页面参数。

Style

指定CSS样式。

Style

CSS styles

示例
LinkPage(Class: #style_link# h5 text-bold, Page: @1roles_view, PageParams: "v_role_id=#recipient.role_id#")

Map

创建可视化地图,并以任意格式显示坐标。

语法
Map(Hmap, MapType, Value)
Map
Hmap

页面上的HTML元素高度。

默认值为100。

Value

地图值,字符串格式的对象。

例如, {"coords":[{"lat":number,"lng":number},]} 或者 {"zoom":int, "center":{"lat":number,"lng":number}}。如果没有指定 center ,则地图窗口将根据指定的坐标自动调整。

MapType

地图类型。

该参数有以下值: hybrid, roadmap, satellite, terrain

示例
Map(MapType:hybrid, Hmap:400, Value:{"coords":[{"lat":55.58774531752405,"lng":36.97260184619233},{"lat":55.58396161622043,"lng":36.973803475831005},{"lat":55.585222890513975,"lng":36.979811624024364},{"lat":55.58803635636347,"lng":36.978781655762646}],"area":146846.65783403456,"address":"Unnamed Road, Moscow, Russia, 143041"})

Money

返回 exp / 10 ^ digit 的字符串值。

语法
Money(Exp, Digit)
Money
Exp

数字的字符串格式。

Digit

exp/10^digit 表达式中10的指数,该值可以是正数或负数。正值决定了小数点后的位数。

示例
Money(Exp, Digit)

Or

该函数返回执行 if 逻辑运算的结果,括号中列出的所有参数以逗号分隔。如果有一个参数不为空字符串、零或 false,参数值为 true,其他情况参数值为 false。如果参数值为 true,则该函数返回 1,其他情况返回 0

语法
Or(parameters)
示例
If(Or(#myval1#,#myval2#), Span(OK))

P

创建 p HTML元素。

语法
P(Body, Class)
    [.Style(Style)]
P
Body

子文本或元素。

Class

类名。

Style

指定CSS样式。

Style

CSS样式。

示例
P(This is the first line.
  This is the second line.)

QRcode

返回带有指定文本的二维码,并创建 qrcode 元素。

语法
QRcode(Text)
QRcode
Text

二维码文字。

示例
QRcode(#name#)

RadioGroup

创建 radiogroup 元素。

语法
RadioGroup(Name, Source, NameColumn, ValueColumn, Value, Class)
    [.Validate(validation parameters)]
    [.Style(Style)]
RadioGroup
Name

元素名称。

Source

DBFindData 函数获取的数据源。

NameColumn

数据源的字段名称。

ValueColumn

数据源的值名称。

使用 Custom 创建的字段不得在该参数中使用。

Value

默认值。

Class

类名。

Validate

验证参数。

Style

指定CSS样式。

Style

CSS样式。

示例
RadioGroup(Name: type_decision, Source: numbers_type_decisions, NameColumn: name, ValueColumn: value)

Range

创建 range 元素,使用步长 StepFromTo (不包括 To)填充整数元素。生成的数据将放入 Source 中,稍后可以在源输入的函数中使用(例如 Table)。如果指定无效参数,则返回空的 Source

语法
Range(Source,From,To,Step)
Range
Source

数据源名称。

From

起始值(i = From)。

To

结束值(i < To)。

Step

数值变化步长,如果未指定该参数,默认为1。

示例
Range(my,0,5)
SetVar(from, 5).(to, -4).(step,-2)
Range(Source: neg, From: #from#, To: #to#, Step: #step#)

Select

创建 select HTML元素。

语法
Select(Name, Source, NameColumn, ValueColumn, Value, Class)
    [.Validate(validation parameters)]
    [.Style(Style)]
Select
Name

元素的名称。

Source

DBFindData 函数获取的数据源。

NameColumn

数据源的字段名称。

ValueColumn

数据源的值名称。

使用 Custom 创建的字段不得在该参数中使用。

Value

默认值。

Class

类名。

Validate

验证参数。

Style

指定CSS样式。

Style

CSS样式。

示例
DBFind(mytable, mysrc)
Select(mysrc, name)

SetTitle

设置页面标题,创建 settitle 元素。

语法
SetTitle(Title)
SetTitle
Title

页面标题。

示例
SetTitle(My page)

SetVar

分配值 Value 给指定变量 Name

语法
SetVar(Name, Value)
SetVar
Name

变量名称。

Value

变量值,可以包含对另一个变量的引用。

示例
SetVar(name, John Smith).(out, I am #name#)
Span(#out#)

Span

创建 span HTML元素。

语法
Span(Body, Class)
    [.Style(Style)]
Span
Body

子文本或元素。

Class

类名。

Style

指定CSS样式。

Style

CSS样式。

示例
This is Span(the first item, myclass1).

Strong

创建 strong HTML元素。

语法
Strong(Body, Class)
Strong
Body

子文本或元素。

Class

类名。

示例
This is Strong(the first item, myclass1).

SysParam

获取平台参数表 system_parameters 中指定参数的值。

语法
SysParam(Name)
SysParam
Name

平台参数名称。

示例
SysParam(max_columns)

Table

创建 table HTML元素。

语法
Table(Source, Columns)
    [.Style(Style)]
Table
Source

指定的数据源名称。

Columns

标题和相应的列名,例如: Title1=column1,Title2=column2

Style

指定CSS样式。

Style

CSS样式。

示例
DBFind(mytable, mysrc)
Table(mysrc,"ID=id,Name=name")

TransactionInfo

该函数按指定哈希值查询交易并返回有关已执行的合约及其参数的信息。

语法
TransactionInfo(Hash)
TransactionInfo
Hash

十六进制字符串格式的交易哈希。

返回值

该函数返回json格式的字符串:

{"contract":"ContractName", "params":{"key": "val"}, "block": "N"}

其中:

  • contract - 合约名称;
  • params - 传递给合约参数的数据;
  • block - 处理该交易的区块ID。
示例
P(TransactionInfo(#hash#))

VarAsIs

分配值 Value 给指定变量 Name。指定的变量值为指定的变量名而不是它们的值。

对于具有变量替换的版本,请参阅 SetVar.

语法
VarAsIs(Name, Value)
VarAsIs
Name

变量名称。

Value

变量值,值中的变量名称不会被替换。 例如,如果 Valueexample #varname#,那么变量的值也是 example #varname#

示例
SetVar(Name,"John")
VarAsIs(name, I am #Name#)
Span(#name#) // I am #Name#

适配移动设备的应用程序样式

排版

标题
  • h1 ... h6
强调类类名
  • .text-muted
  • .text-primary
  • .text-success
  • .text-info
  • .text-warning
  • .text-danger
颜色
  • .bg-danger-dark
  • .bg-danger
  • .bg-danger-light
  • .bg-info-dark
  • .bg-info
  • .bg-info-light
  • .bg-primary-dark
  • .bg-primary
  • .bg-primary-light
  • .bg-success-dark
  • .bg-success
  • .bg-success-light
  • .bg-warning-dark
  • .bg-warning
  • .bg-warning-light
  • .bg-gray-darker
  • .bg-gray-dark
  • .bg-gray
  • .bg-gray-light
  • .bg-gray-lighter

网格

  • .row
  • .row.row-table
  • .col-xs-1 ... .col-xs-12 仅限于使用在 .row.row-table 中。

面板

  • .panel
  • .panel.panel-heading
  • .panel.panel-body
  • .panel.panel-footer

表单

  • .form-control

按钮

  • .btn.btn-default
  • .btn.btn-link
  • .btn.btn-primary
  • .btn.btn-success
  • .btn.btn-info
  • .btn.btn-warning
  • .btn.btn-danger

图标

  • 所有fa类图标来自FontAwesome: fa fa-<icon-name></icon-name>
  • 所有icon类图标来自SimpleLineIcons: icon-<icon-name>

编译器和虚拟机

本节涉及程序编译和虚拟机中 GALang 语言的操作。

源代码存储和编译

合约和功能用Galang语言编写,并存储在生态系统的合约表。

执行合约时,将从数据库中读取其源代码并将其编译为字节码。

合约更改后,其源代码将更新并保存在数据库中。然后编译该源代码,导致相应的虚拟机字节码也被改变。

字节码在任何地方都没有物理保存,因此当再次执行程序时,会重新编译源代码。

所有生态系统的合约表中描述的整个源代码都严格按顺序编译到一个虚拟机中,虚拟机的状态在所有节点上都相同。

调用合约时,虚拟机不会以任何方式更改其状态。执行任何合约或调用函数都发生在每个外部调用时创建的单独运行堆栈上。

每个生态系统都可以拥有一个所谓的虚拟生态系统,可以在一个节点内与区块链外的数据表一起使用,并且不能直接影响区块链或其他虚拟生态系统。在这种情况下,托管这样虚拟生态系统的节点会编译其合约并创建自己的虚拟机。

虚拟机结构

VM结构

虚拟机按如下结构定义在内存中。

type VM struct {
    Block
    ExtCost       func(string) int64
    FuncCallsDB   map[string]struct{}
    Extern        bool
    ShiftContract int64
    logger        *log.Entry
}

VM结构具有以下元素:

  • Block - 包含一个 块结构
  • ExtCost - 一个函数,该函数返回执行外部golang函数的费用;
  • FuncCallsDB - golang函数名称集合,该函数名返回执行成本作为第一个参数。这些函数使用 EXPLAIN 计算处理数据库的成本;
  • Extern - 一个表示合约是否为外部合约的布尔标识,创建VM时,它设置为true,编译代码时不需要显示调用的合约。也就是说,它允许调用将来确定的合约代码;
  • ShiftContract VM中第一个合约的ID;
  • logger VM的错误日志输出。

块结构

虚拟机是由 块Block 对象类型组成的树。

块是包含一些字节码的独立单元。简单地说,您在语言的大括号({})中放入的所有内容都是一个块。

例如,下面的代码创建一个带有函数的块。该块又包含一个带有 if 语句的块,该语句又包含一个带有 while 语句的块。

func my() {
     if true {
          while false {
               ...
           }
     }
}

块按如下结构定义在内存中。

type Block struct {
    Objects  map[string]*ObjInfo
    Type     int
    Owner    *OwnerInfo
    Info     interface{}
    Parent   *Block
    Vars     []reflect.Type
    Code     ByteCodes
    Children Blocks
}

块结构具有以下元素:

  • Objects - 一个 ObjInfo 指针类型的内部对象的映射。例如,如果块中有一个变量,那么可以通过它的名称获得关于它的信息;
  • Type - 块的类型。块为函数时,类型为 ObjFunc。块为合约时,类型为 ObjContract
  • Owner - 一个 OwnerInfo 指针类型的结构。该结构包含有关已编译合约所有者的信息。它在合约编译期间指定或从 contracts 表中获取;
  • Info - 包含有关对象的信息,这取决于块类型;
  • Parent - 指向父块的指针;
  • Vars - 一个包含当前块变量类型的数组;
  • Code - 块本身字节码,当控制权传递给该块时会执行该块字节码,例如,函数调用或者循环体;
  • Children - 一个包含子块的数组,例如,函数嵌套、循环、条件操作符。

ObjInfo结构

ObjInfo 结构包含有关内部对象的信息。

type ObjInfo struct {
   Type int
   Value interface{}
}

ObjInfo结构具有以下元素:

  • Type 是对象类型。它可以是以下值之一:
    • ObjContract合约
    • ObjFunc – 函数;
    • ObjExtFunc – 外部golang函数;
    • ObjVar – 变量;
    • ObjExtend – $name 变量。
  • Value – 包含每种类型的结构。
ContractInfo结构

指向 ObjContract 类型,Value 字段包含 ContractInfo 结构。

type ContractInfo struct {
    ID uint32
    Name string
    Owner *OwnerInfo
    Used map[string]bool
    Tx *[]*FieldInfo
}

ContractInfo结构具有以下元素:

  • ID – 合约ID。调用合约时,该值在区块链中显示;
  • Name – 合约名称;
  • Owner – 关于合约的其他信息;
  • Used – 已被调用的合约名称的映射;
  • Tx – 合约 数据部分 描述的数据数组。
FieldInfo结构

FieldInfo结构用于 ContractInfo 结构并描述合约 数据部分 的元素。

type FieldInfo struct {
      Name string
      Type reflect.Type
      Original uint32
      Tags string
}

FieldInfo结构具有以下元素:

  • Name - 字段名称;
  • Type - 字段类型;
  • Original - 可选项字段;
  • Tags – 该字段的附加标签。
FuncInfo结构

指向 ObjFunc 类型,Value 字段包含 FuncInfo 结构。

type FuncInfo struct {
    Params []reflect.Type
    Results []reflect.Type
    Names *map[string]FuncName
    Variadic bool
    ID uint32
}

FuncInfo结构具有以下元素:

  • Params – 参数类型数组;
  • Results – 返回结果类型数组;
  • Names – 尾部函数的数据映射,例如,DBFind().Columns ()
  • Variadic – 如果函数可以具有可变数量的参数,则为true;
  • ID – 函数ID。
FuncName结构

FuncName结构用于 FuncInfo 并描述尾部函数的数据。

type FuncName struct {
   Params []reflect.Type
   Offset []int
   Variadic bool
}

FuncName结构具有以下元素:

  • Params – 参数类型数组;
  • Offset – 这些变量的偏移量数组。实际上,所有参数在函数中都可以使用点 . 来初始化值;
  • Variadic – 如果尾部函数可以具有可变数量的参数。则为true。
ExtFuncInfo结构

指向 ObjExtFunc 类型,Value 字段包含 ExtFuncInfo 结构。用于描述golang函数。

type ExtFuncInfo struct {
   Name string
   Params []reflect.Type
   Results []reflect.Type
   Auto []string
   Variadic bool
   Func interface{}
}

ExtFuncInfo结构具有以下元素:

  • NameParamsResults 参数和 FuncInfo 结构相同;
  • Auto – 一个变量数组,如果有,则作为附加参数传递给函数,例如,SmartContract 类型的变量 sc
  • Func – golang函数。
VarInfo结构

指向 ObjVar 类型,Value 字段包含一个 VarInfo 结构。

type VarInfo struct {
   Obj *ObjInfo
   Owner *Block
}

VarInfo结构具有以下元素:

  • Obj – 关于变量类型和变量值的信息;
  • Owner – 指向所属块的指针。
ObjExtend值

指向 ObjExtend 类型,Value 字段包含一个字符串,其中包含变量或函数的名称。

虚拟机指令

ByteCode结构

字节码是 ByteCode 类型结构的序列。

type ByteCode struct {
   Cmd uint16
   Value interface{}
}

该结构具有以下字段:

  • Cmd - 存储指令的标识符;
  • Value - 包含操作数(值)。

通常情况,指令对堆栈的顶部元素执行操作,并在必要时将结果值写入其中。

指令标识符

packages/script/cmds_list.go 文件描述了虚拟机指令的标识符。

  • cmdPush – 将 Value 字段的值放到堆栈。例如,将数字和行放入堆栈;
  • cmdVar – 将变量的值放入堆栈。Value 包含一个指向 VarInfo 结构的指针以及关于该变量的信息;
  • cmdExtend – 将外部变量的值放入堆栈。Value 包含一个带有变量名称的字符串(以 $ 开头);
  • cmdCallExtend – 调用外部函数(名称以 $ 开头)。函数的参数从堆栈中获取,函数的结果被放入堆栈。Value 包含一个函数名称(以 $ 开头);
  • cmdPushStr – 将 Value 中的字符串放入堆栈;
  • cmdCall – 调用虚拟机函数,Value 包含 ObjInfo 结构。该指令适用于 ObjExtFunc golang函数和 ObjFunc GALang 函数。调用函数时,将从堆栈中获取其参数,并将结果值放入堆栈;
  • cmdCallVari – 类似于 cmdCall 指令,调用虚拟机函数。该指令用于调用具有可变数量参数的函数;
  • cmdReturn – 用于退出函数,返回值将放入到堆栈,不使用 Value 字段;
  • cmdIf – 将控制权转移到 结构中的字节码,该指令在 Value 字段中传递。仅当 valueToBool 函数调用堆栈顶部元素返回 true 时才会将控制权转移到堆栈。否则控制权转移到下一个指令;
  • cmdElse – 该指令的工作方式与 cmdIf 指令相同,但仅当 valueToBool 函数调用堆栈顶部元素返回 false 时控制权才会转移到指定的块;
  • cmdAssignVar – 从 Value 获取 VarInfo 类型的变量列表。这些变量使用 cmdAssign 指令获取值;
  • cmdAssign – 将堆栈中的值赋给 cmdAssignVar 指令获得的变量;
  • cmdLabel – 控制权在while循环期间被返回时定义一个标记;
  • cmdContinue – 该指令将控制权传递给 cmdLabel 标记。执行循环的新迭代时,不使用 Value
  • cmdWhile – 使用 valueToBool 检查堆栈的顶部元素。如果该值为 true,则从 value 字段调用 结构;
  • cmdBreak – 退出循环;
  • cmdIndex – 通过索引将 maparray 中的值放入堆栈,不使用 Value。例如,(map | array) (index value) => (map | array [index value])
  • cmdSetIndex – 将堆栈顶部元素的值分配给 maparray 的元素,不使用 Value。例如, (map | array) (index value) (value) => (map | array)
  • cmdFuncName – 添加的参数通过用点 . 划分顺序来描述。例如,func name => Func (...) .Name (...)
  • cmdUnwrapArr – 如果堆栈顶部元素为数组,则定义一个布尔标记;
  • cmdMapInit – 初始化 map 的值;
  • cmdArrayInit – 初始化 array 的值;
  • cmdError – 当合约或者函数以某个指定的 error, warning, info 错误终止时,该指令创建。

堆栈操作指令

注解

在当前版本中,这些指令是不完全的自动类型转换。例如, string + float | int | decimal => float | int | decimalfloat + int | str => float,但是 int + string => runtime error

下面是直接处理堆栈的指令。这些指令中不使用 Value 字段。

  • cmdNot – 逻辑否定。(val) => (!ValueToBool(val))
  • cmdSign – 符号变化。(val) => (-val)
  • cmdAdd – 加法。(val1)(val2) => (val1 + val2)
  • cmdSub – 减法。(val1)(val2) => (val1 - val2)
  • cmdMul – 乘法。(val1)(val2) => (val1 * val2)
  • cmdDiv – 除法。(val1)(val2) => (val1 / val2)
  • cmdAnd – 逻辑与。(val1)(val2) => (valueToBool(val1) && valueToBool(val2))
  • cmdOr – 逻辑或。 (val1)(val2) => (valueToBool(val1) || valueToBool(val2))
  • cmdEqual – 等式比较,返回bool。(val1)(val2) => (val1 == val2)
  • cmdNotEq – 不等式比较,返回bool。(val1)(val2) => (val1 != val2)
  • cmdLess – 小于式比较,返回bool。(val1)(val2) => (val1 < val2)
  • cmdNotLess – 大于等于式比较,返回bool。(val1)(val2) => (val1 >= val2)
  • cmdGreat – 大于式比较,返回bool。(val1)(val2) => (val1 > val2)
  • cmdNotGreat – 小于等于式比较,返回bool。(val1)(val2) => (val1 <= val2)

Runtime结构

执行字节码不会影响虚拟机。例如,它允许在单个虚拟机中同时运行各种函数和合约。Runtime 结构用于运行函数和合约,以及任何表达式和字节码。

type RunTime struct {
   stack []interface{}
   blocks []*blockStack
   vars []interface{}
   extend *map[string]interface{}
   vm *VM
   cost int64
   err error
}
  • stack – 执行字节码的堆栈;
  • blocks – 块调用堆栈;
  • vars – 变量堆栈。在块中调用字节码时,其变量将添加到该变量堆栈中。退出块后,变量堆栈的大小将返回到先前的值;
  • extend – 指向外部变量值($name)映射指针;
  • vm – 虚拟机指针;
  • cost – 执行结果的燃料单位;
  • err – 执行时的错误。
blockStack结构

blockStack结构用于 Runtime 结构。

type blockStack struct {
     Block *Block
     Offset int
}
  • Block – 正在执行的块的指针;
  • Offset – 在指定块的字节码中执行的最后一个指令的偏移量。

RunCode函数

字节码在 RunCode 函数中执行。它包含一个循环,为每个字节码指令执行相应的操作。在处理字节码之前,必须初始化必要的数据。

在这里新块被添加到其他块中。

rt.blocks = append(rt.blocks, &blockStack{block, len(rt.vars)})

接下来,获得尾部函数的相关参数信息。这些参数包含在堆栈的最后一个元素中。

var namemap map[string][]interface{}
if block.Type == ObjFunc && block.Info.(*FuncInfo).Names != nil {
    if rt.stack[len(rt.stack)-1] != nil {
        namemap = rt.stack[len(rt.stack)-1].(map[string][]interface{})
    }
    rt.stack = rt.stack[:len(rt.stack)-1]
}

然后,必须使用初始值初始化当前块中定义的所有变量。

start := len(rt.stack)
varoff := len(rt.vars)
for vkey, vpar := range block.Vars {
   rt.cost--
   var value interface{}

由于函数中的变量也是变量,所以我们需要按照函数本身所描述的顺序从堆栈的最后一个元素中取出它们。

if block.Type == ObjFunc && vkey < len(block.Info.(*FuncInfo).Params) {
  value = rt.stack[start-len(block.Info.(*FuncInfo).Params)+vkey]
} else {

在此使用初始值初始化局部变量。

     value = reflect.New(vpar).Elem().Interface()
     if vpar == reflect.TypeOf(map[string]interface{}{}) {
        value = make(map[string]interface{})
     } else if vpar == reflect.TypeOf([]interface{}{}) {
        value = make([]interface{}, 0, len(rt.vars)+1)
     }
  }
  rt.vars = append(rt.vars, value)
}

接下来,更新在尾部函数中传递的变量参数的值。

if namemap != nil {
  for key, item := range namemap {
    params := (*block.Info.(*FuncInfo).Names)[key]
    for i, value := range item {
       if params.Variadic && i >= len(params.Params)-1 {

如果传递的变量参数为可变数量的参数,那么将它们组合成一个变量数组。

              off := varoff + params.Offset[len(params.Params)-1]
              rt.vars[off] = append(rt.vars[off].([]interface{}), value)
          } else {
              rt.vars[varoff+params.Offset[i]] = value
        }
     }
   }
}

之后,我们要做的就是删除作为函数参数从堆栈顶部传递的值,从而移动堆栈。我们已经将它们的值复制到一个变量数组中。

if block.Type == ObjFunc {
     start -= len(block.Info.(*FuncInfo).Params)
}

字节码指令循环执行结束后,我们必须正确地清除堆栈。

last := rt.blocks[len(rt.blocks)-1]

将当前块从块堆栈中删除。

rt.blocks = rt.blocks[:len(rt.blocks)-1]
if status == statusReturn {

如果成功退出已执行的函数,我们将返回值添加到上一个堆栈的尾部。

if last.Block.Type == ObjFunc {
   for count := len(last.Block.Info.(*FuncInfo).Results); count > 0; count-- {
     rt.stack[start] = rt.stack[len(rt.stack)-count]
     start++
   }
   status = statusNormal
 } else {

如您所见,如果我们不执行函数,那么我们就不会恢复堆栈状态并按原样退出函数。原因是函数中已经执行的循环和条件结构也是字节码块。

    return
  }
}
rt.stack = rt.stack[:start]

VM的其他函数操作

使用 NewVM 函数创建虚拟机。每个虚拟机都 **Extend** 函数添加了四个函数:ExecContractMemoryUsageCallContractSettings

for key, item := range ext.Objects {
    fobj := reflect.ValueOf(item).Type()

我们遍历所有传递的对象,只查看函数。

switch fobj.Kind() {
case reflect.Func:

根据接收到的相关该函数的信息填充 ExtFuncInfo 结构,并按名称将其结构添加到顶层的 Objects 映射。

data := ExtFuncInfo{key, make([]reflect.Type, fobj.NumIn()), make([]reflect.Type, fobj.NumOut()),
   make([]string, fobj.NumIn()), fobj.IsVariadic(), item}
for i := 0; i < fobj.NumIn(); i++ {

ExtFuncInfo 结构有一个 Auto 参数数组。通常第一个参数为 sc *SmartContractrt *Runtime,我们不能从 GALang 语言中传递它们,因为在执行一些golang函数时它们对我们来说是必需的。因此,我们指定在调用函数时将自动使用这些变量。在这种情况下,上述四个函数的第一个参数为 rt *Runtime

if isauto, ok := ext.AutoPars[fobj.In(i).String()]; ok {
  data.Auto[i] = isauto
}

赋值有关参数的信息。

  data.Params[i] = fobj.In(i)
}

以及返回值的类型。

for i := 0; i < fobj.NumOut(); i++ {
   data.Results[i] = fobj.Out(i)
}

向根 Objects 添加一个函数,这样编译器可以稍后在使用合约时找到它们。

         vm.Objects[key] = &ObjInfo{ObjExtFunc, data}
    }
}

编译器

compile.go 文件的函数负责编译从词法分析器获得的标记数组。编译可以有条件地分为两个级别,在高层级别,我们处理函数、合约、代码块、条件语句和循环语句、变量定义等等。在底层级别,我们编译循环和条件语句中的代码块或条件内的表达式。

首先,让我们描述简单的低层级别。在 compileEval 函数可以完成将表达式转换为字节码。由于我们是使用堆栈的虚拟机,因此有必要将普通的中缀记录表达式转换为后缀表示法或逆波兰表示法。例如,1+2 转换为 12+,然后将 12 放入堆栈,然后我们对堆栈中的最后两个元素应用加法运算,并将结果写入堆栈。这种 转换算法 可以在互联网上找到。

全局变量 opers = map [uint32] operPrior 包含转换成逆波兰表示法时所必需的操作的优先级。

以下变量在 compileEval 函数开头定义:

  • buffer – 字节码指令的临时缓冲区;
  • bytecode – 字节码指令的最终缓冲区;
  • parcount – 调用函数时用于计算参数的临时缓冲区;
  • setIndex – 当我们分配 maparray 元素时,工作过程中的变量被设置为 true。例如,a["my"] = 10,在这种情况下,我们需要使用指定的 cmdSetIndex 指令。

我们在一个循环体中获得一个标记并作出相应的处理,例如,如果找到大括号,然后停止解析表达式。在移动字符串时,我们会查看前一个语句是否是一个操作符以及是否在括号内,否则我们退出并解析表达式。

case isRCurly, isLCurly:
     i--
     if prevLex == isComma || prevLex == lexOper {
                                return errEndExp
                       }
    break main
case lexNewLine:
      if i > 0 && ((*lexems)[i-1].Type == isComma || (*lexems)[i-1].Type == lexOper) {
           continue main
      }
     for k := len(buffer) - 1; k >= 0; k-- {
          if buffer[k].Cmd == cmdSys {
              continue main
         }
     }
    break main

通常情况下,该算法本身对应于一种转换为逆波兰表示法的算法。考虑到一些必要的合约、函数、索引的调用,以及解析时不会遇到的其他事情和解析 lexIdent 类型标记的选项,我们将检查具有此名称的变量、函数或合约。如果没有找到任何相关内容而且这不是函数或合约调用,那么我们会指出错误。

objInfo, tobj := vm.findObj(lexem.Value.(string), block)
if objInfo == nil && (!vm.Extern || i > *ind || i >= len(*lexems)-2 || (*lexems)[i+1].Type != isLPar) {
      return fmt.Errorf(`unknown identifier %s`, lexem.Value.(string))
}

我们可能会遇到这样的情况,稍后将描述合约调用。在本例中,如果没有找到同名函数和变量,那么我们认为将调用合约。在该编译语言中,合约和函数调用没有区别。但是我们需要通过在字节码中使用的 ExecContract 函数来调用合约。

if objInfo.Type == ObjContract {
    if objInfo.Value != nil {
                              objContract = objInfo.Value.(*Block)
                            }
    objInfo, tobj = vm.findObj(`ExecContract`, block)
    isContract = true
}

我们将到目前为止的变量数量记录在 count 中,该值也会随着函数参数数量一起写入堆栈。在每次后续检测参数时,我们只需在堆栈的最后一个元素中将该数量增加一个单位。

count := 0
if (*lexems)[i+2].Type != isRPar {
    count++
}

我们有已调用合约的列表参数 Used,因此我们需要为合约被调用的情况做标记。如果在没有参数的情况下调用合约,我们必须添加两个空参数去调用 ExecContract,以获得最少两个参数。

if isContract {
   name := StateName((*block)[0].Info.(uint32), lexem.Value.(string))
   for j := len(*block) - 1; j >= 0; j-- {
      topblock := (*block)[j]
      if topblock.Type == ObjContract {
            if topblock.Info.(*ContractInfo).Used == nil {
                 topblock.Info.(*ContractInfo).Used = make(map[string]bool)
            }
           topblock.Info.(*ContractInfo).Used[name] = true
       }
    }
    bytecode = append(bytecode, &ByteCode{cmdPush, name})
    if count == 0 {
       count = 2
       bytecode = append(bytecode, &ByteCode{cmdPush, ""})
       bytecode = append(bytecode, &ByteCode{cmdPush, ""})
     }
    count++

}

If we see that there is a square bracket next, then we add the cmdIndex command to get the value by the index.

if (*lexems)[i+1].Type == isLBrack {
     if objInfo == nil || objInfo.Type != ObjVar {
         return fmt.Errorf(`unknown variable %s`, lexem.Value.(string))
     }
    buffer = append(buffer, &ByteCode{cmdIndex, 0})
}

CompileBlock 函数可以生成对象树和与表达式无关的字节码。编译过程基于有限状态机,就像词法分析器一样,但是有以下不同之处。第一,我们不使用符号但使用标记;第二,我们会立即描述所有状态和转换中的 states 变量。它表示一个按标记类型索引的对象数组,每个标记都具有 compileState 的结构,并在 NewState 中指定一个新状态。如果我们已经解析清楚这是什么结构,那么就可以指定 Func 字段中处理程序的函数。

让我们以主状态为例回顾一下。

如果我们遇到换行符或注释,那么我们会保持相同的状态。如果我们遇到 contract 关键字,那么我们将状态更改为 stateContract 并开始解析该结构。如果我们遇到 func 关键字,那么我们将状态更改为 stateFunc。如果接收到其他标记,那么将调用生成错误的函数。

{ // stateRoot
   lexNewLine: {stateRoot, 0},
   lexKeyword | (keyContract << 8): {stateContract | statePush, 0},
   lexKeyword | (keyFunc << 8): {stateFunc | statePush, 0},
   lexComment: {stateRoot, 0},
   0: {errUnknownCmd, cfError},
},

假设我们遇到了 func 关键字,并且我们已将状态更改为 stateFunc。由于函数名必须跟在 func 关键字后面,因此在更改该函数名时,我们将保持相同的状态。对于所有其他标记,我们生成相应的错误。如果我们在标记标识符中获取了函数名称,那么我们转到 stateFParams 状态,其中我们可以获取函数的参数。

{ // stateFunc
    lexNewLine: {stateFunc, 0},
    lexIdent: {stateFParams, cfNameBlock},
    0: {errMustName, cfError},
},

上述操作的同时,我们将调用 fNameBlock 函数。应该注意的是,块Block 结构是使用 statePush 标记创建的,在这里我们从缓冲区中获取它并填充我们需要的数据。fNameBlock 函数适用于合约和函数(包括嵌套在其中的函数和合约)。它使用相应的结构填充 Info 字段,并将其自身写入父块的 Objects 中。这样以便我们可以通过指定的名称调用该函数或合约。同样,我们为所有状态和变量创建对应的函数。这些函数通常非常小,并且在构造虚拟机树时执行一些工作。

func fNameBlock(buf *[]*Block, state int, lexem *Lexem) error {
    var itype int

    prev := (*buf)[len(*buf)-2]
    fblock := (*buf)[len(*buf)-1]
   name := lexem.Value.(string)
   switch state {
     case stateBlock:
        itype = ObjContract
       name = StateName((*buf)[0].Info.(uint32), name)
       fblock.Info = &ContractInfo{ID: uint32(len(prev.Children) - 1), Name: name,
           Owner: (*buf)[0].Owner}
    default:
       itype = ObjFunc
       fblock.Info = &FuncInfo{}
     }
     fblock.Type = itype
    prev.Objects[name] = &ObjInfo{Type: itype, Value: fblock}
    return nil
}

对于 CompileBlock 函数,它只是遍历所有标记并根据 states 中描述的标记切换状态。几乎所有附加标记对应附加程序代码。

  • statePush – 将 块Block 对象添加到对象树中;
  • statePop – 当块以结束花括号结束时使用;
  • stateStay – 当更改为新状态时,您需要保留当前标记;
  • stateToBlock – 转换到 stateBlock 状态,用于处理 whileif。当处理完表达式后,需要在大括号内处理块使用;
  • stateToBody – 转换到 stateBody 状态;
  • stateFork – 保存标记的位置。当表达式以标识符或带有 $ 名称开头时使用,我们可以进行函数调用或赋值;
  • stateToFork – 用于获取存储在 stateFork 中的标记。该标记将传递给进程函数;
  • stateLabel – 用于插入 cmdLabel 指令。while 结构需要这个标记;
  • stateMustEval – 在 ifwhile 结构的开头检查条件表达式的可用性。

除了 CompileBlock 函数,还应该提到 FlushBlock 函数。但问题是块树是独立于现有虚拟机构建的,更准确地说,我们获取有关虚拟机中存在的函数和合约的信息,但我们将已编译的块收集到一个单独的树中。否则,如果在编译期间发生错误,我们必须将虚拟机的状态回滚到以前的状态。因此,我们单独去编译树,但编译成功后必须调用 FlushContract 函数。这个函数将完成的块树添加到当前虚拟机中。此时编译阶段就完成了。

词法分析器

词法分析器将传入的字符串处理并形成以下类型的标记序列:

  • lexSys - 系统标记,例如:{}[](),. 等;
  • lexOper – 操作标记,例如:+-/\*
  • lexNumber – 数字;
  • lexident – 标识符;
  • lexNewline – 换行符;
  • lexString – 字符串;
  • lexComment – 注释;
  • lexKeyword – 关键字;
  • lexType – 类型;
  • lexExtend – 引用外部变量或函数,例如:$myname

在当前版本中,初步借助于 script/lextable/lextable.go 文件构造了一个转换表(有限状态机)来解析标记,并将其写入 lex_table.go 文件。通常情况下,您可以脱离该文件初始生成的转换表,可以在启动时立即在内存(init())中创建一个转换表。词法分析本身发生在 lex.go 文件中的 lexParser 函数中。

lextable/lextable.go

在这里我们定义了我们的语言用于操作的字母表,并描述有限状态机根据下一个接收到的符号从一种状态变化到另一种状态。

states 包含一个状态列表的JSON对象。

除特定符号外,d 用于表示状态中未指明的所有符号。

n 代表0x0a,s 代表空格,q 代表反引号,Q 代表双引号,r 代表字符 >= 128,a 代表AZ和az,1 代表1-9。

状态的名称是键,值对象中列出了可能的值。然后,对于每一组,都有一种新的状态需要转换。然后是标记的名称,如果我们需要返回到初始状态,第三个参数是服务标志,它指示了如何处理当前符号。

例如,我们有主状态和传入字符 /"/": ["solidus", "", "push next"],

  • push - 给指令记住它在一个单独的堆栈;
  • next - 转到下一个字符,同时我们将状态更改为 solidus,之后,获取下一个角色并查看 solidus 的状态。

如果下一字符有 //*,那么我们转到注释 comment 状态,因为它们以 ///* 开头。显然,每个注释后续都有不同的状态,因为它们以不同的符号结束。

如果下一字符不是 /*,那么我们将堆栈中的所有内容记录为 lexOper 类型的标记,清除堆栈并返回主状态。

以下模块将状态树转换为一个数值数组,并将其写入 lex_table.go 文件。

在第一个循环体中:

我们形成有效符号的字母表。

for ind, ch := range alphabet {
i := byte(ind)

此外,在 state2int 中,我们为每个状态提供了自己的序列标识符。

state2int := map[string]uint{`main`: 0}
if err := json.Unmarshal([]byte(states), &data); err == nil {
for key := range data {
if key != `main` {
state2int[key] = uint(len(state2int))

当我们遍历所有状态和状态中的每个集合以及该集合中的每个符号时,我们写入一个三字节的数字[新状态标识符(0 = main)] + [标记类型(0-没有标记)] + [标记]。

table 数组的二维性在于它分为状态和来自 alphabet 数组的34个输入符号,它们以相同的顺序排列。

我们处于 table 零行上的 main 状态。取第一个字符,在 alphabet 数组中查找其索引,并从给定索引的列中获取值。从接收到的值开始,我们在低位字节接收标记。如果解析完成,第二个字节表示接收到的标记类型。在第三个字节中,我们接收下一个新状态的索引。

所有这些在 lex.go 中的 lexParser 函数中有更详细的描述。

如果想要添加一些新字符,则需要将它们添加到 alphabet 数组并增加 AlphaSize 常量。 如果要添加新的符号组合,则应在状态中对其进行描述,类似于现有选项。在此之后,运行 lextable.go 文件来更新 lex_table.go 文件。

lex.go

lexParser 函数直接生成词法分析,并根据传入的字符串返回一个已接收标记的数组。让我们分析标记的结构。

type Lexem struct {
   Type uint32 // Type of the lexem
   Value interface{} // Value of lexem
   Line uint32 // Line of the lexem
   Column uint32 // Position inside the line
}
  • Type – 标记类型。它有以下值之一:lexSys, lexOper, lexNumber, lexIdent, lexString, lexComment, lexKeyword, lexType, lexExtend
  • Value – 标记的值。值的类型取决于标记类型,让我们更详细地分析一下:
    • lexSys – 包括括号,逗号等。在这种情况下,Type = ch << 8 | lexSys,请参阅 isLPar ... isRBrack 常量,该值为uint32位;
    • lexOper – 值以uint32的形式表示等价的字符序列。请参阅 isNot ... isOr 常量;
    • lexNumber – 数字存储为 int64float64。如果数字有一个小数点,那么为 float64
    • lexIdent – 标识符存储为 字符串string
    • lexNewLine – 换行符。还用于计算行和标记位置;
    • lexString – 行存储为 字符串string
    • lexComment – 注释存储为 字符串string
    • lexKeyword – 关键字仅存储相应的索引,请参阅 keyContract ... keyTail 常量。在这种情况下 Type = KeyID << 8 | lexKeyword。另外,应该注意的是,true,false,nil 关键字会立即转换为 lexNumber 类型的标记,并使用相应的 boolintreface {} 类型;
    • lexType – 该值包含相应的 reflect.Type 类型值;
    • lexExtend – 以美元符号 $ 开头的标识符。这些变量和函数从外部传递,因此分配给特殊类型的标记。该值包含字符串形式的名称,开头没有美元符号。
  • Line – 标记所在行;
  • Column – 标记的行内位置。

让我们详细分析 lexParser 函数。todo 函数根据当前状态和传入符号,查找字母表中的符号索引,并从转换表中获取一个新状态、标记标识符(如果有的话)和其他标记。解析本身包括对每下一个字符依次调用 todo 函数,并切换到新的状态。一旦接收到标记,我们就在输出准则中创建相应的标记并继续解析。应该注意的是,在解析过程中,我们不将标记符号累积到单独的堆栈或数组中,因为我们只是保存标记开始的偏移量。获得标记之后,我们将下一个标记的偏移量移动到当前解析位置。

剩下的就是检查解析中使用的词法状态标志:

  • lexfPush – 该标志意味着我们开始在一个新的标记中累积符号;
  • lexfNext – 必须将该字符添加到当前标记;
  • lexfPop – 接收标记完成,通常,使用该标志我们有解析标记的标识符类型;
  • lexfSkip – 该标志用于从解析中排除字符,例如,字符串中的控件斜线为 \n \r \"。它们会在该词法分析阶段自动替换。

GALang 语言

词法

程序的源代码必须采用UTF-8编码。

以下词法类型:

  • 关键字 - action, break, conditions, continue, contract, data, else, error, false, funcif, info, nil, return, settings, true, var, warning, while
  • 数字 - 只接收十进制数字。有两种基本类型: intfloat。如果数字有一个小数点,它就变成了浮点数 floatint 类型等价于golang中的 int64float 类型等价于golang中的 float64
  • 字符串 - 字符串可以用双引号 ("a string") 或反引号( \`a string\`)。这两种类型的字符串都可以包含换行符。双引号中的字符串可以包含双引号、换行符和用斜杠转义的回车符。例如, "This is a \"first string\".\r\nThis is a second string."
  • 注释 - 有两种类型的评论。单行注释使用两个斜杠符号 (//)。例如,// 这是单行注释。多行注释使用斜杠和星号符号,可以跨越多行。例如,/* 这是多行注释 */.
  • 标识符 - 由a-z和A-Z字母、UTF-8符号、数字和下划线组成的变量和函数的名称。名称可以以字母、下划线、@$ 符号开头。以 $ 开头的名称为在 数据部分 中定义的变量的名称。以 $ 开头的名称还可以用于定义 条件部分操作部分 范围内的全局变量。生态系统的合约可以使用 @ 符号来调用。例如: @1NewTable(...)

类型

在 GALang 类型旁边指定了相应的golang类型。

  • bool - bool,默认值为 false
  • bytes - []byte{},默认值为空字节数组;
  • int - int64,默认值为 0
  • address - uint64,默认值为 0
  • array - []interface{},默认值为空数组;
  • map - map[string]interface{},默认值为空对象数组;
  • money - decimal.Decimal,默认值为 0
  • float - float64,默认值为 0
  • string - string,默认值为空字符串;
  • file - map[string]interface{},默认值为空对象数组。

这些类型的变量用 var 关键字定义。例如,var var1, var2 int。当这样定义一个变量时,它将获得其类型的默认值。

所有变量值都具有 interface{} 类型,然后将它们分配给所需的golang类型。因此,例如 arraymap 类型是golang类型 []interface{}map[string]interface{} 。这两种类型的数组都可以包含任何类型的元素。

表达式

表达式可以包含算术运算、逻辑运算和函数调用。根据操作优先级从左到右计算所有表达式。如果操作优先级相同,评估也从左到右。

从最高优先级到最低优先级的操作列表:

  • 函数调用和圆括号 - 调用函数时,将从左到右计算传递的参数;
  • 一元运算 - 逻辑否定 ! 和算术符号变化 -
  • 乘法和除法 - 算术乘法 * 和除法 /
  • 加法和减法 - 算术加法 + 和减法 -
  • 逻辑比较 - >= > > >=
  • 逻辑相等和不相等 - == !=
  • 逻辑与 - &&
  • 逻辑或 - ||

当评估逻辑与和逻辑或时,在任何情况下都会计算表达式的两侧。

GALang 在编译时没有类型检查。在评估操作数时,会尝试将类型转换为更复杂的类型。复杂度顺序的类型可以按照如下:string, int, float, money,仅实现了部分类型转换。字符串类型支持加法操作,结果会使得字符串连接。例如,string + string = string, money - int = money, int * float = float

对于函数,在执行时会对 stringint 类型执行类型检查。

arraymap 类型可以通过索引来寻址。对于 array 类型,必须将 int 值指定为索引。对于 map 类型,必须指定变量或 string 值。如果将值赋给索引大于当前最大索引的 array 元素,则将向数组添加空元素。这些元素的初始化值为 nil 。例如: .. code:

var my array
my[5] = 0
var mymap map
mymap["index"] = my[3]

在条件逻辑值的表达式中(例如 if,while,&&,||,!),类型会自动转换为逻辑值,如果类型不为默认值,则为true。

var mymap map
var val string
if mymap && val {
...
}

范围

大括号指定一个可以包含局部范围变量的块。默认情况下,变量的范围扩展到它自己的块和所有嵌套的块。在一个块中,可以使用现有变量的名称定义一个新变量。在这种情况下,具有相同名称的外部变量不可用。

var a int
a = 3
{
   var a int
   a = 4
   Println(a) // 4
}
Println(a) // 3

合约执行

当调用合约时,必须将 data 部分中定义的参数传递给它。在执行合约之前,虚拟机接收这些参数并将它们分配给相应的变量($Param)。然后调用预定义的 conditions 函数和 action 函数。

合约执行期间发生的错误可分为两种类型:形式错误和环境错误。形式错误使用特殊命令生成:error, warning, info 以及当内置函数返回 err 不等于 nil 时。

GALang 语言不处理异常。任何错误都会终止合约的执行。由于在执行合约时创建了用于保存变量值的单独堆栈和结构,所以当合约执行完成时,golang垃圾回收机制将自动删除这些数据。

巴科斯范式Backus–Naur Form (BNF)

在计算机科学中,BNF是一种用于无上下文语法的符号技术,通常用于描述计算中使用的语言的语法。

  • <decimal digit>

    '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
    
  • <decimal number>

    <decimal digit> {<decimal digit>}
    
  • <symbol code>

    '''<any symbol>'''
    
  • <real number>

    ['-'] <decimal number'.'[<decimal number>]
    
  • <integer number>

    ['-'] <decimal number> | <symbol code>
    
  • <number>

    '<integer number> | <real number>'
    
  • <letter>

    'A' | 'B' | ... | 'Z' | 'a' | 'b' | ... | 'z' | 0x80 | 0x81 | ... | 0xFF
    
  • <space>

    '0x20'
    
  • <tabulation>

    '0x09'
    
  • <newline>

    '0x0D 0x0A'
    
  • <special symbol>

    '!' | '"' | '$' | ''' | '(' | ')' | '\*' | '+' | ',' | '-' | '.' | '/' | '<' | '=' | '>' | '[' | '\\' | ']' | '_' | '|' | '}' | '{' | <tabulation> | <space> | <newline>
    
  • <symbol>

    <decimal digit> | <letter> | <special symbol>
    
  • <name>

    (<letter> | '_') {<letter> | '_' | <decimal digit>}
    
  • <function name>

    <name>
    
  • <variable name>

    <name>
    
  • <type name>

    <name>
    
  • <string symbol>

    <tabulation> | <space> | '!' | '#' | ... | '[' | ']' | ...
    
  • <string element>

    {<string symbol> | '\"' | '\n' | '\r' }
    
  • <string>

    '"' { <string element> } '"' | '\`'  { <string element> } '\`'
    
  • <assignment operator>

    '='
    
  • <unary operator>

    '-'
    
  • <binary operator>

    '==' | '!=' | '>' | '<' | '<=' | '>=' | '&&' | '||' | '\*' | '/' | '+' | '-'
    
  • <operator>

    <assignment operator> | <unary operator> | <binary operator>
    
  • <parameters>

    <expression> {','<expression>}
    
  • <contract call>

    <contract name> '(' [<parameters>] ')'
    
  • <function call>

    <contract call> [{'.' <name> '(' [<parameters>] ')'}]
    
  • <block contents>

    <block command> {<newline><block command>}
    
  • <block>

    '{'<block contents>'}'
    
  • <block command>

    (<block> | <expression> | <variables definition> | <if> | <while> | break | continue | return)
    
  • <if>

    'if <expression><block> [else <block>]'
    
  • <while>

    'while <expression><block>'
    
  • <contract>

    'contract <name> '{'[<data section>] {<function>} [<conditions>] [<action>]'}''
    
  • <data section>

    'data '{' {<data parameter><newline>} '}''
    
  • <data parameter>

    <variable name> <type name> '"'{<tag>}'"'
    
  • <tag>

    'optional | image | file | hidden | text | polymap | map | address | signature:<name>'
    
  • <conditions>

    'conditions <block>'
    
  • <action>

    'action <block>'
    
  • <function>

    'func <function name>'('[<variable description>{','<variable description>}]')'[{<tail>}] [<type name>] <block>'
    
  • <variable description>

    <variable name> {',' <variable name>} <type name>
    
  • <tail>

    '.'<function name>'('[<variable description>{','<variable description>}]')'
    
  • <variables definition>

    'var <variable description>{','<variable description>}'
    

守护进程

该章节介绍 GAChain 节点如何从技术角度相互交互。

关于服务端守护进程

GAChain 服务端为 go-gachain,它需在每个网络节点上运行。服务端守护进程执行服务端各个功能并支持 GAChain 区块链协议。守护进程在区块链网络中分发区块和交易、生成新区块、验证接收到的区块和交易。守护进程可以防止区块链分叉问题。

验证节点守护进程

验证节点 (有权生成新区块和发送交易)运行以下服务端守护进程:

全节点守护进程

全节点 (只发送交易的节点)运行以下服务端守护进程:

BlockCollection守护进程

BlocksCollection守护进程下载区块并将区块链与其他网络节点同步。

区块链同步

BlocksCollection守护进程通过确定区块链网络中的最大区块高度,请求新区块以及解决区块链中的分叉来同步区块链。

区块链更新检查

BlocksCollection守护进程将当前区块ID的请求发送到所有验证节点。

如果该守护进程的节点的当前区块ID小于任何验证节点的当前区块ID,则该区块链网络节点被认为是过时的。

下载新区块

返回最大当前区块高度的节点被视为最新节点。

该守护进程下载所有尚未知道的区块。

解决分叉

如果在区块链中检测到分叉,则该守护进程通过将所有区块下载到共同的父区块来向后移动分叉。

找到共同的父区块后,将在该守护进程的节点区块链上执行回滚,并将正确的区块添加到区块链中,直到最新的区块。

数据表

BlocksCollection守护进程使用以下数据表:

  • block_chain
  • transactions
  • transactions_status
  • info_block

请求

BlockCollection守护程序向其他守护程序发出以下请求:

  • Type 10 指向所有验证节点中最大的区块ID。
  • Type 7 指向最大区块ID的数据。

BlockGenerator守护进程

BlockGenerator守护进程生成新区块。

预验证

BlockGenerator守护进程通过分析区块链中的最新区块来计划新的区块生成。

如果满足以下条件,则可以生成新区块:

  • 生成最新区块的节点位于验证节点列表中守护进程节点。
  • 自最新未验证区块生成以来经过的最短时间。

区块生成

该守护进程生成新区块后,新区块包含所有新交易。这些交易可以从其他节点的 Disseminator守护进程 接收,也可以由该守护进程的节点生成。生成的区块保存在该节点数据库中。

数据表

BlockGenerator守护程序使用以下表:

  • block_chain (saves new blocks)
  • transactions
  • transactions_status
  • info_block

请求

BlockGenerator守护进程不向其他守护进程发出任何请求。

Disseminator守护进程

Disseminator守护进程将交易和区块发送到所有验证节点。

全节点

在全节点上工作时,守护进程将其节点生成的交易发送到所有验证节点。

验证节点

在验证节点上工作时,守护进程会将生成的区块和交易的哈希值发送到所有验证节点。

然后,验证节点响应其节点未知的交易请求。守护进程发送完整的交易数据作为响应。

数据表

Disseminator守护进程使用以下表:

  • transactions

请求

Disseminator守护进程向其他守护进程发出以下请求:

  • Type 1 向验证节点发送交易和区块哈希。
  • Type 2 从验证节点接收交易数据。

Confirmations守护进程

Confirmations守护进程检查其节点中的所有区块是否存在于大多数其他节点上。

区块确认

当网络中的多个节点已确认区块时,将认为该区块已被确认。

该守护进程从数据库中当前未确认的第一个区块开始逐个确认所有区块。

每个区块都以这种方式确认:

  • 向所有验证节点发送请求,该请求包含了正在确认的区块ID。
  • 所有验证节点对该区块的哈希进行响应。
  • 如果响应的哈希值与守护进程节点上的区块的哈希值匹配,则会增加确认计数器。如果哈希不匹配,取消确认计数器将增加。

数据表

Confirmations守护进程使用以下数据表:

  • confirmation
  • info_block

请求

Confirmations守护进程向其他守护进程发出以下请求:

  • Type 4 向验证节点请求区块哈希。

TCP服务协议

TCP服务协议在验证节点和全节点上工作,TCP服务协议使用TCP上的二进制协议来处理来自BlocksCollection、Disseminator和Confirmation守护进程的请求。

请求类型

每个请求都有一个由请求的前两个字节定义的类型。

Type 1

请求发送者

Disseminator守护进程 发送该请求。

请求数据

交易和区块哈希。

请求处理

区块哈希被添加到区块队列中。

对交易哈希进行解析验证,并选择节点上尚未出现的交易。

响应

无。处理该请求后会发出 Type 2 请求。

Type 2

请求发送者

Disseminator守护进程 发送该请求。

请求数据

交易数据,包括数据大小:

  • data_size (4个字节)

    交易数据的大小,以字节为单位。

  • data (data_size个字节)

    交易数据。

请求处理

验证交易并将其添加到交易队列中。

响应

无。

Type 4

请求发送者

Confirmations守护进程 发送该请求。

请求数据

区块ID。

响应

区块哈希。

如果不存在该ID的区块,则返回 0

Type 7

请求发送者

BlockCollection守护进程 发送该请求。

请求数据

区块ID。

  • block_id (4个字节)
响应

区块数据,包括数据大小。

  • data_size (4个字节)

    区块数据的大小,以字节为单位。

  • data (data_size个字节)

    区块数据。

如果不存在该ID的区块,则关闭连接。

Type 10

请求发送者

BlockCollection守护进程 发送该请求。

请求数据

响应

区块ID。

  • block_id (4个字节)

RESTful API

Govis软件客户端提供的所有功能,包括身份验证,生态系统数据接收,错误处理,数据库表操作,页面和合约执行都可通过 GAChain 平台的REST API获得。

通过使用REST API,开发者可以在不使用Govis软件客户端的情况下访问平台的任何功能。

API命令调用通过寻址执行 /api/v2/command/[param],其中 command 是命令名称,param 是附加参数。请求参数必须使用 Content-Type: x-www-form-urlencoded 格式发送。服务器响应结果为JSON格式。

错误响应处理

在请求执行成功的情况下返回状态 200。如果出现错误,除了错误状态之外,将返回带有以下字段的JSON对象:

  • error

    错误标识符。

  • msg

    错误文本信息。

  • params

    错误的附加参数数组,可以将其放入错误信息中。

响应示例
400 (Bad 请求)
Content-Type: application/json
{
    "err": "E_INVALIDWALLET",
    "msg": "Wallet 1234-5678-9012 is not valid",
    "params": ["1234-5678-9012"]
}

错误列表

E_CONTRACT

不存在 %s 合约

E_DBNIL

数据库为空

E_DELETEDKEY

账户地址已冻结

E_ECOSYSTEM

生态系统 %d 不存在

E_EMPTYPUBLIC

账户公钥无效

E_KEYNOTFOUND

账户地址未找到

E_HASHWRONG

哈希不正确

E_HASHNOTFOUND

哈希未找到

E_HEAVYPAGE

页面加载过多

E_INVALIDWALLET

钱包地址 %s 无效

E_LIMITTXSIZE

该交易大小已超出限制

E_NOTFOUND

页面或菜单内容未找到

E_PARAMNOTFOUND

参数未找到

E_PERMISSION

没有权限

E_QUERY

数据库查询错误

E_RECOVERED

API发生恐慌性错误。

如果出现恐慌性错误,则返回错误。

这个错误意味着您遇到了一个需要查找和修复的bug。

E_SERVER

服务器错误。

如果在golang库函数中有错误,则返回。msg 字段包含错误文本信息。

在响应任何命令时都可能出现 E_SERVER 错误。如果由于输入参数不正确而出现,则可以将其更改为相关错误。在另一种情况下,这个错误报告无效的操作或不正确的系统配置,这需要更详细的调查报告。

E_SIGNATURE

签名不正确

E_STATELOGIN

%s 不是生态系统 %s 内的成员

E_TABLENOTFOUND

数据表 %s 未找到

E_TOKENEXPIRED

会话已失效 %s

E_UNAUTHORIZED

未经授权。

在没有执行登录或会话过期的情况下,除 getuid、login 之外,任何命令都返回 E_UNAUTHORIZED 错误。

E_UNKNOWNUID

未知UID

E_UPDATING

节点正在更新区块链

E_STOPPING

节点已停止

E_NOTIMPLEMENTED

尚未实现

E_BANNED

该账户地址在 %s 乾禁止使用

E_CHECKROLE

拒绝访问

VDE不可用接口

VDE节点不可用的接口请求:

  • metrics
  • txinfo
  • txinfoMultiple
  • appparam
  • appparams
  • appcontent
  • history
  • balance
  • block
  • maxblockid
  • blocks
  • detailed_blocks
  • ecosystemparams
  • systemparams
  • ecosystems
  • ecosystemparam
  • ecosystemname
  • walletHistory
  • tx_record

认证接口

JWT token 用于认证。收到JWT令牌后必须将其放在每个请求头中:Authorization: Bearer TOKEN_HERE

getuid

GET/ 返回一个唯一值, 使用私钥对其签名,然后使用 login 命令将其发送回服务器。

生成临时JWT令牌,在调用 login 时需要将令牌传递给 Authorization

请求
GET
/api/v2/getuid
响应
  • uid

    签名数字。

  • token

    登录时传递的临时令牌。

    临时令牌的生命周期为5秒。

  • network_id

    服务器标识符。

在不需要授权的情况下,将返回以下信息:

  • expire

    过期时间。

  • ecosystem

    生态系统ID。

  • key_id

    账户地址。

  • address

    钱包地址 XXXX-XXXX-.....-XXXX

响应示例
200 (OK)
Content-Type: application/json
{
    "uid": "4999317241855959593",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9........I7LY6XX4IP12En6nr8UPklE9U4qicqg3K9KEzGq_8zE"
    "network_id": "4717243765193692211"
}
错误响应

E_SERVER

login

POST/ 用户身份验证。

应首先调用 getuid 命令,以便接收唯一值并对其进行签名。getuid的临时JWT令牌需要放在请求头中传递。

如果请求成功,则响应中收到的令牌包含在 Authorization 中。

请求
POST
/api/v2/login
  • [ecosystem]

    生态系统ID。

    如果未指定,默认为第一个生态系统ID。

  • [expire]

    JWT令牌的生命周期,以秒为单位,默认为28800。

  • [pubkey]

    十六进制账户公钥。

  • [key_id]

    账户地址 XXXX-...-XXXX

    在公钥已经存储在区块链中的情况下使用此参数。不能与 pubkey 参数一起使用。

  • signature

    通过getuid收到的uid签名。

响应
  • token

    JWT令牌。

  • ecosystem

    生态系统ID。

  • key_id

    账户地址ID

  • address

    钱包地址 XXXX-XXXX-.....-XXXX

  • notify_key

    通知ID。

  • isnode

    该账户地址是否是该节点的所有者。值: true,false

  • isowner

    该账户地址是否是该生态系统的创建者。值: true,false

  • obs

    登录的生态系统是否为VDE。值: true,false

响应示例
200 (OK)
Content-Type: application/json
{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9........AHDRDqDFBoWEHw-9lfIcLobehvNEeIYBB4BIb5J72aQ"
    "ecosystem":"1",
    "key_id":"54321",
    "address": "4321-....-2223"
}
错误响应

E_SERVER, E_UNKNOWNUID, E_SIGNATURE, E_STATELOGIN, E_EMPTYPUBLIC

服务端命令接口

version

GET/ 返回当前服务器版本。

该请求不需要登录授权。

请求
GET
/api/v2/version
响应示例
200 (OK)
Content-Type: application/json
"1.2.6"

数据请求功能接口

balance

GET/ 请求当前生态系统中帐户地址的余额。

请求
GET
/api/v2/balance/{wallet}
  • wallet

    地址标识符,可以任何格式指定 int64, uint64, XXXX-...-XXXX。 在用户当前登录的生态系统中查询该地址。

响应
  • amount

    最小单位的帐户余额。

  • money

    帐户余额。

响应示例
200 (OK)
Content-Type: application/json
{
    "amount": "877450000000000",
    "money": "877.45"
}
错误响应

E_SERVER, E_INVALIDWALLET

blocks

GET/ 返回其中包含每个区块中交易的相关附加信息列表。

该请求不需要登录授权。

请求
GET
/api/v2/blocks
  • block_id

    要查询的起始区块高度。

  • count

    区块数量。

响应
  • 区块高度

    区块中的交易列表以及每个交易的附加信息:

    • hash

      交易哈希。

    • contract_name

      合约名称。

    • params

      合约参数数组。

    • key_id

      对于第一个区块,是签署该交易的第一个区块的账户地址。

      对于所有其他区块,是签署该交易的账户地址。

响应示例
200 (OK)
Content-Type: application/json
{"1":
    [{"hash":"O1LhrjKznrYa0z5n5cej6p5Y1j5E9v/oV27VPRJmfgo=",
    "contract_name":"",
    "params":null,
    "key_id":-118432674655542910}]
}
错误响应

E_SERVER, E_NOTFOUND

detailed_blocks

GET/ 返回其中包含每个区块中交易的详细附加信息列表。

该请求不需要登录授权。

请求
GET
/api/v2/detailed_blocks
响应
  • 区块高度

    • 区块头

      区块头包含以下字段:

      • block_id

        区块高度。

      • time

        区块生成时间戳。

      • key_id

        签署该区块的账户地址。

      • node_position

        在验证节点列表中生成区块的节点的位置。

      • version

        区块结构版本。

    • hash

      区块哈希。

    • node_position

      在验证节点列表中生成区块的节点的位置。

    • key_id

      签署该区块的账户地址。

    • time

      区块生成时间戳。

    • tx_count

      该区块内的交易数。

    • rollback_hash

      区块回滚哈希值。

    • mrkl_root

      该区块交易的默克尔树。

    • bin_data

      区块头、区块内所有交易、上一个区块哈希和生成该区块的节点私钥的序列化。

    • sys_update

      区块内是否包含更新系统参数的交易。

    • 交易

      区块中的交易列表以及每个交易的附加信息:

      • hash

        交易哈希。

      • contract_name

        合约名称。

      • params

        合约参数。

      • key_id

        签署该交易的账户地址。

      • time

        交易生成时间戳。

      • type

        交易类型。

响应示例
200 (OK)
Content-Type: application/json
{"1":
    {"header":
        {"block_id":1,
        "time":1551069320,
        "ecosystem_id":0,
        "key_id":-118432674655542910,
        "node_position":0,
        "version":1},
    "hash":"3NxhvswmpGvRdw8HdkrniI5Mx/q14Z4d5hwGKMp6KHI=",
    "ecosystem_id":0,
    "node_position":0,
    "key_id":-118432674655542910,
    "time":1551069320,
    "tx_count":1,
    "rollbacks_hash":"I2JHugpbdMNxBdNW1Uc0XnbiXFtzB74yD9AK5YI5i/k=",
    "mrkl_root":"MTZiMjY2NGJjOWY3MDAyODlhYjkyMDVhZDQwNDgxNzkxMjY1MWJjNjczNDkyZjk5MWI2Y2JkMjAxNTIwYjUyYg==",
    "bin_data":null,
    "sys_update":false,
    "gen_block":false,
    "stop_count":0,
    "transactions":[
        {"hash":"O1LhrjKznrYa0z5n5cej6p5Y1j5E9v/oV27VPRJmfgo=","contract_name":"","params":null,"key_id":0,"time":0,"type":0}]
    }
}
错误响应

E_SERVER, E_NOTFOUND

/data/{table}/{id}/{column}/{hash}

GET/ 如果指定哈希与指定数据表、字段和记录中的数据匹配,则此请求将返回数据。否则返回错误。

该请求不需要登录授权。

请求
GET
/data/{table}/{id}/{column}/{hash}
  • table

    数据表名称。

  • id

    记录ID。

  • column

    字段名称

  • hash

    请求数据的哈希。

响应
二进制数据

keyinfo

GET/ 返回一个生态系统列表,其中包含注册了指定地址的角色。

该请求不需要登录授权。

请求
GET
/api/v2/keyinfo/{key_id}
  • key_id

    地址标识符,可以任何格式指定 int64, uint64, XXXX-...-XXXX

    该请求在所有生态系统中查询。

响应
  • ecosystem

    生态系统ID。

  • name

    生态系统名称。

  • roles

    具有 idname 字段的角色列表。

响应示例
200 (OK)
Content-Type: application/json
[{
    "ecosystem":"1",
    "name":"platform ecosystem",
    "roles":[{"id":"1","name":"Admin"},{"id":"2","name":"Developer"}]
}]
错误响应

E_SERVER, E_INVALIDWALLET

获取指标接口

keys

GET/ 返回账户地址数量。

请求
GET
/api/v2/metrics/keys
响应示例
200 (OK)
Content-Type: application/json
{
    "count": 28
}

blocks

GET/ 返回区块数量。

请求
GET
/api/v2/metrics/blocks
响应示例
200 (OK)
Content-Type: application/json
{
    "count": 28
}

transactions

GET/ 返回交易总数量。

请求
GET
/api/v2/metrics/transactions
响应示例
200 (OK)
Content-Type: application/json
{
    "count": 28
}

ecosystems

GET/ 返回生态系统的数量。

请求
GET
/api/v2/metrics/ecosystems
响应示例
200 (OK)
Content-Type: application/json
{
    "count": 28
}

fullnodes

GET/ 返回验证节点的数量。

GET
/api/v2/metrics/fullnodes
响应示例
200 (OK)
Content-Type: application/json
{
    "count": 28
}

生态系统接口

ecosystemname

GET/ 通过其标识符返回生态系统的名称。

该请求不需要登录授权。

GET
/api/v2/ecosystemname?id=..
  • id

    生态系统ID。

响应示例
200 (OK)
Content-Type: application/json
{
    "ecosystem_name": "platform_ecosystem"
}
错误响应

E_PARAMNOTFOUND

ecosystems

GET/ 返回生态系统数量。

GET
/api/v2/ecosystems/
响应
  • number

    已安装的生态系统数量。

响应示例
200 (OK)
Content-Type: application/json
{
    "number": 100,
}

appparams/{appID}

GET/ 返回当前或指定生态系统中的应用程序参数列表。

请求
GET
/api/v2/appparams
  • [appid]

    应用程序ID。

  • [ecosystem]

    生态系统ID;如果未指定,将返回当前生态系统的参数。

  • [names]

    接收的参数列表。

    可以指定由逗号分隔的参数名称列表,例如: /api/v2/appparams/1?names=name,mypar

响应
  • list

    数组中的每个元素包含以下参数:

    • name,参数名称;
    • value,参数值;
    • conditions,更改参数的权限。
响应示例
200 (OK)
Content-Type: application/json
{
    "list": [{
        "name": "name",
        "value": "MyState",
        "conditions": "true",
    },
    {
        "name": "mypar",
        "value": "My value",
        "conditions": "true",
    },
    ]
}
错误响应

E_ECOSYSTEM

appparam/{appid}/{name}

GET/ 返回当前或指定生态系统中应用程序 {appid} 的参数 {name} 的相关信息。

请求
GET
/api/v2/appparam/{appid}/{name}[?ecosystem=1]
  • appid

    应用程序ID。

  • name

    请求的参数的名称。

  • [ecosystem]

    生态系统ID(可选参数)。

    默认返回当前的生态系统。

响应
  • id

    参数ID。

  • name

    参数名称。

  • value

    参数值。

  • conditions

    更改参数的权限。

响应示例
200 (OK)
Content-Type: application/json
{
    "id": "10",
    "name": "par",
    "value": "My value",
    "conditions": "true"
}
错误响应

E_ECOSYSTEM, E_PARAMNOTFOUND

ecosystemparams

GET/ 返回生态系统参数列表。

请求
GET
/api/v2/ecosystemparams/[?ecosystem=...&names=...]
  • [ecosystem]

    生态系统ID。如果未指定,将返回当前生态系统ID。

  • [names]

    请求参数列表,以逗号分隔。

    例如: /api/v2/ecosystemparams/?names=name,currency,logo*.

响应
  • list

    数组中的每个元素包含以下参数:

    • name

      参数名称。

    • value

      参数值。

    • conditions

      更改参数的权限。

响应示例
200 (OK)
Content-Type: application/json
{
    "list": [{
        "name": "name",
        "value": "MyState",
        "conditions": "true",
    },
    {
        "name": "currency",
        "value": "MY",
        "conditions": "true",
    },
    ]
}
错误响应

E_ECOSYSTEM

ecosystemparam/{name}

GET/ 返回当前或指定生态系统中参数 {name} 的相关信息。

请求
GET
/api/v2/ecosystemparam/{name}[?ecosystem=1]
  • name

    请求的参数名称。

  • [ecosystem]

    可以指定生态系统ID。默认返回当前的生态系统ID。

响应
  • name

    参数名称。

  • value

    参数值。

  • conditions

    更改参数的权限。

响应示例
200 (OK)
Content-Type: application/json
{
    "name": "currency",
    "value": "MYCUR",
    "conditions": "true"
}
错误响应

E_ECOSYSTEM

tables/[?limit=...&offset=...]

GET/ 返回当前生态系统的数据表列表。可以设置偏移量和条目条数。

请求
  • [limit]

    条目条数,默认25条。

  • [offset]

    偏移量,默认为0。

GET
/api/v2/tables
响应
  • count

    数据表中的条目总数。

  • list

    数组中的每个元素包含以下参数:

    • name

      无前缀的数据表名称。

    • count

      数据表中的条目数。

响应示例
200 (OK)
Content-Type: application/json
{
    "count": "100"
    "list": [{
        "name": "accounts",
        "count": "10",
    },
    {
        "name": "citizens",
        "count": "5",
   },
    ]
}

table/{name}

GET/ 返回当前生态系统请求数据表的相关信息。

返回以下字段信息:

  • name

    数据表名称。

  • insert

    新增条目的权限。

  • new_column

    新增字段权限。

  • update

    更改条目权限。

  • columns

    字段相关信息数组:

    • name

      字段名称。

    • type

      字段数据类型。

    • perm

      更改该字段值的权限。

请求
GET
/api/v2/table/mytable
  • name

    无生态系统前缀的数据表名称。

响应
  • name

    无生态系统前缀的数据表名称。

  • insert

    新增条目的权限。

  • new_column

    新增字段的权限。

  • update

    更改条目权限。

  • conditions

    更改表配置的权限。

  • columns

    字段相关信息数组:

    • name

      字段名称。

    • type

      字段数据类型。

    • perm

      更改该字段值的权限。

响应示例
200 (OK)
Content-Type: application/json
{
    "name": "mytable",
    "insert": "ContractConditions(`MainCondition`)",
    "new_column": "ContractConditions(`MainCondition`)",
    "update": "ContractConditions(`MainCondition`)",
    "conditions": "ContractConditions(`MainCondition`)",
    "columns": [{"name": "mynum", "type": "number", "perm":"ContractConditions(`MainCondition`)" },
        {"name": "mytext", "type": "text", "perm":"ContractConditions(`MainCondition`)" }
    ]
}
错误响应

E_TABLENOTFOUND

list/{name}[?limit=...&offset=...&columns=...]

GET/ 返回当前生态系统中指定数据表条目的列表。可以设置偏移量和条目条数。

请求
  • name

    数据表名称。

  • [limit]

    条目条数,默认25条。

  • [offset]

    偏移量,默认为0。

  • [columns]

    请求列的列表,以逗号分隔,如果未指定,将返回所有列。在所有情况下都会返回id列。

GET
/api/v2/list/mytable?columns=name
响应
  • count

    条目总数。

  • list

    数组中的每个元素包含以下参数:

    • id

      条目ID。

    • 请求列的序列。

响应示例
200 (OK)
Content-Type: application/json
{
    "count": "10"
    "list": [{
        "id": "1",
        "name": "John",
    },
    {
        "id": "2",
        "name": "Mark",
   },
    ]
}

sections[?limit=...&offset=...&lang=]

GET/ 返回当前生态系统的 sections 表条目的列表,可以设置偏移量和条目条数。

如果 role_access 字段包含角色列表,并且不包括当前角色,则不会返回记录。title 字段内数据将被请求头的 Accept-Language 语言资源替换。

请求
  • [limit]

    条目条数,默认25条。

  • [offset]

    偏移量,默认为0。

  • [lang]

    该字段指定多语言资源代码或本地化,例如:en,zh。如果未找到指定的多语言资源,例如:en-US,则在多语言资源组 en 中搜索。

GET
/api/v2/sections
响应
  • count

    sections 表条目总数。

  • list

    数组中每个元素都包含sections表中所有列的信息。

响应示例
200 (OK)
Content-Type: application/json
{
    "count": "2"
    "list": [{
        "id": "1",
        "title": "Development",
       "urlpage": "develop",
       ...
    },
    ]
}
错误响应

E_TABLENOTFOUND

row/{name}/{id}[?columns=]

GET/ 返回当前生态系统中指定数据表的条目。可以指定要返回的列。

请求
  • name

    数据表名称。

  • id

    条目ID。

  • [columns]

    请求列的列表,以逗号分隔,如果未指定,将返回所有列。在所有情况下都会返回id列。

GET
/api/v2/row/mytable/10?columns=name
响应
  • value

    接收列值的数组

    • id

      条目ID。

    • 请求列的序列。

响应示例
200 (OK)
Content-Type: application/json
{
    "values": {
    "id": "10",
    "name": "John",
    }
}
错误响应

E_NOTFOUND

systemparams

GET/ 返回平台参数列表。

请求
GET
/api/v2/systemparams/[?names=...]
  • [names]
    请求参数列表,用逗号分隔。例如 /api/v2/systemparams/?names=max_columns,max_indexes
响应
  • list

    数组中每个元素包含以下参数:

    • name

      参数名称。

    • value

      参数值。

    • conditions

      更改参数的权限。

响应示例
200 (OK)
Content-Type: application/json
{
    "list": [{
        "name": "max_columns",
        "value": "100",
        "conditions": "ContractAccess("@1UpdateSysParam")",
    },
    {
        "name": "max_indexes",
        "value": "1",
        "conditions": "ContractAccess("@1UpdateSysParam")",
    },
    ]
}
错误响应

E_PARAMNOTFOUND

history/{name}/{id}

GET/ 返回当前生态系统中指定数据表中条目的更改记录。

请求
  • name

    数据表名称。

  • id

    条目ID。

响应
  • list

    数组中每个元素包含所请求条目的更改记录。

响应示例
200 (OK)
Content-Type: application/json
{
    "list": [
        {
            "name": "default_page",
            "value": "P(class, Default Ecosystem Page)"
        },
        {
            "menu": "default_menu"
        }
    ]
}

interface/{page|menu|block}/{name}

GET/ 返回当前生态系统指定数据表(pages,menu或blocks)中 name 字段的条目。

GET
/api/v2/interface/page/default_page
请求
  • name

    指定表中条目的名称。

响应
  • id

    条目ID。

  • name

    条目名称。

  • other

    该表的其他列。

响应示例
200 (OK)
Content-Type: application/json
{
    "id": "1",
    "name": "default_page",
"value": "P(Page content)",
"default_menu": "default_menu",
"validate_count": 1
}
错误响应

E_QUERY, E_NOTFOUND

合约功能接口

contracts[?limit=...&offset=...]

GET/ 返回当前生态系统中的合约列表,可以设置偏移量和条目条数。

请求
  • [limit]

    条目条数,默认25条。

  • [offset]

    偏移量,默认为0。

GET
/api/v2/contracts
响应
  • count

    条目总数。

  • list

    数组中每个元素包含以下参数:

    • id

      合约ID。

    • name

      合约名称。

    • value

      合约内容。

    • wallet_id

      合约绑定的账户地址。

    • address

      合约绑定的钱包地址 XXXX-...-XXXX

    • ecosystem_id

      合约所属的生态系统ID。

    • app_id

      合约所属的应用程序ID。

    • conditions

      更改合约的权限。

    • token_id

      作为支付合约费用的通证所在的生态系统ID。

响应示例
  200 (OK)
  Content-Type: application/json
  {
      "count": "10"
      "list": [{
          "id": "1",
          "name": "MainCondition",
          "token_id":"1",
          "wallet_id":"0",
          "value":"contract MainCondition {
conditions {
    if(EcosysParam(`founder_account`)!=$key_id)
    {
        warning `Sorry, you dont have access to this action.`
      }
    }
  }",
  "address":"0000-0000-0000-0000-0000",
  "conditions":"ContractConditions(`MainCondition`)"
   },
  ...
    ]
  }

contract/{name}

GET/ 返回指定合约的相关信息。默认在当前生态系统中查询合约。

请求
  • name

    合约名称。

GET
/api/v2/contract/mycontract
响应
  • id

    VM中合约ID。

  • name

    带生态系统ID的合约名称 @1MainCondition

  • state

    合约所属的生态系统ID。

  • walletid

    合约绑定的账户地址。

  • tokenid

    作为支付合约费用的通证所在的生态系统ID。

  • address

    合约绑定的钱包地址 XXXX-...-XXXX

  • tableid

    contracts 表中合约所在的条目ID。

  • fields

    数组中包含合约 data 部分每个参数的结构信息:

    • name

      参数名称。

    • type

      参数类型。

    • optional

      参数选项,true 表示可选参数,false 表示必选参数。

响应示例
200 (OK)
Content-Type: application/json
{
    "fields" : [
        {"name":"amount", "type":"int", "optional": false},
        {"name":"name", "type":"string", "optional": true}
    ],
    "id": 150,
    "name": "@1mycontract",
    "tableid" : 10,
}
错误响应

E_CONTRACT

sendTX

POST/ 接收参数中的交易并将其添加到交易队列,如果请求执行成功,则返回交易哈希。该哈希可获得区块内对应的交易,在发生错误响应时,该哈希包含在错误文本信息中。

请求
  • tx_key

    交易内容,该参数可指定任何名称,支持接收多个交易。

POST
/api/v2/sendTx

Headers:
Content-Type: multipart/form-data

Parameters:
tx1 - 交易1
txN - 交易N
响应
  • hashes

    交易哈希数组:

    • tx1

      交易1的哈希。

    • txN

      交易N的哈希。

响应示例
200 (OK)
Content-Type: application/json
{
    "hashes": {
        "tx1": "67afbc435634.....",
        "txN": "89ce4498eaf7.....",
}
错误响应

E_LIMITTXSIZE,*E_BANNED*

txstatus

POST/ 返回指定交易哈希的区块ID和错误信息,如果区块ID和错误文本信息的返回值为空,则该交易尚未包含在区块中。

请求
  • data

    交易哈希的JSON列表。

{"hashes":["contract1hash", "contract2hash", "contract3hash"]}
POST
/api/v2/txstatus/
响应
  • results

    数据字典中交易哈希作为键,交易详细作为值。

    hash

    交易哈希。

    • blockid

      如果交易执行成功,则返回区块ID; 如果交易执行失败,则 blockid0

    • result

      通过 $result 变量返回交易结果。

    • errmsg

      如果执行交易失败,则返回错误文本信息。

响应示例
200 (OK)
Content-Type: application/json
{"results":
  {
    "hash1": {
         "blockid": "3123",
         "result": "",
     },
     "hash2": {
          "blockid": "3124",
          "result": "",
     }
   }
 }
错误响应

E_HASHWRONG, E_HASHNOTFOUND

txinfo/{hash}

GET/ 返回指定哈希的交易相关信息,包括区块ID和确认数。如果指定可选参数,可还可返回合约名称及其相关参数。

请求
  • hash

    交易哈希。

  • [contractinfo]

    合约详细参数标识,要获取该交易相关的合约详情,需指定 contractinfo=1

GET
/api/v2/txinfo/c7ef367b494c7ce855f09aa3f1f2af7402535ea627fa615ebd63d437db5d0c8a?contractinfo=1
响应
  • blockid

    包含该交易的区块ID。如果该值为 0,则找不到该哈希的交易。

  • confirm

    该区块 blockid 的确认数。

  • data

    如果指定了 contentinfo=1,则合约详情返回给该参数。

响应示例
200 (OK)
Content-Type: application/json
{
    "blockid": "9",
    "confirm": 11,
    "data": {
        "block": "9",
        "contract": "@1NewContract",
        "params": {
            "ApplicationId": 1,
            "Conditions": "true",
            "Value": "contract crashci4b {\n\t\t\tdata {}\n\t\t}"
        }
    }
}
错误响应

E_HASHWRONG

txinfoMultiple/

GET/ 返回指定哈希的交易相关信息。

请求
  • hash

    交易哈希列表。

  • [contractinfo]

    合约详细参数标识,要获取该交易相关的合约详情,需指定 contractinfo=1

{"hashes":["contract1hash", "contract2hash", "contract3hash"]}
GET
/api/v2/txinfoMultiple/
响应
  • results

    数据字典中交易哈希作为键,交易详细作为值。

    hash

    交易哈希。

    blockid

    包含该交易的区块ID。如果该值为 0,则找不到该哈希的交易。

    confirm

    该区块 blockid 的确认数。

    data

    如果指定了 contentinfo=1,则合约详情返回给该参数。

响应示例
200 (OK)
Content-Type: application/json
{"results":
  {
    "hash1": {
         "blockid": "3123",
         "confirm": "5",
     },
     "hash2": {
          "blockid": "3124",
          "confirm": "3",
     }
   }
 }
错误响应

E_HASHWRONG

/page/validators_count/{name}

GET/ 返回指定页面所需验证的节点数。

请求
  • name

    带生态系统ID的页面名称,格式为 @ecosystem_id%%page_name%,例如 @1main_page

GET
/api/v2/page/validators_count/@1page_name
响应
  • validate_count

    指定页面所需验证的节点数。

响应示例
200 (OK)
Content-Type: application/json
{"validate_count":1}
错误响应

E_NOTFOUND, E_SERVER

content/menu|page/{name}

POST/ 返回指定页面或菜单名称的代码JSON对象树,这是模版引擎处理的结果。

请求
  • name

    页面或菜单名称。

POST
/api/v2/content/page/default
响应
  • menu

    请求 content/page/... 时,页面所属的菜单名称。

  • menutree

    请求 content/page/... 时,页面的菜单JSON对象树。

  • title–head for the menu content/menu/...

    请求 content/menu/... 时,菜单标题。

  • tree

    页面或菜单JSON对象树。

响应示例
200 (OK)
Content-Type: application/json
{
    "tree": {"type":"......",
          "children": [
               {...},
               {...}
          ]
    },
}
错误响应

E_NOTFOUND

content/source/{name}

POST/ 返回指定页面名称的代码JSON对象树。不执行任何函数或接收任何数据。返回的JSON对象树对应于页面模版,可以在可视化页面设计器中使用。如果找不到页面,则返回404错误。 请求 """""""

  • name

    页面名称。

响应
POST
/api/v2/content/source/default
  • tree

    页面的JSON对象树。

响应示例
200 (OK)
Content-Type: application/json
{
    "tree": {"type":"......",
          "children": [
               {...},
               {...}
          ]
    },
}
错误响应

E_NOTFOUND, E_SERVER

content/hash/{name}

POST/ 返回指定页面名称的SHA256哈希,如果找不到页面,则返回404错误。

该请求不需要登录授权。要向其他节点发出请求时接收正确的哈希,还必须传递 ecosystem,keyID,roleID,isMobile 参数。要从其他生态系统接收页面,生态系统ID必须在页面名称中添加前缀。例如:@2mypage

请求
  • name

    带生态系统ID的页面名称。

  • ecosystem

    生态系统ID。

  • keyID

    账户地址。

  • roleID

    角色ID。

  • isMobile

    移动平台的参数标识。

POST
/api/v2/content/hash/default
响应
  • hex

    十六进制哈希值。

响应示例
200 (OK)
Content-Type: application/json
{
    "hash": "b631b8c28761b5bf03c2cfbc2b49e4b6ade5a1c7e2f5b72a6323e50eae2a33c6"
}
错误响应

E_NOTFOUND, E_SERVER, E_HEAVYPAGE

content

POST/ 从 template 参数返回页面代码的JSON对象数,如果将可选参数 source 指定为 true或1,则该JSON对象树不执行任何函数和接收数据。该JSON对象树可以在可视化页面设计器中使用。

该请求不需要登录授权。

请求
  • template

    页面代码。

  • [source]

    如果指定为 true或1,则JSON对象树不执行任何函数和接收数据。

POST
/api/v2/content
响应
  • tree

    JSON对象树。

响应示例
200 (OK)
Content-Type: application/json
{
    "tree": {"type":"......",
          "children": [
               {...},
               {...}
          ]
    },
}
错误响应

E_NOTFOUND, E_SERVER

maxblockid

GET/ 返回当前节点上的最高区块ID。

该请求不需要登录授权。

请求
GET
/api/v2/maxblockid
响应
  • max_block_id

    当前节点上的最高区块ID。

响应示例
200 (OK)
Content-Type: application/json
{
    "max_block_id" : 341,
}
错误响应

E_NOTFOUND

block/{id}

GET/ 返回指定区块ID的相关信息。

该请求不需要登录授权。

请求
  • id

    区块ID。

POST
/api/v2/block/32
响应
  • hash

    区块哈希值。

  • key_id

    签署该区块的账户地址。

  • time

    区块生成时间戳。

  • tx_count

    该区块内的交易总数。

  • rollbacks_hash

    区块回滚哈希值。

  • node_position

    该区块在验证节点列表的位置。

响应示例
200 (OK)
Content-Type: application/json
{
    "hash": "1x4S5s/zNUTopP2YK43SppEyvT2O4DW5OHSpQfp5Tek=",
    "key_id": -118432674655542910,
    "time": 1551145365,
    "tx_count": 3,
    "rollbacks_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
    "node_position": 0,
}
错误响应

E_NOTFOUND

avatar/{ecosystem}/{member}

GET/ 返回 member 表中用户的头像(无需登录即可使用)。

请求
  • ecosystem

    生态系统ID。

  • member

    用户的账户地址。

GET
/api/v2/avatar/1/-118432674655542910
响应

请求头 Content-Type 为图像类型,图像数据在响应体中返回。

响应示例
200 (OK)
Content-Type: image/png
错误响应

E_NOTFOUND E_SERVER

config/centrifugo

GET/ 返回centrifugo的主机地址和端口。

该请求不需要登录授权。

请求
GET
/api/v2/config/centrifugo
响应

响应结果格式 http://address:port,例如: http://127.0.0.1:8100

错误响应

E_SERVER

updnotificator

POST/ 发送尚未发送到centrifugo通知服务的所有消息。仅发送指定生态系统和成员的消息。

该请求不需要登录授权。

请求
  • id

    成员的账户地址。

  • ecosystem

    生态系统ID。

POST
/api/v2/updnotificator
响应示例
200 (OK)
Content-Type: application/json
{
    "result": true
}

平台参数

关于平台参数

平台参数是区块链平台的配置参数。这些参数适用于区块链网络和网络中的所有生态系统。

存储平台参数的位置

平台参数存储在 system_parameters 数据表。

该数据表在区块链网络上创建的第一个(默认)生态系统中

更改平台参数

必须在投票通过后才能更改平台参数。

平台参数配置

新生态系统配置

默认页面和菜单:

默认合约:

数据库配置

数据表限制:

区块生成配置

时间限制:

交易数量限制:

大小限制:

燃料限制:

区块回滚限制:

弃用配置

已弃用参数:

平台参数详情

block_reward

授予生成区块的验证节点的 GAC 通证数量。

接受奖励的帐户在 full_nodes 参数中指定。

blockchain_url

该参数已弃用。

commission_size

佣金百分比。

这笔佣金数量为执行合约总费用按百分比计算得出。该佣金通证单位 GAC。

佣金将转移到 commission_wallet 参数中指定的帐户地址。

commission_wallet

收取佣金的账户地址。

佣金数量由 commission_size 参数指定。

default_ecosystem_contract

新生态系统默认合约的源代码。

该合约为生态系统创建者提供访问权限。

default_ecosystem_menu

新生态系统的默认菜单的源代码。

default_ecosystem_page

新生态系统的默认页面的源代码。

fuel_rate

不同生态系统通证对燃料单位的费率。

该参数的格式:

[["ecosystem_id", "token_to_fuel_rate"], ["ecosystem_id2", "token_to_fuel_rate2"], ...]

  • ecosystem_id

    生态系统ID。

  • token_to_fuel_rate

    通证对燃料单位的费率。

例如:

[["1","1000000000000"], ["2", "1000"]]

生态系统1的一个通证被交换到1000000000000个燃料单位。生态系统2的一个通证被交换到1000个燃料单位。

price_create_rate

新建元素的燃料费率

full_nodes

区块链网络的验证节点列表。

该参数的格式:

[["tcp_host:port1","api_host:port2","wallet_id","node_pub"], ["tpc_host2:port1","api_host2:port2","wallet_id2","node_pub2"]]

  • tcp_host:port1

    节点主机的TCP地址和端口。

    交易和新区块将发送到该主机地址。该主机地址还可用于从第一个区块开始获取完整的区块链。

  • api_host:port2

    节点主机的API地址和端口。

    通过API地址可以在不使用Govis软件客户端的情况下访问平台的任何功能。详情 RESTful API

  • wallet_id

    账户地址,用于收取生成新区块和处理交易的奖励。

  • node_pub

    节点的公钥。此公钥用于验证区块签名。

gap_between_blocks

节点生成前后区块的时间间隔(以秒为单位)。

网络中的所有节点都使用它来确定何时生成新区块,如果当前节点在此时间段内未生成新区块,则转向传递到验证节点列表中的下一个节点。

该参数最小值为 1 秒。

incorrect_blocks_per_day

节点每天在被禁令前允许生成的坏区块数量。

当网络中超过一半的节点从某个节点收到此数量的坏区块时,此节点将在 node_ban_time 时间内从网络中被禁令。

max_block_generation_time

生成区块的最大时间,单位毫秒,该时间内如果未能成功生成区块,则报错超时。

max_block_size

区块最大大小,单位字节。

max_columns

单个数据表的最大字段数。

这个最大值不包括预定义的 id 列。

max_forsign_size

交易签名最大大小,单位字节。

max_fuel_block

单个区块的最大总燃料费用。

max_fuel_tx

单笔交易的最高总燃料费用。

max_indexes

单个数据表中的最大主键字段数。

max_tx_block

单个区块中的最大交易数。

max_tx_block_per_user

一个账户在一个区块内的最大交易数。

max_tx_size

最大交易大小,以字节为单位。

node_ban_time

节点的全局禁令期,以毫秒为单位。

当网络中超过一半的节点从某个节点收到坏区块达到 incorrect_blocks_per_day 数量时,该节点将在该时间内从网络中被禁令。

node_ban_time_local

节点的本地禁令期,以毫秒为单位。

当一个节点从另一个节点接收到不正确的块时,它将在这段时间内本地禁令发送方节点。

number_of_nodes

full_nodes 参数中的最大验证节点数量。

price_create_ecosystem

创建新单个生态系统的燃料费用。

该参数定义了 @1NewEcosystem 合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。

该参数以燃料单位计算。使用 fuel_rateprice_create_rate 将燃料单位转换为 GAC 通证。

price_create_application

创建新单个应用程序的燃料费用。

该参数定义了 @1NewApplication 合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。

该参数以燃料单位计算。使用 fuel_rateprice_create_rate 将燃料单位转换为 GAC 通证。

price_create_table

创建新单个数据表的燃料费用。

该参数定义了 @1NewTable 合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。

该参数以燃料单位计算。使用 fuel_rateprice_create_rate 将燃料单位转换为 GAC 通证。

price_create_column

创建新单个表字段的燃料费用。

该参数定义了 @1NewColumn 合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。

该参数以燃料单位计算。使用 fuel_rateprice_create_rate 将燃料单位转换为 GAC 通证。

price_create_contract

创建新单个合约的燃料费用。

该参数定义了 @1NewContract 合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。

该参数以燃料单位计算。使用 fuel_rateprice_create_rate 将燃料单位转换为 GAC 通证。

price_create_menu

创建新单个菜单的燃料费用。

该参数定义了 @1NewMenu 合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。

该参数以燃料单位计算。使用 fuel_rateprice_create_rate 将燃料单位转换为 GAC 通证。

price_create_page

创建新单个页面的燃料费用。

该参数定义了 @1NewPage 合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。

该参数以燃料单位计算。使用 fuel_rateprice_create_rate 将燃料单位转换为 GAC 通证。

price_exec_address_to_id

调用 AddressToId() 函数的燃料费用,以燃料单位计算。

price_exec_bind_wallet

调用 Activate() 函数的燃料费用,以燃料单位计算。

price_exec_column_condition

调用 ColumnCondition() 函数的燃料费用,以燃料单位计算。

price_exec_compile_contract

调用 CompileContract() 函数的燃料费用,以燃料单位计算。

price_exec_contains

调用 Contains() 函数的燃料费用,以燃料单位计算。

price_exec_contract_by_id

调用 GetContractById() 函数的燃料费用,以燃料单位计算。

price_exec_contract_by_name

调用 GetContractByName() 函数的燃料费用,以燃料单位计算。

price_exec_contracts_list

调用 ContractsList() 函数的燃料费用,以燃料单位计算。

price_exec_create_column

调用 CreateColumn() 函数的燃料费用,以燃料单位计算。

price_exec_create_ecosystem

调用 CreateEcosystem() 函数的燃料费用,以燃料单位计算。

price_exec_create_table

调用 CreateTable() 函数的燃料费用,以燃料单位计算。

price_exec_ecosys_param

调用 EcosysParam() 函数的燃料费用,以燃料单位计算。

price_exec_eval

调用 Eval() 函数的燃料费用,以燃料单位计算。

price_exec_eval_condition

调用 EvalCondition() 函数的燃料费用,以燃料单位计算。

price_exec_flush_contract

调用 FlushContract() 函数的燃料费用,以燃料单位计算。

price_exec_has_prefix

调用 HasPrefix() 函数的燃料费用,以燃料单位计算。

price_exec_id_to_address

调用 IdToAddress() 函数的燃料费用,以燃料单位计算。

price_exec_is_object

调用 IsObject() 函数的燃料费用,以燃料单位计算。

price_exec_join

调用 Join() 函数的燃料费用,以燃料单位计算。

price_exec_json_to_map

调用 JSONToMap() 函数的燃料费用,以燃料单位计算。

price_exec_len

调用 Len() 函数的燃料费用,以燃料单位计算。

price_exec_perm_column

调用 PermColumn() 函数的燃料费用,以燃料单位计算。

price_exec_perm_table

调用 PermTable() 函数的燃料费用,以燃料单位计算。

price_exec_pub_to_id

调用 PubToID() 函数的燃料费用,以燃料单位计算。

price_exec_replace

调用 Replace() 函数的燃料费用,以燃料单位计算。

price_exec_sha256

调用 Sha256() 函数的燃料费用,以燃料单位计算。

price_exec_size

调用 Size() 函数的燃料费用,以燃料单位计算。

price_exec_substr

调用 Substr() 函数的燃料费用,以燃料单位计算。

price_exec_sys_fuel

调用 SysFuel() 函数的燃料费用,以燃料单位计算。

price_exec_sys_param_int

调用 SysParamInt() 函数的燃料费用,以燃料单位计算。

price_exec_sys_param_string

调用 SysParamString() 函数的燃料费用,以燃料单位计算。

price_exec_table_conditions

调用 TableConditions() 函数的燃料费用,以燃料单位计算。

price_exec_unbind_wallet

调用 Deactivate() 函数的燃料费用,以燃料单位计算。

price_exec_update_lang

调用 UpdateLang() 函数的燃料费用,以燃料单位计算。

price_exec_validate_condition

调用 ValidateCondition() 函数的燃料费用,以燃料单位计算。

price_tx_data

交易每1024字节数据的燃料费用,以燃料单位计算。

price_tx_size_wallet

交易大小费用,以 GAC 通证为单位。

除生态系统1之外,在其它生态系统内执行合约将按照比例产生区块空间使用费用,每兆交易大小产生 price_tx_size_wallet GAC 通证费用。

rollback_blocks

在区块链中检测到分叉时可以回滚的最大区块数。

服务端配置文件

该章节介绍服务端配置文件参数。

服务端配置文件简介

服务端配置文件定义了 GAChain 节点的配置。

位置

该文件位于服务端工作目录下,名为 config.toml

部分

配置文件有以下几个部分:

普通部分

定义工作目录DataDir,第一个区块目录FirstBlockPath等参数。

[TCPServer]

定义TCP服务参数。

TCPServer用于节点之间的网络交互。

[HTTP]

定义HTTP服务参数。

HTTPServer提供RESTful API。

[DB]

定义节点数据库PostgreSQL的参数。

[StatsD]

定义节点操作指标收集器StatsD的参数。

[Centrifugo]

定义通知服务Centrifugo的参数。

[Log]

定义了日志服务Log的参数。

[TokenMovement]

定义了通证流通服务TokenMovement的参数。

配置文件示例

PidFilePath = "/gachain-data/go-gachain.pid"
LockFilePath = "/gachain-data/go-gachain.lock"
DataDir = "/gachain-data"
KeysDir = "/gachain-data"
TempDir = "/var/folders/_l/9md_m4ms1651mf5pbng1y1xh0000gn/T/gachain-temp"
FirstBlockPath = "/gachain-data/1block"
TLS = false
TLSCert = ""
TLSKey = ""
OBSMode = "none"
HTTPServerMaxBodySize = 1048576
MaxPageGenerationTime = 3000
NodesAddr = []

[TCPServer]
  Host = "127.0.0.1"
  Port = 7078

[HTTP]
  Host = "127.0.0.1"
  Port = 7079

[DB]
  Name = "gachain"
  Host = "127.0.0.1"
  Port = 5432
  User = "postgres"
  Password = "gachain"
  LockTimeout = 5000

[StatsD]
  Host = "127.0.0.1"
  Port = 8125
  Name = "gachain"

[Centrifugo]
  Secret = "127.0.0.1"
  URL = "127.0.0.1"

[Log]
  LogTo = "stdout"
  LogLevel = "ERROR"
  LogFormat = "text"
  [Log.Syslog]
    Facility = "kern"
    Tag = "go-gachain"

[TokenMovement]
  Host = ""
  Port = 0
  Username = ""
  Password = ""
  To = ""
  From = ""
  Subject = ""

同步监控工具

Desync_monitor是一种特殊工具,可用于验证指定节点上的数据库是否已同步。

该工具可以作为守护进程使用,也可以启动以执行一次性检查。

该工具的操作原理基于以下内容:

  1. 每个区块包含所有交易的所有更改的哈希,请求指定的节点提供其最后一个区块ID;
  2. 然后从所有节点请求具有该ID的区块,并比较上述哈希;
  3. 如果哈希不同,会将同步错误消息发送到命令中指定的电子邮件地址。

位置

该工具位于 tools/desync_monitor/

命令提示标志

可以从命令提示符使用以下标志:

  • confPath – 配置文件的路径。默认文件名为 config.toml
  • nodesList – 请求区块的节点列表,以逗号分隔。默认为 127.0.0.1:7079
  • daemonMode – 作为守护进程启动,应该在每N秒需要验证的情况下使用。该标志默认设置为 false
  • queryingPeriod – 如果工具作为守护进程启动,该参数设置检查之间的时间间隔(以秒为单位)。默认为 1 秒。
  • alertMessageTo – 将向其发送同步警告错误的电子邮件地址。默认为 support@block.vc

    • alertMessageSubj – 在警告消息中的消息主题,默认为节点同步问题;
    • alertMessageFrom – 发送消息的地址,默认为 support@block.vc
    • smtpHost – SMTP服务器主机,用于发送电子邮件,默认为 ""
    • smtpPort – SMTP服务器端口,用于发送电子邮件消息,默认为 25
    • smtpUsername – SMTP服务器用户名,默认为 ""
    • smtpPassword – SMTP服务器密码,默认为 ""

配置

该工具使用toml格式的配置文件。

默认情况下,它会在启动二进制文件的文件夹中查找 config.toml 文件。

可以使用 configPath 标志更改文件路径。

nodes_list = ["http://127.0.0.1:7079", "http://127.0.0.1:7002"]

[daemon]
daemon = false
querying_period = 1

[alert_message]
to = "support@block.vc"
subject = "problem with gachain nodes"
from = "support@block.vc"

[smtp]
host = ""
port = 25
username = ""
password = ""

nodes_list

  • nodes_list – 请求信息的节点(主机)列表。

[daemon]

守护进程模式配置。

  • daemon_mode – 工具作为守护进程工作并执行同步检查。
  • querying_period – 同步检查之间的时间间隔。

[alert_message]

警告消息参数。

  • to – 同步错误警告消息的收件地址;
  • subject – 消息主题;
  • from – 发件地址。

[smtp]

简单邮件传输协议 (Simple Mail Transfer Protocol, SMTP) 服务器的参数,用于发送同步错误消息。

  • host – SMTP服务器主机;
  • port –SMTP服务器端口;
  • username – SMTP服务器用户名;
  • password –SMTP服务器密码;

GAChain 区块链网络部署

本章节演示如何部署自己的区块链网络。

注解

  • 如果您想尝试使用 GAChain 区块链网络,请查看 GAChain Testnet

部署示例

以三个节点为示例部署区块链网络。

三个网络节点:

  • 节点1是区块链网络中的第一个节点,它可以生成新区块并从连接到它的客户端发送交易;
  • 节点2是另一个验证节点,它可以生成新区块并从连接到它的客户端发送交易;
  • 节点3是一个全节点,它不能生成新区块,但可以从连接到它的客户端发送交易。

三个节点部署以下配置:

  • 每个节点都使用自己的PostgreSQL数据库系统实例;
  • 每个节点都使用自己的Centrifugo服务实例;
  • go-gachain服务与其他后端组件部署在同一主机上。

节点使用的示例地址和端口如下表所述:

节点 组件 IP和端口
1 PostgreSQL 127.0.0.1:5432
1 Centrifugo 192.168.1.1:8000
1 go-gachain (TCP服务) 192.168.1.1:7078
1 go-gachain (API服务) 192.168.1.1:7079
2 PostgreSQL 127.0.0.1:5432
2 Centrifugo 192.168.1.2:8000
2 go-gachain (TCP服务) 192.168.1.2:7078
2 go-gachain (API服务) 192.168.1.2:7079
3 PostgreSQL 127.0.0.1:5432
3 Centrifugo 192.168.1.3:8000
3 go-gachain (TCP服务) 192.168.1.3:7078
3 go-gachain (API服务) 192.168.1.3:7079

服务端部署

部署第一个节点

第一个节点是一个特殊节点,因为它必须用于启动区块链网络。区块链的第一个区块由第一个节点生成,所有其他节点从中下载区块链。第一个节点的所有者为平台创始人。

依赖关系和环境设置
sudo

Debian 9的所有命令必须以非root用户身份运行。但是某些系统命令需要执行超级用户权限。默认情况下,Debian 9上没有安装sudo,您必须先安装它。

  1. 成为超级用户。
su -

2) 升级您的系统。 .. code-block:: bash

apt update -y && apt upgrade -y && apt dist-upgrade -y
  1. 安装sudo。
apt install sudo -y
  1. 将您的用户添加到sudo组。
usermod -a -G sudo user
  1. 重启后,更改生效。
Go 语言

按照 官方文档 的说明按照Go。

  1. Golang官方网站 或通过命令行下载最新的稳定版Go(> 1.10.x):
wget https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz
  1. 将安装包解压缩到 /usr/local.
tar -C /usr/local -xzf go1.11.2.linux-amd64.tar.gz
  1. 添加 /usr/local/go/bin 到PATH环境变量 (位于 /etc/profile$HOME/.profile)。
export PATH=$PATH:/usr/local/go/bin
  1. 要使更改生效,请执行 source 该文件,例如:
source $HOME/.profile
  1. 删除临时文件:
rm go1.11.2.linux-amd64.tar.gz
PostgreSQL
  1. 安装PostgreSQL(> v.10)和psql:
sudo apt install -y postgresql
Centrifugo
  1. GitHub 或通过命令行下载Centrifugo 1.8.0版本:
wget https://github.com/centrifugal/centrifugo/releases/download/v1.8.0/centrifugo-1.8.0-linux-amd64.zip \
&& unzip centrifugo-1.8.0-linux-amd64.zip \
&& mkdir centrifugo \
&& mv centrifugo-1.8.0-linux-amd64/* centrifugo/
  1. 删除临时文件:
rm -R centrifugo-1.8.0-linux-amd64 \
&& rm centrifugo-1.8.0-linux-amd64.zip
目录结构

对于Debian 9 系统,建议将区块链平台使用的所有软件存储在单独的目录中。

在这里使用 /opt/gachain 目录,但您可以使用任何目录。在这种情况下,请相应地更改所有命令和配置文件。

  1. 为区块链平台创建一个目录:
sudo mkdir /opt/gachain
  1. 使您的用户成为该目录的所有者:
sudo chown user /opt/gachain/
  1. 为Centrifugo、go-gachain和节点数据创建子目录。所有节点数据都存储在名为 nodeX 的目录中,其中 X 为节点号。根据要部署的节点,node1 为节点1,node2 为节点2,以此类推。
mkdir /opt/gachain/go-gachain \
mkdir /opt/gachain/go-gachain/node1 \
mkdir /opt/gachain/centrifugo \
创建数据库
  1. 将用户密码postgres更改为GAChain的默认密码 gachain。您可以设置自己的密码,但必须在节点配置文件 config.toml 中进行更改。
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'gachain'"
  1. 创建节点当前状态数据库,例如 gachaindb:
sudo -u postgres psql -c "CREATE DATABASE gachaindb"
配置Centrifugo
  1. 创建Centrifugo配置文件:
echo '{"secret":"CENT_SECRET"}' > /opt/gachain/centrifugo/config.json

您可以设置自己的 secret,但是您还必须在节点配置文件 config.toml 中更改它。

安装go-gachain
  1. 从GitHub下载 最新版本的go-gachain
  2. 将go-gachain二进制文件复制到 /opt/gachain/go-gachain 目录。如果您使用的是 默认的Go工作区 则二进制文件位于 $HOME/go/bin 目录:
cp $HOME/go/bin/go-gachain /opt/gachain/go-gachain
配置第一个节点
  1. 创建节点1配置文件:
/opt/gachain/go-gachain/go-gachain config \
    --dataDir=/opt/gachain/go-gachain/node1 \
    --dbName=gachaindb \
    --centSecret="CENT_SECRET" --centUrl=http://192.168.1.1:8000 \
    --httpHost=192.168.1.1 \
    --httpPort=7079 \
    --tcpHost=192.168.1.1 \
    --tcpPort=7078
  1. 生成节点1的密钥,包括节点公私钥和账户公私钥:
/opt/gachain/go-gachain/go-gachain generateKeys \
    --config=/opt/gachain/go-gachain/node1/config.toml
  1. 生成第一个区块:

注解

如果您要创建自己的区块链网络。你必须使用该 --test=true 选项。否则您将无法创建新帐户。

/opt/gachain/go-gachain/go-gachain generateFirstBlock \
    --config=/opt/gachain/go-gachain/node1/config.toml \
    --test=true
  1. 初始化数据库:
/opt/gachain/go-gachain/go-gachain initDatabase \
    --config=/opt/gachain/go-gachain/node1/config.toml
启动第一个节点服务端

要启动第一个节点服务端,您必须启动两个服务:

  • centrifugo
  • go-gachain

如果您没有将这些文件创建 services,那么您可以从不同控制台的目录中执行二进制文件。

  1. 运行centrifugo:
/opt/gachain/centrifugo/centrifugo \
    -a 192.168.1.1 -p 8000 \
    --config /opt/gachain/centrifugo/config.json
  1. 运行go-gachain:
/opt/gachain/go-gachain/go-gachain start \
    --config=/opt/gachain/go-gachain/node1/config.toml

部署其他节点

所有其他节点(节点2和节点3)的部署与第一个节点类似,但有三个不同之处:

  • 您不需要生成第一个区块。但是它必须从节点1复制到当前节点数据目录;
  • 该节点必须通过配置 --nodesAddr 选项从节点1下载区块;
  • 该节点必须使用自己的地址和端口。
节点2

按照以下一系列操作:

  1. 依赖关系和环境设置

  2. 创建数据库

  3. 配置Centrifugo

  4. 安装go-gachain

  5. 创建节点2配置文件:

    /opt/gachain/go-gachain/go-gachain config \
        --dataDir=/opt/gachain/go-gachain/node2 \
        --dbName=gachaindb \
        --centSecret="CENT_SECRET" --centUrl=http://192.168.1.2:8000 \
        --httpHost=192.168.1.2 \
        --httpPort=7079 \
        --tcpHost=192.168.1.2 \
        --tcpPort=7078 \
        --nodesAddr=192.168.1.1
    
  6. 复制第一个区块文件到节点2,例如,您可以通过 scp 在节点2执行该操作:

    scp user@192.168.1.1:/opt/gachain/go-gachain/node1/1block /opt/gachain/go-gachain/node2/
    
  7. 生成节点2的密钥,包括节点公私钥和账户公私钥:

    /opt/gachain/go-gachain/go-gachain generateKeys \
        --config=/opt/gachain/go-gachain/node2/config.toml
    
  8. 初始化数据库:

    ./go-gachain initDatabase --config=node2/config.toml
    
  9. 运行centrifugo:

    /opt/gachain/centrifugo/centrifugo \
        -a 192.168.1.2 -p 8000 \
        --config /opt/gachain/centrifugo/config.json
    
  10. 运行go-gachain:

    /opt/gachain/go-gachain/go-gachain start \
        --config=/opt/gachain/go-gachain/node2/config.toml
    

结果,节点从第一个节点下载区块。该节点不是验证节点,因此无法生成新区块。节点2将后面添加到验证节点列表中。

节点3

按照以下一系列操作:

  1. 依赖关系和环境设置

  2. 创建数据库

  3. 配置Centrifugo

  4. 安装go-gachain

  5. 创建节点3配置文件:

    /opt/gachain/go-gachain/go-gachain config \
        --dataDir=/opt/gachain/go-gachain/node3 \
        --dbName=gachaindb \
        --centSecret="CENT_SECRET" --centUrl=http://192.168.1.3:8000 \
        --httpHost=192.168.1.3 \
        --httpPort=7079 \
        --tcpHost=192.168.1.3 \
        --tcpPort=7078 \
        --nodesAddr=192.168.1.1
    
  6. 复制第一个区块文件到节点3,例如,您可以通过 scp 在节点3执行该操作:

    scp user@192.168.1.1:/opt/gachain/go-gachain/node1/1block /opt/gachain/go-gachain/node3/
    
  7. 生成节点3的密钥,包括节点公私钥和账户公私钥:

    /opt/gachain/go-gachain/go-gachain generateKeys \
        --config=/opt/gachain/go-gachain/node3/config.toml
    
  8. 初始化数据库:

    ./go-gachain initDatabase --config=node3/config.toml
    
  9. 运行centrifugo:

    /opt/gachain/centrifugo/centrifugo \
        -a 192.168.1.3 -p 8000 \
        --config /opt/gachain/centrifugo/config.json
    
  10. 运行go-gachain:

    /opt/gachain/go-gachain/go-gachain start \
        --config=/opt/gachain/go-gachain/node3/config.toml
    

结果,节点从第一个节点下载区块。该节点不是验证节点,因此无法生成新区块。客户端可以连接到该节点,它可以将交易发送到网络。

前端部署

只有在Debian 9(Stretch)64位 官方发行版 上安装 GNOME GUI,Govis客户端才能由 yarn 包管理器构建。

软件先决条件

Node.js
  1. Node.js官方网站 或通过命令行下载Node.js LTS版本8.11 :
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash
  1. 安装Node.js:
sudo apt install -y nodejs
Yarn
  1. yarn的Github仓库 或通过命令行下载Yarn版本1.7.0 :
cd /opt/gachain \
&& wget https://github.com/yarnpkg/yarn/releases/download/v1.7.0/yarn_1.7.0_all.deb
  1. 安装Yarn:
sudo dpkg -i yarn_1.7.0_all.deb && rm yarn_1.7.0_all.deb

构建Govis应用程序

  1. 通过git从 Govis的GitHub仓库 下载Govis的最新版本:
cd /opt/gachain \
&& git clone https://github.com/GACHAIN/gachain-front.git
  1. 通过Yarn安装Govis依赖项:
cd /opt/gachain/gachain-front/ \
&& yarn install
添加区块链网络配置
  1. 创建包含有关节点连接信息的 settings.json 文件:
cp /opt/gachain/gachain-front/public/settings.json.dist \
    /opt/gachain/gachain-front/public/public/settings.json
  1. 在任何文本编辑器中编辑 settings.json 文件,并以此格式添加所需的设置:
http://Node_IP-address:Node_HTTP-Port

三个节点的 settings.json 文件示例:

{
    "fullNodes": [
        "http://192.168.1.1:7079",
        "http://192.168.1.2:7079",
        "http://192.168.1.3:7079"
    ]
}
构建Govis桌面版应用程序
  1. 使用yarn构建桌面版:
cd /opt/gachain/gachain-front \
&& yarn build-desktop
  1. 桌面版将打包成AppImage后缀格式:
yarn release --publish never -l

构建之后,您的应用程序就可以使用了,但是其 连接配置 将无法更改。如果这些设置需要更改,则必须构建新版本的应用程序。

构建Govis Web应用程序
  1. 构建Web应用程序:
cd /opt/gachain/gachain-front/ \
&& yarn build

构建之后,可再发行文件将放置到 /build 目录中。您可以使用您选择的任何Web服务器进行部署,settings.json 文件也必须放在该目录。请注意,如果连接设置发生更改,则无需再次构建应用程序。而是编辑 settings.json 文件并重新启动Web服务器。

  1. 出于开发或测试目的,您可以构建Yarn的Web服务器:
sudo yarn global add serve \
&& serve -s build

之后,您的Govis Web应用程序将在以下位置可用: http://localhost:5000

区块链网络配置

创建创始人的帐户

为第一个节点所有者创建一个帐户。该帐户是新区块链平台的创始人,并具有管理员访问权限。

  1. 运行 Govis (前端);

  2. 使用以下数据导入现有帐户:

    • 节点所有者私钥的备份加载位于 /opt/gachain/go-gachain/node1/PrivateKey 文件中。

      注解

      该目录中有两个私钥文件。PrivateKey 文件用于节点所有者的帐户,可创建节点所有者的帐户。NodePrivateKey 文件是节点本身的私钥,必须保密。

  3. 登录该账户后,由于此时尚未创建角色,因此请选择 Without role 选项。

导入应用、角色和模版

此时,区块链平台处于空白状态。您可以通过添加支持基本生态系统功能的角色、模版和应用程序框架来配置它。

  1. 克隆应用程序存储库;
cd /opt/gachain \
&& git clone https://github.com/GACHAIN/apps.git
  1. 在Govis中导航到 Developer > 导入

  2. 按此顺序导入应用:

    1. /opt/gachain/apps/1_system.json
    2. /opt/gachain/apps/2_lang_res.json
    3. /opt/gachain/apps/3_basic.json
    4. /opt/gachain/apps/4_conditions.json
  3. 导航到 Admin > 角色,然后单击 安装默认角色

  4. 通过右上角的配置文件菜单退出系统;

  5. Admin 角色登录系统;

  6. 导航到 Home > 投票 > 模版列表,然后单击 安装默认模版

将第一个节点添加到节点列表中

  1. 导航到 Admin > 平台参数,然后单击 full_nodes 参数的齿轮图标;

  2. 指定第一个区块链网络节点的参数。

    • public_key - 节点公钥位于 /opt/gachain/go-gachain/node1/NodePublicKey 文件;
    • key_id - 节点所有者的账户地址位于 /opt/gachain/go-gachain/node1/KeyID 文件。
{"api_address":"http://192.168.1.1:7079","key_id":"%node_owner_key_id%","public_key":"%node_public_key%","tcp_address":"192.168.1.1:7078"}

添加其他验证节点

将成员添加到共识角色

默认情况下,只有共识角色(Consensus)的成员才能参与添加其他验证节点所需的投票。这意味着在添加新的验证节点之前,必须为该角色指定生态系统的成员。

在本章节中,创始人的帐户被指定为共识角色的唯一成员。在生产环境中,必须将该角色分配给平台执行治理的成员。

  1. 导航到 Home > 角色 ,然后单击共识角色(Consensus);
  2. 单击 分配 将创始人的帐户分配给该角色。

创建其他节点所有者帐户

  1. 运行Govis;

  2. 使用以下数据导入现有帐户:

    • 节点所有者私钥的备份加载位于 /opt/gachain/go-gachain/node2/PrivateKey 文件中。
  3. 登录该账户后,由于此时尚未创建角色,因此请选择 Without role 选项;

  4. 导航到 Home > 个人信息,然后单击个人信息名称;

  5. 添加帐户详细信息(个人信息名称,说明等)。

添加节点所有者为Validators角色

  1. 新节点所有者操作:

    1. 导航到 Home > 验证者
    2. 单击 创建请求 并填写验证者候选人的申请表;
    3. 单击 发送请求
  2. 创始人操作:

    1. 以共识角色(Consensus)登录;
    2. 导航到 Home > 验证者
    3. 根据候选人的请求点击“播放”图标开始投票;
    4. 导航到 Home > 投票,然后单击 更新投票状态
    5. 单击投票名称并为节点所有者投票。

结果,新节点所有者的帐户被分配给 Validator 角色,并且新节点也被添加到验证节点列表中。