diff --git a/contracts/javascore/ibc/src/main/java/ibc/ics02/client/IBCClient.java b/contracts/javascore/ibc/src/main/java/ibc/ics02/client/IBCClient.java index 37f79b4a1..e8b9e8e82 100644 --- a/contracts/javascore/ibc/src/main/java/ibc/ics02/client/IBCClient.java +++ b/contracts/javascore/ibc/src/main/java/ibc/ics02/client/IBCClient.java @@ -16,7 +16,7 @@ public class IBCClient implements IIBCClient { Logger logger = new Logger("ibc-core"); - IBCStore store = new IBCStore(); + protected IBCStore store = new IBCStore(); public void registerClient(String clientType, Address lightClient) { Context.require(store.clientRegistry.get(clientType) == null, "Already registered."); diff --git a/contracts/javascore/ibc/src/main/java/ibc/ics03/connection/IBCConnection.java b/contracts/javascore/ibc/src/main/java/ibc/ics03/connection/IBCConnection.java index 8eddd0749..dc61035ad 100644 --- a/contracts/javascore/ibc/src/main/java/ibc/ics03/connection/IBCConnection.java +++ b/contracts/javascore/ibc/src/main/java/ibc/ics03/connection/IBCConnection.java @@ -1,5 +1,287 @@ package ibc.ics03.connection; -public class IBCConnection { +import java.math.BigInteger; + +import ibc.icon.interfaces.IIBCConnection; +import ibc.icon.interfaces.ILightClient; +import ibc.icon.interfaces.ILightClientScoreInterface; +import ibc.icon.score.util.Logger; +import ibc.icon.score.util.NullChecker; +import ibc.icon.structs.messages.MsgConnectionOpenAck; +import ibc.icon.structs.messages.MsgConnectionOpenConfirm; +import ibc.icon.structs.messages.MsgConnectionOpenInit; +import ibc.icon.structs.messages.MsgConnectionOpenTry; +import ibc.icon.structs.proto.core.client.Height; +import ibc.icon.structs.proto.core.commitment.MerklePrefix; +import ibc.icon.structs.proto.core.connection.ConnectionEnd; +import ibc.icon.structs.proto.core.connection.Counterparty; +import ibc.icon.structs.proto.core.connection.Version; +import ibc.ics24.host.IBCStore; +import ibc.ics02.client.IBCClient; +import score.Address; +import score.Context; + +public class IBCConnection extends IBCClient implements IIBCConnection { + public static final String v1Identifier = "1"; + public static final String[] supportedV1Features = new String[] { "ORDER_ORDERED", "ORDER_UNORDERED" }; + public static final String commitmentPrefix = "ibc"; + + Logger logger = new Logger("ibc-core"); + + public String connectionOpenInit(MsgConnectionOpenInit msg) { + String connectionId = generateConnectionIdentifier(); + Context.require(store.connections.get(connectionId) == null, "connectionId already exists"); + Address lightClientAddr = store.clientImpls.get(msg.clientId); + NullChecker.requireNotNull(lightClientAddr, "Client does not exist"); + ILightClient client = new ILightClientScoreInterface(lightClientAddr); + Context.require(client.getClientState(msg.clientId) != null, "Client state not found"); + + ConnectionEnd connection = new ConnectionEnd(); + connection.setClientId(msg.clientId); + connection.setVersions(getSupportedVersions()); + connection.setState(ConnectionEnd.State.STATE_INIT); + connection.setDelayPeriod(msg.delayPeriod); + connection.setCounterparty(msg.counterparty); + + updateConnectionCommitment(connectionId); + store.connections.set(connectionId, connection); + + return connectionId; + } + + public String connectionOpenTry(MsgConnectionOpenTry msg) { + // TODO: investigate need to self client validation + Context.require(msg.counterpartyVersions.length > 0, "counterpartyVersions length must be greater than 0"); + + String connectionId = generateConnectionIdentifier(); + Context.require(store.connections.get(connectionId) == null, "connectionId already exists"); + + ConnectionEnd connection = new ConnectionEnd(); + connection.setClientId(msg.clientId); + connection.setVersions(getSupportedVersions()); + connection.setState(ConnectionEnd.State.STATE_TRYOPEN); + connection.setDelayPeriod(msg.delayPeriod); + connection.setCounterparty(msg.counterparty); + + MerklePrefix prefix = new MerklePrefix(); + prefix.setKeyPrefix(commitmentPrefix); + + Counterparty expectedCounterparty = new Counterparty(); + expectedCounterparty.setClientId(msg.clientId); + expectedCounterparty.setConnectionId(""); + expectedCounterparty.setPrefix(prefix); + + ConnectionEnd expectedConnection = new ConnectionEnd(); + expectedConnection.setClientId(msg.counterparty.getClientId()); + expectedConnection.setVersions(msg.counterpartyVersions); + expectedConnection.setState(ConnectionEnd.State.STATE_INIT); + expectedConnection.setDelayPeriod(msg.delayPeriod); + expectedConnection.setCounterparty(expectedCounterparty); + + verifyConnectionState(connection, msg.proofHeight, msg.proofInit, msg.counterparty.getConnectionId(), + expectedConnection); + + verifyClientState( + connection, + msg.proofHeight, + getClientStatePath(connection.counterparty.getClientId()), + msg.proofClient, + msg.clientStateBytes); + // TODO we should also verify a consensus state + + updateConnectionCommitment(connectionId); + store.connections.set(connectionId, connection); + + return connectionId; + } + + public void connectionOpenAck(MsgConnectionOpenAck msg) { + ConnectionEnd connection = store.connections.get(msg.connectionId); + Context.require(connection != null, "connection does not exist"); + ConnectionEnd.State state = connection.getState(); + // TODO should we allow the state to be TRY_OPEN? + Context.require(state.equals(ConnectionEnd.State.STATE_INIT) || state.equals(ConnectionEnd.State.STATE_TRYOPEN), + "connection state is not INIT or TRYOPEN"); + if (state.equals(ConnectionEnd.State.STATE_INIT)) { + Context.require(isSupportedVersion(msg.version), + "connection state is in INIT but the provided version is not supported"); + } else { + Context.require(connection.versions.length == 1 && connection.versions[0].equals(msg.version), + "connection state is in TRYOPEN but the provided version is not set in the previous connection versions"); + } + + // TODO: investigate need to self client validation + // require(validateSelfClient(msg.clientStateBytes), "failed to validate self + // client state"); + + MerklePrefix prefix = new MerklePrefix(); + prefix.setKeyPrefix(commitmentPrefix); + + Counterparty expectedCounterparty = new Counterparty(); + expectedCounterparty.setClientId(connection.getClientId()); + expectedCounterparty.setConnectionId(msg.connectionId); + expectedCounterparty.setPrefix(prefix); + + ConnectionEnd expectedConnection = new ConnectionEnd(); + expectedConnection.setClientId(connection.getClientId()); + expectedConnection.setVersions(new Version[] { msg.version }); + expectedConnection.setState(ConnectionEnd.State.STATE_TRYOPEN); + expectedConnection.setDelayPeriod(connection.delayPeriod); + expectedConnection.setCounterparty(expectedCounterparty); + + verifyConnectionState(connection, msg.proofHeight, msg.proofTry, msg.counterpartyConnectionID, + expectedConnection); + + verifyClientState( + connection, + msg.proofHeight, + getClientStatePath(connection.counterparty.getClientId()), + msg.proofClient, + msg.clientStateBytes); + + // TODO we should also verify a consensus state + + connection.setState(ConnectionEnd.State.STATE_OPEN); + connection.setVersions(expectedConnection.versions); + connection.counterparty.setConnectionId(msg.counterpartyConnectionID); + updateConnectionCommitment(msg.connectionId); + + store.connections.set(msg.connectionId, connection); + } + + public void connectionOpenConfirm(MsgConnectionOpenConfirm msg) { + ConnectionEnd connection = store.connections.get(msg.connectionId); + Context.require(connection != null, "connection does not exist"); + ConnectionEnd.State state = connection.getState(); + Context.require(state.equals(ConnectionEnd.State.STATE_TRYOPEN), "connection state is not TRYOPEN"); + + MerklePrefix prefix = new MerklePrefix(); + prefix.setKeyPrefix(commitmentPrefix); + + Counterparty expectedCounterparty = new Counterparty(); + expectedCounterparty.setClientId(connection.getClientId()); + expectedCounterparty.setConnectionId(msg.connectionId); + expectedCounterparty.setPrefix(prefix); + + ConnectionEnd expectedConnection = new ConnectionEnd(); + expectedConnection.setClientId(connection.getCounterparty().getClientId()); + expectedConnection.setVersions(connection.getVersions()); + expectedConnection.setState(ConnectionEnd.State.STATE_OPEN); + expectedConnection.setDelayPeriod(connection.delayPeriod); + expectedConnection.setCounterparty(expectedCounterparty); + + verifyConnectionState(connection, msg.proofHeight, msg.proofAck, connection.getCounterparty().getConnectionId(), + expectedConnection); + + connection.setState(ConnectionEnd.State.STATE_OPEN); + updateConnectionCommitment(msg.connectionId); + + store.connections.set(msg.connectionId, connection); + } + + /* Verification functions */ + + private void verifyClientState(ConnectionEnd connection, Height height, byte[] path, byte[] proof, + byte[] clientStatebytes) { + ILightClient client = new ILightClientScoreInterface(store.clientImpls.get(connection.getClientId())); + boolean ok = client.verifyMembership( + connection.getClientId(), + height, + BigInteger.ZERO, + BigInteger.ZERO, + proof, + connection.getCounterparty().getPrefix().getKeyPrefix(), + path, + clientStatebytes); + Context.require(ok, "failed to verify clientState"); + } + + private void verifyClientConsensusState(ConnectionEnd connection, Height height, Height consensusHeight, + byte[] proof, byte[] consensusStateBytes) { + byte[] consensusPath = getConsensusStatePath(connection.getCounterparty().getClientId(), + consensusHeight.getRevisionNumber(), + consensusHeight.getRevisionHeight()); + + ILightClient client = new ILightClientScoreInterface(store.clientImpls.get(connection.getClientId())); + boolean ok = client.verifyMembership( + connection.getClientId(), + height, + BigInteger.ZERO, + BigInteger.ZERO, + proof, + connection.getCounterparty().getPrefix().getKeyPrefix(), + consensusPath, + consensusStateBytes); + Context.require(ok, "failed to verify consensus state"); + + } + + private void verifyConnectionState(ConnectionEnd connection, Height height, byte[] proof, String connectionId, + ConnectionEnd counterpartyConnection) { + ILightClient client = new ILightClientScoreInterface(store.clientImpls.get(connection.getClientId())); + boolean ok = client.verifyMembership( + connection.getClientId(), + height, + BigInteger.ZERO, + BigInteger.ZERO, + proof, + connection.getCounterparty().getPrefix().getKeyPrefix(), + getConnectionPath(connectionId), + counterpartyConnection.toBytes()); + Context.require(ok, "failed to verify connection state"); + } + + /* Internal functions */ + + private String generateConnectionIdentifier() { + BigInteger currConnectionSequence = store.nextConnectionSequence.getOrDefault(BigInteger.ZERO); + String identifier = "connection-" + currConnectionSequence.toString(); + store.nextConnectionSequence.set(currConnectionSequence.add(BigInteger.ONE)); + + return identifier; + } + + // TODO: Implement + private byte[] getConnectionPath(String connectionId) { + return new byte[0]; + // IBCCommitment.consensusStatePath( + // connection.getCounterparty().getClientId(), consensusHeight.revision_number, + // consensusHeight.revision_height) + } + + // TODO: Implement + private byte[] getConsensusStatePath(String clientId, BigInteger revisionNumber, BigInteger revisionHeight) { + return new byte[0]; + // IBCCommitment.consensusStatePath( + // connection.getCounterparty().getClientId(), consensusHeight.revision_number, + // consensusHeight.revision_height) + } + + /** + * @dev getSupportedVersions return the supported versions. + * + */ + private Version[] getSupportedVersions() { + Version version = new Version(); + version.setFeatures(supportedV1Features); + version.setIdentifier(v1Identifier); + + return new Version[] { version }; + } + + // TODO implement + private boolean isSupportedVersion(Version version) { + return true; + } + + private void updateConnectionCommitment(String connectionId) { + // TODO IBC-Store: wait for implementation + } + + private byte[] getClientStatePath(String clientId) { + return new byte[0]; + // TODO IBC-Store: wait for implementation + // IBCCommitment.clientStatePath(connection.counterparty.getClientId()) + } } diff --git a/contracts/javascore/ibc/src/test/java/ibc/ics03/connection/ConnectionTest.java b/contracts/javascore/ibc/src/test/java/ibc/ics03/connection/ConnectionTest.java new file mode 100644 index 000000000..5c0b9d672 --- /dev/null +++ b/contracts/javascore/ibc/src/test/java/ibc/ics03/connection/ConnectionTest.java @@ -0,0 +1,410 @@ +package ibc.ics03.connection; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +import java.math.BigInteger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.stubbing.OngoingStubbing; + +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 ibc.icon.interfaces.ILightClient; +import ibc.icon.interfaces.ILightClientScoreInterface; +import ibc.icon.structs.messages.MsgConnectionOpenAck; +import ibc.icon.structs.messages.MsgConnectionOpenConfirm; +import ibc.icon.structs.messages.MsgConnectionOpenInit; +import ibc.icon.structs.messages.MsgConnectionOpenTry; +import ibc.icon.structs.messages.MsgCreateClient; +import ibc.icon.structs.proto.core.client.Height; +import ibc.icon.structs.proto.core.commitment.MerklePrefix; +import ibc.icon.structs.proto.core.connection.ConnectionEnd; +import ibc.icon.structs.proto.core.connection.Counterparty; +import ibc.icon.structs.proto.core.connection.Version; +import ibc.icon.test.MockContract; +import ibc.ics02.client.IBCClient; + +public class ConnectionTest extends TestBase { + private final ServiceManager sm = getServiceManager(); + private final Account owner = sm.createAccount(); + private Score connection; + private MockContract lightClient; + private int currentClientID = 0; + + Height proofHeight = new Height(); + Height consensusHeight = new Height(); + + Counterparty counterparty = new Counterparty(); + MerklePrefix prefix = new MerklePrefix(); + Version version = new Version(); + + BigInteger delayPeriod = BigInteger.TEN; + String clientId; + + @BeforeEach + public void setup() throws Exception { + connection = sm.deploy(owner, IBCConnection.class); + + lightClient = new MockContract<>(ILightClientScoreInterface.class, ILightClient.class, sm, owner); + + proofHeight.revisionHeight = BigInteger.valueOf(5); + proofHeight.revisionNumber = BigInteger.valueOf(6); + proofHeight.revisionHeight = BigInteger.valueOf(7); + proofHeight.revisionNumber = BigInteger.valueOf(8); + + prefix.setKeyPrefix(IBCConnection.commitmentPrefix); + + counterparty.setClientId("counterpartyId"); + counterparty.setConnectionId("connectionId"); + counterparty.setPrefix(prefix); + + version.identifier = IBCConnection.v1Identifier; + version.features = IBCConnection.supportedV1Features; + + clientId = createClient(); + } + + @Test + void connectionOpenInit_clientNotFound() { + // Arrange + MsgConnectionOpenInit msg = new MsgConnectionOpenInit(); + msg.clientId = "non existent"; + + // Act & Assert + String expectedErrorMessage = "Client does not exist"; + Executable openConnectionWithoutClient = () -> connection.invoke(owner, + "connectionOpenInit", msg); + AssertionError e = assertThrows(AssertionError.class, + openConnectionWithoutClient); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + } + + @Test + void connectionOpenInit_clientStateNotFound() { + // Arrange + MsgConnectionOpenInit msg = new MsgConnectionOpenInit(); + msg.clientId = clientId; + + // Act & Assert + String expectedErrorMessage = "Client state not found"; + Executable openConnectionWithoutState = () -> connection.invoke(owner, + "connectionOpenInit", msg); + AssertionError e = assertThrows(AssertionError.class, + openConnectionWithoutState); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + } + + @Test + void connectionOpenInit() { + // Arrange + MsgConnectionOpenInit msg = new MsgConnectionOpenInit(); + msg.clientId = clientId; + msg.counterparty = counterparty; + msg.delayPeriod = delayPeriod; + + when(lightClient.mock.getClientState(msg.clientId)).thenReturn(new byte[0]); + + // Act + connection.invoke(owner, "connectionOpenInit", msg); + + // Assert + // TODO assert Storage/State + // TODO assert commitement + } + + @Test + void connectionOpenTry_MissingVersion() { + // Arrange + MsgConnectionOpenTry msg = new MsgConnectionOpenTry(); + msg.counterpartyVersions = new Version[] {}; + + // Act & Assert + String expectedErrorMessage = "counterpartyVersions length must be greater than 0"; + Executable openConnectionWithoutVersion = () -> connection.invoke(owner, + "connectionOpenTry", msg); + AssertionError e = assertThrows(AssertionError.class, + openConnectionWithoutVersion); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + } + + @Test + void connectionOpenTry_failedConnectionStateVerification() { + // Arrange + MsgConnectionOpenTry msg = new MsgConnectionOpenTry(); + msg.counterpartyVersions = new Version[] {}; + + // Act & Assert + String expectedErrorMessage = "counterpartyVersions length must be greater than 0"; + Executable openConnectionWithoutVersion = () -> connection.invoke(owner, + "connectionOpenTry", msg); + AssertionError e = assertThrows(AssertionError.class, + openConnectionWithoutVersion); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + } + + @Test + void connectionOpenTry_invalidStates() { + // Arrange + MsgConnectionOpenTry msg = new MsgConnectionOpenTry(); + msg.clientId = clientId; + msg.counterparty = counterparty; + msg.delayPeriod = delayPeriod; + msg.clientStateBytes = new byte[1]; + msg.counterpartyVersions = new Version[] { version }; + msg.proofInit = new byte[2]; + msg.proofClient = new byte[3]; + msg.proofConsensus = new byte[4]; + msg.proofHeight = proofHeight; + msg.consensusHeight = consensusHeight; + + Counterparty expectedCounterparty = new Counterparty(); + expectedCounterparty.setClientId(msg.clientId); + expectedCounterparty.setConnectionId(""); + expectedCounterparty.setPrefix(prefix); + + ConnectionEnd expectedConnection = new ConnectionEnd(); + expectedConnection.setClientId(counterparty.getClientId()); + expectedConnection.setVersions(new Version[] { version }); + expectedConnection.setState(ConnectionEnd.State.STATE_INIT); + expectedConnection.setDelayPeriod(msg.delayPeriod); + expectedConnection.setCounterparty(expectedCounterparty); + + // verifyConnectionState + byte[] connectionPath = new byte[0]; // TODO IBC HOST + when(lightClient.mock.verifyMembership(msg.clientId, + msg.proofHeight, BigInteger.ZERO, + BigInteger.ZERO, msg.proofInit, prefix.getKeyPrefix(), connectionPath, expectedConnection.toBytes())) + .thenReturn(false).thenReturn(true); + + // verifyClientState + byte[] clientStatePath = new byte[0]; // TODO IBC HOST + when(lightClient.mock.verifyMembership(msg.clientId, msg.proofHeight, BigInteger.ZERO, + BigInteger.ZERO, msg.proofClient, prefix.getKeyPrefix(), clientStatePath, + msg.clientStateBytes)) + .thenReturn(false); + + // Act & Assert + String expectedErrorMessage = "failed to verify connection state"; + Executable clientVerificationFailed = () -> connection.invoke(owner, + "connectionOpenTry", msg); + AssertionError e = assertThrows(AssertionError.class, + clientVerificationFailed); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + + expectedErrorMessage = "failed to verify clientState"; + Executable stateVerificationFailed = () -> connection.invoke(owner, + "connectionOpenTry", msg); + e = assertThrows(AssertionError.class, + stateVerificationFailed); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + + } + + @Test + void connectionOpenTry() { + // Arrange + MsgConnectionOpenTry msg = new MsgConnectionOpenTry(); + msg.clientId = clientId; + msg.counterparty = counterparty; + msg.delayPeriod = delayPeriod; + msg.clientStateBytes = new byte[1]; + msg.counterpartyVersions = new Version[] { version }; + msg.proofInit = new byte[2]; + msg.proofClient = new byte[3]; + msg.proofConsensus = new byte[4]; + msg.proofHeight = proofHeight; + msg.consensusHeight = consensusHeight; + + Counterparty expectedCounterparty = new Counterparty(); + expectedCounterparty.setClientId(msg.clientId); + expectedCounterparty.setConnectionId(""); + expectedCounterparty.setPrefix(prefix); + + ConnectionEnd expectedConnection = new ConnectionEnd(); + expectedConnection.setClientId(counterparty.getClientId()); + expectedConnection.setVersions(new Version[] { version }); + expectedConnection.setState(ConnectionEnd.State.STATE_INIT); + expectedConnection.setDelayPeriod(msg.delayPeriod); + expectedConnection.setCounterparty(expectedCounterparty); + + // verifyConnectionState + byte[] connectionPath = new byte[0]; // TODO IBC HOST + when(lightClient.mock.verifyMembership(msg.clientId, + msg.proofHeight, BigInteger.ZERO, + BigInteger.ZERO, msg.proofInit, prefix.getKeyPrefix(), connectionPath, expectedConnection.toBytes())) + .thenReturn(true); + + // verifyClientState + byte[] clientStatePath = new byte[0]; // TODO IBC HOST + when(lightClient.mock.verifyMembership(msg.clientId, msg.proofHeight, BigInteger.ZERO, + BigInteger.ZERO, msg.proofClient, prefix.getKeyPrefix(), clientStatePath, + msg.clientStateBytes)) + .thenReturn(true); + + // Act + connection.invoke(owner, "connectionOpenTry", msg); + + // Assert + // TODO assert Storage/State + // TODO assert commitement + } + + @Test + void connectionOpenAck_alreadyOpen() { + // Arrange + connectionOpenConfirm(); + MsgConnectionOpenAck msg = new MsgConnectionOpenAck(); + msg.connectionId = "connection-0"; + + // Act & Assert + String expectedErrorMessage = "connection state is not INIT or TRYOPEN"; + Executable clientVerificationFailed = () -> connection.invoke(owner, + "connectionOpenAck", msg); + AssertionError e = assertThrows(AssertionError.class, + clientVerificationFailed); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + } + + @Test + void connectionOpenAck_wrongVersion() { + // Arrange + connectionOpenTry(); + MsgConnectionOpenAck msg = new MsgConnectionOpenAck(); + msg.connectionId = "connection-0"; + Version wrongVersion = new Version(); + wrongVersion.identifier = "OtherVersion"; + wrongVersion.features = new String[] { "some features" }; + msg.version = wrongVersion; + + // Act & Assert + String expectedErrorMessage = "connection state is in TRYOPEN but the provided version is not set in the previous connection versions"; + Executable clientVerificationFailed = () -> connection.invoke(owner, + "connectionOpenAck", msg); + AssertionError e = assertThrows(AssertionError.class, + clientVerificationFailed); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + } + + @Test + void connectionOpenAck() { + // Arrange + connectionOpenInit(); + MsgConnectionOpenAck msg = new MsgConnectionOpenAck(); + msg.connectionId = "connection-0"; + msg.clientStateBytes = new byte[1]; + msg.version = version; + msg.counterpartyConnectionID = counterparty.clientId; + msg.proofTry = new byte[2]; + msg.proofClient = new byte[3]; + msg.proofConsensus = new byte[4]; + msg.proofHeight = proofHeight; + msg.consensusHeight = consensusHeight; + + Counterparty expectedCounterparty = new Counterparty(); + expectedCounterparty.setClientId(clientId); + expectedCounterparty.setConnectionId(msg.connectionId); + expectedCounterparty.setPrefix(prefix); + + ConnectionEnd expectedConnection = new ConnectionEnd(); + expectedConnection.setClientId(clientId); + expectedConnection.setVersions(new Version[] { version }); + expectedConnection.setState(ConnectionEnd.State.STATE_TRYOPEN); + expectedConnection.setDelayPeriod(delayPeriod); + expectedConnection.setCounterparty(expectedCounterparty); + + // verifyConnectionState + byte[] connectionPath = new byte[0]; // TODO IBC HOST + when(lightClient.mock.verifyMembership(clientId, msg.proofHeight, BigInteger.ZERO, BigInteger.ZERO, + msg.proofTry, prefix.getKeyPrefix(), connectionPath, expectedConnection.toBytes())).thenReturn(true); + + // verifyClientState + byte[] clientStatePath = new byte[0]; // TODO IBC HOST + when(lightClient.mock.verifyMembership(clientId, msg.proofHeight, BigInteger.ZERO, BigInteger.ZERO, + msg.proofClient, prefix.getKeyPrefix(), clientStatePath, msg.clientStateBytes)).thenReturn(true); + + // Act + connection.invoke(owner, "connectionOpenAck", msg); + + // Assert + // TODO assert Storage/State + // TODO assert commitement + + } + + @Test + void connectionOpenConfirm_NotInTryOpen() { + // Arrange + connectionOpenInit(); + MsgConnectionOpenConfirm msg = new MsgConnectionOpenConfirm(); + msg.connectionId = "connection-0"; + msg.proofAck = new byte[1]; + msg.proofHeight = proofHeight; + + // Act & Assert + String expectedErrorMessage = "connection state is not TRYOPEN"; + Executable clientVerificationFailed = () -> connection.invoke(owner, + "connectionOpenConfirm", msg); + AssertionError e = assertThrows(AssertionError.class, + clientVerificationFailed); + assertTrue(e.getMessage().contains(expectedErrorMessage)); + } + + @Test + void connectionOpenConfirm() { + // Arrange + connectionOpenTry(); + MsgConnectionOpenConfirm msg = new MsgConnectionOpenConfirm(); + msg.connectionId = "connection-0"; + msg.proofAck = new byte[1]; + msg.proofHeight = proofHeight; + + Counterparty expectedCounterparty = new Counterparty(); + expectedCounterparty.setClientId(clientId); + expectedCounterparty.setConnectionId(msg.connectionId); + expectedCounterparty.setPrefix(prefix); + + ConnectionEnd expectedConnection = new ConnectionEnd(); + expectedConnection.setClientId(counterparty.getClientId()); + expectedConnection.setVersions(new Version[] { version }); + expectedConnection.setState(ConnectionEnd.State.STATE_OPEN); + expectedConnection.setDelayPeriod(delayPeriod); + expectedConnection.setCounterparty(expectedCounterparty); + + // verifyConnectionState + byte[] connectionPath = new byte[0]; // TODO IBC HOST + when(lightClient.mock.verifyMembership(clientId, msg.proofHeight, BigInteger.ZERO, BigInteger.ZERO, + msg.proofAck, prefix.getKeyPrefix(), connectionPath, expectedConnection.toBytes())).thenReturn(true); + + // Act + connection.invoke(owner, "connectionOpenConfirm", msg); + + // Assert + // TODO assert Storage/State + // TODO assert commitement + + } + + private String createClient() { + MsgCreateClient msg = new MsgCreateClient(); + msg.clientType = "type"; + msg.consensusState = new byte[0]; + msg.clientState = new byte[0]; + // TODO mock lightclient update response + + // Act + connection.invoke(owner, "registerClient", msg.clientType, lightClient.getAddress()); + connection.invoke(owner, "createClient", msg); + + String clientID = msg.clientType + "-" + currentClientID; + currentClientID++; + + return clientID; + } +} diff --git a/contracts/javascore/lib/src/main/java/ibc/icon/interfaces/ILightClient.java b/contracts/javascore/lib/src/main/java/ibc/icon/interfaces/ILightClient.java index 53518e7b0..1b64971ea 100644 --- a/contracts/javascore/lib/src/main/java/ibc/icon/interfaces/ILightClient.java +++ b/contracts/javascore/lib/src/main/java/ibc/icon/interfaces/ILightClient.java @@ -54,7 +54,7 @@ public interface ILightClient { * The caller is expected to construct the full CommitmentPath from a * CommitmentPrefix and a standardized path (as defined in ICS 24). */ - public boolean verifyMembership( + public Boolean verifyMembership( String clientId, Height height, BigInteger delayTimePeriod, @@ -70,7 +70,7 @@ public boolean verifyMembership( * The caller is expected to construct the full CommitmentPath from a * CommitmentPrefix and a standardized path (as defined in ICS 24). */ - public boolean verifyNonMembership( + public Boolean verifyNonMembership( String clientId, Height height, BigInteger delayTimePeriod, diff --git a/contracts/javascore/lib/src/main/java/ibc/icon/structs/proto/core/connection/Version.java b/contracts/javascore/lib/src/main/java/ibc/icon/structs/proto/core/connection/Version.java index da5764942..01fc85327 100644 --- a/contracts/javascore/lib/src/main/java/ibc/icon/structs/proto/core/connection/Version.java +++ b/contracts/javascore/lib/src/main/java/ibc/icon/structs/proto/core/connection/Version.java @@ -1,5 +1,6 @@ package ibc.icon.structs.proto.core.connection; +import java.util.Arrays; import java.util.List; import score.ByteArrayObjectWriter; @@ -60,6 +61,10 @@ public void writeObject(ObjectWriter writer) { writer.end(); } + public boolean equals(Version v) { + return this.identifier == v.identifier && Arrays.equals(this.features, v.features); + } + public String getIdentifier() { return identifier; }