课程笔记,主要是从 PPT 上摘录的,用的课本是刘百祥的《区块链技术基础与实践》(非常不推荐),主要介绍了区块链的基础概念、Go 语言基本语法、HyperLedger Fabric 和 Ethereum,但都是蜻蜓点水。

资源汇总

概念列表

  • 区块链特点
    • 去中心化(架构层 政治层 逻辑层)
    • 不可篡改(哈希函数 链式数据结构)
    • 匿名性 pseudonymous 而非 anonymous
    • 公开账本
  • 编码
    • Base64 Base58 Base58Check
  • 加密
    • 对称加密 DES 3DES AES(密钥长度可谓 128 192 256) RC5 RC6
    • 非对称 RSA DSA ECDH ECDSA
    • 哈希函数 SHA MD5
  • 公钥密码学
    • 数据完整性 Hash 函数 MD5 SHA
    • 数据真实性(不可抵赖)
      • 消息认证码 MAC 可理解为带密钥的 Hash,但需要解决共享密钥的问题
      • 数字签名:私钥加密
    • 公钥的合法性:认证机构 CA,公钥证书 PKC
  • 共识机制
    • 工作量证明 PoW
    • 权益证明 PoS
    • DPOS(EOS 首先提出)
  • 智能合约
    • 2016 年 TheDAO攻击事件
  • Hyperledger Fabric
    • 节点架构
      • 客户端
      • Peer 节点:都是记账节点(Committer);有时承担 背书(Endorser)节点的角色;还可能是 主节点(Leader Peer)承担和排序服务节点通信;
      • Orderer 节点:接收包含背书签名的交易,排序,广播,
      • CA 节点
    • 账本(子系统)
      • 世界状态
      • 交易记录
    • 通道:一个链由1个通道 + 1个账本 + N个成员(peer)组成
    • 成员服务提供者 MSP
  • Go 语言
  • 以太坊 Ethereum
    • EVM
    • 两类账户
      • 外部账户EOA
      • 合约账户CA
  • 地址类型
    • 存储一个 20 字节的值,表示一个以太坊地址
    • 地址类型有成员变量 balance, transfer, send
  • 特殊/全局变量与函数
    • 区块属性
    • 交易属性
      • msg.data msg.sender msg.value(随消息发送的 wei 的数量)
    • 和约相关
      • this 当前和约
  • ERC-20 接口规范
    • 常量 name symbol decimals
    • 函数 totalSupply transfer transferFrom approve allowance
    • 事件 Transfer Approval

概论

  • 分布式账本(Distributed ledger) 以分布式方式存储在多个网络节点的账本(数据库),区块链(Blockchain)是分布式账本的一种,
  • 区块链(Blockchain)互联网底层多种技术的集合体,包括P2P通信协议、分布式存储数据库技术、加密算法、共识算法等技术,通过这些技术的整合创造了一种按时间序列、按区块记录数据的模式。区块链会用到三种底层技术:点对点网络(P2P通信协议)、密码学、分布式一致性算法。区块链系统还会“免费附赠”一种被称为智能合约的功能。

2008年,一位自称“中本聪”的匿名人士在一个邮件讨论组中发布了自己关于数字货币的设想:《比特币:一种点对点的电子现金系统》 “Bitcoin: A Peer-to-Peer Electronic Cash System” (https://bitcoin.org/bitcoin.pdf)

一些基础研究

  • 链式存储的数据 链式存储的数据在计算机领域广泛应用,各类链表结构是非常常见的数据结构 区块链的数据结构特色在于:利用哈希函数计算出的数据块特征并利用特征进行关联的链式结构,数据利用数字签名进行保护,利用易于验证的树状数据结构组装数据块。

  • 如何保护数字文件 《How to Time-Stamp a Digital Document》 基于哈希函数、数字签名、伪随机数生成器的链式时间戳服务设计。文中使用了指向前一份文件的链接哈希,顺序号,当前时间,用户标识,文件内容哈希进行签名,形成链式(Link)的数据证据,确保文件无法被篡改,时间戳服务真实可信。

  • Improving the Efficiency and Reliability of Digital Time-Stamping 定期把需要验证的数据记录组合形成树结构,而验证者只需判断少量信息(tree root)即可验证树的有效性(The root of each tree is then certified)

  • Secure names for bit-strings 这篇文章中,探讨了利用密码学来标记数字文档、URL的方法。 三篇论文都出现在中本聪关于论文《Bitcoin: A Peer-to-Peer Electronic Cash System》的引用列表中,成为比特币重要的研究基础之一。 1997年,S. Haber, W.S. Stornetta, 又于Proceedings of the 4th ACM Conference on Computer and Communications Security 发表了论文 "Secure names for bit-strings," 在这篇文章中,探讨了利用密码学来 标记数字文档、URL 的方法。

  • Hashcash(挖矿)

  • 数字货币 b-money

  • 数字货币 Bit Gold

  • Protocols for public key cryptosystems

  • An introduction to probability theory and its applications

除了各类的数据结构研究以外,早期的一些数字货币研究也给后期的 比特币提供了基础。例如 W. Dai, 在 1998 年的邮件组中所发表的"b- money" (http://www.weidai.com/bmoney.txt),就是一种匿名分布式 电子现金系统架构,从某种意义上,比特币即为该架构的一个具体项目实现。 此外,由 NickSazbo 在 1998 年发表于博客之中的比特黄金(Bit Gold)方案,也可以认为是比特币的基础之一。

区块链特点(能力)

去中心化

  • 如 微博、BT 下载、滴滴
  • 架构层、政治层、逻辑层 看是否为中心化系统
  • 比特币在架构层去中心化,由多个用户和矿工构成;政治层去中心化,没有人可以控制区块链;逻辑层中心化,整体看上去好像一台计算机
  • 去中心化系统的优势:容错、抵抗攻击、抵抗合谋
  • 但也有问题:同步成本、效率、规则难于统一

不可篡改性

区块链之所以能够在去中心化的同时解决信任问题,最本质的原因是区块链具有不可篡改的特性。 其建立在哈希函数与其特殊的链式数据结构之上。

区块是一种记录交易的数据结构。每个区块由 区块头和区块主体组成;区块主体只负责记录前一段时间内的所有交易信息;区块链的大部分功能都由区块头实现。

区块头由三组区块元数据组成:

  • 第一组引用父区块头哈希值的数据,这组元数据用于将该区块与区块链中前一区块相连接。
  • 第二组元数据,即难度、 高度、时间戳和nonce, 与挖矿竞争相关 。
  • 第三组 元数据是merkle树根的哈希值 ,默克尔树(Merkel tree)根为当前区块中所有交易按照树形结构生成的256位哈希值

篡改的理论可能性 - 51%攻击(双花攻击)

区块链的一致性协议中认为:得到半数以上支持的提议将被确定为最终协议。 以比特币为例,假如一个人拥有比特币网络中50%以上的算力,并且不计成本,那么他就可以用自己的绝对算力优势篡改比特币网络中的数据,建立一条比当前主链更长的链,并将自己篡改后的区块链同步到所有对等节点中,这就是51%攻击。

匿名性

两种主流的解释

  • anonymous,完全隐藏个人身份,即在事务处理时不使用任何身份表识;
  • pseudonymous,相对的身份隐藏,即在事务处理时使用某种标识来代替真实身份,即创造一个“假名”或“假身份”来代替自己的真实身份,即非实名

区块链的匿名性是符合第二种解释的匿名性,即通过非实名的方式来达到的相对匿名。 区块链中每一个组织或个人使用一串无意义的数字来代替自己进行交易,这串数字就是地址,并且只要用户愿意,他可以生成无数个地址。地址与真实信息是无关联的,即人们无法把地址与现实生活中的身份对应起来。

匿名的区块链应用 匿名的区块链应用在其他领域也大有作为,比如说基于区块链的匿名社交软件Telegram、WhatApp,借助匿名社交维护人民的言论自由。此外,匿名投票、艺术品拍卖等隐私需求高的场景都是区块链匿名性可以优化的方向

公开账本

大多区块链项目均使用分布式公开账本数据库,它的数据是完全公开和透明的。 在区块链中只可以进行数据的读写而不可进行修改与删除,各节点遵循一致性共识确认进行广播的交易和交易发生的次序,从而形成了统一、唯一的全球交易总账本。

优点

  • 每一个节点本地都可以保存完整总账本
    • 记录着从创世区块到当前时刻的所有交易
  • 区块链中所有的交易都是可查询的
    • 大部分区块链项目都提供区块链浏览器,可以用来查询区块链系统运行状态, 比如说 blockchain.info。
    • 每个人都知道网络的真实状态,防止了诸如双重攻击 (double-spending)之类的攻击
  • 数据公开解决了中心化难以监管、缺乏信任的弊端
    • 将数据的所有权归还给用户,使每个参与其中的用户都成为监管系统数据、维护系统真实可靠性的一员,增强了可信性 。

缺点

  • 容量问题:如何在维持长期运行的详细历史记录与扩展其未来处理日益增长的交易量的能力的需求之间取得平衡
  • 隐私:一个永久记录每笔交易的公共账本也将方便黑客、政府或不法分子跟踪公共记录以及网络参与者的行为,这使区块链参与者的匿名性和隐私受到威胁
  • 黑客攻击:任何基于公共账本的加密货币始终会受到黑客攻击,窃取加密货币以及黑客堵塞网络的威胁,这些问题都亟待去完善 。

编码与密码

相关编码

区块链常见的编码格式:将二进制转可视字符串的算法

  • Base64编码
  • Base58编码
  • Base58Check编码

Base58 在 Base64 的基础上去除了容易混淆的字符,不同应用中的字符集是不同的。Base58 没有校验机制,Base58Check 在此基础上增加了校验机制,在原数据的前面加数据类型后面加校验和,最后进行 Base58 编码。

密码学

简单总结

【以下的一个部分是对于 加解密算法、消息摘要、消息认证技术、数字签名与公钥证书 一文的总结,感觉写得很清楚】

  • 数据传输的安全,即保证不被窃听或解密,在传统的对称加密中双方都需要相同的密钥安全性有问题;而非对称加密利用公私钥的区分可解决该问题。对称加密常见算法有DES 3DES AES RC5 RC6;非对称加密有 RSA 等。
  • 保证数据完整性,即查看数据是否出了差错或被篡改。即「信息摘要/信息指纹」的概念,常见的例子是网上下载的软件会提供散列值进行校验。一般用哈希函数来完成,不可逆,常见算法有 MD5 SHA。
  • 保证数据真实性,即信息认证。有一种「消息认证码(Message Authentication Code,简称MAC)」的功能类似于对称加密,问题在于无法保证发送者不否认该消息。「数字签名(Digital Signature)」可解决该问题——数字签名的重点在于发送方和接收方使用不同的密钥来进行验证,并且保证发送方密钥的唯一性,将公钥算法反过来使用可以达到此目的:A发送消息前,使用私钥对消息进行签名,B接收到消息后,使用配对的公钥对签名进行验证;如果验证通过,说明消息就是A发送的,因为只有A采用配对的私钥;第三方机构也是依据此来进行裁决,保证公正性。
  • 公钥的合法性,上面的公钥加密和数字签名都有如何保证公钥的合法性的问题。解决办法是将公钥交给一个第三方权威机构——认证机构(Certification Authority)CA 来管理。接收方将自己的公钥注册到CA,由CA提供数字签名生成公钥证书(Public-Key Certificate)PKC,简称证书。证书中有CA的签名,接收方可以通过验签来验证公钥的合法性。

简介

网络安全

  • 机密性 Confidentiality:仅发送方和接收方能够理解
  • 完整性 Integrity 1. 消息来源的完整性;2. 消息内容的完整性
  • 可用性 Availability

古典密码学( 1949年之前):密码学是一门艺术 近代密码学(1949~1975):密码学成为科学的分支 现代密码学(1976年之后):密码学的新方向—公钥密码学

近代密码学

主要想法是分块进行加密,利用函数决定置换顺序,密钥决定了函数是如何进行置换的。

对称加密:DES 3DES AES【密钥可以是128,192,256】 RC5 RC6

公钥密码学

1976年,Diffie和Hellman在“New Directions in Cryptography”中首次给出了公钥密码学的定义,开创了现代密码学的新领域 1977年,Rivest,Shamir和Adleman提出了RSA公钥算法 90年代逐步出现了椭圆曲线等其他公钥算法, 主要特点:公钥密码使得发送端和接收端无秘钥传输的保密通信成为可能【为了确认发送方的身份,需要 数字签名】

  • 非对称加密算法
    • 每个用户都有一个公钥PK(可公开)和一个私钥SK(必须保密)
    • 公钥用于加密,私钥进行解密
    • 解决了传统密码体制的两个大难题:密钥分配和数字签名
数字签名

在使用公钥加密算法时,公钥是公开的,因此中间者可以冒充发送方给接收方发送消息,而发送方无法证明该消息不是他发送的。

如果发送方在发送消息时带上只有他自己才能生成的数据(字符串),接收者就能验证消息的来源。通常这个能证实身份的数据(字符串)就称之为数字签名

非对称加密:公钥加密、私钥解密 数字签名:私钥签名、公钥验签

公钥算法

具有实用性的公钥算法只有以下三类,其中区块链中常用的公钥加密算法是椭圆曲线算法。

其他一些数字签名

除了传统理解上的数字签名之外,还包括

  • 群签名:有一定的匿名性(作为群体);但在特殊情况下管理员可以进行追踪信息源(可追追踪性);
  • 环签名:无条件匿名性,作为群体进行签名;一个例子,内个成员举报总统;
  • 盲签名:签名者并不知道消息的内容;

哈希函数

Hash函数h用于将任意长的消息(或数据)m映射为较短的、固定长度的一个值 h(m),函数值h(m)也被称为消息的指纹或摘要。

三个特性:

  1. 碰撞阻力;
  2. 隐秘性:即给出结果无法计算输入;
  3. 谜题友好性:通俗来说就是在反向求解哈希函数时,没有比随机去尝试输入的取值有更好的办法。

默克尔树

在区块链中,使用哈希指针和默克尔树可以用来防止信息被篡改。

在默克尔树中,将数据两两分组,然后每一组建立的两个哈希指针分别指向这两个元素;像这样得到根哈希指针。

另外一个优势,利用默克尔树可以提高隶属证明的效率:要证明某个数据数据默克尔树,仅需要 \(\log(n)\) 次计算。

共识机制

区块链共识机制的目标是使所有的诚实节点保存一致的区块链视图

工作量证明(Proof-of-Work,PoW)

从比特币的挖矿开始说起

工作量证明算法的具体步骤归纳如下:

  • 通过 Merkle Tree 算法生成 Merkle Tree Root;
  • 将所有区块头的相关字段组装成区块头,将80字节的区块头数据作为工作量证明的输入;
  • 不断变更区块头中的随机数 Nonce 的值,对每次变更后的区块头做两次 SHA256 计算,即 SHA256(SHA256(Block Header));
  • 将结果值与当前的难度目标进行比较,如果小于难度目标,则工作量证明完成;

优点:

  • 安全性好
  • 去中心化

缺点

  • 高能耗
  • 速度慢,公式时间长
  • 弱中心化:矿场集中化

PoS(Proof-of-stake,权益证明)

PoS 机制根据每个节点拥有代币的比例和时间,依据算法等比例地降低节点的工作量证明难度,从而加快了寻找随机数的速度 任何人只要拥有电子货币,就可以参与生产区块(无门槛) PoS 类似于财产储存在银行,这种模式会根据持有货币的量和时间,分配相应的利息。 会引入一系列问题,无法确定“记账”节点的数量,无法确定“记账”节点之间的网络环境,节点越多会导致网络环境越复杂,这种不确定性会大大增加区块链分叉的概率,并且由于节点数量与网络环境的复杂性,区块链性能将会趋于低下。

DPoS

号称区块链 3.0 的 EOS 区块链,在 PoS 的基础上进行了优化,采用了 DPoS 机制(Delegated-Proof-of-Stake,代理权益证明) 解决了 PoS 的一些问题,可以事先确定“记账”节点和其数量,并让全网节点投票决定哪些节点成为“记账”节点

将区块生产者由 PoS 机制下的所有节点转换成指定节点数组成的小圈子,作为“记账”节点。这个圈子的节点数取决于区块链的设计,EOS 使用了 21 “记账” 节点由全网所有节点投票得到,这一过程也叫投票选举。指定数量的节点来“记账“不代表其他节点丧失了参与共识的权利,只不过其他节点以一种间接的形式行使共识权。 由于“记账”节点的数量不多,那么可以在算法设计时规定一个固定的出块时间,并由“记账”节点轮流进行记账。

优缺点

提升确认速度:DPoS 区块生产性能非常高,这就意味着交易确认会变得更实时 EOS每生成1个区块只需要0.5秒,一笔交易大概经过6-10次确认,时间不超过1分钟。

大量降低能耗:在DPOS中生产区块的节点数量极少,大致几十或几百个,每次只授权一个生产者在给定时间生产区块,区块生产是井然有序的,这些节点之间的关系是合作而不是竞争,因此不需要消耗大量的算力去竞争记账权,这样就极大地降低了能源消耗

即使在有生产者出现恶意行为的情况下,DPoS 机制也是强健而稳定的,因为可以随时替换掉不合格的区块生产者

但是 DPoS 在一定程度上违背了去中心化的思想,由于数量不多,所以区块生产节点的权力都比较大,可以认为 DPoS 是带有一定中心化思想的共识机制。

拜占庭故事 (Byzantine Generals Problem) 与 pBFT (实用拜占庭式容错 practical Byzantine Fault Tolerance,pBFT)

pBFT 降低了原始的 BFT 的算法复杂度,使得 pBFT 在实际应用中具有可行性,并且做到能够同时容纳故障节点和作恶节点,提供了安全性与鲁棒性; 但是算法复杂度仍然过高,可拓展性比较差,在节点数量变多时,系统性能将会下降的很快,带来更高的延迟;

分布式系统共识的意义

在区块链中,每个参与者都是一个独立的、有自治性的节点。不同节点都各自维护着一条内容相同的数据链,同时也独立地将网络中新产生的数据打包为一个区块,广播给其他节点。 最终只能有一个区块加入到主链,如何选择这个区块是区块链共识的重要任务之一。在这个过程中,如果将主链视为账本,共识算法所做的就是决定将哪个节点整理的账目写入总账,即如何分配记账权 共识机制作为区块链技术的重要组件之一,其目的是让所有诚实节点保存一致的区块链视图,并且能同时满足两个性质,这也是共识机制的目标

  • 一致性:所有诚实节点保存的区块链的前缀部分完全相同。
  • 有效性:由某一诚实节点所发布的信息终将被其他所有的诚实节点记录到自己的区块链中去。

智能合约

早在1994年,几乎是与互联网的概念提出的同时,密码学家和数字货币研究者尼克·萨博发表了《智能合约》(Smart contracts)论文,提出“智能合约”的概念。

传统合约需要交易双方或者多方必须信任彼此,通过协议进行交易。智能合约则不同,智能合约的协议逻辑通过代码来体现,通过代码定义并强制执行,不会通过人的主观干预而发生改变。 换句话说智能合约实际就是一段编写好的代码,一旦被调用就会严格公正地去执行代码的内容。

智能合约概念的提出远早于区块链概念,但为什么到了现在才开始进入大众眼中呢?这其实跟技术有关的,早期的构想是把智能合约烧写到硬件上以此来避免攻击者去攻击合约,但是因为当时的执行,安全和技术落地困难等原因就被搁置了。 2008年中本聪提出的比特币将区块链技术带入大众视野。区块链技术的不可篡改性,可追溯性及多方计算等特性与智能合约的应用需要不谋而合,这使人们开始思考着14年前提出的智能合约概念是不是可以依靠区块链技术去实现。

2014 年一位名叫 Vitalik Buterin(以下简称布特林)的俄罗斯小伙在《比特币杂志》上发表文章《以太坊:一个下一代加密货币和去中心化应用平台》,第一个将区块链与智能合约结合起来的区块链平台正式问世。

每当区块链网络中出现一个新交易,该交易会被矿工节点们捕获并在自己的 EVM 上模拟运行出结果,并将自己的结果广播至网络中等待被其他节点验证,被所有节点承认正确的结果才会成为此交易的正确执行结果,不被承认的交易结果则不会具有记录上链的资格。 这种多方计算并达成共识的机制使作弊成本大大提高,而且区块链的不可篡改性和可追溯性更可以帮助查找以往智能合约的执行记录。

优点:

  • 高效性:相比于传统合约,智能合约的执行不需要人为的第三方权威或中心化代理服务的参与,其能够在任何时候响应用户的请求,从而提升了交易进行的效率。
  • 准确执行:智能合约的所有条款和执行过程是提前制定好的,并在计算机的绝对控制下进行计算。
  • 多方计算:区块链中是多方计算产生结果并达成共识的,如果想要作弊的话就需要最少控制51%以上的节点来帮助作弊,这极大的提高了在区块链网络中作弊的成本。
  • 不可篡改性:区块链中智能合约一旦被部署上链则会永远存储在区块链网络中且不会被篡改(理论上可以从记录该信息的块前再生成新的一条链来重写此交易结果,但是人为分叉一条新链的成本大的超乎想象,因此可以忽略此风险)
  • 可追溯性:所有在区块链网络中执行的智能合约结果一旦被上链后都具有能被查询到。
  • 较低的运行成本:智能合约的执行仅需要电费的支出,这相比于传统合约的人力支出真的少了很大的成本。

当然,只要是人为编写的代码,肯定有出错的可能性。 2016 年,TheDAO攻击事件

三个应用案例

  • 博彩交易
  • 差价和约
  • 遗产分配

Fabric 架构

Hyperledger Fabric 是一个开源的企业级分布式账本技术平台。 最初由 Digital Asset 和 IBM 贡献, 来自一场黑客松 2015年12月,Linux基金会主导发起了 Hyperledger 项目,Hyperledger Fabric 是 Hyperledger 旗下的项目之一。 目标是发展跨行业的区块链技术。该项目是一个全球协作项目,成员包括一些不同领域的先驱者们,这些先驱者们的目标是建立一个强大的、业务驱动的区块链框架

Hyperledger Fabric 特点(与其他区块链系统不同点)

  • 最大的不同:私有和许可。 Hyperledger Fabric通过成员服务提供者(Membership Service Provider)来登记 有的成员。这意味着参与者彼此都是已知的、互相信任的,而不是匿名的、 彼此完全不信任的。
  • 最关键的不同点:是它支持可插拔的共识协议,这使得平台能够更有效地定制以适应特定的场景。
  • 允许账本非公开,提供了建立通道(channel) 的功能,允许参与者为交易新建一个单独的账本。只有在同一个通道中的参与者,才会拥有该通道中的账本,而其他不在此通道中的参与者则看不到这个账本,实现按需共享的目的。 更符合现实生活的商业场景。
  • Hyperledger Fabric 的 SDK 支持多种语言,与其他区块链网络的专用语言相比,大大降低了应用开发的门槛。

节点架构 账本 通道 成员服务提供者 MSP 针对背书策略的交易评估 Fabric 在实际场景中的交易流程

节点架构

客户端节点

客户端或者应用程序代表由最终用户操作的实体,它必须连接到某一个 Peer 节点或者排序服务节点上与区块链网络进行通信。 客户端向背书节点(Endorser)提交交易提案(Transaction Proposal),当收集到足够背书后,向排序服务广播交易,进行排序,生成区块。

Peer 节点

所有的Peer节点都是记账节点(Committer),负责验证从排序服务节点发送的区块里的交易,维护状态数据和账本的副本。 部分节点会执行交易并对结果进行签名背书,充当背书(Endorser)节点的角色。背书节点是动态的角色,是与具体链码绑定的。每个链码在实例化的时候都会设置背书策略(共识)。也只有在应用程序向它发起交易背书请求的时候才是背书节点,其他时候就是普通的记账节点,只负责验证交易并记录账本。 Peer节点还有一种角色是主节点(Leader Peer),代表的是和排序服务节点通信的节点,负责从排序服务节点处获取最新的区块并在组织内部同步。主节点可以通过强制设置产生,也可以动态选举产生。

排序服务节点

接收包含背书签名的交易,对未打包的交易进行排序生成区块,广播给Peer节点。排序服务提供的是原子广播(Atomic Broadcast),保证同一个链上的节点接收到相同的消息,并且有相同的逻辑顺序 提供交付保证的通信架构,为客户端和 Peer 节点提供共享的通信信道,为包含交易的消息提供广播服务。 排序服务的多通道(Multi-Channel)实现了了多链的数据隔离,保证只有同一个链的Peer节点才能访问链上的数据,保护用户数据的隐私。 排序服务可以采用集中式服务,也可以采用分布式协议。可以实现不同级别的容错处理。 一组排序节点形成 排序服务(ordering-service)

CA 节点

CA节点是Fabric的证书颁发机构(Certificate Authority),由服务器和客户端组件组成。 CA节点接收客户端的注册申请,返回注册密码用于用户登录,以便获取身份证书。在区块链网络上所有的操作都会验证用户的身份

账本

Hyperledger Fabric包含一个账本子系统,这个子系统包含两个组件:世界状态(world state)和交易记录。

  • 世界状态组件描述了账本在特定时间点的状态,它是账本的当前快照,通过版本键值对存储(KVS),由区块链上链码进行存取。状态s持续存储并且其更新也被记录,被存储为一个映射K -> (V X N),K是一组键,V是一组值,N是无限有序的版本号集。
  • 交易记录组件记录了产生世界状态当前值的所有交易,它是世界状态的更新历史。它包含所有的状态更改(有效交易)的记录和不成功的状态更改(无效交易)的尝试,由Order服务构建的有序的交易哈希块,并被保存在Peer节点。

通道

客户端连接到通道(Channel)上,在通道上广播的消息会最终发送给通道内所有的Peer节点。通道支持消息的原子广播(Atomic Broadcast),通道给所有相连的Peer节点输出相同的消息,并且有相同的逻辑顺序。这种原子通信也称为全序广播(Total-order Broadcast)。 客户端连接到一个通道上,就可以发送和获取消息。客户端可以连接到多个通道,通道之间相互隔离。通道(Channel)使交易者可以创建不同的账本,达到数据隔离的目的。只有在同一个通道中的参与者,才会拥有该通道中的账本,而其他不在此通道中的参与者则看不到这个账本。 排序服务支持多通道(Multi-Channel)

节点之间,节点与应用通过消息通道通信 节点间通过消息通道和排序服务联系,形成交易信息隔离的通讯链路 环境中可以并行存在多个消息通道,每个通道一个账本 Channel 配置包含 channel 的所有管理信息

  • channel的成员属于哪个组织
  • 包含channel级别的全局配置,比如channel访问策略、区块大小等

Channel 消息通道作用:适配组织架构的多链机制

  • 一个链由1个通道 + 1个账本 + N个成员(peer)组成
  • 共识由Ordering Service提供,可共享
  • 成员可加入多个链

成员服务提供者 MSP

成员服务提供者 MSP 是 Hyperledger Fabric 引入的一个组件,其将证书颁发、用户认证、后台的加密机制和协议都进行了抽象。每个MSP可以定义自己的规则,这些规则包括身份的认证,签名的生成和认证。 每个Fabric区块链网络可以引入一个或者多个MSP来进行网络管理,这样将成员本身和成员之间的操作、规则和流程都模块化。 Hyperledger Fabric 基于 PKI 体系,生成数字证书以标识用户的身份。每个身份和成员服务管理提供者(Membership Service Provider, MSP)的编号进行关联。

  • 成员(Member)是拥有网络唯一根证书的合法独立实体。在 Fabric 区块链中,peer 节点和 app client 这样的网络组件实际上就是一个 Member。
  • 成员服务(Member Service)在许可的区块链网络上认证、授权和管理身份。在peer和order中运行的成员服务的代码都会认证和授权区块链操作。它是基于 PKI 的 MSP 实现。

MSP 的具体功能是成员管理。包括 会员注册、身份保护、内容保密、交易审计功能;保证平台访问的安全性。

针对背书策略的交易评估

背书(Endorsement)(达成共识过程) 背书是指一个节点执行一个交易并返回交易是否通过的消息给生成交易提案的客户端应用程序的过程。 每个chaincode都具有相应的背书策略,用于指定背书节点。链码的调用交易需要经过背书策略要求的背书才会有效。正式的背书策略是以背书为基础,以及潜在的进一步评估为真假状态。 对于部署交易,获得背书的依据是系统系统范围策略(如系统链码)。背书通过被每个peer节点本地独立评估,但所有正确的peer节点以相同的方式评估背书策略。

背书策略(Endorsement policy) 背书策略是认可交易的条件。对于某一链码,背书策略可指定认可交易的最小背书节点数或者最小背书节点百分比。背书策略可以用来防止成员的不良行为。在安装和实例化 Chaincode 时需要指定背书策略。

交易过程

Step1:客户端创建交易并发送给背书peer节点 Step2:验证交易事务协议请求并模拟执行 Step3:验证背书节点签名并发送给排序服务节点 Step4:排序服务节点合并交易并创建区块 Step5:排序服务节点广播给Channel的Leader节点 Step6:账本更新

例题:架构设计

请阅读如下材料,完成项目的区块链架构设计,在“lianmen.docx”文件中填写完成所需步骤。 Hyperledger Fabric可以用来搭建联盟链,现有一个名为university.cn的联盟,其中有5个组织org1、org2、org3、org4和org5决定利用区块链合作传输数据,包括公共数据和秘密数据。联盟商定每个组织创建2个节点,org5额外承担排序节点工作,公共数据允许全部组织访问,秘密数据只允许org1、org3和org5访问。处理公共数据的智能合约有CCP1和CCP2;处理秘密数据的智能合约有CCS1和CCS2。

步骤一:创建节点 生成12个节点的配置文件并完成相关部署,包括10个peer节点,2个orderer节点,说明理由(例:为组织X1生成XXX):为组织org1、org2、org3、org4和org5各生成2个peer节点,共10个;另外为组织org5生成2个orderer节点 步骤二:创建通道 生成2个消息通道的配置文件并完成相关部署,包括1个公共数据通道,1个秘密数据通道,说明理由:公共数据和秘密数据需要数据隔离,所以需要2个通道. 步骤三,通道节点操作: 分别将peer和orderer加入两通道,org1、org2、org3、org4和org5加入公共数据通道ChannelPublic,org1、org3和org5加入秘密数据通道ChannelPrivate,orderer节点为两个通道提供排序服务 步骤四,通道合约操作: 分别在两个通道部署合约,CCP1和CCP2部署在公共数据通道channelPublic,CCS1和CCS2部署在公共数据通道channelPrivate

Go 语言语法

参见 Go语言大纲。下面对于 PPT 内容简单摘录。

程序结构 基础语法 数据类型 变量和常量 运算符 数组(Array)和切片(Slice) 结构体 逻辑控制语句 循环语句 条件语句 函数 接口

相关的包

在chaincode的开发中,需要导入chaincode的依赖库来提供接口支持,需要导入 shim

1
2
3
4
import ( 
"github.com/hyperledger/fabric/core/chaincode/shim"
"fmt"
)

shim库包含一系列chaincode依赖的API,提供和区块链环境进行交互的方法,包括访问状态数据,交易数据,链码之间调用等等。

方法

一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。

定义的语法如下

1
func (v_name v_datatype) func_name() [return_type] {}

接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法或行为定义在一起,在功能和该功能的使用者之间构建了一层薄薄的抽象层,任何其他类型只要实现了这些方法就是实现了这个接口。

Hyperledger Fabric 的 ChainCode 的每个 chaincode 程序都必须实现chaincode 接口,接口中的方法会在响应传来的交易时被调用

1
2
3
4
type Chaincode interface {
Init (stub ChaincodeStubInterface) pb.Response
Invoke (stub ChaincodeStubInterface) pb.Response
}

Init(初始化)方法会在chaincode接收到instantiate(实例化)或者upgrade(升级)交易时被调用,进而使得chaincode顺利执行必要的初始化操作,包括初始化应用的状态。 Invoke(调用)方法会在响应 invoke(调用)交易时被调用以执行交易。

Fabric的chaincode的编写与单元测试(Golang版)

什么是链码(chaincode)

  • 账本
    • 在Fabric中,每个通道都有其账本,每个peer节点都保存着其加入的通道的账本,包含着交易日志(账本数据库)、状态数据库以及历史数据库
    • 账本数据库:以区块链方式存储,是一个交易记录的集合,每个区块中包含一些列有序交易,简单的说,是一系列有序的、不可篡改的状态转移记录日志。每笔交易都可以当做对世界状态的一次查询或者更新,称之为状态转移,状态转移是链码(chaincode)执行(交易)的结果,每个交易都是通过增删改操作提交一系列键值对到账本。
    • 状态数据库:维护账本当前的状态,这些中间状态的最新值是以键值对方式保存在数据库中的。他们被称为世界状态。和链上数据一同组织形成了账本,可以认为世界状态是由区块链上数据完整执行获得的最终结果。
  • 什么是链码
    • 链码(chaincode)是在 Hyperledger Fabric 中所提供的智能合约。从程序的角度,链码可以认为是运行在区块链平台上的可以对外提供服务的小程序。
    • 链码部署在平台上后,对外提供了一组可被调用的接口,应用程序(客户端)通过调用链码这些接口访问这些 world state 所存放的状态,通过状态的 key 来存,取,删除状态。
    • 对许多的应用开发者来说,链码其实才是最重要的,链码可以使用不同的程序设计语言来编写实现,比如:java,go,typescript,JavaScript,不管是哪种语言编写的链码,链码结构和实现方式都是差不多的。

链码的结构

  • 引入必要的包
    • 对于每一个链码,它都会实现预定义的接口,包括Init和Invoke函数接口。所以我们首先为我们的链码引入必要的依赖。这里的shim层是节点与链码交互的中间层。
    • shim包是Fabric系统提供的主要的API,包含了链码和平台交互的接口,在链码中,执行状态修改、状态查询等功能都需要通过shim访问。
  • 声明一个结构体,即chaincode的主体结构体
  • 实现一个能部署到平台中的链码,需要满足特定的约束,具体来说,就是链码都需要实现特定的接口: chaincode 接口,实现其中的 Init() 和 Invoke() 函数,在其中利用 shim.ChaincodeStubInterface 结构,实现跟账本的交互逻辑。
    • Init(初始化)方法:会在chaincode接收到instantiate(实例化)或者upgrade(升级)交易时被调用,进而使得chaincode顺利执行必要的初始化操作,包括初始化应用的状态。
    • Invoke(调用) 方法:主要用于实现链码的内部业务逻辑,用户可以在该方法中实现相关的业务。用Fabric的术语就是会在响应invoke(调用)交易时被调用以执行交易
      • 链码初始化好之后,就能被区块链应用从外部调用。区块链平台将记录下调用的历史(交易日志),并更新worldstate中状态数据。
      • 调用链码中的方法的统一入口为Invoke,比如,如果要调用获取字典项"a"的服务,需要调用Invoke,并带参数("get","a"),其中,"get"为函数名,"a"为参数。参数可以有多个。
  • 主函数,需要调用shim.Start()方法
1
2
3
4
5
6
7
8
9
10
11
type Chaincode interface {
// Init is called during Instantiate transaction after the chaincode container
// has been established for the first time, allowing the chaincode to
// initialize its internal data
Init(stub ChaincodeStubInterface) pb.Response

// Invoke is called to update or query the ledger in a proposal transaction.
// Updated state variables are not committed to the ledger until the
// transaction is committed.
Invoke(stub ChaincodeStubInterface) pb.Response
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package main

import (
"fmt"
"strconv"

"github.com/hyperledger/fabric-chaincode-go/shim" // shim包是Fabric系统提供的上下文环境,提供了Chaincode和Fabric交互的API,在Chaincode中,执行对world state的查询、更新和删除等都需要通过shim
"github.com/hyperledger/fabric-protos-go/peer" // chaincode结构体需要实现Fabric提供的接口 peer
)


// 该结构体需要实现Fabric提供的接口"github.com/hyperledger/fabric/protos/peer",其中必须实现下面的两个方法
type SimpleContract struct {
}

// Init 链码在初始化和升级时调用此接口,初始化相关的数据。
func (t *SimpleContract) Init(stub shim.ChaincodeStubInterface) peer.Response {
//该方法中实现链码初始化或升级是的处理逻辑,
//编写时可以灵活使用stub中的API
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
_, err := strconv.Atoi(args[1])
if err != nil {
return shim.Error("Expecting integer value for state")
}
//将参数中的Key/Value更新到
err = stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create state: %s", args[0]))
}
return shim.Success(nil)
}

func (t *SimpleContract) get(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
key := args[0]
valBytes, err := stub.GetState(key)
if err != nil {
return shim.Error(fmt.Sprintf("Fail to get state: %s", key))
}
if valBytes == nil {
return shim.Error(fmt.Sprintf("Nil amount: %s.", key))
}
return shim.Success(valBytes)
}

func (t *SimpleContract) set(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
_, err := strconv.Atoi(args[1])
if err != nil {
return shim.Error("Expecting integer value for state")
}

err = stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to set state: %s", args[0]))
}
return shim.Success(nil)
}

// Invoke 来自区块链应用的调用的入口
func (t *SimpleContract) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
function, args := stub.GetFunctionAndParameters()
if function == "get" {
return t.get(stub, args)
} else if function == "set" {
return t.set(stub, args)
}
return shim.Error("Invalid invoke function name. Expecting \"get\" \"set\"")
}


func main() {
if err := shim.Start(new(SimpleContract)); err != nil {
fmt.Printf("Error starting SimpleContract chaincode: %s", err)
}
}

需要应用的Fabric的API

作为参数传入的ChaincodeStubInterface接口提供了Chaincode和Fabric交互的API,一个链码最主要的功能就是根据区块链应用调用时所传递的参数,对平台中的信息,特别是账本信息,进行访问和修改。 着重介绍示例代码用到的API GetState/PutState/DelState /GetStateByRange GetFunctionAndParameters GetStringArgs

Fabric区块链的应用中,一个核心的需求就是获取和保存交易的状态信息也就是在worldstate中的状态数据,而其存储都是用键值对(key-value)的方式。 key-value是最简单,同时也是最高效的存储方式。在区块链的应用中,由于平台性能的约束,通常应用的业务逻辑比较简单,key-value足够使用。

  • API:PutState
    • func (s *ChaincodeStub) PutState(key string, value []byte) error
  • API:GetState
    • func (s *ChaincodeStub) PutState(key string, value []byte) error
  • API:DelState
    • func (stub *ChaincodeStub) DelState(key string) error
  • API:GetStateByRange
    • func (stub *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
    • 查询指定范围内的键值,startKey为起始key,endKey为终止key,返回账本状态键迭代器,可用来获取指定的起始键与结束键(半闭半开区间)之间的状态键
  • API:GetFunctionAndParameters
    • func (stub *ChaincodeStub) GetFunctionAndParameters() (function string, params []string)
    • 通过该API,可以提取获取应用App调用链码链码交易中的参数,其中第一个作为被调用的函数名称,剩下的参数作为函数的执行参数。
  • API:GetStringArgs
    • func (stub *ChaincodeStub) GetStringArgs() []string
    • 获取区块链应用App调用链码时调用的参数列表,要求参数列表是字符串。
  • API: Success/Error
    • func Success(payload []byte) pb.Response
    • func Error(msg string) pb.Response
    • 无论是Init还是Invoke,在实现的时候都要求返回一个Response对象,使用Success和Error分别对应于构建处理成功和失败时的Response。 Success 方法负责将正确的消息返回给调用Chaincode的客户端, Error方法将错误的信息返回给调用Chaincode的客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// GetArgs returns the arguments intended for the chaincode Init and Invoke
// as an array of byte arrays.
GetArgs() [][]byte

// GetStringArgs returns the arguments intended for the chaincode Init and
// Invoke as a string array. Only use GetStringArgs if the client passes
// arguments intended to be used as strings.
GetStringArgs() []string

// GetFunctionAndParameters returns the first argument as the function
// name and the rest of the arguments as parameters in a string array.
// Only use GetFunctionAndParameters if the client passes arguments intended
// to be used as strings.
GetFunctionAndParameters() (string, []string)

// GetState returns the value of the specified `key` from the
// ledger. Note that GetState doesn't read data from the writeset, which
// doesn't
// consider data modified by PutState that has not been committed.
// If the key does not exist in the state database, (nil, nil) is returned.
GetState(key string) ([]byte, error)

// PutState puts the specified `key` and `value` into the transaction's
// writeset as a data-write proposal. PutState doesn't effect the ledger
// until the transaction is validated and successfully committed.
// Simple keys must not be an empty string and must not start with a
// null character (0x00) in order to avoid range query collisions with
// composite keys, which internally get prefixed with 0x00 as composite
// key namespace. In addition, if using CouchDB, keys can only contain
// valid UTF-8 strings and cannot begin with an underscore ("_").
PutState(key string, value []byte) error

// DelState records the specified `key` to be deleted in the writeset of
// the transaction proposal. The `key` and its value will be deleted from
// the ledger when the transaction is validated and successfully committed.
DelState(key string) error

编写一个简单的chaincode

链码单元测试

尽管通常链码都比较简单,但是还是需要经过测试验证其正确。我们可以通过自动化的方法,通过写程序验证代码逻辑上的正确性,Fabric提供了自动化测试框架。自动化测试框架的一个最大的优点是:不需要将链码部署到复杂的平台上,不需要启动任何网络节点,通过我们的测试文件就可以在本地对链码中的接口进行调用测试。。 Fabric通过一个名为 MockStub 的类用于单元测试。 MockStub主要提供两个函数来模拟背书节点对链码的调用:MockInit()和MockInvoke(),分别调用Init和Invoke接口。接收的参数均为类型为string的uuid(用于链码开始前和结束后开始事务的标志,无实际意义,可随便设置即可),以及一个二维byte数组(用于测试的提供参数)。 单元测试的要求: 1.需要导入testing包 2.单元测试文件以_test.go结尾 3.测试用例的函数必须以Test_开头

输入 go test 进行测试,加入参数 -v 可查看详细结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package main

import (
"fmt"
"testing"

"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-chaincode-go/shimtest"
)

func performInit(t *testing.T, stub *shimtest.MockStub, args [][]byte) {
//通过stub调用链码中的Init
res := stub.MockInit("1", args)
/*- 1为uuid,用于链码开始前和结束后开始事务的标志,无实际意义
- args为初始化需要的参数*/
if res.Status != shim.OK {
fmt.Println("Init failed", string(res.Message))
//如果没有启动成功,则报告失败
t.FailNow()
}
}

//确认指定key的状态值是否为预期的值
func checkState(t *testing.T, stub *shimtest.MockStub, name string, value string) {
//获取链码维护的指定key的状态值
bytes := stub.State[name]
if bytes == nil {
fmt.Println("State", name, "failed to get value")
t.FailNow()
}
//如果不是期望的值,则报告失败
if string(bytes) != value {
fmt.Println("State value", name, "was not", value, "as expected")
t.FailNow()
}
}

func Test_Init(t *testing.T) {
//构建链码
cc := new(SimpleContract)
// 将其部署到模拟MockStub平台, 传入名称和链码实体
stub := shimtest.NewMockStub("SimpleChaincode", cc)
//带参数(“a","10)调用链码中的Init,并确认是否成功
performInit(t, stub, [][]byte{[]byte("a"), []byte("10")})
//确认初始化后,指定key的状态值是否为预期的值,即健“a" 的vlaue 应该为“10”
checkState(t, stub, "a", "10")
}
func Test_Get(t *testing.T) {
cc := new(SimpleContract)
// 获取MockStub对象, 传入名称和链码实体
stub := shimtest.NewMockStub("SimpleChaincode", cc)
performInit(t, stub, [][]byte{[]byte("a"), []byte("10")})

//调用invoke方法中的get方法,查询a的值,得到a为10,说明get方法成功
res := stub.MockInvoke("1", [][]byte{[]byte("get"), []byte("a")})
if string(res.Payload) != "10" {
fmt.Println("get value a was not 10 as expected")
t.FailNow()
}
}
func Test_Set(t *testing.T) {
cc := new(SimpleContract)
// 获取MockStub对象, 传入名称和链码实体
stub := shimtest.NewMockStub("SimpleChaincode", cc)
performInit(t, stub, [][]byte{[]byte("a"), []byte("10")})

//调用invoke方法中的set方法,update key "a" value "20"
stub.MockInvoke("1", [][]byte{[]byte("set"), []byte("a"), []byte("20")})
checkState(t, stub, "a", "20")
//调用invoke方法中的set方法, add key "b" value "30"
stub.MockInvoke("1", [][]byte{[]byte("set"), []byte("b"), []byte("30")})
checkState(t, stub, "b", "30")
}

以太坊 Ethereum 简介(区块链 2.0)

2014年一位名叫 Vitalik Buterin(以下简称布特林)的俄罗斯小伙在《比特币杂志》上发表文章 ”一代智能合约和去中心化应用平台“——以太坊白皮书 一个开源的“可编程区块链“——Ethdocs ”以太坊在整体上可以看作一个基于交易的状态机: 起始于一个创世状态,然后随着交易的执行状态逐步改变达到一个最终状态。该最终状态将被看作以太坊世界的权威”版本“。状态中包含的信息有:账户余额、信誉度、现实世界相关数据等。——以太坊黄皮书

从“奥林匹克”“前沿”“家园”到 “大都会”(“拜占庭”“君士坦丁堡”)

  • EVM
    • 以太坊虚拟机 (EVM) 是智能合约的运行环境。它是一个对外完全隔离的沙盒环境,在其中运行的代码无法访问网络、文件系统和其他进程。不同合约之间的访问也是有限制的。
  • 账户
    • 以太坊有两类不同类型的账户,分别为外部账户EOA和合约账户CA,它们共用一个地址空间。
      • 外部账户由公钥-私钥对控制,账户地址由公钥生成。该类账户被私钥控制(由人控制),没有关联任何代码
      • 合约账户则由智能合约的代码控制,合约地址在合约创建时自动生成(通过合约创建者的地址和从该地址发出的交易数量计算得到) 。
    • 外部账户可以发起交易,而合约账户不能主动发起交易,只能通过外部账户发起交易触发后,按照预先编写的合约代码执行。
    • 每个账户都记录了四个字段,分别是nonce值,余额,存储哈希和合约代码哈希
      • nonce 值: 为该账户地址发出的交易数量
      • 余额: 用来记录该账户的以太币余额
      • 存储: 是一个键值对形式的持久化存存储
      • 合约代码哈希: 用于指向合约代码,只有合约账户有值,外部账户为空
  • 交易
    • 交易可以看作是从一个帐户发送到另一个帐户的消息。
    • 交易包含消息的接受者,用于确认发送者的签名,二进制数据和发送的以太币的数量。除此以外,交易还包含两个和gas有关的变量: gas price和 gas limit。
    • 交易的目标账户
      • 目标账户是外部账户,则转入指定的以太币数量到该账户。
      • 目标账户是合约账户,则合约代码会被执行,发送的数据会作为代码的参数。
      • 目标账户是零账户(账户地址为 0 ),此交易将创建一个新合约, 发送的数据会转化为EVM字节码并执行,代码的输出将作为合约代码被永久存储。
    • 合约部署就是将编译好的合约字节码通过外部账号发送交易的形式部署到以太坊区块链上(由实际矿工出块之后,才真正部署成功)。
    • 在两个外部账户之间传送消息是价值转移的过程。
    • 从外部账户到合约账户的消息会激活合约账户的代码,允许它执行各种动作(比如转移代币,写入内部存储,挖出一个新代币,执行一些运算等)
  • 货币
  • Gas
    • 和云计算相似,占用区块链的资源(不管是简单的转账交易,还是合约的部署和执行)同样需要付出相应的费用。以太坊的每笔交易都会收取一定数量的gas作为交易的手续费。
    • 以太坊上用 Gas 机制来计费,Gas 也可以认为是一个工作量单位,智能合约越复杂(计算步骤的数量和类型,占用的内存等),用来完成运行就需要越多 Gas。gas limit 指定用户愿意为该笔交易花费的最大gas数量,在 EVM 执行交易时,gas limit会按照特定规则逐渐消耗。
    • 而 Gas price由运行合约的人在提交运行合约请求的时候设定,以确定他愿意为这次交易愿意付出的费用:Gas 价格(用以太币计价) * Gas 数量。发送者账户需要预付的以太币为 gas price * gas limit。【最高可能支付的手续费】

智能合约应用案例

  • 差价和约
    • 金融衍生品是“智能合约”的最普遍的应用之一。实现金融合约的主要挑战是它们中的大部分需要参照一个外部的价格发布器;例如,一个需求非常大的应用是一个用来对冲以太币(或其它密码学货币)相对美元价格波动的智能合约,但该合约需要知道以太币相对美元的价格。最简单的方法是 通过由某特定机构(例如纳斯达克)维护的“数据提供“合约进行,该合约的设计使得该机构能够根据需要更新合约,并提供一个接口使得其它合约能够通过发送一个消息给该合约以获取包含价格信息的回复。

以太坊中的交易

  • 以太坊中的交易(Transaction)是指存储一条从外部账户发送到区块链上另一个账户的消息的签名数据包,它既可以是简单的数字货币一一以太币的转账,也可以是包含智能合约代码的消息 。
  • 一条交易包含以下内容
    • from: 交易发送者的地址,必填;
    • to: 交易接收者的地址,如果为空则意味这是一个创建智能合约的交易;
    • value: 发送者要转移给接收者的以太币数量;
    • data (也写作 input): 存在的数据字段,如果存在,则是表明该交易是一个创建或者调用智能合约交易;
    • Gas Limit(也写作 Gas, StartGas): 表示这个交易允许消耗的最大 Gas 数量;
    • GasPrice: 表示发送者愿意支付给矿工的 Gas 价格;
    • nonce: 用来区别同一用户发出的不同交易的标记;
    • hash: 由以上信息生成的散列值(哈希值),作为交易的 ID;
    • r s v:交易签名的三个部分,由发送者的私钥对交易 hash 进行签名生成
  • 三种类型的交易
      1. 转账交易: 转账是最简单的一种交易, 从一个账户向另一个账户发送以太币 。 发送 转账交易时只需要指定交易的发送者、接收者、转移的以太币数量即可(在客户端发送交易时, Gas Limit、 Gas Price、 nonce、 hash、签名可 以按照默认方式生成)
      1. 创建智能合约的交易: 创建合约是指将合约部署到区块链上,这也是通过发送交易来实现的。 在创建合约的交易中, “to” 字段是一个空字符串,在 data 字段中指定初始化合约的二进制代码,在之后合约被调用时,该代码的执行结果将作为合约代码。
      1. 执行智能合约的交易: 顾名思义,该交易是为了执行已经部署在区块链上的智能合约,在该交易中,需要将 “to” 字段指定为要调用的智能合约的地址,通过“data” 字段指定要调用的方法以及向该方法传递参数。

以太坊的主流开源项目

以太坊客户端

目前,以太坊协议及其客户端有多种语言版本的实现,其中最受欢迎的包括 Go­ethereum、 CPP-ethereum、 Parity 和 Pyethapp 等,这些开源项目均可在以太坊的官方 GitHub 目录下找到( https://github.com/ethereum/)

1 ) Go-ethereum: 以太坊协议 Go 语言实现的版本,既包括了一个独立的以太坊客户端,即本地节点可以作为完全节点或轻节点连接到以太坊的主网络,测试网络或私有网络,用于挖矿,组建私有链,管理账号,部署智能合约等常用功能,不可以编译智能合约。也可作为一个 Go 版本的以太坊库被调用, 即其他进程可以通过HTTP,WebSocket或IPC通道的方式连接到geth的JSON RPC端口,进而访问以太坊网络。 Go-ethereum客户端又称 Geth,是目前使用最为广泛的以太坊客户端。 2) CPP-ethereum:以太坊协议 C++语言实现的版本,也是目前最受欢迎的以太坊客户端之一。 CPP-ethereum 的最大特点是可移植性强,适用于 Windows、 Linux 和 OS X 等各个 版本的操作系统以及多种硬件平台 。 3) Parity :以太坊协议 Rust 语言实现的版本。 Parity客户端实现了以太坊钱包功能,可用于创建和管理以太坊账户,管理账户中的以太币和各种代币以及创建智能合约等。 4) Pyethapp:以太坊协议 Python语言实现的版本,其主要特点为创建了一个易扩展的以太坊核心代码版本。

以太坊浏览器和拓展工具

  • Mist。由以太坊官方开发的工具,用于浏览各类 DApp 项目 。而是将来DAPP(智能合约的应用APP)市场,类似于苹果市场,在这里可以有你的账号,可以浏览、发布和买卖以太坊的DAPP应用。
  • MetaMask 一个用于接人以太坊去中心化网络的浏览器插件 ,目前适用于 Chrome 和 Brave 浏览器 。 用户无需在本地安装运行以太坊节点,只需通过 MetaMask 便可在浏览器上连接以太坊网络,运行以太坊 DApp。
  • Web3.js: 一个兼容了以太坊核心功能的 JavaScript 库,为以太坊客户端及 DApp提 供了一系列以太坊功能调用的 JavaScriptAPI 接口 。
  • Remix:又称为 Browser-Solidity,是一个基于网页浏览器的 SolidityIDE 和编译器。 Remix 网页终端整合了 Solidity 代码的编写、调试和运行等功能,为用户提供了开发以太坊智能合约的综合环境 。
  • Truffle : 一套针对以太坊 DApp 的开发框架,本身是基于 Node.js编写的。 Truffle 框架对 Solidity 智能合约的开发 、 测试、部署等进行全流程管理,帮助开发者更专业地开发 以太坊 DApp。
  • ENS-registrar :以太坊域名服务( Ethereum Name Service, ENS)是为以太坊账户 提供简单、易记域名的服务,类似于互联网的 DNS。 ENS-registrar是一个基于以太坊的开 源 DApp 项目,在以太坊区块链上为以太坊账户提供域名注册服务 。

Solidity 语言

已转移到 Solidity 语言概要,下面简单列举一些概念。

合约结构

Solidity 中的合约( contract )与面向对象编程语言中的类 (Class) 很相似,在一个合约中可以声明多种成员,包括:

  • 状态变量:是永久存储在合约账户存储中的值
  • 函数:函数是合约代码的执行单位, 一个合约中可能包含许许多多提供各种功能的函数 , 它们相互调用,共同组成合约的工作逻辑,一些特殊的函数 如构造函数等
  • 函数修改器 :函数修改器可用于改变函数的行为,在函数执行前或执行后插入其他逻辑
  • 事件:事件是以太坊日志协议的高层次抽象, 用于记录合约执行过程中发生的各种事件和状 态变化。

同时,一个合约可以继承另一个合约。

地址类型

  • 存储一个 20 字节的值,表示一个以太坊地址
  • 地址类型有成员变量 balance, transfer, send

特殊/全局变量与函数

  • 区块属性
  • 交易属性
    • msg.data msg.sender msg.value(随消息发送的 wei 的数量)
  • 和约相关
    • this 当前和约

以太坊Ethereum智能合约:Token实现

Token

Token--以太坊上的数字资产 Digital Asset on Ethereum

  • 以太坊设计目标就是让各种数字资产以智能合约的形式运行在以太坊虚拟机上。 目前, 在以太坊上的众多智能合约之中,应用最为广泛的是代币合约 (Token Contract)
  • 代币合约是在以太坊上管理账户及其拥有的代币的智能合约,实质上可以理解为一张账户地址和对应账户代币余额的映射表。
  • 太坊上数字资产的发行和流通更加简便灵活,相关的操作一般由代币合约创建者在代币合约中实现,包括:
    • 记录账户及代币余额
    • 代币转账
    • 通过铸造代币、销毁代币来增加或减少代币供应总量
    • 数字资产的查询、权限控制,甚至经济学公式计算等

ERC-20 接口规范

  • 常量 name symbol decimals
  • 函数 totalSupply transfer transferFrom approve allowance
  • 事件 Transfer Approval
1
2
3
4
5
6
7
8
9
10
11
12
13
contract EIP20Interface {
string public constant name = ” Token Name";
string public constant symbol =”SYM”;
uint8 public constant decimals = 18;
function totalSupply() public view returns (uint256);
function balanceOf(address _owner) public view returns (uint256 balance);
function transfer(address _to, uint256 _value) public returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
function approve(address _spender, uint256 _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

11 个标准接口可分为三类

  • 常量
    • 代币名称 name
      • 代币名称(name)是由代币合约创建者指定的完整名称,是一串公开的字符串常量【可通过构造方法传值进行初始化,也可直接在代码中指定返回当前 Token 的名称 function name() public view returns (string)】
    • 代币符号 symbol
      • 代币符号( symbol)是由代币合约创建者指定的代币简称,是一串公开的字符串常量, 一般由3~4个大写字母组成,便于标识该代币,如EXT 【可通过构造方法传值进行初始化,也可直接在代码中指定,返回当前 Token 的符号 function symbol() public view returns (string)】
    • 小数点位 decimals
      • 小数点位(decimals)是由代币合约创建者指定的一个公开的无符号整数常量,用于指定代币的最小精度值, 一般为 18。小数点位的数值表示该代币在交易中最小单位在小数点后的位数,比如 18 表示该代币在交易中的最小单位为 l × 10 18 个代币 ,可以分割为小数点后 18 位的精度。一般情况下,当查询一个 Token 余额时,是按照最小精度的整型来显示的【可通过构造方法传值进行初始化,也可直接在代码中指定,返回当前 Token 的精度 function decimals() public view returns (uint8)】
  • 函数功能包含总供应量、余额、转账、从他人处转账、允许量值以及限额 6个功能函数,分别规定了实现代币合约所必需的查询、转账 、权限控制等基本功能的函数接口 。
    • 总供应量 function totalSupply() public view returns (uint256);
      • 用于查看代币当前的总供应量,即当前合约账本中所有账户余额的总和。 该函数没有输入参数,返回值为无符号整数常量。 【 uint256 public totalSupply与上面的属性一样,可通过构造方法传值进行初始化】
    • 余额 function balanceOf(address _owner) public view returns (uint256 balance);
      • 用于查看当前合约中指定账户的代币余额。 该函数的输入参数为账户地址,返回值为账户代币余额,为无符号整数常量 。【所有的地址与余额之间的关联都是通过此 mapping 进行存储: mapping (address => uint256) public balanceOf】
    • 转账 function transfer(address _to, uint256 _value) public returns (bool success);
      • 用于从当前账户向其他账户进行代币转账。 该函数的输入参数为目标账户地址和转账的代币数额,返回值为布尔型变量。 当账户满足当前有足够的余额、 转账数额为正数以及合约编写者指定的其他条件时,转账成功,则合约中当前账户的余额减 少,目标账户中的余额增加,函数返回值为真;否则转账失败,函数返回值为假 。
    • 从他人处转账 function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
      • 用于从他人账户向其他账户进行代币转账,在有些情况下,用户不仅可以使用 transfer( )函数自己发起转账,还可以授权他人在一定限额下调用 transferFrom( )函数从自己账户中转出代币,而无须自己介入。
      • 例如,在一个银行合约中, 由于合约无法控制用户的行为,不能命令用户使用 transfer( )发起转账,但可以由用户提前授权,并通过 transferFrom( )从用户账户中转出钱款,自动完成转账过程,而无须通知用户参与。 该函数的输入参数为转账的发起地址、目标地址以及转账数额。 与 transfer( )函数一样,当转账成功时返回值为真,转账失败则返回值为假 。
    • 允许量值 function approve(address _spender, uint256 _value) public returns (bool success);
      • 用于设定当前账户对指定账户的允许转账量值(allowed)。 该函数的输入参数为代币使用方地址和允许使用的额度,返回值为设置是否成功的布尔型变 量 。
      • 必须对其他人利用transferFrom( )接口从本账户中转走的代币数额进行限制,于是 ERC 20 标 准引入了允许量值 allowed。
      • allowed 是一个二元组, allowed[A][B]记录的是用户 A 对本账户中允许账户B转走的代币额度。 用户A通过调用approve()函数并指定账户B和允许额度,对 allowed[A][B]进行设置;当账户 B 调用 transferFrom( )函数从账户 A 中转出代币时,需先通过检查,确保转出的数额不超过账户 A 设置的 allowed[A][B]值,并且转账之后 allowed[A][B]值会减少相应的数额 。
    • 限额 allowance(address _owner, address _spender) public view returns (uint256 remaining);
      • 用于查看当前的 allowed 值 。 该函数的输入参数为代币持有方A 的地址和代币使用方 B 的地址,返回值为当前在账户 A 中允许账户 B 转出的代币数额之后 allowed[A][B] ,为无符号整型常量 。
  • 智能合约中还包括了记录事件的 event 类型接口, ERC 20 合约标准也对代币合约基本的事件接口进行了规范。 ERC20 标准要求代币合约包含至少两个事件:转账(Transfer)和 允许(Approval)。
    • 转账 event Transfer(address indexed _from, address indexed _to, uint256 _value);
      • Transfer()事件用于记录代币合约最基本的功能一一转账。 事件的输入参数为转账的发起方、接收方以及转账的代币金额,一般位于 transfer( )函数和 transferFrom( )函数中转账成功之后触发。 用户可以从交易收据(receipt)中查看每一笔代币转账的相关信息 。
    • 允许 event Approval(address indexed _owner, address indexed _spender, uint256 _value);
      • Approval( )事件用于记录代币合约的进阶功能一一允许他人从本账户中转出代币。事件的输入是代币的持有者、使用者以及所设置的允许金额,一般位于 approve()函数中,设 置允许限额成功之后触发 。 用户可以从交易收据( receipt)中查看代币持有者对他人设置的 允许转账限额等相关信息 。

Token智能合约实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
pragma solidity ^0.4.21;

// 声明合约和状态变量
contract EIP20 is EIP20Interface {
uint256 constant private MAX_UINT256 = 2**256 - 1;
//balances 保存每个地址的余额
mapping (address => uint256) public balances;
//保存每个地址可以允许其他地址取出的 token 的剩余数量
mapping (address => mapping (address => uint256)) public allowed;
string public name; //token名称
uint8 public decimals; //小数位数
string public symbol; //token代码
uint256 public _totalSupply; //发行总量
}

// 声明合约的构造函数
function EIP20(
uint256 _initialAmount,
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol
) public {
balances[msg.sender] = _initialAmount; //给合约创建者所有的token
_totalSupply = _initialAmount; //更新totalSupply等
name = _tokenName;
decimals = _decimalUnits;
symbol = _tokenSymbol;
}

// 以下三个方法只是简单的从状态变量中返回
function totalSupply() public view returns (uint256) {
return _totalSupply;
}

function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
 
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return allowed[_owner][_spender];
}

// transfer方法是从消息发送者的地址中转出
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balances[msg.sender] >= _value);
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}

// transferFrom方法的实现与transfer方法相似,不同的是transferFrom方法需要指定转出账户 ,以及需要将 allowed[A][B]值减少相应的数额
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
uint256 allowance = allowed[_from][msg.sender];
require(balances[_from] >= _value && allowance >= _value);
balances[_to] += _value;
balances[_from] -= _value;
if (allowance < MAX_UINT256) {
allowed[_from][msg.sender] -= _value;
}
emit Transfer(_from, _to, _value);
return true;
}

// 最后实现approve方法,授权指定账户转账和允许的额度
function approve(address _spender, uint256 _value) public returns (bool success) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}


// Token事件【这里仅仅声明了一下?】
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);