[DEPRECATED] 文档地址已经更新为: https://cryptape.github.io/cita/

介绍

CITA( Cryptape Inter-enterprise Trust Automation )是一个面向企业级应用的支持智能合约的区块链框架,可以为企业级区块链应用提供一个稳固、高效、灵活、可适应未来的运行平台。

依赖

系统平台要求

CITA的运行环境是Linux和OSX操作系统,目前不支持Windows系统。CITA是基于Ubuntu 16.04稳定版开发的,在该系统版本上运行将是正确无误的。如果在Linux系统的其他版本上运行出现问题,建议将系统版本切换到Ubuntu 16.04版本。

安装编译器及开发库

从Github仓库下载CITA的源代码,然后切换到CITA的源代码目录

cd cita
git submodule init
git submodule update

启动 install_develop.sh 脚本安装依赖,命令如下:

scripts/install_develop.sh

安装完成后,可以重新登录使Rust相关的环境变量生效,也可直接使用以下命令立即生效:

source ~/.cargo/env

经过如上设置,CITA的依赖便安装完成了。

安装

单元测试依赖rabbitmq, 如果没有启动, 需要用以下脚本启动并配置

scripts/config_rabbitmq.sh

可以按照自己的需求自行选择相应的编译方式(Debug-调试模式 或 Release-发行模式)

make debug

或者

make release

编译成功后,其生成的可执行文件将放在 target/install 目录下,生产环境下只能看到target/install里面的内容。

配置

先切换到发布件目录,并将bin目录加入到PATH环境变量中:

cd target/install
export PATH=$PWD/bin:$PATH

另外,脚本 admintool.sh 主要用来创建创世块配置、节点相关配置、网络连接配置、私钥配置等相关文件。

设置节点的配置信息,该默认示例Demo中配置了4个节点,对Demo中的节点进行默认初始化的操作命令为:

./bin/admintool.sh

此外,用户可以根据需要更改其中的默认配置,使用命令 admintool.sh -h 来获得详细帮助,允许自定义配置包括:

  • 系统管理员账户
  • 网络列表,按照 IP1:PORT1,IP2:PORT2,IP3:PORT3 ... IPn:PORTn 的格式
  • 共识算法选择,可供选择的有 tendermintraftpoa
  • 加密方法选择
  • 出块时间间隔
  • 单数据块中交易数量限制
  • 累积多少历史交易量后进行重复交易的检查

节点初始化操作成功后,将在发布件目录下生成节点的配置文件,其生成的节点目录为:

  • node0
  • node1
  • node2
  • node3

可以使用 cita start node0 等命令对节点进行启动和停止等操作了。

运行

启动节点的服务步骤都是相同的,以 node0 为例,其启动CITA节点的具体步骤为:

  1. 启动节点 node0 之前需进行初始化:
cita setup node0
  1. 启动节点 node0 的服务:
cita start node0

而停止节点 node0 服务只需执行以下操作:

cita stop node0

此外, cita 命令中还包括其他操作,具体使用可以查看相关说明:

cita

除了上述的基本操作命令,为了方便用户对Demo进行相关测试,我们在目录 cita/tests/integreate_test 下提供了一些测试脚本。 例如,测试所有节点服务启动并成功出块,然后停止节点服务的操作为:

./cita_start.sh

停止所有节点服务的命令为:

./cita_stop.sh

备注:以上示例Demo的节点启动都是位于同一台机器上,如果需要部署到不同的服务器上,只需删除其他节点的配置(“target/install/nodeX”),并保留自己节点的配置,然后将整个目录(即 target/install 目录)拷贝到其他服务器上运行即可。

  1. 将不同节点部署到不同服务器

节点的服务器需要安装依赖

bash ./scripts/install_runtime.sh

将节点拷贝到对应的服务器,并修改 network.toml 目录下的配置文件,修改为对应的IP地址。 使用 cita start 启动各个节点。

验证

  • 查询节点个数

Request:

curl -X POST --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":74}' 127.0.0.1:1337 | jq

Result:

{
  "jsonrpc": "2.0",
  "id": 74,
  "result": "0x3"
}
  • 查询当前块高度。

Request:

curl -X POST --data '{"jsonrpc":"2.0","method":"cita_blockNumber","params":[],"id":83}' 127.0.0.1:1337 | jq

Result:

{
  "jsonrpc": "2.0",
  "id": 83,
  "result": "0x8"
}

返回块高度,表示节点已经开始正常出块。

更多API(如合约调用、交易查询)请参见 RPC调用

区块链

什么是区块链

区块链是一种去中心化的分布式账本系统,用来维持一个由区块组成的不断增长的记录列表。每个区块含有一个时间戳和一个指向前面区块的链接。区块链由一个对等网络 管理,它们共同遵守一个验证新块的协议。在设计上,区块链本身抵制数据的修改。数据一旦被记录进区块链,除非后续区块都被改变以及网络多数串通的情况下,否则 数据不能被更改。功能上,区块链可以作为开放的分布式账本,它可以以一种可验证且永久的方式记录双方之间的交易。

区块链的概念是在2008年中本聪于《比特币白皮书》提出,并在2009年以区块链为核心组成部分实现了数字货币比特币。比特币是第一个在没有权威机构和中央服务器 情况下,解决双花问题的数字货币。另外,区块链也可以用于登记和发行数字化资产、产权凭证、积分等,并以点对点的方式进行转账、支付和交易。区块链系统与传统 的中心化账本系统相比,具有完全公开、不可篡改、防止多重支付等优点,并且不依赖于任何的可信第三方。

智能合约

智能合约是旨在促进、验证或强制执行合约的计算机协议,它是由Nick Szabo于1994年首次提出。

一个智能合约就是一个执行合约条款的计算机化交易协议。智能合约程序不只是一个可以自动执行的计算机程序,它自己就是一个系统参与者,它对接收到的信息进行 回应,它可以接受和存储价值,也可以向外发送信息和价值。这个程序就像一个可以被信任的人,可以临时保管资产,总是按照事先的规则执行操作。

智能合约的模型:一段代码(智能合约),被部署在分享的、复制的账本上,它可以维持自己的状态,控制自己的资产和对接收到的外界信息或者资产进行回应。

智能合约最普遍的应用领域就是金融领域,每一类金融合约都可以程序代码的形式写成智能合约。如差价合约、代币系统、储蓄钱包、作物保险等。

共识

共识是指互相独立的多个系统参与方间对某个问题达成一致的结果。而在区块链中,共识则是在组成网络的分布式节点中达成。某个记账节点提议了一个区块应该包含 哪些交易数据,然后把该区块广播给其他的参与节点,其他节点要就是否使用这个区块达成一致。也就是说,区块链中的共识是指如何将全网交易数据客观记录并且 不可篡改的过程。

实际上,要保障系统满足不同程度的一致性,往往需要通过共识算法来达成。常见共识算法可分为两大类:一是非拜占庭错误的算法,一般包括Paxos、Raft及其变种; 二是能容忍拜占庭错误的情况,一般包括PBFT系列、PoW系列算法等。从概率角度,PBFT系列算法是确定的,一旦达成共识就不可逆转;而PoW系列算法则是不确定的, 但是随着时间推移,被推翻的概率越来越小。

传统的分布式一致性算法,采用的是一个节点一票、少数服从多数的方式,这种方式在联盟链是可用的,因为其中节点需要通过验证才能加入网络。但是在像比特币、 以太坊等公有链,任何人都可以加入网络,竞争记账权。如果采用一节点一票的方式,就很容易遭受女巫攻击:攻击者批量制造大量的节点加入系统,通过绝对多数的 投票权发起攻击。而对于一个节点,是无法辨别其他节点是普通节点还是恶意节点。

为了避免女巫攻击,在公有链中,常用的共识机制为PoW以及PoS。在PoW算法中,要求记账节点花费一定的资源做一些计算,然后向网络其他节点提交计算的工作量证明, 证明能够被快速验证,并且工作容易被度量。因为工作量证明无法伪造,有很高的成本,只有遵守协议约定,才能收回成本,获得收益。而PoS则是用户持有系统代币的 数量决定打包出块的概率,PoS中的代币类似于公司的股权,大股东对系统有更大的发言权,有更多的责任,也有获得更多收益的权利。

公有链

公有链是指世界上任何个体或者机构都可以发送交易,且交易能够获得该区块链的有效确认,任何人都可参与其共识过程,记账权完全由公开的共识算法决定,即整个 网络是开放的。作为中心化或准中心化信任的替代物,公共区块链的安全由“加密数字经济”维护,“加密数字经济”采用工作量证明机制或权益证明机制等方式,将经济 奖励和加密数字验证结合了起来,并遵循着一般原则:每个人从中可获得的经济奖励,与对共识过程做出的贡献成正比。

公有链的特点可以归结于三点:一是保护用户免受开发者的影响,在公有链中程序开发者无权干涉用户,所以区块链可以保护使用他们开发的程序的用户;二是访问门槛低, 任何拥有足够技术能力的人都可以访问,也就是说,只要有一台能够联网的计算机就能够满足访问的条件;三是所有数据默认公开,尽管所有关联的参与者都隐藏自己的 真实身份,这种现象十分的普遍。他们通过他们的公共性来产生自己的安全性,在这里每个参与者都可以看到所有的账户余额和其所有的交易活动。

许可链

只允许通过身份认证的用户加入网络的区块链称为许可链。依照其中节点的所有权又可划分为联盟链和私有链,其中联盟链节点由一个企业联盟维护,私有链节点由 单个企业维护。许可链是共识过程受到预选节点控制的区块链;比如,有一个15个金融机构组成的联盟,每个机构都运行一个节点,为了使每个区块生效则需要获得 其中10个机构的确认。许可链可能允许每个人都可读取,又或许只受限于参与者,或者走混合型路线,例如区块的根哈希及其API对外公开,API可允许外界用来作有限 次数的查询和获取区块链状态的信息。

许可链的特点可以归结于四点:一是身份准入,在许可网络中,只有获得特定许可的节点才能加入节点;二是系统治理,公有链由于技术与治理的高度去中心化,在 紧急状况出现时缺乏高效的协调及干预手段,而许可链中的企业用户具有更好的协调机制,在系统不可用时的干预方法相对高效;三是执行性能,许可链准入机制的存在 杜绝了女巫攻击,使得传统共识算法有了用武之地,使交易处理的延迟和吞吐量获得质的飞跃;四是隐私,区块链上数据对所有共识节点公开,而企业应用对隐私方案的 需求比较大,故需要一个可行的隐私技术方案。

区块链的使用场景

区块链技术,尤其是许可链,可以运用在很多行业和领域。从最初的数字货币,到证券交易结算、会计审计等涉及合约审核的金融领域, 再到政府、医疗、征信体系等公共领域,区块链的版图正在迅速扩张,在全球各个领域形成一次历史性的技术和商业变革。

以清算与支付为例, 在一个标准的银行间资金转移过程中,如果发款银行和接收银行互相没有开立账户,他们将不得不依赖一个中央清算所或者关联银行。 从实行到结算,支付的工作流程要花费数天,而且中间方还要收取一定费用。

区块链可以实现点对点的交易,并将交易数据与全网共享监督,这可以有效提升传统银行业的支付结算效率并降低交易成本。 通过去中心化账本来替代中心机构认证资产所有权,机构共同运行和检验,来防止欺诈和人为操控。

_images/4e1a2af8fb4b7db9098b371badf76fb8.jpg

除了支付结算,区块链技术还可以被应用于银行不同的业务条线:如票据和供应链金融,还有“了解您的客户(KYC)”和“反洗钱(AML)”等风险管理领域。 在证券市场的各个领域,包括证券的发行与交易、清算结算、股东投票等,都可以实现与区块链技术的无缝对接。在保险业,保险经营机构能够应用区块链技术, 将信用记录在公开的网络上,接受全网监督,时间戳的功能会保证所有交易记录不可更改,一次解决信用问题。这样管理成本就大大降低,更可能回归到保险最初的互助本意。 在会计审计领域,区块链系统可以满足利益相关者对于独立审计的客观要求和对审计工作的职业道德要求,可以应用于审计行业并且能够促进审计工作更加透明、高效。 此外,在征信、医疗、公证、能源等领域,区块链都有丰富的应用场景,这里不再一一介绍。

CITA

CITA( Cryptape Inter-enterprise Trust Automation )是一个面向企业级应用的支持智能合约的区块链框架,可以为企业级区块链应用提供一个稳固、高效、灵活、可适应未来的运行平台。

共识

CITA的共识模块实现了两种共识算法,非拜占庭容错的Raft算法和拜占庭容错的Tendermint算法。

  • Raft算法

    系统启动时,每个节点随机超时触发选举,当选举成功后,leader负责接收交易和出块。leader将接收到的交易通过rpc发送到follower,当leader接收到一半以上 follower的append成功响应时,将此交易提交,并保存到交易池。当leader接收到chain发送的status信息时,准备打包交易出块。如果leader所在的节点chain异常, 则会触发新的选举流程。

  • Tendermint算法

    Tendermint是PBFT算法的变种。每个节点轮流打包交易并出块,在每个高度上,只有一个块可以被提交。 当块无法在该轮被提交时,协议会移动到下一轮,由新的节点负责提议一个该高度的块。当超过2/3的验证节点对同一个块precommit,该块才能够被提交。 Tendermint算法可以容忍网络中少于总节点1/3的拜占庭节点。

智能合约

CITA对智能合约的执行器分为两类,一种是解释性合约执行器,另一种是原生合约的执行器,以满足不同用户场景的业务需求。

  • CITA对以太坊智能合约进行封装与兼容,用户在以太坊编写的Solidity合约,同样可以部署、运行在CITA上,一次编写多处运行,无需重新学习其他合约语言,简化编写合约的成本。
  • CITA对原生合约的支持,合约直接转化为机器指令码去执行,大大提高合约的执行效率,满足企业级别的复杂业务逻辑场景。

用户通过调用合约在CITA节点上发送交易,各个节点之间对合约执行的状态、结果进行共识,保证合约正确的执行,以及交易结果的正确。 CITA支持智能合约是图灵完备的,采用系统合约管理用户对系统资源的使用,用户可以编写各自的业务逻辑,适用于不同的业务需求场景。

性能按需伸缩

CITA采用了微服务架构,解耦了服务接口、共识、交易处理、网络同步等服务,可灵活组合与配置以满足行业应用的需要。

针对一般应用,只需将上述服务部署到单个物理机上。针对每秒交易量高达10万 的特殊应用,CITA可通过将交易处理服务分布到多个节点以提升每秒交易量,实 现交易性能的横向扩展。

功能扩展与定制

CITA采用消息总线作为微服务间的通信接口,客户应用可以通过订阅总线上的信 息,实现对CITA的功能扩展,如性能监控、业务监控。

隐私与保密

隐私交易只能被该交易的相关方(参与方)查看,而无关方无法查看其交易内容,从而达到交易保密的功能;

  • 隐私交易提交后,先在本地进行加密,采用PGP加密原理;
    • 根据生成的symmetric key对交易内容进行对称加密,然后对加密后的内容进行HASH摘要值计算;
    • 将原有交易内容替换为HASH摘要值;
    • 对symmetric key进行非对称加密(加密根据本节点私钥和对端节点公钥),生成一对一的加密symmetric key;
  • 将以上步骤中的加密后的内容,交易数据,加密后的symmetric key打包成加密box,通过点对点隐私交易传输协议传送给拥有解密私钥的节点;
  • 当对端节点接收到加密box信息之后,需通过本地私钥和发送节点公钥解密,才能进行后续的操作;
  • 隐私交易的无关方最后只能获取包含根据原有交易内容进行加密之后计算的HASH摘要值的交易,而不能查看其明文内容,这样就实现了隐私交易对无关节点的不可见的功能;
  • 隐私交易只会在参与方节点上执行,而在无关方则直接跳过该笔交易的执行;
  • 隐私交易的执行状态可以向参与方节点进行查询。

身份管理

CITA中分为节点和用户两种身份。其中节点参与整个链的共识或者作为只读节点存在,保存了整条链的信息对外提供服务,用户则是CITA的使用者,消费由节点提供的服务。 对此二者身份的管理保证了接入节点身份和用户身份的可靠性,一定程度上提高了CITA的安全性。

  • 节点身份的管理主要是对节点身份进行验证,控制节点的接入。只有验证通过,允许介入的节点才能够与其他节点建立通讯会话、同步信息、参与共识等。 目前CITA是采用白名单的方式来对节点身份进行管理。白名单由管理机构生成并分发,由运维人员维护和管理,由每个节点本地保存。不在白名单中的节点则意味着验证失败。
  • 用户身份的管理包括用户身份验证以及私钥的管理。身份验证可控制用户的准入,而私钥的管理主要用于对用户私钥的维护。
    • 只有用户通过了身份验证才能够使用CITA提供的服务。身份验证也可由企业提供,包括中心化体系的LDAP或PKI,通过CITA提供的标准接口即可使用。
    • CITA允许已验证通过的用户对其私钥进行维护,可申请定期更新,对私钥进行替换,一定程度上增加私钥的安全性,降低被盗的风险。如私钥不慎丢失,也可根据私钥更新权限申请使用新的私钥。

权限管理

不同于公有链中账号不受管理,CITA作为许可链使用权限管理机制来控制账号的行为,一定程度上提高了CITA的安全性。为了满足企业级应用的需要,CITA使用基于角色的权限管理。 每一种账号可对应多个角色,每个角色可拥有多种权限。

  • 为了统一对账号进行基于角色的权限管理,CITA把用户和节点当成两种不同的角色,目前账号管理主要是对用户角色进行管理,后续可能会支持节点角色的管理。
  • CITA内置了管理员角色和普通角色,其中管理员角色拥有所有权限,拥有管理员角色的账号只拥有此唯一角色被称为管理员。普通角色拥有一般的读取权限及创建角色的权限。
  • 默认除了管理员所有账号拥有普通角色,即所有账号可自定义角色。
  • 用户可对角色进行授予和收回。

配额管理

交易的执行和存储需要使用每个节点的计算资源,包括CPU、磁盘空间、带宽等。而智能合约的执行往往需要更大的计算资源,CITA作为一个支持智能合约的区块链, 需要恰当的机制来限制节点的计算资源的使用,防止被有意或无意的耗尽计算资源。

CITA将节点计算资源的度量称为计算配额,配额管理即为相应的发行和消耗机制。配额的发行机制只是为了补充消耗的计算配额,故CITA默认实行简单的周期性恢复,也可由用户进行自定义的发行策略。 而配额的消耗机制根据CITA执行器的不同而不同,可由设置的配额消耗上限来对其进行管理和限制。配额消耗上限可分为区块的消耗上限和账号消耗上限。 区块的消耗上限即为每个区块能够使用的最大配额值;而账号的消耗上限可对每个账号进行全局性的设置也可对制定账号进行特殊设置。如此不仅可统一对配额上限进行管理,也可针对不同账号灵活分配配额。

系统治理

许可链一般运行在大型金融场景,这类机构一般都有很强的数据一致性和准确性需求,不能容忍一点点的错误。但是当前区块链还处在非常早期的阶段,底层系统的实现和业务智能合约的实现都难免有意想不到的错误,存在数据不一致和错误的风险,这就要求许可链有系统治理的方案。目前区块链常见的方式是更新系统代码并硬分叉,对于数据不一致的的情况一般是清空分叉链数据再进行同步,费时费力并且增加运维难度。

在CITA中用户可以设定超级管理员角色,得益于灵活的身份验证服务设计,超级管理员角色可以有任意的身份验证逻辑。在单中心的治理结构下该角色可以由单一核心用户控制,在多中心的治理结构下,核心用户可以形成类似委员会的治理机构联合控制(例如通过多重签名)超级管理员角色。

中心化治理角色能够通过链外通道协商形成一致行动决议,增强系统在紧急情况下的应对能力。在操作错误、软件错误或是硬件错误等问题发生时,系统可能进入紧急状态。CITA将紧急状态分为交易可恢复(Transaction Recoverable)和消息可修复(Message Recoverable)两类。

由于错误的交易或者是有bug的智能合约生成了错误的视图数据,但是节点依然能够处理交易,此时系统处于交易可恢复紧急状态。在这种情况下,超级管理员可以构造修订交易快速应对。节点在处理修订交易时同样会先将该交易打包入块,再执行交易,因此所有修订交易都将被记录在历史中,为操作审计提供支持。

消息可修复紧急状态发生时,节点无法再正常处理交易并打包,共识服务停滞,但是点对点网络依然能够正常工作。此时超级管理员可以通过CITA提供的管理员工具构造特殊消息并广播,节点收到消息并验证发送者身份后将直接处理,无需共识。

RPC模块

RPC(Remote Procedure Call Protocol)即远程过程调用协议,它是一种通过网 络从远程计算机程序上请求服务,不需要了解底层网络技术的协议,是基于可靠 性、可控制TCP的应用层协议,从而保证了用户数据的传输完整。

在CITA内部专门提供了RPC模块,用于处理用户的RPC请求。其作用,一方 面对用户的请求数据进行简单的校验,对不符合格式的数据进行友好错误状态返 回,对于内部模块,过滤了部分杂乱请求,减少了共识模块等部分压力;

另一方面,从用户角度来说,它是唯一与CITA节点进行数据交互的模块,用户不 需要关注其它模块的运行状态即可得到相应的服务,在一定程度保证了CITA其它 模块的运行安全。

目前,RPC模块是一个可插拔的模块,与其它模块间的数据交互是通过内部数据 交换协议来实现的,模块只做最单一功能,便于与其它模块解耦,这意味着,模 块的可插拔性,给我们提供了进行横向扩展的策略。通过灵活配置RPC模块服务 的数量,允许更多用户的接入来处理大量请求,相当于CITA对用户的接入进行负 载均衡,对请求进行转发,并提供相应的应答。在接入协议实现上,可以定制用 户所需各种传输协议规范、特定场景的接口,如HTTP、WebSocket协议,以满足 其所需服务。

共识

共识算法解决的是针对某个提案(proposal),系统中的节点达成一致的过程。在 区块链系统中,共识算法确保所有正确节点的交易顺序是一致的。CITA共识模块 包括Raft和Tendermint的实现,共识模块负责接收交易并进行简单验证,然后打 包出块。在CITA的实现中,共识以相对独立的形式存在,其他共识算法的实现可 以很方便地集成到CITA中。

链(Chain)

Chain模块可以认为是一个Append only的KV数据库,它以块为单位,不断添加新 的区块到链上,并存储交易以及交易执行后的状态到数据库。

CITA中的Chain模块主要功能有以下几点:

  1. 对共识后产生的区块,处理这些区块中的交易并生成区块哈希,最后添加到链上;
  2. Chain模块对RPC模块提供各种查询功能;
  3. Chain模块对VM模块提供操作数据库的接口。

合约引擎(Contract Engine)

智能合约是运行在可复制、共享的账本上的计算机程序,可以处理信息,接收、 储存和发送价值。而合约引擎则为智能合约提供了一个简单、确定、高效、安全 的执行环境。CITA提供了多种形式的合约引擎,用户使用接口与EVM兼容:

  • EVM合约引擎

用户可使用 Solidity 语言编写智能合约,并在 Remix 集成开发环境中开发 和测试,最后通过CITA的合约创建和调用接口来部署和调用。换句话说,CITA的 合约通过以太坊智能合约的生态,一方面降低用户培养成本,一方面降低安全风 险。

  • 原生合约引擎

使用EVM合约能够快速完成功能开发,并满足初期性能需求。随着业务发展,当 性能上遇到瓶颈时,可以EVM合约开发的基础上,用原生合约来实现,合约调用 接口不变。原生合约支持两种数据访问接口,兼容EVM合约的键值数据库方式与 传统的结构化数据库方式。同时也提供了原生合约的注册表机制,用户可以实现 合约的创建与销毁。

RPC 调用

JSON-RPC 接口

目前,RPC模块提供以下接口,具体详细内容,可见下文介绍。

  • net_peerCount
  • cita_blockNumber
  • cita_sendTransaction
  • cita_getBlockByHash
  • cita_getBlockByNumber
  • cita_getTransaction
  • eth_getTransactionCount
  • eth_getCode
  • eth_getTransactionReceipt
  • eth_call

值得注意的是,以eth开头的RPC接口是为了兼容以太坊而设计的,具体接口详情可以参考 以太坊 接口说明。

JSON-RPC 接口详细介绍

net_peerCount 当前节点连接数

params
return
  • count: 节点连接数量

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":74}' 127.0.0.1:1337 | jq

// Response
{
  "jsonrpc": "2.0",
  "id": 74,
  "result": "0x3"
}

cita_blockNumber 返回当前块高度

params
return
  • height: 当前高度值

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"cita_blockNumber","params":[],"id":83}' 127.0.0.1:1337 | jq

// Response
{
  "jsonrpc": "2.0",
  "id": 83,
  "result": "0x8"
}

cita_sendTransaction 发送交易

params
  • data: 签名的交易
return
  • state: 交易的状态
  • hash: 交易的哈希值

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"cita_sendTransaction","params":["0a28356230373365393233333934346235653732396534366436313866306438656466336439633334611a80040aba030a28356230373365393233333934346235653732396534366436313866306438656466336439633334611a87030a013010a08d0622fd026060604052341561000c57fe5b5b7f4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc6633604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5b60fd806100806000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b11460445780636d4ce63c146061575bfe5b3415604b57fe5b605f60048080359060200190919050506084565b005b3415606857fe5b606e60c6565b6040518082815260200191505060405180910390f35b7fc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b816040518082815260200191505060405180910390a1806000819055505b50565b600060005490505b905600a165627a7a7230582079ba3769927f0f8cf4bec7ce02513b56823c8fc3f4047989951e042a9a04651900292080808080101241d51ca7a0171113478f47357a71c240bd0431f52639741a6721725de276a88d2e723b12f4bbeb1cdddea63f947ddb9db6e2667f08a03af1577c42d3c1a3dc5a7c01208080808010"],"id":1}' 127.0.0.1:1337 | jq

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "hash": "0xf31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b",
    "status": "OK"
  }
}

cita_getTransaction 获取交易信息

params
  • hash: 交易哈希值
return
  • hash: 交易哈希值
  • content: 交易内容
  • blockNumber: 块高度
  • blockHash: 块哈希
  • index: 交易在块中的索引

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"cita_getTransaction","params":["f31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b"],"id":1}' 127.0.0.1:1337 | jq

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "hash": "0xf31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b",
    "content": "0x0a28356230373365393233333934346235653732396534366436313866306438656466336439633334611a80040aba030a28356230373365393233333934346235653732396534366436313866306438656466336439633334611a87030a013010a08d0622fd026060604052341561000c57fe5b5b7f4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc6633604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5b60fd806100806000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b11460445780636d4ce63c146061575bfe5b3415604b57fe5b605f60048080359060200190919050506084565b005b3415606857fe5b606e60c6565b6040518082815260200191505060405180910390f35b7fc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b816040518082815260200191505060405180910390a1806000819055505b50565b600060005490505b905600a165627a7a7230582079ba3769927f0f8cf4bec7ce02513b56823c8fc3f4047989951e042a9a04651900292080808080101241d51ca7a0171113478f47357a71c240bd0431f52639741a6721725de276a88d2e723b12f4bbeb1cdddea63f947ddb9db6e2667f08a03af1577c42d3c1a3dc5a7c01208080808010",
    "blockNumber": "0x5b",
    "blockHash": "0xc68eb999432bcc0712d1b4a1d03c6eb10a27ea0fe34e8f60cb3e02d8ccbcda8d",
    "index": "0x0"
  }
}

cita_getBlockByHash 根据块hash查询块的信息

params
  • hash: 32 bytes, 块的哈希值
  • boolean: 是否返回交易信息(true: 返回详细交易列表,false: 只返回交易hash)
return
  • object: 块中的具体信息

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"cita_getBlockByHash","params":["2f3853b7d3bb3d6bc4bb2103b645aae2b8145125340018209184c7709e04dbc3", true],"id":1}' 127.0.0.1:1337 | jq

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "version": 0,
    "hash": "0x5038c222d460c32fd06df36d58bb7cf5c368a55e207a46ecb18695451bfe4069",
    "header": {
      "timestamp": 1499756200950,
      "prevHash": "0xb28ec1911d375350664b9673a61d952e9a748f3e63606f1440f313c4911fde58",
      "proof": {
        "proposal": "0x0f25d396361c7d54bb16389f6a14bf95207915f91d180d382093e19adfc4133b",
        "height": 902,
        "round": 0,
        "commits": {
          "0x2b027dacd33a41ddb09e21805778f19951776ed5": "0x1532c58faedf9e103dd84aa6aacbd2121aa3a8102faa506e7e152fb10e45bafd31b1c3d372cf5d42f8b27a8bfea112ae194de76d99206f73837ad8c30267e6a501",
          "0x2d74a106464fbdf94e47bb28605a1aa244ab7788": "0x2ec53371cee732d59d23a58cf6cf53d818fb906fdeb5b0521a3a4cdbb75cf29658a1ff5fa95e4dc71563cbed10070c68e2eec0f812fa3be8e019b6df6e9ea66201",
          "0x3efd4959af72e1214ab83caa0f04a0cc3e54d383": "0xb051f0cc41bc3caed472d3c7a35e06d805e8f6d15ccb3efc257d71ee96932c5877a8e52fc29cb3bef73e0edbad62c617c4dd16763709b2604ab8b1db2d87736301",
          "0x5223818f7096520bfad68ce3d5ac959267dbc45f": "0x1cf6f8dc9654d461a317db199de0ed0d2d008762833b3358e269ceb3c412b60b3f1a2bd08f969e0dc1c9ebe1a0710002f853438a6ef3ea048de9b4e67387827400"
        }
      },
      "commit": {
        "stateRoot": "0xe29266e5574bc0c848b513d36403d4da71f99f328d3324e8d3134809c33d4fb4",
        "transactionsRoot": "0xf31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b",
        "receiptsRoot": "0x9646cf2572734b4b13fe1616446ab2658e208cfdbaf25e47ebea9b6327e10c5b",
        "gasUsed": "0x0"
      },
      "height": "0x387"
    },
    "body": {
      "transactions": [
        {
          "hash": "0xf31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b",
          "content": "0x0a28356230373365393233333934346235653732396534366436313866306438656466336439633334611a80040aba030a28356230373365393233333934346235653732396534366436313866306438656466336439633334611a87030a013010a08d0622fd026060604052341561000c57fe5b5b7f4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc6633604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5b60fd806100806000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b11460445780636d4ce63c146061575bfe5b3415604b57fe5b605f60048080359060200190919050506084565b005b3415606857fe5b606e60c6565b6040518082815260200191505060405180910390f35b7fc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b816040518082815260200191505060405180910390a1806000819055505b50565b600060005490505b905600a165627a7a7230582079ba3769927f0f8cf4bec7ce02513b56823c8fc3f4047989951e042a9a04651900292080808080101241d51ca7a0171113478f47357a71c240bd0431f52639741a6721725de276a88d2e723b12f4bbeb1cdddea63f947ddb9db6e2667f08a03af1577c42d3c1a3dc5a7c01208080808010"
        }
      ]
    }
  }
}

cita_getBlockByNumber 根据块高度查询块信息

params
  • quantity: 块高度
  • boolean: 是否返回交易信息(true: 返回详细交易列表 false: 只返回交易hash)
return
  • object: 块中的具体信息

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"cita_getBlockByNumber","params":["0xF9", true],"id":1}' 127.0.0.1:1337 | jq
或者
curl -X POST --data '{"jsonrpc":"2.0","method":"cita_getBlockByNumber","params":["249", true],"id":1}' 127.0.0.1:1337 | jq
或者
curl -X POST --data '{"jsonrpc":"2.0","method":"cita_getBlockByNumber","params":[249, true],"id":1}' 127.0.0.1:1337 | jq

eth_getTransactionReceipt 获取交易凭证

params
  • hash: 交易哈希值
return
  • object: 凭证信息

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["f31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b"],"id":1}' 127.0.0.1:1337 | jq

// reuslt
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "transactionHash": "0xf31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b",
    "transactionIndex": "0x0",
    "blockHash": "0xc68eb999432bcc0712d1b4a1d03c6eb10a27ea0fe34e8f60cb3e02d8ccbcda8d",
    "blockNumber": "0x5b",
    "cumulativeGasUsed": "0xca9d",
    "gasUsed": "0xca9d",
    "contractAddress": "0xea4f6bc98b456ef085da5c424db710489848cab5",
    "logs": [
      {
        "address": "0xea4f6bc98b456ef085da5c424db710489848cab5",
        "topics": [
          "0x4f8cfde3439a1a302c21ca51eec26086efbfd940b8c0279889fc6bb6e73ecc66"
        ],
        "data": "0x0000000000000000000000005b073e9233944b5e729e46d618f0d8edf3d9c34a",
        "blockHash": "0xc68eb999432bcc0712d1b4a1d03c6eb10a27ea0fe34e8f60cb3e02d8ccbcda8d",
        "blockNumber": "0x5b",
        "transactionHash": "0xf31e32611322f410f430ef8141c2237c19dd1034eddef8dedba692ec9851799b",
        "transactionIndex": "0x0",
        "logIndex": "0x0",
        "transactionLogIndex": "0x0"
      }
    ],
    "root": "0xe29266e5574bc0c848b513d36403d4da71f99f328d3324e8d3134809c33d4fb4",
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000010000000000001000000000000000000000000000000000000000000000010000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
  }
}

eth_getTransactionCount 获取交易数

param
  • address: 账户地址
  • height: quantity|tag, (可选, 默认: “latest”) 高度值或者 “latest” 或者 “earliest”
return
  • count: 交易个数

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["5b073e9233944b5e729e46d618f0d8edf3d9c34a","0x5b"],"id":1}' 127.0.0.1:1337 | jq

// reslut
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x1"
}

eth_getCode 查看合约信息

params
  • address: 合约的地址
return
  • object: 合约信息
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getCode","params":["ea4f6bc98b456ef085da5c424db710489848cab5","0x5b"],"id":1}' 127.0.0.1:1337 | jq

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b11460445780636d4ce63c146061575bfe5b3415604b57fe5b605f60048080359060200190919050506084565b005b3415606857fe5b606e60c6565b6040518082815260200191505060405180910390f35b7fc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b816040518082815260200191505060405180910390a1806000819055505b50565b600060005490505b905600a165627a7a7230582079ba3769927f0f8cf4bec7ce02513b56823c8fc3f4047989951e042a9a0465190029"
}

eth_getLogs 根据Topic查询logs。

params
  • object
  • fromBlock: quantity|tag, (可选, 默认: “latest”) 高度值或者 “latest” 或者 “earliest”
  • toBlock: quantity|tag, (可选, 默认: “latest”) 高度值或者 “latest” 或者 “earliest”
  • address: data|array, 20 bytes,(可选)合约地址或者合约地址列表
  • topics: array of data, (可选) 过滤条件,关于topics构造,可以参考 以太坊
return
  • object: 日志信息

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics":["0x8fb1356be6b2a4e49ee94447eb9dcb8783f51c41dcddfe7919f945017d163bf3"],"fromBlock": 0}],"id":74}' 127.0.0.1:1337 | jq

// Response
{
    "jsonrpc":"2.0",
    "id":74,
    "result":[
        {
            "address":"0xea4f6bc98b456ef085da5c424db710489848cab5",
            "topics":[
                "0x8fb1356be6b2a4e49ee94447eb9dcb8783f51c41dcddfe7919f945017d163bf3"
            ],
            "data":"0x0000000000000000000000005b073e9233944b5e729e46d618f0d8edf3d9c34a0000000000000000000000000000000000000000000000000000000000000064",
            "blockHash":"0x3e83b74560860344f4c48d7b8089a18173aecd96b6b2148653c61b5d3f559764",
            "blockNumber":"0x4",
            "transactionHash":"0xb38e5b6572b2613cab8088f93e6835576209f2b796104779b4a43fa5adc737af",
            "transactionIndex":"0x0",
            "logIndex":"0x0",
            "transactionLogIndex":"0x0"
        }
    ]
}

eth_call 合约接口调用

params
  • Object: 合约调用对象
  • from: DATA, 20 Bytes 交易发送方
  • to: DATA, 20 Bytes 交易接收方
  • data: DATA, (可选) 经过签名的数据
  • quantity: 块高度
return
  • hash: 交易哈希值

example:

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0xca35b7d915458ef540ade6068dfe2f44e8fa733c","to":"0xea4f6bc98b456ef085da5c424db710489848cab5","data":"0x6d4ce63c"}, 6],"id":2} 127.0.0.1:1337 | jq

// Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": "0x0000000000000000000000000000000000000000000000000000000000000064"
}

RPC错误返回码和错误介绍

错误码 错误消息
-32700 解析错误
-32600 请求错误
-32601 请求服务方法错误
-32602 非法参数
-32603 网络错误
-32000 to -32099 自定义服务错误

错误示例

// 应发送POST请求,而不是GET请求
curl -X GET -d '{"jsonrpc":"2.0","method":"cita_blockNumber","params":[]}' 127.0.0.1:1337 | jq

{
  "jsonrpc": null,
  "id": null,
  "error": {
    "code": -32600,
    "message": "Invalid request"
  }
}

// hash值没有前缀0x
curl -X POST -d '{"jsonrpc":"2.0","method":"cita_getTransaction","params":["0x0063187e6a84ae731cf9"],"id":2}' 127.0.0.1:1337 | jq

{
  "jsonrpc": null,
  "id": null,
  "error": {
    "code": -32602,
    "message": "param is not hash"
  }
}

// 调用方法错误
curl -X POST --data '{"jsonrpc":"2.0","method":"peerCount","params":[],"id":74}' 127.0.0.1:1337 | jq

{
  "jsonrpc": null,
  "id": null,
  "error": {
    "code": -32601,
    "message": "Method not found"
  }
}

// 交易个数为0或者地址不正确
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["5b073e9233944b5e729e46d618f0d8edf3d9c342",2],"id":1}' 127.0.0.1:1337 | jq

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x0"
}

// 没有部署合约
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getCode","params":["ea4f6bc98b456ef085da5c424db710489848cab9",35],"id":1}' 127.0.0.1:1337 | jq

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": null
}

// 高度未达到
curl -X POST -d '{"jsonrpc":"2.0","method":"cita_getBlockByNumber","params":[99999,true],"id":2}' 127.0.0.1:1337 | jq

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": null
}

智能合约指南

智能合约简介

智能合约是存储在区块链上的一段代码,它们可以被区块链上的交易所触发,被触发之后,这段代码可以从区块链上读取数据或者向区块链上写入数据。

从用户角度来讲,智能合约通常被认为是一个自动担保账户,当特定的条件满足时,程序就会释放和转移资金。

从技术角度来讲,智能合约被认为是网络服务器,只是这些服务器并不是使用IP地址架设在互联网上,而是架设在区块链上,从而可以在其上面运行特定的合约程序。

智能合约是编程在区块链上的汇编语言,我们通常使用更高级的语言例如Solidity等专用语言来编写合约程序,然后将它编译成区块链可以识别的字节码。合约代码被触发后将是自动执行的,要么成功执行,要么所有的状态变化都撤消,这就避免了合约部分执行的情况。

编写智能合约

智能合约是由一组代码和数据组成的程序,位于以太坊区块链上的一个特殊地址中。我们以Solidity编写智能合约为例,简单介绍一下如何编写一个规范的合约程序,合约 HelloWorld.sol 的代码如下:

pragma solidity ^0.4.0;

contract HelloWorld {
    uint balance;

    function update(uint amount) returns (address, uint) {
        balance += amount;
        return (msg.sender, balance);
    }
}

说明:上述示例中 uint balance 表示声明了一个状态变量,类型为 uint (即256位的无符号整数)。 function update(uint amount) returns (address, uint) 表示一个函数,其中参数为 amount ,返回值为一个元组 (address, unit) ,函数中返回值可以只给出类型,而无需具体的变量名。该函数的功能是更新 HelloWord 合约的 balance ,即调用合约成功执行后,将增加 balance 余额。

编写Solidity合约代码的详细说明可参考 Solidity官方开发指南

编译合约

首先我们需要安装Solidity编译器,安装编译器有多种不同的方法,我们推荐安装 solc 编译器稳定版,具体操作如下:

sudo add-apt-repository ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install solc

其他安装编译器的方法可以参考 Solidity编译安装介绍

然后使用 sloc 编译器命令将合约程序编译成字节码:

solc HelloWorld.sol --bin

智能合约编译完成后,将输出如下:

======= HelloWorld.sol:HelloWorld =======
Binary:
6060604052341561000f57600080fd5b5b60f08061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806382ab890a14603d575b600080fd5b3415604757600080fd5b605b600480803590602001909190505060a4565b604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390f35b60008082600080828254019250508190555033600054915091505b9150915600a165627a7a72305820b88062f2366288fcc87eb44079fcc26956e4c806546ad0e75b6927d14de63d950029

生成智能合约函数调用的Hash值的命令如下:

solc HelloWorld.sol --hashes

其输出如下:

======= HelloWorld.sol:HelloWorld =======
Function signatures:
82ab890a: update(uint256)

创建合约

在CITA中是使用 JSON-PRC 来发送交易的,发送创建合约交易之前,首先需要构造创建合约的Json配置文件 config_create.json ,其内容如下:

{
    "category": 1,
    "ipandport": [
        "127.0.0.1:1340"
    ],
    "txnum": 1,
    "threads": 1,
    "code": "6060604052341561000f57600080fd5b5b60f08061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806382ab890a14603d575b600080fd5b3415604757600080fd5b605b600480803590602001909190505060a4565b604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390f35b60008082600080828254019250508190555033600054915091505b9150915600a165627a7a72305820b88062f2366288fcc87eb44079fcc26956e4c806546ad0e75b6927d14de63d950029"
}

其中 category 只有两种取值,等于1时表示创建合约,等于2时表示调用合约; ipandport 表示接收该交易的节点IP地址和端口; txnum 表示发送的交易个数,该配置是为了以后兼容批量交易而预留的扩展接口; threads 表示执行该交易启动的线程个数;而 code 表示编译合约后生成的字节码,即上述编译合约输出的 Binary

然后使用以下命令创建智能合约:

trans_evm --config=config_create.json

执行成功后,便会经过CITA系统的处理,然后返回创建合约交易的执行结果。

调用合约

同理,为了使用 JSON-PRC 调用合约,需要构造调用合约的Json配置文件 config_call.json ,其内容如下:

{
    "category": 2,
    "ipandport": [
        "127.0.0.1:1340"
    ],
    "txnum": 1,
    "threads": 1,
    "code": "82ab890a0000000000000000000000000000000000000000000000000000000012345678"
}

其中需要注意的是 category 等于2表示调用合约,而 code 一般是由合约函数名Hash和它对应的参数值组成的,如果该函数没有参数,则仅仅使用函数名Hash即可。例如上述示例中合约函数名Hash即输出 Function signatures 下的 update(uint256) 对应的Hash值,占前面32比特,其中函数名为 update ,参数类型为 uint256,而参数部分是将参数按位数补齐并序列化,占后面256比特。如果函数包含多个参数,每个参数都占256比特,且参数按照函数的参数顺序排列。

然后使用以下命令调用智能合约:

trans_evm --config=config_call.json

执行成功后,便会经过CITA系统的处理,然后返回调用合约交易的执行结果。

节点白名单管理

CITA中节点分为共识节点和只读节点,交易由共识节点排序并打包成块,再广播 至其他节点,共识完成后即被确认为合法区块。只读节点不参与共识,只同步链 上所有的原始数据。

公有链没有节点准入机制,意味着任何节点都可以接入链并同步其全部的数据, 在满足一定的条件下都可以参加共识。而CITA对于共识节点和只读节点都进行了 准入管理。对于身份验证失败的节点,即使该节点能够在网络层与其他CITA节点 连通,这些CITA节点也会拒绝与之建立通讯会话,如此可避免信息泄漏。

目前CITA对于节点的准入管理采用白名单的方式。每个节点本地保存节点白名单 配置文件,其中记录着允许连接的p2p通信和数据同步的节点,包括其公钥、IP 地址、端口、对应的身份信息等。白名单由管理机构生成并分发,运维人员可对 其进行维护和管理,可选择连接若干其他节点同时可配置若干只读节点,使其承 担数据分析等工作。

共识节点管理

CITA作为一个面向企业级应用的区块链框架,需要保证监管方能够获得相关的权限对共识节点进行管理,包括增加、删除共识节点等操作。对于共识服务方面,需要对其提供实时读取共识节点列表的接口,而中心化管理的方式无法保证各个节点的共识节点列表的安全性及一致性。CITA采用合约的方式来实现共识节点的管理,通过区块链上的合约可以保证共识节点的安全性及一致性。

在CITA初始化创世块阶段,需要初始化一个管理员地址,其拥有管理员角色,将其写入到每个节点的创世块文件中,共识节点管理合约拥有的一个固定地址也写入其中。创世块内容在初始化以后不允许被修改。区块链正常启动之后,将合约写入到创世块中。链外的操作人员可以通过调用RPC接口来实现对共识节点的管理。

对于共识节点的管理,包括添加、删除及获得共识节点。

  • 添加操作分为发起和确认,节点先调用发起请求,申请成为共识节点,由管理员(拥有管理员角色的账号)确认才完成了添加操作;
  • 删除操作只可由管理员执行;
  • 共识服务可获得共识节点列表。

只读节点安装生成后申请成为共识节点,需要进行以下操作:

  • 将账号地址提交给管理员;
  • 节点发起由管理员确认完成,合约中记录其为共识节点;
  • 和其他节点共同修改本地节点白名单;
  • 等待区块数据同步完成后即可参与下一次的共识。

共识节点管理合约接口

  • 准备共识节点-newNode
    • 普通角色即可;
    • 成功后新节点准备成为共识节点,并将其记录在合约共识节点列表中,同时节点将处于new状态;
    • 传入参数address,为新增节点地址;
    • 返回类型为bool,可通过其判断操作成功与否。
  • 确认共识节点-approveNode
    • 需要管理员角色;
    • 新节点成功准备后,可调用此方法确认节点成为共识节点,同时节点将处于consensus状态;
    • 传入参数string,为新增共识节点地址;
    • 返回类型为bool,可通过其判断操作成功与否。
  • 删除共识节点-deleteNode
    • 需要管理员角色;
    • 成功后节点将从节点列表中删除,同时节点将处于close状态;
    • 传入参数为address,为节点地址;
    • 返回类型为bool,可通过其判断操作成功与否。
  • 获取共识节点列表-listNode
    • 只读方法,普通角色即可;
    • 态获取共识节点列表,即状态为consensus的节点;
    • 返回结果为string。节点列表中的多个节点会拼接成一个字符串。之后可通过解析会获得节点列表。
  • 获得节点状态-getStatus
    • 只读方法,普通角色即可;
    • 获取共识节点状态;
    • 传入参数为节点公钥;
    • 返回结果为uint8:
      • 0表示close状态
      • 1表示new状态
      • 2表示consensus状态

账号管理

CITA统一对账号进行基于角色的权限管理。系统内置了两种角色:

  • 管理员角色拥有全部权限。包括管理节点的权限、发送交易的权限、创建合约的权限以及所有普通角色的权限;
  • 普通角色拥有读取的权限以及创建角色的权限。包括验证节点是否是共识节点、获取共识节点列表、判断是否拥有角色、获取角色列表、判断是否拥有权限、获取权限列表等。

其中管理员角色和管理员绑定,即管理员账号有且只有管理员角色。管理员的添加只能由管理员操作,并且无法进行删除操作。角色可由用户创建,可相互授予及收回,而角色对应的权限只可由管理员进行设置。

CITA通过智能合约的方式来对账号进行管理。其中账号管理合约用来管理角色,权限管理合约用来管理角色的权限。

账号管理合约接口

  • 创建角色-createRole
    • 账号调用,需拥有用户角色;
    • 成功后创建一个新的角色,并拥有创建者指定权限,默认创建角色继承创建者角色权限,需调用权限管理合约接口;
    • 传入参数:
      • bytes32: 角色名称
      • uint8[]: 权限列表
    • 返回类型为bool,可通过其判断操作成功与否。
  • 添加管理员-addAdmin
    • 账号调用,需拥有管理员角色;
    • 成功后授予账号管理员角色,即添加了新的管理员;
    • 传入参数: address,为账号地址;
    • 返回类型为bool,可通过其判断操作成功与否。
  • 判断账号是否拥有角色-ownRole
    • 只读方法,账号调用,需拥有用户角色;
    • 判断账号是否拥有指定的角色;
    • 传入参数:
      • address: 账号地址;
      • bytes32: 角色名称。
    • 返回类型为bool,可通过其判断操作成功与否。
  • 查询账号拥有的角色-listRole
    • 账号调用,需拥有用户角色;
    • 读取账号所拥有的权限;
    • 传入参数: address,为账号地址;
    • 返回类型为bytes32[MAX_ROLE],为账号拥有的角色列表。其中MAX_ROLE为角色拥有的最大角色数。
  • 授予指定账号角色-grandRole
    • 用户账号调用,需拥有用户角色;
    • 对给定账号授予已存在的角色,成功后该账号拥有此角色;
    • 传入参数:
      • address: 账号地址;
      • bytes32: 角色名称。
    • 返回类型为bool,可通过其判断操作成功与否。
  • 收回指定账号角色-revokeRole
    • 用户账号调用。账号需为角色创建者或者拥有管理员角色;
    • 撤销账号拥有的角色,成功后该账号失去此角色;
    • 传入参数:
      • address: 账号地址;
      • bytes32: 角色名称。
    • 返回类型为bool,可通过其判断操作成功与否。

权限管理合约接口

  • 设置角色权限-setRolePermission
    • 账号调用,需拥有管理员角色;
    • 只能由管理员授予角色权限,成功后角色拥有指定权限;
    • 传入参数:
      • bytes32: 角色名称;
      • uint8[]: 权限列表。
    • 返回类型为bool,可通过其判断操作成功与否。
  • 判断角色是否拥有权限-ownPermission
    • 只读方法,账号调用,需拥有用户角色;
    • 判断角色是否拥有指定的权限;
    • 传入参数:
      • bytes32: 角色名称。
      • uint8: 权限名称;
    • 返回类型为bool,可通过其判断操作成功与否。
  • 查询角色拥有的权限-listPermission
    • 账号调用,需拥有用户角色;
    • 读取角色所拥有的权限;
    • 传入参数: bytes32,为角色名称;
    • 返回类型为bytes32[MAX_PERMISSION],为角色拥有的权限列表。其中MAX_PERMISSION为角色拥有的最大权限数。

配额管理

通过配额管理合约实现对区块(中的视图)以及用户配额消耗上限的管理:

  • 设置区块配额上限即为每个区块设置统一的配额上限;
  • 设置账号配额上限包括:
    • 默认的账号配额上限,全局设置,即若账号未指定配额上限,默认为此值;
    • 设置指定账号配额上限,可针对不同用户灵活分配对应的配额上限。

配额管理合约接口

  • 设置区块配额上限-setBQL(BQL为BlockQuotaLimit缩写,下同)
    • 需要管理员角色;
    • 设置每个块的配额上限;
    • 传入参数uint,为设置的配额值;
    • 返回类型为bool,可通过其判断成功与否。
  • 设置默认账号配额上限-setDefaultAQL(AQL为BlockQuotaLimit缩写,下同)
    • 需要管理员角色;
    • 设置默认的账号配额上限;
    • 传入参数为uint,为设置的配额值;
    • 返回类型为bool,可通过其判断成功与否。
  • 设置指定账号配额上限-setAQL
    • 需要管理员角色;
    • 设置指定账号的配额上限;
    • 传入参数:
      • address: 指定的账号的地址;
      • uint: 设置的配额值。
    • 返回类型为bool,可通过其判断成功与否。
  • 查询区块配额上限-getBQL
    • 普通角色即可;
    • 查询设置的区块配额上限;
    • 返回类型uint,为查询到的配额上限。
  • 查询默认账号配额上限-getDefaultAQL
    • 普通角色即可;
    • 查询设置的默认账号配额上限;
    • 返回类型uint,为查询到的配额上限。
  • 查询指定账号配额上限-getAQL
    • 普通角色即可;
    • 查询设置的指定账号配额上限;
    • 传入参数为address,为指定的账号地址;
    • 返回类型uint,为查询到的配额上限。

系统架构

微服务

联盟链在交易吞吐量低、交易实时性上很难满足行业应用的要求。另外,区块链 技术发展迅速,单体应用也很难满足演进要求。

为了解决这两个问题, CITA将单个节点按照功解构为交易共识、合约引擎、链 式存储、网络同步、服务网关等多个松耦合的微服务,一方面利用云计算基础设 施来按需提升性能,另一方面,各个组件可独立替换升级。

另外,采用消息总线进行通信,各个微服务可以利用异步消息简化处理,又可以 确保消息的安全、可靠送达。当与外部系统集成时,外部系统可以直接访问消息 总线,而无需CITA做适配。

map to buried treasure

CITA微服务架构

Flow Chart

Before Auth

Detail at CITA doc

                                tx into chain
-------------------------------------------------------------------------------------------------

+-----+  1.send   +---------+  2.forward   +-----------+  3.2.broadcast   +---------+
| APP |  ------>  | jsonRPC |  --------->  | consensus |  ------------->  | network |
+-----+           +---------+              +-----------+                  +---------+
   ^                |  ^                      |  |
   |  4.reply       |  |    3.1.reply         |  |
   -----------------   -----------------------   |
                                                 |  5.package    +-------+  6.process  +---------+
                                                 ------------->  | chain |  -------->  | rocksDB |
                                                                 +-------+             +---------+

After Auth

                                                            node0 (tx into chain)                                               node1
---------------------------------------------------------------------------------------------------------                   -----------

                                               // reply when reveive the tx from the remote
                    ---------------------------------------------------------------------------------
                    |                                                                               ^
                    |  ----------------------------------------------                               ^
                    |  |               3.1.reply                    ^                               ^
                    |  |                                            |                               |
+-----+  0.send   +---------+  1.forward   +------+  2.2.send  +-----------+    3.2.send      +---------+   broadcast:tx   +---------+
| APP |  ------>  | jsonRPC |  --------->  | auth |  ------->  | consensus |  ------------->  | network |  <------------>  | network | ...
+-----+           +---------+              +------+            +-----------+                  +---------+                  +---------+
   ^                | ^  ^                   | ^ ^                    ^                               ^
   |  4.reply       | ^  |     2.1.reply     | ^ | // tx from remote  |            4.broadcast tx    |
   ------------------ ^  --------------------- ^ ---------------------|--------------------------------
                      ^                        ^                      |
                      ^                        |             6.2.sync | 5.package      +-------+  6.1.process
                      |                        |                      -------------->  | chain |  ---->
                      |                        |                                       +-------+      |
                      |                        |       6.3.sync tx hash                  |  |         |
                      |                        -------------------------------------------  |         |
                      |                                                                     |     +---------+
                      -----------------------------------------------------------------------     | rocksDB |
                                                6.4.reply                                         +---------+

Timing Diagram

+-----+             +---------+             +------+                   +-----------+         +-------+              +---------+               +-----------+
| APP |             | jsonRPC |             | auth |                   | consensus |         | chain |              | network |               | other_node|
+-----+             +---------+             +------+                   +-----------+         +-------+              +---------+               +-----------+
   | -----new_tx-------> |                      |                            |                   |                        |                          |
   |                     | --------new_tx-----> |-------------+              |                   |                        |                          |
   |                     |                      |   tx in pool and check     |                   |                        |                          |
   |                     |                      |<------------+              |                   |                        |                          |
   |                     | <-------result------ |                            |                   |                        |                          |
   | <----result-------- |                      | ----------broadcast tx------------------------------------------------> |                          |
   |                     |                      |                            |                   |                        | ----flooding new tx----> |-----+
   |                     |                      |                            |                   |                        |                          |  reflect
   |                     |                      |                            |                   |                        | <---flooding new tx----- |<----+
   |                     |                      | <---------receive new_tx----------------------------------------------- |                          |
   |                     |                      | ----------package_tx-----> |---------+         |                        |                          |
   |                     |                      |                            | consensus txs     |                        |                          |
   |                     |                      |                            |---------+         |                        |                          |
   |                     |                      |                            | ----new_block---> |-----+                  |                          |
   |                     |                      |                            |                   | add block              |                          |
   |                     |                      |                            |                   |<----+                  |                          |
   |                     |                      | <----------tx hash---------------------------- |                        |                          |
   |                     |                      |                            |                   |                        |                          |
   |                     |                      |                            | <---new status--- | ---broadcast status--> |                          |
   |                     |                      |                            |                   |                        | --flooding new status--> |-----+
   | ----request-------> |                      |                            |                   |                        |                          |     |
   |                     | -----forward request------------------------------------------------> |                        |                          |  reflect
   |                     | <----response-------------------------------------------------------- |                        |                          |     |
   |<-forward response-- |                      |                            |                   |                        | <--flooding new status-- |<----+
   |                     |                      |                            |                   | <-----new status------ |                          |
   |                     |                      |                            |                   |-------+                |                          |
   |                     |                      |                            |                   |  if need sync          |                          |
   |                     |                      |                            |                   |<------+                |                          |
   |                     |                      |                            |                   | ---request block-----> |                          |
   |                     |                      |                            |                   |                        | --request block--------> |-----+
   |                     |                      |                            |                   |                        |                          |  reflect
   |                     |                      |                            |                   |                        | <-response block-------- |<----+
   |                     |                      |                            |                   | <--response block----- |                          |
   |                     |                      |                            |                   | -----+                 |                          |
   |                     |                      |                            |                   |  add block             |                          |
   |                     |                      |                            |                   | <----+                 |                          |
   -                     -                      -                            -                   -                        -                          -

ProtocolBuffer

ProtocolBuffer 是一种用于序列化结构数据的语言无关、平台无关的扩展机制, 经常被用于跨进程、跨机器的应用程序间的数据通信,特别是微服务间的数据通 信。它的编译器接受接口描述语言(IDL)作为输入,通过不同语言的插件,可以编译 成各自语言的本地代码。

CITA采用了ProtocolBuffer作为数据序列化的方法。一方面,序列化/反序列化 性能高,另一方面,不同微服务间可以通过接口描述语言很方便的集成到CITA的 微服务架构中。

Block

Block 结构

  • version: u32
  • header: BlockHeader 结构
  • body: BlockBody 结构

BlockHeader 结构

  • prevhash: 上一个块的 Keccak 256-bit 哈希值
  • timestamp: Unix 时间戳
  • proof: Proof 结构,出块人签名
  • commit: Commit 结构,Chain处理结果
  • height: uint64 块号

BlockBody 结构

  • transactions: 交易列表

Commit 结构

  • stateRoot: Keccak 256-bit,状态root
  • transactionsRoot: Keccak 256-bit,交易列表root
  • receiptsRoot: Keccak 256-bit,交易回执root

Transaction

Transaction 结构

  • to: 160-bit address;如果为空表示创建合约
  • content: bytes,交易 data
  • valid_until_block: 交易超时设置
  • signature: bytes,签名
  • crypto: 签名类型

共识

CITA的共识模块主要是保证多个节点对于交易的顺序和Block的内容达成一致。在众多的分布式算法中, 我们选择实现了非拜占庭容错的Raft算法和拜占庭容错的的Tendermint算法。

共识的架构

共识主要有MQ(消息队列)通讯模块、交易池、定时模块、WAL(write ahead log)、算法逻辑模块。

+-------------+       +-------------+       +-----+
| MQ通讯模块   |<----->| 算法逻辑模块 |<----> | WAL |
+-------------+       +-------------+       +-----+
       ^                ^    ^
       |                |    |
       |----------------+    |
       |                     |
  +--------+           +-----------+
  | 交易池  |           | 定时模块  |
  +--------+           +-----------+

MQ通讯模块: CITA的消息通过MQ进行周转,MQ通讯模块负责订阅、发布基于MQ的消息。

交易池: 交易池订阅和存储交易信息,并提供交易的打包、生成Block。还进行交易的持久化,实现快速确认的功能。

定时模块: 提供算法定时服务。使用者向定时模块发送定时请求,定时模块在时间到达后,发送确认信息。

WAL: WAL提供预写日志(write ahead log)的服务,持久化各个节点的投票。用来进行节点崩溃后恢复。

算法逻辑模块: 分布式算法逻辑的实现模块,接受共识其它模块发送过来的信息,根据自身的算法要求,进行算法逻辑相应的处理。 例如对于Tendermint就需要进行一系列的状态转换。

基本前提

  1. 每个共识节点知道其它共识节点的公钥
  2. 每个共识节点发送的投票信息,都必须有自己的签名
  3. 共识节点根据公钥和签名可以验证消息的真实性
  4. 共识节点数量需要满足算法要求的基本数量

基本步骤

虽然分布式算法多种多样,具体落实在CITA中,基本上需要进行如下的步骤:

  1. 共识模块从消息队列中订阅交易信息,放入交易池。如果应用有快速确认的需求,交易池可以对交易进行持久化。
  2. 共识算法根据配置和算法要求,选择一个出块的节点。该出块节点把块的哈希值(Block.Hash)作为共识的主要信息通过MQ通讯模块向其它的节点进行广播。
  3. 当出块节点进过一轮或者多轮投票,收到算法要求的法定多数的投票返回时,向Chain模块确认出块,否则进入重新选择出块节点的计算,由下一个出块节点继续出块。
  4. 接收Chain返回的状态信息,作为出块成功的标志。
  5. 出块成功后,从交易池删除已经达成共识的交易。

Raft算法

Raft作为非拜占庭容错的算法,主要是主节点出块。主节点有故障时,从节点会启动重新选主的流程,所以Raft的机制主要分为两部分:选主流程和 主节点同步信息流程。

选主流程:

  1. 每个节点发送提升请求给其他节点。
  2. 收到1/2+的投票的节点,升为主节点。
  3. 如果有冲突,采用随机退避的方式,再次投票。

主节点同步信息:

  1. 主节点打包交易,出块,把块发送给从节点。
  2. 从节点收到后发送确认信息返回。
  3. 主节点收到1/2+确认后,发送确认信息给从节点。
  4. 主节点持久化和发送Block到Chain进行计算。

Tendermint算法

Tendermint是一种专为区块链设计的高性能共识算法,基于半同步网络假设,Tendermint在保证活性和安全性(Liveness & Safety)的前提下能够容忍1/3的拜占庭节点。 CITA共识节点通过点对点共识消息交换协议对每一个区块交换投票信息,迅速形成多数共识。投票结果最后会被记录在区块里。CITA支持独有的低延迟技术,能够实现 毫秒级交易确认延迟。

Tendermint状态转换

Tendermint是一种状态机副本复制算法(State Machine Replication),在实现上包括以下几个状态: NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->…

                         +-------------------------------------+
                         v                                     |(Wait til `CommmitTime+timeoutCommit`)
                   +-----------+                         +-----+-----+
      +----------> |  Propose  +--------------+          | NewHeight |
      |            +-----------+              |          +-----------+
      |                                       |                ^
      |(Else, after timeoutPrecommit)         v                |
+-----+-----+                           +-----------+          |
| Precommit |  <------------------------+  Prevote  |          |
+-----+-----+                           +-----------+          |
      |(When +2/3 Precommits for block found)                  |
      v                                                        |
+--------------------------------------------------------------------+
|  Commit                                                            |
|                                                                    |
|  * Set CommitTime = now;                                           |
|  * Wait for block, then stage/save/commit block;                   |
+--------------------------------------------------------------------+

Tendermint是随着块的高度增长,多种状态的依次循环的过程。在决定某个高度的块的过程中,可能需要一轮或者多轮的投票。 下面介绍Tendermint在块高度H和第R轮上,进行块的共识的主要流程:

  1. proposal阶段:proposal节点打包交易池中的交易,WAL记录后,通过MQ通讯模块,发送proposal消息给共识的其它节点,然后进入prevote阶段。而非proposal节点在进行一段时间的超时后,进入prevote投票阶段。
  2. prevote阶段:每个共识节点根据收到的proposal信息,进行prevote投票。校验成功则prevote block.hash,校验失败或者没有收到proposal信息则prevote空票。
  3. prevote等待阶段:等待节点收到2/3节点以上的prevote投票,在必要的超时后,进入precommit阶段。
  4. precommit阶段:节点根据prevote阶段收到的投票进行判断,如果收到相同block.hash的prevote投票,超过2/3,则precommit block.hash,否则precommit空值
  5. precommit等待阶段:等待收到的precommit投票数超过节点数的2/3。如果收到precommit相同block.hash投票超过2/3时,进入commit阶段。否则进入新一轮的proposal的阶段。
  6. commit阶段:共识模块把共识完成的block发送给chain模块后,等待chain模块的计算完成后发送的状态信息,然后进入下一个高度 NewHeight。

Tendermint交易池操作流程

  1. 交易池启动时,尝试从KV数据库恢复数据
  2. 交易池订阅MQ的交易信息
  3. 交易池收到交易后,持久化到KV数据库
  4. 交易池收到打包请求,检查交易的有效性,输出有效交易列表
  5. 交易池根据出块的交易列表,删除已经上链的交易

Tendermint故障重启流程

  1. 从WAL模块中,恢复某个块高度的投票信息
  2. 根据恢复后的状态信息,重复投票信息
  3. 进程根据当前状态,继续运行

CITA对Tendermint的优化

Tendermint算法实践上比较优秀,CITA结合联盟链和本身的特定,对其进行了优化。 比如CITA优化了commit阶段的Block广播,使用Chain模块计算交易后的状态消息作为出块成功的标志。 避免了一次的全网的广播,减少对下个高度Block同步的影响。

交易处理

交易验证

CITA中交易通过消息总线进行转发,用户通过RPC模块和系统进行交互。RPC请求分为两种,一种是对链上状态进行查询的请求,一种是需要打包到区块中的交易。

首先在RPC模块,对用户请求进行验证:

  • 是否符合服务规范(目前支持JSON-RPC 2.0);
  • 服务API方法是否合法;
  • 参数个数和格式是否合法。

如果验证通过,则根据请求类型发往不同模块。对于查询请求,发送到链模块,链模块解析请求,并执行然后返回结果。对于交易,则发送到共识模块,共识模块最终形成共识后,发送到链模块,链模块的验证如下:

  • 验证区块格式是否正确
    • PreHash是否正确;
    • 区块签名是否正确;
    • 签名个数是否满足。
  • 执行交易时,需要验证
    • 交易签名是否合法;
    • 是否拥有权限;
    • 发送人是否有足够的配额;
    • 交易是否为重复交易。

交易验证成功则正常执行,验证失败则将错误信息,保持在交易回执中。CITA中交易处理具有原子性,当交易在执行过程中发生错误,整个交易状态会回滚。

_images/tx_validator.png

交易验证

交易执行

对于查询请求,用户通过RPC模块进行请求,首先对交易格式进行验证,验证成功后发送到链/网络模块,验证失败则直接返回失败结果;链/网络模块收到请求之后,进行查询,再将结果返回给RPC模块;最后,RPC将结果返回给用户。

对于交易,RPC模块收到交易请求,首先对交易的格式进行验证,验证成功后发送给共识模块;共识模块首先将交易保存在交易池中,待共识出块时从交易池中获取交易,共识完成后将包含交易的区块发送给链模块。 对于从其他节点收到的交易,通过Network收到后,发送给共识模块,共识模块做相同的处理。

链模块收到共识后的区块之后,首先对区块合法性做必要的验证,验证通过后执行交易并将执行结果存储到本地;待区块内所有的交易执行完成后,将区块、交易、修改状态、交易回执等持久化到数据库中;最后链模块更新状态,并通知共识模块。

_images/tx_proc.png

交易入链

异步交易处理

区块链节点的最主要职责包括点对点网络交互、共识、交易处理以及数据存储四个方面。节点通过共识算法,在系统中形成对交易排序的全局共识,再按照共识 后的顺序对交易进行逐个处理。只要处理过程能保证确定性,所有节点最后都能达到一致的状态,产生相同的本地数据。

在当前的区块链设计中,共识与交易处理耦合程度较高,共识的性能受到交易处理能力的影响。 CITA将共识与交易处理解耦为独立的微服务,共识服务只负责交易排序,并不关心交易内容,交易处理服务只负责对排好顺序的交易进行处理。 此时共识过程可以先于交易处理完成,交易处理服务可以异步执行。异步交易处理技术不仅使CITA具有更好的共识性能,还带来了更有弹性的交易处理能力, 交易负荷可以被更均匀的分摊到一段时间内。

由于交易异步处理,在共识前只能对交易进行有限的检查,例如签名验证。无效的交易有可能通过共识进入交易处理服务,产生一定程度的垃圾数据。 在有必要的情况下,可以通过CITA的交易控制或者垃圾清理技术解决该问题。

视图

账号

视图状态是执行器执行过程中读写的对象,常见的视图状态模型有UTXO及账户两种。在UTXO模型中,由UTXO构成账本视图,每个交易在销毁旧有UTXO的同时创造新的UTXO;在账户模型中,由账户构成世界状态视图,交易在处理过程中可以读写多个账户。 账户模型相对更加简单,实现通用任务更有效率。在企业级应用中往往存在身份验证与授权的需要,这些服务所依赖的数据可以自然的与账户模型关联。CITA默认支持账户模型。用户可以自定义包括UTXO在内的其他状态模型。

在CITA中存在两种账号:外部账号和合约账号。外部账号通常情况下代表用户的身份,用户可以通过外部账号来发送交易。与公链不同,CITA具有用户准入机制。首先用户自行生成私钥和公钥,私钥由用户妥善保存; 然后将公钥通过链外的方式提交给CITA系统中的KYC系统;通过申请之后,系统管理员将用户公钥通过操作账户管理合约,发送交易将用户加入CITA网络中。对于未准入的外部账户,无法向CITA发送交易。同时,CITA内置了基于角色的权限管理,系统管理员(角色)可以根据实际情况灵活配置账户的权限。

为了阻止重播攻击,每笔交易必须有nonce,这就使得账户需要跟踪nonce的使用情况。CITA中用户可以根据实际业务需求,自定义nonce防重放机制。现阶段CITA兼容了Ethereum的自增nonce的防重放机制。

总体来讲,外部账户具有以下特性:

  • 外部账号需要准入机制;
  • 通过私钥控制,可以发送交易;
  • 同一账户可以支持多种签名;
  • 支持用户自定义的交易放重放规则。

合约账户与外部账户最大的区别在于,合约账户通过交易进行创建,合约内部维护一定的代码,合约的执行和调用通过外部账户发送交易来进行。当前CITA支持EVM虚拟机,用户可以通过直接发送合约代码的方式来创建合约,也可以通过合约调用的方式来创建合约。

总体来讲,合约账户具有以下特性:

  • 合约账号通过交易创建,并支持合约创建合约;
  • 合约通过交易调用,并支持合约间互相调用;
  • 合约具有图灵完备性,但是交易对计算资源的配额受系统合约控制;
  • 合约维持自身的特定储存状态;
  • CITA内置系统合约模块,在创始块中生成,方便用户对系统进行管理。

存储

区块链本质上去中心化的分布式复制状态机,每个节点通过持久化的方式来保存自身的状态。CITA使用KV持久化数据存储,支持RocksDB、LevelDB。节点将Block结构,交易以及合约状态等持久化保存到KV数据库中。

为了更高效的检索和更新数据,区块链一般会在内存中维护某种数据结构的视图模型。对于传统的区块链,如Bitcoin采用了Merkle Tree来保存交易;Ethereum采用了Merkle Patricia Tree,一种改进的Merkle Tree来保存状态和交易。 CITA采用了一种更高效的AVL来保存账户状态,并且采用了Simple Merkle Tree来保存交易列表和交易回执。下面我们将分别介绍这几种模型。

Merkle Tree

在Bitcoin中的每个区块都包含了产生于该区块的所有交易,且以Merkle树表示。Merkle树是一种哈希二叉树,它是一种用作快速归纳和校验大规模数据完整性的数据结构。这种二叉树包含加密哈希值。

在比特币网络中,Merkle树被用来归纳一个区块中的所有交易,同时生成整个交易集合的数字指纹,且提供了一种校验区块是否存在某交易的高效途径。生成一棵完整的Merkle树需要递归地对哈希节点对进行哈希,并将新生成的哈希节点插入到Merkle树中,直到只剩一个哈希节点,该节点就是Merkle树的根。

当N个数据元素经过加密后插入Merkle树时,你至多计算2*log2(N)次就能检查出任意某数据元素是否在该树中,这使得该数据结构非常高效。同时Merkle树可以很好的支持轻节点。

_images/merkle-tree.png

Merkle Patricia Trie

在Ethereum中,使用Trie来构建Merkle tree,即Merkle Patricia Trie。它是Ethereum中主要的数据结构,用来存储所有账号的状态以及交易和交易回执。MPT支持高效的检索及动态的插入、删除、修改,Ethereum将其命名为Merkle Patricia Tree(MPT),其示意图如下:

_images/merkle-patricia-trie.png

更多关于MPT的介绍可以参考Ethereum Patricia-Tree

Merkle AVL Tree

对于Ethereum中的MPT,由于Sha3运算的开销并不低。随着账户地址的增多,以及合约存储数据量的增多,Sha3计算的数据量也会增多。对于常见金融领域来讲,千万级数据将会导致MPT性能下降,Sha3的计算将有可能成为瓶颈。 为了提高效率,我们希望在每次更新树时能够计算Sha3的数据量最少。对于树形结构,更新一个节点所需要重新计算的Sha3数据量约为O(mlogmn),其中 m 为分支数,故当 m=2 时,Sha3的计算量最小。 又因为AVL Tree的平衡性更好(尽管这意味着更多的旋转操作,幸运的是旋转操作并不增加Sha3计算),同时又能很好地保持MPT动态插入、删除、修改的特点。因此选用AVL Tree来构建Merkle Tree似乎是一个不错的选择,我们简称之为MAT。

更多关于AVL的介绍可以参考Wiki AVL_tree

Simple Merkle Tree

在Ethreum中,交易和交易回执同样采用MPT树来进行保存。而CITA中,区块中的交易在共识完成后就已经确认了。所以在Chain处理交易时,交易的顺序和交易结果的顺序都是确定不变的。 而MPT树优点是便于保存历史快照可维持可变性,对于静态数据可以采用Merkle树,而不必采用MPT和AVL这样的数据结构。而比特币的Merkle树在处理奇数节点时,需要拷贝节点,额外做一次Sha3计算。 CITA采用了简单的Merkle树来保存,对于奇数个节点情况,计算Sha3的次数会减少。

              *
             / \
           /     \
         /         \
        /            \
      *               *
     / \             / \
    /   \           /   \
   /     \         /     \
  *       *       *       h6
 / \     / \     / \
h0  h1  h2  h3  h4  h5

Bootstrap

通过本文所述方法和项目中的脚本,我们可以快速的搭建好自己的 CITA 私链进行开发测试。

部署CITA

为了简化 CITA 的多机部署,帮助用户快速搭建 CITA 运行环境,我们推荐使用 docker 部署 CITA

安装docker

安装 dockerdocker-compose

sudo apt-get install docker docker-compose  docker.io

国内访问 hub.docker.com 可采用镜像加速:

cat > daemon.json  << EOF
{
   "registry-mirror": [ "https://registry.docker-cn.com" ]
}
EOF
sudo mv daemon.json /etc/docker

获取CITA镜像

获取 CITA 镜像有两种方式:

  1. 一种是直接从 hub.docker.com 拉取
docker pull cryptape/play
  1. 另一种方式是通过发布件构建来获取 CITA 镜像

具体方式有两种,分别是二进制发布件构建和从源码编译生成发布件。

  • 从二进制发布件构建
wget https://github.com/cryptape/cita/releases/download/v0.10/cita.tar.bz2
tar xf cita-v0.1.0.tar.bz2
cd cita
scripts/build_image_from_binary.sh

完成后可以通过 docker images 找到 cryptape/playdocker 镜像。

  • 从源码编译生成发布件
git clone http://github.com/cryptape/cita
cd cita
scripts/build_image_from_source.sh

使用 docker-compose 部署 CITA

  • 准备
mkdir cita
cd cita
wget https://raw.githubusercontent.com/cryptape/cita/develop/scripts/docker-compose.yaml
  • 生成配置数据
sudo docker-compose run admin
  • 启动4个节点
sudo docker-compose up node0 node1 node2 node3
  • 关闭节点
# stop single node
docker-compose stop node0
# start single node
docker-compose start node0
# stop all nodes
docker-compose down

部署智能合约

CITA 完全兼容以太坊的智能合约,solidity 是智能合约最为推荐的语言,因此我们也采用 solidity 语言来编写和测试智能合约。

编译 solidity 文件,返回文件字节码

要想编译 solidity 文件,你需要先安装编译器, solidity 智能合约可以通过多种方式进行编译

  1. 通过在线 solidity 实时编译器来编译。 访问地址
  2. 安装 solc 编译器编译

本文采用第二种方式,solc 编译器是一个来自 C++ 客户端实现的组件,安装方法请参考 这里

安装完成后,在 Terminal 中执行 solc --version ,如果返回值为:

solc, the solidity compiler commandline interface
Version: 0.4.18+commit.9cf6e910.Linux.g++

表示安装成功,接下来就可以使用 solc 命令编译 solidity 文件了。

CITA 工程中包含了智能合约示例 solidity 文件,存放目录地址为 (DIR)/cita/cita/scripts/contracts/tests,其中 $(DIR) 代表工程的目录地址,进入该目录,就可以看到 test_example.sol 文件。

test_example.sol 文件是一个很简单的合约文件,只提供了简单的 getset 方法,我们可以先调用 set 方法存储一个任意数值,然后再调用 get 方法验证存储是否生效,以此来检验合约部署和运行是否正常。

Terminal 执行:

solc test_example.sol --bin

如果文件没有错误,返回结果中将会包括 test_example.sol 的字节码,这个数值就是 CITA 链上该智能合约的唯一标示值。

部署合约,发送者需要构建合约权限

得到 solidity 文件字节码后,就可以将其部署到 CITA 链上了,部署的方法已经用 python 脚本封装,只需要传入私钥和字节码即可。

目前支持的 python 版本是2.7,python 脚本存放的位置为 (DIR)/cita/cita/scripts/contracts/txtool/txtool ,其中 $(DIR) 代表工程的目录地址,使用前你还需要提前安装好 python 脚本的依赖,具体安装方法可以参考 (DIR)/cita/cita/scripts/contracts/txtool 目录下的 README.md 文件。

Terminal 执行以下命令:

python make_tx.py --privkey "352416e1c910e413768c51390dfd791b414212b7b4fe6b1a18f58007fa894214" --code     "606060405234156100105760006000fd5b610015565b60e0806100236000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604b5780636d4ce63c14606c576045565b60006000fd5b341560565760006000fd5b606a60048080359060200190919050506093565b005b341560775760006000fd5b607d60a3565b6040518082815260200191505060405180910390f35b8060006000508190909055505b50565b6000600060005054905060b1565b905600a165627a7a72305820942223976c6dd48a3aa1d4749f45ad270915cfacd9c0bf3583c018d4c86f9da20029"

参数解释: code 为第一步中获得的字节码, privkey 可随意获取

通过 python 脚本发送交易命令

Terminal 中执行: python send_tx.py

结果如下:

{
    "jsonrpc":"2.0",
    "id":1,
    "result":
    {
        "hash":"0xa02fa9a94de11d288449ccbe8c5de5916116433b167eaec37455e402e1ab53d3",
        "status":"Ok"
    }
}

statusOK ,表示合约已经发送到 CITA 链上。

获得来自 CITA 区块链网络的回执

Terminal 中执行:python get_receipt.py

结果如下:

{
    "contractAddress": "0x73552bc4e960a1d53013b40074569ea05b950b4d",          // 合约地址
    "cumulativeGasUsed": "0xafc8",
    "logs": [],
    "blockHash": "0x14a311d9f026ab592e6d156f2ac6244b153816eeec18717802ee9e675f0bfbbd",
    "transactionHash": "0x61854d356645ab5aacd24616e59d76ac639c5a5c2ec79292f8e8fb409b42177b",
    "root": null,
    "errorMessage": null,
    "blockNumber": "0x6",                                                     // 区块高度
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "transactionIndex": "0x0",
    "gasUsed": "0xafc8"
}

这里需要重点关注 contractAddressblockNumber ,下文调用合约方法会用到。

获得合约文件方法的 hash 值

合约的调用是通过发送交易命令完成,调用具体的方法则是通过方法 hash 值完成

Terminal 中执行: solc test_example.sol --hash

结果如下:

======= example.sol:SimpleStorage =======
Function signatures:
6d4ce63c: get()                         // get方法hash值
60fe47b1: set(uint256)                  // set方法hash值

这里的 getset 方法 hash 值是 CITA 链上的唯一标示值,下文调用合约方法会用到。

调用合约文件中的 set 方法

假定我们调用 set 方法,参数为1,也就是说将数值1存储到区块链内存中

Terminal 中执行:

python make_tx.py --privkey "352416e1c910e413768c51390dfd791b414212b7b4fe6b1a18f58007fa894214" --to "73552bc4e960a1d53013b40074569ea05b950b4d" --code "60fe47b10000000000000000000000000000000000000000000000000000000000000001"

privkey 是你的私钥, to 参数是合约的目标地址,code 参数是 set 方法和参数 hash 值的拼接,set 方法的 hash 值为60fe47b1,将参数1转换为uint256,转换成16进制就是64位。

通过 python 脚本发送交易命令

Terminal 中执行: python send_tx.py

{
    "jsonrpc":"2.0",
    "id":1,
    "result":
    {
        "hash":"0xf29935d0221cd8ef2cb6a265e0a963ca172aca4f6e43728d2ccae3127631d590",
        "status":"Ok"
    }
}

获得来自 CITA 区块链网络的回执

Terminal 中执行: python get_receipt.py

结果如下:

{
    "contractAddress": null,
    "cumulativeGasUsed": "0x4f2d",
    "logs": [],
    "blockHash": "0x2a10ae38be9e1816487dbfb34bce7f440d60035e8978146caef5d14608bb222c",
    "transactionHash": "0xf29935d0221cd8ef2cb6a265e0a963ca172aca4f6e43728d2ccae3127631d590",
    "root": null,
    "errorMessage": null,
    "blockNumber": "0x15",                    // 区块的高度
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "transactionIndex": "0x0",
    "gasUsed": "0x4f2d"
}

发送 eth_call Post 请求,验证合约执行效果

调用合约中的 get 方法,验证之前 set 方法的执行效果

curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call", "params":[{"to":"0x73552bc4e960a1d53013b40074569ea05b950b4d", "data":"0x6d4ce63c"}, "latest"],"id":2}' 127.0.0.1:1337

其中 to 参数为合约目标地址, dataget 方法的 hash

结果如下:

{
    "jsonrpc":"2.0",
    "id":2,
    "result":"0x0000000000000000000000000000000000000000000000000000000000000001"
}

如果返回值中 result 值为1,表明合约调用生效

常见问题

词汇表

区块链

  • 区块

一个区块是一个数据包,其中包含零个或多个交易,父块的哈希值,以及其他数据。除了初始的“创世区块”以外每个区块 都包含它父块的哈希值,区块的全部集合被称为区块链,并且包含了一个网络里的全部交易历史。

  • 账户

账户是在总账中的记录,由它的地址来索引,总账包含有关该账户的状态的完整的数据。在一个货币系统里,这包含了货 币余额。

  • 挖矿

通过暴力尝试来找到一个字符串,使得它加上一组交易信息后的哈希值符合特定规则(例如前缀包括若干个0),找到的 人可以宣称新区块被发现,并获得系统奖励。

  • 矿工

参与挖矿的人或组织。

  • 矿机

专门为比特币挖矿而设计得设备,包括基于软件、GPU、FPGA和专用芯片等多种实现。

  • 矿池

采用团队协作方式来集中算力进行挖矿,对产出的虚拟货币进行分配。

  • P2P

点到点的通信网络,网络中所有节点地位均等,不存在中心化的控制机制。

  • 去中心化

不存在第三方中心机构,全体网民可以共同参与、权级平等。

  • 分布式账本

可以在多个站点、不同地理位置或者多个机构组成的网络里进行分享的资产数据库。

  • EVM

以太坊的虚拟机,智能合约运行的环境。

  • FLP Impossibility

FLP不可能性。分布式领域的一个著名理论,它的结论是:在网络可靠,存在节点失效(即便只有一个)的最小化异步模型 系统中,不存在一个可以解决一致性问题的确定性算法。

  • CAP

分布式计算系统不可能同时确保一致性(Consistency)、可用性(Availability)和分区容忍性(Partition), 设计中往往需要弱化对某个特性的保证。一致性指等同于所有节点访问同一份最新的数据副本;可用性指每次请求都能 获取到非错的响应,但是不保证获取的数据为最新数据;分区容忍性指以实际效果而言,分区相当于对通信的时限要求。 系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

  • 女巫攻击

少数节点通过伪造或盗用身份伪装成大量节点,进而对分布式系统进行破坏。

  • UTXO

Unspent Transaction Outputs,未花费的输出。每一笔交易的输入都会引用先前有效交易的未花费输出,再创建新的未花费输出。账户余额就是发往该账户地址的一笔笔未花费输出的累加。

密码学

密码学是数学和计算机科学的分支,研究如何在有敌人存在的环境中安全通信。

  • 哈希

即散列算法,将任意长度的二进制值映射为较短的固定长度的二进制值的算法。

  • 非对称加密

一种特殊的加密,具有在同一时间生成两个密钥的处理(通常称为私钥和公钥),使得利用一个密钥对文档进行加密后, 可以用另一个密钥进行解密。一般地,公钥是公开的,私钥自己保留。

  • 数字签名

用户用私钥为文档产生一段叫做签名的短字符串数据,任何拥有相应公钥的人都可以验证该签名。

  • CA

Certificate Authority,负责证书的创建、颁发,在PKI体系中最为核心的角色。

  • PKI

Public Key Infrastructure,基于公钥体系的安全基础设施,是一组由硬件、软件、参与者、管理政策与流程组成 的基础架构,其目的在于创造、管理、分配、使用、存储以及撤销数字证书。

  • Zero-knowledge proofs

零知识证明,一种密码学技术,允许两方(证明者和验证者)来证明某个提议是真实的,而且无需泄露除了它是真实的之外的任何信息。

  • Merkle Tree

中文名叫默克尔树,又叫哈希树,是一种二叉树,由一个根节点、一组中间节点和一组叶节点组成。 最下面的叶节点包含存储数据或其哈希值,每个中间节点是它的两个孩子节点内容的哈希值,根节点 也是由它的两个子节点内容的哈希值组成。

CITA

即分布式企业间流程自动化系统(Cryptape Inter-enterprise Trust Automation),本质是一个支持智能合约 的区块链框架。CITA将区块链节点的必要功能解耦,通过消息总线交换信息相互协作,各组件是可拔插的,可以配置和定 制相应的服务,能够满足企业级用户的全部需求。

  • 智能合约

一段代码,被部署在分享的、复制的账本上,它可以维持自己的状态,控制自己的资产和对接收到的外界信息或者资产进行回应。

  • 微服务

即CITA将区块链节点的各功能解耦之后的组件,如共识,交易处理,点对点网络协议等这些都是微服务。微服务架构能够 有效提高节点处理能力,并且使CITA非常容易水平扩展。

  • 异步交易处理

即CITA将共识和交易处理解耦为独立的微服务,共识只负责各节点对交易共识并对交易进行排序,而交易内容的处理交给 交易处理服务,从而实现共识过程和交易处理异步执行。异步处理技术不仅使CITA具有更好的共识性能,还带来了更具弹 性的交易处理能力。

  • 执行器

执行器以排好序的交易为输入,在处理过程中相应的更新对应的视图。不同的执行器对于不同的交易输入可能产生不同的视图。

  • 配额

即支持智能合约的区块链用来限制资源使用的机制。由于交易会被复制到多个节点执行和存储,过重的负荷会使有限的资源 消耗殆尽从而导致节点失去响应。

  • 视图

即将执行器的处理结构最终展现给具体应用的组件,用户在配置CITA区块链网络时可设定多个视图,视图相互独立。每个 视图都可以设定相应的交易执行器和状态存储模型,并将交易执行器注册到交易路由,不同视图处理的交易子集可以有交 集,也可以没有交集。

  • 隐私交易

即无关节点不能看见交易中的数据,但是执行和验证智能合约要求所有共识节点都能看到交易中的数据,二者存在天然矛盾。CITA采用了交易局部执行技术,实现了一种隐私方案。

共识

  • PoW

Proof of Work,工作量证明。

  • PoS

Proof of Stake,权益证明。

  • PBFT

Practical Byzantine Fault Tolerance,实用拜占庭容错算法。

  • Tendermint算法

PBFT算法的变种,针对区块链场景进行了特殊优化。

  • Raft算法

一种非拜占庭容错的共识算法,比Paxos更容易理解。

  • 拜占庭将军问题

LESLIE LAMPORT为了更形象的介绍分布式系统共识问题,而杜撰的一个故事。 拜占庭帝国军队的将军们必须全体一致的决定是否攻击某一支敌军。 问题是这些将军在地理上是分隔开来的,并且将军中存在叛徒。叛徒可以任意行动以达到以下目标:欺骗某些将军采取进攻行动; 促成一个不是所有将军都同意的决定,如当将军们不希望进攻时促成进攻行动;或者迷惑某些将军,使他们无法做出决定。如果 叛徒达到了这些目的之一,则任何攻击行动的结果都是注定要失败的,只有完全达成一致的努力才能获得胜利。

版本发布说明

贡献代码

CITA是一个开源项目,任何人都可以参与CITA并贡献代码。

以下是参与CITA并贡献的具体流程:

1. 复制CITA到自己的仓库中 (Fork repository)

首先,进入CITA的Github主页,点击右上角的Fork按钮将CITA复制到自己的Github仓库。

Fork完成后,进入自己的Github主页查看是否已经存在CITA,若存在,说明Fork成功。

2. 创建新的分支 (Create new branch)

从自己的Github主页中进入CITA仓库,点击右侧的Clone or download按钮,复制HTTPS链接。

然后在本地终端使用 git clone <上一步复制的链接> 命令将该项目下载到本地。

下载成功后,使用 git checkout -b <new branch name> 命令创建新分支并切换到该分支。

分支名应尽量简洁并能体现出该分支完成的工作。

3. 提交修改 (Commit changes)

这时就可以在这条分支下贡献自己的代码或对CITA进行修改完善,然后通过 git commit -m "commit message" 将修改提交到本地git仓库中(在提交之前,首先通过git add命令添加修改文件到暂存区)。

新增的代码编码风格参照项目主分支风格,尽量保持于主分支编码风格相同。

通常来说,每次提交应该是原子性的并且修改要容易阅读,尽量避免将文本格式的修改和代码位置的转移与实际的修改代码混淆。

提交信息(Commit message)应该尽量简短精确,并且应该加上修改或新增的文件所在包名作为前缀。

4. 将修改上传到你的仓库 (Push changes to your fork)

在本地提交修改并确认无误之后,便可以使用 git push origin <branch name> 命令上传这些修改到自己的仓库。

5. 创建并提交PR (Create pull request)

在同步完成后,即可通过Create pull request按钮将该分支发送给该项目的维护者等待被合并。如果你的Pull request修复了在Issues中描述的问题,可以使用 special magic keyword 引用该Issue为参考

一个Pull request应该只关注于一件事情,如添加功能,修补bug,重构代码。尽量避免包含这些不同的事情的混杂的Pull request,因为这些Pull request一般内容较多且非常复杂,维护者阅读或者贡献者回过头来查看的时候会造成许多麻烦。

当你的PR被维护者接受并准备合并时,你可能会被要求rebase你的提交,rebase的流程如下所示:

1
2
3
4
5
6
7
8
git checkout <your branch name>
git rebase -i HEAD~n
# n is normally the number of commits in the pull
# set commits from 'pick' to 'squash', save and quit
# on the next screen, edit/refine commit messages
# save and quit
git push -f
# (force push to GitHub)

在添加一个新功能的时候,必须考虑到对该功能的长期维护,在提交一个新功能的时候,你必须考虑清楚自己是否会在将来长期维护它(包括修复bug),那些在未来没有得到有效维护的功能,可能会被项目的维护者移除

重构的PR不应该改变原先这部分代码的行为(就算是这部分代码中存在bug),如果要修复bug,应该在另外一个Pull request中提出。

总的来说,所有的Pull request应该:

  • 有明确的方向,修复一个显而易见的bug或者优化项目的一个特性(如模块化重构)
  • 再次查看时应该清晰易读
  • 在合适的地方有单元测试
  • 遵循主分支编码风格
  • 不要破坏已有的测试套件

CITA 是采用Rust语言编写而成,若要在本机上编译并调试源码,请参考 安装依赖

以上步骤,如有对git命令不熟悉的,请参考 git使用手册