抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

可以简单的把以太坊虚拟机理解为一个公开使用的后端服务器,用户在其上拥有有限的操作空间,每次操作以交易的形式产生,并会根据修改数据的大小向用户收费,称为 Gas,以此支持服务器维护人员 (即矿工)。并且,服务器上的数据会被永久记录,操作后端数据的代码 (对于EVM,是 Solidity 代码) 也存放在服务器上不能被修改。

矿工可以根据 Gas 和数据操作量选择将哪一笔交易加入区块。因此 Solidity 编程要求存储尽量少的数据,节省空间和操作量,以此得到更快速的响应。

使用 Remix 和 Ganache

Remix 是以太坊官方提供的 Solidity 开发环境。在 Remix 内写好代码后可以编译,编译会得到程序的二进制程序接口,称为 ABI。ABI 会提供给 web3.js 来提供操作接口。在编译后可以部署合约,Remix 本身提供了一些 JavaScript 虚拟机来运行智能合约。为了观察到用户和交易的情况,我们可以部署私有链,利用 Ganache。部署后会得到合约地址,你也可以直接在 Remix 和合约交互。

Ganache 可以在本地设置好私有区块链,即开即用。用户可以自行设置端口号,而网络ID为5777而区块链ID为1337。在本地部署好之后,使用 MetaMask 钱包添加私有链。MetaMask 也可以使用其他测试链,虽然其他测试链可以有更多的用户,但在这些链上我们难以观察交易情况。添加私有链后,也记得添加私有链的账户。

说到账户,对于以太坊地址来说,所有链上的地址格式都完全一致。因此,你的任何一个账户都可以在任何链上使用(当然代币不会跟随)。可以尝试把 Ganache 里生成的账户的钱转入一个你主链的地址,操作将会成功且主链地址在私有链上的 ETH 会增加。注意 Ganache 不会追踪除了它生成的账户以外的账户(因为无穷多),但是你仍然可以看到 TX 信息,就在 Ganache 的操作面板。这到底是怎么做到的?得益于钱包地址的随机和海量,生成的两个地址相同的概率几乎是不可能的。

Solidity 代码

设计一个 Solidity 代码,对于每个账户存放一个数据,并且允许用户查看数据。

(很遗憾,Hexo 对于 Solidity 代码暂时没有语法高亮,我也不想使用 hightlight.js,所以我随便选用了一种代码高亮模式。)

Solidity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 声明 License
// SPDX-License-Identifier: GPL-3.0
// 声明编译器版本
pragma solidity >=0.7.0 <0.9.0;

// 合约,就像一个类
contract numberHolder {

// 映射数据结构
mapping(address => uint16) userNumbers;

// 一个事件
event numberSetted(address addr,uint16 num);

// 地址的数据的setter函数
function setNumber(uint16 inputNumber) public{
userNumbers[msg.sender] = inputNumber;
// 发送事件给前端
emit numberSetted(msg.sender, userNumbers[msg.sender]);
}

// 两个只读函数获得用户地址和数据
function getNumber() public view returns (uint16) {
return userNumbers[msg.sender];
}

function getAddress() public view returns (address) {
return msg.sender;
}
}

具体内容可以查看 Solidity 的文档。函数发射的事件需要在前端进行监听。关键字 view 代表了这个函数不会操作数据。全局变量 msg 存储了一些当前合约的信息,例如 msg.sender 就是调用合约的地址。

Web 代码

Web 端我们使用以太坊 JavaScript API和后端交互,叫做 web3.js,使用此库类似于使用 jQuery 的 ajax。实际上,包括部署合约在内的操作都可以用 web3.js 完成,但本次我们不这样做。

在网页端,首先用简单 HTML 完成界面,给用户提示信息,显示一个可变文本并设置两个按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <div class="container">

<h1>数字存放</h1>
在数据框中输入一个数字并点击上传数据,它将会被存储至区块,请注意这会消耗燃料。<br>
点击获得数据将会显示您已经存储的数据,这不会消耗燃料。

<h2 id="info">等待输入</h2>

<label for="data" class="col-lg-2 control-label">您的数据</label>
<input id="data" type="text">


<button id="button_update">上传数据</button>
<button id="button_download">获得数据</button>

</div>

接下来在 <script> 部分设计网页的逻辑。别忘记把 web3.js 和 jQuery 加入到网页中。首先配置好我们的 web3 库:

1
2
3
4
5
6
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
}
else{
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

这声明了,我们的 web3 是由 HTTP 在本地8545端口提供,也就是我们的 Ganache。接下来:

1
2
3
4
5
web3.eth.defaultAccount = web3.eth.accounts[0];

var contractABI = web3.eth.contract(/* ABI Here */)

var myContract = contractABI.at('0x6f0D3623...')

第一行代码说明了我们默认交互账户是 Ganache 里的第一个账户。我们现在还不将 MetaMask 链接功能提供在前端。MetaMask 要求前端网页不能是本地打开的,必须运行在一个服务器上 (Python 搭建的简易 HTTP 服务器就可以),并且需要更复杂且完善的代码。第二行代码需要我们写入之前在 Remix 得到的 ABI,这是我们交互的基础。第三行就是合约本身,从语义上可以理解为:某个 ABI 位于某个确定的地址上。这个地址可以在 Remix 处看到,或在 Ganache 的 TX 信息里找到。

接下来我们来看一下在前端如何调用 Solidity 函数,并用 jQuery 改变前端显示。首先演示一下按钮如何获得数据:

1
2
3
4
5
6
7
$("#button_update").click(function() {
myContract.setNumber($("#data").val());
});

$("#button_download").click(function() {
$("#info").html('您的地址:' + myContract.getAddress.call() + '<br>' + '您存储的数字:' + myContract.getNumber.call() + '<br> 操作:读取数字')
});

对于 jQuery,简单的语法是利用 $("#id") 选中 HTML 对应 ID 的内容,在此是按钮类型。按钮类型具有 click() 方法,我们在内部写出一个函数,意思是“如果按钮被按下,那么执行这个函数,函数的内容是使用合约内的 setNumber() 函数,函数的参数是 HTML 中 ID 为 data 的元素的值”。对于下面获得值的操作,同理。

再来看看怎么监听我们发出的事件:

1
2
3
4
5
6
7
8
9
10
11
var eventSet = myContract.numberSetted()
eventSet.watch(function(error, result){
if(!error) {
$("#info").html('您的地址:' + result.args.addr + '<br>' + '您存储的数字:' + result.args.num
+ '<br>' + '操作:设置数字')
console.log(result)
}
else {
console.log(error)
}
})

基本原理同以上相同,我们将合约的 numberSetted 事件声明成一个变量,对这个变量进行 watch() 方法。里面的函数和刚才的差不多,它具有了两个参数。因为 web3 要求我们进行异常处理,即需要有 if(!error) {} elese {} 的形式。另一个参数 result 里面包含了我们在 Solidity 中写过的两个返回值。如果不太明白,可以在浏览器打开控制台观察一下 log,会完整展现 result 的结构。

网页的样子
网页的样子

总结

目前我们的前端已经可以在本地运行了。不过像这样直接链接私有链,利用索引选择调用合约的用户以及函数的处理方式都非常原始,只能作演示用。之后会尽量设计更完善的代码。

评论