State Transition Functions
State transitions are the functions that move the state machine from one state to another. These contain the logic of the application and determine how the state machine will move from one state to another based on the inputs.
Transition Functions in Stackr
Stackr provides a toolkit to create state transition functions.
A basic transition function in Stackr looks like this:
import { STF, Transitions } from "@stackr/sdk/machine";
import { CounterState } from "./machine";
const increment: STF<CounterState> = {
handler: ({ state }) => {
state += 1;
return state;
},
};
const decrement: STF<CounterState> = {
handler: ({ state }) => {
state -= 1;
return state;
},
};
export const transitions: Transitions<CounterState> = {
increment,
decrement,
};
In the above example, we have two transition functions increment
and decrement
. These functions take the current state as input and return the new state after applying the logic.
Transition Functions in Detail
The most important part of the transition function is the handler
function. This function takes the current state and any other input as arguments and returns the new state. There are several inputs that handler receives which are discussed in the next section. However the most important thing to note here is that the handler function should be a pure function. It should not have any side effects and should only depend on the inputs.
Mutating the State
The state is passed as a reference to the handler function. This means that you can directly mutate the state inside the handler function. Once the operations on the state are complete, you must return the new state otherwise the state machine will not apply the changes to the state
STF is a pure function
The transition function is a pure function. This means that it should not have any side effects and should only depend on the inputs. This is important because the state is verified by Vulcan after the block is sent to it. Vulcan can only run the STF inside a sandboxed environment and cannot run any async operations or access the network or perform any side effects.
Passing Inputs to an STF
The state transition functions accept inputs, which is available through the inputs
argument on the handler. These inputs are derived through which ActionSchema
was used to trigger the STF.
const increment: STF<CounterState> = {
handler: ({ state, inputs }) => {
state += inputs.amount;
return state;
},
};
Variables available inside an STF
There are 6 special variables which always exist inside the handler of the State transition function. These can be used by the developer to write the logic of the state transition function:
state
msgSender
inputs
signature
block
block.height
block.timestamp
block.parentHash
emit
// Without Destructuring
const superCoolStf: STF<MachineState, InputTypes> = {
handler: (props) => {
const state = props.blockemitinputsmsgSendersignaturestate
const height = props.block.heightparentHashtimestamp
},
};
// With Destructuring
const anotherSuperCoolStf: STF<MachineState, InputTypes> = {
handler: ({ ssignaturestate }) => {
},
};
State
state
: The entire current state of the state machine in wrapped format. This is the state that the state transition function is supposed to modify.
Action Data
When an action is dispatched, the inputs are passed to the state transition function. The inputs are the parameters passed to the action schema.
-
msgSender
: The address of the sender of the transaction. -
inputs
: The inputs passed to the action schema. -
signature
: The signature of the msgSender on the inputs.
Block Properties
A block
object containing the current block height, timestamp and the parent block's hash is passed as an argument to the state transition function. These can be used to generate pseudo-random numbers or to implement time-based logic.
-
block.height
: The height of the block in which the transaction is included. -
block.timestamp
: The timestamp of the block in which the transaction is included. -
block.parentHash
: The hash of the parent block of the block in which the transaction is included.
Custom Execution Logs
The emit
variable passed to STF is a method that can be used to record custom execution logs (later accessible at action.logs
).
Example usage:
const increment: STF<CounterState> = {
handler: ({ state, inputs, emit }) => {
state += inputs.amount;
emit({ name: "incremented", value: inputs.amount });
return state;
},
};
Bonus: Pseudo-Random Number Generation
The block.parentHash
can be used to generate pseudo-random numbers. The hash of the parent block is used as a seed to generate pseudo-random numbers. This is useful when you need to generate random numbers in a deterministic way. This is subjected to change with VRF implementation in the future.