726 lines
21 KiB
Go
726 lines
21 KiB
Go
package stf
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"iter"
|
|
"strings"
|
|
|
|
appmodulev2 "cosmossdk.io/core/appmodule/v2"
|
|
corecontext "cosmossdk.io/core/context"
|
|
"cosmossdk.io/core/event"
|
|
"cosmossdk.io/core/gas"
|
|
"cosmossdk.io/core/header"
|
|
"cosmossdk.io/core/log"
|
|
"cosmossdk.io/core/router"
|
|
"cosmossdk.io/core/server"
|
|
"cosmossdk.io/core/store"
|
|
"cosmossdk.io/core/transaction"
|
|
"cosmossdk.io/schema/appdata"
|
|
stfgas "cosmossdk.io/server/v2/stf/gas"
|
|
"cosmossdk.io/server/v2/stf/internal"
|
|
)
|
|
|
|
type eContextKey struct{}
|
|
|
|
var executionContextKey = eContextKey{}
|
|
|
|
// STF is a struct that manages the state transition component of the app.
|
|
type STF[T transaction.Tx] struct {
|
|
logger log.Logger
|
|
|
|
msgRouter coreRouterImpl
|
|
queryRouter coreRouterImpl
|
|
|
|
doPreBlock func(ctx context.Context, txs []T) error
|
|
doBeginBlock func(ctx context.Context) error
|
|
doEndBlock func(ctx context.Context) error
|
|
doValidatorUpdate func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error)
|
|
|
|
doTxValidation func(ctx context.Context, tx T) error
|
|
postTxExec func(ctx context.Context, tx T, success bool) error
|
|
|
|
branchFn branchFn // branchFn is a function that given a readonly state it returns a writable version of it.
|
|
makeGasMeter makeGasMeterFn
|
|
makeGasMeteredState makeGasMeteredStateFn
|
|
}
|
|
|
|
// New returns a new STF instance.
|
|
func New[T transaction.Tx](
|
|
logger log.Logger,
|
|
msgRouterBuilder *MsgRouterBuilder,
|
|
queryRouterBuilder *MsgRouterBuilder,
|
|
doPreBlock func(ctx context.Context, txs []T) error,
|
|
doBeginBlock func(ctx context.Context) error,
|
|
doEndBlock func(ctx context.Context) error,
|
|
doTxValidation func(ctx context.Context, tx T) error,
|
|
doValidatorUpdate func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error),
|
|
postTxExec func(ctx context.Context, tx T, success bool) error,
|
|
branch func(store store.ReaderMap) store.WriterMap,
|
|
) (*STF[T], error) {
|
|
msgRouter, err := msgRouterBuilder.build()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("build msg router: %w", err)
|
|
}
|
|
queryRouter, err := queryRouterBuilder.build()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("build query router: %w", err)
|
|
}
|
|
|
|
return &STF[T]{
|
|
logger: logger,
|
|
msgRouter: msgRouter,
|
|
queryRouter: queryRouter,
|
|
doPreBlock: doPreBlock,
|
|
doBeginBlock: doBeginBlock,
|
|
doEndBlock: doEndBlock,
|
|
doValidatorUpdate: doValidatorUpdate,
|
|
doTxValidation: doTxValidation,
|
|
postTxExec: postTxExec, // TODO
|
|
branchFn: branch,
|
|
makeGasMeter: stfgas.DefaultGasMeter,
|
|
makeGasMeteredState: stfgas.DefaultWrapWithGasMeter,
|
|
}, nil
|
|
}
|
|
|
|
// DeliverSims entrypoint to processes sims transactions similar to DeliverBlock.
|
|
func (s STF[T]) DeliverSims(
|
|
ctx context.Context,
|
|
block *server.BlockRequest[T],
|
|
state store.ReaderMap,
|
|
simsBuilder func(ctx context.Context) iter.Seq[T],
|
|
) (blockResult *server.BlockResponse, newState store.WriterMap, err error) {
|
|
return s.deliverBlock(ctx, block, state, s.doSimsTXs(simsBuilder))
|
|
}
|
|
|
|
// DeliverBlock is our state transition function.
|
|
// It takes a read only view of the state to apply the block to,
|
|
// executes the block and returns the block results and the new state.
|
|
func (s STF[T]) DeliverBlock(
|
|
ctx context.Context,
|
|
block *server.BlockRequest[T],
|
|
state store.ReaderMap,
|
|
) (blockResult *server.BlockResponse, newState store.WriterMap, err error) {
|
|
return s.deliverBlock(ctx, block, state, s.doDeliverTXs)
|
|
}
|
|
|
|
// common code path for DeliverSims and DeliverBlock
|
|
type doInBlockDeliveryFn[T transaction.Tx] func(
|
|
ctx context.Context,
|
|
txs []T,
|
|
newState store.WriterMap,
|
|
hi header.Info,
|
|
) ([]server.TxResult, error)
|
|
|
|
func (s STF[T]) deliverBlock(
|
|
ctx context.Context,
|
|
block *server.BlockRequest[T],
|
|
state store.ReaderMap,
|
|
doInBlockDelivery doInBlockDeliveryFn[T],
|
|
) (blockResult *server.BlockResponse, newState store.WriterMap, err error) {
|
|
// creates a new branchFn state, from the readonly view of the state
|
|
// that can be written to.
|
|
newState = s.branchFn(state)
|
|
hi := header.Info{
|
|
Hash: block.Hash,
|
|
AppHash: block.AppHash,
|
|
ChainID: block.ChainId,
|
|
Time: block.Time,
|
|
Height: int64(block.Height),
|
|
}
|
|
// set header info
|
|
err = s.setHeaderInfo(newState, hi)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to set initial header info, %w", err)
|
|
}
|
|
|
|
exCtx := s.makeContext(ctx, ConsensusIdentity, newState, internal.ExecModeFinalize)
|
|
exCtx.setHeaderInfo(hi)
|
|
|
|
// reset events
|
|
exCtx.events = make([]event.Event, 0)
|
|
// pre block is called separate from begin block in order to prepopulate state
|
|
preBlockEvents, err := s.preBlock(exCtx, block.Txs)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if err = isCtxCancelled(ctx); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// reset events
|
|
exCtx.events = make([]event.Event, 0)
|
|
// begin block
|
|
var beginBlockEvents []event.Event
|
|
if !block.IsGenesis {
|
|
// begin block
|
|
beginBlockEvents, err = s.beginBlock(exCtx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
// check if we need to return early
|
|
if err = isCtxCancelled(ctx); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// execute txs
|
|
txResults, err := doInBlockDelivery(exCtx, block.Txs, newState, hi)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
// reset events
|
|
exCtx.events = make([]event.Event, 0)
|
|
// end block
|
|
endBlockEvents, valset, err := s.endBlock(exCtx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return &server.BlockResponse{
|
|
ValidatorUpdates: valset,
|
|
PreBlockEvents: preBlockEvents,
|
|
BeginBlockEvents: beginBlockEvents,
|
|
TxResults: txResults,
|
|
EndBlockEvents: endBlockEvents,
|
|
}, newState, nil
|
|
}
|
|
|
|
func (s STF[T]) doDeliverTXs(
|
|
exCtx context.Context,
|
|
txs []T,
|
|
newState store.WriterMap,
|
|
hi header.Info,
|
|
) ([]server.TxResult, error) {
|
|
// execute txs
|
|
txResults := make([]server.TxResult, len(txs))
|
|
// TODO: skip first tx if vote extensions are enabled (marko)
|
|
for i, txBytes := range txs {
|
|
// check if we need to return early or continue delivering txs
|
|
if err := isCtxCancelled(exCtx); err != nil {
|
|
return nil, err
|
|
}
|
|
txResults[i] = s.deliverTx(exCtx, newState, txBytes, transaction.ExecModeFinalize, hi, int32(i+1))
|
|
}
|
|
return txResults, nil
|
|
}
|
|
|
|
// deliverTx executes a TX and returns the result.
|
|
func (s STF[T]) deliverTx(
|
|
ctx context.Context,
|
|
state store.WriterMap,
|
|
tx T,
|
|
execMode transaction.ExecMode,
|
|
hi header.Info,
|
|
txIndex int32,
|
|
) server.TxResult {
|
|
// recover in the case of a panic
|
|
var recoveryError error
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
recoveryError = fmt.Errorf("panic during transaction execution: %s", r)
|
|
s.logger.Error("panic during transaction execution", "error", recoveryError)
|
|
}
|
|
}()
|
|
// handle error from GetGasLimit
|
|
gasLimit, gasLimitErr := tx.GetGasLimit()
|
|
if gasLimitErr != nil {
|
|
return server.TxResult{
|
|
Error: gasLimitErr,
|
|
}
|
|
}
|
|
|
|
if recoveryError != nil {
|
|
return server.TxResult{
|
|
Error: recoveryError,
|
|
}
|
|
}
|
|
validateGas, validationEvents, err := s.validateTx(ctx, state, gasLimit, tx, execMode)
|
|
if err != nil {
|
|
return server.TxResult{
|
|
Error: err,
|
|
}
|
|
}
|
|
events := make([]event.Event, 0)
|
|
// set the event indexes, set MsgIndex to 0 in validation events
|
|
for i, e := range validationEvents {
|
|
e.BlockNumber = uint64(hi.Height)
|
|
e.BlockStage = appdata.TxProcessingStage
|
|
e.TxIndex = txIndex
|
|
e.MsgIndex = 0
|
|
e.EventIndex = int32(i + 1)
|
|
events = append(events, e)
|
|
}
|
|
|
|
execResp, execGas, execEvents, err := s.execTx(ctx, state, gasLimit-validateGas, tx, execMode, hi)
|
|
// set the TxIndex in the exec events
|
|
for _, e := range execEvents {
|
|
e.BlockNumber = uint64(hi.Height)
|
|
e.BlockStage = appdata.TxProcessingStage
|
|
e.TxIndex = txIndex
|
|
events = append(events, e)
|
|
}
|
|
|
|
return server.TxResult{
|
|
Events: events,
|
|
GasUsed: execGas + validateGas,
|
|
GasWanted: gasLimit,
|
|
Resp: execResp,
|
|
Error: err,
|
|
}
|
|
}
|
|
|
|
// validateTx validates a transaction given the provided WritableState and gas limit.
|
|
// If the validation is successful, state is committed
|
|
func (s STF[T]) validateTx(
|
|
ctx context.Context,
|
|
state store.WriterMap,
|
|
gasLimit uint64,
|
|
tx T,
|
|
execMode transaction.ExecMode,
|
|
) (gasUsed uint64, events []event.Event, err error) {
|
|
validateState := s.branchFn(state)
|
|
hi, err := s.getHeaderInfo(validateState)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
validateCtx := s.makeContext(ctx, RuntimeIdentity, validateState, execMode)
|
|
validateCtx.setHeaderInfo(hi)
|
|
validateCtx.setGasLimit(gasLimit)
|
|
err = s.doTxValidation(validateCtx, tx)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
consumed := validateCtx.meter.Limit() - validateCtx.meter.Remaining()
|
|
|
|
return consumed, validateCtx.events, applyStateChanges(state, validateState)
|
|
}
|
|
|
|
// execTx executes the tx messages on the provided state. If the tx fails then the state is discarded.
|
|
func (s STF[T]) execTx(
|
|
ctx context.Context,
|
|
state store.WriterMap,
|
|
gasLimit uint64,
|
|
tx T,
|
|
execMode transaction.ExecMode,
|
|
hi header.Info,
|
|
) ([]transaction.Msg, uint64, []event.Event, error) {
|
|
execState := s.branchFn(state)
|
|
|
|
msgsResp, gasUsed, runTxMsgsEvents, txErr := s.runTxMsgs(ctx, execState, gasLimit, tx, execMode, hi)
|
|
if txErr != nil {
|
|
// in case of error during message execution, we do not apply the exec state.
|
|
// instead we run the post exec handler in a new branchFn from the initial state.
|
|
postTxState := s.branchFn(state)
|
|
postTxCtx := s.makeContext(ctx, RuntimeIdentity, postTxState, execMode)
|
|
postTxCtx.setHeaderInfo(hi)
|
|
|
|
postTxErr := s.postTxExec(postTxCtx, tx, false)
|
|
if postTxErr != nil {
|
|
// if the post tx handler fails, then we do not apply any state change to the initial state.
|
|
// we just return the exec gas used and a joined error from TX error and post TX error.
|
|
return nil, gasUsed, nil, errors.Join(txErr, postTxErr)
|
|
}
|
|
// in case post tx is successful, then we commit the post tx state to the initial state,
|
|
// and we return post tx events alongside exec gas used and the error of the tx.
|
|
applyErr := applyStateChanges(state, postTxState)
|
|
if applyErr != nil {
|
|
return nil, 0, nil, applyErr
|
|
}
|
|
// set the event indexes, set MsgIndex to -1 in post tx events
|
|
for i := range postTxCtx.events {
|
|
postTxCtx.events[i].EventIndex = int32(i + 1)
|
|
postTxCtx.events[i].MsgIndex = -1
|
|
}
|
|
|
|
return nil, gasUsed, postTxCtx.events, txErr
|
|
}
|
|
// tx execution went fine, now we use the same state to run the post tx exec handler,
|
|
// in case the execution of the post tx fails, then no state change is applied and the
|
|
// whole execution step is rolled back.
|
|
postTxCtx := s.makeContext(ctx, RuntimeIdentity, execState, execMode) // NO gas limit.
|
|
postTxCtx.setHeaderInfo(hi)
|
|
postTxErr := s.postTxExec(postTxCtx, tx, true)
|
|
if postTxErr != nil {
|
|
// if post tx fails, then we do not apply any state change, we return the post tx error,
|
|
// alongside the gas used.
|
|
return nil, gasUsed, nil, postTxErr
|
|
}
|
|
// both the execution and post tx execution step were successful, so we apply the state changes
|
|
// to the provided state, and we return responses, and events from exec tx and post tx exec.
|
|
applyErr := applyStateChanges(state, execState)
|
|
if applyErr != nil {
|
|
return nil, 0, nil, applyErr
|
|
}
|
|
// set the event indexes, set MsgIndex to -1 in post tx events
|
|
for i := range postTxCtx.events {
|
|
postTxCtx.events[i].EventIndex = int32(i + 1)
|
|
postTxCtx.events[i].MsgIndex = -1
|
|
}
|
|
|
|
return msgsResp, gasUsed, append(runTxMsgsEvents, postTxCtx.events...), nil
|
|
}
|
|
|
|
// runTxMsgs will execute the messages contained in the TX with the provided state.
|
|
func (s STF[T]) runTxMsgs(
|
|
ctx context.Context,
|
|
state store.WriterMap,
|
|
gasLimit uint64,
|
|
tx T,
|
|
execMode transaction.ExecMode,
|
|
hi header.Info,
|
|
) ([]transaction.Msg, uint64, []event.Event, error) {
|
|
txSenders, err := tx.GetSenders()
|
|
if err != nil {
|
|
return nil, 0, nil, err
|
|
}
|
|
msgs, err := tx.GetMessages()
|
|
if err != nil {
|
|
return nil, 0, nil, err
|
|
}
|
|
msgResps := make([]transaction.Msg, len(msgs))
|
|
|
|
execCtx := s.makeContext(ctx, RuntimeIdentity, state, execMode)
|
|
execCtx.setHeaderInfo(hi)
|
|
execCtx.setGasLimit(gasLimit)
|
|
events := make([]event.Event, 0)
|
|
for i, msg := range msgs {
|
|
execCtx.sender = txSenders[i]
|
|
execCtx.events = make([]event.Event, 0) // reset events
|
|
resp, err := s.msgRouter.Invoke(execCtx, msg)
|
|
if err != nil {
|
|
return nil, 0, nil, err // do not wrap the error or we lose the original error type
|
|
}
|
|
msgResps[i] = resp
|
|
for j, e := range execCtx.events {
|
|
e.MsgIndex = int32(i + 1)
|
|
e.EventIndex = int32(j + 1)
|
|
events = append(events, e)
|
|
}
|
|
|
|
// add message event
|
|
events = append(events, createMessageEvent(msg, int32(i+1), int32(len(execCtx.events)+1)))
|
|
}
|
|
|
|
consumed := execCtx.meter.Limit() - execCtx.meter.Remaining()
|
|
return msgResps, consumed, events, nil
|
|
}
|
|
|
|
// Create a message event, with two kv: action, the type url of the message
|
|
// and module, the module of the message.
|
|
func createMessageEvent(msg transaction.Msg, msgIndex, eventIndex int32) event.Event {
|
|
// Assumes that module name is the second element of the msg type URL
|
|
// e.g. "cosmos.bank.v1beta1.MsgSend" => "bank"
|
|
// It returns an empty string if the input is not a valid type URL
|
|
getModuleNameFromTypeURL := func(input string) string {
|
|
moduleName := strings.Split(input, ".")
|
|
if len(moduleName) > 1 {
|
|
return moduleName[1]
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
return event.Event{
|
|
MsgIndex: msgIndex,
|
|
EventIndex: eventIndex,
|
|
Type: "message",
|
|
Attributes: func() ([]appdata.EventAttribute, error) {
|
|
typeURL := msgTypeURL(msg)
|
|
return []appdata.EventAttribute{
|
|
{Key: "action", Value: "/" + typeURL},
|
|
{Key: "module", Value: getModuleNameFromTypeURL(typeURL)},
|
|
}, nil
|
|
},
|
|
Data: func() (json.RawMessage, error) {
|
|
typeURL := msgTypeURL(msg)
|
|
attrs := []appdata.EventAttribute{
|
|
{Key: "action", Value: "/" + typeURL},
|
|
{Key: "module", Value: getModuleNameFromTypeURL(typeURL)},
|
|
}
|
|
|
|
return json.Marshal(attrs)
|
|
},
|
|
}
|
|
}
|
|
|
|
// preBlock executes the pre block logic.
|
|
func (s STF[T]) preBlock(
|
|
ctx *executionContext,
|
|
txs []T,
|
|
) ([]event.Event, error) {
|
|
err := s.doPreBlock(ctx, txs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := range ctx.events {
|
|
ctx.events[i].BlockNumber = uint64(ctx.headerInfo.Height)
|
|
ctx.events[i].BlockStage = appdata.PreBlockStage
|
|
ctx.events[i].EventIndex = int32(i + 1)
|
|
}
|
|
|
|
return ctx.events, nil
|
|
}
|
|
|
|
// beginBlock executes the begin block logic.
|
|
func (s STF[T]) beginBlock(
|
|
ctx *executionContext,
|
|
) (beginBlockEvents []event.Event, err error) {
|
|
err = s.doBeginBlock(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := range ctx.events {
|
|
ctx.events[i].BlockNumber = uint64(ctx.headerInfo.Height)
|
|
ctx.events[i].BlockStage = appdata.BeginBlockStage
|
|
ctx.events[i].EventIndex = int32(i + 1)
|
|
}
|
|
|
|
return ctx.events, nil
|
|
}
|
|
|
|
// endBlock executes the end block logic.
|
|
func (s STF[T]) endBlock(
|
|
ctx *executionContext,
|
|
) ([]event.Event, []appmodulev2.ValidatorUpdate, error) {
|
|
err := s.doEndBlock(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
events := ctx.events
|
|
ctx.events = make([]event.Event, 0) // reset events
|
|
valsetUpdates, err := s.validatorUpdates(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
events = append(events, ctx.events...)
|
|
for i := range events {
|
|
events[i].BlockNumber = uint64(ctx.headerInfo.Height)
|
|
events[i].BlockStage = appdata.EndBlockStage
|
|
events[i].EventIndex = int32(i + 1)
|
|
}
|
|
|
|
return events, valsetUpdates, nil
|
|
}
|
|
|
|
// validatorUpdates returns the validator updates for the current block. It is called by endBlock after the endblock execution has concluded
|
|
func (s STF[T]) validatorUpdates(
|
|
ctx *executionContext,
|
|
) ([]appmodulev2.ValidatorUpdate, error) {
|
|
valSetUpdates, err := s.doValidatorUpdate(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return valSetUpdates, nil
|
|
}
|
|
|
|
// Simulate simulates the execution of a tx on the provided state.
|
|
func (s STF[T]) Simulate(
|
|
ctx context.Context,
|
|
state store.ReaderMap,
|
|
gasLimit uint64,
|
|
tx T,
|
|
) (server.TxResult, store.WriterMap) {
|
|
simulationState := s.branchFn(state)
|
|
hi, err := s.getHeaderInfo(simulationState)
|
|
if err != nil {
|
|
return server.TxResult{}, nil
|
|
}
|
|
txr := s.deliverTx(ctx, simulationState, tx, internal.ExecModeSimulate, hi, 0)
|
|
|
|
return txr, simulationState
|
|
}
|
|
|
|
// ValidateTx will run only the validation steps required for a transaction.
|
|
// Validations are run over the provided state, with the provided gas limit.
|
|
func (s STF[T]) ValidateTx(
|
|
ctx context.Context,
|
|
state store.ReaderMap,
|
|
gasLimit uint64,
|
|
tx T,
|
|
) server.TxResult {
|
|
validationState := s.branchFn(state)
|
|
gasUsed, events, err := s.validateTx(ctx, validationState, gasLimit, tx, transaction.ExecModeCheck)
|
|
return server.TxResult{
|
|
Events: events,
|
|
GasUsed: gasUsed,
|
|
Error: err,
|
|
}
|
|
}
|
|
|
|
// Query executes the query on the provided state with the provided gas limits.
|
|
func (s STF[T]) Query(
|
|
ctx context.Context,
|
|
state store.ReaderMap,
|
|
gasLimit uint64,
|
|
req transaction.Msg,
|
|
) (transaction.Msg, error) {
|
|
queryState := s.branchFn(state)
|
|
hi, err := s.getHeaderInfo(queryState)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queryCtx := s.makeContext(ctx, nil, queryState, internal.ExecModeSimulate)
|
|
queryCtx.setHeaderInfo(hi)
|
|
queryCtx.setGasLimit(gasLimit)
|
|
return s.queryRouter.Invoke(queryCtx, req)
|
|
}
|
|
|
|
// clone clones STF.
|
|
func (s STF[T]) clone() STF[T] {
|
|
return STF[T]{
|
|
logger: s.logger,
|
|
msgRouter: s.msgRouter,
|
|
queryRouter: s.queryRouter,
|
|
doPreBlock: s.doPreBlock,
|
|
doBeginBlock: s.doBeginBlock,
|
|
doEndBlock: s.doEndBlock,
|
|
doValidatorUpdate: s.doValidatorUpdate,
|
|
doTxValidation: s.doTxValidation,
|
|
postTxExec: s.postTxExec,
|
|
branchFn: s.branchFn,
|
|
makeGasMeter: s.makeGasMeter,
|
|
makeGasMeteredState: s.makeGasMeteredState,
|
|
}
|
|
}
|
|
|
|
// executionContext is a struct that holds the context for the execution of a tx.
|
|
type executionContext struct {
|
|
context.Context
|
|
|
|
// unmeteredState is storage without metering. Changes here are propagated to state which is the metered
|
|
// version.
|
|
unmeteredState store.WriterMap
|
|
// state is the gas metered state.
|
|
state store.WriterMap
|
|
// meter is the gas meter.
|
|
meter gas.Meter
|
|
// events are the current events.
|
|
events []event.Event
|
|
// sender is the causer of the state transition.
|
|
sender transaction.Identity
|
|
// headerInfo contains the block info.
|
|
headerInfo header.Info
|
|
// execMode retains information about the exec mode.
|
|
execMode transaction.ExecMode
|
|
|
|
branchFn branchFn
|
|
makeGasMeter makeGasMeterFn
|
|
makeGasMeteredStore makeGasMeteredStateFn
|
|
|
|
msgRouter router.Service
|
|
queryRouter router.Service
|
|
}
|
|
|
|
// setHeaderInfo sets the header info in the state to be used by queries in the future.
|
|
func (e *executionContext) setHeaderInfo(hi header.Info) {
|
|
e.headerInfo = hi
|
|
}
|
|
|
|
// setGasLimit will update the gas limit of the *executionContext
|
|
func (e *executionContext) setGasLimit(limit uint64) {
|
|
meter := e.makeGasMeter(limit)
|
|
meteredState := e.makeGasMeteredStore(meter, e.unmeteredState)
|
|
|
|
e.meter = meter
|
|
e.state = meteredState
|
|
}
|
|
|
|
func (e *executionContext) Value(key any) any {
|
|
if key == executionContextKey {
|
|
return e
|
|
}
|
|
|
|
return e.Context.Value(key)
|
|
}
|
|
|
|
// TODO: too many calls to makeContext can be expensive
|
|
// makeContext creates and returns a new execution context for the STF[T] type.
|
|
// It takes in the following parameters:
|
|
// - ctx: The context.Context object for the execution.
|
|
// - sender: The transaction.Identity object representing the sender of the transaction.
|
|
// - state: The store.WriterMap object for accessing and modifying the state.
|
|
// - gasLimit: The maximum amount of gas allowed for the execution.
|
|
// - execMode: The corecontext.ExecMode object representing the execution mode.
|
|
//
|
|
// It returns a pointer to the executionContext struct
|
|
func (s STF[T]) makeContext(
|
|
ctx context.Context,
|
|
sender transaction.Identity,
|
|
store store.WriterMap,
|
|
execMode transaction.ExecMode,
|
|
) *executionContext {
|
|
valuedCtx := context.WithValue(ctx, corecontext.ExecModeKey, execMode)
|
|
return newExecutionContext(
|
|
valuedCtx,
|
|
s.makeGasMeter,
|
|
s.makeGasMeteredState,
|
|
s.branchFn,
|
|
sender,
|
|
store,
|
|
execMode,
|
|
s.msgRouter,
|
|
s.queryRouter,
|
|
)
|
|
}
|
|
|
|
func newExecutionContext(
|
|
ctx context.Context,
|
|
makeGasMeterFn makeGasMeterFn,
|
|
makeGasMeteredStoreFn makeGasMeteredStateFn,
|
|
branchFn branchFn,
|
|
sender transaction.Identity,
|
|
state store.WriterMap,
|
|
execMode transaction.ExecMode,
|
|
msgRouter coreRouterImpl,
|
|
queryRouter coreRouterImpl,
|
|
) *executionContext {
|
|
meter := makeGasMeterFn(gas.NoGasLimit)
|
|
meteredState := makeGasMeteredStoreFn(meter, state)
|
|
|
|
return &executionContext{
|
|
Context: ctx,
|
|
unmeteredState: state,
|
|
state: meteredState,
|
|
meter: meter,
|
|
events: make([]event.Event, 0),
|
|
headerInfo: header.Info{},
|
|
execMode: execMode,
|
|
sender: sender,
|
|
branchFn: branchFn,
|
|
makeGasMeter: makeGasMeterFn,
|
|
makeGasMeteredStore: makeGasMeteredStoreFn,
|
|
msgRouter: msgRouter,
|
|
queryRouter: queryRouter,
|
|
}
|
|
}
|
|
|
|
// applyStateChanges applies the state changes from the source store to the destination store.
|
|
// It retrieves the state changes from the source store using GetStateChanges method,
|
|
// and then applies those changes to the destination store using ApplyStateChanges method.
|
|
// If an error occurs during the retrieval or application of state changes, it is returned.
|
|
func applyStateChanges(dst, src store.WriterMap) error {
|
|
changes, err := src.GetStateChanges()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return dst.ApplyStateChanges(changes)
|
|
}
|
|
|
|
// isCtxCancelled reports if the context was canceled.
|
|
func isCtxCancelled(ctx context.Context) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
return nil
|
|
}
|
|
}
|