From 05a3159f5f79daa59130651019bb19ec861bfbcb Mon Sep 17 00:00:00 2001 From: zhaoguangwei Date: Tue, 27 Oct 2020 13:51:31 +0800 Subject: [PATCH] add docker-sdk; --- deploy/docker-sdk/pom.xml | 70 +++++++ .../com/jd/blockchain/ContractParams.java | 107 ++++++++++ .../main/java/com/jd/blockchain/SDKDemo.java | 68 +++++++ .../com/jd/blockchain/SDKDemo_Constant.java | 46 +++++ .../java/com/jd/blockchain/SDK_Base_Demo.java | 191 ++++++++++++++++++ .../jd/chain/contract/TransferContract.java | 32 +++ .../contract-compile-1.3.0.RELEASE.car | Bin 0 -> 5129 bytes 7 files changed, 514 insertions(+) create mode 100644 deploy/docker-sdk/pom.xml create mode 100644 deploy/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java create mode 100644 deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java create mode 100644 deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java create mode 100644 deploy/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java create mode 100644 deploy/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java create mode 100644 deploy/docker-sdk/src/main/resources/contract-compile-1.3.0.RELEASE.car diff --git a/deploy/docker-sdk/pom.xml b/deploy/docker-sdk/pom.xml new file mode 100644 index 00000000..1c7e0787 --- /dev/null +++ b/deploy/docker-sdk/pom.xml @@ -0,0 +1,70 @@ + + + + deploy-root + com.jd.blockchain + 1.4.0-SNAPSHOT + + 4.0.0 + + docker-sdk + + + 1.3.0.RELEASE + + + + + com.jd.blockchain + crypto-classic + ${ledger.version} + + + com.jd.blockchain + crypto-sm + ${ledger.version} + + + com.jd.blockchain + ledger-model + ${ledger.version} + + + com.jd.blockchain + sdk-client + ${ledger.version} + + + + + + + maven-assembly-plugin + + false + + jar-with-dependencies + + + + + com.jd.blockchain.SDKDemo + + + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/deploy/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java b/deploy/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java new file mode 100644 index 00000000..b1464b4e --- /dev/null +++ b/deploy/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java @@ -0,0 +1,107 @@ +package com.jd.blockchain; + +import com.jd.blockchain.ledger.BlockchainIdentity; +import com.jd.blockchain.ledger.BlockchainKeypair; + +public class ContractParams { + String contractZipName; + BlockchainKeypair signAdminKey; + BlockchainIdentity contractIdentity; + boolean isDeploy; + boolean isExecute; + boolean hasVersion; //contract's version; + long version; + BlockchainIdentity dataAccount; + String key; + String value; + + public String getContractZipName() { + return contractZipName; + } + + public ContractParams setContractZipName(String contractZipName) { + this.contractZipName = contractZipName; + return this; + } + + public BlockchainKeypair getSignAdminKey() { + return signAdminKey; + } + + public ContractParams setSignAdminKey(BlockchainKeypair signAdminKey) { + this.signAdminKey = signAdminKey; + return this; + } + + public BlockchainIdentity getContractIdentity() { + return contractIdentity; + } + + public ContractParams setContractIdentity(BlockchainIdentity contractIdentity) { + this.contractIdentity = contractIdentity; + return this; + } + + public boolean isDeploy() { + return isDeploy; + } + + public ContractParams setDeploy(boolean deploy) { + isDeploy = deploy; + return this; + } + + public boolean isExecute() { + return isExecute; + } + + public ContractParams setExecute(boolean execute) { + isExecute = execute; + return this; + } + + public boolean isHasVersion() { + return hasVersion; + } + + public ContractParams setHasVersion(boolean hasVersion) { + this.hasVersion = hasVersion; + return this; + } + + public long getVersion() { + return version; + } + + public ContractParams setVersion(long version) { + this.version = version; + return this; + } + + public BlockchainIdentity getDataAccount() { + return dataAccount; + } + + public ContractParams setDataAccount(BlockchainIdentity dataAccount) { + this.dataAccount = dataAccount; + return this; + } + + public String getKey() { + return key; + } + + public ContractParams setKey(String key) { + this.key = key; + return this; + } + + public String getValue() { + return value; + } + + public ContractParams setValue(String value) { + this.value = value; + return this; + } +} diff --git a/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java b/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java new file mode 100644 index 00000000..5e02ccf1 --- /dev/null +++ b/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java @@ -0,0 +1,68 @@ +package com.jd.blockchain; + +import com.jd.blockchain.ledger.*; +import org.apache.commons.codec.binary.Base64; + +import java.util.Random; +import java.util.UUID; + +public class SDKDemo extends SDK_Base_Demo{ + public static void main(String[] args) { + SDKDemo sdkDemo = new SDKDemo(); + //注册用户; + sdkDemo.registerUsers(); + //构建数据账户; + sdkDemo.genDataAccount(); + //发布和执行合约; + sdkDemo.deployContract(); + } + + //注册用户; + public void registerUsers(){ + this.registerUser(); + } + + //构建数据账户; + public void genDataAccount(){ + byte[] arr = new byte[1024]; + new Random().nextBytes(arr); + String value = Base64.encodeBase64String(arr); + this.insertData(null,null,"key1",value,-1); + } + + public BlockchainKeypair insertData(BlockchainKeypair dataAccount, BlockchainKeypair signAdminKey, + String key, String value, long version) { + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = blockchainService.newTransaction(ledgerHash); + //采用KeyGenerator来生成BlockchainKeypair; + if(dataAccount == null){ + dataAccount = BlockchainKeyGenerator.getInstance().generate(); + txTemp.dataAccounts().register(dataAccount.getIdentity()); + } + + System.out.println("current dataAccount=" + dataAccount.getAddress()); + txTemp.dataAccount(dataAccount.getAddress()).setText(key, value, version); + txTemp.dataAccount(dataAccount.getAddress()).setTimestamp(UUID.randomUUID().toString(),System.currentTimeMillis(),-1); + + // TX 准备就绪 + commit(txTemp,signAdminKey); + + //get the version + TypedKVEntry[] kvData = blockchainService.getDataEntries(ledgerHash, + dataAccount.getAddress().toBase58(), key); + System.out.println(String.format("key1 info:key=%s,value=%s,version=%d", + kvData[0].getKey(),kvData[0].getValue().toString(),kvData[0].getVersion())); + + return dataAccount; + } + + public void deployContract(){ + ContractParams contractParams = new ContractParams(); + contractParams.setContractZipName("contract-compile-1.3.0.RELEASE.car").setDeploy(true).setExecute(false); + BlockchainIdentity contractAddress = + this.contractHandle(contractParams); + contractParams.setContractIdentity(contractAddress); + this.contractHandle(contractParams); + this.contractHandle(contractParams.setExecute(true)); + } +} diff --git a/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java b/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java new file mode 100644 index 00000000..ec7ec7c2 --- /dev/null +++ b/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java @@ -0,0 +1,46 @@ +package com.jd.blockchain; + +import com.jd.blockchain.crypto.KeyGenUtils; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainKeypair; +import org.apache.commons.io.FileUtils; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; +import java.io.InputStream; + +public class SDKDemo_Constant { + + public static String GW_IPADDR = "localhost"; + public static int GW_PORT = 8080; + public static String GW_PUB_KEY = "3snPdw7i7PisoLpqqtETdqzQeKVjQReP2Eid9wYK67q9z6trvByGZs"; + public static String GW_PRIV_KEY = "177gk2PbxhHeEdfAAqGfShJQyeV4XvGsJ9CvJFUbToBqwW1YJd5obicySE1St6SvPPaRrUP"; + public static String GW_PASSWORD = "8EjkXVSTxMFjCvNNsTo8RBMDEVQmk7gYkW4SCDuvdsBG"; + + public static PrivKey gwPrivkey0 = KeyGenUtils.decodePrivKey(GW_PRIV_KEY, GW_PASSWORD); + public static PubKey gwPubKey0 = KeyGenUtils.decodePubKey(GW_PUB_KEY); + public static BlockchainKeypair adminKey = new BlockchainKeypair(gwPubKey0, gwPrivkey0); + + public static final byte[] readChainCodes(String contractZip) { + // 构建合约的字节数组; + try { + ClassPathResource contractPath = new ClassPathResource(contractZip); +// File contractFile = new File(contractPath.getURI()); + + InputStream in = contractPath.getInputStream(); + // 将文件写入至config目录下 + File directory = new File("."); + String configPath = directory.getAbsolutePath() + File.separator + "contract.jar"; + File targetFile = new File(configPath); + // 先将原来文件删除再Copy + if (targetFile.exists()) { + FileUtils.forceDelete(targetFile); + } + FileUtils.copyInputStreamToFile(in, targetFile); + return FileUtils.readFileToByteArray(targetFile); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } +} diff --git a/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java b/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java new file mode 100644 index 00000000..51cc1741 --- /dev/null +++ b/deploy/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java @@ -0,0 +1,191 @@ +package com.jd.blockchain; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.sdk.BlockchainService; +import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import com.jd.blockchain.transaction.GenericValueHolder; +import com.jd.blockchain.utils.Bytes; +import com.jd.chain.contract.TransferContract; + +import static com.jd.blockchain.SDKDemo_Constant.readChainCodes; +import static com.jd.blockchain.transaction.ContractReturnValue.decode; + +public abstract class SDK_Base_Demo { + protected BlockchainKeypair adminKey; + + protected HashDigest ledgerHash; + + protected BlockchainService blockchainService; + + public SDK_Base_Demo() { + init(); + } + + public void init() { + // 生成连接网关的账号 + adminKey = SDKDemo_Constant.adminKey; + + // 连接网关 + GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(SDKDemo_Constant.GW_IPADDR, + SDKDemo_Constant.GW_PORT, false, adminKey); + + // 获取网关对应的Service处理类 + blockchainService = serviceFactory.getBlockchainService(); + + HashDigest[] ledgerHashs = blockchainService.getLedgerHashs(); + // 获取当前账本Hash + ledgerHash = ledgerHashs[0]; + } + + public TransactionResponse commit(TransactionTemplate txTpl){ + return this.commitA(txTpl,null); + } + + /** + * 默认使用A方式commit; + * @param txTpl + * @param signAdminKey + * @return + */ + public TransactionResponse commit(TransactionTemplate txTpl, BlockchainKeypair signAdminKey){ + return commitA(txTpl, signAdminKey); + } + + /** + * 采用A方式提交; + * @param txTpl + * @param signAdminKey + * @return + */ + public TransactionResponse commitA(TransactionTemplate txTpl, BlockchainKeypair signAdminKey) { + PreparedTransaction ptx = txTpl.prepare(); + + if(signAdminKey != null){ + System.out.println("signAdminKey's pubKey = "+signAdminKey.getIdentity().getPubKey()); + ptx.sign(signAdminKey); + }else { + System.out.println("adminKey's pubKey = "+adminKey.getIdentity().getPubKey()); + ptx.sign(adminKey); + } + TransactionResponse transactionResponse = ptx.commit(); + + if (transactionResponse.isSuccess()) { + System.out.println(String.format("height=%d, ###OK#, contentHash=%s, executionState=%s", + transactionResponse.getBlockHeight(), + transactionResponse.getContentHash(), transactionResponse.getExecutionState().toString())); + } else { + System.out.println(String.format("height=%d, ###exception#, contentHash=%s, executionState=%s", + transactionResponse.getBlockHeight(), + transactionResponse.getContentHash(), transactionResponse.getExecutionState().toString())); + } + return transactionResponse; + } + + /** + * 生成一个区块链用户,并注册到区块链; + */ + public BlockchainKeypair registerUser() { + return this.registerUser(null,null,null); + } + + public BlockchainKeypair registerUser(String cryptoType, BlockchainKeypair signAdminKey, BlockchainKeypair userKeypair) { + // 在本地定义注册账号的 TX; + TransactionTemplate txTemp = blockchainService.newTransaction(ledgerHash); + if(userKeypair == null){ + if("SM2".equals(cryptoType)){ + userKeypair = BlockchainKeyGenerator.getInstance().generate(cryptoType); + }else { + userKeypair = BlockchainKeyGenerator.getInstance().generate(); + } + } + System.out.println("user'address="+userKeypair.getAddress()); + txTemp.users().register(userKeypair.getIdentity()); + // TX 准备就绪; + commit(txTemp,signAdminKey); + return userKeypair; + } + + public BlockchainKeypair registerUser(BlockchainKeypair signAdminKey, BlockchainKeypair userKeypair) { + return registerUser(null,signAdminKey,userKeypair); + } + + /** + * 生成一个区块链用户,并注册到区块链; + */ + public BlockchainKeypair registerUserByNewSigner(BlockchainKeypair signer) { + return this.registerUser(signer,null); + } + + public BlockchainIdentity createDataAccount() { + // 首先注册一个数据账户 + BlockchainKeypair newDataAccount = BlockchainKeyGenerator.getInstance().generate(); + + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + txTpl.dataAccounts().register(newDataAccount.getIdentity()); + commit(txTpl); + return newDataAccount.getIdentity(); + } + + public String create1(Bytes contractAddress, String address, String account, String content) { + System.out.println(String.format("params,String address=%s, String account=%s, String content=%s, Bytes contractAddress=%s", + address,account,content,contractAddress.toBase58())); + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + // 使用合约创建 + TransferContract guanghu = txTpl.contract(contractAddress, TransferContract.class); + GenericValueHolder result = decode(guanghu.putval(address, account, content, System.currentTimeMillis())); + commit(txTpl); + return result.get(); + } + + public BlockchainIdentity contractHandle(ContractParams contractParams) { + if(contractParams.getContractZipName() == null){ + contractParams.setContractZipName("contract-JDChain-Contract.jar"); + } + // 发布jar包 + // 定义交易模板 + TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); + Bytes contractAddress = null; + if(contractParams.getContractIdentity() != null){ + contractAddress = contractParams.getContractIdentity().getAddress(); + } + + if(contractParams.isDeploy){ + // 将jar包转换为二进制数据 + byte[] contractCode = readChainCodes(contractParams.getContractZipName()); + + // 生成一个合约账号 + if(contractParams.getContractIdentity() == null){ + contractParams.setContractIdentity(BlockchainKeyGenerator.getInstance().generate().getIdentity()); + } + contractAddress = contractParams.getContractIdentity().getAddress(); + System.out.println("contract's address=" + contractAddress); + + // 生成发布合约操作 + if(contractParams.isHasVersion()){ +// txTpl.contracts().deploy(contractParams.contractIdentity, contractCode, contractParams.version); + } else { + txTpl.contracts().deploy(contractParams.contractIdentity, contractCode); + } + + + // 生成预发布交易; + commit(txTpl,contractParams.getSignAdminKey()); + } + + if(contractParams.isExecute){ + // 注册一个数据账户 + if(contractParams.dataAccount == null){ + contractParams.dataAccount = createDataAccount(); + contractParams.key = "jd_zhangsan"; + contractParams.value = "{\"dest\":\"KA006\",\"id\":\"cc-fin08-01\",\"items\":\"FIN001|3030\",\"source\":\"FIN001\"}"; + } + // 获取数据账户地址x + String dataAddress = contractParams.dataAccount.getAddress().toBase58(); + // 打印数据账户地址 + System.out.printf("DataAccountAddress = %s \r\n", dataAddress); + System.out.println("return value = "+create1(contractAddress, dataAddress, contractParams.key, contractParams.value)); + } + return contractParams.contractIdentity; + } +} diff --git a/deploy/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java b/deploy/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java new file mode 100644 index 00000000..a3a41529 --- /dev/null +++ b/deploy/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java @@ -0,0 +1,32 @@ +package com.jd.chain.contract; + +import com.jd.blockchain.contract.Contract; +import com.jd.blockchain.contract.ContractEvent; + +@Contract +public interface TransferContract { + + @ContractEvent(name = "create") + String create(String address, String account, long money); + + @ContractEvent(name = "transfer") + String transfer(String address, String from, String to, long money); + + @ContractEvent(name = "read") + long read(String address, String account); + + @ContractEvent(name = "readAll") + String readAll(String address, String account); + + @ContractEvent(name = "putval1") + String putval(String address, String account, String content, Long time); + + @ContractEvent(name = "putvalBif") + String putvalBifurcation(String address, String account, String content, String isHalf); + + @ContractEvent(name = "getTxSigners") + String getTxSigners(String input); + + @ContractEvent(name = "test") + String test(String input); +} diff --git a/deploy/docker-sdk/src/main/resources/contract-compile-1.3.0.RELEASE.car b/deploy/docker-sdk/src/main/resources/contract-compile-1.3.0.RELEASE.car new file mode 100644 index 0000000000000000000000000000000000000000..cd7c3c825ef6c2d305a0a7e3c1ea974825205a86 GIT binary patch literal 5129 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