AELF初体验(从零开始跑通AELF的生活)

898 阅读9分钟

本次教程的机器:windows

官方的图文教程:官方的图文教程

大家也可以去看官方的教程。一样的。不过我这里会列出一些我遇到的问题以及如何解决。

##教程开始

###所需准备工具

(1)开发环境 Visual Studio Code (vscode),需要安装 c#扩展

官方下载地址:code.visualstudio.com/docs/setup/…

(2)工具软件 Google Chrome 官方下载地址:www.google.cn/intl/zh-CN/…

git 官方下载地址:git-scm.com/book/en/v2/…

dotnet core sdk 2.2 官方下载地址:dotnet.microsoft.com/download

nodejs(不低于 8.0 版本) 官方下载地址:nodejs.cn/download/

MAC 用户还需安装 Homebrew 官方下载地址: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导入整个项目

file 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 点击红框内的地方可以打开界面

image.png

点击terminal就可以打开teminal了

image.png

点击+号可以新开一个teminal,点击垃圾桶可以关掉teminal

image.png

掌握这些基本用法后,我们将会在这个地方多开teminal进行操作

###首先启动整个链(hello world智能合约,后面我会简称为这条链)

cd chain/src/AElf.Boilerplate.Launcher/
dotnet build
dotnet run bin/Debug/netcoreapp2.2/AElf.Boilerplate.Launcher

看到这个字样说明链跑起来了

image.png

特别提醒,只要不爆红,就是正常的,爆黄无视即可

然后链会以500ms的速度刷新,如图现在链的高度是96

image.png

当整条链跑起来后,我们就挂着就行,之后的demo项目跑的时候这条链记得不用关。

  • demo1 测试智能合约 在根目录运行以下代码
cd .\chain\test\HelloWorldContract.Test\
dotnet test

只要测试通过即可

image.png

  • demo2 运行 JS SDK Demo 根目录运行以下代码
cd .\web\JSSDK\
npm install
npm start

全部运行后会弹出网页,alert一个hello world,页面上会显示hello aelf。

出现这个即为成功

如果运行npm start报错的话,直接去JSSDK项目下直接打开Index.html即可

npm start报错

  • demo3 运行 AElf 浏览器插件 Demo 根节点运行以下命令
cd .\web\browserExtension\
npm install
npm start

如果npm start失败,直接去文件夹里打开Index.html即可

这个时候应该会弹窗说什么not ready之类的。因为我们还没安装插件

image.png

用谷歌浏览器打开链接chrome.google.com/webstore/se… 下载安装这个

image.png

安装后刷新一下页面,应该会弹出xxx is ready的字样。这时候页面上的按钮都是点不动的。我们点击浏览器右上角aelf的插件图标。

image.png

输入密码后 ,点击创建钱包。 钱包创建后,如下所示,这时候我们点击密钥对=》创建密钥对 创建成功后,刷新网页,点f12(开发者模式)。

点击getChainStatus,console会输出如下东西

image.png

点击login后,会输出如下

image.png

点击init contract

image.png

点击hello 会alert hello world。会输出如下

image.png

如果是参加活动,录屏记得录下console的东西

完成如上后,demo完成

  • demo4 运行 DAPP Demo——BingoGame

在根目录下运行

cd .\web\browserBingo\
open index.html

如果open index.html运行报错,手动打开Index.html即可

点击register 初始化

image.png

然后点击下注金额,点击play 按照提示进行即可。

那么到了这里,这个4个demo就已经体验完成了。 已经完成任务了。

但是活动还出了一个题目给我们:

image.png

文档链接1:docs.aelf.io/main/main/s…

文档链接2:github.com/AElfProject…

刚开始修改这个demo的时候还没有链接2,链接2是中文的,也清晰一点。以下的教程是我根据链接1的一个摸索历程。

那么我们现在开始学习如何修改hellowrold demo。

修改helloworld

1.项目结构 项目分为两部分,chain部分和web部分。官方教程主要围绕chain部分来讲解

image.png

我们点开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,就可以对比得看

image.png

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;
}

编写完后,我们来编译(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

整个修改过程就是如此。

那么我们要添加的这个斐波拉契数列函数的思路也很明了了

  1. 首先,我们要在protobuf中添加一个rpc方法
  2. 然后添加两个message(入参,返回值/出参)
  3. 在HelloWorldContract.cs文件中实现具体的逻辑
  4. 根据上面指引,在JSSDK中index.html中 在第42行后面增加 helloWorldC.Fibonacci.call(这里是对应的斐波拉契数列函数输入, (err, result) => { alert(result); });

我们来点开这个index.html,添加,但是可能会发现返回的值是undefind。这时候我们看看别的项目是怎么写的.可以看到,调用时直接传一个{key:value}对象进去的,如果你是直接传一个值进去,是会无法识别的。

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按钮后点击查看开奖结果!');
            }
        });

好的,到这里,整个体验过程就结束了,谢谢大家的观看。