image.png
在前面的文章中我们介绍了如何使用以太坊、Web3j以及 Spring Boot 来开发区块链应用,该文章链接:【Java区块链实践】使用以太坊、web3j 与 Spring Boot 开发应用。本文将介绍如何在应用中实现以太坊的智能合约,首先我们需要来看看什么是智能合约。
智能合约是一个运行在 EVM(以太坊虚拟机)上的一个程序。每个智能合约都是代码(函数)和数据的集合,它在以太坊区块链上有一个地址,可以与其他合约进行交互,例如做决策、存储数据或向他人发送 Ether。通常用来编写以太坊智能合约的语言叫做 Solidity,是一种静态类型的高级语言。每个合约都需要被编译,基于编译后的二进制文件,可以为自己的应用创建源代码。Web3j 库提供了工具来帮助我们完成上述工作。在我们开始聊源代码之前,咱们先看看示例系统的架构。
包含了两个独立的应用,分别是 contract-service 和 transaction-service,绝大部分的业务逻辑将放在 contract-service 应用中,它提供了创建智能钱包、部署智能合约到以太坊以及调用智能合约函数的能力。transaction-service 应用仅负责第三方和智能合约拥有者之间的交易,它将会调用 contract-service 应用暴露的接口。contract-service 应用会观察以太坊节点上执行的交易,如果交易与智能合约拥有者的账户相关,应用会负责将本合约所有由合约所有者签署的合约资金转入合约接收者的账户。下图是上面描述的过程表示。
image.png
1. 使用 Solidity 构建一个智能合约
在以太坊中创建智能合约最常用的工具就是 Solidity,Solidity 是一种用于实现智能合约的面向契约高级语言。它受到了 C++、Python 和 Javascript 的影响,被设计为针对以太坊虚拟机 EVM。它是静态类型的,支持继承、库和复杂的用户定义类型,以及一些其他特性。有关该语言的更多信息,可以查询官方文档:http://solidity.readthedocs.io/。
本文中我们的主要目标是构建一个简单的智能合约,并编译和创建必要的源代码。这样我们就不过多介绍使用 Solidity 设计实现智能合约的详细内容。智能合约的实现负责计算交易的费用,基于此计算,它将资金存入交易所有者的账户,并从发送者的账户中提取资金。本合约是由两个用户签署,他们每个人都有自己的智能钱包,由他们的凭证担保。理解这个简单的合约是非常重要的,接下来我们一行行地分析它。
下面每个智能合约都是描述按照一定百分比交易额的手续费,这些交易的接收者费用(1) 和 发送者账户地址(2)。合约前两行声明了用来存储数据的两个变量:fee Solidity 数据类型 uint,receiver 数据类型 address。这些变量值都在合约构造时进行初始化(5)。参数 fee 表明手续费为该交易的百分比,从发送者账户中提取并存入接受者账户。mapping (address => uint) public balances 这行代码表示所有余额将用无符号整型来映射(3)。我们同样定义了发送事件,表示每个合约交易触发信息(4)。getReceiverBalance 函数返回接收者账户余额(6)。最后,有个名为 sendTrx(...) 的函数,可以被外部客户端进行调用,它负责根据合约的费用和交易金额进行提款和存款操作。这需要更多的关注,首先,它需要有 payable 修改器才能够在以太坊账户将进行资金转账。之后,可以从 msg.value 参数中读取交易金额。在然后,我们将调用函数向接收者地址发送给定的金额,并将数值保存到合约余额中。此外,我们可能发送一个事件,可以由客户端应用进行接收。
pragma solidity ^0.4.21;
contract TransactionFee {
// (1)
uint public fee;
// (2)
address public receiver;
// (3)
mapping (address => uint) public balances;
// (4)
event Sent(address from, address to, uint amount, bool sent);
// (5)
constructor(address _receiver, uint _fee) public {
receiver = _receiver;
fee = _fee;
}
// (6)
function getReceiverBalance() public view returns(uint) {
return receiver.balance;
}
// (7)
function sendTrx() public payable {
uint value = msg.value * fee / 100;
bool sent = receiver.send(value);
balances[receiver] += (value);
emit Sent(msg.sender, receiver, value, sent);
}
}
一旦我们创建了一个合约,我们必须编译并且创建源代码,这样我们才能够在我们的应用中部署合约并调用它的函数。有关 Solidity 编译器的相关信息,可以查阅官方网站:https://remix.ethereum.org。
2. 编译合约并创建源代码
Solidity 为编译器提供了最新的 Docker 镜像,正式版本标记为stable,来自于开发分支的不稳定版本标记为nightly。但是,Docker 镜像只包含编译器可执行文件,因此我们必须将 Solidity 合约输入文件进行持久化卷挂载。假设这些文件在我们运行 Docker 容器机器的目录 /home/doc