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

Error while decoding response with Opcode.FUNC #3

Open
chinaryov opened this issue Nov 21, 2022 · 19 comments
Open

Error while decoding response with Opcode.FUNC #3

chinaryov opened this issue Nov 21, 2022 · 19 comments

Comments

@chinaryov
Copy link

chinaryov commented Nov 21, 2022

Hello!

Error while decoding bytes (response from canister):

Exception in thread "main" org.ic4j.candid.CandidError: Unsupported op_code -22 in type table
	at org.ic4j.candid.CandidError.create(CandidError.java:40)
	at org.ic4j.candid.TypeTable.fromBytes(TypeTable.java:108)
	at org.ic4j.candid.Deserializer.fromBytes(Deserializer.java:57)
	at org.ic4j.candid.IDLDeserialize.create(IDLDeserialize.java:32)
	at org.ic4j.candid.parser.IDLArgs.fromBytes(IDLArgs.java:50)

Calling canister ID - "ryjl3-tyaaa-aaaaa-aaaba-cai"
Method:

query_blocks (GetBlocksArgs = { start : BlockIndex; length : Nat64 });

Using parameters start = 0, length = 0;

Expecting response:

public type QueryBlocksResponse = {
    certificate : ?[Nat8];
    blocks : [Block];
    chain_length : Nat64;
    first_block_index : BlockIndex;
    archived_blocks : [
      { callback : QueryArchiveFn; start : BlockIndex; length : Nat64 }
    ];
  };

Best regards,
Alexander

@rdobrik
Copy link
Contributor

rdobrik commented Nov 21, 2022

Alexander, thanks for reporting. Let me take a look what is causing this problem.

@rdobrik
Copy link
Contributor

rdobrik commented Nov 21, 2022

Alexander, we currently do not support func (22) and service types. I need to better analyze use case for that, between Java and Motoko/Rust. Can you share how do you want to use func type in Java?

@chinaryov
Copy link
Author

chinaryov commented Nov 21, 2022

I do not need completely FUNC support on the java side.
Regarding this example I do not need it at all.
As you can see in the response (additionally to FUNC data 'callback') there are some other fields and I need it.

{
  "certificate": [[.....]],
  "blocks": [.....],
  "chain_length": "4901184",
  "first_block_index": "4900000",
  "archived_blocks": [
    {
      "callback": [
        "qjdve-lqaaa-aaaaa-aaaeq-cai",
        "get_blocks"
      ],
      "start": "0",
      "length": "4900000"
    }
  ]
}

Can we do some MOCK for FUNC reponse? (just text info/description of it).

@rdobrik
Copy link
Contributor

rdobrik commented Nov 21, 2022

Yes, let me check how I can use some empty or static value and make it functional.

@rdobrik
Copy link
Contributor

rdobrik commented Nov 22, 2022

I did some deep dive to the code where I have to make a change. I have already some features I want to push out this week in new release, I will let you know, when new release is out.

@chinaryov
Copy link
Author

I did some deep dive to the code where I have to make a change. I have already some features I want to push out this week in new release, I will let you know, when new release is out.

Thank you for your support!

@rdobrik
Copy link
Contributor

rdobrik commented Nov 24, 2022

Alexander, thank you for reporting all those bugs/issues! I started to work on that Function Candid serializer/deserializer. Did not touch that part of code for some time, so I need to analyze it, compare it with Rust implementation. But I am getting there!

@rdobrik
Copy link
Contributor

rdobrik commented Nov 28, 2022

Almost there with func and service Candid implementations. Just have to manage certain use case scenarios, want to make it fully compatible with spec. I will drop some early release soon.

@rdobrik
Copy link
Contributor

rdobrik commented Nov 29, 2022

Alexander, I dropped early beta of Candid library, pushed it to maven central. Tested some func and service scenarios but i need to ad more unit test and need to figure out how to nicely map it to Java classes.

I created 2 new classes Func and Service in org.ic4j.types package, Func has 2 values Principal and name.

So far it works with defined IDLValue, where you can define method signature

List funcArgs = new ArrayList();
List funcRets = new ArrayList();
List funcModes = new ArrayList();

Func funcValue = new Func(Principal.fromString("w7x7r-cok77-xa"),"a");

args.add(IDLType.createType(Type.TEXT));
rets.add(IDLType.createType(Type.NAT));
modes.add(Mode.QUERY);

idlValue = IDLValue.create(funcValue, IDLType.createType(funcArgs,funcRets,funcModes));

maps to (func (text) -> (nat) query)

You can test it with these gradle imports.

implementation group: 'org.ic4j', name: 'ic4j-candid', version: '0.6.18-BETA1'
implementation group: 'org.ic4j', name: 'ic4j-agent', version: '0.6.17'

If you can share with me any Canister with your service so I can test it too?

Let me know if you need any assistance to figure out how to use it or you are receiving any additional errors.

Roman

@chinaryov
Copy link
Author

Hello!

Great news! I will try this beta version.

Canister ID: ryjl3-tyaaa-aaaaa-aaaba-cai
Method name: query_blocks

Alexander

@rdobrik
Copy link
Contributor

rdobrik commented Dec 1, 2022

Adding some goodies to the Agent ProxyBuilder, so developers can call functions dynamically based on value of Func Candid type. Will looks like this. I am going to drop new beta of Candid and Agent soon for testing.

funcValue = new Func(Principal.fromString(TestProperties.IC_CANISTER_ID),"peek");
			
FuncProxy<String> funcProxy = proxyBuilder.getProxy(funcValue, HelloProxy.class);
			
String peek = funcProxy.call("Motoko", BigInteger.valueOf(100));
			
Assertions.assertEquals("Hello, Motoko!",peek);

ProxyBuiider can now also handle Func and Service types from Java Proxy interface.

@Modes(Mode.QUERY)
@Name("echoFunc2")
public Func echoFunc(Func value);

@rdobrik
Copy link
Contributor

rdobrik commented Dec 1, 2022

Dropped new Beta version, both Candid and Agent. New features related to Func and Service. Tested your canister method, looks fine. Also added additional exception handling and debug error messages.

This Is gradle import

implementation group: 'org.ic4j', name: 'ic4j-candid', version: '0.6.18-BETA2'
implementation group: 'org.ic4j', name: 'ic4j-agent', version: '0.6.18-BETA2'

and code I use to call your canister.

Agent agent = new AgentBuilder().transport(ReplicaApacheHttpTransport.create("https://ryjl3-tyaaa-aaaaa-aaaba-cai.ic0.app/")).identity(identity).nonceFactory(new NonceFactory())
						.build();
				
Map<Label,IDLType> rootRecord = new TreeMap<Label,IDLType>();
				
rootRecord.put(Label.createNamedLabel("start"), IDLType.createType(Type.NAT64));
rootRecord.put(Label.createNamedLabel("length"), IDLType.createType(Type.NAT64));
				
IDLType idlType =  IDLType.createType(Type.RECORD, rootRecord);
				
List<IDLValue> args = new ArrayList<IDLValue>();
				
Map<Label, Object> mapValue = new HashMap<Label, Object>();

mapValue.put(Label.createNamedLabel("start"), 0l);
mapValue.put(Label.createNamedLabel("length"), 4900000l);
				
args.add(IDLValue.create(mapValue,idlType));
	
IDLArgs idlArgs = IDLArgs.create(args);
	
byte[] buf = idlArgs.toBytes();
				
CompletableFuture<byte[]> queryResponse = agent.queryRaw(
		Principal.fromString("ryjl3-tyaaa-aaaaa-aaaba-cai"),
		Principal.fromString("ryjl3-tyaaa-aaaaa-aaaba-cai"), "query_blocks", buf, Optional.empty());
				
byte[] queryOutput = queryResponse.get();

IDLArgs outArgs = IDLArgs.fromBytes(queryOutput);

@chinaryov
Copy link
Author

Hello!

Thank you. It works great for me.

@chinaryov
Copy link
Author

Could you please help me to build IDLArgs for canister method:

nftInfosByCollection: (principal, vec nat32) -> (vec NFTInfo) query;

You can try it on this canister:

It works from command line: udtw4-baaaa-aaaah-abc3q-cai

❯ dfx canister --network ic call udtw4-baaaa-aaaah-abc3q-cai nftInfosByCollection '(principal "4pa74-jyaaa-aaaah-abqfq-cai", vec{12:nat32})'
(
  vec {
    record {
      177_766_212 = variant { 1_703_420_727 };
      356_023_379 = 0 : nat64;
      754_031_499 = null;
      922_543_726 = 1 : nat;
      1_190_098_653 = vec {};
    };
  },
)

For example this construction does not work:

    protected List<IDLValue> getMethodArgs() {
        final String canisterId = super.canisterIdentity.getCanisterId();
        final List<IDLValue> argsList = new ArrayList<>();
        argsList.add(IDLValue.create(canisterId, Type.PRINCIPAL));
        argsList.add(IDLValue.create(new Integer[]{32}));
        return argsList;
    }

Result: IDL error: unexpected IDL type when parsing Nat32
I have tried many others combinations without success.

@rdobrik
Copy link
Contributor

rdobrik commented Dec 7, 2022

Hi Alexander, yes definitely, let me take a look how we can do it. If it will eventually require some fixes.

@rdobrik
Copy link
Contributor

rdobrik commented Dec 7, 2022

Actually I just pushed out new beta release with Candid parser feature, this way we can properly map Java types to Candid. Right now it's often not 1 to 1 relationship, and that mapping must be written in your code, now this can be done automatically, just parsing Candid IDL file. I am still testing different scenarios, so this can be a good use case

@rdobrik
Copy link
Contributor

rdobrik commented Dec 8, 2022

Hi Alexander,

This should work. Because your type is nat32 we have to explicitly define IDL Type as a VEC of NAT32. Otherwise serializer doesn't have information about expected type and by default converts Integer to INT32.

final List argsList = new ArrayList<>();
argsList.add(IDLValue.create(canisterId, Type.PRINCIPAL));
argsList.add(IDLValue.create(new Integer[]{32}, IDLType.createType(Type.VEC, IDLType.createType(Type.NAT32))));

@chinaryov
Copy link
Author

It works! Thank you.

Now I see my mistake. I have used a wrong method:

argsList.add(IDLValue.create(new Integer[]{32}, IDLType.createType(Type.VEC, Type.NAT32)));

Same method was in the documentation.

IDLType idlArrayType = IDLType.createType(Type.VEC,Type.INT);
IDLType idlOptionalType = IDLType.createType(Type.OPT,Type.INT);

https://docs.ic4j.com/reference/api-reference/using-idlargs

@rdobrik
Copy link
Contributor

rdobrik commented Dec 12, 2022

I pushed out Beta 4 of IC4J Candid and Agent. Added support for dynamic loading Candid IDL file, either directly from canister or from IDL file. Also added some additional functionality to ProxyBuilder. Now we can create FuncProxy object, that can have preloaded Candid signature of the calling method. So no need to manually construct it or use java POJO classes.
Call to your method looks like this

ProxyBuilder proxyBuilder =  ProxyBuilder.create(agent);
				
Func func = new Func(Principal.fromString("udtw4-baaaa-aaaah-abc3q-cai"), "nftInfosByCollection");
FuncProxy<IDLArgs> funcProxy = proxyBuilder.getFuncProxy(func);
funcProxy.setResponseClass(IDLArgs.class);
				
IDLArgs responseArgs = funcProxy.call(canisterId, new Integer[]{32});

I tested it with your canister IDL , works well (I had to fix a few issues with our Candid parser, you are using name principal as an identifier, that collides with candid principal keyword, so IDL wraps it in double quotes :) ) But fixed now in our code.

It's experimental feature for now, I need to run few more test and test performance with some additional caching , I do not want to load IDL file all the time.

Gradle dependencies for this Beta are like this

implementation group: 'org.ic4j', name: 'ic4j-candid', version: '0.6.18-BETA4'
implementation group: 'org.ic4j', name: 'ic4j-agent', version: '0.6.18-BETA4'

Hopefully we will have final version 0.6.18 out sometimes this or next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants