构建数字生态系统的公共区块链平台¶
关于该文档¶
GAChain 是一个构建数字生态系统的公共区块链平台。
该文档为政务链GAChain系统平台的技术开发文档,包含平台描述、节点部署、开发示例、合约开发语言 GALang 、模版开发语言 GAStyle 的使用方法等。
关于该平台¶
GAChain 区块链是一个安全,简单和可扩展的区块链基础设施,适用于快速增长的协作经济部门。中小型企业将在降低运营成本、加快上市时间、早期发展阶段的筹资方案、业务流程自动化和可扩展性、综合结算系统、合法的反洗钱基础设施、不信任的经济合作、其产品和服务的影响力中受益。GAChain 平台的详细描述位于官网(gachain.org)的白皮书。
GAChain 平台的使用,包括许可条款,受 GAChain 法律文件中规定的条款和条件的约束,该条款和条件可从官网(gachain.org)下载。
政务链平台的基础研发于2011年开展,2016年正式启动政务链(GAChain)的研发,经过政务流程团队、电子政务专家顾问团队和技术团队的努力,至2016第三季度,完成了如下工作:
- 创建了主要网络协议和轻量级软件客户端;
- 开发了智能合约的概念;
- 发布了早期版本的接口界面和合约编程语言。
由于该平台最初是为了实施电子政务领域的项目而开发的,因此团队决定将项目命名为 政务链 (电子政务即服务)。2017年秋季,创建了政务链第一版本(v1)的测试网络。至2017年年底,为广东省南沙自贸区电子服务中心、中央扶贫办、天津市市场监督管理局、香港机场服务管理公司等政企机构开发出相关概念验证项目和解决方案,如多部门电子政务自动协同办公和流转、社会化代收税电子发票系统、精准扶贫平台、供应链管理等。
也是在2017年冬季,政务链团队决定在现有平台的基础上建立一个公共区块链网络,与相关公共安全部门合作,将KYC(个人身份识别)程序整合到用户账户的注册阶段。
由于该项目将不再只适应于政务服务项目,还可广泛用于商业、个人等项目的开发、应用,因此团队将项目的品牌标识做了一定修改:
- 面对政府、事业单位的项目,依然使用“政务链”,英文名为 GAChain ( Government Affairs Chain);
- 面对商业机构、个人客户,中文使用“智乾链”,英文名依然为 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 平台的数据空间被划分为多个相对独立的集群,称为 生态系统,其实现了网络上用户的活动。生态系统是一个自治软件环境,由一定数量的应用程序和用户组成,用户创建这些应用程序并使用它们。任何用户都可以创建一个新的生态系统。
生态系统的软件基础是 应用程序 的集合。
集成开发环境¶
Govis软件客户端包括用于创建区块链应用程序的全套集成开发环境(IDE)。使用此IDE不需要软件开发人员对区块链技术有深刻的了解。
Govis软件客户端提供了数据库表管理工具,合约编辑器,页面编辑器以及在生态系统中创建应用程序所需的其他功能,而无需借助任何其他软件模块。
IDE主要包括以下部分:
- 生态系统参数表;
- 合约编辑器;
- 数据库表管理工具;
- 页面编辑器和可视化页面设计器;
- 多语言资源编辑器;
- 应用程序导入/导出功能。
应用程序¶
应用程序是具有配置访问权限的数据库表、智能合约和用户页面等元素的集合。应用程序元素所属的生态系统由元素名称中的前缀表示,例如 @1ElementName
,其中生态系统ID在 @
符号后的数字 1
表示。在当前生态系统中使用应用程序元素时,可以省略前缀 @1
。这些应用程序可执行有用的功能或实现各种服务。
数据表¶
在 GAChain 平台数据库,每个生态系统可以创建无限数量的数据表。特定生态系统的数据表可以通过包含生态系统ID的前缀来标识,该生态系统ID在该特定生态系统中工作时不会显示在Govis软件客户端中。
数据表不以任何方式绑定和属于某个合约,在数据表访问权限范围内,其可以被所有应用程序使用。
每个生态系统都可以为其应用程序的开发创建一组数据表。这并不排除通过指定表名前缀来访问其他生态系统表的可能性。
通过智能法律配置访问权限来将数据记录至数据表。智能法律用于权限管理。
数据表管理工具¶
用于管理数据表的工具可从Govis软件客户端的 数据表(Tables) 菜单中找到。具有以下功能:
- 查看数据表列表及其条目内容;
- 创建新数据表;
- 添加表字段并指定字段数据类型:
Text
,Date/Time
,Varchar
,Character
,JSON
,Number
,Money
,Double
,Binary
;- 管理插入数据、更新数据和更改表结构的权限。
表数据操作¶
为了更好数据库操作,智语言(GALang Language)合约语言和乾语言(GAStyle Language)模板语言都具有 DBFind 函数,该函数用于从数据表中检索值和数据数组。
合约语言 DBInsert 函数用于向数据表添加条目。DBUpdate 和 DBUpdateExt 函数用于更新现有条目的值,当更新值时,数据表对应数据会更新,而区块链会添加新的交易,同时保留着所有历史交易。数据表的数据只可以被修改,不能被删除。
为了最小化执行合约的时间,DBFind 函数不能同时查询多个数据表,因此不支持 JOIN 请求,需要很多的冗余数据和字段。这就是为什么不建议规范化数据表,而是将所有可用的信息存储在条目中,或者重复其他数据表可用的信息。这不仅是一种强制性措施,还是区块链应用程序的必要条件。就这种情况而言,存储的数据应该是完整的数据,即使其他表的相同数据更新了,该数据也是无法更新的,这在关系型数据库中该数据是同步更新的)。
生态系统参数¶
生态系统参数表( 1_parameters )可从Govis软件客户端生态系统参数菜单查看和投票编辑。生态系统参数可分为以下几组:
一般参数:生态系统创建者的帐户(founder_account)以及其他信息;
访问权限参数:定义应用程序元素的访问权限
- 更新数据表结构( changing_tables );
- 更新合约( changing_contracts );
- 更新用户页面( changing_page );
- 更新菜单( changing_menu );
- 更新多语言资源 ( changing_language )。
技术参数:定义用户样式( stylesheet )等;
用户参数:定义应用程序工作所需的常量或列表(以逗号分隔)。
可以为每个生态系统的参数指定编辑权限。
要检索生态系统参数的值,可使用智语言或乾语言的 EcosysParam 函数,将生态系统参数名称作为参数传递给该函数。要从列表中检索元素,将生态系统参数名称作为第一个参数传递给该函数,将所需元素的计数做为该函数的第二个参数。
访问权限控制机制¶
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)进行管理。它们可能相当复杂,可能包含许多其他合约。
虚拟专用生态系统¶
GAChain 平台可以创建虚拟专用生态系统 Virtual Dedicated Ecosystems(VDE),也叫做链下服务 Off-Blockchain Servers (OBS) ,它具有标准生态系统的全套功能,但在区块链之外工作。在VDE中,可以使用和创建合约和模板语言、数据库表,可以使用Govis软件客户端创建应用程序。可以使用接口方式调用区块链生态系统上的合约。
请求web资源¶
VDE和标准生态系统之间的主要区别在于可以使用(HTTPRequest)和(HTTPPostJSON)合约函数通过 HTTP / HTTPS 请求方式在合约内向任何Web资源发出请求。传递给此函数的参数为:URL,请求方法(GET或POST),请求头和请求参数。
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共识¶
领导节点¶
以下公式确定当前 领导节点(leader node),即必须在当前时间生成新区块的节点。
leader = ((time - first) / step) % nodes
-
leader
当前领导节点。
-
time
当前时间(UNIX)。
-
first
创始区块生成时间 (UNIX)。
-
step
区块生成间隔中的秒数。
-
nodes
节点总数量。
生成新区块¶
新区块由当前时间间隔的 领导节点 生成。在每个时间间隔,领导节点从验证节点列表传递到下一个验证节点。

分叉¶
分叉(fork) 是区块链的替代版本。分叉包含一个或多个独立于区块链其余部分生成的区块。
分叉通常在发生网络节点的一部分不同步,影响分叉概率的因素是高网络延迟,有意或无意的时间限制违规,节点的系统时间不同步。如果网络节点具有显着的地理分布,则必须增加区块生成间隔。
通过遵循最长的区块链规则来解析分叉。当检测到两个版本的区块链时,验证节点将回滚较短版本并接受较长版本。

术语和定义¶
区块链相关术语¶
-
区块链
一种存储数据的信息系统,在系统内传输和处理数据,可以防止数据被伪造和丢失,同时保持数据可靠性; 通过以下方式实现数据保护:
- 将数据写入一系列加密区块的区块链中;
- 在对等网络中分散存储区块链副本;
- 使用共识机制对所有节点上的区块链进行同步;
- 通过在区块链中存储数据传输和处理合约的算法,确保在使用网络执行数据操作时,保证数据可靠性。
-
对等网络
由计算机网络的对等节点组成(没有中央服务器)。
-
哈希
又叫做散列,任意文件或数据集长度的二进制值映射为较短的固定长度的二进制值。
-
区块
在验证交易的格式和签名之后,由验证节点分组到特定数据结构中的交易集合。一个区块包含一个哈希指针作为到前一个区块的链接,这是确保区块链加密安全性的措施之一。
-
区块验证
验证区块结构、生成时间、与前一个区块的兼容性、交易签名以及交易与区块数据的对应关系的正确性。
-
共识
验证节点在向区块链添加新区块过程中使用的验证协议或该类协议的算法。
-
交易
区块链网络上的数据传输操作,或区块链中该类事务的记录。
-
通证
区块链上可流通的加密数字权益证明。存储在寄存器中的一组可识别的数字记录,包括在这些记录之间交换权利份额的机制。
-
标识符
用于识别系统中用户的加密程序。
-
唯一标识符
将账户和用户联系起来的程序,需要法律和组织的努力或其他程序来实现生物识别,以便将用户名与实际用户联系起来。
-
私钥
由其拥有者密存的一串字符串,用于该拥有者访问在网络上的虚拟帐户并签署交易。
-
公钥
用于检查私钥真实性的一串字符,公钥由私钥唯一派生生成。
-
数字签名
文档或消息经数据加密处理后获得的属性,数字签名用于检查文档的完整性(没有修改)和真实性(验证发件人的身份)。
-
智能合约
在区块链中的执行数据存储操作的程序,所有合约都存储在区块链中。
-
交易费用
向验证节点支付执行交易的费用。
-
双重支付
一种对区块链网络攻击方法,结果是一笔交易花费两次同样的通证。
在区块链分叉时会导致这种攻击发生。只有当攻击者控制了网络验证能力的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 模板语言编写的程序代码,可以重复包含在应用程序页面中的代码块。
-
访问权限
获取创建和编辑数据表,合约和页面的访问权限的条件。
对数据表的访问权限可以设置添加行和列,以及编辑列中值的权限。
-
验证节点
网络节点中有权生成和验证区块的节点。验证节点也称作主节点。
-
全节点
网络上的一个节点,用于存储完整区块链的最新版本。
-
并发事务处理
一种通过同时处理来自不同生态系统的数据来提高交易处理速度的方法。
常见问题¶
请简短描述一下 |platform| 平台?
- 是一个区块链平台,旨在构建一个基于集成应用程序开发环境的数字生态系统,该环境具有用于管理数据、接口和智能合约访问权限的多级权限系统。
GAChain 平台是否适用于比特币、以太坊或其他区块链?
- 不适用,GAChain 构建在自身原始区块链的基础上。
GAChain 与其他内置执行智能合约机制的公共区块链平台(Ethereum、Qtum)和仍在设计中的平台(Tezos、EOS)的主要区别是什么?
GAChain 具有上述区块链平台无法找到的独特功能:
- 在单个客户端软件中实现集成应用程序开发环境;
- 用于页面设计的专用模版语言 GAStyle 与合约构建语言 GALang 相互协调;
- 具有用于管理数据、接口和智能合约访问权限的多级权限系统,其中可以将权限授予成员、角色和合约;
- 生态系统,用于创建区块链应用程序和用户与其交互的自治软件环境;
- 法律体系,一套以智能法律(专用的智能合约)编写的规则,规范了平台用户之间的关系,定义了用于解决问题的协议参数变化过程。
GAChain 有自己的加密货币吗?
- 有,GAChain 使用自己的通证GAC。
什么是验证节点?
- 验证节点是有权验证交易和生成新区块的网络节点。
谁可以维护验证节点?
- 具有足够处理能力和容错能力的任何网络节点都可以成为验证节点。GAChain 使用权威证明(PoA)共识机制,节点可以基于生态系统的投票成为验证节点,但只有被投资者(平台通证拥有者)证明具有正常运作能力的生态系统才能参与此类投票。使用这种授权算法,验证节点由主要生态系统运行,因为维护网络运行最符合他们的利益。
什么是平台生态系统?
- 生态系统实际上是用于创建区块链应用程序和其中用户的操作的自治软件环境。
谁可以创建生态系统?
- 平台的所有用户都可以创建新的生态系统。
用户如何成为生态系统的成员?
- 平台网络的生态系统成员注册可在现有任何生态系统中进行,生态系统的策略定义了不同的成员加入程序,该策略在专门的生态系统目录中发布了新生态系统的关键公开信息。
一位用户可以创建多个生态系统吗?
- 是的,每位用户都可以创建任意数量的生态系统,同时也可以成为多个生态系统的成员。
什么是平台应用程序?
- 应用程序是实现功能或服务的完整软件产品。应用程序由数据库表、合约和页面组成。
什么编程语言用于创建应用程序?
什么软件用于创建应用程序和用户交互?
- 应用程序在Govis软件客户端中编写和执行,不需要其他软件。
平台合约可以使用第三方API接口访问数据吗?
- 不可以,合约只能直接访问区块链中存储的数据,VDE 用于处理外部数据源。
保存在区块链中的合约可以更改吗?
- 是的,合约可以更改。更改合约的权限由其创建者指定,合约创建者可以指定拒绝更改的权限,或者指定合约和成员进行更改的权限,或者在智能法律中配置一组复杂的条件。
- Govis软件客户端提供对所有合约版本的访问。
什么是智能法律?
- 智慧法律是一种合约,旨在控制和限制常规合约的运作,从而控制和限制生态系统成员的活动。
- 一套智能法律可以被视为一个生态系统的法律体系。
合约可以调用执行其他合约吗?
- 可以,合约可以通过直接寻址的方式并为其提供参数来调用其他合约,或者通过链接名称调用合约,更多参阅:智能合约。
应用程序工作是否需要主合约?
- 不需要,合约是执行某些功能的自治程序模块。每个合约配置了接收指定的数据,然后检查这些数据的正确性,并执行一些操作,这些操作当作交易被记录在数据库。
应用程序可以为不同语言本地化吗?
- 可以,软件客户端拥有内置的本地化支持机制,可以创建任何语言的页面。
可以在不使用GAStyle模板语言的情况下创建页面吗?
- 可以,使用平台 RESTful API 可以做到。
页面是否存储在区块链中?
- 是的,页面和合约都存储在区块链中,这可以防止它们被伪造。
哪些类型的数据库可以用于合约的操作?
- 目前使用PostgreSQL数据库,后续会更改。
如何管理对数据表中数据的访问?
- 可以为生态系统成员、角色或指定合约配置添加新字段、新条目或更改列中数据的权限。但执行特定操作而创建的合约除外。
生态系统中的应用程序可以与来自另一个生态系统的应用程序交换数据吗?
- 可以,通过适用于所有生态系统的全局数据表可以组织数据交换。
是否应该从头开始编写新生态系统中的所有应用程序?
不需要,每个新的生态系统都有一些开箱即用的应用程序:
- 管理生态系统成员和角色的机制;
- 发行和配置其他通证;
- 投票系统;
- 通知系统;
- 生态系统成员间的消息通信。
可以对这些应用程序进行编辑和配置,以满足任何生态系统的特殊需求。
应用程序的运作是否有任何费用?
- 是的,使用验证节点的资源需要在平台中支付通证。
谁支付应用程序的运作费用?
绑定账户地址,目前有4种方式支付应用程序的运作费用:
- 合约调用者,默认账户地址,当用户调用合约时,该用户的账户地址支付;
- 合约绑定者,合约创建者指定的账户地址,所有用户调用该合约的费用,由该账户地址支付;
- 生态系统创建者,生态系统内所有应用程序的运作费用由生态系统创建者支付;
- 生态系统专属钱包,每个生态系统都有独有的账户地址,如果生态系统创建者激活了该账户地址,生态系统内所有应用程序的运作费用由该账户地址支付。
支付优先级:生态系统专属钱包 > 生态系统创建者 > 合约绑定者 > 合约调用者。
如何保护生态系统内的应用程序免受其漏洞的攻击?
- 平台团队也知道没有办法完全避免应用程序代码中的错误,特别是考虑到应用程序可以由任何用户编写。这就是我们决定建立一种消除利用漏洞后果机制的原因。法律体系可以停止应用程序的攻击操作,并使用一些交易来恢复到原来状态。法律体系中规定了执行该类合约的权限和授予这些权限的投票程序。
GAChain在未来的计划中实现哪些新功能
- 可视化智能合约设计器;
- 支持混合数据库(SQL和NoSQL) ;
- 来自不同生态系统的交易的并行多线程处理;
- 在客户端执行资源密集型计算;
- 生态系统托管和计算能力交换;
- 子节点,只存储服务器上部分区块;
- 语义参考(本体)用于统一平台内数据的操作等。
如何证明GAChain的可操作性??
- 在 GAChain 平台上实施了一系列概念论证项目和案例:社会化代收税及电子发票生成和流转系统、医疗器械监管及防伪追溯系统、融资及监管系统、投票/民调系统、工商登记、贸易金融工具、资产登记合约管理系统等。
应用程序开发教程¶
本章节介绍如何在 GAChain 平台编写一个简单的应用程序。
目标¶
应用程序开始时很简单,随着教程的深入,其复杂性也在增加。
应用程序的最终版本在数据表中存储为简单消息(字符串),其中包含时间戳和消息发送者的帐户标识符。用户可以访问这些消息列表并从应用程序页面添加新消息。该应用程序的页面可以从生态系统菜单中访问。
第1部分:环境¶
Govis¶
Govis是 GAChain 的唯一客户端,Govis为所有用户和生态系统角色提供功能。应用程序开发人员可以在Govis中开发和测试应用程序,生态系统管理员使用Govis来管理生态系统,用户可以通过Govis与生态系统应用进行交互。
在本教程中,你将在Govis客户端中编写合约代码、页面模版代码和执行其他所有操作。Govis还提供一种恢复、保存和执行合约代码,管理数据结构(数据表),分配访问权限和创建应用程序的方法。
每个节点都有自己的Govis客户端实例。
第2部分:合约¶
您的第一个简单的应用程序为“Hello,World!”。
注解
应用程序在表中存储为字符串。它没有任何用户页面。
创始人账户¶
具有 Developer 角色的帐户可以使用生态系统的“root”特权。默认情况下,该角色可以访问所有操作。在新的生态系统中,创始人帐户被分配给 Admin 角色。您必须使用这个帐户来引入生态系统的重大更改,比如创建新的应用程序和数据表。
使用创始人的帐户登录生态系统。
新应用程序¶
一旦您作为生态系统的创始人登录,您就可以创建一个新的应用程序。
创建新应用程序:
转到 Developer 选项卡;
在左侧的菜单中选择 应用程序;
在 应用程序 页面选择 创建;
在 名称 字段中指定应用程序的名称;
在 更改条件 指定
true
:
true
表示任何人都可以更改应用程序;另一种选择是指定
ContractConditions("MainCondition")
。除了创始人之外,将禁止任何人对进行应用程序更改。您的应用程序将显示在应用程序列表中,单击指定应用程序的名称字段使其激活。
注解
在 Developer 选项卡中选择应用程序可以更轻松地导航与所选应用程序相关的资源,它对生态系统没有影响。无论选择哪一个,所有生态系统应用程序仍然可用。
新数据表¶
要存储数据,应用程序需要一个数据表。在Govis创建该数据表。
创建数据表:
在 Developer 选项卡,选择 应用程序 - 名称 > 数据表;
这显示所选应用程序的所有数据表。如果该列表为空,则您的应用还没有任何数据表。
单击 创建;
Govis将显示创建数据表页面。
在 名称 字段中指定您的数据表名称;
本教程使用
apptable
数据表名称。添加列。名为
message
,其类型为Text
;对于读写权限,在每个字段指定为
true
;这将允许任何人在数据表上执行插入条目、更改条目、添加列和读取条目数据;
作为选项,您可以限定读写权限给创始人帐户,在这种情况下,请在该字段中指定为
ContractConditions("MainCondition")
。
新合约¶
创建合约¶
在 Developer 选项卡选择 应用程序 - 名称 > 合约;
这将显示所选应用程序的所有合约。新应用程序该列表将为空。
单击 创建;
将在编辑器中打开一个新的合约模版。
空合约模版如下所示:
contract ... {
data {
}
conditions {
}
action {
}
}
条件部分¶
填写 conditions
部分。简单的验证条件是指定的字符串不能为空,如果 Message
长度为 0
,则合约将在执行时生成带有已定义的消息警告。
conditions {
// avoid writing empty strings
if Size($Message) == 0 {
error "Message is empty"
}
}
完整合约代码¶
以下部分是完整合约的代码。
GAChain 的所有合约都像这样构建,包含 data
、conditions
和 action
部分。
contract AppContract {
data {
Message string
}
conditions {
// avoid writing empty strings
if Size($Message) == 0 {
error "Message is empty"
}
}
action {
DBInsert("apptable", {message: $Message})
}
}
第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页面:
导航到 Developer 选项卡 > 应用程序 - 名称 > 页面;
单击 创建;
可视化设计器将在新选项卡中打开。
设计器视图¶
默认页面为空。您可以使用预定义的结构快速填充页面。
创建一个基本的表单:
在右侧的视图选择器中,单击 视图化(Designer);
视图将切换到可视化设计器。
从左侧菜单中,选择 Table With Header 并将其拖到页面上。
将出现包含多个元素的表格。
开发者视图¶
GAChain 的用户页面用 Gastyle 编写。您需要为页面编写代码,因此请切换到开发者(Developer)的视图。
切换到开发者(Developer)视图。
在右侧的视图选择器中,单击 开发者。
视图将切换到包含页面代码的代码编辑器。
获取数据表数据¶
目前为止,页面模版并没有做什么。接下来就得更改代码,以便页面显示来自 apptable
表的数据。
想要请求表中数据,使用 DBFind 函数;
以下示例中该函数调用从
apptable
表中获取数据,并将其放入src_table
源中。并按时间戳字段对其进行排序。该src_table
源稍后用作页面上表视图的数据源。DBFind(Name: apptable, Source: src_table).Columns(Columns: "author,timestamp,message").Order(timestamp)
想要显示
src_table
源中的数据,在Table
函数中将其指定为一个源以及列标题。Table(Columns: "AUTHOR=author,TIME=timestamp,MESSAGE=message", Source: src_table)
在右侧的视图选择器中,单击 预览 以检查数据是否正确显示。
完整页面代码¶
以下是该部分的完整页面代码。该基本页面将在稍后进行扩展。
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)
}
}
第4部分:应用程序¶
在前面的部分中,您创建了一个合约,一个用于存储数据的表,以及一个用于显示该数据的基本UI页面。
在该部分中,您将确定最终的应用程序,因此它的外观和操作类似于实际应用程序。
菜单¶
页面需要链接到一个菜单,例如,在 Home 选项卡上显示的 default_page
页面链接到默认生态系统菜单 default_menu
。
由于应用程序教程很简单(只有一个页面),因此无需为其创建单独的菜单。默认菜单中的新菜单项就足够了。
注解
您可以通过在 Developer 选项卡 > 应用程序 - 名称 > 页面 中编辑页面属性来定义页面显示的菜单。例如,如果您的应用程序有多个页面,则可能需要创建一个菜单以在这些页面之间导航并将其分配给应用程序的所有页面。
添加菜单项¶
与所有其他资源一样,可以在Govis中创建和编辑菜单:
导航到 Developer 选项卡 > 菜单;
单击
default_menu
条目名称;编辑器将在新选项卡中打开。
将新菜单项添加到模版的末尾。该菜单项将打开应用程序页面。该图标来自 FontAwesome 图标集。
MenuItem(Title:Messages, Page:AppPage, Icon:"fa fa-envelope")
单击 保存。
发送消息¶
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#") }

添加按钮后,保存页面并从 Home > Messages 菜单项进行测试。
页面刷新¶
实现的最后一项功能就是自动更新位于页面上的表格,当用户发送新消息时,它必须显示在表格中。
除了执行合同之外,您还可以通过 Send 按钮重新打开当前页面来实现这一点。必须将 #table_view_offset#
参数传递到该页面,而不进行任何更改。
添加 Page
和 PageParams
参数到 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#")
}
}
结论¶
本教程将介绍生态系统的基本应用程序。它没有为应用程序开发者解析其他重要的主题,比如布局样式、管理访问权限以及应用程序和资源之间的交互。有关这些高级主题的更多信息,请参阅其他文档。
智能合约¶
- 合约结构
- 变量
- 嵌套合约
- 文件上传
- JSON格式查询语句
- 日期时间格式查询语句
- GALang 合约语言
- GALang 函数功能分类
- GALang 函数参考
- AppParam
- DBFind
- DBRow
- DBSelectMetrics
- EcosysParam
- GetHistory
- GetHistoryRow
- GetColumnType
- GetDataFromXLSX
- GetRowsCountXLSX
- LangRes
- GetBlock
- DBInsert
- DBUpdate
- DBUpdateExt
- DelColumn
- DelTable
- Append
- Join
- Split
- Len
- Row
- One
- GetMapKeys
- SortedKeys
- CallContract
- ContractAccess
- ContractConditions
- EvalCondition
- GetContractById
- GetContractByName
- RoleAccess
- TransactionInfo
- Throw
- ValidateCondition
- AddressToId
- IdToAddress
- PubToID
- DecodeBase64
- EncodeBase64
- Float
- HexToBytes
- FormatMoney
- Random
- Int
- Hash
- Sha256
- Str
- JSONEncode
- JSONEncodeIndent
- JSONDecode
- HasPrefix
- Contains
- Replace
- Size
- Sprintf
- Substr
- ToLower
- ToUpper
- TrimSpace
- Floor
- Log
- Log10
- Pow
- Round
- Sqrt
- StringToBytes
- BytesToString
- SysParamString
- SysParamInt
- DBUpdateSysParam
- UpdateNotifications
- UpdateRolesNotifications
- HTTPRequest
- HTTPPostJSON
- BlockTime
- DateTime
- UnixDateTime
- CreateOBS
- GetOBSList
- RunOBS
- StopOBS
- RemoveOBS
- 系统合约
- NewEcosystem
- EditEcosystemName
- NewContract
- EditContract
- BindWallet
- UnbindWallet
- NewParameter
- EditParameter
- NewMenu
- EditMenu
- AppendMenu
- NewPage
- EditPage
- AppendPage
- NewBlock
- EditBlock
- NewTable
- EditTable
- NewColumn
- EditColumn
- NewLang
- EditLang
- Import
- ImportUpload
- NewAppParam
- EditAppParam
- NewDelayedContract
- EditDelayedContract
- UploadBinary
智能合约(下文称为合约)是应用程序的基本元素。用户在页面中执行合约通常是单个操作,结果是更改或创建数据库的条目。应用程序的所有数据操作形成了合约系统,这些合约通过数据库或者合约内容的函数彼此交互。
合约结构¶
使用 contract 关键字声明合约,后面接上合约名称,合约内容必须用大括号括起来。合约结构有三个主要部分:
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"
}
变量¶
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})
}
}
嵌套合约¶
在合约的 conditions 和 action 部分可以嵌套合约。嵌套合约可以直接调用,合约参数在其合约名称后面的括号中指定,例如,@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
- 布尔值,true
和false
;
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 循环,可以在合约和函数中使用。这些语句可以相互嵌套。
if 和 while 关键字后必须跟条件语句。如果条件语句返回一个数字,则当其值为0,被视为 false。
val == 0 等于 !val,val != 0 等于 val。if 语句可以有 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 还支持标准的算术运算:+
, -
, *
, /
。
string 和 bytes 类型可以作为条件语句,如果类型长度大于零时,条件为 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 函数功能分类¶
数据库检索值:
更改数据表值:
数组操作:
合约和权限操作:
地址操作:
变量值操作:
算术运算:
JSON格式操作:
字符串操作:
字节操作:
SQL格式的日期和时间操作:
平台参数操作:
VDE模式函数操作:
主VDE节点函数操作:
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 day
,30 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
}
GetHistory¶
返回指定数据表中条目更改的历史记录。
返回值¶
返回 map 类型的对象数组。这些数组指定数据表中条目更改的历史记录。
每个数组都包含下一次更改之前的记录字段。
数组按从最近更改顺序排序。
数组中 id 字段指向 rollback_tx 表的 id。block_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¶
返回指定表中指定字段的数据类型。
返回值¶
返回以下类型: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)
LangRes¶
返回指定多语言资源。其标签 lang 为双字符语言代码,例如:en, zh
。如果所选的语言标签没有语言资源,则返回 en
标签的语言资源。
示例¶
warning LangRes("@1confirm", "en")
error LangRes("@1problems", "zh")
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")
Join¶
将 in 数组的元素合并到具有指定 sep 分隔符的字符串中。
示例¶
var val string, myarr array
myarr[0] = "first"
myarr[1] = 10
val = Join(myarr, ",")
Split¶
使用 sep 分隔符将 in 字符串拆分为元素,并将它们放入数组中。
示例¶
var myarr array
myarr = Split("first,second,third", ",")
Row¶
The list parameter must not be specified in this case. 返回数组列表的第一个对象数组。如果列表为空,则返回结果为空。该函数主要与 DBFind 函数一起使用,使用时该函数不能指定参数。
示例¶
var ret map
ret = DBFind("contracts").Columns("id,value").WhereId(10).Row()
Println(ret)
One¶
返回数组列表中第一个对象数组的字段值。如果列表数组为空,则返回nil。该函数主要与 DBFind 函数一起使用,使用时该函数不能指定参数。
示例¶
var ret string
ret = DBFind("contracts").Columns("id,value").WhereId(10).One("value")
if ret != nil {
Println(ret)
}
GetMapKeys¶
返回对象数组中的键数组。
示例¶
var val map
var arr array
val["k1"] = "v1"
val["k2"] = "v2"
arr = GetMapKeys(val)
SortedKeys¶
返回对象数组中的键数组,该数组已排序。
示例¶
var val map
var arr array
val["k2"] = "v2"
val["k1"] = "v1"
arr = SortedKeys(val)
CallContract¶
调用指定名称的合约。合约数据部分的所有参数必须列入一个对象数组中。该函数返回指定合约分配给 $result 变量的值。
示例¶
var par map
par["Name"] = "My Name"
CallContract("MyContract", par)
ContractAccess¶
检查执行合约的名称是否与参数中列出的名称之一匹配。通常用于控制合约对数据表访问。编辑数据表字段或在表权限部分的插入和新列字段时,在权限字段中指定该函数。
示例¶
ContractAccess("MyContract")
ContractAccess("MyContract","SimpleContract")
ContractConditions¶
从指定名称的合约中调用 conditions 条件部分。
对于该类的合约,数据部分必须为空。如果条件部分执行没有错误,则返回 true。如果在执行期间生成错误,则父合约也将以此错误结束。该函数通常用于控制合约对表的访问,并且可以在编辑系统表时在权限字段中调用。
示例¶
ContractConditions("MainCondition")
EvalCondition¶
从 tablename 表中获取带有 'name' 字段记录中的 condfield 字段的值,并检查 condfield 字段值的条件。
语法¶
EvalCondition(tablename string, name string, condfield string)
-
tablename
数据表名称。
-
name
根据 'name' 字段查询值。
-
condfield
需要检查条件的字段名称。
示例¶
EvalCondition(`menu`, $Name, `conditions`)
GetContractById¶
该函数通过合约ID返回其合约名称。如果找不到合约,则返回空字符串。
示例¶
var name string
name = GetContractById($IdContract)
RoleAccess¶
检查合约调用者的角色ID是否与参数中指定的ID之一匹配。
使用该函数可以控制对数据表和其他数据的合约访问。
示例¶
RoleAccess(1)
RoleAccess(1, 3)
TransactionInfo¶
按指定的哈希值查询交易并返回已执行的合约及其参数的信息。
返回值¶
该函数返回json格式的字符串:
{"contract":"ContractName", "params":{"key": "val"}, "block": "N"}
contract
合约名称。
params
传递给合约参数的数据。
block
处理该交易的区块ID。
示例¶
var out map
out = JSONDecode(TransactionInfo(hash))
ValidateCondition¶
尝试编译 condition 参数指定的条件。如果在编译过程中发生错误,则生成错误并终止调用合约。该函数旨在检查条件格式的正确性。
示例¶
ValidateCondition(`ContractAccess("@1MyContract")`, 1)
FormatMoney¶
返回 exp / 10 ^ digit 的字符串值。
语法¶
FormatMoney(exp string, digit int) string
-
exp
数字的字符串格式。
-
digit
exp/10^digit
表达式中10的指数,该值可以是正数或负数。正值决定了小数点后的位数。
示例¶
s = FormatMoney("78236475917384", 0)
JSONEncode¶
将数字、字符串或数组转换为JSON格式的字符串。
示例¶
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格式的字符串转换为数字,字符串或数组。
示例¶
var mydata map
mydata = JSONDecode(`{"name": "John Smith", "company": "Smith's company"}`)
Sprintf¶
该函数根据指定的模板和参数创建一个字符串。
可用的通配符:
%d
(整数)%s
(字符串)%f
(浮点型)%v
(任何类型)
示例¶
out = Sprintf("%s=%d", mypar, 6448)
Substr¶
返回从偏移量 offset (默认从0开始计算)开始的指定字符串中获取的子字符串,其最大长度限制为 length。
如果偏移量或长度限制小于零、偏移量大于长度限制的值,则返回一个空字符串。
如果偏移量和长度限制的总和大于字符串字节数,则子字符串将从偏移量开始返回到指定字符串的末尾。
示例¶
var s string
s = Substr($Name, 1, 10)
DBUpdateSysParam¶
更新平台参数的值和条件。如果您不需要更改值或条件,请在相应参数中指定空字符串。
示例¶
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)
系统合约¶
系统合约是在启动 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" - 合约绑定的钱包地址。
NewParameter¶
早当前生态系统添加了一个新生态系统参数。
参数:
- Name string - 参数名称;
- Value string - 参数值;
- Conditions string - 更改参数的条件。
EditParameter¶
更改当前生态系统中的现有生态系统参数。
参数:
- Name string - 要更改的参数名称;
- Value string - 新参数值;
- Conditions 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表示在加载和离开页面时检查页面。
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"}
。
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
格式。
模版语言¶
- 页面构建
- GAStyle 模版语言
- GAStyle 函数功能分类
- GAStyle 函数参考
- Address
- AddressToId
- AddToolButton
- And
- AppParam
- ArrayToSource
- Binary
- Button
- Calculate
- Chart
- CmpTime
- Code
- CodeAsIs
- Data
- DateTime
- DBFind
- Example
- Div
- EcosysParam
- Em
- ForList
- Form
- GetColumnType
- GetHistory
- GetVar
- Hint
- If
- Image
- ImageInput
- Include
- Input
- InputErr
- InputMap
- JsonToSource
- Label
- LangRes
- LinkPage
- Map
- MenuGroup
- MenuItem
- Money
- Or
- P
- QRcode
- RadioGroup
- Range
- Select
- SetTitle
- SetVar
- Span
- Strong
- SysParam
- Table
- TransactionInfo
- VarAsIs
- 适配移动设备的应用程序样式
页面构建¶
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
;如果没有指定地址,以当前用户的账户地址作为参数。
示例¶
Span(Your wallet: Address(#account#))
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(1, type, Source: mytype)
ArrayToSource¶
创建一个 arraytosource 元素,并用JSON数组的键值对填充它。得到的数据被放入 Source 元素,该元素稍后可以在源输入的函数中使用(例如 Table)。
示例¶
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
float 和 money 类型指定小数点后的有效位数。
-
示例¶
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( P(This is the first line.
Span(This is the second line.))
)
CodeAsIs¶
创建用于显示指定代码的 code 元素。
此函数不会将变量替换为其值。例如,#name#
将按原样显示。
示例¶
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
数据集。
每行一条记录。列值必须用逗号分隔。Data 和 Columns 应设置相同的顺序。
对于带有逗号的值,将该值放在双引号中 (
"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 参数中返回总计数。
如果未指定 Where 和 WhereID,将返回数据表的总行数。
-
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
的值等于Value
,Condition 为真。如果调用了多个 Show 或 Hide,则至少有一个 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 参数一起使用。
-
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)
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
。
示例¶
SetVar(coltype,GetColumnType(members, member_name))Div(){#coltype#}
GetHistory¶
创建 gethistory 元素,使用指定数据表的条目的历史更改记录来填充它。生成的数据将放入 Source 元素中。该元素稍后可以在源输入的函数中使用(例如 Table)。
数组按从最近更改顺序排序。
数组中 id 字段指向 rollback_tx 表的 id。block_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# 将被解释为一个字符串值。
示例¶
If(GetVar(name)){#name#}.Else{Name is unknown}
Hint¶
创建 hint 元素,用于提示。
示例¶
Hint(Icon: "icon-wrench",Title:$@1pa_settings$,Text: This is a hint text)
If¶
条件声明。
返回满足 Condition 的第一个 If 或 ElseIf 的子元素。否则返回 Else 的子元素。
语法¶
If(Condition){ Body }
[.ElseIf(Condition){ Body }]
[.Else{ Body }]
-
If
-
Condition
如果条件等于 空字符串,0 或 false,则认为该条件未满足。在所有其他情况下,该条件被认为是满足的。
-
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)
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(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"})
Or¶
该函数返回执行 if 逻辑运算的结果,括号中列出的所有参数以逗号分隔。如果有一个参数不为空字符串、零或 false
,参数值为 true
,其他情况参数值为 false
。如果参数值为 true
,则该函数返回 1
,其他情况返回 0
。
语法¶
Or(parameters)
示例¶
If(Or(#myval1#,#myval2#), Span(OK))
RadioGroup¶
创建 radiogroup 元素。
语法¶
RadioGroup(Name, Source, NameColumn, ValueColumn, Value, Class)
[.Validate(validation parameters)]
[.Style(Style)]
-
RadioGroup
-
Name
元素名称。
-
NameColumn
数据源的字段名称。
-
ValueColumn
数据源的值名称。
使用 Custom 创建的字段不得在该参数中使用。
-
Value
默认值。
-
Class
类名。
-
-
Validate
验证参数。
-
Style
指定CSS样式。
-
Style
CSS样式。
-
示例¶
RadioGroup(Name: type_decision, Source: numbers_type_decisions, NameColumn: name, ValueColumn: value)
Range¶
创建 range 元素,使用步长 Step 从 From 到 To (不包括 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
元素的名称。
-
NameColumn
数据源的字段名称。
-
ValueColumn
数据源的值名称。
使用 Custom 创建的字段不得在该参数中使用。
-
Value
默认值。
-
Class
类名。
-
-
Validate
验证参数。
-
Style
指定CSS样式。
-
Style
CSS样式。
-
示例¶
DBFind(mytable, mysrc)
Select(mysrc, name)
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¶
该函数按指定哈希值查询交易并返回有关已执行的合约及其参数的信息。
返回值¶
该函数返回json格式的字符串:
{"contract":"ContractName", "params":{"key": "val"}, "block": "N"}
其中:
- contract - 合约名称;
- params - 传递给合约参数的数据;
- block - 处理该交易的区块ID。
示例¶
P(TransactionInfo(#hash#))
适配移动设备的应用程序样式¶
排版¶
标题¶
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
编译器和虚拟机¶
本节涉及程序编译和虚拟机中 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 – 合约 数据部分 描述的数据数组。
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结构具有以下元素:
- Name、Params、Results 参数和 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 – 通过索引将 map 或 array 中的值放入堆栈,不使用 Value。例如,
(map | array) (index value) => (map | array [index value])
;- cmdSetIndex – 将堆栈顶部元素的值分配给 map 或 array 的元素,不使用 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 | decimal
,float + 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** 函数添加了四个函数:ExecContract、MemoryUsage、CallContract 和 Settings。
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 *SmartContract
或 rt *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+
,然后将 1
和 2
放入堆栈,然后我们对堆栈中的最后两个元素应用加法运算,并将结果写入堆栈。这种 转换算法 可以在互联网上找到。
全局变量 opers = map [uint32] operPrior
包含转换成逆波兰表示法时所必需的操作的优先级。
以下变量在 compileEval 函数开头定义:
- buffer – 字节码指令的临时缓冲区;
- bytecode – 字节码指令的最终缓冲区;
- parcount – 调用函数时用于计算参数的临时缓冲区;
- setIndex – 当我们分配 map 或 array 元素时,工作过程中的变量被设置为 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 状态,用于处理 while 和 if。当处理完表达式后,需要在大括号内处理块使用;
- stateToBody – 转换到 stateBody 状态;
- stateFork – 保存标记的位置。当表达式以标识符或带有
$
名称开头时使用,我们可以进行函数调用或赋值;- stateToFork – 用于获取存储在 stateFork 中的标记。该标记将传递给进程函数;
- stateLabel – 用于插入 cmdLabel 指令。while 结构需要这个标记;
- stateMustEval – 在 if 和 while 结构的开头检查条件表达式的可用性。
除了 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 – 数字存储为 int64 或 float64。如果数字有一个小数点,那么为 float64;
- lexIdent – 标识符存储为 字符串string;
- lexNewLine – 换行符。还用于计算行和标记位置;
- lexString – 行存储为 字符串string;
- lexComment – 注释存储为 字符串string;
- lexKeyword – 关键字仅存储相应的索引,请参阅
keyContract ... keyTail
常量。在这种情况下Type = KeyID << 8 | lexKeyword
。另外,应该注意的是,true,false,nil
关键字会立即转换为 lexNumber 类型的标记,并使用相应的bool
和intreface {}
类型; - lexType – 该值包含相应的
reflect.Type
类型值; - lexExtend – 以美元符号
$
开头的标识符。这些变量和函数从外部传递,因此分配给特殊类型的标记。该值包含字符串形式的名称,开头没有美元符号。
- lexSys – 包括括号,逗号等。在这种情况下,
- Line – 标记所在行;
- Column – 标记的行内位置。
让我们详细分析 lexParser 函数。todo 函数根据当前状态和传入符号,查找字母表中的符号索引,并从转换表中获取一个新状态、标记标识符(如果有的话)和其他标记。解析本身包括对每下一个字符依次调用 todo 函数,并切换到新的状态。一旦接收到标记,我们就在输出准则中创建相应的标记并继续解析。应该注意的是,在解析过程中,我们不将标记符号累积到单独的堆栈或数组中,因为我们只是保存标记开始的偏移量。获得标记之后,我们将下一个标记的偏移量移动到当前解析位置。
剩下的就是检查解析中使用的词法状态标志:
- lexfPush – 该标志意味着我们开始在一个新的标记中累积符号;
- lexfNext – 必须将该字符添加到当前标记;
- lexfPop – 接收标记完成,通常,使用该标志我们有解析标记的标识符类型;
- lexfSkip – 该标志用于从解析中排除字符,例如,字符串中的控件斜线为
\n \r \"
。它们会在该词法分析阶段自动替换。
GALang 语言¶
词法¶
程序的源代码必须采用UTF-8编码。
以下词法类型:
- 关键字 -
action
,break
,conditions
,continue
,contract
,data
,else
,error
,false
,func
,if
,info
,nil
,return
,settings
,true
,var
,warning
,while
;- 数字 - 只接收十进制数字。有两种基本类型: int 和 float。如果数字有一个小数点,它就变成了浮点数 float。int 类型等价于golang中的 int64。float 类型等价于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类型。因此,例如 array 和 map 类型是golang类型 []interface{} 和 map[string]interface{} 。这两种类型的数组都可以包含任何类型的元素。
表达式¶
表达式可以包含算术运算、逻辑运算和函数调用。根据操作优先级从左到右计算所有表达式。如果操作优先级相同,评估也从左到右。
从最高优先级到最低优先级的操作列表:
- 函数调用和圆括号 - 调用函数时,将从左到右计算传递的参数;
- 一元运算 - 逻辑否定
!
和算术符号变化-
; - 乘法和除法 - 算术乘法
*
和除法/
; - 加法和减法 - 算术加法
+
和减法-
; - 逻辑比较 -
>= > > >=
; - 逻辑相等和不相等 -
== !=
; - 逻辑与 -
&&
; - 逻辑或 -
||
。
当评估逻辑与和逻辑或时,在任何情况下都会计算表达式的两侧。
GALang 在编译时没有类型检查。在评估操作数时,会尝试将类型转换为更复杂的类型。复杂度顺序的类型可以按照如下:string, int, float, money
,仅实现了部分类型转换。字符串类型支持加法操作,结果会使得字符串连接。例如,string + string = string, money - int = money, int * float = float
。
对于函数,在执行时会对 string
和 int
类型执行类型检查。
array 和 map 类型可以通过索引来寻址。对于 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 区块链协议。守护进程在区块链网络中分发区块和交易、生成新区块、验证接收到的区块和交易。守护进程可以防止区块链分叉问题。
验证节点守护进程¶
验证节点 (有权生成新区块和发送交易)运行以下服务端守护进程:
生成新区块。
从其他节点下载新区块。
确认节点上存在的区块也存在于大多数其他节点上。
将交易和区块分发给其他主节点。
QueueParserBlocks 守护进程
处理区块队列中的区块,区块队列包含来自其他节点的区块。
区块处理逻辑和 BlockCollection守护进程 相同。
QueueParserTx 守护进程
验证交易队列中的交易。
Scheduler 守护进程
按任务计划运行合约。
全节点守护进程¶
全节点 (只发送交易的节点)运行以下服务端守护进程:
- BlockCollection守护进程
- Confirmations守护进程
- Disseminator守护进程
- QueueParserTx
- Scheduler
BlockCollection守护进程¶
BlocksCollection守护进程下载区块并将区块链与其他网络节点同步。
BlockGenerator守护进程¶
BlockGenerator守护进程生成新区块。
预验证¶
BlockGenerator守护进程通过分析区块链中的最新区块来计划新的区块生成。
如果满足以下条件,则可以生成新区块:
- 生成最新区块的节点位于验证节点列表中守护进程节点。
- 自最新未验证区块生成以来经过的最短时间。
区块生成¶
该守护进程生成新区块后,新区块包含所有新交易。这些交易可以从其他节点的 Disseminator守护进程 接收,也可以由该守护进程的节点生成。生成的区块保存在该节点数据库中。
数据表¶
BlockGenerator守护程序使用以下表:
- block_chain (saves new blocks)
- transactions
- transactions_status
- info_block
请求¶
BlockGenerator守护进程不向其他守护进程发出任何请求。
Confirmations守护进程¶
Confirmations守护进程检查其节点中的所有区块是否存在于大多数其他节点上。
区块确认¶
当网络中的多个节点已确认区块时,将认为该区块已被确认。
该守护进程从数据库中当前未确认的第一个区块开始逐个确认所有区块。
每个区块都以这种方式确认:
- 向所有验证节点发送请求,该请求包含了正在确认的区块ID。
- 所有验证节点对该区块的哈希进行响应。
- 如果响应的哈希值与守护进程节点上的区块的哈希值匹配,则会增加确认计数器。如果哈希不匹配,取消确认计数器将增加。
TCP服务协议¶
TCP服务协议在验证节点和全节点上工作,TCP服务协议使用TCP上的二进制协议来处理来自BlocksCollection、Disseminator和Confirmation守护进程的请求。
请求类型¶
每个请求都有一个由请求的前两个字节定义的类型。
Type 7¶
请求发送者¶
BlockCollection守护进程 发送该请求。
RESTful API¶
Govis软件客户端提供的所有功能,包括身份验证,生态系统数据接收,错误处理,数据库表操作,页面和合约执行都可通过 GAChain 平台的REST API获得。
通过使用REST API,开发者可以在不使用Govis软件客户端的情况下访问平台的任何功能。
API命令调用通过寻址执行 /api/v2/command/[param]
,其中 command
是命令名称,param
是附加参数。请求参数必须使用 Content-Type: x-www-form-urlencoded
格式发送。服务器响应结果为JSON格式。
- 错误响应处理
- VDE不可用接口
- 认证接口
- 服务端命令接口
- 数据请求功能接口
- 获取指标接口
- 生态系统接口
- ecosystemname
- ecosystems
- appparams/{appID}
- appparam/{appid}/{name}
- ecosystemparams
- ecosystemparam/{name}
- tables/[?limit=...&offset=...]
- table/{name}
- list/{name}[?limit=...&offset=...&columns=...]
- sections[?limit=...&offset=...&lang=]
- row/{name}/{id}[?columns=]
- systemparams
- history/{name}/{id}
- interface/{page|menu|block}/{name}
- 合约功能接口
错误响应处理¶
在请求执行成功的情况下返回状态 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
数据请求功能接口¶
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/ 返回其中包含每个区块中交易的相关附加信息列表。
该请求不需要登录授权。
响应¶
区块高度
区块中的交易列表以及每个交易的附加信息:
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/ 如果指定哈希与指定数据表、字段和记录中的数据匹配,则此请求将返回数据。否则返回错误。
该请求不需要登录授权。
响应¶
二进制数据
keyinfo¶
GET/ 返回一个生态系统列表,其中包含注册了指定地址的角色。
该请求不需要登录授权。
响应¶
ecosystem
生态系统ID。
name
生态系统名称。
roles
具有 id 和 name 字段的角色列表。
响应示例¶
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
}
生态系统接口¶
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/ 返回当前生态系统的数据表列表。可以设置偏移量和条目条数。
响应¶
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
更改该字段值的权限。
响应¶
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"
}
]
}
合约功能接口¶
contracts[?limit=...&offset=...]¶
GET/ 返回当前生态系统中的合约列表,可以设置偏移量和条目条数。
响应¶
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/ 返回指定合约的相关信息。默认在当前生态系统中查询合约。
响应¶
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; 如果交易执行失败,则 blockid 为 0。
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/source/{name}¶
POST/ 返回指定页面名称的代码JSON对象树。不执行任何函数或接收任何数据。返回的JSON对象树对应于页面模版,可以在可视化页面设计器中使用。如果找不到页面,则返回404错误。 请求 """""""
name
页面名称。
响应示例¶
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对象树可以在可视化页面设计器中使用。
该请求不需要登录授权。
响应¶
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的相关信息。
该请求不需要登录授权。
响应¶
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 表中用户的头像(无需登录即可使用)。
响应¶
请求头 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通知服务的所有消息。仅发送指定生态系统和成员的消息。
该请求不需要登录授权。
响应示例¶
200 (OK)
Content-Type: application/json
{
"result": true
}
平台参数¶
平台参数配置¶
燃料通证配置¶
奖励和佣金:
燃料费率转换:
交易大小数据价格:
新建元素价格:
- price_create_ecosystem
- price_create_table
- price_create_column
- price_create_contract
- price_create_menu
- price_create_page
- price_create_application
运营价格:
平台参数详情¶
block_reward¶
授予生成区块的验证节点的 GAC 通证数量。
接受奖励的帐户在 full_nodes 参数中指定。
blockchain_url¶
该参数已弃用。
commission_size¶
commission_wallet¶
收取佣金的账户地址。
佣金数量由 commission_size 参数指定。
default_ecosystem_contract¶
新生态系统默认合约的源代码。
该合约为生态系统创建者提供访问权限。
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_rate 和 price_create_rate 将燃料单位转换为 GAC 通证。
price_create_application¶
创建新单个应用程序的燃料费用。
该参数定义了
@1NewApplication
合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。该参数以燃料单位计算。使用 fuel_rate 和 price_create_rate 将燃料单位转换为 GAC 通证。
price_create_table¶
创建新单个数据表的燃料费用。
该参数定义了
@1NewTable
合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。该参数以燃料单位计算。使用 fuel_rate 和 price_create_rate 将燃料单位转换为 GAC 通证。
price_create_column¶
创建新单个表字段的燃料费用。
该参数定义了
@1NewColumn
合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。该参数以燃料单位计算。使用 fuel_rate 和 price_create_rate 将燃料单位转换为 GAC 通证。
price_create_contract¶
创建新单个合约的燃料费用。
该参数定义了
@1NewContract
合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。该参数以燃料单位计算。使用 fuel_rate 和 price_create_rate 将燃料单位转换为 GAC 通证。
price_create_page¶
创建新单个页面的燃料费用。
该参数定义了
@1NewPage
合约的额外燃料费用。执行该合约时,还会计算执行本合约各项函数的燃料费用,并计入总费用。该参数以燃料单位计算。使用 fuel_rate 和 price_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是一种特殊工具,可用于验证指定节点上的数据库是否已同步。
该工具可以作为守护进程使用,也可以启动以执行一次性检查。
该工具的操作原理基于以下内容:
- 每个区块包含所有交易的所有更改的哈希,请求指定的节点提供其最后一个区块ID;
- 然后从所有节点请求具有该ID的区块,并比较上述哈希;
- 如果哈希不同,会将同步错误消息发送到命令中指定的电子邮件地址。
位置¶
该工具位于 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 – 请求信息的节点(主机)列表。
[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,您必须先安装它。
- 成为超级用户。
su -
2) 升级您的系统。 .. code-block:: bash
apt update -y && apt upgrade -y && apt dist-upgrade -y
- 安装sudo。
apt install sudo -y
- 将您的用户添加到sudo组。
usermod -a -G sudo user
- 重启后,更改生效。
Go 语言¶
按照 官方文档 的说明按照Go。
- 从 Golang官方网站 或通过命令行下载最新的稳定版Go(> 1.10.x):
wget https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz
- 将安装包解压缩到
/usr/local
.
tar -C /usr/local -xzf go1.11.2.linux-amd64.tar.gz
- 添加
/usr/local/go/bin
到PATH环境变量 (位于/etc/profile
或$HOME/.profile
)。
export PATH=$PATH:/usr/local/go/bin
- 要使更改生效,请执行
source
该文件,例如:
source $HOME/.profile
- 删除临时文件:
rm go1.11.2.linux-amd64.tar.gz
Centrifugo¶
- 从 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/
- 删除临时文件:
rm -R centrifugo-1.8.0-linux-amd64 \
&& rm centrifugo-1.8.0-linux-amd64.zip
目录结构¶
对于Debian 9 系统,建议将区块链平台使用的所有软件存储在单独的目录中。
在这里使用 /opt/gachain
目录,但您可以使用任何目录。在这种情况下,请相应地更改所有命令和配置文件。
- 为区块链平台创建一个目录:
sudo mkdir /opt/gachain
- 使您的用户成为该目录的所有者:
sudo chown user /opt/gachain/
- 为Centrifugo、go-gachain和节点数据创建子目录。所有节点数据都存储在名为
nodeX
的目录中,其中X
为节点号。根据要部署的节点,node1
为节点1,node2
为节点2,以此类推。
mkdir /opt/gachain/go-gachain \
mkdir /opt/gachain/go-gachain/node1 \
mkdir /opt/gachain/centrifugo \
创建数据库¶
- 将用户密码postgres更改为GAChain的默认密码 gachain。您可以设置自己的密码,但必须在节点配置文件 config.toml 中进行更改。
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'gachain'"
- 创建节点当前状态数据库,例如 gachaindb:
sudo -u postgres psql -c "CREATE DATABASE gachaindb"
配置Centrifugo¶
- 创建Centrifugo配置文件:
echo '{"secret":"CENT_SECRET"}' > /opt/gachain/centrifugo/config.json
您可以设置自己的 secret,但是您还必须在节点配置文件 config.toml 中更改它。
安装go-gachain¶
- 从GitHub下载 最新版本的go-gachain :
- 将go-gachain二进制文件复制到
/opt/gachain/go-gachain
目录。如果您使用的是 默认的Go工作区 则二进制文件位于$HOME/go/bin
目录:
cp $HOME/go/bin/go-gachain /opt/gachain/go-gachain
配置第一个节点¶
- 创建节点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的密钥,包括节点公私钥和账户公私钥:
/opt/gachain/go-gachain/go-gachain generateKeys \
--config=/opt/gachain/go-gachain/node1/config.toml
- 生成第一个区块:
注解
如果您要创建自己的区块链网络。你必须使用该 --test=true
选项。否则您将无法创建新帐户。
/opt/gachain/go-gachain/go-gachain generateFirstBlock \
--config=/opt/gachain/go-gachain/node1/config.toml \
--test=true
- 初始化数据库:
/opt/gachain/go-gachain/go-gachain initDatabase \
--config=/opt/gachain/go-gachain/node1/config.toml
启动第一个节点服务端¶
要启动第一个节点服务端,您必须启动两个服务:
- centrifugo
- go-gachain
如果您没有将这些文件创建 services,那么您可以从不同控制台的目录中执行二进制文件。
- 运行centrifugo:
/opt/gachain/centrifugo/centrifugo \
-a 192.168.1.1 -p 8000 \
--config /opt/gachain/centrifugo/config.json
- 运行go-gachain:
/opt/gachain/go-gachain/go-gachain start \
--config=/opt/gachain/go-gachain/node1/config.toml
部署其他节点¶
所有其他节点(节点2和节点3)的部署与第一个节点类似,但有三个不同之处:
- 您不需要生成第一个区块。但是它必须从节点1复制到当前节点数据目录;
- 该节点必须通过配置
--nodesAddr
选项从节点1下载区块; - 该节点必须使用自己的地址和端口。
节点2¶
按照以下一系列操作:
创建节点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复制第一个区块文件到节点2,例如,您可以通过
scp
在节点2执行该操作:scp user@192.168.1.1:/opt/gachain/go-gachain/node1/1block /opt/gachain/go-gachain/node2/生成节点2的密钥,包括节点公私钥和账户公私钥:
/opt/gachain/go-gachain/go-gachain generateKeys \ --config=/opt/gachain/go-gachain/node2/config.toml初始化数据库:
./go-gachain initDatabase --config=node2/config.toml
运行centrifugo:
/opt/gachain/centrifugo/centrifugo \ -a 192.168.1.2 -p 8000 \ --config /opt/gachain/centrifugo/config.json运行go-gachain:
/opt/gachain/go-gachain/go-gachain start \ --config=/opt/gachain/go-gachain/node2/config.toml
结果,节点从第一个节点下载区块。该节点不是验证节点,因此无法生成新区块。节点2将后面添加到验证节点列表中。
节点3¶
按照以下一系列操作:
创建节点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复制第一个区块文件到节点3,例如,您可以通过
scp
在节点3执行该操作:scp user@192.168.1.1:/opt/gachain/go-gachain/node1/1block /opt/gachain/go-gachain/node3/生成节点3的密钥,包括节点公私钥和账户公私钥:
/opt/gachain/go-gachain/go-gachain generateKeys \ --config=/opt/gachain/go-gachain/node3/config.toml初始化数据库:
./go-gachain initDatabase --config=node3/config.toml
运行centrifugo:
/opt/gachain/centrifugo/centrifugo \ -a 192.168.1.3 -p 8000 \ --config /opt/gachain/centrifugo/config.json运行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¶
- 从 Node.js官方网站 或通过命令行下载Node.js LTS版本8.11 :
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash
- 安装Node.js:
sudo apt install -y nodejs
Yarn¶
- 从 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
- 安装Yarn:
sudo dpkg -i yarn_1.7.0_all.deb && rm yarn_1.7.0_all.deb
构建Govis应用程序¶
- 通过git从 Govis的GitHub仓库 下载Govis的最新版本:
cd /opt/gachain \
&& git clone https://github.com/GACHAIN/gachain-front.git
- 通过Yarn安装Govis依赖项:
cd /opt/gachain/gachain-front/ \
&& yarn install
添加区块链网络配置¶
- 创建包含有关节点连接信息的 settings.json 文件:
cp /opt/gachain/gachain-front/public/settings.json.dist \
/opt/gachain/gachain-front/public/public/settings.json
- 在任何文本编辑器中编辑 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桌面版应用程序¶
- 使用yarn构建桌面版:
cd /opt/gachain/gachain-front \
&& yarn build-desktop
- 桌面版将打包成AppImage后缀格式:
yarn release --publish never -l
构建之后,您的应用程序就可以使用了,但是其 连接配置 将无法更改。如果这些设置需要更改,则必须构建新版本的应用程序。
构建Govis Web应用程序¶
- 构建Web应用程序:
cd /opt/gachain/gachain-front/ \
&& yarn build
构建之后,可再发行文件将放置到 /build 目录中。您可以使用您选择的任何Web服务器进行部署,settings.json 文件也必须放在该目录。请注意,如果连接设置发生更改,则无需再次构建应用程序。而是编辑 settings.json 文件并重新启动Web服务器。
- 出于开发或测试目的,您可以构建Yarn的Web服务器:
sudo yarn global add serve \
&& serve -s build
之后,您的Govis Web应用程序将在以下位置可用: http://localhost:5000
。
区块链网络配置¶
创建创始人的帐户¶
为第一个节点所有者创建一个帐户。该帐户是新区块链平台的创始人,并具有管理员访问权限。
运行 Govis (前端);
使用以下数据导入现有帐户:
节点所有者私钥的备份加载位于
/opt/gachain/go-gachain/node1/PrivateKey
文件中。注解
该目录中有两个私钥文件。
PrivateKey
文件用于节点所有者的帐户,可创建节点所有者的帐户。NodePrivateKey
文件是节点本身的私钥,必须保密。
登录该账户后,由于此时尚未创建角色,因此请选择 Without role 或 无 选项。
导入应用、角色和模版¶
此时,区块链平台处于空白状态。您可以通过添加支持基本生态系统功能的角色、模版和应用程序框架来配置它。
- 克隆应用程序存储库;
cd /opt/gachain \
&& git clone https://github.com/GACHAIN/apps.git
在Govis中导航到 Developer > 导入;
按此顺序导入应用:
- /opt/gachain/apps/1_system.json
- /opt/gachain/apps/2_lang_res.json
- /opt/gachain/apps/3_basic.json
- /opt/gachain/apps/4_conditions.json
导航到 Admin > 角色,然后单击 安装默认角色;
通过右上角的配置文件菜单退出系统;
以 Admin 角色登录系统;
导航到 Home > 投票 > 模版列表,然后单击 安装默认模版。
将第一个节点添加到节点列表中¶
导航到 Admin > 平台参数,然后单击 full_nodes 参数的齿轮图标;
指定第一个区块链网络节点的参数。
- public_key - 节点公钥位于
/opt/gachain/go-gachain/node1/NodePublicKey
文件; - key_id - 节点所有者的账户地址位于
/opt/gachain/go-gachain/node1/KeyID
文件。
- public_key - 节点公钥位于
{"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)的成员才能参与添加其他验证节点所需的投票。这意味着在添加新的验证节点之前,必须为该角色指定生态系统的成员。
在本章节中,创始人的帐户被指定为共识角色的唯一成员。在生产环境中,必须将该角色分配给平台执行治理的成员。
- 导航到 Home > 角色 ,然后单击共识角色(Consensus);
- 单击 分配 将创始人的帐户分配给该角色。
创建其他节点所有者帐户¶
运行Govis;
使用以下数据导入现有帐户:
- 节点所有者私钥的备份加载位于
/opt/gachain/go-gachain/node2/PrivateKey
文件中。
- 节点所有者私钥的备份加载位于
登录该账户后,由于此时尚未创建角色,因此请选择 Without role 或 无 选项;
导航到 Home > 个人信息,然后单击个人信息名称;
添加帐户详细信息(个人信息名称,说明等)。
添加节点所有者为Validators角色¶
新节点所有者操作:
- 导航到 Home > 验证者;
- 单击 创建请求 并填写验证者候选人的申请表;
- 单击 发送请求。
创始人操作:
- 以共识角色(Consensus)登录;
- 导航到 Home > 验证者;
- 根据候选人的请求点击“播放”图标开始投票;
- 导航到 Home > 投票,然后单击 更新投票状态;
- 单击投票名称并为节点所有者投票。
结果,新节点所有者的帐户被分配给 Validator 角色,并且新节点也被添加到验证节点列表中。