From 24813ff09d0dfca2716a29d1b730d158f4867ceb Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 09:06:36 +0545 Subject: [PATCH 01/10] feat: added cluster connection contract for icon --- .../javascore/cluster-connection/build.gradle | 55 +++ .../adapter/cluster/ClusterConnection.java | 312 ++++++++++++++++++ .../cluster/ClusterConnectionTest.java | 297 +++++++++++++++++ contracts/javascore/settings.gradle | 4 +- 4 files changed, 667 insertions(+), 1 deletion(-) create mode 100644 contracts/javascore/cluster-connection/build.gradle create mode 100644 contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java create mode 100644 contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java diff --git a/contracts/javascore/cluster-connection/build.gradle b/contracts/javascore/cluster-connection/build.gradle new file mode 100644 index 00000000..91358eed --- /dev/null +++ b/contracts/javascore/cluster-connection/build.gradle @@ -0,0 +1,55 @@ +version = '0.1.0' + +dependencies { + implementation project(':xcall-lib') + + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' + testImplementation 'foundation.icon:javaee-unittest:0.11.1' + testImplementation project(':test-lib') +} + +test { + useJUnitPlatform() + finalizedBy jacocoTestReport +} + +optimizedJar { + dependsOn(project(':xcall-lib').jar) + mainClassName = 'xcall.adapter.cluster.ClusterConnection' + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +deployJar { + endpoints { + berlin { + uri = 'https://berlin.net.solidwallet.io/api/v3' + nid = 0x7 + } + lisbon { + uri = 'https://lisbon.net.solidwallet.io/api/v3' + nid = 0x2 + } + local { + uri = 'http://localhost:9082/api/v3' + nid = 0x3 + } + mainnet { + uri = 'https://ctz.solidwallet.io/api/v3' + nid = 0x1 + } + uat { + uri = project.findProperty('uat.host') as String + nid = property('uat.nid') as Integer + to = "$mockDApp"?:null + } + } + keystore = rootProject.hasProperty('keystoreName') ? "$keystoreName" : '' + password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : '' + parameters { + arg('_relayer', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") + arg('_xCall', "$xCall") + } +} \ No newline at end of file diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java new file mode 100644 index 00000000..30affe7c --- /dev/null +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -0,0 +1,312 @@ +/* + * Copyright 2022 ICON Foundation + * + * 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 xcall.adapter.cluster; + +import java.math.BigInteger; +import score.Context; + +import score.Address; +import score.BranchDB; +import score.DictDB; +import score.VarDB; +import score.ArrayDB; +import scorex.util.ArrayList; + +import score.annotation.EventLog; +import score.annotation.External; +import score.annotation.Payable; +import java.util.List; + + + +public class ClusterConnection { + protected final VarDB
xCall = Context.newVarDB("callService", Address.class); + protected final VarDB
adminAddress = Context.newVarDB("relayer", Address.class); + protected final VarDB reqSignerCnt = Context.newVarDB("reqSignerCnt", BigInteger.class); + private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); + private final ArrayDB
signers = Context.newArrayDB("signers", Address.class); + + protected final DictDB messageFees = Context.newDictDB("messageFees", BigInteger.class); + protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); + protected final BranchDB> receipts = Context.newBranchDB("receipts", + Boolean.class); + + public ClusterConnection(Address _relayer, Address _xCall) { + if (xCall.get() == null) { + xCall.set(_xCall); + adminAddress.set(_relayer); + connSn.set(BigInteger.ZERO); + signers.add(_relayer); + SignerAdded(_relayer); + } + } + + /** + * Retrieves the signers. + * + * @return The signers . + */ + @External(readonly = true) + public Address[] listSigners() { + Address[] sgs = new Address[signers.size()]; + for(int i=0; i < signers.size(); i++) { + sgs[i] = signers.get(i); + } + return sgs; + } + + @External + public void addSigner(Address signer) { + OnlyAdmin(); + if (!signerExists(signer)){ + signers.add(signer); + SignerAdded(signer); + } + } + + @External + public void removeSigner(Address _signer) { + OnlyAdmin(); + Context.require(_signer != adminAddress.get(),"cannot remove admin"); + if (signerExists(_signer)){ + Address top = this.signers.pop(); + if (!top.equals(_signer)) { + for (int i = 0; i < this.signers.size(); i++) { + if (_signer.equals(this.signers.get(i))) { + this.signers.set(i, top); + break; + } + } + } + SignerRemoved(_signer); + } + } + + @EventLog(indexed = 2) + public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { + } + + @EventLog(indexed = 1) + public void SignerAdded(Address _signer) { + } + + @EventLog(indexed = 1) + public void SignerRemoved(Address _signer) { + } + + /** + * Sets the admin address. + * + * @param _relayer the new admin address + */ + @External + public void setAdmin(Address _relayer) { + OnlyAdmin(); + adminAddress.set(_relayer); + } + + /** + * Retrieves the admin address. + * + * @return The admin address. + */ + @External(readonly = true) + public Address admin() { + return adminAddress.get(); + } + + /** + * Sets the required signer count + * + * @param _signerCnt the new required signer count + */ + @External + public void setRequiredSignerCount(BigInteger _signerCnt) { + OnlyAdmin(); + reqSignerCnt.set(_signerCnt); + } + + /** + * Retrieves the required signer count. + * + * @return The required signer count. + */ + @External(readonly = true) + public BigInteger requiredSignerCount() { + return reqSignerCnt.get(); + } + + /** + * Sets the fee to the target network + * + * @param networkId String Network Id of target chain + * @param messageFee The fee needed to send a Message + * @param responseFee The fee of the response + */ + @External + public void setFee(String networkId, BigInteger messageFee, BigInteger responseFee) { + OnlyAdmin(); + messageFees.set(networkId, messageFee); + responseFees.set(networkId, responseFee); + } + + /** + * Returns the fee associated with the given destination address. + * + * @param to String Network Id of target chain + * @param response whether the responding fee is included + * @return The fee of sending a message to a given destination network + */ + @External(readonly = true) + public BigInteger getFee(String to, boolean response) { + BigInteger messageFee = messageFees.getOrDefault(to, BigInteger.ZERO); + if (response) { + BigInteger responseFee = responseFees.getOrDefault(to, BigInteger.ZERO); + return messageFee.add(responseFee); + } + return messageFee; + } + + /** + * Sends a message to the specified network. + * + * @param to Network Id of destination network + * @param svc name of the service + * @param sn positive for two-way message, zero for one-way message, negative + * for response(for xcall message) + * @param msg serialized bytes of Service Message + */ + @Payable + @External + public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { + Context.require(Context.getCaller().equals(xCall.get()), "Only xCall can send messages"); + BigInteger fee = BigInteger.ZERO; + if (sn.compareTo(BigInteger.ZERO) > 0) { + fee = getFee(to, true); + } else if (sn.equals(BigInteger.ZERO)) { + fee = getFee(to, false); + } + + BigInteger nextConnSn = connSn.get().add(BigInteger.ONE); + connSn.set(nextConnSn); + + Context.require(Context.getValue().compareTo(fee) >= 0, "Insufficient balance"); + Message(to, nextConnSn, msg); + } + + /** + * Receives a message from a source network. + * + * @param srcNetwork the source network id from which the message is received + * @param _connSn the serial number of the connection message + * @param msg serialized bytes of Service Message + */ + @External + public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, + byte[][] signatures) { + OnlyAdmin(); + List
uniqueSigners = new ArrayList<>(); + for (byte[] signature : signatures) { + Address signer = getSigner(msg, signature); + Context.require(signerExists(signer), "Invalid signer"); + if (!uniqueSigners.contains(signer)){ + uniqueSigners.add(signer); + } + } + if (uniqueSigners.size() >= reqSignerCnt.get().intValue()){ + recvMessage(srcNetwork, _connSn, msg); + } + } + + private boolean signerExists(Address signer) { + for (int i = 0; i < signers.size(); i++) { + if (signers.get(i).equals(signer)) { + return true; + } + } + return false; + } + + /** + * Receives a message from a source network. + * + * @param srcNetwork the source network id from which the message is received + * @param _connSn the serial number of the connection message + * @param msg serialized bytes of Service Message + */ + @External + public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { + OnlyAdmin(); + Context.require(!receipts.at(srcNetwork).getOrDefault(_connSn, false), "Duplicate Message"); + receipts.at(srcNetwork).set(_connSn, true); + Context.call(xCall.get(), "handleMessage", srcNetwork, msg); + } + + private Address getSigner(byte[] msg,byte[] sig){ + byte[] hashMessage = getHash(msg); + byte[] key = Context.recoverKey("ecdsa-secp256k1", hashMessage, sig, true); + return Context.getAddressFromKey(key); + } + + private byte[] getHash(byte[] msg){ + return Context.hash("keccak-256", msg); + } + + /** + * Reverts a message. + * + * @param sn the serial number of xcall message representing the message to + * revert + */ + @External + public void revertMessage(BigInteger sn) { + OnlyAdmin(); + Context.call(xCall.get(), "handleError", sn); + } + + /** + * Claim the fees. + * + */ + @External + public void claimFees() { + OnlyAdmin(); + Context.transfer(admin(), Context.getBalance(Context.getAddress())); + } + + /** + * Get the receipts for a given source network and serial number. + * + * @param srcNetwork the source network id + * @param _connSn the serial number of connection message + * @return the receipt if is has been recived or not + */ + @External(readonly = true) + public boolean getReceipts(String srcNetwork, BigInteger _connSn) { + return receipts.at(srcNetwork).getOrDefault(_connSn, false); + } + + /** + * Checks if the caller of the function is the admin. + * + * @return true if the caller is the admin, false otherwise + */ + private void OnlyAdmin() { + Context.require(Context.getCaller().equals(adminAddress.get()), "Only admin can call this function"); + } + +} \ No newline at end of file diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java new file mode 100644 index 00000000..77cd6396 --- /dev/null +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -0,0 +1,297 @@ +package xcall.adapter.cluster; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import java.security.*; + +import java.beans.Transient; +import java.math.BigInteger; +import score.Context; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jcajce.provider.digest.Keccak; + +import foundation.icon.icx.KeyWallet; + + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import com.iconloop.score.test.Account; +import com.iconloop.score.test.Score; +import com.iconloop.score.test.ServiceManager; +import com.iconloop.score.test.TestBase; + +import xcall.adapter.cluster.ClusterConnection; +import score.UserRevertedException; +import score.Address; +import foundation.icon.ee.types.Bytes; +import foundation.icon.icx.Call; +import foundation.icon.score.client.RevertedException; +import foundation.icon.xcall.CSMessage; +import foundation.icon.xcall.CSMessageRequest; +import foundation.icon.xcall.CallService; +import foundation.icon.xcall.CallServiceReceiver; +import foundation.icon.xcall.CallServiceScoreInterface; +import foundation.icon.xcall.ConnectionScoreInterface; +import foundation.icon.xcall.Connection; +import foundation.icon.xcall.NetworkAddress; +import s.java.math.BigDecimal; + +import xcall.icon.test.MockContract; + +public class ClusterConnectionTest extends TestBase { + protected final ServiceManager sm = getServiceManager(); + + protected final Account owner = sm.createAccount(); + protected final Account user = sm.createAccount(); + protected final Account admin = sm.createAccount(); + protected final Account xcallMock = sm.createAccount(); + + protected final Account source_relayer = sm.createAccount(); + protected final Account destination_relayer = sm.createAccount(); + + protected Score xcall, connection; + protected CallService xcallSpy; + protected ClusterConnection connectionSpy; + + protected static String nidSource = "nid.source"; + protected static String nidTarget = "nid.target"; + + // static MockedStatic contextMock; + + protected MockContract callservice; + + // @BeforeAll + // protected static void init() { + // contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS); + // } + + @BeforeEach + public void setup() throws Exception { + Security.addProvider(new BouncyCastleProvider()); + callservice = new MockContract<>(CallServiceScoreInterface.class, CallService.class, sm, owner); + + // xcall = sm.deploy(owner, CallService.class, nidSource); + // xcallSpy = (CallService) spy(xcall.getInstance()); + // xcall.setInstance(xcallSpy); + // contextMock.reset(); + + connection = sm.deploy(owner, ClusterConnection.class, source_relayer.getAddress(), + callservice.getAddress()); + connectionSpy = (ClusterConnection) spy(connection.getInstance()); + connection.setInstance(connectionSpy); + } + + @Test + public void testSetAdmin() { + // connection.invoke(source_relayer, "setFee", "0xevm", BigInteger.TEN, + // BigInteger.TEN); + + connection.invoke(source_relayer, "setAdmin", admin.getAddress()); + assertEquals(connection.call("admin"), admin.getAddress()); + } + + @Test + public void testSetAdmin_unauthorized() { + UserRevertedException e = assertThrows(UserRevertedException.class, + () -> connection.invoke(user, "setAdmin", admin.getAddress())); + assertEquals("Reverted(0): " + "Only admin can call this function", e.getMessage()); + } + + @Test + public void setFee() { + connection.invoke(source_relayer, "setFee", nidTarget, BigInteger.TEN, BigInteger.TEN); + assertEquals(connection.call("getFee", nidTarget, true), BigInteger.TEN.add(BigInteger.TEN)); + } + + @Test + public void sendMessage() { + connection.invoke(callservice.account, "sendMessage", nidTarget, "xcall", BigInteger.ONE, "test".getBytes()); + verify(connectionSpy).Message(nidTarget, BigInteger.ONE, "test".getBytes()); + } + + @Test + public void testRecvMessage() { + connection.invoke(source_relayer, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes()); + verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); + } + + @Test + public void testRecvMessage_unauthorized(){ + + UserRevertedException e = assertThrows(UserRevertedException.class, ()-> connection.invoke(xcallMock, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes())); + assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + } + + @Test + public void testSendMessage_unauthorized() { + UserRevertedException e = assertThrows(UserRevertedException.class, + () -> connection.invoke(user, "sendMessage", nidTarget, "xcall", BigInteger.ONE, "test".getBytes())); + assertEquals("Reverted(0): " + "Only xCall can send messages", e.getMessage()); + } + + @Test + public void testRecvMessage_duplicateMsg(){ + connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); + + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(source_relayer, "recvMessage", + nidSource, BigInteger.ONE, "test".getBytes())); + assertEquals(e.getMessage(), "Reverted(0): "+"Duplicate Message"); + } + + @Test + public void testRevertMessage() { + + connection.invoke(source_relayer, "revertMessage", BigInteger.ONE); + } + + @Test + public void testRevertMessage_unauthorized(){ + UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(user, "revertMessage", BigInteger.ONE)); + assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + + } + + @Test + public void testSetFeesUnauthorized(){ + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "setFee", "0xevm", + BigInteger.TEN, BigInteger.TEN)); + assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + } + + @Test + public void testClaimFees(){ + setFee(); + connection.invoke(source_relayer, "claimFees"); + assertEquals(source_relayer.getBalance(), BigInteger.ZERO); + + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(callservice.account, "sendMessage", nidTarget, + "xcall", BigInteger.ONE, "null".getBytes())); + assertEquals(e.getMessage(), "Reverted(0): Insufficient balance"); + + try (MockedStatic contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS)) { + contextMock.when(() -> Context.getValue()).thenReturn(BigInteger.valueOf(20)); + connection.invoke(callservice.account, "sendMessage", nidTarget,"xcall", BigInteger.ONE, "null".getBytes()); + } + + + try (MockedStatic contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS)) { + contextMock.when(() -> Context.getBalance(connection.getAddress())).thenReturn(BigInteger.valueOf(20)); + contextMock.when(() -> Context.transfer(source_relayer.getAddress(),BigInteger.valueOf(20))).then(invocationOnMock -> null); + connection.invoke(source_relayer, "claimFees"); + } + } + + @Test + public void testClaimFees_unauthorized(){ + setFee(); + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "claimFees")); + assertEquals(e.getMessage(), "Reverted(0): "+"Only admin can call this function"); + } + + public MockedStatic.Verification value() { + return () -> Context.getValue(); + } + + @Test + public void testGetReceipt(){ + assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), + false); + + connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); + + assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), + true); + } + + @Test + public void testRecvMessageWithSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + byte[][] byteArray = new byte[1][]; + KeyWallet wallet = KeyWallet.create(); + byteArray[0] = wallet.sign(messageHash); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.ONE); + connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); + } + + @Test + public void testRecvMessageWithMultiSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + byte[][] byteArray = new byte[2][]; + KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); + byteArray[0] = wallet.sign(messageHash); + byteArray[1] = wallet2.sign(messageHash); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); + } + + @Test + public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + KeyWallet wallet = KeyWallet.create(); + byte[][] byteArray = new byte[1][]; + byteArray[0] = wallet.sign(messageHash); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + verifyNoInteractions(callservice.mock); + } + + + // Hash the message with Keccak-256 + public static byte[] keccak256(byte[] input) { + Keccak.Digest256 keccak256 = new Keccak.Digest256(); + return keccak256.digest(input); + } + + @Test + public void testAddSigners() throws Exception{ + KeyWallet wallet = KeyWallet.create(); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 2); + } + + @Test + public void testAddNRemoveSigners() throws Exception{ + KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); + KeyWallet wallet3 = KeyWallet.create(); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 3); + + connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet3.getAddress().toString())); + signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 3); + + connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet2.getAddress().toString())); + signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 2); + } + +} \ No newline at end of file diff --git a/contracts/javascore/settings.gradle b/contracts/javascore/settings.gradle index 6e6fdcdf..e654c0c0 100644 --- a/contracts/javascore/settings.gradle +++ b/contracts/javascore/settings.gradle @@ -3,7 +3,9 @@ include( 'test-lib', 'xcall', 'xcall-lib', - 'centralized-connection' + 'centralized-connection', + 'cluster-connection' + ) include(':dapp-simple') From 370edb798c1165d4209be7f55d7326b9f056ba5d Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 09:34:26 +0545 Subject: [PATCH 02/10] chore: added constraints --- .../adapter/cluster/ClusterConnection.java | 36 ++++++++++--------- .../cluster/ClusterConnectionTest.java | 16 ++------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 30affe7c..51d1ee4f 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -208,29 +208,31 @@ public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { Message(to, nextConnSn, msg); } - /** + /** * Receives a message from a source network. * * @param srcNetwork the source network id from which the message is received * @param _connSn the serial number of the connection message * @param msg serialized bytes of Service Message + * @param signatures array of signatures */ - @External - public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, - byte[][] signatures) { - OnlyAdmin(); - List
uniqueSigners = new ArrayList<>(); - for (byte[] signature : signatures) { - Address signer = getSigner(msg, signature); - Context.require(signerExists(signer), "Invalid signer"); - if (!uniqueSigners.contains(signer)){ - uniqueSigners.add(signer); - } - } - if (uniqueSigners.size() >= reqSignerCnt.get().intValue()){ - recvMessage(srcNetwork, _connSn, msg); - } - } + @External + public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, + byte[][] signatures) { + OnlyAdmin(); + Context.require(signatures.length >= reqSignerCnt.get().intValue(), "Not enough signatures"); + List
uniqueSigners = new ArrayList<>(); + for (byte[] signature : signatures) { + Address signer = getSigner(msg, signature); + Context.require(signerExists(signer), "Invalid signature provided"); + if (!uniqueSigners.contains(signer)) { + uniqueSigners.add(signer); + } + } + if (uniqueSigners.size() >= reqSignerCnt.get().intValue()) { + recvMessage(srcNetwork, _connSn, msg); + } + } private boolean signerExists(Address signer) { for (int i = 0; i < signers.size(); i++) { diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 77cd6396..6fa7dd38 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -75,20 +75,11 @@ public class ClusterConnectionTest extends TestBase { protected MockContract callservice; - // @BeforeAll - // protected static void init() { - // contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS); - // } - @BeforeEach public void setup() throws Exception { Security.addProvider(new BouncyCastleProvider()); callservice = new MockContract<>(CallServiceScoreInterface.class, CallService.class, sm, owner); - // xcall = sm.deploy(owner, CallService.class, nidSource); - // xcallSpy = (CallService) spy(xcall.getInstance()); - // xcall.setInstance(xcallSpy); - // contextMock.reset(); connection = sm.deploy(owner, ClusterConnection.class, source_relayer.getAddress(), callservice.getAddress()); @@ -98,8 +89,6 @@ public void setup() throws Exception { @Test public void testSetAdmin() { - // connection.invoke(source_relayer, "setFee", "0xevm", BigInteger.TEN, - // BigInteger.TEN); connection.invoke(source_relayer, "setAdmin", admin.getAddress()); assertEquals(connection.call("admin"), admin.getAddress()); @@ -256,12 +245,13 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byteArray[0] = wallet.sign(messageHash); connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); - connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + UserRevertedException e = assertThrows(UserRevertedException.class, + ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); + assertEquals("Reverted(0): Not enough signatures", e.getMessage()); verifyNoInteractions(callservice.mock); } - // Hash the message with Keccak-256 public static byte[] keccak256(byte[] input) { Keccak.Digest256 keccak256 = new Keccak.Digest256(); return keccak256.digest(input); From e851994872eb7e9badef34bc09a5d51de707635c Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 11:15:28 +0545 Subject: [PATCH 03/10] chore: code refactorings --- .../adapter/cluster/ClusterConnection.java | 90 +++++++++---------- .../cluster/ClusterConnectionTest.java | 32 +++---- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 51d1ee4f..0044e441 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -36,9 +36,9 @@ public class ClusterConnection { protected final VarDB
xCall = Context.newVarDB("callService", Address.class); protected final VarDB
adminAddress = Context.newVarDB("relayer", Address.class); - protected final VarDB reqSignerCnt = Context.newVarDB("reqSignerCnt", BigInteger.class); + protected final VarDB reqValidatorCnt = Context.newVarDB("reqValidatorCnt", BigInteger.class); private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); - private final ArrayDB
signers = Context.newArrayDB("signers", Address.class); + private final ArrayDB
validators = Context.newArrayDB("signers", Address.class); protected final DictDB messageFees = Context.newDictDB("messageFees", BigInteger.class); protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); @@ -50,49 +50,49 @@ public ClusterConnection(Address _relayer, Address _xCall) { xCall.set(_xCall); adminAddress.set(_relayer); connSn.set(BigInteger.ZERO); - signers.add(_relayer); - SignerAdded(_relayer); + validators.add(_relayer); + ValidatorAdded(_relayer); } } /** - * Retrieves the signers. + * Retrieves the validators. * - * @return The signers . + * @return The validators . */ @External(readonly = true) - public Address[] listSigners() { - Address[] sgs = new Address[signers.size()]; - for(int i=0; i < signers.size(); i++) { - sgs[i] = signers.get(i); + public Address[] listValidators() { + Address[] sgs = new Address[validators.size()]; + for(int i = 0; i < validators.size(); i++) { + sgs[i] = validators.get(i); } return sgs; } @External - public void addSigner(Address signer) { + public void addValidator(Address _validator) { OnlyAdmin(); - if (!signerExists(signer)){ - signers.add(signer); - SignerAdded(signer); + if (!validatorExists(_validator)){ + validators.add(_validator); + ValidatorAdded(_validator); } } @External - public void removeSigner(Address _signer) { + public void removeValidator(Address _validator) { OnlyAdmin(); - Context.require(_signer != adminAddress.get(),"cannot remove admin"); - if (signerExists(_signer)){ - Address top = this.signers.pop(); - if (!top.equals(_signer)) { - for (int i = 0; i < this.signers.size(); i++) { - if (_signer.equals(this.signers.get(i))) { - this.signers.set(i, top); + Context.require(_validator != adminAddress.get(),"cannot remove admin"); + if (validatorExists(_validator)){ + Address top = this.validators.pop(); + if (!top.equals(_validator)) { + for (int i = 0; i < this.validators.size(); i++) { + if (_validator.equals(this.validators.get(i))) { + this.validators.set(i, top); break; } } - } - SignerRemoved(_signer); + } + ValidatorRemoved(_validator); } } @@ -101,11 +101,11 @@ public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { } @EventLog(indexed = 1) - public void SignerAdded(Address _signer) { + public void ValidatorAdded(Address _validator) { } @EventLog(indexed = 1) - public void SignerRemoved(Address _signer) { + public void ValidatorRemoved(Address _validator) { } /** @@ -130,24 +130,24 @@ public Address admin() { } /** - * Sets the required signer count + * Sets the required validator count * - * @param _signerCnt the new required signer count + * @param _validatorCnt the new required validator count */ @External - public void setRequiredSignerCount(BigInteger _signerCnt) { + public void setRequiredValidatorCount(BigInteger _validatorCnt) { OnlyAdmin(); - reqSignerCnt.set(_signerCnt); + reqValidatorCnt.set(_validatorCnt); } /** - * Retrieves the required signer count. + * Retrieves the required validator count. * - * @return The required signer count. + * @return The required validator count. */ @External(readonly = true) - public BigInteger requiredSignerCount() { - return reqSignerCnt.get(); + public BigInteger requiredValidatorCount() { + return reqValidatorCnt.get(); } /** @@ -220,23 +220,23 @@ public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, byte[][] signatures) { OnlyAdmin(); - Context.require(signatures.length >= reqSignerCnt.get().intValue(), "Not enough signatures"); - List
uniqueSigners = new ArrayList<>(); + Context.require(signatures.length >= reqValidatorCnt.get().intValue(), "Not enough signatures"); + List
uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { - Address signer = getSigner(msg, signature); - Context.require(signerExists(signer), "Invalid signature provided"); - if (!uniqueSigners.contains(signer)) { - uniqueSigners.add(signer); + Address validator = getValidator(msg, signature); + Context.require(validatorExists(validator), "Invalid signature provided"); + if (!uniqueValidators.contains(validator)) { + uniqueValidators.add(validator); } } - if (uniqueSigners.size() >= reqSignerCnt.get().intValue()) { + if (uniqueValidators.size() >= reqValidatorCnt.get().intValue()) { recvMessage(srcNetwork, _connSn, msg); } } - private boolean signerExists(Address signer) { - for (int i = 0; i < signers.size(); i++) { - if (signers.get(i).equals(signer)) { + private boolean validatorExists(Address _validator) { + for (int i = 0; i < validators.size(); i++) { + if (validators.get(i).equals(_validator)) { return true; } } @@ -258,7 +258,7 @@ public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { Context.call(xCall.get(), "handleMessage", srcNetwork, msg); } - private Address getSigner(byte[] msg,byte[] sig){ + private Address getValidator(byte[] msg, byte[] sig){ byte[] hashMessage = getHash(msg); byte[] key = Context.recoverKey("ecdsa-secp256k1", hashMessage, sig, true); return Context.getAddressFromKey(key); diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 6fa7dd38..61438ad1 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -214,8 +214,8 @@ public void testRecvMessageWithSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.ONE); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -229,9 +229,9 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -243,8 +243,8 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ KeyWallet wallet = KeyWallet.create(); byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough signatures", e.getMessage()); @@ -260,8 +260,8 @@ public static byte[] keccak256(byte[] input) { @Test public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - Address[] signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } @@ -270,17 +270,17 @@ public void testAddNRemoveSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); KeyWallet wallet2 = KeyWallet.create(); KeyWallet wallet3 = KeyWallet.create(); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); - Address[] signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 3); - connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet3.getAddress().toString())); - signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString())); + signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 3); - connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet2.getAddress().toString())); - signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet2.getAddress().toString())); + signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } From 70ad7558a5732a98bcc65eff70c2cd05eb4237c2 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Mon, 7 Oct 2024 15:34:31 +0545 Subject: [PATCH 04/10] chore: updated check and reverted if not enough valid sigs --- .../xcall/adapter/cluster/ClusterConnection.java | 5 ++--- .../adapter/cluster/ClusterConnectionTest.java | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 0044e441..2cbc74c5 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -229,9 +229,8 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt uniqueValidators.add(validator); } } - if (uniqueValidators.size() >= reqValidatorCnt.get().intValue()) { - recvMessage(srcNetwork, _connSn, msg); - } + Context.require(uniqueValidators.size() >= reqValidatorCnt.get().intValue(), "Not enough valid signatures"); + recvMessage(srcNetwork, _connSn, msg); } private boolean validatorExists(Address _validator) { diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 61438ad1..52f82be0 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -251,6 +251,22 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ verifyNoInteractions(callservice.mock); } + @Test + public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + KeyWallet wallet = KeyWallet.create(); + byte[][] byteArray = new byte[2][]; + byteArray[0] = wallet.sign(messageHash); + byteArray[1] = wallet.sign(messageHash); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + UserRevertedException e = assertThrows(UserRevertedException.class, + ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); + assertEquals("Reverted(0): Not enough valid signatures", e.getMessage()); + verifyNoInteractions(callservice.mock); + } + public static byte[] keccak256(byte[] input) { Keccak.Digest256 keccak256 = new Keccak.Digest256(); From b5efc7720f447fc3326bbe3884d068adc1bc8591 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Tue, 8 Oct 2024 11:55:20 +0545 Subject: [PATCH 05/10] fix: revert early and added lookup for validators --- .../adapter/cluster/ClusterConnection.java | 39 ++++++++----------- .../cluster/ClusterConnectionTest.java | 15 +++---- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 2cbc74c5..bb440c38 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -44,6 +44,7 @@ public class ClusterConnection { protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); protected final BranchDB> receipts = Context.newBranchDB("receipts", Boolean.class); + private final DictDB validatorsLookup = Context.newDictDB("validatorsLookup", Boolean.class); public ClusterConnection(Address _relayer, Address _xCall) { if (xCall.get() == null) { @@ -51,6 +52,7 @@ public ClusterConnection(Address _relayer, Address _xCall) { adminAddress.set(_relayer); connSn.set(BigInteger.ZERO); validators.add(_relayer); + validatorsLookup.set(_relayer,true); ValidatorAdded(_relayer); } } @@ -72,28 +74,30 @@ public Address[] listValidators() { @External public void addValidator(Address _validator) { OnlyAdmin(); - if (!validatorExists(_validator)){ - validators.add(_validator); - ValidatorAdded(_validator); - } + Context.require(validatorsLookup.get(_validator)==null,"Validator already exists"); + validators.add(_validator); + validatorsLookup.set(_validator,true); + ValidatorAdded(_validator); } @External public void removeValidator(Address _validator) { OnlyAdmin(); Context.require(_validator != adminAddress.get(),"cannot remove admin"); - if (validatorExists(_validator)){ - Address top = this.validators.pop(); - if (!top.equals(_validator)) { - for (int i = 0; i < this.validators.size(); i++) { - if (_validator.equals(this.validators.get(i))) { - this.validators.set(i, top); - break; - } + Context.require(validatorsLookup.get(_validator)!=null,"Validator doesn't exists"); + Context.require((this.validators.size() - 1) >= reqValidatorCnt.get().intValue(),"Validator size less than required count after removal"); + Address top = this.validators.pop(); + if (!top.equals(_validator)) { + for (int i = 0; i < this.validators.size(); i++) { + if (_validator.equals(this.validators.get(i))) { + this.validators.set(i, top); + break; } } + validatorsLookup.set(_validator,null); ValidatorRemoved(_validator); } + } @EventLog(indexed = 2) @@ -224,7 +228,7 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt List
uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { Address validator = getValidator(msg, signature); - Context.require(validatorExists(validator), "Invalid signature provided"); + Context.require(validatorsLookup.get(validator)!=null, "Invalid signature provided"); if (!uniqueValidators.contains(validator)) { uniqueValidators.add(validator); } @@ -233,15 +237,6 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt recvMessage(srcNetwork, _connSn, msg); } - private boolean validatorExists(Address _validator) { - for (int i = 0; i < validators.size(); i++) { - if (validators.get(i).equals(_validator)) { - return true; - } - } - return false; - } - /** * Receives a message from a source network. * diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 52f82be0..fd021c14 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -284,18 +284,19 @@ public void testAddSigners() throws Exception{ @Test public void testAddNRemoveSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - KeyWallet wallet2 = KeyWallet.create(); KeyWallet wallet3 = KeyWallet.create(); connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); Address[] signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 3); + assertEquals(signers.length, 2); - connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString())); - signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 3); + UserRevertedException e = assertThrows(UserRevertedException.class, + ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString()))); + assertEquals("Reverted(0): Validator doesn't exists", e.getMessage()); - connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet2.getAddress().toString())); + UserRevertedException ex = assertThrows(UserRevertedException.class, + ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet.getAddress().toString()))); + assertEquals("Reverted(0): Validator size less than required count after removal", ex.getMessage()); signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } From 53bcc9c8840745cf64cfb76cdae2a187a4fea995 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Thu, 17 Oct 2024 16:43:55 +0545 Subject: [PATCH 06/10] fix: cluster connection spec change --- .../adapter/cluster/ClusterConnection.java | 165 ++++++++++++------ .../cluster/ClusterConnectionTest.java | 94 ++++------ 2 files changed, 138 insertions(+), 121 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index bb440c38..f60f0e4e 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -21,6 +21,7 @@ import score.Address; import score.BranchDB; +import score.ByteArrayObjectWriter; import score.DictDB; import score.VarDB; import score.ArrayDB; @@ -35,8 +36,9 @@ public class ClusterConnection { protected final VarDB
xCall = Context.newVarDB("callService", Address.class); - protected final VarDB
adminAddress = Context.newVarDB("relayer", Address.class); - protected final VarDB reqValidatorCnt = Context.newVarDB("reqValidatorCnt", BigInteger.class); + protected final VarDB
adminAddress = Context.newVarDB("admin", Address.class); + protected final VarDB
relayerAddress = Context.newVarDB("relayer", Address.class); + protected final VarDB validatorsThreshold = Context.newVarDB("reqValidatorCnt", BigInteger.class); private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); private final ArrayDB
validators = Context.newArrayDB("signers", Address.class); @@ -44,16 +46,12 @@ public class ClusterConnection { protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); protected final BranchDB> receipts = Context.newBranchDB("receipts", Boolean.class); - private final DictDB validatorsLookup = Context.newDictDB("validatorsLookup", Boolean.class); - public ClusterConnection(Address _relayer, Address _xCall) { if (xCall.get() == null) { xCall.set(_xCall); - adminAddress.set(_relayer); + adminAddress.set(Context.getCaller()); + relayerAddress.set(_relayer); connSn.set(BigInteger.ZERO); - validators.add(_relayer); - validatorsLookup.set(_relayer,true); - ValidatorAdded(_relayer); } } @@ -71,57 +69,87 @@ public Address[] listValidators() { return sgs; } +/** + * Adds a list of validators and sets the validation threshold. + * + * Clears existing validators and adds the provided addresses as validators. + * Ensures that the caller is an admin and that the number of validators + * meets or exceeds the specified threshold. + * + * @param _validators an array of addresses to be added as validators + * @param _threshold the minimum required number of validators + * @throws Exception if the number of validators is less than the threshold + */ @External - public void addValidator(Address _validator) { + public void addValidator(Address[] _validators, BigInteger _threshold) { OnlyAdmin(); - Context.require(validatorsLookup.get(_validator)==null,"Validator already exists"); - validators.add(_validator); - validatorsLookup.set(_validator,true); - ValidatorAdded(_validator); + clearValidators(); + for (Address validator : _validators) { + if(!isValidator(validator)) { + validators.add(validator); + } + } + Context.require(validators.size() >= _threshold.intValue(), "Not enough validators"); + validatorsThreshold.set(_threshold); + ValidatorSetAdded(_validators, _threshold); } - @External - public void removeValidator(Address _validator) { - OnlyAdmin(); - Context.require(_validator != adminAddress.get(),"cannot remove admin"); - Context.require(validatorsLookup.get(_validator)!=null,"Validator doesn't exists"); - Context.require((this.validators.size() - 1) >= reqValidatorCnt.get().intValue(),"Validator size less than required count after removal"); - Address top = this.validators.pop(); - if (!top.equals(_validator)) { - for (int i = 0; i < this.validators.size(); i++) { - if (_validator.equals(this.validators.get(i))) { - this.validators.set(i, top); - break; - } - } - validatorsLookup.set(_validator,null); - ValidatorRemoved(_validator); + /** + * Clear the current validators. + * + * This is a private helper method called by addValidator. + */ + private void clearValidators() { + for(int i = 0; i < validators.size(); i++) { + validators.set(i, null); } + } +/** + * Checks if the provided address is a validator. + * + * @param validator the address to check for validation + * @return true if the address is a validator, false otherwise + */ + @External(readonly = true) + public boolean isValidator(Address validator) { + for(int i = 0; i < validators.size(); i++) { + if(validator.equals(validators.get(i))) { + return true; + } + } + return false; } @EventLog(indexed = 2) public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { } - @EventLog(indexed = 1) - public void ValidatorAdded(Address _validator) { - } - - @EventLog(indexed = 1) - public void ValidatorRemoved(Address _validator) { + @EventLog(indexed = 0) + public void ValidatorSetAdded(Address[] _validators, BigInteger _threshold) { } /** - * Sets the admin address. + * Sets the relayer address. * * @param _relayer the new admin address */ @External - public void setAdmin(Address _relayer) { + public void setRelayer(Address _relayer) { OnlyAdmin(); - adminAddress.set(_relayer); - } + relayerAddress.set(_relayer); + } + + /** + * Sets the admin address. + * + * @param _admin the new admin address + */ + @External + public void setAdmin(Address _admin) { + OnlyAdmin(); + adminAddress.set(_admin); + } /** * Retrieves the admin address. @@ -141,7 +169,7 @@ public Address admin() { @External public void setRequiredValidatorCount(BigInteger _validatorCnt) { OnlyAdmin(); - reqValidatorCnt.set(_validatorCnt); + validatorsThreshold.set(_validatorCnt); } /** @@ -151,7 +179,7 @@ public void setRequiredValidatorCount(BigInteger _validatorCnt) { */ @External(readonly = true) public BigInteger requiredValidatorCount() { - return reqValidatorCnt.get(); + return validatorsThreshold.get(); } /** @@ -163,7 +191,7 @@ public BigInteger requiredValidatorCount() { */ @External public void setFee(String networkId, BigInteger messageFee, BigInteger responseFee) { - OnlyAdmin(); + OnlyRelayer(); messageFees.set(networkId, messageFee); responseFees.set(networkId, responseFee); } @@ -223,17 +251,18 @@ public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { @External public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, byte[][] signatures) { - OnlyAdmin(); - Context.require(signatures.length >= reqValidatorCnt.get().intValue(), "Not enough signatures"); + OnlyRelayer(); + Context.require(signatures.length >= validatorsThreshold.get().intValue(), "Not enough signatures"); + byte[] messageHash = getMessageHash(srcNetwork, _connSn, msg); List
uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { - Address validator = getValidator(msg, signature); - Context.require(validatorsLookup.get(validator)!=null, "Invalid signature provided"); + Address validator = getValidator(messageHash, signature); + Context.require(isValidator(validator), "Invalid signature provided"); if (!uniqueValidators.contains(validator)) { uniqueValidators.add(validator); } } - Context.require(uniqueValidators.size() >= reqValidatorCnt.get().intValue(), "Not enough valid signatures"); + Context.require(uniqueValidators.size() >= validatorsThreshold.get().intValue(), "Not enough valid signatures"); recvMessage(srcNetwork, _connSn, msg); } @@ -246,22 +275,17 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt */ @External public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { - OnlyAdmin(); + OnlyRelayer(); Context.require(!receipts.at(srcNetwork).getOrDefault(_connSn, false), "Duplicate Message"); receipts.at(srcNetwork).set(_connSn, true); Context.call(xCall.get(), "handleMessage", srcNetwork, msg); } private Address getValidator(byte[] msg, byte[] sig){ - byte[] hashMessage = getHash(msg); - byte[] key = Context.recoverKey("ecdsa-secp256k1", hashMessage, sig, true); + byte[] key = Context.recoverKey("ecdsa-secp256k1", msg, sig, true); return Context.getAddressFromKey(key); } - private byte[] getHash(byte[] msg){ - return Context.hash("keccak-256", msg); - } - /** * Reverts a message. * @@ -270,7 +294,7 @@ private byte[] getHash(byte[] msg){ */ @External public void revertMessage(BigInteger sn) { - OnlyAdmin(); + OnlyRelayer(); Context.call(xCall.get(), "handleError", sn); } @@ -280,8 +304,8 @@ public void revertMessage(BigInteger sn) { */ @External public void claimFees() { - OnlyAdmin(); - Context.transfer(admin(), Context.getBalance(Context.getAddress())); + OnlyRelayer(); + Context.transfer(relayerAddress.get(), Context.getBalance(Context.getAddress())); } /** @@ -296,6 +320,15 @@ public boolean getReceipts(String srcNetwork, BigInteger _connSn) { return receipts.at(srcNetwork).getOrDefault(_connSn, false); } + /** + * Checks if the caller of the function is the admin. + * + * @return true if the caller is the admin, false otherwise + */ + private void OnlyRelayer() { + Context.require(Context.getCaller().equals(relayerAddress.get()), "Only relayer can call this function"); + } + /** * Checks if the caller of the function is the admin. * @@ -305,4 +338,22 @@ private void OnlyAdmin() { Context.require(Context.getCaller().equals(adminAddress.get()), "Only admin can call this function"); } + /** + * Gets the hash of a message. + * + * @param srcNetwork the source network id + * @param _connSn the serial number of connection message + * @param msg the message to hash + * @return the hash of the message + */ + private byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[] msg) { + ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); + writer.beginList(3); + writer.write(srcNetwork); + writer.write(_connSn); + writer.write(msg); + writer.end(); + return Context.hash("keccak-256", writer.toByteArray()); + } + } \ No newline at end of file diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index fd021c14..914272bc 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -2,31 +2,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.AdditionalMatchers.aryEq; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.security.*; -import java.beans.Transient; import java.math.BigInteger; import score.Context; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jcajce.provider.digest.Keccak; import foundation.icon.icx.KeyWallet; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -35,21 +26,12 @@ import com.iconloop.score.test.ServiceManager; import com.iconloop.score.test.TestBase; -import xcall.adapter.cluster.ClusterConnection; import score.UserRevertedException; import score.Address; -import foundation.icon.ee.types.Bytes; -import foundation.icon.icx.Call; -import foundation.icon.score.client.RevertedException; -import foundation.icon.xcall.CSMessage; -import foundation.icon.xcall.CSMessageRequest; +import score.ByteArrayObjectWriter; import foundation.icon.xcall.CallService; -import foundation.icon.xcall.CallServiceReceiver; import foundation.icon.xcall.CallServiceScoreInterface; -import foundation.icon.xcall.ConnectionScoreInterface; -import foundation.icon.xcall.Connection; -import foundation.icon.xcall.NetworkAddress; -import s.java.math.BigDecimal; + import xcall.icon.test.MockContract; @@ -90,7 +72,7 @@ public void setup() throws Exception { @Test public void testSetAdmin() { - connection.invoke(source_relayer, "setAdmin", admin.getAddress()); + connection.invoke(owner, "setAdmin", admin.getAddress()); assertEquals(connection.call("admin"), admin.getAddress()); } @@ -123,7 +105,7 @@ public void testRecvMessage() { public void testRecvMessage_unauthorized(){ UserRevertedException e = assertThrows(UserRevertedException.class, ()-> connection.invoke(xcallMock, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes())); - assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); } @Test @@ -151,7 +133,7 @@ public void testRevertMessage() { @Test public void testRevertMessage_unauthorized(){ UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(user, "revertMessage", BigInteger.ONE)); - assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); } @@ -159,7 +141,7 @@ public void testRevertMessage_unauthorized(){ public void testSetFeesUnauthorized(){ UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "setFee", "0xevm", BigInteger.TEN, BigInteger.TEN)); - assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); } @Test @@ -189,7 +171,7 @@ public void testClaimFees(){ public void testClaimFees_unauthorized(){ setFee(); UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "claimFees")); - assertEquals(e.getMessage(), "Reverted(0): "+"Only admin can call this function"); + assertEquals(e.getMessage(), "Reverted(0): "+"Only relayer can call this function"); } public MockedStatic.Verification value() { @@ -210,12 +192,12 @@ public void testGetReceipt(){ @Test public void testRecvMessageWithSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); byte[][] byteArray = new byte[1][]; KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.ONE); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -223,15 +205,14 @@ public void testRecvMessageWithSignatures() throws Exception{ @Test public void testRecvMessageWithMultiSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); byte[][] byteArray = new byte[2][]; KeyWallet wallet = KeyWallet.create(); KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(wallet2.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -239,12 +220,12 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ @Test public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough signatures", e.getMessage()); @@ -254,13 +235,13 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ @Test public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); byte[][] byteArray = new byte[2][]; byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough valid signatures", e.getMessage()); @@ -268,37 +249,22 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep } - public static byte[] keccak256(byte[] input) { - Keccak.Digest256 keccak256 = new Keccak.Digest256(); - return keccak256.digest(input); + public static byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[] msg) { + ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); + writer.beginList(3); + writer.write(srcNetwork); + writer.write(_connSn); + writer.write(msg); + writer.end(); + return Context.hash("keccak-256", writer.toByteArray()); } - @Test public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + Address[] validators = new Address[] {Address.fromString(owner.getAddress().toString()), Address.fromString(wallet.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } - @Test - public void testAddNRemoveSigners() throws Exception{ - KeyWallet wallet = KeyWallet.create(); - KeyWallet wallet3 = KeyWallet.create(); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); - Address[] signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 2); - - UserRevertedException e = assertThrows(UserRevertedException.class, - ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString()))); - assertEquals("Reverted(0): Validator doesn't exists", e.getMessage()); - - UserRevertedException ex = assertThrows(UserRevertedException.class, - ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet.getAddress().toString()))); - assertEquals("Reverted(0): Validator size less than required count after removal", ex.getMessage()); - signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 2); - } - } \ No newline at end of file From 80c53bcb29f20a98436d9aa294cc4dde11f6ab45 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Thu, 17 Oct 2024 17:21:26 +0545 Subject: [PATCH 07/10] fix: event data type changed --- .../main/java/xcall/adapter/cluster/ClusterConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index f60f0e4e..bf8103ed 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -91,7 +91,7 @@ public void addValidator(Address[] _validators, BigInteger _threshold) { } Context.require(validators.size() >= _threshold.intValue(), "Not enough validators"); validatorsThreshold.set(_threshold); - ValidatorSetAdded(_validators, _threshold); + ValidatorSetAdded(_validators.toString(), _threshold); } /** @@ -126,7 +126,7 @@ public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { } @EventLog(indexed = 0) - public void ValidatorSetAdded(Address[] _validators, BigInteger _threshold) { + public void ValidatorSetAdded(String _validators, BigInteger _threshold) { } /** From 4c8b626d7b64caee2c8212f7772ca836797610dc Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Fri, 25 Oct 2024 09:03:56 +0545 Subject: [PATCH 08/10] fix: cluster connection fix --- .../adapter/cluster/ClusterConnection.java | 13 +----- .../cluster/ClusterConnectionTest.java | 43 +++---------------- 2 files changed, 7 insertions(+), 49 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index bf8103ed..9c358cf2 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -81,7 +81,7 @@ public Address[] listValidators() { * @throws Exception if the number of validators is less than the threshold */ @External - public void addValidator(Address[] _validators, BigInteger _threshold) { + public void updateValidators(Address[] _validators, BigInteger _threshold) { OnlyAdmin(); clearValidators(); for (Address validator : _validators) { @@ -266,16 +266,7 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt recvMessage(srcNetwork, _connSn, msg); } - /** - * Receives a message from a source network. - * - * @param srcNetwork the source network id from which the message is received - * @param _connSn the serial number of the connection message - * @param msg serialized bytes of Service Message - */ - @External - public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { - OnlyRelayer(); + private void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { Context.require(!receipts.at(srcNetwork).getOrDefault(_connSn, false), "Duplicate Message"); receipts.at(srcNetwork).set(_connSn, true); Context.call(xCall.get(), "handleMessage", srcNetwork, msg); diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 914272bc..f165323a 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -95,19 +95,6 @@ public void sendMessage() { verify(connectionSpy).Message(nidTarget, BigInteger.ONE, "test".getBytes()); } - @Test - public void testRecvMessage() { - connection.invoke(source_relayer, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes()); - verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); - } - - @Test - public void testRecvMessage_unauthorized(){ - - UserRevertedException e = assertThrows(UserRevertedException.class, ()-> connection.invoke(xcallMock, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes())); - assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); - } - @Test public void testSendMessage_unauthorized() { UserRevertedException e = assertThrows(UserRevertedException.class, @@ -115,15 +102,6 @@ public void testSendMessage_unauthorized() { assertEquals("Reverted(0): " + "Only xCall can send messages", e.getMessage()); } - @Test - public void testRecvMessage_duplicateMsg(){ - connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); - - UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(source_relayer, "recvMessage", - nidSource, BigInteger.ONE, "test".getBytes())); - assertEquals(e.getMessage(), "Reverted(0): "+"Duplicate Message"); - } - @Test public void testRevertMessage() { @@ -178,17 +156,6 @@ public MockedStatic.Verification value() { return () -> Context.getValue(); } - @Test - public void testGetReceipt(){ - assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), - false); - - connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); - - assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), - true); - } - @Test public void testRecvMessageWithSignatures() throws Exception{ byte[] data = "test".getBytes(); @@ -197,7 +164,7 @@ public void testRecvMessageWithSignatures() throws Exception{ KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.ONE); + connection.invoke(owner, "updateValidators", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -212,7 +179,7 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(wallet2.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -225,7 +192,7 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough signatures", e.getMessage()); @@ -241,7 +208,7 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough valid signatures", e.getMessage()); @@ -262,7 +229,7 @@ public static byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[ public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); Address[] validators = new Address[] {Address.fromString(owner.getAddress().toString()), Address.fromString(wallet.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } From 13157fef8e78faad51415ada3fe93c78d275a617 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Fri, 8 Nov 2024 15:54:21 +0545 Subject: [PATCH 09/10] fix: updated new func signature to recv pubkey byte array --- .../adapter/cluster/ClusterConnection.java | 56 ++++++++++++------- .../cluster/ClusterConnectionTest.java | 45 +++++++++++++-- 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 9c358cf2..0c2b026e 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -30,6 +30,8 @@ import score.annotation.EventLog; import score.annotation.External; import score.annotation.Payable; + +import java.util.Arrays; import java.util.List; @@ -40,7 +42,7 @@ public class ClusterConnection { protected final VarDB
relayerAddress = Context.newVarDB("relayer", Address.class); protected final VarDB validatorsThreshold = Context.newVarDB("reqValidatorCnt", BigInteger.class); private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); - private final ArrayDB
validators = Context.newArrayDB("signers", Address.class); + private final ArrayDB validators = Context.newArrayDB("signers", String.class); protected final DictDB messageFees = Context.newDictDB("messageFees", BigInteger.class); protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); @@ -61,8 +63,8 @@ public ClusterConnection(Address _relayer, Address _xCall) { * @return The validators . */ @External(readonly = true) - public Address[] listValidators() { - Address[] sgs = new Address[validators.size()]; + public String[] listValidators() { + String[] sgs = new String[validators.size()]; for(int i = 0; i < validators.size(); i++) { sgs[i] = validators.get(i); } @@ -76,17 +78,18 @@ public Address[] listValidators() { * Ensures that the caller is an admin and that the number of validators * meets or exceeds the specified threshold. * - * @param _validators an array of addresses to be added as validators + * @param _validators an array of compressed publickey bytes to be added as validators * @param _threshold the minimum required number of validators * @throws Exception if the number of validators is less than the threshold */ @External - public void updateValidators(Address[] _validators, BigInteger _threshold) { + public void updateValidators(byte[][] _validators, BigInteger _threshold) { OnlyAdmin(); clearValidators(); - for (Address validator : _validators) { - if(!isValidator(validator)) { - validators.add(validator); + for (byte[] validator : _validators) { + String hexValidator = bytesToHex(validator); + if(!isValidator(hexValidator)) { + validators.add(bytesToHex(validator)); } } Context.require(validators.size() >= _threshold.intValue(), "Not enough validators"); @@ -106,13 +109,12 @@ private void clearValidators() { } /** - * Checks if the provided address is a validator. + * Checks if the provided compressed pubkey bytes is a validator. * - * @param validator the address to check for validation - * @return true if the address is a validator, false otherwise + * @param validator the compressed publickey bytes to check for validation + * @return true if the compressed pubkey bytes is a validator, false otherwise */ - @External(readonly = true) - public boolean isValidator(Address validator) { + private boolean isValidator(String validator) { for(int i = 0; i < validators.size(); i++) { if(validator.equals(validators.get(i))) { return true; @@ -254,12 +256,13 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt OnlyRelayer(); Context.require(signatures.length >= validatorsThreshold.get().intValue(), "Not enough signatures"); byte[] messageHash = getMessageHash(srcNetwork, _connSn, msg); - List
uniqueValidators = new ArrayList<>(); + List uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { - Address validator = getValidator(messageHash, signature); - Context.require(isValidator(validator), "Invalid signature provided"); - if (!uniqueValidators.contains(validator)) { - uniqueValidators.add(validator); + byte[] validator = getValidator(messageHash, signature); + String hexValidator = bytesToHex(validator); + Context.require(isValidator(hexValidator), "Invalid signature provided"); + if (!uniqueValidators.contains(hexValidator)) { + uniqueValidators.add(hexValidator); } } Context.require(uniqueValidators.size() >= validatorsThreshold.get().intValue(), "Not enough valid signatures"); @@ -272,9 +275,20 @@ private void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { Context.call(xCall.get(), "handleMessage", srcNetwork, msg); } - private Address getValidator(byte[] msg, byte[] sig){ - byte[] key = Context.recoverKey("ecdsa-secp256k1", msg, sig, true); - return Context.getAddressFromKey(key); + private String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); // Mask with 0xff to handle negative values correctly + if (hex.length() == 1) { + hexString.append('0'); // Add a leading zero if hex length is 1 + } + hexString.append(hex); + } + return hexString.toString(); + } + + private byte[] getValidator(byte[] msg, byte[] sig){ + return Context.recoverKey("ecdsa-secp256k1", msg, sig, true); } /** diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index f165323a..15d2ef7f 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -5,9 +5,12 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import java.nio.charset.StandardCharsets; import java.security.*; import java.math.BigInteger; +import java.util.Arrays; + import score.Context; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -163,12 +166,26 @@ public void testRecvMessageWithSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString())}; + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } + private byte[] compressPublicKey(byte[] uncompressedPublicKey) { + byte[] xBytes = Arrays.copyOfRange(uncompressedPublicKey, 1, 33); // 32 bytes for x + byte[] yBytes = Arrays.copyOfRange(uncompressedPublicKey, 33, 65); // 32 bytes for y + BigInteger y = new BigInteger(1, yBytes); + byte prefix = (y.testBit(0)) ? (byte) 0x03 : (byte) 0x02; + byte[] compressedKey = new byte[33]; + compressedKey[0] = prefix; + System.arraycopy(xBytes, 0, compressedKey, 1, 32); + return compressedKey; + + } + @Test public void testRecvMessageWithMultiSignatures() throws Exception{ byte[] data = "test".getBytes(); @@ -178,7 +195,11 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(wallet2.getAddress().toString())}; + byte[] compressPublicKey = compressPublicKey(wallet.getPublicKey().toByteArray()); + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()) + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); @@ -189,9 +210,13 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[] data = "test".getBytes(); byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); @@ -204,10 +229,14 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep byte[] data = "test".getBytes(); byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); byte[][] byteArray = new byte[2][]; byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); @@ -228,9 +257,13 @@ public static byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[ @Test public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - Address[] validators = new Address[] {Address.fromString(owner.getAddress().toString()), Address.fromString(wallet.getAddress().toString())}; + KeyWallet wallet2 = KeyWallet.create(); + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); - Address[] signers = connection.call(Address[].class,"listValidators"); + String[] signers = connection.call(String[].class,"listValidators"); assertEquals(signers.length, 2); } From d7f11de37e2b4fbfc8f80c9e6b0ff10cc9f74e15 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Mon, 11 Nov 2024 10:23:24 +0545 Subject: [PATCH 10/10] fix: used uncompressed pubkey --- .../adapter/cluster/ClusterConnection.java | 2 +- .../cluster/ClusterConnectionTest.java | 30 ++++++------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 0c2b026e..6546187f 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -288,7 +288,7 @@ private String bytesToHex(byte[] bytes) { } private byte[] getValidator(byte[] msg, byte[] sig){ - return Context.recoverKey("ecdsa-secp256k1", msg, sig, true); + return Context.recoverKey("ecdsa-secp256k1", msg, sig, false); } /** diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 15d2ef7f..c856f5a7 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -167,24 +167,13 @@ public void testRecvMessageWithSignatures() throws Exception{ KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } - private byte[] compressPublicKey(byte[] uncompressedPublicKey) { - byte[] xBytes = Arrays.copyOfRange(uncompressedPublicKey, 1, 33); // 32 bytes for x - byte[] yBytes = Arrays.copyOfRange(uncompressedPublicKey, 33, 65); // 32 bytes for y - BigInteger y = new BigInteger(1, yBytes); - byte prefix = (y.testBit(0)) ? (byte) 0x03 : (byte) 0x02; - byte[] compressedKey = new byte[33]; - compressedKey[0] = prefix; - System.arraycopy(xBytes, 0, compressedKey, 1, 32); - return compressedKey; - - } @Test public void testRecvMessageWithMultiSignatures() throws Exception{ @@ -195,10 +184,9 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - byte[] compressPublicKey = compressPublicKey(wallet.getPublicKey().toByteArray()); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()) + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); @@ -214,8 +202,8 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, @@ -234,8 +222,8 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, @@ -259,8 +247,8 @@ public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); KeyWallet wallet2 = KeyWallet.create(); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); String[] signers = connection.call(String[].class,"listValidators");