jack0son / woke-dapp

Woke Network 🐦→🌐³ Monorepo containing dApp client, smart-contracts, and back-end services.
http://woke.network
2 stars 0 forks source link

Atomicity and Isolation of transactions broken in several actors. #57

Open jack0son opened 4 years ago

jack0son commented 4 years ago

Because several actors dispatch to a separate action to perform persistent state updates or complete another stage of the transaction, atomicity and isolation is not conserved.

Consider the case of a separate message to perform a persistent state update, for example in the nonce actor. When a dispatch to get_nonce is made, if the nonce is updated, the new nonce is dispatched to the set_nonce action of the nonce actor (ctx.self).

If a second get_nonce message had arrived directly after the first, it would receive the same state as the first message and proceed to update the nonce as well, making a duplicate and unnecessary call to the Ethereum node. In this case, the bug is fixed by having the get_nonce action return the new nonce, so in effect the state update is duplicated by get_nonce and set_nonce.

In this case multiple actions served as a simple work around to seperate persistence from the core logic, and breaking acidity has no serious consequence, but there are situations where action composition is more critical. Care has not been taken to avoid this same bug in other actors. The tip actor implements a resolver which is called as separate message. This must cause side effects.

Proposed solution

All actions should call an action function which is described outside the action defintion such that if it is necessary for one action to call some others, they are able to do so with out dispatching a new message, thus maintaining the acidity of the message.

The actor's actions should be composed of functions which accept message bundles.

Example

// -- Not ACID
const actor = {
    actions: {
        'mutate': (msg, ctx, state) => {
            const { greeting } = msg;
            dispatch(ctx.self, { greeting });
            return { ...state, greeting: greeting || 'go away' };
        },

        'persist': async (msg, ctx, state) => {
            if(ctx.persist) {
                await ctx.persist(msg);
            }
            return { ...state, greeting: msg.greeting };
        }
    }
}

// -- ACID
function action_mutate(msg, ctx, state) {
    const { greeting } = msg;
    dispatch(ctx.self, { greeting });
    return { ...state, greeting: greeting || 'go away' };

    return action_persist({ greeting }, ctx, state);
}

function action_persist(msg, ctx, state) {
    if(ctx.persist) {
        await ctx.persist(msg);
    }
    return { ...state, greeting: msg.greeting };
}

const actor = {
    actions: {
        'mutate': action_mutate,
        'persist': action_persist,
    }
}