使用 Web3j 在 Java 中呼叫智能合約函式的問題
我對用 Java 編寫/使用智能合約相當陌生,我目前正在嘗試使用我的智能合約。我已經編寫並測試了它,編譯它,將它部署到 Ropsten,並使用 web3j-cli 從它建構了我的 Java 類。到目前為止,沒有任何問題。
現在,我的合約有一個函式
run(string memory x)
,如果我傳遞了參數“test”,它將返回字元串“ok”,如果傳遞了任何其他字元串,則返回“not ok”。暫時僅用於測試/鍛煉目的。現在我在我的 Java 程式碼中所做的是建構一個 Web3j 客戶端並通過我的 Infura 帳戶連接到 Ethereum/Ropsten。
try { HttpService httpService = new HttpService(INFURA_ENDPOINT); // Infura endpoint for ropsten String auth = new String(":" + "yyyxxxzzz"); // Infura secret byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); String authHeader = "Basic " + new String(encodedAuth); httpService.addHeader(HttpHeaders.AUTHORIZATION, authHeader); Web3j web3j = Web3j.build(new HttpService(INFURA_ENDPOINT)); String privateKeyString = "xyz"; // private key as exported from Metamask for my account in ropsten String address = "0x8e329a83B96fc80cc00C0acd5efbf78699b2F299"; // address for said account Credentials credentials = Credentials.create(privateKeyString); String contractAddress = "0xabcdef"; // address of the deployed contract in ropsten final BigInteger gasPrice = BigInteger.valueOf(2205000); final BigInteger gasLimit = BigInteger.valueOf(14300000); final ContractGasProvider gasProvider = new StaticGasProvider(gasPrice, gasLimit); final Test contract = Test.load(contractAddress, web3j, credentials, gasProvider); String a = contract.run("xyz").send(); System.out.println("run(xyz): " + a); } catch(Exception e) { System.out.println("Web3j: " + e.getMessage() + "\n"); e.printStackTrace(); }
現在,在我生成的合約 Java 類中顯式呼叫該函式
run()
如下所示:public RemoteFunctionCall<String> run(String name) { final Function function = new Function(FUNC_RUN, Arrays.<Type>asList(new org.web3j.abi.datatypes.Utf8String(name)), Arrays.<TypeReference<?>>asList(new TypeReference<Utf8String>() {})); return executeRemoteCallSingleValueReturn(function, String.class); }
編輯:這是智能合約的 Solidity 程式碼:
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract Test { function run(string memory name) public pure returns (string memory) { if(strcmp(name, "test")) { return "ok"; } return "not ok"; } function strcmp(string memory a, string memory b) internal pure returns (bool) { if(bytes(a).length != bytes(b).length) { return false; } else { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); } } }
在 remix.ethereum 中按預期工作。該合約已部署在 Ropsten,位於
0x8e329a83B96fc80cc00C0acd5efbf78699b2F299
現在,當我執行它時,諸如此類的呼叫
contract.run("xyz")
執行沒有問題,並且 Infura 也收到了 - 因為我可以在我的儀表板中輕鬆檢查和驗證。然而,只要我附加一個send()
(與sendAsync().get()
很多相同),我就會得到以下異常:Exception in thread "main" java.lang.NoSuchMethodError: 'okhttp3.RequestBody okhttp3.RequestBody.create(java.lang.String, okhttp3.MediaType)' at org.web3j.protocol.http.HttpService.performIO(HttpService.java:154) at org.web3j.protocol.Service.send(Service.java:48) at org.web3j.protocol.core.Request.send(Request.java:87) at org.web3j.tx.RawTransactionManager.sendCall(RawTransactionManager.java:155) at org.web3j.tx.ManagedTransaction.call(ManagedTransaction.java:134) at org.web3j.tx.Contract.executeCall(Contract.java:292) at org.web3j.tx.Contract.executeCallSingleValueReturn(Contract.java:300) at org.web3j.tx.Contract.executeCallSingleValueReturn(Contract.java:311) at org.web3j.tx.Contract.lambda$executeRemoteCallSingleValueReturn$1(Contract.java:399) at org.web3j.protocol.core.RemoteCall.send(RemoteCall.java:42) at com.example.web3j.TestWeb3j.main(TestWeb3j.java:56)
我主要關注教程https://dzone.com/articles/blockchain-simplified-with-ethereum-example-with-j和https://trimplement.com/blog/2020/03/coding-smart-contracts -tutorial-infura/在這裡。據我了解,a
send()
應該呼叫合約的函式並讓我返回返回的字元串。我已經在 Infura-board 上問過這個問題,但沒有成功(https://community.infura.io/t/calling-functions-on-smart-contract-fails/2401)。我究竟做錯了什麼?
編輯:或者以不同的方式問:任何人都可以為我提供一個最小的工作範例,說明如何通過 Infura 在 Ropsten 上使用 Java 15 上的 Web3j 呼叫智能合約中的函式(理想情況下,這個智能合約中的函式)?
我在 Java 15 上的 Web3j 4.8.2 上的 SpringBoot 2.4.1 中執行所有這些
編輯:IntelliJ IDEA 2020.3.2 上的 SpringBoot 2.4.3/Web3j 4.8.4:同樣的問題
編輯 2:不幸的是,提議的解決方案不起作用。這是整個項目的程式碼:
@SpringBootApplication public class SpringBootWeb3j { private final static String INFURA_ENDPOINT = "https://ropsten.infura.io/v3/"; private final static String INFURA_PROJECT_ID = "myProjectID"; private final static String INFURA_PROJECT_SECRET = "myProjectSecret"; public static void main(String[] args) { try { HttpService httpService = new HttpService(INFURA_ENDPOINT + INFURA_PROJECT_ID); String auth = new String(":" + INFURA_PROJECT_SECRET); byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); String authHeader = "Basic " + new String(encodedAuth); httpService.addHeader(HttpHeaders.AUTHORIZATION, authHeader); Web3j web3j = Web3j.build(httpService); ECKeyPair ecKeyPair = Keys.createEcKeyPair(); Credentials credentials = Credentials.create(ecKeyPair); String contractAddress = "0x8e329a83B96fc80cc00C0acd5efbf78699b2F299"; final BigInteger gasPrice = BigInteger.valueOf(2205000); final BigInteger gasLimit = BigInteger.valueOf(14300000); final ContractGasProvider gasProvider = new StaticGasProvider(gasPrice, gasLimit); TransactionManager manager = new RawTransactionManager(web3j, credentials, 200, 500); final Test contract = Test.load(contractAddress, web3j, manager, gasProvider); String a = contract.run("xyz").send(); // <-- throws exception System.out.println("run(xyz): " + a); } catch(Exception e) { System.out.println("Problem with Web3j: " + e.getMessage() + "\n"); e.printStackTrace(); } SpringApplication.run(SpringBootWeb3j.class, args); } }
完整編譯的智能合約:
/** * <p>Auto generated code. * <p><strong>Do not modify!</strong> * <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>, * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the * <a href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update. * * <p>Generated with web3j version 4.5.16. */ @SuppressWarnings("rawtypes") public class Test extends Contract { public static final String BINARY = "608060405234801561001057600080fd5b506102a0806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80639352fad214610030575b600080fd5b61004361003e366004610134565b610059565b60405161005091906101f1565b60405180910390f35b606061008182604051806040016040528060048152602001631d195cdd60e21b8152506100c9565b156100a557506040805180820190915260028152616f6b60f01b60208201526100c4565b506040805180820190915260068152656e6f74206f6b60d01b60208201525b919050565b600081518351146100dc5750600061012e565b816040516020016100ed91906101d5565b604051602081830303815290604052805190602001208360405160200161011491906101d5565b604051602081830303815290604052805190602001201490505b92915050565b60006020808385031215610146578182fd5b823567ffffffffffffffff8082111561015d578384fd5b818501915085601f830112610170578384fd5b81358181111561018257610182610254565b604051601f8201601f19168101850183811182821017156101a5576101a5610254565b60405281815283820185018810156101bb578586fd5b818585018683013790810190930193909352509392505050565b600082516101e7818460208701610224565b9190910192915050565b6000602082528251806020840152610210816040850160208701610224565b601f01601f19169190910160400192915050565b60005b8381101561023f578181015183820152602001610227565b8381111561024e576000848401525b50505050565b634e487b7160e01b600052604160045260246000fdfea2646970667358221220eb9d0f71f0d251b9767f571c8b5add883a8f864291930492c9b9775e665bcfe664736f6c63430008000033"; public static final String FUNC_RUN = "run"; @Deprecated protected Test(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); } protected Test(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, credentials, contractGasProvider); } @Deprecated protected Test(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); } protected Test(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); } public RemoteFunctionCall<String> run(String name) { final Function function = new Function(FUNC_RUN, Arrays.asList(new org.web3j.abi.datatypes.Utf8String(name)), Arrays.asList(new TypeReference<Utf8String>() {})); return executeRemoteCallSingleValueReturn(function, String.class); } @Deprecated public static Test load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { return new Test(contractAddress, web3j, credentials, gasPrice, gasLimit); } @Deprecated public static Test load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { return new Test(contractAddress, web3j, transactionManager, gasPrice, gasLimit); } public static Test load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { return new Test(contractAddress, web3j, credentials, contractGasProvider); } public static Test load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { return new Test(contractAddress, web3j, transactionManager, contractGasProvider); } public static RemoteCall<Test> deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { return deployRemoteCall(Test.class, web3j, credentials, contractGasProvider, BINARY, ""); } @Deprecated public static RemoteCall<Test> deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { return deployRemoteCall(Test.class, web3j, credentials, gasPrice, gasLimit, BINARY, ""); } public static RemoteCall<Test> deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { return deployRemoteCall(Test.class, web3j, transactionManager, contractGasProvider, BINARY, ""); } @Deprecated public static RemoteCall<Test> deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { return deployRemoteCall(Test.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, ""); } }
當我們在這裡時,這裡是 build.gradle:
plugins { id 'org.springframework.boot' version '2.4.3' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '15' repositories { mavenCentral() } dependencies { implementation group: 'org.web3j', name: 'core', version: '4.8.4' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() }
項目範例可在https://gitlab.com/ErrorUsernameAlreadyTaken/springbootweb3j
將您的 build.gradle 修改為此並重建(我通常使用 maven),問題可能出在 web3j 中使用的 okhttp
dependencies { // https://mvnrepository.com/artifact/org.web3j/core implementation 'org.web3j:core:4.8.4' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' } dependencies{ implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.3.1' implementation("com.squareup.okhttp3:logging-interceptor:4.3.1") } test { useJUnitPlatform() }
這將解決問題,但可能會產生另一個錯誤。所以:
嘗試找出 Web3j 支持哪個solidity 版本(因為 0.8 非常新),然後使用新版本部署合約並使用 Web3j 生成該合約的 java 程式碼
這裡有一些關於 web3j 合約互動的文件 http://docs.web3j.io/latest/smart_contracts/interacting_with_smart_contract/
此方法可能有助於在呼叫您的方法之前檢查契約是否有效:http: //docs.web3j.io/latest/smart_contracts/contract_validity/ http://docs.web3j.io/latest/quickstart/#loading-智能合約
您的契約中的另一件事是您使用不需要 tx 的“公共純”方法,嘗試更好的儲存契約,它有 2 種方法,第一種是交易並返回 tx 雜湊,另一種方法是 get 方法無需交易即可獲得價值 https://docs.soliditylang.org/en/v0.4.24/introduction-to-smart-contracts.html
試試看,告訴我是否一切正常