Awarded: ICDevs.org Bounty #39 - Async Flow - One Shot - Motoko - $6,000

This is what I was thinking. I think the question is that with the 5 minute queue, should the first retry happen before or after the 5 minutes(give or take some time).

Hi!

I realized here that there is a flaw in the com_asyncFlow_fin (or com_asyncFlow_ackack) function Since it is sent once (without resending attempts), I think this flaw will need to be fixed. But thatā€™s not the point. I realized here that the sending attempts themselves (if there is no confirmation) load the network. Letā€™s say 10 attempts to send a new message and 10 confirmation attempts load the network (10+10)/2 = 10 times!. The two here is the count of the usual exchange. The way out of the situation I see so far is this: the 1st attempt is single, perhaps the second attempt is single. Further confirmation attempts( letā€™s say from the 3rd to the 10th) must be sent in bulk (i.e., using Data Collections (List) ). That is, send multiple confirmations in one request

To my understanding, the purpose of this library is to allow canisters to communicate with 3ā€™rd party canisters without worrying about malicious actors that can render your own canister un-upgrade-able. For context, check out this post. It is meant as a stopgap until a permanent solution to the upgrade issue is implemented by Dfinity.

Using this library will be ~3x more expensive, but it will enable untrusted 3rd party communication today. Some people might find the tradeoff worth-while.

I understand. Iā€™m also leaning towards the main option. But there is an option to optimize the number of requests. I think after writing the main library, it will be possible to think about improvements.

Having a batch endpoint is an interesting proposition.

Hi! skilesare

I have a ready-made implementation. She was ready a couple of days ago. But there are packaging problems. Link to github

Now about the problem: the actual asynchronous data exchange in this library excludes the use of async ā€” await operators. Using these operators will result in waiting for the result to be returned. My code in the implementation of actors (Sender ā€” Receiver) does not use them. Since they are immediately created in canisters during assembly.
Of course, the main task of the library is to hide (encapsulate) the work of the library and simplify its use. Therefore, I wanted to take out the main logic of the work, that is, the code into a separate class or module. But in this case, the compiler (SDK) requires the use of asynchronous functions.

For example, a section of code in the senderā€™s canister (not packaged):

canister_receiver.com_asyncFlow_newMessage(msg);//NEW 

But if this code is packaged in a class or a separate module. Then it turns out such a construction:
(incorrectly)

class SourceSender(){
       public shared({caller}) func com_asyncFlow_newMessage(msg: MessageType): async(){
       //****//
       await canister_receiver.com_asyncFlow_newMessage(msg);//NEW 
 }
}

I will think about how to get away from asynchrony in the class. Perhaps the callback functions will help in this.
Update 1 (the callback function also requires asynchrony in the function parameters).
What are your opinions on this?

I think this requirement is expected. You may want to use async* in your library so that if the library cancels the send for some reason it doesnā€™t actually cause an await.

Thatā€™s right, Iā€™m not using async-await* at the moment. Everything works fine without async-await. But this is in Sender-Receiver cans, but if the code is packaged in a separate module, the SDK (compiler) forces you to add code with async-await*, otherwise calls between containers are not compiled.

Iā€™m still considering exit solutions out of the problem. I thought the callback functions would help, but they also eventually require the async-await construct.*

I also realized that the created class (in the actor) and even the module (in the actor) will be interpreted by the scanner and/or compiler as containers and require an asynchronous async-await* construct.

Update 1 I think Iā€™ve found a solution, but Iā€™m not 100% sure

Hi!
Skilesare

Iā€™ve been using your recommendations here. They were needed, thanks again.
I also have a question about asynchrony.

Looking through the forum thread and sdk:
I found some innovations on async* and await*, but they require the return of the result and not void. It is also not known until the end whether it will work correctly. This is more useful for internal calls within a single canister. And the async-await(async*-await*) construction introduced once is then required everywhere.

I would like to leave two solutions:

  • This is a template (example) of two canisters. Where there is no asynchrony.
  • Library. Where forced asynchrony is used. Asynchrony will be used until the situation is clarified or in connection with new developments in the language.
1 Like

I may not be understanding your issue correctly. Could you do a motoko playground that shows the issue?

I will try to answer now.

For example, the Sender:

When the main code is in canisters, the Sender and receiver calls between them are as follows:

receiver.com_asyncFlow_new(msg);
receiver.com_asyncFlow_fin(fin_msg)

When I wrap the main logic in a class (or put it in a module), it doesnā€™t compile. It is required to enter async-await
ā€œasynchronously".

await receiver.com_asyncFlow_new(msg);
await receiver.com_asyncFlow_fin(fin_msg)

Approximation

//version in actors

actor S{
    func a(){
        canister.send();
    }
}


//version wrapped in a library

actor Other{
    let lib = Lib()
    func a():async(){
        await lib.a();
    }
}


module{
    class Lib{
        func a():async(){
            await canister.send();
        }
    }
}

The first option is ā€œsent- forgotā€
The second option is waiting for the result.
Although in both cases there will be an answer either with an error or with a result.

It would be ideal to do following the logic of ā€œsent- forgotā€. But it doesnā€™t work! (If wrapped in a class) Therefore, at the moment I have the library wrapped in classes with an asynchronous version.

See the echo.mo and amodule.mo. This seems to be working for me.

Updated with async* which may help?

Hi!

Thanks,

I think async* await* is more preferable. Iā€™m not 100% sure, but this should throw off the expectations of in-module calls within a single container.

In truth, I have formed the opinion that a normal call is not much different from an asynchronous one within this SDK. Since in any case there will be a response either from the system or from the canister.

I had a version with async* await*, then I removed it. Since there are modular calls inside and calls between canisters within the same function. In my case, I am not sure of the correctness of such code. In a simple version, where there is one call and it is inside one module, this should work.

Iā€™m in the final stages now. I need to arrange and pack in mops. I think Iā€™ll come back to the async-await issue later.

Hi!
I want to share the work done

Source code of the library:

Example

Distribution via the package manager

https://mops.one/

Latest version

https://mops.one/maf

The check will take time. Of the shortcomings that I would like to change:

  1. Change TreeMap to a stable collection
  2. In the MessageType type, use not a Blob but a Generalized type

Hi!
I have done some improvement and optimization work.
I will describe it more briefly:
-System timers have been moved to the library. This is now hidden from library users.
-A stable version of hashmap has been added, in theory it should be faster than triemap.
-Changed the use of asynchrony (async* await*)
-Now at the first unsuccessful attempts to send and confirm. Repeated attempts are sent in packets satisfying the condition (by time)

More details: Internet Computer Content Validation Bootstrap

1 Like

Hi!
I havenā€™t seen what could be improved yet.
The only thing that was done: this is a complete refactoring of the code.
I also want to add one more detail. It concerns the synchronic of async* await*. There is no support at the moment in timers
timer.recording Timer(time, job)
(synchrony with an asterisk)
Itā€™s not critical. I asked this question. (System Timer support async*)
But there is no support in the latest version (MOC) yet.