I have a the following canister:
ic_cdk::export::candid::define_service!(
UnsortedService : {
"b" : ic_cdk::export::candid::func!(() -> (bool) query);
"a" : ic_cdk::export::candid::func!(() -> (bool) query)
}
);
ic_cdk::export::candid::define_service!(
SortedService : {
"a" : ic_cdk::export::candid::func!(() -> (bool) query);
"b" : ic_cdk::export::candid::func!(() -> (bool) query)
}
);
#[ic_cdk_macros::query]
fn check_sorted_service(service: SortedService) -> SortedService {
service
}
#[ic_cdk_macros::query]
fn check_unsorted_service(service: UnsortedService) -> UnsortedService {
service
}
There are two services defined. One that has the functions in alphabetical order and one out of order.
I have one function for each of these services that simple takes the corresponding service as a parameter and returns it.
Everything is compiling fine, but when I try to call check_unsorted_service
dcc service_canister check_unsorted_service '(service "r7inp-6aaaa-aaaaa-aaabq-cai")'
I get the following error:
Error deserializing blob 0x4449444c0269020162010161016a00017e01010100010a00000000000000030101
Error: Failed to deserialize idl blob: Invalid data.
Caused by: Failed to deserialize idl blob: Invalid data.
Cannot parse header 4449444c0269020162010161016a00017e01010100010a00000000000000030101
Invalid table entry 0: Service(ServType { len: 2, meths: [Meths { len: 1, name: "b", ty: IndexType { index: 1 } }, Meths { len: 1, name: "a", ty: IndexType { index: 1 } }] })
method name a duplicate or not sorted
If I call the other method it works just fine
$ dcc service_canister check_sorted_service '(service "r7inp-6aaaa-aaaaa-aaabq-cai")'
(service "r7inp-6aaaa-aaaaa-aaabq-cai")
The other canister looks like this
#[ic_cdk_macros::query]
fn a() -> bool {
return true;
}
#[ic_cdk_macros::query]
fn b() -> bool {
return false
}
Any thoughts on why this is happening?
1 Like
I am seeing this on both DFX 0.13.1 and 0.14.1
Here are my dependencies
[dependencies]
candid = "=0.9.0-beta.3"
ic-cdk = "=0.8.0-beta.0"
ic-cdk-macros = "0.6.10"
serde = "1.0.137"
1 Like
Thanks for reporting this. This is a bug, I will fix it. The macro expansion should sort the methods.
4 Likes
Thank you for looking into it!
1 Like
Awesome thanks! Good luck
Is there an issue or PR for this?
lastmjs
September 13, 2023, 7:55pm
8
Do you know if dfx has this fix integrated yet?
lastmjs
September 13, 2023, 8:04pm
9
We are running into this issue again, we’re still debugging, but it seems like the problem is arising from dfx itself when trying to deserialize a Candid service that we return from an Azle canister.
Is it possible the dfx dependencies haven’t been updated to include this fix?
Here’s the error:
$ dfx canister call service serviceParam '(service "aaaaa-aa")'Error deserializing blob 0x4449444c036a000171006a00017e01016902077570646174653100067175657279310101020100Error: Failed to deserialize idl blob: Invalid data.Caused by: Failed to deserialize idl blob: Invalid data. Cannot parse header 4449444c036a000171006a00017e01016902077570646174653100067175657279310101020100 Invalid table entry 2: Service(ServType { len: 2, meths: [Meths { len: 7, name: "update1", ty: IndexType { index: 0 } }, Meths { len: 6, name: "query1", ty: IndexType { index: 1 } }] }) method name query1 duplicate or not sorted
Here’s a slightly simplified version of the Azle method that was called:
class extends Service {
@query([], bool)
query1(): bool {
return true;
}
@update([], text)
update1(): text {
return 'SomeService update1';
}
}
@query([SomeService], SomeService)
serviceParam(someService: SomeService): SomeService {
return someService;
}
lastmjs
September 13, 2023, 8:11pm
10
I’m thinking this is a problem with @dfinity/candid
, I think the same fix for candid
in Rust needs to be done for @dfinity/candid
. @kpeacock
lastmjs
September 13, 2023, 8:42pm
11
It is indeed a problem in @dfinity/candid
. Debugging we found a quick fix. Notice we changed the sort function in the constructor:
export class ServiceClass extends ConstructType {
constructor(fields) {
super();
// this._fields = Object.entries(fields).sort((a, b) => idlLabelToId(a[0]) - idlLabelToId(b[0]));
this._fields = Object.entries(fields).sort((a, b) => {
if (a[0] < b[0]) {
return -1;
}
if (a[0] > b[0]) {
return 1;
}
return 0;
});
}
accept(v, d) {
return v.visitService(this, d);
}
covariant(x) {
if (x && x._isPrincipal)
return true;
throw new Error(`Invalid ${this.display()} argument: ${toReadableString(x)}`);
}
encodeValue(x) {
const buf = x.toUint8Array();
const len = lebEncode(buf.length);
return concat(new Uint8Array([1]), len, buf);
}
_buildTypeTableImpl(T) {
this._fields.forEach(([_, func]) => func.buildTypeTable(T));
const opCode = slebEncode(-23 /* IDLTypeIds.Service */);
const len = lebEncode(this._fields.length);
const meths = this._fields.map(([label, func]) => {
const labelBuf = new TextEncoder().encode(label);
const labelLen = lebEncode(labelBuf.length);
return concat(labelLen, labelBuf, func.encodeType(T));
});
T.add(this, concat(opCode, len, ...meths));
}
decodeValue(b) {
return decodePrincipalId(b);
}
get name() {
const fields = this._fields.map(([key, value]) => key + ':' + value.name);
return `service {${fields.join('; ')}}`;
}
valueToString(x) {
return `service "${x.toText()}"`;
}
}
chenyan
September 13, 2023, 8:49pm
12
You are right. That’s a bug on the agent-js side. The sorting function should be alphabet, not based on the IDL hash.
1 Like
lastmjs
September 13, 2023, 8:57pm
13
Do you have an ETA on when this bug could be fixed? We’re trying to decide if we should maintain a fork or not.
There’s a similar issue in @dfinity/agent
v0.19.3.
Update: Looks like a fix was recently merged so the JS agent part should be resolved in the next release
See fix: service ordering must be alphabetical by lastmjs · Pull Request #781 · dfinity/agent-js · GitHub