diff --git a/Blockchain/Linux基础(补充).md b/Blockchain/Linux基础(补充).md deleted file mode 100644 index 31a152c..0000000 --- a/Blockchain/Linux基础(补充).md +++ /dev/null @@ -1,372 +0,0 @@ -# 新手建议 - -## 学习Linux的注意事项 - -* Linux严格区分大小写(命令全都是小写)—— 命令、文件名、选项等均区分大小写 - -* Linux中**所有内容**以文件形式保存,包括硬件 - - * 硬盘文件是/dev/sd[a-p] - * 光盘文件是/dev/sr0等 - -* Windows通过扩展名区分文件类型,还有图标可以区分;Linux不靠扩展名区分文件类型,靠文件权限区分,但也有一些约定俗成的扩展名: - - * 压缩包:"*.gz", "*.bz2", "*.tar.bz2", "*.tgz"等 - * 二进制软件包:".rpm" - * 网页文件:"*.sh" - * 配置文件:"*.conf" - - 注意:这些扩展名不是必要的,即时不加扩展名也没有影响,只是便于管理而已 - -* Linux所有存储设备都必须挂在之后用户才能使用,包括硬盘、U盘、光盘(将设备与挂载点连接的过程就是挂载) - -* Windows下的程序不能直接在Linux中安装和运行 - -## 服务器管理和维护建议 - -### 服务器管理 - -| 目录名 | 目录作用 | -| :----------: | :----------------------------------------------------------: | -| /bin/ | 存放系统命令的目录,普通用户和超级用户都可以执行,不过放在/bin下的命令在单用户模式下也可以执行 | -| /sbin/ | 保存和系统环境设置相关的命令,只有超级用户可以使用这些命令进行系统环境设置,但是有些命令可以允许普通用户查看 | -| /usr/bin/ | 存放系统命令的目录,普通用户和超级用户都可以执行,这些命令和系统启动无关,在单用户模式下不能执行 | -| /usr/sbin/ | 存放根文件系统不必要的系统管理命令,例如多数服务程序。只有超级用户可以使用 | -| /boot/ | 系统启动目录,保存系统启动相关的文件,如内核文件和启动引导程序(grub)文件等 | -| /dev/ | 设备文件保存位置,我们已经说过Linux中所有内容以文件形式保存,包括硬件,这个目录就是用来 保存所有硬件设备的 | -| /etc/ | 配置文件保存位置,系统内所有采用默认安装方式(npm安装)的服务的配置文件全部保存在这个目录中,如用户账户和密码,服务的启动脚本,常用服务的配置文件等 | -| /home/ | 每个用户的默认登陆位置,普通用户的home目录就是在/home下建立一个和用户名相同的目录 | -| /lib/ | 系统调用的函数库保存位置 | -| /lost+found/ | 当系统意外崩溃或机器意外关机时,产生的一些文件碎片放在这里,当系统启动的过程中fsck工具会对其进行检查,并修复已经损坏的文件系统。这个目录只在每个分区中出现,例如/lost+found就是根分区的备份恢复目录,/boot/lost+found就是/boot分区的备份恢复目录 | -| /media/ | 挂载目录,系统建议是用来挂载媒体设备的,例如软盘和光盘 | -| /mnt/ | 挂载目录,建议挂载额外设备,如U盘,移动硬盘和其他操作系统的分区 | -| /misc/ | 挂载目录,系统建议用来挂载NFS服务的共享目录 | -| /opt/ | 第三方安装的软件保存位置,但现在更多的是保存在/usr/local中 | -| /proc/ | 虚拟文件系统,该目录的数据不保存到硬盘中,而是保存到内存中。主要保存系统的内核、进程、外部设备状态和网络状态灯,如/proc/cpuinfo是保存CPU信息的,/proc/devices是保存设备驱动的列表的,/proc/filesystems是保存 文件系统列表的,/proc/net/是保存网络协议信息的 | -| /sys/ | 虚拟文件系统,主要保存内核相关信息 | -| /root/ | 超级用户的家目录 | -| /srv/ | 服务数据目录, 一些系统服务启动后可以在这个目录保存需要的数据 | -| /tmp/ | 临时目录,系统存放临时文件的目录,该目录下所有用户都可以访问和写入,我们建议此目录不能保存重要数据,最好每次开机都把该目录清空 | -| /usr/ | 系统软件资源目录,注意usr不是user的缩写,而是"Unix Software Resource"的缩写,所以不是存放用户数据,而是存放系统软件资源的目录。系统中安装的软件大多数都在这里 | -| /var/ | 动态数据保存位置,主要保存缓存、日志以及软件运行所产生的文件 | - -### 服务器注意事项 - -1. 远程服务器不允许关机,只能重启 -2. 重启时应该关闭服务 -3. 不要在服务器的访问高峰运行高负载命令 -4. 远程配置防火墙时不要把自己踢出服务器(可以设置每五分钟将防火墙规则重置一次,配置完之后再取消该设置) -5. 指定合理的密码规范并定期更新 -6. 合理分配权限 -7. 定期备份重要数据和日志 - -磁盘分区是用分区编辑器在磁盘上划分几个逻辑部分,碟片一旦划分成数个分区,不同类的目录和文件 可以存储进不同的分区。 - -# 系统分区 - -## 分区类型 - -* 主分区:最多只能有4个 -* 扩展分区: - * 最多只能有1个 - * 主分区加扩展分区最多有4个 - * 不能写入数据,只能包含逻辑分区(这种限制是硬盘的限制) -* 逻辑分区 - -## 格式化 - -硬盘经过正确分区后仍不能写入数据,我们的硬盘还必须经过格式化之后才能写入数据。格式化又称逻辑格式化,它是根据用户选定的文件系统(如FAT16、FAT32、NTFS、EXT 2、EXT3、EXT4等),在磁盘的特定区域写入特定数据,在分区中划分出一片用于存放文件分配表、目录表等用于文件管理的磁盘空间。格式化就是按照文件系统的规则将硬盘分成等大小的数据块,我们把数据块称为block。 - -> 注:Windows可以识别的系统有FAT16、FAT32、NTFS;Linux可以识别的系统有EXT2、EXT3、EXT4 - -## 设备文件名 - -#### 硬盘设备文件名 - -Windows是直接分区——>格式化——>分配盘符即可使用,Linux需要分区——>格式化——>给分区建立设备文件名——>分配盘符才能使用。 - -| 硬件 | 设备文件名 | -| ----------------- | ------------------- | -| IDE硬盘 | /dev/hd[a-d] | -| SCSI/SATA/USB硬盘 | /dev/sd[a-p] | -| 光驱 | /dev/cdrom或dev/sr0 | -| 软盘 | /dev/fd[0-1] | -| 打印机(25针) | /dev/lp[0-2] | -| 打印机(USB) | /dev/usb/lp[0-15] | -| 鼠标 | /dev/mouse | - -#### 分区设备文件名 - -分区设备文件名直接**在硬盘设备文件名后面加分区号**即可,如 - -* IDE硬盘接口第一个分区:/dev/hda1(如今几乎看不到) -* SCSI硬盘接口、SATA硬盘接口的第一个分区:/dev/sda1 - -> IDE硬盘是最古老的硬盘,理论最高传输速度是133M/s -> -> SCSI硬盘接口与IDE硬盘同时代,更加昂贵但速度更快,理论最高传输速度可达200M/s,但这种硬盘主要用在服务器上 -> -> 但上两种硬盘接口如今已经基本淘汰,如今使用更多的是小口的SATA串口硬盘,SATA已发展到3代,其理论传输速度最高可达500M/s,目前不管是服务器还是个人机基本使用的都是SATA硬盘接口。 - -需要留意的是,逻辑分区永远都是从5开始的 - -## 挂载 - -挂载实际上就是Windows中分配盘符的过程,盘符则被相应地称为挂载点,必须分区的分区有以下两种: - -1. 根分区:/ -2. swap分区(交换分区):可以理解为虚拟内存,当真正内存不够用时,可以使用这部分交换分区的硬盘空间来当内存,理论上来说交换分区应该是内存的两倍,但最大不超过2GB - -若无这两个分区,Linux不能正常使用,但我们还推荐把/boot单独分区,这是为了防止Linux系统启动不起来,一般200MB即可。 - -# 远程登陆管理工具 - -## 网络连接 - -网络连接从虚拟机设置中可以看到,一共有三种:桥接、NAT和Host-only,下面讲解其区别: - -* 桥接:桥接意味着虚拟机如同一个单独的主机一样访问Wifi等,也可以和其他机器通信 -* NAT:虚拟机仅能和主机通信,但若主机可以访问互联网,虚拟机也可以访问互联网 -* Host-only:虚拟机仅能和主机本机通信,不能访问互联网 - -## 网络配置 - -1. 首先调成Host-only模式,使得虚拟机仅与主机连接 -2. 在主机上找到VMware Network Adapter VMnet1的IP地址,我本地地址为192.168.19.1 -3. 在虚拟机上使用`ishw -c netwowrk`命令找到logical name,此即为虚拟机的网卡名称,我的虚拟网卡名称为ens33 -4. 使用命令ifconfig [不等于IP地址] logical name,例如我使用的是`ifconfig ens33 192.168.19.2` -5. 此时再ifconfig即可看到我们设置的已生效 -6. 我们可以在主机ping这个IP地址看到生效 -7. 使用secureCRT连接即可 - -需要注意的是,以上方法配置IP地址时不是永久生效的,也就是重新启动电脑时就失效了,若想永久生效需要改变配置文件 - -若使用NAT模式,则步骤简单很多,只需要ifconfig获得IP地址之后直接用secureCRT连接即可 - -## WinSCP - -另外推荐一个Windows主机与Linux虚拟机进行文件传输的工具——WinSCP,操作方法与上面类似,只需输入对应的IP地址即可连接。 - -# Linux常用命令 - -## 一、最常用命令 - -这是我们**使用得最多**的命令了,**Linux最基础的命令**! - -- 可用 `pwd`命令查看用户的当前目录 -- 可用 `cd` 命令来切换目录 -- `.`表示当前目录 -- `..` 表示当前目录的上一级目录(父目录) -- `-`表示用 cd 命令切换目录**前**所在的目录 -- `~` 表示**用户主目录**的绝对路径名 - -**绝对路径:** - -- 以斜线(/)开头 ,描述到文件位置的**完整说明** ,任何时候你想指定文件名的时候都可以使用 - -**相对路径 :** - -- 不以斜线(/)开头 ,指定**相对于你的当前工作目录而言的位置** ,可以被用作指定文件名的简捷方式 - -## 二、文件处理命令 - -### 1. 命令格式与目录处理命令`ls` - -**命令格式**:`命令[-选项][-参数]`,例:`ls -la /etc` - -**说明**: - -1. 个别命令使用不遵循此格式 -2. 当有多个选项时,可以写在一起 -3. 简化选项与完整选项:`-a` 等于 `--all` - -`ls`命令的语法: - -1. `ls -a`可以显示所有文件,包括隐藏文件(以点.开头的文件是隐藏文件) - -2. 若希望查询的不是当前目录,可以使用`ls+其他目录`进行查询 - -3. `ls -l`可以显示更多属性(long),属性阐述如下: -1. 第一列分为三个部分,第一部分(如d告诉我们文件的类型是一个目录,-为二进制文件,1为软链接文件),drwx表示该文件支持读写和执行操作,r,w,x分别对应读、写、执行三个权限,三列分别对应所有者,所属组,其他人的权限 - -2. 第二列的2、2、3等表示调用次数 - 3. 第三列表示所有者,也就是这个文件的总负责人(拥有文件的所有权,可转让) -4. 第四列表示所属组,也就是可以操作这个文件的人 - 5. 第五列表示文件大小,默认单位是字节(很反Windows) -6. 最后一个是文件的最后一次修改时间(Linux没有创建时间这个概念) - -4. `ls -lh`比原先的更人性化(humanitarian),它将对应的单位也显示了出来,`-h`实际上是一个通用选项,很多命令都可以加 - -5. `-d`显示当前目录本身而不显示目录下的数据,一般与`-l`结合使用,如`ls -ld /etc` - -6. `ls -id`可以查看当前目录对应的文件ID - -### 2. 目录处理命令 - -##### `mkdir` - -**语法**:`mkdir -p [目录名]` - -**功能描述**:创建新目录,`-p`递归创建(若一个目录本身不存在,可以在创建这个目录的同时创建子目录),也可以同时创建多个目录 - -##### `cd` - -**语法**:`cd directory` - -**功能描述**:改变当前目录 - -##### `pwd` - -**语法**:`pwd` - -**功能描述**:显示当前目录(print working directory) - -##### `rmdir` - -**语法**:`rmdir [目录名]` - -**功能描述**:删除空目录(若目录非空则不能删除) - -##### `cp` - -**语法**:`cp -rf [源文件或目录] [目标目录] -r 复制目录 -p 保留文件属性(文件创建时间等不发生变化)` - -**功能描述**:复制文件或目录 - -##### `mv` - -**语法**:`mv [源文件或目录] [目标目录]` - -**功能描述**:剪切文件、改名 - -##### `rm` - -**语法**:`rm -rf [文件或目录] -r 删除目录 -f 强制执行` - -**功能描述**:删除文件 - -### 3. 文件处理命令 - -##### `touch` - -**语法**:`touch [文件名]` - -**功能描述**:创建空文件 - -##### `cat` - -**语法**:`cat [文件名]` - -**功能描述**:显示文件内容 `-n`可显示行号 - -##### `tac` - -与`cat`相反,可以倒着显示 - -##### `more` - -`cat`命令显示的往往过多,若希望分页显示可以使用`more`,用法与`cat`相同,使用时按空格可以一页页往后翻,使用q或Q退出 - -##### `less` - -由于`more`无法向上翻,我们可以使用`less`命令,可以使用page up一页页往上翻,也可以使用上箭头一行行往上翻,其他操作与`more`相同。另外`less`还可以进行搜索,比如想要搜索关键词service,可以输入/service进行检索,页面会对这些关键词进行高亮,可以使用`n`找到其他关键词位置 - -##### `head` - -若只想要看文件的前几行,可以使用`head -n`加指定行数,若不加则默认显示前10行 - -##### `tail` - -与`head`类似 ,但是显示后面几行。 - -常用搭配为:`tail -f`,该命令会动态显示文件末尾内容 - -## 三、链接命令`ln` - -**语法**:`ln -s [原文件] [目标文件] -s 创建软链接` - -**功能描述**:生成链接文件 - -**示例**: - -* `ln -s /etc/issue issue.soft`:生成软链接 -* `ln /etc/issue issue.hard`:生成硬链接 - -**软链接和硬链接的区别** - -我们使用`ls -l`查看这两个文件的信息: - -``` --rw-r--r-- 2 root root 26 Jul 15 2020 issue.hard -lrwxrwxrwx 1 root root 10 Jan 31 04:55 issue.soft -> /etc/issue -``` - -我们会发现这两个文件的信息相差的非常多,软链接文件开头的文件类型是`l(link)`,三个权限都是`rwx`,即可读可写可执行,软链接文件就类似于Windows的快捷方式,用处是便于做管理,我们可以看到最后有一个箭头指向`/etc/issue`。另外我们看到这个文件只有31个字节,因为它只是一个符号链接。我们可以总结得出软链接的三个特点: - -1. 权限是`rwx` -2. 文件很小,只是符号链接 -3. 箭头指向源文件 - -下面我们看硬链接的特点,我们首先分别查看 这两个文件的信息: - -``` -ls -l issue.hard -ls -l /etc/issue -``` - -我们可以看到这两个文件的所有信息一模一样,包括文件的大小,这类似于拷贝,似乎相当于`cp -p`,而硬链接和`cp -p`的最大不同就是硬链接可以实现同步更新,我们可以做一个简单的实验,我们先查看硬链接文件,然后往源文件中写入文件,可以发现硬链接文件也被同时修改了,当然软链接也会同步修改。 - -但当我们将源文件复制到另一个位置并删除原位置文件之后,再试图打开软链接会提示“没有那个文件或目录”,而且再显示这个目录软链接会标红并一直闪,而硬链接可以正常访问,没有影响,这就是硬链接和软连接的不同之处。 - -实际上我们可以通过命令`ls -i`来识别其`i`节点以辨别出是硬链接还是软链接,硬链接和源文件的`i`节点相同,软链接则不同。 - -硬链接相当于一个同步文件,但可以做实时备份(一个文件删了不会影响另一个文件),硬链接有两个限制,这也是硬链接和软链接的区别: - -1. 不能跨分区 -2. 不能针对目录使用 - -## 四、权限管理命令 - -Linux用户一共分成三类,分别是所有者(U),所属组(G)和其他人(O),权限也分成三类,分别是`r`,`w`,`x`,对应读、写、执行,我们首先学习如何更改权限。 - -#### `chmod` - -更改文件的人只能是文件所有者或者管理员root用户,更改文件权限有两种方式,第一种方式如下: - -``` -chmod [{ugoa}{+-=}{rwx}][文件或目录] -``` - -其中第一个花括号里`u`,`g`,`o`,`a`分别表示所有者,所属组,其他人和所有人,第二个花括号`+`和`-`分别表示增加和减少权限,`=`表示成为后面的权限。第二种方式如下: - -``` -chmod [mod=421][文件或目录] -R 递归修改 -``` - -数字的意思只是将三个权限位分别用数字来表示,比如`r`用4表示,`w`用2表示,`x`用1表示,则若要表示`rwxrw-r--`则记为`764` - -#### `chown` - -命令英文原意是`change file ownership`,作用是改变文件或目录的所有者,改变文件file的所有者为user的具体用法为: - -``` -chown user file -``` - -要注意只有root和文件的所有者可以改变文件的权限 - -#### `chgrp` - -命令英文原意是`change file group ownership`,作用是改变文件或目录的所属组,若具体用法和前面`chown`相同。我们可以使用`groupadd`命令添加组(使用`useradd`命令添加用户) - -#### `umask` - -命令英文原意是`the user file-creation mask`,作用是显示、设置文件的缺省权限,语法是: - -```shell -umask [-S] -``` - -其中`-S`的作用是显示新建文件的缺省权限,但需要注意的是缺省创建文件时不可以有可执行权限的,所以当`touch`创建文件时会发现所有权限都少了`x`。 - -当我们直接使用`umask`时,比如显示0022,第一个0是特殊权限,我们暂时不涉及,第二只第四位分别是所有者、所属组和其他人,我们的最终权限实际上是`777-022=755`,也就是`rwx r-x r-x`,当然这指的是目录,如果是文件由于没有可执行权限,文件权限应当是`rw- r-- r--`,当然缺省创建的权限可以更改,直接使用`umask 077`即可将文件缺省权限更改为`rwx --- ---`,但不推荐做这种更改 \ No newline at end of file diff --git a/Blockchain/part1_基础知识介绍.md b/Blockchain/part1_基础知识介绍.md deleted file mode 100644 index 96e52b7..0000000 --- a/Blockchain/part1_基础知识介绍.md +++ /dev/null @@ -1,509 +0,0 @@ - 注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 - -本次组队学习重点在于以太坊基础知识、以太坊客户端以及以太坊solidity编程,因此本节教程重点在于以太坊核心知识点的掌握,区块链部分的基础知识可以作为补充,请学习者量力而行。另外若学习者觉得本节内容难度太高,可以先对基本知识点有一个概览,在第二节以及第三节实战内容学习完成之后再深入学习本节内容。 - -# 一、区块链简介 # - -## 1.1、区块链与区块链技术 ## - -在阅读本教程之前,[大家对比特币原理不太了解同学可以先阅读下此博客~](http://blog.codinglabs.org/articles/bitcoin-mechanism-make-easy.html),大家对比特币有简单了解后对于区块链会有更好的认识。 - -**区块链**是将记录(区块)通过密码学串联并加密的链式数据结构。而**区块链技术**,是通过P2P网络和区块链来实现数据存储的**去中心化**、**不可逆**和**不可篡改**。比特币正是构建在区块链技术上的典型应用。通过区块链技术,我们可以将信息(数据、程序)保存在区块上并接入到区块链中,这样就实现了信息的去中心化存储、不可逆和不可篡改。**区块链应用**是指利用区块链技术开发的应用。 - -## 1.2、区块链历史 ## - -2008年,一个网名叫中本聪(Satoshi Nakamoto)的人发表了一篇名为《比特币:一种点对点电子货币系统》的论文,论文中首次提到了“区块链”这一概念。2009年,中本聪创立了以区块链为底层技术的比特币网络,开发出了第一个区块,被称为“创世区块”。该阶段被称为“区块链1.0”。 - -由于比特币是一个电子货币系统,所以主要功能就是记账。但随后人们发现,区块链技术作为比特币的底层技术,功能可以远远不止于记账,许多关于“未知的信任”的问题,都可以通过区块链来解决,例如电子存证、信息记录等。于是在比特币的基础上,诞生了带有智能合约的区块链系统,即允许开发者通过编写智能合约来实现特定的逻辑,这一阶段被称为“区块链2.0”。这一阶段的主要代表是以太坊。 - -随后,人们想要提升区块链应用的性能,于是出现了EOS、ArcBlock等系统,其特点是高性能、大吞吐量,但由于引入了超级节点、云节点等特性,弱化了“去中心化”这一特点,因此受到较大的争议。这一阶段被称为“区块链3.0”。 - -由于比特币是一款电子货币,可扩展性较低,而所谓的“区块链3.0”目前受到较大争议,且部分项目的底层算法完全不同于典型的区块链,因此学习区块链2.0中的以太坊是目前学习区块链的最佳方式。 - -## 1.3、区块链基础技术与算法 ## - -区块链技术不是单独的一项技术,而是一系列技术组成的技术栈,其具有以下的特点: - -* 数据分布式存储 -* 存储的数据不可逆、不可篡改、可回溯 -* 数据的创建和维护由所有参与方共同参与 - -为了实现这些特点、维护区块链应用的稳定运行,区块链技术中包含了分布式存储技术、密码学技术、共识机制以及区块链2.0提出的智能合约。 - - - -### 1.3.1、区块 - -区块链由一个个区块(block)组成。区块很像数据库的记录,每次写入数据,就是创建一个区块。 - -
- -
-
中心化存储
-每个区块包含两个部分。 - -> - 区块头(Head):记录当前区块的特征值 -> - 区块体(Body):实际数据 - -区块头包含了当前区块的多项特征值。 - -> - 生成时间 -> - 实际数据(即区块体)的哈希 -> - 上一个区块的哈希 -> - ... - -### 1.3.2、分布式存储技术 ### - -与传统的数据存储技术不同,在区块链技术中,数据并不是集中存放在某个数据中心上,也不是由某个权威机构或是大多数节点来存储,而是分散存储在区块链网络中的每一个节点上。 - -
- -
-
中心化存储
- -
- -
-
分布式存储
-**节点和区块的关系是什么?** - -可以用共享文档来简单描述:所有可以访问共享文档的账号就叫做节点,当然全节点需要同步共享文档,也就是拥有全部的区块数据区块就是共享文档。每个人更新了,所有人都可以查看最新的文档 - -### 1.3.3、密码学技术 ### - -为了实现数据的不可逆、不可篡改和可回溯,区块链技术采用了一系列密码学算法和技术,包括哈希算法、Merkle 树、非对称加密算法。 - -##### 哈希算法 ##### - -哈希算法是一个单向函数,可以将任意长度的输入数据转化为固定长度的输出数据(哈希值),哈希值就是这段输入数据唯一的数值表现。由于在计算上不可能找到哈希值相同而输入值不同的字符串,因此两段数据的哈希值相同,就可以认为这两段数据也是相同的,所以哈希算法常被用于对数据进行验证。 - -在区块链中,数据存储在区块里。每个区块都有一个区块头,区块头中存储了一个将该区块所有数据经过哈希算法得到的哈希值,同时,每个区块中还存储了前一个区块的哈希值,这样就形成了区块链。如果想要篡改某一个区块A中的数据,就会导致A的哈希值发生变化,后一个区块B就无法通过哈希值正确地指向A,这样篡改者又必须篡改B中的数据......也就是说,篡改者需要篡改被篡改的区块以及后面的所有区块,才能让所有的节点都接受篡改。 - -##### Merkle树 ##### - -Merkle树是一种树形结构,在区块链中,Merkle树的叶子节点是区块中数据的哈希值,非叶子节点是其子结点组合后的哈希值,这样由叶子节点开始逐层往上计算,最终形成一个Merkle根,记录在区块的头部,这样就可以保证每一笔交易都无法篡改。 - -
- -
-
Merkle 树
-##### 非对称加密技术 ##### - -非对称加密技术使用两个非对称密钥:公钥和私钥。公钥和私钥具有两个特点: - -1. 通过其中一个密钥加密信息后,使用另一个密钥才能解开 -2. 公钥一般可以公开,私钥则保密 - -在区块链中,非对称加密技术主要用于信息加密、数字签名和登录认证。在信息加密场景中,信息发送者A使用接收者B提供的公钥对信息进行加密,B收到加密的信息后再通过自己的私钥进行解密。再数字签名场景中,发送者A通过自己的私钥对信息进行加密,其他人通过A提供的公钥来对信息进行验证,证明信息确实是由A发出。在登录认证场景中,客户端使用私钥加密登录信息后进行发送,其他人通过客户端公钥来认证登录信息。 - -- RSA 算法 - - ​ RSA加密算法是最常用的非对称加密算法,CFCA在证书服务中离不了它。但是有不少新来的同事对它不太了解,恰好看到一本书中作者用实例对它进行了简化而生动的描述,使得高深的数学理论能够被容易地理解。 - ​ RSA是第一个比较完善的公开密钥算法,它既能用于加密,也能用于数字签名。RSA以它的三个发明者Ron Rivest, Adi Shamir, Leonard Adleman的名字首字母命名,这个算法经受住了多年深入的密码分析,虽然密码分析者既不能证明也不能否定RSA的安全性,但这恰恰说明该算法有一定的可信性,目前它已经成为最流行的公开密钥算法。 -   RSA的安全基于大数分解的难度。其公钥和私钥是一对大素数(100到200位十进制数或更大)的函数。从一个公钥和密文恢复出明文的难度,等价于分解两个大素数之积(这是公认的数学难题)。 - -- ECC 椭圆曲线算法 - - 具体可以参见此文章:[ECC椭圆曲线加密算法:介绍](https://zhuanlan.zhihu.com/p/36326221) - - - -### 1.3.4、共识机制 ### - -区块链系统是一个分布式系统,分布式系统要解决都首要问题就是一致性问题,也就是如何使多个孤立的节点达成共识。在中心化系统中,由于有一个中心服务器这样的“领导”来统一各个节点,因此达成一致性几乎没有问题。但在去中心化场景下,由于各个节点是相互独立的,就可能会出现许多不一致的问题,例如由于网络状况等因素部分节点可能会有延迟、故障甚至宕机,造成节点之间通信的不可靠,因此一致性问题是分布式系统中一个很令人头疼的问题。 - -由 Eirc Brewer 提出,Lynch 等人证明的 CAP 定理为解决分布式系统中的一致性问题提供了思路。CAP 定理的描述如下:在分布式系统中,**一致性**、**可用性**和**分区容错性**三者不可兼得。这三个术语的解释如下: - -* 一致性(**C**onsistency):所有节点在同一时刻拥有同样的值(等同于所有节点访问同一份最新的数据副本 -* 可用性(**A**vailability):每个请求都可以在有限时间内收到确定其是否成功的响应 -* 分区容错性(**P**artition tolerance):分区是指部分节点因为网络原因无法与其他节点达成一致。分区容错性是指由网络原因导致的系统分区不影响系统的正常运行。例如,由于网络原因系统被分为 A, B, C, D 四个区,A, B 中的节点无法正常工作,但 C, D 组成的分区仍能提供正常服务。 - -在某些场景下,对一致性、可用性和分区容错性中的某一个特性要求不高时,就可以考虑弱化该特性,来保证整个系统的容错能力。区块链中常见的共识机制的基本思路正是来自 CAP 定理,部分区块链应用中用到的共识机制如下表: - -| 共识机制 | 应用 | -| -------- | ---------------------------------- | -| PoW | 比特币、莱特币、以太坊的前三个阶段 | -| PoS | PeerCoin、NXT、以太坊的第四个阶段 | -| PBFT | Hyperledger Fabric | - -##### PoW(Proof of Work,工作量证明) ##### - -PoW 机制的大致流程如下: - -1. 向所有节点广播新交易和一个数学问题 -2. 最先解决了数学问题的节点将交易打包成区块,对全网广播 -3. 其他节点验证广播区块的节点是否解决了数学问题(完成了一定的工作量),验证通过则接受该区块,并将该区块的哈希值放入下一个区块中,表示承认该区块 - -由于在 PoW 机制中,区块的产生需要解决一个数学问题,也就是所谓的**挖矿**,这往往要消耗较大的算力和电力,因此节点们倾向于在**最长的链**的基础上添加区块,因为如果节点想在自己的链上添加新的区块,那么就需要重新计算 1 个或 $n$ 个这样的数学问题(每添加一个区块就需要计算一个)。因此在比特币中最长的链被认为是合法的链,这样节点间就形成了一套“共识”。 - -PoW 机制的优点是完全去中心化,缺点是需要依赖数学运算,资源的消耗会比其他的共识机制高,可监管性弱,同时每次达成共识需要全网共同参与运算,性能较低。 - -##### PoS(Proof of Stack,股权证明) ##### - -PoS 针对 PoW 的缺点做出了改进。PoS 要求参与者预先放置一些货币在区块链上用于换取“股权”,从而成为**验证者(Validator)**,验证者具有产生区块的权利。PoS 机制会按照存放货币的量和时间给验证者分配相应的利息,同时还引入了奖惩机制,打包错误区块的验证者将失去他的股权——即投入的货币以及产生区块的权利。PoS 机制的大致流程如下: - -1. 加入 PoS 机制的都是持币人,称为验证者 -2. PoS 算法根据验证者持币的多少在验证者中挑选出一个给予产生区块的权利 -3. 如果一定时间内没有产生区块,PoS 就挑选下一个验证者,给予产生区块的权利 -4. 如果某个验证者打包了一份欺诈性交易,PoS 将剥夺他的股权 - -PoS 的优点在于: - -1. 引入了利息,使得像比特币这样发币总数有限的通货紧缩系统在一定时间后不会“无币可发” -2. 引入了奖惩机制使节点的运行更加可控,同时更好地防止攻击 -3. 与 PoW 相比,不需要为了生成新区块而消耗大量电力和算力 -4. 与 PoW 相比,缩短了达成共识所需的时间 - -由于 PoS 机制需要用户已经持有一定数量的货币,没有提供在区块链应用创立初始阶段处理数字货币的方法,因此使用 PoS 机制的区块链应用会在发布时预先出售货币,或在初期采用 PoW,让矿工获得货币后再转换成 PoS,例如以太坊现阶段采用的是 PoW 机制,在第四阶段“宁静”(Serenity)中将过渡到 PoS。 - -##### 拜占庭将军问题(Byzantine Generals Problem) ##### - -拜占庭将军问题是分布式网络中的通信容错问题,可以描述为: - -> 一组拜占庭将军各领一支队伍共同围困一座城市。各支军队的行动策略限定为进攻或撤离两种。因为部分军队进攻而部分军队撤离可能会造成灾难性的后果,因此各将军决定通过投标来达成一致策略,即“共进退”。因为各将军位于城市不同方向,他们只能通过信使互相联系。在投票过程中每位将军都将自己的选择(进攻或撤退)通过信使分别通知其他所有将军,这样一来每位将军根据自己的投票和其他所有将军送来的信息就可以知道共同投票的结果,进而做出行动。 - -
- -
- - -拜占庭将军的问题在于,将军中可能出现叛徒。假设3名将军中有1名叛徒,2名忠诚将军一人投进攻票,一人投撤退票,这时叛徒可能会故意给投进攻的将军投进攻票,而给投撤退的将军投撤退票。这就导致一名将军带队发起进攻,而另外一名将军带队撤退。 - -另外,由于将军之间通过信使进行通讯,即使所有将军都忠诚,也不能排除信使被敌人截杀,甚至信使叛变等情况。 - -假设存在叛变将军或信使出问题等情况,如果忠诚将军仍然能够通过投票来决定他们的战略,便称系统达到了**拜占庭容错(Byzantine Fault Tolerance)**。 - -拜占庭问题对应到区块链中,将军就是节点,信使就是网络等通信系统,要解决的是存在恶意节点、网络错误等情况下系统的一致性问题。 - -**PBFT(Practical Byzantine Fault Tolerance)** 是第一个得到广泛应用且比较高效的拜占庭容错算法,能够在节点数量不小于 $n=3f+1$ 的情况下容忍 $f$ 个拜占庭节点(恶意节点)。 - - - -# 二、以太坊介绍 # - -首先我们要知道我们为什么要学习以太坊,主要有以下四个原因: - -* 以太坊是区块链2.0的代表,学习以太坊能了解到区块链技术的所有知识 -* 引入了智能合约,拓宽了区块链的应用场景 -* 对开发者友好、对用户友好,容易编写出简单的区块链应用,学习趣味性高 -* Solidity 语法与 Javascript、Go 等语言接近,易上手 - -## 2.1、以太坊简介 ## - -区块链技术常常被认为是自互联网诞生以来最具颠覆性的技术,然而,自比特币诞生后一直没有很好的区块链应用开发平台。想要在比特币基础上开发区块链应用是非常复杂繁琐的,因为比特币仅仅是一个加密数字货币系统,无法用来实现更广阔的业务需求。以太坊是目前使用最广泛的支持完备应用开发的共有区块链系统。 - -和比特币不同,比特币只适合加密数字货币场景,不具备图灵完备性,也缺乏保存实时状态的账户概念,以及存在 PoW 机制带来的效率和资源浪费的问题,而以太坊作为区块链2.0的代表,目标是扩展智能合约和建立一个去中心化应用平台,具有图灵完备的特性、更高效的共识机制、支持智能合约等多种应用场景,使得开发者能够很方便地在以太坊上开发出基于区块链的应用。 - -### 2.1.1、以太坊的发展 ### - -2014年, Vitalik Buterin 发表了文章《以太坊:一个下一代智能合约和去中心化应用平台》。同年,Buterin 在迈阿密比特币会议中宣布启动以太坊项目,并提出了多项创新性的区块链技术。2015年,以太坊CCO Stephan Tual 在官方博客上宣布以太坊系统诞生,主网上线。 - -以太坊发展至今经历了“前沿”(Frontier)、“家园”(Homestead)以及现在所处的“大都会”(Metropolis)三个阶段。第四阶段“宁静”(Serenity)将作为以太坊的最后一个阶段,目前尚未有计划发布日期。 - -### 2.1.2、以太坊的特点 ### - -以太坊团队和外界对以太坊的描述都是“世界计算机”,这代表它是一个开源的、全球的去中心化计算架构。它执行称为智能合约的程序,并使用区块链来同步和存储系统状态,以及使用名为以太币的加密数字货币来计量和约束执行操作的资源成本。同时,以太坊提供了一系列的接口,使得开发者能够通过以太坊来开发去中心化 Web 应用DApps。 - -### 2.1.3、智能合约 ### - -相比比特币,以太坊最大的特点就是引入了**智能合约**。智能合约本质上就是一段编写好的程序,可以在特定的条件下被触发并执行特定的操作。由于区块链具有不可逆和不可篡改的特点,因此智能合约与区块链结合后,就成了一份“强制执行”的合约。 - -以太坊能够作为一个去中心化应用平台和”世界计算机”,其核心就是智能合约。智能合约的引入,使得开发者能够实现许多(理论上是任何)业务逻辑。如果说比特币是通过区块链技术开发的特定计算器,那么引入了智能合约的以太坊就是基于区块链技术的通用计算机。可以简单的理解成:比特币的交易系统就是一份写死的智能合约,而以太坊则将智能合约的开发权限交给开发者。 - -以太坊提供了对智能合约的全面支持,包括编写智能合约编程语言 **Solidity** 和运行智能合约的**以太坊虚拟机(Ethereum Virtual Machine,EVM)**。 - -### 2.1.4、幽灵协议 ### - -幽灵合约的英文是“Greedy Heaviest Observed Subtree" (GHOST) protocol,在介绍幽灵协议之前,先介绍以太坊中的叔区块、叔块奖励和叔块引用奖励这三个概念。 - -
- -
- - -假设目前以太坊区块链中的区块高度(区块链上的区块个数)为6,现在产生了一笔新的交易,矿工A先将该笔交易打包成了区块 Block 7,在矿工A将 Block 7 广播到其他节点的这段时间里,矿工B和矿工C又分别产生了 Block 8 和 Block 9。Block 7、Block 8、Block 9 都指向 Block 6,即 Block 6 是他们的父区块。由于 Block 7 是最先产生的,因此 Block 7 被认为是有效区块,Block 8 和 Block 9 就是**叔区块**(作废区块)。 - -
- -
- - -现在链上的区块高度为7,在这基础上又产生了新的交易,并被打包成了 Block 10。在以太坊中,Block 10 除了可以引用它的父区块 Block 7 外,还可以引用叔区块 Block 8 和 Block 9。并且,Block 8 和 Block 9 的矿工会因此获得一笔奖励,称为**叔块奖励**,Block 10 的矿工除了基础奖励之外,由于引用了叔区块,还会获得一笔额外的**叔块引用奖励**。 - -**幽灵协议**是以太坊的一大创新。由于在比特币中的出块时间被设计为10分钟,而以太坊为了提高出块速度,将出块时间设计为12秒(实际14~15秒左右),这样的高速出块意味着高速确认,高速确认会带来区块的**高作废率**和**低安全性**。因为区块需要花一定的时间才能广播至全网,如果矿工 A 挖出了一个区块,而矿工 B 碰巧在 A 的区块扩散至 B 之前挖出了另一个区块,矿工 B 的区块就会作废并且没有对区块链的网络安全做出贡献。此外,这样的高速确认还会带来**中心化**的问题:如果 A 拥有全网 30% 的算力而 B 拥有 10% 的算力,那么 A 将会在 70% 的时间内都在产生作废区块,而 B 在 90% 的时间内都在产生作废区块,这样,B 永远追不上 A,后果是 A 通过其算力份额拥有对挖矿过程实际上的控制权,出现了算力垄断,弱化了去中心化。 - -幽灵协议正是为了解决上述问题而引入的,协议的主要内容如下: - -- 计算最长链时,不仅包括当前区块的父区块和祖区块,还包括祖先块的作废的后代区块(叔区块),将它们综合考虑来计算哪一个区块拥有支持其的最大工作量证明。这解决了网络安全性的问题 -- 以太坊付给以“叔区块”身份为新块确认作出贡献的废区块87.5%的奖励(叔块奖励),把它们纳入计算的“侄子区块”将获得奖励的12.5%(叔块引用奖励)。这就使得即使产生作废区块的矿工也能够参与区块链网络贡献并获得奖励,解决了中心化倾向的问题 -- 叔区块最深可以被其父母的第二代至第七代后辈区块引用。这样做是为了: - - 降低引用叔区块的计算复杂性 - - 过多的叔块引用奖励会剥夺矿工在主链上挖矿的激励,使得矿工有转向公开攻击者链上挖矿的倾向(即公开攻击者可能会恶意产生大量作废区块,无限引用将会诱使矿工转移到攻击者的链上,从而抛弃合法的主链) - - 计算表明带有激励的五层幽灵协议即使在出块时间为15s的情况下也实现了了95%以上的效率,而拥有25%算力的矿工从中心化得到的益处小于3% - -### 2.1.5、以太坊的组成部分 ### - -在以太坊中,包括了 P2P 网络、共识机制、交易、状态机、客户端这几个组成部分。 - -* P2P 网络:在以太坊主网上运行,可通过TCP端口30303访问,并运行称为 ÐΞVp2p 的协议。 -* 共识机制:以太坊目前使用名为 Ethash 的 POW 算法,计划在将来会过渡到称为 Casper 的 POS 算法。 -* 交易:以太坊中的交易本质上是网络消息,包括发送者、接收者、值和数据载荷(payload)。 -* 状态机:以太坊的状态转移由以太坊虚拟机(Ethereum Virtual Machine,EVM)处理,EVM 能够将智能合约编译成机器码并执行。 -* 客户端:用于用户和以太坊进行交互操作的软件实现,最突出的是 Go-Ethereum(Geth) 和 Parity。 - -### 2.1.6、以太坊中的概念 ### - -* 账户:以太坊中的账户类似于银行账户、应用账户,每个账户有一个20字节的地址。账户又分为**普通账户**(又叫外部账户,External Owned Account, EOA)和**合约账户**(Contract)。普通账户是由以太坊使用者创建的账户,包含地址、余额和随机数;合约账户是创建智能合约时建立的账户,包含存储空间和合约代码 -* 状态:状态是由账户和两个账户之间价值的转移以及信息的状态转换构成的 -* 地址:地址是一个账户 ECDSA 公钥的 Keccak 散列最右边的160位,通过地址可以在以太坊上接收或发送交易。在 Etherscan 上,可以通过地址来查询一个账户的信息 -* 交易:以太坊中的交易不仅包括发送和接收以太币,还包括向合约账户发送交易来调用合约代码、向空用户发送交易来生成以交易信息为代码块的合约账户 -* Gas:Gas 是以太坊中的一种机制,用于执行智能合约或交易操作的虚拟燃料。由于以太坊是图灵完备的,为了避免开发者无意或恶意编写出死循环等浪费资源或滥用资源的情况,以太坊中的每一笔交易都需支付一定的 Gas (燃料费),即需支付一定的以太币作为 Gas。Gas 的金额通常是由交易的发起者指定并支付的 -* 挖矿:和比特币类似,以太坊同样通过挖矿来产生区块。在以太坊目前的 PoW 机制下,每当一笔交易发出并广播,就会吸引矿工来将该交易打包成区块。每产生一个区块都会有一笔**固定奖励**给矿工,目前的固定奖励是3个以太。同时,区块中所有操作所需的 Gas 也会作为奖励给矿工。与比特币不同的是,以太坊中产生叔块的矿工可能会获得叔块奖励,引用叔块的矿工会获得叔块引用奖励 -* DApp(去中心化应用):通过智能合约,开发者能够设计想要的逻辑,相当于是网站的后端。而 DApp 则相当于是一个完整的网站(前端+后端),因此 DApp = 智能合约 + Web 前端。以太坊提供了一个名为 web3.js 的 Javascript 库,通过 web3.js 可以实现 Web 与以太坊区块链的交互和与智能合约的交互,方便开发者创建 DApp - -## 2.2、以太坊基础 ## - -### 2.2.1、以太坊中的货币 ### - -以太坊中的货币称为 **以太币**,单位为**以太(Ether)**,也称 ETH 或符号 Ξ。以太可以被分割为更小的单位,最小的单位是 wei,1 以太 = $10^18$ wei。以太币各单位的名称及之间的关系如下表: - -7 - - - -![image-20210219000835894](.\pic\image-20210219000835894.png) - -### 2.2.2、以太坊钱包 ### - -以太坊钱包是用于创建和广播交易的应用程序,常用的钱包有 - -* MetaMask,一款基于浏览器扩展的钱包,可以很方便地添加到 Chrome, FireFox 等支持扩展的浏览器中 -* Jaxx,一款跨平台、多币种的钱包 -* MyEtherWallet(MEW),一款基于 Web 的钱包,可以在任何浏览器中运行 -* Emerald Wallet,一款被设计来用于以太坊经典区块链的钱包,但也与其他以太坊区块链兼容 - -#### MetaMask 基础 #### - -以 Chrome 为例,访问 [Google 网上应用商店](https://chrome.google.com/webstore/category/extensions),搜索 MetaMask 并添加至 Chrome - -![image-20210219101124978](.\pic\image-20210219101124978.png) - - - -添加完成后 Chrome 会自动打开初始化页面 - - - -![image-20210219101226095](.\pic\image-20210219101226095.png) - - - -初次使用创建钱包 - - - -![image-20210219101300792](.\pic\image-20210219101300792.png) - - - -为钱包设置密码 - - - -![image-20210219101332089](.\pic\image-20210219101332089.png) - - - -创建密码后,MetaMask 会生成一串密语,密语是12个随机的英文单词,用于防止密码忘记。密语可以直接当成密码使用,因此需要妥善保管 - - - -![image-20210219102028033](.\pic\image-20210219102028033.png) - - - -注册完毕后就可以在 Chrome 地址栏右边的扩展程序栏点击 🦊 图标使用 MetaMask 了 - - - -![image-20210219102255927](.\pic\image-20210219102255927.png) - -![image-20210219102322360](.\pic\image-20210219102322360.png) - -#### 获取测试以太 #### - -除了以太坊主网以外,以太坊还提供了 Ropsten, Kovan, Rinkeby, Goerli 这几个公共测试网络,另外还支持局域网测试网络和自建测试网络。在这里我们切换到 Ropsten 测试网络 - -![image-20210219105616335](.\pic\image-20210219105616335.png) - - - -随后点击 **Buy** 按钮,点击**测试水管**下方的获取以太 - - - -![image-20210219105824087](.\pic\image-20210219105824087.png) - - - -在打开的页面中点击 request 1 ether from faucet 就可以得到1个测试以太,当然,可以多次点击。 - -![image-20210219105911910](.\pic\image-20210219105911910.png) - -![2021-02-19_110327](.\pic\2021-02-19_110327.png) - -测试以太仅供测试使用,除此之外没有任何价值,测试完毕后剩下的以太可以发送到水龙头账户捐赠给水龙头,以供他人测试使用。 - -## 2.3、以太坊交易的数据结构 - -在以太坊网络中,交易执行属于一个事务。具有原子性、一致性、隔离性、持久性特点。 - -- 原子性: 是不可分割的最小执行单位,要么做,要么不做。 -- 一致性: 同一笔交易执行,必然是将以太坊账本从一个一致性状态变到另一个一致性状态。 -- 隔离性: 交易执行途中不会受其他交易干扰。 -- 持久性: 一旦交易提交,则对以太坊账本的改变是永久性的。后续的操作不会对其有任何影响。 - -以太坊交易的本质是由外部拥有的账户发起的签名消息,由以太坊网络传输,并被序列化后记录在以太坊区块链上,**交易是唯一可以触发状态更改或导致合约在EVM中执行的事物** - -### 2.3.1、交易的数据结构 - -以太坊的数据结构主要可以分为四部分:`nonce`、`gas`、交易目标和消息(主要部分)、交易签名 - -![](pic/transaction-struct.png) - -开头是一个 uint64 类型的数字,称之为随机数。用于撤销交易、防止双花和修改以太坊账户的 Nonce 值。 - -第二部分是关于交易执行限制的设置,gas 为愿意供以太坊虚拟机运行的燃料上限。 `gasPrice` 是愿意支付的燃料单价。`gasPrcie * gas` 则为愿意为这笔交易支付的最高手续费。 - -第三部分是交易发送者输入以太坊虚拟机执行此交易的初始信息: 虚拟机操作对象(接收方 To)、从交易发送方转移到操作对象的资产(Value),以及虚拟机运行时入参(input)。其中 To 为空时,意味着虚拟机无可操作对象,**此时虚拟机将利用 input 内容部署一个新合约**。 - -第四部分是交易发送方对交易的签名结果,可以利用交易内容和签名结果反向推导出签名者,即交易发送方地址。以上总结如下: - -* `nonce`:由发起人EOA发出的序列号,用于防止交易消息重播。 -* `gas price`:交易发起人愿意支付的gas单价(wei)。 -* `start gas`:交易发起人愿意支付的最大gas量。 -* `to`:目的以太坊地址。 -* `value`:要发送到目的地的以太数量。 -* `data`:可变长度二进制数据负载(payload)。 -* `v,r,s`:发起人EOA的ECDSA签名的三个组成部分。 -* 交易消息的结构使用递归长度前缀(RLP)编码方案进行序列化,该方案专为在以太坊中准确和字节完美的数据序列化而创建。 - -### 2.3.2、交易中的`nonce` - -按以太坊黄皮书的定义, `nonce`是一个标量值,它等于从这个地址发送的交易数,或者对于关联code的帐户来说,是这个帐户创建合约的数量。因此`nonce`便有以下特征: - -* `nonce`不会明确存储为区块链中帐户状态的一部分。相反,它是通过计算发送地址的已确认交易的数量来动态计算的。 -* `nonce`值还用于防止错误计算账户余额。`nonce`强制来自任何地址的交易按顺序处理,没有间隔,无论节点接收它们的顺序如何。 -* 使用`nonce`确保所有节点计算相同的余额和正确的序列交易,等同于用于防止比特币“双重支付”(“重放攻击”)的机制。但是,由于以太坊跟踪账户余额并且不单独跟踪 `UTXO` ,因此只有在错误地计算账户余额时才会发生“双重支付”。`nonce`机制可以防止这种情况发生。 - -### 2.3.3、并发和`nonce` - -以太坊是一个允许操作(节点,客户端,DApps)并发的系统,但强制执行单例状态。例如,出块的时候只有一个系统状态。假如我们有多个独立的钱包应用或客户端,比如 MetaMask 和 Geth,它们可以使用相同的地址生成交易。如果我们希望它们都够同时发送交易,该怎么设置交易的`nonce`呢?一般有以下两种做法: - -* 用一台服务器为各个应用分配`nonce`,先来先服务——可能出现单点故障,并且失败的交易会将后续交易阻塞。 -* 生成交易后不分配`nonce`,也不签名,而是把它放入一个队列等待。另起一个节点跟踪`nonce`并签名交易。同样会有单点故障的可能,而且跟踪`nonce`和签名的节点是无法实现真正并发的。 - -### 2.3.4、交易中的`gas` - -Gas 中译是:瓦斯、汽油,代表一种可燃气体。 这形象地比喻以太坊的交易手续费计算模式,不同于比特币中**直接**支付比特币作为转账手续费, 以太坊视为一个去中心化的计算网络,当你发送Token、执行合约、转移以太币或者在此区块上干其他的时候,计算机在处理这笔交易时需要进行计算消耗网络资源,这样你必须支付燃油费购买燃料才能让计算机为你工作。最终燃料费作为手续费支付给矿工。 - -> 注:可以在Etherscan上查询gas price与confirmation time的关系,如下图 - -![image-20210223204943672](pic/gas.jpg) - -因为手续费等于`gasPrice * gasUsed`,用户在转账,特别是执行智能合约时 gasUsed 无法提前预知。 这样存在一个风险,当用户的交易涉及一个恶意的智能合约,该合约执行将消耗无限的燃料, 这样会导致交易方的余额全部消耗(恶意的智能合约有可能是程序Bug,如合约执行陷入一个死循环)。 - -为了避免合约中的错误引起不可预计的燃料消耗,用户需要在发送交易时设定允许消耗的燃料上限,即 gasLimit。 这样不管合约是否良好,最坏情况也只是消耗 gasLimit 量的燃料。 - -然而,一笔交易所必须支付的燃料已经在区块中通过该交易已执行的计算量记录。 如果你不想支出太多燃料,而故意设置过低的 gasLimit 是没太多帮助的。 你必须支付足够燃料来支付本交易所必要的计算资源。如果交易尚未执行完成,而燃料已用完, 将出现一个 `Out of Gas` 的错误。特别注意的是,即使交易失败,你也必须为已占用的计算资源所支付手续费。 比如,你通过合约给 TFBOYS 投票,设置 gasPrice=2 gwei,gasLimit=40000(实现投票需要40001的燃料开销), 最终你投票失败且仍然需要支付 40000*2 gwei= 80000 gwei= 0.00008 ETH。 - -另外,如果最终 gasUsed 低于 gasLimit,即燃料未用完。则剩余燃料(gasLimit - gasUsed )将在交易后退还给你。 比如你发送 1 Ether 到另一个账户B,设置 gas limit 为 400000,将有 400000 - 21000 返回给你。 - -> 注意:21000 是标准转账交易的gasUsed。因此一笔标准的转账交易你可以设置 gasLimit 为21000 - -## 2.4、以太坊账户 - -对比比特币的UTXO余额模型,以太坊使用“账户”余额模型。 以太坊丰富了账户内容,除余额外还能自定义存放任意多数据。 并利用账户数据的可维护性,构建智能合约账户。下面我们首先将比特币的UTXO余额模型与以太坊账户进行比较,说明其各自的优缺点以及适用性。 - -### 2.4.1、比特币UTXO和以太坊账户结构比较 - -在当前的区块链项目中,主要有两种记录保存方式,**一种是账户/余额模型,一种是UTXO模型**。比特币采用就是UTXO模型,以太坊、EOS等则采用的是账户/余额模型。 - - - -### 2.4.2、比特币UTXO - -UTXO是 Unspent Transaction Output的缩写,意思是**未花费的输出,**可以简单理解为还没有用掉的收款。比如韩梅梅收到一笔比特币,她没有用掉,这笔比特币对她来说就是一个UTXO。关于UTXO的具体介绍大家可以查看[这篇文章](https://zhuanlan.zhihu.com/p/74050135)。 - -**UTXO 核心设计思路是:它记录交易事件,而不记录最终状态。**要计算某个用户有多少比特币,就要对其钱包里所有的UTXO求和,得到结果就是他的持币数量。UTXO模型在转账交易时,是以UTXO为单位的,也就是说在支付时,调用的是整数倍UTXO,比如1个UTXO,3个UTXO,没有0.5个UTXO的说法。 - -* 比特币在基于UTXO的结构中存储有关用户余额的数据,系统的整个状态就是一组UTXO的集合,每个UTXO都有一个所有者和一个面值(就像不同的硬币),而交易会花费若干个输入的UTXO,并根据规则创建若干个新的UTXO -* 每个引用的输入必须有效并且尚未花费,对于一个交易,必须包含有每个输入的所有者匹配的签名,总输入必须大于等于总输出值。所以系统中用户的余额是用户具有私钥的UTXO的总值 - -### 2.4.3、以太坊账户 - -为什么以太坊不用UTXO呢?显然是因为麻烦,以太坊的做法更符合直觉,以太坊中的状态就是系统中所有账户的列表,每个账户都包含了一个余额和以太坊**特殊定义的数据**(代码和内部存储)。如果发送账户有足够多的余额来进行支付,则交易有效,在这种情况下发送账户先扣款,而收款账户将记入这笔收入。**如果接受账户有相关代码,则代码会自动运行,并且它的内部存储也可能被更改,或者代码还可能向其他账户发送额外的消息,这就会导致进一步的借贷资金关系。** - -### 2.4.4、优缺点比较 - -**比特币UTXO的优点**: - -* 更高程度的隐私:如果用户为他们收到的每笔交易使用新地址,那么通常很难将账户互相链接。这很大程度上适用于货币,但不太适用于任何dapps,因为dapps通常涉及跟踪和用户绑定的复杂状态,可能不存在像货币那样简单的用户状态划分方案 -* 潜在的可扩展性:UTXO在理论上更符合可扩展性要求,因为我们只需要依赖拥有UTXO的那些人去维护基于Merkle树的所有权证明就够了,即使包括所有者在内的每个人都决定忘记该数据,那么也只有所有者受到对应的UTXO的损失,不影响接下来的交易。而在账户模式中,如果每个人都丢失了与账户相对应的Merkle树的部分,那将会使得和该账户有关的消息完全无法处理,包括发币给它。 - -**以太坊账户模式的优点**: - -* 可以节省大量空间:不将UTXOs分开存储,而是合成一个账户;每个交易只需要一个输入、一个签名并产生一个输出 -* 更好的可替代性:货币本质上都是同质化、可替代的;UTXO的设计使得货币从来源分成了“可花费”和“不可花费”两类,这在实际应用中很难有对应模型 -* 更加简单:更容易编码和理解,特别是设计复杂脚本的时候,UTXO的脚本逻辑复杂时更令人费解 -* 便于维护持久轻节点:只要沿着特定方向扫描状态树,轻节点 可以很容易地随时访问账户相关的所有数据。而UTXO地每个交易都会使得状态引用发生改变,这对应节点来说长时间运行Dapp会有很大压力 - -### 2.4.5、总结 - -| | BitCoin | Ethereum | -| ------------ | ---------------- | ---------------- | -| **设计定位** | 现金系统 | 去中心化应用平台 | -| **数据组成** | 交易列表(账本) | 交易和账户状态 | -| **交易对象** | UTXO | Accounts | -| **代码控制** | 脚本 | 智能合约 | - -## 2.5、以太坊账户类型 - -以太坊作为智能合约操作平台,将账户划分为两类:外部账户(EOAs)和合约账户(contract account),下面分别做简要介绍: - - - -### 2.5.1、外部账户(EOA) - -外部账户是由人来控制的,也就是常规理解的普通账户,外部账户包含以太币余额,主要作用就是发送交易(是广义的交易,包括转币和触发合约代码),是由用户私钥控制的,没有关联代码,所有在以太坊上交易的发起者都是外部账户。 - -外部账户特点总结: - -1. 拥有以太余额。 -2. 能发送交易,包括转账和执行合约代码。 -3. 被私钥控制。 -4. 没有相关的可执行代码。 - -### 2.5.2、合约账户(CA) - -合约账户有时也叫内部账户,有对应的以太币余额和关联代码,它是由代码控制的,可以通过交易或来自其他合约的调用消息来触发代码执行,执行代码时可以操作自己的存储空间,也可以调用其他合约 - -合约账户特点总结: - -1. 拥有以太余额。 -2. 有相关的可执行代码(合约代码)。 -3. 合约代码能够被交易或者其他合约消息调用。 -4. 合约代码被执行时可再调用其他合约代码。 -5. 合约代码被执行时可执行复杂运算,可永久地改变合约内部的数据存储。 - - -如果大家对概念还理解不深可以先尝试学习后面部分,本教程内容有限,推荐大家有精力阅读以下读物: -- [区块链学习的书籍](https://www.zhihu.com/question/61156867) -- [区块链入门教程](https://www.ruanyifeng.com/blog/2017/12/blockchain-tutorial.html) -- [IBM教程](https://developer.ibm.com/zh/technologies/blockchain/tutorials/) - -**参考自:** -1. [比特币白皮书]https://www.8btc.com/wiki/bitcoin-a-peer-to-peer-electronic-cash-system) -2. [以太坊白皮书](https://ethfans.org/posts/ethereum-whitepaper) -3. [超级账本白皮书](https://www.chainnode.com/doc/399) -4. [闪电网络白皮书](https://www.chainnode.com/doc/399) \ No newline at end of file diff --git a/Blockchain/part2_Solidity基础.md b/Blockchain/part2_Solidity基础.md deleted file mode 100644 index 2ed530e..0000000 --- a/Blockchain/part2_Solidity基础.md +++ /dev/null @@ -1,1013 +0,0 @@ - 注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 - -# Solidity 入门教学 - -## 1、 简介 - -### 1.1 Solidity是什么 -- Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。这门语言受到了 C++,Python 和 Javascript 语言的影响,设计的目的是能在以太坊虚拟机(EVM)上运行。 -- Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。 -- 内含的类型除了常见编程语言中的标准类型,还包括 `address`等以太坊独有的类型,Solidity 源码文件通常以 .sol 作为扩展名 -- 目前尝试 Solidity 编程的推荐方式是使用 Remix。Remix是一个基于 Web 浏览器的 IDE,它可以让你编写 Solidity 智能合约,然后部署并运行该智能合约。 - -### 1.2 Solidity语言特性 - -Solidity的语法接近于JavaScript,是一种面向对象的语言。但作为一种真正意义上运行在网络上的去中心合约,它又有很多的不同: -- 以太坊底层基于帐户,而不是 [UTXO](https://cloud.tencent.com/developer/article/1367743),所以增加了一个特殊的address 的数据类型用于定位用户和合约账户。 -- 语言内嵌框架支持支付。提供了 `payable` 等关键字,可以在语言层面直接支持支付。 -- 使用区块链进行数据存储。数据的每一个状态都可以永久存储,所以在使用时需要确定变量使用内存,还是区块链存储。 -- 运行环境是在去中心化的网络上,所以需要强调合约或函数执行的调用的方式。 -- 不同的异常机制。一旦出现异常,所有的执行都将会被回撤,这主要是为了保证合约执行的原子性,以避免中间状态出现的数据不一致。 - -### 1.3 Solidity源码和智能合约 - -Solidity 源代码要成为可以运行在以太坊上的智能合约需要经历如下的 - -**步骤:** -1. 用 Solidity 编写的智能合约源代码需要先使用编译器编译为字节码(Bytecode),编译过程中会同时产生智能合约的二进制接口规范(Application Binary Interface,简称为ABI); -2. 通过交易(Transaction)的方式将字节码部署到以太坊网络,每次成功部署都会产生一个新的智能合约账户; -3. 使用 Javascript 编写的 DApp 通常通过 web3.js + ABI去调用智能合约中的函数来实现数据的读取和修改。 - -### 1.4 合约结构 - -- 状态变量(State Variables)作为合约状态的一部分,值会永久保存在存储空间内。 -- 函数(Functions)合约中可执行的代码块。 -- 函数修饰器(Function Modifiers)在函数声明中,用来补充修饰函数的语义。 -- 事件(Events)非常方便的 EVM 日志工具接口。 - - -## 2、 Solidity编译器安装以及简单使用 - -Remix 是一个开源的 IDE,是一个浏览器在线编辑器。作为 Solidity 智能合约开发环境,Solidity IDE Remix(在线浏览器编辑器)提供基本的编译、部署至本地或测试网络、执行合约等功能。 - -### 2.1 remix安装以及使用 - -1. **浏览器端配置** - -在浏览器端有俩个选择,分别为英文版与中文版(有些许差别) - -- Remix中文版地址:[http://remix.hubwiz.com](http://remix.hubwiz.com) - -- Remix英文版地址(**推荐**):[https://remix.ethereum.org/](https://remix.ethereum.org/) - -**PS.可能需要科学上网** - -
- -
-
图 1
-
- -下面都以`英文版`为例子介绍 - -1、**浏览器输入 [https://remix.ethereum.org/](https://remix.ethereum.org/)** - -如果出现加载慢,加载不完全的情况,刷新几次即可 - -2、左侧可以看到我们所有的文件,下面是我们的remix控制台 - -
- -
-
图 2
-
- -上图小图标从左到右依次为: -- 创建新文件 -- 创建新文件夹 -- Github代码片段分享 -- 表示打开一个本地文件 - -控制台图片如下: - -
- -
-
图 3
-
- -- 1 从左至右表示隐藏控制台、清除控制台输出、pending的交易数量 -- 2 表示监听所有交易 -- 3 表示搜索框 -- 4 表示输出区域 -- 5 表示使用JavaScript与以太坊交互的区域,可以使用Web3对象 - - -3、点击文件样式图标输入我们的文件名即可(以.sol为后缀) - -
- -
-
图 4
-
-
- -4、安装必要的插件 - -点击插件管理器,页面中为这个图标 - -
- -
-
图 5
-
- -
- - -- 安装compiler - - 搜索关键字compiler -
- -
-
图 6
-
- -5、写一个简单的样例 - -```javascript -pragma solidity ^0.4.0; - -contract SimpleStorage { - uint storedData; - - function set(uint x) public { - storedData = x; - } - - function get() public view returns (uint) { - return storedData; - } -} -``` - -第一行就是告诉大家源代码使用Solidity版本0.4.0写的,并且使用0.4.0以上版本运行也没问题(最高到0.5.0,但是不包含0.5.0)。这是为了确保合约不会在新的编译器版本中突然行为异常。关键字 `pragma` 的含义是,一般来说,pragmas(编译指令)是告知编译器如何处理源代码的指令的。 - -Solidity中合约的含义就是一组代码(它的 函数 )和数据(它的 状态 ),它们位于以太坊区块链的一个特定地址上。 代码行 `uint storedData`; 声明一个类型为 `uint` (256位无符号整数)的状态变量,叫做 `storedData` 。 你可以认为它是数据库里的一个位置,可以通过调用管理数据库代码的函数进行查询和变更。对于以太坊来说,上述的合约就是拥有合约(owning contract)。在这种情况下,函数 `set` 和 `get` 可以用来变更或取出变量的值。 - -要访问一个状态变量,并不需要像 `this.` 这样的前缀,虽然这是其他语言常见的做法。 - -该合约能完成的事情并不多(由于以太坊构建的基础架构的原因):它能允许任何人在合约中存储一个单独的数字,并且这个数字可以被世界上任何人访问,且没有可行的办法阻止你发布这个数字。当然,任何人都可以再次调用 `set` ,传入不同的值,覆盖你的数字,但是这个数字仍会被存储在区块链的历史记录中。 - -
- -
-
图 7
-
- -点击`compile test.sol`,可以看到编译按钮,建议将`Auto compile`打钩(自动编译),之后会在编译图标上看到一个以绿色为背景的对勾。 - -编译组件说明: -- `Compiler`可以选择Solidity的编译器版本 -- `Language`可以选择编程语言 -- `EVM Version`可以选择EVM虚拟机版本 -- `Auto compile`可以设置自动编译,修改完代码后自动执行编译操作 -- `Enable optimization`可以设置对编译进行优化 -- `Hide warnings`可以设置隐藏警告信息。 -- `Contract`选择需要编译的合约 -- `Publish on Swarm`和`Publish on Ipfs`分别将合约上传到Swarm和Ipfs这两个分布式文件系统上去 -- `Compilation Details`很重要,可以查看编译的信息,包括ABI、字节码、函数Hash等 -- `ABI`和`Bytecode`分别复制ABI和字节码。 -- 再下面的部分空白用来显示编译的Warnings和Errors。 - -我们点击`Compilation Details`就能看到编译之后的一些信息,如下图所示(部分) - -
- -
-
图 8
-
-- `NAME`:合约名 -- `METADATA`:一些编译相关的信息,比如版本、所用的语言、设置等 -- `BYTECODE`:写入区块的字节码 -- `ABI`:此智能合约对应的 ABI ,也就是我们合约里面定义的一些接口 -- `WEB3DEPLOY`:智能合约编译之后的发布命令,这个就是比较重要的,之后的web3就是调用这段命令来部署合约的 -- `METADATAHASH`:数据的一个哈希值 -- `SWARMLOCATION`:Swarm网络的一个地址 -- `FUNCTIONHASHES`:合约定义的方法的hash,其实我们执行合约的时候就是通过这个hash去找到对应的方法进行执行的 -- `GASESTIMATES`:关于矿工费的一个预算,在ETH上进行合约的部署,执行等都是需要矿工费的。一般合约代码越多矿工费越高。 - - -点击下面的run图标,可以看到部署,以及账户信息,环境等等 - -
- -
-
图 9
-
- -点击deploy之后天可以看到自己的合约已经部署完成,打开之后可以看见我们写的函数`set`,`get`了,给`set`函数输入一个值,点击`get`会得到相应的值 - -
- -
-
图 10
-
- -- `Environment` 表示合约部署的环境。`Javascript VM`是虚拟了一个节点,而`Injected Web3`和`Web3 Provider`则真正连接一个节点。 -- `Account`代表不同的虚拟账户,每个虚拟账户每个有 100 ETH -- `Deploy`表示合约部署按钮 -- `Deployed Contracts`表示已经部署的合约 - - - -中文版界面与英文版界面有些许不一致,但都大同小异,想了解同学可以查看本博客(界面与中文版大致相同): -[Solidity语言编辑器REMIX指导大全](https://cloud.tencent.com/developer/article/1182404) - -2. **本地配置:** - - [win下](https://cloud.tencent.com/developer/article/1374376) - - [ubuntu下](https://blog.csdn.net/qq_41944960/article/details/100134020) - - -3. **Docker** - -我们为编译器提供了最新的docker构建。 stable 仓库里的是已发布的版本,nightly 仓库则是在开发分支中的带有不稳定变更的版本。 - -```script -docker run ethereum/solc:stable solc --version -``` - -目前,docker 镜像只含有 solc 的可执行程序,因此你需要额外的工作去把源代码和输出目录连接起来。 - -## 3、Solidity基础操作 - -**由于篇幅有限,以下只会讲解一些较基础、重要的概念(足够后面使用),有些可能会一带而过或者“忽略”,如果大家途中有没太明白地方建议先百度、Google,或者查看此教程[Solifity中文文档](https://solidity-cn.readthedocs.io/zh/develop/index.html)、[Solidity英文文档](https://remix-ide.readthedocs.io/en/latest/index.html)** - -### 3.1 Solidity源文件布局 - -**源文件可以被版本杂注pragma所注解,表明要求的编译器版本** -- 例如: -```javascript -pragma solidity ^0.4.0; -``` -这样,源文件将既不允许低于 0.4.0 版本的编译器编译, 也不允许高于(包含) 0.5.0 版本的编译器编译(第二个条件因使用 ^ 被添加)。 这种做法的考虑是,编译器在 0.5.0 版本之前不会有重大变更,所以可确保源代码始终按预期被编译。 上面例子中不固定编译器的具体版本号,因此编译器的补丁版也可以使用。 - -**import(导入其它源文件)** -- Solidity 所支持的导入语句import,语法同 JavaScript(从ES6 起)非常类似 - -```javascript -import "filename"; -``` -从“filename”中导入所有的全局符号到当前全局作用域中 -```javascript -import * as symbolName from "filename"; -``` -创建一个新的全局符号 symbolName,其成员均来自 “filename”中全局符号 -```javascript -import {symbol1 as alias, symbol2} from "filename"; -``` -创建新的全局符号 alias 和 symbol2,分别从 "filename" 引用 symbol1 和 symbol2 -```javascript -import "filename" as symbolName; -``` -这条语句等同于 import * as symbolName from "filename"; - -**注释** - -可以使用单行注释(//)和多行注释(/*...*/) - -```javascript -// 这是一个单行注释。 - -/* -这是一个 -多行注释。 -*/ -``` - -### 3.2 数据类型与运算符 - -### 3.2.1 Solidity值类型介绍 - -- **布尔(bool)**: - - -可能的取值为字符常量值 true 或 false - -例子: -```javascript -pragma solidity ^0.4.0; - -contract helloworld { - bool boola=true; //声明一个布尔类型的值,只用一个等号 - function booltesta() public view returns(bool){ - return boola; - } - - function booltestb(int a,int b) public pure returns(bool){ - return a==b; - } -} -``` -
- -
-
图 11
-
- -- **整型(int/uint)**: - -`int` / `uint` :分别表示有符号和无符号的不同位数的整型变量。 支持关键字 `uint8` 到 `uint256` (无符号,从 8 位到 256 位)以及 `int8` 到 `int256`,以 8 位为步长递增。 `uint` 和 `int` 分别是 `uint256` 和 `int256` 的别名。 - -- **定长浮点型(fixed / ufixed)**: - -`fixed `/ `ufixed`:表示各种大小的有符号和无符号的定长浮点型。 在关键字 `ufixedMxN` 和 `fixedMxN` 中,`M` 表示该类型占用的位数,`N` 表示可用的小数位数。 `M `必须能整除 8,即 8 到 256 位。 `N `则可以是从 0 到 80 之间的任意数。 `ufixed` 和 `fixed` 分别是 `ufixed128x19` 和 `fixed128x19` 的别名。 - -- **地址(address 重点,后面细讲)**: - -地址类型存储一个 20 字节的值(以太坊地址的大小)。 地址类型也有成员变量,并作为所有合约的基础。 - - **地址类型成员变量**:`balance` 和 `transfer` - - 可以使用 balance 属性来查询一个地址的余额, 也可以使用 transfer 函数向一个地址发送 以太币 (以 wei 为单位): - - ```javascript -address x = 0x123; -address myAddress = this; -if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); - ``` - -注:如果 `x` 是一个合约地址,它的代码(更具体来说是它的 fallback 函数,如果有的话)会跟 `transfer` 函数调用一起执行(这是 EVM 的一个特性,无法阻止)。 如果在执行过程中用光了 gas 或者因为任何原因执行失败,以太币 交易会被打回,当前的合约也会在终止的同时抛出异常。 - -- **定长字节数组**: - -关键字有 bytes1, bytes2, bytes3, ..., bytes32 -`.length` 表示这个字节数组的长度(只读). - -注:可以将 `byte[]` 当作字节数组使用,但这种方式非常浪费存储空间,准确来说,是在传入调用时,每个元素会浪费 31 字节。 更好地做法是使用 `bytes`。 - - -- **变长字节数组** - -`bytes`:变长字节数组。它并不是值类型。 - -`string`:变长 UTF-8 编码字符串类型。并不是值类型。 - - -- **地址字面常数(Address Literals)** - -比如像 `0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF` 这样的通过了地址校验和测试的十六进制字面常数属于 `address` 类型。 长度在 39 到 41 个数字的,没有通过校验和测试而产生了一个警告的十六进制字面常数视为正常的有理数字面常数。 - - - -- **有理数和整数字面常数** - -整数字面常数由范围在 0-9 的一串数字组成,表现成十进制。 例如,69 表示数字 69。 Solidity 中是没有八进制的,因此前置 0 是无效的。 - -十进制小数字面常数带有一个 .,至少在其一边会有一个数字。 比如:`1.,.1`,和 `1.3`。 - -科学符号也是支持的,尽管指数必须是整数,但底数可以是小数。 比如:`2e10, -2e10, 2e-10, 2.5e1`。 - -数值字面常数表达式本身支持任意精度,除非它们被转换成了非字面常数类型(也就是说,当它们出现在非字面常数表达式中时就会发生转换)。 这意味着在数值常量表达式中, 计算不会溢出而除法也不会截断。 - -例如, `(2**800 + 1) - 2**800` 的结果是字面常数 1 (属于 `uint8` 类型),尽管计算的中间结果已经超过了 以太坊虚拟机 的机器字长度。 此外, `.5 * 8` 的结果是整型 4 (尽管有非整型参与了计算) - -- **字符串字面常数** - -字符串字面常数是指由双引号或单引号引起来的字符串(`"foo" `或者 `'bar'`)。 不像在 C 语言中那样带有结束符;`"foo"` 相当于 3 个字节而不是 4 个。 和整数字面常数一样,字符串字面常数的类型也可以发生改变,但它们可以隐式地转换成 `bytes1,……,bytes32`,如果合适的话,还可以转换成 `bytes` 以及 `string`。 - -- **十六进制字面常数** - -十六进制字面常数以关键字 `hex` 打头,后面紧跟着用单引号或双引号引起来的字符串(例如,`hex"001122FF"`)。 字符串的内容必须是一个十六进制的字符串,它们的值将使用二进制表示。 - -- **枚举(enum)**: - -一种用户可以定义类型的方法,与C语言类似,默认从0开始递增,一般用来模拟合约的状态 - -```javascript -pragma solidity ^0.4.16; - -contract test { - enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }; - ActionChoices choice; - ActionChoices constant defaultChoice = ActionChoices.GoStraight; - - function setGoStraight() public { - choice = ActionChoices.GoStraight; - } - - // 由于枚举类型不属于 |ABI| 的一部分,因此对于所有来自 Solidity 外部的调用, - // "getChoice" 的签名会自动被改成 "getChoice() returns (uint8)"。 - // 整数类型的大小已经足够存储所有枚举类型的值,随着值的个数增加, - // 可以逐渐使用 `uint16` 或更大的整数类型。 - function getChoice() public view returns (ActionChoices) { - return choice; - } - - function getDefaultChoice() public pure returns (uint) { - return uint(defaultChoice); - } -} -``` - -- **函数(function)**: - -函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量,也可以将一个函数作为参数进行传递,还能在函数调用中返回函数类型变量。 函数类型有两类:- 内部(`internal`) 函数和 外部(`external`) 函数: - -内部函数只能在当前合约内被调用(更具体来说,在当前代码块内,包括内部库函数和继承的函数中),因为它们不能在当前合约上下文的外部被执行。 调用一个内部函数是通过跳转到它的入口标签来实现的,就像在当前合约的内部调用一个函数。 - -外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。 - -函数类型表示成如下的形式 - -
- -
-
图 12
-
- -```javascript -function () {internal|external} [pure|constant|view|payable] [returns ()] -``` - -与参数类型相反,返回类型不能为空 —— 如果函数类型不需要返回,则需要删除整个 `returns ()` 部分。 - -函数类型默认是内部函数,因此不需要声明 `internal` 关键字。 与此相反的是,合约中的函数本身默认是 `public `的,只有当它被当做类型名称时,默认才是内部函数。 - -有两种方法可以访问当前合约中的函数:一种是直接使用它的名字,`f` ,另一种是使用 `this.f` 。 前者适用于内部函数,后者适用于外部函数。 - -如果当函数类型的变量还没有初始化时就调用它的话会引发一个异常。 如果在一个函数被 `delete` 之后调用它也会发生相同的情况。 - -如果外部函数类型在 Solidity 的上下文环境以外的地方使用,它们会被视为 `function` 类型。 该类型将函数地址紧跟其函数标识一起编码为一个 `bytes24` 类型。 - -请注意,当前合约的 public 函数既可以被当作内部函数也可以被当作外部函数使用。 如果想将一个函数当作内部函数使用,就用 `f` 调用,如果想将其当作外部函数,使用 `this.f` 。 - -**Solidity函数可见性** - -函数的可见性可以指定为 external,public ,internal 或者 private;对于状态变量,不能设置为 external ,默认是 internal。 - -- external :外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。 一个外部函数 f不能从内部调用(即 f 不起作用,但 this.f() 可以)。 当收到大量数据的时候,外部函数有时候会更有效率。 -- public :public 函数是合约接口的一部分,可以在内部或通过消息调用。对于 public 状态变量, 会自动生成一个 getter 函数。 -- internal :这些函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不使用 this 调用。 -- private :private 函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。 - -**Solidity函数状态可变性** - -- pure:纯函数,不允许修改或访问状态 -- view:不允许修改状态 -- payable:允许从消息调用中接收以太币Ether 。 -- constant:与view相同,一般只修饰状态变量,不允许赋值(除初始化以外) - -**内部函数调用** - -当前合约中的函数可以直接(“从内部”)调用,也可以递归调用,就像下边这个荒谬的例子一样 -```javascript -pragma solidity ^0.4.16; - -contract C { - function g(uint a) public pure returns (uint ret) { return f(); } - function f() internal pure returns (uint ret) { return g(7) + f(); } -} -``` -这些函数调用在 EVM 中被解释为简单的跳转。这样做的效果就是当前内存不会被清除,也就是说,通过内部调用在函数之间传递内存引用是非常有效的。 - -**外部函数调用** - -表达式 `this.g(8)`; 和 `c.g(2)`; (其中 c 是合约实例)也是有效的函数调用,但是这种情况下,函数将会通过一个消息调用来被“外部调用”,而不是直接的跳转。 请注意,不可以在构造函数中通过 this 来调用函数,因为此时真实的合约实例还没有被创建。 - -如果想要调用其他合约的函数,需要外部调用。对于一个外部调用,所有的函数参数都需要被复制到内存。 - -当调用其他合约的函数时,随函数调用发送的 Wei 和 gas 的数量可以分别由特定选项 `.value()` 和 `.gas()` 指定: - -```javascript -pragma solidity ^0.4.0; - -contract InfoFeed { - function info() public payable returns (uint ret) { return 42; } -} - -contract Consumer { - InfoFeed feed; - function setFeed(address addr) public { feed = InfoFeed(addr); } - function callFeed() public { feed.info.value(10).gas(800)(); } -} -``` -`payable` 修饰符要用于修饰 `info`,否则,.`value()` 选项将不可用。 - -注意,表达式 `InfoFeed(addr)` 进行了一个的显式类型转换,说明”我们知道给定地址的合约类型是 `InfoFeed` “并且这不会执行构造函数。 显式类型转换需要谨慎处理。绝对不要在一个你不清楚类型的合约上执行函数调用。 - -我们也可以直接使用 `function setFeed(InfoFeed _feed) { feed = _feed; }` 。 注意一个事实,`feed.info.value(10).gas(800)` 只(局部地)设置了与函数调用一起发送的 Wei 值和 gas 的数量,只有最后的圆括号执行了真正的调用。 - -如果被调函数所在合约不存在(也就是账户中不包含代码)或者被调用合约本身抛出异常或者 gas 用完等,函数调用会抛出异常。 - - -### 3.2.2 引用类型介绍 - -比起之前讨论过的值类型,在处理复杂的类型(即占用的空间超过 256 位的类型)时,我们需要更加谨慎。 由于拷贝这些类型变量的开销相当大,我们不得不考虑它的存储位置,是将它们保存在 **内存** (并不是永久存储)中, 还是 **存储** (保存状态变量的地方)中。 - -- **数据位置** - -所有的复杂类型,即 **数组** 和 **结构** 类型,都有一个额外属性,“数据位置”,说明数据是保存在 **内存** 中还是 **存储** 中。 根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后增加关键字 `storage` 或 `memory` 进行修改。 函数参数(包括返回的参数)的数据位置默认是 `memory`, 局部变量的数据位置默认是 `storage`,状态变量的数据位置强制是 `storage`。 - -也存在第三种数据位置, `calldata` ,这是一块只读的,且不会永久存储的位置,用来存储函数参数。 外部函数的参数(非返回参数)的数据位置被强制指定为 `calldata `,效果跟 `memory` 差不多。 - -例子: -```javascript -pragma solidity ^0.4.0; - -contract C { - uint[] x; // x 的数据存储位置是 storage - - // memoryArray 的数据存储位置是 memory - function f(uint[] memoryArray) public { - x = memoryArray; // 将整个数组拷贝到 storage 中,可行 - var y = x; // 分配一个指针(其中 y 的数据存储位置是 storage),可行 - y[7]; // 返回第 8 个元素,可行 - y.length = 2; // 通过 y 修改 x,可行 - delete x; // 清除数组,同时修改 y,可行 - // 下面的就不可行了;需要在 storage 中创建新的未命名的临时数组, / - // 但 storage 是“静态”分配的: - // y = memoryArray; - // 下面这一行也不可行,因为这会“重置”指针, - // 但并没有可以让它指向的合适的存储位置。 - // delete y; - - g(x); // 调用 g 函数,同时移交对 x 的引用 - h(x); // 调用 h 函数,同时在 memory 中创建一个独立的临时拷贝 - } - - function g(uint[] storage storageArray) internal {} - function h(uint[] memoryArray) public {} -} -``` -归纳: - -强制指定的数据位置: - 1. 外部函数的参数(不包括返回参数): calldata - 2. 状态变量: storage - -默认数据位置: -1. 函数参数(包括返回参数): memory -2. 所有其它局部变量: storage - - -- **数组** - -数组可以在声明时指定长度,也可以动态调整大小。 对于 **存储** 的数组来说,元素类型可以是任意的(即元素也可以是数组类型,映射类型或者结构体)。 对于 **内存** 的数组来说,元素类型不能是映射类型,如果作为 `public` 函数的参数,它只能是 `ABI` 类型。 - -一个元素类型为 `T`,固定长度为 `k` 的数组可以声明为 `T[k]`,而动态数组声明为 `T[]`。 - -举个例子,一个长度为 5,元素类型为 `uint` 的动态数组的数组,应声明为 `uint[][5]` (注意这里跟其它语言比,数组长度的声明位置是反的)。 要访问第三个动态数组的第二个元素,你应该使用 `x[2][1]`(数组下标是从 0 开始的,且访问数组时的下标顺序与声明时相反,也就是说,`x[2]` 是从右边减少了一级)。。 - -`bytes` 和 `string` 类型的变量是特殊的数组。 `bytes` 类似于 `byte[]`,但它在 `calldata` 中会被“紧打包”(译者注:将元素连续地存在一起,不会按每 32 字节一单元的方式来存放)。 `string` 与 `bytes` 相同,但(暂时)不允许用长度或索引来访问。 - -注: -如果想要访问以字节表示的字符串 s,请使用 `bytes(s)`.`length / bytes(s)[7] = 'x'`;。 注意这时你访问的是 `UTF-8` 形式的低级` bytes` 类型,而不是单个的字符。 - -**成员** - -`length`: - -数组有 length 成员变量表示当前数组的长度。 动态数组可以在 **存储** (而不是 **内存** )中通过改变成员变量 .length 改变数组大小。 并不能通过访问超出当前数组长度的方式实现自动扩展数组的长度。 一经创建,**内存** 数组的大小就是固定的(但却是动态的,也就是说,它依赖于运行时的参数)。 -`push`: - 变长的 **存储** 数组以及 bytes 类型(而不是 string 类型)都有一个叫做 push 的成员函数,它用来附加新的元素到数组末尾。 这个函数将返回新的数组长度。 - -例子: -```javascript -pragma solidity ^0.4.16; - -contract ArrayContract { - uint[2**20] m_aLotOfIntegers; - // 注意下面的代码并不是一对动态数组, - // 而是一个数组元素为一对变量的动态数组(也就是数组元素为长度为 2 的定长数组的动态数组)。 - bool[2][] m_pairsOfFlags; - // newPairs 存储在 memory 中 —— 函数参数默认的存储位置 - - function setAllFlagPairs(bool[2][] newPairs) public { - // 向一个 storage 的数组赋值会替代整个数组 - m_pairsOfFlags = newPairs; - } - - function setFlagPair(uint index, bool flagA, bool flagB) public { - // 访问一个不存在的数组下标会引发一个异常 - m_pairsOfFlags[index][0] = flagA; - m_pairsOfFlags[index][1] = flagB; - } - - function changeFlagArraySize(uint newSize) public { - // 如果 newSize 更小,那么超出的元素会被清除 - m_pairsOfFlags.length = newSize; - } - - function clear() public { - // 这些代码会将数组全部清空 - delete m_pairsOfFlags; - delete m_aLotOfIntegers; - // 这里也是实现同样的功能 - m_pairsOfFlags.length = 0; - } - - bytes m_byteData; - - function byteArrays(bytes data) public { - // 字节的数组(语言意义中的 byte 的复数 ``bytes``)不一样,因为它们不是填充式存储的, - // 但可以当作和 "uint8[]" 一样对待 - m_byteData = data; - m_byteData.length += 7; - m_byteData[3] = byte(8); - delete m_byteData[2]; - } - - function addFlag(bool[2] flag) public returns (uint) { - return m_pairsOfFlags.push(flag); - } - - function createMemoryArray(uint size) public pure returns (bytes) { - // 使用 `new` 创建动态 memory 数组: - uint[2][] memory arrayOfPairs = new uint[2][](size); - // 创建一个动态字节数组: - bytes memory b = new bytes(200); - for (uint i = 0; i < b.length; i++) - b[i] = byte(i); - return b; - } -} -``` - -- **结构体** - -Solidity 支持通过构造结构体的形式定义新的类型,以下是一个结构体的示例: -```c++ -struct Funder { - address addr; - uint amount; -} - -struct Campaign { - address beneficiary; - uint fundingGoal; - uint numFunders; - uint amount; - mapping (uint => Funder) funders; -} -``` -- **映射** -映射类型在声明时的形式为 `mapping(_KeyType => _ValueType)`。 其中 `_KeyType` 可以是除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。 `_ValueType` 可以是包括映射类型在内的任何类型。 - -映射可以视作 哈希表 ,它们在实际的初始化过程中创建每个可能的 key, 并将其映射到字节形式全是零的值:一个类型的 默认值。然而下面是映射与哈希表不同的地方: 在映射中,实际上并不存储 key,而是存储它的 `keccak256` 哈希值,从而便于查询实际的值。 - -正因为如此,映射是没有长度的,也没有 `key` 的集合或 `value` 的集合的概念。 - -只有状态变量(或者在 internal 函数中的对于存储变量的引用)可以使用映射类型。。 - -可以将映射声明为 `public`,然后来让 Solidity 创建一个 getter。` _KeyType` 将成为 getter 的必须参数,并且 getter 会返回 `_ValueType`。 - -`_ValueType` 也可以是一个映射。这时在使用 getter 时将将需要递归地传入每个 `_KeyType `参数。 - -```javascript -pragma solidity ^0.4.0; - -contract MappingExample { - mapping(address => uint) public balances; - - function update(uint newBalance) public { - balances[msg.sender] = newBalance; - } -} - -contract MappingUser { - function f() public returns (uint) { - MappingExample m = new MappingExample(); - m.update(100); - return m.balances(this); - } -} -``` -#### 3.2.3 涉及 LValues 的运算符 -- **删除** - -`delete a` 的结果是将 `a` 的类型在初始化时的值赋值给 `a`。即对于整型变量来说,相当于 `a = 0`, 但 delete 也适用于数组,对于动态数组来说,是将数组的长度设为 0,而对于静态数组来说,是将数组中的所有元素重置。 如果对象是结构体,则将结构体中的所有属性重置。 - -delete 对整个映射是无效的(因为映射的键可以是任意的,通常也是未知的)。 因此在你删除一个结构体时,结果将重置所有的非映射属性,这个过程是递归进行的,除非它们是映射。 然而,单个的键及其映射的值是可以被删除的。 - -理解 `delete a `的效果就像是给 `a` 赋值很重要,换句话说,这相当于在 `a `中存储了一个新的对象。 - -```javascript -pragma solidity ^0.4.0; - -contract DeleteExample { - uint data; - uint[] dataArray; - - function f() public { - uint x = data; - delete x; // 将 x 设为 0,并不影响数据 - delete data; // 将 data 设为 0,并不影响 x,因为它仍然有个副本 - uint[] storage y = dataArray; - delete dataArray; - // 将 dataArray.length 设为 0,但由于 uint[] 是一个复杂的对象,y 也将受到影响, - // 因为它是一个存储位置是 storage 的对象的别名。 - // 另一方面:"delete y" 是非法的,引用了 storage 对象的局部变量只能由已有的 storage 对象赋值。 - } -} -``` - -### 3.3 单位和全局变量 - - -### 3.3.1 以太币单位 - -以太币 单位之间的换算就是在数字后边加上 `wei`、 `finney`、 `szabo` 或 `ether` 来实现的,如果后面没有单位,缺省为 `Wei`。例如 `2 ether == 2000 finney` 的逻辑判断值为 `true`。 - - -### 3.3.2 时间单位 - -秒是缺省时间单位,在时间单位之间,数字后面带有 `seconds`、 `minutes`、 `hours`、 `days`、 `weeks` 和 `years` 的可以进行换算,基本换算关系与现实生活相符。 - -### 3.3.3 特殊变量和函数 - -在全局命名空间中已经存在了(预设了)一些特殊的变量和函数,他们主要用来提供关于区块链的信息或一些通用的工具函数。 - -**区块和交易属性** - -- `block.blockhash(uint blockNumber) returns (bytes32)`:指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块;而 blocks 从 0.4.22 版本开始已经不推荐使用,由 `blockhash(uint blockNumber)` 代替 -- `block.coinbase (address)`: 挖出当前区块的矿工地 -- `block.difficulty (uint)`: 当前区块难度 -- `block.gaslimit (uint)`: 当前区块 `gas` 限额 -- `block.number (uint`): 当前区块号 -- `block.timestamp (uint)`: 自 `unix epoch` 起始当前区块以秒计的时间戳 -- `gasleft() returns (uint256)`:剩余的 `gas` -- `msg.data (bytes)`: 完整的 `calldata` -- `msg.gas (uint)`: 剩余 `gas` - 自 0.4.21 版本开始已经不推荐使用,由 gesleft() 代替 -- **`msg.sender (address)`:** 消息发送者(当前调用) -- `msg.sig (bytes4)`: calldata 的前 4 字节(也就是函数标识符) -- `msg.value (uint)`: 随消息发送的 wei 的数量 -- `now (uint)`: 目前区块时间戳(`block.timestamp`) -- `tx.gasprice (uint)`: 交易的` gas` 价格 -- `tx.origin (address)`: 交易发起者(完全的调用链) - -**[ABI 编码函数](https://solidity-cn.readthedocs.io/zh/develop/abi-spec.html#abi)** - - -- `abi.encode(...) returns (bytes)`: ABI - 对给定参数进行编码 -- `abi.encodePacked(...) returns (bytes)`:对给定参数执行 紧打包编码 -- `abi.encodeWithSelector(bytes4 selector, ...) returns (bytes):` ABI - 对给定参数进行编码,并以给定的函数选择器作为起始的 4 字节数据一起返回 -- `abi.encodeWithSignature(string signature, ...) returns (bytes)`:等价于 `abi.encodeWithSelector(bytes4(keccak256(signature), ...)` - -**错误处理** - -- `assert(bool condition)`: - 如果条件不满足,则使当前交易没有效果 — 用于检查内部错误。 -- `require(bool condition)`: - 如果条件不满足则撤销状态更改 - 用于检查由输入或者外部组件引起的错误。 -- `require(bool condition, string message)`: - 如果条件不满足则撤销状态更改 - 用于检查由输入或者外部组件引起的错误,可以同时提供一个错误消息。 -- `revert()`: - 终止运行并撤销状态更改。 -- `revert(string reason)`: - 终止运行并撤销状态更改,可以同时提供一个解释性的字符串。 - -**地址相关** - - -- `
.balance (uint256)`: - 以 Wei 为单位的 地址类型 的余额。 -- `
.transfer(uint256 amount)`: - 向 地址类型 发送数量为 amount 的 Wei,失败时抛出异常,发送 2300 gas 的矿工费,不可调节。 -- `
.send(uint256 amount) returns (bool)`: - 向 地址类型 发送数量为 amount 的 Wei,失败时返回 false,发送 2300 gas 的矿工费用,不可调节。 -- `
.call(...) returns (bool)`: - 发出低级函数 CALL,失败时返回 false,发送所有可用 gas,可调节。 -- `
.callcode(...) returns (bool)`: - 发出低级函数 CALLCODE,失败时返回 false,发送所有可用 gas,可调节。 -- `
.delegatecall(...) returns (bool):` - 发出低级函数 DELEGATECALL,失败时返回 false,发送所有可用 gas,可调节。 - -### 3.4 表达式和控制结构(*) - -### 3.4.1 控制结构 - - -avaScript 中的大部分控制结构在 Solidity 中都是可用的,除了 `switch` 和 `goto`。 因此 Solidity 中有 `if,else,while,do,for,break,continue,return,? : `这些与在 C 或者 JavaScript 中表达相同语义的关键词。 - -用于表示条件的括号 **不可以** 被省略,单语句体两边的花括号可以被省略。 - -注意,与 C 和 JavaScript 不同, Solidity 中非布尔类型数值不能转换为布尔类型,因此 `if (1) { ... }` 的写法在 Solidity 中 无效 。 - -当一个函数有多个输出参数时, `return (v0, v1, ...,vn)` 写法可以返回多个值。不过元素的个数必须与输出参数的个数相同 - -### 3.4.2 通过 new 创建合约 -使用关键字 `new` 可以创建一个新合约。待创建合约的完整代码必须事先知道,因此递归的创建依赖是不可能的。 - -```javascript -pragma solidity ^0.4.0; - -contract D { - uint x; - function D(uint a) public payable { - x = a; - } -} - -contract C { - D d = new D(4); // 将作为合约 C 构造函数的一部分执行 - - function createD(uint arg) public { - D newD = new D(arg); - } - - function createAndEndowD(uint arg, uint amount) public payable { - //随合约的创建发送 ether - D newD = (new D).value(amount)(arg); - } -} -``` -如示例中所示,使用 `.value()` 选项创建 `D` 的实例时可以转发 `Ether`,但是不可能限制 `gas` 的数量。如果创建失败(可能因为栈溢出,或没有足够的余额或其他问题),会引发异常。 - -### 3.4.3 错误处理:Assert, Require, Revert and Exceptions - -`Solidity` 使用状态恢复异常来处理错误。这种异常将撤消对当前调用(及其所有子调用)中的状态所做的所有更改,并且还向调用者标记错误。 便利函数 `assert` 和 `require` 可用于检查条件并在条件不满足时抛出异常。`assert` 函数只能用于测试内部错误,并检查非变量。 - - `require` 函数用于确认条件有效性,例如输入变量,或合约状态变量是否满足条件,或验证外部合约调用返回的值。 如果使用得当,分析工具可以评估你的合约,并标示出那些会使 `assert` 失败的条件和函数调用。 正常工作的代码不会导致一个 `assert `语句的失败;如果这发生了,那就说明出现了一个需要你修复的 bug。 - -还有另外两种触发异常的方法:`revert` 函数可以用来标记错误并恢复当前的调用。 `revert` 调用中包含有关错误的详细信息是可能的,这个消息会被返回给调用者。已经不推荐的关键字 `throw` 也可以用来替代 `revert()` (但无法返回错误消息)。 - -在下例中,你可以看到如何轻松使用``require``检查输入条件以及如何使用``assert``检查内部错误,注意,你可以给 require 提供一个消息字符串,而 assert 不行。 - -```javascript -pragma solidity ^0.4.22; - -contract Sharer { - function sendHalf(address addr) public payable returns (uint balance) { - require(msg.value % 2 == 0, "Even value required."); - uint balanceBeforeTransfer = this.balance; - addr.transfer(msg.value / 2); - //由于转移函数在失败时抛出异常并且不能在这里回调,因此我们应该没有办法仍然有一半的钱。 - assert(this.balance == balanceBeforeTransfer - msg.value / 2); - return this.balance; - } -} -``` -### 3.5 合约 - -Solidity 合约类似于面向对象语言中的类。合约中有用于数据持久化的状态变量,和可以修改状态变量的函数。 调用另一个合约实例的函数时,会执行一个 EVM 函数调用,这个操作会切换执行时的上下文,这样,前一个合约的状态变量就不能访问了。 - -### 3.5.1 创建合约 - -可以通过以太坊交易“从外部”或从 Solidity 合约内部创建合约。 -创建合约时,会执行一次构造函数(与合约同名的函数)。构造函数是可选的。只允许有一个构造函数,这意味着不支持重载。 - -在内部,构造函数参数在合约代码之后通过 `ABI` 编码 传递,但是如果你使用 `web3.js` 则不必关心这个问题。 - -如果一个合约想要创建另一个合约,那么创建者必须知晓被创建合约的源代码(和二进制代码)。 这意味着不可能循环创建依赖项。 - -### 3.5.2 getter 函数 - -编译器自动为所有 `public` 状态变量创建 `getter` 函数。对于下面给出的合约,编译器会生成一个名为 `data` 的函数, 该函数不会接收任何参数并返回一个 `uint` ,即状态变量 `data` 的值。可以在声明时完成状态变量的初始化 - -```javascript -pragma solidity ^0.4.0; - -contract C { - uint public data = 42; -} - -contract Caller { - C c = new C(); - function f() public { - uint local = c.data(); - } -} -``` -getter 函数具有外部可见性。如果在内部访问 getter(即没有 this. ),它被认为一个状态变量。 如果它是外部访问的(即用 this. ),它被认为为一个函数。 - -### 3.5.3 View 函数 - -可以将函数声明为 view 类型,这种情况下要保证不修改状态。 - -下面的语句被认为是修改状态: - -1. 修改状态变量。 -2. 产生事件。 -3. 创建其它合约。 -4. 使用 selfdestruct。 -5. 通过调用发送以太币。 -6. 调用任何没有标记为 view 或者 pure 的函数。 -7. 使用低级调用。 -8. 使用包含特定操作码的内联汇编。 - -```javascript -pragma solidity ^0.4.16; - -contract C { - function f(uint a, uint b) public view returns (uint) { - return a * (b + 42) + now; - } -} -``` -### 3.5.4 Pure 函数 - -函数可以声明为 pure ,在这种情况下,承诺不读取或修改状态。 - -除了上面解释的状态修改语句列表之外,以下被认为是从状态中读取: - -1. 读取状态变量。 -2. 访问 this.balance 或者
.balance。 -3. 访问 block,tx, msg 中任意成员 (除 msg.sig 和 msg.data 之外)。 -4. 调用任何未标记为 pure 的函数。 -5. 使用包含某些操作码的内联汇编。 - -```javascript -pragma solidity ^0.4.16; - -contract C { - function f(uint a, uint b) public pure returns (uint) { - return a * (b + 42); - } -} -``` - - - - - -## 四、练习题 - -### 4.1 将固定长度字节数组转化为`string`类型 - -```javascript -pragma solidity ^0.4.0; - -contract bytes32tostring{ - - bytes10 testword=0x68656c6c6f776f726c64; //为helloworld - function bytes32tostringF() public view returns(string){ - - } -} -``` - -### 4.2 实现一个带有简单逻辑判断及多种数学运算的Solidity程序 - - - -**参考自:** - -1. 黄皮书:https://github.com/yuange1024/ethereum_yellowpaper/blob/master/ethereum_yellow_paper_cn.pdf - -2. 白皮书:https://github.com/ethereum/wiki/wiki/White-Paper - [INlinKC](https://blog.csdn.net/weixin_45067603) - https://ethfans.org/wikis/Home -3. 以太坊solidity学习记录: [https://blog.csdn.net/weixin_45067603/article/details/105726491](https://blog.csdn.net/weixin_45067603/article/details/105726491) -4. [尚硅谷区块链全套Go语言→GoWeb→以太坊→项目实战](https://www.bilibili.com/video/BV1sJ411D72u) diff --git a/Blockchain/part3_web3js.md b/Blockchain/part3_web3js.md deleted file mode 100644 index f81c973..0000000 --- a/Blockchain/part3_web3js.md +++ /dev/null @@ -1,1151 +0,0 @@ -注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 - -## 一、以太坊客户端 - -### 1.1、什么是以太坊客户端 - -- 以太坊客户端是一个软件应用程序,它实现以太坊规范并通过p2p网络与其他以太坊客户端进行通信。如果不同的以太坊客户端符合参考规范和标准化通信协议,则可以进行相互操作。 -- 以太坊是一个开源项目,由“黄皮书”正式规范定义。除了各种以太坊改进提案之外,此正式规范还定义了以太坊客户端的标准行为。 -- 因为以太坊有明确的正式规范,以太网客户端有了许多独立开发的软件实现,它们之间又可以彼此交互。 - - -### 1.2、基于以太坊规范的网络 - -- 存在各种基于以太坊规范的网络,这些网络基本符合以太坊“黄皮书”中定义的形式规范,但它们之间可能相互也可能不相互操作。 -- 这些基于以太坊的网络中有:以太坊,以太坊经典,Ella,Expanse,Ubiq,Musicoin等等。 -- 虽然大多数在协议级别兼容,但这些网络通常具有特殊要求,以太坊客户端软件的维护人员、需要进行微小更改、以支持每个网络的功能或属性 - - -### 1.3、以太坊的多种客户端 - -- [go-ethereum ( Go )](https://github.com/ethereum/go-ethereum) - 官方推荐,开发使用最多 -- parity ( Rust ) - 最轻便客户端,在历次以太坊网络攻击中表现卓越 -- cpp-ethereum (C++) - -- pyethapp (python) - -- ethereumjs-lib ( javascript ) - -- EthereumJ / Harmony ( Java ) - -### 1.4、以太坊全节点 - -- 全节点是整个主链的一个副本,存储并维护链上的所有数据,并随时验证新区块的合法性。 -- 区块链的健康和扩展弹性,取决于具有许多独立操作和地理上分散的全节点。每个全节点都可以帮助其他新节点获取区块数据,并提供所有交易和合约的独立验证。 -- 运行全节点将耗费巨大的成本,包括硬件资源和带宽。 -- 以太坊开发不需要在实时网络(主网)上运行的全节点。我们可以使用测试网络的节点来代替,也可以用本地私链,或者使用服务商提供的基于云的以太坊客户端;这些几乎都可以执行所有操作。 - -### 1.5、远程客户端和轻节点 - -- 远程客户端 - - 不存储区块链的本地副本或验证块和交易。这些客户端一般只提供钱包的功能,可以创建和广播交易。远程客户端可用于连接到现有网络,MetaMask 就是一个这样的客户端。 - -- 轻节点 - - 不保存链上的区块历史数据,只保存区块链当前的状态。轻节点可以对块和交易进行验证。 - -* 全节点的优缺点 - * 优点 - * 为以太坊网络的灵活性和抗审查性提供有力支持 - * 权威地验证所有交易 - * 可以直接与公众区块链上的任何合约交互 - * 可以离线查询区块链状态(账户、合约等) - * 可以直接把自己的合约部署到公共区块链中 - * 缺点 - * 需要巨大的硬件和带宽资源,而且会不断增长 - * 第一次下载往往需要几天才能完全同步 - * 必须及时维护、升级并保持在线状态以同步区块 - - ##### 公共测试网络节点的优缺点 - - * 优点 - * 一个testnet节点需要同步和存储更少的数据,大约10GB,具体取决于不同的网络 - * 一个testnet节点一般可以在几个小时内完成同步 - * 部署合约或进行交易只需要发送测试以太,可以从”水龙头“免费获得 - * 测试网络是公共区块链,有许多其他用户和合约运行(区别于私链) - * 缺点 - * 测试网络上使用测试以太没有价值。因此无法测试交易对手的安全性,因为没有任何利害关系 - * 测试网络上的测试无法涵盖所有真实主网特性。例如:交易费用虽然是发送交易所必需的,但由于gas免费,因此 testnet上往往不会考虑。而且一般来说,测试网络不会像主网一样经常拥堵 - - ##### 本地私链的优缺点 - - * 优点 - * 磁盘上几乎没有数据,也不同步别的数据,是一个完全干净的环境 - * 无需获取测试以太,可以分配任意以太,也可以随时自己挖矿获得 - * 没有其他用户与合约,无外部干扰 - * 缺点 - * 没有其他用户意味与公链的行为不同,发送的交易并不存在空间或交易顺序的竞争 - * 除自己之外没有矿工意味着挖矿更容易预测,因此无法测试公链上发生的某些情况 - * 没有其他合约意味着必须部署要测试的所有内容,包括所有的依赖项和合约库 - -我们的教程主要基于本地私链的搭建,以后的交易等也主要基于我们的私链,因此以太坊客户端及私链的搭建在我们本次学习中至关重要。 - -**JSON-RPC** - -- 以太坊客户端提供了API 和一组远程调用(RPC)命令,这些命令被编码为 JSON。这被称为 JSON-RPC API。本质上,JSON-RPCAPI 就是一个接口,允许我们编写的程序使用以太坊客户端作为网关,访问以太坊网络和链上数据。 -- 通常,RPC 接口作为一个 HTTP 服务,端口设定为 8545。出于安全原因,默认情况下,它仅限于接受来自localhost 的连接。 -- 要访问JSON-RPC API,我们可以使用编程语言编写的专用库,例如JavaScript的 web3.js。 -- 或者也可以手动构建HTTP请求并发送/接收JSON编码的请求,如: - -```javascript -curl -X POST -H "Content-Type:application/json" --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' http://127.0.0.1:8545 -``` - - -## 二、用 Geth 搭建以太坊私链 - -### 2.1安装 go - -大家首先输入`go version`查看自己是否配置成功go环境,若不成功参考下面博客: - -[go : GoLand安装及环境配置](https://blog.csdn.net/qq_44702847/article/details/108597386) - -若成功则如下图所示 -
- -
-
图 1
-
- -### 2.2 安装 Geth - -安装 Geth 有很多种方式,这里主要就 Linux 环境给出两种方法:系统包管理器(apt-get)安装和源码安装。更加推荐大家用源码安装,在整个过程中可以看到 Geth 各组件的构建步骤。 - -其他OS安装方法见[本教程](https://geth.ethereum.org/docs/install-and-build/installing-geth) - -**方法一、apt-get** - -```javascript -sudo apt-get install software-properties-common -sudo add-apt-repository -y ppa:ethereum/ethereum -sudo apt-get update -sudo apt-get install ethereum -``` - - -**方法二、源码安装** - -1. 克隆 github 仓库我们的第一步是克隆 git 仓库,以获取源代码的副本。 - - -```javascript -git clone https://github.com/ethereum/go-ethereum.git -``` - -2. 从源码构建 Geth要构建 Geth,切换到下载源代码的目录并使用 make 命令: - - -```javascript -cd go-ethereum -make geth -``` - -如果一切顺利,我们将看到 Go 编译器构建每个组件,直到它生成 geth 可执行文件: - -```javascript -build/env.sh go run build/ci.go install ./cmd/geth ->>> /usr/local/go/bin/go install -ldflags -X -main.gitCommit=58a1e13e6dd7f52a1d5e67bee47d23fd6cfdee5c -v ./cmd/geth -github.com/ethereum/go-ethereum/common/hexutil -github.com/ethereum/go-ethereum/common/math -github.com/ethereum/go-ethereum/crypto/sha3 github.com/ethereum/go-ethereum/rlp -github.com/ethereum/go-ethereum/crypto/secp256k1 -github.com/ethereum/go-ethereum/common [...] -github.com/ethereum/go-ethereum/cmd/utils -github.com/ethereum/go-ethereum/cmd/geth Done building. Run "build/bin/geth" to -launch geth. -``` - - 查看 geth version,确保在真正运行之前安装正常: - -
- -
-
图 2
-
- -### 启动节点同步 - -安装好了 Geth,现在我们可以尝试运行一下它。执行下面的命令,geth 就会开始同步区块,并存储在当前目录下。 - -这里的 --syncmode fast 参数表示我们会以“快速”模式同步区块。在这种模式下,我们只会下载每个区块头和区块体,但不会执行验证所有的交易,直到所有区块同步完毕再去获取一个系统当前的状态。这样就节省了很多交易验证的时间。 - - ```javascript -geth –datadir . --syncmode fast - ``` ---datadir:后面的参数是区块数据及秘钥存放目录 - -通常,在同步以太坊区块链时,客户端会一开始就下载并验证每个块和每个交易,也就是说从创世区块开始。 毫无疑问,如果我们不加 --syncmode fast 参数,同步将花费很长时间并且具有很高的资源要求(它将需要更多的 RAM,如果你没有快速存储,则需要很长时间)。有些文章会把这个参数写成 --fast,这是以前快速同步模式的参数写法,现在已经被 –syncmode fast取代。如果我们想同步测试网络的区块,可以用下面的命令: - - ```javascript -geth --testnet --datadir . --syncmode fast - ``` - ---testnet 这个参数会告诉 geth 启动并连接到最新的测试网络,也就是 Ropsten。测试网络的区块和交易数量会明显少于主网,所以会更快一点。但即使是用快速模式同步测试网络,也会需要几个小时的时间 - -### 2.3 搭建自己的私有链 - -因为公共网络的区块数量太多,同步耗时太长,我们为了方便快速了解 Geth,可以试着用它来搭一个只属于自己的私链。首先,我们需要创建网络的“创世”(genesis)状态,这写在一个小小的 JSON 文件里(例如,我们将其命名为 genesis.json,保存到当前目录下): - - ```javascript -{ -"config": { - "chainId": 15 - }, -"difficulty": "2000", -"gasLimit": "2100000", -"alloc": { - "7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "300000" }, - "f41c74c9ae680c1aa78f42e5647a62f353b7bdde": { "balance": "400000" } - } -} - ``` -genesis.json介绍 -
- -
-
图 3
-
-要创建一条以它作为创世块的区块链,我们可以使用下面的命令: - ```javascript -geth --datadir . init genesis.json - ``` -初始化完成后目录下多了geth和keystore两个文件夹: - -- geth:保存该链上的区块数据 -- keystore:保存该链上的账户信息 - -**可能遇到问题**: -- Fatal: invalid genesis file: missing 0x prefix for hex data:这个错误信息意思很明白,就是你的json文件中,对于16进制数据,需要加上0x前缀 - -- Fatal: invalid genesis file: hex string has odd length: 从Geth 1.6版本开始,设置的十六进制数值,不能是奇数位, 比如不能是0x0,而应该是0x00。 - -- Fatal: failed to write genesis block: genesis has no chain configuration :这个错误信息,就是说,你的配置文件中,缺少config部分。 - -- Error: invalid sender: 这个错误虽然不会导致私有链初始化时出现失败的情况,但是会在以后的转账(web3.eth.sendTransaction),或者部署智能合约的时候产生。解决方法就是chainId 不能设置为0。 如果你完全按照Geth官方文档上给出的配置文件进行配置,就会产生这个错误。 - -在当前目录下运行 geth,就会启动这条私链,注意要将 networked 设置为与创世块配置里的chainId 一致。 - - ```javascript -// 简单开启 -(base) haobo@haobo:~/home/mnt/bitcoin/test$ geth --datadir . --networkid 150 --nodiscover console - -// 更一般的形式 -(base) haobo@haobo:~/home/mnt/bitcoin/test$ geth --networkid 150 --datadir "." --identity "kexin" --rpc --rpcport "8545" --rpcaddr "localhost" --port "30303" --nodiscover --allow-insecure-unlock --rpcapi "eth,net,web3,personal,admin,shh,txpool,debug,miner" console - ``` -参数含义: -
- -
-
图 4
-
-我们可以看到节点正常启动: -
- -
-
图 5
-
-启动完之后,就可以通过`admin.nodeInfo.protocols.eth`来获取到刚启动的节点的一些信息(如下),比较上文初始化的配置,相关内容是一致的。 -
- -
-
图 6
-
-恭喜!我们已经成功启动了一条自己的私链。 - -## 3、Geth 控制台命令 - -`Geth Console` 是一个交互式的 JavaScript 执行环境,其中 > 是命令提示符,里面内置了一些用来操作以太坊的 JavaScript对象,我们可以直接调用这些对象来获取区块链上的相关信息。 - -**这些对象主要包括:** - -- eth:主要包含对区块链进行访问和交互相关的方法; -- net:主要包含查看 p2p 网络状态的方法; -- admin:主要包含与管理节点相关的方法; -- miner:主要包含挖矿相关的一些方法; -- personal:包含账户管理的方法; -- txpool:包含查看交易内存池的方法; -- web3:包含以上所有对象,还包含一些通用方法。 - - - -**常用命令有:** - - -- personal.newAccount():创建账户; -- personal.unlockAccount():解锁账户; -- eth.accounts:枚举系统中的账户; -- eth.getBalance():查看账户余额,返回值的单位是 Wei(Wei 是以太坊中最小货币面额单位,类似比特币中的聪,1 ether = 10^18 Wei); -- eth.blockNumber:列出区块总数; -- eth.getTransaction():获取交易; -- eth.getBlock():获取区块; -- miner.start():开始挖矿; -- miner.stop():停止挖矿; -- web3.fromWei():Wei 换算成以太币; -- web3.toWei():以太币换算成 Wei; -- txpool.status:交易池中的状态; -- admin.addPeer():连接到其他节点 - -### 3.1 操作测试 - -**3.1.1 创建账户** - -进入控制台后,可以通过使用命令来与私有链进行交互。创建一个新的账户: - -```javascript -> personal.newAccount() -Passphrase: -Repeat passphrase: -"0xc8248c7ecbfd7c4104923275b99fafb308bbff92" -``` - -输入两遍密码后,生成账户地址。以同样的方式,可创建多个账户,查看账户: - -```javascript -> eth.accounts -``` -查看账户余额 - -```javascript -> eth.getBalance(eth.accounts[0]) -0 -``` -
- -
-
图 7
-
-
- -**3.1.2 挖矿** - -启动挖矿: - -```javascript -> miner.start(1) -``` - -其中 `start` 的参数表示挖矿使用的线程数。第一次启动挖矿会先生成挖矿所需的 `DAG `文件,这个过程有点慢,等进度达到 100% 后,就会开始挖矿,此时屏幕会被挖矿信息刷屏。 - -停止挖矿,在 控制台 中输入: - -```javascript -> miner.stop() -``` - -挖到一个区块会奖励以太币,挖矿所得的奖励会进入矿工的账户,这个账户叫做 coinbase,默认情况下 coinbase 是本地账户中的第一个账户,可以通过 miner.setEtherbase() 将其他账户设置成 coinbase。 - -可以使用以下命令,当新区块挖出后,挖矿即可结束。 - -```javascript -> miner.start(1);admin.sleepBlocks(1);miner.stop(); -``` - -**3.1.3 交易** - -目前,账户 0 已经挖到了 3 个块的奖励,账户 1 的余额还是0: -```javascript -> eth.getBalance(eth.accounts[0]) -15000000000000000000 -> eth.getBalance(eth.accounts[1]) -0 -``` - -我们要从账户 0 向账户 1 转账,先解锁账户 0,才能发起交易: - -```javascript -> personal.unlockAccount(eth.accounts[0]) -Unlock account 0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802 -Passphrase: -true -``` -发送交易,账户 0 -> 账户 1: - -```javascript -> amount = web3.toWei(5,'ether') -"5000000000000000000" -> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount}) -INFO [09-12|07:38:12] Submitted transaction fullhash=0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce recipient=0x02bee2a1582bbf58c42bbdfe7b8db4685d4d4c62 -"0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce" -``` - -此时如果没有挖矿,用 `txpool.status` 命令可以看到本地交易池中有一个待确认的交易,可以使用 `eth.getBlock("pending", true).transactions`查看当前待确认交易。使用下面命令开始挖矿。 - -```javascript ->miner.start(1);admin.sleepBlocks(1);miner.stop(); -``` - -新区块挖出后,挖矿结束,查看账户 1 的余额,已经收到了账户 0 的以太币: -```javascript -> web3.fromWei(eth.getBalance(eth.accounts[1]),'ether') -5 -``` - -**3.1.3 查看交易和区块** - -查看当前区块总数: -```javascript -> eth.blockNumber -4 -``` - -通过区块号查看区块: -```javascript -> eth.getBlock(4) -``` - -通过交易 Hash 查看交易(Hash 值包含在上面交易返回值中): - - -```javascript -> eth.getTransaction("0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce") -``` - -**3.1.3 其他节点加入** - -此时,私有链已经通过该节点创建好了,如果其他节点想加入,需要通过以太坊客户端连接到该私有区块网络,并连接该网络的节点来同步区块信息。在其他主机上安装以太坊客户端Geth,通过Geth命令进入该私有区块链,注意要指定相同的网络号。 - -假设有两个节点:节点一和节点二,NetWorkID 都是 6666,通过下面的步骤就可以从节点一连接到节点二。 - -首先要知道节点二的 enode 信息,在节点二的 Geth Console 中执行下面的命令查看 enode 信息: - -```javascript -> admin.nodeInfo.enode -"enode://d465bcbd5c34da7f4b8e00cbf9dd18e7e2c38fbd6642b7435f340c7d5168947ff2b822146e1dc1b07e02f7c15d5ca09249a92f1d0caa34587c9b2743172259ee@[::]:30303" -``` -然后在节点一的 Geth Console 中执行 `admin.addPeer()`,就可以连接到节点二: - -```javascript -> admin.addPeer("enode://d465bcbd5c34da7f4b8e00cbf9dd18e7e2c38fbd6642b7435f340c7d5168947ff2b822146e1dc1b07e02f7c15d5ca09249a92f1d0caa34587c9b2743172259ee@[::]:30303") -``` -`addPeer()` 的参数就是节点二的 enode 信息,注意要把 enode 中的 `[::]` 替换成节点二的 IP 地址。连接成功后,节点二就会开始同步节点一的区块,同步完成后,任意一个节点开始挖矿,另一个节点会自动同步区块,向任意一个节点发送交易,另一个节点也会收到该笔交易。 - -通过 `admin.peers`可以查看连接到的其他节点信息,通过 `net.peerCount`可以查看已连接到的节点数量。 - -除了上面的方法,也可以在启动节点的时候指定`--bootnodes`选项连接到其他节点。 - -> 如果只是自己测试开发使用,建议使用dev环境,在需要在启动时增加`–dev`参数即可,在dev模式下会监听交易,一旦有交易发送就会打包然后挖矿确认,且默认的`account[0]`开发者账户初始有一大堆以太币。 - -## 3、智能合约操作 - -### 3.1、创建和编译智能合约 - -经过part2的学习大家已经基本上掌握了Solidity,接下来我们编写一个智能合约: - -该合约包含一个方法 multiply(),将输入的两个数相乘后输出: -```javascript -pragma solidity ^0.4.0; -contract TestContract -{ - function multiply(uint a, uint b) returns (uint) - { - return a * b; - } -} -``` -将上面的代码复制到Remix编辑器里,程序将自动完成编译。 -
- -
-
图 8
-
-点击 run 在Environment中设选择JavaScript VM, Value可设置为1,点击Deploy,则可创建该部署智能合约的交易。 - -因为我们要将该智能合约部署到私有链上,需要得到智能合约编译后的EVM二进制码和JSON ABI(Application Binary Interface)。将生成的交易保存到scenario.json文件,点击箭头所指按钮 - -
- -
-
图 9
-
-其中38-65行为该智能合约的ABI(注意前面还有一个[符号),ABI指定了合约接口,包括可调用的合约方法、变量、事件等。 - -
- -
-
图 10
-
-`input`字段为合约EVM二进制码,可点击直接复制。 - -在Linux下可以直接使用安装好的编译器进行编译,把合约代码保存到文件名为testContract.sol 里,通过下面两个命令分别得到EVM二进制码和JSON ABI。 - -如果没有安装solc先执行 - -```javascript -sudo snap install solc -``` -接下来执行 -```javascript -$solc --bin testContract.sol -$solc --abi testContract.sol -``` -
- -
-
图 11
-
-### 3.2、部署智能合约 - -回到 Geth 的控制台,用变量 code 和 abi 记录上面两个值: - -```javascript -> code = "608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a7231582049ecffb2740a6e31f7c8fbf4a928b88d3a95f417b985dc23cd1ad4c06a9b043864736f6c63430005100032" -> abi = [{ - "0xd1ef8ab8f12bde83ebaee1be4183c75f45ab5835643812016a7751173bfb9dc0": [ - { - "constant": true, - "inputs": [ - { - "name": "a", - "type": "uint256" - }, - { - "name": "b", - "type": "uint256" - } - ], - "name": "multiply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } - ] - }] -``` -使用账户 0 来部署合约,首先解锁账户: - -```javascript -> personal.unlockAccount(eth.accounts[0]) -Unlock account 0xb51654f60dee35265558a1d2e61468fe00f12888 -Passphrase: -true -``` -创建合约实例,发送部署合约的交易: - -```javascript -> myContract = eth.contract(abi) -... -> contract = myContract.new({from:eth.accounts[0],data:code,gas:1000000}) - -``` - -
- -
-
图 12
-
-
-此时如果没有挖矿,用 `txpool.status` 命令可以看到本地交易池中有一个待确认的交易。使用 `miner.start()` 命令开始挖矿,一段时间后交易会被确认。通过查询该交易可得到合约地址,使用命令: - - -```javascript ->eth.getTransactionReceipt("0x085b66b2591ee31c3ad58a66ca485bd19bea6c1fc8ca7550a896853ab52855a6") -contractAddress: "0xd92845cc4bffc1d6a4b6a389933b88880d5ded24" -``` - -### 3.3、调用智能合约 - -使用以下命令通过发送交易来调用合约,sendTransaction 方法的前几个参数应该与合约中 multiply 方法的输入参数对应。这种情况下,交易会通过挖矿记录到区块链中: - -```javascript ->contract.multiply.sendTransaction(2, 4, {from:eth.accounts[0]}) -``` -在本地运行该方法可直接查看返回结果,不会记录到区块链中,命令如下: - -```javascript ->contract.multiply.call(2,4) -8 -``` - -如果其他节点要调用这个已经部署好的合约,需要知道该合约的地址以及ABI。可以通过发送交易调用,也可以本地调用。我们以本地调用为例。 -创建合约实例: - -```javascript ->abi = [{ - "0xd1ef8ab8f12bde83ebaee1be4183c75f45ab5835643812016a7751173bfb9dc0": [ - { - "constant": true, - "inputs": [ - { - "name": "a", - "type": "uint256" - }, - { - "name": "b", - "type": "uint256" - } - ], - "name": "multiply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } - ] - }] ->sample=eth.contract(abi) ->samplecontract=sample.at("0xd92845cc4bffc1d6a4b6a389933b88880d5ded24") -``` -调用合约 - -```javascript ->samplecontract.multiply.call(2,4) -8 -``` - -## 4、web3.js 简介 - -我们除了通过Geth的JavaScript Console进行交互以外,还有许多第三方库可以使用,方便开发基于以太坊区块链的应用: - -
- -
-
图 13
-
-本文使用web3.js与Geth客户端交互,首先搭建开发环境。 - - -### 4.1 环境搭建 - - -**4.1.1 node.js安装** - -更新源 -```javascript -sudo apt-get update -sudo apt-get install -y python-software-properties software-properties-common -sudo add-apt-repository ppa:chris-lea/node.js -sudo apt-get update -``` - -node.js、npm安装 -```javascript -sudo apt-get install nodejs -sudo apt install nodejs-legacy -sudo apt install npm -``` -安装完后,可以通过 `node --version npm --version` 查看是否安装成功及版本号。npm 包管理工具随 node 一起安装,如果版本太低,建议升到新版本。 - - -**4.1.2 web3.js模块安装** -使用npm可完成本地安装、全局安装模块。 -```javascript - npm install -global //全局安装 - npm install //本地安装 -``` - - -我这里选择使用本地安装模块,这样方便开发的应用移植、上线等。创建一个工程文件夹etherjs。在该文件夹下初始化一个新的 package.json 文件,使用下面命令自动生成。 -```javascript -npm init -y -``` - -本地安装并添加模块名到 package.json -```javascript -npm install --save -或者npm install --save-dev -``` - -区别在于--save-dev 是你开发时候依赖的东西,--save 是你发布之后还依赖的东西。一般使用--save。 -```javascript -npm install web3 --save -``` - - -如果这样安装不成功,使用下面命令安装指定版本: -```javascript -npm install web3@^0.20.1 --save -``` - -**4.1.3 solc.js模块安装** -solc是用来编译智能合约的模块 -```javascript -npm install solc --save -``` - -**4.1.4 编译器——Visual Studio Code** - -这里选择Visual Studio Code,适合node.js开发,集成的终端可以很方便运行程序。 - -安装Ubuntu Make -```javascript -sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make -sudo apt-get update -sudo apt-get install ubuntu-make -``` -安装visual-studio-code -```javascript -umake web visual-studio-code -``` -安装完成后,直接搜索Visual Studio Code应用,把图标拖拽到Unity启动器上,就可以方便使用了。 - - -### 4.2 web3.js 介绍 - -web3js 的全称是Web3 JavaScript app API,它是一个JavaScript API库。要使DApper在以太坊上运行,我们可以使用web3.js库提供的web3对象。web3.js通过RPC调用与本地节点通信,它可以用于任何暴露了RPC层的以太坊节点,web3包含了eth对象 - web3.eth(专门与以太坊区块链交互)和 shh对象 - web3.shh(用于与 Whisper交互)[Whisper是以太坊生态系统的一部分,主要用来做消息传递] - -如果我们想要在以太坊上开发合约,目前来说最方便的方法就是调用Web3.js库,它会给我们一个Web3对象。我们进入geth控制台,直接键入web3就可以看到所有的方法。下面主要介绍如何通过web3js创建合约并调用 - - -**4.2.1异步回调(callback)** - -- web3js API 设计的最初目的,主要是为了和本地 RPC 节点共同使用,所以默认情况下发送的是同步 HTTP 请求 -- 如果要发送异步请求,可以在函数的最后一个参数位置上,传入一个回调函数。回调函数是可选(optioanl)的 -- 我们一般采用的回调风格是所谓的“错误优先”,例如: - - ```javascript -web3.eth.getBlock(48, function(error, result){ -if(!error) -  console.log(JSON.stringify(result)); -else -  console.error(error); -}); - ``` - -**4.2.2 回调 Promise 事件(v1.0.0)** - -- 为了帮助 web3 集成到不同标准的所有类型项目中,1.0.0 版本提供了多种方式来处理异步函数。大多数的 web3 对象允许将一个回调函数作为最后一个函数参数传入,同时会返回一个promise 用于链式函数调用。 -- 以太坊作为一个区块链系统,一次请求具有不同的结束阶段。为了满足这样的要求,1.0.0 版本将这类函数调用的返回值包成一个“承诺事件”(promiEvent),这是一个 promise 和EventEmitter 的结合体。 -- PromiEvent 的用法就像 promise 一样,另外还加入了.on,.once 和.off方法 - - - ```javascript -web3.eth.sendTransaction({from: '0x123...', data: '0x432...'}) -.once('transactionHash', function(hash){ ... }) -.once('receipt', function(receipt){ ... }) -.on('confirmation', function(confNumber, receipt){ ... }) -.on('error', function(error){ ... }) -.then(function(receipt){ // will be fired once the receipt is mined }); - ``` - -**4.2.3 应用二进制接口(ABI)** - -- web3.js 通过以太坊智能合约的 json 接口(Application Binary Interface,ABI)创建一个 JavaScript 对象,用来在 js代码中描述\ -- 函数(functions) -- type:函数类型,默认“function”,也可能是“constructor” -- constant, payable, stateMutability:函数的状态可变性 -- inputs, outputs: 函数输入、输出参数描述列表 -- 事件(events) -- type:类型,总是“event” -- inputs:输入对象列表,包括 name、type、indexed - -**4.2.4 批处理请求(batch requests)** - -- 批处理请求允许我们将请求排序,然后一起处理它们。 -- 注意:批量请求不会更快。实际上,在某些情况下,一次性地发出许多请求会更快,因为请求是异步处理的。 -- 批处理请求主要用于确保请求的顺序,并串行处理。 - - - ```javascript -var batch = web3.createBatch(); -batch.add(web3.eth.getBalance.request('0x0000000000000000 -000000000000000000000000', 'latest', callback)); -batch.add(web3.eth.contract(abi).at(address).balance.request(a -ddress, callback2)); -batch.execute(); - ``` - -**4.2.5 大数处理(big numbers)** - -- JavaScript 中默认的数字精度较小,所以web3.js 会自动添加一个依赖库 BigNumber,专门用于大数处理 -- 对于数值,我们应该习惯把它转换成 BigNumber 对象来处理 - - ```javascript -var balance = new -BigNumber('131242344353464564564574574567456'); -// or var balance = web3.eth.getBalance(someAddress); -balance.plus(21).toString(10); -//"131242344353464564564574574567477" - ``` - -- BigNumber.toString(10) 对小数只保留20位浮点精度。所以推荐的做法是,我们内部总是用 wei 来表示余额(大整数),只有在需要显示给用户看的时候才转换为ether或其它单位 - - -### 4.3 常用 API —— 基本信息查询 - -**4.3.1 查看 web3 版本** - -- v0.2x.x:web3.version.api -- v1.0.0:web3.version - -查看 web3 连接到的节点版本( clientVersion ) -- 同步:web3.version.node -- 异步:web3.version.getNode((error,result)=>{console.log(result)}) -- v1.0.0:web3.eth.getNodeInfo().then(console.log) - -**4.3.2 基本信息查询** - -获取 network id -- 同步:web3.version.network -- 异步:web3.version.getNetwork((err, res)=>{console.log(res)}) -- v1.0.0:web3.eth.net.getId().then(console.log) - -获取节点的以太坊协议版本 -- 同步:web3.version.ethereum -- 异步:web3.version.getEthereum((err, res)=>{console.log(res)} -- v1.0.0:web3.eth.getProtocolVersion().then(console.log) - - -**4.3.3 网络状态查询** - -是否有节点连接 / 监听,返回 true/false -- 同步:web3.isConnect() 或者 web3.net.listening -- 异步:web3.net.getListening((err,res)=>console.log(res)) -- v1.0.0:web3.eth.net.isListening().then(console.log) - - -查看当前连接的 peer 节点 -- 同步:web3.net.peerCount -- 异步:web3.net.getPeerCount((err,res)=>console.log(res)) -- v1.0.0:web3.eth.net.getPeerCount().then(console.log) - -**4.3.4 Provider** - -查看当前设置的 web3 provider -- web3.currentProvider - -查看浏览器环境设置的 web3 provider ( v1.0.0 ) -- web3.givenProvider - -设置 provider -- web3.setProvider(provider) -- web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')) - - -### 4.4 web3 通用工具方法 - -以太单位转换 -- web3.fromWei web3.toWei数据类型转换 -- web3.toString web3.toDecimal web3.toBigNumber字符编码转换 -- web3.toHex web3.toAscii web3.toUtf8 web3.fromUtf8地址相关 -- web3.isAddress web3.toChecksumAddress - -### 4.5 web3.eth - -**4.5.1 账户相关** - -coinbase 查询 - -- 同步:web3.eth.coinbase -- 异步:web3.eth.getCoinbase( (err, res)=>console.log(res) ) -- v1.0.0:web3.eth.getCoinbase().then(console.log) - -账户查询 - -- 同步:web3.eth.accounts -- 异步:web3.eth.getAccounts( (err, res)=>console.log(res) ) -- v1.0.0:web3.eth.getAccounts().then(console.log) - -**4.5.2 区块相关** - -区块高度查询 -- 同步:web3.eth. blockNumber -- 异步:web3.eth.getBlockNumber( callback ) - -gasPrice 查询 -- 同步:web3.eth.gasPrice -- 异步:web3.eth.getGasPrice( callback ) - -区块查询 -- 同步:web3.eth.getBlockNumber( hashStringOrBlockNumber[ ,returnTransactionObjects] ) -- 异步:web3.eth.getBlockNumber( hashStringOrBlockNumber, callback ) - -块中交易数量查询 -- 同步:web3.eth.getBlockTransactionCount( hashStringOrBlockNumber ) -- 异步:web3.eth.getBlockTransactionCount( hashStringOrBlockNumber, callback ) - -**4.5.3 交易相关** - -余额查询 -- 同步:web3.eth.getBalance(addressHexString [, defaultBlock]) -- 异步:web3.eth.getBalance(addressHexString \[, defaultBlock\]\[, callback\]) - -交易查询 -- 同步:web3.eth.getTransaction(transactionHash) -- 异步:web3.eth.getTransaction(transactionHash [, callback]) - -交易执行相关 -- 交易收据查询(已进块) -- 同步:web3.eth.getTransactionReceipt(hashString) -- 异步:web3.eth.getTransactionReceipt(hashString [,callback]) -- 估计 gas 消耗量 -- 同步:web3.eth.estimateGas(callObject) -- 异步:web3.eth.estimateGas(callObject [, callback]) - -**4.5.4 发送交易** - -- web3.eth.sendTransaction(transactionObject [, callback]) -- 交易对象: -- from:发送地址 -- to:接收地址,如果是创建合约交易,可不填 -- value:交易金额,以wei为单位,可选 -- gas:交易消耗 gas 上限,可选 -- gasPrice:交易 gas 单价,可选 -- data:交易携带的字串数据,可选 -- nonce:整数 nonce 值,可选 - -**4.5.5 消息调用** - -- web3.eth.call(callObject [, defaultBlock] [, callback]) -- 参数: - - 调用对象:与交易对象相同,只是from也是可选的 - - 默认区块:默认“latest”,可以传入指定的区块高度 - - 回调函数,如果没有则为同步调用 - -```javascript -var result = web3.eth.call({ to:"0xc4abd0339eb8d57087278718986382264244252f", -data:"0xc6888fa1000000000000000000000000000000000000000000000000000 0000000000003" }); -console.log(result); -``` - -**4.5.6 日志过滤(事件监听)** -```javascript -web3.eth.filter( filterOptions [ , callback ] ) -// filterString 可以是 'latest' or 'pending' -var filter = web3.eth.filter(filterString); -// 或者可以填入一个日志过滤 options -var filter = web3.eth.filter(options); -// 监听日志变化 -filter.watch(function(error, result){ if (!error) console.log(result); }); -// 还可以用传入回调函数的方法,立刻开始监听日志 -web3.eth.filter(options, function(error, result){ -if (!error) console.log(result); -}); -``` - -**4.5.7 合约相关 —— 创建合约** - -web3.eth.contract - -```javascript -var MyContract = web3.eth.contract(abiArray); -// 通过地址初始化合约实例 -var contractInstance = MyContract.at(address); -// 或者部署一个新合约 -var contractInstance = MyContract.new([constructorParam1][, constructorParam2], {data: '0x12345...', from:myAccount, gas: 1000000}); -``` - -**4.5.8 调用合约函数** - -可以通过已创建的合约实例,直接调用合约函数 - -```javascript -// 直接调用,自动按函数类型决定用 sendTransaction 还是 call -myContractInstance.myMethod(param1 [, param2, ...] [,transactionObject] [, defaultBlock] [, callback]); -// 显式以消息调用形式 call 该函数 -myContractInstance.myMethod.call(param1 [, param2, ...] [,transactionObject] [, defaultBlock] [, callback]); -// 显式以发送交易形式调用该函数 -myContractInstance.myMethod.sendTransaction(param1 [,param2, ...] [, transactionObject] [, callback]); -``` - -**4.5.9 监听合约事件** - -合约的 event 类似于 filter,可以设置过滤选项来监听 - -```javascript -var event = myContractInstance.MyEvent({valueA: 23}[, additionalFilterObject]) -// 监听事件 -event.watch(function(error, result){ -if (!error) -  console.log(result); -}); -// 还可以用传入回调函数的方法,立刻开始监听事件 -var event = myContractInstance.MyEvent([{valueA: 23}][, additionalFilterObject] , function(error, result){ -  if (!error) console.log(result); -} -); -``` - -## 5、交互实现——部署智能合约 - -通过编写一个depoly.js程序实现自动化的部署智能合约。首先要保持Geth客户端正常运行,并开启rpc。 - -```javascript -geth --identity "TestNode" --rpc --rpcport "8545" --datadir data0 --port "30303" --nodiscover --networkid 6666 --rpcapi admin,eth,miner,personal,txpool,eth,web3,net console -``` -合约应该在智能合约编译器(如remix)调试好,然后将其写到test.sol文件里。 -```javascript -pragma solidity ^0.4.0; -contract TestContract -{ - function multiply(uint a, uint b) returns (uint) - { - return a * b; - } -} -``` -使用solc模块生成合约的code和abi,我将该过程自定义为一个模块test.js,方便depoly.js调用。 -```javascript -var fs = require('fs'); -var solc = require('solc'); -//compile smart contract to get bytecode and abi - -var source = fs.readFileSync("./test.sol",'utf8'); //读取代码 - //console.log("compiling contract..."); -var compiledcontract = solc.compile(source); //编译 - //console.log('done'); -for (var contractName in compiledcontract.contracts){ - var bytecode = compiledcontract.contracts[contractName].bytecode; - var abi = JSON.parse(compiledcontract.contracts[contractName].interface); -} -//console.log(JSON.stringify(abi, undefined, 2)); -//console.log(bytecode); -//console.log(abi); - -function bytecode(){ - console.log(bytecode); -} -function abi(){ - console.log(abi); -} -module.exports = {bytecode:bytecode,abi:abi}; - -``` -depoly.js通过与Geth交互部署智能合约。当合约被区块链确认后,会直接返回合约地址。 -```javascript -var Web3 = require('web3'); -var contract = require('./test'); -var web; - -//connect to node -var ethereumUri = 'http://localhost:8545'; -if (typeof web3 !== 'undefined') { - web3 = new Web3(web3.currentProvider); -} else { - // set the provider you want from Web3.providers - web3 = new Web3(new Web3.providers.HttpProvider(ethereumUri)); -} -//查询区块链中基本的账户信息 -if(!web3.isConnected()){ - throw new Error('unable to connect to ethereum node at '+ ethereumUri); -}else{ - console.log('connected to etherum node at '+ ethereumUri); - var coinbase = web3.eth.accounts[0]; - console.log('coinbase:' + coinbase); - var balance = web3.eth.getBalance(coinbase); - console.log('balance:' + web3.fromWei(balance, 'ether') + " ETH"); - var accounts = web3.eth.accounts; - console.log(accounts); -} -//通过coinbase部署智能合约 -var abi = contract.abi; -var bytecode = contract.bytecode; - -if (web3.personal.unlockAccount(coinbase, '123')) { - console.log(`${coinbase} is unlocaked`); -}else{ - console.log(`unlock failed, ${coinbase}`); -} - -var gasEstimate = web3.eth.estimateGas({data: '0x' + bytecode}); //gas估计 -console.log('gasEstimate = ' + gasEstimate); -var MyContract = web3.eth.contract(abi); -console.log('deploying contract...'); -var myContractReturned = MyContract.new({ - from: coinbase, - data: '0x'+ bytecode, - gas: gasEstimate + 50000 -}, function (err, myContract) { - if (!err) { - if (!myContract.address) { - console.log(`myContract.transactionHash = ${myContract.transactionHash}`); // The hash of the transaction, which deploys the contract - // check address on the second call (contract deployed) - } else { - console.log(`myContract.address = ${myContract.address}`); // the contract address - global.contractAddress = myContract.address; - } - - } else { - console.log(err); - } -}); -``` - - -**参考自:** - -[Go Ethereum](https://geth.ethereum.org/docs/install-and-build/installing-geth) - -[以太坊私有链Geth控制台操作教程](https://www.jianshu.com/p/9fa31e4cdf4d) - -[尚硅谷区块链全套Go语言→GoWeb→以太坊→项目实战](https://www.bilibili.com/video/BV1sJ411D72u) -[web3.js 1.0中文手册](http://cw.hubwiz.com/card/c/web3.js-1.0/) diff --git a/Blockchain/part4_合约编写实例补充.md b/Blockchain/part4_合约编写实例补充.md deleted file mode 100644 index 5d1a5f7..0000000 --- a/Blockchain/part4_合约编写实例补充.md +++ /dev/null @@ -1,402 +0,0 @@ - 注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 - -# 合约编写实战实例 - -## 一、简单代币合约 - -```javascript -pragma solidity > 0.4.22; - -contract Coin{ - //这里我们定义了一个address 作为key, uint做为value的hashTable balances; 我们还定义了一个address的变量minter; - address public minter; - mapping(address=>uint) balances; - event Sent(address from, address to, uint amount); - constructor(){ - //代表创建这个合约的账户地址,被赋值给变量minter. - minter = msg.sender; - } - - //添加一个挖矿合约 - function mint(address receiver, uint amount) public{ - require(msg.sender == minter); - balances[receiver] += amount; - - } - function send(address receiver, uint amount) public{ - require(balances[msg.sender] >= amount); - balances[msg.sender] -= amount; - balances[receiver] += amount; - emit Sent(msg.sender,receiver,amount); - } - -} -``` - -解析: -上面实现一个简单的加密货币,币在这里可以无中生有,但只有创建合约的人才能做到,且任何人都可以给他人转币,无需注册名和密码。 - -`address`类型是一个160位的值,不允许任何算数操作,这种类型适合存储合约地址或外部人员。 - -`mappings`可看作是一个哈希表,它会执行虚拟初始化,以使得所有可能存在的键都映射到一个字节表示为全零的值。 - -`event Sent(address from, address to, uint amount)`;声明了一个所谓的事件,它在send函数最后一行被发出。用户界面可以监听区块链上正在发送的事件,且不会花费太多成本,一旦它被发出,监听该事件的listener都将收到通知,而所有的事件都包含了`from`,`t`o和`amoun`t三个参数,可方便追踪事务。 - -`msg.sender`始终是当前函数或者外部函数调用的来源地址。 - -最后真正被用户和其他合约所调用的,用于完成本合约功能的方法是`mint`和`send`。若`mint`被合约创建者外的其他调用则说明都不会发生。 - -`send`函数可被任何人用于向其他人发送代币,前提是发送者拥有这些代币,若使用合约发送代币给一个地址,当在区块链浏览器上查到该地址时时看不到任何相关信息的,因为,实际上发送币和更改余额的信息仅仅存在特定合约的数据存储器中。通过使用事件,可非常简单地为新币创建一个区块链浏览器来追踪交易和余额。 - -
- -
-
-
- - - -## 二、水龙头合约 - -在前面我们通过 Ropsten 测试网络的水龙头(Faucet)获取了一些以太币,并提到可以向水龙头账户发送以太币来捐赠以太币。实际上,水龙头账户是一个合约账户,水龙头就是一份合约,而整个网站就是合约+前端组成的DApp。下面我们通过 Remix 来编写一个简单的水龙头合约,借此了解如何创建、部署合约以及一些 Solidity 的基本语法。 - -首先打开 Remix,并新建一个名为 faucet.sol 的文件,该文件就是 Solidity 的源文件 - -
- -
-
-
- -打开 faucet.sol,并写入如下代码 - -```javascript -pragma solidity ^0.7.0; - -contract faucet { - function withdraw (uint amount) public { - require (amount <= 1e18); - msg.sender.transfer (amount); - } - - receive () external payable {} -} -``` - -通过这几行代码我们就实现了一个非常简单的水龙头合约。首行代码 `pragma solidity ^0.7.0 `是一个**杂注**,指定了我们的源文件使用的编译器版本不能低于 0.7.0,也不能高于 0.8.0。 - -`contract faucet{...}` 声明了一个合约对象,合约对象类似面向对象语言中的类,对象名必须跟文件名相同。 - -接下来通过 `function withdraw (uint amount) public {...}` 创建了一个名为 withdraw 的函数,该函数接收一个无符号整数(uint)作为参数,并且被声明为 public 函数,意为可以被其他合约调用。 - -withdraw 函数体中的 `require` 是 Solidity 的内置函数,用来检测括号中的条件是否满足。条件满足则继续执行合约,条件不满足则合约停止执行,回撤所有执行过的操作,并抛出异常。在这里我们通过 `require (amount <= 1e18)` 来检测输入的以太币值是否小于等于1个以太。 - -接下来的这一行 `msg.sender.transfer (amount)` 就是实际的提款操作了。`msg` 是 Solidity 中内置的对象,所有合约都可以访问,它代表触发此合约的交易。也就是说当我们调用 `withdraw` 函数的时候实际上触发了一笔交易,并用 `msg` 来表示它。`sender` 是交易 `msg` 的属性,表示了交易的发件人地址。函数 `transfer` 是一个内置函数,它接收一个参数作为以太币的数量,并将该数量的以太币从合约账户发送到调用合约的用户的地址中。 - -最后一行是一个特殊的函数 `receive` ,这是所谓的 `fallback` 或 `default` 函数。当合约中的其他函数无法处理发送到合约中的交易信息时,就会执行该函数。在这里,我们将该函数声明为 `external` 和 `payable` ,`external` 意味着该函数可以接收来自外部账户的调用,`payable` 意味着该函数可以接收来自外部账户发送的以太币。 - -这样,当我们调用合约中的 `withdraw` 并提供一个参数时,我们可以从这份合约中提出以太币;当我们向合约发送以太币时,就会调用 `receive` 函数往合约中捐赠以太币。 - -代码编写完毕后,在 Remix 左侧的功能栏中选择第二项,并点击 *Compile faucet.sol* 来编译我们的 sol 文件。 - -
- -
-
-
- - -编译完成后会出现一个 Warning,提示我们添加 SPDX license,可以忽略。 - -随后选择 Remix 左侧工具栏的第三项,进入合约部署界面 - -
- -
-
-
- - -首先将 ENVIRONMENT 选择为 Injected Web3,这样才能通过 MetaMask 钱包来发送交易。 - -随后点击 Deploy 部署合约,MetaMask 会弹出部署合约的交易界面 - -
- -
-
-
- - -因为该笔交易是合约创建交易,因此我们支付的以太币为0,但仍需支付一定的 Gas 费用,可以自己设定 Gas 的价格。 - -合约部署成功后会收到 Chrome 的消息提示,并在 Remix 的 Deployed Contracts 中也会有显示 - -
- -
-
-
- - -这样我们就完成了这个水龙头合约的部署。 - -#### 水龙头测试 #### - -我们刚刚创建的水龙头中还没有以太坊,因此我们可以通过 MetaMask 向水龙头合约的地址中发送一些以太坊。水龙头合约的地址会显示在 Remix 中的,见上图 FAUCET AT 0X7A4...34219,可以直接复制。 - -
- -
-
-
- - -交易被确认后,我们的水龙头中就有了0.999726个以太币,现在我们可以通过 Remix 中合约一栏的 withdraw 按钮来提取以太币了。需要注意,这里输入的以太币个数是以 wei 为单位的。 - -
- -
-
-
- - -点击 withdraw 后,会弹出警告框 - -
- -
-
-
- - -这是因为目前我们还没有设置这笔交易的 Gas,不用担心,点击 Send Transaction 后,在弹出的 MetaMask 中设置即可。 - -交易被确认后,我们得到了刚刚提取的0.999726个以太币 - -
- -
-
-
- - -若大家没有执行成功可以重新做一次、查找其他资料或者[观看此视频](https://www.bilibili.com/video/BV1sJ411D72u?p=465) - -## 三、投票合约的实现 - - - -本次教程将以一个较复杂的投票合约作为结束,我们希望实现的功能是为每个(投票)建议建立一份合约,然后作为合约的创造者-主席,主席将赋予每个成员(地址)投票权,而成员的投票权可以选择委托给其他人也可以自己投票,结束时将返回投票最多的提案。听起来很简单一个功能实现起来却较为复杂,下面我们拆分开进行讲解 - -注: - -1. 代码可直接在Remix编辑器的已有solidity文件中找到,在contract/_Ballot.sol文件里 -2. 若学习者前面部分掌握较牢固,不妨尝试直接自行阅读代码,无需阅读本节内容 - - -首先我们定义成员类型,我们为每个投票者定义权重、是否已投票、 - -```javascript -struct Voter { - uint weight; // weight is accumulated by delegation - bool voted; // if true, that person already voted - address delegate; // person delegated to - uint vote; // index of the voted proposal -} -``` - -然后我们定义提案类型,包含提案名和投票总数: - -```javascript -struct Proposal { - bytes32 name; // short name (up to 32 bytes) - uint voteCount; // number of accumulated votes -} -``` - -定义三个变量,主席是一个公开的地址,建立投票者与地址的映射,然后定义提案动态数组: - -```javascript -address public chairperson; -mapping(address => Voter) public voters; -Proposal[] public proposals; -``` - -- `address public chairperson`:投票发起人,类型为 address。 -- `mapping(address => Voter) public voters`:所有投票人,类型为 `address` 到 `Voter` 的映射。 -- `Proposal[] public proposals`:所有提案,类型为动态大小的 `Proposal` 数组。 - -3 个状态变量都使用了 `public` 关键字,使得变量可以被外部访问(即通过消息调用)。事实上,编译器会自动为 `public `的变量创建同名的 `getter` 函数,供外部直接读取。 - -我们还需要为每个投票赋予初始权值,并将主席的权重设置为1。我们一般使用`constructor`赋初值,这与C++等语言类似: - -```javascript -constructor(bytes32[] memory proposalNames) { - chairperson = msg.sender; - voters[chairperson].weight = 1; - - for (uint i = 0; i < proposalNames.length; i++) { - proposals.push(Proposal({ - name: proposalNames[i], - voteCount: 0 - })); - } -} -``` - -所有提案的名称通过参数 `bytes32[] proposalNames` 传入,逐个记录到状态变量 `proposals` 中。同时用 `msg.sender` 获取当前调用消息的发送者的地址,记录为投票发起人 `chairperson`,该发起人投票权重设为 1。 - -接下来我们需要给每个投票者赋予权重: - -```javascript -function giveRightToVote(address voter) public { - require( - msg.sender == chairperson, - "Only chairperson can give right to vote." - ); - require( - !voters[voter].voted, - "The voter already voted." - ); - require(voters[voter].weight == 0); - voters[voter].weight = 1; -} -``` - -该函数给 `address voter` 赋予投票权,即将 `voter` 的投票权重设为 1,存入 `voters` 状态变量。 - -上面这个函数只有投票发起人 `chairperson` 可以调用。这里用到了 `require((msg.sender == chairperson) && !voters[voter].voted)` 函数。如果` require` 中表达式结果为 `false`,这次调用会中止,且回滚所有状态和以太币余额的改变到调用前。但已消耗的 `Gas` 不会返还。 - -下面一段是整段代码的重点,其作用是委托其他人代理投票,基本思路是: - -1. 使用`require`判断委托人是否已投票(若投过票再委托则重复投票),并判断被委托对象是否是自己 -2. 当判断被委托人不是0地址(主席)时,被委托人代理委托人的票,【绕口警告】由于被委托人也可能委托了别人,因此这里需要一直循环直到找到最后没有委托别人的被委托人为止! -3. 委托人找到对应的被委托人,委托人已投票(避免重复投票) -4. 判断被委托人是否已投票,若投了票则将被委托人投的提案票数加上委托人的权重,若未投票则令被委托人的权重加上委托人的权重(以后投票自然相当于投两票) - -注:该函数使用了 `while` 循环,这里合约编写者需要十分谨慎,防止调用者消耗过多 `Gas`,甚至出现死循环。 - -```javascript -function delegate(address to) public { - Voter storage sender = voters[msg.sender]; - require(!sender.voted, "You already voted."); - require(to != msg.sender, "Self-delegation is disallowed."); - - while (voters[to].delegate != address(0)) { - to = voters[to].delegate; - require(to != msg.sender, "Found loop in delegation."); - } - sender.voted = true; - sender.delegate = to; - Voter storage delegate_ = voters[to]; - if (delegate_.voted) { - proposals[delegate_.vote].voteCount += sender.weight; - } else { - delegate_.weight += sender.weight; - } -} -``` - -投票部分仅是几个简单的条件判断: - -```javascript -function vote(uint proposal) public { - Voter storage sender = voters[msg.sender]; - require(sender.weight != 0, "Has no right to vote"); - require(!sender.voted, "Already voted."); - sender.voted = true; - sender.vote = proposal; - proposals[proposal].voteCount += sender.weight; - } -``` - -用 `voters[msg.sender]` 获取投票人,即此次调用的发起人。接下来检查是否是重复投票,如果不是,进行投票后相关状态变量的更新。 - -接下来是计算获胜提案: - -```javascript -function winningProposal() public view - returns (uint winningProposal_) -{ - uint winningVoteCount = 0; - for (uint p = 0; p < proposals.length; p++) { - if (proposals[p].voteCount > winningVoteCount) { - winningVoteCount = proposals[p].voteCount; - winningProposal_ = p; - } - } -} -``` - -`returns (uint winningProposal)` 指定了函数的返回值类型,`constant` 表示该函数不会改变合约状态变量的值。 - -最后是查询获胜者名称: - -```javascript -function winnerName() public view - returns (bytes32 winnerName_) -{ - winnerName_ = proposals[winningProposal()].name; -} -``` - -这里采用内部调用 `winningProposal()` 函数的方式获得获胜提案。如果需要采用外部调用,则需要写为 `this.winningProposal()`。 - - - -**参考自:** - -[尚硅谷区块链全套Go语言→GoWeb→以太坊→项目实战](https://www.bilibili.com/video/BV1sJ411D72u) -[web3.js 1.0中文手册](http://cw.hubwiz.com/card/c/web3.js-1.0/) - diff --git a/Blockchain/pic/2021-02-19_110327.png b/Blockchain/pic/2021-02-19_110327.png deleted file mode 100644 index 7b03f57..0000000 Binary files a/Blockchain/pic/2021-02-19_110327.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_120756.png b/Blockchain/pic/2021-02-19_120756.png deleted file mode 100644 index f3e64de..0000000 Binary files a/Blockchain/pic/2021-02-19_120756.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_125917.png b/Blockchain/pic/2021-02-19_125917.png deleted file mode 100644 index 2d77359..0000000 Binary files a/Blockchain/pic/2021-02-19_125917.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_130053.png b/Blockchain/pic/2021-02-19_130053.png deleted file mode 100644 index 35b98d3..0000000 Binary files a/Blockchain/pic/2021-02-19_130053.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_133508.png b/Blockchain/pic/2021-02-19_133508.png deleted file mode 100644 index 78c5567..0000000 Binary files a/Blockchain/pic/2021-02-19_133508.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_134110.png b/Blockchain/pic/2021-02-19_134110.png deleted file mode 100644 index c7e7ba5..0000000 Binary files a/Blockchain/pic/2021-02-19_134110.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_134257.png b/Blockchain/pic/2021-02-19_134257.png deleted file mode 100644 index 75d426b..0000000 Binary files a/Blockchain/pic/2021-02-19_134257.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_134634.png b/Blockchain/pic/2021-02-19_134634.png deleted file mode 100644 index eb63545..0000000 Binary files a/Blockchain/pic/2021-02-19_134634.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_135113.png b/Blockchain/pic/2021-02-19_135113.png deleted file mode 100644 index a8f69cc..0000000 Binary files a/Blockchain/pic/2021-02-19_135113.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_135337.png b/Blockchain/pic/2021-02-19_135337.png deleted file mode 100644 index 1b3998b..0000000 Binary files a/Blockchain/pic/2021-02-19_135337.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_135413.png b/Blockchain/pic/2021-02-19_135413.png deleted file mode 100644 index b24c373..0000000 Binary files a/Blockchain/pic/2021-02-19_135413.png and /dev/null differ diff --git a/Blockchain/pic/2021-02-19_142407.png b/Blockchain/pic/2021-02-19_142407.png deleted file mode 100644 index 10704d3..0000000 Binary files a/Blockchain/pic/2021-02-19_142407.png and /dev/null differ diff --git a/Blockchain/pic/EOA_CA.png b/Blockchain/pic/EOA_CA.png deleted file mode 100644 index 9612e9b..0000000 Binary files a/Blockchain/pic/EOA_CA.png and /dev/null differ diff --git a/Blockchain/pic/bg2017122703.png b/Blockchain/pic/bg2017122703.png deleted file mode 100644 index 5c91bc5..0000000 Binary files a/Blockchain/pic/bg2017122703.png and /dev/null differ diff --git a/Blockchain/pic/gas.jpg b/Blockchain/pic/gas.jpg deleted file mode 100644 index 13bd71c..0000000 Binary files a/Blockchain/pic/gas.jpg and /dev/null differ diff --git a/Blockchain/pic/image-20210219000835894.png b/Blockchain/pic/image-20210219000835894.png deleted file mode 100644 index 5fea35e..0000000 Binary files a/Blockchain/pic/image-20210219000835894.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219101124978.png b/Blockchain/pic/image-20210219101124978.png deleted file mode 100644 index 28411df..0000000 Binary files a/Blockchain/pic/image-20210219101124978.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219101226095.png b/Blockchain/pic/image-20210219101226095.png deleted file mode 100644 index 8ddead4..0000000 Binary files a/Blockchain/pic/image-20210219101226095.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219101300792.png b/Blockchain/pic/image-20210219101300792.png deleted file mode 100644 index 2dcad74..0000000 Binary files a/Blockchain/pic/image-20210219101300792.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219101332089.png b/Blockchain/pic/image-20210219101332089.png deleted file mode 100644 index e74b090..0000000 Binary files a/Blockchain/pic/image-20210219101332089.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219102028033.png b/Blockchain/pic/image-20210219102028033.png deleted file mode 100644 index 966feb4..0000000 Binary files a/Blockchain/pic/image-20210219102028033.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219102255927.png b/Blockchain/pic/image-20210219102255927.png deleted file mode 100644 index 8acef84..0000000 Binary files a/Blockchain/pic/image-20210219102255927.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219102322360.png b/Blockchain/pic/image-20210219102322360.png deleted file mode 100644 index 513c9de..0000000 Binary files a/Blockchain/pic/image-20210219102322360.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219102359096.png b/Blockchain/pic/image-20210219102359096.png deleted file mode 100644 index aa9a394..0000000 Binary files a/Blockchain/pic/image-20210219102359096.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219105522149.png b/Blockchain/pic/image-20210219105522149.png deleted file mode 100644 index f3e6648..0000000 Binary files a/Blockchain/pic/image-20210219105522149.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219105616335.png b/Blockchain/pic/image-20210219105616335.png deleted file mode 100644 index a23c2dd..0000000 Binary files a/Blockchain/pic/image-20210219105616335.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219105824087.png b/Blockchain/pic/image-20210219105824087.png deleted file mode 100644 index c03a2b4..0000000 Binary files a/Blockchain/pic/image-20210219105824087.png and /dev/null differ diff --git a/Blockchain/pic/image-20210219105911910.png b/Blockchain/pic/image-20210219105911910.png deleted file mode 100644 index eff8c5c..0000000 Binary files a/Blockchain/pic/image-20210219105911910.png and /dev/null differ diff --git a/Blockchain/pic/image1.png b/Blockchain/pic/image1.png deleted file mode 100644 index 8435a7e..0000000 Binary files a/Blockchain/pic/image1.png and /dev/null differ diff --git a/Blockchain/pic/image2.png b/Blockchain/pic/image2.png deleted file mode 100644 index 5688b8c..0000000 Binary files a/Blockchain/pic/image2.png and /dev/null differ diff --git a/Blockchain/pic/image3.png b/Blockchain/pic/image3.png deleted file mode 100644 index c3bfdca..0000000 Binary files a/Blockchain/pic/image3.png and /dev/null differ diff --git a/Blockchain/pic/image4.png b/Blockchain/pic/image4.png deleted file mode 100644 index 3ce8d94..0000000 Binary files a/Blockchain/pic/image4.png and /dev/null differ diff --git a/Blockchain/pic/image5.png b/Blockchain/pic/image5.png deleted file mode 100644 index 3038f58..0000000 Binary files a/Blockchain/pic/image5.png and /dev/null differ diff --git a/Blockchain/pic/image6.png b/Blockchain/pic/image6.png deleted file mode 100644 index 82f5ecc..0000000 Binary files a/Blockchain/pic/image6.png and /dev/null differ diff --git a/Blockchain/pic/part2-1.png b/Blockchain/pic/part2-1.png deleted file mode 100644 index 8c0da19..0000000 Binary files a/Blockchain/pic/part2-1.png and /dev/null differ diff --git a/Blockchain/pic/part2-10.png b/Blockchain/pic/part2-10.png deleted file mode 100644 index f0b447e..0000000 Binary files a/Blockchain/pic/part2-10.png and /dev/null differ diff --git a/Blockchain/pic/part2-11.png b/Blockchain/pic/part2-11.png deleted file mode 100644 index 06cca10..0000000 Binary files a/Blockchain/pic/part2-11.png and /dev/null differ diff --git a/Blockchain/pic/part2-12.png b/Blockchain/pic/part2-12.png deleted file mode 100644 index 5eb5b8e..0000000 Binary files a/Blockchain/pic/part2-12.png and /dev/null differ diff --git a/Blockchain/pic/part2-13.png b/Blockchain/pic/part2-13.png deleted file mode 100644 index e9433fa..0000000 Binary files a/Blockchain/pic/part2-13.png and /dev/null differ diff --git a/Blockchain/pic/part2-14.png b/Blockchain/pic/part2-14.png deleted file mode 100644 index f87ff72..0000000 Binary files a/Blockchain/pic/part2-14.png and /dev/null differ diff --git a/Blockchain/pic/part2-15.png b/Blockchain/pic/part2-15.png deleted file mode 100644 index abebc5d..0000000 Binary files a/Blockchain/pic/part2-15.png and /dev/null differ diff --git a/Blockchain/pic/part2-16.png b/Blockchain/pic/part2-16.png deleted file mode 100644 index 7a91692..0000000 Binary files a/Blockchain/pic/part2-16.png and /dev/null differ diff --git a/Blockchain/pic/part2-17.png b/Blockchain/pic/part2-17.png deleted file mode 100644 index d831f99..0000000 Binary files a/Blockchain/pic/part2-17.png and /dev/null differ diff --git a/Blockchain/pic/part2-18.png b/Blockchain/pic/part2-18.png deleted file mode 100644 index c42e156..0000000 Binary files a/Blockchain/pic/part2-18.png and /dev/null differ diff --git a/Blockchain/pic/part2-19.png b/Blockchain/pic/part2-19.png deleted file mode 100644 index cf84b29..0000000 Binary files a/Blockchain/pic/part2-19.png and /dev/null differ diff --git a/Blockchain/pic/part2-2.png b/Blockchain/pic/part2-2.png deleted file mode 100644 index a8ecb2c..0000000 Binary files a/Blockchain/pic/part2-2.png and /dev/null differ diff --git a/Blockchain/pic/part2-20.png b/Blockchain/pic/part2-20.png deleted file mode 100644 index cf3cb04..0000000 Binary files a/Blockchain/pic/part2-20.png and /dev/null differ diff --git a/Blockchain/pic/part2-21.png b/Blockchain/pic/part2-21.png deleted file mode 100644 index 84896de..0000000 Binary files a/Blockchain/pic/part2-21.png and /dev/null differ diff --git a/Blockchain/pic/part2-22.png b/Blockchain/pic/part2-22.png deleted file mode 100644 index db238cf..0000000 Binary files a/Blockchain/pic/part2-22.png and /dev/null differ diff --git a/Blockchain/pic/part2-23.png b/Blockchain/pic/part2-23.png deleted file mode 100644 index 256124b..0000000 Binary files a/Blockchain/pic/part2-23.png and /dev/null differ diff --git a/Blockchain/pic/part2-24.png b/Blockchain/pic/part2-24.png deleted file mode 100644 index 6007692..0000000 Binary files a/Blockchain/pic/part2-24.png and /dev/null differ diff --git a/Blockchain/pic/part2-3.png b/Blockchain/pic/part2-3.png deleted file mode 100644 index 1ff0dda..0000000 Binary files a/Blockchain/pic/part2-3.png and /dev/null differ diff --git a/Blockchain/pic/part2-4.png b/Blockchain/pic/part2-4.png deleted file mode 100644 index 8879b10..0000000 Binary files a/Blockchain/pic/part2-4.png and /dev/null differ diff --git a/Blockchain/pic/part2-5.png b/Blockchain/pic/part2-5.png deleted file mode 100644 index 6308a3d..0000000 Binary files a/Blockchain/pic/part2-5.png and /dev/null differ diff --git a/Blockchain/pic/part2-6.png b/Blockchain/pic/part2-6.png deleted file mode 100644 index debad8c..0000000 Binary files a/Blockchain/pic/part2-6.png and /dev/null differ diff --git a/Blockchain/pic/part2-7.png b/Blockchain/pic/part2-7.png deleted file mode 100644 index f9a3ea0..0000000 Binary files a/Blockchain/pic/part2-7.png and /dev/null differ diff --git a/Blockchain/pic/part2-8.png b/Blockchain/pic/part2-8.png deleted file mode 100644 index ed642a5..0000000 Binary files a/Blockchain/pic/part2-8.png and /dev/null differ diff --git a/Blockchain/pic/part2-9.png b/Blockchain/pic/part2-9.png deleted file mode 100644 index 8c1e817..0000000 Binary files a/Blockchain/pic/part2-9.png and /dev/null differ diff --git a/Blockchain/pic/part3-1.png b/Blockchain/pic/part3-1.png deleted file mode 100644 index 094515c..0000000 Binary files a/Blockchain/pic/part3-1.png and /dev/null differ diff --git a/Blockchain/pic/part3-10.png b/Blockchain/pic/part3-10.png deleted file mode 100644 index 1c2c0c6..0000000 Binary files a/Blockchain/pic/part3-10.png and /dev/null differ diff --git a/Blockchain/pic/part3-11.png b/Blockchain/pic/part3-11.png deleted file mode 100644 index d6f6474..0000000 Binary files a/Blockchain/pic/part3-11.png and /dev/null differ diff --git a/Blockchain/pic/part3-12.png b/Blockchain/pic/part3-12.png deleted file mode 100644 index 30c4582..0000000 Binary files a/Blockchain/pic/part3-12.png and /dev/null differ diff --git a/Blockchain/pic/part3-13.png b/Blockchain/pic/part3-13.png deleted file mode 100644 index a1f9783..0000000 Binary files a/Blockchain/pic/part3-13.png and /dev/null differ diff --git a/Blockchain/pic/part3-2.png b/Blockchain/pic/part3-2.png deleted file mode 100644 index 2d74cd1..0000000 Binary files a/Blockchain/pic/part3-2.png and /dev/null differ diff --git a/Blockchain/pic/part3-3.png b/Blockchain/pic/part3-3.png deleted file mode 100644 index 824fd9b..0000000 Binary files a/Blockchain/pic/part3-3.png and /dev/null differ diff --git a/Blockchain/pic/part3-4.png b/Blockchain/pic/part3-4.png deleted file mode 100644 index 152a017..0000000 Binary files a/Blockchain/pic/part3-4.png and /dev/null differ diff --git a/Blockchain/pic/part3-5.png b/Blockchain/pic/part3-5.png deleted file mode 100644 index 9b62fe6..0000000 Binary files a/Blockchain/pic/part3-5.png and /dev/null differ diff --git a/Blockchain/pic/part3-6.png b/Blockchain/pic/part3-6.png deleted file mode 100644 index 00e7473..0000000 Binary files a/Blockchain/pic/part3-6.png and /dev/null differ diff --git a/Blockchain/pic/part3-7.png b/Blockchain/pic/part3-7.png deleted file mode 100644 index f6dfe7a..0000000 Binary files a/Blockchain/pic/part3-7.png and /dev/null differ diff --git a/Blockchain/pic/part3-8.png b/Blockchain/pic/part3-8.png deleted file mode 100644 index 212eb36..0000000 Binary files a/Blockchain/pic/part3-8.png and /dev/null differ diff --git a/Blockchain/pic/part3-9.png b/Blockchain/pic/part3-9.png deleted file mode 100644 index 567481f..0000000 Binary files a/Blockchain/pic/part3-9.png and /dev/null differ diff --git a/Blockchain/pic/picfile b/Blockchain/pic/picfile deleted file mode 100644 index ca8de66..0000000 --- a/Blockchain/pic/picfile +++ /dev/null @@ -1 +0,0 @@ -This folder contains some necessary picture diff --git a/Blockchain/pic/rating.png b/Blockchain/pic/rating.png deleted file mode 100644 index 204d972..0000000 Binary files a/Blockchain/pic/rating.png and /dev/null differ diff --git a/Blockchain/pic/transaction-struct.png b/Blockchain/pic/transaction-struct.png deleted file mode 100644 index da26c42..0000000 Binary files a/Blockchain/pic/transaction-struct.png and /dev/null differ diff --git a/Blockchain/pic/utxo_com.jpg b/Blockchain/pic/utxo_com.jpg deleted file mode 100644 index 77c03d0..0000000 Binary files a/Blockchain/pic/utxo_com.jpg and /dev/null differ diff --git a/Blockchain/readme.md b/Blockchain/readme.md deleted file mode 100644 index 93a5f75..0000000 --- a/Blockchain/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -## 简介 - -项目负责人来写项目的介绍吧。 \ No newline at end of file