diff --git a/Blockchain/Linux基础(补充).md b/Blockchain/Linux基础(补充).md new file mode 100644 index 0000000..31a152c --- /dev/null +++ b/Blockchain/Linux基础(补充).md @@ -0,0 +1,372 @@ +# 新手建议 + +## 学习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 new file mode 100644 index 0000000..96e52b7 --- /dev/null +++ b/Blockchain/part1_基础知识介绍.md @@ -0,0 +1,509 @@ + 注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 + +本次组队学习重点在于以太坊基础知识、以太坊客户端以及以太坊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 new file mode 100644 index 0000000..c17f1e4 --- /dev/null +++ b/Blockchain/part2_Solidity基础.md @@ -0,0 +1,1012 @@ + 注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 + +# 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 + + +**参考自:** + +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) \ No newline at end of file diff --git a/Blockchain/part3_web3js.md b/Blockchain/part3_web3js.md new file mode 100644 index 0000000..d5f51d8 --- /dev/null +++ b/Blockchain/part3_web3js.md @@ -0,0 +1,1151 @@ +注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 + +## 一、以太坊客户端 + +### 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/) \ No newline at end of file diff --git a/Blockchain/part4_合约编写实例补充.md b/Blockchain/part4_合约编写实例补充.md new file mode 100644 index 0000000..e3ffefd --- /dev/null +++ b/Blockchain/part4_合约编写实例补充.md @@ -0,0 +1,406 @@ + 注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 + +# 合约编写实战实例 + +## 一、简单代币合约 + +```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) + +## 三、投票合约的实现 + +最后是一个较复杂的投票合约作为结束,我们希望实现的功能是有一个主席,主席将赋予每个成员投票权,而成员的投票权可以委托给其他人,结束时将返回投票最多的提案。听起来很简单一个功能实现起来却较为复杂,下面我们拆分开进行讲解 + + + +# 3.1、投票合约的实现 + +本次教程将以一个较复杂的投票合约作为结束,我们希望实现的功能是为每个(投票)建议建立一份合约,然后作为合约的创造者-主席,主席将赋予每个成员(地址)投票权,而成员的投票权可以选择委托给其他人也可以自己投票,结束时将返回投票最多的提案。听起来很简单一个功能实现起来却较为复杂,下面我们拆分开进行讲解 + +注: + +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 new file mode 100644 index 0000000..7b03f57 Binary files /dev/null and b/Blockchain/pic/2021-02-19_110327.png differ diff --git a/Blockchain/pic/2021-02-19_120756.png b/Blockchain/pic/2021-02-19_120756.png new file mode 100644 index 0000000..f3e64de Binary files /dev/null and b/Blockchain/pic/2021-02-19_120756.png differ diff --git a/Blockchain/pic/2021-02-19_125917.png b/Blockchain/pic/2021-02-19_125917.png new file mode 100644 index 0000000..2d77359 Binary files /dev/null and b/Blockchain/pic/2021-02-19_125917.png differ diff --git a/Blockchain/pic/2021-02-19_130053.png b/Blockchain/pic/2021-02-19_130053.png new file mode 100644 index 0000000..35b98d3 Binary files /dev/null and b/Blockchain/pic/2021-02-19_130053.png differ diff --git a/Blockchain/pic/2021-02-19_133508.png b/Blockchain/pic/2021-02-19_133508.png new file mode 100644 index 0000000..78c5567 Binary files /dev/null and b/Blockchain/pic/2021-02-19_133508.png differ diff --git a/Blockchain/pic/2021-02-19_134110.png b/Blockchain/pic/2021-02-19_134110.png new file mode 100644 index 0000000..c7e7ba5 Binary files /dev/null and b/Blockchain/pic/2021-02-19_134110.png differ diff --git a/Blockchain/pic/2021-02-19_134257.png b/Blockchain/pic/2021-02-19_134257.png new file mode 100644 index 0000000..75d426b Binary files /dev/null and b/Blockchain/pic/2021-02-19_134257.png differ diff --git a/Blockchain/pic/2021-02-19_134634.png b/Blockchain/pic/2021-02-19_134634.png new file mode 100644 index 0000000..eb63545 Binary files /dev/null and b/Blockchain/pic/2021-02-19_134634.png differ diff --git a/Blockchain/pic/2021-02-19_135113.png b/Blockchain/pic/2021-02-19_135113.png new file mode 100644 index 0000000..a8f69cc Binary files /dev/null and b/Blockchain/pic/2021-02-19_135113.png differ diff --git a/Blockchain/pic/2021-02-19_135337.png b/Blockchain/pic/2021-02-19_135337.png new file mode 100644 index 0000000..1b3998b Binary files /dev/null and b/Blockchain/pic/2021-02-19_135337.png differ diff --git a/Blockchain/pic/2021-02-19_135413.png b/Blockchain/pic/2021-02-19_135413.png new file mode 100644 index 0000000..b24c373 Binary files /dev/null and b/Blockchain/pic/2021-02-19_135413.png differ diff --git a/Blockchain/pic/2021-02-19_142407.png b/Blockchain/pic/2021-02-19_142407.png new file mode 100644 index 0000000..10704d3 Binary files /dev/null and b/Blockchain/pic/2021-02-19_142407.png differ diff --git a/Blockchain/pic/EOA_CA.png b/Blockchain/pic/EOA_CA.png new file mode 100644 index 0000000..9612e9b Binary files /dev/null and b/Blockchain/pic/EOA_CA.png differ diff --git a/Blockchain/pic/bg2017122703.png b/Blockchain/pic/bg2017122703.png new file mode 100644 index 0000000..5c91bc5 Binary files /dev/null and b/Blockchain/pic/bg2017122703.png differ diff --git a/Blockchain/pic/gas.jpg b/Blockchain/pic/gas.jpg new file mode 100644 index 0000000..13bd71c Binary files /dev/null and b/Blockchain/pic/gas.jpg differ diff --git a/Blockchain/pic/image-20210219000835894.png b/Blockchain/pic/image-20210219000835894.png new file mode 100644 index 0000000..5fea35e Binary files /dev/null and b/Blockchain/pic/image-20210219000835894.png differ diff --git a/Blockchain/pic/image-20210219101124978.png b/Blockchain/pic/image-20210219101124978.png new file mode 100644 index 0000000..28411df Binary files /dev/null and b/Blockchain/pic/image-20210219101124978.png differ diff --git a/Blockchain/pic/image-20210219101226095.png b/Blockchain/pic/image-20210219101226095.png new file mode 100644 index 0000000..8ddead4 Binary files /dev/null and b/Blockchain/pic/image-20210219101226095.png differ diff --git a/Blockchain/pic/image-20210219101300792.png b/Blockchain/pic/image-20210219101300792.png new file mode 100644 index 0000000..2dcad74 Binary files /dev/null and b/Blockchain/pic/image-20210219101300792.png differ diff --git a/Blockchain/pic/image-20210219101332089.png b/Blockchain/pic/image-20210219101332089.png new file mode 100644 index 0000000..e74b090 Binary files /dev/null and b/Blockchain/pic/image-20210219101332089.png differ diff --git a/Blockchain/pic/image-20210219102028033.png b/Blockchain/pic/image-20210219102028033.png new file mode 100644 index 0000000..966feb4 Binary files /dev/null and b/Blockchain/pic/image-20210219102028033.png differ diff --git a/Blockchain/pic/image-20210219102255927.png b/Blockchain/pic/image-20210219102255927.png new file mode 100644 index 0000000..8acef84 Binary files /dev/null and b/Blockchain/pic/image-20210219102255927.png differ diff --git a/Blockchain/pic/image-20210219102322360.png b/Blockchain/pic/image-20210219102322360.png new file mode 100644 index 0000000..513c9de Binary files /dev/null and b/Blockchain/pic/image-20210219102322360.png differ diff --git a/Blockchain/pic/image-20210219102359096.png b/Blockchain/pic/image-20210219102359096.png new file mode 100644 index 0000000..aa9a394 Binary files /dev/null and b/Blockchain/pic/image-20210219102359096.png differ diff --git a/Blockchain/pic/image-20210219105522149.png b/Blockchain/pic/image-20210219105522149.png new file mode 100644 index 0000000..f3e6648 Binary files /dev/null and b/Blockchain/pic/image-20210219105522149.png differ diff --git a/Blockchain/pic/image-20210219105616335.png b/Blockchain/pic/image-20210219105616335.png new file mode 100644 index 0000000..a23c2dd Binary files /dev/null and b/Blockchain/pic/image-20210219105616335.png differ diff --git a/Blockchain/pic/image-20210219105824087.png b/Blockchain/pic/image-20210219105824087.png new file mode 100644 index 0000000..c03a2b4 Binary files /dev/null and b/Blockchain/pic/image-20210219105824087.png differ diff --git a/Blockchain/pic/image-20210219105911910.png b/Blockchain/pic/image-20210219105911910.png new file mode 100644 index 0000000..eff8c5c Binary files /dev/null and b/Blockchain/pic/image-20210219105911910.png differ diff --git a/Blockchain/pic/image1.png b/Blockchain/pic/image1.png new file mode 100644 index 0000000..8435a7e Binary files /dev/null and b/Blockchain/pic/image1.png differ diff --git a/Blockchain/pic/image2.png b/Blockchain/pic/image2.png new file mode 100644 index 0000000..5688b8c Binary files /dev/null and b/Blockchain/pic/image2.png differ diff --git a/Blockchain/pic/image3.png b/Blockchain/pic/image3.png new file mode 100644 index 0000000..c3bfdca Binary files /dev/null and b/Blockchain/pic/image3.png differ diff --git a/Blockchain/pic/image4.png b/Blockchain/pic/image4.png new file mode 100644 index 0000000..3ce8d94 Binary files /dev/null and b/Blockchain/pic/image4.png differ diff --git a/Blockchain/pic/image5.png b/Blockchain/pic/image5.png new file mode 100644 index 0000000..3038f58 Binary files /dev/null and b/Blockchain/pic/image5.png differ diff --git a/Blockchain/pic/image6.png b/Blockchain/pic/image6.png new file mode 100644 index 0000000..82f5ecc Binary files /dev/null and b/Blockchain/pic/image6.png differ diff --git a/Blockchain/pic/part2-1.png b/Blockchain/pic/part2-1.png new file mode 100644 index 0000000..8c0da19 Binary files /dev/null and b/Blockchain/pic/part2-1.png differ diff --git a/Blockchain/pic/part2-10.png b/Blockchain/pic/part2-10.png new file mode 100644 index 0000000..f0b447e Binary files /dev/null and b/Blockchain/pic/part2-10.png differ diff --git a/Blockchain/pic/part2-11.png b/Blockchain/pic/part2-11.png new file mode 100644 index 0000000..06cca10 Binary files /dev/null and b/Blockchain/pic/part2-11.png differ diff --git a/Blockchain/pic/part2-12.png b/Blockchain/pic/part2-12.png new file mode 100644 index 0000000..5eb5b8e Binary files /dev/null and b/Blockchain/pic/part2-12.png differ diff --git a/Blockchain/pic/part2-13.png b/Blockchain/pic/part2-13.png new file mode 100644 index 0000000..e9433fa Binary files /dev/null and b/Blockchain/pic/part2-13.png differ diff --git a/Blockchain/pic/part2-14.png b/Blockchain/pic/part2-14.png new file mode 100644 index 0000000..f87ff72 Binary files /dev/null and b/Blockchain/pic/part2-14.png differ diff --git a/Blockchain/pic/part2-15.png b/Blockchain/pic/part2-15.png new file mode 100644 index 0000000..abebc5d Binary files /dev/null and b/Blockchain/pic/part2-15.png differ diff --git a/Blockchain/pic/part2-16.png b/Blockchain/pic/part2-16.png new file mode 100644 index 0000000..7a91692 Binary files /dev/null and b/Blockchain/pic/part2-16.png differ diff --git a/Blockchain/pic/part2-17.png b/Blockchain/pic/part2-17.png new file mode 100644 index 0000000..d831f99 Binary files /dev/null and b/Blockchain/pic/part2-17.png differ diff --git a/Blockchain/pic/part2-18.png b/Blockchain/pic/part2-18.png new file mode 100644 index 0000000..c42e156 Binary files /dev/null and b/Blockchain/pic/part2-18.png differ diff --git a/Blockchain/pic/part2-19.png b/Blockchain/pic/part2-19.png new file mode 100644 index 0000000..cf84b29 Binary files /dev/null and b/Blockchain/pic/part2-19.png differ diff --git a/Blockchain/pic/part2-2.png b/Blockchain/pic/part2-2.png new file mode 100644 index 0000000..a8ecb2c Binary files /dev/null and b/Blockchain/pic/part2-2.png differ diff --git a/Blockchain/pic/part2-20.png b/Blockchain/pic/part2-20.png new file mode 100644 index 0000000..cf3cb04 Binary files /dev/null and b/Blockchain/pic/part2-20.png differ diff --git a/Blockchain/pic/part2-21.png b/Blockchain/pic/part2-21.png new file mode 100644 index 0000000..84896de Binary files /dev/null and b/Blockchain/pic/part2-21.png differ diff --git a/Blockchain/pic/part2-22.png b/Blockchain/pic/part2-22.png new file mode 100644 index 0000000..db238cf Binary files /dev/null and b/Blockchain/pic/part2-22.png differ diff --git a/Blockchain/pic/part2-23.png b/Blockchain/pic/part2-23.png new file mode 100644 index 0000000..256124b Binary files /dev/null and b/Blockchain/pic/part2-23.png differ diff --git a/Blockchain/pic/part2-24.png b/Blockchain/pic/part2-24.png new file mode 100644 index 0000000..6007692 Binary files /dev/null and b/Blockchain/pic/part2-24.png differ diff --git a/Blockchain/pic/part2-3.png b/Blockchain/pic/part2-3.png new file mode 100644 index 0000000..1ff0dda Binary files /dev/null and b/Blockchain/pic/part2-3.png differ diff --git a/Blockchain/pic/part2-4.png b/Blockchain/pic/part2-4.png new file mode 100644 index 0000000..8879b10 Binary files /dev/null and b/Blockchain/pic/part2-4.png differ diff --git a/Blockchain/pic/part2-5.png b/Blockchain/pic/part2-5.png new file mode 100644 index 0000000..6308a3d Binary files /dev/null and b/Blockchain/pic/part2-5.png differ diff --git a/Blockchain/pic/part2-6.png b/Blockchain/pic/part2-6.png new file mode 100644 index 0000000..debad8c Binary files /dev/null and b/Blockchain/pic/part2-6.png differ diff --git a/Blockchain/pic/part2-7.png b/Blockchain/pic/part2-7.png new file mode 100644 index 0000000..f9a3ea0 Binary files /dev/null and b/Blockchain/pic/part2-7.png differ diff --git a/Blockchain/pic/part2-8.png b/Blockchain/pic/part2-8.png new file mode 100644 index 0000000..ed642a5 Binary files /dev/null and b/Blockchain/pic/part2-8.png differ diff --git a/Blockchain/pic/part2-9.png b/Blockchain/pic/part2-9.png new file mode 100644 index 0000000..8c1e817 Binary files /dev/null and b/Blockchain/pic/part2-9.png differ diff --git a/Blockchain/pic/part3-1.png b/Blockchain/pic/part3-1.png new file mode 100644 index 0000000..094515c Binary files /dev/null and b/Blockchain/pic/part3-1.png differ diff --git a/Blockchain/pic/part3-10.png b/Blockchain/pic/part3-10.png new file mode 100644 index 0000000..1c2c0c6 Binary files /dev/null and b/Blockchain/pic/part3-10.png differ diff --git a/Blockchain/pic/part3-11.png b/Blockchain/pic/part3-11.png new file mode 100644 index 0000000..d6f6474 Binary files /dev/null and b/Blockchain/pic/part3-11.png differ diff --git a/Blockchain/pic/part3-12.png b/Blockchain/pic/part3-12.png new file mode 100644 index 0000000..30c4582 Binary files /dev/null and b/Blockchain/pic/part3-12.png differ diff --git a/Blockchain/pic/part3-13.png b/Blockchain/pic/part3-13.png new file mode 100644 index 0000000..a1f9783 Binary files /dev/null and b/Blockchain/pic/part3-13.png differ diff --git a/Blockchain/pic/part3-2.png b/Blockchain/pic/part3-2.png new file mode 100644 index 0000000..2d74cd1 Binary files /dev/null and b/Blockchain/pic/part3-2.png differ diff --git a/Blockchain/pic/part3-3.png b/Blockchain/pic/part3-3.png new file mode 100644 index 0000000..824fd9b Binary files /dev/null and b/Blockchain/pic/part3-3.png differ diff --git a/Blockchain/pic/part3-4.png b/Blockchain/pic/part3-4.png new file mode 100644 index 0000000..152a017 Binary files /dev/null and b/Blockchain/pic/part3-4.png differ diff --git a/Blockchain/pic/part3-5.png b/Blockchain/pic/part3-5.png new file mode 100644 index 0000000..9b62fe6 Binary files /dev/null and b/Blockchain/pic/part3-5.png differ diff --git a/Blockchain/pic/part3-6.png b/Blockchain/pic/part3-6.png new file mode 100644 index 0000000..00e7473 Binary files /dev/null and b/Blockchain/pic/part3-6.png differ diff --git a/Blockchain/pic/part3-7.png b/Blockchain/pic/part3-7.png new file mode 100644 index 0000000..f6dfe7a Binary files /dev/null and b/Blockchain/pic/part3-7.png differ diff --git a/Blockchain/pic/part3-8.png b/Blockchain/pic/part3-8.png new file mode 100644 index 0000000..212eb36 Binary files /dev/null and b/Blockchain/pic/part3-8.png differ diff --git a/Blockchain/pic/part3-9.png b/Blockchain/pic/part3-9.png new file mode 100644 index 0000000..567481f Binary files /dev/null and b/Blockchain/pic/part3-9.png differ diff --git a/Blockchain/pic/picfile b/Blockchain/pic/picfile new file mode 100644 index 0000000..ca8de66 --- /dev/null +++ b/Blockchain/pic/picfile @@ -0,0 +1 @@ +This folder contains some necessary picture diff --git a/Blockchain/pic/rating.png b/Blockchain/pic/rating.png new file mode 100644 index 0000000..204d972 Binary files /dev/null and b/Blockchain/pic/rating.png differ diff --git a/Blockchain/pic/transaction-struct.png b/Blockchain/pic/transaction-struct.png new file mode 100644 index 0000000..da26c42 Binary files /dev/null and b/Blockchain/pic/transaction-struct.png differ diff --git a/Blockchain/pic/utxo_com.jpg b/Blockchain/pic/utxo_com.jpg new file mode 100644 index 0000000..77c03d0 Binary files /dev/null and b/Blockchain/pic/utxo_com.jpg differ diff --git a/LanQiao/readme.md b/LanQiao/readme.md index 523353d..6b6c154 100644 --- a/LanQiao/readme.md +++ b/LanQiao/readme.md @@ -21,17 +21,24 @@ ### Task00:熟悉规则(1天) - 组队、修改群昵称 - 熟悉打卡规则 + ### Task01:热身练习(2天) - 完成热身练习文件夹中的7道题目 - 熟悉基本输入输出及蓝桥杯的练习系统的使用方法。 -### Task02 基础练习(3天) + +### Task02:基础练习(3天) - 完成基础练习文件夹中的7道题目 -### Task03 基础练习2(3天) + +### Task03:基础练习2(3天) - 完成基础练习2文件夹中的8道题目 -### Task4 真题练习(3天) + +### Task04:真题练习(3天) - 完成真题练习文件夹中的10道题目 -### Task5 真题练习2(3天) + +### Task05:真题练习2(3天) - 完成真题练习文件夹中的8道题目 + + ## 开源贡献者 韩绘锦:华北电力大学 diff --git a/Scratch/readme.md b/Scratch/readme.md index e69de29..64b9d16 100644 --- a/Scratch/readme.md +++ b/Scratch/readme.md @@ -0,0 +1,102 @@ + + +# 青少年编程(Scratch)电子学会一级 + +开源学习内容:https://github.com/datawhalechina/team-learning-program/tree/master/Scratch + +## 基本信息 + +- 贡献人员:马燕鹏、王思齐 +- 学习周期:12天,每天平均花费时间1小时,根据个人学习接受能力强弱有所浮动。 +- 学习形式:模拟测试题 +- 人群定位:Scratch初学者 +- 先修内容:无 +- 难度系数:低 + +## 学习目标 + +由于测试题目为往年电子学会Scratch一级考试的真题,所以通过做题来掌握Scratch一级考试的知识点,为通过考试做准备。 + +**一级考试大纲与说明** + +(一)考核目标 + +学生对编程软件的界面认识和基本操作,初步能够导入角色和设置背景,并通过对角色的不同操作以及加入声音,形成一个具有简单顺序结构代码的作品,同时针对参加1级考试的学生将进行简单的逻辑推理能力的考查。 + +(二)能力目标 + +通过本级考试的学生,对软件认识良好,会进行软件的基本操作,能完成基本作品。 + +(三)考试标准 + +1、初步学会使用编程工具,理解编程工具中的核心概念。 + +- 1)理解编程环境界面中功能区的分布与作用; +- 2)能够完成拖拽程序模块到程序区的操作并进行正确的连接; +- 3)能够通过舞台区按钮完成运行与停止程序的操作; +- 4)会使用角色的移动、旋转指令模块; +- 5)能为作品添加背景音乐,并设置声音的播放代码; +- 6)能够绘制背景并对背景进行切换; +- 7)能够打开计算机上已保存的项目和保存新制作的项目。 + +2、按照规定的功能编写出完整的顺序结构程序。 + +- 1)掌握顺序结构流程图的画法; +- 2)理解参数的概念,能够调整指令模块中的参数; +- 3)能够完成一个顺序结构的程序; +- 4)程序中包含播放一段音频和切换背景; +- 5)程序中包含切换角色的造型,角色移动和旋转; +- 6)按指定的要求保存作品。 + +(四)知识块 + +- [x] 1. 熟悉编程软件:舞台区,角色区,模块区,脚本区,造型标签,声音标签,背景标签,新建和保存作品,语言的选择,从本地打开软件,程序的运行和停止 +- [x] 2. 角色的导入:库导入,绘制,本地导入等方式,大小设置,顺序结构流程图 +- [x] 3. 背景的认识:选取合适的背景,背景和角色的区别,背景的切换 +- [x] 4. 角色的操作:移动,旋转,造型切换 +- [x] 5. 声音的导入:导入声音并设置为背景音乐,设定声音音效,设定声音音量,设定声音的播放和停止 +- [x] 6. 逻辑推理,编程数学:逻辑推理,形象思维(图形推理) + + +(五)题型配比及分值 + +![](https://img-blog.csdnimg.cn/20210216175928249.png) + + + + +## 任务安排 + +### Task00:熟悉规则【1天】 + +- 组队、修改群昵称。 +- 熟悉打卡规则。 + +### Task01:熟悉Scratch【2天】 +- 熟悉Scratch考级(一级)的考试标准 +- 熟悉Scratch考级(一级)的知识点 + + +### Task02:模拟题(一)【2天】 +- 课堂派:判断题15个,单选题20个 +- 写博客:编程题(01 森林的一天、02 舞者凯希) + + +### Task03:模拟题(二)【2天】 +- 课堂派:判断题15个,单选题20个 +- 写博客:编程题(03 小狗散步、04 猫捉老鼠) + + +### Task04:模拟题(三)【2天】 +- 课堂派:判断题15个,单选题20个 +- 写博客:编程题(05 城堡漫步、06 火箭发射) + +### Task05:模拟题(四)【2天】 +- 课堂派:判断题15个,单选题20个 +- 写博客:编程题(07 飞向太空、08 小狗长大记) + + +### Task06:模拟题(五)【2天】 +- 课堂派:判断题15个,单选题20个 +- 写博客:编程题(09 运动起来、10 动物园之旅) + diff --git a/Scratch/等级考试-01/参考答案.md b/Scratch/等级考试-01/参考答案.md new file mode 100644 index 0000000..cb1fd58 --- /dev/null +++ b/Scratch/等级考试-01/参考答案.md @@ -0,0 +1,12 @@ +# 参考答案 + +- [01 森林的一天](https://mp.weixin.qq.com/s/k-_r7xHGVW335e1FyJKIhg) +- [02 舞者凯希](https://mp.weixin.qq.com/s/km6rJbI9Ih0heThkWZF27g) +- [03 小狗散步](https://mp.weixin.qq.com/s/LelNrrmRTIUocde-x0kVzQ) +- [04 猫捉老鼠](https://mp.weixin.qq.com/s/jPMGhincv_nnk4VOJ1XyzQ) +- [05 城堡漫步](https://mp.weixin.qq.com/s/S--pZGgJvv5LHU1mX3p15w) +- [06 火箭发射](https://mp.weixin.qq.com/s/M_agy8qJCmBIZ6ui6sK3Ww) +- [07 飞向太空](https://mp.weixin.qq.com/s/9eN4MRVCQcV4z7otV7Omfg) +- [08 小狗长大记](https://mp.weixin.qq.com/s/Gh5jCN2fu6-clPcYevuWlw) +- [09 运动起来](https://mp.weixin.qq.com/s/amK1Rxi_uqoyJFy-nDB9ug) +- [10 动物园之旅](https://mp.weixin.qq.com/s/Ff_GB4AnBtrEHwVTCB45qw) \ No newline at end of file diff --git a/Turtle/Day1:画几个简单的图形.md b/Turtle/Day1:画几个简单的图形.md new file mode 100644 index 0000000..49cf62a --- /dev/null +++ b/Turtle/Day1:画几个简单的图形.md @@ -0,0 +1,76 @@ +通过前面的学习,相信你已经能够参考【图图】的说明来进行绘图创造了,那么从现在开始,我们将正式开始各种趣味图形绘画。 + +![图片](https://uploader.shimo.im/f/z5w4Mp2Q2dvxv2kL.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +🔑 在【冲鸭】之前,请记住一个【锦囊妙计】,后面的画图过程中会涉及到各种各样的功能,有时我们可能会忘记指令怎么写,所以在每次训练的开头会为大家附上一个【图图】功能查询表,忘了就去看看吧。 + +|向前走
|t.forward(100)
| +|:----|:----|:----|:----| +|向后走
|t.back(100)
| +|向左转
|t.left(90)
| +|向右转
|t.right(90)
| + + +万事俱备,只欠冲锋,好啦,正式开始我们图形挑战之旅吧!!! + +# 🚀挑战1: + +这是一道送分题,别说你不会,赶紧的~ + +题目描述:请参考下面图片中的内容,从左向右画出一条长度为100的直线吧。 + +![图片](https://uploader.shimo.im/f/MmUDB3ej2w5xtdWB.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战2: + +送分题又来了! + +题目描述:请参考下面图片中的内容,从左向右画出一个度数为90的角吧。 + +(边的长度自己决定就好啦,可以也设置成100) + +![图片](https://uploader.shimo.im/f/9rXYtOPvhuGaBXNV.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 📺示例1: + +下面,我们将一起来画几个简单的图形,第一个图形是一个三条边长度都为100的三角形,想一想该怎么画出来,如果想不出来,可以试试在草稿纸上画一画,要注意每次【图图】画完一条线后旋转的角度哦。 + +![图片](https://uploader.shimo.im/f/y88d2B5n0SbNBcBU.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +下面是三角形的答案,一定要自己尝试后再来看哦: + +**讲解**:三角形一共有三条边,【图图】向前100后,完成第一条边的绘制,然后需要向右旋转120度,接着又向前100,完成第二条边后再向右旋转120度,向前100,完成最后一条边的绘制 + +```plain +import turtle as t + +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +#连续画三条边 +t.forward(100) +t.right(120) +t.forward(100) +t.right(120) +t.forward(100) +t.right(120) +t.done() +``` +# 🚀挑战3: + +通过学习后你已经能画出三角形了,那么,再用同样的方式来试试正方形吧 + +![图片](https://uploader.shimo.im/f/8kVczix7Ad8vYuWM.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战4: + +看来你已经能绘制简单图形了,那么现在来尝试一下复杂一点的“十字”图吧,方法和前面的图形差不多哦,但是注意每转动一次后移动的距离: + +![图片](https://uploader.shimo.im/f/JcOy5RnSxjJbrFic.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +真棒,这么快就完成了第一天的打卡内容了,是不是觉得还挺简单,哈哈,别着急,后面会逐渐提高难度,小心哦~ + +![图片](https://uploader.shimo.im/f/ou0HQfieY21z7jXg.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + + + + diff --git a/Turtle/Day2:你妈妈叫你回家啦.md b/Turtle/Day2:你妈妈叫你回家啦.md new file mode 100644 index 0000000..1bf173c --- /dev/null +++ b/Turtle/Day2:你妈妈叫你回家啦.md @@ -0,0 +1,108 @@ +# 🔑技能包: + +别忘了昨天学习的技能哦: + +|向前走
|t.forward(100)
| +|:----|:----|:----|:----| +|向后走
|t.back(100)
| +|向左转
|t.left(90)
| +|向右转
|t.right(90)
| + + +—————————————————————————————————————————————— + +今天是第二天,主题是【你妈妈叫你回家啦】 + +![图片](https://uploader.shimo.im/f/VOm1PbEwChZi0cRU.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +老师,你在开玩笑? + +哈哈,今天的指令确实和回家有关系,叫做【回家指令】,【回家指令】这是个啥?名字起得这么有创意(无聊)吗?难道要让图图回家? + +咳咳咳,回答正确,就是一个让图图回家的指令。 + +# 1、回家指令介绍 + +## (1)简介 + +为什么叫做回家指令呢?这是因为他是通过英文名home()翻译过来的,通过这一条指令可以帮助图图回到起点,也就是说,即使你通过指令让图图跑到了千里之外,我也能用这个指令让他回来,是不是一个很6的指令: + +![图片](https://uploader.shimo.im/f/Ud76cAcedR1IhshP.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +## (2)格式 + +那么,这么6的指令可以用来做什么呢?我来举个例子🌰吧: + +### 📺示例1: + +题目描述:请参考下面图片中的内容,画出一个直角三角形吧: + +![图片](https://uploader.shimo.im/f/jeiHYXRKE8aeeWe5.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +只知道两个直角边分别是80和60,但是另外一条斜边长度不知道,这个时候怎么办呢? + +**路人甲:**不知道哎 + +**路人乙:**我是初中生,我学过【勾股定理】可以算 + +**隔壁小明:**难道是用home指令 + +是的,隔壁小明真聪明,通过home指令就能解决这个问题,最后一条边其实就是图图返回起点走的路: + +![图片](https://uploader.shimo.im/f/sPin1Jnb5tPoocUl.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +程序如下: + +```plain +import turtle as t + +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.forward(80) +t.right(90) +t.forward(60) +t.home() # 返回起点 +t.done() +``` +这样,通过home指令,让图图自动返回到起点,于是就画出了最后一条边,是不是非常简单,来试试吧! + +# 🚀挑战1: + +题目描述:请参考下面图片中的内容,画出一个钝角三角形吧。 + +(备注:钝角三角形就是有一个角度数大于90的的三角形) + +![图片](https://uploader.shimo.im/f/gOh79n3MxtgcoYEG.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战2: + +题目描述:请参考下面图片中的内容,画出对应的图形吧。 + +![图片](https://uploader.shimo.im/f/gE4tnK9swOtGDjGq.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战3: + +有点厉害啊,已经解决了两个挑战了,那么咱们增加下难度吧! + +题目描述:请参考下面图片中的内容,画出有一条对角线的正方形吧。 + +备注1:对角线就是两个相对的角顶点的连接线 + +备注2:图图回到起点后会自动旋转到水平向右 + +![图片](https://uploader.shimo.im/f/W435f35qwvvquhbq.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战4: + +能做到这里看来你已经掌握home指令的诀窍了,既然这样,那就来完成最后的挑战吧。 + +题目描述:请参考下面图片中的内容,画出一个直角梯形。 + +备注:上底就是较短的一条边,下底就是较长的一条边 + +提示:上底、下底、高的长度需要你自己设定,只要能画出类似的直角梯形就可以啦 + +![图片](https://uploader.shimo.im/f/L6igm9Gio52P8kqJ.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + + + diff --git a/Turtle/Day3:初识循环.md b/Turtle/Day3:初识循环.md new file mode 100644 index 0000000..14bec66 --- /dev/null +++ b/Turtle/Day3:初识循环.md @@ -0,0 +1,129 @@ +今天,正儿八经给大家普及下新知识。 + +⛽️知识加油站——循环 + +循环在我们生活中非常普遍,比如我们乘坐旋转木马,旋转木马会一直循环,又比如说月球会一直不停绕着太阳旋转: + +![图片](https://uploader.shimo.im/f/v20NLdEY5vmO9o9s.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +而在编程中循环也是一个非常重要知识,它可以让程序一直重复执行,比如下面这个程序,虽然只输入了一句话,但是通过设置循环次数为5,就能打印5次“我喜欢看海贼王”: + +```plain +for i in range(5): + print('我喜欢看海贼王') +``` +## 循环的格式 + +想使用循环非常简单,只需要按照下面的格式设置就可以了: + +```plain +# 括号里填写你想循环的次数,写上冒号后,换行,空4个空格后就可以写循环的内容 +for i in range(次数): + 循环的内容 +``` +📺练习1: + +使用循环画一个正方形。 + +![图片](https://uploader.shimo.im/f/3yj46wvFLN6I4S7T.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +一起来分析下,要画一个正方形,需要下面这些步骤: + +```plain +向前100 +旋转90度 +向前100 +旋转90度 +向前100 +旋转90度 +向前100 +旋转90度 +``` +可以看成,我们要让计算机重复执行4次下面的内容: + +```plain +下面的内容要重复执行4次: +向前100 +向右旋转90度 +``` +这样就就可以写成循环了: + +```plain +for i in range(4): + t.forward(100) + t.right(90) +``` +完整代码: + +```plain +import turtle as t + +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +#连续画四条边 +for i in range(4): + t.forward(100) + t.right(90) +t.done() +``` +🚀挑战1: + +题目描述:通过循环画出一个边长为100的等边三角形 + +备注:等边三角形就是三条边的长度相等、三个角角度都为60度的三角形 + +![图片](https://uploader.shimo.im/f/vvvLj67CO4lNmlDr.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +🚀挑战2: + +题目描述:通过循环画出一个正六边形 + +备注:正六边形每个角的度数(大小)都一样,6个角总共720度 + +提示:需要先计算正六边形的每个角大小,再得出图图每次需要转多少度,这是小学三年级除法的难度,你敢说不会?ㄟ(▔︵▔ㄟ) + +![图片](https://uploader.shimo.im/f/O5R5DJFoGg2DP58d.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +🚀挑战3: + +能完成上面两道题,说明你还是很厉害的,那么咱们就提高一点难度吧,这道题你还能做对,那可就非常厉害了。 + +题目描述:使用循环,画出一个五角星:(可以像上面那样,先写出文字步骤来分析一下哦~) + +👽拓展:如果画好了,还可以给五角星加上颜色 + +![图片](https://uploader.shimo.im/f/xpbOnEmE6z9VJeHo.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +🚀挑战4: + +光阴似箭,日月如梭,一转眼,你,竟然,做到了最后一道题,你,这么,厉害,爸妈知道吗?如果不知道就赶紧告诉他们吧。 + +下面是第三天的终极挑战:画一个圆 + +![图片](https://uploader.shimo.im/f/EIGelDQKcdrDrkos.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +提示:画圆其实很简单,虽然你看着是一个圆,但其实,他可以看成是很多短短的边组成的,那么怎么来画圆呢?你可以看一看下面这个视频: + +[https://baike.baidu.com/item/%E5%89%B2%E5%9C%86%E6%9C%AF/595781?secondId=25702197](https://baike.baidu.com/item/%E5%89%B2%E5%9C%86%E6%9C%AF/595781?secondId=25702197) + +![图片](https://uploader.shimo.im/f/wbE6BsDlqZagEc21.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +看完视频是不是发现啦,古代人通过在圆内画多边形来近似测量圆一周的长度,多边形边越多,形状上就越接近圆,那么基于这个方法,我们可以用图图来帮我们画一个圆,怎么来画呢,我们知道圆有360°,也就是说,如果要画圆,【图图】需要围绕中心走360次,每一次走一步,每走一步向右转1度,画出一个有360边的多边形就可以了。 + +下面就是画圆的关键,如果你不能理解,那就记住这个有趣的画圆公式吧: + +```plain +import turtle as t + +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +#请删除这一行,然后写上循环360次的程序吧 + t.forward(1) #向前走一步 + t.right(1) #向右转一度 + +t.done() +``` +如果你已经画好了圆,再试一试修改forward里面的数字,看看会发生什么变化~ + +# + diff --git a/Turtle/Day4:up!up!down!down!.md b/Turtle/Day4:up!up!down!down!.md new file mode 100644 index 0000000..a228075 --- /dev/null +++ b/Turtle/Day4:up!up!down!down!.md @@ -0,0 +1,94 @@ +A:老师,你是在欺负我英语不好吗?整个英文标题 + +老师:对啊,你怎么知道我在欺负你 + +A:... ... + +# 1、抬笔与落笔 + +## (1)简介 + +今天我们一起来学习一个新技能【抬笔】与【落笔】。 + +前面我们画了这么多图形,但是大家有没有发现,我们从来没有抬过笔,这是一件非常不科学的事,就好比,你笔尖从没离开过画纸,但却写下了下面这四个字(不能写连笔): + +![图片](https://uploader.shimo.im/f/DNOyGRVpuS8hqtz1.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +所以,在常规的绘画和写字中,我们其实是需要【抬笔】和【落笔】这两个动作的,同样的,图图也有这两个指令: + +## (2)格式 + +```plain +t.up() # 抬笔 +t.down() # 落笔 +``` +把这两句放到完整的程序中,先前进100步,然后抬笔,前进50步,然后落笔,然后再前进100步, + +想一想可能画出的图形是什么样子,然后再运行一下这段代码看一看吧: + +```plain +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.forward(100) +t.up() #抬笔 +t.forward(50) +t.down() #落笔 +t.forward(100) +t.done() +``` +# 🚀挑战1: + +题目描述:画出一条虚线 + +(1)实线每一段长为10 + +(2)间断距离为5 + +提示:使用循环会更方便哦 + +![图片](https://uploader.shimo.im/f/JhWBeyJdBiGQwufS.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战2: + +题目描述:绘制8条平行线 + +(1)每条线的长度为100 + +(2)相邻两条线同一端之间的距离为20 + +(备注:平行线倾斜程度自己设定就好,可以用文字写出走的步骤,看看循环的部分在哪里) + +![图片](https://uploader.shimo.im/f/pazI1NHqBi2XkqtY.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战3: + +不知不觉,又来到了最后一题,哦不对,是倒数第二题,这一题和上面一题难度差不多,耐心一点就能画出来。 + +题目描述:请画出四个平行的小旗子 + +(1)旗子高40 + +(2)旗面宽 + +![图片](https://uploader.shimo.im/f/pmNf7tLQE3GyNH7U.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战4: + +不知不觉,终于来到了最后一题,你还记得之前画过的的圆吗?这次有了抬笔功能的配合,我们就可以画出一个很常见的汽车标志啦: + +(1)两个圆中心之间的距离是85 + +(2)想一想每画完一个圆后【图图】要向前移动多少呢? + +![图片](https://uploader.shimo.im/f/wBVgm9UwCbUncGru.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +画一个圆的关键步骤参考: + +```plain +for i in range(360): + t.forward(1) + t.right(1) +``` + + diff --git a/Turtle/Day5:循环升级.md b/Turtle/Day5:循环升级.md new file mode 100644 index 0000000..928ed88 --- /dev/null +++ b/Turtle/Day5:循环升级.md @@ -0,0 +1,139 @@ +**老师**:通过两天的训练,相信大家对于循环已经掌握的非常好了,今天我们要学习的是【循环中的循环】 + +**A**:【循环中的循环】?老师,你是在传授我武林秘籍吗? + +**老师**:你是猴子派来的吗?总想着武林秘籍 + +**A**:那什么是【循环中的循环】啊? + +**老师**:听我细细道来 + +# 1、循环中的循环 + +循环中的循环是编程中常用的一种程序的结构,他也被称为【循环嵌套】,也就是在一个大循环里套着小循环,听起来有点复杂,但其实非常的简单,一起来学习一下吧: + +在学习【循环嵌套】前我们先来练习一道题,这样有助于我们理解【循环嵌套】的含义。 + +# 🚀挑战1: + +题目描述:参考下面图示,借助循环,画出四个并排在一起的正方形 + +(1)正方形的边长是40 + +(2)两个正方形之间的距离是20 + +![图片](https://uploader.shimo.im/f/9JvrXBlJSw8OuycC.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +⬇️⬇️⬇️ 做完后再看,实在做不出来也可以看,不到万不得已还是别看 ⬇️⬇️⬇️ + + +--- + + +**解析**:大家说如果要画四个正方形,需要重复画几个一样的正方形? + +4个对吧,那如果用图图来画一个正方形需要重复几次【向前走100向右转90度】呢 + +也是4次,所以我们来看看,如果要通过图图来画出它需要怎么做: + +```plain +#画第一个正方形 +for i in range(4): + t.forward(40) + t.right(90) +#画完后要向前移动到到第二个正方形的顶点,需要向前移动60步,移动的时候是抬笔的状态 +t.up() +t.forward(60) +t.down() +``` +![图片](https://uploader.shimo.im/f/i9jE9VYQDwoMHGI9.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +如果还要画三次应该怎么办呢?哈哈,对的,继续用同样的语句三遍就行了 + +```plain +#画第一个正方形 +for i in range(4): + t.forward(40) + t.right(90) +t.up() +t.forward(60) +t.down() +#画第二个正方形 +for i in range(4): + t.forward(40) + t.right(90) +t.up() +t.forward(60) +t.down() +#画第三个正方形 +for i in range(4): + t.forward(40) + t.right(90) +t.up() +t.forward(60) +t.down() +#画第四个正方形 +for i in range(4): + t.forward(40) + t.right(90) +t.up() +t.forward(60) +t.down() +``` +这个时候你发现了什么,是不是下面段落连续出现了4次,我们学习循环时说过,当重复不断出现时可以使用循环,这里仍然可以的,将这个循环的逻辑写成中文就是: + +```plain +下面的内容要循环4次: + for i in range(4): + t.forward(40) + t.right(90) + t.up() + t.forward(60) + t.down() +``` +把中文也变成程序,和之前循环是一样的方式,加上for i in range(),但是一定要记得,后面这个整体前面有四个空格,还记得之前讲循环的时候说过吧,加四个空格是为了告诉程序,下面的内容归我管,要循环4次,可不要忘了哦: + +![图片](https://uploader.shimo.im/f/hXQivhdLgQK3j7mD.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +是不是觉得好像听懂了,又好像有一点疑惑,怎么办呢?很简单,再来做两道题自然就懂了: + +# 🚀挑战2: + +题目描述:参考下面的图示,借助循环,画出四个并排在一起的三角形 + +(提示:➡️可以不用循环嵌套,但是呢,使用循环嵌套又会更简单,算了,用不用看你吧 + +➡️如果使用嵌套,你可以参考上面画4个正方形的方法哦,先用中文分析循环次数,在写成程序) + +(1)等边三角形的边长是40 + +(2)两个三角形之间的距离是20 + +![图片](https://uploader.shimo.im/f/8T5mc6z8VKs2L9Fg.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战3: + +不得了不得了,已经做到第3题了,少年我看你天赋异禀啊! + +题目描述:参考下面的图示,借助循环,画出一个风车 + +![图片](https://uploader.shimo.im/f/E0pfiSvFjcIEL3hr.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +👽拓展:在风车的基础上进行创作吧,看看你的风车能比下面的好看吗? + +![图片](https://uploader.shimo.im/f/9inOstKpjljcCLmW.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +![图片](https://uploader.shimo.im/f/VeaMiDeb7kYMnVjq.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 🚀挑战4(选做): + +这道题选做哦,如果你觉得前面的已经难不倒你了,那就再来试试这一道题吧! + +题目描述:参考下面的图示,借助循环,画出一个风车 + +(1)菱形的两个角分别是60°和90° + +![图片](https://uploader.shimo.im/f/BumBYN0F8YnkfShm.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + + + diff --git a/Turtle/Day6:乾坤大挪移——坐标.md b/Turtle/Day6:乾坤大挪移——坐标.md new file mode 100644 index 0000000..d55ac63 --- /dev/null +++ b/Turtle/Day6:乾坤大挪移——坐标.md @@ -0,0 +1,93 @@ +# 1、坐标 + +地瓜:土豆土豆,我是地瓜,我现在已经被敌人包围了,请求支援 + +土豆:地瓜地瓜,我是土豆,请立刻告诉我你的坐标 + +![图片](https://uploader.shimo.im/f/DhnYpoXEg77ElkYm.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +什么是坐标呢?读完上面这个对话你肯定有点感觉了吧,坐标就是一个物体在空间中的位置,就比如上面飞机在天空中的位置,你需要告诉友军他的位置,友军才能去营救。 + +又比如说下面这个图,就是一个平面空间,我们在上面画了两根轴来帮助大家记录坐标,这两根轴就叫做坐标轴,两根轴交叉的就是坐标为(0,0)的地方,那么怎么来读取坐标呢?其实非常简单,看【图图】对应坐标轴上的数字就可以了。 + +![图片](https://uploader.shimo.im/f/LfrVWDN6OYr0pq3K.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +就比如这样,图图的位置分别对应横轴的1,对应纵轴的3,所以他的坐标是(1,3): + +![图片](https://uploader.shimo.im/f/nzm1q3NzzoM7nfT6.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +## 🚀挑战1: + +题目描述:请你写出图图在在下图中的位置: + +(对应横轴和纵轴来看哦~) + +![图片](https://uploader.shimo.im/f/6fneCP2r8lbCmYYO.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 2、goto功能 + +那么,怎么才能帮图图到指定的坐标呢?这就要使用到一个有趣的功能goto,它可以帮助我们去到指定的坐标位置,但是在使用它之前需要记住两个重要秘诀: + +### 秘诀1:如果使用goto功能时没有抬笔,那么图图会向着指定的位置画过去 + +这个秘诀说的什么意思呢?我们一起来看看: + +(1)goto语句的用法 + +```plain +t.goto(x, y) # x,y就是对应的坐标 +``` +如果我使用下面这一段语句,你猜猜会发生什么: + +```plain +t.goto(80,80) +``` +出现了下面的图像,原因是因为我们让图图去坐标是(80,80)的位置,所以图图就直接移动过去了,不过使用goto语句,图图的朝向是不会改变的哦: + +![图片](https://uploader.shimo.im/f/axsrooNYe9fh63Tm.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +那么,如果我想让图图**瞬间移动**过去要怎么办呢,这时候秘诀2了 + +### 秘诀2:要让图图瞬间移动到某个坐标,而不留下痕迹,就要使用抬笔和落笔功能 + +```plain +t.up() +t.goto(x,y) # 比如t.goto(80,80) +t.down +``` +## 🚀挑战2: + +请你将图图**瞬间移动**到坐标为(100,100)的地方,如下图: + +(提示:下图中的红色圆圈只是示意图图的一开始的位置,忽略就可以) + +![图片](https://uploader.shimo.im/f/wlX5I63zyyaerDvy.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# 3、乾坤大挪移 + +有小伙伴儿会问:“乾坤大挪移是什么?”,很简单,就是让图图进行各种移动,各种来考你的题目,hiahiahiahia~ + +## 🚀挑战3: + +还记得小时候用过的钉板吗?我们通过钉板制作各种各样的图形,比如下面这种。 + +那么现在请使用goto语句,参考下面的坐标画出对应的图形: + +(备注:如果画出来的图形很小,可以把坐标都放大10倍哦,比如把(1,1)改成(10,10),把(5,1)改成(50,10)) + +![图片](https://uploader.shimo.im/f/ycPBUneY6Y07aVXG.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +## 🚀挑战4: + +看来你已经简单了解了坐标和使用坐标的方式,那么,再来考考你吧。 + +题目描述:请先将图图移动到坐标为(100,100)的位置,然后画出一个边长为50的五角星 + +(备注:五角星的每个角都是36°,其他角的参数参考下图2) + +![图片](https://uploader.shimo.im/f/seYOykeuVza9t1nj.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +![图片](https://uploader.shimo.im/f/rOfsXoJcBrUmCx93.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + + + diff --git a/Turtle/Day7:最后的挑战.md b/Turtle/Day7:最后的挑战.md new file mode 100644 index 0000000..cf30f8a --- /dev/null +++ b/Turtle/Day7:最后的挑战.md @@ -0,0 +1,84 @@ +恭喜你已经完成了前六天的任务,在前六天中你学习到了编程中的循环,画图模块中的方向功能、抬笔功能、颜色填充功能、回家功能,并且熟悉了常见的图形、多边形,能够坚持到第七天,你一定非常厉害,既然这样,那么就请准备好,迎接最后的挑战吧! + +最后的挑战 + +世界上有非常多的国家,咱们中国只是其中一个,除了中国,还有很多国家分布在世界各地,他们有的领土大,有的领土小,有的很有名气,有的可能你还没听说过,但是无论哪个国家,他们都有一个象征自己国家的物品,你知道是什么吗? + +![图片](https://uploader.shimo.im/f/Tk018h7NxjXx3lra.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +对啦,这个物品就是国旗,即使是新成立的国家,也需要设计属于自己的国旗,而每一种国旗都有自己不同的含义,比如咱们中国的国旗,这可不是简单在上面涂个颜色、画几个星星,星星的角度和数量都是有讲究的。在中国国旗上,红色象征革命,旗上的五颗五角星及其相互关系象征共产党领导下的革命人民大团结。五角星用黄色是为了在红地上显出光明,四颗小五角星各有一尖正对着大星的中心点,表示围绕着一个中心而团结。 + +![图片](https://uploader.shimo.im/f/MHFtjlKwrYGr5QZs.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +当然啦其他国家的国旗也有属于自己国家、自己历史的意义。比如冰岛,这可不是一个岛,他也是一个国家,他的国旗是这样的: + +![图片](https://uploader.shimo.im/f/m82shaoOJO93ipYq.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +采用了红色、蓝色和白色,象征冰岛是屹立于汪洋大海的一个炽热而又被冰雪覆盖的美丽岛国,十字图案源自丹麦国旗图案,表示冰岛历史上与挪威、丹麦的关系。 + +国旗虽上面的图案虽然看起来简单,但是要画出来可不容易,今天我们就为大家准备了下面2种国旗,选择1种你觉得对自己有一定挑战的国旗,并通过图图将他画出来吧。 + +## ⛽️加油站—图层 + +在开始画图前我们需要再学习一个重要的知识——图层,认识了图层,你使用图图就能起到事半功倍的作用。 + +什么是图层呢?其实非常简单,你可以理解成一张图是由多层图叠在一起的,比如下面的【瑞士国旗】: + +![图片](https://uploader.shimo.im/f/pcEKIiuPN7Rk8TlM.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +它可以由两层组合在一起,最下面一层是红色,上面一层是白色的十字: + +![图片](https://uploader.shimo.im/f/m9c1AKac9tlT1RC0.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +所以,如果要使用图图,就可以先画最下面一层红色背景,再画白色的十字。 + +### 📺练习1: + +说说下面的【加拿大国旗】可以由几层组成,分别是那几层呢? + +![图片](https://uploader.shimo.im/f/uAKTLidwNFnEgWXu.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +答案:可以有三层组成:最下面一层是红色长方形,中间层是白色长方形,最上面一层是红色枫叶 + +## 🚀挑战开始! + +### (1)冰岛国旗 难度系数🌟🌟🌟 + +参考: + +设置成蓝色:t.color('#0048E0') + +设置成红色:t.color('#FF0F00') + +设置成白色:t.color('white') + +![图片](https://uploader.shimo.im/f/HSyca4QVdhGFHEBd.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +参考坐标图: + +![图片](https://uploader.shimo.im/f/vFfmBpeGnMyTwovL.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +### (2)朝鲜国旗 难度系数🌟🌟🌟🌟 + +参考: + +(1) + +设置成蓝色:t.color('#0048E0') + +设置成红色:t.color('#FF0F00') + +设置成白色:t.color('white') + +(2) + +五角星的小边长度为23 + +![图片](https://uploader.shimo.im/f/2sw8CxM2Oap8dHpv.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +参考坐标图: + +![图片](https://uploader.shimo.im/f/bQJENhM7icWKLQCW.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +# + diff --git a/Turtle/安装Python与配置.md b/Turtle/安装Python与配置.md new file mode 100644 index 0000000..49ddaff --- /dev/null +++ b/Turtle/安装Python与配置.md @@ -0,0 +1,234 @@ +本篇主要介绍 Python 的安装与环境配置方法。Python作为一门通用型的编程语言,可以通过很多方法完成安装,同时,也可根据实际需求搭建不同类型的开发环境。由于 Jupyter 是最通用的开发环境,同时,Jupyter 本身也是 Notebook 形式的开发环境,非常适合初学者上手使用。因此,本次内容将主要采用 Jupyter Notebook/Jupyter Lab 来进行演示,本篇也将详细介绍如何通过通用科学计算平台 Anaconda 来进行 Python和Jupyter 的安装。 + + +# 1 Anaconda下载与安装 + +Anaconda 是一个工具包,将 Python 和许多与科学计算相关的库捆绑在一起,形成了一个方便的科学计算环境,你安装了 Ananconda 就相当于安装了 Python 外加这些库,省去了自己下载和安装各种库的麻烦,方便初学者专注于学习 Python。 + +![Anaconda](https://img-blog.csdnimg.cn/20210116220515354.png) + +此处,我们采用 Anaconda 进行 Python 安装和开发环境搭建。 + + +## 1.1 下载Anaconda + +访问Anaconda官网(https://www.anaconda.com),在下拉菜单中的 Products 里选择 Individual Edition,个人版,同时也是免费版。此版本中不涉及付费内容,可供个人用户使用。 + +![下载Anaconda](https://img-blog.csdnimg.cn/202101162206290.png) + +进入页面后,点击Download,会自动跳转到操作系统选择的界面。 + +![下载Anaconda](https://img-blog.csdnimg.cn/20210116220743451.png) + +此时,可根据自身操作系统进行选择和下载。 + +![选择安装的OS](https://img-blog.csdnimg.cn/20210116220927323.png) + +当然,windows 64的用户也可直接通过下述网盘连接进行下载,该版本为 Anaconda3-2020.11-Windows-x86_64。 + +- 链接:https://pan.baidu.com/s/1IEasB0epWpPRhgYdgSCHaA +- 提取码:i6zj + +## 1.2 安装Anaconda + +下载完成后,即可开始安装。双击安装文件,进入欢迎界面,点击 Next。 + +![安装Anaconda](https://img-blog.csdnimg.cn/20210116221119969.png) + +点击同意,进入到下一步。 + +![安装Anaconda](https://img-blog.csdnimg.cn/20210116221214469.png) + +选择软件使用权限,是指针对当前登录用户还是所有用户,二者都行,无特殊要求。 + +![安装Anaconda](https://img-blog.csdnimg.cn/20210116221311317.png) + +选择安装位置,完成安装。 + +![安装Anaconda](https://img-blog.csdnimg.cn/20210116221406406.png) + +如果出现此页面,需要勾选配置环境变量选项。 + +![安装Anaconda](https://img-blog.csdnimg.cn/20210116221521364.png) + +无需安装VS Code,直接跳过即可。 + +![安装Anaconda](https://img-blog.csdnimg.cn/20210304211327560.png) + +安装完成后,在开始菜单栏,或者软件安装位置,找到 Anaconda Navigator 并打开。 + +![Anaconda Navigator](https://img-blog.csdnimg.cn/20210116222105775.png) + +进入到如下界面。 + +![Anaconda Navigator](https://img-blog.csdnimg.cn/20210116223127877.png) + +我们能看到 Anaconda 中集成了非常多数据科学计算相关的功能,并且,在安装过程中,也完成了 Python 的安装和环境变量的设置,以及 Jupyter和PyCharm 的安装。其中 Jupyter 是本次学习将用到的代码编辑工具,而 PyCharm 则是一款集成开发环境(IDE),本次学习并不涉及。 + + +## 1.3 启动Jupyter + +我们能够看到,在 Anaconda 中有两个 Jupyter 组件,一个是**Notebook**,一个是**JupyterLab**。其中,Lab 是 Notebook 的升级版,用户交互界面更加友好,并且拥有许多额外辅助功能,例如代码框分屏、文件管理系统等,但相比 Notebook,Lab 并不支持第三方插件,因此如果是想使用 Jupyter 丰富的插件,则只能选择 Notebook。不过二者在实际编程的功能使用上没有区别,本次学习推荐使用 JupyterLab。 + +点击 JupyterLab,启动相关服务,系统会自动打开浏览器并进入到 JupyterLab 界面。 + +![JupyterLab](https://img-blog.csdnimg.cn/20210116222942554.png) + +能够成功弹出浏览器窗口,则说明安装成功。如果浏览器关闭,再次点击 Anaconda 中 Jupyter Lab 组件中的 Launch 即可再次打开 Jupyter 界面。 + +![JupterLab](https://img-blog.csdnimg.cn/20210116223349824.png) + +或者在浏览器里直接输入 http://localhost:8890/lab。 + + +--- +# 2 Jupyter基本操作 + +接下来,简单介绍 Jupyter 的基本操作。 + +## 2.1 简单代码编写尝试 + +在 JupyterLab 主界面中,左边是文件目录,右边是编程界面,首次登陆时,点击 Python3 即可创建一个新的编程文件。 + +![新建Python3文件](https://img-blog.csdnimg.cn/20210116223814229.png) + +如下所示: + +![新建Python3文件](https://img-blog.csdnimg.cn/20210116223946535.png) + +同时,在左侧文件目录,也会出现一个新的`ipynb`文件,也就是正在编辑的代码文件。 + +> ipynb 文件是 ipython Notebook 的简写,Jupyter 脱胎于 ipython 编辑器,因此 Jupyter 文件仍然保留了 ipynb 的文件类型命名方式。 + +接下来,简单尝试在右侧代码框中输入 Python 代码。点击右侧代码框(cell)中输入`a = 1`。 + + + +![Cell](https://img-blog.csdnimg.cn/20210116224321371.png) + +也就是令`a = 1`,然后`shift+enter`执行该代码。执行完成后,会自动新生成一个 cell,接下来的代码就可以在新生成的 cell 中执行。在新生成的 cell 中,输入`a`能够看到,返回结果就是`a`的赋值。 + +![输出a](https://img-blog.csdnimg.cn/20210116224500349.png) + +至此,我们就完成了一次简单的 Python 代码编写和运行。 + +## 2.2 Notebook式编辑环境 + +将代码写入一个个**cell**,代码文件由一个个cell组成,书写代码时就像一行一行在记笔记,就是所谓的 Notebook 式的代码编辑环境。Notebook 式代码编辑环境其实也是 REPL(Read Eval Print Loop)环境的一种,即交互式编译。简单来说,交互式编译就是指允许用户逐行输入代码、逐行查看结果,从而逐行进行调试。这无疑是大幅降低了代码编写的难度,这也是建议 Python 初学者使用 Jupyter 的原因。 + +## 2.3 Jupyter的基本操作 + +由于后续 Jupyter 将作为主力代码编辑器,因此我们有必要深入了解 Jupyter 的一些常用功能。当然,Jupyter 本身也是一个独立的软件,具体软件的功能介绍可以查看 [Jupyter](https://jupyter.org/) 官网(https://jupyter.org),里面有 Jupyter 所有功能的完整介绍。 + +![Jupyter](https://img-blog.csdnimg.cn/20210116224919888.png) + +此处先介绍实际学习过程中常用的功能。 + +### 2.3.1 cell类型选择 + +在 Jupyter 中,每个 cell 除了代码以外,还可以使用 Markdown 语法输入文本内容,以及尚未确定格式的草稿。 +- 选定一个 cell 后,选择 code 则是代码内容; +- 选择 Markdown 则是使用 Markdown 语法输入文本内容; +- 选择 Raw 则是草稿内容,不会输出任何结果。 + +![Cell格式](https://img-blog.csdnimg.cn/20210116225405249.png) + +例如,使用Markdown语法打印标题: + +![Markdown格式](https://img-blog.csdnimg.cn/20210116225630969.png) + +同样,是`shift+enter`执行 Markdown 语法。 + +![Markdown格式](https://img-blog.csdnimg.cn/20210116225735316.png) + +可以看出,jupyter 还是个不错的笔记工具,同时,也非常适合编写数据分析报告。 + +### 2.3.2 cell不同模式及快捷键 + +cell 有两种不同模式,选中 cell 时是 **command(命令)** 模式,而单击 cell 内,出现光标闪烁时,则是进入了 cell 内容的 **edit(编辑)** 模式,在编辑模式下,可以进行内容输入,而在命令模式下,则可使用一些 cell 快捷键对其进行操作。 + + +快捷键 | 操作| 快捷键 | 操作 +:---:|---|:---:|--- +a | 在上方插入一个cell | b | 在下方插入一个cell +x | 剪切该cell | c | 复制该cell +v | 在cell下方粘贴复制的cell | m | 转为markdown模式 +y | 转为code模式 | r | 转为raw模式 +z | 撤销操作 | 双击d | 删除该cell + + +### 2.3.3 JupyterLab 文件管理系统 + +相比 Notebook,JupyterLab 拥有非常便捷的文件管理系统,我们前面已经尝试,当创建一个新的`ipy`文件时,左侧文件栏将出现对应文件。JupyterLab 左侧就是其文件管理界面,在其中,我们可以进行文件创建、文件夹创建、文件上传等操作。 + +![文件管理系统](https://img-blog.csdnimg.cn/20210116230936963.png) + + +### 2.3.4 JupyterLab 文件系统主目录及修改方式 + +那么,我们创建的`ipy`文件存在哪呢?
+在 Anaconda 中,一般系统会默认 Jupyter 的主目录就是系统的文档目录。但文档目录在 C 盘下,如果是首次安装 Jupyter,并希望单独设置一个文件夹作为默认主目录,可以按照如下步骤进行操作: + + +**(1)在 Anaconda 中打开 CMD.exe Prompt,进入命令行界面** + +![CMD.exe Launch](https://img-blog.csdnimg.cn/20210116231238985.png) + +当然,此处也可以`win+r`,然后输入`cmd`进入命令行。 + +![win+r](https://img-blog.csdnimg.cn/20210116231553772.png) + +![console](https://img-blog.csdnimg.cn/20210116231701231.png) + + +**(2)生成 Jupyter 配置文件** + +在命令行中,输入 + +```python +jupyter notebook --generate-config +``` + +![生成Jupyter配置文件](https://img-blog.csdnimg.cn/20210116232216845.png) + +注意上述配置文件的保存路径。若已有配置文件,再次输入命令将可选择是否覆盖原配置文件。当然,覆盖原配置文件将导致原配置失效。 + +![生成Jupyter配置文件](https://img-blog.csdnimg.cn/20210116232619111.png) + +**(3)修改主目录配置** + +接下来,按照命令行中提示的配置文件路径,找到配置文件。 + +![config文件](https://img-blog.csdnimg.cn/20210116232843233.png) + +可以用文本编辑器打开,能够看到所有的 Jupyter 可选配置。 + +![配置文件内容](https://img-blog.csdnimg.cn/20210116233019653.png) + +`ctrl+f`进入搜索栏,搜索`c.NotebookApp.notebook_dir` + +![查找内容](https://img-blog.csdnimg.cn/20210117001217743.png) + +将对应位置的#号删除,使其配置生效,并在等号后面输入新的主目录文件夹位置(自行选择文件位置),保存退出,并在**重启Jupyter后生效**。 + + +**(4)查看新的主目录** + +进入对应文件夹位置,查看文件夹内文件和 JupyterLab 内显示文件是否一致。 + +![新的主目录](https://img-blog.csdnimg.cn/20210117001556793.png) + +![新的主目录](https://img-blog.csdnimg.cn/20210117001703673.png) + +至此,新的主目录文件设置成功。当然,任何对主目录文件的操作都会同步至 JupyterLab 的文件栏页。 + +> 不难发现,Jupyter文件系统主目录就类似于其他编程语言的操作空间概念。 + +### 2.3.5 停止`ipy`进程 + +由于 Python 代码在运行过程中,对象都存储在内存中,因此,为了合理控制内存,在必要的情况下需要手动终止 Jupyter 进程。此时可以使用左侧栏的 KERNEL SESSIONS 功能,进行操作。 + +![关闭进程](https://img-blog.csdnimg.cn/20210117001949969.png) + +点击 SHUT DOWN 即可关闭对话。 + diff --git a/Turtle/预备课1:认识新伙伴图图.md b/Turtle/预备课1:认识新伙伴图图.md new file mode 100644 index 0000000..d5859f2 --- /dev/null +++ b/Turtle/预备课1:认识新伙伴图图.md @@ -0,0 +1,149 @@ +# 1、Turtle图图 + +在开始练习前,先来认识一下接下来会陪伴你好几天的程序小伙伴儿turtle【图图】吧,为什么叫做turtle【图图】呢,是因为它长这样: + +![](https://uploader.shimo.im/f/AkuqgJsogBts0F5p.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +弄错了,是这个: + +![](https://uploader.shimo.im/f/4QBA56EovtcJ0piU.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +虽然有点丑丑的,但确实是一直货真价实的乌龟,乌龟的英文是turtle,你也可以叫它【图图】,当然你要是叫它小乌龟,也......没啥问题......😓 + +# 2、图图的设置 + +天啊!乌龟竟然能画图,开玩笑吗? + +不过这确实是一只会画图的乌龟,就像乌龟在海滩上爬行能留下痕迹一样,程序中的【图图】也能帮助我们画出各种各样的线条。 + +![图片](https://uploader.shimo.im/f/VWA9RDoWLPUZWEl1.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +## (1)导入图图库 + +想要使用图图帮助你画图,需要提前做一些设置,设置很简单,一共就两步: + +1、导入图图库 + +2、设定重要信息 + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.done() +``` +**第一行** 就是导入【图图】库,然后给图图起了个新的名字t(后面会用到) + +**第二行** 是固定的设置,咱们每次写上就行 + +**第三行** 是给图图设置一个形状,这里是turtle(乌龟形状),你也可以改成下面的英文: + +```python +arrow circle square triangle classic +``` + +**第四行** 也是一定要有的语句,它的作用是告诉程序,“我画完啦!” + +### 练习1: + +将第四行的`?`改成其它几个英文单词,看看有什么不一样吧: + +(单词:`arrow` `circle` `square` `triangle` `classic`) + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='?') +t.done() +``` +# 3、行动吧!图图 + +## (1)图图走起来 + +掌握了图图的基本设置,就可以让图图开始行动啦,我们先学习最简单的向前走: + +```python +t.forward(100) # 向前走100步 +``` +想让图图向前走几步,数字就是多少,把这行代码写入到前面的程序中试试: + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.forward(100) #向前走100步 +t.done() +``` +既然有向前走,那么,当然还有向后退,赶紧试试吧: + +```plain +t.back(100) # 向后退100步 +``` +## (2)向左转 + +既然能走,当然可以转弯啊,图图可是“艾科不来梅西多亚瑟星球”最能转的乌龟 + +向左转也很简单,就是下面这个口令啦: + +```python +t.left(90) #向左转90度 +``` +再加上向前走我们就能画出一个直角啦: + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.forward(100) #向前走100步 +t.left(90) #向左转90度 +t.forward(100) #向前走100步 +t.done() +``` + +### 挑战1: + +(1)修改`left`中的角度,看看图图能画出下面这些角吗?(钝角和锐角) + +(2)除了向左转,也有向右转哦,它的指令是这样的`t.right(100)`,赶紧试试吧! + +![](https://uploader.shimo.im/f/2n3ynCWK6sZdJfPG.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +![](https://uploader.shimo.im/f/EWcUBkt7VGHNeHxO.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +### 练习2: + +如果要画出下面这个角度为30度的角应该怎么办呢? + +![](https://uploader.shimo.im/f/TgUIqgO5xkqSAqPa.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.forward(100) #向前走100步 +t.right(30) #向右转30度 +t.forward(100) #向前走100步 +t.done() +``` +这样写对吗? + +哈哈,当然不对,来画一条辅助线看看,图图实际转动的度数是红色箭头指示的角度,我们知道平角等于180度,平角也就是下面这个平平的线啦: + +![](https://uploader.shimo.im/f/nAb3zgK10t64OOCh.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +所以图图实际转动的角度等于180 - 30 = 150,之后我们会画很多这样的角,你也可以做一条辅助线,用180减去标出来的角,就是要转动的角度啦: + +![](https://uploader.shimo.im/f/axJJ8Qi6C7kXJ2iT.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +正确的写法: + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.forward(100) #向前走100步 +t.right(150) #向右转150度 +t.forward(100) #向前走100步 +t.done() +``` + diff --git a/Turtle/预备课2:图图的其他技能.md b/Turtle/预备课2:图图的其他技能.md new file mode 100644 index 0000000..21ec44a --- /dev/null +++ b/Turtle/预备课2:图图的其他技能.md @@ -0,0 +1,73 @@ + +学完【预备课1】你是不是认为图图只是一个能简单移动的乌龟? + +其实图图的能力相当强大,当你学的知识越来越多后甚至可以使用图图去创作这样一幅作品: + +![](https://uploader.shimo.im/f/4EVNHcnfOp8580JK.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + +当然,这是很久以后了!!! + +![](https://uploader.shimo.im/f/qom5FEiaKkKVAMk8.png!thumbnail?fileGuid=886kd3qYgXXTyTTW) + + +不过,千里之行,始于足下,我们再来学习一个基础技能吧:给图画涂色。 + +给图画涂色其实很简单,和我们用颜料笔画画的步骤是一样的 + +(1)先用笔在颜料盘中粘一个颜色 + +(2)然后开始涂色 + +(3)当涂完颜色后结束涂色 + +用程序写也是这三步: + +```python +t.color('red') +#color是颜色的英文,括号里写上你想使用的颜色的英文单词 + +t.begin_fill() +#开始填充颜色,begin:开始,fill:填充 + +t.end_fill() +#结束填充颜色,end:结束 +``` + +涂色的格式是这样的: + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.color('red') #颜色设置成红色 +t.begin_fill() #开始填充 + +# 这里写要画的图形的指令 + +t.end_fill() #结束填充 +t.done() +``` +如果要填充一个直角三角形可以这样写: + +```python +import turtle as t +t.TurtleScreen._RUNNING = True +t.shape(name='turtle') +t.color('red') #颜色设置成红色 +t.begin_fill() #开始填充 +t.forward(60) +t.right(90) +t.forward(80) +t.right(143) +t.forward(100) +t.right(127) +t.end_fill() #结束填充 +t.done() +``` +将`red`改成其他的颜色试试吧: + +- 黄色:yellow +- 蓝色:blue +- 绿色:green +- 紫色:purple +- 粉丝:pink \ No newline at end of file