使用 以太坊、web3j 和 Spring Boot 体验区块链
原文链接:piotrminkowski.wordpress.com/2018/06/22/…
作者: Piotr Mińkowski
译者: Junz
在过去的几个月里,区块链是IT世界的流行语之一。 该术语与加密货币有关,并与比特币一起创建。它是分散的,不可变的数据结构,分为块,使用加密算法进行链接和保护。此结构中的每个块通常都包含前一个块的加密哈希、时间戳和事务数据。区块链由对等网络管理,并且在节点间通信期间,每个新块在添加之前都是验证过的。 这是区块链的理论的一小部分。 简而言之,这是一种技术,它允许我们以分散的方式管理双方之间的事务。现在,问题是我们如何在我们的系统中实现它。
下面介绍以太坊。 它是由Vitarik Buterin创建的分散式平台,为应用程序开发提供脚本语言。它基于比特币的思想,并由名为 Ether 的新加密货币驱动。 今天,Ether 是比特币之后的第二大加密货币。 以太坊技术的核心是 EVM(以太坊虚拟机),它可以被视为类似于 JVM 的东西,但使用完全分散的节点网络。要在 Java 世界中实现基于 Ethereum 的事务,我们使用 web3j 库。 这是一个轻量级、响应式、类型安全的 Java 和 Android 库,用于与以太坊区块链上的节点集成。更多细节可以在其网站 web3j.io 上找到。
1、本地运行以太坊
虽然网上有很多关于区块链和以太坊的文章,但要找到一个解决方案来描述如何在本地机器上运行以太网的即用型实例并不容易。值得一提的是,通常我们可以使用两种最受欢迎的以太坊客户端:Geth 和 Parity。事实证明,我们可以使用 Docker 容器在本地轻松运行 Geth 节点。 默认情况下,它将节点连接到以太坊主网络。 或者,您可以将其连接到测试网络或 Rinkeby 网络。 但是开始的最佳选择就是在开发模式下通过在 Docker 容器运行命令的方式设置 --dev 参数来运行它。这是在开发模式下启动 Docker 容器并在端口 8545 上公开 Ethereum RPC API 的命令。
$ docker run -d --name ethereum -p 8545:8545 -p 30303:30303 ethereum/client-go --rpc --rpcaddr "0.0.0.0" --rpcapi="db,eth,net,web3,personal" --rpccorsdomain "*" --dev
在开发模式下运行该容器时,一个非常好的消息是您的默认测试帐户上有大量的 Ethers。在这种情况下,您无需开开发任何 Ethers 就可开始测试。 太棒了! 现在,让我们创建一些其他测试帐户,并查看一些内容。为此我们需要在 Docker 容器中运行 Geth 的交互式 JavaScript 控制台。
$ docker exec -it ethereum geth attach ipc:/tmp/geth.ipc
2、使用JavaScript控制台管理以太坊节点
运行 JavaScript 控制台后,您可以轻松显示默认帐户(coinbase),所有可用帐户的列表及其余额。 我的以太坊节点结果如下图所示。
现在,我们必须创建一些测试帐户。 我们可以通过调用 personal.newAccount(password) 函数来实现。 创建所需帐户后,您可以使用 JavaScript 控制台执行一些测试事务,并将一些资金从基本帐户转移到新创建的帐户。 以下是用于创建帐户和执行事务的命令。
3、系统架构
我们的示例系统的架构非常简单。 我不想让任何事情复杂化,只是告诉你如何将事务发送到 Geth 节点并接收通知。 当 transaction-service 向以太坊节点发送新事务时,bonus-service 观察节点并监听传入事务。 然后,从他的帐户收到的每10笔交易中,它会向发件人的帐户发送一次奖励。 下图展示了我们的示例系统的体系结构。
4、在Spring Boot应用中启用Web3j
我认为现在我们已经明确了我们想要做什么。首先,我们应该包含所有必需的依赖项,以便能够在 Spring Boot 应用程序中使用 web3j 库。幸运的是,有一个启动器可以引入。
<dependency>
<groupId>org.web3j</groupId>
<artifactId>web3j-spring-boot-starter</artifactId>
<version>1.6.0</version>
</dependency>
因为我们在 Docker 容器上运行 Ethereum Geth 客户端,所以我们需要更改 web3j 的自动配置客户端地址。
spring:
application:
name: transaction-service
server:
port: ${PORT:8090}
web3j:
client-address: http://192.168.99.100:8545
5、构建应用程序
如果我们将 web3j starter 包含在项目依赖项中,您只需要自动装配 Web3j bean。 Web3j 负责向 Geth 客户端节点发送事务。 如果已被节点接受,则它接收具有事务散列的响应,如果它已被拒绝,则接收错误对象。 在创建交易对象时,将 gas 限制设置为最小值 21000 非常重要。如果发送的值较低,则可能会收到错误错误:Error: intrinsic gas too low。
@Service
public class BlockchainService {
private static final Logger LOGGER = LoggerFactory.getLogger(BlockchainService.class);
@Autowired
Web3j web3j;
public BlockchainTransaction process(BlockchainTransaction trx) throws IOException {
EthAccounts accounts = web3j.ethAccounts().send();
EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(accounts.getAccounts().get(trx.getFromId()), DefaultBlockParameterName.LATEST).send();
Transaction transaction = Transaction.createEtherTransaction(accounts.getAccounts().get(trx.getFromId()), transactionCount.getTransactionCount(), BigInteger.valueOf(trx.getValue()), BigInteger.valueOf(21_000), accounts.getAccounts().get(trx.getToId()),BigInteger.valueOf(trx.getValue()));
EthSendTransaction response = web3j.ethSendTransaction(transaction).send();
if (response.getError() != null) {
trx.setAccepted(false);
return trx;
}
trx.setAccepted(true);
String txHash = response.getTransactionHash();
LOGGER.info("Tx hash: {}", txHash);
trx.setId(txHash);
EthGetTransactionReceipt receipt = web3j.ethGetTransactionReceipt(txHash).send();
if (receipt.getTransactionReceipt().isPresent()) {
LOGGER.info("Tx receipt: {}", receipt.getTransactionReceipt().get().getCumulativeGasUsed().intValue());
}
return trx;
}
}
上面显示的 @Service bean由控制器调用。 POST方法的实现将 BlockchainTransaction 对象作为参数。 您可以发送发件人ID,收件人ID和交易金额。 发件人和接收者ID等同于查询 eth.account[index] 中的索引。
@RestController
public class BlockchainController {
@Autowired
BlockchainService service;
@PostMapping("/transaction")
public BlockchainTransaction execute(@RequestBody BlockchainTransaction transaction) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException {
return service.process(transaction);
}
}
您可以使用以下命令通过调用 POST 方法发送测试事务。
$ curl --header "Content-Type: application/json" --request POST --data '{"fromId":2,"toId":1,"value":3}' http://localhost:8090/transaction
在发送任何交易之前,您还应该解锁发件人帐户。
应用程序 bonus-service 监听由以太坊节点处理的事务。 它通过调用 web3j.transactionObservable().subscribe(...) 方法来订阅来自 Web3j 库的通知。 它会从该地址发送的每10笔交易中将收到的交易金额返回给发件人的帐户一次。 这是应用程序 bonus-service 中观察方法的实现。
@Autowired
Web3j web3j;
@PostConstruct
public void listen() {
Subscription subscription = web3j.transactionObservable().subscribe(tx -> {
LOGGER.info("New tx: id={}, block={}, from={}, to={}, value={}", tx.getHash(), tx.getBlockHash(), tx.getFrom(), tx.getTo(), tx.getValue().intValue());
try {
EthCoinbase coinbase = web3j.ethCoinbase().send();
EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(tx.getFrom(), DefaultBlockParameterName.LATEST).send();
LOGGER.info("Tx count: {}", transactionCount.getTransactionCount().intValue());
if (transactionCount.getTransactionCount().intValue() % 10 == 0) {
EthGetTransactionCount tc = web3j.ethGetTransactionCount(coinbase.getAddress(), DefaultBlockParameterName.LATEST).send();
Transaction transaction = Transaction.createEtherTransaction(coinbase.getAddress(), tc.getTransactionCount(), tx.getValue(), BigInteger.valueOf(21_000), tx.getFrom(), tx.getValue());
web3j.ethSendTransaction(transaction).send();
}
} catch (IOException e) {
LOGGER.error("Error getting transactions", e);
}
});
LOGGER.info("Subscribed");
}
总结
区块链和加密货币不是一个容易开始的主题。 通过提供完整的脚本语言,以太坊简化了使用区块链的应用程序的开发。 使用 web3j 库以及以太坊 Geth 客户端的 Spring Boot 和 docker 映像,可以快速启动实现区块链技术的解决方案的本地开发。 如果您想在本地尝试,只需克隆我在 GitHub 上提供的存储库 github.com/piomin/samp…