Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<fix>(codec): add encode and decode by contract definition interfaces. #935

Merged
merged 1 commit into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, TransactionData> assembleTransaction()
throws JniException, ContractCodecException {

/// 如果abi固定,可以在初始化时就加载好
ContractABIDefinition contractABIDefinition = abiDefinitionFactory.loadABI(abi);
ContractCodecJsonWrapper contractCodecJsonWrapper = new ContractCodecJsonWrapper();
List<ABIDefinition> 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<String, TransactionData> 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);
}
}
166 changes: 166 additions & 0 deletions src/main/java/org/fisco/bcos/sdk/v3/codec/ContractCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,68 @@ public byte[] encodeMethodFromString(String abi, String methodName, List<String>
throw new ContractCodecException(errorMsg);
}

public byte[] encodeMethodFromStringByContractABIDefinition(
ContractABIDefinition contractABIDefinition, String methodName, List<String> params)
throws ContractCodecException {
List<ABIDefinition> 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<String> 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<String> params)
throws ContractCodecException {
ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
Expand Down Expand Up @@ -1029,6 +1091,57 @@ public List<Object> decodeEvent(String abi, String eventName, EventLog log)
throw new ContractCodecException(errorMsg);
}

public List<Object> decodeEventByContractABIDefinition(
ContractABIDefinition contractABIDefinition, String eventName, EventLog log)
throws ContractCodecException {
List<ABIDefinition> 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<Object> params = new ArrayList<>();
if (!log.getData().equals("0x")) {
params =
ContractCodecTools.decodeJavaObject(inputObject, log.getData(), isWasm);
}
List<String> 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<Object> decodeEventByAbiDefinition(ABIDefinition abiDefinition, EventLog log)
throws ContractCodecException {
ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition);
try {
List<Object> params = new ArrayList<>();
if (!log.getData().equals("0x")) {
params = ContractCodecTools.decodeJavaObject(inputObject, log.getData(), isWasm);
}
List<String> 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<Object> decodeEventByTopic(String abi, String eventTopic, EventLog log)
throws ContractCodecException {
ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
Expand Down Expand Up @@ -1090,6 +1203,59 @@ public List<String> decodeEventToString(String abi, String eventName, EventLog l
throw new ContractCodecException(errorMsg);
}

public List<String> decodeEventToStringByContractABIDefinition(
ContractABIDefinition contractABIDefinition, String eventName, EventLog log)
throws ContractCodecException {
List<ABIDefinition> 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<String> params = new ArrayList<>();
if (!log.getData().equals("0x")) {
params =
contractCodecJsonWrapper.decode(
inputObject, Hex.decode(log.getData()), isWasm);
}
List<String> 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<String> decodeEventToStringByABIDefinition(
ABIDefinition abiDefinition, EventLog log) throws ContractCodecException {
ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition);
try {
List<String> params = new ArrayList<>();
if (!log.getData().equals("0x")) {
params =
contractCodecJsonWrapper.decode(
inputObject, Hex.decode(log.getData()), isWasm);
}
List<String> 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<String> decodeEventByTopicToString(String abi, String eventTopic, EventLog log)
throws ContractCodecException {
ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
Expand Down
Loading