集成外部签名服务构造交易¶
标签:java-sdk
发送交易
外部签名
组装交易
合约调用
AssembleTransactionProcessor已经支持和覆盖了常见的合约操作接口。但是在真实的业务场景中,对于某些特定的业务场景,需要调用硬件加密机或远程签名服务对该哈希进行签名。为此,我们在AssembleTransactionProcessor的基础上进一步提供了AssembleTransactionWithRemoteSignProcessor,来便于用户集成自定义签名服务。
1. 概念解析:部署和调用¶
部署、调用(交易和查询)的相关概念可参考概念解析:部署和调用
2. 快速上手¶
SDK支持同步和异步方式来调用合约。在快速上手环节,首先展示使用同步方式来调用合约。
2.1 准备abi和binary文件¶
控制台提供一个专门的编译合约工具,方便开发者将Solidity/webankblockchain-liquid(以下简称WBC-Liquid)合约文件编译生成Java文件和abi、binary文件,具体使用方式参考这里。
通过运行contract2java 脚本,生成的abi和binary文件分别位于contracts/sdk/abi、contracts/sdk/bin目录下(其中,国密版本编译产生的文件位于contracts/sdk/abi/sm和contracts/sdk/bin/sm文件夹下)。可将文件复制到项目的目录下,例如src/main/resources/abi和src/main/resources/bin。
为了便于演示,我们使用了以下HelloWorld的Solidity合约。
pragma solidity ^0.6.0;
contract HelloWorld{
string public name;
constructor() public{
name = "Hello, World!";
}
function set(string memory n) public{
name = n;
}
}
编译合约,生成abi和binary:
# 切换到控制台所在目录
$ cd ~/fisco/console
# 调用sol2java.sh脚本,编译contracts/solidity/目录下的HelloWorld合约:
$ bash contract2java.sh solidity -p org -s contracts/solidity/HelloWorld.sol
# 生成的abi位于contracts/sdk/abi/HelloWorld.abi路径
$ ls contracts/sdk/abi/HelloWorld.abi
# 生成的非国密版本的bin位于contracts/sdk/bin/HelloWorld.bin路径
$ ls contracts/sdk/bin/HelloWorld.bin
# 生成的国密版本bin位于contracts/sdk/bin/sm/HelloWorld.bin路径
$ ls contracts/sdk/bin/sm/HelloWorld.bin
至此HelloWorld
合约的abi和binary文件均已生成
2.2 初始化SDK¶
基于配置文件,初始化SDK,如:
// 初始化BcosSDK对象
BcosSDK sdk = BcosSDK.build(configFile);
// 获取Client对象,此处传入的群组名 group0
Client client = sdk.getClient("group0");
// 构造AssembleTransactionProcessor对象,需要传入client对象,CryptoKeyPair对象和abi、binary文件存放的路径。abi和binary文件需要在上一步复制到定义的文件夹中。
CryptoKeyPair keyPair = client.getCryptoSuite().getCryptoKeyPair();
2.3 初始化配置对象¶
2.3.1 自定义外部签名服务¶
外部签名的服务需要实现RemoteSignProviderInterface
接口。
public interface RemoteSignProviderInterface {
// 同步签名接口
public SignatureResult requestForSign(byte[] dataToSign, int cryptoType);
// 异步签名接口
public void requestForSignAsync(
byte[] dataToSign, int cryptoType, RemoteSignCallbackInterface callback);
}
用户可按需实现requestForSign和requestForSignAsync
接口,实现调用外部签名服务,同步或异步返回结果的逻辑。具体的业务逻辑视业务场景自主封装,可以是调用硬件加签机服务,也可以是调用外部托管的签名服务。当异步签名接口结果返回后,会自动回调RemoteSignCallbackInterface
中定义的handleSignedTransaction
接口。该接口定义如下:
public interface RemoteSignCallbackInterface {
/**
* receive the signature,and execute the callback function later.
*
* @param signature
* @return result code
*/
public int handleSignedTransaction(SignatureResult signature);
}
为了便于演示,我们创建一个外部签名服务的Mock类(代码位置src/integration-test/java/org/fisco/bcos/sdk/v3/test/transaction/mock/RemoteSignProviderMock
),该类模拟实现了RemoteSignProviderInterface
的同步签名接口requestForSign
和异步签名接口requestForSignAsync
。
2.3.2 部署、交易和查询¶
Java SDK提供了基于abi和binary文件来直接部署和调用合约的方式。本场景下适用于默认的情况,通过创建和使用AssembleTransactionWithRemoteSignProcessor
对象来完成合约相关的部署、调用和查询等操作。此处, 假设我们创建了一个外部签名的Mock类 RemoteSignProviderMock
。
// remoteSignProviderMock 对象需实现RemoteSignCallbackInterface 接口
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor =
TransactionProcessorFactory.createAssembleTransactionWithRemoteSignProcessor(
client, cryptoKeyPair, "src/main/resources/abi/", "src/main/resources/bin/", remoteSignProviderMock);
2.3.3 仅交易和查询¶
假如只交易和查询,而不部署合约,那么就不需要复制binary文件,且在构造时无需传入binary文件的路径,例如binary路径参数可传入空字符串。
// remoteSignProviderMock 对象需实现RemoteSignCallbackInterface 接口
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor =
TransactionProcessorFactory.createAssembleTransactionWithRemoteSignProcessor(
client, cryptoKeyPair, "src/main/resources/abi/", "", remoteSignProviderMock);
2.4 发送操作指令¶
完成初始化SDK和配置对象后,可以发起合约操作指令。
2.4.1 同步方式部署合约¶
部署合约调用了deployByContractLoader
方法,传入合约名和构造函数的参数,上链部署合约,并获得TransactionResponse
的结果。
// 部署HelloWorld合约。第一个参数为合约名称,第二个参数为合约构造函数的列表,是List<Object>类型。
TransactionResponse response =
assembleTransactionWithRemoteSignProcessor.deployByContractLoader("HelloWorld", new ArrayList<>());
TransactionResponse
的数据结构如下:
returnCode: 返回的响应码。其中0为成功。
returnMessages: 返回的错误信息。
TransactionReceipt:上链返回的交易回执。
ContractAddress: 部署或调用的合约地址。
values: 如果调用的函数存在返回值,则返回解析后的交易返回值,返回Json格式的字符串。
events: 如果有触发日志记录,则返回解析后的日志返回值,返回Json格式的字符串。
receiptMessages: 返回解析后的交易回执信息。
例如,部署HelloWorld
合约的返回结果:
{
"id" : 15,
"jsonrpc" : "2.0",
"result" :
{
"blockNumber" : 3,
"checksumContractAddress" : "",
"contractAddress" : "",
"extraData" : "",
"from" : "0xa38e104bb4a92a52452b48342c935f68df20c2ce",
"gasUsed" : "13088",
"hash" : "0xaa9f2aedfaa5714d07933b6ad286ce4bdf3d33172109efe464acf217c386afb7",
"logEntries" : [],
"message" : "",
"output" : "0x",
"status" : 0,
"to" : "0x4721d1a77e0e76851d460073e64ea06d9c104194",
"transactionHash" : "0x113b6fba39b8c52bd87a23a260321505f67ef5f04741b988357f2c1c8838d628",
"version" : 0
}
}
2.4.2 同步方式发送交易¶
调用合约交易使用了sendTransactionAndGetResponseByContractLoader
来调用合约交易,此处展示了如何调用HelloWorld
中的set
函数。
// 创建调用交易函数的参数,此处为传入一个参数
List<Object> params = Lists.newArrayList("test");
// 调用HelloWorld合约,合约地址为helloWorldAddress, 调用函数名为『set』,函数参数类型为params
TransactionResponse transactionResponse = assembleTransactionWithRemoteSignProcessor.sendTransactionAndGetResponse(
helloWorldAddrss, abi, "set", params);
例如,调用HelloWorld
合约的返回如下:
{
"id" : 15,
"jsonrpc" : "2.0",
"result" :
{
"blockNumber" : 3,
"checksumContractAddress" : "",
"contractAddress" : "",
"extraData" : "",
"from" : "0xa38e104bb4a92a52452b48342c935f68df20c2ce",
"gasUsed" : "13088",
"hash" : "0xaa9f2aedfaa5714d07933b6ad286ce4bdf3d33172109efe464acf217c386afb7",
"logEntries" : [],
"message" : "",
"output" : "0x",
"status" : 0,
"to" : "0x4721d1a77e0e76851d460073e64ea06d9c104194",
"transactionHash" : "0x113b6fba39b8c52bd87a23a260321505f67ef5f04741b988357f2c1c8838d628",
"version" : 0
}
}
2.4.3 调用合约查询接口¶
查询合约直接通过调用链上的节点查询函数即可返回结果,无需共识;因此所有的查询交易都是同步的。查询合约使用了sendCallByContractLoader
函数来查询合约,此处展示了如何调用HelloWorld
中的name
函数来进行查询。
// 查询HelloWorld合约的『name』函数,合约地址为helloWorldAddress,参数为空
CallResponse callResponse1 = assembleTransactionWithRemoteSignProcessor.sendCallByContractLoader("HelloWorld", helloWorldAddrss, "name", new ArrayList<>());
查询函数返回如下:
{
"id" : 19,
"jsonrpc" : "2.0",
"result" :
{
"blockNumber" : 3,
"output" : "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000",
"status" : 0
}
}
3. 更多操作接口¶
在调用外部签名服务的时候,可通过同步或异步的方式。异步调用可采用callback或CompletableFuture等方式。
3.1 采用callback的方式异步操作合约¶
3.1.1 定义回调类¶
异步调用外部签名服务的时候,可以自定义回调类,实现和重写回调处理函数。
自定义的回调类需要继承抽象类RemoteSignCallbackInterface
, 实现handleSignedTransaction
方法。
例如,我们定义一个简单的回调类。该回调类实现了异步回调发送交易到节点的效果。
public class RemoteSignCallbackMock implements RemoteSignCallbackInterface {
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor;
RawTransaction rawTransaction;
public RemoteSignCallbackMock(
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor,
RawTransaction rawTransaction) {
this.assembleTransactionWithRemoteSignProcessor =
assembleTransactionWithRemoteSignProcessor;
this.rawTransaction = rawTransaction;
}
/**
* 签名结果回调的实现
*
* @param signatureStr 签名服务回调返回的签名结果串
* @return *
*/
@Override
public int handleSignedTransaction(String signatureStr) {
System.out.println(System.currentTimeMillis() + " SignatureResult: " + signatureStr);
// 完成了交易签名后,将其发送出去
TransactionReceipt tr =
assembleTransactionWithRemoteSignProcessor.signAndPush(
rawTransaction, signatureStr);
return 0;
}
}
3.1.2 采用callback的方式异步部署合约¶
首先,创建一个回调类的实例。然后使用deployAsync
方法异步部署合约。
// 创建 RawTransaction
long transactionData = assembleTransactionWithRemoteSignProcessor.getRawTransactionForConstructor(abi, bin, new ArrayList<>());
// 创建回调实例
RemoteSignCallbackMock callbackMock = new RemoteSignCallbackMock(assembleTransactionWithRemoteSignProcessor, transactionData, 0);
// 异步部署合约
assembleTransactionWithRemoteSignProcessor.deployAsync(transactionData, callbackMock);
3.1.3 采用callback的方式调用交易¶
参考部署合约交易,可采用异步的方式发送交易。
// 创建 RawTransaction
long sendTxRawTransaction = assembleTransactionWithRemoteSignProcessor.getRawTransaction(helloWorldAddress, abi, "set", params);
// 创建回调实例
RemoteSignCallbackMock callbackMock2 = new RemoteSignCallbackMock(assembleTransactionWithRemoteSignProcessor, sendTxRawTransaction, 0);
// 发送异步回调交易
assembleTransactionWithRemoteSignProcessor.sendTransactionAsync(helloWorldAddress, abi, "set", this.params, callbackMock2);
3.2 采用CompletableFuture的方式异步操作合约¶
3.2.1 采用CompletableFuture的方式部署合约¶
SDK还支持使用CompletableFuture封装的方式异步部署合约。
// 异步部署交易,并获得CompletableFuture<TransactionReceipt> 对象
CompletableFuture<TransactionReceipt> future = assembleTransactionWithRemoteSignProcessor.deployAsync(abi, bin, new ArrayList<>());
// 定义正常返回的业务逻辑
future.thenAccept(
tr -> {
doSomething(tr);
});
// 定义异常返回的业务逻辑
future.exceptionally(
e -> {
doSomething(e);
return null;
});
3.2.2 采用CompletableFuture的方式发送交易¶
同部署合约。
// 异步部署交易,并获得CompletableFuture<TransactionReceipt> 对象
CompletableFuture<TransactionReceipt> future2 = assembleTransactionWithRemoteSignProcessor.sendTransactionAsync(
helloWorldAddrss, abi, "set", params);
// 定义正常返回的业务逻辑
future.thenAccept(
tr -> {
doSomething(tr);
});
// 定义异常返回的业务逻辑
future.exceptionally(
e -> {
doSomething(e);
return null;
});
4. 详细API功能介绍¶
AssembleTransactionWithRemoteSignProcessor
支持自定义参数发送交易,支持异步的方式来发送交易,也支持返回多种封装方式的结果。
AssembleTransactionWithRemoteSignProcessor
继承了 AssembleTransactionProcessor
类,实现了AssembleTransactionWithRemoteSignProviderInterface
接口。
继承的接口参考AssembleTransactionWithRemoteSignProcessor。
详细的API功能如下。
void deployAsync(RawTransaction rawTransaction, RemoteSignCallbackInterface remoteSignCallbackInterface): 传入部署合约的RawTransaction报文和签名服务的callback来部署合约,自动执行回调函数。
void deployAsync(String abi, String bin, List<Object> params, RemoteSignCallbackInterface remoteSignCallbackInterface) : 传入合约abi、bin和构造函数参数和签名服务的callback来部署合约,自动执行回调函数。
void deployByContractLoaderAsync(String contractName, List<Object> params, RemoteSignCallbackInterface remoteSignCallbackInterface): 传入合约名和构造参数以及callback,来异步部署合约
void sendTransactionAndGetReceiptByContractLoaderAsync(String contractName, String to, String functionName, List<Object> params,RemoteSignCallbackInterface remoteSignCallbackInterface): 传入调用合约名、合约地址、函数名、函数参数、签名服务的callback,异步发送交易。
CompletableFuture<TransactionReceipt> sendTransactionAsync(String to,String abi,String functionName,List<Object> params,RemoteSignCallbackInterface remoteSignCallbackInterface): 传入调用合约地址、abi、函数名、函数参数、签名服务的callback,同步返回调用,异步获取CompletableFuture处理回执结果。
CompletableFuture<TransactionReceipt> sendTransactionAsync(String to, String abi, String functionName, List<Object> params): 传入调用合约地址、abi、函数名、函数参数,同步签名,并同步返回调用,异步获取CompletableFuture处理回执结果。
TransactionReceipt signAndPush(RawTransaction rawTransaction, String signatureStr): 传入RawTransaction和签名结果,推送给节点,同步接收交易回执。
CompletableFuture<TransactionReceipt> signAndPush(RawTransaction rawTransaction, byte[] rawTxHash) : 传入RawTransaction和签名结果,同步调用签名服务,并异步接收交易回执结果。