Contract-Invocation

使用 Web3j 在 Java 中呼叫智能合約函式的問題

  • March 2, 2021

我對用 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-jhttps://trimplement.com/blog/2020/03/coding-smart-contracts -tutorial-infura/在這裡。據我了解,asend()應該呼叫合約的函式並讓我返回返回的字元串。我已經在 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

試試看,告訴我是否一切正常

引用自:https://ethereum.stackexchange.com/questions/91801