Context-aware Flipper
Our previous example works just like any standard smart-contract on any other blockchain. The logic has some state where it stores user's state (values). Anyone can set, get or flip their own value.
But just like on any other blockchain, if multiple users want to modify their value, they need to do it one after another. If a million users try to flip their values, the logic will be locked for each user, the value flipped, then another user's invocation will flip its own value and so again and again. And it will take quite some time to finish.
The core concept of MOI blockchain that resolves the problem above is to remove the data from a central location in the logic
into actor's context
(users are called actors
in MOI, any user or even logic can be an actor
). So instead of a Map
on a logic, we keep the value
on each actor's context. In this manner, logic Flipper
doesn't need to be locked and can be used to modify all actor's data in parallel, so in the above case of a million actors wanting to flip their state, they can all do it at once in parallel, gaining massive performance.
Modified Coco code
Let's write the new code and compare it with the original Flipper
with central storage of values. The new module is called ContextFlipper
, we can put it in a separate folder with its own coco.nut
file and the source in context_flipper.coco
:
coco ContextFlipper
state actor:
value Bool
endpoint enlist Init():
mutate true -> ContextFlipper.Sender.value
endpoint dynamic Flip():
mutate value <- ContextFlipper.Sender.value:
value = !value
endpoint Mode() -> (value Bool):
observe value <- ContextFlipper.Sender.value
endpoint dynamic Set(value Bool):
mutate value -> ContextFlipper.Sender.value
Testing the code
First, compile the code and init Cocolab, so you get a compiled logic ContextFlipper and default_user as the Sender.
coco compile
coco lab init
With context-aware logics, state on every actor is not initialized by deploy
command, but by enlist
. So let's enlist
default_user
to use the logic and check the results.
enlist ContextFlipper.Init()
observe ContextFlipper.Sender.value
Init
has set the value
for the logic ContextFlipper
on the actor that has made the call (also denoted by Sender
).
So observing the Sender
's value on the logic ContextFlipper
returns true
.
Sender
means default.sender
in Cocolab, but you can also use user's name (default_user
in our example) or even the identifier.
Let's look what identifier has the user and try other options for observing the data:
users
default_user [0x3354b6f98a3920ee671c0982c37439f98995104330a771c9d457372ad8da1a13]
The output should look like this, 32-byte identifier will be unique for everyone. Now, we can try all three options to observe the value and they should all return the same result as Sender
is default_user
and its identifier is 0x3354b6f98a3920ee671c0982c37439f98995104330a771c9d457372ad8da1a13
observe ContextFlipper.Sender.value
observe ContextFlipper.default_user.value
observe.ContextFlipper.0x3354b6f98a3920ee671c0982c37439f98995104330a771c9d457372ad8da1a13.value
Flip the value and repeat the above commands and they should return the same value again
invoke ContextFlipper.Flip()
observe ContextFlipper.Sender.value
observe ContextFlipper.default_user.value
observe.ContextFlipper.0x3354b6f98a3920ee671c0982c37439f98995104330a771c9d457372ad8da1a13.value
Let's see how this works for a different user. As in our first example, register a new user, make it the default.sender
and
it should be able to work with its ContextFlipper
value - all this while default_user
's value remains as it was.
register Robert
wipe default.sender
set default.sender Robert
enlist ContextFlipper.Init()
observe ContextFlipper.Robert.value
observe ContextFlipper.default_user.value
Let's flip Robert's value and observe both his and default_user
's value.
invoke ContextFlipper.Flip()
observe ContextFlipper.Robert.value
observe ContextFlipper.default_user.value
Actor's context is the most important concept in MOI, but to fully use Coco, let's explore the language in more detail.