ic4j / ic4j-candid

Java Candid for The Internet Computer (IC4J) is a set of native Java libraries to manage the Internet Computer Candid types
Apache License 2.0
3 stars 1 forks source link

Error while decoding response with Opcode.FUNC #3

Open chinaryov opened 1 year ago

chinaryov commented 1 year ago

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 commented 1 year ago

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

rdobrik commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

rdobrik commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

Hello!

Great news! I will try this beta version.

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

Alexander

rdobrik commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

Hello!

Thank you. It works great for me.

chinaryov commented 1 year ago

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 commented 1 year ago

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

rdobrik commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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.