美文网首页Spring Web开发
【Java区块链实践】使用以太坊、web3j 与 Spring

【Java区块链实践】使用以太坊、web3j 与 Spring

作者: 程序员精进 | 来源:发表于2018-07-29 17:26 被阅读11次

原文首发于『程序员精进』博客,原文链接:【Java区块链实践】使用以太坊、web3j 与 Spring Boot 开发应用

image.png

区块链(Blockchain) 是最近一年是技术圈和行业内最为流行的词语,这个术语与加密货币有关,与 比特币 这个术语一同创造。区块链本身是一种分为块的去中心化、不可变的数据结构,使用加密算法进行链接和保护。此结构中的每个区块都包含前一个区块的加密哈希、时间戳和交易数据。区块链由对等网络管理,并且在节点间通信时,每个新的区块在被添加上链前都要被验证。这里只是关于区块链技术理论的一些简单描述,简而言之,这是一种技术,它允许我们以去中心化的方式管理双方的交易。那么,这样的技术我们怎么在系统中实现它。

这里需要推荐下 以太坊,它是由业内称为V神的 Vitarik Buterin 创建的去中心化平台,它为应用程序开发提供了脚本语言。它基于比特币的想法,并由一种名为 Ether 的新加密货币所驱动。今天,Ether 是比特币之后的第二大加密货币。以太坊技术的核心是 EVM(以太坊虚拟机),它可以被视为类似于 JVM 的东西,但是用完全去中心化的网络节点。如果在 Java 中实现基于以太坊技术的交易呢?本文我们将是用 web3j 库,这是一个轻量级、reactive、类型安全的 Java 和 Android 库,用于与以太坊区块链上的节点来集成。关于这个库的更多细节可以在其官方找到:https://web3j.io

1. 本地运行 Ethereum

网上有很多关于区块链和以太坊的文章,但是告诉你怎么在本地运行准备就绪的 Ethereum 实例文章并不多。值得一提的是,我们通常可以使用两种最为流行的 Ethereum 客户端 GethParity。我们使用 Docker 容器可以很方便地在本地运行 Geth 节点。默认情况下,它将节点连接到以太坊网络,或者,你可以将其链接到测试网络或 Rinkeby 网络,最好是在开发环境下运行,因此在 Docker 容器运行命令加个 --dev 参数就好。

下面的命令,将在开发模式下启动一个 Docker 容器,并将 Ethereum RPC API 暴露在 8545 端口。

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,这样就不需要进行挖矿就可以开始我们的测试了。首先,我们先创建一些其他的测试账户,并做一些检查,做这些事情我们需要在 Docker 容器中运行 Geth 的交互式 Javascript 控制台。

$ docker exec -it ethereum geth attach ipc:/tmp/geth.ipc

2. 使用 Javascript 控制台管理以太坊节点

在运行 Javascript 控制台后,我们可以简单的显示出默认账号(coinbase)、所有可用账户列表以及余额。下面是我的以太坊节点截屏:

image.png

现在,我们需要创建一些测试账户,我们可以调用 personal.newAccount(password) 函数来完成。在创建完必要的账户后,就可以使用 Javascript 控制台做一些测试交易,将一些资金从基本账户转账至新创建的账户。下面是我用来创建账户和执行交易的命令。

image.png

3. 系统架构设计

我们示例系统的架构十分简单,这里我不想让事情变得过于复杂,因为主要是展示如何将交易发送到 Geth 节点并接收到通知。transaction-service 服务向以太坊节点发送新的交易,bonus-service 服务是观察节点并监听所有传入的交易。然后,它每从发件人账户收到10笔交易,就想发件人账户发送一次奖金。下面这张图展示了我们的示例系统架构:

image.png

4. Spring Boot 应用启用 Web3j

我认为已经说清楚了正在做的事情,因此,我们现在来看具体的 Java 实现。首先,我们要使用 web3j 库的话先要引入所有必要依赖到我们的 Spring Boot 应用中。幸运的是,我们有个 starter 依赖可以直接引入,省却了麻烦。

<dependency>
    <groupId>org.web3j</groupId>
    <artifactId>web3j-spring-boot-starter</artifactId>
    <version>1.6.0</version>
</dependency>

因为我们在 Docker 容器中运行以太坊客户端 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

在发送任何交易之前,你需要解锁发件人账户。

image.png

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 库和 Spring Boot、以太坊 Geth 客户端镜像一起来使用,可以快速帮助我们实现区块链技术的本地开发,是不错的解决方案。本文提到的相关代码已托管至 GitHub:https://github.com/piomin/sample-spring-blockchain.git

相关文章

网友评论

    本文标题:【Java区块链实践】使用以太坊、web3j 与 Spring

    本文链接:https://www.haomeiwen.com/subject/duqtvftx.html