• Overview
  • Design
  • API Reference
  • Changelog
  • Contribute
Show / Hide Table of Contents
  • Libplanet
    • Address
    • AddressExtension
    • ByteUtil
    • Hashcash
    • Hashcash.Stamp
    • HashDigest<T>
    • HashDigestExtension
    • Nonce
  • Libplanet.Action
    • AccountStateGetter
    • ActionEvaluation
    • ActionTypeAttribute
    • IAccountStateDelta
    • IAction
    • IActionContext
    • IRandom
    • MissingActionTypeException
    • PolymorphicAction<T>
    • RandomExtension
    • UnexpectedlyTerminatedActionException
  • Libplanet.Blockchain
    • BlockChain<T>
    • BlockChain<T>.TipChangedEventArgs
    • IncompleteBlockStatesException
    • MineBlockEventArgs<T>
  • Libplanet.Blockchain.Policies
    • BlockPolicy<T>
    • IBlockPolicy<T>
  • Libplanet.Blocks
    • Block<T>
    • InvalidBlockDifficultyException
    • InvalidBlockException
    • InvalidBlockHashException
    • InvalidBlockIndexException
    • InvalidBlockNonceException
    • InvalidBlockPreviousHashException
    • InvalidBlockTimestampException
    • InvalidGenesisBlockException
  • Libplanet.Crypto
    • CryptoConfig
    • DefaultCryptoBackend
    • ICryptoBackend
    • InvalidCiphertextException
    • PrivateKey
    • PublicKey
    • SymmetricKey
  • Libplanet.KeyStore
    • IncorrectPassphraseException
    • InvalidKeyJsonException
    • KeyJsonException
    • MismatchedAddressException
    • ProtectedPrivateKey
    • UnsupportedKeyJsonException
  • Libplanet.KeyStore.Ciphers
    • Aes128Ctr
    • ICipher
  • Libplanet.KeyStore.Kdfs
    • IKdf
    • Pbkdf2<T>
    • Scrypt
  • Libplanet.Net
    • ActionExecutionState
    • BlockDownloadState
    • BoundPeer
    • DifferentAppProtocolVersionException
    • DifferentProtocolVersionEventArgs
    • IceServer
    • IceServerException
    • InvalidMessageException
    • NoSwarmContextException
    • Peer
    • PeerNotFoundException
    • PeerState
    • PreloadBlockDownloadFailEventArgs
    • PreloadState
    • StateDownloadState
    • Swarm<T>
    • SwarmException
  • Libplanet.Net.Protocols
    • PeerDiscoveryException
  • Libplanet.Serialization
    • SerializationInfoExtension
  • Libplanet.Store
    • BaseIndex<TKey, TVal>
    • BaseStore
    • BlockSet<T>
    • ChainIdNotFoundException
    • DefaultStore
    • IStore
    • StoreExtension
    • TransactionSet<T>
  • Libplanet.Tx
    • InvalidTxException
    • InvalidTxIdException
    • InvalidTxNonceException
    • InvalidTxPublicKeyException
    • InvalidTxSignatureException
    • InvalidTxUpdatedAddressesException
    • Transaction<T>
    • TxId

Interface IAction

An in-game action. Every action should be replayable, because multiple nodes in a network should execute an action and get the same result.

A “class” which implements this interface is analogous to a function, and its instance is analogous to a partial function application, in other words, a function with some bound arguments. Those parameters that will be bound at runtime should be represented as fields or properties in an action class, and bound argument values to these parameters should be received through a constructor parameters of that class.

From a perspective of security, an action class belongs to the network protocol, and property values in an action belong to a node's will (i.e., a user/player's choice). That means if you define an action class it also defines what every honest node can do in the network. Even if a malicious node changes their own action code it won't affect other honest nodes in the network.

For example, where honest nodes share the common action Heal(Target) => PreviousStates[Target] + 1, suppose a malicious node m changes their own Heal action code to Heal(Target) => PreviousStates[Target] + 2 (2 instead of 1), and then send an action Heal(m). Fortunately, this action does not work as m's intention, because the changed code in itself is not used by other honest nodes, so they still increase only 1, not 2. The effect of that double healing is a sort of “illusion” only visible to the malicious node alone.

In conclusion, action code is a part of the protocol and it works with consensus in the network, so only things each node can affect the network in general is property values of each action they sign and send, not code of an action.

Namespace: Libplanet.Action
Assembly: Libplanet.dll
Syntax
public interface IAction
Examples

The following example shows how to implement an action of three types of in-game logic:

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Bencodex.Types;
using Libplanet;
using Libplanet.Action;
public class MyAction : IAction
{
    // Declare an enum type to distinguish types of in-game logic.
    public enum ActType { CreateCharacter, Attack, Heal }
    // Declare properties (or fields) to store "bound" argument values.
    public ActType Type { get; private set; }
    public Address TargetAddress { get; private set; }
    // Action must has a public parameterless constructor.
    // Usually this is used only by Libplanet's internals.
    public MyAction() {}
    // Take argument values to "bind" through constructor parameters.
    public MyAction(ActType type, Address targetAddress)
    {
        Type = type;
        TargetAddress = targetAddress;
    }
    // The main game logic belongs to here.  It takes the
    // previous states through its parameter named context,
    // and is offered "bound" argument values through
    // its own properties (or fields).
    IAccountStateDelta IAction.Execute(IActionContext context)
    {
        // Gets the state immediately before this action is executed.
        // ImmutableDictionary<string, uint> is just for example,
        // As far as it is serializable, you can store any types.
        // (We recommend to use immutable types though.)
        var state =
            context.PreviousStates.GetState(TargetAddress);
        Dictionary dictionary;
        // This variable purposes to store the state
        // right after this action finishes.
        IImmutableDictionary<IKey, IValue> nextState;
        // Does different things depending on the action's type.
        // This way is against the common principals of programming
        // as it is just an example.  You could compare this with
        // a better example of PolymorphicAction<T> class.
        switch (Type)
        {
            case ActType.CreateCharacter:
                if (!TargetAddress.Equals(context.Signer))
                    throw new Exception(
                        "TargetAddress of CreateCharacter action " +
                        "only can be the same address to the " +
                        "Transaction<T>.Signer.");
                else if (!(state is null))
                    throw new Exception(
                        "Character was already created.");
                nextState = ImmutableDictionary<IKey, IValue>.Empty
                    .Add((Text)"hp", (Integer)20);
                break;
            case ActType.Attack:
                dictionary = (Bencodex.Types.Dictionary)state;
                nextState =
                    dictionary.SetItem(
                        (Text)"hp",
                        (Integer)Math.Max(
                            dictionary.GetValue<Integer>("hp") - 5,
                            0)
                    );
                break;
            case ActType.Heal:
                dictionary = (Bencodex.Types.Dictionary)state;
                nextState =
                    dictionary.SetItem(
                        (Text)"hp",
                        (Integer)Math.Min(
                            dictionary.GetValue<Integer>("hp") + 5,
                            20)
                    );
                break;
            default:
                throw new Exception(
                    "Properties are not properly initialized.");
        }
        // Builds a delta (dirty) from previous to next states, and
        // returns it.
        return context.PreviousStates.SetState(TargetAddress,
            (Dictionary)nextState);
    }
    // Side effects, i.e., any effects on other than states, are
    // done here.
    void IAction.Render(
        IActionContext context,
        IAccountStateDelta nextStates)
    {
        Character c;
        // You could compare this with a better example of
        // PolymorphicAction<T> class.
        switch (Type)
        {
            case ActType.CreateCharacter:
                c = new Character
                {
                    Address = TargetAddress,
                    Hp = 0,
                };
                break;
            case ActType.Attack:
            case ActType.Heal:
                c = Character.GetByAddress(TargetAddress);
                break;
            default:
                return;
        }
        c.Hp =
            ((Bencodex.Types.Dictionary)nextStates.GetState(TargetAddress))
                .GetValue<Integer>("hp");
        c.Draw();
    }
    // Sometimes a block to which an action belongs can be
    // a "stale."  If that action already has been rendered,
    // it should be undone.
    void IAction.Unrender(
        IActionContext context,
        IAccountStateDelta nextStates)
    {
        Character c = Character.GetByAddress(TargetAddress);
        // You could compare this with a better example of
        // PolymorphicAction<T> class.
        switch (Type)
        {
            case ActType.CreateCharacter:
                c.Hide();
                break;
            case ActType.Attack:
            case ActType.Heal:
                IAccountStateDelta prevStates = context.PreviousStates;
                c.Hp = ((Bencodex.Types.Dictionary)prevStates.GetState(TargetAddress))
                    .GetValue<Integer>("hp");
                c.Draw();
                break;
            default:
                break;
        }
    }
    // Serializes its "bound arguments" so that they are transmitted
    // over network or stored to the persistent storage.
    IValue IAction.PlainValue =>
        new Bencodex.Types.Dictionary(new Dictionary<IKey, IValue>
        {
            [(Text)"type"] = (Integer)(int)Type,
            [(Text)"target_address"] = (Binary)TargetAddress.ToByteArray(),
        });
    // Deserializes "bound arguments".  That is, it is inverse
    // of PlainValue property.
    void IAction.LoadPlainValue(
        IValue plainValue)
    {
        var dictionary = (Bencodex.Types.Dictionary)plainValue;
        Type = (ActType)(int)dictionary.GetValue<Integer>("type");
        TargetAddress =
            new Address(dictionary.GetValue<Binary>("target_address").Value);
    }
}

Note that the above example has several bad practices. Compare this example with PolymorphicAction<T>'s example.

Properties

| Improve this Doc View Source

PlainValue

Serializes values bound to an action, which is held by properties (or fields) of an action, so that they can be transmitted over network or saved to persistent storage.

Serialized values are deserialized by LoadPlainValue(IValue) method later.

Declaration
IValue PlainValue { get; }
Property Value
Type Description
IValue

A Bencodex value which encodes this action's bound values (held by properties or fields).

See Also
LoadPlainValue(IValue)

Methods

| Improve this Doc View Source

Execute(IActionContext)

Executes the main game logic of an action. This should be deterministic.

Through the context object, it receives information such as a transaction signer, its states immediately before the execution, and a deterministic random seed.

Other “bound” information resides in the action object in itself, as its properties (or fields).

A returned IAccountStateDelta object functions as a delta which shifts from previous states to next states.

Declaration
IAccountStateDelta Execute(IActionContext context)
Parameters
Type Name Description
IActionContext context

A context object containing addresses that signed the transaction, states immediately before the execution, and a PRNG object which produces deterministic random numbers. See IActionContext for details.

Returns
Type Description
IAccountStateDelta

A map of changed states (so-called "dirty").

Remarks

This method should be deterministic: for structurally (member-wise) equal actions and IActionContexts, the same result should be returned. Side effects should be avoided, because an action's Execute(IActionContext) method can be called more than once, the time it's called is difficult to predict.

For changing in-memory game states or drawing graphics, write such code in the Render(IActionContext, IAccountStateDelta) method instead. The Render(IActionContext, IAccountStateDelta) method is guaranteed to be called only once, and only after an action is transmitted to other nodes in the network.

For randomness, never use nor any other PRNGs provided by other than Libplanet. Use Random instead. Random guarantees the same action has the consistent result for every node in the network.

Also do not perform I/O operations such as file system access or networking. These bring an action indeterministic. You maybe fine to log messages for debugging purpose, but equivalent messages could be logged multiple times.

Although it might be surprising, floating-point arithmetics are underspecified so that it can make different results on different machines, platforms, runtimes, compilers, and builds.

Lastly, you need to be aware and keep in mind that there is a global state named on .NET; if you format numbers, dates and times, currencies, or other such things into strings and parse these strings back these can rely on , so that the same action make different results on two differently configured systems like Thai language and French language. In order to make these types of conversions deterministic, you have to explicitly pass .

For more on determinism in general, please read also Tendermint ABCI's docs on determinism.

See Also
IActionContext
| Improve this Doc View Source

LoadPlainValue(IValue)

Deserializes serialized data (i.e., data PlainValue property made), and then fills this action's properties (or fields) with the deserialized values.

Declaration
void LoadPlainValue(IValue plainValue)
Parameters
Type Name Description
IValue plainValue

Data (made by PlainValue property) to be deserialized and assigned to this action's properties (or fields).

See Also
PlainValue
| Improve this Doc View Source

Render(IActionContext, IAccountStateDelta)

Does things that should be done right after this action is spread to the network or is “confirmed” (kind of) by each peer node.

Usually, this method updates the in-memory game states (if exist), and then sends a signal to the UI thread (usually the main thread) so that the graphics on the display is redrawn.

Declaration
void Render(IActionContext context, IAccountStateDelta nextStates)
Parameters
Type Name Description
IActionContext context

The equivalent context object to what Execute(IActionContext) method had received. That means PreviousStates are the states right before this action executed. For the states after this action executed, use the nextStates argument instead.

IAccountStateDelta nextStates

The states right after this action executed, which means it is equivalent to what Execute(IActionContext) method returned.

| Improve this Doc View Source

Unrender(IActionContext, IAccountStateDelta)

Does things that should be undone right after this action is invalidated (mostly due to a block which this action has belonged to becoming considered a stale).

This method takes the equivalent arguments to Render(IActionContext, IAccountStateDelta) method.

Declaration
void Unrender(IActionContext context, IAccountStateDelta nextStates)
Parameters
Type Name Description
IActionContext context

The equivalent context object to what Execute(IActionContext) method had received. That means PreviousStates are the states right before this action executed. For the states after this action executed, use the nextStates argument instead.

IAccountStateDelta nextStates

The states right after this action executed, which means it is equivalent to what Execute(IActionContext) method returned.

Remarks

As a rule of thumb, this should be the inverse of Render(IActionContext, IAccountStateDelta) method with redrawing the graphics on the display at the finish.

  • Improve this Doc
  • View Source
Back to top Copyright © 2019–2020 Planetarium