Integrate external signature services to construct transactions¶
Tags: “java-sdk” “send transaction” “external signature” “assembly transaction” “contract invocation” “
AssembleTransactionProcessorCommon contract operation interfaces have been supported and covered。However, in real business scenarios, for some specific business scenarios, you need to call the hardware encryption machine or remote signing service to sign the hash。To this end, we further provide AssembleTransactionWithRemoteSignProcessor on top of AssembleTransactionProcessor to facilitate user integration with custom signing services。
1. Concept resolution: deployment and invocation¶
Concepts related to deployment and invocation (transactions and queries) can be found in Concept Resolution: Deployment and Invocation
2. Get started quickly¶
SDK supports synchronous and asynchronous ways to invoke contracts。In the Quick Start section, first show the use of synchronous methods to call the contract。
2.1 Prepare abi and binary files¶
The console provides a dedicated contract compilation tool that allows developers to compile Solidity / webankblockchain-liquid (hereinafter referred to as WBC-Liquid) contract files to generate Java files, abi, and binary files(./quick_start.html#contract2java-sh)。
By running the contract2java script, the generated abi and binary files are located in the contracts / sdk / abi and contracts / sdk / bin directories respectively (the files generated by the compilation of the national secret version are located in the contracts / sdk / abi / sm and contracts / sdk / bin / sm folders respectively)。You can copy files to the project directory, such as src / main / resources / abi and src / main / resources / bin。
For demonstration purposes, we used the following Solidity contract for HelloWorld。
pragma solidity ^0.6.0;
contract HelloWorld{
string public name;
constructor() public{
name = "Hello, World!";
}
function set(string memory n) public{
name = n;
}
}
Compile contracts to generate abi and binary:
# Switch to the directory where the console is located
$ cd ~/fisco/console
# Call the sol2java.sh script to compile the HelloWorld contract in the contracts / consolidation / directory:
$ bash contract2java.sh solidity -p org -s contracts/solidity/HelloWorld.sol
# The generated abi is in the contract / sdk / abi / HelloWorld.abi path
$ ls contracts/sdk/abi/HelloWorld.abi
# The generated non-secret version of the bin is located in the contract / sdk / bin / HelloWorld.bin path
$ ls contracts/sdk/bin/HelloWorld.bin
# The generated state secret version bin is located in the path of contracts / sdk / bin / sm / HelloWorld.bin
$ ls contracts/sdk/bin/sm/HelloWorld.bin
The abi and binary files for the ‘HelloWorld’ contract have now been generated
2.2 Initialize SDK¶
Initialize the SDK based on the configuration file, such as:
/ / Initialize the BcosSDK object
BcosSDK sdk = BcosSDK.build(configFile);
/ / Obtain the client object. The group name is group0
Client client = sdk.getClient("group0");
/ / To construct an AssembleTransactionProcessor object, you must pass in the client object, the CryptoKeyPair object, and the path where the abi and binary files are stored。The abi and binary files need to be copied to the defined folder in the previous step。
CryptoKeyPair keyPair = client.getCryptoSuite().getCryptoKeyPair();
2.3 Initialize Configuration Object¶
2.3.1 Custom External Signature Service¶
Externally signed services need to implement the ‘RemoteSignProviderInterface’ interface。
public interface RemoteSignProviderInterface {
/ / synchronous signature interface
public SignatureResult requestForSign(byte[] dataToSign, int cryptoType);
/ / Asynchronous signature interface
public void requestForSignAsync(
byte[] dataToSign, int cryptoType, RemoteSignCallbackInterface callback);
}
Users can implement the ‘requestForSign and requestForSignAsync’ interfaces on demand to implement the logic of calling external signature services and returning results synchronously or asynchronously。The specific business logic is encapsulated autonomously depending on the business scenario, either calling the hardware signing machine service or calling an externally managed signature service。The ‘handleSignedTransaction’ interface defined in ‘RemoteSignCallbackInterface’ is automatically called back when the result of the asynchronous signature interface is returned。The interface is defined as follows:
public interface RemoteSignCallbackInterface {
/**
* receive the signature,and execute the callback function later.
*
* @param signature
* @return result code
*/
public int handleSignedTransaction(SignatureResult signature);
}
For demonstration purposes, we create a Mock class of an external signature service(Code Location ‘src / integration-test / java / org / fisco / bcos / sdk / v3 / test / transaction / mock / RemoteSignProviderMock’)This class simulates the synchronous signature interface ‘requestForSign’ and the asynchronous signature interface ‘requestForSignAsync’。
2.3.2 Deployment, trading and querying¶
Java SDK provides a way to directly deploy and invoke contracts based on abi and binary files。This scenario applies to the default situation, by creating and using the ‘AssembleTransactionWithRemoteSignProcessor’ object to complete contract-related deployment, invocation, and query operations。Here, suppose we create an externally signed Mock class’ RemoteSignProviderMock’。
/ / The remoteSignProviderMock object must implement the RemoteSignCallbackInterface interface
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor =
TransactionProcessorFactory.createAssembleTransactionWithRemoteSignProcessor(
client, cryptoKeyPair, "src/main/resources/abi/", "src/main/resources/bin/", remoteSignProviderMock);
2.3.3 Transactions and Enquiries Only¶
If you only trade and query, but do not deploy the contract, then you do not need to copy the binary file, and you do not need to pass in the path of the binary file during construction, for example, the binary path parameter can be passed in an empty string。
/ / The remoteSignProviderMock object must implement the RemoteSignCallbackInterface interface
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor =
TransactionProcessorFactory.createAssembleTransactionWithRemoteSignProcessor(
client, cryptoKeyPair, "src/main/resources/abi/", "", remoteSignProviderMock);
2.4 Send operation instruction¶
After initializing the SDK and configuring objects, you can initiate contract operation instructions。
2.4.1 Deploy contracts synchronously¶
The deployment contract calls the ‘deployByContractLoader’ method, passes in the contract name and constructor parameters, uploads the deployment contract, and obtains the result of the ‘TransactionResponse’。
/ / Deploy the HelloWorld contract。The first parameter is the contract name, and the second parameter is the list of contract constructors, which is List<Object>Type。
TransactionResponse response =
assembleTransactionWithRemoteSignProcessor.deployByContractLoader("HelloWorld", new ArrayList<>());
The data structure of ‘TransactionResponse’ is as follows:
returnCode: Response code returned。where 0 is success。
returnMessages: Error message returned。
TransactionReceipt: transaction receipt returned on the chain。
ContractAddress: Address of contract deployed or invoked。
values: If the called function has a return value, it returns the parsed transaction return value and a string in JSON format。
events: If there is a trigger log record, the parsed log return value is returned, and a string in JSON format is returned。
receiptMessages: Returns the parsed transaction receipt information。
For example, deploying the ‘HelloWorld’ contract returns:
{
"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 Send transactions synchronously¶
Calling a contract transaction uses’ sendTransactionAndGetResponseByContractLoader ‘to invoke a contract transaction. Here’s how to call the’ set ‘function in’ HelloWorld’。
/ / Create a parameter to call the transaction function. Here, a parameter is passed in
List<Object> params = Lists.newArrayList("test");
/ / Call the HelloWorld contract. The contract address is helloWorldAddress, the function name is set, and the function parameter type is params
TransactionResponse transactionResponse = assembleTransactionWithRemoteSignProcessor.sendTransactionAndGetResponse(
helloWorldAddrss, abi, "set", params);
For example, calling the ‘HelloWorld’ contract returns the following:
{
"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 Call the contract query interface¶
Query contracts can return results directly by calling the node query function on the chain without consensus;So all query transactions are synchronized。Querying a contract uses the ‘sendCallByContractLoader’ function to query the contract. This section shows how to call the ‘name’ function in ‘HelloWorld’ to query the contract。
/ / Query the name function of the HelloWorld contract. The contract address is helloWorldAddress and the parameter is empty
CallResponse callResponse1 = assembleTransactionWithRemoteSignProcessor.sendCallByContractLoader("HelloWorld", helloWorldAddrss, "name", new ArrayList<>());
The query function returns the following:
{
"id" : 19,
"jsonrpc" : "2.0",
"result" :
{
"blockNumber" : 3,
"output" : "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000",
"status" : 0
}
}
3. More operation interface¶
When calling an external signature service, you can do so either synchronously or asynchronously。Asynchronous calls can be made in a way such as callback or CompletableFuture。
3.1 Asynchronous operation contract by callback¶
3.1.1 Define callback class¶
When calling the external signature service asynchronously, you can customize the callback class, implement and rewrite the callback handler function。
The custom callback class needs to inherit the abstract class’ RemoteSignCallbackInterface ‘and implement the’ handleSignedTransaction ‘method。
For example, we define a simple callback class。This callback class implements the effect of asynchronous callbacks sending transactions to nodes。
public class RemoteSignCallbackMock implements RemoteSignCallbackInterface {
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor;
RawTransaction rawTransaction;
public RemoteSignCallbackMock(
AssembleTransactionWithRemoteSignProcessor assembleTransactionWithRemoteSignProcessor,
RawTransaction rawTransaction) {
this.assembleTransactionWithRemoteSignProcessor =
assembleTransactionWithRemoteSignProcessor;
this.rawTransaction = rawTransaction;
}
/**
* Implementation of signature result callback
*
* The signature result string returned by the @ param signatureStr signature service callback
* @return *
*/
@Override
public int handleSignedTransaction(String signatureStr) {
System.out.println(System.currentTimeMillis() + " SignatureResult: " + signatureStr);
/ / After signing the transaction, send it out
TransactionReceipt tr =
assembleTransactionWithRemoteSignProcessor.signAndPush(
rawTransaction, signatureStr);
return 0;
}
}
3.1.2 Asynchronous deployment of contracts using callback¶
First, create an instance of the callback class。Then use the ‘deployAsync’ method to deploy the contract asynchronously。
/ / Create RawTransaction
long transactionData = assembleTransactionWithRemoteSignProcessor.getRawTransactionForConstructor(abi, bin, new ArrayList<>());
/ / Create a callback instance
RemoteSignCallbackMock callbackMock = new RemoteSignCallbackMock(assembleTransactionWithRemoteSignProcessor, transactionData, 0);
/ / Asynchronous deployment contract
assembleTransactionWithRemoteSignProcessor.deployAsync(transactionData, callbackMock);
3.1.3 Call the transaction by callback¶
Reference deployment contract transactions, which can be sent asynchronously。
/ / Create RawTransaction
long sendTxRawTransaction = assembleTransactionWithRemoteSignProcessor.getRawTransaction(helloWorldAddress, abi, "set", params);
/ / Create a callback instance
RemoteSignCallbackMock callbackMock2 = new RemoteSignCallbackMock(assembleTransactionWithRemoteSignProcessor, sendTxRawTransaction, 0);
/ / Send asynchronous callback transaction
assembleTransactionWithRemoteSignProcessor.sendTransactionAsync(helloWorldAddress, abi, "set", this.params, callbackMock2);
3.2 Asynchronous operation of contracts using CompletableFuture¶
3.2.1 Deploy contracts using CompletableFuture¶
The SDK also supports asynchronous contract deployment using CompletableFuture encapsulation。
/ / Deploy the transaction asynchronously and get CompletableFuture<TransactionReceipt> Object
CompletableFuture<TransactionReceipt> future = assembleTransactionWithRemoteSignProcessor.deployAsync(abi, bin, new ArrayList<>());
/ / Define the business logic returned normally
future.thenAccept(
tr -> {
doSomething(tr);
});
/ / Define the business logic returned by the exception
future.exceptionally(
e -> {
doSomething(e);
return null;
});
3.2.2 Sending transactions using CompletableFuture¶
Same deployment contract。
/ / Deploy the transaction asynchronously and get CompletableFuture<TransactionReceipt> Object
CompletableFuture<TransactionReceipt> future2 = assembleTransactionWithRemoteSignProcessor.sendTransactionAsync(
helloWorldAddrss, abi, "set", params);
/ / Define the business logic returned normally
future.thenAccept(
tr -> {
doSomething(tr);
});
/ / Define the business logic returned by the exception
future.exceptionally(
e -> {
doSomething(e);
return null;
});
4. Detailed API function introduction¶
‘AssembleTransactionWithRemoteSignProcessor ‘supports custom parameters to send transactions, supports asynchronous methods to send transactions, and supports returning results in multiple encapsulation methods。
‘AssembleTransactionWithRemoteSignProcessor ‘inherits the’ AssembleTransactionProcessor ‘class and implements the’ AssembleTransactionWithRemoteSignProviderInterface ‘interface。
Inherited Interface Reference AssembleTransactionWithRemoteSignProcessor。
The detailed API functions are as follows。
void deployAsync(RawTransaction rawTransaction, RemoteSignCallbackInterface remoteSignCallbackInterface): Input the RawTransaction packet of the deployment contract and the callback of the signature service to deploy the contract and automatically execute the callback function。
void deployAsync(String abi, String bin, List<Object> params, RemoteSignCallbackInterface remoteSignCallbackInterface) : The contract is deployed by passing in contract abi, bin, constructor parameters and the callback of the signature service, and the callback function is automatically executed。
void deployByContractLoaderAsync(String contractName, List<Object> params, RemoteSignCallbackInterface remoteSignCallbackInterface): Enter the contract name, construction parameters, and callback to deploy the contract asynchronously
void sendTransactionAndGetReceiptByContractLoaderAsync(String contractName, String to, String functionName, List<Object> params,RemoteSignCallbackInterface remoteSignCallbackInterface): Call the contract name, contract address, function name, function parameters, and callback of the signature service. Send the transaction asynchronously。
CompletableFuture<TransactionReceipt> sendTransactionAsync(String to,String abi,String functionName,List<Object> params,RemoteSignCallbackInterface remoteSignCallbackInterface): Pass in the callback of the calling contract address, abi, function name, function parameter, and signature service, return the call synchronously, and asynchronously obtain the CompletableFuture processing receipt result。
CompletableFuture<TransactionReceipt> sendTransactionAsync(String to, String abi, String functionName, List<Object> params): Pass in the call contract address, abi, function name, function parameters, synchronous signature, and synchronous return call, asynchronously obtain CompletableFuture processing receipt results。
TransactionReceipt signAndPush(RawTransaction rawTransaction, String signatureStr): Incoming RawTransaction and signature results, pushing them to nodes, and receiving transaction receipts simultaneously。
CompletableFuture<TransactionReceipt> signAndPush(RawTransaction rawTransaction, byte[] rawTxHash) : Incoming RawTransaction and the signature result, calling the signature service synchronously, and receiving the transaction receipt result asynchronously。