在尝试本节的样例代码前,需要保证目标智能合约已经按照
蚂蚁区块链BaaS平台应用开发指南(三):从一个简单合约开始
中的做法编译部署成功。
在上一节里,我们通过Cloud IDE部署了一个最简单的智能合约,并且通过Cloud IDE成功的调用了合约的方法。拿传统应用的开发来类比,这就像在数据库上增加了一个存储过程,然后通过外部应用来触发这个存储过程的执行。那么,对于区块链来说,外部应用又如何来调用部署好的智能合约?在这一节中,我们将会通过蚂蚁区块链提供到JavaSDK来接入我们的链,然后调用上一节中部署的合约。官网的文档提供了详细的步骤和一个略微有些复杂的Demo,具体信息可以参见这里。
在这篇文章里,我们的目标更简单:接入目标链,调用已部署的合约。
和Cloud IDE一样,一个Java程序要和链进行通信必须满足:(1)客户端能确认链的身份;(2)链能确认调用者的身份。
客户端要确认链的身份, 需要提前获取对应的CA证书。
对于Java程序,需要在代码中提供trustCa
文件,trustCa
文件的在链卡片右上角···按钮->下载TrustCA获取。
client.crt
和client.key
。user.key
。准备好上述四个文件,在后续的Java工程中,程序需要读取配置上述文件。
再做一次说明,在蚂蚁区块链平台BaaS上,一个用户(机构)可以有多个账户。平台为每一个用户(机构)签发一张×××书(client.crt)和对应的私钥(client.key),用户可以创建多个账户,每个账户有自己的公私钥(user.key, user.pub)。
私钥以及证书的密码一定不要混淆。
Java环境要求:JDK 7+,Maven 3.5.4+
//安装 SDK 到本地仓库
mvn install:install-file -Dfile=mychainx-sdk-0.10.2.4.2.jar -DgroupId=com.alipay.mychainx -DartifactId=mychainx-sdk -Dversion=0.10.2.4.2 -Dpackaging=jar -DpomFile=pom.xml
//安装 Netty 依赖到本地仓库,注意选择对应平台 netty-tcnative-openssl-static 版本,注意修改 classifier,macOS :osx-x86_64、linux:linux-x86_64、windows:windows-x86_64
mvn install:install-file -Dfile=netty-tcnative-openssl-static-2.0.17-Final-mychain-osx-x86_64.jar -DgroupId=io.netty -DartifactId=netty-tcnative-openssl-static -Dversion=2.0.17-Final-mychain -Dpackaging=jar -Dclassifier=
注意按照开发平台修改OS-classifiler
更多信息见官方文档
client.crt
、client.key
、trustCa
及 user.key
文件放入到resources
目录中。pom.xml
中添加依赖。
com.alipay.mychainx
mychainx-sdk
0.10.2.4.2
kr.motd.maven
os-maven-plugin
1.6.1
CallContractDemo
的类,内容参考下面的代码:import com.alipay.mychain.sdk.api.Mychain;
import com.alipay.mychain.sdk.api.request.MychainParams;
import com.alipay.mychain.sdk.api.request.contract.CallContractRequest;
import com.alipay.mychain.sdk.api.result.MychainBaseResult;
import com.alipay.mychain.sdk.config.ISslOption;
import com.alipay.mychain.sdk.config.MychainEnv;
import com.alipay.mychain.sdk.config.SslBytesOption;
import com.alipay.mychain.sdk.domain.account.Identity;
import com.alipay.mychain.sdk.message.response.ReplyTransactionReceipt;
import com.alipay.mychain.sdk.message.response.Response;
import com.alipay.mychain.sdk.network.ClientTypeEnum;
import com.alipay.mychain.sdk.tools.codec.CodecTypeEnum;
import com.alipay.mychain.sdk.tools.codec.contract.ContractParameters;
import com.alipay.mychain.sdk.tools.codec.contract.ContractReturnValues;
import com.alipay.mychain.sdk.tools.crypto.KeyLoder;
import com.alipay.mychain.sdk.tools.hash.HashTypeEnum;
import com.alipay.mychain.sdk.tools.log.LoggerFactory;
import com.alipay.mychain.sdk.tools.sign.SignTypeEnum;
import com.alipay.mychain.sdk.tools.utils.ByteUtils;
import com.alipay.mychain.sdk.tools.utils.Utils;
import org.slf4j.Logger;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class CallContractDemo {
private static Mychain sdk;
// 配置目标链的接入IP和端口号。
// 以上信息可以通过 BaaS平台 > 区块链卡片 > 详情 > 区块链浏览器 > 节点 找到。
// 参见:蚂蚁区块链BaaS平台应用开发指南(二):准备工作 查看区块链详情 小节
private static String host = "*.*.*.*";
private static int port = 18130;
// 设置用户×××书,证书私钥和私钥密码。
private static String keyFilePath = "client.key";
private static String certFilePath = "client.crt";
private static String clientKeyPassword = "****";
// 设置trustCa证书。
// trustStore密码默认为mychain。
private static String trustStoreFilePath = "trustCa";
private static String trustStorePassword = "mychain";
// 设置交易发起者的账户私钥,私钥密码和账户名。
// 可以使用默认账户,即申请证书步骤一并申请的账户
private static String userPrivateKeyFile = "user.key";
private static String userPassword = "****";
private static String accountName = "Your Account Name";
// 设置调用合约名
// 合约名需要是第三节中所部署的合约名称
private static String targetContractName = "Target Contract Name";
private static MychainEnv env;
private static void initMychainEnv() {
// 默认值,不用修改
env = buildMychainEnv("test_sdk");
}
private static void initLogger() {
Logger logger = org.slf4j.LoggerFactory.getLogger(CallContractDemo.class);
LoggerFactory.setInstance(logger);
}
private static MychainEnv buildMychainEnv(String identity) {
InetSocketAddress inetSocketAddress = InetSocketAddress.createUnresolved(host, port);
// build ssl option
// 构建SSL选项
ISslOption sslOption = new SslBytesOption.Builder()
.keyBytes(Utils.readFileToByteArray(CallContractDemo.class.getClassLoader().getResource(keyFilePath).getPath()))
.certBytes(Utils.readFileToByteArray(CallContractDemo.class.getClassLoader().getResource(certFilePath).getPath()))
.keyPassword(clientKeyPassword)
.trustStorePassword(trustStorePassword)
.trustStoreBytes(Utils.readFileToByteArray(CallContractDemo.class.getClassLoader().getResource(trustStoreFilePath).getPath()))
.build();
// 可将同一条链的不同接入节点放入备份节点中。
List backupNodes = new ArrayList();
// backupNodes.add(InetSocketAddress.createUnresolved("47.100.27.162", 18130));
// backupNodes.add(InetSocketAddress.createUnresolved("106.14.218.105", 18130));
// backupNodes.add(InetSocketAddress.createUnresolved("47.100.115.103", 18130));
HashTypeEnum hashType = HashTypeEnum.SHA256;
SignTypeEnum signType = SignTypeEnum.ECDSA;
return MychainEnv.build(identity, ClientTypeEnum.TLS, hashType,
signType, CodecTypeEnum.RLP, inetSocketAddress, sslOption, backupNodes);
}
private static void initSdk() {
sdk = new Mychain();
MychainBaseResult initResult = sdk.init(env);
if (!initResult.isSuccess()) {
System.out.println("initSdk: sdk init failed.");
}
}
public static void main(String[] args) throws Exception{
initLogger();
// 初始化环境。
initMychainEnv();
// 初始化SDK。
initSdk();
// Get the Identity of my account.
// 获取账户的Identity
Identity userIdentity = Utils.getIdentityByName(accountName, env);
// Get the private key of the account.
// 获取账户的私钥
// 注意检查userPrivateKeyFileInputStream是否为空。
InputStream userPrivateKeyFileInputStream = CallContractDemo.class.getClassLoader().getResourceAsStream(userPrivateKeyFile);
PrivateKey userPrivateKey = KeyLoder.getPrivateKeyFromPKCS8(userPrivateKeyFileInputStream,userPassword);
// Add the key to the key list. Note: in this demo, the account only have one single private key.
// 把私钥添加到一个私钥列表中。注:本例中,账户只有一把私钥。
ArrayList userPrivateKeyArrayList = new ArrayList();
userPrivateKeyArrayList.add(userPrivateKey);
// Build a CallContract request
// Build a CallContract request - 1: build Mychain parameters
// 构造CallContrace请求 - 1: 构造Mychain参数。
MychainParams mychainParams = new MychainParams.Builder()
.gas(BigInteger.valueOf(4000000))
.privateKeyList(userPrivateKeyArrayList)
.build();
// Build a CallContract request - 2: get the identity of the contract
// 构造CallContrace请求 - 2: 获取合约Identity。
// 通过合约名获取合约实例的Identity,合约名和上一节中通过Cloud IDE部署的合约名一致。
Identity contractIdentity = Utils.getIdentityByName(targetContractName,env);
// Build a CallContract request - 3: build contract parameters
// 构造CallContrace请求 - 3: 构造调用方法参数。这里设置的是需要调用的方法名。
ContractParameters crtParameters = new ContractParameters("get()");
// Build a CallContract request - 4: build CallContract request
// 构造CallContrace请求 - 4: 构造请求
CallContractRequest request = CallContractRequest.build(
userIdentity,
contractIdentity,
crtParameters,
BigInteger.ZERO,
mychainParams
);
// Call the contract
// 调用合约
MychainBaseResult replyTransactionReceipt = sdk.getContractService().callContract(request);
// Get the returned value from the raw bytes output.
// 获取合约返回数据,并从返回的字节码中获取数据
String rawOutput = ByteUtils.toHexString(replyTransactionReceipt.getData().getTransactionReceipt().getOutput());
ContractReturnValues contractReturnValues = new ContractReturnValues(rawOutput);
// 我们事先知道get方法会返回一个Uint类型的值
BigInteger storedData = contractReturnValues.getUint();
System.out.println("Returned storedData from the contract is: " + storedData);
System.out.println("======================================");
System.out.println("Let us set a new value.");
// Build anther CallContract request
// 构造另外一个合约调用请求。
// 构造调用方法参数,这次我们需要调用set方法
ContractParameters crtParameters2 = new ContractParameters("set(uint256)");
// Give a random Integer
// 设置一个随机数。
Random r = new Random();
Integer value = r.nextInt(100);
// 设置set方法的传入参数值。
crtParameters2.addUint(BigInteger.valueOf(value));
// Build a CallContract request
// 构造请求
CallContractRequest request2 = CallContractRequest.build(
userIdentity,
contractIdentity,
crtParameters2,
BigInteger.ZERO,
mychainParams
);
// Call the contract
// 调用合约
MychainBaseResult replyTransactionReceipt2 = sdk.getContractService().callContract(request2);
if (replyTransactionReceipt2.isSuccess()){
System.out.println("New value of the storedData is: " + value);
}else{
System.out.println("Failed to set the value:" + value);
}
sdk.shutDown();
}
}
上述代码中,我们通过Java程序来调用上一节中部署好的合约:
clientKeyPassword
:用户证书密钥(client.key)的密码userPassword
:账户私钥(user.key)的密码accountName
:账户名contractName
:要调用的目标合约名trustStorePassword
:目前默认为mychain
,没有特殊说明不用修改。host
和port
:链的接入节点 IP和端口号。可通过区块链卡片
> 详情
> 节点
处获取,参见下图。选一个IP填入即可,其他的节点IP可以放入backupNodes
中。
Returned storedData from the contract is: 77
======================================
Let us set a new value.
New value of the storedData is: 16
Returned storedData from the contract is: 16
======================================
Let us set a new value.
New value of the storedData is: 86
至此,Java代码成功的调用到了链上合约的方法,并正确获取到了返回值。可以看到,调用一个合约的主要步骤为