diff --git a/docs/learn/beginner/01-tx-lifecycle.md b/docs/learn/beginner/01-tx-lifecycle.md index f3813a4c0b75..6662115d1992 100644 --- a/docs/learn/beginner/01-tx-lifecycle.md +++ b/docs/learn/beginner/01-tx-lifecycle.md @@ -13,9 +13,8 @@ This document describes the lifecycle of a transaction from creation to committe * [Anatomy of a Cosmos SDK Application](./00-app-anatomy.md) ::: -## Creation -### Transaction Creation +## Transaction Creation One of the main application interfaces is the command-line interface. The transaction `Tx` can be created by the user inputting a command in the following format from the [command-line](../advanced/07-cli.md), providing the type of transaction in `[command]`, arguments in `[args]`, and configurations such as gas prices in `[flags]`: @@ -27,7 +26,7 @@ This command automatically **creates** the transaction, **signs** it using the a There are several required and optional flags for transaction creation. The `--from` flag specifies which [account](./03-accounts.md) the transaction is originating from. For example, if the transaction is sending coins, the funds are drawn from the specified `from` address. -#### Gas and Fees +### Gas and Fees Additionally, there are several [flags](../advanced/07-cli.md) users can use to indicate how much they are willing to pay in [fees](./04-gas-fees.md): @@ -41,7 +40,7 @@ The ultimate value of the fees paid is equal to the gas multiplied by the gas pr Later, validators decide whether or not to include the transaction in their block by comparing the given or calculated `gas-prices` to their local `min-gas-prices`. `Tx` is rejected if its `gas-prices` is not high enough, so users are incentivized to pay more. -#### CLI Example +### CLI Example Users of the application `app` can enter the following command into their CLI to generate a transaction to send 1000uatom from a `senderAddress` to a `recipientAddress`. The command specifies how much gas they are willing to pay: an automatic estimate scaled up by 1.5 times, with a gas price of 0.025uatom per unit gas. @@ -49,220 +48,105 @@ Users of the application `app` can enter the following command into their CLI to appd tx send 1000uatom --from --gas auto --gas-adjustment 1.5 --gas-prices 0.025uatom ``` -#### Other Transaction Creation Methods +### Other Transaction Creation Methods The command-line is an easy way to interact with an application, but `Tx` can also be created using a [gRPC or REST interface](../advanced/06-grpc_rest.md) or some other entry point defined by the application developer. From the user's perspective, the interaction depends on the web interface or wallet they are using (e.g. creating `Tx` using [Keplr](https://www.keplr.app/) and signing it with a Ledger Nano S). -## Addition to Mempool +## Transaction Broadcasting -Each full-node (running CometBFT) that receives a `Tx` sends an [ABCI message](https://docs.cometbft.com/v1.0/spec/abci/abci++_methods#checktx) -`abci.CheckTxRequest` to the application layer to check for validity, and receives an `abci.CheckTxResponse`. If the `Tx` passes the checks, it is held in the node's -[**Mempool**](https://docs.cometbft.com/v1.0/explanation/core/mempool/), an in-memory pool of transactions unique to each node, pending inclusion in a block - honest nodes discard a `Tx` if it is found to be invalid. Prior to consensus, nodes continuously check incoming transactions and gossip them to their peers. +This is the next phase, where a transactison is sent from a client (such as a wallet or a command-line interface) to the network of nodes. This process is consensus-agnostic, meaning it can work with various consensus engines. -### Types of Checks +Below are the steps involved in transaction broadcasting: -The full-nodes perform stateless, then stateful checks on `Tx` during `CheckTx`, with the goal to -identify and reject an invalid transaction as early on as possible to avoid wasted computation. +1. **Transaction Creation and Signing:** +Transactions are created and signed using the client's private key to ensure authenticity and integrity. -**_Stateless_** checks do not require nodes to access state - light clients or offline nodes can do -them - and are thus less computationally expensive. Stateless checks include making sure addresses -are not empty, enforcing nonnegative numbers, and other logic specified in the definitions. +2. **Broadcasting to the Network:** +The signed transaction is sent to the network. This is handled by the `BroadcastTx` function in the client context. -**_Stateful_** checks validate transactions and messages based on a committed state. Examples -include checking that the relevant values exist and can be transacted with, the address -has sufficient funds, and the sender is authorized or has the correct ownership to transact. -At any given moment, full-nodes typically have [multiple versions](../advanced/00-baseapp.md#state-updates) -of the application's internal state for different purposes. For example, nodes execute state -changes while in the process of verifying transactions, but still need a copy of the last committed -state in order to answer queries - they should not respond using state with uncommitted changes. +3. **Network Propagation:** +Once received by a node, the transaction is propagated to other nodes in the network. This ensures that all nodes have a copy of the transaction. -In order to verify a `Tx`, full-nodes call `CheckTx`, which includes both _stateless_ and _stateful_ -checks. Further validation happens later in the [`DeliverTx`](../advanced/00-baseapp.md#delivertx) stage. `CheckTx` goes -through several steps, beginning with decoding `Tx`. +4. **Consensus Engine Interaction:** +The specific method of broadcasting may vary depending on the consensus engine used. The SDK's design allows for easy integration with any consensus engine by configuring the clientCtx appropriately. + +The function `BroadcastTx` in `client/tx/tx.go` demonstrates how a transaction is prepared, signed, and broadcasted. Here's the relevant part of the function that handles the broadcasting: + +```go +res, err := clientCtx.BroadcastTx(txBytes) +if err != nil { + return err +} +``` + +**Configuration:** + +To adapt this function for different consensus engines, ensure that the `clientCtx` is configured with the correct network settings and transaction handling mechanisms for your chosen engine. This might involve setting up specific encoders, decoders, and network endpoints that are compatible with the engine. + +## Transaction Processing + +After a transaction is broadcasted to the network, it undergoes several processing steps to ensure its validity. These steps are run by the application's `BaseApp`, which is in chargee of trasaction processing. Within the SDK we wrap the `BaseApp` with a runtime layer defined in `runtime/app.go`. This is where `BaseApp` is extended with additional functionality to handle transactions allowing for more flexibility and customisation, for example being able to swap out CometBFT for another consensus engine. The key steps in transaction processing are: ### Decoding -When `Tx` is received by the application from the underlying consensus engine (e.g. CometBFT ), it is still in its [encoded](../advanced/05-encoding.md) `[]byte` form and needs to be unmarshaled in order to be processed. Then, the [`runTx`](../advanced/00-baseapp.md#runtx-antehandler-runmsgs-posthandler) function is called to run in `runTxModeCheck` mode, meaning the function runs all checks but exits before executing messages and writing state changes. +The transaction is decoded from its binary format into a structured format that the application can understand. -### ValidateBasic (deprecated) +### Validation -Messages ([`sdk.Msg`](../advanced/01-transactions.md#messages)) are extracted from transactions (`Tx`). The `ValidateBasic` method of the `sdk.Msg` interface implemented by the module developer is run for each transaction. -To discard obviously invalid messages, the `BaseApp` type calls the `ValidateBasic` method very early in the processing of the message in the [`CheckTx`](../advanced/00-baseapp.md#checktx) and [`DeliverTx`](../advanced/00-baseapp.md#delivertx) transactions. -`ValidateBasic` can include only **stateless** checks (the checks that do not require access to the state). +Preliminary checks are performed. These include signature verification to ensure the transaction hasn't been tampered with and checking if the transaction meets the minimum fee requirements, which is handled by the AnteHandler. The Antehandler is invoked during the `runTx` method in `BaseApp`. -:::warning -The `ValidateBasic` method on messages has been deprecated in favor of validating messages directly in their respective [`Msg` services](../../build/building-modules/03-msg-services.md#Validation). +### Routing -Read [RFC 001](https://docs.cosmos.network/main/rfc/rfc-001-tx-validation) for more details. -::: +How Routing Works: +The transaction is routed to the appropriate module based on the message type. Each message type is associated with a specific module, which is responsible for processing the message. The `BaseApp` uses a `MsgServiceRouter` to direct the transaction to the correct module. -:::note -`BaseApp` still calls `ValidateBasic` on messages that implements that method for backwards compatibility. -::: +1. **Transaction Type Identification:** +Each transaction contains one or more messages (`sdk.Msg`), and each message has a `Type()` method that identifies its type. This type is used to determine the appropriate module to handle the message. +2. **Module Routing:** +The BaseApp holds a `MsgServiceRouter` which maps each message type to a specific module's handler. When a transaction is processed, `BaseApp` uses this router to direct the message to the correct module. -#### Guideline -`ValidateBasic` should not be used anymore. Message validation should be performed in the `Msg` service when [handling a message](../../build/building-modules/03-msg-services.md#validation) in a module Msg Server. +### Example of Routing -### AnteHandler +Let's say there is a transaction that involves transferring tokens. The message type might be `MsgSend`, and the `MsgServiceRouter` in `BaseApp` would route this message to the bank module's handler. The bank module would then validate the transaction details (like sender balance) and update the state to reflect the transfer if valid.. -`AnteHandler`s even though optional, are in practice very often used to perform signature verification, gas calculation, fee deduction, and other core operations related to blockchain transactions. +### Module Execution -A copy of the cached context is provided to the `AnteHandler`, which performs limited checks specified for the transaction type. Using a copy allows the `AnteHandler` to do stateful checks for `Tx` without modifying the last committed state, and revert back to the original if the execution fails. +1. The transaction is first routed to the appropriate module based on the message type. This is handled by the `MsgServiceRouter`. -For example, the [`auth`](https://github.com/cosmos/cosmos-sdk/tree/main/x/auth) module `AnteHandler` checks and increments sequence numbers, checks signatures and account numbers, and deducts fees from the first signer of the transaction - all state changes are made using the `checkState`. +2. Each module has specific handlers that are triggered once the message is routed to the module. These handlers contain the logic needed to process the transaction, such as updating account balances, transferring tokens, or other state changes. -:::warning -Ante handlers only run on a transaction. If a transaction embed multiple messages (like some x/authz, x/gov transactions for instance), the ante handlers only have awareness of the outer message. Inner messages are mostly directly routed to the [message router](https://docs.cosmos.network/main/learn/advanced/baseapp#msg-service-router) and will skip the chain of ante handlers. Keep that in mind when designing your own ante handler. -::: +3. During the execution, the module's handler will modify the state as required by the business logic. This could involve writing to the module's portion of the state store. -### Gas - -The [`Context`](../advanced/02-context.md), which keeps a `GasMeter` that tracks how much gas is used during the execution of `Tx`, is initialized. The user-provided amount of gas for `Tx` is known as `GasWanted`. If `GasConsumed`, the amount of gas consumed during execution, ever exceeds `GasWanted`, the execution stops and the changes made to the cached copy of the state are not committed. Otherwise, `CheckTx` sets `GasUsed` equal to `GasConsumed` and returns it in the result. After calculating the gas and fee values, validator-nodes check that the user-specified `gas-prices` is greater than their locally defined `min-gas-prices`. - -### Discard or Addition to Mempool - -If at any point during `CheckTx` the `Tx` fails, it is discarded and the transaction lifecycle ends -there. Otherwise, if it passes `CheckTx` successfully, the default protocol is to relay it to peer -nodes and add it to the Mempool so that the `Tx` becomes a candidate to be included in the next block. - -The **mempool** serves the purpose of keeping track of transactions seen by all full-nodes. -Full-nodes keep a **mempool cache** of the last `mempool.cache_size` transactions they have seen, as a first line of -defense to prevent replay attacks. Ideally, `mempool.cache_size` is large enough to encompass all -of the transactions in the full mempool. If the mempool cache is too small to keep track of all -the transactions, `CheckTx` is responsible for identifying and rejecting replayed transactions. - -Currently existing preventative measures include fees and a `sequence` (nonce) counter to distinguish -replayed transactions from identical but valid ones. If an attacker tries to spam nodes with many -copies of a `Tx`, full-nodes keeping a mempool cache reject all identical copies instead of running -`CheckTx` on them. Even if the copies have incremented `sequence` numbers, attackers are -disincentivized by the need to pay fees. - -Validator nodes keep a mempool to prevent replay attacks, just as full-nodes do, but also use it as -a pool of unconfirmed transactions in preparation of block inclusion. Note that even if a `Tx` -passes all checks at this stage, it is still possible to be found invalid later on, because -`CheckTx` does not fully validate the transaction (that is, it does not actually execute the messages). - -## Inclusion in a Block - -Consensus, the process through which validator nodes come to agreement on which transactions to -accept, happens in **rounds**. Each round begins with a proposer creating a block of the most -recent transactions and ends with **validators**, special full-nodes with voting power responsible -for consensus, agreeing to accept the block or go with a `nil` block instead. Validator nodes -execute the consensus algorithm adopted in [CometBFT](https://docs.cometbft.com/v1.0/spec/consensus/), -confirming the transactions using ABCI requests to the application, in order to come to this agreement. - -The first step of consensus is the **block proposal**. One proposer amongst the validators is chosen -by the consensus algorithm to create and propose a block - in order for a `Tx` to be included, it -must be in this proposer's mempool. - -## State Changes - -The next step of consensus is to execute the transactions to fully validate them. All full-nodes -that receive a block proposal from the correct proposer execute the transactions by calling the ABCI function `FinalizeBlock`. -As mentioned throughout the documentation `BeginBlock`, `ExecuteTx` and `EndBlock` are called within FinalizeBlock. -Although every full-node operates individually and locally, the outcome is always consistent and unequivocal. This is because the state changes brought about by the messages are predictable, and the transactions are specifically sequenced in the proposed block. - -```text - -------------------------- - | Receive Block Proposal | - -------------------------- - | - v - ------------------------- - | FinalizeBlock | - ------------------------- - | - v - ------------------- - | BeginBlock | - ------------------- - | - v - -------------------- - | ExecuteTx(tx0) | - | ExecuteTx(tx1) | - | ExecuteTx(tx2) | - | ExecuteTx(tx3) | - | . | - | . | - | . | - ------------------- - | - v - -------------------- - | EndBlock | - -------------------- - | - v - ----------------------- - | Consensus | - ----------------------- - | - v - ----------------------- - | Commit | - ----------------------- +4. Modules can emit events and log information during execution, which are used for monitoring and querying transaction outcomes. + +```go +func handleMsgSend(ctx sdk.Context, keeper BankKeeper, msg MsgSend) error { + if keeper.GetBalance(ctx, msg.Sender).Amount < msg.Amount { + return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "sender does not have enough tokens") + } + keeper.SendCoins(ctx, msg.Sender, msg.Receiver, msg.Amount) + ctx.EventManager().EmitEvent( + sdk.NewEvent("transfer", sdk.NewAttribute("from", msg.Sender.String()), sdk.NewAttribute("to", msg.Receiver.String()), sdk.NewAttribute("amount", msg.Amount.String())), + ) + return nil +} ``` -### Transaction Execution - -The `FinalizeBlock` ABCI function defined in [`BaseApp`](../advanced/00-baseapp.md) does the bulk of the -state transitions: it is run for each transaction in the block in sequential order as committed -to during consensus. Under the hood, transaction execution is almost identical to `CheckTx` but calls the -[`runTx`](../advanced/00-baseapp.md#runtx) function in deliver mode instead of check mode. -Instead of using their `checkState`, full-nodes use `finalizeblock`: - -* **Decoding:** Since `FinalizeBlock` is an ABCI call, `Tx` is received in the encoded `[]byte` form. - Nodes first unmarshal the transaction, using the [`TxConfig`](./00-app-anatomy.md#register-codec) defined in the app, then call `runTx` in `execModeFinalize`, which is very similar to `CheckTx` but also executes and writes state changes. - -* **Checks and `AnteHandler`:** Full-nodes call `validateBasicMsgs` and `AnteHandler` again. This second check - happens because they may not have seen the same transactions during the addition to Mempool stage - and a malicious proposer may have included invalid ones. One difference here is that the - `AnteHandler` does not compare `gas-prices` to the node's `min-gas-prices` since that value is local - to each node - differing values across nodes yield nondeterministic results. - -* **`MsgServiceRouter`:** After `CheckTx` exits, `FinalizeBlock` continues to run - [`runMsgs`](../advanced/00-baseapp.md#runtx-antehandler-runmsgs-posthandler) to fully execute each `Msg` within the transaction. - Since the transaction may have messages from different modules, `BaseApp` needs to know which module - to find the appropriate handler. This is achieved using `BaseApp`'s `MsgServiceRouter` so that it can be processed by the module's Protobuf [`Msg` service](../../build/building-modules/03-msg-services.md). - For `LegacyMsg` routing, the `Route` function is called via the [module manager](../../build/building-modules/01-module-manager.md) to retrieve the route name and find the legacy [`Handler`](../../build/building-modules/03-msg-services.md#handler-type) within the module. - -* **`Msg` service:** Protobuf `Msg` service is responsible for executing each message in the `Tx` and causes state transitions to persist in `finalizeBlockState`. - -* **PostHandlers:** [`PostHandler`](../advanced/00-baseapp.md#posthandler)s run after the execution of the message. If they fail, the state change of `runMsgs`, as well of `PostHandlers`, are both reverted. - -* **Gas:** While a `Tx` is being delivered, a `GasMeter` is used to keep track of how much - gas is being used; if execution completes, `GasUsed` is set and returned in the - `abci.ExecTxResult`. If execution halts because `BlockGasMeter` or `GasMeter` has run out or something else goes - wrong, a deferred function at the end appropriately errors or panics. - -If there are any failed state changes resulting from a `Tx` being invalid or `GasMeter` running out, -the transaction processing terminates and any state changes are reverted. Invalid transactions in a -block proposal cause validator nodes to reject the block and vote for a `nil` block instead. - -### Commit - -The final step is for nodes to commit the block and state changes. Validator nodes -perform the previous step of executing state transitions in order to validate the transactions, -then sign the block to confirm it. Full nodes that are not validators do not -participate in consensus - i.e. they cannot vote - but listen for votes to understand whether or -not they should commit the state changes. - -When they receive enough validator votes (2/3+ _precommits_ weighted by voting power), full nodes commit to a new block to be added to the blockchain and -finalize the state transitions in the application layer. A new state root is generated to serve as -a merkle proof for the state transitions. Applications use the [`Commit`](../advanced/00-baseapp.md#commit) -ABCI method inherited from [Baseapp](../advanced/00-baseapp.md); it syncs all the state transitions by -writing the `deliverState` into the application's internal state. As soon as the state changes are -committed, `checkState` starts afresh from the most recently committed state and `deliverState` -resets to `nil` in order to be consistent and reflect the changes. - -Note that not all blocks have the same number of transactions and it is possible for consensus to -result in a `nil` block or one with none at all. In a public blockchain network, it is also possible -for validators to be **byzantine**, or malicious, which may prevent a `Tx` from being committed in -the blockchain. Possible malicious behaviors include the proposer deciding to censor a `Tx` by -excluding it from the block or a validator voting against the block. - -At this point, the transaction lifecycle of a `Tx` is over: nodes have verified its validity, -delivered it by executing its state changes, and committed those changes. The `Tx` itself, -in `[]byte` form, is stored in a block and appended to the blockchain. +## Post-Transaction Handling + +After execution, any additional actions that need to be taken are processed. This could include updating logs, sending events, or handling errors. + +These steps are managed by `BaseApp` in the Cosmos SDK, which routes transactions to the appropriate handlers and manages state transitions. + +After a transaction is executed in the Cosmos SDK, several steps are taken to finalise the process: + +1. Event Emission: Modules emit events that can be used for logging, monitoring, or triggering other workflows. These events are collected during the transaction execution. + +2. Logging: Information about the transaction execution, such as success or failure, and any significant state changes, are logged for audit and diagnostic purposes. + +3. Error Handling: If any errors occur during transaction execution, they are handled appropriately, which may include rolling back certain operations to maintain state consistency. + +4. State Commitment: Changes made to the state during the transaction are finalized and written to the blockchain. This step is crucial as it ensures that all state transitions are permanently recorded. + +After post-transaction handling in the Cosmos SDK, the exact sequence of the transaction lifecycle is dependent on the consensus mechanism used. This includes how transactions are grouped into blocks, how blocks are validated, and how consensus is achieved among validators to commit the block to the blockchain. Each consensus protocol may implement these steps differently to ensure network agreement and maintain the integrity of the blockchain state.