From ad5ff0668dbb7f8356fa1314f0f2bcedc1d5063f Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Wed, 4 Aug 2021 09:59:56 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E5=85=BC=E5=AE=B9=E5=BA=95=E5=B1=82?= =?UTF-8?q?=E4=BF=AE=E6=94=B9,=E5=8C=BA=E5=88=86=E4=BB=A5=E5=A4=AA?= =?UTF-8?q?=E5=9D=8A=E5=9C=B0=E5=9D=80=E5=92=8Cplaton=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/main/java/com/platon/protocol/core/Request.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/java/com/platon/protocol/core/Request.java b/core/src/main/java/com/platon/protocol/core/Request.java index 87f0713a..c256ef6d 100644 --- a/core/src/main/java/com/platon/protocol/core/Request.java +++ b/core/src/main/java/com/platon/protocol/core/Request.java @@ -12,6 +12,7 @@ public class Request { private static AtomicLong nextId = new AtomicLong(0); private String jsonrpc = "2.0"; + private boolean bech32 = true; //bech32的值来区分是以太坊/PlatON调用 private String method; private List params; private long id; @@ -42,6 +43,14 @@ public void setJsonrpc(String jsonrpc) { this.jsonrpc = jsonrpc; } + public boolean getBech32() { + return bech32; + } + + public void setBech32(boolean bech32) { + this.bech32 = bech32; + } + public String getMethod() { return method; } From e56c33d437fd96b82eed57542d8e860b17e233e8 Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Mon, 9 Aug 2021 18:22:58 +0800 Subject: [PATCH 2/8] =?UTF-8?q?1.Add=20additional=20admin=20rpc=20calls=20?= =?UTF-8?q?2.Support=20admin=20txpool=5Fcontent=20method=203.BIP-44=20impl?= =?UTF-8?q?ementation=204.=E5=91=BD=E4=BB=A4=E8=A1=8C=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=94=AF=E6=8C=81truffle=E6=A0=BC=E5=BC=8F=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=90=88=E7=BA=A6=E9=AA=A8=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TruffleJsonFunctionWrapperGenerator.java | 6 +- .../java/com/platon/protocol/admin/Admin.java | 2 + .../protocol/admin/JsonRpc2_0Admin.java | 12 +- .../admin/methods/response/TxPoolContent.java | 81 +++++++++ .../admin/methods/response/TxPoolStatus.java | 30 +++ .../methods/response/admin/AdminDataDir.java | 23 +++ .../platon/protocol/core/JsonRpc2_0Web3j.java | 27 +++ .../java/com/platon/protocol/core/Platon.java | 11 ++ .../platon/protocol/admin/RequestTest.java | 18 +- .../platon/protocol/admin/ResponseTest.java | 65 ++++++- .../com/platon/protocol/core/RequestTest.java | 131 ++++++++----- .../platon/protocol/core/ResponseTest.java | 30 +++ .../main/java/com/platon/crypto/Base58.java | 75 ++++++++ .../com/platon/crypto/Bip32ECKeyPair.java | 172 ++++++++++++++++++ .../com/platon/crypto/Bip44WalletUtils.java | 78 ++++++++ .../src/main/java/com/platon/crypto/Sign.java | 12 +- .../java/com/platon/crypto/Bip32Test.java | 158 ++++++++++++++++ .../platon/crypto/Bip44WalletUtilsTest.java | 95 ++++++++++ .../test/java/com/platon/crypto/SignTest.java | 8 + .../com/platon/crypto/WalletUtilsTest.java | 2 +- .../src/main/java/com/platon/crypto/Hash.java | 22 +++ 21 files changed, 996 insertions(+), 62 deletions(-) create mode 100644 core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolContent.java create mode 100644 core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolStatus.java create mode 100644 core/src/main/java/com/platon/protocol/admin/methods/response/admin/AdminDataDir.java create mode 100644 crypto/src/main/java/com/platon/crypto/Base58.java create mode 100644 crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java create mode 100644 crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java create mode 100644 crypto/src/test/java/com/platon/crypto/Bip32Test.java create mode 100644 crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java diff --git a/codegen/src/main/java/com/platon/codegen/TruffleJsonFunctionWrapperGenerator.java b/codegen/src/main/java/com/platon/codegen/TruffleJsonFunctionWrapperGenerator.java index 0c9c2675..a787aa58 100644 --- a/codegen/src/main/java/com/platon/codegen/TruffleJsonFunctionWrapperGenerator.java +++ b/codegen/src/main/java/com/platon/codegen/TruffleJsonFunctionWrapperGenerator.java @@ -118,9 +118,13 @@ private void generate() throws IOException, ClassNotFoundException { } else { addresses = Collections.EMPTY_MAP; } + String bytecode = c.getBytecode(); + if(!Strings.isBlank(bytecode) && bytecode.startsWith("0x")){ + bytecode = bytecode.substring(2); + } new SolidityFunctionWrapper(useJavaNativeTypes) .generateJavaFiles(contractName, - c.getBytecode(), + bytecode, c.getAbi(), destinationDirLocation.toString(), basePackageName, diff --git a/core/src/main/java/com/platon/protocol/admin/Admin.java b/core/src/main/java/com/platon/protocol/admin/Admin.java index 533bd259..99ff7710 100644 --- a/core/src/main/java/com/platon/protocol/admin/Admin.java +++ b/core/src/main/java/com/platon/protocol/admin/Admin.java @@ -5,6 +5,7 @@ import com.platon.protocol.admin.methods.response.NewAccountIdentifier; import com.platon.protocol.admin.methods.response.PersonalListAccounts; import com.platon.protocol.admin.methods.response.PersonalUnlockAccount; +import com.platon.protocol.admin.methods.response.TxPoolContent; import com.platon.protocol.core.Request; import com.platon.protocol.core.methods.request.Transaction; import com.platon.protocol.core.methods.response.PlatonSendTransaction; @@ -40,4 +41,5 @@ public Request personalUnlockAccount( public Request personalSendTransaction( Transaction transaction, String password); + public Request txPoolContent(); } diff --git a/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java b/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java index 16d511d0..21675f81 100644 --- a/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java +++ b/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java @@ -4,6 +4,7 @@ import com.platon.protocol.admin.methods.response.NewAccountIdentifier; import com.platon.protocol.admin.methods.response.PersonalListAccounts; import com.platon.protocol.admin.methods.response.PersonalUnlockAccount; +import com.platon.protocol.admin.methods.response.TxPoolContent; import com.platon.protocol.core.JsonRpc2_0Web3j; import com.platon.protocol.core.Request; import com.platon.protocol.core.methods.request.Transaction; @@ -88,5 +89,14 @@ public Request personalSendTransaction( web3jService, PlatonSendTransaction.class); } - + + @Override + public Request txPoolContent() { + return new Request<>( + "txpool_content", + Collections.emptyList(), + web3jService, + TxPoolContent.class); + } + } diff --git a/core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolContent.java b/core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolContent.java new file mode 100644 index 00000000..18f7dc55 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolContent.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.platon.protocol.admin.methods.response; + + +import com.platon.protocol.core.Response; +import com.platon.protocol.core.methods.response.Transaction; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** txpool_content */ +public class TxPoolContent extends Response { + public static class TxPoolContentResult { + + private Map> pending; + private Map> queued; + + public TxPoolContentResult() {} + + public TxPoolContentResult( + Map> pending, + Map> queued) { + this.pending = + Collections.unmodifiableMap( + pending.entrySet().stream() + .collect( + Collectors.toMap( + (Map.Entry> + i) -> i.getKey().toLowerCase(), + (Map.Entry> + i) -> + Collections.unmodifiableMap( + i.getValue())))); + this.queued = + Collections.unmodifiableMap( + queued.entrySet().stream() + .collect( + Collectors.toMap( + (Map.Entry> + i) -> i.getKey().toLowerCase(), + (Map.Entry> + i) -> + Collections.unmodifiableMap( + i.getValue())))); + } + + public Map> getPending() { + return pending; + } + + public Map> getQueued() { + return queued; + } + + public List getPendingTransactions() { + return pending.values().stream() + .flatMap(i -> i.values().stream()) + .collect(Collectors.toList()); + } + + public List getQueuedTransactions() { + return queued.values().stream() + .flatMap(i -> i.values().stream()) + .collect(Collectors.toList()); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolStatus.java b/core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolStatus.java new file mode 100644 index 00000000..216bacfb --- /dev/null +++ b/core/src/main/java/com/platon/protocol/admin/methods/response/TxPoolStatus.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.platon.protocol.admin.methods.response; + + +import com.platon.protocol.core.Response; +import com.platon.utils.Numeric; + +import java.util.Map; + +/** txpool_status. */ +public class TxPoolStatus extends Response> { + public Integer getPending() { + return Numeric.decodeQuantity((String) getResult().get("pending")).intValue(); + } + + public Integer getQueued() { + return Numeric.decodeQuantity((String) getResult().get("queued")).intValue(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/admin/methods/response/admin/AdminDataDir.java b/core/src/main/java/com/platon/protocol/admin/methods/response/admin/AdminDataDir.java new file mode 100644 index 00000000..57c19fbb --- /dev/null +++ b/core/src/main/java/com/platon/protocol/admin/methods/response/admin/AdminDataDir.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.platon.protocol.admin.methods.response.admin; + +import com.platon.protocol.core.Response; + +/** String response type. */ +public class AdminDataDir extends Response { + public String getDataDir() { + return getResult(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java b/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java index 4aa436b3..5926d895 100644 --- a/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java +++ b/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java @@ -2,6 +2,9 @@ import com.platon.protocol.Web3j; import com.platon.protocol.Web3jService; +import com.platon.protocol.admin.methods.response.BooleanResponse; +import com.platon.protocol.admin.methods.response.TxPoolStatus; +import com.platon.protocol.admin.methods.response.admin.AdminDataDir; import com.platon.protocol.core.methods.request.ShhFilter; import com.platon.protocol.core.methods.response.*; import com.platon.protocol.rx.JsonRpc2_0Rx; @@ -86,6 +89,24 @@ public Request netPeerCount() { NetPeerCount.class); } + @Override + public Request adminAddPeer(String url) { + return new Request<>( + "admin_addPeer", Arrays.asList(url), web3jService, BooleanResponse.class); + } + + @Override + public Request adminRemovePeer(String url) { + return new Request<>( + "admin_removePeer", Arrays.asList(url), web3jService, BooleanResponse.class); + } + + @Override + public Request adminDataDir() { + return new Request<>( + "admin_datadir", Collections.emptyList(), web3jService, AdminDataDir.class); + } + @Override public Request platonProtocolVersion() { return new Request<>( @@ -514,6 +535,12 @@ public Request shhGetMessages(BigInteger filterId) { ShhMessages.class); } + @Override + public Request txPoolStatus() { + return new Request<>( + "txpool_status", Collections.emptyList(), web3jService, TxPoolStatus.class); + } + @Override public Observable newHeadsNotifications() { return web3jService.subscribe( diff --git a/core/src/main/java/com/platon/protocol/core/Platon.java b/core/src/main/java/com/platon/protocol/core/Platon.java index 485986b4..fe08d633 100644 --- a/core/src/main/java/com/platon/protocol/core/Platon.java +++ b/core/src/main/java/com/platon/protocol/core/Platon.java @@ -1,5 +1,8 @@ package com.platon.protocol.core; +import com.platon.protocol.admin.methods.response.BooleanResponse; +import com.platon.protocol.admin.methods.response.TxPoolStatus; +import com.platon.protocol.admin.methods.response.admin.AdminDataDir; import com.platon.protocol.core.methods.request.ShhFilter; import com.platon.protocol.core.methods.response.*; @@ -19,6 +22,12 @@ public interface Platon { Request netPeerCount(); + Request adminAddPeer(String url); + + Request adminRemovePeer(String url); + + Request adminDataDir(); + Request platonProtocolVersion(); Request platonSyncing(); @@ -123,6 +132,8 @@ Request shhPost( Request shhGetMessages(BigInteger filterId); + Request txPoolStatus(); + Request platonEvidences(); Request getProgramVersion(); diff --git a/core/src/test/java/com/platon/protocol/admin/RequestTest.java b/core/src/test/java/com/platon/protocol/admin/RequestTest.java index ecac40ac..d7292a39 100644 --- a/core/src/test/java/com/platon/protocol/admin/RequestTest.java +++ b/core/src/test/java/com/platon/protocol/admin/RequestTest.java @@ -20,7 +20,7 @@ protected void initWeb3Client(HttpService httpService) { public void testPersonalListAccounts() throws Exception { web3j.personalListAccounts().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"personal_listAccounts\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"personal_listAccounts\"," + "\"params\":[],\"id\":1}"); } @@ -28,7 +28,7 @@ public void testPersonalListAccounts() throws Exception { public void testPersonalNewAccount() throws Exception { web3j.personalNewAccount("password").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"personal_newAccount\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"personal_newAccount\"," + "\"params\":[\"password\"],\"id\":1}"); } @@ -48,7 +48,7 @@ public void testPersonalSendTransaction() throws Exception { ).send(); //CHECKSTYLE:OFF - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"personal_sendTransaction\",\"params\":[{\"from\":\"FROM\",\"to\":\"TO\",\"gas\":\"0x1\",\"gasPrice\":\"0xa\",\"value\":\"0x0\",\"data\":\"0xDATA\",\"nonce\":\"0x1\"},\"password\"],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"personal_sendTransaction\",\"params\":[{\"from\":\"FROM\",\"to\":\"TO\",\"gas\":\"0x1\",\"gasPrice\":\"0xa\",\"value\":\"0x0\",\"data\":\"0xDATA\",\"nonce\":\"0x1\"},\"password\"],\"id\":1}"); //CHECKSTYLE:ON } @@ -57,7 +57,7 @@ public void testPersonalUnlockAccount() throws Exception { web3j.personalUnlockAccount( "0xfc390d8a8ddb591b010fda52f4db4945742c3809", "hunter2", BigInteger.ONE).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"personal_unlockAccount\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"personal_unlockAccount\"," + "\"params\":[\"0xfc390d8a8ddb591b010fda52f4db4945742c3809\",\"hunter2\",1]," + "\"id\":1}"); } @@ -66,8 +66,16 @@ public void testPersonalUnlockAccount() throws Exception { public void testPersonalUnlockAccountNoDuration() throws Exception { web3j.personalUnlockAccount("0xfc390d8a8ddb591b010fda52f4db4945742c3809", "hunter2").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"personal_unlockAccount\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"personal_unlockAccount\"," + "\"params\":[\"0xfc390d8a8ddb591b010fda52f4db4945742c3809\",\"hunter2\",null]," + "\"id\":1}"); } + + @Test + public void testTxPoolContent() throws Exception { + web3j.txPoolContent().send(); + + verifyResult( + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"txpool_content\"," + "\"params\":[],\"id\":1}"); + } } diff --git a/core/src/test/java/com/platon/protocol/admin/ResponseTest.java b/core/src/test/java/com/platon/protocol/admin/ResponseTest.java index 9bc6caef..9d31f76c 100644 --- a/core/src/test/java/com/platon/protocol/admin/ResponseTest.java +++ b/core/src/test/java/com/platon/protocol/admin/ResponseTest.java @@ -8,8 +8,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Parity/Geth Shared Protocol Response tests. @@ -90,5 +89,65 @@ public void testPersonalUnlockAccount() { deserialiseResponse(PersonalUnlockAccount.class); assertTrue(personalUnlockAccount.accountUnlocked()); } - + + @Test + public void testTxPoolContent() { + buildResponse( + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"id\": 1,\n" + + " \"result\": {\n" + + " \"pending\": {\n" + + " \"0x0032D05F320fa74C871E892F48F0e6387c0Dfe95\": {\n" + + " \"0\": {\n" + + " \"blockHash\": null,\n" + + " \"blockNumber\": null,\n" + + " \"from\": \"0x0032d05f320fa74c871e892f48f0e6387c0dfe95\",\n" + + " \"gas\": \"0x63cad\",\n" + + " \"gasPrice\": \"0x1\",\n" + + " \"hash\": \"0x56cf53cbd377535c14b28cd373fa43d129f501b1a20b36903fd14b747c3f6cf5\",\n" + + " \"input\": \"0x608060405234801561001057600080fd5b5060405161060a38038061060a833981018060405281019080805190602001909291908051820192919060200180519060200190929190805190602001909291908051906020019092919050505084848160008173ffffffffffffffffffffffffffffffffffffffff1614151515610116576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506000815111156101a35773ffffffffffffffffffffffffffffffffffffffff60005416600080835160208501846127105a03f46040513d6000823e600082141561019f573d81fd5b5050505b5050600081111561036d57600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156102b7578273ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f1935050505015156102b2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001807f436f756c64206e6f74207061792073616665206372656174696f6e207769746881526020017f206574686572000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b61036c565b6102d1828483610377640100000000026401000000009004565b151561036b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001807f436f756c64206e6f74207061792073616665206372656174696f6e207769746881526020017f20746f6b656e000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b5b5b5050505050610490565b600060608383604051602401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040527fa9059cbb000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090506000808251602084016000896127105a03f16040513d6000823e3d60008114610473576020811461047b5760009450610485565b829450610485565b8151158315171594505b505050509392505050565b61016b8061049f6000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600a165627a7a7230582007fffd557dfc8c4d2fdf56ba6381a6ce5b65b6260e1492d87f26c6d4f1d0410800290000000000000000000000008942595a2dc5181df0465af0d7be08c8f23c93af00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000004ba9692da667218aa968ced8cbe59fe193e0d7860000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006968500000000000000000000000000000000000000000000000000000000000001240ec78d9e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000006ea3e1c44f2ad3e54cf32a25eb9fab965fc010f0000000000000000000000009542597a73c7371f07b4532bda22d39cfc4912180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n" + + " \"nonce\": \"0x0\",\n" + + " \"to\": null,\n" + + " \"transactionIndex\": null,\n" + + " \"value\": \"0x0\",\n" + + " \"v\": \"0x1b\",\n" + + " \"r\": \"0x1b53058de9ed675d8b6583c559d23013e941905dad28dfd95ba3ff4f38ace0\",\n" + + " \"s\": \"0x8631cfd224ee69034f8040d2297a28229ee91cd5acdabc7f1de5be62220\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"queued\": {\n" + + " \"0x00Bf700CeB382877F8bFa38b05fcC81126f4f228\": {\n" + + " \"49\": {\n" + + " \"blockHash\": null,\n" + + " \"blockNumber\": null,\n" + + " \"from\": \"0x00bf700ceb382877f8bfa38b05fcc81126f4f228\",\n" + + " \"gas\": \"0xfa00\",\n" + + " \"gasPrice\": \"0x3b9aca00\",\n" + + " \"hash\": \"0xa87ab980c4d277de6c4faf2670a2ec1b6e577482e582c8082d208b7e630cf395\",\n" + + " \"input\": \"0xa6ab36f2000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000009590f23a286dade6fbf778ca651ad560d4e02fdc\",\n" + + " \"nonce\": \"0x31\",\n" + + " \"to\": \"0x974d7219184a41d4f5e3664ddce808c7853d3ab4\",\n" + + " \"transactionIndex\": null,\n" + + " \"value\": \"0x0\",\n" + + " \"v\": \"0x2b\",\n" + + " \"r\": \"0xc747162f13dd24fd03dd00c66c5c3d222595908319333c2b08e7f7def8dad2e7\",\n" + + " \"s\": \"0x8e6a2c4e3794d782d51f950e0ba546f44412e8c2409bd39149d78d7ca316e8a\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"); + + TxPoolContent content = deserialiseResponse(TxPoolContent.class); + + assertEquals( + content.getResult().getPendingTransactions().get(0).getFrom(), + "0x0032d05f320fa74c871e892f48f0e6387c0dfe95"); + assertEquals( + content.getResult().getQueuedTransactions().get(0).getFrom(), + "0x00bf700ceb382877f8bfa38b05fcc81126f4f228"); + } + } diff --git a/core/src/test/java/com/platon/protocol/core/RequestTest.java b/core/src/test/java/com/platon/protocol/core/RequestTest.java index d55e4074..1c89bcec 100644 --- a/core/src/test/java/com/platon/protocol/core/RequestTest.java +++ b/core/src/test/java/com/platon/protocol/core/RequestTest.java @@ -27,7 +27,7 @@ public void testWeb3ClientVersion() throws Exception { web3j.web3ClientVersion().send(); verifyResult( - "{\"jsonrpc\":\"2.0\",\"method\":\"web3_clientVersion\",\"params\":[],\"id\":1}"); + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"web3_clientVersion\",\"params\":[],\"id\":1}"); } @Test @@ -35,7 +35,7 @@ public void testWeb3Sha3() throws Exception { web3j.web3Sha3("0x68656c6c6f20776f726c64").send(); verifyResult( - "{\"jsonrpc\":\"2.0\",\"method\":\"web3_sha3\"," + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"web3_sha3\"," + "\"params\":[\"0x68656c6c6f20776f726c64\"],\"id\":1}"); } @@ -43,21 +43,21 @@ public void testWeb3Sha3() throws Exception { public void testNetVersion() throws Exception { web3j.netVersion().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"net_version\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"net_version\",\"params\":[],\"id\":1}"); } @Test public void testNetListening() throws Exception { web3j.netListening().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"net_listening\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"net_listening\",\"params\":[],\"id\":1}"); } @Test public void testNetPeerCount() throws Exception { web3j.netPeerCount().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"net_peerCount\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"net_peerCount\",\"params\":[],\"id\":1}"); } @Test @@ -65,35 +65,35 @@ public void testEthProtocolVersion() throws Exception { web3j.platonProtocolVersion().send(); verifyResult( - "{\"jsonrpc\":\"2.0\",\"method\":\"platon_protocolVersion\",\"params\":[],\"id\":1}"); + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_protocolVersion\",\"params\":[],\"id\":1}"); } @Test public void testEthSyncing() throws Exception { web3j.platonSyncing().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_syncing\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_syncing\",\"params\":[],\"id\":1}"); } @Test public void testEthGasPrice() throws Exception { web3j.platonGasPrice().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_gasPrice\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_gasPrice\",\"params\":[],\"id\":1}"); } @Test public void testEthAccounts() throws Exception { web3j.platonAccounts().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_accounts\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_accounts\",\"params\":[],\"id\":1}"); } @Test public void testEthBlockNumber() throws Exception { web3j.platonBlockNumber().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_blockNumber\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_blockNumber\",\"params\":[],\"id\":1}"); } @Test @@ -102,7 +102,7 @@ public void testEthGetBalance() throws Exception { DefaultBlockParameterName.LATEST).send(); verifyResult( - "{\"jsonrpc\":\"2.0\",\"method\":\"platon_getBalance\"," + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getBalance\"," + "\"params\":[\"0x407d73d8a49eeb85d32cf465507dd71d507100c1\",\"latest\"]," + "\"id\":1}"); } @@ -112,7 +112,7 @@ public void testEthGetStorageAt() throws Exception { web3j.platonGetStorageAt("0x295a70b2de5e3953354a6a8344e616ed314d7251", BigInteger.ZERO, DefaultBlockParameterName.LATEST).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getStorageAt\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getStorageAt\"," + "\"params\":[\"0x295a70b2de5e3953354a6a8344e616ed314d7251\",\"0x0\",\"latest\"]," + "\"id\":1}"); } @@ -122,7 +122,7 @@ public void testEthGetTransactionCount() throws Exception { web3j.platonGetTransactionCount("0x407d73d8a49eeb85d32cf465507dd71d507100c1", DefaultBlockParameterName.LATEST).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getTransactionCount\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getTransactionCount\"," + "\"params\":[\"0x407d73d8a49eeb85d32cf465507dd71d507100c1\",\"latest\"]," + "\"id\":1}"); } @@ -133,7 +133,7 @@ public void testEthGetBlockTransactionCountByHash() throws Exception { "0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238").send(); //CHECKSTYLE:OFF - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getBlockTransactionCountByHash\",\"params\":[\"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238\"],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getBlockTransactionCountByHash\",\"params\":[\"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238\"],\"id\":1}"); //CHECKSTYLE:ON } @@ -142,7 +142,7 @@ public void testEthGetBlockTransactionCountByNumber() throws Exception { web3j.platonGetBlockTransactionCountByNumber( DefaultBlockParameter.valueOf(Numeric.toBigInt("0xe8"))).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getBlockTransactionCountByNumber\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getBlockTransactionCountByNumber\"," + "\"params\":[\"0xe8\"],\"id\":1}"); } @@ -151,7 +151,7 @@ public void testEthGetCode() throws Exception { web3j.platonGetCode("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", DefaultBlockParameter.valueOf(Numeric.toBigInt("0x2"))).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getCode\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getCode\"," + "\"params\":[\"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\",\"0x2\"],\"id\":1}"); } @@ -160,7 +160,7 @@ public void testEthSign() throws Exception { web3j.platonSign("0x8a3106a3e50576d4b6794a0e74d3bb5f8c9acaab", "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_sign\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_sign\"," + "\"params\":[\"0x8a3106a3e50576d4b6794a0e74d3bb5f8c9acaab\"," + "\"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\"]," + "\"id\":1}"); @@ -179,7 +179,7 @@ public void testEthSendTransaction() throws Exception { + "970870f072445675058bb8eb970870f072445675")).send(); //CHECKSTYLE:OFF - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_sendTransaction\",\"params\":[{\"from\":\"0xb60e8dd61c5d32be8058bb8eb970870f07233155\",\"to\":\"0xb60e8dd61c5d32be8058bb8eb970870f07233155\",\"gas\":\"0x76c0\",\"gasPrice\":\"0x9184e72a000\",\"value\":\"0x9184e72a\",\"data\":\"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675\",\"nonce\":\"0x1\"}],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_sendTransaction\",\"params\":[{\"from\":\"0xb60e8dd61c5d32be8058bb8eb970870f07233155\",\"to\":\"0xb60e8dd61c5d32be8058bb8eb970870f07233155\",\"gas\":\"0x76c0\",\"gasPrice\":\"0x9184e72a000\",\"value\":\"0x9184e72a\",\"data\":\"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675\",\"nonce\":\"0x1\"}],\"id\":1}"); //CHECKSTYLE:ON } @@ -190,7 +190,7 @@ public void testEthSendRawTransaction() throws Exception { + "072445675058bb8eb970870f072445675").send(); //CHECKSTYLE:OFF - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_sendRawTransaction\",\"params\":[\"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675\"],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_sendRawTransaction\",\"params\":[\"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675\"],\"id\":1}"); //CHECKSTYLE:ON } @@ -203,7 +203,7 @@ public void testEthCall() throws Exception { "0x0"), DefaultBlockParameter.valueOf("latest")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_call\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_call\"," + "\"params\":[{\"from\":\"0xa70e8dd61c5d32be8058bb8eb970870f07233155\"," + "\"to\":\"0xb60e8dd61c5d32be8058bb8eb970870f07233155\",\"data\":\"0x0\"}," + "\"latest\"],\"id\":1}"); @@ -216,7 +216,7 @@ public void testEthEstimateGas() throws Exception { "0xa70e8dd61c5d32be8058bb8eb970870f07233155", "0x52b93c80364dc2dd4444c146d73b9836bbbb2b3f", "0x0")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_estimateGas\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_estimateGas\"," + "\"params\":[{\"from\":\"0xa70e8dd61c5d32be8058bb8eb970870f07233155\"," + "\"to\":\"0x52b93c80364dc2dd4444c146d73b9836bbbb2b3f\",\"data\":\"0x0\"}]," + "\"id\":1}"); @@ -229,7 +229,7 @@ public void testEthEstimateGasContractCreation() throws Exception { "0x52b93c80364dc2dd4444c146d73b9836bbbb2b3f", BigInteger.ONE, BigInteger.TEN, "")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_estimateGas\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_estimateGas\"," + "\"params\":[{\"from\":\"0x52b93c80364dc2dd4444c146d73b9836bbbb2b3f\"," + "\"gasPrice\":\"0xa\",\"data\":\"0x\",\"nonce\":\"0x1\"}],\"id\":1}"); } @@ -240,7 +240,7 @@ public void testEthGetBlockByHash() throws Exception { "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", true).send(); verifyResult( - "{\"jsonrpc\":\"2.0\",\"method\":\"platon_getBlockByHash\",\"params\":[" + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getBlockByHash\",\"params\":[" + "\"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331\"" + ",true],\"id\":1}"); } @@ -250,7 +250,7 @@ public void testEthGetBlockByNumber() throws Exception { web3j.platonGetBlockByNumber( DefaultBlockParameter.valueOf(Numeric.toBigInt("0x1b4")), true).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getBlockByNumber\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getBlockByNumber\"," + "\"params\":[\"0x1b4\",true],\"id\":1}"); } @@ -259,7 +259,7 @@ public void testEthGetTransactionByHash() throws Exception { web3j.platonGetTransactionByHash( "0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getTransactionByHash\",\"params\":[" + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getTransactionByHash\",\"params\":[" + "\"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238\"]," + "\"id\":1}"); } @@ -271,7 +271,7 @@ public void testEthGetTransactionByBlockHashAndIndex() throws Exception { BigInteger.ZERO).send(); //CHECKSTYLE:OFF - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getTransactionByBlockHashAndIndex\",\"params\":[\"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331\",\"0x0\"],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getTransactionByBlockHashAndIndex\",\"params\":[\"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331\",\"0x0\"],\"id\":1}"); //CHECKSTYLE:ON } @@ -280,7 +280,7 @@ public void testEthGetTransactionByBlockNumberAndIndex() throws Exception { web3j.platonGetTransactionByBlockNumberAndIndex( DefaultBlockParameter.valueOf(Numeric.toBigInt("0x29c")), BigInteger.ZERO).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getTransactionByBlockNumberAndIndex\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getTransactionByBlockNumberAndIndex\"," + "\"params\":[\"0x29c\",\"0x0\"],\"id\":1}"); } @@ -289,7 +289,7 @@ public void testEthGetTransactionReceipt() throws Exception { web3j.platonGetTransactionReceipt( "0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getTransactionReceipt\",\"params\":[" + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getTransactionReceipt\",\"params\":[" + "\"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238\"]," + "\"id\":1}"); } @@ -301,7 +301,7 @@ public void testEthNewFilter() throws Exception { web3j.platonNewFilter(ethFilter).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_newFilter\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_newFilter\"," + "\"params\":[{\"topics\":[\"0x12341234\"]}],\"id\":1}"); } @@ -309,7 +309,7 @@ public void testEthNewFilter() throws Exception { public void testEthNewBlockFilter() throws Exception { web3j.platonNewBlockFilter().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_newBlockFilter\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_newBlockFilter\"," + "\"params\":[],\"id\":1}"); } @@ -317,7 +317,7 @@ public void testEthNewBlockFilter() throws Exception { public void testEthNewPendingTransactionFilter() throws Exception { web3j.platonNewPendingTransactionFilter().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_newPendingTransactionFilter\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_newPendingTransactionFilter\"," + "\"params\":[],\"id\":1}"); } @@ -325,7 +325,7 @@ public void testEthNewPendingTransactionFilter() throws Exception { public void testEthUninstallFilter() throws Exception { web3j.platonUninstallFilter(Numeric.toBigInt("0xb")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_uninstallFilter\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_uninstallFilter\"," + "\"params\":[\"0x0b\"],\"id\":1}"); } @@ -333,7 +333,7 @@ public void testEthUninstallFilter() throws Exception { public void testEthGetFilterChanges() throws Exception { web3j.platonGetFilterChanges(Numeric.toBigInt("0x16")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getFilterChanges\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getFilterChanges\"," + "\"params\":[\"0x16\"],\"id\":1}"); } @@ -341,7 +341,7 @@ public void testEthGetFilterChanges() throws Exception { public void testEthGetFilterLogs() throws Exception { web3j.platonGetFilterLogs(Numeric.toBigInt("0x16")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getFilterLogs\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getFilterLogs\"," + "\"params\":[\"0x16\"],\"id\":1}"); } @@ -351,7 +351,7 @@ public void testEthGetLogs() throws Exception { "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b")) .send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"platon_getLogs\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getLogs\"," + "\"params\":[{\"topics\":[" + "\"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b\"]}]," + "\"id\":1}"); @@ -365,7 +365,7 @@ public void testEthGetLogsWithNumericBlockRange() throws Exception { .send(); verifyResult( - "{\"jsonrpc\":\"2.0\",\"method\":\"platon_getLogs\"," + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"platon_getLogs\"," + "\"params\":[{\"topics\":[],\"fromBlock\":\"0xe8\"," + "\"toBlock\":\"latest\",\"address\":[\"\"]}],\"id\":1}"); } @@ -374,7 +374,7 @@ public void testEthGetLogsWithNumericBlockRange() throws Exception { public void testDbPutString() throws Exception { web3j.dbPutString("testDB", "myKey", "myString").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"db_putString\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"db_putString\"," + "\"params\":[\"testDB\",\"myKey\",\"myString\"],\"id\":1}"); } @@ -382,7 +382,7 @@ public void testDbPutString() throws Exception { public void testDbGetString() throws Exception { web3j.dbGetString("testDB", "myKey").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"db_getString\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"db_getString\"," + "\"params\":[\"testDB\",\"myKey\"],\"id\":1}"); } @@ -390,7 +390,7 @@ public void testDbGetString() throws Exception { public void testDbPutHex() throws Exception { web3j.dbPutHex("testDB", "myKey", "0x68656c6c6f20776f726c64").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"db_putHex\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"db_putHex\"," + "\"params\":[\"testDB\",\"myKey\",\"0x68656c6c6f20776f726c64\"],\"id\":1}"); } @@ -398,7 +398,7 @@ public void testDbPutHex() throws Exception { public void testDbGetHex() throws Exception { web3j.dbGetHex("testDB", "myKey").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"db_getHex\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"db_getHex\"," + "\"params\":[\"testDB\",\"myKey\"],\"id\":1}"); } @@ -406,7 +406,7 @@ public void testDbGetHex() throws Exception { public void testShhVersion() throws Exception { web3j.shhVersion().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_version\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_version\"," + "\"params\":[],\"id\":1}"); } @@ -421,7 +421,7 @@ public void testShhPost() throws Exception { Numeric.toBigInt("0x64"), Numeric.toBigInt("0x64"))).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_post\",\"params\":[{\"from\":\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\",\"to\":\"0x3e245533f97284d442460f2998cd41858798ddf04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a0d4d661997d3940272b717b1\",\"topics\":[\"0x776869737065722d636861742d636c69656e74\",\"0x4d5a695276454c39425154466b61693532\"],\"payload\":\"0x7b2274797065223a226d6\",\"priority\":\"0x64\",\"ttl\":\"0x64\"}],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_post\",\"params\":[{\"from\":\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\",\"to\":\"0x3e245533f97284d442460f2998cd41858798ddf04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a0d4d661997d3940272b717b1\",\"topics\":[\"0x776869737065722d636861742d636c69656e74\",\"0x4d5a695276454c39425154466b61693532\"],\"payload\":\"0x7b2274797065223a226d6\",\"priority\":\"0x64\",\"ttl\":\"0x64\"}],\"id\":1}"); //CHECKSTYLE:ON } @@ -429,7 +429,7 @@ public void testShhPost() throws Exception { public void testShhNewIdentity() throws Exception { web3j.shhNewIdentity().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_newIdentity\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_newIdentity\",\"params\":[],\"id\":1}"); } @Test @@ -437,7 +437,7 @@ public void testShhHasIdentity() throws Exception { //CHECKSTYLE:OFF web3j.shhHasIdentity("0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_hasIdentity\",\"params\":[\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\"],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_hasIdentity\",\"params\":[\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\"],\"id\":1}"); //CHECKSTYLE:ON } @@ -445,7 +445,7 @@ public void testShhHasIdentity() throws Exception { public void testShhNewGroup() throws Exception { web3j.shhNewGroup().send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_newGroup\",\"params\":[],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_newGroup\",\"params\":[],\"id\":1}"); } @Test @@ -453,7 +453,7 @@ public void testShhAddToGroup() throws Exception { //CHECKSTYLE:OFF web3j.shhAddToGroup("0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1").send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_addToGroup\",\"params\":[\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\"],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_addToGroup\",\"params\":[\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\"],\"id\":1}"); //CHECKSTYLE:ON } @@ -464,7 +464,7 @@ public void testShhNewFilter() throws Exception { new ShhFilter("0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1") .addSingleTopic("0x12341234bf4b564f")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_newFilter\",\"params\":[{\"topics\":[\"0x12341234bf4b564f\"],\"to\":\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\"}],\"id\":1}"); + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_newFilter\",\"params\":[{\"topics\":[\"0x12341234bf4b564f\"],\"to\":\"0x04f96a5e25610293e42a73908e93ccc8c4d4dc0edcfa9fa872f50cb214e08ebf61a03e245533f97284d442460f2998cd41858798ddfd4d661997d3940272b717b1\"}],\"id\":1}"); //CHECKSTYLE:ON } @@ -472,7 +472,7 @@ public void testShhNewFilter() throws Exception { public void testShhUninstallFilter() throws Exception { web3j.shhUninstallFilter(Numeric.toBigInt("0x7")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_uninstallFilter\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_uninstallFilter\"," + "\"params\":[\"0x07\"],\"id\":1}"); } @@ -480,7 +480,7 @@ public void testShhUninstallFilter() throws Exception { public void testShhGetFilterChanges() throws Exception { web3j.shhGetFilterChanges(Numeric.toBigInt("0x7")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_getFilterChanges\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_getFilterChanges\"," + "\"params\":[\"0x07\"],\"id\":1}"); } @@ -488,7 +488,38 @@ public void testShhGetFilterChanges() throws Exception { public void testShhGetMessages() throws Exception { web3j.shhGetMessages(Numeric.toBigInt("0x7")).send(); - verifyResult("{\"jsonrpc\":\"2.0\",\"method\":\"shh_getMessages\"," + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"shh_getMessages\"," + "\"params\":[\"0x07\"],\"id\":1}"); } + + @Test + public void testAdminAddPeer() throws Exception { + web3j.adminAddPeer("url").send(); + + verifyResult( + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"admin_addPeer\",\"params\":[\"url\"],\"id\":1}"); + } + + @Test + public void testAdminDataDir() throws Exception { + web3j.adminDataDir().send(); + + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"admin_datadir\",\"params\":[],\"id\":1}"); + } + + @Test + public void testAdminRemovePeer() throws Exception { + web3j.adminRemovePeer("url").send(); + + verifyResult( + "{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"admin_removePeer\",\"params\":[\"url\"],\"id\":1}"); + } + + @Test + public void testTxPoolStatus() throws Exception { + web3j.txPoolStatus().send(); + + verifyResult("{\"jsonrpc\":\"2.0\",\"bech32\":true,\"method\":\"txpool_status\",\"params\":[],\"id\":1}"); + } + } diff --git a/core/src/test/java/com/platon/protocol/core/ResponseTest.java b/core/src/test/java/com/platon/protocol/core/ResponseTest.java index f5a8f753..88f138a6 100644 --- a/core/src/test/java/com/platon/protocol/core/ResponseTest.java +++ b/core/src/test/java/com/platon/protocol/core/ResponseTest.java @@ -1,6 +1,8 @@ package com.platon.protocol.core; import com.platon.protocol.ResponseTester; +import com.platon.protocol.admin.methods.response.TxPoolStatus; +import com.platon.protocol.admin.methods.response.admin.AdminDataDir; import com.platon.protocol.core.methods.response.*; import com.platon.utils.Numeric; import org.junit.Test; @@ -1257,4 +1259,32 @@ public void testSshMessages() { ShhMessages shhMessages = deserialiseResponse(ShhMessages.class); assertThat(shhMessages.getMessages(), equalTo(messages)); } + + @Test + public void testAdminDataDir() { + buildResponse( + "{\n" + + " \"jsonrpc\":\"2.0\",\n" + + " \"id\":22,\n" + + " \"result\":\"sampleDir\"\n" + + "}"); + + AdminDataDir dataDir = deserialiseResponse(AdminDataDir.class); + assertEquals(dataDir.getDataDir(), "sampleDir"); + } + + @Test + public void testTxPoolStatus() { + buildResponse( + "{\n" + + " \"jsonrpc\":\"2.0\",\n" + + " \"id\":22,\n" + + " \"result\":{ \"pending\": \"0x9\",\n" + + " \"queued\": \"0x7\"}\n" + + "}"); + + TxPoolStatus status = deserialiseResponse(TxPoolStatus.class); + assert(status.getPending() == 9); + assert(status.getQueued() == 7); + } } diff --git a/crypto/src/main/java/com/platon/crypto/Base58.java b/crypto/src/main/java/com/platon/crypto/Base58.java new file mode 100644 index 00000000..30b56eb7 --- /dev/null +++ b/crypto/src/main/java/com/platon/crypto/Base58.java @@ -0,0 +1,75 @@ +/* + * Copyright 2011 Google Inc. + * Copyright 2018 Andreas Schildbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.platon.crypto; + + +import java.util.Arrays; + +public class Base58 { + private static final char[] ALPHABET = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final char ENCODED_ZERO = ALPHABET[0]; + + /** + * Encodes the given bytes as a base58 string (no checksum is appended). + * + * @param input the bytes to encode + * @return the base58-encoded string + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input.length && input[zeros] == 0) { + ++zeros; + } + // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) + input = Arrays.copyOf(input, input.length); // since we modify it in-place + char[] encoded = new char[input.length * 2]; // upper bound + int outputStart = encoded.length; + for (int inputStart = zeros; inputStart < input.length; ) { + encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; + if (input[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Preserve exactly as many leading encoded zeros in output as there + // were leading zeros in input. + while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { + ++outputStart; + } + while (--zeros >= 0) { + encoded[--outputStart] = ENCODED_ZERO; + } + // Return encoded string (including encoded leading zeros). + return new String(encoded, outputStart, encoded.length - outputStart); + } + + private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { + // this is just long division which accounts for the base of the input digits + int remainder = 0; + for (int i = firstDigit; i < number.length; i++) { + int digit = (int) number[i] & 0xFF; + int temp = remainder * base + digit; + number[i] = (byte) (temp / divisor); + remainder = temp % divisor; + } + return (byte) remainder; + } +} \ No newline at end of file diff --git a/crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java b/crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java new file mode 100644 index 00000000..0bdedd7c --- /dev/null +++ b/crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java @@ -0,0 +1,172 @@ +package com.platon.crypto; + +import com.platon.utils.Numeric; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static com.platon.crypto.Hash.hmacSha512; +import static com.platon.crypto.Hash.sha256hash160; + +/** + * BIP-32 key pair. + * + *

Adapted from: + * https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java + */ +public class Bip32ECKeyPair extends ECKeyPair { + static final int HARDENED_BIT = 0x80000000; + + private final boolean parentHasPrivate; + private final int childNumber; + private final int depth; + private final byte[] chainCode; + private int parentFingerprint; + + private ECPoint publicKeyPoint; + + public Bip32ECKeyPair(BigInteger privateKey, BigInteger publicKey, int childNumber, + byte[] chainCode, Bip32ECKeyPair parent) { + super(privateKey, publicKey); + this.parentHasPrivate = parent != null && parent.hasPrivateKey(); + this.childNumber = childNumber; + this.depth = parent == null ? 0 : parent.depth + 1; + this.chainCode = Arrays.copyOf(chainCode, chainCode.length); + this.parentFingerprint = parent != null ? parent.getFingerprint() : 0; + } + + public static Bip32ECKeyPair create(BigInteger privateKey, byte[] chainCode) { + return new Bip32ECKeyPair(privateKey, Sign.publicKeyFromPrivate(privateKey), + 0, chainCode, null); + } + + public static Bip32ECKeyPair create(byte[] privateKey, byte[] chainCode) { + return create(Numeric.toBigInt(privateKey), chainCode); + } + + public static Bip32ECKeyPair generateKeyPair(byte[] seed) { + byte[] i = hmacSha512("Bitcoin seed".getBytes(), seed); + byte[] il = Arrays.copyOfRange(i, 0, 32); + byte[] ir = Arrays.copyOfRange(i, 32, 64); + Arrays.fill(i, (byte) 0); + Bip32ECKeyPair keypair = Bip32ECKeyPair.create(il, ir); + Arrays.fill(il, (byte) 0); + Arrays.fill(ir, (byte) 0); + + return keypair; + } + + public static Bip32ECKeyPair deriveKeyPair(Bip32ECKeyPair master, int[] path) { + Bip32ECKeyPair curr = master; + if (path != null) { + for (int childNumber : path) { + curr = curr.deriveChildKey(childNumber); + } + } + + return curr; + } + + private Bip32ECKeyPair deriveChildKey(int childNumber) { + if (!hasPrivateKey()) { + byte[] parentPublicKey = getPublicKeyPoint().getEncoded(true); + ByteBuffer data = ByteBuffer.allocate(37); + data.put(parentPublicKey); + data.putInt(childNumber); + byte[] i = hmacSha512(getChainCode(), data.array()); + byte[] il = Arrays.copyOfRange(i, 0, 32); + byte[] chainCode = Arrays.copyOfRange(i, 32, 64); + Arrays.fill(i, (byte) 0); + BigInteger ilInt = new BigInteger(1, il); + Arrays.fill(il, (byte) 0); + ECPoint ki = Sign.publicPointFromPrivate(ilInt).add(getPublicKeyPoint()); + + return new Bip32ECKeyPair(null, + Sign.publicFromPoint(ki.getEncoded(true)), + childNumber, chainCode, this); + } else { + ByteBuffer data = ByteBuffer.allocate(37); + if (isHardened(childNumber)) { + data.put(getPrivateKeyBytes33()); + } else { + byte[] parentPublicKey = getPublicKeyPoint().getEncoded(true); + data.put(parentPublicKey); + } + data.putInt(childNumber); + byte[] i = hmacSha512(getChainCode(), data.array()); + byte[] il = Arrays.copyOfRange(i, 0, 32); + byte[] chainCode = Arrays.copyOfRange(i, 32, 64); + Arrays.fill(i, (byte) 0); + BigInteger ilInt = new BigInteger(1, il); + Arrays.fill(il, (byte) 0); + BigInteger privateKey = getPrivateKey().add(ilInt).mod(Sign.CURVE.getN()); + + return new Bip32ECKeyPair(privateKey, Sign.publicKeyFromPrivate(privateKey), + childNumber, chainCode, this); + } + } + + private int getFingerprint() { + byte[] id = getIdentifier(); + return id[3] & 0xFF | (id[2] & 0xFF) << 8 | (id[1] & 0xFF) << 16 | (id[0] & 0xFF) << 24; + } + + public int getDepth() { + return depth; + } + + public int getParentFingerprint() { + return parentFingerprint; + } + + public byte[] getChainCode() { + return chainCode; + } + + public int getChildNumber() { + return childNumber; + } + + private byte[] getIdentifier() { + return sha256hash160(getPublicKeyPoint().getEncoded(true)); + } + + public ECPoint getPublicKeyPoint() { + if (publicKeyPoint == null) { + publicKeyPoint = Sign.publicPointFromPrivate(getPrivateKey()); + } + return publicKeyPoint; + } + + public byte[] getPrivateKeyBytes33() { + final int numBytes = 33; + + byte[] bytes33 = new byte[numBytes]; + byte[] priv = bigIntegerToBytes32(getPrivateKey()); + System.arraycopy(priv, 0, bytes33, numBytes - priv.length, priv.length); + return bytes33; + } + + private boolean hasPrivateKey() { + return this.getPrivateKey() != null || parentHasPrivate; + } + + private static byte[] bigIntegerToBytes32(BigInteger b) { + final int numBytes = 32; + + byte[] src = b.toByteArray(); + byte[] dest = new byte[numBytes]; + boolean isFirstByteOnlyForSign = src[0] == 0; + int length = isFirstByteOnlyForSign ? src.length - 1 : src.length; + int srcPos = isFirstByteOnlyForSign ? 1 : 0; + int destPos = numBytes - length; + System.arraycopy(src, srcPos, dest, destPos, length); + return dest; + } + + private static boolean isHardened(int a) { + return (a & HARDENED_BIT) != 0; + } +} diff --git a/crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java b/crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java new file mode 100644 index 00000000..82d99c5e --- /dev/null +++ b/crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java @@ -0,0 +1,78 @@ +package com.platon.crypto; + +import java.io.File; +import java.io.IOException; + +import static com.platon.crypto.Bip32ECKeyPair.HARDENED_BIT; + +public class Bip44WalletUtils extends WalletUtils { + + /** + * Generates a BIP-44 compatible Ethereum wallet on top of BIP-39 generated seed. + * + * @param password Will be used for both wallet encryption and passphrase for BIP-39 seed + * @param destinationDirectory The directory containing the wallet + * @return A BIP-39 compatible Ethereum wallet + * @throws CipherException if the underlying cipher is not available + * @throws IOException if the destination cannot be written to + */ + public static Bip39Wallet generateBip44Wallet(String password, File destinationDirectory) + throws CipherException, IOException { + return generateBip44Wallet(password, destinationDirectory, false); + } + + /** + * Generates a BIP-44 compatible Ethereum wallet on top of BIP-39 generated seed. + * + * @param password Will be used for both wallet encryption and passphrase for BIP-39 seed + * @param destinationDirectory The directory containing the wallet + * @param testNet should use the testNet derive path + * @return A BIP-39 compatible Ethereum wallet + * @throws CipherException if the underlying cipher is not available + * @throws IOException if the destination cannot be written to + */ + public static Bip39Wallet generateBip44Wallet(String password, File destinationDirectory, + boolean testNet) + throws CipherException, IOException { + byte[] initialEntropy = new byte[16]; + SecureRandomUtils.secureRandom().nextBytes(initialEntropy); + + String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); + byte[] seed = MnemonicUtils.generateSeed(mnemonic, null); + + Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); + Bip32ECKeyPair bip44Keypair = generateBip44KeyPair(masterKeypair, testNet); + + String walletFile = generateWalletFile(password, bip44Keypair, destinationDirectory, false); + + return new Bip39Wallet(walletFile, mnemonic); + } + + public static Bip32ECKeyPair generateBip44KeyPair(Bip32ECKeyPair master) { + return generateBip44KeyPair(master, false); + } + + public static Bip32ECKeyPair generateBip44KeyPair(Bip32ECKeyPair master, boolean testNet) { + if (testNet) { + // /m/44'/0'/0 + final int[] path = {44 | HARDENED_BIT, 0 | HARDENED_BIT, 0 | HARDENED_BIT, 0}; + return Bip32ECKeyPair.deriveKeyPair(master, path); + } else { + // m/44'/60'/0'/0 + final int[] path = {44 | HARDENED_BIT, 60 | HARDENED_BIT, 0 | HARDENED_BIT, 0}; + return Bip32ECKeyPair.deriveKeyPair(master, path); + } + } + + public static Credentials loadBip44Credentials(String password, String mnemonic) { + return loadBip44Credentials(password, mnemonic, false); + } + + public static Credentials loadBip44Credentials(String password, String mnemonic, + boolean testNet) { + byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); + Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); + Bip32ECKeyPair bip44Keypair = generateBip44KeyPair(masterKeypair, testNet); + return Credentials.create(bip44Keypair); + } +} \ No newline at end of file diff --git a/crypto/src/main/java/com/platon/crypto/Sign.java b/crypto/src/main/java/com/platon/crypto/Sign.java index a23ccd75..bef97729 100644 --- a/crypto/src/main/java/com/platon/crypto/Sign.java +++ b/crypto/src/main/java/com/platon/crypto/Sign.java @@ -211,7 +211,7 @@ public static BigInteger publicKeyFromPrivate(BigInteger privKey) { /** * Returns public key point from the given private key. */ - private static ECPoint publicPointFromPrivate(BigInteger privKey) { + public static ECPoint publicPointFromPrivate(BigInteger privKey) { /* * TODO: FixedPointCombMultiplier currently doesn't support scalars longer than the group * order, but that could change in future versions. @@ -222,6 +222,16 @@ private static ECPoint publicPointFromPrivate(BigInteger privKey) { return new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey); } + /** + * Returns public key point from the given curve. + * + * @param bits representing the point on the curve + * @return BigInteger encoded public key + */ + public static BigInteger publicFromPoint(byte[] bits) { + return new BigInteger(1, Arrays.copyOfRange(bits, 1, bits.length)); // remove prefix + } + public static class SignatureData { private final byte[] v; private final byte[] r; diff --git a/crypto/src/test/java/com/platon/crypto/Bip32Test.java b/crypto/src/test/java/com/platon/crypto/Bip32Test.java new file mode 100644 index 00000000..aa0ccd8d --- /dev/null +++ b/crypto/src/test/java/com/platon/crypto/Bip32Test.java @@ -0,0 +1,158 @@ +package com.platon.crypto; + +import com.platon.utils.Numeric; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.nio.ByteBuffer; + +import static com.platon.crypto.Bip32ECKeyPair.HARDENED_BIT; +import static com.platon.crypto.Hash.sha256; + +/** + * BIP-32 implementation test. + * + *

Test vectors taken from BIP-32 definition + * https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + */ +public class Bip32Test { + @SuppressWarnings("checkstyle:LineLength") + @Test + public void deriveKeyPairVector1() { + // Chain m + testGenerated("000102030405060708090a0b0c0d0e0f", + "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + null); + + // Chain m/0H + testGenerated("000102030405060708090a0b0c0d0e0f", + "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", + "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", + new int[]{0 | HARDENED_BIT}); + + // Chain m/0H/1 + testGenerated("000102030405060708090a0b0c0d0e0f", + "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", + "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", + new int[]{0 | HARDENED_BIT, 1}); + + // Chain m/0H/1/2H + testGenerated("000102030405060708090a0b0c0d0e0f", + "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", + "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", + new int[]{0 | HARDENED_BIT, 1, 2 | HARDENED_BIT}); + + // Chain m/0H/1/2H/2 + testGenerated("000102030405060708090a0b0c0d0e0f", + "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", + "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", + new int[]{0 | HARDENED_BIT, 1, 2 | HARDENED_BIT, 2}); + + // Chain m/0H/1/2H/2/1000000000 + testGenerated("000102030405060708090a0b0c0d0e0f", + "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", + "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", + new int[]{0 | HARDENED_BIT, 1, 2 | HARDENED_BIT, 2, 1000000000}); + } + + @SuppressWarnings("checkstyle:LineLength") + @Test + public void deriveKeyPairVector2() { + // Chain m + testGenerated("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", + "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", + null); + + // Chain m/0 + testGenerated("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", + "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", + new int[]{0}); + + // Chain m/0/2147483647H + testGenerated("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", + "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", + new int[]{0, 2147483647 | HARDENED_BIT}); + + // Chain m/0/2147483647H/1 + testGenerated("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", + "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", + new int[]{0, 2147483647 | HARDENED_BIT, 1}); + + // Chain m/0/2147483647H/1/2147483646H + testGenerated("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", + "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", + new int[]{0, 2147483647 | HARDENED_BIT, 1, 2147483646 | HARDENED_BIT}); + + // Chain m/0/2147483647H/1/2147483646H/2 + testGenerated("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", + "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", + new int[]{0, 2147483647 | HARDENED_BIT, 1, 2147483646 | HARDENED_BIT, 2}); + } + + @SuppressWarnings("checkstyle:LineLength") + @Test + public void deriveKeyPairVector3() { + // Chain m + testGenerated("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", + "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", + null); + + // Chain m/0H + testGenerated("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", + "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", + new int[]{0 | HARDENED_BIT}); + } + + private void testGenerated(String seed, String expectedPriv, String expectedPub, int[] path) { + Bip32ECKeyPair pair = Bip32ECKeyPair.generateKeyPair(Numeric.hexStringToByteArray(seed)); + assertNotNull(pair); + + pair = Bip32ECKeyPair.deriveKeyPair(pair, path); + assertNotNull(pair); + + assertEquals(expectedPriv, Base58.encode(addChecksum(serializePrivate(pair)))); + assertEquals(expectedPub, Base58.encode(addChecksum(serializePublic(pair)))); + } + + static byte[] addChecksum(byte[] input) { + int inputLength = input.length; + byte[] checksummed = new byte[inputLength + 4]; + System.arraycopy(input, 0, checksummed, 0, inputLength); + byte[] checksum = hashTwice(input); + System.arraycopy(checksum, 0, checksummed, inputLength, 4); + return checksummed; + } + + static byte[] serializePublic(Bip32ECKeyPair pair) { + return serialize(pair, 0x0488B21E, true); + } + + static byte[] serializePrivate(Bip32ECKeyPair pair) { + return serialize(pair, 0x0488ADE4, false); + } + + private static byte[] hashTwice(byte[] input) { + return sha256(sha256(input)); + } + + private static byte[] serialize(Bip32ECKeyPair pair, int header, boolean pub) { + ByteBuffer ser = ByteBuffer.allocate(78); + ser.putInt(header); + ser.put((byte) pair.getDepth()); + ser.putInt(pair.getParentFingerprint()); + ser.putInt(pair.getChildNumber()); + ser.put(pair.getChainCode()); + ser.put(pub ? pair.getPublicKeyPoint().getEncoded(true) : pair.getPrivateKeyBytes33()); + return ser.array(); + } +} \ No newline at end of file diff --git a/crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java b/crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java new file mode 100644 index 00000000..1c05e4d9 --- /dev/null +++ b/crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java @@ -0,0 +1,95 @@ +package com.platon.crypto; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static com.platon.crypto.Bip32Test.*; +import static com.platon.crypto.SampleKeys.PASSWORD; +import static com.platon.crypto.WalletUtilsTest.createTempDir; +import static org.junit.Assert.assertEquals; + +import java.io.File; + +public class Bip44WalletUtilsTest { + + private File tempDir; + + @Before + public void setUp() throws Exception { + tempDir = createTempDir(); + } + + @After + public void tearDown() throws Exception { + for (File file:tempDir.listFiles()) { + file.delete(); + } + tempDir.delete(); + } + + @SuppressWarnings("checkstyle:LineLength") + @Test + public void generateBip44KeyPair() { + String mnemonic = "spider elbow fossil truck deal circle divert sleep safe report laundry above"; + byte[] seed = MnemonicUtils.generateSeed(mnemonic, null); + String seedStr = bytesToHex(seed); + assertEquals("f0d2ab78b96acd147119abad1cd70eb4fec4f0e0a95744cf532e6a09347b08101213b4cbf50eada0eb89cba444525fe28e69707e52aa301c6b47ce1c5ef82eb5", + seedStr); + + Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); + assertEquals("xprv9s21ZrQH143K2yA9Cdad5gjqHRC7apVUgEyYq5jXeXigDZ3PfEnps44tJprtMXr7PZivEsin6Qrbad7PuiEy4tn5jAEK6A3U46f9KvfRCmD", + Base58.encode(addChecksum(serializePrivate(masterKeypair)))); + + Bip32ECKeyPair bip44Keypair = Bip44WalletUtils.generateBip44KeyPair(masterKeypair); + + assertEquals("xprv9zvpunws9gusoXVkmqAXWQm5z5hjR5kY3ifRGL7M8Kpjn8kRhavkGnFLjnFWPGGS2gAUw8rP33Lmj6SwZUpwy2mn2fXRYWzGa9WRTnE8DPz", + Base58.encode(addChecksum(serializePrivate(bip44Keypair)))); + assertEquals("xpub6DvBKJUkz4UB21aDsrhXsYhpY7YDpYUPQwb24iWxgfMiew5aF8EzpaZpb567bYYbMfUnPwFNuRYvVpMGQUcaGPMoXUEUZKFvx7LaU5b7zBD", + Base58.encode(addChecksum(serializePublic(bip44Keypair)))); + } + + @SuppressWarnings("checkstyle:LineLength") + @Test + public void generateBip44KeyPairTestNet() { + String mnemonic = "spider elbow fossil truck deal circle divert sleep safe report laundry above"; + byte[] seed = MnemonicUtils.generateSeed(mnemonic, null); + String seedStr = bytesToHex(seed); + assertEquals("f0d2ab78b96acd147119abad1cd70eb4fec4f0e0a95744cf532e6a09347b08101213b4cbf50eada0eb89cba444525fe28e69707e52aa301c6b47ce1c5ef82eb5", + seedStr); + + Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); + assertEquals("xprv9s21ZrQH143K2yA9Cdad5gjqHRC7apVUgEyYq5jXeXigDZ3PfEnps44tJprtMXr7PZivEsin6Qrbad7PuiEy4tn5jAEK6A3U46f9KvfRCmD", + Base58.encode(addChecksum(serializePrivate(masterKeypair)))); + + Bip32ECKeyPair bip44Keypair = Bip44WalletUtils.generateBip44KeyPair(masterKeypair, true); + + assertEquals("xprv9zhLxq63By3SX5hAMKnxjGy7L18bnn7GzDQv53eYYqeRX9M82riC1dqovamttwFpk2ZkDQxgcikBQzs1DTu2KShJJqnqgx83EftUB3k39uc", + Base58.encode(addChecksum(serializePrivate(bip44Keypair)))); + assertEquals("xpub6DghNLcw2LbjjZmdTMKy6Quqt2y6CEq8MSLWsS4A7BBQPwgGaQ2SZSAHmsrqBVxLegjW2mBfcvDBhpeEqCmucTTPJiNLHQkiDuKwHs9gEtk", + Base58.encode(addChecksum(serializePublic(bip44Keypair)))); + } + + @Test + public void testGenerateBip44Wallets() throws Exception { + Bip39Wallet wallet = Bip44WalletUtils.generateBip44Wallet(PASSWORD, tempDir); + byte[] seed = MnemonicUtils.generateSeed(wallet.getMnemonic(), PASSWORD); + Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); + Bip32ECKeyPair bip44Keypair = Bip44WalletUtils.generateBip44KeyPair(masterKeypair); + Credentials credentials = Credentials.create(bip44Keypair); + + assertEquals(credentials, + Bip44WalletUtils.loadBip44Credentials(PASSWORD, wallet.getMnemonic())); + } + + private String bytesToHex(byte[] bytes) { + final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); + + char[] chars = new char[2 * bytes.length]; + for (int i = 0; i < bytes.length; ++i) { + chars[2 * i] = HEX_CHARS[(bytes[i] & 0xF0) >>> 4]; + chars[2 * i + 1] = HEX_CHARS[bytes[i] & 0x0F]; + } + return new String(chars); + } +} \ No newline at end of file diff --git a/crypto/src/test/java/com/platon/crypto/SignTest.java b/crypto/src/test/java/com/platon/crypto/SignTest.java index 8e800ec7..60d6ef35 100644 --- a/crypto/src/test/java/com/platon/crypto/SignTest.java +++ b/crypto/src/test/java/com/platon/crypto/SignTest.java @@ -1,6 +1,7 @@ package com.platon.crypto; import com.platon.utils.Numeric; +import org.bouncycastle.math.ec.ECPoint; import org.junit.Ignore; import org.junit.Test; @@ -49,4 +50,11 @@ public void testInvalidSignature() throws SignatureException { Sign.signedMessageToKey( TEST_MESSAGE, new Sign.SignatureData((byte) 27, new byte[]{1}, new byte[]{0})); } + + @Test + public void testPublicKeyFromPrivatePoint() { + ECPoint point = Sign.publicPointFromPrivate(SampleKeys.PRIVATE_KEY); + assertThat(Sign.publicFromPoint(point.getEncoded(false)), + equalTo(SampleKeys.PUBLIC_KEY)); + } } diff --git a/crypto/src/test/java/com/platon/crypto/WalletUtilsTest.java b/crypto/src/test/java/com/platon/crypto/WalletUtilsTest.java index 27946df6..077aa0a1 100644 --- a/crypto/src/test/java/com/platon/crypto/WalletUtilsTest.java +++ b/crypto/src/test/java/com/platon/crypto/WalletUtilsTest.java @@ -207,7 +207,7 @@ public void testGetTestnetKeyDirectory() { } - private static File createTempDir() throws Exception { + static File createTempDir() throws Exception { return Files.createTempDirectory( WalletUtilsTest.class.getSimpleName() + "-testkeys").toFile(); } diff --git a/utils/src/main/java/com/platon/crypto/Hash.java b/utils/src/main/java/com/platon/crypto/Hash.java index 9eeea0fe..0bc583d7 100644 --- a/utils/src/main/java/com/platon/crypto/Hash.java +++ b/utils/src/main/java/com/platon/crypto/Hash.java @@ -1,6 +1,10 @@ package com.platon.crypto; import com.platon.utils.Numeric; +import org.bouncycastle.crypto.digests.RIPEMD160Digest; +import org.bouncycastle.crypto.digests.SHA512Digest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.jcajce.provider.digest.Keccak; import java.nio.charset.StandardCharsets; @@ -74,4 +78,22 @@ public static byte[] sha256(byte[] input) { throw new RuntimeException("Couldn't find a SHA-256 provider", e); } } + + public static byte[] hmacSha512(byte[] key, byte[] input) { + HMac hMac = new HMac(new SHA512Digest()); + hMac.init(new KeyParameter(key)); + hMac.update(input, 0, input.length); + byte[] out = new byte[64]; + hMac.doFinal(out, 0); + return out; + } + + public static byte[] sha256hash160(byte[] input) { + byte[] sha256 = sha256(input); + RIPEMD160Digest digest = new RIPEMD160Digest(); + digest.update(sha256, 0, sha256.length); + byte[] out = new byte[20]; + digest.doFinal(out, 0); + return out; + } } From 23e4064ee6d8f9a307200a309f813549be0b61b1 Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Thu, 12 Aug 2021 10:05:13 +0800 Subject: [PATCH 3/8] =?UTF-8?q?toString=E6=96=B9=E6=B3=95=E5=8C=85?= =?UTF-8?q?=E5=90=AB=E6=95=8F=E6=84=9F=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crypto/src/main/java/com/platon/crypto/Bip39Wallet.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crypto/src/main/java/com/platon/crypto/Bip39Wallet.java b/crypto/src/main/java/com/platon/crypto/Bip39Wallet.java index b4edb416..51fa9cd8 100644 --- a/crypto/src/main/java/com/platon/crypto/Bip39Wallet.java +++ b/crypto/src/main/java/com/platon/crypto/Bip39Wallet.java @@ -28,11 +28,4 @@ public String getMnemonic() { return mnemonic; } - @Override - public String toString() { - return "Bip39Wallet{" - + "filename='" + filename + '\'' - + ", mnemonic='" + mnemonic + '\'' - + '}'; - } } From 4dae4092a4ef0d4cff034322185d6ad70da2662b Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Thu, 12 Aug 2021 17:29:47 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E6=9B=B4=E6=96=B00.16.1.0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- doc/Java-SDK-en.md | 126 +++++++++++++++++++++++++++++++++++++++++++-- doc/Java-SDK-zh.md | 126 +++++++++++++++++++++++++++++++++++++++++++-- gradle.properties | 2 +- 4 files changed, 249 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index de1e4028..44898865 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ com.alaya.sdk core - 0.16.0.0 + 0.16.1.0 ``` or ``` -compile "com.alaya.sdk:core:0.16.0.0" +compile "com.alaya.sdk:core:0.16.1.0" ``` * use in project diff --git a/doc/Java-SDK-en.md b/doc/Java-SDK-en.md index 76932c4d..057e3da6 100644 --- a/doc/Java-SDK-en.md +++ b/doc/Java-SDK-en.md @@ -16,7 +16,7 @@ Depending on the build tool, use the following methods to add related dependenci > Project configuration: ```xml - platon-public + alaya-public https://sdk.platon.network/nexus/content/groups/public/ ``` @@ -26,7 +26,7 @@ Depending on the build tool, use the following methods to add related dependenci com.alaya.sdk core - 0.16.0.0 + 0.16.1.0 ``` @@ -41,7 +41,7 @@ repositories { > gradle way of reference: ``` -compile "com.alaya.sdk:core:0.16.0.0" +compile "com.alaya.sdk:core:0.16.1.0" ``` ## Basic API Usage @@ -253,6 +253,78 @@ Request request = currentValidWeb3j.netPeerCount(); BigInteger req = request.send().getQuantity(); ``` +### adminAddPeer + +> Add a peer to the client node + +* **parameters** + + String : peer's URL + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse property is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.adminAddPeer("enode://0abaf3219f454f3d07b6cbcf3c10b6b4ccf605202868e2043b6f5db12b745df0604ef01ef4cb523adc6d9e14b83a76dd09f862e3fe77205d8ac83df707969b47@[::]:16789"); +Boolean resp = request.send().getResult(); +``` + +### adminRemovePeer + +> Remove a peer from the client node + +* **parameters** + + String : peer's URL + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse property is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.adminRemovePeer("enode://0abaf3219f454f3d07b6cbcf3c10b6b4ccf605202868e2043b6f5db12b745df0604ef01ef4cb523adc6d9e14b83a76dd09f862e3fe77205d8ac83df707969b47@[::]:16789"); +Boolean resp = request.send().getResult(); +``` + +### adminDataDir + +> Return the current node data directory + +* **parameters** + + no + +* **return value** + +```java +Request +``` + +The result in the AdminDataDir property is the corresponding storage data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.adminDataDir(); +String resp = request.send().getDataDir(); +``` + ### platonProtocolVersion > Returns the current platon protocol version @@ -1154,6 +1226,54 @@ Request request = currentValidWeb3j.dbGetHex(databaseName, keyName) String req = request.send(). GetStoredValue(); ``` +### txPoolStatus + +> Returns the status of the transaction pool + +* **parameters** + + no + +* **return value** + +```java +Request +``` + +The result in the TxPoolStatus property is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.txPoolStatus(); +TxPoolStatus txPoolStatus = request.send(); +``` + +### txPoolContent + +> Return transaction pool content + +* **parameters** + + no + +* **return value** + +```java +Request +``` + +The result in the TxPoolContent property is the corresponding storage data + +* **Example** + +```java +JsonRpc2_0Admin platonWeb3j = new JsonRpc2_0Admin(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.txPoolContent(); +TxPoolContent txPoolContent = request.send(); +``` + ### platonEvidences > Return double sign report data diff --git a/doc/Java-SDK-zh.md b/doc/Java-SDK-zh.md index 1dc6654a..edf1707a 100644 --- a/doc/Java-SDK-zh.md +++ b/doc/Java-SDK-zh.md @@ -16,7 +16,7 @@ sidebar_label: Java SDK > 项目配置: ```xml - platon-public + alaya-public https://sdk.platon.network/nexus/content/groups/public/ ``` @@ -26,7 +26,7 @@ sidebar_label: Java SDK com.alaya.sdk core - 0.16.0.0 + 0.16.1.0 ``` @@ -41,7 +41,7 @@ repositories { > gradle引用方式: ``` -compile "com.alaya.sdk:core:0.16.0.0" +compile "com.alaya.sdk:core:0.16.1.0" ``` ## 基础api使用 @@ -252,6 +252,78 @@ Request request = currentValidWeb3j.netPeerCount(); BigInteger req = request.send().getQuantity(); ``` +### adminAddPeer + +> 新增一个端点到客户端节点 + +* **参数** + + String :端点URL + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.adminAddPeer("enode://0abaf3219f454f3d07b6cbcf3c10b6b4ccf605202868e2043b6f5db12b745df0604ef01ef4cb523adc6d9e14b83a76dd09f862e3fe77205d8ac83df707969b47@[::]:16789"); +Boolean resp = request.send().getResult(); +``` + +### adminRemovePeer + +> 从客户端节点移除一个端点 + +* **参数** + + String :端点URL + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.adminRemovePeer("enode://0abaf3219f454f3d07b6cbcf3c10b6b4ccf605202868e2043b6f5db12b745df0604ef01ef4cb523adc6d9e14b83a76dd09f862e3fe77205d8ac83df707969b47@[::]:16789"); +Boolean resp = request.send().getResult(); +``` + +### adminDataDir + +> 返回当前节点数据目录 + +* **参数** + + ​ 无 + +* **返回值** + +```java +Request +``` + +AdminDataDir属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.adminDataDir(); +String resp = request.send().getDataDir(); +``` + ### platonProtocolVersion > 返回当前platon协议版本 @@ -1153,6 +1225,54 @@ Request request = currentValidWeb3j.dbGetHex(databaseName,keyName) String req = request.send().getStoredValue(); ``` +### txPoolStatus + +> 返回交易池状态 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +TxPoolStatus属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.txPoolStatus(); +TxPoolStatus txPoolStatus = request.send(); +``` + +### txPoolContent + +> 返回交易池内容 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +TxPoolContent属性中的result即为对应存储数据 + +* **示例** + +```java +JsonRpc2_0Admin platonWeb3j = new JsonRpc2_0Admin(new HttpService("http://127.0.0.1:6789")); +Request request = platonWeb3j.txPoolContent(); +TxPoolContent txPoolContent = request.send(); +``` + ### platonEvidences > 返回双签举报数据 diff --git a/gradle.properties b/gradle.properties index 56048813..d48a1ed8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.alaya.sdk -version=0.16.0.0 +version=0.16.1.0 mavenReleases=https://sdk.platon.network/nexus/content/repositories/releases/ mavenSnapshots=https://sdk.platon.network/nexus/content/repositories/snapshots/ systemProp.sonar.host.url=http://192.168.16.173:9000 From 2d9aadf66cc036744f8d96d101aa5a5cfb7e2019 Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Fri, 13 Aug 2021 10:49:22 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9HD=E9=92=B1=E5=8C=85?= =?UTF-8?q?=E7=9A=84=E5=B8=81=E7=A7=8D=E4=BB=A3=E7=A0=81=E4=B8=BA206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/platon/crypto/Bip32ECKeyPair.java | 2 +- .../com/platon/crypto/Bip44WalletUtils.java | 4 ++-- .../platon/crypto/Bip44WalletUtilsTest.java | 21 +++++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java b/crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java index 0bdedd7c..41a75548 100644 --- a/crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java +++ b/crypto/src/main/java/com/platon/crypto/Bip32ECKeyPair.java @@ -69,7 +69,7 @@ public static Bip32ECKeyPair deriveKeyPair(Bip32ECKeyPair master, int[] path) { return curr; } - private Bip32ECKeyPair deriveChildKey(int childNumber) { + public Bip32ECKeyPair deriveChildKey(int childNumber) { if (!hasPrivateKey()) { byte[] parentPublicKey = getPublicKeyPoint().getEncoded(true); ByteBuffer data = ByteBuffer.allocate(37); diff --git a/crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java b/crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java index 82d99c5e..ce81769a 100644 --- a/crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java +++ b/crypto/src/main/java/com/platon/crypto/Bip44WalletUtils.java @@ -58,8 +58,8 @@ public static Bip32ECKeyPair generateBip44KeyPair(Bip32ECKeyPair master, boolean final int[] path = {44 | HARDENED_BIT, 0 | HARDENED_BIT, 0 | HARDENED_BIT, 0}; return Bip32ECKeyPair.deriveKeyPair(master, path); } else { - // m/44'/60'/0'/0 - final int[] path = {44 | HARDENED_BIT, 60 | HARDENED_BIT, 0 | HARDENED_BIT, 0}; + // m/44'/206'/0'/0 + final int[] path = {44 | HARDENED_BIT, 206 | HARDENED_BIT, 0 | HARDENED_BIT, 0}; return Bip32ECKeyPair.deriveKeyPair(master, path); } } diff --git a/crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java b/crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java index 1c05e4d9..8f495747 100644 --- a/crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java +++ b/crypto/src/test/java/com/platon/crypto/Bip44WalletUtilsTest.java @@ -1,6 +1,8 @@ package com.platon.crypto; +import com.platon.parameters.NetworkParameters; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -43,12 +45,27 @@ public void generateBip44KeyPair() { Bip32ECKeyPair bip44Keypair = Bip44WalletUtils.generateBip44KeyPair(masterKeypair); - assertEquals("xprv9zvpunws9gusoXVkmqAXWQm5z5hjR5kY3ifRGL7M8Kpjn8kRhavkGnFLjnFWPGGS2gAUw8rP33Lmj6SwZUpwy2mn2fXRYWzGa9WRTnE8DPz", + assertEquals("xprvA1UQTpt1bAYoZCH2VvKERY9jEAoGQZiPdrZWg5Z4YoCGcBeSgri9JwdVsUM5HGbhZ9UmcGeSW2MNZCtJwXofUHB5KcswT1Sgr3sDfQhhZqK", Base58.encode(addChecksum(serializePrivate(bip44Keypair)))); - assertEquals("xpub6DvBKJUkz4UB21aDsrhXsYhpY7YDpYUPQwb24iWxgfMiew5aF8EzpaZpb567bYYbMfUnPwFNuRYvVpMGQUcaGPMoXUEUZKFvx7LaU5b7zBD", + assertEquals("xpub6ETksLQuRY76mgMVbwrEng6TnCdkp2SF15V7UTxg78jFUyybEQ2Prjwyimr7fPdjGz6UugnjXL4Gt8gNAocgFTtKjRp6azm9NCh7DDpE5oR", Base58.encode(addChecksum(serializePublic(bip44Keypair)))); } + @SuppressWarnings("checkstyle:LineLength") + @Test + public void deriveChildKey() { + NetworkParameters.selectAlaya(); + String mnemonic = "spider elbow fossil truck deal circle divert sleep safe report laundry above"; + byte[] seed = MnemonicUtils.generateSeed(mnemonic, null); + Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); + Bip32ECKeyPair bip44Keypair = Bip44WalletUtils.generateBip44KeyPair(masterKeypair); + for (int i = 0; i < 1; i++) { + Bip32ECKeyPair bip32ECKeyPair = bip44Keypair.deriveChildKey(i); + String address = Credentials.create(bip32ECKeyPair).getAddress(); + Assert.assertEquals("atp17rp5teqsstfnn8w80vvmthepn78p0dnn07f9rz",address); + } + } + @SuppressWarnings("checkstyle:LineLength") @Test public void generateBip44KeyPairTestNet() { From 4a52be1b57d1430a204647a6d3327b8c3bb0d0bf Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Fri, 17 Sep 2021 14:15:13 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=83=A8=E5=88=86RPC?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platon/contracts/ppos/BaseContract.java | 85 ++++------ .../java/com/platon/protocol/admin/Admin.java | 50 ++++-- .../protocol/admin/JsonRpc2_0Admin.java | 98 +++++++++-- .../methods/response/PersonalEcRecover.java | 11 ++ .../response/PersonalImportRawKey.java | 11 ++ .../methods/response/PersonalListWallets.java | 63 +++++++ .../platon/protocol/core/JsonRpc2_0Web3j.java | 159 +++++++++++++++++- .../java/com/platon/protocol/core/Platon.java | 40 ++++- .../core/methods/response/AdminNodeInfo.java | 107 ++++++++++++ .../methods/response/AdminPeerEvents.java | 12 ++ .../core/methods/response/AdminPeers.java | 95 +++++++++++ .../response/DebugWaitSlashingNodeList.java | 14 ++ .../methods/response/PlatonGetAddressHrp.java | 7 + .../response/PlatonRawTransaction.java | 8 + .../response/PlatonSignTransaction.java | 33 ++++ .../response/bean/WaitSlashingNode.java | 53 ++++++ .../protocol/websocket/WebSocketService.java | 78 +-------- .../src/main/java/com/platon/tx/Contract.java | 29 +++- .../tx/exceptions/PlatonCallException.java | 44 +++++ .../PlatonCallTimeoutException.java | 44 +++++ .../java/com/platon/crypto/WalletUtils.java | 59 ++----- .../protocol/core/JsonRpc2_0Web3jTest.java | 76 +++++++++ 22 files changed, 960 insertions(+), 216 deletions(-) create mode 100644 core/src/main/java/com/platon/protocol/admin/methods/response/PersonalEcRecover.java create mode 100644 core/src/main/java/com/platon/protocol/admin/methods/response/PersonalImportRawKey.java create mode 100644 core/src/main/java/com/platon/protocol/admin/methods/response/PersonalListWallets.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/AdminNodeInfo.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/AdminPeerEvents.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/AdminPeers.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/DebugWaitSlashingNodeList.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/PlatonGetAddressHrp.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/PlatonRawTransaction.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/PlatonSignTransaction.java create mode 100644 core/src/main/java/com/platon/protocol/core/methods/response/bean/WaitSlashingNode.java create mode 100644 core/src/main/java/com/platon/tx/exceptions/PlatonCallException.java create mode 100644 core/src/main/java/com/platon/tx/exceptions/PlatonCallTimeoutException.java create mode 100644 integration-tests/src/test/java/com/platon/protocol/core/JsonRpc2_0Web3jTest.java diff --git a/core/src/main/java/com/platon/contracts/ppos/BaseContract.java b/core/src/main/java/com/platon/contracts/ppos/BaseContract.java index 66f35ff6..d0292a32 100644 --- a/core/src/main/java/com/platon/contracts/ppos/BaseContract.java +++ b/core/src/main/java/com/platon/contracts/ppos/BaseContract.java @@ -1,5 +1,4 @@ package com.platon.contracts.ppos; - import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.annotation.JSONField; import com.platon.contracts.ppos.abi.Function; @@ -30,56 +29,47 @@ import com.platon.tx.ReadonlyTransactionManager; import com.platon.tx.TransactionManager; import com.platon.tx.exceptions.ContractCallException; +import com.platon.tx.exceptions.PlatonCallException; +import com.platon.tx.exceptions.PlatonCallTimeoutException; import com.platon.tx.gas.ContractGasProvider; import com.platon.tx.gas.GasProvider; import com.platon.utils.JSONUtil; import com.platon.utils.Numeric; +import com.platon.utils.Strings; import org.bouncycastle.util.encoders.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.io.IOException; import java.math.BigInteger; import java.util.Collections; import java.util.List; - - /** * 内置合约基类 * @author chendai */ public abstract class BaseContract extends ManagedTransaction { - private static final Logger log = LoggerFactory.getLogger(BaseContract.class); - protected String contractAddress; protected TransactionReceipt transactionReceipt; - protected BaseContract(String contractAddress, Web3j web3j, TransactionManager transactionManager) { super(web3j, transactionManager); this.contractAddress = contractAddress; } - protected BaseContract(String contractAddress, Web3j web3j, Credentials credentials) { this(contractAddress, web3j, new RawTransactionManager(web3j, credentials)); } - protected BaseContract(String contractAddress, Web3j web3j) { this(contractAddress, web3j, new ReadonlyTransactionManager(web3j, contractAddress)); } - public String getContractAddress() { return contractAddress; } - protected RemoteCall> executeRemoteCallObjectValueReturn(Function function, Class returnType) { return new RemoteCall<>(() -> executeCallObjectValueReturn(function, returnType)); } - protected RemoteCall>> executeRemoteCallListValueReturn(Function function, Class returnType) { return new RemoteCall<>(() -> executeCallListValueReturn(function, returnType)); } - private CallResponse executeCallObjectValueReturn(Function function, Class returnType) throws IOException { PlatonCall ethCall = web3j.platonCall( Transaction.createEthCallTransaction( @@ -87,16 +77,28 @@ private CallResponse executeCallObjectValueReturn(Function function, Clas DefaultBlockParameterName.LATEST) .send(); + //判断底层返回的错误信息是否包含超时信息 + if(ethCall.hasError()){ + Response.Error error = ethCall.getError(); + String message = error.getMessage(); + String lowMessage = !Strings.isBlank(message)? message.toLowerCase() : null; + //包含timeout则抛超时异常,其他错误则直接抛出runtime异常 + if(!Strings.isBlank(lowMessage) + && lowMessage.contains("timeout")){ + throw new PlatonCallTimeoutException(error.getCode(),error.getMessage(),ethCall); + } else { + throw new PlatonCallException(error.getCode(),error.getMessage(),ethCall); + } + } + String result = Numeric.cleanHexPrefix(ethCall.getValue()); if(result==null || "".equals(result)){ throw new ContractCallException("Empty value (0x) returned from contract"); } - CallRet callRet = JSONUtil.parseObject(new String(Hex.decode(result)), CallRet.class); if (callRet == null) { throw new ContractCallException("Unable to convert response: " + result); } - CallResponse callResponse = new CallResponse(); if (callRet.isStatusOk()) { callResponse.setCode(callRet.getCode()); @@ -111,7 +113,6 @@ private CallResponse executeCallObjectValueReturn(Function function, Clas } return callResponse; } - private BigInteger numberDecoder(Object number) { if(number instanceof String) { String numberStr = (String)number; @@ -123,7 +124,6 @@ private BigInteger numberDecoder(Object number) { throw new MessageDecodingException("Can not decode number value = " + number); } } - private CallResponse> executeCallListValueReturn(Function function, Class returnType) throws IOException { PlatonCall ethCall = web3j.platonCall( Transaction.createEthCallTransaction( @@ -131,16 +131,28 @@ private CallResponse> executeCallListValueReturn(Function function, DefaultBlockParameterName.LATEST) .send(); + //判断底层返回的错误信息是否包含超时信息 + if(ethCall.hasError()){ + Response.Error error = ethCall.getError(); + String message = error.getMessage(); + String lowMessage = !Strings.isBlank(message)? message.toLowerCase() : null; + //包含timeout则抛超时异常,其他错误则直接抛出runtime异常 + if(!Strings.isBlank(lowMessage) + && lowMessage.contains("timeout")){ + throw new PlatonCallTimeoutException(error.getCode(),error.getMessage(),ethCall); + } else { + throw new PlatonCallException(error.getCode(),error.getMessage(),ethCall); + } + } + String result = Numeric.cleanHexPrefix(ethCall.getValue()); if(result==null || "".equals(result)){ throw new ContractCallException("Empty value (0x) returned from contract"); } - CallRet callRet = JSONUtil.parseObject(new String(Hex.decode(result)), CallRet.class); if (callRet == null) { throw new ContractCallException("Unable to convert response: " + result); } - CallResponse> callResponse = new CallResponse>(); if (callRet.isStatusOk()) { callResponse.setCode(callRet.getCode()); @@ -149,21 +161,17 @@ private CallResponse> executeCallListValueReturn(Function function, callResponse.setCode(callRet.getCode()); callResponse.setErrMsg(callRet.getRet().toString()); } - if(callRet.getCode() == ErrorCode.OBJECT_NOT_FOUND){ callResponse.setCode(ErrorCode.SUCCESS); callResponse.setData(Collections.emptyList()); } - return callResponse; } - public static class CallRet{ @JSONField(name = "Code") private int code; @JSONField(name = "Ret") private Object ret; - public boolean isStatusOk() { return code == ErrorCode.SUCCESS; } @@ -179,42 +187,32 @@ public Object getRet() { public void setRet(Object ret) { this.ret = ret; } - @Override public String toString() { return "CallRet [code=" + code + ", ret=" + ret + "]"; } } - protected RemoteCall executeRemoteCallTransactionStep1(Function function, GasProvider gasProvider) { return new RemoteCall<>(() -> executeTransactionStep1(function, BigInteger.ZERO,gasProvider)); } - protected RemoteCall executeRemoteCallTransactionStep1(Function function) { return new RemoteCall<>(() -> executeTransactionStep1(function, BigInteger.ZERO, getDefaultGasProvider(function))); } - private RemoteCall executeRemoteCallTransactionStep2(PlatonSendTransaction ethSendTransaction) { return new RemoteCall<>(() -> executeTransactionStep2(ethSendTransaction)); } - public RemoteCall getTransactionResponse(PlatonSendTransaction ethSendTransaction){ return executeRemoteCallTransactionStep2(ethSendTransaction); } - protected RemoteCall executeRemoteCallTransaction(Function function) { return new RemoteCall<>(() -> executeTransaction(function, BigInteger.ZERO, getDefaultGasProvider(function))); } - protected RemoteCall executeRemoteCallTransaction(Function function, GasProvider gasProvider) { return new RemoteCall<>(() -> executeTransaction(function, BigInteger.ZERO, gasProvider)); } - - protected GasProvider getDefaultGasProvider(Function function) throws IOException, EstimateGasException { return getDefaultGasProviderRemote(function); } - private GasProvider getDefaultGasProviderRemote(Function function) throws IOException, EstimateGasException { Transaction transaction = Transaction.createEthCallTransaction(transactionManager.getFromAddress(), contractAddress, EncoderUtils.functionEncoder(function)); PlatonEstimateGas platonEstimateGas = web3j.platonEstimateGas(transaction).send(); @@ -232,14 +230,12 @@ private GasProvider getDefaultGasProviderRemote(Function function) throws IOExce BigInteger gasPrice = getDefaultGasPrice(function.getType()); return new ContractGasProvider(gasPrice, gasLimit); } - private GasProvider getDefaultGasProviderLocal(Function function) throws IOException, NoSupportFunctionType { BigInteger gasLimit = EstimateGasUtil.getGasLimit(function); BigInteger gasPrice = getDefaultGasPrice(function.getType()); GasProvider gasProvider = new ContractGasProvider(gasPrice, gasLimit); return gasProvider; } - /** * 获得默认的gasPrice * Alaya_chainID或者Alaya_hrp时,gasPrice缩小100倍, @@ -279,52 +275,35 @@ private BigInteger getDefaultGasPrice(int type) throws IOException { } } } - - - - private TransactionResponse executeTransaction(Function function, BigInteger vonValue, GasProvider gasProvider)throws TransactionException, IOException { - TransactionReceipt receipt = send(contractAddress, EncoderUtils.functionEncoder(function), vonValue, gasProvider.getGasPrice(), gasProvider.getGasLimit()); - return getResponseFromTransactionReceipt(receipt); } - private PlatonSendTransaction executeTransactionStep1(Function function, BigInteger vonValue, GasProvider gasProvider) throws IOException { - return sendPlatonRawTransaction(contractAddress, EncoderUtils.functionEncoder(function), vonValue, gasProvider.getGasPrice(), gasProvider.getGasLimit()); - } - private TransactionResponse executeTransactionStep2(PlatonSendTransaction ethSendTransaction) throws IOException, TransactionException { - TransactionReceipt receipt = getTransactionReceipt(ethSendTransaction); - return getResponseFromTransactionReceipt(receipt); } - private TransactionResponse getResponseFromTransactionReceipt(TransactionReceipt transactionReceipt) throws TransactionException { List logs = transactionReceipt.getLogs(); if(logs==null||logs.isEmpty()){ throw new TransactionException("TransactionReceipt logs is empty"); } - String logData = logs.get(0).getData(); if(null == logData || "".equals(logData) ){ throw new TransactionException("TransactionReceipt log data is empty"); } - RlpList rlp = RlpDecoder.decode(Numeric.hexStringToByteArray(logData)); List rlpList = ((RlpList)(rlp.getValues().get(0))).getValues(); String decodedStatus = new String(((RlpString)rlpList.get(0)).getBytes()); int statusCode = Integer.parseInt(decodedStatus); - TransactionResponse transactionResponse = new TransactionResponse(); transactionResponse.setCode(statusCode); transactionResponse.setTransactionReceipt(transactionReceipt); - return transactionResponse; } -} +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/admin/Admin.java b/core/src/main/java/com/platon/protocol/admin/Admin.java index 99ff7710..eb19e636 100644 --- a/core/src/main/java/com/platon/protocol/admin/Admin.java +++ b/core/src/main/java/com/platon/protocol/admin/Admin.java @@ -2,44 +2,60 @@ import com.platon.protocol.Web3j; import com.platon.protocol.Web3jService; -import com.platon.protocol.admin.methods.response.NewAccountIdentifier; -import com.platon.protocol.admin.methods.response.PersonalListAccounts; -import com.platon.protocol.admin.methods.response.PersonalUnlockAccount; -import com.platon.protocol.admin.methods.response.TxPoolContent; +import com.platon.protocol.admin.methods.response.*; import com.platon.protocol.core.Request; +import com.platon.protocol.core.methods.response.PlatonSignTransaction; import com.platon.protocol.core.methods.request.Transaction; import com.platon.protocol.core.methods.response.PlatonSendTransaction; +import com.platon.protocol.core.methods.response.VoidResponse; import java.math.BigInteger; import java.util.concurrent.ScheduledExecutorService; /** - * JSON-RPC Request object building factory for common Parity and Geth. + * JSON-RPC Request object building factory for common Parity and Geth. */ public interface Admin extends Web3j { static Admin build(Web3jService web3jService) { return new JsonRpc2_0Admin(web3jService); } - + static Admin build( Web3jService web3jService, long pollingInterval, ScheduledExecutorService scheduledExecutorService) { return new JsonRpc2_0Admin(web3jService, pollingInterval, scheduledExecutorService); } - public Request personalListAccounts(); - - public Request personalNewAccount(String password); - - public Request personalUnlockAccount( + Request personalImportRawKey(String keydata, String password); + + Request personalLockAccount(String accountId); + + Request personalSign(String message, String accountId, String password); + + Request personalSignAndSendTransaction(Transaction transaction, String password); + + Request personalSignTransaction(Transaction transaction, String password); + + Request personalEcRecover(String message, String signiture); + + Request personalListWallets(); + + Request personalListAccounts(); + + Request personalNewAccount(String password); + + Request personalUnlockAccount( String address, String passphrase, BigInteger duration); - - public Request personalUnlockAccount( + + Request personalUnlockAccount( String address, String passphrase); - - public Request personalSendTransaction( + + Request personalOpenWallet(String url, String passphrase); + + Request personalSendTransaction( Transaction transaction, String password); - public Request txPoolContent(); -} + Request txPoolContent(); + +} diff --git a/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java b/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java index 21675f81..fb52eec2 100644 --- a/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java +++ b/core/src/main/java/com/platon/protocol/admin/JsonRpc2_0Admin.java @@ -1,14 +1,13 @@ package com.platon.protocol.admin; import com.platon.protocol.Web3jService; -import com.platon.protocol.admin.methods.response.NewAccountIdentifier; -import com.platon.protocol.admin.methods.response.PersonalListAccounts; -import com.platon.protocol.admin.methods.response.PersonalUnlockAccount; -import com.platon.protocol.admin.methods.response.TxPoolContent; +import com.platon.protocol.admin.methods.response.*; import com.platon.protocol.core.JsonRpc2_0Web3j; import com.platon.protocol.core.Request; +import com.platon.protocol.core.methods.response.PlatonSignTransaction; import com.platon.protocol.core.methods.request.Transaction; import com.platon.protocol.core.methods.response.PlatonSendTransaction; +import com.platon.protocol.core.methods.response.VoidResponse; import java.math.BigInteger; import java.util.ArrayList; @@ -25,12 +24,78 @@ public class JsonRpc2_0Admin extends JsonRpc2_0Web3j implements Admin { public JsonRpc2_0Admin(Web3jService web3jService) { super(web3jService); } - + public JsonRpc2_0Admin(Web3jService web3jService, long pollingInterval, - ScheduledExecutorService scheduledExecutorService) { + ScheduledExecutorService scheduledExecutorService) { super(web3jService, pollingInterval, scheduledExecutorService); } + @Override + public Request personalImportRawKey(String keydata, String password) { + return new Request<>( + "personal_importRawKey", + Arrays.asList(keydata, password), + web3jService, + PersonalImportRawKey.class); + } + + @Override + public Request personalLockAccount(String accountId) { + return new Request<>( + "personal_lockAccount", + Arrays.asList(accountId), + web3jService, + BooleanResponse.class); + } + + @Override + public Request personalSign( + String message, String accountId, String password) { + return new Request<>( + "personal_sign", + Arrays.asList(message, accountId, password), + web3jService, + PersonalSign.class); + } + + @Override + public Request personalSignAndSendTransaction( + Transaction transaction, String password) { + return new Request<>( + "personal_signAndSendTransaction", + Arrays.asList(transaction, password), + web3jService, + PersonalSign.class); + } + + @Override + public Request personalSignTransaction(Transaction transaction, String password) { + return new Request<>( + "personal_signTransaction", + Arrays.asList(transaction, password), + web3jService, + PlatonSignTransaction.class); + } + + @Override + public Request personalEcRecover( + String hexMessage, String signedMessage) { + return new Request<>( + "personal_ecRecover", + Arrays.asList(hexMessage, signedMessage), + web3jService, + PersonalEcRecover.class); + } + + @Override + public Request personalListWallets() { + return new Request<>( + "personal_listWallets", + Collections.emptyList(), + web3jService, + PersonalListWallets.class); + } + @Override public Request personalListAccounts() { return new Request<>( @@ -47,7 +112,7 @@ public Request personalNewAccount(String password) { Arrays.asList(password), web3jService, NewAccountIdentifier.class); - } + } @Override public Request personalUnlockAccount( @@ -56,7 +121,7 @@ public Request personalUnlockAccount( List attributes = new ArrayList<>(3); attributes.add(accountId); attributes.add(password); - + if (duration != null) { // Parity has a bug where it won't support a duration // See https://github.com/ethcore/parity/issues/1215 @@ -65,21 +130,30 @@ public Request personalUnlockAccount( // we still need to include the null value, otherwise Parity rejects request attributes.add(null); } - + return new Request<>( "personal_unlockAccount", attributes, web3jService, PersonalUnlockAccount.class); } - + @Override public Request personalUnlockAccount( String accountId, String password) { - + return personalUnlockAccount(accountId, password, null); } - + + @Override + public Request personalOpenWallet(String url, String passphrase) { + return new Request<>( + "personal_openWallet", + Arrays.asList(url, passphrase), + web3jService, + VoidResponse.class); + } + @Override public Request personalSendTransaction( Transaction transaction, String passphrase) { diff --git a/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalEcRecover.java b/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalEcRecover.java new file mode 100644 index 00000000..25769b5c --- /dev/null +++ b/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalEcRecover.java @@ -0,0 +1,11 @@ +package com.platon.protocol.admin.methods.response; + + +import com.platon.protocol.core.Response; + +/** personal_ecRecover. */ +public class PersonalEcRecover extends Response { + public String getRecoverAccountId() { + return getResult(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalImportRawKey.java b/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalImportRawKey.java new file mode 100644 index 00000000..121315f3 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalImportRawKey.java @@ -0,0 +1,11 @@ +package com.platon.protocol.admin.methods.response; + + +import com.platon.protocol.core.Response; + +/** personal_importRawKey. */ +public class PersonalImportRawKey extends Response { + public String getAccountId() { + return getResult(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalListWallets.java b/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalListWallets.java new file mode 100644 index 00000000..695d6ed9 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/admin/methods/response/PersonalListWallets.java @@ -0,0 +1,63 @@ +package com.platon.protocol.admin.methods.response; + +import com.platon.protocol.core.Response; + +import java.util.List; + +/** personal_listWallets **/ +public class PersonalListWallets extends Response> { + + + public static class Wallet { + private List accounts; + private String status; + private String url; + + public List getAccounts() { + return accounts; + } + + public void setAccounts(List accounts) { + this.accounts = accounts; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + } + + public static class Account { + private String url; + private String address; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java b/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java index 5926d895..f386b3fd 100644 --- a/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java +++ b/core/src/main/java/com/platon/protocol/core/JsonRpc2_0Web3j.java @@ -5,6 +5,8 @@ import com.platon.protocol.admin.methods.response.BooleanResponse; import com.platon.protocol.admin.methods.response.TxPoolStatus; import com.platon.protocol.admin.methods.response.admin.AdminDataDir; +import com.platon.protocol.core.methods.response.DebugWaitSlashingNodeList; +import com.platon.protocol.core.methods.response.PlatonSignTransaction; import com.platon.protocol.core.methods.request.ShhFilter; import com.platon.protocol.core.methods.response.*; import com.platon.protocol.rx.JsonRpc2_0Rx; @@ -12,6 +14,7 @@ import com.platon.protocol.websocket.events.NewHeadsNotification; import com.platon.utils.Async; import com.platon.utils.Numeric; +import com.platon.utils.Strings; import rx.Observable; import java.io.IOException; @@ -89,6 +92,18 @@ public Request netPeerCount() { NetPeerCount.class); } + @Override + public Request adminNodeInfo() { + return new Request<>( + "admin_nodeInfo", Collections.emptyList(), web3jService, AdminNodeInfo.class); + } + + @Override + public Request adminPeers() { + return new Request<>( + "admin_peers", Collections.emptyList(), web3jService, AdminPeers.class); + } + @Override public Request adminAddPeer(String url) { return new Request<>( @@ -107,6 +122,60 @@ public Request adminDataDir() { "admin_datadir", Collections.emptyList(), web3jService, AdminDataDir.class); } + @Override + public Request adminStartRPC(String host,int port,String cors,String apis) { + if(host == null){ + host = "localhost"; + } + if(cors == null){ + cors = ""; + } + if(Strings.isBlank(apis)){ + apis = "platon,net,web3,debug,admin"; + } + return new Request<>( + "admin_startRPC", Arrays.asList(host,port,cors,apis), web3jService, BooleanResponse.class); + } + + @Override + public Request adminStartWS(String host,int port,String cors,String apis) { + if(host == null){ + host = "localhost"; + } + if(cors == null){ + cors = ""; + } + if(Strings.isBlank(apis)){ + apis = "platon,net,web3,debug,admin"; + } + return new Request<>( + "admin_startWS", Arrays.asList(host,port,cors,apis), web3jService, BooleanResponse.class); + } + + @Override + public Request adminStopRPC() { + return new Request<>( + "admin_stopRPC", Collections.emptyList(), web3jService, BooleanResponse.class); + } + + @Override + public Request adminStopWS() { + return new Request<>( + "admin_stopWS", Collections.emptyList(), web3jService, BooleanResponse.class); + } + + @Override + public Request adminExportChain(String file) { + return new Request<>( + "admin_exportChain", Arrays.asList(file), web3jService, BooleanResponse.class); + } + + @Override + public Request adminImportChain(String file) { + return new Request<>( + "admin_importChain", Arrays.asList(file), web3jService, BooleanResponse.class); + } + @Override public Request platonProtocolVersion() { return new Request<>( @@ -691,32 +760,32 @@ public Request platonEvidences() { PlatonEvidences.class); } - @Override - public Request getProgramVersion() { + @Override + public Request getProgramVersion() { return new Request<>( "admin_getProgramVersion", Collections.emptyList(), web3jService, AdminProgramVersion.class); - } + } - @Override - public Request getSchnorrNIZKProve() { + @Override + public Request getSchnorrNIZKProve() { return new Request<>( "admin_getSchnorrNIZKProve", Collections.emptyList(), web3jService, AdminSchnorrNIZKProve.class); - } + } - @Override - public Request getEconomicConfig() { + @Override + public Request getEconomicConfig() { return new Request<>( "debug_economicConfig", Collections.emptyList(), web3jService, DebugEconomicConfig.class); - } + } @Override public Request getChainId() { @@ -726,4 +795,76 @@ public Request getChainId() { web3jService, PlatonChainId.class); } + + @Override + public Request getWaitSlashingNodeList() { + return new Request<>( + "debug_getWaitSlashingNodeList", + Collections.emptyList(), + web3jService, + DebugWaitSlashingNodeList.class); + } + + @Override + public Request platonGetRawTransactionByHash(String transactionHash) { + return new Request<>( + "platon_getRawTransactionByHash", + Arrays.asList(transactionHash), + web3jService, + PlatonRawTransaction.class); + } + + @Override + public Request platonGetRawTransactionByBlockHashAndIndex(String blockHash, String index) { + return new Request<>( + "platon_getRawTransactionByBlockHashAndIndex", + Arrays.asList(blockHash,index), + web3jService, + PlatonRawTransaction.class); + } + + @Override + public Request platonGetRawTransactionByBlockNumberAndIndex(String blockNumber, String index) { + return new Request<>( + "platon_getRawTransactionByBlockNumberAndIndex", + Arrays.asList(blockNumber,index), + web3jService, + PlatonRawTransaction.class); + } + + @Override + public Request platonGetAddressHrp() { + return new Request<>( + "platon_getAddressHrp", + Collections.emptyList(), + web3jService, + PlatonGetAddressHrp.class); + } + + @Override + public Request platonSignTransaction(com.platon.protocol.core.methods.request.Transaction transaction) { + return new Request<>( + "platon_signTransaction", + Arrays.asList(transaction), + web3jService, + PlatonSignTransaction.class); + } + + @Override + public Request minerSetGasPrice(String minGasPrice) { + return new Request<>( + "miner_setGasPrice", + Arrays.asList(minGasPrice), + web3jService, + BooleanResponse.class); + } + + @Override + public Request adminPeerEvents() { + return new Request<>( + "admin_peerEvents", + Collections.emptyList(), + web3jService, + AdminPeerEvents.class); + } } diff --git a/core/src/main/java/com/platon/protocol/core/Platon.java b/core/src/main/java/com/platon/protocol/core/Platon.java index fe08d633..d27c5fe2 100644 --- a/core/src/main/java/com/platon/protocol/core/Platon.java +++ b/core/src/main/java/com/platon/protocol/core/Platon.java @@ -3,6 +3,8 @@ import com.platon.protocol.admin.methods.response.BooleanResponse; import com.platon.protocol.admin.methods.response.TxPoolStatus; import com.platon.protocol.admin.methods.response.admin.AdminDataDir; +import com.platon.protocol.core.methods.response.DebugWaitSlashingNodeList; +import com.platon.protocol.core.methods.response.PlatonSignTransaction; import com.platon.protocol.core.methods.request.ShhFilter; import com.platon.protocol.core.methods.response.*; @@ -22,12 +24,28 @@ public interface Platon { Request netPeerCount(); + Request adminNodeInfo(); + + Request adminPeers(); + Request adminAddPeer(String url); Request adminRemovePeer(String url); Request adminDataDir(); + Request adminStartRPC(String host,int port,String cors,String apis); + + Request adminStartWS(String host,int port,String cors,String apis); + + Request adminStopRPC(); + + Request adminStopWS(); + + Request adminExportChain(String file); + + Request adminImportChain(String file); + Request platonProtocolVersion(); Request platonSyncing(); @@ -135,12 +153,28 @@ Request shhPost( Request txPoolStatus(); Request platonEvidences(); - + Request getProgramVersion(); - + Request getSchnorrNIZKProve(); - + Request getEconomicConfig(); Request getChainId(); + + Request getWaitSlashingNodeList(); + + Request platonGetRawTransactionByHash(String transactionHash); + + Request platonGetRawTransactionByBlockHashAndIndex(String blockHash, String index); + + Request platonGetRawTransactionByBlockNumberAndIndex(String blockNumber, String index); + + Request platonGetAddressHrp(); + + Request platonSignTransaction(com.platon.protocol.core.methods.request.Transaction transaction); + + Request minerSetGasPrice(String minGasPrice); + + Request adminPeerEvents(); } diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/AdminNodeInfo.java b/core/src/main/java/com/platon/protocol/core/methods/response/AdminNodeInfo.java new file mode 100644 index 00000000..3b04368d --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/AdminNodeInfo.java @@ -0,0 +1,107 @@ +package com.platon.protocol.core.methods.response; + +import java.io.IOException; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.platon.protocol.ObjectMapperFactory; +import com.platon.protocol.core.Response; + + +/** admin_nodeInfo. */ +public class AdminNodeInfo extends Response { + @JsonIgnoreProperties(ignoreUnknown = true) + @Override + @JsonDeserialize(using = AdminNodeInfo.ResponseDeserialiser.class) + public void setResult(NodeInfo result) { + super.setResult(result); + } + + public static class NodeInfo { + private String enode; + private String id; + private String ip; + private String listenAddr; + private String name; + private String consensus; + + @JsonProperty("protocols") + @SuppressWarnings("unchecked") + private void consensusDeserializer(Map protocols) { + if (protocols.containsKey("istanbul")) { + consensus = "istanbul"; + } else if (protocols.containsKey("clique")) { + consensus = "clique"; + } else if (protocols.containsKey("eth")) { + Map eth = (Map) protocols.get("eth"); + consensus = (String) eth.get("consensus"); + } else { + consensus = "unknown"; + } + } + + public NodeInfo() {} + + public NodeInfo( + String enode, + String id, + String ip, + String listenAddr, + String name, + String consensus) { + this.enode = enode; + this.id = id; + this.ip = ip; + this.listenAddr = listenAddr; + this.name = name; + this.consensus = consensus; + } + + public String getEnode() { + return enode; + } + + public String getId() { + return id; + } + + public String getIp() { + return ip; + } + + public String getListenAddr() { + return listenAddr; + } + + public String getName() { + return name; + } + + public String getConsensus() { + return consensus; + } + } + + public static class ResponseDeserialiser extends JsonDeserializer { + + private ObjectReader objectReader = ObjectMapperFactory.getObjectReader(); + + @Override + public NodeInfo deserialize( + JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + if (jsonParser.getCurrentToken() != JsonToken.VALUE_NULL) { + return objectReader.readValue(jsonParser, NodeInfo.class); + } else { + return null; // null is wrapped by Optional in above getter + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/AdminPeerEvents.java b/core/src/main/java/com/platon/protocol/core/methods/response/AdminPeerEvents.java new file mode 100644 index 00000000..9e11df40 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/AdminPeerEvents.java @@ -0,0 +1,12 @@ +package com.platon.protocol.core.methods.response; + +import com.platon.protocol.core.Response; + +/** + * @Author liushuyu + * @Date 2021/9/16 11:37 + * @Version + * @Desc + */ +public class AdminPeerEvents extends Response { +} diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/AdminPeers.java b/core/src/main/java/com/platon/protocol/core/methods/response/AdminPeers.java new file mode 100644 index 00000000..b4b3e329 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/AdminPeers.java @@ -0,0 +1,95 @@ +package com.platon.protocol.core.methods.response; + +import java.io.IOException; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.platon.protocol.ObjectMapperFactory; +import com.platon.protocol.core.Response; + + +/** admin_peers. */ +public class AdminPeers extends Response> { + @JsonIgnoreProperties(ignoreUnknown = true) + @Override + @JsonDeserialize(using = AdminPeers.ResponseDeserializer.class) + public void setResult(List result) { + super.setResult(result); + } + + public static class Peer { + public Peer() {} + + public Peer(String id, String name, String enode, PeerNetwork network) { + this.id = id; + this.name = name; + this.network = network; + this.enode = enode; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getEnode() { + return enode; + } + + private String id; + private String name; + private PeerNetwork network; + private String enode; + + public PeerNetwork getNetwork() { + return network; + } + } + + public static class PeerNetwork { + + public PeerNetwork() {} + + private String localAddress; + private String remoteAddress; + + public PeerNetwork(String localAddress, String remoteAddress) { + this.localAddress = localAddress; + this.remoteAddress = remoteAddress; + } + + public String getLocalAddress() { + return localAddress; + } + + public String getRemoteAddress() { + return remoteAddress; + } + } + + public static class ResponseDeserializer extends JsonDeserializer> { + + private ObjectReader objectReader = ObjectMapperFactory.getObjectReader(); + + @Override + public List deserialize( + JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + if (jsonParser.getCurrentToken() != JsonToken.VALUE_NULL) { + return objectReader.readValue(jsonParser, new TypeReference>() {}); + } else { + return null; // null is wrapped by Optional in above getter + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/DebugWaitSlashingNodeList.java b/core/src/main/java/com/platon/protocol/core/methods/response/DebugWaitSlashingNodeList.java new file mode 100644 index 00000000..db46730d --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/DebugWaitSlashingNodeList.java @@ -0,0 +1,14 @@ +package com.platon.protocol.core.methods.response; + +import com.platon.protocol.core.Response; +import com.platon.protocol.core.methods.response.bean.WaitSlashingNode; +import com.platon.utils.JSONUtil; +import java.util.List; +/** + * debug_getWaitSlashingNodeList + */ +public class DebugWaitSlashingNodeList extends Response { + public List get() { + return JSONUtil.parseArray(getResult(), WaitSlashingNode.class); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/PlatonGetAddressHrp.java b/core/src/main/java/com/platon/protocol/core/methods/response/PlatonGetAddressHrp.java new file mode 100644 index 00000000..2fa79e22 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/PlatonGetAddressHrp.java @@ -0,0 +1,7 @@ +package com.platon.protocol.core.methods.response; + +import com.platon.protocol.core.Response; + + +public class PlatonGetAddressHrp extends Response { +} diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/PlatonRawTransaction.java b/core/src/main/java/com/platon/protocol/core/methods/response/PlatonRawTransaction.java new file mode 100644 index 00000000..aee0d815 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/PlatonRawTransaction.java @@ -0,0 +1,8 @@ +package com.platon.protocol.core.methods.response; + + +import com.platon.protocol.core.Response; + +/** platon_getRawTransactionByHash **/ +public class PlatonRawTransaction extends Response { +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/PlatonSignTransaction.java b/core/src/main/java/com/platon/protocol/core/methods/response/PlatonSignTransaction.java new file mode 100644 index 00000000..7e9e16b6 --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/PlatonSignTransaction.java @@ -0,0 +1,33 @@ +package com.platon.protocol.core.methods.response; + +import com.platon.protocol.core.Response; + +/** + * @Author liushuyu + * @Date 2021/9/16 11:21 + * @Version + * @Desc + */ +public class PlatonSignTransaction extends Response { + + public static class SignTransaction { + private String raw; + private Transaction tx; + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public Transaction getTx() { + return tx; + } + + public void setTx(Transaction tx) { + this.tx = tx; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/core/methods/response/bean/WaitSlashingNode.java b/core/src/main/java/com/platon/protocol/core/methods/response/bean/WaitSlashingNode.java new file mode 100644 index 00000000..b2dfd45a --- /dev/null +++ b/core/src/main/java/com/platon/protocol/core/methods/response/bean/WaitSlashingNode.java @@ -0,0 +1,53 @@ +package com.platon.protocol.core.methods.response.bean; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigInteger; + +/** + * @Author liushuyu + * @Date 2021/7/6 14:38 + * @Version + * @Desc + */ +public class WaitSlashingNode { + + //零出块的节点ID + @JsonProperty("NodeId") + private String nodeId; + //观察期内第一次零出块时所处共识轮数 + @JsonProperty("Round") + private BigInteger round; + //零出块次数位图(从Round开始,1表示该轮未出块) + @JsonProperty("CountBit") + private BigInteger countBit; + public String getNodeId() { + return nodeId; + } + @JsonIgnore + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + public BigInteger getRound() { + return round; + } + @JsonIgnore + public void setRound(BigInteger round) { + this.round = round; + } + public BigInteger getCountBit() { + return countBit; + } + @JsonIgnore + public void setCountBit(BigInteger countBit) { + this.countBit = countBit; + } + @Override + public String toString() { + return "WaitSlashingNode{" + + "nodeId='" + nodeId + '\'' + + ", round=" + round + + ", countBit=" + countBit + + '}'; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java b/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java index 6c67816b..8976df10 100644 --- a/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java +++ b/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java @@ -1,5 +1,4 @@ package com.platon.protocol.websocket; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -17,12 +16,12 @@ import java.io.IOException; import java.net.ConnectException; +import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.Map; import java.util.concurrent.*; - /** * Web socket service that allows to interact with JSON-RPC via WebSocket protocol. * @@ -35,19 +34,15 @@ * request. */ public class WebSocketService implements Web3jService { - private static final Logger log = LoggerFactory.getLogger(WebSocketService.class); - // Timeout for JSON-RPC requests static final long REQUEST_TIMEOUT = 60; - // WebSocket client private final WebSocketClient webSocketClient; // Executor to schedule request timeouts private final ScheduledExecutorService executor; // Object mapper to map incoming JSON objects private final ObjectMapper objectMapper; - // Map of a sent request id to objects necessary to process this request private Map> requestForId = new ConcurrentHashMap<>(); // Map of a sent subscription request id to objects necessary to process @@ -56,16 +51,13 @@ public class WebSocketService implements Web3jService { = new ConcurrentHashMap<>(); // Map of a subscription id to objects necessary to process incoming events private Map> subscriptionForId = new ConcurrentHashMap<>(); - public WebSocketService(String serverUrl, boolean includeRawResponses) { this(new WebSocketClient(parseURI(serverUrl)), includeRawResponses); } - public WebSocketService(WebSocketClient webSocketClient, - boolean includeRawResponses) { + boolean includeRawResponses) { this(webSocketClient, Executors.newScheduledThreadPool(1), includeRawResponses); } - WebSocketService(WebSocketClient webSocketClient, ScheduledExecutorService executor, boolean includeRawResponses) { @@ -73,7 +65,6 @@ public WebSocketService(WebSocketClient webSocketClient, this.executor = executor; this.objectMapper = ObjectMapperFactory.getObjectMapper(includeRawResponses); } - /** * Connect to a WebSocket server. * @@ -88,34 +79,28 @@ public void connect() throws ConnectException { log.warn("Interrupted while connecting via WebSocket protocol"); } } - private void connectToWebSocket() throws InterruptedException, ConnectException { boolean connected = webSocketClient.connectBlocking(); if (!connected) { throw new ConnectException("Failed to connect to WebSocket"); } } - private void setWebSocketListener() { webSocketClient.setListener(new WebSocketListener() { @Override public void onMessage(String message) throws IOException { onWebSocketMessage(message); } - @Override public void onError(Exception e) { log.error("Received error from a WebSocket connection", e); } - @Override public void onClose() { onWebSocketClose(); } }); } - - @Override public T send(Request request, Class responseType) throws IOException { try { @@ -127,16 +112,13 @@ public T send(Request request, Class responseType) throw if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } - throw new RuntimeException("Unexpected exception", e.getCause()); } } - @Override public CompletableFuture sendAsync( Request request, Class responseType) { - CompletableFuture result = new CompletableFuture<>(); long requestId = request.getId(); requestForId.put(requestId, new WebSocketRequest<>(result, responseType)); @@ -145,36 +127,30 @@ public CompletableFuture sendAsync( } catch (IOException e) { closeRequest(requestId, e); } - return result; } - private void sendRequest(Request request, long requestId) throws JsonProcessingException { String payload = objectMapper.writeValueAsString(request); log.debug("Sending request: {}", payload); webSocketClient.send(payload); setRequestTimeout(requestId); } - private void setRequestTimeout(long requestId) { executor.schedule( () -> closeRequest( - requestId, - new IOException( - String.format("Request with id %d timed out", requestId))), + requestId, + new IOException( + String.format("Request with id %d timed out", requestId))), REQUEST_TIMEOUT, TimeUnit.SECONDS); } - void closeRequest(long requestId, Exception e) { CompletableFuture result = requestForId.get(requestId).getOnReply(); requestForId.remove(requestId); result.completeExceptionally(e); } - void onWebSocketMessage(String messageStr) throws IOException { JsonNode replyJson = parseToTree(messageStr); - if (isReply(replyJson)) { processRequestReply(messageStr, replyJson); } else if (isSubscriptionEvent(replyJson)) { @@ -183,7 +159,6 @@ void onWebSocketMessage(String messageStr) throws IOException { throw new IOException("Unknown message type"); } } - private void processRequestReply(String replyStr, JsonNode replyJson) throws IOException { long replyId = getReplyId(replyJson); WebSocketRequest request = getAndRemoveRequest(replyId); @@ -194,13 +169,11 @@ private void processRequestReply(String replyStr, JsonNode replyJson) throws IOE if (reply instanceof PlatonSubscribe) { processSubscriptionResponse(replyId, (PlatonSubscribe) reply); } - sendReplyToListener(request, reply); } catch (IllegalArgumentException e) { sendExceptionToListener(replyStr, request, e); } } - private void processSubscriptionResponse(long replyId, PlatonSubscribe reply) throws IOException { WebSocketSubscription subscription = subscriptionRequestForId.get(replyId); processSubscriptionResponse( @@ -209,7 +182,6 @@ private void processSubscriptionResponse(long replyId, PlatonSubscribe reply) th subscription.getResponseType() ); } - private > void processSubscriptionResponse( PlatonSubscribe subscriptionReply, BehaviorSubject subject, @@ -220,7 +192,6 @@ private > void processSubscriptionResponse( reportSubscriptionError(subject, subscriptionReply); } } - private > void establishSubscription( BehaviorSubject subject, Class responseType, PlatonSubscribe subscriptionReply) { log.info("Subscribed to RPC events with id {}", @@ -229,7 +200,6 @@ private > void establishSubscription( subscriptionReply.getSubscriptionId(), new WebSocketSubscription<>(subject, responseType)); } - private > String getSubscriptionId(BehaviorSubject subject) { return subscriptionForId.entrySet().stream() .filter(entry -> entry.getValue().getSubject() == subject) @@ -237,7 +207,6 @@ private > String getSubscriptionId(BehaviorSubject .findFirst() .orElse(null); } - private > void reportSubscriptionError( BehaviorSubject subject, PlatonSubscribe subscriptionReply) { Response.Error error = subscriptionReply.getError(); @@ -249,11 +218,9 @@ private > void reportSubscriptionError( )) ); } - private void sendReplyToListener(WebSocketRequest request, Object reply) { request.getOnReply().complete(reply); } - private void sendExceptionToListener( String replyStr, WebSocketRequest request, @@ -266,36 +233,29 @@ private void sendExceptionToListener( request.getResponseType()), e)); } - private void processSubscriptionEvent(String replyStr, JsonNode replyJson) { log.info("Processing event: {}", replyStr); String subscriptionId = extractSubscriptionId(replyJson); WebSocketSubscription subscription = subscriptionForId.get(subscriptionId); - if (subscription != null) { sendEventToSubscriber(replyJson, subscription); } else { log.warn("No subscriber for WebSocket event with subscription id {}", subscriptionId); } } - private String extractSubscriptionId(JsonNode replyJson) { return replyJson.get("params").get("subscription").asText(); } - private void sendEventToSubscriber(JsonNode replyJson, WebSocketSubscription subscription) { Object event = objectMapper.convertValue(replyJson, subscription.getResponseType()); subscription.getSubject().onNext(event); } - private boolean isReply(JsonNode replyJson) { return replyJson.has("id"); } - private boolean isSubscriptionEvent(JsonNode replyJson) { return replyJson.has("method"); } - private JsonNode parseToTree(String replyStr) throws IOException { try { return objectMapper.readTree(replyStr); @@ -303,7 +263,6 @@ private JsonNode parseToTree(String replyStr) throws IOException { throw new IOException("Failed to parse incoming WebSocket message", e); } } - private WebSocketRequest getAndRemoveRequest(long id) throws IOException { if (!requestForId.containsKey(id)) { throw new IOException( @@ -314,22 +273,18 @@ private WebSocketRequest getAndRemoveRequest(long id) throws IOException { requestForId.remove(id); return request; } - private long getReplyId(JsonNode replyJson) throws IOException { JsonNode idField = replyJson.get("id"); if (idField == null) { throw new IOException("'id' field is missing in the reply"); } - if (!idField.isIntegralNumber()) { throw new IOException( String.format("'id' expected to be long, but it is: '%s'", idField.asText())); } - return idField.longValue(); } - private static URI parseURI(String serverUrl) { try { return new URI(serverUrl); @@ -337,7 +292,6 @@ private static URI parseURI(String serverUrl) { throw new RuntimeException(String.format("Failed to parse URL: '%s'", serverUrl), e); } } - @Override public > Observable subscribe( Request request, @@ -347,22 +301,17 @@ public > Observable subscribe( // before first client is subscribed and we need to // preserve it BehaviorSubject subject = BehaviorSubject.create(); - // We need to subscribe synchronously, since if we return // an Observable to a client before we got a reply // a client can unsubscribe before we know a subscription // id and this can cause a race condition subscribeToEventsStream(request, subject, responseType); - return subject .doOnUnsubscribe(() -> closeSubscription(subject, unsubscribeMethod)); - } - private > void subscribeToEventsStream( Request request, BehaviorSubject subject, Class responseType) { - subscriptionRequestForId.put( request.getId(), new WebSocketSubscription<>(subject, responseType)); @@ -374,7 +323,6 @@ private > void subscribeToEventsStream( subject.onError(e); } } - private > void closeSubscription( BehaviorSubject subject, String unsubscribeMethod) { subject.onCompleted(); @@ -386,7 +334,6 @@ private > void closeSubscription( log.warn("Trying to unsubscribe from a non-existing subscription. Race condition?"); } } - private void unsubscribeFromEventsStream(String subscriptionId, String unsubscribeMethod) { sendAsync(unsubscribeRequest(subscriptionId, unsubscribeMethod), PlatonUnsubscribe.class) .thenAccept(ethUnsubscribe -> { @@ -398,44 +345,37 @@ private void unsubscribeFromEventsStream(String subscriptionId, String unsubscri return null; }); } - private Request unsubscribeRequest( String subscriptionId, String unsubscribeMethod) { return new Request<>( - unsubscribeMethod, - Collections.singletonList(subscriptionId), - this, - PlatonUnsubscribe.class); + unsubscribeMethod, + Collections.singletonList(subscriptionId), + this, + PlatonUnsubscribe.class); } - @Override public void close() { webSocketClient.close(); executor.shutdown(); } - void onWebSocketClose() { closeOutstandingRequests(); closeOutstandingSubscriptions(); } - private void closeOutstandingRequests() { requestForId.values().forEach(request -> { request.getOnReply() .completeExceptionally(new IOException("Connection was closed")); }); } - private void closeOutstandingSubscriptions() { subscriptionForId.values().forEach(subscription -> { subscription.getSubject() .onError(new IOException("Connection was closed")); }); } - // Method visible for unit-tests boolean isWaitingForReply(long requestId) { return requestForId.containsKey(requestId); } } - diff --git a/core/src/main/java/com/platon/tx/Contract.java b/core/src/main/java/com/platon/tx/Contract.java index a33a6cd7..b11f64b1 100644 --- a/core/src/main/java/com/platon/tx/Contract.java +++ b/core/src/main/java/com/platon/tx/Contract.java @@ -1,5 +1,4 @@ package com.platon.tx; - import com.platon.abi.solidity.*; import com.platon.abi.solidity.datatypes.Address; import com.platon.abi.solidity.datatypes.Event; @@ -10,6 +9,7 @@ import com.platon.protocol.core.DefaultBlockParameter; import com.platon.protocol.core.DefaultBlockParameterName; import com.platon.protocol.core.RemoteCall; +import com.platon.protocol.core.Response; import com.platon.protocol.core.methods.request.Transaction; import com.platon.protocol.core.methods.response.Log; import com.platon.protocol.core.methods.response.PlatonCall; @@ -17,9 +17,12 @@ import com.platon.protocol.core.methods.response.TransactionReceipt; import com.platon.protocol.exceptions.TransactionException; import com.platon.tx.exceptions.ContractCallException; +import com.platon.tx.exceptions.PlatonCallException; +import com.platon.tx.exceptions.PlatonCallTimeoutException; import com.platon.tx.gas.DefaultGasProvider; import com.platon.tx.gas.GasProvider; import com.platon.utils.Numeric; +import com.platon.utils.Strings; import java.io.IOException; import java.lang.reflect.Constructor; @@ -149,6 +152,20 @@ private List executeCall( defaultBlockParameter) .send(); + //判断底层返回的错误信息是否包含超时信息 + if(ethCall.hasError()){ + Response.Error error = ethCall.getError(); + String message = error.getMessage(); + String lowMessage = !Strings.isBlank(message)? message.toLowerCase() : null; + //包含timeout则抛超时异常,其他错误则直接抛出runtime异常 + if(!Strings.isBlank(lowMessage) + && lowMessage.contains("timeout")){ + throw new PlatonCallTimeoutException(error.getCode(),error.getMessage(),ethCall); + } else { + throw new PlatonCallException(error.getCode(),error.getMessage(),ethCall); + } + } + String value = ethCall.getValue(); return FunctionReturnDecoder.decode(value, function.getOutputParameters()); } @@ -205,7 +222,7 @@ private TransactionReceipt executeTransaction( * Given the duration required to execute a transaction. * * @param data to send in transaction - * @param vonValue in Von to send in transaction + * @param weiValue in Von to send in transaction * @return {@link Optional} containing our transaction receipt * @throws IOException if the call to the node fails * @throws TransactionException if the transaction was not mined while waiting @@ -268,7 +285,7 @@ private static T create( return contract; } - protected static T deploy(Class type, Web3j web3j, Credentials credentials, GasProvider contractGasProvider, String binary, String encodedConstructor, BigInteger value) throws RuntimeException, TransactionException { + protected static T deploy(Class type, Web3j web3j, Credentials credentials, GasProvider contractGasProvider, String binary, String encodedConstructor, BigInteger value) throws RuntimeException, TransactionException, IOException { try { Constructor constructor = type.getDeclaredConstructor(String.class, Web3j.class, Credentials.class, GasProvider.class); @@ -280,12 +297,14 @@ protected static T deploy(Class type, Web3j web3j, Crede return create(contract, binary, encodedConstructor, value); } catch (TransactionException e) { throw e; + } catch (IOException e) { + throw e; } catch (Exception e) { throw new RuntimeException(e); } } - protected static T deploy(Class type, Web3j web3j, TransactionManager transactionManager, GasProvider contractGasProvider, String binary, String encodedConstructor, BigInteger value) throws RuntimeException, TransactionException { + protected static T deploy(Class type, Web3j web3j, TransactionManager transactionManager, GasProvider contractGasProvider, String binary, String encodedConstructor, BigInteger value) throws RuntimeException, TransactionException, IOException{ try { Constructor constructor = type.getDeclaredConstructor(String.class, Web3j.class, TransactionManager.class, GasProvider.class); @@ -296,6 +315,8 @@ protected static T deploy(Class type, Web3j web3j, Trans return create(contract, binary, encodedConstructor, value); } catch (TransactionException e) { throw e; + } catch (IOException e) { + throw e; } catch (Exception e) { throw new RuntimeException(e); } diff --git a/core/src/main/java/com/platon/tx/exceptions/PlatonCallException.java b/core/src/main/java/com/platon/tx/exceptions/PlatonCallException.java new file mode 100644 index 00000000..a8e2f01a --- /dev/null +++ b/core/src/main/java/com/platon/tx/exceptions/PlatonCallException.java @@ -0,0 +1,44 @@ +package com.platon.tx.exceptions; + +import com.platon.protocol.core.Response; + +import java.io.IOException; + +/** + * @Author liushuyu + * @Date 2021/5/12 16:46 + * @Version + * @Desc + */ +public class PlatonCallException extends RuntimeException { + + private Response response; + private int code; + private String msg; + + public PlatonCallException(int code, String msg, Response response){ + this.code = code; + this.msg = msg; + this.response = response; + } + + public PlatonCallException(int code, String msg, Response response, Throwable ex){ + super(ex); + this.code = code; + this.msg = msg; + this.response = response; + } + + public Response getResponse() { + return response; + } + + public int getCode() { + return code; + } + + public String getMsg() { + return msg; + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/platon/tx/exceptions/PlatonCallTimeoutException.java b/core/src/main/java/com/platon/tx/exceptions/PlatonCallTimeoutException.java new file mode 100644 index 00000000..745e10e3 --- /dev/null +++ b/core/src/main/java/com/platon/tx/exceptions/PlatonCallTimeoutException.java @@ -0,0 +1,44 @@ +package com.platon.tx.exceptions; + +import com.platon.protocol.core.Response; + +import java.io.IOException; + +/** + * @Author liushuyu + * @Date 2021/5/12 16:46 + * @Version + * @Desc + */ +public class PlatonCallTimeoutException extends IOException { + + private Response response; + private int code; + private String msg; + + public PlatonCallTimeoutException(int code,String msg,Response response){ + this.code = code; + this.msg = msg; + this.response = response; + } + + public PlatonCallTimeoutException(int code,String msg,Response response,Throwable ex){ + super(ex); + this.code = code; + this.msg = msg; + this.response = response; + } + + public Response getResponse() { + return response; + } + + public int getCode() { + return code; + } + + public String getMsg() { + return msg; + } + +} \ No newline at end of file diff --git a/crypto/src/main/java/com/platon/crypto/WalletUtils.java b/crypto/src/main/java/com/platon/crypto/WalletUtils.java index 1f1e97ea..4d40f8b2 100644 --- a/crypto/src/main/java/com/platon/crypto/WalletUtils.java +++ b/crypto/src/main/java/com/platon/crypto/WalletUtils.java @@ -1,5 +1,4 @@ package com.platon.crypto; - import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -7,7 +6,6 @@ import com.platon.utils.Files; import com.platon.utils.Numeric; import org.bouncycastle.util.encoders.Hex; - import java.io.File; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; @@ -17,38 +15,32 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static com.platon.crypto.Hash.sha256; import static com.platon.crypto.Keys.ADDRESS_LENGTH_IN_HEX; import static com.platon.crypto.Keys.PRIVATE_KEY_LENGTH_IN_HEX; - /** * Utility functions for working with Wallet files. */ public class WalletUtils { - private static final ObjectMapper objectMapper = new ObjectMapper(); private static final SecureRandom secureRandom = SecureRandomUtils.secureRandom(); - static { objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } - public static String generateFullNewWalletFile(String password, File destinationDirectory) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException { - return generateNewWalletFile(password, destinationDirectory, true); } - public static String generateLightNewWalletFile(String password, File destinationDirectory) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException { - return generateNewWalletFile(password, destinationDirectory, false); } - /** * create a platON standard wallet * @@ -65,11 +57,9 @@ public static String generatePlatONWalletFile(String password, File destinationD throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException { ECKeyPair ecKeyPair = Keys.createEcKeyPair(); - String fileName = generatePlatONWalletFile(password, ecKeyPair, destinationDirectory); return fileName; } - /** * create a platON standard Bip39 wallet * @@ -83,16 +73,12 @@ public static Bip39Wallet generatePlatONBip39Wallet(String password, File destin throws CipherException, IOException { byte[] initialEntropy = new byte[16]; secureRandom.nextBytes(initialEntropy); - String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); ECKeyPair ecKeyPair = ECKeyPair.create(sha256(seed)); - String fileName = generatePlatONWalletFile(password, ecKeyPair, destinationDirectory); - return new Bip39Wallet(fileName, mnemonic); } - /** * Create platON standard wallet with ecKeyPair * @@ -105,51 +91,38 @@ public static Bip39Wallet generatePlatONBip39Wallet(String password, File destin */ public static String generatePlatONWalletFile(String password, ECKeyPair ecKeyPair, File destinationDirectory) throws CipherException, IOException { - WalletFile walletFile = Wallet.createPlatON(password,ecKeyPair); - String fileName = getWalletFileName(walletFile); File destination = new File(destinationDirectory, fileName); - objectMapper.writeValue(destination, walletFile); - return fileName; } - public static String generateNewWalletFile(String password, File destinationDirectory) throws CipherException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, IOException { return generateFullNewWalletFile(password, destinationDirectory); } - public static String generateNewWalletFile( String password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { - ECKeyPair ecKeyPair = Keys.createEcKeyPair(); return generateWalletFile(password, ecKeyPair, destinationDirectory, useFullScrypt); } - public static String generateWalletFile( String password, ECKeyPair ecKeyPair, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException { - WalletFile walletFile; if (useFullScrypt) { walletFile = Wallet.createStandard(password, ecKeyPair); } else { walletFile = Wallet.createLight(password, ecKeyPair); } - String fileName = getWalletFileName(walletFile); File destination = new File(destinationDirectory, fileName); - objectMapper.writeValue(destination, walletFile); - return fileName; } - /** * Generates a BIP-39 compatible Ethereum wallet. The private key for the wallet can * be calculated using following algorithm: @@ -167,21 +140,16 @@ public static Bip39Wallet generateBip39Wallet(String password, File destinationD throws CipherException, IOException { byte[] initialEntropy = new byte[16]; secureRandom.nextBytes(initialEntropy); - String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); ECKeyPair privateKey = ECKeyPair.create(sha256(seed)); - String walletFile = generateWalletFile(password, privateKey, destinationDirectory, false); - return new Bip39Wallet(walletFile, mnemonic); } - public static Credentials loadCredentials(String password, String source) throws IOException, CipherException { return loadCredentials(password, new File(source)); } - //private static final Pattern OLD_ADDRESS_PATTERN = Pattern.compile(".*address\":[\\s]*\".*"); public static Credentials loadCredentials(String password, File source) throws IOException, CipherException { WalletFile walletFile = loadWalletFile(source); @@ -191,14 +159,12 @@ public static Credentials loadCredentials(String password, File source) throws I } return credentials; } - private static final String MAIN_TEST_ADDRESS_REGEX = "\\\"address\\\"\\s*:\\s*\\{\\s*\\\"mainnet\\\"\\s*:\\s*\\\"([A-Za-z0-9]+)\\\"[^}]*\\}"; public static WalletFile loadWalletFile(File source) throws IOException{ // 统一把source文件中的address值替换为“{}”,兼容新旧格式钱包文件的加载 String fileContent = Files.readString(source); fileContent = fileContent.replaceAll(MAIN_TEST_ADDRESS_REGEX, "\"address\": \"$1\""); WalletFile walletFile = objectMapper.readValue(fileContent, WalletFile.class); - //eth钱包文件中的地址,是0x开头的,转成Bech32格式 /*if(Numeric.containsHexPrefix(walletFile.getAddress())){ walletFile.setAddress(Bech32.addressEncode(NetworkParameters.getHrp(), walletFile.getAddress())); @@ -208,27 +174,21 @@ public static WalletFile loadWalletFile(File source) throws IOException{ walletFile.setAddress(Bech32.convertToUnifiedAddress(walletFile.getAddress())); return walletFile; } - public static Credentials loadBip39Credentials(String password, String mnemonic) { byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); return Credentials.create(ECKeyPair.create(sha256(seed))); } - private static String getWalletFileName(WalletFile walletFile) { DateTimeFormatter format = DateTimeFormatter.ofPattern( "'UTC--'yyyy-MM-dd'T'HH-mm-ss.nVV'--'"); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); - return now.format(format) + walletFile.getAddress() + ".json"; } - public static String getDefaultKeyDirectory() { return getDefaultKeyDirectory(System.getProperty("os.name")); } - static String getDefaultKeyDirectory(String osName1) { String osName = osName1.toLowerCase(); - if (osName.startsWith("mac")) { return String.format( "%s%sLibrary%sEthereum", System.getProperty("user.home"), File.separator, @@ -239,16 +199,14 @@ static String getDefaultKeyDirectory(String osName1) { return String.format("%s%s.ethereum", System.getProperty("user.home"), File.separator); } } - public static String getTestnetKeyDirectory() { return String.format( "%s%stestnet%skeystore", getDefaultKeyDirectory(), File.separator, File.separator); } - public static String getMainnetKeyDirectory() { return String.format("%s%skeystore", getDefaultKeyDirectory(), File.separator); } - + /** * Get keystore destination directory for a Rinkeby network. * @return a String containing destination directory @@ -257,13 +215,18 @@ public static String getRinkebyKeyDirectory() { return String.format( "%s%srinkeby%skeystore", getDefaultKeyDirectory(), File.separator, File.separator); } - public static boolean isValidPrivateKey(String privateKey) { String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; } public static boolean isValidAddress(String input) { + //exclude blank characters and uppercase letters + Pattern pattern = Pattern.compile("^[a-z0-9]+$"); + Matcher matcher = pattern.matcher(input); + if(!matcher.find()){ + return false; + } String cleanInput; try{ byte [] bytes = Bech32.addressDecode(input); @@ -272,13 +235,11 @@ public static boolean isValidAddress(String input) { }catch (Exception e){ return false; } - try { Numeric.toBigIntNoPrefix(cleanInput); } catch (NumberFormatException e) { return false; } - return cleanInput.length() == ADDRESS_LENGTH_IN_HEX; } -} +} \ No newline at end of file diff --git a/integration-tests/src/test/java/com/platon/protocol/core/JsonRpc2_0Web3jTest.java b/integration-tests/src/test/java/com/platon/protocol/core/JsonRpc2_0Web3jTest.java new file mode 100644 index 00000000..caeac57d --- /dev/null +++ b/integration-tests/src/test/java/com/platon/protocol/core/JsonRpc2_0Web3jTest.java @@ -0,0 +1,76 @@ +package com.platon.protocol.core; + +import com.platon.parameters.NetworkParameters; +import com.platon.protocol.Web3j; +import com.platon.protocol.core.methods.response.DebugWaitSlashingNodeList; +import com.platon.protocol.core.methods.response.PlatonChainId; +import com.platon.protocol.http.HttpService; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + * @Author liushuyu + * @Date 2021/7/6 15:52 + * @Version + * @Desc + */ +public class JsonRpc2_0Web3jTest { + + private static final Logger logger = LoggerFactory.getLogger("JsonRpc2_0Web3jTest"); + + @Before + public void init(){ + NetworkParameters.selectPlatON(); + } + + /** + * 底层1.0.0版本 + * @throws IOException + */ + @Test + public void testChainId_version15_0_0() throws IOException { + Web3j web3j = Web3j.build(new HttpService("http://192.168.120.150:6789")); + PlatonChainId send = web3j.getChainId().send(); + Assert.assertTrue(send.getError() != null); + } + + /** + * 底层1.1.0版本 + * @throws IOException + */ + @Test + public void testChainId_version16_0_0() throws IOException { + Web3j web3j = Web3j.build(new HttpService("http://192.168.120.150:6780")); + PlatonChainId send = web3j.getChainId().send(); + Assert.assertTrue(send.getError() == null); + } + + /** + * 底层1.0.0版本 + * @throws IOException + */ + @Test + public void testGetWaitSlashingNodeList_version15_0_0() throws IOException { + Web3j web3j = Web3j.build(new HttpService("http://192.168.120.150:6789")); + DebugWaitSlashingNodeList nodeList = web3j.getWaitSlashingNodeList().send(); + Assert.assertTrue(nodeList.getResult() != null); + Assert.assertTrue(nodeList.getError() == null); + } + + /** + * 底层1.1.0版本 + * @throws IOException + */ + @Test + public void testGetWaitSlashingNodeList_version16_0_0() throws IOException { + Web3j web3j = Web3j.build(new HttpService("http://192.168.120.150:6780")); + DebugWaitSlashingNodeList nodeList = web3j.getWaitSlashingNodeList().send(); + Assert.assertTrue(nodeList.getResult() != null); + Assert.assertTrue(nodeList.getError() == null); + } +} \ No newline at end of file From 38f549be25880258b079a72b20b68d54eb4ee170 Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Fri, 17 Sep 2021 14:33:11 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platon/contracts/ppos/BaseContract.java | 54 +++++++++++++++- .../protocol/websocket/WebSocketService.java | 61 +++++++++++++++++++ .../java/com/platon/crypto/WalletUtils.java | 49 ++++++++++++++- 3 files changed, 162 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/platon/contracts/ppos/BaseContract.java b/core/src/main/java/com/platon/contracts/ppos/BaseContract.java index d0292a32..d5ab9ec8 100644 --- a/core/src/main/java/com/platon/contracts/ppos/BaseContract.java +++ b/core/src/main/java/com/platon/contracts/ppos/BaseContract.java @@ -1,4 +1,5 @@ package com.platon.contracts.ppos; + import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.annotation.JSONField; import com.platon.contracts.ppos.abi.Function; @@ -39,37 +40,49 @@ import org.bouncycastle.util.encoders.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import java.io.IOException; import java.math.BigInteger; import java.util.Collections; import java.util.List; + + /** * 内置合约基类 * @author chendai */ public abstract class BaseContract extends ManagedTransaction { + private static final Logger log = LoggerFactory.getLogger(BaseContract.class); + protected String contractAddress; protected TransactionReceipt transactionReceipt; + protected BaseContract(String contractAddress, Web3j web3j, TransactionManager transactionManager) { super(web3j, transactionManager); this.contractAddress = contractAddress; } + protected BaseContract(String contractAddress, Web3j web3j, Credentials credentials) { this(contractAddress, web3j, new RawTransactionManager(web3j, credentials)); } + protected BaseContract(String contractAddress, Web3j web3j) { this(contractAddress, web3j, new ReadonlyTransactionManager(web3j, contractAddress)); } + public String getContractAddress() { return contractAddress; } + protected RemoteCall> executeRemoteCallObjectValueReturn(Function function, Class returnType) { return new RemoteCall<>(() -> executeCallObjectValueReturn(function, returnType)); } + protected RemoteCall>> executeRemoteCallListValueReturn(Function function, Class returnType) { return new RemoteCall<>(() -> executeCallListValueReturn(function, returnType)); } + private CallResponse executeCallObjectValueReturn(Function function, Class returnType) throws IOException { PlatonCall ethCall = web3j.platonCall( Transaction.createEthCallTransaction( @@ -95,10 +108,12 @@ private CallResponse executeCallObjectValueReturn(Function function, Clas if(result==null || "".equals(result)){ throw new ContractCallException("Empty value (0x) returned from contract"); } + CallRet callRet = JSONUtil.parseObject(new String(Hex.decode(result)), CallRet.class); if (callRet == null) { throw new ContractCallException("Unable to convert response: " + result); } + CallResponse callResponse = new CallResponse(); if (callRet.isStatusOk()) { callResponse.setCode(callRet.getCode()); @@ -113,6 +128,7 @@ private CallResponse executeCallObjectValueReturn(Function function, Clas } return callResponse; } + private BigInteger numberDecoder(Object number) { if(number instanceof String) { String numberStr = (String)number; @@ -124,6 +140,7 @@ private BigInteger numberDecoder(Object number) { throw new MessageDecodingException("Can not decode number value = " + number); } } + private CallResponse> executeCallListValueReturn(Function function, Class returnType) throws IOException { PlatonCall ethCall = web3j.platonCall( Transaction.createEthCallTransaction( @@ -149,10 +166,12 @@ private CallResponse> executeCallListValueReturn(Function function, if(result==null || "".equals(result)){ throw new ContractCallException("Empty value (0x) returned from contract"); } + CallRet callRet = JSONUtil.parseObject(new String(Hex.decode(result)), CallRet.class); if (callRet == null) { throw new ContractCallException("Unable to convert response: " + result); } + CallResponse> callResponse = new CallResponse>(); if (callRet.isStatusOk()) { callResponse.setCode(callRet.getCode()); @@ -161,17 +180,21 @@ private CallResponse> executeCallListValueReturn(Function function, callResponse.setCode(callRet.getCode()); callResponse.setErrMsg(callRet.getRet().toString()); } + if(callRet.getCode() == ErrorCode.OBJECT_NOT_FOUND){ callResponse.setCode(ErrorCode.SUCCESS); callResponse.setData(Collections.emptyList()); } + return callResponse; } + public static class CallRet{ @JSONField(name = "Code") private int code; @JSONField(name = "Ret") private Object ret; + public boolean isStatusOk() { return code == ErrorCode.SUCCESS; } @@ -187,32 +210,42 @@ public Object getRet() { public void setRet(Object ret) { this.ret = ret; } + @Override public String toString() { return "CallRet [code=" + code + ", ret=" + ret + "]"; } } + protected RemoteCall executeRemoteCallTransactionStep1(Function function, GasProvider gasProvider) { return new RemoteCall<>(() -> executeTransactionStep1(function, BigInteger.ZERO,gasProvider)); } + protected RemoteCall executeRemoteCallTransactionStep1(Function function) { return new RemoteCall<>(() -> executeTransactionStep1(function, BigInteger.ZERO, getDefaultGasProvider(function))); } + private RemoteCall executeRemoteCallTransactionStep2(PlatonSendTransaction ethSendTransaction) { return new RemoteCall<>(() -> executeTransactionStep2(ethSendTransaction)); } + public RemoteCall getTransactionResponse(PlatonSendTransaction ethSendTransaction){ return executeRemoteCallTransactionStep2(ethSendTransaction); } + protected RemoteCall executeRemoteCallTransaction(Function function) { return new RemoteCall<>(() -> executeTransaction(function, BigInteger.ZERO, getDefaultGasProvider(function))); } + protected RemoteCall executeRemoteCallTransaction(Function function, GasProvider gasProvider) { return new RemoteCall<>(() -> executeTransaction(function, BigInteger.ZERO, gasProvider)); } + + protected GasProvider getDefaultGasProvider(Function function) throws IOException, EstimateGasException { return getDefaultGasProviderRemote(function); } + private GasProvider getDefaultGasProviderRemote(Function function) throws IOException, EstimateGasException { Transaction transaction = Transaction.createEthCallTransaction(transactionManager.getFromAddress(), contractAddress, EncoderUtils.functionEncoder(function)); PlatonEstimateGas platonEstimateGas = web3j.platonEstimateGas(transaction).send(); @@ -230,12 +263,14 @@ private GasProvider getDefaultGasProviderRemote(Function function) throws IOExce BigInteger gasPrice = getDefaultGasPrice(function.getType()); return new ContractGasProvider(gasPrice, gasLimit); } + private GasProvider getDefaultGasProviderLocal(Function function) throws IOException, NoSupportFunctionType { BigInteger gasLimit = EstimateGasUtil.getGasLimit(function); BigInteger gasPrice = getDefaultGasPrice(function.getType()); GasProvider gasProvider = new ContractGasProvider(gasPrice, gasLimit); return gasProvider; } + /** * 获得默认的gasPrice * Alaya_chainID或者Alaya_hrp时,gasPrice缩小100倍, @@ -275,35 +310,52 @@ private BigInteger getDefaultGasPrice(int type) throws IOException { } } } + + + + private TransactionResponse executeTransaction(Function function, BigInteger vonValue, GasProvider gasProvider)throws TransactionException, IOException { + TransactionReceipt receipt = send(contractAddress, EncoderUtils.functionEncoder(function), vonValue, gasProvider.getGasPrice(), gasProvider.getGasLimit()); + return getResponseFromTransactionReceipt(receipt); } + private PlatonSendTransaction executeTransactionStep1(Function function, BigInteger vonValue, GasProvider gasProvider) throws IOException { + return sendPlatonRawTransaction(contractAddress, EncoderUtils.functionEncoder(function), vonValue, gasProvider.getGasPrice(), gasProvider.getGasLimit()); + } + private TransactionResponse executeTransactionStep2(PlatonSendTransaction ethSendTransaction) throws IOException, TransactionException { + TransactionReceipt receipt = getTransactionReceipt(ethSendTransaction); + return getResponseFromTransactionReceipt(receipt); } + private TransactionResponse getResponseFromTransactionReceipt(TransactionReceipt transactionReceipt) throws TransactionException { List logs = transactionReceipt.getLogs(); if(logs==null||logs.isEmpty()){ throw new TransactionException("TransactionReceipt logs is empty"); } + String logData = logs.get(0).getData(); if(null == logData || "".equals(logData) ){ throw new TransactionException("TransactionReceipt log data is empty"); } + RlpList rlp = RlpDecoder.decode(Numeric.hexStringToByteArray(logData)); List rlpList = ((RlpList)(rlp.getValues().get(0))).getValues(); String decodedStatus = new String(((RlpString)rlpList.get(0)).getBytes()); int statusCode = Integer.parseInt(decodedStatus); + TransactionResponse transactionResponse = new TransactionResponse(); transactionResponse.setCode(statusCode); transactionResponse.setTransactionReceipt(transactionReceipt); + return transactionResponse; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java b/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java index 8976df10..dc19bc59 100644 --- a/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java +++ b/core/src/main/java/com/platon/protocol/websocket/WebSocketService.java @@ -1,4 +1,5 @@ package com.platon.protocol.websocket; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -22,6 +23,7 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.*; + /** * Web socket service that allows to interact with JSON-RPC via WebSocket protocol. * @@ -34,15 +36,19 @@ * request. */ public class WebSocketService implements Web3jService { + private static final Logger log = LoggerFactory.getLogger(WebSocketService.class); + // Timeout for JSON-RPC requests static final long REQUEST_TIMEOUT = 60; + // WebSocket client private final WebSocketClient webSocketClient; // Executor to schedule request timeouts private final ScheduledExecutorService executor; // Object mapper to map incoming JSON objects private final ObjectMapper objectMapper; + // Map of a sent request id to objects necessary to process this request private Map> requestForId = new ConcurrentHashMap<>(); // Map of a sent subscription request id to objects necessary to process @@ -51,13 +57,16 @@ public class WebSocketService implements Web3jService { = new ConcurrentHashMap<>(); // Map of a subscription id to objects necessary to process incoming events private Map> subscriptionForId = new ConcurrentHashMap<>(); + public WebSocketService(String serverUrl, boolean includeRawResponses) { this(new WebSocketClient(parseURI(serverUrl)), includeRawResponses); } + public WebSocketService(WebSocketClient webSocketClient, boolean includeRawResponses) { this(webSocketClient, Executors.newScheduledThreadPool(1), includeRawResponses); } + WebSocketService(WebSocketClient webSocketClient, ScheduledExecutorService executor, boolean includeRawResponses) { @@ -65,6 +74,7 @@ public WebSocketService(WebSocketClient webSocketClient, this.executor = executor; this.objectMapper = ObjectMapperFactory.getObjectMapper(includeRawResponses); } + /** * Connect to a WebSocket server. * @@ -79,28 +89,34 @@ public void connect() throws ConnectException { log.warn("Interrupted while connecting via WebSocket protocol"); } } + private void connectToWebSocket() throws InterruptedException, ConnectException { boolean connected = webSocketClient.connectBlocking(); if (!connected) { throw new ConnectException("Failed to connect to WebSocket"); } } + private void setWebSocketListener() { webSocketClient.setListener(new WebSocketListener() { @Override public void onMessage(String message) throws IOException { onWebSocketMessage(message); } + @Override public void onError(Exception e) { log.error("Received error from a WebSocket connection", e); } + @Override public void onClose() { onWebSocketClose(); } }); } + + @Override public T send(Request request, Class responseType) throws IOException { try { @@ -112,13 +128,16 @@ public T send(Request request, Class responseType) throw if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } + throw new RuntimeException("Unexpected exception", e.getCause()); } } + @Override public CompletableFuture sendAsync( Request request, Class responseType) { + CompletableFuture result = new CompletableFuture<>(); long requestId = request.getId(); requestForId.put(requestId, new WebSocketRequest<>(result, responseType)); @@ -127,14 +146,17 @@ public CompletableFuture sendAsync( } catch (IOException e) { closeRequest(requestId, e); } + return result; } + private void sendRequest(Request request, long requestId) throws JsonProcessingException { String payload = objectMapper.writeValueAsString(request); log.debug("Sending request: {}", payload); webSocketClient.send(payload); setRequestTimeout(requestId); } + private void setRequestTimeout(long requestId) { executor.schedule( () -> closeRequest( @@ -144,13 +166,16 @@ private void setRequestTimeout(long requestId) { REQUEST_TIMEOUT, TimeUnit.SECONDS); } + void closeRequest(long requestId, Exception e) { CompletableFuture result = requestForId.get(requestId).getOnReply(); requestForId.remove(requestId); result.completeExceptionally(e); } + void onWebSocketMessage(String messageStr) throws IOException { JsonNode replyJson = parseToTree(messageStr); + if (isReply(replyJson)) { processRequestReply(messageStr, replyJson); } else if (isSubscriptionEvent(replyJson)) { @@ -159,6 +184,7 @@ void onWebSocketMessage(String messageStr) throws IOException { throw new IOException("Unknown message type"); } } + private void processRequestReply(String replyStr, JsonNode replyJson) throws IOException { long replyId = getReplyId(replyJson); WebSocketRequest request = getAndRemoveRequest(replyId); @@ -169,11 +195,13 @@ private void processRequestReply(String replyStr, JsonNode replyJson) throws IOE if (reply instanceof PlatonSubscribe) { processSubscriptionResponse(replyId, (PlatonSubscribe) reply); } + sendReplyToListener(request, reply); } catch (IllegalArgumentException e) { sendExceptionToListener(replyStr, request, e); } } + private void processSubscriptionResponse(long replyId, PlatonSubscribe reply) throws IOException { WebSocketSubscription subscription = subscriptionRequestForId.get(replyId); processSubscriptionResponse( @@ -182,6 +210,7 @@ private void processSubscriptionResponse(long replyId, PlatonSubscribe reply) th subscription.getResponseType() ); } + private > void processSubscriptionResponse( PlatonSubscribe subscriptionReply, BehaviorSubject subject, @@ -192,6 +221,7 @@ private > void processSubscriptionResponse( reportSubscriptionError(subject, subscriptionReply); } } + private > void establishSubscription( BehaviorSubject subject, Class responseType, PlatonSubscribe subscriptionReply) { log.info("Subscribed to RPC events with id {}", @@ -200,6 +230,7 @@ private > void establishSubscription( subscriptionReply.getSubscriptionId(), new WebSocketSubscription<>(subject, responseType)); } + private > String getSubscriptionId(BehaviorSubject subject) { return subscriptionForId.entrySet().stream() .filter(entry -> entry.getValue().getSubject() == subject) @@ -207,6 +238,7 @@ private > String getSubscriptionId(BehaviorSubject .findFirst() .orElse(null); } + private > void reportSubscriptionError( BehaviorSubject subject, PlatonSubscribe subscriptionReply) { Response.Error error = subscriptionReply.getError(); @@ -218,9 +250,11 @@ private > void reportSubscriptionError( )) ); } + private void sendReplyToListener(WebSocketRequest request, Object reply) { request.getOnReply().complete(reply); } + private void sendExceptionToListener( String replyStr, WebSocketRequest request, @@ -233,29 +267,36 @@ private void sendExceptionToListener( request.getResponseType()), e)); } + private void processSubscriptionEvent(String replyStr, JsonNode replyJson) { log.info("Processing event: {}", replyStr); String subscriptionId = extractSubscriptionId(replyJson); WebSocketSubscription subscription = subscriptionForId.get(subscriptionId); + if (subscription != null) { sendEventToSubscriber(replyJson, subscription); } else { log.warn("No subscriber for WebSocket event with subscription id {}", subscriptionId); } } + private String extractSubscriptionId(JsonNode replyJson) { return replyJson.get("params").get("subscription").asText(); } + private void sendEventToSubscriber(JsonNode replyJson, WebSocketSubscription subscription) { Object event = objectMapper.convertValue(replyJson, subscription.getResponseType()); subscription.getSubject().onNext(event); } + private boolean isReply(JsonNode replyJson) { return replyJson.has("id"); } + private boolean isSubscriptionEvent(JsonNode replyJson) { return replyJson.has("method"); } + private JsonNode parseToTree(String replyStr) throws IOException { try { return objectMapper.readTree(replyStr); @@ -263,6 +304,7 @@ private JsonNode parseToTree(String replyStr) throws IOException { throw new IOException("Failed to parse incoming WebSocket message", e); } } + private WebSocketRequest getAndRemoveRequest(long id) throws IOException { if (!requestForId.containsKey(id)) { throw new IOException( @@ -273,18 +315,22 @@ private WebSocketRequest getAndRemoveRequest(long id) throws IOException { requestForId.remove(id); return request; } + private long getReplyId(JsonNode replyJson) throws IOException { JsonNode idField = replyJson.get("id"); if (idField == null) { throw new IOException("'id' field is missing in the reply"); } + if (!idField.isIntegralNumber()) { throw new IOException( String.format("'id' expected to be long, but it is: '%s'", idField.asText())); } + return idField.longValue(); } + private static URI parseURI(String serverUrl) { try { return new URI(serverUrl); @@ -292,6 +338,7 @@ private static URI parseURI(String serverUrl) { throw new RuntimeException(String.format("Failed to parse URL: '%s'", serverUrl), e); } } + @Override public > Observable subscribe( Request request, @@ -301,17 +348,22 @@ public > Observable subscribe( // before first client is subscribed and we need to // preserve it BehaviorSubject subject = BehaviorSubject.create(); + // We need to subscribe synchronously, since if we return // an Observable to a client before we got a reply // a client can unsubscribe before we know a subscription // id and this can cause a race condition subscribeToEventsStream(request, subject, responseType); + return subject .doOnUnsubscribe(() -> closeSubscription(subject, unsubscribeMethod)); + } + private > void subscribeToEventsStream( Request request, BehaviorSubject subject, Class responseType) { + subscriptionRequestForId.put( request.getId(), new WebSocketSubscription<>(subject, responseType)); @@ -323,6 +375,7 @@ private > void subscribeToEventsStream( subject.onError(e); } } + private > void closeSubscription( BehaviorSubject subject, String unsubscribeMethod) { subject.onCompleted(); @@ -334,6 +387,7 @@ private > void closeSubscription( log.warn("Trying to unsubscribe from a non-existing subscription. Race condition?"); } } + private void unsubscribeFromEventsStream(String subscriptionId, String unsubscribeMethod) { sendAsync(unsubscribeRequest(subscriptionId, unsubscribeMethod), PlatonUnsubscribe.class) .thenAccept(ethUnsubscribe -> { @@ -345,6 +399,7 @@ private void unsubscribeFromEventsStream(String subscriptionId, String unsubscri return null; }); } + private Request unsubscribeRequest( String subscriptionId, String unsubscribeMethod) { return new Request<>( @@ -353,29 +408,35 @@ private Request unsubscribeRequest( this, PlatonUnsubscribe.class); } + @Override public void close() { webSocketClient.close(); executor.shutdown(); } + void onWebSocketClose() { closeOutstandingRequests(); closeOutstandingSubscriptions(); } + private void closeOutstandingRequests() { requestForId.values().forEach(request -> { request.getOnReply() .completeExceptionally(new IOException("Connection was closed")); }); } + private void closeOutstandingSubscriptions() { subscriptionForId.values().forEach(subscription -> { subscription.getSubject() .onError(new IOException("Connection was closed")); }); } + // Method visible for unit-tests boolean isWaitingForReply(long requestId) { return requestForId.containsKey(requestId); } } + diff --git a/crypto/src/main/java/com/platon/crypto/WalletUtils.java b/crypto/src/main/java/com/platon/crypto/WalletUtils.java index 4d40f8b2..6336b54e 100644 --- a/crypto/src/main/java/com/platon/crypto/WalletUtils.java +++ b/crypto/src/main/java/com/platon/crypto/WalletUtils.java @@ -1,4 +1,5 @@ package com.platon.crypto; + import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -6,6 +7,7 @@ import com.platon.utils.Files; import com.platon.utils.Numeric; import org.bouncycastle.util.encoders.Hex; + import java.io.File; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; @@ -21,26 +23,34 @@ import static com.platon.crypto.Hash.sha256; import static com.platon.crypto.Keys.ADDRESS_LENGTH_IN_HEX; import static com.platon.crypto.Keys.PRIVATE_KEY_LENGTH_IN_HEX; + /** * Utility functions for working with Wallet files. */ public class WalletUtils { + private static final ObjectMapper objectMapper = new ObjectMapper(); private static final SecureRandom secureRandom = SecureRandomUtils.secureRandom(); + static { objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } + public static String generateFullNewWalletFile(String password, File destinationDirectory) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException { + return generateNewWalletFile(password, destinationDirectory, true); } + public static String generateLightNewWalletFile(String password, File destinationDirectory) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException { + return generateNewWalletFile(password, destinationDirectory, false); } + /** * create a platON standard wallet * @@ -57,9 +67,11 @@ public static String generatePlatONWalletFile(String password, File destinationD throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException { ECKeyPair ecKeyPair = Keys.createEcKeyPair(); + String fileName = generatePlatONWalletFile(password, ecKeyPair, destinationDirectory); return fileName; } + /** * create a platON standard Bip39 wallet * @@ -73,12 +85,16 @@ public static Bip39Wallet generatePlatONBip39Wallet(String password, File destin throws CipherException, IOException { byte[] initialEntropy = new byte[16]; secureRandom.nextBytes(initialEntropy); + String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); ECKeyPair ecKeyPair = ECKeyPair.create(sha256(seed)); + String fileName = generatePlatONWalletFile(password, ecKeyPair, destinationDirectory); + return new Bip39Wallet(fileName, mnemonic); } + /** * Create platON standard wallet with ecKeyPair * @@ -91,38 +107,51 @@ public static Bip39Wallet generatePlatONBip39Wallet(String password, File destin */ public static String generatePlatONWalletFile(String password, ECKeyPair ecKeyPair, File destinationDirectory) throws CipherException, IOException { + WalletFile walletFile = Wallet.createPlatON(password,ecKeyPair); + String fileName = getWalletFileName(walletFile); File destination = new File(destinationDirectory, fileName); + objectMapper.writeValue(destination, walletFile); + return fileName; } + public static String generateNewWalletFile(String password, File destinationDirectory) throws CipherException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, IOException { return generateFullNewWalletFile(password, destinationDirectory); } + public static String generateNewWalletFile( String password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + ECKeyPair ecKeyPair = Keys.createEcKeyPair(); return generateWalletFile(password, ecKeyPair, destinationDirectory, useFullScrypt); } + public static String generateWalletFile( String password, ECKeyPair ecKeyPair, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException { + WalletFile walletFile; if (useFullScrypt) { walletFile = Wallet.createStandard(password, ecKeyPair); } else { walletFile = Wallet.createLight(password, ecKeyPair); } + String fileName = getWalletFileName(walletFile); File destination = new File(destinationDirectory, fileName); + objectMapper.writeValue(destination, walletFile); + return fileName; } + /** * Generates a BIP-39 compatible Ethereum wallet. The private key for the wallet can * be calculated using following algorithm: @@ -140,16 +169,21 @@ public static Bip39Wallet generateBip39Wallet(String password, File destinationD throws CipherException, IOException { byte[] initialEntropy = new byte[16]; secureRandom.nextBytes(initialEntropy); + String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); ECKeyPair privateKey = ECKeyPair.create(sha256(seed)); + String walletFile = generateWalletFile(password, privateKey, destinationDirectory, false); + return new Bip39Wallet(walletFile, mnemonic); } + public static Credentials loadCredentials(String password, String source) throws IOException, CipherException { return loadCredentials(password, new File(source)); } + //private static final Pattern OLD_ADDRESS_PATTERN = Pattern.compile(".*address\":[\\s]*\".*"); public static Credentials loadCredentials(String password, File source) throws IOException, CipherException { WalletFile walletFile = loadWalletFile(source); @@ -159,12 +193,14 @@ public static Credentials loadCredentials(String password, File source) throws I } return credentials; } + private static final String MAIN_TEST_ADDRESS_REGEX = "\\\"address\\\"\\s*:\\s*\\{\\s*\\\"mainnet\\\"\\s*:\\s*\\\"([A-Za-z0-9]+)\\\"[^}]*\\}"; public static WalletFile loadWalletFile(File source) throws IOException{ // 统一把source文件中的address值替换为“{}”,兼容新旧格式钱包文件的加载 String fileContent = Files.readString(source); fileContent = fileContent.replaceAll(MAIN_TEST_ADDRESS_REGEX, "\"address\": \"$1\""); WalletFile walletFile = objectMapper.readValue(fileContent, WalletFile.class); + //eth钱包文件中的地址,是0x开头的,转成Bech32格式 /*if(Numeric.containsHexPrefix(walletFile.getAddress())){ walletFile.setAddress(Bech32.addressEncode(NetworkParameters.getHrp(), walletFile.getAddress())); @@ -174,21 +210,27 @@ public static WalletFile loadWalletFile(File source) throws IOException{ walletFile.setAddress(Bech32.convertToUnifiedAddress(walletFile.getAddress())); return walletFile; } + public static Credentials loadBip39Credentials(String password, String mnemonic) { byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); return Credentials.create(ECKeyPair.create(sha256(seed))); } + private static String getWalletFileName(WalletFile walletFile) { DateTimeFormatter format = DateTimeFormatter.ofPattern( "'UTC--'yyyy-MM-dd'T'HH-mm-ss.nVV'--'"); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + return now.format(format) + walletFile.getAddress() + ".json"; } + public static String getDefaultKeyDirectory() { return getDefaultKeyDirectory(System.getProperty("os.name")); } + static String getDefaultKeyDirectory(String osName1) { String osName = osName1.toLowerCase(); + if (osName.startsWith("mac")) { return String.format( "%s%sLibrary%sEthereum", System.getProperty("user.home"), File.separator, @@ -199,10 +241,12 @@ static String getDefaultKeyDirectory(String osName1) { return String.format("%s%s.ethereum", System.getProperty("user.home"), File.separator); } } + public static String getTestnetKeyDirectory() { return String.format( "%s%stestnet%skeystore", getDefaultKeyDirectory(), File.separator, File.separator); } + public static String getMainnetKeyDirectory() { return String.format("%s%skeystore", getDefaultKeyDirectory(), File.separator); } @@ -215,6 +259,7 @@ public static String getRinkebyKeyDirectory() { return String.format( "%s%srinkeby%skeystore", getDefaultKeyDirectory(), File.separator, File.separator); } + public static boolean isValidPrivateKey(String privateKey) { String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; @@ -235,11 +280,13 @@ public static boolean isValidAddress(String input) { }catch (Exception e){ return false; } + try { Numeric.toBigIntNoPrefix(cleanInput); } catch (NumberFormatException e) { return false; } + return cleanInput.length() == ADDRESS_LENGTH_IN_HEX; } -} \ No newline at end of file +} From ddcf219ffd86fdd6d5608676c1bb7a145f95d244 Mon Sep 17 00:00:00 2001 From: liushuyu <754053007@qq.com> Date: Mon, 11 Oct 2021 18:03:59 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E6=9B=B4=E6=96=B0api=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- doc/Java-SDK-en.md | 637 ++++++++++++++++++++++++++++++++++++++++++++- doc/Java-SDK-zh.md | 625 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1257 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 44898865..0d7a1cce 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Build ``` - git clone https://github.com/PlatONnetwork/client-sdk-java.git + git clone https://github.com/AlayaNetwork/client-sdk-java.git cd client-sdk-java/ ./gradlew clean jar //Generate jar package ./gradlew clean distZip //Generate code generation skeleton tool diff --git a/doc/Java-SDK-en.md b/doc/Java-SDK-en.md index 057e3da6..b035cfb8 100644 --- a/doc/Java-SDK-en.md +++ b/doc/Java-SDK-en.md @@ -1497,21 +1497,21 @@ String debugEconomicConfig = req.send().getEconomicConfigStr(); ### getChainId -> Get chain ID +> Get chain ID -- **parameters** +* **parameters** - no + No -- **return value** +* **return value** ```java Request ``` -The String in the PlatonChainId property is the corresponding stored data +The String in the PlatonChainId attribute is the corresponding storage data -- **Example** +* **Example** ```java Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); @@ -1519,6 +1519,631 @@ Request req = platonWeb3j.getChainId(); BigInteger chainId = req.send().getChainId(); ``` +### adminNodeInfo + +> Retrieve all the information we know about the host node at protocol granularity + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the AdminNodeInfo attribute is the corresponding storage data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +AdminNodeInfo nodeInfo = platonWeb3j.adminNodeInfo().send(); +``` + +### adminPeers + +> Retrieve all the information we know about each individual Peer at protocol granularity + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the AdminPeers attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +AdminPeers adminPeers = platonWeb3j.adminPeers().send(); +``` + +### adminStartRPC + +> Start the HTTP RPC API server + +* **parameters** + + - String : host : Network address to listen on + - Integer : port : Network port to listen on + - String : cors : Cross-origin resource sharing header to be used + - String : apis : API modules to provide services through the service interface + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +WebSocketService webSocketService = new WebSocketService("ws://127.0.0.1:7790",false); +webSocketService.connect(); +Web3j platonWeb3j = Web3j.build(webSocketService); +BooleanResponse send = platonWeb3j.adminStartRPC("127.0.0.1", 6789, null, null).send(); +``` + +### adminStartWS + +> Start the websocket RPC API server + +* **parameters** + + - String : host : Network address to listen on + - Integer : port : Network port to listen on + - String : cors : Cross-origin resource sharing header to be used + - String : apis : API modules to provide services through the service interface + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = web3j.adminStartWS("127.0.0.1", 7789, null, null).send(); +``` + +### adminStopRPC + +> Close the currently started HTTP RPC end node + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +WebSocketService webSocketService = new WebSocketService("ws://127.0.0.1:7790",false); +webSocketService.connect(); +Web3j platonWeb3j = Web3j.build(webSocketService); +BooleanResponse send = platonWeb3j.adminStopRPC().send(); +``` + +### adminStopWS + +> Close the currently started WebSocket RPC end node + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +WebSocketService webSocketService = new WebSocketService("ws://127.0.0.1:7790",false); +webSocketService.connect(); +Web3j platonWeb3j = Web3j.build(webSocketService); +BooleanResponse send = platonWeb3j.adminStopWS().send(); +``` + +### adminExportChain + +> Export the current blockchain to a local file + +* **parameters** + + - String : file : file name + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = platonWeb3j.adminExportChain("1").send(); +``` + +### adminImportChain + +> Import blockchain from local file + +* **parameters** + + - String : file : file name + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = platonWeb3j.adminImportChain("1").send(); +``` + +### getWaitSlashingNodeList + +> Get the node with zero block, the list of nodes that are observed because of zero block + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The WaitSlashingNode list object in the DebugWaitSlashingNodeList property is the corresponding storage data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request req = platonWeb3j.getWaitSlashingNodeList(); +DebugWaitSlashingNodeList nodeList = req.send(); +``` + +### platonGetRawTransactionByHash + +> Returns the number of transaction bytes for a given Hash + +* **parameters** + + - String : hash : Transaction hash + +* **return value** + +```java +Request +``` + +The result in the PlatonRawTransaction attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonRawTransaction send = platonWeb3j.platonGetRawTransactionByHash("0x5b99...").send(); +``` + +### platonGetRawTransactionByBlockHashAndIndex + +> Return transactions for a given block hash and index + +* **parameters** + + - String : hash : Block hash + - String : index : index + +* **return value** + +```java +Request +``` + +The result in the PlatonRawTransaction attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonRawTransaction send = platonWeb3j.platonGetRawTransactionByBlockHashAndIndex("0xa34...", "0x1").send(); +``` + +### platonGetRawTransactionByBlockNumberAndIndex + +> Return transactions with a given block number and index + +* **parameters** + + - String : blockNumber : Block number + - String : index : index + +* **return value** + +```java +Request +``` + +The result in the PlatonRawTransaction attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonRawTransaction send = platonWeb3j.platonGetRawTransactionByBlockNumberAndIndex("0x1", "0x1").send(); +``` + +### platonGetAddressHrp + +> Get chain Hrp + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the PlatonGetAddressHrp attribute is the corresponding storage data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonGetAddressHrp send = platonWeb3j.platonGetAddressHrp().send(); +``` + +### platonSignTransaction + +> The from account will be used to sign the given transaction. The node needs to have the account private key corresponding to the given sender address, and it needs to be unlocked + +* **parameters** + + - Transaction : transaction : Transaction object + +* **return value** + +```java +Request +``` + +The result in the PlatonSignTransaction attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Transaction transaction = new Transaction("atp1xxx",nonce,gasPrice,gasLimit,toAddress,value,data); +PlatonSignTransaction send = platonWeb3j.platonSignTransaction(transaction).send(); +``` + +### minerSetGasPrice + +> Set the minimum gas price acceptable to miners + +* **parameters** + + - String : minGasPrice : Lowest gas price + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = platonWeb3j.minerSetGasPrice("0x1").send(); +``` + +### adminPeerEvents + +> Create an RPC subscription, which receives peer events from the node's p2p server + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the AdminPeerEvents attribute is the corresponding storage data + +* **Example** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +AdminPeerEvents send = web3j.adminPeerEvents().send(); +``` + +### personalImportRawKey + +> Store the given hexadecimal coded key in the key directory and encrypt it with a password + +* **parameters** + + - String : keydata : Private key + - String : password : password + +* **return value** + +```java +Request +``` + +The result in the PersonalImportRawKey attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalImportRawKey key = admin.personalImportRawKey("03axxx", "000000").send(); +``` + +### personalLockAccount + +> Lock the account associated with a given address + +* **parameters** + + - String : address : Account address + +* **return value** + +```java +Request +``` + +The result in the BooleanResponse attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse response = admin.personalLockAccount("atp1cxxx").send(); +``` + +### personalSign + +> Use the given account to sign the transaction, the account needs to exist in the node's account library + +* **parameters** + + - String : message : The hexadecimal code string of the transaction + - String : accountId : Account address + - String : password : Account address password + +* **return value** + +```java +Request +``` + +The result in the PersonalSign attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +RawTransaction transaction = RawTransaction.createTransaction(nonce,gasPrice,gasLimit,toAddress,value,data); +byte[] encode = TransactionEncoder.encode(transaction); +String hexSignedTransaction = Numeric.toHexString(encode); +PersonalSign send = admin.personalSign(hexSignedTransaction, "atp1xxx","000000").send(); +``` + +### personalSignAndSendTransaction + +> Use the from address of the transaction to sign and send the transaction. The from account needs to exist in the node's account library + +* **parameters** + + - Transaction : transaction : Transaction object + - String : password : Account address password + +* **return value** + +```java +Request +``` + +The result in the PersonalSign attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +Transaction transaction = new Transaction("atp1xxx",nonce,gasPrice,gasLimit,toAddress,value,data); +PersonalSign send = admin.personalSignAndSendTransaction(transaction, "000000").send(); +``` + +### personalSignTransaction + +> Use the from address of the transaction to sign, and the from account needs to exist in the node's account database + +* **parameters** + + - Transaction : transaction : Transaction object + - String : password : From account address password + +* **return value** + +```java +Request +``` + +The result in the PlatonSignTransaction attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +Transaction transaction = new Transaction("atp1xxx",nonce,gasPrice,gasLimit,toAddress,value,data); +PlatonSignTransaction send = admin.personalSignTransaction(transaction,"000000").send(); +``` + +### personalEcRecover + +> Returns the address of the account used to create the signature + +* **parameters** + + - String : message : Raw data + - String : signiture : Signed data + +* **return value** + +```java +Request +``` + +The result in the PersonalEcRecover attribute is the corresponding storage data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalEcRecover send = admin.personalEcRecover("0xebxxx","0xa4f5xxx").send(); +``` + +### personalListWallets + +> Return the list of wallets managed by this node + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the PersonalListWallets attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalListWallets send = admin.personalListWallets().send(); +``` + +### personalOpenWallet + +> Start the hardware wallet opening program, establish a USB connection and try to authenticate with the provided password. Please note that this method may return an additional challenge that needs to be opened a second time (for example, Trezor PIN matrix challenge) + +* **parameters** + + - String : url : URL + - String : passphrase : password + +* **return value** + +```java +Request +``` + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +VoidResponse send = admin.personalOpenWallet("http://localhost:8080", "000000").send(); +``` + +### personalUnlockAccount + +> Use the given password to unlock the account associated with the given address for a duration of seconds. The default is 300 seconds. If the account is unlocked, it will return an indication + +* **parameters** + + - String : address : address + - String : passphrase : password + +* **return value** + +```java +Request +``` + +The result in the PersonalUnlockAccount attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalUnlockAccount response = admin.personalUnlockAccount("atp1xxx", "111111").send(); +``` + +### personalListAccounts + +> Returns a list of addresses of accounts managed by this node + +* **parameters** + + No + +* **return value** + +```java +Request +``` + +The result in the PersonalListAccounts attribute is the corresponding stored data + +* **Example** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalListAccounts send = admin.personalListAccounts().send(); +``` + ## System Contract Call diff --git a/doc/Java-SDK-zh.md b/doc/Java-SDK-zh.md index edf1707a..342b4bac 100644 --- a/doc/Java-SDK-zh.md +++ b/doc/Java-SDK-zh.md @@ -1517,6 +1517,631 @@ Request req = platonWeb3j.getChainId(); BigInteger chainId = req.send().getChainId(); ``` +### adminNodeInfo + +> 以协议粒度检索我们知道的有关主机节点的所有信息。 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +AdminNodeInfo属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +AdminNodeInfo nodeInfo = platonWeb3j.adminNodeInfo().send(); +``` + +### adminPeers + +> 以协议粒度检索我们知道的关于每个单独 Peer 的所有信息 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +AdminPeers属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +AdminPeers adminPeers = platonWeb3j.adminPeers().send(); +``` + +### adminStartRPC + +> 启动 HTTP RPC API 服务器 + +* **参数** + + - String : host : 要监听的网络地址 + - Integer : port : 要监听的网络端口 + - String : cors : 要使用的跨源资源共享头 + - String : apis : 要透过该服务接口提供服务的API模块 + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +WebSocketService webSocketService = new WebSocketService("ws://127.0.0.1:7790",false); +webSocketService.connect(); +Web3j platonWeb3j = Web3j.build(webSocketService); +BooleanResponse send = platonWeb3j.adminStartRPC("127.0.0.1", 6789, null, null).send(); +``` + +### adminStartWS + +> 启动 websocket RPC API 服务器 + +* **参数** + + - String : host : 要监听的网络地址 + - Integer : port : 要监听的网络端口 + - String : cors : 要使用的跨源资源共享头 + - String : apis : 要透过该服务接口提供服务的API模块 + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = web3j.adminStartWS("127.0.0.1", 7789, null, null).send(); +``` + +### adminStopRPC + +> 关闭当前启动的HTTP RPC端结点 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +WebSocketService webSocketService = new WebSocketService("ws://127.0.0.1:7790",false); +webSocketService.connect(); +Web3j platonWeb3j = Web3j.build(webSocketService); +BooleanResponse send = platonWeb3j.adminStopRPC().send(); +``` + +### adminStopWS + +> 关闭当前启动的WebSocket RPC端结点 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +WebSocketService webSocketService = new WebSocketService("ws://127.0.0.1:7790",false); +webSocketService.connect(); +Web3j platonWeb3j = Web3j.build(webSocketService); +BooleanResponse send = platonWeb3j.adminStopWS().send(); +``` + +### adminExportChain + +> 将当前区块链导出到本地文件中 + +* **参数** + + - String : file : 文件名 + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = platonWeb3j.adminExportChain("1").send(); +``` + +### adminImportChain + +> 从本地文件导入区块链 + +* **参数** + + - String : file : 文件名 + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = platonWeb3j.adminImportChain("1").send(); +``` + +### getWaitSlashingNodeList + +> 获取零出块的节点,因为零出块而被观察的节点列表 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +DebugWaitSlashingNodeList属性中的WaitSlashingNode列表对象即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Request req = platonWeb3j.getWaitSlashingNodeList(); +DebugWaitSlashingNodeList nodeList = req.send(); +``` + +### platonGetRawTransactionByHash + +> 返回给定Hash的交易字节数 + +* **参数** + + - String : hash : 交易hash + +* **返回值** + +```java +Request +``` + +PlatonRawTransaction属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonRawTransaction send = platonWeb3j.platonGetRawTransactionByHash("0x5b99...").send(); +``` + +### platonGetRawTransactionByBlockHashAndIndex + +> 返回给定区块哈希和索引的交易 + +* **参数** + + - String : hash : 块hash + - String : index : 索引 + +* **返回值** + +```java +Request +``` + +PlatonRawTransaction属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonRawTransaction send = platonWeb3j.platonGetRawTransactionByBlockHashAndIndex("0xa34...", "0x1").send(); +``` + +### platonGetRawTransactionByBlockNumberAndIndex + +> 返回给定区块号和索引的交易 + +* **参数** + + - String : blockNumber : 块号 + - String : index : 索引 + +* **返回值** + +```java +Request +``` + +PlatonRawTransaction属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonRawTransaction send = platonWeb3j.platonGetRawTransactionByBlockNumberAndIndex("0x1", "0x1").send(); +``` + +### platonGetAddressHrp + +> 获取链Hrp + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +PlatonGetAddressHrp属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +PlatonGetAddressHrp send = platonWeb3j.platonGetAddressHrp().send(); +``` + +### platonSignTransaction + +> 将使用 from 帐户签署给定的交易。节点需要有给定的发件人地址对应的账户私钥,并且需要解锁 + +* **参数** + + - Transaction : transaction : 交易对象 + +* **返回值** + +```java +Request +``` + +PlatonSignTransaction属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +Transaction transaction = new Transaction("atp1xxx",nonce,gasPrice,gasLimit,toAddress,value,data); +PlatonSignTransaction send = platonWeb3j.platonSignTransaction(transaction).send(); +``` + +### minerSetGasPrice + +> 设置矿工可接受的最低gas price + +* **参数** + + - String : minGasPrice : 最低gas price + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse send = platonWeb3j.minerSetGasPrice("0x1").send(); +``` + +### adminPeerEvents + +> 创建一个 RPC 订阅,它从节点的 p2p服务器接收 peer 事件 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +AdminPeerEvents属性中的result即为对应存储数据 + +* **示例** + +```java +Web3j platonWeb3j = Web3j.build(new HttpService("http://127.0.0.1:6789")); +AdminPeerEvents send = web3j.adminPeerEvents().send(); +``` + +### personalImportRawKey + +> 将给定的十六进制编码密钥存储到密钥目录中,并使用密码对其进行加密 + +* **参数** + + - String : keydata : 私钥 + - String : password : 密码 + +* **返回值** + +```java +Request +``` + +PersonalImportRawKey属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalImportRawKey key = admin.personalImportRawKey("03axxx", "000000").send(); +``` + +### personalLockAccount + +> 锁定与给定地址关联的帐户 + +* **参数** + + - String : address : 账户地址 + +* **返回值** + +```java +Request +``` + +BooleanResponse属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +BooleanResponse response = admin.personalLockAccount("atp1cxxx").send(); +``` + +### personalSign + +> 使用给定的账户对交易进行签名,账户需要存在于节点的账户库中 + +* **参数** + + - String : message : 交易的16进制编码字符串 + - String : accountId : 账户地址 + - String : password : 账户地址密码 + +* **返回值** + +```java +Request +``` + +PersonalSign属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +RawTransaction transaction = RawTransaction.createTransaction(nonce,gasPrice,gasLimit,toAddress,value,data); +byte[] encode = TransactionEncoder.encode(transaction); +String hexSignedTransaction = Numeric.toHexString(encode); +PersonalSign send = admin.personalSign(hexSignedTransaction, "atp1xxx","000000").send(); +``` + +### personalSignAndSendTransaction + +> 使用交易的from地址进行签名并发送交易,from账户需要存在于节点的账户库中 + +* **参数** + + - Transaction : transaction : 交易对象 + - String : password : 账户地址密码 + +* **返回值** + +```java +Request +``` + +PersonalSign属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +Transaction transaction = new Transaction("atp1xxx",nonce,gasPrice,gasLimit,toAddress,value,data); +PersonalSign send = admin.personalSignAndSendTransaction(transaction, "000000").send(); +``` + +### personalSignTransaction + +> 使用交易的from地址进行签名,from账户需要存在于节点的账户库中 + +* **参数** + + - Transaction : transaction : 交易对象 + - String : password : from账户地址密码 + +* **返回值** + +```java +Request +``` + +PlatonSignTransaction属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +Transaction transaction = new Transaction("atp1xxx",nonce,gasPrice,gasLimit,toAddress,value,data); +PlatonSignTransaction send = admin.personalSignTransaction(transaction,"000000").send(); +``` + +### personalEcRecover + +> 返回用于创建签名的帐户的地址 + +* **参数** + + - String : message : 原始数据 + - String : signiture : 签名后的数据 + +* **返回值** + +```java +Request +``` + +PersonalEcRecover属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalEcRecover send = admin.personalEcRecover("0xebxxx","0xa4f5xxx").send(); +``` + +### personalListWallets + +> 返回此节点管理的钱包列表 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +PersonalListWallets属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalListWallets send = admin.personalListWallets().send(); +``` + +### personalOpenWallet + +> 启动硬件钱包打开程序,建立 USB 连接并尝试通过提供的密码进行身份验证。请注意,该方法可能会返回一个需要第二次打开的额外质询(例如,Trezor PIN 矩阵质询) + +* **参数** + + - String : url : 网址 + - String : passphrase : 密码 + +* **返回值** + +```java +Request +``` + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +VoidResponse send = admin.personalOpenWallet("http://localhost:8080", "000000").send(); +``` + +### personalUnlockAccount + +> 使用给定密码解锁与给定地址关联的帐户持续时间秒。默认 300 秒。如果帐户已解锁,它会返回一个指示 + +* **参数** + + - String : address : 地址 + - String : passphrase : 密码 + +* **返回值** + +```java +Request +``` + +PersonalUnlockAccount属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalUnlockAccount response = admin.personalUnlockAccount("atp1xxx", "111111").send(); +``` + +### personalListAccounts + +> 返回此节点管理的帐户的地址列表 + +* **参数** + + 无 + +* **返回值** + +```java +Request +``` + +PersonalListAccounts属性中的result即为对应存储数据 + +* **示例** + +```java +Admin admin = Admin.build(new HttpService("http://127.0.0.1:6789")); +PersonalListAccounts send = admin.personalListAccounts().send(); +``` + ## 系统合约调用 系统接口主要包含经济模型和治理相关的合约接口: