Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement persistent message type in Java #215

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

package foundation.icon.xcall.messages;

public class PersistentMessage extends Message {
public static final int TYPE = 3;
private byte[] data;

public PersistentMessage(byte[] data) {
this.data = data;
}

public int getType() {
return TYPE;
}

public byte[] getData() {
return data;
}

public byte[] toBytes() {
return data;
}

public static CallMessage fromBytes(byte[] bytes) {
return new CallMessage(bytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import foundation.icon.xcall.messages.CallMessage;
import foundation.icon.xcall.messages.CallMessageWithRollback;
import foundation.icon.xcall.messages.Message;
import foundation.icon.xcall.messages.PersistentMessage;
import foundation.icon.xcall.messages.XCallEnvelope;


Expand Down Expand Up @@ -316,7 +317,6 @@ public void RollbackExecuted(BigInteger _sn) {
public void CallMessageSent(Address _from, String _to, BigInteger _sn) {
}


private void sendMessage(String[] _sources, String netTo, int msgType, BigInteger sn, byte[] data) {
Address[] sources = prepareProtocols(_sources, netTo);
CSMessage msg = new CSMessage(msgType, data);
Expand Down Expand Up @@ -383,6 +383,7 @@ public ProcessResult(boolean needResponse, byte[] data) {
private ProcessResult preProcessMessage(BigInteger sn, NetworkAddress to, XCallEnvelope envelope) {
switch (envelope.getType()) {
case CallMessage.TYPE:
case PersistentMessage.TYPE:
return new ProcessResult(false, envelope.getMessage());
case CallMessageWithRollback.TYPE:
Address caller = Context.getCaller();
Expand All @@ -404,6 +405,9 @@ private void executeMessage(BigInteger reqId, CSMessageRequest req, byte[] data)
case CallMessage.TYPE:
tryExecuteCall(reqId, to, req.getFrom(), data, protocols);
break;
case PersistentMessage.TYPE:
_executeCall(reqId, to, req.getFrom(), data, protocols);
break;
case CallMessageWithRollback.TYPE: {
int code = tryExecuteCall(reqId, to, req.getFrom(), data, protocols);
BigInteger sn = req.getSn().negate();
Expand Down Expand Up @@ -451,10 +455,7 @@ private void handleResult(byte[] data) {
BigInteger resSn = msgRes.getSn();
RollbackData req = rollbacks.get(resSn);

if (req == null) {
Context.println("handleResult: no request for " + resSn);
return; // just ignore
}
Context.require(req != null, "CallRequest Not Found For " + resSn.toString());

byte[] hash = Context.hash("sha-256", data);
DictDB<String, Boolean> pending = pendingResponses.at(hash);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import com.iconloop.score.test.TestBase;

import foundation.icon.xcall.messages.CallMessageWithRollback;
import foundation.icon.xcall.messages.Message;
import foundation.icon.xcall.messages.PersistentMessage;
import foundation.icon.xcall.messages.XCallEnvelope;
import score.UserRevertedException;
import xcall.icon.test.MockContract;

Expand Down Expand Up @@ -125,6 +128,25 @@ public void sendMessage_multiProtocol() throws Exception {
verify(xcallSpy).CallMessageSent(dapp.getAddress(), ethDapp.toString(), BigInteger.ONE);
}


@Test
public void sendMessage_persistent() {
// Arrange
byte[] data = "test".getBytes();
Message message = new PersistentMessage(data);
XCallEnvelope envelope = new XCallEnvelope(message);
xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress());

// Act
xcall.invoke(dapp.account, "sendCall", ethDapp.toString(), envelope.toBytes());

// Assert
CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, PersistentMessage.TYPE, data, null);
CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes());
verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ZERO), aryEq(msg.toBytes()));
verify(xcallSpy).CallMessageSent(dapp.getAddress(), ethDapp.toString(), BigInteger.ONE);
}

@Test
public void handleResponse_singleProtocol() {
// Arrange
Expand Down Expand Up @@ -343,6 +365,39 @@ public void executeCall_failedExecution() {
verify(xcallSpy).CallExecuted(BigInteger.ONE, 0, "score.RevertedException");
}

@Test
public void executeCall_persistent_failedExecution() {
// Arrange
byte[] data = "test".getBytes();
CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, PersistentMessage.TYPE, data, baseSource);
CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes());
xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes());
// Act
doThrow(new UserRevertedException()).when(dapp.mock).handleCallMessage(ethDapp.toString(), data, new String[]{baseConnection.getAddress().toString()});
assertThrows(Exception.class, () -> xcall.invoke(user, "executeCall", BigInteger.ONE, data));
}

@Test
public void executeCall_persistent() throws Exception {
// Arrange
byte[] data = "test".getBytes();
MockContract<DefaultCallServiceReceiver> defaultDapp = new MockContract<>(DefaultCallServiceReceiverScoreInterface.class, DefaultCallServiceReceiver.class, sm, owner);
CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), defaultDapp.getAddress().toString(), BigInteger.ONE, PersistentMessage.TYPE, data, null);
CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes());

xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress());
xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes());

// Act
xcall.invoke(user, "executeCall", BigInteger.ONE, data);

// Assert
verify(defaultDapp.mock).handleCallMessage(ethDapp.toString(), data);
verify(xcallSpy).CallExecuted(BigInteger.ONE, 1, "");
Exception e = assertThrows(Exception.class, () -> xcall.invoke(user, "executeCall", BigInteger.ONE, data));
assertEquals("Reverted(0): InvalidRequestId", e.getMessage());
}

ibrizsabin marked this conversation as resolved.
Show resolved Hide resolved
@Test
public void rollback_singleProtocol() {
// Arrange
Expand Down
12 changes: 6 additions & 6 deletions docs/adr/xcall.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,13 +503,13 @@ payable external sendCall(String _to, byte[] _data) returns Integer {
msgReq = CSMessageRequest(from, to.account(), sn, envelope.type, msg, envelope.destinations)
msg = CSMessage(CSMessage.REQUEST, msgReq.toBytes()).toBytes()
assert msg.length <= MAX_DATA_SIZE

if isReply(_to.netId,envelope.sources) && !needResponse:
replyState = null
callReply[caller]=msg
emit CallMessageSent(caller, dst.toString(), sn)
return sn

sendSn = needResponse ? sn : 0
if protocolConfig.sources == []:
src = defaultConnection[to.net()]
Expand Down Expand Up @@ -671,7 +671,7 @@ internal function handleResult(data byte[]) {
req = rollbacks[resSn]

if req == null:
return
throw "CallRequest Not Found For {resSn}"

if !verifyProtocols(req.netTo, req.protocolConfig, hash(data)):
return
Expand All @@ -681,7 +681,7 @@ internal function handleResult(data byte[]) {
case CSMessageResult.SUCCESS:
if result.getMessage()!=null:
handleReply(req,result.getMessage())

rollbacks[resSn] = null
successfulResponses[resSn] = 1
break
Expand Down Expand Up @@ -778,7 +778,7 @@ internal function executeCall(int id, String from, byte[] data, String[] protoco
internal function isReply(String netId, String[] sources) {
if replyState != null:
return replyState.fromNid == netid && replyState.protocols.equals(sources)

return false
}
```
Expand Down Expand Up @@ -831,7 +831,7 @@ external readonly function getFee(String _net,

if isReply(_net, sources) && !_rollback {
return 0
}
}
fee = protocolFee
if _sources == [] {
return defaultConnection[_net]->getFee(_net, _rollback) + fee
Expand Down