本次教程的机器:windows
官方的图文教程:官方的图文教程
大家也可以去看官方的教程。一样的。不过我这里会列出一些我遇到的问题以及如何解决。
教程开始
所需准备工具
(1)开发环境
Visual Studio Code (vscode),需要安装 c#扩展
官方下载地址:https://code.visualstudio.com/docs/setup/setup-overview
(2)工具软件
Google Chrome 官方下载地址:https://www.google.cn/intl/zh-CN/chrome/
git 官方下载地址:https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
dotnet core sdk 2.2 官方下载地址:https://dotnet.microsoft.com/download
nodejs(不低于 8.0 版本) 官方下载地址:http://nodejs.cn/download/
MAC 用户还需安装 Homebrew 官方下载地址:https://brew.sh/
如果你有代理,在下载Homebrew的时候如果不走代理(windows忽略这条)
可以终端设置(每次都要设置)
···
export http_proxy=socks5://127.0.0.1:1080 # 配置http访问的
export https_proxy=socks5://127.0.0.1:1080 # 配置https
export all_proxy=socks5://127.0.0.1:1080 # 配置http和https访
···
nodejs建议装稳定版,目前是10点多
运行demo
1.下载demo源码https://github.com/AElfProject/aelf-boilerplate
喜欢用git下的可以使用git clone进行下载
git clone https://github.com/AElfProject/aelf-boilerplate
如果git下载太慢直接直接github的download走起
下载后打开vscode,点击左上角的File=》Open Folder导入整个项目
然后 打开AElf.Boilerplate.sln文件(位于根目录下的chain文件夹里)
打开 vscode 后若出现下图中的提示框,请分别选择“yes”和“Restore”
2.下载protobuf和unzip
如果是非windows用户,直接按以下命令下载即可
sh chain/scripts/install.sh 命令下载protobuf 脚本
如果下载出错建议手动下载并且配置环境变量(通用)
原教程的手动安装教程:链接
官方教程中给了几个建议,这里建议直接用第三个,如下:
111. 手动下载 protoc
下载地址: [https://github.com/protocolbuffers/protobuf/releases/tag/v3.7.0](https://github.com/protocolbuffers/protobuf/releases/tag/v3.7.0)
1. 下载 protoc-3.7.0-win64/32 根据系统判断下载哪个版本
2. 解压缩到任意目录(路径需要全英文)
3. 设置环境变量(解压缩目录/bin)
222. 手动下载unzip
下载地址: [https://sourceforge.net/projects/gnuwin32/files/unzip/5.51-1/unzip-5.51-1.exe/download?use_mirror=nchc&download=](https://sourceforge.net/projects/gnuwin32/files/unzip/5.51-1/unzip-5.51-1.exe/download?use_mirror=nchc&download=)
1. 安装到任意目录(路径需要全英文)
2. 设置环境变量 安装目录/bin
手动安装完记得退出当前的命令行重新打开(不然环境变量不生效)
demo开始
当上面的环境都配置好之后,就可以开始运行demo了。
首先介绍一下vscode的teminal
点击红框内的地方可以打开界面
点击terminal就可以打开teminal了
点击+号可以新开一个teminal,点击垃圾桶可以关掉teminal
掌握这些基本用法后,我们将会在这个地方多开teminal进行操作
首先启动整个链(hello world智能合约,后面我会简称为这条链)
cd chain/src/AElf.Boilerplate.Launcher/
dotnet build
dotnet run bin/Debug/netcoreapp2.2/AElf.Boilerplate.Launcher
看到这个字样说明链跑起来了
特别提醒,只要不爆红,就是正常的,爆黄无视即可
如果出现以下报错(这几个字是白色的,不是红色报错),说明没有下载到一个文件,
手动到https://github.com/AElfProject/contract-plugin/releases/download/v1.0.2/contract_csharp_plugin-v1.0.2-win32.zip这个地址下载并放到chain/scripts文件夹下即可
unzip: cannot find either aelf-boilerplate/chain/scripts/contract_csharp_plugin-v1.0.2-win32.zip or aelf-boilerplate/chain/scripts/contract_csharp_plugin-v1.0.2-win32.zip.zip.
Could Not Find aelf-boilerplate\chain\scripts\contract_csharp_plugin-v1.0.2-win32.zip
--contract_out: protoc-gen-contract: The system cannot find the file specified.
如果正常运行,链会以500ms的速度刷新,如图现在链的高度是96
当整条链跑起来后,我们就挂着就行,之后的demo项目跑的时候这条链记得不用关。
- demo1 测试智能合约
在根目录运行以下代码
cd .\chain\test\HelloWorldContract.Test\
dotnet test
只要测试通过即可
- demo2 运行 JS SDK Demo
根目录运行以下代码
cd .\web\JSSDK\
npm install
npm start
全部运行后会弹出网页,alert一个hello world,页面上会显示hello aelf。
出现这个即为成功
如果运行npm start报错的话,直接去JSSDK项目下直接打开Index.html即可
- demo3 运行 AElf 浏览器插件 Demo
根节点运行以下命令
cd .\web\browserExtension\
npm install
npm start
如果npm start失败,直接去文件夹里打开Index.html即可
这个时候应该会弹窗说什么not ready之类的。因为我们还没安装插件
用谷歌浏览器打开链接https://chrome.google.com/webstore/search/aelf?hl=en
下载安装这个
安装后刷新一下页面,应该会弹出xxx is ready的字样。这时候页面上的按钮都是点不动的。我们点击浏览器右上角aelf的插件图标。
输入密码后 ,点击创建钱包。
钱包创建后,如下所示,这时候我们点击密钥对=》创建密钥对
创建成功后,刷新网页,点f12(开发者模式)。
点击getChainStatus,console会输出如下东西
点击login后,会输出如下
点击init contract
点击hello 会alert hello world。会输出如下
如果是参加活动,录屏记得录下console的东西
完成如上后,demo完成
- demo4 运行 DAPP Demo——BingoGame
在根目录下运行
cd .\web\browserBingo\
open index.html
如果open index.html运行报错,手动打开Index.html即可
点击register 初始化
然后点击下注金额,点击play 按照提示进行即可。
那么到了这里,这个4个demo就已经体验完成了。
已经完成任务了。
但是活动还出了一个题目给我们:
文档链接1:https://docs.aelf.io/main/main/smartcontracthelloworld
文档链接2:https://github.com/AElfProject/aelf-boilerplate/blob/hello-world/docs/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6%E7%BC%96%E5%86%99.md
刚开始修改这个demo的时候还没有链接2,链接2是中文的,也清晰一点。以下的教程是我根据链接1的一个摸索历程。
那么我们现在开始学习如何修改hellowrold demo。
修改helloworld
1.项目结构
项目分为两部分,chain部分和web部分。官方教程主要围绕chain部分来讲解
我们点开chain文件夹,里面有四个文件夹,分别是protobuf,scripts,src和test。scripts是这个demo的构建脚本,不是重点。
- protobuf 这个文件是定义protobuf的地方
- src
1.AElf.Boilerplate.Launcher: 可以理解为区块链代码(node,节点,原文是一个用命令行跑起来的节点),其他项目依靠这个节点跑起来才能正常运行
2.AElf.Boilerplate.Mainchain: 上面这个节点的library
3.HelloWorldContract: Hello World contract的实现,也是本文重点 - test 测试代码
Hello World contract
下面介绍如何写一个智能合约。我们将在这个已有的项目上进行。
在AELF中,合约(contract)使用protobuf来实现/执行,并且被定义为一个服务(in AElf, contracts are defined as services and are implemented using Protobuf),这句话看不懂没关系。继续。
我们点开chain/protobuf/hello_world.proto.
syntax = "proto3";
import "aelf_options.proto";
import "google/protobuf/empty.proto";
option csharp_namespace = "HelloWorldContract";
service HelloWorldContract {
option (aelf.csharp_state) = "HelloWorldContractState";
rpc Hello (google.protobuf.Empty) returns (HelloReturn) { }
}
message HelloReturn {
string Value = 1;
}
可以看到定义了一个方法(rpc)Hello和一个类型(message)HelloReturn。
这里的语法是protobuf的语法,可以百度搜索以下具体的语法。
怎么理解这里的rpc和message呢。我们可以把rpc看成是抽象类,这里只定义名字(Hello),入参(google.protobuf.Empty),出参(HelloReturn)。我们可以把message看成是一个类。这个类可以用在入参和出参上,如果你没有参数,那么使用google.protobuf.Empty即刻。到了这里,你可以看出,其实这个google.protobuf.Empty也是一个定义的类,所以你就知道了,rpc方法的入参出参,你就直接扔一个自定义类(message)即可。
好了,既然定义了抽象方法(rpc),那么总得有一个实现类。
打开chain/src/helloworldcontract,点开HelloWorldContract.cs 和 HelloWorldContractState.cs这两个文件。
提示:如果你连续点开得话,会发现只会打开一个页面,我们会觉得很不方便,这时候我们可以右键文件,open in side,就可以对比得看
HelloWorldContract.cs
如下,可以看到这里实现了Hello方法,入参是一个empty。返回了一个HelloReturn。这里可以看到,返回是new了一个返回message,并且是key value得形式,key是我们在protobuf中定义得名字得,value就是我们想要返回得东西
using Google.Protobuf.WellKnownTypes;
namespace HelloWorldContract
{
public partial class HelloWorldContract : HelloWorldContractContainer.HelloWorldContractBase
{
public override HelloReturn Hello(Empty input)
{
return new HelloReturn {Value = "Hello world!"};
}
}
}
HelloWorldContractState.cs
这个State是我们这个helloContract得一个状态容器(我是这么理解得),目前是一个空实现(This class represents the state of the contract. It is empty now)
using AElf.Sdk.CSharp.State;
namespace HelloWorldContract
{
public class HelloWorldContractState : ContractState
{
}
}
点开chain/test/helloworldcontract.test/helloworldcontracttest.cs
可以看到测试代码如下:
使用HelloWorldContractStub.Hello.CallAsync去调用方法
await 去等待结果返回,方法是异步得(await是js得新特性)
使用result.Value.ShouldBe去进行测试
public class HelloWorldContractTest : HelloWorldContractTestBase
{
[Fact]
public async Task HelloCall_ReturnsHelloWorldMessage()
{
var result = await HelloWorldContractStub.Hello.CallAsync(new Empty());
result.Value.ShouldBe("Hello world!");
}
}
知道了大概得流程走向后,现在我们要开始修改项目了。
首先,回到protobuf得定义处,chain/protobuf/hello_world.proto.,添加两个rpc方法
rpc Visit (Visitor) returns (google.protobuf.Empty) { }
rpc GetVisitors (google.protobuf.Empty) returns (VisitorList) { }
添加两个message(repeated是protobuf中的一种限定修饰符,从字面意思看有“重复”的意思,实际上它就是用来指定某一个字段可以存放同一个类型的多个数据(当然也可以是0个或者1个),相当于C++中的vector或者Java中的List。)
message Visitor {
string Name = 1;
}
message VisitorList {
repeated string Names = 1;
}
注意,上面的message的序号是从1开始的,不可以从0开始,否则会爆field numbers must be positive integers这样的提示
编写完后,我们来编译(build)
cd chain/src/HelloWorldContract/
dotnet build
题外话1:如果你在之后得操作中,发现一些定义好像没生效之类得话,可以删除HelloworldContract/protobuf目录下得xxx.c.cs和xxx.g.cs文件,然后重新编译即可,在你没有删除得时候,编译会爆黄,而删除后编译,则不会爆黄
题外话2:c#跟java得ide不太一样,java得ide编写的时候会自动帮你编译,然后你可以直接导入,代码就不会爆红。但是使用vscode写c#的时候,如果你定义了还爆红,那么可以尝试build多一次,等待个5s,就可以了
题外话3:vscode中,ctrl+. 可以提示
然后我们接下来去实现
点开src/HelloWorldContract/HelloWorldContractState.cs,在HelloWorldContractState这个类里面添加这句代码(个人猜测是c#的get set,未验证)
public SingletonState<VisitorList> Visitors { get; set; }
点开src/HelloWorldContract/HelloWorldContract.cs
我们去实现那两个方法
可以看到,我们可以使用State.方法名.Value去获取值
public override Empty Visit(Visitor visitor)
{
if (State.Visitors.Value == null)
State.Visitors.Value = new VisitorList();
State.Visitors.Value.Names.Add(visitor.Name);
return new Empty();
}
public override VisitorList GetVisitors(Empty input)
{
return State.Visitors.Value;
}
然后运行
dotnet build
如果vscode爆红,并不绝对代表代码有错,放心去编译把
接下来我们测试一下编写的代码
点开chain/test/HelloWorldContract.Test/HelloWorldContractTest.cs
添加测试代码:
public async Task VisitCall_AddsVisitorToVisitorList()
{
await HelloWorldContractStub.Visit.SendAsync(new Visitor { Name = "Jon Snow"});
var result = await HelloWorldContractStub.GetVisitors.CallAsync(new Empty());
result.ShouldBe(new VisitorList { Names = { "Jon Snow" }});
}
运行下面命令即可进行测试(如果发现没有结果,可以看看是不是目录cd错了)
dotnet test
整个修改过程就是如此。当然,新增代码后,整个链要重启一下,不然会不更新
那么我们要添加的这个斐波拉契数列函数的思路也很明了了
- 首先,我们要在protobuf中添加一个rpc方法
- 然后添加两个message(入参,返回值/出参)
- 在HelloWorldContract.cs文件中实现具体的逻辑
- 根据上面指引,在JSSDK中index.html中
在第42行后面增加
helloWorldC.Fibonacci.call(这里是对应的斐波拉契数列函数输入, (err, result) => {
alert(result);
});
我们来点开这个index.html,添加,但是可能会发现返回的值是undefind。这时候我们看看别的项目是怎么写的.可以看到,调用时直接传一个{key:value}对象进去的,如果你是直接传一个值进去,是会无法识别的。而取值的话是用的result.名字去取得。
bingoGameContract.Play({Value: value}, (error, result) => {
if (result) {
console.log(result);
play.style.display = 'none';
loader.style.display = 'inline-block';
txId = result.TransactionId;
setTimeout(() => {
bingo.style.display = 'inline-block';
loader.style.display = 'none';
}, 20000);
alert('耐心等待20s,出现Bingo按钮后点击查看开奖结果!');
}
});
好的,到这里,整个体验过程就结束了,谢谢大家的观看。