From f96305a512bfabaeb3383b5b28f17a7bed3ca626 Mon Sep 17 00:00:00 2001 From: Kyon <32325790+kyonRay@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:44:11 +0800 Subject: [PATCH] (codec): add encode and decode by contract definition interfaces. (#935) --- build.gradle | 2 +- .../v3/test/demo/AssembleTransactionDemo.java | 193 ++++++++++++++++++ .../bcos/sdk/v3/codec/ContractCodec.java | 166 +++++++++++++++ 3 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 src/integration-test/java/org/fisco/bcos/sdk/v3/test/demo/AssembleTransactionDemo.java diff --git a/build.gradle b/build.gradle index 1d1cb98fe..2fae00bcc 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ ext { // integrationTest.mustRunAfter test allprojects { group = 'org.fisco-bcos.java-sdk' - version = '3.7.0' + version = '3.7.1' apply plugin: 'maven-publish' apply plugin: 'idea' diff --git a/src/integration-test/java/org/fisco/bcos/sdk/v3/test/demo/AssembleTransactionDemo.java b/src/integration-test/java/org/fisco/bcos/sdk/v3/test/demo/AssembleTransactionDemo.java new file mode 100644 index 000000000..69969cc9f --- /dev/null +++ b/src/integration-test/java/org/fisco/bcos/sdk/v3/test/demo/AssembleTransactionDemo.java @@ -0,0 +1,193 @@ +package org.fisco.bcos.sdk.v3.test.demo; + +import java.io.ByteArrayOutputStream; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.fisco.bcos.sdk.jni.common.JniException; +import org.fisco.bcos.sdk.jni.utilities.tx.TransactionData; +import org.fisco.bcos.sdk.jni.utilities.tx.TransactionDataV1; +import org.fisco.bcos.sdk.jni.utilities.tx.TransactionDataV2; +import org.fisco.bcos.sdk.jni.utilities.tx.TransactionStructBuilderJniObj; +import org.fisco.bcos.sdk.jni.utilities.tx.TransactionVersion; +import org.fisco.bcos.sdk.v3.BcosSDK; +import org.fisco.bcos.sdk.v3.client.Client; +import org.fisco.bcos.sdk.v3.codec.ContractCodec; +import org.fisco.bcos.sdk.v3.codec.ContractCodecException; +import org.fisco.bcos.sdk.v3.codec.wrapper.ABIDefinition; +import org.fisco.bcos.sdk.v3.codec.wrapper.ABIDefinitionFactory; +import org.fisco.bcos.sdk.v3.codec.wrapper.ABIObject; +import org.fisco.bcos.sdk.v3.codec.wrapper.ABIObjectFactory; +import org.fisco.bcos.sdk.v3.codec.wrapper.ContractABIDefinition; +import org.fisco.bcos.sdk.v3.codec.wrapper.ContractCodecJsonWrapper; +import org.fisco.bcos.sdk.v3.codec.wrapper.ContractCodecTools; +import org.fisco.bcos.sdk.v3.crypto.CryptoSuite; +import org.fisco.bcos.sdk.v3.crypto.hash.Keccak256; +import org.fisco.bcos.sdk.v3.crypto.signature.SignatureResult; +import org.fisco.bcos.sdk.v3.model.ConstantConfig; +import org.fisco.bcos.sdk.v3.model.CryptoType; +import org.fisco.bcos.sdk.v3.model.TransactionReceipt; +import org.fisco.bcos.sdk.v3.test.transaction.mock.RemoteSignProviderMock; +import org.fisco.bcos.sdk.v3.transaction.manager.transactionv1.dto.TransactionRequestWithStringParams; +import org.fisco.bcos.sdk.v3.transaction.manager.transactionv1.utils.TransactionRequestBuilder; +import org.fisco.bcos.sdk.v3.transaction.model.exception.ContractException; +import org.fisco.bcos.sdk.v3.transaction.signer.RemoteSignProviderInterface; +import org.fisco.bcos.sdk.v3.utils.Hex; +import org.fisco.bcos.sdk.v3.utils.Numeric; + +class AssembleTransaction { + private String abi; + private TransactionRequestWithStringParams transactionRequest; + private ABIDefinitionFactory abiDefinitionFactory; + private ContractCodec contractCodec; + + public AssembleTransaction(TransactionRequestWithStringParams transactionRequest) { + this.transactionRequest = transactionRequest; + this.abi = transactionRequest.getAbi(); + this.abiDefinitionFactory = new ABIDefinitionFactory(new Keccak256()); + contractCodec = new ContractCodec(new Keccak256(), false); + } + + public AssembleTransaction(String abi, TransactionRequestWithStringParams transactionRequest) { + this(transactionRequest); + this.abi = abi; + } + + public Pair assembleTransaction() + throws JniException, ContractCodecException { + + /// 如果abi固定,可以在初始化时就加载好 + ContractABIDefinition contractABIDefinition = abiDefinitionFactory.loadABI(abi); + ContractCodecJsonWrapper contractCodecJsonWrapper = new ContractCodecJsonWrapper(); + List abiDefinitions = + contractABIDefinition.getFunctions().get(transactionRequest.getMethod()); + if (abiDefinitions == null) { + throw new RuntimeException("Method not found in ABI"); + } + ABIDefinition abiDefinition = abiDefinitions.get(0); + + /// 下面的代码是替换掉了原来的encodeMethodFromString + // byte[] encode = contractCodec.encodeMethodFromString(abi, transactionRequest.getMethod(), + // transactionRequest.getStringParams()); + ABIObject inputABIObject = ABIObjectFactory.createInputObject(abiDefinition); + byte[] encode = null; + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(abiDefinition.getMethodId(new Keccak256())); + outputStream.write( + ContractCodecTools.encode( + contractCodecJsonWrapper.encode( + inputABIObject, transactionRequest.getStringParams()), + false)); + encode = outputStream.toByteArray(); + } catch (Exception e) { + throw new ContractCodecException("encode input error"); + } + + TransactionData transactionData = + new TransactionData() + .buildVersion(transactionRequest.getVersion().getValue()) + .buildGroupId("group0") + .buildChainId("chain0") + .buildTo(transactionRequest.getTo()) + .buildNonce(transactionRequest.getNonce()) + .buildInput(encode) + .buildAbi(transactionRequest.getAbi()) + .buildBlockLimit(transactionRequest.getBlockLimit().longValue()); + if (transactionRequest.getVersion().getValue() >= TransactionVersion.V1.getValue()) { + transactionData = + new TransactionDataV1(transactionData) + .buildGasLimit(0) + .buildGasPrice("") + .buildValue(Numeric.toHexString(transactionRequest.getValue())) + .buildMaxFeePerGas("") + .buildMaxPriorityFeePerGas(""); + } + if (transactionRequest.getVersion().getValue() >= TransactionVersion.V2.getValue()) { + transactionData = + new TransactionDataV2((TransactionDataV1) transactionData) + .buildExtension(transactionRequest.getExtension()); + } + + String transactionDataHash = + TransactionStructBuilderJniObj.calcTransactionDataStructHash( + CryptoType.ECDSA_TYPE, transactionData); + + return new MutablePair<>(transactionDataHash, transactionData); + } + + public String assembleSignedTx(String signature, String txHash, TransactionData txData) + throws JniException { + return TransactionStructBuilderJniObj.createEncodedTransaction( + txData, signature, txHash, 0, "extraData"); + } +} + +class SignTransaction { + private final RemoteSignProviderInterface remoteSignProviderMock; + + public SignTransaction() { + remoteSignProviderMock = new RemoteSignProviderMock(new CryptoSuite(CryptoType.ECDSA_TYPE)); + } + + public String sign(String hash) { + SignatureResult signatureResult = + remoteSignProviderMock.requestForSign(Hex.decode(hash), CryptoType.ECDSA_TYPE); + return Hex.toHexString(signatureResult.encode()); + } +} + +class SendTransaction { + private Client client; + + public SendTransaction(Client client) { + this.client = client; + } + + public TransactionReceipt sendTx(String signedTx) { + return client.sendTransaction(signedTx, false).getTransactionReceipt(); + } +} + +public class AssembleTransactionDemo { + public static BcosSDK sdk; + public static Client client; + private static final String configFile = + "src/integration-test/resources/" + ConstantConfig.CONFIG_FILE_NAME; + + public static void main(String[] args) + throws ContractException, JniException, ContractCodecException { + sdk = BcosSDK.build(configFile); + client = sdk.getClient("group0"); + + HelloWorld deployed = HelloWorld.deploy(client, client.getCryptoSuite().getCryptoKeyPair()); + + TransactionRequestBuilder requestBuilder = new TransactionRequestBuilder(); + TransactionRequestWithStringParams set = + requestBuilder + .setAbi(HelloWorld.getABI()) + .setBlockLimit(client.getBlockLimit()) + .setNonce(UUID.randomUUID().toString().replace("-", "")) + .setTo(deployed.getContractAddress()) + .setMethod("set") + .buildStringParamsRequest(Collections.singletonList("Hello, World!")); + AssembleTransaction assembleTransaction = new AssembleTransaction(HelloWorld.getABI(), set); + Pair hashTransactionDataPair = + assembleTransaction.assembleTransaction(); + + SignTransaction signTransaction = new SignTransaction(); + String sign = signTransaction.sign(hashTransactionDataPair.getLeft()); + + String signedTx = + assembleTransaction.assembleSignedTx( + sign, + hashTransactionDataPair.getLeft(), + hashTransactionDataPair.getRight()); + + SendTransaction sendTransaction = new SendTransaction(client); + TransactionReceipt transactionReceipt = sendTransaction.sendTx(signedTx); + System.out.println(transactionReceipt); + } +} diff --git a/src/main/java/org/fisco/bcos/sdk/v3/codec/ContractCodec.java b/src/main/java/org/fisco/bcos/sdk/v3/codec/ContractCodec.java index 03073a956..21abcd3b9 100644 --- a/src/main/java/org/fisco/bcos/sdk/v3/codec/ContractCodec.java +++ b/src/main/java/org/fisco/bcos/sdk/v3/codec/ContractCodec.java @@ -537,6 +537,68 @@ public byte[] encodeMethodFromString(String abi, String methodName, List throw new ContractCodecException(errorMsg); } + public byte[] encodeMethodFromStringByContractABIDefinition( + ContractABIDefinition contractABIDefinition, String methodName, List params) + throws ContractCodecException { + List methods = contractABIDefinition.getFunctions().get(methodName); + if (methods == null) { + logger.debug( + "Invalid methodName: {}, all the functions are: {}", + methodName, + contractABIDefinition.getFunctions()); + throw new ContractCodecException( + "Invalid method " + + methodName + + " , supported functions are: " + + contractABIDefinition.getFunctions().keySet()); + } + + for (ABIDefinition abiDefinition : methods) { + if (abiDefinition.getInputs().size() == params.size()) { + ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + byte[] methodId = abiDefinition.getMethodId(cryptoSuite); + ABIObject abiObject = contractCodecJsonWrapper.encode(inputObject, params); + byte[] encode = abiObject.encode(isWasm); + outputStream.write(methodId); + outputStream.write(encode); + return outputStream.toByteArray(); + } catch (Exception e) { + logger.error(" exception in encodeMethodFromString : {}", e.getMessage()); + } + } + } + + String errorMsg = + " cannot encode in encodeMethodFromString with appropriate interface ABI, make sure params match"; + logger.error(errorMsg); + throw new ContractCodecException(errorMsg); + } + + public byte[] encodeMethodFromStringByABIDefinition( + ABIDefinition abiDefinition, List params) throws ContractCodecException { + if (abiDefinition.getInputs().size() == params.size()) { + ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + byte[] methodId = abiDefinition.getMethodId(cryptoSuite); + ABIObject abiObject = contractCodecJsonWrapper.encode(inputObject, params); + byte[] encode = abiObject.encode(isWasm); + outputStream.write(methodId); + outputStream.write(encode); + return outputStream.toByteArray(); + } catch (Exception e) { + logger.error( + " exception in encodeMethodFromStringByAbiDefinition : {}", e.getMessage()); + } + } + String errorMsg = + " cannot encode in encodeMethodFromStringByAbiDefinition with appropriate interface ABI, make sure params match"; + logger.error(errorMsg); + throw new ContractCodecException(errorMsg); + } + public byte[] encodeMethodByIdFromString(String abi, byte[] methodId, List params) throws ContractCodecException { ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi); @@ -1029,6 +1091,57 @@ public List decodeEvent(String abi, String eventName, EventLog log) throw new ContractCodecException(errorMsg); } + public List decodeEventByContractABIDefinition( + ContractABIDefinition contractABIDefinition, String eventName, EventLog log) + throws ContractCodecException { + List events = contractABIDefinition.getEvents().get(eventName); + if (events == null) { + throw new ContractCodecException( + "Invalid event " + + eventName + + ", supported events are: " + + contractABIDefinition.getEvents().keySet()); + } + for (ABIDefinition abiDefinition : events) { + ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition); + try { + List params = new ArrayList<>(); + if (!log.getData().equals("0x")) { + params = + ContractCodecTools.decodeJavaObject(inputObject, log.getData(), isWasm); + } + List topics = log.getTopics(); + return this.mergeEventParamsAndTopics(abiDefinition, params, topics); + } catch (Exception e) { + logger.error(" exception in decodeEventToObject : {}", e.getMessage()); + } + } + + String errorMsg = " cannot decode in decodeEventToObject with appropriate interface ABI"; + logger.error(errorMsg); + throw new ContractCodecException(errorMsg); + } + + public List decodeEventByAbiDefinition(ABIDefinition abiDefinition, EventLog log) + throws ContractCodecException { + ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition); + try { + List params = new ArrayList<>(); + if (!log.getData().equals("0x")) { + params = ContractCodecTools.decodeJavaObject(inputObject, log.getData(), isWasm); + } + List topics = log.getTopics(); + return this.mergeEventParamsAndTopics(abiDefinition, params, topics); + } catch (Exception e) { + logger.error(" exception in decodeEventByAbiDefinition : {}", e.getMessage()); + } + + String errorMsg = + " cannot decode in decodeEventByAbiDefinition with appropriate interface ABI"; + logger.error(errorMsg); + throw new ContractCodecException(errorMsg); + } + public List decodeEventByTopic(String abi, String eventTopic, EventLog log) throws ContractCodecException { ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi); @@ -1090,6 +1203,59 @@ public List decodeEventToString(String abi, String eventName, EventLog l throw new ContractCodecException(errorMsg); } + public List decodeEventToStringByContractABIDefinition( + ContractABIDefinition contractABIDefinition, String eventName, EventLog log) + throws ContractCodecException { + List events = contractABIDefinition.getEvents().get(eventName); + if (events == null) { + throw new ContractCodecException( + "Invalid event " + + eventName + + ", current supported events are: " + + contractABIDefinition.getEvents().keySet()); + } + for (ABIDefinition abiDefinition : events) { + ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition); + try { + List params = new ArrayList<>(); + if (!log.getData().equals("0x")) { + params = + contractCodecJsonWrapper.decode( + inputObject, Hex.decode(log.getData()), isWasm); + } + List topics = log.getTopics(); + return this.mergeEventParamsAndTopicsToString(abiDefinition, params, topics); + } catch (Exception e) { + logger.error(" exception in decodeEventToString : {}", e.getMessage()); + } + } + + String errorMsg = " cannot decode in decodeEventToString with appropriate interface ABI"; + logger.error(errorMsg); + throw new ContractCodecException(errorMsg); + } + + public List decodeEventToStringByABIDefinition( + ABIDefinition abiDefinition, EventLog log) throws ContractCodecException { + ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition); + try { + List params = new ArrayList<>(); + if (!log.getData().equals("0x")) { + params = + contractCodecJsonWrapper.decode( + inputObject, Hex.decode(log.getData()), isWasm); + } + List topics = log.getTopics(); + return this.mergeEventParamsAndTopicsToString(abiDefinition, params, topics); + } catch (Exception e) { + logger.error(" exception in decodeEventToString : {}", e.getMessage()); + } + + String errorMsg = " cannot decode in decodeEventToString with appropriate interface ABI"; + logger.error(errorMsg); + throw new ContractCodecException(errorMsg); + } + public List decodeEventByTopicToString(String abi, String eventTopic, EventLog log) throws ContractCodecException { ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);